aboutsummaryrefslogtreecommitdiffstats
path: root/core
diff options
context:
space:
mode:
authorChris Robinson <[email protected]>2021-12-09 22:00:35 -0800
committerChris Robinson <[email protected]>2021-12-09 22:00:35 -0800
commita97dba6f417ef36140fe31b7b24fd48f620254f3 (patch)
treec7b60af368ee17c538051e7a93d75ea34aa99199 /core
parentb72402d3e723befabc0bdfd4c7579fff6b92c13c (diff)
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.
Diffstat (limited to 'core')
-rw-r--r--core/filters/splitter.cpp30
-rw-r--r--core/filters/splitter.h1
-rw-r--r--core/voice.cpp44
-rw-r--r--core/voice.h2
4 files changed, 73 insertions, 4 deletions
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
@@ -95,6 +95,36 @@ void BandSplitterR<Real>::processHfScale(const al::span<Real> samples, const Rea
}
template<typename Real>
+void BandSplitterR<Real>::processScale(const al::span<Real> 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<typename Real>
void BandSplitterR<Real>::applyAllpass(const al::span<Real> samples) const
{
const Real coeff{mCoeff};
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<const Real> input, Real *hpout, Real *lpout);
void processHfScale(const al::span<Real> samples, const Real hfscale);
+ void processScale(const al::span<Real> 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<float,BufferLineSize> FilterBuf{Device->FilteredData};
@@ -840,11 +840,49 @@ void Voice::prepare(DeviceBase *device)
const BandSplitter splitter{device->mXOverFreq / static_cast<float>(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<float>(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<HistoryLine,16> mPrevSamples{2};
struct ChannelData {
- float mAmbiScale;
+ float mAmbiHFScale, mAmbiLFScale;
BandSplitter mAmbiSplitter;
DirectParams mDryParams;