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 <string_view>
39 #include "alnumbers.h"
42 #include "phase_shifter.h"
47 #include "win_main_utf8.h"
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
>;
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
99 * T = j(-0.1432*W + 0.6512*X) - 0.7071068*Y
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)
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());
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>{{
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());
257 size_t num_files
{0}, num_encoded
{0};
258 for(size_t fidx
{1};fidx
< args
.size();++fidx
)
260 if(args
[fidx
] == "-bhj")
265 if(args
[fidx
] == "-thj")
270 if(args
[fidx
] == "-phj")
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";
287 SndFilePtr infile
{sf_open(std::string
{args
[fidx
]}.c_str(), SFM_READ
, &ininfo
)};
290 fprintf(stderr
, "Failed to open %.*s\n", al::sizei(args
[fidx
]), args
[fidx
].data());
293 printf("Converting %.*s to %s...\n", al::sizei(args
[fidx
]), args
[fidx
].data(),
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())
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
))
340 else if(match_chanmap(chanmap
, stereomap
))
342 else if(match_chanmap(chanmap
, quadmap
))
344 else if(match_chanmap(chanmap
, x51map
))
346 else if(match_chanmap(chanmap
, x51rearmap
))
348 else if(match_chanmap(chanmap
, x71map
))
350 else if(match_chanmap(chanmap
, x714map
))
352 else if(match_chanmap(chanmap
, ambi2dmap
) || match_chanmap(chanmap
, ambi3dmap
))
361 mapstr
= std::to_string(chanmap
[0]);
362 for(int idx
: al::span
<int>{chanmap
}.subspan
<1>())
365 mapstr
+= std::to_string(idx
);
368 fprintf(stderr
, " ... %zu channels not supported (map: %s)\n", chanmap
.size(),
373 else if(ininfo
.channels
== 1)
375 fprintf(stderr
, " ... assuming front-center\n");
377 chanmap
[0] = SF_CHANNEL_MAP_CENTER
;
379 else if(ininfo
.channels
== 2)
381 fprintf(stderr
, " ... assuming WFX order stereo\n");
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");
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");
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
;
412 fprintf(stderr
, " ... unmapped %d-channel audio not supported\n", ininfo
.channels
);
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
)};
424 fprintf(stderr
, " ... failed to create %s\n", outname
.c_str());
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
})
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
);
457 LeadOut
-= remaining
;
460 for(auto&& buf
: ambmem
)
463 auto got
= static_cast<size_t>(sgot
);
466 /* B-Format is already in the correct order. It just needs a
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
)
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
);
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
);
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
);
523 sf_count_t wrote
{sf_writef_float(outfile
.get(), outmem
.data(),
524 static_cast<sf_count_t
>(got
))};
526 fprintf(stderr
, " ... failed to write samples: %d\n", sf_error(outfile
.get()));
528 total_wrote
+= static_cast<size_t>(wrote
);
530 printf(" ... wrote %zu samples (%" PRId64
").\n", total_wrote
, int64_t{ininfo
.frames
});
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
);
538 printf("Encoded %s%zu file%s\n", (num_encoded
> 1) ? "all " : "", num_encoded
,
539 (num_encoded
== 1) ? "" : "s");
545 int main(int argc
, char *argv
[])
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
});