Add options to encode 3- and 4-channel UHJ to uhjencoder

The generated files won't play correctly if the player doesn't know they're 3-
and 4-channel UHJ (the third and fourth channels must be ignored, or decoded,
for playback). This is largely just for completion's sake, just in case someone
has a use for it.
master
Chris Robinson 2022-02-04 18:05:25 -08:00
parent 97dbd9a191
commit 9ba489360c
1 changed files with 71 additions and 19 deletions

View File

@ -67,14 +67,17 @@ struct UhjEncoder {
/* Delays and processing storage for the unfiltered signal. */
alignas(16) std::array<float,BufferLineSize+sFilterDelay> mS{};
alignas(16) std::array<float,BufferLineSize+sFilterDelay> mD{};
alignas(16) std::array<float,BufferLineSize+sFilterDelay> mT{};
alignas(16) std::array<float,BufferLineSize+sFilterDelay> mQ{};
/* History for the FIR filter. */
alignas(16) std::array<float,sFilterDelay*2 - 1> mWXHistory{};
alignas(16) std::array<float,sFilterDelay*2 - 1> mWXHistory1{};
alignas(16) std::array<float,sFilterDelay*2 - 1> mWXHistory2{};
alignas(16) std::array<float,BufferLineSize + sFilterDelay*2> mTemp{};
void encode(const FloatBufferSpan LeftOut, const FloatBufferSpan RightOut,
const FloatBufferLine *InSamples, const size_t SamplesToDo);
void encode(const al::span<FloatBufferLine> OutSamples,
const al::span<FloatBufferLine,4> InSamples, const size_t SamplesToDo);
DEF_NEWDEL(UhjEncoder)
};
@ -95,15 +98,13 @@ const PhaseShifterT<UhjEncoder::sFilterDelay*2> PShift{};
* where j is a wide-band +90 degree phase shift. T is excluded from 2-channel
* output, and Q is excluded from 2- and 3-channel output.
*/
void UhjEncoder::encode(const FloatBufferSpan LeftOut, const FloatBufferSpan RightOut,
const FloatBufferLine *InSamples, const size_t SamplesToDo)
void UhjEncoder::encode(const al::span<FloatBufferLine> OutSamples,
const al::span<FloatBufferLine,4> InSamples, const size_t SamplesToDo)
{
float *RESTRICT left{al::assume_aligned<16>(LeftOut.data())};
float *RESTRICT right{al::assume_aligned<16>(RightOut.data())};
const float *RESTRICT winput{al::assume_aligned<16>(InSamples[0].data())};
const float *RESTRICT xinput{al::assume_aligned<16>(InSamples[1].data())};
const float *RESTRICT yinput{al::assume_aligned<16>(InSamples[2].data())};
const float *RESTRICT zinput{al::assume_aligned<16>(InSamples[3].data())};
/* Combine the previously delayed S/D signal with the input. */
@ -119,23 +120,58 @@ void UhjEncoder::encode(const FloatBufferSpan LeftOut, const FloatBufferSpan Rig
[](const float y) noexcept -> float { return 0.6554516f*y; });
/* D += j(-0.3420201*W + 0.5098604*X) */
auto tmpiter = std::copy(mWXHistory.cbegin(), mWXHistory.cend(), mTemp.begin());
auto tmpiter = std::copy(mWXHistory1.cbegin(), mWXHistory1.cend(), mTemp.begin());
std::transform(winput, winput+SamplesToDo, xinput, tmpiter,
[](const float w, const float x) noexcept -> float
{ return -0.3420201f*w + 0.5098604f*x; });
std::copy_n(mTemp.cbegin()+SamplesToDo, mWXHistory.size(), mWXHistory.begin());
std::copy_n(mTemp.cbegin()+SamplesToDo, mWXHistory1.size(), mWXHistory1.begin());
PShift.processAccum({mD.data(), SamplesToDo}, mTemp.data());
/* Left = (S + D)/2.0 */
float *RESTRICT left{al::assume_aligned<16>(OutSamples[0].data())};
for(size_t i{0};i < SamplesToDo;i++)
left[i] = (mS[i] + mD[i]) * 0.5f;
/* Right = (S - D)/2.0 */
float *RESTRICT right{al::assume_aligned<16>(OutSamples[1].data())};
for(size_t i{0};i < SamplesToDo;i++)
right[i] = (mS[i] - mD[i]) * 0.5f;
if(OutSamples.size() > 2)
{
/* T = -0.7071068*Y */
sideiter = mT.begin() + sFilterDelay;
std::transform(yinput, yinput+SamplesToDo, sideiter,
[](const float y) noexcept -> float { return -0.7071068f*y; });
/* T += j(-0.1432*W + 0.6512*X) */
tmpiter = std::copy(mWXHistory2.cbegin(), mWXHistory2.cend(), mTemp.begin());
std::transform(winput, winput+SamplesToDo, xinput, tmpiter,
[](const float w, const float x) noexcept -> float
{ return -0.1432f*w + 0.6512f*x; });
std::copy_n(mTemp.cbegin()+SamplesToDo, mWXHistory2.size(), mWXHistory2.begin());
PShift.processAccum({mT.data(), SamplesToDo}, mTemp.data());
float *RESTRICT t{al::assume_aligned<16>(OutSamples[2].data())};
for(size_t i{0};i < SamplesToDo;i++)
t[i] = mT[i];
}
if(OutSamples.size() > 3)
{
/* Q = 0.9772*Z */
sideiter = mQ.begin() + sFilterDelay;
std::transform(zinput, zinput+SamplesToDo, sideiter,
[](const float z) noexcept -> float { return 0.9772f*z; });
float *RESTRICT q{al::assume_aligned<16>(OutSamples[3].data())};
for(size_t i{0};i < SamplesToDo;i++)
q[i] = mQ[i];
}
/* Copy the future samples to the front for next time. */
std::copy(mS.cbegin()+SamplesToDo, mS.cbegin()+SamplesToDo+sFilterDelay, mS.begin());
std::copy(mD.cbegin()+SamplesToDo, mD.cbegin()+SamplesToDo+sFilterDelay, mD.begin());
std::copy(mT.cbegin()+SamplesToDo, mT.cbegin()+SamplesToDo+sFilterDelay, mT.begin());
std::copy(mQ.cbegin()+SamplesToDo, mQ.cbegin()+SamplesToDo+sFilterDelay, mQ.begin());
}
@ -214,9 +250,25 @@ int main(int argc, char **argv)
return 1;
}
uint uhjchans{2};
size_t num_files{0}, num_encoded{0};
for(int fidx{1};fidx < argc;++fidx)
{
if(strcmp(argv[fidx], "-bhj") == 0)
{
uhjchans = 2;
continue;
}
if(strcmp(argv[fidx], "-thj") == 0)
{
uhjchans = 3;
continue;
}
if(strcmp(argv[fidx], "-phj") == 0)
{
uhjchans = 4;
continue;
}
++num_files;
std::string outname{argv[fidx]};
@ -335,7 +387,7 @@ int main(int argc, char **argv)
SF_INFO outinfo{};
outinfo.frames = ininfo.frames;
outinfo.samplerate = ininfo.samplerate;
outinfo.channels = 2;
outinfo.channels = static_cast<int>(uhjchans);
outinfo.format = SF_FORMAT_PCM_24 | SF_FORMAT_FLAC;
SndFilePtr outfile{sf_open(outname.c_str(), SFM_WRITE, &outinfo)};
if(!outfile)
@ -345,11 +397,11 @@ int main(int argc, char **argv)
}
auto encoder = std::make_unique<UhjEncoder>();
auto splbuf = al::vector<FloatBufferLine, 16>(static_cast<uint>(ininfo.channels+9));
auto splbuf = al::vector<FloatBufferLine, 16>(static_cast<uint>(9+ininfo.channels)+uhjchans);
auto ambmem = al::span<FloatBufferLine,4>{&splbuf[0], 4};
auto encmem = al::span<FloatBufferLine,2>{&splbuf[4], 2};
auto srcmem = al::span<float,BufferLineSize>{splbuf[6].data(), BufferLineSize};
auto outmem = al::span<float,BufferLineSize*2>{splbuf[7].data(), BufferLineSize*2};
auto encmem = al::span<FloatBufferLine,4>{&splbuf[4], 4};
auto srcmem = al::span<float,BufferLineSize>{splbuf[8].data(), BufferLineSize};
auto outmem = al::span<float>{splbuf[9].data(), BufferLineSize*uhjchans};
/* A number of initial samples need to be skipped to cut the lead-in
* from the all-pass filter delay. The same number of samples need to
@ -361,7 +413,7 @@ int main(int argc, char **argv)
sf_count_t LeadOut{UhjEncoder::sFilterDelay};
while(LeadIn > 0 || LeadOut > 0)
{
auto inmem = splbuf[9].data();
auto inmem = outmem.data() + outmem.size();
auto sgot = sf_readf_float(infile.get(), inmem, BufferLineSize);
sgot = std::max<sf_count_t>(sgot, 0);
@ -416,7 +468,7 @@ int main(int argc, char **argv)
}
}
encoder->encode(encmem[0], encmem[1], ambmem.data(), got);
encoder->encode(encmem.subspan(0, uhjchans), ambmem, got);
if(LeadIn >= got)
{
LeadIn -= got;
@ -424,13 +476,13 @@ int main(int argc, char **argv)
}
got -= LeadIn;
for(size_t c{0};c < 2;++c)
for(size_t c{0};c < uhjchans;++c)
{
constexpr float max_val{8388607.0f / 8388608.0f};
auto clamp = [](float v, float mn, float mx) noexcept
{ return std::min(std::max(v, mn), mx); };
for(size_t i{0};i < got;++i)
outmem[i*2 + c] = clamp(encmem[c][LeadIn+i], -1.0f, max_val);
outmem[i*uhjchans + c] = clamp(encmem[c][LeadIn+i], -1.0f, max_val);
}
LeadIn = 0;