Add an option to change the UHJ decoder method
For 2-channel UHJ, two decoding equations are provided in the original paper. The alternative one is most often referenced for 2-channel UHJ decoding, but the original/general one can also be used by assuming T is fully attenuated (which the format allows for, as T can be variably attenuated by a factor between 0 and 1 to deal with an imperfect transmission medium). Neither method can be perfect for 2-channel UHJ, it's irrevocably lossy to the original source, but my subjective testing indicates the general equation produces less audibly errant results.
This commit is contained in:
parent
d681573803
commit
f045694ce0
@ -130,8 +130,8 @@ struct UhjDecoder {
|
|||||||
|
|
||||||
alignas(16) std::array<float,BufferLineSize + sFilterSize*2> mTemp{};
|
alignas(16) std::array<float,BufferLineSize + sFilterSize*2> mTemp{};
|
||||||
|
|
||||||
void decode(const float *RESTRICT InSamples, const al::span<FloatBufferLine> OutSamples,
|
void decode(const float *RESTRICT InSamples, const size_t InChannels,
|
||||||
const size_t SamplesToDo);
|
const al::span<FloatBufferLine> OutSamples, const size_t SamplesToDo);
|
||||||
void decode2(const float *RESTRICT InSamples, const al::span<FloatBufferLine,3> OutSamples,
|
void decode2(const float *RESTRICT InSamples, const al::span<FloatBufferLine,3> OutSamples,
|
||||||
const size_t SamplesToDo);
|
const size_t SamplesToDo);
|
||||||
|
|
||||||
@ -307,7 +307,7 @@ void allpass_process(al::span<float> dst, const float *RESTRICT src)
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Decoding 3- and 4-channel UHJ is done as:
|
/* Decoding UHJ is done as:
|
||||||
*
|
*
|
||||||
* S = Left + Right
|
* S = Left + Right
|
||||||
* D = Left - Right
|
* D = Left - Right
|
||||||
@ -317,7 +317,10 @@ void allpass_process(al::span<float> dst, const float *RESTRICT src)
|
|||||||
* Y = 0.795954*D - 0.676406*T + j(0.186626*S)
|
* Y = 0.795954*D - 0.676406*T + j(0.186626*S)
|
||||||
* Z = 1.023332*Q
|
* Z = 1.023332*Q
|
||||||
*
|
*
|
||||||
* where j is a +90 degree phase shift. 3-channel UHJ excludes Q/Z.
|
* where j is a +90 degree phase shift. 3-channel UHJ excludes Q, while 2-
|
||||||
|
* channel excludes Q and T. The B-Format signal reconstructed from 2-channel
|
||||||
|
* UHJ should not be run through a normal B-Format decoder, as it needs
|
||||||
|
* different shelf filters.
|
||||||
*
|
*
|
||||||
* NOTE: Some sources specify
|
* NOTE: Some sources specify
|
||||||
*
|
*
|
||||||
@ -377,13 +380,11 @@ void allpass_process(al::span<float> dst, const float *RESTRICT src)
|
|||||||
*
|
*
|
||||||
* Not halving produces a result matching the original input.
|
* Not halving produces a result matching the original input.
|
||||||
*/
|
*/
|
||||||
void UhjDecoder::decode(const float *RESTRICT InSamples,
|
void UhjDecoder::decode(const float *RESTRICT InSamples, const size_t InChannels,
|
||||||
const al::span<FloatBufferLine> OutSamples, const size_t SamplesToDo)
|
const al::span<FloatBufferLine> OutSamples, const size_t SamplesToDo)
|
||||||
{
|
{
|
||||||
ASSUME(SamplesToDo > 0);
|
ASSUME(SamplesToDo > 0);
|
||||||
|
|
||||||
const size_t Channels{OutSamples.size()};
|
|
||||||
|
|
||||||
float *woutput{OutSamples[0].data()};
|
float *woutput{OutSamples[0].data()};
|
||||||
float *xoutput{OutSamples[1].data()};
|
float *xoutput{OutSamples[1].data()};
|
||||||
float *youtput{OutSamples[2].data()};
|
float *youtput{OutSamples[2].data()};
|
||||||
@ -394,27 +395,29 @@ void UhjDecoder::decode(const float *RESTRICT InSamples,
|
|||||||
|
|
||||||
/* S = Left + Right */
|
/* S = Left + Right */
|
||||||
for(size_t i{0};i < SamplesToDo;++i)
|
for(size_t i{0};i < SamplesToDo;++i)
|
||||||
mS[sFilterSize+i] = InSamples[i*Channels + 0] + InSamples[i*Channels + 1];
|
mS[sFilterSize+i] = InSamples[i*InChannels + 0] + InSamples[i*InChannels + 1];
|
||||||
|
|
||||||
/* D = Left - Right */
|
/* D = Left - Right */
|
||||||
for(size_t i{0};i < SamplesToDo;++i)
|
for(size_t i{0};i < SamplesToDo;++i)
|
||||||
mD[sFilterSize+i] = InSamples[i*Channels + 0] - InSamples[i*Channels + 1];
|
mD[sFilterSize+i] = InSamples[i*InChannels + 0] - InSamples[i*InChannels + 1];
|
||||||
|
|
||||||
|
if(InChannels > 2)
|
||||||
|
{
|
||||||
/* T */
|
/* T */
|
||||||
for(size_t i{0};i < SamplesToDo;++i)
|
for(size_t i{0};i < SamplesToDo;++i)
|
||||||
mT[sFilterSize+i] = InSamples[i*Channels + 2];
|
mT[sFilterSize+i] = InSamples[i*InChannels + 2];
|
||||||
|
}
|
||||||
if(Channels > 3)
|
if(InChannels > 3)
|
||||||
{
|
{
|
||||||
/* Q */
|
/* Q */
|
||||||
for(size_t i{0};i < SamplesToDo;++i)
|
for(size_t i{0};i < SamplesToDo;++i)
|
||||||
mQ[sFilterSize+i] = InSamples[i*Channels + 3];
|
mQ[sFilterSize+i] = InSamples[i*InChannels + 3];
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Precompute j(0.828347*D + 0.767835*T) and store in xoutput. */
|
/* Precompute j(0.828347*D + 0.767835*T) and store in xoutput. */
|
||||||
auto tmpiter = std::copy(mDTHistory.cbegin(), mDTHistory.cend(), mTemp.begin());
|
auto tmpiter = std::copy(mDTHistory.cbegin(), mDTHistory.cend(), mTemp.begin());
|
||||||
std::transform(mD.cbegin(), mD.cbegin()+SamplesToDo+sFilterSize, mT.cbegin(), tmpiter,
|
std::transform(mD.cbegin(), mD.cbegin()+SamplesToDo+sFilterSize, mT.cbegin(), tmpiter,
|
||||||
[](const float D, const float T) noexcept { return 0.828347f*D + 0.767835f*T; });
|
[](const float d, const float t) noexcept { return 0.828347f*d + 0.767835f*t; });
|
||||||
std::copy_n(mTemp.cbegin()+SamplesToDo, mDTHistory.size(), mDTHistory.begin());
|
std::copy_n(mTemp.cbegin()+SamplesToDo, mDTHistory.size(), mDTHistory.begin());
|
||||||
allpass_process({xoutput, SamplesToDo}, mTemp.data());
|
allpass_process({xoutput, SamplesToDo}, mTemp.data());
|
||||||
|
|
||||||
@ -438,7 +441,7 @@ void UhjDecoder::decode(const float *RESTRICT InSamples,
|
|||||||
youtput[i] = 0.795954f*mD[i] - 0.676406f*mT[i] + 0.186626f*youtput[i];
|
youtput[i] = 0.795954f*mD[i] - 0.676406f*mT[i] + 0.186626f*youtput[i];
|
||||||
}
|
}
|
||||||
|
|
||||||
if(Channels > 3)
|
if(OutSamples.size() > 3)
|
||||||
{
|
{
|
||||||
float *zoutput{OutSamples[3].data()};
|
float *zoutput{OutSamples[3].data()};
|
||||||
/* Z = 1.023332*Q */
|
/* Z = 1.023332*Q */
|
||||||
@ -452,12 +455,12 @@ void UhjDecoder::decode(const float *RESTRICT InSamples,
|
|||||||
std::copy(mQ.begin()+SamplesToDo, mQ.begin()+SamplesToDo+sFilterSize, mQ.begin());
|
std::copy(mQ.begin()+SamplesToDo, mQ.begin()+SamplesToDo+sFilterSize, mQ.begin());
|
||||||
}
|
}
|
||||||
|
|
||||||
/* There is a difference with decoding 2-channel UHJ compared to 3-channel, due
|
/* This is an alternative equation for decoding 2-channel UHJ. Not sure what
|
||||||
* to 2-channel having lost some of the original signal. The B-Format signal
|
* the intended benefit is over the above equation as this slightly reduces the
|
||||||
* reconstructed from 2-channel UHJ should not be run through a normal B-Format
|
* amount of the original left response and has more of the phase-shifted
|
||||||
* decoder, as it needs different shelf filters.
|
* forward response on the left response.
|
||||||
*
|
*
|
||||||
* 2-channel UHJ decoding is done as:
|
* This decoding is done as:
|
||||||
*
|
*
|
||||||
* S = Left + Right
|
* S = Left + Right
|
||||||
* D = Left - Right
|
* D = Left - Right
|
||||||
@ -523,13 +526,32 @@ int main(int argc, char **argv)
|
|||||||
{
|
{
|
||||||
if(argc < 2 || std::strcmp(argv[1], "-h") == 0 || std::strcmp(argv[1], "--help") == 0)
|
if(argc < 2 || std::strcmp(argv[1], "-h") == 0 || std::strcmp(argv[1], "--help") == 0)
|
||||||
{
|
{
|
||||||
printf("Usage: %s <filename.wav...>\n", argv[0]);
|
printf("Usage: %s <[options] filename.wav...>\n\n"
|
||||||
|
" Options:\n"
|
||||||
|
" --general Use the general equations for 2-channel UHJ (default).\n"
|
||||||
|
" --alternative Use the alternative equations for 2-channel UHJ.\n"
|
||||||
|
"\n"
|
||||||
|
"Note: When decoding 2-channel UHJ to an .amb file, the result should not use\n"
|
||||||
|
"the normal B-Format shelf filters! Only 3- and 4-channel UHJ can accurately\n"
|
||||||
|
"reconstruct the original B-Format signal.",
|
||||||
|
argv[0]);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t num_files{0}, num_decoded{0};
|
size_t num_files{0}, num_decoded{0};
|
||||||
|
bool use_general{true};
|
||||||
for(int fidx{1};fidx < argc;++fidx)
|
for(int fidx{1};fidx < argc;++fidx)
|
||||||
{
|
{
|
||||||
|
if(std::strcmp(argv[fidx], "--general") == 0)
|
||||||
|
{
|
||||||
|
use_general = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if(std::strcmp(argv[fidx], "--alternative") == 0)
|
||||||
|
{
|
||||||
|
use_general = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
++num_files;
|
++num_files;
|
||||||
SF_INFO ininfo{};
|
SF_INFO ininfo{};
|
||||||
SndFilePtr infile{sf_open(argv[fidx], SFM_READ, &ininfo)};
|
SndFilePtr infile{sf_open(argv[fidx], SFM_READ, &ininfo)};
|
||||||
@ -553,7 +575,8 @@ int main(int argc, char **argv)
|
|||||||
fprintf(stderr, "%s is not a 2-, 3-, or 4-channel file\n", argv[fidx]);
|
fprintf(stderr, "%s is not a 2-, 3-, or 4-channel file\n", argv[fidx]);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
printf("Converting %s from %d-channel UHJ...\n", argv[fidx], ininfo.channels);
|
printf("Converting %s from %d-channel UHJ%s...\n", argv[fidx], ininfo.channels,
|
||||||
|
(ininfo.channels == 2) ? use_general ? " (general)" : " (alternative)" : "");
|
||||||
|
|
||||||
std::string outname{argv[fidx]};
|
std::string outname{argv[fidx]};
|
||||||
auto lastslash = outname.find_last_of('/');
|
auto lastslash = outname.find_last_of('/');
|
||||||
@ -634,10 +657,10 @@ int main(int argc, char **argv)
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto got = static_cast<size_t>(sgot);
|
auto got = static_cast<size_t>(sgot);
|
||||||
if(ininfo.channels == 2)
|
if(ininfo.channels > 2 || use_general)
|
||||||
|
decoder->decode(inmem.get(), static_cast<uint>(ininfo.channels), decmem, got);
|
||||||
|
else
|
||||||
decoder->decode2(inmem.get(), decmem, got);
|
decoder->decode2(inmem.get(), decmem, got);
|
||||||
else if(ininfo.channels == 3 || ininfo.channels == 4)
|
|
||||||
decoder->decode(inmem.get(), decmem, got);
|
|
||||||
for(size_t i{0};i < got;++i)
|
for(size_t i{0};i < got;++i)
|
||||||
{
|
{
|
||||||
for(size_t j{0};j < outchans;++j)
|
for(size_t j{0};j < outchans;++j)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user