2 * 2-channel UHJ Encoder
4 * Copyright (c) Chris Robinson <chris.kcat@gmail.com>
6 * Permission is hereby granted, free of charge, to any person obtaining a copy
7 * of this software and associated documentation files (the "Software"), to deal
8 * in the Software without restriction, including without limitation the rights
9 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 * copies of the Software, and to permit persons to whom the Software is
11 * furnished to do so, subject to the following conditions:
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
36 #include "alnumbers.h"
38 #include "opthelpers.h"
39 #include "phase_shifter.h"
44 #include "win_main_utf8.h"
49 struct SndFileDeleter
{
50 void operator()(SNDFILE
*sndfile
) { sf_close(sndfile
); }
52 using SndFilePtr
= std::unique_ptr
<SNDFILE
,SndFileDeleter
>;
55 using uint
= unsigned int;
57 constexpr uint BufferLineSize
{1024};
59 using FloatBufferLine
= std::array
<float,BufferLineSize
>;
60 using FloatBufferSpan
= al::span
<float,BufferLineSize
>;
64 constexpr static size_t sFilterDelay
{1024};
66 /* Delays and processing storage for the unfiltered signal. */
67 alignas(16) std::array
<float,BufferLineSize
+sFilterDelay
> mW
{};
68 alignas(16) std::array
<float,BufferLineSize
+sFilterDelay
> mX
{};
69 alignas(16) std::array
<float,BufferLineSize
+sFilterDelay
> mY
{};
70 alignas(16) std::array
<float,BufferLineSize
+sFilterDelay
> mZ
{};
72 alignas(16) std::array
<float,BufferLineSize
> mS
{};
73 alignas(16) std::array
<float,BufferLineSize
> mD
{};
74 alignas(16) std::array
<float,BufferLineSize
> mT
{};
76 /* History for the FIR filter. */
77 alignas(16) std::array
<float,sFilterDelay
*2 - 1> mWXHistory1
{};
78 alignas(16) std::array
<float,sFilterDelay
*2 - 1> mWXHistory2
{};
80 alignas(16) std::array
<float,BufferLineSize
+ sFilterDelay
*2> mTemp
{};
82 void encode(const al::span
<FloatBufferLine
> OutSamples
,
83 const al::span
<FloatBufferLine
,4> InSamples
, const size_t SamplesToDo
);
86 const PhaseShifterT
<UhjEncoder::sFilterDelay
*2> PShift
{};
89 /* Encoding UHJ from B-Format is done as:
91 * S = 0.9396926*W + 0.1855740*X
92 * D = j(-0.3420201*W + 0.5098604*X) + 0.6554516*Y
96 * T = j(-0.1432*W + 0.6512*X) - 0.7071068*Y
99 * where j is a wide-band +90 degree phase shift. T is excluded from 2-channel
100 * output, and Q is excluded from 2- and 3-channel output.
102 void UhjEncoder::encode(const al::span
<FloatBufferLine
> OutSamples
,
103 const al::span
<FloatBufferLine
,4> InSamples
, const size_t SamplesToDo
)
105 const float *RESTRICT winput
{al::assume_aligned
<16>(InSamples
[0].data())};
106 const float *RESTRICT xinput
{al::assume_aligned
<16>(InSamples
[1].data())};
107 const float *RESTRICT yinput
{al::assume_aligned
<16>(InSamples
[2].data())};
108 const float *RESTRICT zinput
{al::assume_aligned
<16>(InSamples
[3].data())};
110 /* Combine the previously delayed input signal with the new input. */
111 std::copy_n(winput
, SamplesToDo
, mW
.begin()+sFilterDelay
);
112 std::copy_n(xinput
, SamplesToDo
, mX
.begin()+sFilterDelay
);
113 std::copy_n(yinput
, SamplesToDo
, mY
.begin()+sFilterDelay
);
114 std::copy_n(zinput
, SamplesToDo
, mZ
.begin()+sFilterDelay
);
116 /* S = 0.9396926*W + 0.1855740*X */
117 for(size_t i
{0};i
< SamplesToDo
;++i
)
118 mS
[i
] = 0.9396926f
*mW
[i
] + 0.1855740f
*mX
[i
];
120 /* Precompute j(-0.3420201*W + 0.5098604*X) and store in mD. */
121 auto tmpiter
= std::copy(mWXHistory1
.cbegin(), mWXHistory1
.cend(), mTemp
.begin());
122 std::transform(winput
, winput
+SamplesToDo
, xinput
, tmpiter
,
123 [](const float w
, const float x
) noexcept
-> float
124 { return -0.3420201f
*w
+ 0.5098604f
*x
; });
125 std::copy_n(mTemp
.cbegin()+SamplesToDo
, mWXHistory1
.size(), mWXHistory1
.begin());
126 PShift
.process({mD
.data(), SamplesToDo
}, mTemp
.data());
128 /* D = j(-0.3420201*W + 0.5098604*X) + 0.6554516*Y */
129 for(size_t i
{0};i
< SamplesToDo
;++i
)
130 mD
[i
] = mD
[i
] + 0.6554516f
*mY
[i
];
132 /* Left = (S + D)/2.0 */
133 float *RESTRICT left
{al::assume_aligned
<16>(OutSamples
[0].data())};
134 for(size_t i
{0};i
< SamplesToDo
;i
++)
135 left
[i
] = (mS
[i
] + mD
[i
]) * 0.5f
;
136 /* Right = (S - D)/2.0 */
137 float *RESTRICT right
{al::assume_aligned
<16>(OutSamples
[1].data())};
138 for(size_t i
{0};i
< SamplesToDo
;i
++)
139 right
[i
] = (mS
[i
] - mD
[i
]) * 0.5f
;
141 if(OutSamples
.size() > 2)
143 /* Precompute j(-0.1432*W + 0.6512*X) and store in mT. */
144 tmpiter
= std::copy(mWXHistory2
.cbegin(), mWXHistory2
.cend(), mTemp
.begin());
145 std::transform(winput
, winput
+SamplesToDo
, xinput
, tmpiter
,
146 [](const float w
, const float x
) noexcept
-> float
147 { return -0.1432f
*w
+ 0.6512f
*x
; });
148 std::copy_n(mTemp
.cbegin()+SamplesToDo
, mWXHistory2
.size(), mWXHistory2
.begin());
149 PShift
.process({mT
.data(), SamplesToDo
}, mTemp
.data());
151 /* T = j(-0.1432*W + 0.6512*X) - 0.7071068*Y */
152 float *RESTRICT t
{al::assume_aligned
<16>(OutSamples
[2].data())};
153 for(size_t i
{0};i
< SamplesToDo
;i
++)
154 t
[i
] = mT
[i
] - 0.7071068f
*mY
[i
];
156 if(OutSamples
.size() > 3)
159 float *RESTRICT q
{al::assume_aligned
<16>(OutSamples
[3].data())};
160 for(size_t i
{0};i
< SamplesToDo
;i
++)
161 q
[i
] = 0.9772f
*mZ
[i
];
164 /* Copy the future samples to the front for next time. */
165 std::copy(mW
.cbegin()+SamplesToDo
, mW
.cbegin()+SamplesToDo
+sFilterDelay
, mW
.begin());
166 std::copy(mX
.cbegin()+SamplesToDo
, mX
.cbegin()+SamplesToDo
+sFilterDelay
, mX
.begin());
167 std::copy(mY
.cbegin()+SamplesToDo
, mY
.cbegin()+SamplesToDo
+sFilterDelay
, mY
.begin());
168 std::copy(mZ
.cbegin()+SamplesToDo
, mZ
.cbegin()+SamplesToDo
+sFilterDelay
, mZ
.begin());
178 /* Azimuth is counter-clockwise. */
179 constexpr std::array StereoMap
{
180 SpeakerPos
{SF_CHANNEL_MAP_LEFT
, 30.0f
, 0.0f
},
181 SpeakerPos
{SF_CHANNEL_MAP_RIGHT
, -30.0f
, 0.0f
},
183 constexpr std::array QuadMap
{
184 SpeakerPos
{SF_CHANNEL_MAP_LEFT
, 45.0f
, 0.0f
},
185 SpeakerPos
{SF_CHANNEL_MAP_RIGHT
, -45.0f
, 0.0f
},
186 SpeakerPos
{SF_CHANNEL_MAP_REAR_LEFT
, 135.0f
, 0.0f
},
187 SpeakerPos
{SF_CHANNEL_MAP_REAR_RIGHT
, -135.0f
, 0.0f
},
189 constexpr std::array X51Map
{
190 SpeakerPos
{SF_CHANNEL_MAP_LEFT
, 30.0f
, 0.0f
},
191 SpeakerPos
{SF_CHANNEL_MAP_RIGHT
, -30.0f
, 0.0f
},
192 SpeakerPos
{SF_CHANNEL_MAP_CENTER
, 0.0f
, 0.0f
},
193 SpeakerPos
{SF_CHANNEL_MAP_LFE
, 0.0f
, 0.0f
},
194 SpeakerPos
{SF_CHANNEL_MAP_SIDE_LEFT
, 110.0f
, 0.0f
},
195 SpeakerPos
{SF_CHANNEL_MAP_SIDE_RIGHT
, -110.0f
, 0.0f
},
197 constexpr std::array X51RearMap
{
198 SpeakerPos
{SF_CHANNEL_MAP_LEFT
, 30.0f
, 0.0f
},
199 SpeakerPos
{SF_CHANNEL_MAP_RIGHT
, -30.0f
, 0.0f
},
200 SpeakerPos
{SF_CHANNEL_MAP_CENTER
, 0.0f
, 0.0f
},
201 SpeakerPos
{SF_CHANNEL_MAP_LFE
, 0.0f
, 0.0f
},
202 SpeakerPos
{SF_CHANNEL_MAP_REAR_LEFT
, 110.0f
, 0.0f
},
203 SpeakerPos
{SF_CHANNEL_MAP_REAR_RIGHT
, -110.0f
, 0.0f
},
205 constexpr std::array X71Map
{
206 SpeakerPos
{SF_CHANNEL_MAP_LEFT
, 30.0f
, 0.0f
},
207 SpeakerPos
{SF_CHANNEL_MAP_RIGHT
, -30.0f
, 0.0f
},
208 SpeakerPos
{SF_CHANNEL_MAP_CENTER
, 0.0f
, 0.0f
},
209 SpeakerPos
{SF_CHANNEL_MAP_LFE
, 0.0f
, 0.0f
},
210 SpeakerPos
{SF_CHANNEL_MAP_REAR_LEFT
, 150.0f
, 0.0f
},
211 SpeakerPos
{SF_CHANNEL_MAP_REAR_RIGHT
, -150.0f
, 0.0f
},
212 SpeakerPos
{SF_CHANNEL_MAP_SIDE_LEFT
, 90.0f
, 0.0f
},
213 SpeakerPos
{SF_CHANNEL_MAP_SIDE_RIGHT
, -90.0f
, 0.0f
},
215 constexpr std::array X714Map
{
216 SpeakerPos
{SF_CHANNEL_MAP_LEFT
, 30.0f
, 0.0f
},
217 SpeakerPos
{SF_CHANNEL_MAP_RIGHT
, -30.0f
, 0.0f
},
218 SpeakerPos
{SF_CHANNEL_MAP_CENTER
, 0.0f
, 0.0f
},
219 SpeakerPos
{SF_CHANNEL_MAP_LFE
, 0.0f
, 0.0f
},
220 SpeakerPos
{SF_CHANNEL_MAP_REAR_LEFT
, 150.0f
, 0.0f
},
221 SpeakerPos
{SF_CHANNEL_MAP_REAR_RIGHT
, -150.0f
, 0.0f
},
222 SpeakerPos
{SF_CHANNEL_MAP_SIDE_LEFT
, 90.0f
, 0.0f
},
223 SpeakerPos
{SF_CHANNEL_MAP_SIDE_RIGHT
, -90.0f
, 0.0f
},
224 SpeakerPos
{SF_CHANNEL_MAP_TOP_FRONT_LEFT
, 45.0f
, 35.0f
},
225 SpeakerPos
{SF_CHANNEL_MAP_TOP_FRONT_RIGHT
, -45.0f
, 35.0f
},
226 SpeakerPos
{SF_CHANNEL_MAP_TOP_REAR_LEFT
, 135.0f
, 35.0f
},
227 SpeakerPos
{SF_CHANNEL_MAP_TOP_REAR_RIGHT
, -135.0f
, 35.0f
},
230 constexpr auto GenCoeffs(double x
/*+front*/, double y
/*+left*/, double z
/*+up*/) noexcept
232 /* Coefficients are +3dB of FuMa. */
233 return std::array
<float,4>{{
235 static_cast<float>(al::numbers::sqrt2
* x
),
236 static_cast<float>(al::numbers::sqrt2
* y
),
237 static_cast<float>(al::numbers::sqrt2
* z
)
244 int main(int argc
, char **argv
)
246 if(argc
< 2 || std::strcmp(argv
[1], "-h") == 0 || std::strcmp(argv
[1], "--help") == 0)
248 printf("Usage: %s <infile...>\n\n", argv
[0]);
253 size_t num_files
{0}, num_encoded
{0};
254 for(int fidx
{1};fidx
< argc
;++fidx
)
256 if(strcmp(argv
[fidx
], "-bhj") == 0)
261 if(strcmp(argv
[fidx
], "-thj") == 0)
266 if(strcmp(argv
[fidx
], "-phj") == 0)
273 std::string outname
{argv
[fidx
]};
274 size_t lastslash
{outname
.find_last_of('/')};
275 if(lastslash
!= std::string::npos
)
276 outname
.erase(0, lastslash
+1);
277 size_t extpos
{outname
.find_last_of('.')};
278 if(extpos
!= std::string::npos
)
279 outname
.resize(extpos
);
280 outname
+= ".uhj.flac";
283 SndFilePtr infile
{sf_open(argv
[fidx
], SFM_READ
, &ininfo
)};
286 fprintf(stderr
, "Failed to open %s\n", argv
[fidx
]);
289 printf("Converting %s to %s...\n", argv
[fidx
], outname
.c_str());
291 /* Work out the channel map, preferably using the actual channel map
292 * from the file/format, but falling back to assuming WFX order.
294 al::span
<const SpeakerPos
> spkrs
;
295 auto chanmap
= std::vector
<int>(static_cast<uint
>(ininfo
.channels
), SF_CHANNEL_MAP_INVALID
);
296 if(sf_command(infile
.get(), SFC_GET_CHANNEL_MAP_INFO
, chanmap
.data(),
297 ininfo
.channels
*int{sizeof(int)}) == SF_TRUE
)
299 static const std::array
<int,2> stereomap
{{SF_CHANNEL_MAP_LEFT
, SF_CHANNEL_MAP_RIGHT
}};
300 static const std::array
<int,4> quadmap
{{SF_CHANNEL_MAP_LEFT
, SF_CHANNEL_MAP_RIGHT
,
301 SF_CHANNEL_MAP_REAR_LEFT
, SF_CHANNEL_MAP_REAR_RIGHT
}};
302 static const std::array
<int,6> x51map
{{SF_CHANNEL_MAP_LEFT
, SF_CHANNEL_MAP_RIGHT
,
303 SF_CHANNEL_MAP_CENTER
, SF_CHANNEL_MAP_LFE
,
304 SF_CHANNEL_MAP_SIDE_LEFT
, SF_CHANNEL_MAP_SIDE_RIGHT
}};
305 static const std::array
<int,6> x51rearmap
{{SF_CHANNEL_MAP_LEFT
, SF_CHANNEL_MAP_RIGHT
,
306 SF_CHANNEL_MAP_CENTER
, SF_CHANNEL_MAP_LFE
,
307 SF_CHANNEL_MAP_REAR_LEFT
, SF_CHANNEL_MAP_REAR_RIGHT
}};
308 static const std::array
<int,8> x71map
{{SF_CHANNEL_MAP_LEFT
, SF_CHANNEL_MAP_RIGHT
,
309 SF_CHANNEL_MAP_CENTER
, SF_CHANNEL_MAP_LFE
,
310 SF_CHANNEL_MAP_REAR_LEFT
, SF_CHANNEL_MAP_REAR_RIGHT
,
311 SF_CHANNEL_MAP_SIDE_LEFT
, SF_CHANNEL_MAP_SIDE_RIGHT
}};
312 static const std::array
<int,12> x714map
{{SF_CHANNEL_MAP_LEFT
, SF_CHANNEL_MAP_RIGHT
,
313 SF_CHANNEL_MAP_CENTER
, SF_CHANNEL_MAP_LFE
,
314 SF_CHANNEL_MAP_REAR_LEFT
, SF_CHANNEL_MAP_REAR_RIGHT
,
315 SF_CHANNEL_MAP_SIDE_LEFT
, SF_CHANNEL_MAP_SIDE_RIGHT
,
316 SF_CHANNEL_MAP_TOP_FRONT_LEFT
, SF_CHANNEL_MAP_TOP_FRONT_RIGHT
,
317 SF_CHANNEL_MAP_TOP_REAR_LEFT
, SF_CHANNEL_MAP_TOP_REAR_RIGHT
}};
318 static const std::array
<int,3> ambi2dmap
{{SF_CHANNEL_MAP_AMBISONIC_B_W
,
319 SF_CHANNEL_MAP_AMBISONIC_B_X
, SF_CHANNEL_MAP_AMBISONIC_B_Y
}};
320 static const std::array
<int,4> ambi3dmap
{{SF_CHANNEL_MAP_AMBISONIC_B_W
,
321 SF_CHANNEL_MAP_AMBISONIC_B_X
, SF_CHANNEL_MAP_AMBISONIC_B_Y
,
322 SF_CHANNEL_MAP_AMBISONIC_B_Z
}};
324 auto match_chanmap
= [](const al::span
<int> a
, const al::span
<const int> b
) -> bool
326 if(a
.size() != b
.size())
328 auto find_channel
= [b
](const int id
) -> bool
329 { return std::find(b
.begin(), b
.end(), id
) != b
.end(); };
330 return std::all_of(a
.cbegin(), a
.cend(), find_channel
);
332 if(match_chanmap(chanmap
, stereomap
))
334 else if(match_chanmap(chanmap
, quadmap
))
336 else if(match_chanmap(chanmap
, x51map
))
338 else if(match_chanmap(chanmap
, x51rearmap
))
340 else if(match_chanmap(chanmap
, x71map
))
342 else if(match_chanmap(chanmap
, x714map
))
344 else if(match_chanmap(chanmap
, ambi2dmap
) || match_chanmap(chanmap
, ambi3dmap
))
353 mapstr
= std::to_string(chanmap
[0]);
354 for(int idx
: al::span
<int>{chanmap
}.subspan
<1>())
357 mapstr
+= std::to_string(idx
);
360 fprintf(stderr
, " ... %zu channels not supported (map: %s)\n", chanmap
.size(),
365 else if(ininfo
.channels
== 2)
367 fprintf(stderr
, " ... assuming WFX order stereo\n");
369 chanmap
[0] = SF_CHANNEL_MAP_FRONT_LEFT
;
370 chanmap
[1] = SF_CHANNEL_MAP_FRONT_RIGHT
;
372 else if(ininfo
.channels
== 6)
374 fprintf(stderr
, " ... assuming WFX order 5.1\n");
376 chanmap
[0] = SF_CHANNEL_MAP_FRONT_LEFT
;
377 chanmap
[1] = SF_CHANNEL_MAP_FRONT_RIGHT
;
378 chanmap
[2] = SF_CHANNEL_MAP_FRONT_CENTER
;
379 chanmap
[3] = SF_CHANNEL_MAP_LFE
;
380 chanmap
[4] = SF_CHANNEL_MAP_SIDE_LEFT
;
381 chanmap
[5] = SF_CHANNEL_MAP_SIDE_RIGHT
;
383 else if(ininfo
.channels
== 8)
385 fprintf(stderr
, " ... assuming WFX order 7.1\n");
387 chanmap
[0] = SF_CHANNEL_MAP_FRONT_LEFT
;
388 chanmap
[1] = SF_CHANNEL_MAP_FRONT_RIGHT
;
389 chanmap
[2] = SF_CHANNEL_MAP_FRONT_CENTER
;
390 chanmap
[3] = SF_CHANNEL_MAP_LFE
;
391 chanmap
[4] = SF_CHANNEL_MAP_REAR_LEFT
;
392 chanmap
[5] = SF_CHANNEL_MAP_REAR_RIGHT
;
393 chanmap
[6] = SF_CHANNEL_MAP_SIDE_LEFT
;
394 chanmap
[7] = SF_CHANNEL_MAP_SIDE_RIGHT
;
398 fprintf(stderr
, " ... unmapped %d-channel audio not supported\n", ininfo
.channels
);
403 outinfo
.frames
= ininfo
.frames
;
404 outinfo
.samplerate
= ininfo
.samplerate
;
405 outinfo
.channels
= static_cast<int>(uhjchans
);
406 outinfo
.format
= SF_FORMAT_PCM_24
| SF_FORMAT_FLAC
;
407 SndFilePtr outfile
{sf_open(outname
.c_str(), SFM_WRITE
, &outinfo
)};
410 fprintf(stderr
, " ... failed to create %s\n", outname
.c_str());
414 auto encoder
= std::make_unique
<UhjEncoder
>();
415 auto splbuf
= al::vector
<FloatBufferLine
, 16>(static_cast<uint
>(ininfo
.channels
)+9+size_t{uhjchans
});
416 auto ambmem
= al::span
{splbuf
}.subspan
<0,4>();
417 auto encmem
= al::span
{splbuf
}.subspan
<4,4>();
418 auto srcmem
= al::span
{splbuf
[8]};
419 auto outmem
= al::span
<float>{splbuf
[9].data(), size_t{BufferLineSize
}*uhjchans
};
421 /* A number of initial samples need to be skipped to cut the lead-in
422 * from the all-pass filter delay. The same number of samples need to
423 * be fed through the encoder after reaching the end of the input file
424 * to ensure none of the original input is lost.
426 size_t total_wrote
{0};
427 size_t LeadIn
{UhjEncoder::sFilterDelay
};
428 sf_count_t LeadOut
{UhjEncoder::sFilterDelay
};
429 while(LeadIn
> 0 || LeadOut
> 0)
431 auto inmem
= outmem
.data() + outmem
.size();
432 auto sgot
= sf_readf_float(infile
.get(), inmem
, BufferLineSize
);
434 sgot
= std::max
<sf_count_t
>(sgot
, 0);
435 if(sgot
< BufferLineSize
)
437 const sf_count_t remaining
{std::min(BufferLineSize
- sgot
, LeadOut
)};
438 std::fill_n(inmem
+ sgot
*ininfo
.channels
, remaining
*ininfo
.channels
, 0.0f
);
440 LeadOut
-= remaining
;
443 for(auto&& buf
: ambmem
)
446 auto got
= static_cast<size_t>(sgot
);
449 /* B-Format is already in the correct order. It just needs a
452 static constexpr float scale
{al::numbers::sqrt2_v
<float>};
453 const size_t chans
{std::min
<size_t>(static_cast<uint
>(ininfo
.channels
), 4u)};
454 for(size_t c
{0};c
< chans
;++c
)
456 for(size_t i
{0};i
< got
;++i
)
457 ambmem
[c
][i
] = inmem
[i
*static_cast<uint
>(ininfo
.channels
)] * scale
;
461 else for(const int chanid
: chanmap
)
463 /* Skip LFE. Or mix directly into W? Or W+X? */
464 if(chanid
== SF_CHANNEL_MAP_LFE
)
470 const auto spkr
= std::find_if(spkrs
.cbegin(), spkrs
.cend(),
471 [chanid
](const SpeakerPos
&pos
){return pos
.mChannelID
== chanid
;});
472 if(spkr
== spkrs
.cend())
474 fprintf(stderr
, " ... failed to find channel ID %d\n", chanid
);
478 for(size_t i
{0};i
< got
;++i
)
479 srcmem
[i
] = inmem
[i
* static_cast<uint
>(ininfo
.channels
)];
482 static constexpr auto Deg2Rad
= al::numbers::pi
/ 180.0;
483 const auto coeffs
= GenCoeffs(
484 std::cos(spkr
->mAzimuth
*Deg2Rad
) * std::cos(spkr
->mElevation
*Deg2Rad
),
485 std::sin(spkr
->mAzimuth
*Deg2Rad
) * std::cos(spkr
->mElevation
*Deg2Rad
),
486 std::sin(spkr
->mElevation
*Deg2Rad
));
487 for(size_t c
{0};c
< 4;++c
)
489 for(size_t i
{0};i
< got
;++i
)
490 ambmem
[c
][i
] += srcmem
[i
] * coeffs
[c
];
494 encoder
->encode(encmem
.subspan(0, uhjchans
), ambmem
, got
);
502 for(size_t c
{0};c
< uhjchans
;++c
)
504 static constexpr float max_val
{8388607.0f
/ 8388608.0f
};
505 for(size_t i
{0};i
< got
;++i
)
506 outmem
[i
*uhjchans
+ c
] = std::clamp(encmem
[c
][LeadIn
+i
], -1.0f
, max_val
);
510 sf_count_t wrote
{sf_writef_float(outfile
.get(), outmem
.data(),
511 static_cast<sf_count_t
>(got
))};
513 fprintf(stderr
, " ... failed to write samples: %d\n", sf_error(outfile
.get()));
515 total_wrote
+= static_cast<size_t>(wrote
);
517 printf(" ... wrote %zu samples (%" PRId64
").\n", total_wrote
, int64_t{ininfo
.frames
});
521 fprintf(stderr
, "Failed to encode any input files\n");
522 else if(num_encoded
< num_files
)
523 fprintf(stderr
, "Encoded %zu of %zu files\n", num_encoded
, num_files
);
525 printf("Encoded %s%zu file%s\n", (num_encoded
> 1) ? "all " : "", num_encoded
,
526 (num_encoded
== 1) ? "" : "s");