]> git.tdb.fi Git - ext/openal.git/blob - utils/uhjencoder.cpp
Import OpenAL Soft 1.23.1 sources
[ext/openal.git] / utils / uhjencoder.cpp
1 /*
2  * 2-channel UHJ Encoder
3  *
4  * Copyright (c) Chris Robinson <chris.kcat@gmail.com>
5  *
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:
12  *
13  * The above copyright notice and this permission notice shall be included in
14  * all copies or substantial portions of the Software.
15  *
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.
23  */
24
25 #include "config.h"
26
27 #include <array>
28 #include <cstring>
29 #include <inttypes.h>
30 #include <memory>
31 #include <stddef.h>
32 #include <string>
33 #include <utility>
34 #include <vector>
35
36 #include "almalloc.h"
37 #include "alnumbers.h"
38 #include "alspan.h"
39 #include "opthelpers.h"
40 #include "phase_shifter.h"
41 #include "vector.h"
42
43 #include "sndfile.h"
44
45 #include "win_main_utf8.h"
46
47
48 namespace {
49
50 struct SndFileDeleter {
51     void operator()(SNDFILE *sndfile) { sf_close(sndfile); }
52 };
53 using SndFilePtr = std::unique_ptr<SNDFILE,SndFileDeleter>;
54
55
56 using uint = unsigned int;
57
58 constexpr uint BufferLineSize{1024};
59
60 using FloatBufferLine = std::array<float,BufferLineSize>;
61 using FloatBufferSpan = al::span<float,BufferLineSize>;
62
63
64 struct UhjEncoder {
65     constexpr static size_t sFilterDelay{1024};
66
67     /* Delays and processing storage for the unfiltered signal. */
68     alignas(16) std::array<float,BufferLineSize+sFilterDelay> mW{};
69     alignas(16) std::array<float,BufferLineSize+sFilterDelay> mX{};
70     alignas(16) std::array<float,BufferLineSize+sFilterDelay> mY{};
71     alignas(16) std::array<float,BufferLineSize+sFilterDelay> mZ{};
72
73     alignas(16) std::array<float,BufferLineSize> mS{};
74     alignas(16) std::array<float,BufferLineSize> mD{};
75     alignas(16) std::array<float,BufferLineSize> mT{};
76
77     /* History for the FIR filter. */
78     alignas(16) std::array<float,sFilterDelay*2 - 1> mWXHistory1{};
79     alignas(16) std::array<float,sFilterDelay*2 - 1> mWXHistory2{};
80
81     alignas(16) std::array<float,BufferLineSize + sFilterDelay*2> mTemp{};
82
83     void encode(const al::span<FloatBufferLine> OutSamples,
84         const al::span<FloatBufferLine,4> InSamples, const size_t SamplesToDo);
85
86     DEF_NEWDEL(UhjEncoder)
87 };
88
89 const PhaseShifterT<UhjEncoder::sFilterDelay*2> PShift{};
90
91
92 /* Encoding UHJ from B-Format is done as:
93  *
94  * S = 0.9396926*W + 0.1855740*X
95  * D = j(-0.3420201*W + 0.5098604*X) + 0.6554516*Y
96  *
97  * Left = (S + D)/2.0
98  * Right = (S - D)/2.0
99  * T = j(-0.1432*W + 0.6512*X) - 0.7071068*Y
100  * Q = 0.9772*Z
101  *
102  * where j is a wide-band +90 degree phase shift. T is excluded from 2-channel
103  * output, and Q is excluded from 2- and 3-channel output.
104  */
105 void UhjEncoder::encode(const al::span<FloatBufferLine> OutSamples,
106     const al::span<FloatBufferLine,4> InSamples, const size_t SamplesToDo)
107 {
108     const float *RESTRICT winput{al::assume_aligned<16>(InSamples[0].data())};
109     const float *RESTRICT xinput{al::assume_aligned<16>(InSamples[1].data())};
110     const float *RESTRICT yinput{al::assume_aligned<16>(InSamples[2].data())};
111     const float *RESTRICT zinput{al::assume_aligned<16>(InSamples[3].data())};
112
113     /* Combine the previously delayed input signal with the new input. */
114     std::copy_n(winput, SamplesToDo, mW.begin()+sFilterDelay);
115     std::copy_n(xinput, SamplesToDo, mX.begin()+sFilterDelay);
116     std::copy_n(yinput, SamplesToDo, mY.begin()+sFilterDelay);
117     std::copy_n(zinput, SamplesToDo, mZ.begin()+sFilterDelay);
118
119     /* S = 0.9396926*W + 0.1855740*X */
120     for(size_t i{0};i < SamplesToDo;++i)
121         mS[i] = 0.9396926f*mW[i] + 0.1855740f*mX[i];
122
123     /* Precompute j(-0.3420201*W + 0.5098604*X) and store in mD. */
124     auto tmpiter = std::copy(mWXHistory1.cbegin(), mWXHistory1.cend(), mTemp.begin());
125     std::transform(winput, winput+SamplesToDo, xinput, tmpiter,
126         [](const float w, const float x) noexcept -> float
127         { return -0.3420201f*w + 0.5098604f*x; });
128     std::copy_n(mTemp.cbegin()+SamplesToDo, mWXHistory1.size(), mWXHistory1.begin());
129     PShift.process({mD.data(), SamplesToDo}, mTemp.data());
130
131     /* D = j(-0.3420201*W + 0.5098604*X) + 0.6554516*Y */
132     for(size_t i{0};i < SamplesToDo;++i)
133         mD[i] = mD[i] + 0.6554516f*mY[i];
134
135     /* Left = (S + D)/2.0 */
136     float *RESTRICT left{al::assume_aligned<16>(OutSamples[0].data())};
137     for(size_t i{0};i < SamplesToDo;i++)
138         left[i] = (mS[i] + mD[i]) * 0.5f;
139     /* Right = (S - D)/2.0 */
140     float *RESTRICT right{al::assume_aligned<16>(OutSamples[1].data())};
141     for(size_t i{0};i < SamplesToDo;i++)
142         right[i] = (mS[i] - mD[i]) * 0.5f;
143
144     if(OutSamples.size() > 2)
145     {
146         /* Precompute j(-0.1432*W + 0.6512*X) and store in mT. */
147         tmpiter = std::copy(mWXHistory2.cbegin(), mWXHistory2.cend(), mTemp.begin());
148         std::transform(winput, winput+SamplesToDo, xinput, tmpiter,
149             [](const float w, const float x) noexcept -> float
150             { return -0.1432f*w + 0.6512f*x; });
151         std::copy_n(mTemp.cbegin()+SamplesToDo, mWXHistory2.size(), mWXHistory2.begin());
152         PShift.process({mT.data(), SamplesToDo}, mTemp.data());
153
154         /* T = j(-0.1432*W + 0.6512*X) - 0.7071068*Y */
155         float *RESTRICT t{al::assume_aligned<16>(OutSamples[2].data())};
156         for(size_t i{0};i < SamplesToDo;i++)
157             t[i] = mT[i] - 0.7071068f*mY[i];
158     }
159     if(OutSamples.size() > 3)
160     {
161         /* Q = 0.9772*Z */
162         float *RESTRICT q{al::assume_aligned<16>(OutSamples[3].data())};
163         for(size_t i{0};i < SamplesToDo;i++)
164             q[i] = 0.9772f*mZ[i];
165     }
166
167     /* Copy the future samples to the front for next time. */
168     std::copy(mW.cbegin()+SamplesToDo, mW.cbegin()+SamplesToDo+sFilterDelay, mW.begin());
169     std::copy(mX.cbegin()+SamplesToDo, mX.cbegin()+SamplesToDo+sFilterDelay, mX.begin());
170     std::copy(mY.cbegin()+SamplesToDo, mY.cbegin()+SamplesToDo+sFilterDelay, mY.begin());
171     std::copy(mZ.cbegin()+SamplesToDo, mZ.cbegin()+SamplesToDo+sFilterDelay, mZ.begin());
172 }
173
174
175 struct SpeakerPos {
176     int mChannelID;
177     float mAzimuth;
178     float mElevation;
179 };
180
181 /* Azimuth is counter-clockwise. */
182 constexpr SpeakerPos StereoMap[2]{
183     { SF_CHANNEL_MAP_LEFT,   30.0f, 0.0f },
184     { SF_CHANNEL_MAP_RIGHT, -30.0f, 0.0f },
185 }, QuadMap[4]{
186     { SF_CHANNEL_MAP_LEFT,         45.0f, 0.0f },
187     { SF_CHANNEL_MAP_RIGHT,       -45.0f, 0.0f },
188     { SF_CHANNEL_MAP_REAR_LEFT,   135.0f, 0.0f },
189     { SF_CHANNEL_MAP_REAR_RIGHT, -135.0f, 0.0f },
190 }, X51Map[6]{
191     { SF_CHANNEL_MAP_LEFT,         30.0f, 0.0f },
192     { SF_CHANNEL_MAP_RIGHT,       -30.0f, 0.0f },
193     { SF_CHANNEL_MAP_CENTER,        0.0f, 0.0f },
194     { SF_CHANNEL_MAP_LFE, 0.0f, 0.0f },
195     { SF_CHANNEL_MAP_SIDE_LEFT,   110.0f, 0.0f },
196     { SF_CHANNEL_MAP_SIDE_RIGHT, -110.0f, 0.0f },
197 }, X51RearMap[6]{
198     { SF_CHANNEL_MAP_LEFT,         30.0f, 0.0f },
199     { SF_CHANNEL_MAP_RIGHT,       -30.0f, 0.0f },
200     { SF_CHANNEL_MAP_CENTER,        0.0f, 0.0f },
201     { SF_CHANNEL_MAP_LFE, 0.0f, 0.0f },
202     { SF_CHANNEL_MAP_REAR_LEFT,   110.0f, 0.0f },
203     { SF_CHANNEL_MAP_REAR_RIGHT, -110.0f, 0.0f },
204 }, X71Map[8]{
205     { SF_CHANNEL_MAP_LEFT,         30.0f, 0.0f },
206     { SF_CHANNEL_MAP_RIGHT,       -30.0f, 0.0f },
207     { SF_CHANNEL_MAP_CENTER,        0.0f, 0.0f },
208     { SF_CHANNEL_MAP_LFE, 0.0f, 0.0f },
209     { SF_CHANNEL_MAP_REAR_LEFT,   150.0f, 0.0f },
210     { SF_CHANNEL_MAP_REAR_RIGHT, -150.0f, 0.0f },
211     { SF_CHANNEL_MAP_SIDE_LEFT,    90.0f, 0.0f },
212     { SF_CHANNEL_MAP_SIDE_RIGHT,  -90.0f, 0.0f },
213 }, X714Map[12]{
214     { SF_CHANNEL_MAP_LEFT,         30.0f,  0.0f },
215     { SF_CHANNEL_MAP_RIGHT,       -30.0f,  0.0f },
216     { SF_CHANNEL_MAP_CENTER,        0.0f,  0.0f },
217     { SF_CHANNEL_MAP_LFE, 0.0f, 0.0f },
218     { SF_CHANNEL_MAP_REAR_LEFT,   150.0f,  0.0f },
219     { SF_CHANNEL_MAP_REAR_RIGHT, -150.0f,  0.0f },
220     { SF_CHANNEL_MAP_SIDE_LEFT,    90.0f,  0.0f },
221     { SF_CHANNEL_MAP_SIDE_RIGHT,  -90.0f,  0.0f },
222     { SF_CHANNEL_MAP_TOP_FRONT_LEFT,    45.0f, 35.0f },
223     { SF_CHANNEL_MAP_TOP_FRONT_RIGHT,  -45.0f, 35.0f },
224     { SF_CHANNEL_MAP_TOP_REAR_LEFT,    135.0f, 35.0f },
225     { SF_CHANNEL_MAP_TOP_REAR_RIGHT,  -135.0f, 35.0f },
226 };
227
228 constexpr auto GenCoeffs(double x /*+front*/, double y /*+left*/, double z /*+up*/) noexcept
229 {
230     /* Coefficients are +3dB of FuMa. */
231     return std::array<float,4>{{
232         1.0f,
233         static_cast<float>(al::numbers::sqrt2 * x),
234         static_cast<float>(al::numbers::sqrt2 * y),
235         static_cast<float>(al::numbers::sqrt2 * z)
236     }};
237 }
238
239 } // namespace
240
241
242 int main(int argc, char **argv)
243 {
244     if(argc < 2 || std::strcmp(argv[1], "-h") == 0 || std::strcmp(argv[1], "--help") == 0)
245     {
246         printf("Usage: %s <infile...>\n\n", argv[0]);
247         return 1;
248     }
249
250     uint uhjchans{2};
251     size_t num_files{0}, num_encoded{0};
252     for(int fidx{1};fidx < argc;++fidx)
253     {
254         if(strcmp(argv[fidx], "-bhj") == 0)
255         {
256             uhjchans = 2;
257             continue;
258         }
259         if(strcmp(argv[fidx], "-thj") == 0)
260         {
261             uhjchans = 3;
262             continue;
263         }
264         if(strcmp(argv[fidx], "-phj") == 0)
265         {
266             uhjchans = 4;
267             continue;
268         }
269         ++num_files;
270
271         std::string outname{argv[fidx]};
272         size_t lastslash{outname.find_last_of('/')};
273         if(lastslash != std::string::npos)
274             outname.erase(0, lastslash+1);
275         size_t extpos{outname.find_last_of('.')};
276         if(extpos != std::string::npos)
277             outname.resize(extpos);
278         outname += ".uhj.flac";
279
280         SF_INFO ininfo{};
281         SndFilePtr infile{sf_open(argv[fidx], SFM_READ, &ininfo)};
282         if(!infile)
283         {
284             fprintf(stderr, "Failed to open %s\n", argv[fidx]);
285             continue;
286         }
287         printf("Converting %s to %s...\n", argv[fidx], outname.c_str());
288
289         /* Work out the channel map, preferably using the actual channel map
290          * from the file/format, but falling back to assuming WFX order.
291          */
292         al::span<const SpeakerPos> spkrs;
293         auto chanmap = std::vector<int>(static_cast<uint>(ininfo.channels), SF_CHANNEL_MAP_INVALID);
294         if(sf_command(infile.get(), SFC_GET_CHANNEL_MAP_INFO, chanmap.data(),
295             ininfo.channels*int{sizeof(int)}) == SF_TRUE)
296         {
297             static const std::array<int,2> stereomap{{SF_CHANNEL_MAP_LEFT, SF_CHANNEL_MAP_RIGHT}};
298             static const std::array<int,4> quadmap{{SF_CHANNEL_MAP_LEFT, SF_CHANNEL_MAP_RIGHT,
299                 SF_CHANNEL_MAP_REAR_LEFT, SF_CHANNEL_MAP_REAR_RIGHT}};
300             static const std::array<int,6> x51map{{SF_CHANNEL_MAP_LEFT, SF_CHANNEL_MAP_RIGHT,
301                 SF_CHANNEL_MAP_CENTER, SF_CHANNEL_MAP_LFE,
302                 SF_CHANNEL_MAP_SIDE_LEFT, SF_CHANNEL_MAP_SIDE_RIGHT}};
303             static const std::array<int,6> x51rearmap{{SF_CHANNEL_MAP_LEFT, SF_CHANNEL_MAP_RIGHT,
304                 SF_CHANNEL_MAP_CENTER, SF_CHANNEL_MAP_LFE,
305                 SF_CHANNEL_MAP_REAR_LEFT, SF_CHANNEL_MAP_REAR_RIGHT}};
306             static const std::array<int,8> x71map{{SF_CHANNEL_MAP_LEFT, SF_CHANNEL_MAP_RIGHT,
307                 SF_CHANNEL_MAP_CENTER, SF_CHANNEL_MAP_LFE,
308                 SF_CHANNEL_MAP_REAR_LEFT, SF_CHANNEL_MAP_REAR_RIGHT,
309                 SF_CHANNEL_MAP_SIDE_LEFT, SF_CHANNEL_MAP_SIDE_RIGHT}};
310             static const std::array<int,12> x714map{{SF_CHANNEL_MAP_LEFT, SF_CHANNEL_MAP_RIGHT,
311                 SF_CHANNEL_MAP_CENTER, SF_CHANNEL_MAP_LFE,
312                 SF_CHANNEL_MAP_REAR_LEFT, SF_CHANNEL_MAP_REAR_RIGHT,
313                 SF_CHANNEL_MAP_SIDE_LEFT, SF_CHANNEL_MAP_SIDE_RIGHT,
314                 SF_CHANNEL_MAP_TOP_FRONT_LEFT, SF_CHANNEL_MAP_TOP_FRONT_RIGHT,
315                 SF_CHANNEL_MAP_TOP_REAR_LEFT, SF_CHANNEL_MAP_TOP_REAR_RIGHT}};
316             static const std::array<int,3> ambi2dmap{{SF_CHANNEL_MAP_AMBISONIC_B_W,
317                 SF_CHANNEL_MAP_AMBISONIC_B_X, SF_CHANNEL_MAP_AMBISONIC_B_Y}};
318             static const std::array<int,4> ambi3dmap{{SF_CHANNEL_MAP_AMBISONIC_B_W,
319                 SF_CHANNEL_MAP_AMBISONIC_B_X, SF_CHANNEL_MAP_AMBISONIC_B_Y,
320                 SF_CHANNEL_MAP_AMBISONIC_B_Z}};
321
322             auto match_chanmap = [](const al::span<int> a, const al::span<const int> b) -> bool
323             {
324                 if(a.size() != b.size())
325                     return false;
326                 for(const int id : a)
327                 {
328                     if(std::find(b.begin(), b.end(), id) != b.end())
329                         return false;
330                 }
331                 return true;
332             };
333             if(match_chanmap(chanmap, stereomap))
334                 spkrs = StereoMap;
335             else if(match_chanmap(chanmap, quadmap))
336                 spkrs = QuadMap;
337             else if(match_chanmap(chanmap, x51map))
338                 spkrs = X51Map;
339             else if(match_chanmap(chanmap, x51rearmap))
340                 spkrs = X51RearMap;
341             else if(match_chanmap(chanmap, x71map))
342                 spkrs = X71Map;
343             else if(match_chanmap(chanmap, x714map))
344                 spkrs = X714Map;
345             else if(match_chanmap(chanmap, ambi2dmap) || match_chanmap(chanmap, ambi3dmap))
346             {
347                 /* Do nothing. */
348             }
349             else
350             {
351                 std::string mapstr;
352                 if(!chanmap.empty())
353                 {
354                     mapstr = std::to_string(chanmap[0]);
355                     for(int idx : al::span<int>{chanmap}.subspan<1>())
356                     {
357                         mapstr += ',';
358                         mapstr += std::to_string(idx);
359                     }
360                 }
361                 fprintf(stderr, " ... %zu channels not supported (map: %s)\n", chanmap.size(),
362                     mapstr.c_str());
363                 continue;
364             }
365         }
366         else if(ininfo.channels == 2)
367         {
368             fprintf(stderr, " ... assuming WFX order stereo\n");
369             spkrs = StereoMap;
370             chanmap[0] = SF_CHANNEL_MAP_FRONT_LEFT;
371             chanmap[1] = SF_CHANNEL_MAP_FRONT_RIGHT;
372         }
373         else if(ininfo.channels == 6)
374         {
375             fprintf(stderr, " ... assuming WFX order 5.1\n");
376             spkrs = X51Map;
377             chanmap[0] = SF_CHANNEL_MAP_FRONT_LEFT;
378             chanmap[1] = SF_CHANNEL_MAP_FRONT_RIGHT;
379             chanmap[2] = SF_CHANNEL_MAP_FRONT_CENTER;
380             chanmap[3] = SF_CHANNEL_MAP_LFE;
381             chanmap[4] = SF_CHANNEL_MAP_SIDE_LEFT;
382             chanmap[5] = SF_CHANNEL_MAP_SIDE_RIGHT;
383         }
384         else if(ininfo.channels == 8)
385         {
386             fprintf(stderr, " ... assuming WFX order 7.1\n");
387             spkrs = X71Map;
388             chanmap[0] = SF_CHANNEL_MAP_FRONT_LEFT;
389             chanmap[1] = SF_CHANNEL_MAP_FRONT_RIGHT;
390             chanmap[2] = SF_CHANNEL_MAP_FRONT_CENTER;
391             chanmap[3] = SF_CHANNEL_MAP_LFE;
392             chanmap[4] = SF_CHANNEL_MAP_REAR_LEFT;
393             chanmap[5] = SF_CHANNEL_MAP_REAR_RIGHT;
394             chanmap[6] = SF_CHANNEL_MAP_SIDE_LEFT;
395             chanmap[7] = SF_CHANNEL_MAP_SIDE_RIGHT;
396         }
397         else
398         {
399             fprintf(stderr, " ... unmapped %d-channel audio not supported\n", ininfo.channels);
400             continue;
401         }
402
403         SF_INFO outinfo{};
404         outinfo.frames = ininfo.frames;
405         outinfo.samplerate = ininfo.samplerate;
406         outinfo.channels = static_cast<int>(uhjchans);
407         outinfo.format = SF_FORMAT_PCM_24 | SF_FORMAT_FLAC;
408         SndFilePtr outfile{sf_open(outname.c_str(), SFM_WRITE, &outinfo)};
409         if(!outfile)
410         {
411             fprintf(stderr, " ... failed to create %s\n", outname.c_str());
412             continue;
413         }
414
415         auto encoder = std::make_unique<UhjEncoder>();
416         auto splbuf = al::vector<FloatBufferLine, 16>(static_cast<uint>(9+ininfo.channels)+uhjchans);
417         auto ambmem = al::span<FloatBufferLine,4>{splbuf.data(), 4};
418         auto encmem = al::span<FloatBufferLine,4>{&splbuf[4], 4};
419         auto srcmem = al::span<float,BufferLineSize>{splbuf[8].data(), BufferLineSize};
420         auto outmem = al::span<float>{splbuf[9].data(), BufferLineSize*uhjchans};
421
422         /* A number of initial samples need to be skipped to cut the lead-in
423          * from the all-pass filter delay. The same number of samples need to
424          * be fed through the encoder after reaching the end of the input file
425          * to ensure none of the original input is lost.
426          */
427         size_t total_wrote{0};
428         size_t LeadIn{UhjEncoder::sFilterDelay};
429         sf_count_t LeadOut{UhjEncoder::sFilterDelay};
430         while(LeadIn > 0 || LeadOut > 0)
431         {
432             auto inmem = outmem.data() + outmem.size();
433             auto sgot = sf_readf_float(infile.get(), inmem, BufferLineSize);
434
435             sgot = std::max<sf_count_t>(sgot, 0);
436             if(sgot < BufferLineSize)
437             {
438                 const sf_count_t remaining{std::min(BufferLineSize - sgot, LeadOut)};
439                 std::fill_n(inmem + sgot*ininfo.channels, remaining*ininfo.channels, 0.0f);
440                 sgot += remaining;
441                 LeadOut -= remaining;
442             }
443
444             for(auto&& buf : ambmem)
445                 buf.fill(0.0f);
446
447             auto got = static_cast<size_t>(sgot);
448             if(spkrs.empty())
449             {
450                 /* B-Format is already in the correct order. It just needs a
451                  * +3dB boost.
452                  */
453                 static constexpr float scale{al::numbers::sqrt2_v<float>};
454                 const size_t chans{std::min<size_t>(static_cast<uint>(ininfo.channels), 4u)};
455                 for(size_t c{0};c < chans;++c)
456                 {
457                     for(size_t i{0};i < got;++i)
458                         ambmem[c][i] = inmem[i*static_cast<uint>(ininfo.channels)] * scale;
459                     ++inmem;
460                 }
461             }
462             else for(const int chanid : chanmap)
463             {
464                 /* Skip LFE. Or mix directly into W? Or W+X? */
465                 if(chanid == SF_CHANNEL_MAP_LFE)
466                 {
467                     ++inmem;
468                     continue;
469                 }
470
471                 const auto spkr = std::find_if(spkrs.cbegin(), spkrs.cend(),
472                     [chanid](const SpeakerPos &pos){return pos.mChannelID == chanid;});
473                 if(spkr == spkrs.cend())
474                 {
475                     fprintf(stderr, " ... failed to find channel ID %d\n", chanid);
476                     continue;
477                 }
478
479                 for(size_t i{0};i < got;++i)
480                     srcmem[i] = inmem[i * static_cast<uint>(ininfo.channels)];
481                 ++inmem;
482
483                 static constexpr auto Deg2Rad = al::numbers::pi / 180.0;
484                 const auto coeffs = GenCoeffs(
485                     std::cos(spkr->mAzimuth*Deg2Rad) * std::cos(spkr->mElevation*Deg2Rad),
486                     std::sin(spkr->mAzimuth*Deg2Rad) * std::cos(spkr->mElevation*Deg2Rad),
487                     std::sin(spkr->mElevation*Deg2Rad));
488                 for(size_t c{0};c < 4;++c)
489                 {
490                     for(size_t i{0};i < got;++i)
491                         ambmem[c][i] += srcmem[i] * coeffs[c];
492                 }
493             }
494
495             encoder->encode(encmem.subspan(0, uhjchans), ambmem, got);
496             if(LeadIn >= got)
497             {
498                 LeadIn -= got;
499                 continue;
500             }
501
502             got -= LeadIn;
503             for(size_t c{0};c < uhjchans;++c)
504             {
505                 constexpr float max_val{8388607.0f / 8388608.0f};
506                 auto clamp = [](float v, float mn, float mx) noexcept
507                 { return std::min(std::max(v, mn), mx); };
508                 for(size_t i{0};i < got;++i)
509                     outmem[i*uhjchans + c] = clamp(encmem[c][LeadIn+i], -1.0f, max_val);
510             }
511             LeadIn = 0;
512
513             sf_count_t wrote{sf_writef_float(outfile.get(), outmem.data(),
514                 static_cast<sf_count_t>(got))};
515             if(wrote < 0)
516                 fprintf(stderr, " ... failed to write samples: %d\n", sf_error(outfile.get()));
517             else
518                 total_wrote += static_cast<size_t>(wrote);
519         }
520         printf(" ... wrote %zu samples (%" PRId64 ").\n", total_wrote, int64_t{ininfo.frames});
521         ++num_encoded;
522     }
523     if(num_encoded == 0)
524         fprintf(stderr, "Failed to encode any input files\n");
525     else if(num_encoded < num_files)
526         fprintf(stderr, "Encoded %zu of %zu files\n", num_encoded, num_files);
527     else
528         printf("Encoded %s%zu file%s\n", (num_encoded > 1) ? "all " : "", num_encoded,
529             (num_encoded == 1) ? "" : "s");
530     return 0;
531 }