uhjencoder.cpp 21 KB

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