diff options
-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 | ||||
-rw-r--r-- | core/device.h | 36 | ||||
-rw-r--r-- | core/mixer.h | 18 |
7 files changed, 173 insertions, 104 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; } diff --git a/core/device.h b/core/device.h index 6317067e..7d50c54d 100644 --- a/core/device.h +++ b/core/device.h @@ -95,6 +95,8 @@ struct DistanceComp { }; +#define INVALID_CHANNEL_INDEX ~0u + struct BFChannelConfig { float Scale; uint Index; @@ -105,6 +107,37 @@ struct MixParams { std::array<BFChannelConfig,MaxAmbiChannels> AmbiMap{}; al::span<FloatBufferLine> Buffer; + + /** + * Helper to set an identity/pass-through panning for ambisonic mixing. The + * source is expected to be a 3D ACN/N3D ambisonic buffer, and for each + * channel [0...count), the given functor is called with the source channel + * index, destination channel index, and the gain for that channel. If the + * destination channel is INVALID_CHANNEL_INDEX, the given source channel + * is not used for output. + */ + template<typename F> + void setAmbiMixParams(const MixParams &inmix, const float gainbase, F func) const + { + const size_t numIn{inmix.Buffer.size()}; + const size_t numOut{Buffer.size()}; + for(size_t i{0};i < numIn;++i) + { + auto idx = INVALID_CHANNEL_INDEX; + auto gain = 0.0f; + + for(size_t j{0};j < numOut;++j) + { + if(AmbiMap[j].Index == inmix.AmbiMap[i].Index) + { + idx = static_cast<uint>(j); + gain = AmbiMap[j].Scale * gainbase; + break; + } + } + func(i, idx, gain); + } + } }; struct RealMixParams { @@ -303,13 +336,10 @@ private: uint renderSamples(const uint numSamples); }; - /* Must be less than 15 characters (16 including terminating null) for * compatibility with pthread_setname_np limitations. */ #define MIXER_THREAD_NAME "alsoft-mixer" #define RECORD_THREAD_NAME "alsoft-record" -#define INVALID_CHANNEL_INDEX ~0u - #endif /* CORE_DEVICE_H */ diff --git a/core/mixer.h b/core/mixer.h index 8624996f..66bcb170 100644 --- a/core/mixer.h +++ b/core/mixer.h @@ -92,22 +92,4 @@ inline std::array<float,MaxAmbiChannels> CalcAngleCoeffs(const float azimuth, void ComputePanGains(const MixParams *mix, const float*RESTRICT coeffs, const float ingain, const al::span<float,MaxAmbiChannels> gains); - -/** Helper to set an identity/pass-through panning for ambisonic mixing (3D input). */ -template<typename T, typename I, typename F> -auto SetAmbiPanIdentity(T iter, I count, F func) -> std::enable_if_t<std::is_integral<I>::value> -{ - if(count < 1) return; - - std::array<float,MaxAmbiChannels> coeffs{{1.0f}}; - func(*iter, coeffs); - ++iter; - for(I i{1};i < count;++i,++iter) - { - coeffs[i-1] = 0.0f; - coeffs[i ] = 1.0f; - func(*iter, coeffs); - } -} - #endif /* CORE_MIXER_H */ |