From a97dba6f417ef36140fe31b7b24fd48f620254f3 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Thu, 9 Dec 2021 22:00:35 -0800 Subject: Improve the 2-channel UHJ response This attempts to correct for the differences needed for 2-channel UHJ's shelf filters given the output shelf filters. It's far from ideal, but better than nothing. --- core/filters/splitter.cpp | 30 ++++++++++++++++++++++++++++++ core/filters/splitter.h | 1 + core/voice.cpp | 44 +++++++++++++++++++++++++++++++++++++++++--- core/voice.h | 2 +- 4 files changed, 73 insertions(+), 4 deletions(-) (limited to 'core') diff --git a/core/filters/splitter.cpp b/core/filters/splitter.cpp index 5cc670b7..8ebc482f 100644 --- a/core/filters/splitter.cpp +++ b/core/filters/splitter.cpp @@ -94,6 +94,36 @@ void BandSplitterR::processHfScale(const al::span samples, const Rea mApZ1 = ap_z1; } +template +void BandSplitterR::processScale(const al::span samples, const Real hfscale, const Real lfscale) +{ + const Real ap_coeff{mCoeff}; + const Real lp_coeff{mCoeff*0.5f + 0.5f}; + Real lp_z1{mLpZ1}; + Real lp_z2{mLpZ2}; + Real ap_z1{mApZ1}; + auto proc_sample = [hfscale,lfscale,ap_coeff,lp_coeff,&lp_z1,&lp_z2,&ap_z1](const Real in) noexcept -> Real + { + Real d{(in - lp_z1) * lp_coeff}; + Real lp_y{lp_z1 + d}; + lp_z1 = lp_y + d; + + d = (lp_y - lp_z2) * lp_coeff; + lp_y = lp_z2 + d; + lp_z2 = lp_y + d; + + Real ap_y{in*ap_coeff + ap_z1}; + ap_z1 = in - ap_y*ap_coeff; + + /* Apply separate factors to the high and low frequencies. */ + return (ap_y-lp_y)*hfscale + lp_y*lfscale; + }; + std::transform(samples.begin(), samples.end(), samples.begin(), proc_sample); + mLpZ1 = lp_z1; + mLpZ2 = lp_z2; + mApZ1 = ap_z1; +} + template void BandSplitterR::applyAllpass(const al::span samples) const { diff --git a/core/filters/splitter.h b/core/filters/splitter.h index ba548c10..f08f26c3 100644 --- a/core/filters/splitter.h +++ b/core/filters/splitter.h @@ -24,6 +24,7 @@ public: void process(const al::span input, Real *hpout, Real *lpout); void processHfScale(const al::span samples, const Real hfscale); + void processScale(const al::span samples, const Real hfscale, const Real lfscale); /* The all-pass portion of the band splitter. Applies the same phase shift * without splitting the signal. Note that each use of this method is diff --git a/core/voice.cpp b/core/voice.cpp index f40be71a..a8c5b281 100644 --- a/core/voice.cpp +++ b/core/voice.cpp @@ -647,8 +647,8 @@ void Voice::mix(const State vstate, ContextBase *Context, const uint SamplesToDo {Device->ResampledData, DstBufferSize})}; ++voiceSamples; if((mFlags&VoiceIsAmbisonic)) - chandata.mAmbiSplitter.processHfScale({ResampledData, DstBufferSize}, - chandata.mAmbiScale); + chandata.mAmbiSplitter.processScale({ResampledData, DstBufferSize}, + chandata.mAmbiHFScale, chandata.mAmbiLFScale); /* Now filter and mix to the appropriate outputs. */ const al::span FilterBuf{Device->FilteredData}; @@ -840,11 +840,49 @@ void Voice::prepare(DeviceBase *device) const BandSplitter splitter{device->mXOverFreq / static_cast(device->Frequency)}; for(auto &chandata : mChans) { - chandata.mAmbiScale = scales[*(OrderFromChan++)]; + chandata.mAmbiHFScale = scales[*(OrderFromChan++)]; + chandata.mAmbiLFScale = 1.0f; chandata.mAmbiSplitter = splitter; chandata.mDryParams = DirectParams{}; std::fill_n(chandata.mWetParams.begin(), device->NumAuxSends, SendParams{}); } + /* 2-channel UHJ needs different shelf filters. However, we can't just + * use different shelf filters after mixing it and with any old speaker + * setup the user has. To make this work, we apply the expected shelf + * filters for decoding UHJ2 to quad (only needs LF scaling), and act + * as if those 4 channels are encoded back onto first-order B-Format, + * which then upsamples to higher order as normal (only needs HF + * scaling). + * + * This isn't perfect, but without an entirely separate and limited + * UHJ2 path, it's better than nothing. + */ + if(mFmtChannels == FmtUHJ2) + { + mChans[0].mAmbiLFScale = 0.661f; + mChans[1].mAmbiLFScale = 1.293f; + mChans[2].mAmbiLFScale = 1.293f; + } + mFlags |= VoiceIsAmbisonic; + } + else if(mFmtChannels == FmtUHJ2 && !device->mUhjEncoder) + { + /* 2-channel UHJ with first-order output also needs the shelf filter + * correction applied, except with UHJ output (UHJ2->B-Format->UHJ2 is + * identity, so don't mess with it). + */ + const BandSplitter splitter{device->mXOverFreq / static_cast(device->Frequency)}; + for(auto &chandata : mChans) + { + chandata.mAmbiHFScale = 1.0f; + chandata.mAmbiLFScale = 1.0f; + chandata.mAmbiSplitter = splitter; + chandata.mDryParams = DirectParams{}; + std::fill_n(chandata.mWetParams.begin(), device->NumAuxSends, SendParams{}); + } + mChans[0].mAmbiLFScale = 0.661f; + mChans[1].mAmbiLFScale = 1.293f; + mChans[2].mAmbiLFScale = 1.293f; mFlags |= VoiceIsAmbisonic; } else diff --git a/core/voice.h b/core/voice.h index 169517d5..94385884 100644 --- a/core/voice.h +++ b/core/voice.h @@ -241,7 +241,7 @@ struct Voice { al::vector mPrevSamples{2}; struct ChannelData { - float mAmbiScale; + float mAmbiHFScale, mAmbiLFScale; BandSplitter mAmbiSplitter; DirectParams mDryParams; -- cgit v1.2.3