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 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
>;
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
100 * Right = (S - D)/2.0
101 * T = j(-0.1432*W + 0.6512*X) - 0.7071068*Y
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)
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());
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>{{
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"
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"
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"
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());
271 args
= args
.subspan(1);
274 size_t num_files
{0}, num_encoded
{0};
275 auto process_arg
= [&uhjchans
,&num_files
,&num_encoded
](std::string_view arg
) -> void
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";
304 SndFilePtr infile
{sf_open(std::string
{arg
}.c_str(), SFM_READ
, &ininfo
)};
307 fprintf(stderr
, "Failed to open %.*s\n", al::sizei(arg
), arg
.data());
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())
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
))
356 else if(match_chanmap(chanmap
, stereomap
))
358 else if(match_chanmap(chanmap
, quadmap
))
360 else if(match_chanmap(chanmap
, x51map
))
362 else if(match_chanmap(chanmap
, x51rearmap
))
364 else if(match_chanmap(chanmap
, x71map
))
366 else if(match_chanmap(chanmap
, x714map
))
368 else if(match_chanmap(chanmap
, ambi2dmap
) || match_chanmap(chanmap
, ambi3dmap
))
377 mapstr
= std::to_string(chanmap
[0]);
378 for(int idx
: al::span
<int>{chanmap
}.subspan
<1>())
381 mapstr
+= std::to_string(idx
);
384 fprintf(stderr
, " ... %zu channels not supported (map: %s)\n", chanmap
.size(),
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
;
409 fprintf(stderr
, " ... unhandled %d-channel B-Format\n", ininfo
.channels
);
413 else if(ininfo
.channels
== 1)
415 fprintf(stderr
, " ... assuming front-center\n");
417 chanmap
[0] = SF_CHANNEL_MAP_CENTER
;
419 else if(ininfo
.channels
== 2)
421 fprintf(stderr
, " ... assuming WFX order stereo\n");
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");
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");
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
;
452 fprintf(stderr
, " ... unmapped %d-channel audio not supported\n", ininfo
.channels
);
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
)};
464 fprintf(stderr
, " ... failed to create %s\n", outname
.c_str());
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
})
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
);
497 LeadOut
-= remaining
;
500 for(auto&& buf
: ambmem
)
503 auto got
= static_cast<size_t>(sgot
);
506 /* B-Format is already in the correct order. It just needs a
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
)
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
);
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
);
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
);
563 sf_count_t wrote
{sf_writef_float(outfile
.get(), outmem
.data(),
564 static_cast<sf_count_t
>(got
))};
566 fprintf(stderr
, " ... failed to write samples: %d\n", sf_error(outfile
.get()));
568 total_wrote
+= static_cast<size_t>(wrote
);
570 printf(" ... wrote %zu samples (%" PRId64
").\n", total_wrote
, int64_t{ininfo
.frames
});
573 std::for_each(args
.begin(), args
.end(), process_arg
);
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
);
580 printf("Encoded %s%zu file%s\n", (num_encoded
> 1) ? "all " : "", num_encoded
,
581 (num_encoded
== 1) ? "" : "s");
587 int main(int argc
, char **argv
)
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
});