diff options
author | Chris Robinson <[email protected]> | 2023-01-04 22:02:29 -0800 |
---|---|---|
committer | Chris Robinson <[email protected]> | 2023-01-04 23:03:15 -0800 |
commit | 078b50e0ed6a0b89aa2e1f84bbbebbf24b885a4e (patch) | |
tree | db7bee5d11af40ae01d6210cfd41f6fa60a247f9 /alc | |
parent | e38413a4fbb0b4f85950c929141856a767993f4f (diff) |
Simplify handling effect output for spatial effects
Effects are given a 3D ambisonic buffer of the same order as the device, for
processing surround sound. Effects that pass input channels to matching output
channels as it processes them don't need to mix each input channel to all
output channels.
At most, an input channel may mix to a different output channel, if the target
buffer uses a different channel layout, and need a gain adjustment, if it uses
a different scaling. With a 2D output buffer, a number of channels can be
skipped altogether.
Diffstat (limited to 'alc')
-rw-r--r-- | alc/effects/autowah.cpp | 45 | ||||
-rw-r--r-- | alc/effects/compressor.cpp | 33 | ||||
-rw-r--r-- | alc/effects/equalizer.cpp | 56 | ||||
-rw-r--r-- | alc/effects/modulator.cpp | 46 | ||||
-rw-r--r-- | alc/effects/vmorpher.cpp | 43 |
5 files changed, 140 insertions, 83 deletions
diff --git a/alc/effects/autowah.cpp b/alc/effects/autowah.cpp index b5e259f4..8dfee45d 100644 --- a/alc/effects/autowah.cpp +++ b/alc/effects/autowah.cpp @@ -65,14 +65,16 @@ struct AutowahState final : public EffectState { } mEnv[BufferLineSize]; struct { + uint mTargetChannel{INVALID_CHANNEL_INDEX}; + /* Effect filters' history. */ struct { float z1, z2; - } Filter; + } mFilter; /* Effect gains for each output channel */ - float CurrentGains[MaxAmbiChannels]; - float TargetGains[MaxAmbiChannels]; + float mCurrentGain; + float mTargetGain; } mChans[MaxAmbiChannels]; /* Effects buffers */ @@ -108,9 +110,10 @@ void AutowahState::deviceUpdate(const DeviceBase*, const Buffer&) for(auto &chan : mChans) { - std::fill(std::begin(chan.CurrentGains), std::end(chan.CurrentGains), 0.0f); - chan.Filter.z1 = 0.0f; - chan.Filter.z2 = 0.0f; + chan.mTargetChannel = INVALID_CHANNEL_INDEX; + chan.mFilter.z1 = 0.0f; + chan.mFilter.z2 = 0.0f; + chan.mCurrentGain = 0.0f; } } @@ -131,9 +134,12 @@ void AutowahState::update(const ContextBase *context, const EffectSlot *slot, mBandwidthNorm = (MaxFreq-MinFreq) / frequency; mOutTarget = target.Main->Buffer; - auto set_gains = [slot,target](auto &chan, al::span<const float,MaxAmbiChannels> coeffs) - { ComputePanGains(target.Main, coeffs.data(), slot->Gain, chan.TargetGains); }; - SetAmbiPanIdentity(std::begin(mChans), slot->Wet.Buffer.size(), set_gains); + auto set_channel = [this](size_t idx, uint target, float gain) + { + mChans[idx].mTargetChannel = target; + mChans[idx].mTargetGain = gain; + }; + target.Main->setAmbiMixParams(slot->Wet, slot->Gain, set_channel); } void AutowahState::process(const size_t samplesToDo, @@ -165,17 +171,24 @@ void AutowahState::process(const size_t samplesToDo, } mEnvDelay = env_delay; - auto chandata = std::addressof(mChans[0]); + auto chandata = std::begin(mChans); for(const auto &insamples : samplesIn) { + const size_t outidx{chandata->mTargetChannel}; + if(outidx == INVALID_CHANNEL_INDEX) + { + ++chandata; + continue; + } + /* This effectively inlines BiquadFilter_setParams for a peaking * filter and BiquadFilter_processC. The alpha and cosine components * for the filter coefficients were previously calculated with the * envelope. Because the filter changes for each sample, the * coefficients are transient and don't need to be held. */ - float z1{chandata->Filter.z1}; - float z2{chandata->Filter.z2}; + float z1{chandata->mFilter.z1}; + float z2{chandata->mFilter.z2}; for(size_t i{0u};i < samplesToDo;i++) { @@ -197,12 +210,12 @@ void AutowahState::process(const size_t samplesToDo, z2 = input*(b[2]/a[0]) - output*(a[2]/a[0]); mBufferOut[i] = output; } - chandata->Filter.z1 = z1; - chandata->Filter.z2 = z2; + chandata->mFilter.z1 = z1; + chandata->mFilter.z2 = z2; /* Now, mix the processed sound data to the output. */ - MixSamples({mBufferOut, samplesToDo}, samplesOut, chandata->CurrentGains, - chandata->TargetGains, samplesToDo, 0); + MixSamples({mBufferOut, samplesToDo}, {&samplesOut[outidx], 1}, &chandata->mCurrentGain, + &chandata->mTargetGain, samplesToDo, 0); ++chandata; } } diff --git a/alc/effects/compressor.cpp b/alc/effects/compressor.cpp index 6202e58e..a4333539 100644 --- a/alc/effects/compressor.cpp +++ b/alc/effects/compressor.cpp @@ -64,7 +64,10 @@ namespace { struct CompressorState final : public EffectState { /* Effect gains for each channel */ - float mGain[MaxAmbiChannels][MaxAmbiChannels]{}; + struct { + uint mTarget{INVALID_CHANNEL_INDEX}; + float mGain{1.0f}; + } mChans[MaxAmbiChannels]; /* Effect parameters */ bool mEnabled{true}; @@ -103,9 +106,12 @@ void CompressorState::update(const ContextBase*, const EffectSlot *slot, mEnabled = props->Compressor.OnOff; mOutTarget = target.Main->Buffer; - auto set_gains = [slot,target](auto &gains, al::span<const float,MaxAmbiChannels> coeffs) - { ComputePanGains(target.Main, coeffs.data(), slot->Gain, gains); }; - SetAmbiPanIdentity(std::begin(mGain), slot->Wet.Buffer.size(), set_gains); + auto set_channel = [this](size_t idx, uint target, float gain) + { + mChans[idx].mTarget = target; + mChans[idx].mGain = gain; + }; + target.Main->setAmbiMixParams(slot->Wet, slot->Gain, set_channel); } void CompressorState::process(const size_t samplesToDo, @@ -158,19 +164,22 @@ void CompressorState::process(const size_t samplesToDo, mEnvFollower = env; /* Now compress the signal amplitude to output. */ - auto changains = std::addressof(mGain[0]); + auto chan = std::cbegin(mChans); for(const auto &input : samplesIn) { - const float *outgains{*(changains++)}; - for(FloatBufferLine &output : samplesOut) + const size_t outidx{chan->mTarget}; + if(outidx != INVALID_CHANNEL_INDEX) { - const float gain{*(outgains++)}; + const float *RESTRICT src{input.data() + base}; + float *RESTRICT dst{samplesOut[outidx].data() + base}; + const float gain{chan->mGain}; if(!(std::fabs(gain) > GainSilenceThreshold)) - continue; - - for(size_t i{0u};i < td;i++) - output[base+i] += input[base+i] * gains[i] * gain; + { + for(size_t i{0u};i < td;i++) + dst[i] += src[i] * gains[i] * gain; + } } + ++chan; } base += td; diff --git a/alc/effects/equalizer.cpp b/alc/effects/equalizer.cpp index 4bce34a2..de067911 100644 --- a/alc/effects/equalizer.cpp +++ b/alc/effects/equalizer.cpp @@ -87,12 +87,14 @@ namespace { struct EqualizerState final : public EffectState { struct { + uint mTargetChannel{INVALID_CHANNEL_INDEX}; + /* Effect parameters */ - BiquadFilter filter[4]; + BiquadFilter mFilter[4]; /* Effect gains for each channel */ - float CurrentGains[MaxAmbiChannels]{}; - float TargetGains[MaxAmbiChannels]{}; + float mCurrentGain{}; + float mTargetGain{}; } mChans[MaxAmbiChannels]; alignas(16) FloatBufferLine mSampleBuffer{}; @@ -111,8 +113,10 @@ void EqualizerState::deviceUpdate(const DeviceBase*, const Buffer&) { for(auto &e : mChans) { - std::for_each(std::begin(e.filter), std::end(e.filter), std::mem_fn(&BiquadFilter::clear)); - std::fill(std::begin(e.CurrentGains), std::end(e.CurrentGains), 0.0f); + e.mTargetChannel = INVALID_CHANNEL_INDEX; + std::for_each(std::begin(e.mFilter), std::end(e.mFilter), + std::mem_fn(&BiquadFilter::clear)); + e.mCurrentGain = 0.0f; } } @@ -131,48 +135,56 @@ void EqualizerState::update(const ContextBase *context, const EffectSlot *slot, */ gain = std::sqrt(props->Equalizer.LowGain); f0norm = props->Equalizer.LowCutoff / frequency; - mChans[0].filter[0].setParamsFromSlope(BiquadType::LowShelf, f0norm, gain, 0.75f); + mChans[0].mFilter[0].setParamsFromSlope(BiquadType::LowShelf, f0norm, gain, 0.75f); gain = std::sqrt(props->Equalizer.Mid1Gain); f0norm = props->Equalizer.Mid1Center / frequency; - mChans[0].filter[1].setParamsFromBandwidth(BiquadType::Peaking, f0norm, gain, + mChans[0].mFilter[1].setParamsFromBandwidth(BiquadType::Peaking, f0norm, gain, props->Equalizer.Mid1Width); gain = std::sqrt(props->Equalizer.Mid2Gain); f0norm = props->Equalizer.Mid2Center / frequency; - mChans[0].filter[2].setParamsFromBandwidth(BiquadType::Peaking, f0norm, gain, + mChans[0].mFilter[2].setParamsFromBandwidth(BiquadType::Peaking, f0norm, gain, props->Equalizer.Mid2Width); gain = std::sqrt(props->Equalizer.HighGain); f0norm = props->Equalizer.HighCutoff / frequency; - mChans[0].filter[3].setParamsFromSlope(BiquadType::HighShelf, f0norm, gain, 0.75f); + mChans[0].mFilter[3].setParamsFromSlope(BiquadType::HighShelf, f0norm, gain, 0.75f); /* Copy the filter coefficients for the other input channels. */ for(size_t i{1u};i < slot->Wet.Buffer.size();++i) { - mChans[i].filter[0].copyParamsFrom(mChans[0].filter[0]); - mChans[i].filter[1].copyParamsFrom(mChans[0].filter[1]); - mChans[i].filter[2].copyParamsFrom(mChans[0].filter[2]); - mChans[i].filter[3].copyParamsFrom(mChans[0].filter[3]); + mChans[i].mFilter[0].copyParamsFrom(mChans[0].mFilter[0]); + mChans[i].mFilter[1].copyParamsFrom(mChans[0].mFilter[1]); + mChans[i].mFilter[2].copyParamsFrom(mChans[0].mFilter[2]); + mChans[i].mFilter[3].copyParamsFrom(mChans[0].mFilter[3]); } mOutTarget = target.Main->Buffer; - auto set_gains = [slot,target](auto &chan, al::span<const float,MaxAmbiChannels> coeffs) - { ComputePanGains(target.Main, coeffs.data(), slot->Gain, chan.TargetGains); }; - SetAmbiPanIdentity(std::begin(mChans), slot->Wet.Buffer.size(), set_gains); + auto set_channel = [this](size_t idx, uint target, float gain) + { + mChans[idx].mTargetChannel = target; + mChans[idx].mTargetGain = gain; + }; + target.Main->setAmbiMixParams(slot->Wet, slot->Gain, set_channel); } void EqualizerState::process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut) { const al::span<float> buffer{mSampleBuffer.data(), samplesToDo}; - auto chan = std::addressof(mChans[0]); + auto chan = std::begin(mChans); for(const auto &input : samplesIn) { - const al::span<const float> inbuf{input.data(), samplesToDo}; - DualBiquad{chan->filter[0], chan->filter[1]}.process(inbuf, buffer.begin()); - DualBiquad{chan->filter[2], chan->filter[3]}.process(buffer, buffer.begin()); - - MixSamples(buffer, samplesOut, chan->CurrentGains, chan->TargetGains, samplesToDo, 0u); + const size_t outidx{chan->mTargetChannel}; + if(outidx != INVALID_CHANNEL_INDEX) + { + const al::span<const float> inbuf{input.data(), samplesToDo}; + DualBiquad{chan->mFilter[0], chan->mFilter[1]}.process(inbuf, buffer.begin()); + DualBiquad{chan->mFilter[2], chan->mFilter[3]}.process(buffer, buffer.begin()); + + MixSamples(buffer, {&samplesOut[outidx], 1}, &chan->mCurrentGain, &chan->mTargetGain, + samplesToDo, 0u); + } ++chan; } } diff --git a/alc/effects/modulator.cpp b/alc/effects/modulator.cpp index c854f1e9..29009247 100644 --- a/alc/effects/modulator.cpp +++ b/alc/effects/modulator.cpp @@ -84,10 +84,12 @@ struct ModulatorState final : public EffectState { uint mStep{1}; struct { - BiquadFilter Filter; + uint mTargetChannel{INVALID_CHANNEL_INDEX}; - float CurrentGains[MaxAmbiChannels]{}; - float TargetGains[MaxAmbiChannels]{}; + BiquadFilter mFilter; + + float mCurrentGain{}; + float mTargetGain{}; } mChans[MaxAmbiChannels]; @@ -104,8 +106,9 @@ void ModulatorState::deviceUpdate(const DeviceBase*, const Buffer&) { for(auto &e : mChans) { - e.Filter.clear(); - std::fill(std::begin(e.CurrentGains), std::end(e.CurrentGains), 0.0f); + e.mTargetChannel = INVALID_CHANNEL_INDEX; + e.mFilter.clear(); + e.mCurrentGain = 0.0f; } } @@ -129,14 +132,17 @@ void ModulatorState::update(const ContextBase *context, const EffectSlot *slot, float f0norm{props->Modulator.HighPassCutoff / static_cast<float>(device->Frequency)}; f0norm = clampf(f0norm, 1.0f/512.0f, 0.49f); /* Bandwidth value is constant in octaves. */ - mChans[0].Filter.setParamsFromBandwidth(BiquadType::HighPass, f0norm, 1.0f, 0.75f); + mChans[0].mFilter.setParamsFromBandwidth(BiquadType::HighPass, f0norm, 1.0f, 0.75f); for(size_t i{1u};i < slot->Wet.Buffer.size();++i) - mChans[i].Filter.copyParamsFrom(mChans[0].Filter); + mChans[i].mFilter.copyParamsFrom(mChans[0].mFilter); mOutTarget = target.Main->Buffer; - auto set_gains = [slot,target](auto &chan, al::span<const float,MaxAmbiChannels> coeffs) - { ComputePanGains(target.Main, coeffs.data(), slot->Gain, chan.TargetGains); }; - SetAmbiPanIdentity(std::begin(mChans), slot->Wet.Buffer.size(), set_gains); + auto set_channel = [this](size_t idx, uint target, float gain) + { + mChans[idx].mTargetChannel = target; + mChans[idx].mTargetGain = gain; + }; + target.Main->setAmbiMixParams(slot->Wet, slot->Gain, set_channel); } void ModulatorState::process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut) @@ -153,14 +159,18 @@ void ModulatorState::process(const size_t samplesToDo, const al::span<const Floa auto chandata = std::begin(mChans); for(const auto &input : samplesIn) { - alignas(16) float temps[MAX_UPDATE_SAMPLES]; - - chandata->Filter.process({&input[base], td}, temps); - for(size_t i{0u};i < td;i++) - temps[i] *= modsamples[i]; - - MixSamples({temps, td}, samplesOut, chandata->CurrentGains, chandata->TargetGains, - samplesToDo-base, base); + const size_t outidx{chandata->mTargetChannel}; + if(outidx != INVALID_CHANNEL_INDEX) + { + alignas(16) float temps[MAX_UPDATE_SAMPLES]; + + chandata->mFilter.process({&input[base], td}, temps); + for(size_t i{0u};i < td;i++) + temps[i] *= modsamples[i]; + + MixSamples({temps, td}, {&samplesOut[outidx], 1}, &chandata->mCurrentGain, + &chandata->mTargetGain, samplesToDo-base, base); + } ++chandata; } diff --git a/alc/effects/vmorpher.cpp b/alc/effects/vmorpher.cpp index 9834c335..869c004e 100644 --- a/alc/effects/vmorpher.cpp +++ b/alc/effects/vmorpher.cpp @@ -143,12 +143,14 @@ struct FormantFilter struct VmorpherState final : public EffectState { struct { + uint mTargetChannel{INVALID_CHANNEL_INDEX}; + /* Effect parameters */ - FormantFilter Formants[NUM_FILTERS][NUM_FORMANTS]; + FormantFilter mFormants[NUM_FILTERS][NUM_FORMANTS]; /* Effect gains for each channel */ - float CurrentGains[MaxAmbiChannels]{}; - float TargetGains[MaxAmbiChannels]{}; + float mCurrentGain{}; + float mTargetGain{}; } mChans[MaxAmbiChannels]; void (*mGetSamples)(float*RESTRICT, uint, const uint, size_t){}; @@ -229,11 +231,12 @@ void VmorpherState::deviceUpdate(const DeviceBase*, const Buffer&) { for(auto &e : mChans) { - std::for_each(std::begin(e.Formants[VOWEL_A_INDEX]), std::end(e.Formants[VOWEL_A_INDEX]), + e.mTargetChannel = INVALID_CHANNEL_INDEX; + std::for_each(std::begin(e.mFormants[VOWEL_A_INDEX]), std::end(e.mFormants[VOWEL_A_INDEX]), std::mem_fn(&FormantFilter::clear)); - std::for_each(std::begin(e.Formants[VOWEL_B_INDEX]), std::end(e.Formants[VOWEL_B_INDEX]), + std::for_each(std::begin(e.mFormants[VOWEL_B_INDEX]), std::end(e.mFormants[VOWEL_B_INDEX]), std::mem_fn(&FormantFilter::clear)); - std::fill(std::begin(e.CurrentGains), std::end(e.CurrentGains), 0.0f); + e.mCurrentGain = 0.0f; } } @@ -265,14 +268,17 @@ void VmorpherState::update(const ContextBase *context, const EffectSlot *slot, /* Copy the filter coefficients to the input channels. */ for(size_t i{0u};i < slot->Wet.Buffer.size();++i) { - std::copy(vowelA.begin(), vowelA.end(), std::begin(mChans[i].Formants[VOWEL_A_INDEX])); - std::copy(vowelB.begin(), vowelB.end(), std::begin(mChans[i].Formants[VOWEL_B_INDEX])); + std::copy(vowelA.begin(), vowelA.end(), std::begin(mChans[i].mFormants[VOWEL_A_INDEX])); + std::copy(vowelB.begin(), vowelB.end(), std::begin(mChans[i].mFormants[VOWEL_B_INDEX])); } mOutTarget = target.Main->Buffer; - auto set_gains = [slot,target](auto &chan, al::span<const float,MaxAmbiChannels> coeffs) - { ComputePanGains(target.Main, coeffs.data(), slot->Gain, chan.TargetGains); }; - SetAmbiPanIdentity(std::begin(mChans), slot->Wet.Buffer.size(), set_gains); + auto set_channel = [this](size_t idx, uint target, float gain) + { + mChans[idx].mTargetChannel = target; + mChans[idx].mTargetGain = gain; + }; + target.Main->setAmbiMixParams(slot->Wet, slot->Gain, set_channel); } void VmorpherState::process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut) @@ -291,8 +297,15 @@ void VmorpherState::process(const size_t samplesToDo, const al::span<const Float auto chandata = std::begin(mChans); for(const auto &input : samplesIn) { - auto& vowelA = chandata->Formants[VOWEL_A_INDEX]; - auto& vowelB = chandata->Formants[VOWEL_B_INDEX]; + const size_t outidx{chandata->mTargetChannel}; + if(outidx == INVALID_CHANNEL_INDEX) + { + ++chandata; + continue; + } + + auto& vowelA = chandata->mFormants[VOWEL_A_INDEX]; + auto& vowelB = chandata->mFormants[VOWEL_B_INDEX]; /* Process first vowel. */ std::fill_n(std::begin(mSampleBufferA), td, 0.0f); @@ -313,8 +326,8 @@ void VmorpherState::process(const size_t samplesToDo, const al::span<const Float blended[i] = lerpf(mSampleBufferA[i], mSampleBufferB[i], mLfo[i]); /* Now, mix the processed sound data to the output. */ - MixSamples({blended, td}, samplesOut, chandata->CurrentGains, chandata->TargetGains, - samplesToDo-base, base); + MixSamples({blended, td}, {&samplesOut[outidx], 1}, &chandata->mCurrentGain, + &chandata->mTargetGain, samplesToDo-base, base); ++chandata; } |