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.master
parent
b72402d3e7
commit
a97dba6f41
|
@ -94,6 +94,36 @@ void BandSplitterR<Real>::processHfScale(const al::span<Real> samples, const Rea
|
|||
mApZ1 = ap_z1;
|
||||
}
|
||||
|
||||
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
|
||||
{
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -241,7 +241,7 @@ struct Voice {
|
|||
al::vector<HistoryLine,16> mPrevSamples{2};
|
||||
|
||||
struct ChannelData {
|
||||
float mAmbiScale;
|
||||
float mAmbiHFScale, mAmbiLFScale;
|
||||
BandSplitter mAmbiSplitter;
|
||||
|
||||
DirectParams mDryParams;
|
||||
|
|
Loading…
Reference in New Issue