uhjencoder.cpp 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592
  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 <cmath>
  29. #include <cstddef>
  30. #include <cstdio>
  31. #include <memory>
  32. #include <string>
  33. #include <string_view>
  34. #include <vector>
  35. #include "alnumbers.h"
  36. #include "alspan.h"
  37. #include "fmt/core.h"
  38. #include "phase_shifter.h"
  39. #include "vector.h"
  40. #include "sndfile.h"
  41. #include "win_main_utf8.h"
  42. namespace {
  43. using namespace std::string_view_literals;
  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. fmt::println("Usage: {} <[options] infile...>\n\n"
  217. " Options:\n"
  218. " -bhj Encode 2-channel UHJ, aka \"BJH\" (default).\n"
  219. " -thj Encode 3-channel UHJ, aka \"TJH\".\n"
  220. " -phj Encode 4-channel UHJ, aka \"PJH\".\n"
  221. "\n"
  222. "3-channel UHJ supplements 2-channel UHJ with an extra channel that allows full\n"
  223. "reconstruction of first-order 2D ambisonics. 4-channel UHJ supplements 3-channel\n"
  224. "UHJ with an extra channel carrying height information, providing for full\n"
  225. "reconstruction of first-order 3D ambisonics.\n"
  226. "\n"
  227. "Note: The third and fourth channels should be ignored if they're not being\n"
  228. "decoded. Unlike the first two channels, they are not designed for undecoded\n"
  229. "playback, so the resulting files will not play correctly if this isn't handled.",
  230. args[0]);
  231. return 1;
  232. }
  233. args = args.subspan(1);
  234. uint uhjchans{2};
  235. size_t num_files{0}, num_encoded{0};
  236. auto process_arg = [&uhjchans,&num_files,&num_encoded](std::string_view arg) -> void
  237. {
  238. if(arg == "-bhj"sv)
  239. {
  240. uhjchans = 2;
  241. return;
  242. }
  243. if(arg == "-thj"sv)
  244. {
  245. uhjchans = 3;
  246. return;
  247. }
  248. if(arg == "-phj"sv)
  249. {
  250. uhjchans = 4;
  251. return;
  252. }
  253. ++num_files;
  254. auto outname = std::string{arg};
  255. const auto lastslash = outname.rfind('/');
  256. if(lastslash != std::string::npos)
  257. outname.erase(0, lastslash+1);
  258. const auto extpos = outname.rfind('.');
  259. if(extpos != std::string::npos)
  260. outname.resize(extpos);
  261. outname += ".uhj.flac";
  262. SF_INFO ininfo{};
  263. SndFilePtr infile{sf_open(std::string{arg}.c_str(), SFM_READ, &ininfo)};
  264. if(!infile)
  265. {
  266. fmt::println(stderr, "Failed to open {}", arg);
  267. return;
  268. }
  269. fmt::println("Converting {} to {}...", arg, outname);
  270. /* Work out the channel map, preferably using the actual channel map
  271. * from the file/format, but falling back to assuming WFX order.
  272. */
  273. al::span<const SpeakerPos> spkrs;
  274. auto chanmap = std::vector<int>(static_cast<uint>(ininfo.channels), SF_CHANNEL_MAP_INVALID);
  275. if(sf_command(infile.get(), SFC_GET_CHANNEL_MAP_INFO, chanmap.data(),
  276. ininfo.channels*int{sizeof(int)}) == SF_TRUE)
  277. {
  278. static const std::array<int,1> monomap{{SF_CHANNEL_MAP_CENTER}};
  279. static const std::array<int,2> stereomap{{SF_CHANNEL_MAP_LEFT, SF_CHANNEL_MAP_RIGHT}};
  280. static const std::array<int,4> quadmap{{SF_CHANNEL_MAP_LEFT, SF_CHANNEL_MAP_RIGHT,
  281. SF_CHANNEL_MAP_REAR_LEFT, SF_CHANNEL_MAP_REAR_RIGHT}};
  282. static const std::array<int,6> x51map{{SF_CHANNEL_MAP_LEFT, SF_CHANNEL_MAP_RIGHT,
  283. SF_CHANNEL_MAP_CENTER, SF_CHANNEL_MAP_LFE,
  284. SF_CHANNEL_MAP_SIDE_LEFT, SF_CHANNEL_MAP_SIDE_RIGHT}};
  285. static const std::array<int,6> x51rearmap{{SF_CHANNEL_MAP_LEFT, SF_CHANNEL_MAP_RIGHT,
  286. SF_CHANNEL_MAP_CENTER, SF_CHANNEL_MAP_LFE,
  287. SF_CHANNEL_MAP_REAR_LEFT, SF_CHANNEL_MAP_REAR_RIGHT}};
  288. static const std::array<int,8> x71map{{SF_CHANNEL_MAP_LEFT, SF_CHANNEL_MAP_RIGHT,
  289. SF_CHANNEL_MAP_CENTER, SF_CHANNEL_MAP_LFE,
  290. SF_CHANNEL_MAP_REAR_LEFT, SF_CHANNEL_MAP_REAR_RIGHT,
  291. SF_CHANNEL_MAP_SIDE_LEFT, SF_CHANNEL_MAP_SIDE_RIGHT}};
  292. static const std::array<int,12> x714map{{SF_CHANNEL_MAP_LEFT, SF_CHANNEL_MAP_RIGHT,
  293. SF_CHANNEL_MAP_CENTER, SF_CHANNEL_MAP_LFE,
  294. SF_CHANNEL_MAP_REAR_LEFT, SF_CHANNEL_MAP_REAR_RIGHT,
  295. SF_CHANNEL_MAP_SIDE_LEFT, SF_CHANNEL_MAP_SIDE_RIGHT,
  296. SF_CHANNEL_MAP_TOP_FRONT_LEFT, SF_CHANNEL_MAP_TOP_FRONT_RIGHT,
  297. SF_CHANNEL_MAP_TOP_REAR_LEFT, SF_CHANNEL_MAP_TOP_REAR_RIGHT}};
  298. static const std::array<int,3> ambi2dmap{{SF_CHANNEL_MAP_AMBISONIC_B_W,
  299. SF_CHANNEL_MAP_AMBISONIC_B_X, SF_CHANNEL_MAP_AMBISONIC_B_Y}};
  300. static const std::array<int,4> ambi3dmap{{SF_CHANNEL_MAP_AMBISONIC_B_W,
  301. SF_CHANNEL_MAP_AMBISONIC_B_X, SF_CHANNEL_MAP_AMBISONIC_B_Y,
  302. SF_CHANNEL_MAP_AMBISONIC_B_Z}};
  303. auto match_chanmap = [](const al::span<int> a, const al::span<const int> b) -> bool
  304. {
  305. if(a.size() != b.size())
  306. return false;
  307. auto find_channel = [b](const int id) -> bool
  308. { return std::find(b.begin(), b.end(), id) != b.end(); };
  309. return std::all_of(a.cbegin(), a.cend(), find_channel);
  310. };
  311. if(match_chanmap(chanmap, monomap))
  312. spkrs = MonoMap;
  313. else if(match_chanmap(chanmap, stereomap))
  314. spkrs = StereoMap;
  315. else if(match_chanmap(chanmap, quadmap))
  316. spkrs = QuadMap;
  317. else if(match_chanmap(chanmap, x51map))
  318. spkrs = X51Map;
  319. else if(match_chanmap(chanmap, x51rearmap))
  320. spkrs = X51RearMap;
  321. else if(match_chanmap(chanmap, x71map))
  322. spkrs = X71Map;
  323. else if(match_chanmap(chanmap, x714map))
  324. spkrs = X714Map;
  325. else if(match_chanmap(chanmap, ambi2dmap) || match_chanmap(chanmap, ambi3dmap))
  326. {
  327. /* Do nothing. */
  328. }
  329. else
  330. {
  331. std::string mapstr;
  332. if(!chanmap.empty())
  333. {
  334. mapstr = std::to_string(chanmap[0]);
  335. for(int idx : al::span<int>{chanmap}.subspan<1>())
  336. {
  337. mapstr += ',';
  338. mapstr += std::to_string(idx);
  339. }
  340. }
  341. fmt::println(stderr, " ... {} channels not supported (map: {})", chanmap.size(),
  342. mapstr);
  343. return;
  344. }
  345. }
  346. else if(sf_command(infile.get(), SFC_WAVEX_GET_AMBISONIC, nullptr,
  347. 0) == SF_AMBISONIC_B_FORMAT)
  348. {
  349. if(ininfo.channels == 4)
  350. {
  351. fmt::println(stderr, " ... detected FuMa 3D B-Format");
  352. chanmap[0] = SF_CHANNEL_MAP_AMBISONIC_B_W;
  353. chanmap[1] = SF_CHANNEL_MAP_AMBISONIC_B_X;
  354. chanmap[2] = SF_CHANNEL_MAP_AMBISONIC_B_Y;
  355. chanmap[3] = SF_CHANNEL_MAP_AMBISONIC_B_Z;
  356. }
  357. else if(ininfo.channels == 3)
  358. {
  359. fmt::println(stderr, " ... detected FuMa 2D B-Format");
  360. chanmap[0] = SF_CHANNEL_MAP_AMBISONIC_B_W;
  361. chanmap[1] = SF_CHANNEL_MAP_AMBISONIC_B_X;
  362. chanmap[2] = SF_CHANNEL_MAP_AMBISONIC_B_Y;
  363. }
  364. else
  365. {
  366. fmt::println(stderr, " ... unhandled {}-channel B-Format", ininfo.channels);
  367. return;
  368. }
  369. }
  370. else if(ininfo.channels == 1)
  371. {
  372. fmt::println(stderr, " ... assuming front-center");
  373. spkrs = MonoMap;
  374. chanmap[0] = SF_CHANNEL_MAP_CENTER;
  375. }
  376. else if(ininfo.channels == 2)
  377. {
  378. fmt::println(stderr, " ... assuming WFX order stereo");
  379. spkrs = StereoMap;
  380. chanmap[0] = SF_CHANNEL_MAP_LEFT;
  381. chanmap[1] = SF_CHANNEL_MAP_RIGHT;
  382. }
  383. else if(ininfo.channels == 6)
  384. {
  385. fmt::println(stderr, " ... assuming WFX order 5.1");
  386. spkrs = X51Map;
  387. chanmap[0] = SF_CHANNEL_MAP_LEFT;
  388. chanmap[1] = SF_CHANNEL_MAP_RIGHT;
  389. chanmap[2] = SF_CHANNEL_MAP_CENTER;
  390. chanmap[3] = SF_CHANNEL_MAP_LFE;
  391. chanmap[4] = SF_CHANNEL_MAP_SIDE_LEFT;
  392. chanmap[5] = SF_CHANNEL_MAP_SIDE_RIGHT;
  393. }
  394. else if(ininfo.channels == 8)
  395. {
  396. fmt::println(stderr, " ... assuming WFX order 7.1");
  397. spkrs = X71Map;
  398. chanmap[0] = SF_CHANNEL_MAP_LEFT;
  399. chanmap[1] = SF_CHANNEL_MAP_RIGHT;
  400. chanmap[2] = SF_CHANNEL_MAP_CENTER;
  401. chanmap[3] = SF_CHANNEL_MAP_LFE;
  402. chanmap[4] = SF_CHANNEL_MAP_REAR_LEFT;
  403. chanmap[5] = SF_CHANNEL_MAP_REAR_RIGHT;
  404. chanmap[6] = SF_CHANNEL_MAP_SIDE_LEFT;
  405. chanmap[7] = SF_CHANNEL_MAP_SIDE_RIGHT;
  406. }
  407. else
  408. {
  409. fmt::println(stderr, " ... unmapped {}-channel audio not supported", ininfo.channels);
  410. return;
  411. }
  412. SF_INFO outinfo{};
  413. outinfo.frames = ininfo.frames;
  414. outinfo.samplerate = ininfo.samplerate;
  415. outinfo.channels = static_cast<int>(uhjchans);
  416. outinfo.format = SF_FORMAT_PCM_24 | SF_FORMAT_FLAC;
  417. SndFilePtr outfile{sf_open(outname.c_str(), SFM_WRITE, &outinfo)};
  418. if(!outfile)
  419. {
  420. fmt::println(stderr, " ... failed to create {}", outname);
  421. return;
  422. }
  423. auto encoder = std::make_unique<UhjEncoder>();
  424. auto splbuf = al::vector<FloatBufferLine, 16>(9);
  425. auto ambmem = al::span{splbuf}.subspan<0,4>();
  426. auto encmem = al::span{splbuf}.subspan<4,4>();
  427. auto srcmem = al::span{splbuf[8]};
  428. auto membuf = al::vector<float,16>((static_cast<uint>(ininfo.channels)+size_t{uhjchans})
  429. * BufferLineSize);
  430. auto outmem = al::span{membuf}.first(size_t{BufferLineSize}*uhjchans);
  431. auto inmem = al::span{membuf}.last(size_t{BufferLineSize}
  432. * static_cast<uint>(ininfo.channels));
  433. /* A number of initial samples need to be skipped to cut the lead-in
  434. * from the all-pass filter delay. The same number of samples need to
  435. * be fed through the encoder after reaching the end of the input file
  436. * to ensure none of the original input is lost.
  437. */
  438. size_t total_wrote{0};
  439. size_t LeadIn{UhjEncoder::sFilterDelay};
  440. sf_count_t LeadOut{UhjEncoder::sFilterDelay};
  441. while(LeadIn > 0 || LeadOut > 0)
  442. {
  443. auto sgot = sf_readf_float(infile.get(), inmem.data(), BufferLineSize);
  444. sgot = std::max<sf_count_t>(sgot, 0);
  445. if(sgot < BufferLineSize)
  446. {
  447. const sf_count_t remaining{std::min(BufferLineSize - sgot, LeadOut)};
  448. std::fill_n(inmem.begin() + sgot*ininfo.channels, remaining*ininfo.channels, 0.0f);
  449. sgot += remaining;
  450. LeadOut -= remaining;
  451. }
  452. for(auto&& buf : ambmem)
  453. buf.fill(0.0f);
  454. auto got = static_cast<size_t>(sgot);
  455. if(spkrs.empty())
  456. {
  457. /* B-Format is already in the correct order. It just needs a
  458. * +3dB boost.
  459. */
  460. static constexpr float scale{al::numbers::sqrt2_v<float>};
  461. const size_t chans{std::min<size_t>(static_cast<uint>(ininfo.channels), 4u)};
  462. for(size_t c{0};c < chans;++c)
  463. {
  464. for(size_t i{0};i < got;++i)
  465. ambmem[c][i] = inmem[i*static_cast<uint>(ininfo.channels) + c] * scale;
  466. }
  467. }
  468. else for(size_t idx{0};idx < chanmap.size();++idx)
  469. {
  470. const int chanid{chanmap[idx]};
  471. /* Skip LFE. Or mix directly into W? Or W+X? */
  472. if(chanid == SF_CHANNEL_MAP_LFE)
  473. continue;
  474. const auto spkr = std::find_if(spkrs.cbegin(), spkrs.cend(),
  475. [chanid](const SpeakerPos pos){return pos.mChannelID == chanid;});
  476. if(spkr == spkrs.cend())
  477. {
  478. fmt::println(stderr, " ... failed to find channel ID {}", chanid);
  479. continue;
  480. }
  481. for(size_t i{0};i < got;++i)
  482. srcmem[i] = inmem[i*static_cast<uint>(ininfo.channels) + idx];
  483. static constexpr auto Deg2Rad = al::numbers::pi / 180.0;
  484. const auto coeffs = GenCoeffs(
  485. std::cos(spkr->mAzimuth*Deg2Rad) * std::cos(spkr->mElevation*Deg2Rad),
  486. std::sin(spkr->mAzimuth*Deg2Rad) * std::cos(spkr->mElevation*Deg2Rad),
  487. std::sin(spkr->mElevation*Deg2Rad));
  488. for(size_t c{0};c < 4;++c)
  489. {
  490. for(size_t i{0};i < got;++i)
  491. ambmem[c][i] += srcmem[i] * coeffs[c];
  492. }
  493. }
  494. encoder->encode(encmem.subspan(0, uhjchans), ambmem, got);
  495. if(LeadIn >= got)
  496. {
  497. LeadIn -= got;
  498. continue;
  499. }
  500. got -= LeadIn;
  501. for(size_t c{0};c < uhjchans;++c)
  502. {
  503. static constexpr float max_val{8388607.0f / 8388608.0f};
  504. for(size_t i{0};i < got;++i)
  505. outmem[i*uhjchans + c] = std::clamp(encmem[c][LeadIn+i], -1.0f, max_val);
  506. }
  507. LeadIn = 0;
  508. sf_count_t wrote{sf_writef_float(outfile.get(), outmem.data(),
  509. static_cast<sf_count_t>(got))};
  510. if(wrote < 0)
  511. fmt::println(stderr, " ... failed to write samples: {}", sf_error(outfile.get()));
  512. else
  513. total_wrote += static_cast<size_t>(wrote);
  514. }
  515. fmt::println(" ... wrote {} samples ({}).", total_wrote, ininfo.frames);
  516. ++num_encoded;
  517. };
  518. std::for_each(args.begin(), args.end(), process_arg);
  519. if(num_encoded == 0)
  520. fmt::println(stderr, "Failed to encode any input files");
  521. else if(num_encoded < num_files)
  522. fmt::println(stderr, "Encoded {} of {} files", num_encoded, num_files);
  523. else
  524. fmt::println("Encoded {}{} file{}", (num_encoded > 1) ? "all " : "", num_encoded,
  525. (num_encoded == 1) ? "" : "s");
  526. return 0;
  527. }
  528. } /* namespace */
  529. int main(int argc, char **argv)
  530. {
  531. assert(argc >= 0);
  532. auto args = std::vector<std::string_view>(static_cast<unsigned int>(argc));
  533. std::copy_n(argv, args.size(), args.begin());
  534. return main(al::span{args});
  535. }