Don't handle EFX functions in the router
[openal-soft.git] / utils / uhjencoder.cpp
blobb380ed8653a918ca3c45545609f132d181bdf018
1 /*
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
22 * THE SOFTWARE.
25 #include "config.h"
27 #include <array>
28 #include <cstring>
29 #include <inttypes.h>
30 #include <memory>
31 #include <stddef.h>
32 #include <string>
33 #include <utility>
34 #include <vector>
36 #include "almalloc.h"
37 #include "alnumbers.h"
38 #include "alspan.h"
39 #include "opthelpers.h"
40 #include "phase_shifter.h"
41 #include "vector.h"
43 #include "sndfile.h"
45 #include "win_main_utf8.h"
48 namespace {
50 struct SndFileDeleter {
51 void operator()(SNDFILE *sndfile) { sf_close(sndfile); }
53 using SndFilePtr = std::unique_ptr<SNDFILE,SndFileDeleter>;
56 using uint = unsigned int;
58 constexpr uint BufferLineSize{1024};
60 using FloatBufferLine = std::array<float,BufferLineSize>;
61 using FloatBufferSpan = al::span<float,BufferLineSize>;
64 struct UhjEncoder {
65 constexpr static size_t sFilterDelay{1024};
67 /* Delays and processing storage for the unfiltered signal. */
68 alignas(16) std::array<float,BufferLineSize+sFilterDelay> mS{};
69 alignas(16) std::array<float,BufferLineSize+sFilterDelay> mD{};
70 alignas(16) std::array<float,BufferLineSize+sFilterDelay> mT{};
71 alignas(16) std::array<float,BufferLineSize+sFilterDelay> mQ{};
73 /* History for the FIR filter. */
74 alignas(16) std::array<float,sFilterDelay*2 - 1> mWXHistory1{};
75 alignas(16) std::array<float,sFilterDelay*2 - 1> mWXHistory2{};
77 alignas(16) std::array<float,BufferLineSize + sFilterDelay*2> mTemp{};
79 void encode(const al::span<FloatBufferLine> OutSamples,
80 const al::span<FloatBufferLine,4> InSamples, const size_t SamplesToDo);
82 DEF_NEWDEL(UhjEncoder)
85 const PhaseShifterT<UhjEncoder::sFilterDelay*2> PShift{};
88 /* Encoding UHJ from B-Format is done as:
90 * S = 0.9396926*W + 0.1855740*X
91 * D = j(-0.3420201*W + 0.5098604*X) + 0.6554516*Y
93 * Left = (S + D)/2.0
94 * Right = (S - D)/2.0
95 * T = j(-0.1432*W + 0.6512*X) - 0.7071068*Y
96 * Q = 0.9772*Z
98 * where j is a wide-band +90 degree phase shift. T is excluded from 2-channel
99 * output, and Q is excluded from 2- and 3-channel output.
101 void UhjEncoder::encode(const al::span<FloatBufferLine> OutSamples,
102 const al::span<FloatBufferLine,4> InSamples, const size_t SamplesToDo)
104 const float *RESTRICT winput{al::assume_aligned<16>(InSamples[0].data())};
105 const float *RESTRICT xinput{al::assume_aligned<16>(InSamples[1].data())};
106 const float *RESTRICT yinput{al::assume_aligned<16>(InSamples[2].data())};
107 const float *RESTRICT zinput{al::assume_aligned<16>(InSamples[3].data())};
109 /* Combine the previously delayed S/D signal with the input. */
111 /* S = 0.9396926*W + 0.1855740*X */
112 auto miditer = mS.begin() + sFilterDelay;
113 std::transform(winput, winput+SamplesToDo, xinput, miditer,
114 [](const float w, const float x) noexcept -> float
115 { return 0.9396926f*w + 0.1855740f*x; });
117 /* D = 0.6554516*Y */
118 auto sideiter = mD.begin() + sFilterDelay;
119 std::transform(yinput, yinput+SamplesToDo, sideiter,
120 [](const float y) noexcept -> float { return 0.6554516f*y; });
122 /* D += j(-0.3420201*W + 0.5098604*X) */
123 auto tmpiter = std::copy(mWXHistory1.cbegin(), mWXHistory1.cend(), mTemp.begin());
124 std::transform(winput, winput+SamplesToDo, xinput, tmpiter,
125 [](const float w, const float x) noexcept -> float
126 { return -0.3420201f*w + 0.5098604f*x; });
127 std::copy_n(mTemp.cbegin()+SamplesToDo, mWXHistory1.size(), mWXHistory1.begin());
128 PShift.processAccum({mD.data(), SamplesToDo}, mTemp.data());
130 /* Left = (S + D)/2.0 */
131 float *RESTRICT left{al::assume_aligned<16>(OutSamples[0].data())};
132 for(size_t i{0};i < SamplesToDo;i++)
133 left[i] = (mS[i] + mD[i]) * 0.5f;
134 /* Right = (S - D)/2.0 */
135 float *RESTRICT right{al::assume_aligned<16>(OutSamples[1].data())};
136 for(size_t i{0};i < SamplesToDo;i++)
137 right[i] = (mS[i] - mD[i]) * 0.5f;
139 if(OutSamples.size() > 2)
141 /* T = -0.7071068*Y */
142 sideiter = mT.begin() + sFilterDelay;
143 std::transform(yinput, yinput+SamplesToDo, sideiter,
144 [](const float y) noexcept -> float { return -0.7071068f*y; });
146 /* T += j(-0.1432*W + 0.6512*X) */
147 tmpiter = std::copy(mWXHistory2.cbegin(), mWXHistory2.cend(), mTemp.begin());
148 std::transform(winput, winput+SamplesToDo, xinput, 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.processAccum({mT.data(), SamplesToDo}, mTemp.data());
154 float *RESTRICT t{al::assume_aligned<16>(OutSamples[2].data())};
155 for(size_t i{0};i < SamplesToDo;i++)
156 t[i] = mT[i];
158 if(OutSamples.size() > 3)
160 /* Q = 0.9772*Z */
161 sideiter = mQ.begin() + sFilterDelay;
162 std::transform(zinput, zinput+SamplesToDo, sideiter,
163 [](const float z) noexcept -> float { return 0.9772f*z; });
165 float *RESTRICT q{al::assume_aligned<16>(OutSamples[3].data())};
166 for(size_t i{0};i < SamplesToDo;i++)
167 q[i] = mQ[i];
170 /* Copy the future samples to the front for next time. */
171 std::copy(mS.cbegin()+SamplesToDo, mS.cbegin()+SamplesToDo+sFilterDelay, mS.begin());
172 std::copy(mD.cbegin()+SamplesToDo, mD.cbegin()+SamplesToDo+sFilterDelay, mD.begin());
173 std::copy(mT.cbegin()+SamplesToDo, mT.cbegin()+SamplesToDo+sFilterDelay, mT.begin());
174 std::copy(mQ.cbegin()+SamplesToDo, mQ.cbegin()+SamplesToDo+sFilterDelay, mQ.begin());
178 struct SpeakerPos {
179 int mChannelID;
180 float mAzimuth;
181 float mElevation;
184 /* Azimuth is counter-clockwise. */
185 constexpr SpeakerPos StereoMap[2]{
186 { SF_CHANNEL_MAP_LEFT, 30.0f, 0.0f },
187 { SF_CHANNEL_MAP_RIGHT, -30.0f, 0.0f },
188 }, QuadMap[4]{
189 { SF_CHANNEL_MAP_LEFT, 45.0f, 0.0f },
190 { SF_CHANNEL_MAP_RIGHT, -45.0f, 0.0f },
191 { SF_CHANNEL_MAP_REAR_LEFT, 135.0f, 0.0f },
192 { SF_CHANNEL_MAP_REAR_RIGHT, -135.0f, 0.0f },
193 }, X51Map[6]{
194 { SF_CHANNEL_MAP_LEFT, 30.0f, 0.0f },
195 { SF_CHANNEL_MAP_RIGHT, -30.0f, 0.0f },
196 { SF_CHANNEL_MAP_CENTER, 0.0f, 0.0f },
197 { SF_CHANNEL_MAP_LFE, 0.0f, 0.0f },
198 { SF_CHANNEL_MAP_SIDE_LEFT, 110.0f, 0.0f },
199 { SF_CHANNEL_MAP_SIDE_RIGHT, -110.0f, 0.0f },
200 }, X51RearMap[6]{
201 { SF_CHANNEL_MAP_LEFT, 30.0f, 0.0f },
202 { SF_CHANNEL_MAP_RIGHT, -30.0f, 0.0f },
203 { SF_CHANNEL_MAP_CENTER, 0.0f, 0.0f },
204 { SF_CHANNEL_MAP_LFE, 0.0f, 0.0f },
205 { SF_CHANNEL_MAP_REAR_LEFT, 110.0f, 0.0f },
206 { SF_CHANNEL_MAP_REAR_RIGHT, -110.0f, 0.0f },
207 }, X71Map[8]{
208 { SF_CHANNEL_MAP_LEFT, 30.0f, 0.0f },
209 { SF_CHANNEL_MAP_RIGHT, -30.0f, 0.0f },
210 { SF_CHANNEL_MAP_CENTER, 0.0f, 0.0f },
211 { SF_CHANNEL_MAP_LFE, 0.0f, 0.0f },
212 { SF_CHANNEL_MAP_REAR_LEFT, 150.0f, 0.0f },
213 { SF_CHANNEL_MAP_REAR_RIGHT, -150.0f, 0.0f },
214 { SF_CHANNEL_MAP_SIDE_LEFT, 90.0f, 0.0f },
215 { SF_CHANNEL_MAP_SIDE_RIGHT, -90.0f, 0.0f },
216 }, X714Map[12]{
217 { SF_CHANNEL_MAP_LEFT, 30.0f, 0.0f },
218 { SF_CHANNEL_MAP_RIGHT, -30.0f, 0.0f },
219 { SF_CHANNEL_MAP_CENTER, 0.0f, 0.0f },
220 { SF_CHANNEL_MAP_LFE, 0.0f, 0.0f },
221 { SF_CHANNEL_MAP_REAR_LEFT, 150.0f, 0.0f },
222 { SF_CHANNEL_MAP_REAR_RIGHT, -150.0f, 0.0f },
223 { SF_CHANNEL_MAP_SIDE_LEFT, 90.0f, 0.0f },
224 { SF_CHANNEL_MAP_SIDE_RIGHT, -90.0f, 0.0f },
225 { SF_CHANNEL_MAP_TOP_FRONT_LEFT, 45.0f, 35.0f },
226 { SF_CHANNEL_MAP_TOP_FRONT_RIGHT, -45.0f, 35.0f },
227 { SF_CHANNEL_MAP_TOP_REAR_LEFT, 135.0f, 35.0f },
228 { SF_CHANNEL_MAP_TOP_REAR_RIGHT, -135.0f, 35.0f },
231 constexpr auto GenCoeffs(double x /*+front*/, double y /*+left*/, double z /*+up*/) noexcept
233 /* Coefficients are +3dB of FuMa. */
234 return std::array<float,4>{{
235 1.0f,
236 static_cast<float>(al::numbers::sqrt2 * x),
237 static_cast<float>(al::numbers::sqrt2 * y),
238 static_cast<float>(al::numbers::sqrt2 * z)
242 } // namespace
245 int main(int argc, char **argv)
247 if(argc < 2 || std::strcmp(argv[1], "-h") == 0 || std::strcmp(argv[1], "--help") == 0)
249 printf("Usage: %s <infile...>\n\n", argv[0]);
250 return 1;
253 uint uhjchans{2};
254 size_t num_files{0}, num_encoded{0};
255 for(int fidx{1};fidx < argc;++fidx)
257 if(strcmp(argv[fidx], "-bhj") == 0)
259 uhjchans = 2;
260 continue;
262 if(strcmp(argv[fidx], "-thj") == 0)
264 uhjchans = 3;
265 continue;
267 if(strcmp(argv[fidx], "-phj") == 0)
269 uhjchans = 4;
270 continue;
272 ++num_files;
274 std::string outname{argv[fidx]};
275 size_t lastslash{outname.find_last_of('/')};
276 if(lastslash != std::string::npos)
277 outname.erase(0, lastslash+1);
278 size_t extpos{outname.find_last_of('.')};
279 if(extpos != std::string::npos)
280 outname.resize(extpos);
281 outname += ".uhj.flac";
283 SF_INFO ininfo{};
284 SndFilePtr infile{sf_open(argv[fidx], SFM_READ, &ininfo)};
285 if(!infile)
287 fprintf(stderr, "Failed to open %s\n", argv[fidx]);
288 continue;
290 printf("Converting %s to %s...\n", argv[fidx], outname.c_str());
292 /* Work out the channel map, preferably using the actual channel map
293 * from the file/format, but falling back to assuming WFX order.
295 * TODO: Map indices when the channel order differs from the virtual
296 * speaker position maps.
298 al::span<const SpeakerPos> spkrs;
299 auto chanmap = std::vector<int>(static_cast<uint>(ininfo.channels), SF_CHANNEL_MAP_INVALID);
300 if(sf_command(infile.get(), SFC_GET_CHANNEL_MAP_INFO, chanmap.data(),
301 ininfo.channels*int{sizeof(int)}) == SF_TRUE)
303 static const std::array<int,2> stereomap{{SF_CHANNEL_MAP_LEFT, SF_CHANNEL_MAP_RIGHT}};
304 static const std::array<int,4> quadmap{{SF_CHANNEL_MAP_LEFT, SF_CHANNEL_MAP_RIGHT,
305 SF_CHANNEL_MAP_REAR_LEFT, SF_CHANNEL_MAP_REAR_RIGHT}};
306 static const std::array<int,6> x51map{{SF_CHANNEL_MAP_LEFT, SF_CHANNEL_MAP_RIGHT,
307 SF_CHANNEL_MAP_CENTER, SF_CHANNEL_MAP_LFE,
308 SF_CHANNEL_MAP_SIDE_LEFT, SF_CHANNEL_MAP_SIDE_RIGHT}};
309 static const std::array<int,6> x51rearmap{{SF_CHANNEL_MAP_LEFT, SF_CHANNEL_MAP_RIGHT,
310 SF_CHANNEL_MAP_CENTER, SF_CHANNEL_MAP_LFE,
311 SF_CHANNEL_MAP_REAR_LEFT, SF_CHANNEL_MAP_REAR_RIGHT}};
312 static const std::array<int,8> x71map{{SF_CHANNEL_MAP_LEFT, SF_CHANNEL_MAP_RIGHT,
313 SF_CHANNEL_MAP_CENTER, SF_CHANNEL_MAP_LFE,
314 SF_CHANNEL_MAP_REAR_LEFT, SF_CHANNEL_MAP_REAR_RIGHT,
315 SF_CHANNEL_MAP_SIDE_LEFT, SF_CHANNEL_MAP_SIDE_RIGHT}};
316 static const std::array<int,12> x714map{{SF_CHANNEL_MAP_LEFT, SF_CHANNEL_MAP_RIGHT,
317 SF_CHANNEL_MAP_CENTER, SF_CHANNEL_MAP_LFE,
318 SF_CHANNEL_MAP_REAR_LEFT, SF_CHANNEL_MAP_REAR_RIGHT,
319 SF_CHANNEL_MAP_SIDE_LEFT, SF_CHANNEL_MAP_SIDE_RIGHT,
320 SF_CHANNEL_MAP_TOP_FRONT_LEFT, SF_CHANNEL_MAP_TOP_FRONT_RIGHT,
321 SF_CHANNEL_MAP_TOP_REAR_LEFT, SF_CHANNEL_MAP_TOP_REAR_RIGHT}};
322 static const std::array<int,3> ambi2dmap{{SF_CHANNEL_MAP_AMBISONIC_B_W,
323 SF_CHANNEL_MAP_AMBISONIC_B_X, SF_CHANNEL_MAP_AMBISONIC_B_Y}};
324 static const std::array<int,4> ambi3dmap{{SF_CHANNEL_MAP_AMBISONIC_B_W,
325 SF_CHANNEL_MAP_AMBISONIC_B_X, SF_CHANNEL_MAP_AMBISONIC_B_Y,
326 SF_CHANNEL_MAP_AMBISONIC_B_Z}};
328 auto match_chanmap = [](const al::span<int> a, const al::span<const int> b) -> bool
330 return a.size() == b.size()
331 && std::mismatch(a.begin(), a.end(), b.begin(), b.end()).first == a.end();
333 if(match_chanmap(chanmap, stereomap))
334 spkrs = StereoMap;
335 else if(match_chanmap(chanmap, quadmap))
336 spkrs = QuadMap;
337 else if(match_chanmap(chanmap, x51map))
338 spkrs = X51Map;
339 else if(match_chanmap(chanmap, x51rearmap))
340 spkrs = X51RearMap;
341 else if(match_chanmap(chanmap, x71map))
342 spkrs = X71Map;
343 else if(match_chanmap(chanmap, x714map))
344 spkrs = X714Map;
345 else if(match_chanmap(chanmap, ambi2dmap) || match_chanmap(chanmap, ambi3dmap))
347 /* Do nothing. */
349 else
351 std::string mapstr;
352 if(chanmap.size() > 0)
354 mapstr = std::to_string(chanmap[0]);
355 for(int idx : al::span<int>{chanmap}.subspan<1>())
357 mapstr += ',';
358 mapstr += std::to_string(idx);
361 fprintf(stderr, " ... %zu channels not supported (map: %s)\n", chanmap.size(),
362 mapstr.c_str());
363 continue;
366 else if(ininfo.channels == 2)
368 fprintf(stderr, " ... assuming WFX order stereo\n");
369 spkrs = StereoMap;
371 else if(ininfo.channels == 6)
373 fprintf(stderr, " ... assuming WFX order 5.1\n");
374 spkrs = X51Map;
376 else if(ininfo.channels == 8)
378 fprintf(stderr, " ... assuming WFX order 7.1\n");
379 spkrs = X71Map;
381 else
383 fprintf(stderr, " ... unmapped %d-channel audio not supported\n", ininfo.channels);
384 continue;
387 SF_INFO outinfo{};
388 outinfo.frames = ininfo.frames;
389 outinfo.samplerate = ininfo.samplerate;
390 outinfo.channels = static_cast<int>(uhjchans);
391 outinfo.format = SF_FORMAT_PCM_24 | SF_FORMAT_FLAC;
392 SndFilePtr outfile{sf_open(outname.c_str(), SFM_WRITE, &outinfo)};
393 if(!outfile)
395 fprintf(stderr, " ... failed to create %s\n", outname.c_str());
396 continue;
399 auto encoder = std::make_unique<UhjEncoder>();
400 auto splbuf = al::vector<FloatBufferLine, 16>(static_cast<uint>(9+ininfo.channels)+uhjchans);
401 auto ambmem = al::span<FloatBufferLine,4>{&splbuf[0], 4};
402 auto encmem = al::span<FloatBufferLine,4>{&splbuf[4], 4};
403 auto srcmem = al::span<float,BufferLineSize>{splbuf[8].data(), BufferLineSize};
404 auto outmem = al::span<float>{splbuf[9].data(), BufferLineSize*uhjchans};
406 /* A number of initial samples need to be skipped to cut the lead-in
407 * from the all-pass filter delay. The same number of samples need to
408 * be fed through the encoder after reaching the end of the input file
409 * to ensure none of the original input is lost.
411 size_t total_wrote{0};
412 size_t LeadIn{UhjEncoder::sFilterDelay};
413 sf_count_t LeadOut{UhjEncoder::sFilterDelay};
414 while(LeadIn > 0 || LeadOut > 0)
416 auto inmem = outmem.data() + outmem.size();
417 auto sgot = sf_readf_float(infile.get(), inmem, BufferLineSize);
419 sgot = std::max<sf_count_t>(sgot, 0);
420 if(sgot < BufferLineSize)
422 const sf_count_t remaining{std::min(BufferLineSize - sgot, LeadOut)};
423 std::fill_n(inmem + sgot*ininfo.channels, remaining*ininfo.channels, 0.0f);
424 sgot += remaining;
425 LeadOut -= remaining;
428 for(auto&& buf : ambmem)
429 buf.fill(0.0f);
431 auto got = static_cast<size_t>(sgot);
432 if(spkrs.empty())
434 /* B-Format is already in the correct order. It just needs a
435 * +3dB boost.
437 constexpr float scale{al::numbers::sqrt2_v<float>};
438 const size_t chans{std::min<size_t>(static_cast<uint>(ininfo.channels), 4u)};
439 for(size_t c{0};c < chans;++c)
441 for(size_t i{0};i < got;++i)
442 ambmem[c][i] = inmem[i*static_cast<uint>(ininfo.channels)] * scale;
443 ++inmem;
446 else for(auto&& spkr : spkrs)
448 /* Skip LFE. Or mix directly into W? Or W+X? */
449 if(spkr.mChannelID == SF_CHANNEL_MAP_LFE)
451 ++inmem;
452 continue;
455 for(size_t i{0};i < got;++i)
456 srcmem[i] = inmem[i * static_cast<uint>(ininfo.channels)];
457 ++inmem;
459 constexpr auto Deg2Rad = al::numbers::pi / 180.0;
460 const auto coeffs = GenCoeffs(
461 std::cos(spkr.mAzimuth*Deg2Rad) * std::cos(spkr.mElevation*Deg2Rad),
462 std::sin(spkr.mAzimuth*Deg2Rad) * std::cos(spkr.mElevation*Deg2Rad),
463 std::sin(spkr.mElevation*Deg2Rad));
464 for(size_t c{0};c < 4;++c)
466 for(size_t i{0};i < got;++i)
467 ambmem[c][i] += srcmem[i] * coeffs[c];
471 encoder->encode(encmem.subspan(0, uhjchans), ambmem, got);
472 if(LeadIn >= got)
474 LeadIn -= got;
475 continue;
478 got -= LeadIn;
479 for(size_t c{0};c < uhjchans;++c)
481 constexpr float max_val{8388607.0f / 8388608.0f};
482 auto clamp = [](float v, float mn, float mx) noexcept
483 { return std::min(std::max(v, mn), mx); };
484 for(size_t i{0};i < got;++i)
485 outmem[i*uhjchans + c] = clamp(encmem[c][LeadIn+i], -1.0f, max_val);
487 LeadIn = 0;
489 sf_count_t wrote{sf_writef_float(outfile.get(), outmem.data(),
490 static_cast<sf_count_t>(got))};
491 if(wrote < 0)
492 fprintf(stderr, " ... failed to write samples: %d\n", sf_error(outfile.get()));
493 else
494 total_wrote += static_cast<size_t>(wrote);
496 printf(" ... wrote %zu samples (%" PRId64 ").\n", total_wrote, int64_t{ininfo.frames});
497 ++num_encoded;
499 if(num_encoded == 0)
500 fprintf(stderr, "Failed to encode any input files\n");
501 else if(num_encoded < num_files)
502 fprintf(stderr, "Encoded %zu of %zu files\n", num_encoded, num_files);
503 else
504 printf("Encoded %s%zu file%s\n", (num_encoded > 1) ? "all " : "", num_encoded,
505 (num_encoded == 1) ? "" : "s");
506 return 0;