Recogmize jack64 for finding the JACK library name
[openal-soft.git] / utils / uhjencoder.cpp
blob451dee18c2dfc500e6669cb3e076b964a00bdb59
1 /*
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
22 * THE SOFTWARE.
25 #include "config.h"
27 #include <algorithm>
28 #include <array>
29 #include <cassert>
30 #include <cinttypes>
31 #include <cmath>
32 #include <cstddef>
33 #include <cstdio>
34 #include <memory>
35 #include <string>
36 #include <string_view>
37 #include <vector>
39 #include "alnumbers.h"
40 #include "alspan.h"
41 #include "alstring.h"
42 #include "phase_shifter.h"
43 #include "vector.h"
45 #include "sndfile.h"
47 #include "win_main_utf8.h"
50 namespace {
52 using namespace std::string_view_literals;
54 struct SndFileDeleter {
55 void operator()(SNDFILE *sndfile) { sf_close(sndfile); }
57 using SndFilePtr = std::unique_ptr<SNDFILE,SndFileDeleter>;
60 using uint = unsigned int;
62 constexpr uint BufferLineSize{1024};
64 using FloatBufferLine = std::array<float,BufferLineSize>;
65 using FloatBufferSpan = al::span<float,BufferLineSize>;
68 struct UhjEncoder {
69 constexpr static size_t sFilterDelay{1024};
71 /* Delays and processing storage for the unfiltered signal. */
72 alignas(16) std::array<float,BufferLineSize+sFilterDelay> mW{};
73 alignas(16) std::array<float,BufferLineSize+sFilterDelay> mX{};
74 alignas(16) std::array<float,BufferLineSize+sFilterDelay> mY{};
75 alignas(16) std::array<float,BufferLineSize+sFilterDelay> mZ{};
77 alignas(16) std::array<float,BufferLineSize> mS{};
78 alignas(16) std::array<float,BufferLineSize> mD{};
79 alignas(16) std::array<float,BufferLineSize> mT{};
81 /* History for the FIR filter. */
82 alignas(16) std::array<float,sFilterDelay*2 - 1> mWXHistory1{};
83 alignas(16) std::array<float,sFilterDelay*2 - 1> mWXHistory2{};
85 alignas(16) std::array<float,BufferLineSize + sFilterDelay*2> mTemp{};
87 void encode(const al::span<FloatBufferLine> OutSamples,
88 const al::span<const FloatBufferLine,4> InSamples, const size_t SamplesToDo);
91 const PhaseShifterT<UhjEncoder::sFilterDelay*2> PShift{};
94 /* Encoding UHJ from B-Format is done as:
96 * S = 0.9396926*W + 0.1855740*X
97 * D = j(-0.3420201*W + 0.5098604*X) + 0.6554516*Y
99 * Left = (S + D)/2.0
100 * Right = (S - D)/2.0
101 * T = j(-0.1432*W + 0.6512*X) - 0.7071068*Y
102 * Q = 0.9772*Z
104 * where j is a wide-band +90 degree phase shift. T is excluded from 2-channel
105 * output, and Q is excluded from 2- and 3-channel output.
107 void UhjEncoder::encode(const al::span<FloatBufferLine> OutSamples,
108 const al::span<const FloatBufferLine,4> InSamples, const size_t SamplesToDo)
110 const auto winput = al::span{InSamples[0]}.first(SamplesToDo);
111 const auto xinput = al::span{InSamples[1]}.first(SamplesToDo);
112 const auto yinput = al::span{InSamples[2]}.first(SamplesToDo);
113 const auto zinput = al::span{InSamples[3]}.first(SamplesToDo);
115 /* Combine the previously delayed input signal with the new input. */
116 std::copy(winput.begin(), winput.end(), mW.begin()+sFilterDelay);
117 std::copy(xinput.begin(), xinput.end(), mX.begin()+sFilterDelay);
118 std::copy(yinput.begin(), yinput.end(), mY.begin()+sFilterDelay);
119 std::copy(zinput.begin(), zinput.end(), mZ.begin()+sFilterDelay);
121 /* S = 0.9396926*W + 0.1855740*X */
122 for(size_t i{0};i < SamplesToDo;++i)
123 mS[i] = 0.9396926f*mW[i] + 0.1855740f*mX[i];
125 /* Precompute j(-0.3420201*W + 0.5098604*X) and store in mD. */
126 auto tmpiter = std::copy(mWXHistory1.cbegin(), mWXHistory1.cend(), mTemp.begin());
127 std::transform(winput.begin(), winput.end(), xinput.begin(), tmpiter,
128 [](const float w, const float x) noexcept -> float
129 { return -0.3420201f*w + 0.5098604f*x; });
130 std::copy_n(mTemp.cbegin()+SamplesToDo, mWXHistory1.size(), mWXHistory1.begin());
131 PShift.process(al::span{mD}.first(SamplesToDo), mTemp);
133 /* D = j(-0.3420201*W + 0.5098604*X) + 0.6554516*Y */
134 for(size_t i{0};i < SamplesToDo;++i)
135 mD[i] = mD[i] + 0.6554516f*mY[i];
137 /* Left = (S + D)/2.0 */
138 auto left = al::span{OutSamples[0]};
139 for(size_t i{0};i < SamplesToDo;i++)
140 left[i] = (mS[i] + mD[i]) * 0.5f;
141 /* Right = (S - D)/2.0 */
142 auto right = al::span{OutSamples[1]};
143 for(size_t i{0};i < SamplesToDo;i++)
144 right[i] = (mS[i] - mD[i]) * 0.5f;
146 if(OutSamples.size() > 2)
148 /* Precompute j(-0.1432*W + 0.6512*X) and store in mT. */
149 tmpiter = std::copy(mWXHistory2.cbegin(), mWXHistory2.cend(), mTemp.begin());
150 std::transform(winput.begin(), winput.end(), xinput.begin(), tmpiter,
151 [](const float w, const float x) noexcept -> float
152 { return -0.1432f*w + 0.6512f*x; });
153 std::copy_n(mTemp.cbegin()+SamplesToDo, mWXHistory2.size(), mWXHistory2.begin());
154 PShift.process(al::span{mT}.first(SamplesToDo), mTemp);
156 /* T = j(-0.1432*W + 0.6512*X) - 0.7071068*Y */
157 auto t = al::span{OutSamples[2]};
158 for(size_t i{0};i < SamplesToDo;i++)
159 t[i] = mT[i] - 0.7071068f*mY[i];
161 if(OutSamples.size() > 3)
163 /* Q = 0.9772*Z */
164 auto q = al::span{OutSamples[3]};
165 for(size_t i{0};i < SamplesToDo;i++)
166 q[i] = 0.9772f*mZ[i];
169 /* Copy the future samples to the front for next time. */
170 std::copy(mW.cbegin()+SamplesToDo, mW.cbegin()+SamplesToDo+sFilterDelay, mW.begin());
171 std::copy(mX.cbegin()+SamplesToDo, mX.cbegin()+SamplesToDo+sFilterDelay, mX.begin());
172 std::copy(mY.cbegin()+SamplesToDo, mY.cbegin()+SamplesToDo+sFilterDelay, mY.begin());
173 std::copy(mZ.cbegin()+SamplesToDo, mZ.cbegin()+SamplesToDo+sFilterDelay, mZ.begin());
177 struct SpeakerPos {
178 int mChannelID;
179 float mAzimuth;
180 float mElevation;
183 /* Azimuth is counter-clockwise. */
184 constexpr std::array MonoMap{
185 SpeakerPos{SF_CHANNEL_MAP_CENTER, 0.0f, 0.0f},
187 constexpr std::array StereoMap{
188 SpeakerPos{SF_CHANNEL_MAP_LEFT, 30.0f, 0.0f},
189 SpeakerPos{SF_CHANNEL_MAP_RIGHT, -30.0f, 0.0f},
191 constexpr std::array QuadMap{
192 SpeakerPos{SF_CHANNEL_MAP_LEFT, 45.0f, 0.0f},
193 SpeakerPos{SF_CHANNEL_MAP_RIGHT, -45.0f, 0.0f},
194 SpeakerPos{SF_CHANNEL_MAP_REAR_LEFT, 135.0f, 0.0f},
195 SpeakerPos{SF_CHANNEL_MAP_REAR_RIGHT, -135.0f, 0.0f},
197 constexpr std::array X51Map{
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_SIDE_LEFT, 110.0f, 0.0f},
203 SpeakerPos{SF_CHANNEL_MAP_SIDE_RIGHT, -110.0f, 0.0f},
205 constexpr std::array X51RearMap{
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, 110.0f, 0.0f},
211 SpeakerPos{SF_CHANNEL_MAP_REAR_RIGHT, -110.0f, 0.0f},
213 constexpr std::array X71Map{
214 SpeakerPos{SF_CHANNEL_MAP_LEFT, 30.0f, 0.0f},
215 SpeakerPos{SF_CHANNEL_MAP_RIGHT, -30.0f, 0.0f},
216 SpeakerPos{SF_CHANNEL_MAP_CENTER, 0.0f, 0.0f},
217 SpeakerPos{SF_CHANNEL_MAP_LFE, 0.0f, 0.0f},
218 SpeakerPos{SF_CHANNEL_MAP_REAR_LEFT, 150.0f, 0.0f},
219 SpeakerPos{SF_CHANNEL_MAP_REAR_RIGHT, -150.0f, 0.0f},
220 SpeakerPos{SF_CHANNEL_MAP_SIDE_LEFT, 90.0f, 0.0f},
221 SpeakerPos{SF_CHANNEL_MAP_SIDE_RIGHT, -90.0f, 0.0f},
223 constexpr std::array X714Map{
224 SpeakerPos{SF_CHANNEL_MAP_LEFT, 30.0f, 0.0f},
225 SpeakerPos{SF_CHANNEL_MAP_RIGHT, -30.0f, 0.0f},
226 SpeakerPos{SF_CHANNEL_MAP_CENTER, 0.0f, 0.0f},
227 SpeakerPos{SF_CHANNEL_MAP_LFE, 0.0f, 0.0f},
228 SpeakerPos{SF_CHANNEL_MAP_REAR_LEFT, 150.0f, 0.0f},
229 SpeakerPos{SF_CHANNEL_MAP_REAR_RIGHT, -150.0f, 0.0f},
230 SpeakerPos{SF_CHANNEL_MAP_SIDE_LEFT, 90.0f, 0.0f},
231 SpeakerPos{SF_CHANNEL_MAP_SIDE_RIGHT, -90.0f, 0.0f},
232 SpeakerPos{SF_CHANNEL_MAP_TOP_FRONT_LEFT, 45.0f, 35.0f},
233 SpeakerPos{SF_CHANNEL_MAP_TOP_FRONT_RIGHT, -45.0f, 35.0f},
234 SpeakerPos{SF_CHANNEL_MAP_TOP_REAR_LEFT, 135.0f, 35.0f},
235 SpeakerPos{SF_CHANNEL_MAP_TOP_REAR_RIGHT, -135.0f, 35.0f},
238 constexpr auto GenCoeffs(double x /*+front*/, double y /*+left*/, double z /*+up*/) noexcept
240 /* Coefficients are +3dB of FuMa. */
241 return std::array<float,4>{{
242 1.0f,
243 static_cast<float>(al::numbers::sqrt2 * x),
244 static_cast<float>(al::numbers::sqrt2 * y),
245 static_cast<float>(al::numbers::sqrt2 * z)
250 int main(al::span<std::string_view> args)
252 if(args.size() < 2 || args[1] == "-h" || args[1] == "--help")
254 printf("Usage: %.*s <[options] infile...>\n\n"
255 " Options:\n"
256 " -bhj Encode 2-channel UHJ, aka \"BJH\" (default).\n"
257 " -thj Encode 3-channel UHJ, aka \"TJH\".\n"
258 " -phj Encode 4-channel UHJ, aka \"PJH\".\n"
259 "\n"
260 "3-channel UHJ supplements 2-channel UHJ with an extra channel that allows full\n"
261 "reconstruction of first-order 2D ambisonics. 4-channel UHJ supplements 3-channel\n"
262 "UHJ with an extra channel carrying height information, providing for full\n"
263 "reconstruction of first-order 3D ambisonics.\n"
264 "\n"
265 "Note: The third and fourth channels should be ignored if they're not being\n"
266 "decoded. Unlike the first two channels, they are not designed for undecoded\n"
267 "playback, so the resulting files will not play correctly if this isn't handled.\n",
268 al::sizei(args[0]), args[0].data());
269 return 1;
271 args = args.subspan(1);
273 uint uhjchans{2};
274 size_t num_files{0}, num_encoded{0};
275 auto process_arg = [&uhjchans,&num_files,&num_encoded](std::string_view arg) -> void
277 if(arg == "-bhj"sv)
279 uhjchans = 2;
280 return;
282 if(arg == "-thj"sv)
284 uhjchans = 3;
285 return;
287 if(arg == "-phj"sv)
289 uhjchans = 4;
290 return;
292 ++num_files;
294 auto outname = std::string{arg};
295 const auto lastslash = outname.rfind('/');
296 if(lastslash != std::string::npos)
297 outname.erase(0, lastslash+1);
298 const auto extpos = outname.rfind('.');
299 if(extpos != std::string::npos)
300 outname.resize(extpos);
301 outname += ".uhj.flac";
303 SF_INFO ininfo{};
304 SndFilePtr infile{sf_open(std::string{arg}.c_str(), SFM_READ, &ininfo)};
305 if(!infile)
307 fprintf(stderr, "Failed to open %.*s\n", al::sizei(arg), arg.data());
308 return;
310 printf("Converting %.*s to %s...\n", al::sizei(arg), arg.data(), outname.c_str());
312 /* Work out the channel map, preferably using the actual channel map
313 * from the file/format, but falling back to assuming WFX order.
315 al::span<const SpeakerPos> spkrs;
316 auto chanmap = std::vector<int>(static_cast<uint>(ininfo.channels), SF_CHANNEL_MAP_INVALID);
317 if(sf_command(infile.get(), SFC_GET_CHANNEL_MAP_INFO, chanmap.data(),
318 ininfo.channels*int{sizeof(int)}) == SF_TRUE)
320 static const std::array<int,1> monomap{{SF_CHANNEL_MAP_CENTER}};
321 static const std::array<int,2> stereomap{{SF_CHANNEL_MAP_LEFT, SF_CHANNEL_MAP_RIGHT}};
322 static const std::array<int,4> quadmap{{SF_CHANNEL_MAP_LEFT, SF_CHANNEL_MAP_RIGHT,
323 SF_CHANNEL_MAP_REAR_LEFT, SF_CHANNEL_MAP_REAR_RIGHT}};
324 static const std::array<int,6> x51map{{SF_CHANNEL_MAP_LEFT, SF_CHANNEL_MAP_RIGHT,
325 SF_CHANNEL_MAP_CENTER, SF_CHANNEL_MAP_LFE,
326 SF_CHANNEL_MAP_SIDE_LEFT, SF_CHANNEL_MAP_SIDE_RIGHT}};
327 static const std::array<int,6> x51rearmap{{SF_CHANNEL_MAP_LEFT, SF_CHANNEL_MAP_RIGHT,
328 SF_CHANNEL_MAP_CENTER, SF_CHANNEL_MAP_LFE,
329 SF_CHANNEL_MAP_REAR_LEFT, SF_CHANNEL_MAP_REAR_RIGHT}};
330 static const std::array<int,8> x71map{{SF_CHANNEL_MAP_LEFT, SF_CHANNEL_MAP_RIGHT,
331 SF_CHANNEL_MAP_CENTER, SF_CHANNEL_MAP_LFE,
332 SF_CHANNEL_MAP_REAR_LEFT, SF_CHANNEL_MAP_REAR_RIGHT,
333 SF_CHANNEL_MAP_SIDE_LEFT, SF_CHANNEL_MAP_SIDE_RIGHT}};
334 static const std::array<int,12> x714map{{SF_CHANNEL_MAP_LEFT, SF_CHANNEL_MAP_RIGHT,
335 SF_CHANNEL_MAP_CENTER, SF_CHANNEL_MAP_LFE,
336 SF_CHANNEL_MAP_REAR_LEFT, SF_CHANNEL_MAP_REAR_RIGHT,
337 SF_CHANNEL_MAP_SIDE_LEFT, SF_CHANNEL_MAP_SIDE_RIGHT,
338 SF_CHANNEL_MAP_TOP_FRONT_LEFT, SF_CHANNEL_MAP_TOP_FRONT_RIGHT,
339 SF_CHANNEL_MAP_TOP_REAR_LEFT, SF_CHANNEL_MAP_TOP_REAR_RIGHT}};
340 static const std::array<int,3> ambi2dmap{{SF_CHANNEL_MAP_AMBISONIC_B_W,
341 SF_CHANNEL_MAP_AMBISONIC_B_X, SF_CHANNEL_MAP_AMBISONIC_B_Y}};
342 static const std::array<int,4> ambi3dmap{{SF_CHANNEL_MAP_AMBISONIC_B_W,
343 SF_CHANNEL_MAP_AMBISONIC_B_X, SF_CHANNEL_MAP_AMBISONIC_B_Y,
344 SF_CHANNEL_MAP_AMBISONIC_B_Z}};
346 auto match_chanmap = [](const al::span<int> a, const al::span<const int> b) -> bool
348 if(a.size() != b.size())
349 return false;
350 auto find_channel = [b](const int id) -> bool
351 { return std::find(b.begin(), b.end(), id) != b.end(); };
352 return std::all_of(a.cbegin(), a.cend(), find_channel);
354 if(match_chanmap(chanmap, monomap))
355 spkrs = MonoMap;
356 else if(match_chanmap(chanmap, stereomap))
357 spkrs = StereoMap;
358 else if(match_chanmap(chanmap, quadmap))
359 spkrs = QuadMap;
360 else if(match_chanmap(chanmap, x51map))
361 spkrs = X51Map;
362 else if(match_chanmap(chanmap, x51rearmap))
363 spkrs = X51RearMap;
364 else if(match_chanmap(chanmap, x71map))
365 spkrs = X71Map;
366 else if(match_chanmap(chanmap, x714map))
367 spkrs = X714Map;
368 else if(match_chanmap(chanmap, ambi2dmap) || match_chanmap(chanmap, ambi3dmap))
370 /* Do nothing. */
372 else
374 std::string mapstr;
375 if(!chanmap.empty())
377 mapstr = std::to_string(chanmap[0]);
378 for(int idx : al::span<int>{chanmap}.subspan<1>())
380 mapstr += ',';
381 mapstr += std::to_string(idx);
384 fprintf(stderr, " ... %zu channels not supported (map: %s)\n", chanmap.size(),
385 mapstr.c_str());
386 return;
389 else if(sf_command(infile.get(), SFC_WAVEX_GET_AMBISONIC, nullptr,
390 0) == SF_AMBISONIC_B_FORMAT)
392 if(ininfo.channels == 4)
394 fprintf(stderr, " ... detected FuMa 3D B-Format\n");
395 chanmap[0] = SF_CHANNEL_MAP_AMBISONIC_B_W;
396 chanmap[1] = SF_CHANNEL_MAP_AMBISONIC_B_X;
397 chanmap[2] = SF_CHANNEL_MAP_AMBISONIC_B_Y;
398 chanmap[3] = SF_CHANNEL_MAP_AMBISONIC_B_Z;
400 else if(ininfo.channels == 3)
402 fprintf(stderr, " ... detected FuMa 2D B-Format\n");
403 chanmap[0] = SF_CHANNEL_MAP_AMBISONIC_B_W;
404 chanmap[1] = SF_CHANNEL_MAP_AMBISONIC_B_X;
405 chanmap[2] = SF_CHANNEL_MAP_AMBISONIC_B_Y;
407 else
409 fprintf(stderr, " ... unhandled %d-channel B-Format\n", ininfo.channels);
410 return;
413 else if(ininfo.channels == 1)
415 fprintf(stderr, " ... assuming front-center\n");
416 spkrs = MonoMap;
417 chanmap[0] = SF_CHANNEL_MAP_CENTER;
419 else if(ininfo.channels == 2)
421 fprintf(stderr, " ... assuming WFX order stereo\n");
422 spkrs = StereoMap;
423 chanmap[0] = SF_CHANNEL_MAP_LEFT;
424 chanmap[1] = SF_CHANNEL_MAP_RIGHT;
426 else if(ininfo.channels == 6)
428 fprintf(stderr, " ... assuming WFX order 5.1\n");
429 spkrs = X51Map;
430 chanmap[0] = SF_CHANNEL_MAP_LEFT;
431 chanmap[1] = SF_CHANNEL_MAP_RIGHT;
432 chanmap[2] = SF_CHANNEL_MAP_CENTER;
433 chanmap[3] = SF_CHANNEL_MAP_LFE;
434 chanmap[4] = SF_CHANNEL_MAP_SIDE_LEFT;
435 chanmap[5] = SF_CHANNEL_MAP_SIDE_RIGHT;
437 else if(ininfo.channels == 8)
439 fprintf(stderr, " ... assuming WFX order 7.1\n");
440 spkrs = X71Map;
441 chanmap[0] = SF_CHANNEL_MAP_LEFT;
442 chanmap[1] = SF_CHANNEL_MAP_RIGHT;
443 chanmap[2] = SF_CHANNEL_MAP_CENTER;
444 chanmap[3] = SF_CHANNEL_MAP_LFE;
445 chanmap[4] = SF_CHANNEL_MAP_REAR_LEFT;
446 chanmap[5] = SF_CHANNEL_MAP_REAR_RIGHT;
447 chanmap[6] = SF_CHANNEL_MAP_SIDE_LEFT;
448 chanmap[7] = SF_CHANNEL_MAP_SIDE_RIGHT;
450 else
452 fprintf(stderr, " ... unmapped %d-channel audio not supported\n", ininfo.channels);
453 return;
456 SF_INFO outinfo{};
457 outinfo.frames = ininfo.frames;
458 outinfo.samplerate = ininfo.samplerate;
459 outinfo.channels = static_cast<int>(uhjchans);
460 outinfo.format = SF_FORMAT_PCM_24 | SF_FORMAT_FLAC;
461 SndFilePtr outfile{sf_open(outname.c_str(), SFM_WRITE, &outinfo)};
462 if(!outfile)
464 fprintf(stderr, " ... failed to create %s\n", outname.c_str());
465 return;
468 auto encoder = std::make_unique<UhjEncoder>();
469 auto splbuf = al::vector<FloatBufferLine, 16>(9);
470 auto ambmem = al::span{splbuf}.subspan<0,4>();
471 auto encmem = al::span{splbuf}.subspan<4,4>();
472 auto srcmem = al::span{splbuf[8]};
473 auto membuf = al::vector<float,16>((static_cast<uint>(ininfo.channels)+size_t{uhjchans})
474 * BufferLineSize);
475 auto outmem = al::span{membuf}.first(size_t{BufferLineSize}*uhjchans);
476 auto inmem = al::span{membuf}.last(size_t{BufferLineSize}
477 * static_cast<uint>(ininfo.channels));
479 /* A number of initial samples need to be skipped to cut the lead-in
480 * from the all-pass filter delay. The same number of samples need to
481 * be fed through the encoder after reaching the end of the input file
482 * to ensure none of the original input is lost.
484 size_t total_wrote{0};
485 size_t LeadIn{UhjEncoder::sFilterDelay};
486 sf_count_t LeadOut{UhjEncoder::sFilterDelay};
487 while(LeadIn > 0 || LeadOut > 0)
489 auto sgot = sf_readf_float(infile.get(), inmem.data(), BufferLineSize);
491 sgot = std::max<sf_count_t>(sgot, 0);
492 if(sgot < BufferLineSize)
494 const sf_count_t remaining{std::min(BufferLineSize - sgot, LeadOut)};
495 std::fill_n(inmem.begin() + sgot*ininfo.channels, remaining*ininfo.channels, 0.0f);
496 sgot += remaining;
497 LeadOut -= remaining;
500 for(auto&& buf : ambmem)
501 buf.fill(0.0f);
503 auto got = static_cast<size_t>(sgot);
504 if(spkrs.empty())
506 /* B-Format is already in the correct order. It just needs a
507 * +3dB boost.
509 static constexpr float scale{al::numbers::sqrt2_v<float>};
510 const size_t chans{std::min<size_t>(static_cast<uint>(ininfo.channels), 4u)};
511 for(size_t c{0};c < chans;++c)
513 for(size_t i{0};i < got;++i)
514 ambmem[c][i] = inmem[i*static_cast<uint>(ininfo.channels) + c] * scale;
517 else for(size_t idx{0};idx < chanmap.size();++idx)
519 const int chanid{chanmap[idx]};
520 /* Skip LFE. Or mix directly into W? Or W+X? */
521 if(chanid == SF_CHANNEL_MAP_LFE)
522 continue;
524 const auto spkr = std::find_if(spkrs.cbegin(), spkrs.cend(),
525 [chanid](const SpeakerPos pos){return pos.mChannelID == chanid;});
526 if(spkr == spkrs.cend())
528 fprintf(stderr, " ... failed to find channel ID %d\n", chanid);
529 continue;
532 for(size_t i{0};i < got;++i)
533 srcmem[i] = inmem[i*static_cast<uint>(ininfo.channels) + idx];
535 static constexpr auto Deg2Rad = al::numbers::pi / 180.0;
536 const auto coeffs = GenCoeffs(
537 std::cos(spkr->mAzimuth*Deg2Rad) * std::cos(spkr->mElevation*Deg2Rad),
538 std::sin(spkr->mAzimuth*Deg2Rad) * std::cos(spkr->mElevation*Deg2Rad),
539 std::sin(spkr->mElevation*Deg2Rad));
540 for(size_t c{0};c < 4;++c)
542 for(size_t i{0};i < got;++i)
543 ambmem[c][i] += srcmem[i] * coeffs[c];
547 encoder->encode(encmem.subspan(0, uhjchans), ambmem, got);
548 if(LeadIn >= got)
550 LeadIn -= got;
551 continue;
554 got -= LeadIn;
555 for(size_t c{0};c < uhjchans;++c)
557 static constexpr float max_val{8388607.0f / 8388608.0f};
558 for(size_t i{0};i < got;++i)
559 outmem[i*uhjchans + c] = std::clamp(encmem[c][LeadIn+i], -1.0f, max_val);
561 LeadIn = 0;
563 sf_count_t wrote{sf_writef_float(outfile.get(), outmem.data(),
564 static_cast<sf_count_t>(got))};
565 if(wrote < 0)
566 fprintf(stderr, " ... failed to write samples: %d\n", sf_error(outfile.get()));
567 else
568 total_wrote += static_cast<size_t>(wrote);
570 printf(" ... wrote %zu samples (%" PRId64 ").\n", total_wrote, int64_t{ininfo.frames});
571 ++num_encoded;
573 std::for_each(args.begin(), args.end(), process_arg);
575 if(num_encoded == 0)
576 fprintf(stderr, "Failed to encode any input files\n");
577 else if(num_encoded < num_files)
578 fprintf(stderr, "Encoded %zu of %zu files\n", num_encoded, num_files);
579 else
580 printf("Encoded %s%zu file%s\n", (num_encoded > 1) ? "all " : "", num_encoded,
581 (num_encoded == 1) ? "" : "s");
582 return 0;
585 } /* namespace */
587 int main(int argc, char **argv)
589 assert(argc >= 0);
590 auto args = std::vector<std::string_view>(static_cast<unsigned int>(argc));
591 std::copy_n(argv, args.size(), args.begin());
592 return main(al::span{args});