Properly check for empty paths
[openal-soft.git] / utils / uhjencoder.cpp
blob118ae328973107259e163015e793e65f5ddd73cc
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 struct SndFileDeleter {
53 void operator()(SNDFILE *sndfile) { sf_close(sndfile); }
55 using SndFilePtr = std::unique_ptr<SNDFILE,SndFileDeleter>;
58 using uint = unsigned int;
60 constexpr uint BufferLineSize{1024};
62 using FloatBufferLine = std::array<float,BufferLineSize>;
63 using FloatBufferSpan = al::span<float,BufferLineSize>;
66 struct UhjEncoder {
67 constexpr static size_t sFilterDelay{1024};
69 /* Delays and processing storage for the unfiltered signal. */
70 alignas(16) std::array<float,BufferLineSize+sFilterDelay> mW{};
71 alignas(16) std::array<float,BufferLineSize+sFilterDelay> mX{};
72 alignas(16) std::array<float,BufferLineSize+sFilterDelay> mY{};
73 alignas(16) std::array<float,BufferLineSize+sFilterDelay> mZ{};
75 alignas(16) std::array<float,BufferLineSize> mS{};
76 alignas(16) std::array<float,BufferLineSize> mD{};
77 alignas(16) std::array<float,BufferLineSize> mT{};
79 /* History for the FIR filter. */
80 alignas(16) std::array<float,sFilterDelay*2 - 1> mWXHistory1{};
81 alignas(16) std::array<float,sFilterDelay*2 - 1> mWXHistory2{};
83 alignas(16) std::array<float,BufferLineSize + sFilterDelay*2> mTemp{};
85 void encode(const al::span<FloatBufferLine> OutSamples,
86 const al::span<const FloatBufferLine,4> InSamples, const size_t SamplesToDo);
89 const PhaseShifterT<UhjEncoder::sFilterDelay*2> PShift{};
92 /* Encoding UHJ from B-Format is done as:
94 * S = 0.9396926*W + 0.1855740*X
95 * D = j(-0.3420201*W + 0.5098604*X) + 0.6554516*Y
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
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.
105 void UhjEncoder::encode(const al::span<FloatBufferLine> OutSamples,
106 const al::span<const FloatBufferLine,4> InSamples, const size_t SamplesToDo)
108 const auto winput = al::span{InSamples[0]}.first(SamplesToDo);
109 const auto xinput = al::span{InSamples[1]}.first(SamplesToDo);
110 const auto yinput = al::span{InSamples[2]}.first(SamplesToDo);
111 const auto zinput = al::span{InSamples[3]}.first(SamplesToDo);
113 /* Combine the previously delayed input signal with the new input. */
114 std::copy(winput.begin(), winput.end(), mW.begin()+sFilterDelay);
115 std::copy(xinput.begin(), xinput.end(), mX.begin()+sFilterDelay);
116 std::copy(yinput.begin(), yinput.end(), mY.begin()+sFilterDelay);
117 std::copy(zinput.begin(), zinput.end(), mZ.begin()+sFilterDelay);
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];
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.begin(), winput.end(), xinput.begin(), 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(al::span{mD}.first(SamplesToDo), mTemp);
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];
135 /* Left = (S + D)/2.0 */
136 auto left = al::span{OutSamples[0]};
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 auto right = al::span{OutSamples[1]};
141 for(size_t i{0};i < SamplesToDo;i++)
142 right[i] = (mS[i] - mD[i]) * 0.5f;
144 if(OutSamples.size() > 2)
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.begin(), winput.end(), xinput.begin(), 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(al::span{mT}.first(SamplesToDo), mTemp);
154 /* T = j(-0.1432*W + 0.6512*X) - 0.7071068*Y */
155 auto t = al::span{OutSamples[2]};
156 for(size_t i{0};i < SamplesToDo;i++)
157 t[i] = mT[i] - 0.7071068f*mY[i];
159 if(OutSamples.size() > 3)
161 /* Q = 0.9772*Z */
162 auto q = al::span{OutSamples[3]};
163 for(size_t i{0};i < SamplesToDo;i++)
164 q[i] = 0.9772f*mZ[i];
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());
175 struct SpeakerPos {
176 int mChannelID;
177 float mAzimuth;
178 float mElevation;
181 /* Azimuth is counter-clockwise. */
182 constexpr std::array MonoMap{
183 SpeakerPos{SF_CHANNEL_MAP_CENTER, 0.0f, 0.0f},
185 constexpr std::array StereoMap{
186 SpeakerPos{SF_CHANNEL_MAP_LEFT, 30.0f, 0.0f},
187 SpeakerPos{SF_CHANNEL_MAP_RIGHT, -30.0f, 0.0f},
189 constexpr std::array QuadMap{
190 SpeakerPos{SF_CHANNEL_MAP_LEFT, 45.0f, 0.0f},
191 SpeakerPos{SF_CHANNEL_MAP_RIGHT, -45.0f, 0.0f},
192 SpeakerPos{SF_CHANNEL_MAP_REAR_LEFT, 135.0f, 0.0f},
193 SpeakerPos{SF_CHANNEL_MAP_REAR_RIGHT, -135.0f, 0.0f},
195 constexpr std::array X51Map{
196 SpeakerPos{SF_CHANNEL_MAP_LEFT, 30.0f, 0.0f},
197 SpeakerPos{SF_CHANNEL_MAP_RIGHT, -30.0f, 0.0f},
198 SpeakerPos{SF_CHANNEL_MAP_CENTER, 0.0f, 0.0f},
199 SpeakerPos{SF_CHANNEL_MAP_LFE, 0.0f, 0.0f},
200 SpeakerPos{SF_CHANNEL_MAP_SIDE_LEFT, 110.0f, 0.0f},
201 SpeakerPos{SF_CHANNEL_MAP_SIDE_RIGHT, -110.0f, 0.0f},
203 constexpr std::array X51RearMap{
204 SpeakerPos{SF_CHANNEL_MAP_LEFT, 30.0f, 0.0f},
205 SpeakerPos{SF_CHANNEL_MAP_RIGHT, -30.0f, 0.0f},
206 SpeakerPos{SF_CHANNEL_MAP_CENTER, 0.0f, 0.0f},
207 SpeakerPos{SF_CHANNEL_MAP_LFE, 0.0f, 0.0f},
208 SpeakerPos{SF_CHANNEL_MAP_REAR_LEFT, 110.0f, 0.0f},
209 SpeakerPos{SF_CHANNEL_MAP_REAR_RIGHT, -110.0f, 0.0f},
211 constexpr std::array X71Map{
212 SpeakerPos{SF_CHANNEL_MAP_LEFT, 30.0f, 0.0f},
213 SpeakerPos{SF_CHANNEL_MAP_RIGHT, -30.0f, 0.0f},
214 SpeakerPos{SF_CHANNEL_MAP_CENTER, 0.0f, 0.0f},
215 SpeakerPos{SF_CHANNEL_MAP_LFE, 0.0f, 0.0f},
216 SpeakerPos{SF_CHANNEL_MAP_REAR_LEFT, 150.0f, 0.0f},
217 SpeakerPos{SF_CHANNEL_MAP_REAR_RIGHT, -150.0f, 0.0f},
218 SpeakerPos{SF_CHANNEL_MAP_SIDE_LEFT, 90.0f, 0.0f},
219 SpeakerPos{SF_CHANNEL_MAP_SIDE_RIGHT, -90.0f, 0.0f},
221 constexpr std::array X714Map{
222 SpeakerPos{SF_CHANNEL_MAP_LEFT, 30.0f, 0.0f},
223 SpeakerPos{SF_CHANNEL_MAP_RIGHT, -30.0f, 0.0f},
224 SpeakerPos{SF_CHANNEL_MAP_CENTER, 0.0f, 0.0f},
225 SpeakerPos{SF_CHANNEL_MAP_LFE, 0.0f, 0.0f},
226 SpeakerPos{SF_CHANNEL_MAP_REAR_LEFT, 150.0f, 0.0f},
227 SpeakerPos{SF_CHANNEL_MAP_REAR_RIGHT, -150.0f, 0.0f},
228 SpeakerPos{SF_CHANNEL_MAP_SIDE_LEFT, 90.0f, 0.0f},
229 SpeakerPos{SF_CHANNEL_MAP_SIDE_RIGHT, -90.0f, 0.0f},
230 SpeakerPos{SF_CHANNEL_MAP_TOP_FRONT_LEFT, 45.0f, 35.0f},
231 SpeakerPos{SF_CHANNEL_MAP_TOP_FRONT_RIGHT, -45.0f, 35.0f},
232 SpeakerPos{SF_CHANNEL_MAP_TOP_REAR_LEFT, 135.0f, 35.0f},
233 SpeakerPos{SF_CHANNEL_MAP_TOP_REAR_RIGHT, -135.0f, 35.0f},
236 constexpr auto GenCoeffs(double x /*+front*/, double y /*+left*/, double z /*+up*/) noexcept
238 /* Coefficients are +3dB of FuMa. */
239 return std::array<float,4>{{
240 1.0f,
241 static_cast<float>(al::numbers::sqrt2 * x),
242 static_cast<float>(al::numbers::sqrt2 * y),
243 static_cast<float>(al::numbers::sqrt2 * z)
248 int main(al::span<std::string_view> args)
250 if(args.size() < 2 || args[1] == "-h" || args[1] == "--help")
252 printf("Usage: %.*s <infile...>\n\n", al::sizei(args[0]), args[0].data());
253 return 1;
256 uint uhjchans{2};
257 size_t num_files{0}, num_encoded{0};
258 for(size_t fidx{1};fidx < args.size();++fidx)
260 if(args[fidx] == "-bhj")
262 uhjchans = 2;
263 continue;
265 if(args[fidx] == "-thj")
267 uhjchans = 3;
268 continue;
270 if(args[fidx] == "-phj")
272 uhjchans = 4;
273 continue;
275 ++num_files;
277 std::string outname{args[fidx]};
278 size_t lastslash{outname.find_last_of('/')};
279 if(lastslash != std::string::npos)
280 outname.erase(0, lastslash+1);
281 size_t extpos{outname.find_last_of('.')};
282 if(extpos != std::string::npos)
283 outname.resize(extpos);
284 outname += ".uhj.flac";
286 SF_INFO ininfo{};
287 SndFilePtr infile{sf_open(std::string{args[fidx]}.c_str(), SFM_READ, &ininfo)};
288 if(!infile)
290 fprintf(stderr, "Failed to open %.*s\n", al::sizei(args[fidx]), args[fidx].data());
291 continue;
293 printf("Converting %.*s to %s...\n", al::sizei(args[fidx]), args[fidx].data(),
294 outname.c_str());
296 /* Work out the channel map, preferably using the actual channel map
297 * from the file/format, but falling back to assuming WFX order.
299 al::span<const SpeakerPos> spkrs;
300 auto chanmap = std::vector<int>(static_cast<uint>(ininfo.channels), SF_CHANNEL_MAP_INVALID);
301 if(sf_command(infile.get(), SFC_GET_CHANNEL_MAP_INFO, chanmap.data(),
302 ininfo.channels*int{sizeof(int)}) == SF_TRUE)
304 static const std::array<int,1> monomap{{SF_CHANNEL_MAP_CENTER}};
305 static const std::array<int,2> stereomap{{SF_CHANNEL_MAP_LEFT, SF_CHANNEL_MAP_RIGHT}};
306 static const std::array<int,4> quadmap{{SF_CHANNEL_MAP_LEFT, SF_CHANNEL_MAP_RIGHT,
307 SF_CHANNEL_MAP_REAR_LEFT, SF_CHANNEL_MAP_REAR_RIGHT}};
308 static const std::array<int,6> x51map{{SF_CHANNEL_MAP_LEFT, SF_CHANNEL_MAP_RIGHT,
309 SF_CHANNEL_MAP_CENTER, SF_CHANNEL_MAP_LFE,
310 SF_CHANNEL_MAP_SIDE_LEFT, SF_CHANNEL_MAP_SIDE_RIGHT}};
311 static const std::array<int,6> x51rearmap{{SF_CHANNEL_MAP_LEFT, SF_CHANNEL_MAP_RIGHT,
312 SF_CHANNEL_MAP_CENTER, SF_CHANNEL_MAP_LFE,
313 SF_CHANNEL_MAP_REAR_LEFT, SF_CHANNEL_MAP_REAR_RIGHT}};
314 static const std::array<int,8> x71map{{SF_CHANNEL_MAP_LEFT, SF_CHANNEL_MAP_RIGHT,
315 SF_CHANNEL_MAP_CENTER, SF_CHANNEL_MAP_LFE,
316 SF_CHANNEL_MAP_REAR_LEFT, SF_CHANNEL_MAP_REAR_RIGHT,
317 SF_CHANNEL_MAP_SIDE_LEFT, SF_CHANNEL_MAP_SIDE_RIGHT}};
318 static const std::array<int,12> x714map{{SF_CHANNEL_MAP_LEFT, SF_CHANNEL_MAP_RIGHT,
319 SF_CHANNEL_MAP_CENTER, SF_CHANNEL_MAP_LFE,
320 SF_CHANNEL_MAP_REAR_LEFT, SF_CHANNEL_MAP_REAR_RIGHT,
321 SF_CHANNEL_MAP_SIDE_LEFT, SF_CHANNEL_MAP_SIDE_RIGHT,
322 SF_CHANNEL_MAP_TOP_FRONT_LEFT, SF_CHANNEL_MAP_TOP_FRONT_RIGHT,
323 SF_CHANNEL_MAP_TOP_REAR_LEFT, SF_CHANNEL_MAP_TOP_REAR_RIGHT}};
324 static const std::array<int,3> ambi2dmap{{SF_CHANNEL_MAP_AMBISONIC_B_W,
325 SF_CHANNEL_MAP_AMBISONIC_B_X, SF_CHANNEL_MAP_AMBISONIC_B_Y}};
326 static const std::array<int,4> ambi3dmap{{SF_CHANNEL_MAP_AMBISONIC_B_W,
327 SF_CHANNEL_MAP_AMBISONIC_B_X, SF_CHANNEL_MAP_AMBISONIC_B_Y,
328 SF_CHANNEL_MAP_AMBISONIC_B_Z}};
330 auto match_chanmap = [](const al::span<int> a, const al::span<const int> b) -> bool
332 if(a.size() != b.size())
333 return false;
334 auto find_channel = [b](const int id) -> bool
335 { return std::find(b.begin(), b.end(), id) != b.end(); };
336 return std::all_of(a.cbegin(), a.cend(), find_channel);
338 if(match_chanmap(chanmap, monomap))
339 spkrs = MonoMap;
340 else if(match_chanmap(chanmap, stereomap))
341 spkrs = StereoMap;
342 else if(match_chanmap(chanmap, quadmap))
343 spkrs = QuadMap;
344 else if(match_chanmap(chanmap, x51map))
345 spkrs = X51Map;
346 else if(match_chanmap(chanmap, x51rearmap))
347 spkrs = X51RearMap;
348 else if(match_chanmap(chanmap, x71map))
349 spkrs = X71Map;
350 else if(match_chanmap(chanmap, x714map))
351 spkrs = X714Map;
352 else if(match_chanmap(chanmap, ambi2dmap) || match_chanmap(chanmap, ambi3dmap))
354 /* Do nothing. */
356 else
358 std::string mapstr;
359 if(!chanmap.empty())
361 mapstr = std::to_string(chanmap[0]);
362 for(int idx : al::span<int>{chanmap}.subspan<1>())
364 mapstr += ',';
365 mapstr += std::to_string(idx);
368 fprintf(stderr, " ... %zu channels not supported (map: %s)\n", chanmap.size(),
369 mapstr.c_str());
370 continue;
373 else if(ininfo.channels == 1)
375 fprintf(stderr, " ... assuming front-center\n");
376 spkrs = MonoMap;
377 chanmap[0] = SF_CHANNEL_MAP_CENTER;
379 else if(ininfo.channels == 2)
381 fprintf(stderr, " ... assuming WFX order stereo\n");
382 spkrs = StereoMap;
383 chanmap[0] = SF_CHANNEL_MAP_LEFT;
384 chanmap[1] = SF_CHANNEL_MAP_RIGHT;
386 else if(ininfo.channels == 6)
388 fprintf(stderr, " ... assuming WFX order 5.1\n");
389 spkrs = X51Map;
390 chanmap[0] = SF_CHANNEL_MAP_LEFT;
391 chanmap[1] = SF_CHANNEL_MAP_RIGHT;
392 chanmap[2] = SF_CHANNEL_MAP_CENTER;
393 chanmap[3] = SF_CHANNEL_MAP_LFE;
394 chanmap[4] = SF_CHANNEL_MAP_SIDE_LEFT;
395 chanmap[5] = SF_CHANNEL_MAP_SIDE_RIGHT;
397 else if(ininfo.channels == 8)
399 fprintf(stderr, " ... assuming WFX order 7.1\n");
400 spkrs = X71Map;
401 chanmap[0] = SF_CHANNEL_MAP_LEFT;
402 chanmap[1] = SF_CHANNEL_MAP_RIGHT;
403 chanmap[2] = SF_CHANNEL_MAP_CENTER;
404 chanmap[3] = SF_CHANNEL_MAP_LFE;
405 chanmap[4] = SF_CHANNEL_MAP_REAR_LEFT;
406 chanmap[5] = SF_CHANNEL_MAP_REAR_RIGHT;
407 chanmap[6] = SF_CHANNEL_MAP_SIDE_LEFT;
408 chanmap[7] = SF_CHANNEL_MAP_SIDE_RIGHT;
410 else
412 fprintf(stderr, " ... unmapped %d-channel audio not supported\n", ininfo.channels);
413 continue;
416 SF_INFO outinfo{};
417 outinfo.frames = ininfo.frames;
418 outinfo.samplerate = ininfo.samplerate;
419 outinfo.channels = static_cast<int>(uhjchans);
420 outinfo.format = SF_FORMAT_PCM_24 | SF_FORMAT_FLAC;
421 SndFilePtr outfile{sf_open(outname.c_str(), SFM_WRITE, &outinfo)};
422 if(!outfile)
424 fprintf(stderr, " ... failed to create %s\n", outname.c_str());
425 continue;
428 auto encoder = std::make_unique<UhjEncoder>();
429 auto splbuf = al::vector<FloatBufferLine, 16>(9);
430 auto ambmem = al::span{splbuf}.subspan<0,4>();
431 auto encmem = al::span{splbuf}.subspan<4,4>();
432 auto srcmem = al::span{splbuf[8]};
433 auto membuf = al::vector<float,16>((static_cast<uint>(ininfo.channels)+size_t{uhjchans})
434 * BufferLineSize);
435 auto outmem = al::span{membuf}.first(size_t{BufferLineSize}*uhjchans);
436 auto inmem = al::span{membuf}.last(size_t{BufferLineSize}
437 * static_cast<uint>(ininfo.channels));
439 /* A number of initial samples need to be skipped to cut the lead-in
440 * from the all-pass filter delay. The same number of samples need to
441 * be fed through the encoder after reaching the end of the input file
442 * to ensure none of the original input is lost.
444 size_t total_wrote{0};
445 size_t LeadIn{UhjEncoder::sFilterDelay};
446 sf_count_t LeadOut{UhjEncoder::sFilterDelay};
447 while(LeadIn > 0 || LeadOut > 0)
449 auto sgot = sf_readf_float(infile.get(), inmem.data(), BufferLineSize);
451 sgot = std::max<sf_count_t>(sgot, 0);
452 if(sgot < BufferLineSize)
454 const sf_count_t remaining{std::min(BufferLineSize - sgot, LeadOut)};
455 std::fill_n(inmem.begin() + sgot*ininfo.channels, remaining*ininfo.channels, 0.0f);
456 sgot += remaining;
457 LeadOut -= remaining;
460 for(auto&& buf : ambmem)
461 buf.fill(0.0f);
463 auto got = static_cast<size_t>(sgot);
464 if(spkrs.empty())
466 /* B-Format is already in the correct order. It just needs a
467 * +3dB boost.
469 static constexpr float scale{al::numbers::sqrt2_v<float>};
470 const size_t chans{std::min<size_t>(static_cast<uint>(ininfo.channels), 4u)};
471 for(size_t c{0};c < chans;++c)
473 for(size_t i{0};i < got;++i)
474 ambmem[c][i] = inmem[i*static_cast<uint>(ininfo.channels) + c] * scale;
477 else for(size_t idx{0};idx < chanmap.size();++idx)
479 const int chanid{chanmap[idx]};
480 /* Skip LFE. Or mix directly into W? Or W+X? */
481 if(chanid == SF_CHANNEL_MAP_LFE)
482 continue;
484 const auto spkr = std::find_if(spkrs.cbegin(), spkrs.cend(),
485 [chanid](const SpeakerPos &pos){return pos.mChannelID == chanid;});
486 if(spkr == spkrs.cend())
488 fprintf(stderr, " ... failed to find channel ID %d\n", chanid);
489 continue;
492 for(size_t i{0};i < got;++i)
493 srcmem[i] = inmem[i*static_cast<uint>(ininfo.channels) + idx];
495 static constexpr auto Deg2Rad = al::numbers::pi / 180.0;
496 const auto coeffs = GenCoeffs(
497 std::cos(spkr->mAzimuth*Deg2Rad) * std::cos(spkr->mElevation*Deg2Rad),
498 std::sin(spkr->mAzimuth*Deg2Rad) * std::cos(spkr->mElevation*Deg2Rad),
499 std::sin(spkr->mElevation*Deg2Rad));
500 for(size_t c{0};c < 4;++c)
502 for(size_t i{0};i < got;++i)
503 ambmem[c][i] += srcmem[i] * coeffs[c];
507 encoder->encode(encmem.subspan(0, uhjchans), ambmem, got);
508 if(LeadIn >= got)
510 LeadIn -= got;
511 continue;
514 got -= LeadIn;
515 for(size_t c{0};c < uhjchans;++c)
517 static constexpr float max_val{8388607.0f / 8388608.0f};
518 for(size_t i{0};i < got;++i)
519 outmem[i*uhjchans + c] = std::clamp(encmem[c][LeadIn+i], -1.0f, max_val);
521 LeadIn = 0;
523 sf_count_t wrote{sf_writef_float(outfile.get(), outmem.data(),
524 static_cast<sf_count_t>(got))};
525 if(wrote < 0)
526 fprintf(stderr, " ... failed to write samples: %d\n", sf_error(outfile.get()));
527 else
528 total_wrote += static_cast<size_t>(wrote);
530 printf(" ... wrote %zu samples (%" PRId64 ").\n", total_wrote, int64_t{ininfo.frames});
531 ++num_encoded;
533 if(num_encoded == 0)
534 fprintf(stderr, "Failed to encode any input files\n");
535 else if(num_encoded < num_files)
536 fprintf(stderr, "Encoded %zu of %zu files\n", num_encoded, num_files);
537 else
538 printf("Encoded %s%zu file%s\n", (num_encoded > 1) ? "all " : "", num_encoded,
539 (num_encoded == 1) ? "" : "s");
540 return 0;
543 } /* namespace */
545 int main(int argc, char *argv[])
547 assert(argc >= 0);
548 auto args = std::vector<std::string_view>(static_cast<unsigned int>(argc));
549 std::copy_n(argv, args.size(), args.begin());
550 return main(al::span{args});