uhjencoder.cpp 22 KB

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