aboutsummaryrefslogtreecommitdiffstats
path: root/alc/effects
diff options
context:
space:
mode:
Diffstat (limited to 'alc/effects')
-rw-r--r--alc/effects/autowah.cpp298
-rw-r--r--alc/effects/base.h196
-rw-r--r--alc/effects/chorus.cpp538
-rw-r--r--alc/effects/compressor.cpp222
-rw-r--r--alc/effects/dedicated.cpp159
-rw-r--r--alc/effects/distortion.cpp269
-rw-r--r--alc/effects/echo.cpp271
-rw-r--r--alc/effects/equalizer.cpp337
-rw-r--r--alc/effects/fshifter.cpp301
-rw-r--r--alc/effects/modulator.cpp279
-rw-r--r--alc/effects/null.cpp164
-rw-r--r--alc/effects/pshifter.cpp405
-rw-r--r--alc/effects/reverb.cpp2102
-rw-r--r--alc/effects/vmorpher.cpp430
14 files changed, 5971 insertions, 0 deletions
diff --git a/alc/effects/autowah.cpp b/alc/effects/autowah.cpp
new file mode 100644
index 00000000..96292636
--- /dev/null
+++ b/alc/effects/autowah.cpp
@@ -0,0 +1,298 @@
+/**
+ * OpenAL cross platform audio library
+ * Copyright (C) 2018 by Raul Herraiz.
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ * Or go to http://www.gnu.org/copyleft/lgpl.html
+ */
+
+#include "config.h"
+
+#include <cmath>
+#include <cstdlib>
+
+#include <algorithm>
+
+#include "alcmain.h"
+#include "alcontext.h"
+#include "alAuxEffectSlot.h"
+#include "alError.h"
+#include "alu.h"
+#include "filters/biquad.h"
+#include "vecmat.h"
+
+namespace {
+
+#define MIN_FREQ 20.0f
+#define MAX_FREQ 2500.0f
+#define Q_FACTOR 5.0f
+
+struct ALautowahState final : public EffectState {
+ /* Effect parameters */
+ ALfloat mAttackRate;
+ ALfloat mReleaseRate;
+ ALfloat mResonanceGain;
+ ALfloat mPeakGain;
+ ALfloat mFreqMinNorm;
+ ALfloat mBandwidthNorm;
+ ALfloat mEnvDelay;
+
+ /* Filter components derived from the envelope. */
+ struct {
+ ALfloat cos_w0;
+ ALfloat alpha;
+ } mEnv[BUFFERSIZE];
+
+ struct {
+ /* Effect filters' history. */
+ struct {
+ ALfloat z1, z2;
+ } Filter;
+
+ /* Effect gains for each output channel */
+ ALfloat CurrentGains[MAX_OUTPUT_CHANNELS];
+ ALfloat TargetGains[MAX_OUTPUT_CHANNELS];
+ } mChans[MAX_AMBI_CHANNELS];
+
+ /* Effects buffers */
+ alignas(16) ALfloat mBufferOut[BUFFERSIZE];
+
+
+ ALboolean deviceUpdate(const ALCdevice *device) override;
+ void update(const ALCcontext *context, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target) override;
+ void process(const ALsizei samplesToDo, const FloatBufferLine *RESTRICT samplesIn, const ALsizei numInput, const al::span<FloatBufferLine> samplesOut) override;
+
+ DEF_NEWDEL(ALautowahState)
+};
+
+ALboolean ALautowahState::deviceUpdate(const ALCdevice*)
+{
+ /* (Re-)initializing parameters and clear the buffers. */
+
+ mAttackRate = 1.0f;
+ mReleaseRate = 1.0f;
+ mResonanceGain = 10.0f;
+ mPeakGain = 4.5f;
+ mFreqMinNorm = 4.5e-4f;
+ mBandwidthNorm = 0.05f;
+ mEnvDelay = 0.0f;
+
+ for(auto &e : mEnv)
+ {
+ e.cos_w0 = 0.0f;
+ e.alpha = 0.0f;
+ }
+
+ 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;
+ }
+
+ return AL_TRUE;
+}
+
+void ALautowahState::update(const ALCcontext *context, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target)
+{
+ const ALCdevice *device{context->Device};
+
+ const ALfloat ReleaseTime{clampf(props->Autowah.ReleaseTime, 0.001f, 1.0f)};
+
+ mAttackRate = expf(-1.0f / (props->Autowah.AttackTime*device->Frequency));
+ mReleaseRate = expf(-1.0f / (ReleaseTime*device->Frequency));
+ /* 0-20dB Resonance Peak gain */
+ mResonanceGain = std::sqrt(std::log10(props->Autowah.Resonance)*10.0f / 3.0f);
+ mPeakGain = 1.0f - std::log10(props->Autowah.PeakGain/AL_AUTOWAH_MAX_PEAK_GAIN);
+ mFreqMinNorm = MIN_FREQ / device->Frequency;
+ mBandwidthNorm = (MAX_FREQ-MIN_FREQ) / device->Frequency;
+
+ mOutTarget = target.Main->Buffer;
+ for(size_t i{0u};i < slot->Wet.Buffer.size();++i)
+ {
+ auto coeffs = GetAmbiIdentityRow(i);
+ ComputePanGains(target.Main, coeffs.data(), slot->Params.Gain, mChans[i].TargetGains);
+ }
+}
+
+void ALautowahState::process(const ALsizei samplesToDo, const FloatBufferLine *RESTRICT samplesIn, const ALsizei numInput, const al::span<FloatBufferLine> samplesOut)
+{
+ const ALfloat attack_rate = mAttackRate;
+ const ALfloat release_rate = mReleaseRate;
+ const ALfloat res_gain = mResonanceGain;
+ const ALfloat peak_gain = mPeakGain;
+ const ALfloat freq_min = mFreqMinNorm;
+ const ALfloat bandwidth = mBandwidthNorm;
+
+ ALfloat env_delay{mEnvDelay};
+ for(ALsizei i{0};i < samplesToDo;i++)
+ {
+ ALfloat w0, sample, a;
+
+ /* Envelope follower described on the book: Audio Effects, Theory,
+ * Implementation and Application.
+ */
+ sample = peak_gain * std::fabs(samplesIn[0][i]);
+ a = (sample > env_delay) ? attack_rate : release_rate;
+ env_delay = lerp(sample, env_delay, a);
+
+ /* Calculate the cos and alpha components for this sample's filter. */
+ w0 = minf((bandwidth*env_delay + freq_min), 0.46f) * al::MathDefs<float>::Tau();
+ mEnv[i].cos_w0 = cosf(w0);
+ mEnv[i].alpha = sinf(w0)/(2.0f * Q_FACTOR);
+ }
+ mEnvDelay = env_delay;
+
+ ASSUME(numInput > 0);
+ for(ALsizei c{0};c < numInput;++c)
+ {
+ /* 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.
+ */
+ ALfloat z1{mChans[c].Filter.z1};
+ ALfloat z2{mChans[c].Filter.z2};
+
+ for(ALsizei i{0};i < samplesToDo;i++)
+ {
+ const ALfloat alpha = mEnv[i].alpha;
+ const ALfloat cos_w0 = mEnv[i].cos_w0;
+ ALfloat input, output;
+ ALfloat a[3], b[3];
+
+ b[0] = 1.0f + alpha*res_gain;
+ b[1] = -2.0f * cos_w0;
+ b[2] = 1.0f - alpha*res_gain;
+ a[0] = 1.0f + alpha/res_gain;
+ a[1] = -2.0f * cos_w0;
+ a[2] = 1.0f - alpha/res_gain;
+
+ input = samplesIn[c][i];
+ output = input*(b[0]/a[0]) + z1;
+ z1 = input*(b[1]/a[0]) - output*(a[1]/a[0]) + z2;
+ z2 = input*(b[2]/a[0]) - output*(a[2]/a[0]);
+ mBufferOut[i] = output;
+ }
+ mChans[c].Filter.z1 = z1;
+ mChans[c].Filter.z2 = z2;
+
+ /* Now, mix the processed sound data to the output. */
+ MixSamples(mBufferOut, samplesOut, mChans[c].CurrentGains, mChans[c].TargetGains,
+ samplesToDo, 0, samplesToDo);
+ }
+}
+
+
+void ALautowah_setParamf(EffectProps *props, ALCcontext *context, ALenum param, ALfloat val)
+{
+ switch(param)
+ {
+ case AL_AUTOWAH_ATTACK_TIME:
+ if(!(val >= AL_AUTOWAH_MIN_ATTACK_TIME && val <= AL_AUTOWAH_MAX_ATTACK_TIME))
+ SETERR_RETURN(context, AL_INVALID_VALUE,,"Autowah attack time out of range");
+ props->Autowah.AttackTime = val;
+ break;
+
+ case AL_AUTOWAH_RELEASE_TIME:
+ if(!(val >= AL_AUTOWAH_MIN_RELEASE_TIME && val <= AL_AUTOWAH_MAX_RELEASE_TIME))
+ SETERR_RETURN(context, AL_INVALID_VALUE,,"Autowah release time out of range");
+ props->Autowah.ReleaseTime = val;
+ break;
+
+ case AL_AUTOWAH_RESONANCE:
+ if(!(val >= AL_AUTOWAH_MIN_RESONANCE && val <= AL_AUTOWAH_MAX_RESONANCE))
+ SETERR_RETURN(context, AL_INVALID_VALUE,,"Autowah resonance out of range");
+ props->Autowah.Resonance = val;
+ break;
+
+ case AL_AUTOWAH_PEAK_GAIN:
+ if(!(val >= AL_AUTOWAH_MIN_PEAK_GAIN && val <= AL_AUTOWAH_MAX_PEAK_GAIN))
+ SETERR_RETURN(context, AL_INVALID_VALUE,,"Autowah peak gain out of range");
+ props->Autowah.PeakGain = val;
+ break;
+
+ default:
+ alSetError(context, AL_INVALID_ENUM, "Invalid autowah float property 0x%04x", param);
+ }
+}
+void ALautowah_setParamfv(EffectProps *props, ALCcontext *context, ALenum param, const ALfloat *vals)
+{ ALautowah_setParamf(props, context, param, vals[0]); }
+
+void ALautowah_setParami(EffectProps*, ALCcontext *context, ALenum param, ALint)
+{ alSetError(context, AL_INVALID_ENUM, "Invalid autowah integer property 0x%04x", param); }
+void ALautowah_setParamiv(EffectProps*, ALCcontext *context, ALenum param, const ALint*)
+{ alSetError(context, AL_INVALID_ENUM, "Invalid autowah integer vector property 0x%04x", param); }
+
+void ALautowah_getParamf(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *val)
+{
+ switch(param)
+ {
+ case AL_AUTOWAH_ATTACK_TIME:
+ *val = props->Autowah.AttackTime;
+ break;
+
+ case AL_AUTOWAH_RELEASE_TIME:
+ *val = props->Autowah.ReleaseTime;
+ break;
+
+ case AL_AUTOWAH_RESONANCE:
+ *val = props->Autowah.Resonance;
+ break;
+
+ case AL_AUTOWAH_PEAK_GAIN:
+ *val = props->Autowah.PeakGain;
+ break;
+
+ default:
+ alSetError(context, AL_INVALID_ENUM, "Invalid autowah float property 0x%04x", param);
+ }
+
+}
+void ALautowah_getParamfv(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *vals)
+{ ALautowah_getParamf(props, context, param, vals); }
+
+void ALautowah_getParami(const EffectProps*, ALCcontext *context, ALenum param, ALint*)
+{ alSetError(context, AL_INVALID_ENUM, "Invalid autowah integer property 0x%04x", param); }
+void ALautowah_getParamiv(const EffectProps*, ALCcontext *context, ALenum param, ALint*)
+{ alSetError(context, AL_INVALID_ENUM, "Invalid autowah integer vector property 0x%04x", param); }
+
+DEFINE_ALEFFECT_VTABLE(ALautowah);
+
+
+struct AutowahStateFactory final : public EffectStateFactory {
+ EffectState *create() override { return new ALautowahState{}; }
+ EffectProps getDefaultProps() const noexcept override;
+ const EffectVtable *getEffectVtable() const noexcept override { return &ALautowah_vtable; }
+};
+
+EffectProps AutowahStateFactory::getDefaultProps() const noexcept
+{
+ EffectProps props{};
+ props.Autowah.AttackTime = AL_AUTOWAH_DEFAULT_ATTACK_TIME;
+ props.Autowah.ReleaseTime = AL_AUTOWAH_DEFAULT_RELEASE_TIME;
+ props.Autowah.Resonance = AL_AUTOWAH_DEFAULT_RESONANCE;
+ props.Autowah.PeakGain = AL_AUTOWAH_DEFAULT_PEAK_GAIN;
+ return props;
+}
+
+} // namespace
+
+EffectStateFactory *AutowahStateFactory_getFactory()
+{
+ static AutowahStateFactory AutowahFactory{};
+ return &AutowahFactory;
+}
diff --git a/alc/effects/base.h b/alc/effects/base.h
new file mode 100644
index 00000000..4f48de22
--- /dev/null
+++ b/alc/effects/base.h
@@ -0,0 +1,196 @@
+#ifndef EFFECTS_BASE_H
+#define EFFECTS_BASE_H
+
+#include "alcmain.h"
+#include "almalloc.h"
+#include "alspan.h"
+#include "atomic.h"
+
+
+struct ALeffectslot;
+
+
+union EffectProps {
+ struct {
+ // Shared Reverb Properties
+ ALfloat Density;
+ ALfloat Diffusion;
+ ALfloat Gain;
+ ALfloat GainHF;
+ ALfloat DecayTime;
+ ALfloat DecayHFRatio;
+ ALfloat ReflectionsGain;
+ ALfloat ReflectionsDelay;
+ ALfloat LateReverbGain;
+ ALfloat LateReverbDelay;
+ ALfloat AirAbsorptionGainHF;
+ ALfloat RoomRolloffFactor;
+ ALboolean DecayHFLimit;
+
+ // Additional EAX Reverb Properties
+ ALfloat GainLF;
+ ALfloat DecayLFRatio;
+ ALfloat ReflectionsPan[3];
+ ALfloat LateReverbPan[3];
+ ALfloat EchoTime;
+ ALfloat EchoDepth;
+ ALfloat ModulationTime;
+ ALfloat ModulationDepth;
+ ALfloat HFReference;
+ ALfloat LFReference;
+ } Reverb;
+
+ struct {
+ ALfloat AttackTime;
+ ALfloat ReleaseTime;
+ ALfloat Resonance;
+ ALfloat PeakGain;
+ } Autowah;
+
+ struct {
+ ALint Waveform;
+ ALint Phase;
+ ALfloat Rate;
+ ALfloat Depth;
+ ALfloat Feedback;
+ ALfloat Delay;
+ } Chorus; /* Also Flanger */
+
+ struct {
+ ALboolean OnOff;
+ } Compressor;
+
+ struct {
+ ALfloat Edge;
+ ALfloat Gain;
+ ALfloat LowpassCutoff;
+ ALfloat EQCenter;
+ ALfloat EQBandwidth;
+ } Distortion;
+
+ struct {
+ ALfloat Delay;
+ ALfloat LRDelay;
+
+ ALfloat Damping;
+ ALfloat Feedback;
+
+ ALfloat Spread;
+ } Echo;
+
+ struct {
+ ALfloat LowCutoff;
+ ALfloat LowGain;
+ ALfloat Mid1Center;
+ ALfloat Mid1Gain;
+ ALfloat Mid1Width;
+ ALfloat Mid2Center;
+ ALfloat Mid2Gain;
+ ALfloat Mid2Width;
+ ALfloat HighCutoff;
+ ALfloat HighGain;
+ } Equalizer;
+
+ struct {
+ ALfloat Frequency;
+ ALint LeftDirection;
+ ALint RightDirection;
+ } Fshifter;
+
+ struct {
+ ALfloat Frequency;
+ ALfloat HighPassCutoff;
+ ALint Waveform;
+ } Modulator;
+
+ struct {
+ ALint CoarseTune;
+ ALint FineTune;
+ } Pshifter;
+
+ struct {
+ ALfloat Rate;
+ ALint PhonemeA;
+ ALint PhonemeB;
+ ALint PhonemeACoarseTuning;
+ ALint PhonemeBCoarseTuning;
+ ALint Waveform;
+ } Vmorpher;
+
+ struct {
+ ALfloat Gain;
+ } Dedicated;
+};
+
+
+struct EffectVtable {
+ void (*const setParami)(EffectProps *props, ALCcontext *context, ALenum param, ALint val);
+ void (*const setParamiv)(EffectProps *props, ALCcontext *context, ALenum param, const ALint *vals);
+ void (*const setParamf)(EffectProps *props, ALCcontext *context, ALenum param, ALfloat val);
+ void (*const setParamfv)(EffectProps *props, ALCcontext *context, ALenum param, const ALfloat *vals);
+
+ void (*const getParami)(const EffectProps *props, ALCcontext *context, ALenum param, ALint *val);
+ void (*const getParamiv)(const EffectProps *props, ALCcontext *context, ALenum param, ALint *vals);
+ void (*const getParamf)(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *val);
+ void (*const getParamfv)(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *vals);
+};
+
+#define DEFINE_ALEFFECT_VTABLE(T) \
+const EffectVtable T##_vtable = { \
+ T##_setParami, T##_setParamiv, \
+ T##_setParamf, T##_setParamfv, \
+ T##_getParami, T##_getParamiv, \
+ T##_getParamf, T##_getParamfv, \
+}
+
+
+struct EffectTarget {
+ MixParams *Main;
+ RealMixParams *RealOut;
+};
+
+struct EffectState {
+ RefCount mRef{1u};
+
+ al::span<FloatBufferLine> mOutTarget;
+
+
+ virtual ~EffectState() = default;
+
+ virtual ALboolean deviceUpdate(const ALCdevice *device) = 0;
+ virtual void update(const ALCcontext *context, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target) = 0;
+ virtual void process(const ALsizei samplesToDo, const FloatBufferLine *RESTRICT samplesIn, const ALsizei numInput, const al::span<FloatBufferLine> samplesOut) = 0;
+
+ void IncRef() noexcept;
+ void DecRef() noexcept;
+};
+
+
+struct EffectStateFactory {
+ virtual ~EffectStateFactory() { }
+
+ virtual EffectState *create() = 0;
+ virtual EffectProps getDefaultProps() const noexcept = 0;
+ virtual const EffectVtable *getEffectVtable() const noexcept = 0;
+};
+
+
+EffectStateFactory *NullStateFactory_getFactory(void);
+EffectStateFactory *ReverbStateFactory_getFactory(void);
+EffectStateFactory *StdReverbStateFactory_getFactory(void);
+EffectStateFactory *AutowahStateFactory_getFactory(void);
+EffectStateFactory *ChorusStateFactory_getFactory(void);
+EffectStateFactory *CompressorStateFactory_getFactory(void);
+EffectStateFactory *DistortionStateFactory_getFactory(void);
+EffectStateFactory *EchoStateFactory_getFactory(void);
+EffectStateFactory *EqualizerStateFactory_getFactory(void);
+EffectStateFactory *FlangerStateFactory_getFactory(void);
+EffectStateFactory *FshifterStateFactory_getFactory(void);
+EffectStateFactory *ModulatorStateFactory_getFactory(void);
+EffectStateFactory *PshifterStateFactory_getFactory(void);
+EffectStateFactory* VmorpherStateFactory_getFactory(void);
+
+EffectStateFactory *DedicatedStateFactory_getFactory(void);
+
+
+#endif /* EFFECTS_BASE_H */
diff --git a/alc/effects/chorus.cpp b/alc/effects/chorus.cpp
new file mode 100644
index 00000000..d475b57a
--- /dev/null
+++ b/alc/effects/chorus.cpp
@@ -0,0 +1,538 @@
+/**
+ * OpenAL cross platform audio library
+ * Copyright (C) 2013 by Mike Gorchak
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ * Or go to http://www.gnu.org/copyleft/lgpl.html
+ */
+
+#include "config.h"
+
+#include <algorithm>
+#include <climits>
+#include <cmath>
+#include <cstdlib>
+#include <iterator>
+
+#include "AL/al.h"
+#include "AL/alc.h"
+#include "AL/efx.h"
+
+#include "alAuxEffectSlot.h"
+#include "alcmain.h"
+#include "alError.h"
+#include "alcontext.h"
+#include "almalloc.h"
+#include "alnumeric.h"
+#include "alspan.h"
+#include "alu.h"
+#include "ambidefs.h"
+#include "effects/base.h"
+#include "math_defs.h"
+#include "opthelpers.h"
+#include "vector.h"
+
+
+namespace {
+
+static_assert(AL_CHORUS_WAVEFORM_SINUSOID == AL_FLANGER_WAVEFORM_SINUSOID, "Chorus/Flanger waveform value mismatch");
+static_assert(AL_CHORUS_WAVEFORM_TRIANGLE == AL_FLANGER_WAVEFORM_TRIANGLE, "Chorus/Flanger waveform value mismatch");
+
+enum class WaveForm {
+ Sinusoid,
+ Triangle
+};
+
+void GetTriangleDelays(ALint *delays, const ALsizei start_offset, const ALsizei lfo_range,
+ const ALfloat lfo_scale, const ALfloat depth, const ALsizei delay, const ALsizei todo)
+{
+ ASSUME(start_offset >= 0);
+ ASSUME(lfo_range > 0);
+ ASSUME(todo > 0);
+
+ ALsizei offset{start_offset};
+ auto gen_lfo = [&offset,lfo_range,lfo_scale,depth,delay]() -> ALint
+ {
+ offset = (offset+1)%lfo_range;
+ return fastf2i((1.0f - std::abs(2.0f - lfo_scale*offset)) * depth) + delay;
+ };
+ std::generate_n(delays, todo, gen_lfo);
+}
+
+void GetSinusoidDelays(ALint *delays, const ALsizei start_offset, const ALsizei lfo_range,
+ const ALfloat lfo_scale, const ALfloat depth, const ALsizei delay, const ALsizei todo)
+{
+ ASSUME(start_offset >= 0);
+ ASSUME(lfo_range > 0);
+ ASSUME(todo > 0);
+
+ ALsizei offset{start_offset};
+ auto gen_lfo = [&offset,lfo_range,lfo_scale,depth,delay]() -> ALint
+ {
+ ASSUME(delay >= 0);
+ offset = (offset+1)%lfo_range;
+ return fastf2i(std::sin(lfo_scale*offset) * depth) + delay;
+ };
+ std::generate_n(delays, todo, gen_lfo);
+}
+
+struct ChorusState final : public EffectState {
+ al::vector<ALfloat,16> mSampleBuffer;
+ ALsizei mOffset{0};
+
+ ALsizei mLfoOffset{0};
+ ALsizei mLfoRange{1};
+ ALfloat mLfoScale{0.0f};
+ ALint mLfoDisp{0};
+
+ /* Gains for left and right sides */
+ struct {
+ ALfloat Current[MAX_OUTPUT_CHANNELS]{};
+ ALfloat Target[MAX_OUTPUT_CHANNELS]{};
+ } mGains[2];
+
+ /* effect parameters */
+ WaveForm mWaveform{};
+ ALint mDelay{0};
+ ALfloat mDepth{0.0f};
+ ALfloat mFeedback{0.0f};
+
+
+ ALboolean deviceUpdate(const ALCdevice *device) override;
+ void update(const ALCcontext *context, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target) override;
+ void process(const ALsizei samplesToDo, const FloatBufferLine *RESTRICT samplesIn, const ALsizei numInput, const al::span<FloatBufferLine> samplesOut) override;
+
+ DEF_NEWDEL(ChorusState)
+};
+
+ALboolean ChorusState::deviceUpdate(const ALCdevice *Device)
+{
+ const ALfloat max_delay = maxf(AL_CHORUS_MAX_DELAY, AL_FLANGER_MAX_DELAY);
+ size_t maxlen;
+
+ maxlen = NextPowerOf2(float2int(max_delay*2.0f*Device->Frequency) + 1u);
+ if(maxlen <= 0) return AL_FALSE;
+
+ if(maxlen != mSampleBuffer.size())
+ {
+ mSampleBuffer.resize(maxlen);
+ mSampleBuffer.shrink_to_fit();
+ }
+
+ std::fill(mSampleBuffer.begin(), mSampleBuffer.end(), 0.0f);
+ for(auto &e : mGains)
+ {
+ std::fill(std::begin(e.Current), std::end(e.Current), 0.0f);
+ std::fill(std::begin(e.Target), std::end(e.Target), 0.0f);
+ }
+
+ return AL_TRUE;
+}
+
+void ChorusState::update(const ALCcontext *Context, const ALeffectslot *Slot, const EffectProps *props, const EffectTarget target)
+{
+ static constexpr ALsizei mindelay = MAX_RESAMPLE_PADDING << FRACTIONBITS;
+
+ switch(props->Chorus.Waveform)
+ {
+ case AL_CHORUS_WAVEFORM_TRIANGLE:
+ mWaveform = WaveForm::Triangle;
+ break;
+ case AL_CHORUS_WAVEFORM_SINUSOID:
+ mWaveform = WaveForm::Sinusoid;
+ break;
+ }
+
+ /* The LFO depth is scaled to be relative to the sample delay. Clamp the
+ * delay and depth to allow enough padding for resampling.
+ */
+ const ALCdevice *device{Context->Device};
+ const auto frequency = static_cast<ALfloat>(device->Frequency);
+ mDelay = maxi(float2int(props->Chorus.Delay*frequency*FRACTIONONE + 0.5f), mindelay);
+ mDepth = minf(props->Chorus.Depth * mDelay, static_cast<ALfloat>(mDelay - mindelay));
+
+ mFeedback = props->Chorus.Feedback;
+
+ /* Gains for left and right sides */
+ ALfloat coeffs[2][MAX_AMBI_CHANNELS];
+ CalcDirectionCoeffs({-1.0f, 0.0f, 0.0f}, 0.0f, coeffs[0]);
+ CalcDirectionCoeffs({ 1.0f, 0.0f, 0.0f}, 0.0f, coeffs[1]);
+
+ mOutTarget = target.Main->Buffer;
+ ComputePanGains(target.Main, coeffs[0], Slot->Params.Gain, mGains[0].Target);
+ ComputePanGains(target.Main, coeffs[1], Slot->Params.Gain, mGains[1].Target);
+
+ ALfloat rate{props->Chorus.Rate};
+ if(!(rate > 0.0f))
+ {
+ mLfoOffset = 0;
+ mLfoRange = 1;
+ mLfoScale = 0.0f;
+ mLfoDisp = 0;
+ }
+ else
+ {
+ /* Calculate LFO coefficient (number of samples per cycle). Limit the
+ * max range to avoid overflow when calculating the displacement.
+ */
+ ALsizei lfo_range = float2int(minf(frequency/rate + 0.5f, static_cast<ALfloat>(INT_MAX/360 - 180)));
+
+ mLfoOffset = float2int(static_cast<ALfloat>(mLfoOffset)/mLfoRange*lfo_range + 0.5f) % lfo_range;
+ mLfoRange = lfo_range;
+ switch(mWaveform)
+ {
+ case WaveForm::Triangle:
+ mLfoScale = 4.0f / mLfoRange;
+ break;
+ case WaveForm::Sinusoid:
+ mLfoScale = al::MathDefs<float>::Tau() / mLfoRange;
+ break;
+ }
+
+ /* Calculate lfo phase displacement */
+ ALint phase{props->Chorus.Phase};
+ if(phase < 0) phase = 360 + phase;
+ mLfoDisp = (mLfoRange*phase + 180) / 360;
+ }
+}
+
+void ChorusState::process(const ALsizei samplesToDo, const FloatBufferLine *RESTRICT samplesIn, const ALsizei /*numInput*/, const al::span<FloatBufferLine> samplesOut)
+{
+ const auto bufmask = static_cast<ALsizei>(mSampleBuffer.size()-1);
+ const ALfloat feedback{mFeedback};
+ const ALsizei avgdelay{(mDelay + (FRACTIONONE>>1)) >> FRACTIONBITS};
+ ALfloat *RESTRICT delaybuf{mSampleBuffer.data()};
+ ALsizei offset{mOffset};
+
+ for(ALsizei base{0};base < samplesToDo;)
+ {
+ const ALsizei todo = mini(256, samplesToDo-base);
+ ALint moddelays[2][256];
+ alignas(16) ALfloat temps[2][256];
+
+ if(mWaveform == WaveForm::Sinusoid)
+ {
+ GetSinusoidDelays(moddelays[0], mLfoOffset, mLfoRange, mLfoScale, mDepth, mDelay,
+ todo);
+ GetSinusoidDelays(moddelays[1], (mLfoOffset+mLfoDisp)%mLfoRange, mLfoRange, mLfoScale,
+ mDepth, mDelay, todo);
+ }
+ else /*if(mWaveform == WaveForm::Triangle)*/
+ {
+ GetTriangleDelays(moddelays[0], mLfoOffset, mLfoRange, mLfoScale, mDepth, mDelay,
+ todo);
+ GetTriangleDelays(moddelays[1], (mLfoOffset+mLfoDisp)%mLfoRange, mLfoRange, mLfoScale,
+ mDepth, mDelay, todo);
+ }
+ mLfoOffset = (mLfoOffset+todo) % mLfoRange;
+
+ for(ALsizei i{0};i < todo;i++)
+ {
+ // Feed the buffer's input first (necessary for delays < 1).
+ delaybuf[offset&bufmask] = samplesIn[0][base+i];
+
+ // Tap for the left output.
+ ALint delay{offset - (moddelays[0][i]>>FRACTIONBITS)};
+ ALfloat mu{(moddelays[0][i]&FRACTIONMASK) * (1.0f/FRACTIONONE)};
+ temps[0][i] = cubic(delaybuf[(delay+1) & bufmask], delaybuf[(delay ) & bufmask],
+ delaybuf[(delay-1) & bufmask], delaybuf[(delay-2) & bufmask],
+ mu);
+
+ // Tap for the right output.
+ delay = offset - (moddelays[1][i]>>FRACTIONBITS);
+ mu = (moddelays[1][i]&FRACTIONMASK) * (1.0f/FRACTIONONE);
+ temps[1][i] = cubic(delaybuf[(delay+1) & bufmask], delaybuf[(delay ) & bufmask],
+ delaybuf[(delay-1) & bufmask], delaybuf[(delay-2) & bufmask],
+ mu);
+
+ // Accumulate feedback from the average delay of the taps.
+ delaybuf[offset&bufmask] += delaybuf[(offset-avgdelay) & bufmask] * feedback;
+ offset++;
+ }
+
+ for(ALsizei c{0};c < 2;c++)
+ MixSamples(temps[c], samplesOut, mGains[c].Current, mGains[c].Target, samplesToDo-base,
+ base, todo);
+
+ base += todo;
+ }
+
+ mOffset = offset;
+}
+
+
+void Chorus_setParami(EffectProps *props, ALCcontext *context, ALenum param, ALint val)
+{
+ switch(param)
+ {
+ case AL_CHORUS_WAVEFORM:
+ if(!(val >= AL_CHORUS_MIN_WAVEFORM && val <= AL_CHORUS_MAX_WAVEFORM))
+ SETERR_RETURN(context, AL_INVALID_VALUE,, "Invalid chorus waveform");
+ props->Chorus.Waveform = val;
+ break;
+
+ case AL_CHORUS_PHASE:
+ if(!(val >= AL_CHORUS_MIN_PHASE && val <= AL_CHORUS_MAX_PHASE))
+ SETERR_RETURN(context, AL_INVALID_VALUE,, "Chorus phase out of range");
+ props->Chorus.Phase = val;
+ break;
+
+ default:
+ alSetError(context, AL_INVALID_ENUM, "Invalid chorus integer property 0x%04x", param);
+ }
+}
+void Chorus_setParamiv(EffectProps *props, ALCcontext *context, ALenum param, const ALint *vals)
+{ Chorus_setParami(props, context, param, vals[0]); }
+void Chorus_setParamf(EffectProps *props, ALCcontext *context, ALenum param, ALfloat val)
+{
+ switch(param)
+ {
+ case AL_CHORUS_RATE:
+ if(!(val >= AL_CHORUS_MIN_RATE && val <= AL_CHORUS_MAX_RATE))
+ SETERR_RETURN(context, AL_INVALID_VALUE,, "Chorus rate out of range");
+ props->Chorus.Rate = val;
+ break;
+
+ case AL_CHORUS_DEPTH:
+ if(!(val >= AL_CHORUS_MIN_DEPTH && val <= AL_CHORUS_MAX_DEPTH))
+ SETERR_RETURN(context, AL_INVALID_VALUE,, "Chorus depth out of range");
+ props->Chorus.Depth = val;
+ break;
+
+ case AL_CHORUS_FEEDBACK:
+ if(!(val >= AL_CHORUS_MIN_FEEDBACK && val <= AL_CHORUS_MAX_FEEDBACK))
+ SETERR_RETURN(context, AL_INVALID_VALUE,, "Chorus feedback out of range");
+ props->Chorus.Feedback = val;
+ break;
+
+ case AL_CHORUS_DELAY:
+ if(!(val >= AL_CHORUS_MIN_DELAY && val <= AL_CHORUS_MAX_DELAY))
+ SETERR_RETURN(context, AL_INVALID_VALUE,, "Chorus delay out of range");
+ props->Chorus.Delay = val;
+ break;
+
+ default:
+ alSetError(context, AL_INVALID_ENUM, "Invalid chorus float property 0x%04x", param);
+ }
+}
+void Chorus_setParamfv(EffectProps *props, ALCcontext *context, ALenum param, const ALfloat *vals)
+{ Chorus_setParamf(props, context, param, vals[0]); }
+
+void Chorus_getParami(const EffectProps *props, ALCcontext *context, ALenum param, ALint *val)
+{
+ switch(param)
+ {
+ case AL_CHORUS_WAVEFORM:
+ *val = props->Chorus.Waveform;
+ break;
+
+ case AL_CHORUS_PHASE:
+ *val = props->Chorus.Phase;
+ break;
+
+ default:
+ alSetError(context, AL_INVALID_ENUM, "Invalid chorus integer property 0x%04x", param);
+ }
+}
+void Chorus_getParamiv(const EffectProps *props, ALCcontext *context, ALenum param, ALint *vals)
+{ Chorus_getParami(props, context, param, vals); }
+void Chorus_getParamf(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *val)
+{
+ switch(param)
+ {
+ case AL_CHORUS_RATE:
+ *val = props->Chorus.Rate;
+ break;
+
+ case AL_CHORUS_DEPTH:
+ *val = props->Chorus.Depth;
+ break;
+
+ case AL_CHORUS_FEEDBACK:
+ *val = props->Chorus.Feedback;
+ break;
+
+ case AL_CHORUS_DELAY:
+ *val = props->Chorus.Delay;
+ break;
+
+ default:
+ alSetError(context, AL_INVALID_ENUM, "Invalid chorus float property 0x%04x", param);
+ }
+}
+void Chorus_getParamfv(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *vals)
+{ Chorus_getParamf(props, context, param, vals); }
+
+DEFINE_ALEFFECT_VTABLE(Chorus);
+
+
+struct ChorusStateFactory final : public EffectStateFactory {
+ EffectState *create() override { return new ChorusState{}; }
+ EffectProps getDefaultProps() const noexcept override;
+ const EffectVtable *getEffectVtable() const noexcept override { return &Chorus_vtable; }
+};
+
+EffectProps ChorusStateFactory::getDefaultProps() const noexcept
+{
+ EffectProps props{};
+ props.Chorus.Waveform = AL_CHORUS_DEFAULT_WAVEFORM;
+ props.Chorus.Phase = AL_CHORUS_DEFAULT_PHASE;
+ props.Chorus.Rate = AL_CHORUS_DEFAULT_RATE;
+ props.Chorus.Depth = AL_CHORUS_DEFAULT_DEPTH;
+ props.Chorus.Feedback = AL_CHORUS_DEFAULT_FEEDBACK;
+ props.Chorus.Delay = AL_CHORUS_DEFAULT_DELAY;
+ return props;
+}
+
+
+void Flanger_setParami(EffectProps *props, ALCcontext *context, ALenum param, ALint val)
+{
+ switch(param)
+ {
+ case AL_FLANGER_WAVEFORM:
+ if(!(val >= AL_FLANGER_MIN_WAVEFORM && val <= AL_FLANGER_MAX_WAVEFORM))
+ SETERR_RETURN(context, AL_INVALID_VALUE,, "Invalid flanger waveform");
+ props->Chorus.Waveform = val;
+ break;
+
+ case AL_FLANGER_PHASE:
+ if(!(val >= AL_FLANGER_MIN_PHASE && val <= AL_FLANGER_MAX_PHASE))
+ SETERR_RETURN(context, AL_INVALID_VALUE,, "Flanger phase out of range");
+ props->Chorus.Phase = val;
+ break;
+
+ default:
+ alSetError(context, AL_INVALID_ENUM, "Invalid flanger integer property 0x%04x", param);
+ }
+}
+void Flanger_setParamiv(EffectProps *props, ALCcontext *context, ALenum param, const ALint *vals)
+{ Flanger_setParami(props, context, param, vals[0]); }
+void Flanger_setParamf(EffectProps *props, ALCcontext *context, ALenum param, ALfloat val)
+{
+ switch(param)
+ {
+ case AL_FLANGER_RATE:
+ if(!(val >= AL_FLANGER_MIN_RATE && val <= AL_FLANGER_MAX_RATE))
+ SETERR_RETURN(context, AL_INVALID_VALUE,, "Flanger rate out of range");
+ props->Chorus.Rate = val;
+ break;
+
+ case AL_FLANGER_DEPTH:
+ if(!(val >= AL_FLANGER_MIN_DEPTH && val <= AL_FLANGER_MAX_DEPTH))
+ SETERR_RETURN(context, AL_INVALID_VALUE,, "Flanger depth out of range");
+ props->Chorus.Depth = val;
+ break;
+
+ case AL_FLANGER_FEEDBACK:
+ if(!(val >= AL_FLANGER_MIN_FEEDBACK && val <= AL_FLANGER_MAX_FEEDBACK))
+ SETERR_RETURN(context, AL_INVALID_VALUE,, "Flanger feedback out of range");
+ props->Chorus.Feedback = val;
+ break;
+
+ case AL_FLANGER_DELAY:
+ if(!(val >= AL_FLANGER_MIN_DELAY && val <= AL_FLANGER_MAX_DELAY))
+ SETERR_RETURN(context, AL_INVALID_VALUE,, "Flanger delay out of range");
+ props->Chorus.Delay = val;
+ break;
+
+ default:
+ alSetError(context, AL_INVALID_ENUM, "Invalid flanger float property 0x%04x", param);
+ }
+}
+void Flanger_setParamfv(EffectProps *props, ALCcontext *context, ALenum param, const ALfloat *vals)
+{ Flanger_setParamf(props, context, param, vals[0]); }
+
+void Flanger_getParami(const EffectProps *props, ALCcontext *context, ALenum param, ALint *val)
+{
+ switch(param)
+ {
+ case AL_FLANGER_WAVEFORM:
+ *val = props->Chorus.Waveform;
+ break;
+
+ case AL_FLANGER_PHASE:
+ *val = props->Chorus.Phase;
+ break;
+
+ default:
+ alSetError(context, AL_INVALID_ENUM, "Invalid flanger integer property 0x%04x", param);
+ }
+}
+void Flanger_getParamiv(const EffectProps *props, ALCcontext *context, ALenum param, ALint *vals)
+{ Flanger_getParami(props, context, param, vals); }
+void Flanger_getParamf(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *val)
+{
+ switch(param)
+ {
+ case AL_FLANGER_RATE:
+ *val = props->Chorus.Rate;
+ break;
+
+ case AL_FLANGER_DEPTH:
+ *val = props->Chorus.Depth;
+ break;
+
+ case AL_FLANGER_FEEDBACK:
+ *val = props->Chorus.Feedback;
+ break;
+
+ case AL_FLANGER_DELAY:
+ *val = props->Chorus.Delay;
+ break;
+
+ default:
+ alSetError(context, AL_INVALID_ENUM, "Invalid flanger float property 0x%04x", param);
+ }
+}
+void Flanger_getParamfv(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *vals)
+{ Flanger_getParamf(props, context, param, vals); }
+
+DEFINE_ALEFFECT_VTABLE(Flanger);
+
+
+/* Flanger is basically a chorus with a really short delay. They can both use
+ * the same processing functions, so piggyback flanger on the chorus functions.
+ */
+struct FlangerStateFactory final : public EffectStateFactory {
+ EffectState *create() override { return new ChorusState{}; }
+ EffectProps getDefaultProps() const noexcept override;
+ const EffectVtable *getEffectVtable() const noexcept override { return &Flanger_vtable; }
+};
+
+EffectProps FlangerStateFactory::getDefaultProps() const noexcept
+{
+ EffectProps props{};
+ props.Chorus.Waveform = AL_FLANGER_DEFAULT_WAVEFORM;
+ props.Chorus.Phase = AL_FLANGER_DEFAULT_PHASE;
+ props.Chorus.Rate = AL_FLANGER_DEFAULT_RATE;
+ props.Chorus.Depth = AL_FLANGER_DEFAULT_DEPTH;
+ props.Chorus.Feedback = AL_FLANGER_DEFAULT_FEEDBACK;
+ props.Chorus.Delay = AL_FLANGER_DEFAULT_DELAY;
+ return props;
+}
+
+} // namespace
+
+EffectStateFactory *ChorusStateFactory_getFactory()
+{
+ static ChorusStateFactory ChorusFactory{};
+ return &ChorusFactory;
+}
+
+EffectStateFactory *FlangerStateFactory_getFactory()
+{
+ static FlangerStateFactory FlangerFactory{};
+ return &FlangerFactory;
+}
diff --git a/alc/effects/compressor.cpp b/alc/effects/compressor.cpp
new file mode 100644
index 00000000..4a487097
--- /dev/null
+++ b/alc/effects/compressor.cpp
@@ -0,0 +1,222 @@
+/**
+ * OpenAL cross platform audio library
+ * Copyright (C) 2013 by Anis A. Hireche
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ * Or go to http://www.gnu.org/copyleft/lgpl.html
+ */
+
+#include "config.h"
+
+#include <cstdlib>
+
+#include "alcmain.h"
+#include "alcontext.h"
+#include "alu.h"
+#include "alAuxEffectSlot.h"
+#include "alError.h"
+#include "vecmat.h"
+
+
+namespace {
+
+#define AMP_ENVELOPE_MIN 0.5f
+#define AMP_ENVELOPE_MAX 2.0f
+
+#define ATTACK_TIME 0.1f /* 100ms to rise from min to max */
+#define RELEASE_TIME 0.2f /* 200ms to drop from max to min */
+
+
+struct CompressorState final : public EffectState {
+ /* Effect gains for each channel */
+ ALfloat mGain[MAX_AMBI_CHANNELS][MAX_OUTPUT_CHANNELS]{};
+
+ /* Effect parameters */
+ ALboolean mEnabled{AL_TRUE};
+ ALfloat mAttackMult{1.0f};
+ ALfloat mReleaseMult{1.0f};
+ ALfloat mEnvFollower{1.0f};
+
+
+ ALboolean deviceUpdate(const ALCdevice *device) override;
+ void update(const ALCcontext *context, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target) override;
+ void process(const ALsizei samplesToDo, const FloatBufferLine *RESTRICT samplesIn, const ALsizei numInput, const al::span<FloatBufferLine> samplesOut) override;
+
+ DEF_NEWDEL(CompressorState)
+};
+
+ALboolean CompressorState::deviceUpdate(const ALCdevice *device)
+{
+ /* Number of samples to do a full attack and release (non-integer sample
+ * counts are okay).
+ */
+ const ALfloat attackCount = static_cast<ALfloat>(device->Frequency) * ATTACK_TIME;
+ const ALfloat releaseCount = static_cast<ALfloat>(device->Frequency) * RELEASE_TIME;
+
+ /* Calculate per-sample multipliers to attack and release at the desired
+ * rates.
+ */
+ mAttackMult = std::pow(AMP_ENVELOPE_MAX/AMP_ENVELOPE_MIN, 1.0f/attackCount);
+ mReleaseMult = std::pow(AMP_ENVELOPE_MIN/AMP_ENVELOPE_MAX, 1.0f/releaseCount);
+
+ return AL_TRUE;
+}
+
+void CompressorState::update(const ALCcontext*, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target)
+{
+ mEnabled = props->Compressor.OnOff;
+
+ mOutTarget = target.Main->Buffer;
+ for(size_t i{0u};i < slot->Wet.Buffer.size();++i)
+ {
+ auto coeffs = GetAmbiIdentityRow(i);
+ ComputePanGains(target.Main, coeffs.data(), slot->Params.Gain, mGain[i]);
+ }
+}
+
+void CompressorState::process(const ALsizei samplesToDo, const FloatBufferLine *RESTRICT samplesIn, const ALsizei numInput, const al::span<FloatBufferLine> samplesOut)
+{
+ for(ALsizei base{0};base < samplesToDo;)
+ {
+ ALfloat gains[256];
+ const ALsizei td{mini(256, samplesToDo-base)};
+
+ /* Generate the per-sample gains from the signal envelope. */
+ ALfloat env{mEnvFollower};
+ if(mEnabled)
+ {
+ for(ALsizei i{0};i < td;++i)
+ {
+ /* Clamp the absolute amplitude to the defined envelope limits,
+ * then attack or release the envelope to reach it.
+ */
+ const ALfloat amplitude{clampf(std::fabs(samplesIn[0][base+i]), AMP_ENVELOPE_MIN,
+ AMP_ENVELOPE_MAX)};
+ if(amplitude > env)
+ env = minf(env*mAttackMult, amplitude);
+ else if(amplitude < env)
+ env = maxf(env*mReleaseMult, amplitude);
+
+ /* Apply the reciprocal of the envelope to normalize the volume
+ * (compress the dynamic range).
+ */
+ gains[i] = 1.0f / env;
+ }
+ }
+ else
+ {
+ /* Same as above, except the amplitude is forced to 1. This helps
+ * ensure smooth gain changes when the compressor is turned on and
+ * off.
+ */
+ for(ALsizei i{0};i < td;++i)
+ {
+ const ALfloat amplitude{1.0f};
+ if(amplitude > env)
+ env = minf(env*mAttackMult, amplitude);
+ else if(amplitude < env)
+ env = maxf(env*mReleaseMult, amplitude);
+
+ gains[i] = 1.0f / env;
+ }
+ }
+ mEnvFollower = env;
+
+ /* Now compress the signal amplitude to output. */
+ ASSUME(numInput > 0);
+ for(ALsizei j{0};j < numInput;j++)
+ {
+ const ALfloat *outgains{mGain[j]};
+ for(FloatBufferLine &output : samplesOut)
+ {
+ const ALfloat gain{*(outgains++)};
+ if(!(std::fabs(gain) > GAIN_SILENCE_THRESHOLD))
+ continue;
+
+ for(ALsizei i{0};i < td;i++)
+ output[base+i] += samplesIn[j][base+i] * gains[i] * gain;
+ }
+ }
+
+ base += td;
+ }
+}
+
+
+void Compressor_setParami(EffectProps *props, ALCcontext *context, ALenum param, ALint val)
+{
+ switch(param)
+ {
+ case AL_COMPRESSOR_ONOFF:
+ if(!(val >= AL_COMPRESSOR_MIN_ONOFF && val <= AL_COMPRESSOR_MAX_ONOFF))
+ SETERR_RETURN(context, AL_INVALID_VALUE,, "Compressor state out of range");
+ props->Compressor.OnOff = val;
+ break;
+
+ default:
+ alSetError(context, AL_INVALID_ENUM, "Invalid compressor integer property 0x%04x",
+ param);
+ }
+}
+void Compressor_setParamiv(EffectProps *props, ALCcontext *context, ALenum param, const ALint *vals)
+{ Compressor_setParami(props, context, param, vals[0]); }
+void Compressor_setParamf(EffectProps*, ALCcontext *context, ALenum param, ALfloat)
+{ alSetError(context, AL_INVALID_ENUM, "Invalid compressor float property 0x%04x", param); }
+void Compressor_setParamfv(EffectProps*, ALCcontext *context, ALenum param, const ALfloat*)
+{ alSetError(context, AL_INVALID_ENUM, "Invalid compressor float-vector property 0x%04x", param); }
+
+void Compressor_getParami(const EffectProps *props, ALCcontext *context, ALenum param, ALint *val)
+{
+ switch(param)
+ {
+ case AL_COMPRESSOR_ONOFF:
+ *val = props->Compressor.OnOff;
+ break;
+
+ default:
+ alSetError(context, AL_INVALID_ENUM, "Invalid compressor integer property 0x%04x",
+ param);
+ }
+}
+void Compressor_getParamiv(const EffectProps *props, ALCcontext *context, ALenum param, ALint *vals)
+{ Compressor_getParami(props, context, param, vals); }
+void Compressor_getParamf(const EffectProps*, ALCcontext *context, ALenum param, ALfloat*)
+{ alSetError(context, AL_INVALID_ENUM, "Invalid compressor float property 0x%04x", param); }
+void Compressor_getParamfv(const EffectProps*, ALCcontext *context, ALenum param, ALfloat*)
+{ alSetError(context, AL_INVALID_ENUM, "Invalid compressor float-vector property 0x%04x", param); }
+
+DEFINE_ALEFFECT_VTABLE(Compressor);
+
+
+struct CompressorStateFactory final : public EffectStateFactory {
+ EffectState *create() override { return new CompressorState{}; }
+ EffectProps getDefaultProps() const noexcept override;
+ const EffectVtable *getEffectVtable() const noexcept override { return &Compressor_vtable; }
+};
+
+EffectProps CompressorStateFactory::getDefaultProps() const noexcept
+{
+ EffectProps props{};
+ props.Compressor.OnOff = AL_COMPRESSOR_DEFAULT_ONOFF;
+ return props;
+}
+
+} // namespace
+
+EffectStateFactory *CompressorStateFactory_getFactory()
+{
+ static CompressorStateFactory CompressorFactory{};
+ return &CompressorFactory;
+}
diff --git a/alc/effects/dedicated.cpp b/alc/effects/dedicated.cpp
new file mode 100644
index 00000000..b31b3750
--- /dev/null
+++ b/alc/effects/dedicated.cpp
@@ -0,0 +1,159 @@
+/**
+ * OpenAL cross platform audio library
+ * Copyright (C) 2011 by Chris Robinson.
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ * Or go to http://www.gnu.org/copyleft/lgpl.html
+ */
+
+#include "config.h"
+
+#include <cstdlib>
+#include <cmath>
+#include <algorithm>
+
+#include "alcmain.h"
+#include "alcontext.h"
+#include "alAuxEffectSlot.h"
+#include "alError.h"
+#include "alu.h"
+
+
+namespace {
+
+struct DedicatedState final : public EffectState {
+ ALfloat mCurrentGains[MAX_OUTPUT_CHANNELS];
+ ALfloat mTargetGains[MAX_OUTPUT_CHANNELS];
+
+
+ ALboolean deviceUpdate(const ALCdevice *device) override;
+ void update(const ALCcontext *context, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target) override;
+ void process(const ALsizei samplesToDo, const FloatBufferLine *RESTRICT samplesIn, const ALsizei numInput, const al::span<FloatBufferLine> samplesOut) override;
+
+ DEF_NEWDEL(DedicatedState)
+};
+
+ALboolean DedicatedState::deviceUpdate(const ALCdevice*)
+{
+ std::fill(std::begin(mCurrentGains), std::end(mCurrentGains), 0.0f);
+ return AL_TRUE;
+}
+
+void DedicatedState::update(const ALCcontext*, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target)
+{
+ std::fill(std::begin(mTargetGains), std::end(mTargetGains), 0.0f);
+
+ const ALfloat Gain{slot->Params.Gain * props->Dedicated.Gain};
+
+ if(slot->Params.EffectType == AL_EFFECT_DEDICATED_LOW_FREQUENCY_EFFECT)
+ {
+ const int idx{!target.RealOut ? -1 : GetChannelIdxByName(*target.RealOut, LFE)};
+ if(idx != -1)
+ {
+ mOutTarget = target.RealOut->Buffer;
+ mTargetGains[idx] = Gain;
+ }
+ }
+ else if(slot->Params.EffectType == AL_EFFECT_DEDICATED_DIALOGUE)
+ {
+ /* Dialog goes to the front-center speaker if it exists, otherwise it
+ * plays from the front-center location. */
+ const int idx{!target.RealOut ? -1 : GetChannelIdxByName(*target.RealOut, FrontCenter)};
+ if(idx != -1)
+ {
+ mOutTarget = target.RealOut->Buffer;
+ mTargetGains[idx] = Gain;
+ }
+ else
+ {
+ ALfloat coeffs[MAX_AMBI_CHANNELS];
+ CalcDirectionCoeffs({0.0f, 0.0f, -1.0f}, 0.0f, coeffs);
+
+ mOutTarget = target.Main->Buffer;
+ ComputePanGains(target.Main, coeffs, Gain, mTargetGains);
+ }
+ }
+}
+
+void DedicatedState::process(const ALsizei samplesToDo, const FloatBufferLine *RESTRICT samplesIn, const ALsizei /*numInput*/, const al::span<FloatBufferLine> samplesOut)
+{
+ MixSamples(samplesIn[0].data(), samplesOut, mCurrentGains, mTargetGains, samplesToDo, 0,
+ samplesToDo);
+}
+
+
+void Dedicated_setParami(EffectProps*, ALCcontext *context, ALenum param, ALint)
+{ alSetError(context, AL_INVALID_ENUM, "Invalid dedicated integer property 0x%04x", param); }
+void Dedicated_setParamiv(EffectProps*, ALCcontext *context, ALenum param, const ALint*)
+{ alSetError(context, AL_INVALID_ENUM, "Invalid dedicated integer-vector property 0x%04x", param); }
+void Dedicated_setParamf(EffectProps *props, ALCcontext *context, ALenum param, ALfloat val)
+{
+ switch(param)
+ {
+ case AL_DEDICATED_GAIN:
+ if(!(val >= 0.0f && std::isfinite(val)))
+ SETERR_RETURN(context, AL_INVALID_VALUE,, "Dedicated gain out of range");
+ props->Dedicated.Gain = val;
+ break;
+
+ default:
+ alSetError(context, AL_INVALID_ENUM, "Invalid dedicated float property 0x%04x", param);
+ }
+}
+void Dedicated_setParamfv(EffectProps *props, ALCcontext *context, ALenum param, const ALfloat *vals)
+{ Dedicated_setParamf(props, context, param, vals[0]); }
+
+void Dedicated_getParami(const EffectProps*, ALCcontext *context, ALenum param, ALint*)
+{ alSetError(context, AL_INVALID_ENUM, "Invalid dedicated integer property 0x%04x", param); }
+void Dedicated_getParamiv(const EffectProps*, ALCcontext *context, ALenum param, ALint*)
+{ alSetError(context, AL_INVALID_ENUM, "Invalid dedicated integer-vector property 0x%04x", param); }
+void Dedicated_getParamf(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *val)
+{
+ switch(param)
+ {
+ case AL_DEDICATED_GAIN:
+ *val = props->Dedicated.Gain;
+ break;
+
+ default:
+ alSetError(context, AL_INVALID_ENUM, "Invalid dedicated float property 0x%04x", param);
+ }
+}
+void Dedicated_getParamfv(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *vals)
+{ Dedicated_getParamf(props, context, param, vals); }
+
+DEFINE_ALEFFECT_VTABLE(Dedicated);
+
+
+struct DedicatedStateFactory final : public EffectStateFactory {
+ EffectState *create() override { return new DedicatedState{}; }
+ EffectProps getDefaultProps() const noexcept override;
+ const EffectVtable *getEffectVtable() const noexcept override { return &Dedicated_vtable; }
+};
+
+EffectProps DedicatedStateFactory::getDefaultProps() const noexcept
+{
+ EffectProps props{};
+ props.Dedicated.Gain = 1.0f;
+ return props;
+}
+
+} // namespace
+
+EffectStateFactory *DedicatedStateFactory_getFactory()
+{
+ static DedicatedStateFactory DedicatedFactory{};
+ return &DedicatedFactory;
+}
diff --git a/alc/effects/distortion.cpp b/alc/effects/distortion.cpp
new file mode 100644
index 00000000..59557395
--- /dev/null
+++ b/alc/effects/distortion.cpp
@@ -0,0 +1,269 @@
+/**
+ * OpenAL cross platform audio library
+ * Copyright (C) 2013 by Mike Gorchak
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ * Or go to http://www.gnu.org/copyleft/lgpl.html
+ */
+
+#include "config.h"
+
+#include <cmath>
+#include <cstdlib>
+
+#include <cmath>
+
+#include "alcmain.h"
+#include "alcontext.h"
+#include "alAuxEffectSlot.h"
+#include "alError.h"
+#include "alu.h"
+#include "filters/biquad.h"
+
+
+namespace {
+
+struct DistortionState final : public EffectState {
+ /* Effect gains for each channel */
+ ALfloat mGain[MAX_OUTPUT_CHANNELS]{};
+
+ /* Effect parameters */
+ BiquadFilter mLowpass;
+ BiquadFilter mBandpass;
+ ALfloat mAttenuation{};
+ ALfloat mEdgeCoeff{};
+
+ ALfloat mBuffer[2][BUFFERSIZE]{};
+
+
+ ALboolean deviceUpdate(const ALCdevice *device) override;
+ void update(const ALCcontext *context, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target) override;
+ void process(const ALsizei samplesToDo, const FloatBufferLine *RESTRICT samplesIn, const ALsizei numInput, const al::span<FloatBufferLine> samplesOut) override;
+
+ DEF_NEWDEL(DistortionState)
+};
+
+ALboolean DistortionState::deviceUpdate(const ALCdevice*)
+{
+ mLowpass.clear();
+ mBandpass.clear();
+ return AL_TRUE;
+}
+
+void DistortionState::update(const ALCcontext *context, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target)
+{
+ const ALCdevice *device{context->Device};
+
+ /* Store waveshaper edge settings. */
+ const ALfloat edge{
+ minf(std::sin(al::MathDefs<float>::Pi()*0.5f * props->Distortion.Edge), 0.99f)};
+ mEdgeCoeff = 2.0f * edge / (1.0f-edge);
+
+ ALfloat cutoff{props->Distortion.LowpassCutoff};
+ /* Bandwidth value is constant in octaves. */
+ ALfloat bandwidth{(cutoff / 2.0f) / (cutoff * 0.67f)};
+ /* Multiply sampling frequency by the amount of oversampling done during
+ * processing.
+ */
+ auto frequency = static_cast<ALfloat>(device->Frequency);
+ mLowpass.setParams(BiquadType::LowPass, 1.0f, cutoff / (frequency*4.0f),
+ mLowpass.rcpQFromBandwidth(cutoff / (frequency*4.0f), bandwidth));
+
+ cutoff = props->Distortion.EQCenter;
+ /* Convert bandwidth in Hz to octaves. */
+ bandwidth = props->Distortion.EQBandwidth / (cutoff * 0.67f);
+ mBandpass.setParams(BiquadType::BandPass, 1.0f, cutoff / (frequency*4.0f),
+ mBandpass.rcpQFromBandwidth(cutoff / (frequency*4.0f), bandwidth));
+
+ ALfloat coeffs[MAX_AMBI_CHANNELS];
+ CalcDirectionCoeffs({0.0f, 0.0f, -1.0f}, 0.0f, coeffs);
+
+ mOutTarget = target.Main->Buffer;
+ ComputePanGains(target.Main, coeffs, slot->Params.Gain*props->Distortion.Gain, mGain);
+}
+
+void DistortionState::process(const ALsizei samplesToDo, const FloatBufferLine *RESTRICT samplesIn, const ALsizei /*numInput*/, const al::span<FloatBufferLine> samplesOut)
+{
+ const ALfloat fc{mEdgeCoeff};
+ for(ALsizei base{0};base < samplesToDo;)
+ {
+ /* Perform 4x oversampling to avoid aliasing. Oversampling greatly
+ * improves distortion quality and allows to implement lowpass and
+ * bandpass filters using high frequencies, at which classic IIR
+ * filters became unstable.
+ */
+ ALsizei todo{mini(BUFFERSIZE, (samplesToDo-base) * 4)};
+
+ /* Fill oversample buffer using zero stuffing. Multiply the sample by
+ * the amount of oversampling to maintain the signal's power.
+ */
+ for(ALsizei i{0};i < todo;i++)
+ mBuffer[0][i] = !(i&3) ? samplesIn[0][(i>>2)+base] * 4.0f : 0.0f;
+
+ /* First step, do lowpass filtering of original signal. Additionally
+ * perform buffer interpolation and lowpass cutoff for oversampling
+ * (which is fortunately first step of distortion). So combine three
+ * operations into the one.
+ */
+ mLowpass.process(mBuffer[1], mBuffer[0], todo);
+
+ /* Second step, do distortion using waveshaper function to emulate
+ * signal processing during tube overdriving. Three steps of
+ * waveshaping are intended to modify waveform without boost/clipping/
+ * attenuation process.
+ */
+ for(ALsizei i{0};i < todo;i++)
+ {
+ ALfloat smp{mBuffer[1][i]};
+
+ smp = (1.0f + fc) * smp/(1.0f + fc*fabsf(smp));
+ smp = (1.0f + fc) * smp/(1.0f + fc*fabsf(smp)) * -1.0f;
+ smp = (1.0f + fc) * smp/(1.0f + fc*fabsf(smp));
+
+ mBuffer[0][i] = smp;
+ }
+
+ /* Third step, do bandpass filtering of distorted signal. */
+ mBandpass.process(mBuffer[1], mBuffer[0], todo);
+
+ todo >>= 2;
+ const ALfloat *outgains{mGain};
+ for(FloatBufferLine &output : samplesOut)
+ {
+ /* Fourth step, final, do attenuation and perform decimation,
+ * storing only one sample out of four.
+ */
+ const ALfloat gain{*(outgains++)};
+ if(!(std::fabs(gain) > GAIN_SILENCE_THRESHOLD))
+ continue;
+
+ for(ALsizei i{0};i < todo;i++)
+ output[base+i] += gain * mBuffer[1][i*4];
+ }
+
+ base += todo;
+ }
+}
+
+
+void Distortion_setParami(EffectProps*, ALCcontext *context, ALenum param, ALint)
+{ alSetError(context, AL_INVALID_ENUM, "Invalid distortion integer property 0x%04x", param); }
+void Distortion_setParamiv(EffectProps*, ALCcontext *context, ALenum param, const ALint*)
+{ alSetError(context, AL_INVALID_ENUM, "Invalid distortion integer-vector property 0x%04x", param); }
+void Distortion_setParamf(EffectProps *props, ALCcontext *context, ALenum param, ALfloat val)
+{
+ switch(param)
+ {
+ case AL_DISTORTION_EDGE:
+ if(!(val >= AL_DISTORTION_MIN_EDGE && val <= AL_DISTORTION_MAX_EDGE))
+ SETERR_RETURN(context, AL_INVALID_VALUE,, "Distortion edge out of range");
+ props->Distortion.Edge = val;
+ break;
+
+ case AL_DISTORTION_GAIN:
+ if(!(val >= AL_DISTORTION_MIN_GAIN && val <= AL_DISTORTION_MAX_GAIN))
+ SETERR_RETURN(context, AL_INVALID_VALUE,, "Distortion gain out of range");
+ props->Distortion.Gain = val;
+ break;
+
+ case AL_DISTORTION_LOWPASS_CUTOFF:
+ if(!(val >= AL_DISTORTION_MIN_LOWPASS_CUTOFF && val <= AL_DISTORTION_MAX_LOWPASS_CUTOFF))
+ SETERR_RETURN(context, AL_INVALID_VALUE,, "Distortion low-pass cutoff out of range");
+ props->Distortion.LowpassCutoff = val;
+ break;
+
+ case AL_DISTORTION_EQCENTER:
+ if(!(val >= AL_DISTORTION_MIN_EQCENTER && val <= AL_DISTORTION_MAX_EQCENTER))
+ SETERR_RETURN(context, AL_INVALID_VALUE,, "Distortion EQ center out of range");
+ props->Distortion.EQCenter = val;
+ break;
+
+ case AL_DISTORTION_EQBANDWIDTH:
+ if(!(val >= AL_DISTORTION_MIN_EQBANDWIDTH && val <= AL_DISTORTION_MAX_EQBANDWIDTH))
+ SETERR_RETURN(context, AL_INVALID_VALUE,, "Distortion EQ bandwidth out of range");
+ props->Distortion.EQBandwidth = val;
+ break;
+
+ default:
+ alSetError(context, AL_INVALID_ENUM, "Invalid distortion float property 0x%04x",
+ param);
+ }
+}
+void Distortion_setParamfv(EffectProps *props, ALCcontext *context, ALenum param, const ALfloat *vals)
+{ Distortion_setParamf(props, context, param, vals[0]); }
+
+void Distortion_getParami(const EffectProps*, ALCcontext *context, ALenum param, ALint*)
+{ alSetError(context, AL_INVALID_ENUM, "Invalid distortion integer property 0x%04x", param); }
+void Distortion_getParamiv(const EffectProps*, ALCcontext *context, ALenum param, ALint*)
+{ alSetError(context, AL_INVALID_ENUM, "Invalid distortion integer-vector property 0x%04x", param); }
+void Distortion_getParamf(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *val)
+{
+ switch(param)
+ {
+ case AL_DISTORTION_EDGE:
+ *val = props->Distortion.Edge;
+ break;
+
+ case AL_DISTORTION_GAIN:
+ *val = props->Distortion.Gain;
+ break;
+
+ case AL_DISTORTION_LOWPASS_CUTOFF:
+ *val = props->Distortion.LowpassCutoff;
+ break;
+
+ case AL_DISTORTION_EQCENTER:
+ *val = props->Distortion.EQCenter;
+ break;
+
+ case AL_DISTORTION_EQBANDWIDTH:
+ *val = props->Distortion.EQBandwidth;
+ break;
+
+ default:
+ alSetError(context, AL_INVALID_ENUM, "Invalid distortion float property 0x%04x",
+ param);
+ }
+}
+void Distortion_getParamfv(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *vals)
+{ Distortion_getParamf(props, context, param, vals); }
+
+DEFINE_ALEFFECT_VTABLE(Distortion);
+
+
+struct DistortionStateFactory final : public EffectStateFactory {
+ EffectState *create() override { return new DistortionState{}; }
+ EffectProps getDefaultProps() const noexcept override;
+ const EffectVtable *getEffectVtable() const noexcept override { return &Distortion_vtable; }
+};
+
+EffectProps DistortionStateFactory::getDefaultProps() const noexcept
+{
+ EffectProps props{};
+ props.Distortion.Edge = AL_DISTORTION_DEFAULT_EDGE;
+ props.Distortion.Gain = AL_DISTORTION_DEFAULT_GAIN;
+ props.Distortion.LowpassCutoff = AL_DISTORTION_DEFAULT_LOWPASS_CUTOFF;
+ props.Distortion.EQCenter = AL_DISTORTION_DEFAULT_EQCENTER;
+ props.Distortion.EQBandwidth = AL_DISTORTION_DEFAULT_EQBANDWIDTH;
+ return props;
+}
+
+} // namespace
+
+EffectStateFactory *DistortionStateFactory_getFactory()
+{
+ static DistortionStateFactory DistortionFactory{};
+ return &DistortionFactory;
+}
diff --git a/alc/effects/echo.cpp b/alc/effects/echo.cpp
new file mode 100644
index 00000000..c10f2eb2
--- /dev/null
+++ b/alc/effects/echo.cpp
@@ -0,0 +1,271 @@
+/**
+ * OpenAL cross platform audio library
+ * Copyright (C) 2009 by Chris Robinson.
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ * Or go to http://www.gnu.org/copyleft/lgpl.html
+ */
+
+#include "config.h"
+
+#include <cmath>
+#include <cstdlib>
+
+#include <algorithm>
+
+#include "alcmain.h"
+#include "alcontext.h"
+#include "alFilter.h"
+#include "alAuxEffectSlot.h"
+#include "alError.h"
+#include "alu.h"
+#include "filters/biquad.h"
+#include "vector.h"
+
+
+namespace {
+
+struct EchoState final : public EffectState {
+ al::vector<ALfloat,16> mSampleBuffer;
+
+ // The echo is two tap. The delay is the number of samples from before the
+ // current offset
+ struct {
+ ALsizei delay{0};
+ } mTap[2];
+ ALsizei mOffset{0};
+
+ /* The panning gains for the two taps */
+ struct {
+ ALfloat Current[MAX_OUTPUT_CHANNELS]{};
+ ALfloat Target[MAX_OUTPUT_CHANNELS]{};
+ } mGains[2];
+
+ BiquadFilter mFilter;
+ ALfloat mFeedGain{0.0f};
+
+ alignas(16) ALfloat mTempBuffer[2][BUFFERSIZE];
+
+ ALboolean deviceUpdate(const ALCdevice *device) override;
+ void update(const ALCcontext *context, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target) override;
+ void process(const ALsizei samplesToDo, const FloatBufferLine *RESTRICT samplesIn, const ALsizei numInput, const al::span<FloatBufferLine> samplesOut) override;
+
+ DEF_NEWDEL(EchoState)
+};
+
+ALboolean EchoState::deviceUpdate(const ALCdevice *Device)
+{
+ ALuint maxlen;
+
+ // Use the next power of 2 for the buffer length, so the tap offsets can be
+ // wrapped using a mask instead of a modulo
+ maxlen = float2int(AL_ECHO_MAX_DELAY*Device->Frequency + 0.5f) +
+ float2int(AL_ECHO_MAX_LRDELAY*Device->Frequency + 0.5f);
+ maxlen = NextPowerOf2(maxlen);
+ if(maxlen <= 0) return AL_FALSE;
+
+ if(maxlen != mSampleBuffer.size())
+ {
+ mSampleBuffer.resize(maxlen);
+ mSampleBuffer.shrink_to_fit();
+ }
+
+ std::fill(mSampleBuffer.begin(), mSampleBuffer.end(), 0.0f);
+ for(auto &e : mGains)
+ {
+ std::fill(std::begin(e.Current), std::end(e.Current), 0.0f);
+ std::fill(std::begin(e.Target), std::end(e.Target), 0.0f);
+ }
+
+ return AL_TRUE;
+}
+
+void EchoState::update(const ALCcontext *context, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target)
+{
+ const ALCdevice *device = context->Device;
+ const auto frequency = static_cast<ALfloat>(device->Frequency);
+
+ mTap[0].delay = maxi(float2int(props->Echo.Delay*frequency + 0.5f), 1);
+ mTap[1].delay = float2int(props->Echo.LRDelay*frequency + 0.5f) + mTap[0].delay;
+
+ const ALfloat gainhf{maxf(1.0f - props->Echo.Damping, 0.0625f)}; /* Limit -24dB */
+ mFilter.setParams(BiquadType::HighShelf, gainhf, LOWPASSFREQREF/frequency,
+ mFilter.rcpQFromSlope(gainhf, 1.0f));
+
+ mFeedGain = props->Echo.Feedback;
+
+ /* Convert echo spread (where 0 = center, +/-1 = sides) to angle. */
+ const ALfloat angle{std::asin(props->Echo.Spread)};
+
+ ALfloat coeffs[2][MAX_AMBI_CHANNELS];
+ CalcAngleCoeffs(-angle, 0.0f, 0.0f, coeffs[0]);
+ CalcAngleCoeffs( angle, 0.0f, 0.0f, coeffs[1]);
+
+ mOutTarget = target.Main->Buffer;
+ ComputePanGains(target.Main, coeffs[0], slot->Params.Gain, mGains[0].Target);
+ ComputePanGains(target.Main, coeffs[1], slot->Params.Gain, mGains[1].Target);
+}
+
+void EchoState::process(const ALsizei samplesToDo, const FloatBufferLine *RESTRICT samplesIn, const ALsizei /*numInput*/, const al::span<FloatBufferLine> samplesOut)
+{
+ const auto mask = static_cast<ALsizei>(mSampleBuffer.size()-1);
+ ALfloat *RESTRICT delaybuf{mSampleBuffer.data()};
+ ALsizei offset{mOffset};
+ ALsizei tap1{offset - mTap[0].delay};
+ ALsizei tap2{offset - mTap[1].delay};
+ ALfloat z1, z2;
+
+ ASSUME(samplesToDo > 0);
+ ASSUME(mask > 0);
+
+ std::tie(z1, z2) = mFilter.getComponents();
+ for(ALsizei i{0};i < samplesToDo;)
+ {
+ offset &= mask;
+ tap1 &= mask;
+ tap2 &= mask;
+
+ ALsizei td{mini(mask+1 - maxi(offset, maxi(tap1, tap2)), samplesToDo-i)};
+ do {
+ /* Feed the delay buffer's input first. */
+ delaybuf[offset] = samplesIn[0][i];
+
+ /* Get delayed output from the first and second taps. Use the
+ * second tap for feedback.
+ */
+ mTempBuffer[0][i] = delaybuf[tap1++];
+ mTempBuffer[1][i] = delaybuf[tap2++];
+ const float feedb{mTempBuffer[1][i++]};
+
+ /* Add feedback to the delay buffer with damping and attenuation. */
+ delaybuf[offset++] += mFilter.processOne(feedb, z1, z2) * mFeedGain;
+ } while(--td);
+ }
+ mFilter.setComponents(z1, z2);
+ mOffset = offset;
+
+ for(ALsizei c{0};c < 2;c++)
+ MixSamples(mTempBuffer[c], samplesOut, mGains[c].Current, mGains[c].Target, samplesToDo, 0,
+ samplesToDo);
+}
+
+
+void Echo_setParami(EffectProps*, ALCcontext *context, ALenum param, ALint)
+{ alSetError(context, AL_INVALID_ENUM, "Invalid echo integer property 0x%04x", param); }
+void Echo_setParamiv(EffectProps*, ALCcontext *context, ALenum param, const ALint*)
+{ alSetError(context, AL_INVALID_ENUM, "Invalid echo integer-vector property 0x%04x", param); }
+void Echo_setParamf(EffectProps *props, ALCcontext *context, ALenum param, ALfloat val)
+{
+ switch(param)
+ {
+ case AL_ECHO_DELAY:
+ if(!(val >= AL_ECHO_MIN_DELAY && val <= AL_ECHO_MAX_DELAY))
+ SETERR_RETURN(context, AL_INVALID_VALUE,, "Echo delay out of range");
+ props->Echo.Delay = val;
+ break;
+
+ case AL_ECHO_LRDELAY:
+ if(!(val >= AL_ECHO_MIN_LRDELAY && val <= AL_ECHO_MAX_LRDELAY))
+ SETERR_RETURN(context, AL_INVALID_VALUE,, "Echo LR delay out of range");
+ props->Echo.LRDelay = val;
+ break;
+
+ case AL_ECHO_DAMPING:
+ if(!(val >= AL_ECHO_MIN_DAMPING && val <= AL_ECHO_MAX_DAMPING))
+ SETERR_RETURN(context, AL_INVALID_VALUE,, "Echo damping out of range");
+ props->Echo.Damping = val;
+ break;
+
+ case AL_ECHO_FEEDBACK:
+ if(!(val >= AL_ECHO_MIN_FEEDBACK && val <= AL_ECHO_MAX_FEEDBACK))
+ SETERR_RETURN(context, AL_INVALID_VALUE,, "Echo feedback out of range");
+ props->Echo.Feedback = val;
+ break;
+
+ case AL_ECHO_SPREAD:
+ if(!(val >= AL_ECHO_MIN_SPREAD && val <= AL_ECHO_MAX_SPREAD))
+ SETERR_RETURN(context, AL_INVALID_VALUE,, "Echo spread out of range");
+ props->Echo.Spread = val;
+ break;
+
+ default:
+ alSetError(context, AL_INVALID_ENUM, "Invalid echo float property 0x%04x", param);
+ }
+}
+void Echo_setParamfv(EffectProps *props, ALCcontext *context, ALenum param, const ALfloat *vals)
+{ Echo_setParamf(props, context, param, vals[0]); }
+
+void Echo_getParami(const EffectProps*, ALCcontext *context, ALenum param, ALint*)
+{ alSetError(context, AL_INVALID_ENUM, "Invalid echo integer property 0x%04x", param); }
+void Echo_getParamiv(const EffectProps*, ALCcontext *context, ALenum param, ALint*)
+{ alSetError(context, AL_INVALID_ENUM, "Invalid echo integer-vector property 0x%04x", param); }
+void Echo_getParamf(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *val)
+{
+ switch(param)
+ {
+ case AL_ECHO_DELAY:
+ *val = props->Echo.Delay;
+ break;
+
+ case AL_ECHO_LRDELAY:
+ *val = props->Echo.LRDelay;
+ break;
+
+ case AL_ECHO_DAMPING:
+ *val = props->Echo.Damping;
+ break;
+
+ case AL_ECHO_FEEDBACK:
+ *val = props->Echo.Feedback;
+ break;
+
+ case AL_ECHO_SPREAD:
+ *val = props->Echo.Spread;
+ break;
+
+ default:
+ alSetError(context, AL_INVALID_ENUM, "Invalid echo float property 0x%04x", param);
+ }
+}
+void Echo_getParamfv(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *vals)
+{ Echo_getParamf(props, context, param, vals); }
+
+DEFINE_ALEFFECT_VTABLE(Echo);
+
+
+struct EchoStateFactory final : public EffectStateFactory {
+ EffectState *create() override { return new EchoState{}; }
+ EffectProps getDefaultProps() const noexcept override;
+ const EffectVtable *getEffectVtable() const noexcept override { return &Echo_vtable; }
+};
+
+EffectProps EchoStateFactory::getDefaultProps() const noexcept
+{
+ EffectProps props{};
+ props.Echo.Delay = AL_ECHO_DEFAULT_DELAY;
+ props.Echo.LRDelay = AL_ECHO_DEFAULT_LRDELAY;
+ props.Echo.Damping = AL_ECHO_DEFAULT_DAMPING;
+ props.Echo.Feedback = AL_ECHO_DEFAULT_FEEDBACK;
+ props.Echo.Spread = AL_ECHO_DEFAULT_SPREAD;
+ return props;
+}
+
+} // namespace
+
+EffectStateFactory *EchoStateFactory_getFactory()
+{
+ static EchoStateFactory EchoFactory{};
+ return &EchoFactory;
+}
diff --git a/alc/effects/equalizer.cpp b/alc/effects/equalizer.cpp
new file mode 100644
index 00000000..69ab5021
--- /dev/null
+++ b/alc/effects/equalizer.cpp
@@ -0,0 +1,337 @@
+/**
+ * OpenAL cross platform audio library
+ * Copyright (C) 2013 by Mike Gorchak
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ * Or go to http://www.gnu.org/copyleft/lgpl.html
+ */
+
+#include "config.h"
+
+#include <cmath>
+#include <cstdlib>
+
+#include <algorithm>
+#include <functional>
+
+#include "alcmain.h"
+#include "alcontext.h"
+#include "alAuxEffectSlot.h"
+#include "alError.h"
+#include "alu.h"
+#include "filters/biquad.h"
+#include "vecmat.h"
+
+
+namespace {
+
+/* The document "Effects Extension Guide.pdf" says that low and high *
+ * frequencies are cutoff frequencies. This is not fully correct, they *
+ * are corner frequencies for low and high shelf filters. If they were *
+ * just cutoff frequencies, there would be no need in cutoff frequency *
+ * gains, which are present. Documentation for "Creative Proteus X2" *
+ * software describes 4-band equalizer functionality in a much better *
+ * way. This equalizer seems to be a predecessor of OpenAL 4-band *
+ * equalizer. With low and high shelf filters we are able to cutoff *
+ * frequencies below and/or above corner frequencies using attenuation *
+ * gains (below 1.0) and amplify all low and/or high frequencies using *
+ * gains above 1.0. *
+ * *
+ * Low-shelf Low Mid Band High Mid Band High-shelf *
+ * corner center center corner *
+ * frequency frequency frequency frequency *
+ * 50Hz..800Hz 200Hz..3000Hz 1000Hz..8000Hz 4000Hz..16000Hz *
+ * *
+ * | | | | *
+ * | | | | *
+ * B -----+ /--+--\ /--+--\ +----- *
+ * O |\ | | | | | | /| *
+ * O | \ - | - - | - / | *
+ * S + | \ | | | | | | / | *
+ * T | | | | | | | | | | *
+ * ---------+---------------+------------------+---------------+-------- *
+ * C | | | | | | | | | | *
+ * U - | / | | | | | | \ | *
+ * T | / - | - - | - \ | *
+ * O |/ | | | | | | \| *
+ * F -----+ \--+--/ \--+--/ +----- *
+ * F | | | | *
+ * | | | | *
+ * *
+ * Gains vary from 0.126 up to 7.943, which means from -18dB attenuation *
+ * up to +18dB amplification. Band width varies from 0.01 up to 1.0 in *
+ * octaves for two mid bands. *
+ * *
+ * Implementation is based on the "Cookbook formulae for audio EQ biquad *
+ * filter coefficients" by Robert Bristow-Johnson *
+ * http://www.musicdsp.org/files/Audio-EQ-Cookbook.txt */
+
+
+struct EqualizerState final : public EffectState {
+ struct {
+ /* Effect parameters */
+ BiquadFilter filter[4];
+
+ /* Effect gains for each channel */
+ ALfloat CurrentGains[MAX_OUTPUT_CHANNELS]{};
+ ALfloat TargetGains[MAX_OUTPUT_CHANNELS]{};
+ } mChans[MAX_AMBI_CHANNELS];
+
+ ALfloat mSampleBuffer[BUFFERSIZE]{};
+
+
+ ALboolean deviceUpdate(const ALCdevice *device) override;
+ void update(const ALCcontext *context, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target) override;
+ void process(const ALsizei samplesToDo, const FloatBufferLine *RESTRICT samplesIn, const ALsizei numInput, const al::span<FloatBufferLine> samplesOut) override;
+
+ DEF_NEWDEL(EqualizerState)
+};
+
+ALboolean EqualizerState::deviceUpdate(const ALCdevice*)
+{
+ 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);
+ }
+ return AL_TRUE;
+}
+
+void EqualizerState::update(const ALCcontext *context, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target)
+{
+ const ALCdevice *device = context->Device;
+ auto frequency = static_cast<ALfloat>(device->Frequency);
+ ALfloat gain, f0norm;
+
+ /* Calculate coefficients for the each type of filter. Note that the shelf
+ * filters' gain is for the reference frequency, which is the centerpoint
+ * of the transition band.
+ */
+ gain = maxf(sqrtf(props->Equalizer.LowGain), 0.0625f); /* Limit -24dB */
+ f0norm = props->Equalizer.LowCutoff/frequency;
+ mChans[0].filter[0].setParams(BiquadType::LowShelf, gain, f0norm,
+ BiquadFilter::rcpQFromSlope(gain, 0.75f));
+
+ gain = maxf(props->Equalizer.Mid1Gain, 0.0625f);
+ f0norm = props->Equalizer.Mid1Center/frequency;
+ mChans[0].filter[1].setParams(BiquadType::Peaking, gain, f0norm,
+ BiquadFilter::rcpQFromBandwidth(f0norm, props->Equalizer.Mid1Width));
+
+ gain = maxf(props->Equalizer.Mid2Gain, 0.0625f);
+ f0norm = props->Equalizer.Mid2Center/frequency;
+ mChans[0].filter[2].setParams(BiquadType::Peaking, gain, f0norm,
+ BiquadFilter::rcpQFromBandwidth(f0norm, props->Equalizer.Mid2Width));
+
+ gain = maxf(sqrtf(props->Equalizer.HighGain), 0.0625f);
+ f0norm = props->Equalizer.HighCutoff/frequency;
+ mChans[0].filter[3].setParams(BiquadType::HighShelf, gain, f0norm,
+ BiquadFilter::rcpQFromSlope(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]);
+ }
+
+ mOutTarget = target.Main->Buffer;
+ for(size_t i{0u};i < slot->Wet.Buffer.size();++i)
+ {
+ auto coeffs = GetAmbiIdentityRow(i);
+ ComputePanGains(target.Main, coeffs.data(), slot->Params.Gain, mChans[i].TargetGains);
+ }
+}
+
+void EqualizerState::process(const ALsizei samplesToDo, const FloatBufferLine *RESTRICT samplesIn, const ALsizei numInput, const al::span<FloatBufferLine> samplesOut)
+{
+ ASSUME(numInput > 0);
+ for(ALsizei c{0};c < numInput;c++)
+ {
+ mChans[c].filter[0].process(mSampleBuffer, samplesIn[c].data(), samplesToDo);
+ mChans[c].filter[1].process(mSampleBuffer, mSampleBuffer, samplesToDo);
+ mChans[c].filter[2].process(mSampleBuffer, mSampleBuffer, samplesToDo);
+ mChans[c].filter[3].process(mSampleBuffer, mSampleBuffer, samplesToDo);
+
+ MixSamples(mSampleBuffer, samplesOut, mChans[c].CurrentGains, mChans[c].TargetGains,
+ samplesToDo, 0, samplesToDo);
+ }
+}
+
+
+void Equalizer_setParami(EffectProps*, ALCcontext *context, ALenum param, ALint)
+{ alSetError(context, AL_INVALID_ENUM, "Invalid equalizer integer property 0x%04x", param); }
+void Equalizer_setParamiv(EffectProps*, ALCcontext *context, ALenum param, const ALint*)
+{ alSetError(context, AL_INVALID_ENUM, "Invalid equalizer integer-vector property 0x%04x", param); }
+void Equalizer_setParamf(EffectProps *props, ALCcontext *context, ALenum param, ALfloat val)
+{
+ switch(param)
+ {
+ case AL_EQUALIZER_LOW_GAIN:
+ if(!(val >= AL_EQUALIZER_MIN_LOW_GAIN && val <= AL_EQUALIZER_MAX_LOW_GAIN))
+ SETERR_RETURN(context, AL_INVALID_VALUE,, "Equalizer low-band gain out of range");
+ props->Equalizer.LowGain = val;
+ break;
+
+ case AL_EQUALIZER_LOW_CUTOFF:
+ if(!(val >= AL_EQUALIZER_MIN_LOW_CUTOFF && val <= AL_EQUALIZER_MAX_LOW_CUTOFF))
+ SETERR_RETURN(context, AL_INVALID_VALUE,, "Equalizer low-band cutoff out of range");
+ props->Equalizer.LowCutoff = val;
+ break;
+
+ case AL_EQUALIZER_MID1_GAIN:
+ if(!(val >= AL_EQUALIZER_MIN_MID1_GAIN && val <= AL_EQUALIZER_MAX_MID1_GAIN))
+ SETERR_RETURN(context, AL_INVALID_VALUE,, "Equalizer mid1-band gain out of range");
+ props->Equalizer.Mid1Gain = val;
+ break;
+
+ case AL_EQUALIZER_MID1_CENTER:
+ if(!(val >= AL_EQUALIZER_MIN_MID1_CENTER && val <= AL_EQUALIZER_MAX_MID1_CENTER))
+ SETERR_RETURN(context, AL_INVALID_VALUE,, "Equalizer mid1-band center out of range");
+ props->Equalizer.Mid1Center = val;
+ break;
+
+ case AL_EQUALIZER_MID1_WIDTH:
+ if(!(val >= AL_EQUALIZER_MIN_MID1_WIDTH && val <= AL_EQUALIZER_MAX_MID1_WIDTH))
+ SETERR_RETURN(context, AL_INVALID_VALUE,, "Equalizer mid1-band width out of range");
+ props->Equalizer.Mid1Width = val;
+ break;
+
+ case AL_EQUALIZER_MID2_GAIN:
+ if(!(val >= AL_EQUALIZER_MIN_MID2_GAIN && val <= AL_EQUALIZER_MAX_MID2_GAIN))
+ SETERR_RETURN(context, AL_INVALID_VALUE,, "Equalizer mid2-band gain out of range");
+ props->Equalizer.Mid2Gain = val;
+ break;
+
+ case AL_EQUALIZER_MID2_CENTER:
+ if(!(val >= AL_EQUALIZER_MIN_MID2_CENTER && val <= AL_EQUALIZER_MAX_MID2_CENTER))
+ SETERR_RETURN(context, AL_INVALID_VALUE,, "Equalizer mid2-band center out of range");
+ props->Equalizer.Mid2Center = val;
+ break;
+
+ case AL_EQUALIZER_MID2_WIDTH:
+ if(!(val >= AL_EQUALIZER_MIN_MID2_WIDTH && val <= AL_EQUALIZER_MAX_MID2_WIDTH))
+ SETERR_RETURN(context, AL_INVALID_VALUE,, "Equalizer mid2-band width out of range");
+ props->Equalizer.Mid2Width = val;
+ break;
+
+ case AL_EQUALIZER_HIGH_GAIN:
+ if(!(val >= AL_EQUALIZER_MIN_HIGH_GAIN && val <= AL_EQUALIZER_MAX_HIGH_GAIN))
+ SETERR_RETURN(context, AL_INVALID_VALUE,, "Equalizer high-band gain out of range");
+ props->Equalizer.HighGain = val;
+ break;
+
+ case AL_EQUALIZER_HIGH_CUTOFF:
+ if(!(val >= AL_EQUALIZER_MIN_HIGH_CUTOFF && val <= AL_EQUALIZER_MAX_HIGH_CUTOFF))
+ SETERR_RETURN(context, AL_INVALID_VALUE,, "Equalizer high-band cutoff out of range");
+ props->Equalizer.HighCutoff = val;
+ break;
+
+ default:
+ alSetError(context, AL_INVALID_ENUM, "Invalid equalizer float property 0x%04x", param);
+ }
+}
+void Equalizer_setParamfv(EffectProps *props, ALCcontext *context, ALenum param, const ALfloat *vals)
+{ Equalizer_setParamf(props, context, param, vals[0]); }
+
+void Equalizer_getParami(const EffectProps*, ALCcontext *context, ALenum param, ALint*)
+{ alSetError(context, AL_INVALID_ENUM, "Invalid equalizer integer property 0x%04x", param); }
+void Equalizer_getParamiv(const EffectProps*, ALCcontext *context, ALenum param, ALint*)
+{ alSetError(context, AL_INVALID_ENUM, "Invalid equalizer integer-vector property 0x%04x", param); }
+void Equalizer_getParamf(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *val)
+{
+ switch(param)
+ {
+ case AL_EQUALIZER_LOW_GAIN:
+ *val = props->Equalizer.LowGain;
+ break;
+
+ case AL_EQUALIZER_LOW_CUTOFF:
+ *val = props->Equalizer.LowCutoff;
+ break;
+
+ case AL_EQUALIZER_MID1_GAIN:
+ *val = props->Equalizer.Mid1Gain;
+ break;
+
+ case AL_EQUALIZER_MID1_CENTER:
+ *val = props->Equalizer.Mid1Center;
+ break;
+
+ case AL_EQUALIZER_MID1_WIDTH:
+ *val = props->Equalizer.Mid1Width;
+ break;
+
+ case AL_EQUALIZER_MID2_GAIN:
+ *val = props->Equalizer.Mid2Gain;
+ break;
+
+ case AL_EQUALIZER_MID2_CENTER:
+ *val = props->Equalizer.Mid2Center;
+ break;
+
+ case AL_EQUALIZER_MID2_WIDTH:
+ *val = props->Equalizer.Mid2Width;
+ break;
+
+ case AL_EQUALIZER_HIGH_GAIN:
+ *val = props->Equalizer.HighGain;
+ break;
+
+ case AL_EQUALIZER_HIGH_CUTOFF:
+ *val = props->Equalizer.HighCutoff;
+ break;
+
+ default:
+ alSetError(context, AL_INVALID_ENUM, "Invalid equalizer float property 0x%04x", param);
+ }
+}
+void Equalizer_getParamfv(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *vals)
+{ Equalizer_getParamf(props, context, param, vals); }
+
+DEFINE_ALEFFECT_VTABLE(Equalizer);
+
+
+struct EqualizerStateFactory final : public EffectStateFactory {
+ EffectState *create() override { return new EqualizerState{}; }
+ EffectProps getDefaultProps() const noexcept override;
+ const EffectVtable *getEffectVtable() const noexcept override { return &Equalizer_vtable; }
+};
+
+EffectProps EqualizerStateFactory::getDefaultProps() const noexcept
+{
+ EffectProps props{};
+ props.Equalizer.LowCutoff = AL_EQUALIZER_DEFAULT_LOW_CUTOFF;
+ props.Equalizer.LowGain = AL_EQUALIZER_DEFAULT_LOW_GAIN;
+ props.Equalizer.Mid1Center = AL_EQUALIZER_DEFAULT_MID1_CENTER;
+ props.Equalizer.Mid1Gain = AL_EQUALIZER_DEFAULT_MID1_GAIN;
+ props.Equalizer.Mid1Width = AL_EQUALIZER_DEFAULT_MID1_WIDTH;
+ props.Equalizer.Mid2Center = AL_EQUALIZER_DEFAULT_MID2_CENTER;
+ props.Equalizer.Mid2Gain = AL_EQUALIZER_DEFAULT_MID2_GAIN;
+ props.Equalizer.Mid2Width = AL_EQUALIZER_DEFAULT_MID2_WIDTH;
+ props.Equalizer.HighCutoff = AL_EQUALIZER_DEFAULT_HIGH_CUTOFF;
+ props.Equalizer.HighGain = AL_EQUALIZER_DEFAULT_HIGH_GAIN;
+ return props;
+}
+
+} // namespace
+
+EffectStateFactory *EqualizerStateFactory_getFactory()
+{
+ static EqualizerStateFactory EqualizerFactory{};
+ return &EqualizerFactory;
+}
diff --git a/alc/effects/fshifter.cpp b/alc/effects/fshifter.cpp
new file mode 100644
index 00000000..b47aa00e
--- /dev/null
+++ b/alc/effects/fshifter.cpp
@@ -0,0 +1,301 @@
+/**
+ * OpenAL cross platform audio library
+ * Copyright (C) 2018 by Raul Herraiz.
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ * Or go to http://www.gnu.org/copyleft/lgpl.html
+ */
+
+#include "config.h"
+
+#include <cmath>
+#include <cstdlib>
+#include <array>
+#include <complex>
+#include <algorithm>
+
+#include "alcmain.h"
+#include "alcontext.h"
+#include "alAuxEffectSlot.h"
+#include "alError.h"
+#include "alu.h"
+
+#include "alcomplex.h"
+
+namespace {
+
+using complex_d = std::complex<double>;
+
+#define HIL_SIZE 1024
+#define OVERSAMP (1<<2)
+
+#define HIL_STEP (HIL_SIZE / OVERSAMP)
+#define FIFO_LATENCY (HIL_STEP * (OVERSAMP-1))
+
+/* Define a Hann window, used to filter the HIL input and output. */
+/* Making this constexpr seems to require C++14. */
+std::array<ALdouble,HIL_SIZE> InitHannWindow()
+{
+ std::array<ALdouble,HIL_SIZE> ret;
+ /* Create lookup table of the Hann window for the desired size, i.e. HIL_SIZE */
+ for(ALsizei i{0};i < HIL_SIZE>>1;i++)
+ {
+ ALdouble val = std::sin(al::MathDefs<double>::Pi() * i / ALdouble{HIL_SIZE-1});
+ ret[i] = ret[HIL_SIZE-1-i] = val * val;
+ }
+ return ret;
+}
+alignas(16) const std::array<ALdouble,HIL_SIZE> HannWindow = InitHannWindow();
+
+
+struct FshifterState final : public EffectState {
+ /* Effect parameters */
+ ALsizei mCount{};
+ ALsizei mPhaseStep{};
+ ALsizei mPhase{};
+ ALdouble mLdSign{};
+
+ /*Effects buffers*/
+ ALfloat mInFIFO[HIL_SIZE]{};
+ complex_d mOutFIFO[HIL_SIZE]{};
+ complex_d mOutputAccum[HIL_SIZE]{};
+ complex_d mAnalytic[HIL_SIZE]{};
+ complex_d mOutdata[BUFFERSIZE]{};
+
+ alignas(16) ALfloat mBufferOut[BUFFERSIZE]{};
+
+ /* Effect gains for each output channel */
+ ALfloat mCurrentGains[MAX_OUTPUT_CHANNELS]{};
+ ALfloat mTargetGains[MAX_OUTPUT_CHANNELS]{};
+
+
+ ALboolean deviceUpdate(const ALCdevice *device) override;
+ void update(const ALCcontext *context, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target) override;
+ void process(const ALsizei samplesToDo, const FloatBufferLine *RESTRICT samplesIn, const ALsizei numInput, const al::span<FloatBufferLine> samplesOut) override;
+
+ DEF_NEWDEL(FshifterState)
+};
+
+ALboolean FshifterState::deviceUpdate(const ALCdevice*)
+{
+ /* (Re-)initializing parameters and clear the buffers. */
+ mCount = FIFO_LATENCY;
+ mPhaseStep = 0;
+ mPhase = 0;
+ mLdSign = 1.0;
+
+ std::fill(std::begin(mInFIFO), std::end(mInFIFO), 0.0f);
+ std::fill(std::begin(mOutFIFO), std::end(mOutFIFO), complex_d{});
+ std::fill(std::begin(mOutputAccum), std::end(mOutputAccum), complex_d{});
+ std::fill(std::begin(mAnalytic), std::end(mAnalytic), complex_d{});
+
+ std::fill(std::begin(mCurrentGains), std::end(mCurrentGains), 0.0f);
+ std::fill(std::begin(mTargetGains), std::end(mTargetGains), 0.0f);
+
+ return AL_TRUE;
+}
+
+void FshifterState::update(const ALCcontext *context, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target)
+{
+ const ALCdevice *device{context->Device};
+
+ ALfloat step{props->Fshifter.Frequency / static_cast<ALfloat>(device->Frequency)};
+ mPhaseStep = fastf2i(minf(step, 0.5f) * FRACTIONONE);
+
+ switch(props->Fshifter.LeftDirection)
+ {
+ case AL_FREQUENCY_SHIFTER_DIRECTION_DOWN:
+ mLdSign = -1.0;
+ break;
+
+ case AL_FREQUENCY_SHIFTER_DIRECTION_UP:
+ mLdSign = 1.0;
+ break;
+
+ case AL_FREQUENCY_SHIFTER_DIRECTION_OFF:
+ mPhase = 0;
+ mPhaseStep = 0;
+ break;
+ }
+
+ ALfloat coeffs[MAX_AMBI_CHANNELS];
+ CalcDirectionCoeffs({0.0f, 0.0f, -1.0f}, 0.0f, coeffs);
+
+ mOutTarget = target.Main->Buffer;
+ ComputePanGains(target.Main, coeffs, slot->Params.Gain, mTargetGains);
+}
+
+void FshifterState::process(const ALsizei samplesToDo, const FloatBufferLine *RESTRICT samplesIn, const ALsizei /*numInput*/, const al::span<FloatBufferLine> samplesOut)
+{
+ static constexpr complex_d complex_zero{0.0, 0.0};
+ ALfloat *RESTRICT BufferOut = mBufferOut;
+ ALsizei j, k, base;
+
+ for(base = 0;base < samplesToDo;)
+ {
+ const ALsizei todo{mini(HIL_SIZE-mCount, samplesToDo-base)};
+
+ ASSUME(todo > 0);
+
+ /* Fill FIFO buffer with samples data */
+ k = mCount;
+ for(j = 0;j < todo;j++,k++)
+ {
+ mInFIFO[k] = samplesIn[0][base+j];
+ mOutdata[base+j] = mOutFIFO[k-FIFO_LATENCY];
+ }
+ mCount += todo;
+ base += todo;
+
+ /* Check whether FIFO buffer is filled */
+ if(mCount < HIL_SIZE) continue;
+ mCount = FIFO_LATENCY;
+
+ /* Real signal windowing and store in Analytic buffer */
+ for(k = 0;k < HIL_SIZE;k++)
+ {
+ mAnalytic[k].real(mInFIFO[k] * HannWindow[k]);
+ mAnalytic[k].imag(0.0);
+ }
+
+ /* Processing signal by Discrete Hilbert Transform (analytical signal). */
+ complex_hilbert(mAnalytic);
+
+ /* Windowing and add to output accumulator */
+ for(k = 0;k < HIL_SIZE;k++)
+ mOutputAccum[k] += 2.0/OVERSAMP*HannWindow[k]*mAnalytic[k];
+
+ /* Shift accumulator, input & output FIFO */
+ for(k = 0;k < HIL_STEP;k++) mOutFIFO[k] = mOutputAccum[k];
+ for(j = 0;k < HIL_SIZE;k++,j++) mOutputAccum[j] = mOutputAccum[k];
+ for(;j < HIL_SIZE;j++) mOutputAccum[j] = complex_zero;
+ for(k = 0;k < FIFO_LATENCY;k++)
+ mInFIFO[k] = mInFIFO[k+HIL_STEP];
+ }
+
+ /* Process frequency shifter using the analytic signal obtained. */
+ for(k = 0;k < samplesToDo;k++)
+ {
+ double phase = mPhase * ((1.0/FRACTIONONE) * al::MathDefs<double>::Tau());
+ BufferOut[k] = static_cast<float>(mOutdata[k].real()*std::cos(phase) +
+ mOutdata[k].imag()*std::sin(phase)*mLdSign);
+
+ mPhase += mPhaseStep;
+ mPhase &= FRACTIONMASK;
+ }
+
+ /* Now, mix the processed sound data to the output. */
+ MixSamples(BufferOut, samplesOut, mCurrentGains, mTargetGains, maxi(samplesToDo, 512), 0,
+ samplesToDo);
+}
+
+
+void Fshifter_setParamf(EffectProps *props, ALCcontext *context, ALenum param, ALfloat val)
+{
+ switch(param)
+ {
+ case AL_FREQUENCY_SHIFTER_FREQUENCY:
+ if(!(val >= AL_FREQUENCY_SHIFTER_MIN_FREQUENCY && val <= AL_FREQUENCY_SHIFTER_MAX_FREQUENCY))
+ SETERR_RETURN(context, AL_INVALID_VALUE,,"Frequency shifter frequency out of range");
+ props->Fshifter.Frequency = val;
+ break;
+
+ default:
+ alSetError(context, AL_INVALID_ENUM, "Invalid frequency shifter float property 0x%04x", param);
+ }
+}
+void Fshifter_setParamfv(EffectProps *props, ALCcontext *context, ALenum param, const ALfloat *vals)
+{ Fshifter_setParamf(props, context, param, vals[0]); }
+
+void Fshifter_setParami(EffectProps *props, ALCcontext *context, ALenum param, ALint val)
+{
+ switch(param)
+ {
+ case AL_FREQUENCY_SHIFTER_LEFT_DIRECTION:
+ if(!(val >= AL_FREQUENCY_SHIFTER_MIN_LEFT_DIRECTION && val <= AL_FREQUENCY_SHIFTER_MAX_LEFT_DIRECTION))
+ SETERR_RETURN(context, AL_INVALID_VALUE,,"Frequency shifter left direction out of range");
+ props->Fshifter.LeftDirection = val;
+ break;
+
+ case AL_FREQUENCY_SHIFTER_RIGHT_DIRECTION:
+ if(!(val >= AL_FREQUENCY_SHIFTER_MIN_RIGHT_DIRECTION && val <= AL_FREQUENCY_SHIFTER_MAX_RIGHT_DIRECTION))
+ SETERR_RETURN(context, AL_INVALID_VALUE,,"Frequency shifter right direction out of range");
+ props->Fshifter.RightDirection = val;
+ break;
+
+ default:
+ alSetError(context, AL_INVALID_ENUM, "Invalid frequency shifter integer property 0x%04x", param);
+ }
+}
+void Fshifter_setParamiv(EffectProps *props, ALCcontext *context, ALenum param, const ALint *vals)
+{ Fshifter_setParami(props, context, param, vals[0]); }
+
+void Fshifter_getParami(const EffectProps *props, ALCcontext *context, ALenum param, ALint *val)
+{
+ switch(param)
+ {
+ case AL_FREQUENCY_SHIFTER_LEFT_DIRECTION:
+ *val = props->Fshifter.LeftDirection;
+ break;
+ case AL_FREQUENCY_SHIFTER_RIGHT_DIRECTION:
+ *val = props->Fshifter.RightDirection;
+ break;
+ default:
+ alSetError(context, AL_INVALID_ENUM, "Invalid frequency shifter integer property 0x%04x", param);
+ }
+}
+void Fshifter_getParamiv(const EffectProps *props, ALCcontext *context, ALenum param, ALint *vals)
+{ Fshifter_getParami(props, context, param, vals); }
+
+void Fshifter_getParamf(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *val)
+{
+ switch(param)
+ {
+ case AL_FREQUENCY_SHIFTER_FREQUENCY:
+ *val = props->Fshifter.Frequency;
+ break;
+
+ default:
+ alSetError(context, AL_INVALID_ENUM, "Invalid frequency shifter float property 0x%04x", param);
+ }
+}
+void Fshifter_getParamfv(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *vals)
+{ Fshifter_getParamf(props, context, param, vals); }
+
+DEFINE_ALEFFECT_VTABLE(Fshifter);
+
+
+struct FshifterStateFactory final : public EffectStateFactory {
+ EffectState *create() override { return new FshifterState{}; }
+ EffectProps getDefaultProps() const noexcept override;
+ const EffectVtable *getEffectVtable() const noexcept override { return &Fshifter_vtable; }
+};
+
+EffectProps FshifterStateFactory::getDefaultProps() const noexcept
+{
+ EffectProps props{};
+ props.Fshifter.Frequency = AL_FREQUENCY_SHIFTER_DEFAULT_FREQUENCY;
+ props.Fshifter.LeftDirection = AL_FREQUENCY_SHIFTER_DEFAULT_LEFT_DIRECTION;
+ props.Fshifter.RightDirection = AL_FREQUENCY_SHIFTER_DEFAULT_RIGHT_DIRECTION;
+ return props;
+}
+
+} // namespace
+
+EffectStateFactory *FshifterStateFactory_getFactory()
+{
+ static FshifterStateFactory FshifterFactory{};
+ return &FshifterFactory;
+}
diff --git a/alc/effects/modulator.cpp b/alc/effects/modulator.cpp
new file mode 100644
index 00000000..086482d7
--- /dev/null
+++ b/alc/effects/modulator.cpp
@@ -0,0 +1,279 @@
+/**
+ * OpenAL cross platform audio library
+ * Copyright (C) 2009 by Chris Robinson.
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ * Or go to http://www.gnu.org/copyleft/lgpl.html
+ */
+
+#include "config.h"
+
+#include <cmath>
+#include <cstdlib>
+
+#include <cmath>
+#include <algorithm>
+
+#include "alcmain.h"
+#include "alcontext.h"
+#include "alAuxEffectSlot.h"
+#include "alError.h"
+#include "alu.h"
+#include "filters/biquad.h"
+#include "vecmat.h"
+
+
+namespace {
+
+#define MAX_UPDATE_SAMPLES 128
+
+#define WAVEFORM_FRACBITS 24
+#define WAVEFORM_FRACONE (1<<WAVEFORM_FRACBITS)
+#define WAVEFORM_FRACMASK (WAVEFORM_FRACONE-1)
+
+inline ALfloat Sin(ALsizei index)
+{
+ return std::sin(static_cast<ALfloat>(index) *
+ (al::MathDefs<float>::Tau() / ALfloat{WAVEFORM_FRACONE}));
+}
+
+inline ALfloat Saw(ALsizei index)
+{
+ return static_cast<ALfloat>(index)*(2.0f/WAVEFORM_FRACONE) - 1.0f;
+}
+
+inline ALfloat Square(ALsizei index)
+{
+ return static_cast<ALfloat>(((index>>(WAVEFORM_FRACBITS-2))&2) - 1);
+}
+
+inline ALfloat One(ALsizei)
+{
+ return 1.0f;
+}
+
+template<ALfloat func(ALsizei)>
+void Modulate(ALfloat *RESTRICT dst, ALsizei index, const ALsizei step, ALsizei todo)
+{
+ ALsizei i;
+ for(i = 0;i < todo;i++)
+ {
+ index += step;
+ index &= WAVEFORM_FRACMASK;
+ dst[i] = func(index);
+ }
+}
+
+
+struct ModulatorState final : public EffectState {
+ void (*mGetSamples)(ALfloat*RESTRICT, ALsizei, const ALsizei, ALsizei){};
+
+ ALsizei mIndex{0};
+ ALsizei mStep{1};
+
+ struct {
+ BiquadFilter Filter;
+
+ ALfloat CurrentGains[MAX_OUTPUT_CHANNELS]{};
+ ALfloat TargetGains[MAX_OUTPUT_CHANNELS]{};
+ } mChans[MAX_AMBI_CHANNELS];
+
+
+ ALboolean deviceUpdate(const ALCdevice *device) override;
+ void update(const ALCcontext *context, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target) override;
+ void process(const ALsizei samplesToDo, const FloatBufferLine *RESTRICT samplesIn, const ALsizei numInput, const al::span<FloatBufferLine> samplesOut) override;
+
+ DEF_NEWDEL(ModulatorState)
+};
+
+ALboolean ModulatorState::deviceUpdate(const ALCdevice*)
+{
+ for(auto &e : mChans)
+ {
+ e.Filter.clear();
+ std::fill(std::begin(e.CurrentGains), std::end(e.CurrentGains), 0.0f);
+ }
+ return AL_TRUE;
+}
+
+void ModulatorState::update(const ALCcontext *context, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target)
+{
+ const ALCdevice *device{context->Device};
+
+ const float step{props->Modulator.Frequency / static_cast<ALfloat>(device->Frequency)};
+ mStep = fastf2i(clampf(step*WAVEFORM_FRACONE, 0.0f, ALfloat{WAVEFORM_FRACONE-1}));
+
+ if(mStep == 0)
+ mGetSamples = Modulate<One>;
+ else if(props->Modulator.Waveform == AL_RING_MODULATOR_SINUSOID)
+ mGetSamples = Modulate<Sin>;
+ else if(props->Modulator.Waveform == AL_RING_MODULATOR_SAWTOOTH)
+ mGetSamples = Modulate<Saw>;
+ else /*if(props->Modulator.Waveform == AL_RING_MODULATOR_SQUARE)*/
+ mGetSamples = Modulate<Square>;
+
+ ALfloat f0norm{props->Modulator.HighPassCutoff / static_cast<ALfloat>(device->Frequency)};
+ f0norm = clampf(f0norm, 1.0f/512.0f, 0.49f);
+ /* Bandwidth value is constant in octaves. */
+ mChans[0].Filter.setParams(BiquadType::HighPass, 1.0f, f0norm,
+ BiquadFilter::rcpQFromBandwidth(f0norm, 0.75f));
+ for(size_t i{1u};i < slot->Wet.Buffer.size();++i)
+ mChans[i].Filter.copyParamsFrom(mChans[0].Filter);
+
+ mOutTarget = target.Main->Buffer;
+ for(size_t i{0u};i < slot->Wet.Buffer.size();++i)
+ {
+ auto coeffs = GetAmbiIdentityRow(i);
+ ComputePanGains(target.Main, coeffs.data(), slot->Params.Gain, mChans[i].TargetGains);
+ }
+}
+
+void ModulatorState::process(const ALsizei samplesToDo, const FloatBufferLine *RESTRICT samplesIn, const ALsizei numInput, const al::span<FloatBufferLine> samplesOut)
+{
+ for(ALsizei base{0};base < samplesToDo;)
+ {
+ alignas(16) ALfloat modsamples[MAX_UPDATE_SAMPLES];
+ ALsizei td = mini(MAX_UPDATE_SAMPLES, samplesToDo-base);
+ ALsizei c, i;
+
+ mGetSamples(modsamples, mIndex, mStep, td);
+ mIndex += (mStep*td) & WAVEFORM_FRACMASK;
+ mIndex &= WAVEFORM_FRACMASK;
+
+ ASSUME(numInput > 0);
+ for(c = 0;c < numInput;c++)
+ {
+ alignas(16) ALfloat temps[MAX_UPDATE_SAMPLES];
+
+ mChans[c].Filter.process(temps, &samplesIn[c][base], td);
+ for(i = 0;i < td;i++)
+ temps[i] *= modsamples[i];
+
+ MixSamples(temps, samplesOut, mChans[c].CurrentGains, mChans[c].TargetGains,
+ samplesToDo-base, base, td);
+ }
+
+ base += td;
+ }
+}
+
+
+void Modulator_setParamf(EffectProps *props, ALCcontext *context, ALenum param, ALfloat val)
+{
+ switch(param)
+ {
+ case AL_RING_MODULATOR_FREQUENCY:
+ if(!(val >= AL_RING_MODULATOR_MIN_FREQUENCY && val <= AL_RING_MODULATOR_MAX_FREQUENCY))
+ SETERR_RETURN(context, AL_INVALID_VALUE,, "Modulator frequency out of range");
+ props->Modulator.Frequency = val;
+ break;
+
+ case AL_RING_MODULATOR_HIGHPASS_CUTOFF:
+ if(!(val >= AL_RING_MODULATOR_MIN_HIGHPASS_CUTOFF && val <= AL_RING_MODULATOR_MAX_HIGHPASS_CUTOFF))
+ SETERR_RETURN(context, AL_INVALID_VALUE,, "Modulator high-pass cutoff out of range");
+ props->Modulator.HighPassCutoff = val;
+ break;
+
+ default:
+ alSetError(context, AL_INVALID_ENUM, "Invalid modulator float property 0x%04x", param);
+ }
+}
+void Modulator_setParamfv(EffectProps *props, ALCcontext *context, ALenum param, const ALfloat *vals)
+{ Modulator_setParamf(props, context, param, vals[0]); }
+void Modulator_setParami(EffectProps *props, ALCcontext *context, ALenum param, ALint val)
+{
+ switch(param)
+ {
+ case AL_RING_MODULATOR_FREQUENCY:
+ case AL_RING_MODULATOR_HIGHPASS_CUTOFF:
+ Modulator_setParamf(props, context, param, static_cast<ALfloat>(val));
+ break;
+
+ case AL_RING_MODULATOR_WAVEFORM:
+ if(!(val >= AL_RING_MODULATOR_MIN_WAVEFORM && val <= AL_RING_MODULATOR_MAX_WAVEFORM))
+ SETERR_RETURN(context, AL_INVALID_VALUE,, "Invalid modulator waveform");
+ props->Modulator.Waveform = val;
+ break;
+
+ default:
+ alSetError(context, AL_INVALID_ENUM, "Invalid modulator integer property 0x%04x", param);
+ }
+}
+void Modulator_setParamiv(EffectProps *props, ALCcontext *context, ALenum param, const ALint *vals)
+{ Modulator_setParami(props, context, param, vals[0]); }
+
+void Modulator_getParami(const EffectProps *props, ALCcontext *context, ALenum param, ALint *val)
+{
+ switch(param)
+ {
+ case AL_RING_MODULATOR_FREQUENCY:
+ *val = static_cast<ALint>(props->Modulator.Frequency);
+ break;
+ case AL_RING_MODULATOR_HIGHPASS_CUTOFF:
+ *val = static_cast<ALint>(props->Modulator.HighPassCutoff);
+ break;
+ case AL_RING_MODULATOR_WAVEFORM:
+ *val = props->Modulator.Waveform;
+ break;
+
+ default:
+ alSetError(context, AL_INVALID_ENUM, "Invalid modulator integer property 0x%04x", param);
+ }
+}
+void Modulator_getParamiv(const EffectProps *props, ALCcontext *context, ALenum param, ALint *vals)
+{ Modulator_getParami(props, context, param, vals); }
+void Modulator_getParamf(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *val)
+{
+ switch(param)
+ {
+ case AL_RING_MODULATOR_FREQUENCY:
+ *val = props->Modulator.Frequency;
+ break;
+ case AL_RING_MODULATOR_HIGHPASS_CUTOFF:
+ *val = props->Modulator.HighPassCutoff;
+ break;
+
+ default:
+ alSetError(context, AL_INVALID_ENUM, "Invalid modulator float property 0x%04x", param);
+ }
+}
+void Modulator_getParamfv(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *vals)
+{ Modulator_getParamf(props, context, param, vals); }
+
+DEFINE_ALEFFECT_VTABLE(Modulator);
+
+
+struct ModulatorStateFactory final : public EffectStateFactory {
+ EffectState *create() override { return new ModulatorState{}; }
+ EffectProps getDefaultProps() const noexcept override;
+ const EffectVtable *getEffectVtable() const noexcept override { return &Modulator_vtable; }
+};
+
+EffectProps ModulatorStateFactory::getDefaultProps() const noexcept
+{
+ EffectProps props{};
+ props.Modulator.Frequency = AL_RING_MODULATOR_DEFAULT_FREQUENCY;
+ props.Modulator.HighPassCutoff = AL_RING_MODULATOR_DEFAULT_HIGHPASS_CUTOFF;
+ props.Modulator.Waveform = AL_RING_MODULATOR_DEFAULT_WAVEFORM;
+ return props;
+}
+
+} // namespace
+
+EffectStateFactory *ModulatorStateFactory_getFactory()
+{
+ static ModulatorStateFactory ModulatorFactory{};
+ return &ModulatorFactory;
+}
diff --git a/alc/effects/null.cpp b/alc/effects/null.cpp
new file mode 100644
index 00000000..e55c8699
--- /dev/null
+++ b/alc/effects/null.cpp
@@ -0,0 +1,164 @@
+#include "config.h"
+
+#include <cstdlib>
+
+#include "AL/al.h"
+#include "AL/alc.h"
+
+#include "alcmain.h"
+#include "alcontext.h"
+#include "alAuxEffectSlot.h"
+#include "alError.h"
+
+
+namespace {
+
+struct NullState final : public EffectState {
+ NullState();
+ ~NullState() override;
+
+ ALboolean deviceUpdate(const ALCdevice *device) override;
+ void update(const ALCcontext *context, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target) override;
+ void process(const ALsizei samplesToDo, const FloatBufferLine *RESTRICT samplesIn, const ALsizei numInput, const al::span<FloatBufferLine> samplesOut) override;
+
+ DEF_NEWDEL(NullState)
+};
+
+/* This constructs the effect state. It's called when the object is first
+ * created.
+ */
+NullState::NullState() = default;
+
+/* This destructs the effect state. It's called only when the effect instance
+ * is no longer used.
+ */
+NullState::~NullState() = default;
+
+/* This updates the device-dependant effect state. This is called on state
+ * initialization and any time the device parameters (e.g. playback frequency,
+ * format) have been changed. Will always be followed by a call to the update
+ * method, if successful.
+ */
+ALboolean NullState::deviceUpdate(const ALCdevice* /*device*/)
+{
+ return AL_TRUE;
+}
+
+/* This updates the effect state with new properties. This is called any time
+ * the effect is (re)loaded into a slot.
+ */
+void NullState::update(const ALCcontext* /*context*/, const ALeffectslot* /*slot*/,
+ const EffectProps* /*props*/, const EffectTarget /*target*/)
+{
+}
+
+/* This processes the effect state, for the given number of samples from the
+ * input to the output buffer. The result should be added to the output buffer,
+ * not replace it.
+ */
+void NullState::process(const ALsizei /*samplesToDo*/,
+ const FloatBufferLine *RESTRICT /*samplesIn*/, const ALsizei /*numInput*/,
+ const al::span<FloatBufferLine> /*samplesOut*/)
+{
+}
+
+
+void NullEffect_setParami(EffectProps* /*props*/, ALCcontext *context, ALenum param, ALint /*val*/)
+{
+ switch(param)
+ {
+ default:
+ alSetError(context, AL_INVALID_ENUM, "Invalid null effect integer property 0x%04x", param);
+ }
+}
+void NullEffect_setParamiv(EffectProps *props, ALCcontext *context, ALenum param, const ALint *vals)
+{
+ switch(param)
+ {
+ default:
+ NullEffect_setParami(props, context, param, vals[0]);
+ }
+}
+void NullEffect_setParamf(EffectProps* /*props*/, ALCcontext *context, ALenum param, ALfloat /*val*/)
+{
+ switch(param)
+ {
+ default:
+ alSetError(context, AL_INVALID_ENUM, "Invalid null effect float property 0x%04x", param);
+ }
+}
+void NullEffect_setParamfv(EffectProps *props, ALCcontext *context, ALenum param, const ALfloat *vals)
+{
+ switch(param)
+ {
+ default:
+ NullEffect_setParamf(props, context, param, vals[0]);
+ }
+}
+
+void NullEffect_getParami(const EffectProps* /*props*/, ALCcontext *context, ALenum param, ALint* /*val*/)
+{
+ switch(param)
+ {
+ default:
+ alSetError(context, AL_INVALID_ENUM, "Invalid null effect integer property 0x%04x", param);
+ }
+}
+void NullEffect_getParamiv(const EffectProps *props, ALCcontext *context, ALenum param, ALint *vals)
+{
+ switch(param)
+ {
+ default:
+ NullEffect_getParami(props, context, param, vals);
+ }
+}
+void NullEffect_getParamf(const EffectProps* /*props*/, ALCcontext *context, ALenum param, ALfloat* /*val*/)
+{
+ switch(param)
+ {
+ default:
+ alSetError(context, AL_INVALID_ENUM, "Invalid null effect float property 0x%04x", param);
+ }
+}
+void NullEffect_getParamfv(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *vals)
+{
+ switch(param)
+ {
+ default:
+ NullEffect_getParamf(props, context, param, vals);
+ }
+}
+
+DEFINE_ALEFFECT_VTABLE(NullEffect);
+
+
+struct NullStateFactory final : public EffectStateFactory {
+ EffectState *create() override;
+ EffectProps getDefaultProps() const noexcept override;
+ const EffectVtable *getEffectVtable() const noexcept override;
+};
+
+/* Creates EffectState objects of the appropriate type. */
+EffectState *NullStateFactory::create()
+{ return new NullState{}; }
+
+/* Returns an ALeffectProps initialized with this effect type's default
+ * property values.
+ */
+EffectProps NullStateFactory::getDefaultProps() const noexcept
+{
+ EffectProps props{};
+ return props;
+}
+
+/* Returns a pointer to this effect type's global set/get vtable. */
+const EffectVtable *NullStateFactory::getEffectVtable() const noexcept
+{ return &NullEffect_vtable; }
+
+} // namespace
+
+EffectStateFactory *NullStateFactory_getFactory()
+{
+ static NullStateFactory NullFactory{};
+ return &NullFactory;
+}
diff --git a/alc/effects/pshifter.cpp b/alc/effects/pshifter.cpp
new file mode 100644
index 00000000..39d3cf1a
--- /dev/null
+++ b/alc/effects/pshifter.cpp
@@ -0,0 +1,405 @@
+/**
+ * OpenAL cross platform audio library
+ * Copyright (C) 2018 by Raul Herraiz.
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ * Or go to http://www.gnu.org/copyleft/lgpl.html
+ */
+
+#include "config.h"
+
+#ifdef HAVE_SSE_INTRINSICS
+#include <emmintrin.h>
+#endif
+
+#include <cmath>
+#include <cstdlib>
+#include <array>
+#include <complex>
+#include <algorithm>
+
+#include "alcmain.h"
+#include "alcontext.h"
+#include "alAuxEffectSlot.h"
+#include "alError.h"
+#include "alu.h"
+
+#include "alcomplex.h"
+
+
+namespace {
+
+using complex_d = std::complex<double>;
+
+#define STFT_SIZE 1024
+#define STFT_HALF_SIZE (STFT_SIZE>>1)
+#define OVERSAMP (1<<2)
+
+#define STFT_STEP (STFT_SIZE / OVERSAMP)
+#define FIFO_LATENCY (STFT_STEP * (OVERSAMP-1))
+
+inline int double2int(double d)
+{
+#if defined(HAVE_SSE_INTRINSICS)
+ return _mm_cvttsd_si32(_mm_set_sd(d));
+
+#elif ((defined(__GNUC__) || defined(__clang__)) && (defined(__i386__) || defined(__x86_64__)) && \
+ !defined(__SSE2_MATH__)) || (defined(_MSC_VER) && defined(_M_IX86_FP) && _M_IX86_FP < 2)
+
+ int sign, shift;
+ int64_t mant;
+ union {
+ double d;
+ int64_t i64;
+ } conv;
+
+ conv.d = d;
+ sign = (conv.i64>>63) | 1;
+ shift = ((conv.i64>>52)&0x7ff) - (1023+52);
+
+ /* Over/underflow */
+ if(UNLIKELY(shift >= 63 || shift < -52))
+ return 0;
+
+ mant = (conv.i64&0xfffffffffffff_i64) | 0x10000000000000_i64;
+ if(LIKELY(shift < 0))
+ return (int)(mant >> -shift) * sign;
+ return (int)(mant << shift) * sign;
+
+#else
+
+ return static_cast<int>(d);
+#endif
+}
+
+/* Define a Hann window, used to filter the STFT input and output. */
+/* Making this constexpr seems to require C++14. */
+std::array<ALdouble,STFT_SIZE> InitHannWindow()
+{
+ std::array<ALdouble,STFT_SIZE> ret;
+ /* Create lookup table of the Hann window for the desired size, i.e. HIL_SIZE */
+ for(ALsizei i{0};i < STFT_SIZE>>1;i++)
+ {
+ ALdouble val = std::sin(al::MathDefs<double>::Pi() * i / ALdouble{STFT_SIZE-1});
+ ret[i] = ret[STFT_SIZE-1-i] = val * val;
+ }
+ return ret;
+}
+alignas(16) const std::array<ALdouble,STFT_SIZE> HannWindow = InitHannWindow();
+
+
+struct ALphasor {
+ ALdouble Amplitude;
+ ALdouble Phase;
+};
+
+struct ALfrequencyDomain {
+ ALdouble Amplitude;
+ ALdouble Frequency;
+};
+
+
+/* Converts complex to ALphasor */
+inline ALphasor rect2polar(const complex_d &number)
+{
+ ALphasor polar;
+ polar.Amplitude = std::abs(number);
+ polar.Phase = std::arg(number);
+ return polar;
+}
+
+/* Converts ALphasor to complex */
+inline complex_d polar2rect(const ALphasor &number)
+{ return std::polar<double>(number.Amplitude, number.Phase); }
+
+
+struct PshifterState final : public EffectState {
+ /* Effect parameters */
+ ALsizei mCount;
+ ALsizei mPitchShiftI;
+ ALfloat mPitchShift;
+ ALfloat mFreqPerBin;
+
+ /* Effects buffers */
+ ALfloat mInFIFO[STFT_SIZE];
+ ALfloat mOutFIFO[STFT_STEP];
+ ALdouble mLastPhase[STFT_HALF_SIZE+1];
+ ALdouble mSumPhase[STFT_HALF_SIZE+1];
+ ALdouble mOutputAccum[STFT_SIZE];
+
+ complex_d mFFTbuffer[STFT_SIZE];
+
+ ALfrequencyDomain mAnalysis_buffer[STFT_HALF_SIZE+1];
+ ALfrequencyDomain mSyntesis_buffer[STFT_HALF_SIZE+1];
+
+ alignas(16) ALfloat mBufferOut[BUFFERSIZE];
+
+ /* Effect gains for each output channel */
+ ALfloat mCurrentGains[MAX_OUTPUT_CHANNELS];
+ ALfloat mTargetGains[MAX_OUTPUT_CHANNELS];
+
+
+ ALboolean deviceUpdate(const ALCdevice *device) override;
+ void update(const ALCcontext *context, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target) override;
+ void process(const ALsizei samplesToDo, const FloatBufferLine *RESTRICT samplesIn, const ALsizei numInput, const al::span<FloatBufferLine> samplesOut) override;
+
+ DEF_NEWDEL(PshifterState)
+};
+
+ALboolean PshifterState::deviceUpdate(const ALCdevice *device)
+{
+ /* (Re-)initializing parameters and clear the buffers. */
+ mCount = FIFO_LATENCY;
+ mPitchShiftI = FRACTIONONE;
+ mPitchShift = 1.0f;
+ mFreqPerBin = device->Frequency / static_cast<ALfloat>(STFT_SIZE);
+
+ std::fill(std::begin(mInFIFO), std::end(mInFIFO), 0.0f);
+ std::fill(std::begin(mOutFIFO), std::end(mOutFIFO), 0.0f);
+ std::fill(std::begin(mLastPhase), std::end(mLastPhase), 0.0);
+ std::fill(std::begin(mSumPhase), std::end(mSumPhase), 0.0);
+ std::fill(std::begin(mOutputAccum), std::end(mOutputAccum), 0.0);
+ std::fill(std::begin(mFFTbuffer), std::end(mFFTbuffer), complex_d{});
+ std::fill(std::begin(mAnalysis_buffer), std::end(mAnalysis_buffer), ALfrequencyDomain{});
+ std::fill(std::begin(mSyntesis_buffer), std::end(mSyntesis_buffer), ALfrequencyDomain{});
+
+ std::fill(std::begin(mCurrentGains), std::end(mCurrentGains), 0.0f);
+ std::fill(std::begin(mTargetGains), std::end(mTargetGains), 0.0f);
+
+ return AL_TRUE;
+}
+
+void PshifterState::update(const ALCcontext*, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target)
+{
+ const float pitch{std::pow(2.0f,
+ static_cast<ALfloat>(props->Pshifter.CoarseTune*100 + props->Pshifter.FineTune) / 1200.0f
+ )};
+ mPitchShiftI = fastf2i(pitch*FRACTIONONE);
+ mPitchShift = mPitchShiftI * (1.0f/FRACTIONONE);
+
+ ALfloat coeffs[MAX_AMBI_CHANNELS];
+ CalcDirectionCoeffs({0.0f, 0.0f, -1.0f}, 0.0f, coeffs);
+
+ mOutTarget = target.Main->Buffer;
+ ComputePanGains(target.Main, coeffs, slot->Params.Gain, mTargetGains);
+}
+
+void PshifterState::process(const ALsizei samplesToDo, const FloatBufferLine *RESTRICT samplesIn, const ALsizei /*numInput*/, const al::span<FloatBufferLine> samplesOut)
+{
+ /* Pitch shifter engine based on the work of Stephan Bernsee.
+ * http://blogs.zynaptiq.com/bernsee/pitch-shifting-using-the-ft/
+ */
+
+ static constexpr ALdouble expected{al::MathDefs<double>::Tau() / OVERSAMP};
+ const ALdouble freq_per_bin{mFreqPerBin};
+ ALfloat *RESTRICT bufferOut{mBufferOut};
+ ALsizei count{mCount};
+
+ for(ALsizei i{0};i < samplesToDo;)
+ {
+ do {
+ /* Fill FIFO buffer with samples data */
+ mInFIFO[count] = samplesIn[0][i];
+ bufferOut[i] = mOutFIFO[count - FIFO_LATENCY];
+
+ count++;
+ } while(++i < samplesToDo && count < STFT_SIZE);
+
+ /* Check whether FIFO buffer is filled */
+ if(count < STFT_SIZE) break;
+ count = FIFO_LATENCY;
+
+ /* Real signal windowing and store in FFTbuffer */
+ for(ALsizei k{0};k < STFT_SIZE;k++)
+ {
+ mFFTbuffer[k].real(mInFIFO[k] * HannWindow[k]);
+ mFFTbuffer[k].imag(0.0);
+ }
+
+ /* ANALYSIS */
+ /* Apply FFT to FFTbuffer data */
+ complex_fft(mFFTbuffer, -1.0);
+
+ /* Analyze the obtained data. Since the real FFT is symmetric, only
+ * STFT_HALF_SIZE+1 samples are needed.
+ */
+ for(ALsizei k{0};k < STFT_HALF_SIZE+1;k++)
+ {
+ /* Compute amplitude and phase */
+ ALphasor component{rect2polar(mFFTbuffer[k])};
+
+ /* Compute phase difference and subtract expected phase difference */
+ double tmp{(component.Phase - mLastPhase[k]) - k*expected};
+
+ /* Map delta phase into +/- Pi interval */
+ int qpd{double2int(tmp / al::MathDefs<double>::Pi())};
+ tmp -= al::MathDefs<double>::Pi() * (qpd + (qpd%2));
+
+ /* Get deviation from bin frequency from the +/- Pi interval */
+ tmp /= expected;
+
+ /* Compute the k-th partials' true frequency, twice the amplitude
+ * for maintain the gain (because half of bins are used) and store
+ * amplitude and true frequency in analysis buffer.
+ */
+ mAnalysis_buffer[k].Amplitude = 2.0 * component.Amplitude;
+ mAnalysis_buffer[k].Frequency = (k + tmp) * freq_per_bin;
+
+ /* Store actual phase[k] for the calculations in the next frame*/
+ mLastPhase[k] = component.Phase;
+ }
+
+ /* PROCESSING */
+ /* pitch shifting */
+ for(ALsizei k{0};k < STFT_HALF_SIZE+1;k++)
+ {
+ mSyntesis_buffer[k].Amplitude = 0.0;
+ mSyntesis_buffer[k].Frequency = 0.0;
+ }
+
+ for(ALsizei k{0};k < STFT_HALF_SIZE+1;k++)
+ {
+ ALsizei j{(k*mPitchShiftI) >> FRACTIONBITS};
+ if(j >= STFT_HALF_SIZE+1) break;
+
+ mSyntesis_buffer[j].Amplitude += mAnalysis_buffer[k].Amplitude;
+ mSyntesis_buffer[j].Frequency = mAnalysis_buffer[k].Frequency * mPitchShift;
+ }
+
+ /* SYNTHESIS */
+ /* Synthesis the processing data */
+ for(ALsizei k{0};k < STFT_HALF_SIZE+1;k++)
+ {
+ ALphasor component;
+ ALdouble tmp;
+
+ /* Compute bin deviation from scaled freq */
+ tmp = mSyntesis_buffer[k].Frequency/freq_per_bin - k;
+
+ /* Calculate actual delta phase and accumulate it to get bin phase */
+ mSumPhase[k] += (k + tmp) * expected;
+
+ component.Amplitude = mSyntesis_buffer[k].Amplitude;
+ component.Phase = mSumPhase[k];
+
+ /* Compute phasor component to cartesian complex number and storage it into FFTbuffer*/
+ mFFTbuffer[k] = polar2rect(component);
+ }
+ /* zero negative frequencies for recontruct a real signal */
+ for(ALsizei k{STFT_HALF_SIZE+1};k < STFT_SIZE;k++)
+ mFFTbuffer[k] = complex_d{};
+
+ /* Apply iFFT to buffer data */
+ complex_fft(mFFTbuffer, 1.0);
+
+ /* Windowing and add to output */
+ for(ALsizei k{0};k < STFT_SIZE;k++)
+ mOutputAccum[k] += HannWindow[k] * mFFTbuffer[k].real() /
+ (0.5 * STFT_HALF_SIZE * OVERSAMP);
+
+ /* Shift accumulator, input & output FIFO */
+ ALsizei j, k;
+ for(k = 0;k < STFT_STEP;k++) mOutFIFO[k] = static_cast<ALfloat>(mOutputAccum[k]);
+ for(j = 0;k < STFT_SIZE;k++,j++) mOutputAccum[j] = mOutputAccum[k];
+ for(;j < STFT_SIZE;j++) mOutputAccum[j] = 0.0;
+ for(k = 0;k < FIFO_LATENCY;k++)
+ mInFIFO[k] = mInFIFO[k+STFT_STEP];
+ }
+ mCount = count;
+
+ /* Now, mix the processed sound data to the output. */
+ MixSamples(bufferOut, samplesOut, mCurrentGains, mTargetGains, maxi(samplesToDo, 512), 0,
+ samplesToDo);
+}
+
+
+void Pshifter_setParamf(EffectProps*, ALCcontext *context, ALenum param, ALfloat)
+{ alSetError(context, AL_INVALID_ENUM, "Invalid pitch shifter float property 0x%04x", param); }
+void Pshifter_setParamfv(EffectProps*, ALCcontext *context, ALenum param, const ALfloat*)
+{ alSetError(context, AL_INVALID_ENUM, "Invalid pitch shifter float-vector property 0x%04x", param); }
+
+void Pshifter_setParami(EffectProps *props, ALCcontext *context, ALenum param, ALint val)
+{
+ switch(param)
+ {
+ case AL_PITCH_SHIFTER_COARSE_TUNE:
+ if(!(val >= AL_PITCH_SHIFTER_MIN_COARSE_TUNE && val <= AL_PITCH_SHIFTER_MAX_COARSE_TUNE))
+ SETERR_RETURN(context, AL_INVALID_VALUE,,"Pitch shifter coarse tune out of range");
+ props->Pshifter.CoarseTune = val;
+ break;
+
+ case AL_PITCH_SHIFTER_FINE_TUNE:
+ if(!(val >= AL_PITCH_SHIFTER_MIN_FINE_TUNE && val <= AL_PITCH_SHIFTER_MAX_FINE_TUNE))
+ SETERR_RETURN(context, AL_INVALID_VALUE,,"Pitch shifter fine tune out of range");
+ props->Pshifter.FineTune = val;
+ break;
+
+ default:
+ alSetError(context, AL_INVALID_ENUM, "Invalid pitch shifter integer property 0x%04x", param);
+ }
+}
+void Pshifter_setParamiv(EffectProps *props, ALCcontext *context, ALenum param, const ALint *vals)
+{ Pshifter_setParami(props, context, param, vals[0]); }
+
+void Pshifter_getParami(const EffectProps *props, ALCcontext *context, ALenum param, ALint *val)
+{
+ switch(param)
+ {
+ case AL_PITCH_SHIFTER_COARSE_TUNE:
+ *val = props->Pshifter.CoarseTune;
+ break;
+ case AL_PITCH_SHIFTER_FINE_TUNE:
+ *val = props->Pshifter.FineTune;
+ break;
+
+ default:
+ alSetError(context, AL_INVALID_ENUM, "Invalid pitch shifter integer property 0x%04x", param);
+ }
+}
+void Pshifter_getParamiv(const EffectProps *props, ALCcontext *context, ALenum param, ALint *vals)
+{ Pshifter_getParami(props, context, param, vals); }
+
+void Pshifter_getParamf(const EffectProps*, ALCcontext *context, ALenum param, ALfloat*)
+{ alSetError(context, AL_INVALID_ENUM, "Invalid pitch shifter float property 0x%04x", param); }
+void Pshifter_getParamfv(const EffectProps*, ALCcontext *context, ALenum param, ALfloat*)
+{ alSetError(context, AL_INVALID_ENUM, "Invalid pitch shifter float vector-property 0x%04x", param); }
+
+DEFINE_ALEFFECT_VTABLE(Pshifter);
+
+
+struct PshifterStateFactory final : public EffectStateFactory {
+ EffectState *create() override;
+ EffectProps getDefaultProps() const noexcept override;
+ const EffectVtable *getEffectVtable() const noexcept override { return &Pshifter_vtable; }
+};
+
+EffectState *PshifterStateFactory::create()
+{ return new PshifterState{}; }
+
+EffectProps PshifterStateFactory::getDefaultProps() const noexcept
+{
+ EffectProps props{};
+ props.Pshifter.CoarseTune = AL_PITCH_SHIFTER_DEFAULT_COARSE_TUNE;
+ props.Pshifter.FineTune = AL_PITCH_SHIFTER_DEFAULT_FINE_TUNE;
+ return props;
+}
+
+} // namespace
+
+EffectStateFactory *PshifterStateFactory_getFactory()
+{
+ static PshifterStateFactory PshifterFactory{};
+ return &PshifterFactory;
+}
diff --git a/alc/effects/reverb.cpp b/alc/effects/reverb.cpp
new file mode 100644
index 00000000..ac996b3f
--- /dev/null
+++ b/alc/effects/reverb.cpp
@@ -0,0 +1,2102 @@
+/**
+ * Ambisonic reverb engine for the OpenAL cross platform audio library
+ * Copyright (C) 2008-2017 by Chris Robinson and Christopher Fitzgerald.
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ * Or go to http://www.gnu.org/copyleft/lgpl.html
+ */
+
+#include "config.h"
+
+#include <cstdio>
+#include <cstdlib>
+#include <cmath>
+
+#include <array>
+#include <numeric>
+#include <algorithm>
+#include <functional>
+
+#include "alcmain.h"
+#include "alcontext.h"
+#include "alu.h"
+#include "alAuxEffectSlot.h"
+#include "alListener.h"
+#include "alError.h"
+#include "bformatdec.h"
+#include "filters/biquad.h"
+#include "vector.h"
+#include "vecmat.h"
+
+/* This is a user config option for modifying the overall output of the reverb
+ * effect.
+ */
+ALfloat ReverbBoost = 1.0f;
+
+namespace {
+
+using namespace std::placeholders;
+
+/* The number of samples used for cross-faded delay lines. This can be used
+ * to balance the compensation for abrupt line changes and attenuation due to
+ * minimally lengthed recursive lines. Try to keep this below the device
+ * update size.
+ */
+constexpr int FADE_SAMPLES{128};
+
+/* The number of spatialized lines or channels to process. Four channels allows
+ * for a 3D A-Format response. NOTE: This can't be changed without taking care
+ * of the conversion matrices, and a few places where the length arrays are
+ * assumed to have 4 elements.
+ */
+constexpr int NUM_LINES{4};
+
+
+/* The B-Format to A-Format conversion matrix. The arrangement of rows is
+ * deliberately chosen to align the resulting lines to their spatial opposites
+ * (0:above front left <-> 3:above back right, 1:below front right <-> 2:below
+ * back left). It's not quite opposite, since the A-Format results in a
+ * tetrahedron, but it's close enough. Should the model be extended to 8-lines
+ * in the future, true opposites can be used.
+ */
+alignas(16) constexpr ALfloat B2A[NUM_LINES][MAX_AMBI_CHANNELS]{
+ { 0.288675134595f, 0.288675134595f, 0.288675134595f, 0.288675134595f },
+ { 0.288675134595f, -0.288675134595f, -0.288675134595f, 0.288675134595f },
+ { 0.288675134595f, 0.288675134595f, -0.288675134595f, -0.288675134595f },
+ { 0.288675134595f, -0.288675134595f, 0.288675134595f, -0.288675134595f }
+};
+
+/* Converts A-Format to B-Format. */
+alignas(16) constexpr ALfloat A2B[NUM_LINES][NUM_LINES]{
+ { 0.866025403785f, 0.866025403785f, 0.866025403785f, 0.866025403785f },
+ { 0.866025403785f, -0.866025403785f, 0.866025403785f, -0.866025403785f },
+ { 0.866025403785f, -0.866025403785f, -0.866025403785f, 0.866025403785f },
+ { 0.866025403785f, 0.866025403785f, -0.866025403785f, -0.866025403785f }
+};
+
+
+constexpr ALfloat FadeStep{1.0f / FADE_SAMPLES};
+
+/* The all-pass and delay lines have a variable length dependent on the
+ * effect's density parameter, which helps alter the perceived environment
+ * size. The size-to-density conversion is a cubed scale:
+ *
+ * density = min(1.0, pow(size, 3.0) / DENSITY_SCALE);
+ *
+ * The line lengths scale linearly with room size, so the inverse density
+ * conversion is needed, taking the cube root of the re-scaled density to
+ * calculate the line length multiplier:
+ *
+ * length_mult = max(5.0, cbrt(density*DENSITY_SCALE));
+ *
+ * The density scale below will result in a max line multiplier of 50, for an
+ * effective size range of 5m to 50m.
+ */
+constexpr ALfloat DENSITY_SCALE{125000.0f};
+
+/* All delay line lengths are specified in seconds.
+ *
+ * To approximate early reflections, we break them up into primary (those
+ * arriving from the same direction as the source) and secondary (those
+ * arriving from the opposite direction).
+ *
+ * The early taps decorrelate the 4-channel signal to approximate an average
+ * room response for the primary reflections after the initial early delay.
+ *
+ * Given an average room dimension (d_a) and the speed of sound (c) we can
+ * calculate the average reflection delay (r_a) regardless of listener and
+ * source positions as:
+ *
+ * r_a = d_a / c
+ * c = 343.3
+ *
+ * This can extended to finding the average difference (r_d) between the
+ * maximum (r_1) and minimum (r_0) reflection delays:
+ *
+ * r_0 = 2 / 3 r_a
+ * = r_a - r_d / 2
+ * = r_d
+ * r_1 = 4 / 3 r_a
+ * = r_a + r_d / 2
+ * = 2 r_d
+ * r_d = 2 / 3 r_a
+ * = r_1 - r_0
+ *
+ * As can be determined by integrating the 1D model with a source (s) and
+ * listener (l) positioned across the dimension of length (d_a):
+ *
+ * r_d = int_(l=0)^d_a (int_(s=0)^d_a |2 d_a - 2 (l + s)| ds) dl / c
+ *
+ * The initial taps (T_(i=0)^N) are then specified by taking a power series
+ * that ranges between r_0 and half of r_1 less r_0:
+ *
+ * R_i = 2^(i / (2 N - 1)) r_d
+ * = r_0 + (2^(i / (2 N - 1)) - 1) r_d
+ * = r_0 + T_i
+ * T_i = R_i - r_0
+ * = (2^(i / (2 N - 1)) - 1) r_d
+ *
+ * Assuming an average of 1m, we get the following taps:
+ */
+constexpr std::array<ALfloat,NUM_LINES> EARLY_TAP_LENGTHS{{
+ 0.0000000e+0f, 2.0213520e-4f, 4.2531060e-4f, 6.7171600e-4f
+}};
+
+/* The early all-pass filter lengths are based on the early tap lengths:
+ *
+ * A_i = R_i / a
+ *
+ * Where a is the approximate maximum all-pass cycle limit (20).
+ */
+constexpr std::array<ALfloat,NUM_LINES> EARLY_ALLPASS_LENGTHS{{
+ 9.7096800e-5f, 1.0720356e-4f, 1.1836234e-4f, 1.3068260e-4f
+}};
+
+/* The early delay lines are used to transform the primary reflections into
+ * the secondary reflections. The A-format is arranged in such a way that
+ * the channels/lines are spatially opposite:
+ *
+ * C_i is opposite C_(N-i-1)
+ *
+ * The delays of the two opposing reflections (R_i and O_i) from a source
+ * anywhere along a particular dimension always sum to twice its full delay:
+ *
+ * 2 r_a = R_i + O_i
+ *
+ * With that in mind we can determine the delay between the two reflections
+ * and thus specify our early line lengths (L_(i=0)^N) using:
+ *
+ * O_i = 2 r_a - R_(N-i-1)
+ * L_i = O_i - R_(N-i-1)
+ * = 2 (r_a - R_(N-i-1))
+ * = 2 (r_a - T_(N-i-1) - r_0)
+ * = 2 r_a (1 - (2 / 3) 2^((N - i - 1) / (2 N - 1)))
+ *
+ * Using an average dimension of 1m, we get:
+ */
+constexpr std::array<ALfloat,NUM_LINES> EARLY_LINE_LENGTHS{{
+ 5.9850400e-4f, 1.0913150e-3f, 1.5376658e-3f, 1.9419362e-3f
+}};
+
+/* The late all-pass filter lengths are based on the late line lengths:
+ *
+ * A_i = (5 / 3) L_i / r_1
+ */
+constexpr std::array<ALfloat,NUM_LINES> LATE_ALLPASS_LENGTHS{{
+ 1.6182800e-4f, 2.0389060e-4f, 2.8159360e-4f, 3.2365600e-4f
+}};
+constexpr auto LATE_ALLPASS_LENGTHS_size = LATE_ALLPASS_LENGTHS.size();
+
+/* The late lines are used to approximate the decaying cycle of recursive
+ * late reflections.
+ *
+ * Splitting the lines in half, we start with the shortest reflection paths
+ * (L_(i=0)^(N/2)):
+ *
+ * L_i = 2^(i / (N - 1)) r_d
+ *
+ * Then for the opposite (longest) reflection paths (L_(i=N/2)^N):
+ *
+ * L_i = 2 r_a - L_(i-N/2)
+ * = 2 r_a - 2^((i - N / 2) / (N - 1)) r_d
+ *
+ * For our 1m average room, we get:
+ */
+constexpr std::array<ALfloat,NUM_LINES> LATE_LINE_LENGTHS{{
+ 1.9419362e-3f, 2.4466860e-3f, 3.3791220e-3f, 3.8838720e-3f
+}};
+constexpr auto LATE_LINE_LENGTHS_size = LATE_LINE_LENGTHS.size();
+
+
+struct DelayLineI {
+ /* The delay lines use interleaved samples, with the lengths being powers
+ * of 2 to allow the use of bit-masking instead of a modulus for wrapping.
+ */
+ ALsizei Mask{0};
+ ALfloat (*Line)[NUM_LINES]{nullptr};
+
+
+ void write(ALsizei offset, const ALsizei c, const ALfloat *RESTRICT in, const ALsizei count) const noexcept
+ {
+ ASSUME(count > 0);
+ for(ALsizei i{0};i < count;)
+ {
+ offset &= Mask;
+ ALsizei td{mini(Mask+1 - offset, count - i)};
+ do {
+ Line[offset++][c] = in[i++];
+ } while(--td);
+ }
+ }
+};
+
+struct VecAllpass {
+ DelayLineI Delay;
+ ALfloat Coeff{0.0f};
+ ALsizei Offset[NUM_LINES][2]{};
+
+ void processFaded(const al::span<FloatBufferLine,NUM_LINES> samples, ALsizei offset,
+ const ALfloat xCoeff, const ALfloat yCoeff, ALfloat fade, const ALsizei todo);
+ void processUnfaded(const al::span<FloatBufferLine,NUM_LINES> samples, ALsizei offset,
+ const ALfloat xCoeff, const ALfloat yCoeff, const ALsizei todo);
+};
+
+struct T60Filter {
+ /* Two filters are used to adjust the signal. One to control the low
+ * frequencies, and one to control the high frequencies.
+ */
+ ALfloat MidGain[2]{0.0f, 0.0f};
+ BiquadFilter HFFilter, LFFilter;
+
+ void calcCoeffs(const ALfloat length, const ALfloat lfDecayTime, const ALfloat mfDecayTime,
+ const ALfloat hfDecayTime, const ALfloat lf0norm, const ALfloat hf0norm);
+
+ /* Applies the two T60 damping filter sections. */
+ void process(ALfloat *samples, const ALsizei todo)
+ {
+ HFFilter.process(samples, samples, todo);
+ LFFilter.process(samples, samples, todo);
+ }
+};
+
+struct EarlyReflections {
+ /* A Gerzon vector all-pass filter is used to simulate initial diffusion.
+ * The spread from this filter also helps smooth out the reverb tail.
+ */
+ VecAllpass VecAp;
+
+ /* An echo line is used to complete the second half of the early
+ * reflections.
+ */
+ DelayLineI Delay;
+ ALsizei Offset[NUM_LINES][2]{};
+ ALfloat Coeff[NUM_LINES][2]{};
+
+ /* The gain for each output channel based on 3D panning. */
+ ALfloat CurrentGain[NUM_LINES][MAX_OUTPUT_CHANNELS]{};
+ ALfloat PanGain[NUM_LINES][MAX_OUTPUT_CHANNELS]{};
+
+ void updateLines(const ALfloat density, const ALfloat diffusion, const ALfloat decayTime,
+ const ALfloat frequency);
+};
+
+struct LateReverb {
+ /* A recursive delay line is used fill in the reverb tail. */
+ DelayLineI Delay;
+ ALsizei Offset[NUM_LINES][2]{};
+
+ /* Attenuation to compensate for the modal density and decay rate of the
+ * late lines.
+ */
+ ALfloat DensityGain[2]{0.0f, 0.0f};
+
+ /* T60 decay filters are used to simulate absorption. */
+ T60Filter T60[NUM_LINES];
+
+ /* A Gerzon vector all-pass filter is used to simulate diffusion. */
+ VecAllpass VecAp;
+
+ /* The gain for each output channel based on 3D panning. */
+ ALfloat CurrentGain[NUM_LINES][MAX_OUTPUT_CHANNELS]{};
+ ALfloat PanGain[NUM_LINES][MAX_OUTPUT_CHANNELS]{};
+
+ void updateLines(const ALfloat density, const ALfloat diffusion, const ALfloat lfDecayTime,
+ const ALfloat mfDecayTime, const ALfloat hfDecayTime, const ALfloat lf0norm,
+ const ALfloat hf0norm, const ALfloat frequency);
+};
+
+struct ReverbState final : public EffectState {
+ /* All delay lines are allocated as a single buffer to reduce memory
+ * fragmentation and management code.
+ */
+ al::vector<ALfloat,16> mSampleBuffer;
+
+ struct {
+ /* Calculated parameters which indicate if cross-fading is needed after
+ * an update.
+ */
+ ALfloat Density{AL_EAXREVERB_DEFAULT_DENSITY};
+ ALfloat Diffusion{AL_EAXREVERB_DEFAULT_DIFFUSION};
+ ALfloat DecayTime{AL_EAXREVERB_DEFAULT_DECAY_TIME};
+ ALfloat HFDecayTime{AL_EAXREVERB_DEFAULT_DECAY_HFRATIO * AL_EAXREVERB_DEFAULT_DECAY_TIME};
+ ALfloat LFDecayTime{AL_EAXREVERB_DEFAULT_DECAY_LFRATIO * AL_EAXREVERB_DEFAULT_DECAY_TIME};
+ ALfloat HFReference{AL_EAXREVERB_DEFAULT_HFREFERENCE};
+ ALfloat LFReference{AL_EAXREVERB_DEFAULT_LFREFERENCE};
+ } mParams;
+
+ /* Master effect filters */
+ struct {
+ BiquadFilter Lp;
+ BiquadFilter Hp;
+ } mFilter[NUM_LINES];
+
+ /* Core delay line (early reflections and late reverb tap from this). */
+ DelayLineI mDelay;
+
+ /* Tap points for early reflection delay. */
+ ALsizei mEarlyDelayTap[NUM_LINES][2]{};
+ ALfloat mEarlyDelayCoeff[NUM_LINES][2]{};
+
+ /* Tap points for late reverb feed and delay. */
+ ALsizei mLateFeedTap{};
+ ALsizei mLateDelayTap[NUM_LINES][2]{};
+
+ /* Coefficients for the all-pass and line scattering matrices. */
+ ALfloat mMixX{0.0f};
+ ALfloat mMixY{0.0f};
+
+ EarlyReflections mEarly;
+
+ LateReverb mLate;
+
+ /* Indicates the cross-fade point for delay line reads [0,FADE_SAMPLES]. */
+ ALsizei mFadeCount{0};
+
+ /* Maximum number of samples to process at once. */
+ ALsizei mMaxUpdate[2]{BUFFERSIZE, BUFFERSIZE};
+
+ /* The current write offset for all delay lines. */
+ ALsizei mOffset{0};
+
+ /* Temporary storage used when processing. */
+ alignas(16) std::array<FloatBufferLine,NUM_LINES> mTempSamples{};
+ alignas(16) std::array<FloatBufferLine,NUM_LINES> mEarlyBuffer{};
+ alignas(16) std::array<FloatBufferLine,NUM_LINES> mLateBuffer{};
+
+ using MixOutT = void (ReverbState::*)(const al::span<FloatBufferLine> samplesOut,
+ const ALsizei todo);
+
+ MixOutT mMixOut{&ReverbState::MixOutPlain};
+ std::array<ALfloat,MAX_AMBI_ORDER+1> mOrderScales{};
+ std::array<std::array<BandSplitter,NUM_LINES>,2> mAmbiSplitter;
+
+
+ void MixOutPlain(const al::span<FloatBufferLine> samplesOut, const ALsizei todo)
+ {
+ ASSUME(todo > 0);
+
+ /* Convert back to B-Format, and mix the results to output. */
+ for(ALsizei c{0};c < NUM_LINES;c++)
+ {
+ std::fill_n(mTempSamples[0].begin(), todo, 0.0f);
+ MixRowSamples(mTempSamples[0], A2B[c], mEarlyBuffer, 0, todo);
+ MixSamples(mTempSamples[0].data(), samplesOut, mEarly.CurrentGain[c],
+ mEarly.PanGain[c], todo, 0, todo);
+ }
+
+ for(ALsizei c{0};c < NUM_LINES;c++)
+ {
+ std::fill_n(mTempSamples[0].begin(), todo, 0.0f);
+ MixRowSamples(mTempSamples[0], A2B[c], mLateBuffer, 0, todo);
+ MixSamples(mTempSamples[0].data(), samplesOut, mLate.CurrentGain[c], mLate.PanGain[c],
+ todo, 0, todo);
+ }
+ }
+
+ void MixOutAmbiUp(const al::span<FloatBufferLine> samplesOut, const ALsizei todo)
+ {
+ ASSUME(todo > 0);
+
+ for(ALsizei c{0};c < NUM_LINES;c++)
+ {
+ std::fill_n(mTempSamples[0].begin(), todo, 0.0f);
+ MixRowSamples(mTempSamples[0], A2B[c], mEarlyBuffer, 0, todo);
+
+ /* Apply scaling to the B-Format's HF response to "upsample" it to
+ * higher-order output.
+ */
+ const ALfloat hfscale{(c==0) ? mOrderScales[0] : mOrderScales[1]};
+ mAmbiSplitter[0][c].applyHfScale(mTempSamples[0].data(), hfscale, todo);
+
+ MixSamples(mTempSamples[0].data(), samplesOut, mEarly.CurrentGain[c],
+ mEarly.PanGain[c], todo, 0, todo);
+ }
+
+ for(ALsizei c{0};c < NUM_LINES;c++)
+ {
+ std::fill_n(mTempSamples[0].begin(), todo, 0.0f);
+ MixRowSamples(mTempSamples[0], A2B[c], mLateBuffer, 0, todo);
+
+ const ALfloat hfscale{(c==0) ? mOrderScales[0] : mOrderScales[1]};
+ mAmbiSplitter[1][c].applyHfScale(mTempSamples[0].data(), hfscale, todo);
+
+ MixSamples(mTempSamples[0].data(), samplesOut, mLate.CurrentGain[c], mLate.PanGain[c],
+ todo, 0, todo);
+ }
+ }
+
+ bool allocLines(const ALfloat frequency);
+
+ void updateDelayLine(const ALfloat earlyDelay, const ALfloat lateDelay, const ALfloat density,
+ const ALfloat decayTime, const ALfloat frequency);
+ void update3DPanning(const ALfloat *ReflectionsPan, const ALfloat *LateReverbPan,
+ const ALfloat earlyGain, const ALfloat lateGain, const EffectTarget &target);
+
+ ALboolean deviceUpdate(const ALCdevice *device) override;
+ void update(const ALCcontext *context, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target) override;
+ void process(const ALsizei samplesToDo, const FloatBufferLine *RESTRICT samplesIn, const ALsizei numInput, const al::span<FloatBufferLine> samplesOut) override;
+
+ DEF_NEWDEL(ReverbState)
+};
+
+/**************************************
+ * Device Update *
+ **************************************/
+
+inline ALfloat CalcDelayLengthMult(ALfloat density)
+{ return maxf(5.0f, std::cbrt(density*DENSITY_SCALE)); }
+
+/* Given the allocated sample buffer, this function updates each delay line
+ * offset.
+ */
+inline ALvoid RealizeLineOffset(ALfloat *sampleBuffer, DelayLineI *Delay)
+{
+ union {
+ ALfloat *f;
+ ALfloat (*f4)[NUM_LINES];
+ } u;
+ u.f = &sampleBuffer[reinterpret_cast<ptrdiff_t>(Delay->Line) * NUM_LINES];
+ Delay->Line = u.f4;
+}
+
+/* Calculate the length of a delay line and store its mask and offset. */
+ALuint CalcLineLength(const ALfloat length, const ptrdiff_t offset, const ALfloat frequency,
+ const ALuint extra, DelayLineI *Delay)
+{
+ /* All line lengths are powers of 2, calculated from their lengths in
+ * seconds, rounded up.
+ */
+ auto samples = static_cast<ALuint>(float2int(std::ceil(length*frequency)));
+ samples = NextPowerOf2(samples + extra);
+
+ /* All lines share a single sample buffer. */
+ Delay->Mask = samples - 1;
+ Delay->Line = reinterpret_cast<ALfloat(*)[NUM_LINES]>(offset);
+
+ /* Return the sample count for accumulation. */
+ return samples;
+}
+
+/* Calculates the delay line metrics and allocates the shared sample buffer
+ * for all lines given the sample rate (frequency). If an allocation failure
+ * occurs, it returns AL_FALSE.
+ */
+bool ReverbState::allocLines(const ALfloat frequency)
+{
+ /* All delay line lengths are calculated to accomodate the full range of
+ * lengths given their respective paramters.
+ */
+ ALuint totalSamples{0u};
+
+ /* Multiplier for the maximum density value, i.e. density=1, which is
+ * actually the least density...
+ */
+ ALfloat multiplier{CalcDelayLengthMult(AL_EAXREVERB_MAX_DENSITY)};
+
+ /* The main delay length includes the maximum early reflection delay, the
+ * largest early tap width, the maximum late reverb delay, and the
+ * largest late tap width. Finally, it must also be extended by the
+ * update size (BUFFERSIZE) for block processing.
+ */
+ ALfloat length{AL_EAXREVERB_MAX_REFLECTIONS_DELAY + EARLY_TAP_LENGTHS.back()*multiplier +
+ AL_EAXREVERB_MAX_LATE_REVERB_DELAY +
+ (LATE_LINE_LENGTHS.back() - LATE_LINE_LENGTHS.front())/float{LATE_LINE_LENGTHS_size}*multiplier};
+ totalSamples += CalcLineLength(length, totalSamples, frequency, BUFFERSIZE, &mDelay);
+
+ /* The early vector all-pass line. */
+ length = EARLY_ALLPASS_LENGTHS.back() * multiplier;
+ totalSamples += CalcLineLength(length, totalSamples, frequency, 0, &mEarly.VecAp.Delay);
+
+ /* The early reflection line. */
+ length = EARLY_LINE_LENGTHS.back() * multiplier;
+ totalSamples += CalcLineLength(length, totalSamples, frequency, 0, &mEarly.Delay);
+
+ /* The late vector all-pass line. */
+ length = LATE_ALLPASS_LENGTHS.back() * multiplier;
+ totalSamples += CalcLineLength(length, totalSamples, frequency, 0, &mLate.VecAp.Delay);
+
+ /* The late delay lines are calculated from the largest maximum density
+ * line length.
+ */
+ length = LATE_LINE_LENGTHS.back() * multiplier;
+ totalSamples += CalcLineLength(length, totalSamples, frequency, 0, &mLate.Delay);
+
+ totalSamples *= NUM_LINES;
+ if(totalSamples != mSampleBuffer.size())
+ {
+ mSampleBuffer.resize(totalSamples);
+ mSampleBuffer.shrink_to_fit();
+ }
+
+ /* Clear the sample buffer. */
+ std::fill(mSampleBuffer.begin(), mSampleBuffer.end(), 0.0f);
+
+ /* Update all delays to reflect the new sample buffer. */
+ RealizeLineOffset(mSampleBuffer.data(), &mDelay);
+ RealizeLineOffset(mSampleBuffer.data(), &mEarly.VecAp.Delay);
+ RealizeLineOffset(mSampleBuffer.data(), &mEarly.Delay);
+ RealizeLineOffset(mSampleBuffer.data(), &mLate.VecAp.Delay);
+ RealizeLineOffset(mSampleBuffer.data(), &mLate.Delay);
+
+ return true;
+}
+
+ALboolean ReverbState::deviceUpdate(const ALCdevice *device)
+{
+ const auto frequency = static_cast<ALfloat>(device->Frequency);
+
+ /* Allocate the delay lines. */
+ if(!allocLines(frequency))
+ return AL_FALSE;
+
+ const ALfloat multiplier{CalcDelayLengthMult(AL_EAXREVERB_MAX_DENSITY)};
+
+ /* The late feed taps are set a fixed position past the latest delay tap. */
+ mLateFeedTap = float2int(
+ (AL_EAXREVERB_MAX_REFLECTIONS_DELAY + EARLY_TAP_LENGTHS.back()*multiplier) * frequency);
+
+ /* Clear filters and gain coefficients since the delay lines were all just
+ * cleared (if not reallocated).
+ */
+ for(auto &filter : mFilter)
+ {
+ filter.Lp.clear();
+ filter.Hp.clear();
+ }
+
+ for(auto &coeff : mEarlyDelayCoeff)
+ std::fill(std::begin(coeff), std::end(coeff), 0.0f);
+ for(auto &coeff : mEarly.Coeff)
+ std::fill(std::begin(coeff), std::end(coeff), 0.0f);
+
+ mLate.DensityGain[0] = 0.0f;
+ mLate.DensityGain[1] = 0.0f;
+ for(auto &t60 : mLate.T60)
+ {
+ t60.MidGain[0] = 0.0f;
+ t60.MidGain[1] = 0.0f;
+ t60.HFFilter.clear();
+ t60.LFFilter.clear();
+ }
+
+ for(auto &gains : mEarly.CurrentGain)
+ std::fill(std::begin(gains), std::end(gains), 0.0f);
+ for(auto &gains : mEarly.PanGain)
+ std::fill(std::begin(gains), std::end(gains), 0.0f);
+ for(auto &gains : mLate.CurrentGain)
+ std::fill(std::begin(gains), std::end(gains), 0.0f);
+ for(auto &gains : mLate.PanGain)
+ std::fill(std::begin(gains), std::end(gains), 0.0f);
+
+ /* Reset counters and offset base. */
+ mFadeCount = 0;
+ std::fill(std::begin(mMaxUpdate), std::end(mMaxUpdate), BUFFERSIZE);
+ mOffset = 0;
+
+ if(device->mAmbiOrder > 1)
+ {
+ mMixOut = &ReverbState::MixOutAmbiUp;
+ mOrderScales = BFormatDec::GetHFOrderScales(1, device->mAmbiOrder);
+ }
+ else
+ {
+ mMixOut = &ReverbState::MixOutPlain;
+ mOrderScales.fill(1.0f);
+ }
+ mAmbiSplitter[0][0].init(400.0f / frequency);
+ std::fill(mAmbiSplitter[0].begin()+1, mAmbiSplitter[0].end(), mAmbiSplitter[0][0]);
+ std::fill(mAmbiSplitter[1].begin(), mAmbiSplitter[1].end(), mAmbiSplitter[0][0]);
+
+ return AL_TRUE;
+}
+
+/**************************************
+ * Effect Update *
+ **************************************/
+
+/* Calculate a decay coefficient given the length of each cycle and the time
+ * until the decay reaches -60 dB.
+ */
+inline ALfloat CalcDecayCoeff(const ALfloat length, const ALfloat decayTime)
+{ return std::pow(REVERB_DECAY_GAIN, length/decayTime); }
+
+/* Calculate a decay length from a coefficient and the time until the decay
+ * reaches -60 dB.
+ */
+inline ALfloat CalcDecayLength(const ALfloat coeff, const ALfloat decayTime)
+{ return std::log10(coeff) * decayTime / std::log10(REVERB_DECAY_GAIN); }
+
+/* Calculate an attenuation to be applied to the input of any echo models to
+ * compensate for modal density and decay time.
+ */
+inline ALfloat CalcDensityGain(const ALfloat a)
+{
+ /* The energy of a signal can be obtained by finding the area under the
+ * squared signal. This takes the form of Sum(x_n^2), where x is the
+ * amplitude for the sample n.
+ *
+ * Decaying feedback matches exponential decay of the form Sum(a^n),
+ * where a is the attenuation coefficient, and n is the sample. The area
+ * under this decay curve can be calculated as: 1 / (1 - a).
+ *
+ * Modifying the above equation to find the area under the squared curve
+ * (for energy) yields: 1 / (1 - a^2). Input attenuation can then be
+ * calculated by inverting the square root of this approximation,
+ * yielding: 1 / sqrt(1 / (1 - a^2)), simplified to: sqrt(1 - a^2).
+ */
+ return std::sqrt(1.0f - a*a);
+}
+
+/* Calculate the scattering matrix coefficients given a diffusion factor. */
+inline ALvoid CalcMatrixCoeffs(const ALfloat diffusion, ALfloat *x, ALfloat *y)
+{
+ /* The matrix is of order 4, so n is sqrt(4 - 1). */
+ ALfloat n{std::sqrt(3.0f)};
+ ALfloat t{diffusion * std::atan(n)};
+
+ /* Calculate the first mixing matrix coefficient. */
+ *x = std::cos(t);
+ /* Calculate the second mixing matrix coefficient. */
+ *y = std::sin(t) / n;
+}
+
+/* Calculate the limited HF ratio for use with the late reverb low-pass
+ * filters.
+ */
+ALfloat CalcLimitedHfRatio(const ALfloat hfRatio, const ALfloat airAbsorptionGainHF,
+ const ALfloat decayTime, const ALfloat SpeedOfSound)
+{
+ /* Find the attenuation due to air absorption in dB (converting delay
+ * time to meters using the speed of sound). Then reversing the decay
+ * equation, solve for HF ratio. The delay length is cancelled out of
+ * the equation, so it can be calculated once for all lines.
+ */
+ ALfloat limitRatio{1.0f / (CalcDecayLength(airAbsorptionGainHF, decayTime) * SpeedOfSound)};
+
+ /* Using the limit calculated above, apply the upper bound to the HF ratio.
+ */
+ return minf(limitRatio, hfRatio);
+}
+
+
+/* Calculates the 3-band T60 damping coefficients for a particular delay line
+ * of specified length, using a combination of two shelf filter sections given
+ * decay times for each band split at two reference frequencies.
+ */
+void T60Filter::calcCoeffs(const ALfloat length, const ALfloat lfDecayTime,
+ const ALfloat mfDecayTime, const ALfloat hfDecayTime, const ALfloat lf0norm,
+ const ALfloat hf0norm)
+{
+ const ALfloat mfGain{CalcDecayCoeff(length, mfDecayTime)};
+ const ALfloat lfGain{maxf(CalcDecayCoeff(length, lfDecayTime)/mfGain, 0.001f)};
+ const ALfloat hfGain{maxf(CalcDecayCoeff(length, hfDecayTime)/mfGain, 0.001f)};
+
+ MidGain[1] = mfGain;
+ LFFilter.setParams(BiquadType::LowShelf, lfGain, lf0norm,
+ LFFilter.rcpQFromSlope(lfGain, 1.0f));
+ HFFilter.setParams(BiquadType::HighShelf, hfGain, hf0norm,
+ HFFilter.rcpQFromSlope(hfGain, 1.0f));
+}
+
+/* Update the early reflection line lengths and gain coefficients. */
+void EarlyReflections::updateLines(const ALfloat density, const ALfloat diffusion,
+ const ALfloat decayTime, const ALfloat frequency)
+{
+ const ALfloat multiplier{CalcDelayLengthMult(density)};
+
+ /* Calculate the all-pass feed-back/forward coefficient. */
+ VecAp.Coeff = std::sqrt(0.5f) * std::pow(diffusion, 2.0f);
+
+ for(ALsizei i{0};i < NUM_LINES;i++)
+ {
+ /* Calculate the length (in seconds) of each all-pass line. */
+ ALfloat length{EARLY_ALLPASS_LENGTHS[i] * multiplier};
+
+ /* Calculate the delay offset for each all-pass line. */
+ VecAp.Offset[i][1] = float2int(length * frequency);
+
+ /* Calculate the length (in seconds) of each delay line. */
+ length = EARLY_LINE_LENGTHS[i] * multiplier;
+
+ /* Calculate the delay offset for each delay line. */
+ Offset[i][1] = float2int(length * frequency);
+
+ /* Calculate the gain (coefficient) for each line. */
+ Coeff[i][1] = CalcDecayCoeff(length, decayTime);
+ }
+}
+
+/* Update the late reverb line lengths and T60 coefficients. */
+void LateReverb::updateLines(const ALfloat density, const ALfloat diffusion,
+ const ALfloat lfDecayTime, const ALfloat mfDecayTime, const ALfloat hfDecayTime,
+ const ALfloat lf0norm, const ALfloat hf0norm, const ALfloat frequency)
+{
+ /* Scaling factor to convert the normalized reference frequencies from
+ * representing 0...freq to 0...max_reference.
+ */
+ const ALfloat norm_weight_factor{frequency / AL_EAXREVERB_MAX_HFREFERENCE};
+
+ const ALfloat late_allpass_avg{
+ std::accumulate(LATE_ALLPASS_LENGTHS.begin(), LATE_ALLPASS_LENGTHS.end(), 0.0f) /
+ float{LATE_ALLPASS_LENGTHS_size}};
+
+ /* To compensate for changes in modal density and decay time of the late
+ * reverb signal, the input is attenuated based on the maximal energy of
+ * the outgoing signal. This approximation is used to keep the apparent
+ * energy of the signal equal for all ranges of density and decay time.
+ *
+ * The average length of the delay lines is used to calculate the
+ * attenuation coefficient.
+ */
+ const ALfloat multiplier{CalcDelayLengthMult(density)};
+ ALfloat length{std::accumulate(LATE_LINE_LENGTHS.begin(), LATE_LINE_LENGTHS.end(), 0.0f) /
+ float{LATE_LINE_LENGTHS_size} * multiplier};
+ length += late_allpass_avg * multiplier;
+ /* The density gain calculation uses an average decay time weighted by
+ * approximate bandwidth. This attempts to compensate for losses of energy
+ * that reduce decay time due to scattering into highly attenuated bands.
+ */
+ const ALfloat bandWeights[3]{
+ lf0norm*norm_weight_factor,
+ hf0norm*norm_weight_factor - lf0norm*norm_weight_factor,
+ 1.0f - hf0norm*norm_weight_factor};
+ DensityGain[1] = CalcDensityGain(
+ CalcDecayCoeff(length,
+ bandWeights[0]*lfDecayTime + bandWeights[1]*mfDecayTime + bandWeights[2]*hfDecayTime
+ )
+ );
+
+ /* Calculate the all-pass feed-back/forward coefficient. */
+ VecAp.Coeff = std::sqrt(0.5f) * std::pow(diffusion, 2.0f);
+
+ for(ALsizei i{0};i < NUM_LINES;i++)
+ {
+ /* Calculate the length (in seconds) of each all-pass line. */
+ length = LATE_ALLPASS_LENGTHS[i] * multiplier;
+
+ /* Calculate the delay offset for each all-pass line. */
+ VecAp.Offset[i][1] = float2int(length * frequency);
+
+ /* Calculate the length (in seconds) of each delay line. */
+ length = LATE_LINE_LENGTHS[i] * multiplier;
+
+ /* Calculate the delay offset for each delay line. */
+ Offset[i][1] = float2int(length*frequency + 0.5f);
+
+ /* Approximate the absorption that the vector all-pass would exhibit
+ * given the current diffusion so we don't have to process a full T60
+ * filter for each of its four lines.
+ */
+ length += lerp(LATE_ALLPASS_LENGTHS[i], late_allpass_avg, diffusion) * multiplier;
+
+ /* Calculate the T60 damping coefficients for each line. */
+ T60[i].calcCoeffs(length, lfDecayTime, mfDecayTime, hfDecayTime, lf0norm, hf0norm);
+ }
+}
+
+
+/* Update the offsets for the main effect delay line. */
+void ReverbState::updateDelayLine(const ALfloat earlyDelay, const ALfloat lateDelay,
+ const ALfloat density, const ALfloat decayTime, const ALfloat frequency)
+{
+ const ALfloat multiplier{CalcDelayLengthMult(density)};
+
+ /* Early reflection taps are decorrelated by means of an average room
+ * reflection approximation described above the definition of the taps.
+ * This approximation is linear and so the above density multiplier can
+ * be applied to adjust the width of the taps. A single-band decay
+ * coefficient is applied to simulate initial attenuation and absorption.
+ *
+ * Late reverb taps are based on the late line lengths to allow a zero-
+ * delay path and offsets that would continue the propagation naturally
+ * into the late lines.
+ */
+ for(ALsizei i{0};i < NUM_LINES;i++)
+ {
+ ALfloat length{earlyDelay + EARLY_TAP_LENGTHS[i]*multiplier};
+ mEarlyDelayTap[i][1] = float2int(length * frequency);
+
+ length = EARLY_TAP_LENGTHS[i]*multiplier;
+ mEarlyDelayCoeff[i][1] = CalcDecayCoeff(length, decayTime);
+
+ length = lateDelay + (LATE_LINE_LENGTHS[i] - LATE_LINE_LENGTHS.front()) /
+ float{LATE_LINE_LENGTHS_size} * multiplier;
+ mLateDelayTap[i][1] = mLateFeedTap + float2int(length * frequency);
+ }
+}
+
+/* Creates a transform matrix given a reverb vector. The vector pans the reverb
+ * reflections toward the given direction, using its magnitude (up to 1) as a
+ * focal strength. This function results in a B-Format transformation matrix
+ * that spatially focuses the signal in the desired direction.
+ */
+alu::Matrix GetTransformFromVector(const ALfloat *vec)
+{
+ /* Normalize the panning vector according to the N3D scale, which has an
+ * extra sqrt(3) term on the directional components. Converting from OpenAL
+ * to B-Format also requires negating X (ACN 1) and Z (ACN 3). Note however
+ * that the reverb panning vectors use left-handed coordinates, unlike the
+ * rest of OpenAL which use right-handed. This is fixed by negating Z,
+ * which cancels out with the B-Format Z negation.
+ */
+ ALfloat norm[3];
+ ALfloat mag{std::sqrt(vec[0]*vec[0] + vec[1]*vec[1] + vec[2]*vec[2])};
+ if(mag > 1.0f)
+ {
+ norm[0] = vec[0] / mag * -al::MathDefs<float>::Sqrt3();
+ norm[1] = vec[1] / mag * al::MathDefs<float>::Sqrt3();
+ norm[2] = vec[2] / mag * al::MathDefs<float>::Sqrt3();
+ mag = 1.0f;
+ }
+ else
+ {
+ /* If the magnitude is less than or equal to 1, just apply the sqrt(3)
+ * term. There's no need to renormalize the magnitude since it would
+ * just be reapplied in the matrix.
+ */
+ norm[0] = vec[0] * -al::MathDefs<float>::Sqrt3();
+ norm[1] = vec[1] * al::MathDefs<float>::Sqrt3();
+ norm[2] = vec[2] * al::MathDefs<float>::Sqrt3();
+ }
+
+ return alu::Matrix{
+ 1.0f, 0.0f, 0.0f, 0.0f,
+ norm[0], 1.0f-mag, 0.0f, 0.0f,
+ norm[1], 0.0f, 1.0f-mag, 0.0f,
+ norm[2], 0.0f, 0.0f, 1.0f-mag
+ };
+}
+
+/* Update the early and late 3D panning gains. */
+void ReverbState::update3DPanning(const ALfloat *ReflectionsPan, const ALfloat *LateReverbPan,
+ const ALfloat earlyGain, const ALfloat lateGain, const EffectTarget &target)
+{
+ /* Create matrices that transform a B-Format signal according to the
+ * panning vectors.
+ */
+ const alu::Matrix earlymat{GetTransformFromVector(ReflectionsPan)};
+ const alu::Matrix latemat{GetTransformFromVector(LateReverbPan)};
+
+ mOutTarget = target.Main->Buffer;
+ for(ALsizei i{0};i < NUM_LINES;i++)
+ {
+ const ALfloat coeffs[MAX_AMBI_CHANNELS]{earlymat[0][i], earlymat[1][i], earlymat[2][i],
+ earlymat[3][i]};
+ ComputePanGains(target.Main, coeffs, earlyGain, mEarly.PanGain[i]);
+ }
+ for(ALsizei i{0};i < NUM_LINES;i++)
+ {
+ const ALfloat coeffs[MAX_AMBI_CHANNELS]{latemat[0][i], latemat[1][i], latemat[2][i],
+ latemat[3][i]};
+ ComputePanGains(target.Main, coeffs, lateGain, mLate.PanGain[i]);
+ }
+}
+
+void ReverbState::update(const ALCcontext *Context, const ALeffectslot *Slot, const EffectProps *props, const EffectTarget target)
+{
+ const ALCdevice *Device{Context->Device};
+ const ALlistener &Listener = Context->Listener;
+ const auto frequency = static_cast<ALfloat>(Device->Frequency);
+
+ /* Calculate the master filters */
+ ALfloat hf0norm{minf(props->Reverb.HFReference / frequency, 0.49f)};
+ /* Restrict the filter gains from going below -60dB to keep the filter from
+ * killing most of the signal.
+ */
+ ALfloat gainhf{maxf(props->Reverb.GainHF, 0.001f)};
+ mFilter[0].Lp.setParams(BiquadType::HighShelf, gainhf, hf0norm,
+ mFilter[0].Lp.rcpQFromSlope(gainhf, 1.0f));
+ ALfloat lf0norm{minf(props->Reverb.LFReference / frequency, 0.49f)};
+ ALfloat gainlf{maxf(props->Reverb.GainLF, 0.001f)};
+ mFilter[0].Hp.setParams(BiquadType::LowShelf, gainlf, lf0norm,
+ mFilter[0].Hp.rcpQFromSlope(gainlf, 1.0f));
+ for(ALsizei i{1};i < NUM_LINES;i++)
+ {
+ mFilter[i].Lp.copyParamsFrom(mFilter[0].Lp);
+ mFilter[i].Hp.copyParamsFrom(mFilter[0].Hp);
+ }
+
+ /* Update the main effect delay and associated taps. */
+ updateDelayLine(props->Reverb.ReflectionsDelay, props->Reverb.LateReverbDelay,
+ props->Reverb.Density, props->Reverb.DecayTime, frequency);
+
+ /* Update the early lines. */
+ mEarly.updateLines(props->Reverb.Density, props->Reverb.Diffusion, props->Reverb.DecayTime,
+ frequency);
+
+ /* Get the mixing matrix coefficients. */
+ CalcMatrixCoeffs(props->Reverb.Diffusion, &mMixX, &mMixY);
+
+ /* If the HF limit parameter is flagged, calculate an appropriate limit
+ * based on the air absorption parameter.
+ */
+ ALfloat hfRatio{props->Reverb.DecayHFRatio};
+ if(props->Reverb.DecayHFLimit && props->Reverb.AirAbsorptionGainHF < 1.0f)
+ hfRatio = CalcLimitedHfRatio(hfRatio, props->Reverb.AirAbsorptionGainHF,
+ props->Reverb.DecayTime, Listener.Params.ReverbSpeedOfSound
+ );
+
+ /* Calculate the LF/HF decay times. */
+ const ALfloat lfDecayTime{clampf(props->Reverb.DecayTime * props->Reverb.DecayLFRatio,
+ AL_EAXREVERB_MIN_DECAY_TIME, AL_EAXREVERB_MAX_DECAY_TIME)};
+ const ALfloat hfDecayTime{clampf(props->Reverb.DecayTime * hfRatio,
+ AL_EAXREVERB_MIN_DECAY_TIME, AL_EAXREVERB_MAX_DECAY_TIME)};
+
+ /* Update the late lines. */
+ mLate.updateLines(props->Reverb.Density, props->Reverb.Diffusion, lfDecayTime,
+ props->Reverb.DecayTime, hfDecayTime, lf0norm, hf0norm, frequency);
+
+ /* Update early and late 3D panning. */
+ const ALfloat gain{props->Reverb.Gain * Slot->Params.Gain * ReverbBoost};
+ update3DPanning(props->Reverb.ReflectionsPan, props->Reverb.LateReverbPan,
+ props->Reverb.ReflectionsGain*gain, props->Reverb.LateReverbGain*gain, target);
+
+ /* Calculate the max update size from the smallest relevant delay. */
+ mMaxUpdate[1] = mini(BUFFERSIZE, mini(mEarly.Offset[0][1], mLate.Offset[0][1]));
+
+ /* Determine if delay-line cross-fading is required. Density is essentially
+ * a master control for the feedback delays, so changes the offsets of many
+ * delay lines.
+ */
+ if(mParams.Density != props->Reverb.Density ||
+ /* Diffusion and decay times influences the decay rate (gain) of the
+ * late reverb T60 filter.
+ */
+ mParams.Diffusion != props->Reverb.Diffusion ||
+ mParams.DecayTime != props->Reverb.DecayTime ||
+ mParams.HFDecayTime != hfDecayTime ||
+ mParams.LFDecayTime != lfDecayTime ||
+ /* HF/LF References control the weighting used to calculate the density
+ * gain.
+ */
+ mParams.HFReference != props->Reverb.HFReference ||
+ mParams.LFReference != props->Reverb.LFReference)
+ mFadeCount = 0;
+ mParams.Density = props->Reverb.Density;
+ mParams.Diffusion = props->Reverb.Diffusion;
+ mParams.DecayTime = props->Reverb.DecayTime;
+ mParams.HFDecayTime = hfDecayTime;
+ mParams.LFDecayTime = lfDecayTime;
+ mParams.HFReference = props->Reverb.HFReference;
+ mParams.LFReference = props->Reverb.LFReference;
+}
+
+
+/**************************************
+ * Effect Processing *
+ **************************************/
+
+/* Applies a scattering matrix to the 4-line (vector) input. This is used
+ * for both the below vector all-pass model and to perform modal feed-back
+ * delay network (FDN) mixing.
+ *
+ * The matrix is derived from a skew-symmetric matrix to form a 4D rotation
+ * matrix with a single unitary rotational parameter:
+ *
+ * [ d, a, b, c ] 1 = a^2 + b^2 + c^2 + d^2
+ * [ -a, d, c, -b ]
+ * [ -b, -c, d, a ]
+ * [ -c, b, -a, d ]
+ *
+ * The rotation is constructed from the effect's diffusion parameter,
+ * yielding:
+ *
+ * 1 = x^2 + 3 y^2
+ *
+ * Where a, b, and c are the coefficient y with differing signs, and d is the
+ * coefficient x. The final matrix is thus:
+ *
+ * [ x, y, -y, y ] n = sqrt(matrix_order - 1)
+ * [ -y, x, y, y ] t = diffusion_parameter * atan(n)
+ * [ y, -y, x, y ] x = cos(t)
+ * [ -y, -y, -y, x ] y = sin(t) / n
+ *
+ * Any square orthogonal matrix with an order that is a power of two will
+ * work (where ^T is transpose, ^-1 is inverse):
+ *
+ * M^T = M^-1
+ *
+ * Using that knowledge, finding an appropriate matrix can be accomplished
+ * naively by searching all combinations of:
+ *
+ * M = D + S - S^T
+ *
+ * Where D is a diagonal matrix (of x), and S is a triangular matrix (of y)
+ * whose combination of signs are being iterated.
+ */
+inline void VectorPartialScatter(ALfloat *RESTRICT out, const ALfloat *RESTRICT in,
+ const ALfloat xCoeff, const ALfloat yCoeff)
+{
+ out[0] = xCoeff*in[0] + yCoeff*( in[1] + -in[2] + in[3]);
+ out[1] = xCoeff*in[1] + yCoeff*(-in[0] + in[2] + in[3]);
+ out[2] = xCoeff*in[2] + yCoeff*( in[0] + -in[1] + in[3]);
+ out[3] = xCoeff*in[3] + yCoeff*(-in[0] + -in[1] + -in[2] );
+}
+
+/* Utilizes the above, but reverses the input channels. */
+void VectorScatterRevDelayIn(const DelayLineI delay, ALint offset, const ALfloat xCoeff,
+ const ALfloat yCoeff, const ALsizei base, const al::span<const FloatBufferLine,NUM_LINES> in,
+ const ALsizei count)
+{
+ ASSUME(base >= 0);
+ ASSUME(count > 0);
+
+ for(ALsizei i{0};i < count;)
+ {
+ offset &= delay.Mask;
+ ALsizei td{mini(delay.Mask+1 - offset, count-i)};
+ do {
+ ALfloat f[NUM_LINES];
+ for(ALsizei j{0};j < NUM_LINES;j++)
+ f[NUM_LINES-1-j] = in[j][base+i];
+ ++i;
+
+ VectorPartialScatter(delay.Line[offset++], f, xCoeff, yCoeff);
+ } while(--td);
+ }
+}
+
+/* This applies a Gerzon multiple-in/multiple-out (MIMO) vector all-pass
+ * filter to the 4-line input.
+ *
+ * It works by vectorizing a regular all-pass filter and replacing the delay
+ * element with a scattering matrix (like the one above) and a diagonal
+ * matrix of delay elements.
+ *
+ * Two static specializations are used for transitional (cross-faded) delay
+ * line processing and non-transitional processing.
+ */
+void VecAllpass::processUnfaded(const al::span<FloatBufferLine,NUM_LINES> samples, ALsizei offset,
+ const ALfloat xCoeff, const ALfloat yCoeff, const ALsizei todo)
+{
+ const DelayLineI delay{Delay};
+ const ALfloat feedCoeff{Coeff};
+
+ ASSUME(todo > 0);
+
+ ALsizei vap_offset[NUM_LINES];
+ for(ALsizei j{0};j < NUM_LINES;j++)
+ vap_offset[j] = offset - Offset[j][0];
+ for(ALsizei i{0};i < todo;)
+ {
+ for(ALsizei j{0};j < NUM_LINES;j++)
+ vap_offset[j] &= delay.Mask;
+ offset &= delay.Mask;
+
+ ALsizei maxoff{offset};
+ for(ALsizei j{0};j < NUM_LINES;j++)
+ maxoff = maxi(maxoff, vap_offset[j]);
+ ALsizei td{mini(delay.Mask+1 - maxoff, todo - i)};
+
+ do {
+ ALfloat f[NUM_LINES];
+ for(ALsizei j{0};j < NUM_LINES;j++)
+ {
+ const ALfloat input{samples[j][i]};
+ const ALfloat out{delay.Line[vap_offset[j]++][j] - feedCoeff*input};
+ f[j] = input + feedCoeff*out;
+
+ samples[j][i] = out;
+ }
+ ++i;
+
+ VectorPartialScatter(delay.Line[offset++], f, xCoeff, yCoeff);
+ } while(--td);
+ }
+}
+void VecAllpass::processFaded(const al::span<FloatBufferLine,NUM_LINES> samples, ALsizei offset,
+ const ALfloat xCoeff, const ALfloat yCoeff, ALfloat fade, const ALsizei todo)
+{
+ const DelayLineI delay{Delay};
+ const ALfloat feedCoeff{Coeff};
+
+ ASSUME(todo > 0);
+
+ fade *= 1.0f/FADE_SAMPLES;
+ ALsizei vap_offset[NUM_LINES][2];
+ for(ALsizei j{0};j < NUM_LINES;j++)
+ {
+ vap_offset[j][0] = offset - Offset[j][0];
+ vap_offset[j][1] = offset - Offset[j][1];
+ }
+ for(ALsizei i{0};i < todo;)
+ {
+ for(ALsizei j{0};j < NUM_LINES;j++)
+ {
+ vap_offset[j][0] &= delay.Mask;
+ vap_offset[j][1] &= delay.Mask;
+ }
+ offset &= delay.Mask;
+
+ ALsizei maxoff{offset};
+ for(ALsizei j{0};j < NUM_LINES;j++)
+ maxoff = maxi(maxoff, maxi(vap_offset[j][0], vap_offset[j][1]));
+ ALsizei td{mini(delay.Mask+1 - maxoff, todo - i)};
+
+ do {
+ fade += FadeStep;
+ ALfloat f[NUM_LINES];
+ for(ALsizei j{0};j < NUM_LINES;j++)
+ f[j] = delay.Line[vap_offset[j][0]++][j]*(1.0f-fade) +
+ delay.Line[vap_offset[j][1]++][j]*fade;
+
+ for(ALsizei j{0};j < NUM_LINES;j++)
+ {
+ const ALfloat input{samples[j][i]};
+ const ALfloat out{f[j] - feedCoeff*input};
+ f[j] = input + feedCoeff*out;
+
+ samples[j][i] = out;
+ }
+ ++i;
+
+ VectorPartialScatter(delay.Line[offset++], f, xCoeff, yCoeff);
+ } while(--td);
+ }
+}
+
+/* This generates early reflections.
+ *
+ * This is done by obtaining the primary reflections (those arriving from the
+ * same direction as the source) from the main delay line. These are
+ * attenuated and all-pass filtered (based on the diffusion parameter).
+ *
+ * The early lines are then fed in reverse (according to the approximately
+ * opposite spatial location of the A-Format lines) to create the secondary
+ * reflections (those arriving from the opposite direction as the source).
+ *
+ * The early response is then completed by combining the primary reflections
+ * with the delayed and attenuated output from the early lines.
+ *
+ * Finally, the early response is reversed, scattered (based on diffusion),
+ * and fed into the late reverb section of the main delay line.
+ *
+ * Two static specializations are used for transitional (cross-faded) delay
+ * line processing and non-transitional processing.
+ */
+void EarlyReflection_Unfaded(ReverbState *State, const ALsizei offset, const ALsizei todo,
+ const ALsizei base, const al::span<FloatBufferLine,NUM_LINES> out)
+{
+ const al::span<FloatBufferLine,NUM_LINES> temps{State->mTempSamples};
+ const DelayLineI early_delay{State->mEarly.Delay};
+ const DelayLineI main_delay{State->mDelay};
+ const ALfloat mixX{State->mMixX};
+ const ALfloat mixY{State->mMixY};
+
+ ASSUME(todo > 0);
+
+ /* First, load decorrelated samples from the main delay line as the primary
+ * reflections.
+ */
+ for(ALsizei j{0};j < NUM_LINES;j++)
+ {
+ ALsizei early_delay_tap{offset - State->mEarlyDelayTap[j][0]};
+ const ALfloat coeff{State->mEarlyDelayCoeff[j][0]};
+ for(ALsizei i{0};i < todo;)
+ {
+ early_delay_tap &= main_delay.Mask;
+ ALsizei td{mini(main_delay.Mask+1 - early_delay_tap, todo - i)};
+ do {
+ temps[j][i++] = main_delay.Line[early_delay_tap++][j] * coeff;
+ } while(--td);
+ }
+ }
+
+ /* Apply a vector all-pass, to help color the initial reflections based on
+ * the diffusion strength.
+ */
+ State->mEarly.VecAp.processUnfaded(temps, offset, mixX, mixY, todo);
+
+ /* Apply a delay and bounce to generate secondary reflections, combine with
+ * the primary reflections and write out the result for mixing.
+ */
+ for(ALsizei j{0};j < NUM_LINES;j++)
+ {
+ ALint feedb_tap{offset - State->mEarly.Offset[j][0]};
+ const ALfloat feedb_coeff{State->mEarly.Coeff[j][0]};
+
+ ASSUME(base >= 0);
+ for(ALsizei i{0};i < todo;)
+ {
+ feedb_tap &= early_delay.Mask;
+ ALsizei td{mini(early_delay.Mask+1 - feedb_tap, todo - i)};
+ do {
+ out[j][base+i] = temps[j][i] + early_delay.Line[feedb_tap++][j]*feedb_coeff;
+ ++i;
+ } while(--td);
+ }
+ }
+ for(ALsizei j{0};j < NUM_LINES;j++)
+ early_delay.write(offset, NUM_LINES-1-j, temps[j].data(), todo);
+
+ /* Also write the result back to the main delay line for the late reverb
+ * stage to pick up at the appropriate time, appplying a scatter and
+ * bounce to improve the initial diffusion in the late reverb.
+ */
+ const ALsizei late_feed_tap{offset - State->mLateFeedTap};
+ VectorScatterRevDelayIn(main_delay, late_feed_tap, mixX, mixY, base,
+ {out.cbegin(), out.cend()}, todo);
+}
+void EarlyReflection_Faded(ReverbState *State, const ALsizei offset, const ALsizei todo,
+ const ALfloat fade, const ALsizei base, const al::span<FloatBufferLine,NUM_LINES> out)
+{
+ const al::span<FloatBufferLine,NUM_LINES> temps{State->mTempSamples};
+ const DelayLineI early_delay{State->mEarly.Delay};
+ const DelayLineI main_delay{State->mDelay};
+ const ALfloat mixX{State->mMixX};
+ const ALfloat mixY{State->mMixY};
+
+ ASSUME(todo > 0);
+
+ for(ALsizei j{0};j < NUM_LINES;j++)
+ {
+ ALsizei early_delay_tap0{offset - State->mEarlyDelayTap[j][0]};
+ ALsizei early_delay_tap1{offset - State->mEarlyDelayTap[j][1]};
+ const ALfloat oldCoeff{State->mEarlyDelayCoeff[j][0]};
+ const ALfloat oldCoeffStep{-oldCoeff / FADE_SAMPLES};
+ const ALfloat newCoeffStep{State->mEarlyDelayCoeff[j][1] / FADE_SAMPLES};
+ ALfloat fadeCount{fade};
+
+ for(ALsizei i{0};i < todo;)
+ {
+ early_delay_tap0 &= main_delay.Mask;
+ early_delay_tap1 &= main_delay.Mask;
+ ALsizei td{mini(main_delay.Mask+1 - maxi(early_delay_tap0, early_delay_tap1), todo-i)};
+ do {
+ fadeCount += 1.0f;
+ const ALfloat fade0{oldCoeff + oldCoeffStep*fadeCount};
+ const ALfloat fade1{newCoeffStep*fadeCount};
+ temps[j][i++] =
+ main_delay.Line[early_delay_tap0++][j]*fade0 +
+ main_delay.Line[early_delay_tap1++][j]*fade1;
+ } while(--td);
+ }
+ }
+
+ State->mEarly.VecAp.processFaded(temps, offset, mixX, mixY, fade, todo);
+
+ for(ALsizei j{0};j < NUM_LINES;j++)
+ {
+ ALint feedb_tap0{offset - State->mEarly.Offset[j][0]};
+ ALint feedb_tap1{offset - State->mEarly.Offset[j][1]};
+ const ALfloat feedb_oldCoeff{State->mEarly.Coeff[j][0]};
+ const ALfloat feedb_oldCoeffStep{-feedb_oldCoeff / FADE_SAMPLES};
+ const ALfloat feedb_newCoeffStep{State->mEarly.Coeff[j][1] / FADE_SAMPLES};
+ ALfloat fadeCount{fade};
+
+ ASSUME(base >= 0);
+ for(ALsizei i{0};i < todo;)
+ {
+ feedb_tap0 &= early_delay.Mask;
+ feedb_tap1 &= early_delay.Mask;
+ ALsizei td{mini(early_delay.Mask+1 - maxi(feedb_tap0, feedb_tap1), todo - i)};
+
+ do {
+ fadeCount += 1.0f;
+ const ALfloat fade0{feedb_oldCoeff + feedb_oldCoeffStep*fadeCount};
+ const ALfloat fade1{feedb_newCoeffStep*fadeCount};
+ out[j][base+i] = temps[j][i] +
+ early_delay.Line[feedb_tap0++][j]*fade0 +
+ early_delay.Line[feedb_tap1++][j]*fade1;
+ ++i;
+ } while(--td);
+ }
+ }
+ for(ALsizei j{0};j < NUM_LINES;j++)
+ early_delay.write(offset, NUM_LINES-1-j, temps[j].data(), todo);
+
+ const ALsizei late_feed_tap{offset - State->mLateFeedTap};
+ VectorScatterRevDelayIn(main_delay, late_feed_tap, mixX, mixY, base,
+ {out.cbegin(), out.cend()}, todo);
+}
+
+/* This generates the reverb tail using a modified feed-back delay network
+ * (FDN).
+ *
+ * Results from the early reflections are mixed with the output from the late
+ * delay lines.
+ *
+ * The late response is then completed by T60 and all-pass filtering the mix.
+ *
+ * Finally, the lines are reversed (so they feed their opposite directions)
+ * and scattered with the FDN matrix before re-feeding the delay lines.
+ *
+ * Two variations are made, one for for transitional (cross-faded) delay line
+ * processing and one for non-transitional processing.
+ */
+void LateReverb_Unfaded(ReverbState *State, const ALsizei offset, const ALsizei todo,
+ const ALsizei base, const al::span<FloatBufferLine,NUM_LINES> out)
+{
+ const al::span<FloatBufferLine,NUM_LINES> temps{State->mTempSamples};
+ const DelayLineI late_delay{State->mLate.Delay};
+ const DelayLineI main_delay{State->mDelay};
+ const ALfloat mixX{State->mMixX};
+ const ALfloat mixY{State->mMixY};
+
+ ASSUME(todo > 0);
+
+ /* First, load decorrelated samples from the main and feedback delay lines.
+ * Filter the signal to apply its frequency-dependent decay.
+ */
+ for(ALsizei j{0};j < NUM_LINES;j++)
+ {
+ ALsizei late_delay_tap{offset - State->mLateDelayTap[j][0]};
+ ALsizei late_feedb_tap{offset - State->mLate.Offset[j][0]};
+ const ALfloat midGain{State->mLate.T60[j].MidGain[0]};
+ const ALfloat densityGain{State->mLate.DensityGain[0] * midGain};
+ for(ALsizei i{0};i < todo;)
+ {
+ late_delay_tap &= main_delay.Mask;
+ late_feedb_tap &= late_delay.Mask;
+ ALsizei td{mini(
+ mini(main_delay.Mask+1 - late_delay_tap, late_delay.Mask+1 - late_feedb_tap),
+ todo - i)};
+ do {
+ temps[j][i++] =
+ main_delay.Line[late_delay_tap++][j]*densityGain +
+ late_delay.Line[late_feedb_tap++][j]*midGain;
+ } while(--td);
+ }
+ State->mLate.T60[j].process(temps[j].data(), todo);
+ }
+
+ /* Apply a vector all-pass to improve micro-surface diffusion, and write
+ * out the results for mixing.
+ */
+ State->mLate.VecAp.processUnfaded(temps, offset, mixX, mixY, todo);
+
+ for(ALsizei j{0};j < NUM_LINES;j++)
+ std::copy_n(temps[j].begin(), todo, out[j].begin()+base);
+
+ /* Finally, scatter and bounce the results to refeed the feedback buffer. */
+ VectorScatterRevDelayIn(late_delay, offset, mixX, mixY, base,
+ {out.cbegin(), out.cend()}, todo);
+}
+void LateReverb_Faded(ReverbState *State, const ALsizei offset, const ALsizei todo,
+ const ALfloat fade, const ALsizei base, const al::span<FloatBufferLine,NUM_LINES> out)
+{
+ const al::span<FloatBufferLine,NUM_LINES> temps{State->mTempSamples};
+ const DelayLineI late_delay{State->mLate.Delay};
+ const DelayLineI main_delay{State->mDelay};
+ const ALfloat mixX{State->mMixX};
+ const ALfloat mixY{State->mMixY};
+
+ ASSUME(todo > 0);
+
+ for(ALsizei j{0};j < NUM_LINES;j++)
+ {
+ const ALfloat oldMidGain{State->mLate.T60[j].MidGain[0]};
+ const ALfloat midGain{State->mLate.T60[j].MidGain[1]};
+ const ALfloat oldMidStep{-oldMidGain / FADE_SAMPLES};
+ const ALfloat midStep{midGain / FADE_SAMPLES};
+ const ALfloat oldDensityGain{State->mLate.DensityGain[0] * oldMidGain};
+ const ALfloat densityGain{State->mLate.DensityGain[1] * midGain};
+ const ALfloat oldDensityStep{-oldDensityGain / FADE_SAMPLES};
+ const ALfloat densityStep{densityGain / FADE_SAMPLES};
+ ALsizei late_delay_tap0{offset - State->mLateDelayTap[j][0]};
+ ALsizei late_delay_tap1{offset - State->mLateDelayTap[j][1]};
+ ALsizei late_feedb_tap0{offset - State->mLate.Offset[j][0]};
+ ALsizei late_feedb_tap1{offset - State->mLate.Offset[j][1]};
+ ALfloat fadeCount{fade};
+
+ for(ALsizei i{0};i < todo;)
+ {
+ late_delay_tap0 &= main_delay.Mask;
+ late_delay_tap1 &= main_delay.Mask;
+ late_feedb_tap0 &= late_delay.Mask;
+ late_feedb_tap1 &= late_delay.Mask;
+ ALsizei td{mini(
+ mini(main_delay.Mask+1 - maxi(late_delay_tap0, late_delay_tap1),
+ late_delay.Mask+1 - maxi(late_feedb_tap0, late_feedb_tap1)),
+ todo - i)};
+ do {
+ fadeCount += 1.0f;
+ const ALfloat fade0{oldDensityGain + oldDensityStep*fadeCount};
+ const ALfloat fade1{densityStep*fadeCount};
+ const ALfloat gfade0{oldMidGain + oldMidStep*fadeCount};
+ const ALfloat gfade1{midStep*fadeCount};
+ temps[j][i++] =
+ main_delay.Line[late_delay_tap0++][j]*fade0 +
+ main_delay.Line[late_delay_tap1++][j]*fade1 +
+ late_delay.Line[late_feedb_tap0++][j]*gfade0 +
+ late_delay.Line[late_feedb_tap1++][j]*gfade1;
+ } while(--td);
+ }
+ State->mLate.T60[j].process(temps[j].data(), todo);
+ }
+
+ State->mLate.VecAp.processFaded(temps, offset, mixX, mixY, fade, todo);
+
+ for(ALsizei j{0};j < NUM_LINES;j++)
+ std::copy_n(temps[j].begin(), todo, out[j].begin()+base);
+
+ VectorScatterRevDelayIn(late_delay, offset, mixX, mixY, base,
+ {out.cbegin(), out.cend()}, todo);
+}
+
+void ReverbState::process(const ALsizei samplesToDo, const FloatBufferLine *RESTRICT samplesIn, const ALsizei numInput, const al::span<FloatBufferLine> samplesOut)
+{
+ ALsizei fadeCount{mFadeCount};
+
+ ASSUME(samplesToDo > 0);
+
+ /* Convert B-Format to A-Format for processing. */
+ const al::span<FloatBufferLine,NUM_LINES> afmt{mTempSamples};
+ for(ALsizei c{0};c < NUM_LINES;c++)
+ {
+ std::fill_n(afmt[c].begin(), samplesToDo, 0.0f);
+ MixRowSamples(afmt[c], B2A[c], {samplesIn, samplesIn+numInput}, 0, samplesToDo);
+
+ /* Band-pass the incoming samples. */
+ mFilter[c].Lp.process(afmt[c].data(), afmt[c].data(), samplesToDo);
+ mFilter[c].Hp.process(afmt[c].data(), afmt[c].data(), samplesToDo);
+ }
+
+ /* Process reverb for these samples. */
+ for(ALsizei base{0};base < samplesToDo;)
+ {
+ ALsizei todo{samplesToDo - base};
+ /* If cross-fading, don't do more samples than there are to fade. */
+ if(FADE_SAMPLES-fadeCount > 0)
+ {
+ todo = mini(todo, FADE_SAMPLES-fadeCount);
+ todo = mini(todo, mMaxUpdate[0]);
+ }
+ todo = mini(todo, mMaxUpdate[1]);
+ ASSUME(todo > 0 && todo <= BUFFERSIZE);
+
+ const ALsizei offset{mOffset + base};
+ ASSUME(offset >= 0);
+
+ /* Feed the initial delay line. */
+ for(ALsizei c{0};c < NUM_LINES;c++)
+ mDelay.write(offset, c, afmt[c].data()+base, todo);
+
+ /* Process the samples for reverb. */
+ if(UNLIKELY(fadeCount < FADE_SAMPLES))
+ {
+ auto fade = static_cast<ALfloat>(fadeCount);
+
+ /* Generate early reflections and late reverb. */
+ EarlyReflection_Faded(this, offset, todo, fade, base, mEarlyBuffer);
+
+ LateReverb_Faded(this, offset, todo, fade, base, mLateBuffer);
+
+ /* Step fading forward. */
+ fadeCount += todo;
+ if(fadeCount >= FADE_SAMPLES)
+ {
+ /* Update the cross-fading delay line taps. */
+ fadeCount = FADE_SAMPLES;
+ for(ALsizei c{0};c < NUM_LINES;c++)
+ {
+ mEarlyDelayTap[c][0] = mEarlyDelayTap[c][1];
+ mEarlyDelayCoeff[c][0] = mEarlyDelayCoeff[c][1];
+ mEarly.VecAp.Offset[c][0] = mEarly.VecAp.Offset[c][1];
+ mEarly.Offset[c][0] = mEarly.Offset[c][1];
+ mEarly.Coeff[c][0] = mEarly.Coeff[c][1];
+ mLateDelayTap[c][0] = mLateDelayTap[c][1];
+ mLate.VecAp.Offset[c][0] = mLate.VecAp.Offset[c][1];
+ mLate.Offset[c][0] = mLate.Offset[c][1];
+ mLate.T60[c].MidGain[0] = mLate.T60[c].MidGain[1];
+ }
+ mLate.DensityGain[0] = mLate.DensityGain[1];
+ mMaxUpdate[0] = mMaxUpdate[1];
+ }
+ }
+ else
+ {
+ /* Generate early reflections and late reverb. */
+ EarlyReflection_Unfaded(this, offset, todo, base, mEarlyBuffer);
+
+ LateReverb_Unfaded(this, offset, todo, base, mLateBuffer);
+ }
+
+ base += todo;
+ }
+ mOffset = (mOffset+samplesToDo) & 0x3fffffff;
+ mFadeCount = fadeCount;
+
+ /* Finally, mix early reflections and late reverb. */
+ (this->*mMixOut)(samplesOut, samplesToDo);
+}
+
+
+void EAXReverb_setParami(EffectProps *props, ALCcontext *context, ALenum param, ALint val)
+{
+ switch(param)
+ {
+ case AL_EAXREVERB_DECAY_HFLIMIT:
+ if(!(val >= AL_EAXREVERB_MIN_DECAY_HFLIMIT && val <= AL_EAXREVERB_MAX_DECAY_HFLIMIT))
+ SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb decay hflimit out of range");
+ props->Reverb.DecayHFLimit = val;
+ break;
+
+ default:
+ alSetError(context, AL_INVALID_ENUM, "Invalid EAX reverb integer property 0x%04x",
+ param);
+ }
+}
+void EAXReverb_setParamiv(EffectProps *props, ALCcontext *context, ALenum param, const ALint *vals)
+{ EAXReverb_setParami(props, context, param, vals[0]); }
+void EAXReverb_setParamf(EffectProps *props, ALCcontext *context, ALenum param, ALfloat val)
+{
+ switch(param)
+ {
+ case AL_EAXREVERB_DENSITY:
+ if(!(val >= AL_EAXREVERB_MIN_DENSITY && val <= AL_EAXREVERB_MAX_DENSITY))
+ SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb density out of range");
+ props->Reverb.Density = val;
+ break;
+
+ case AL_EAXREVERB_DIFFUSION:
+ if(!(val >= AL_EAXREVERB_MIN_DIFFUSION && val <= AL_EAXREVERB_MAX_DIFFUSION))
+ SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb diffusion out of range");
+ props->Reverb.Diffusion = val;
+ break;
+
+ case AL_EAXREVERB_GAIN:
+ if(!(val >= AL_EAXREVERB_MIN_GAIN && val <= AL_EAXREVERB_MAX_GAIN))
+ SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb gain out of range");
+ props->Reverb.Gain = val;
+ break;
+
+ case AL_EAXREVERB_GAINHF:
+ if(!(val >= AL_EAXREVERB_MIN_GAINHF && val <= AL_EAXREVERB_MAX_GAINHF))
+ SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb gainhf out of range");
+ props->Reverb.GainHF = val;
+ break;
+
+ case AL_EAXREVERB_GAINLF:
+ if(!(val >= AL_EAXREVERB_MIN_GAINLF && val <= AL_EAXREVERB_MAX_GAINLF))
+ SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb gainlf out of range");
+ props->Reverb.GainLF = val;
+ break;
+
+ case AL_EAXREVERB_DECAY_TIME:
+ if(!(val >= AL_EAXREVERB_MIN_DECAY_TIME && val <= AL_EAXREVERB_MAX_DECAY_TIME))
+ SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb decay time out of range");
+ props->Reverb.DecayTime = val;
+ break;
+
+ case AL_EAXREVERB_DECAY_HFRATIO:
+ if(!(val >= AL_EAXREVERB_MIN_DECAY_HFRATIO && val <= AL_EAXREVERB_MAX_DECAY_HFRATIO))
+ SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb decay hfratio out of range");
+ props->Reverb.DecayHFRatio = val;
+ break;
+
+ case AL_EAXREVERB_DECAY_LFRATIO:
+ if(!(val >= AL_EAXREVERB_MIN_DECAY_LFRATIO && val <= AL_EAXREVERB_MAX_DECAY_LFRATIO))
+ SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb decay lfratio out of range");
+ props->Reverb.DecayLFRatio = val;
+ break;
+
+ case AL_EAXREVERB_REFLECTIONS_GAIN:
+ if(!(val >= AL_EAXREVERB_MIN_REFLECTIONS_GAIN && val <= AL_EAXREVERB_MAX_REFLECTIONS_GAIN))
+ SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb reflections gain out of range");
+ props->Reverb.ReflectionsGain = val;
+ break;
+
+ case AL_EAXREVERB_REFLECTIONS_DELAY:
+ if(!(val >= AL_EAXREVERB_MIN_REFLECTIONS_DELAY && val <= AL_EAXREVERB_MAX_REFLECTIONS_DELAY))
+ SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb reflections delay out of range");
+ props->Reverb.ReflectionsDelay = val;
+ break;
+
+ case AL_EAXREVERB_LATE_REVERB_GAIN:
+ if(!(val >= AL_EAXREVERB_MIN_LATE_REVERB_GAIN && val <= AL_EAXREVERB_MAX_LATE_REVERB_GAIN))
+ SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb late reverb gain out of range");
+ props->Reverb.LateReverbGain = val;
+ break;
+
+ case AL_EAXREVERB_LATE_REVERB_DELAY:
+ if(!(val >= AL_EAXREVERB_MIN_LATE_REVERB_DELAY && val <= AL_EAXREVERB_MAX_LATE_REVERB_DELAY))
+ SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb late reverb delay out of range");
+ props->Reverb.LateReverbDelay = val;
+ break;
+
+ case AL_EAXREVERB_AIR_ABSORPTION_GAINHF:
+ if(!(val >= AL_EAXREVERB_MIN_AIR_ABSORPTION_GAINHF && val <= AL_EAXREVERB_MAX_AIR_ABSORPTION_GAINHF))
+ SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb air absorption gainhf out of range");
+ props->Reverb.AirAbsorptionGainHF = val;
+ break;
+
+ case AL_EAXREVERB_ECHO_TIME:
+ if(!(val >= AL_EAXREVERB_MIN_ECHO_TIME && val <= AL_EAXREVERB_MAX_ECHO_TIME))
+ SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb echo time out of range");
+ props->Reverb.EchoTime = val;
+ break;
+
+ case AL_EAXREVERB_ECHO_DEPTH:
+ if(!(val >= AL_EAXREVERB_MIN_ECHO_DEPTH && val <= AL_EAXREVERB_MAX_ECHO_DEPTH))
+ SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb echo depth out of range");
+ props->Reverb.EchoDepth = val;
+ break;
+
+ case AL_EAXREVERB_MODULATION_TIME:
+ if(!(val >= AL_EAXREVERB_MIN_MODULATION_TIME && val <= AL_EAXREVERB_MAX_MODULATION_TIME))
+ SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb modulation time out of range");
+ props->Reverb.ModulationTime = val;
+ break;
+
+ case AL_EAXREVERB_MODULATION_DEPTH:
+ if(!(val >= AL_EAXREVERB_MIN_MODULATION_DEPTH && val <= AL_EAXREVERB_MAX_MODULATION_DEPTH))
+ SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb modulation depth out of range");
+ props->Reverb.ModulationDepth = val;
+ break;
+
+ case AL_EAXREVERB_HFREFERENCE:
+ if(!(val >= AL_EAXREVERB_MIN_HFREFERENCE && val <= AL_EAXREVERB_MAX_HFREFERENCE))
+ SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb hfreference out of range");
+ props->Reverb.HFReference = val;
+ break;
+
+ case AL_EAXREVERB_LFREFERENCE:
+ if(!(val >= AL_EAXREVERB_MIN_LFREFERENCE && val <= AL_EAXREVERB_MAX_LFREFERENCE))
+ SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb lfreference out of range");
+ props->Reverb.LFReference = val;
+ break;
+
+ case AL_EAXREVERB_ROOM_ROLLOFF_FACTOR:
+ if(!(val >= AL_EAXREVERB_MIN_ROOM_ROLLOFF_FACTOR && val <= AL_EAXREVERB_MAX_ROOM_ROLLOFF_FACTOR))
+ SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb room rolloff factor out of range");
+ props->Reverb.RoomRolloffFactor = val;
+ break;
+
+ default:
+ alSetError(context, AL_INVALID_ENUM, "Invalid EAX reverb float property 0x%04x",
+ param);
+ }
+}
+void EAXReverb_setParamfv(EffectProps *props, ALCcontext *context, ALenum param, const ALfloat *vals)
+{
+ switch(param)
+ {
+ case AL_EAXREVERB_REFLECTIONS_PAN:
+ if(!(std::isfinite(vals[0]) && std::isfinite(vals[1]) && std::isfinite(vals[2])))
+ SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb reflections pan out of range");
+ props->Reverb.ReflectionsPan[0] = vals[0];
+ props->Reverb.ReflectionsPan[1] = vals[1];
+ props->Reverb.ReflectionsPan[2] = vals[2];
+ break;
+ case AL_EAXREVERB_LATE_REVERB_PAN:
+ if(!(std::isfinite(vals[0]) && std::isfinite(vals[1]) && std::isfinite(vals[2])))
+ SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb late reverb pan out of range");
+ props->Reverb.LateReverbPan[0] = vals[0];
+ props->Reverb.LateReverbPan[1] = vals[1];
+ props->Reverb.LateReverbPan[2] = vals[2];
+ break;
+
+ default:
+ EAXReverb_setParamf(props, context, param, vals[0]);
+ break;
+ }
+}
+
+void EAXReverb_getParami(const EffectProps *props, ALCcontext *context, ALenum param, ALint *val)
+{
+ switch(param)
+ {
+ case AL_EAXREVERB_DECAY_HFLIMIT:
+ *val = props->Reverb.DecayHFLimit;
+ break;
+
+ default:
+ alSetError(context, AL_INVALID_ENUM, "Invalid EAX reverb integer property 0x%04x",
+ param);
+ }
+}
+void EAXReverb_getParamiv(const EffectProps *props, ALCcontext *context, ALenum param, ALint *vals)
+{ EAXReverb_getParami(props, context, param, vals); }
+void EAXReverb_getParamf(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *val)
+{
+ switch(param)
+ {
+ case AL_EAXREVERB_DENSITY:
+ *val = props->Reverb.Density;
+ break;
+
+ case AL_EAXREVERB_DIFFUSION:
+ *val = props->Reverb.Diffusion;
+ break;
+
+ case AL_EAXREVERB_GAIN:
+ *val = props->Reverb.Gain;
+ break;
+
+ case AL_EAXREVERB_GAINHF:
+ *val = props->Reverb.GainHF;
+ break;
+
+ case AL_EAXREVERB_GAINLF:
+ *val = props->Reverb.GainLF;
+ break;
+
+ case AL_EAXREVERB_DECAY_TIME:
+ *val = props->Reverb.DecayTime;
+ break;
+
+ case AL_EAXREVERB_DECAY_HFRATIO:
+ *val = props->Reverb.DecayHFRatio;
+ break;
+
+ case AL_EAXREVERB_DECAY_LFRATIO:
+ *val = props->Reverb.DecayLFRatio;
+ break;
+
+ case AL_EAXREVERB_REFLECTIONS_GAIN:
+ *val = props->Reverb.ReflectionsGain;
+ break;
+
+ case AL_EAXREVERB_REFLECTIONS_DELAY:
+ *val = props->Reverb.ReflectionsDelay;
+ break;
+
+ case AL_EAXREVERB_LATE_REVERB_GAIN:
+ *val = props->Reverb.LateReverbGain;
+ break;
+
+ case AL_EAXREVERB_LATE_REVERB_DELAY:
+ *val = props->Reverb.LateReverbDelay;
+ break;
+
+ case AL_EAXREVERB_AIR_ABSORPTION_GAINHF:
+ *val = props->Reverb.AirAbsorptionGainHF;
+ break;
+
+ case AL_EAXREVERB_ECHO_TIME:
+ *val = props->Reverb.EchoTime;
+ break;
+
+ case AL_EAXREVERB_ECHO_DEPTH:
+ *val = props->Reverb.EchoDepth;
+ break;
+
+ case AL_EAXREVERB_MODULATION_TIME:
+ *val = props->Reverb.ModulationTime;
+ break;
+
+ case AL_EAXREVERB_MODULATION_DEPTH:
+ *val = props->Reverb.ModulationDepth;
+ break;
+
+ case AL_EAXREVERB_HFREFERENCE:
+ *val = props->Reverb.HFReference;
+ break;
+
+ case AL_EAXREVERB_LFREFERENCE:
+ *val = props->Reverb.LFReference;
+ break;
+
+ case AL_EAXREVERB_ROOM_ROLLOFF_FACTOR:
+ *val = props->Reverb.RoomRolloffFactor;
+ break;
+
+ default:
+ alSetError(context, AL_INVALID_ENUM, "Invalid EAX reverb float property 0x%04x",
+ param);
+ }
+}
+void EAXReverb_getParamfv(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *vals)
+{
+ switch(param)
+ {
+ case AL_EAXREVERB_REFLECTIONS_PAN:
+ vals[0] = props->Reverb.ReflectionsPan[0];
+ vals[1] = props->Reverb.ReflectionsPan[1];
+ vals[2] = props->Reverb.ReflectionsPan[2];
+ break;
+ case AL_EAXREVERB_LATE_REVERB_PAN:
+ vals[0] = props->Reverb.LateReverbPan[0];
+ vals[1] = props->Reverb.LateReverbPan[1];
+ vals[2] = props->Reverb.LateReverbPan[2];
+ break;
+
+ default:
+ EAXReverb_getParamf(props, context, param, vals);
+ break;
+ }
+}
+
+DEFINE_ALEFFECT_VTABLE(EAXReverb);
+
+
+struct ReverbStateFactory final : public EffectStateFactory {
+ EffectState *create() override { return new ReverbState{}; }
+ EffectProps getDefaultProps() const noexcept override;
+ const EffectVtable *getEffectVtable() const noexcept override { return &EAXReverb_vtable; }
+};
+
+EffectProps ReverbStateFactory::getDefaultProps() const noexcept
+{
+ EffectProps props{};
+ props.Reverb.Density = AL_EAXREVERB_DEFAULT_DENSITY;
+ props.Reverb.Diffusion = AL_EAXREVERB_DEFAULT_DIFFUSION;
+ props.Reverb.Gain = AL_EAXREVERB_DEFAULT_GAIN;
+ props.Reverb.GainHF = AL_EAXREVERB_DEFAULT_GAINHF;
+ props.Reverb.GainLF = AL_EAXREVERB_DEFAULT_GAINLF;
+ props.Reverb.DecayTime = AL_EAXREVERB_DEFAULT_DECAY_TIME;
+ props.Reverb.DecayHFRatio = AL_EAXREVERB_DEFAULT_DECAY_HFRATIO;
+ props.Reverb.DecayLFRatio = AL_EAXREVERB_DEFAULT_DECAY_LFRATIO;
+ props.Reverb.ReflectionsGain = AL_EAXREVERB_DEFAULT_REFLECTIONS_GAIN;
+ props.Reverb.ReflectionsDelay = AL_EAXREVERB_DEFAULT_REFLECTIONS_DELAY;
+ props.Reverb.ReflectionsPan[0] = AL_EAXREVERB_DEFAULT_REFLECTIONS_PAN_XYZ;
+ props.Reverb.ReflectionsPan[1] = AL_EAXREVERB_DEFAULT_REFLECTIONS_PAN_XYZ;
+ props.Reverb.ReflectionsPan[2] = AL_EAXREVERB_DEFAULT_REFLECTIONS_PAN_XYZ;
+ props.Reverb.LateReverbGain = AL_EAXREVERB_DEFAULT_LATE_REVERB_GAIN;
+ props.Reverb.LateReverbDelay = AL_EAXREVERB_DEFAULT_LATE_REVERB_DELAY;
+ props.Reverb.LateReverbPan[0] = AL_EAXREVERB_DEFAULT_LATE_REVERB_PAN_XYZ;
+ props.Reverb.LateReverbPan[1] = AL_EAXREVERB_DEFAULT_LATE_REVERB_PAN_XYZ;
+ props.Reverb.LateReverbPan[2] = AL_EAXREVERB_DEFAULT_LATE_REVERB_PAN_XYZ;
+ props.Reverb.EchoTime = AL_EAXREVERB_DEFAULT_ECHO_TIME;
+ props.Reverb.EchoDepth = AL_EAXREVERB_DEFAULT_ECHO_DEPTH;
+ props.Reverb.ModulationTime = AL_EAXREVERB_DEFAULT_MODULATION_TIME;
+ props.Reverb.ModulationDepth = AL_EAXREVERB_DEFAULT_MODULATION_DEPTH;
+ props.Reverb.AirAbsorptionGainHF = AL_EAXREVERB_DEFAULT_AIR_ABSORPTION_GAINHF;
+ props.Reverb.HFReference = AL_EAXREVERB_DEFAULT_HFREFERENCE;
+ props.Reverb.LFReference = AL_EAXREVERB_DEFAULT_LFREFERENCE;
+ props.Reverb.RoomRolloffFactor = AL_EAXREVERB_DEFAULT_ROOM_ROLLOFF_FACTOR;
+ props.Reverb.DecayHFLimit = AL_EAXREVERB_DEFAULT_DECAY_HFLIMIT;
+ return props;
+}
+
+
+void StdReverb_setParami(EffectProps *props, ALCcontext *context, ALenum param, ALint val)
+{
+ switch(param)
+ {
+ case AL_REVERB_DECAY_HFLIMIT:
+ if(!(val >= AL_REVERB_MIN_DECAY_HFLIMIT && val <= AL_REVERB_MAX_DECAY_HFLIMIT))
+ SETERR_RETURN(context, AL_INVALID_VALUE,, "Reverb decay hflimit out of range");
+ props->Reverb.DecayHFLimit = val;
+ break;
+
+ default:
+ alSetError(context, AL_INVALID_ENUM, "Invalid reverb integer property 0x%04x", param);
+ }
+}
+void StdReverb_setParamiv(EffectProps *props, ALCcontext *context, ALenum param, const ALint *vals)
+{ StdReverb_setParami(props, context, param, vals[0]); }
+void StdReverb_setParamf(EffectProps *props, ALCcontext *context, ALenum param, ALfloat val)
+{
+ switch(param)
+ {
+ case AL_REVERB_DENSITY:
+ if(!(val >= AL_REVERB_MIN_DENSITY && val <= AL_REVERB_MAX_DENSITY))
+ SETERR_RETURN(context, AL_INVALID_VALUE,, "Reverb density out of range");
+ props->Reverb.Density = val;
+ break;
+
+ case AL_REVERB_DIFFUSION:
+ if(!(val >= AL_REVERB_MIN_DIFFUSION && val <= AL_REVERB_MAX_DIFFUSION))
+ SETERR_RETURN(context, AL_INVALID_VALUE,, "Reverb diffusion out of range");
+ props->Reverb.Diffusion = val;
+ break;
+
+ case AL_REVERB_GAIN:
+ if(!(val >= AL_REVERB_MIN_GAIN && val <= AL_REVERB_MAX_GAIN))
+ SETERR_RETURN(context, AL_INVALID_VALUE,, "Reverb gain out of range");
+ props->Reverb.Gain = val;
+ break;
+
+ case AL_REVERB_GAINHF:
+ if(!(val >= AL_REVERB_MIN_GAINHF && val <= AL_REVERB_MAX_GAINHF))
+ SETERR_RETURN(context, AL_INVALID_VALUE,, "Reverb gainhf out of range");
+ props->Reverb.GainHF = val;
+ break;
+
+ case AL_REVERB_DECAY_TIME:
+ if(!(val >= AL_REVERB_MIN_DECAY_TIME && val <= AL_REVERB_MAX_DECAY_TIME))
+ SETERR_RETURN(context, AL_INVALID_VALUE,, "Reverb decay time out of range");
+ props->Reverb.DecayTime = val;
+ break;
+
+ case AL_REVERB_DECAY_HFRATIO:
+ if(!(val >= AL_REVERB_MIN_DECAY_HFRATIO && val <= AL_REVERB_MAX_DECAY_HFRATIO))
+ SETERR_RETURN(context, AL_INVALID_VALUE,, "Reverb decay hfratio out of range");
+ props->Reverb.DecayHFRatio = val;
+ break;
+
+ case AL_REVERB_REFLECTIONS_GAIN:
+ if(!(val >= AL_REVERB_MIN_REFLECTIONS_GAIN && val <= AL_REVERB_MAX_REFLECTIONS_GAIN))
+ SETERR_RETURN(context, AL_INVALID_VALUE,, "Reverb reflections gain out of range");
+ props->Reverb.ReflectionsGain = val;
+ break;
+
+ case AL_REVERB_REFLECTIONS_DELAY:
+ if(!(val >= AL_REVERB_MIN_REFLECTIONS_DELAY && val <= AL_REVERB_MAX_REFLECTIONS_DELAY))
+ SETERR_RETURN(context, AL_INVALID_VALUE,, "Reverb reflections delay out of range");
+ props->Reverb.ReflectionsDelay = val;
+ break;
+
+ case AL_REVERB_LATE_REVERB_GAIN:
+ if(!(val >= AL_REVERB_MIN_LATE_REVERB_GAIN && val <= AL_REVERB_MAX_LATE_REVERB_GAIN))
+ SETERR_RETURN(context, AL_INVALID_VALUE,, "Reverb late reverb gain out of range");
+ props->Reverb.LateReverbGain = val;
+ break;
+
+ case AL_REVERB_LATE_REVERB_DELAY:
+ if(!(val >= AL_REVERB_MIN_LATE_REVERB_DELAY && val <= AL_REVERB_MAX_LATE_REVERB_DELAY))
+ SETERR_RETURN(context, AL_INVALID_VALUE,, "Reverb late reverb delay out of range");
+ props->Reverb.LateReverbDelay = val;
+ break;
+
+ case AL_REVERB_AIR_ABSORPTION_GAINHF:
+ if(!(val >= AL_REVERB_MIN_AIR_ABSORPTION_GAINHF && val <= AL_REVERB_MAX_AIR_ABSORPTION_GAINHF))
+ SETERR_RETURN(context, AL_INVALID_VALUE,, "Reverb air absorption gainhf out of range");
+ props->Reverb.AirAbsorptionGainHF = val;
+ break;
+
+ case AL_REVERB_ROOM_ROLLOFF_FACTOR:
+ if(!(val >= AL_REVERB_MIN_ROOM_ROLLOFF_FACTOR && val <= AL_REVERB_MAX_ROOM_ROLLOFF_FACTOR))
+ SETERR_RETURN(context, AL_INVALID_VALUE,, "Reverb room rolloff factor out of range");
+ props->Reverb.RoomRolloffFactor = val;
+ break;
+
+ default:
+ alSetError(context, AL_INVALID_ENUM, "Invalid reverb float property 0x%04x", param);
+ }
+}
+void StdReverb_setParamfv(EffectProps *props, ALCcontext *context, ALenum param, const ALfloat *vals)
+{ StdReverb_setParamf(props, context, param, vals[0]); }
+
+void StdReverb_getParami(const EffectProps *props, ALCcontext *context, ALenum param, ALint *val)
+{
+ switch(param)
+ {
+ case AL_REVERB_DECAY_HFLIMIT:
+ *val = props->Reverb.DecayHFLimit;
+ break;
+
+ default:
+ alSetError(context, AL_INVALID_ENUM, "Invalid reverb integer property 0x%04x", param);
+ }
+}
+void StdReverb_getParamiv(const EffectProps *props, ALCcontext *context, ALenum param, ALint *vals)
+{ StdReverb_getParami(props, context, param, vals); }
+void StdReverb_getParamf(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *val)
+{
+ switch(param)
+ {
+ case AL_REVERB_DENSITY:
+ *val = props->Reverb.Density;
+ break;
+
+ case AL_REVERB_DIFFUSION:
+ *val = props->Reverb.Diffusion;
+ break;
+
+ case AL_REVERB_GAIN:
+ *val = props->Reverb.Gain;
+ break;
+
+ case AL_REVERB_GAINHF:
+ *val = props->Reverb.GainHF;
+ break;
+
+ case AL_REVERB_DECAY_TIME:
+ *val = props->Reverb.DecayTime;
+ break;
+
+ case AL_REVERB_DECAY_HFRATIO:
+ *val = props->Reverb.DecayHFRatio;
+ break;
+
+ case AL_REVERB_REFLECTIONS_GAIN:
+ *val = props->Reverb.ReflectionsGain;
+ break;
+
+ case AL_REVERB_REFLECTIONS_DELAY:
+ *val = props->Reverb.ReflectionsDelay;
+ break;
+
+ case AL_REVERB_LATE_REVERB_GAIN:
+ *val = props->Reverb.LateReverbGain;
+ break;
+
+ case AL_REVERB_LATE_REVERB_DELAY:
+ *val = props->Reverb.LateReverbDelay;
+ break;
+
+ case AL_REVERB_AIR_ABSORPTION_GAINHF:
+ *val = props->Reverb.AirAbsorptionGainHF;
+ break;
+
+ case AL_REVERB_ROOM_ROLLOFF_FACTOR:
+ *val = props->Reverb.RoomRolloffFactor;
+ break;
+
+ default:
+ alSetError(context, AL_INVALID_ENUM, "Invalid reverb float property 0x%04x", param);
+ }
+}
+void StdReverb_getParamfv(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *vals)
+{ StdReverb_getParamf(props, context, param, vals); }
+
+DEFINE_ALEFFECT_VTABLE(StdReverb);
+
+
+struct StdReverbStateFactory final : public EffectStateFactory {
+ EffectState *create() override { return new ReverbState{}; }
+ EffectProps getDefaultProps() const noexcept override;
+ const EffectVtable *getEffectVtable() const noexcept override { return &StdReverb_vtable; }
+};
+
+EffectProps StdReverbStateFactory::getDefaultProps() const noexcept
+{
+ EffectProps props{};
+ props.Reverb.Density = AL_REVERB_DEFAULT_DENSITY;
+ props.Reverb.Diffusion = AL_REVERB_DEFAULT_DIFFUSION;
+ props.Reverb.Gain = AL_REVERB_DEFAULT_GAIN;
+ props.Reverb.GainHF = AL_REVERB_DEFAULT_GAINHF;
+ props.Reverb.GainLF = 1.0f;
+ props.Reverb.DecayTime = AL_REVERB_DEFAULT_DECAY_TIME;
+ props.Reverb.DecayHFRatio = AL_REVERB_DEFAULT_DECAY_HFRATIO;
+ props.Reverb.DecayLFRatio = 1.0f;
+ props.Reverb.ReflectionsGain = AL_REVERB_DEFAULT_REFLECTIONS_GAIN;
+ props.Reverb.ReflectionsDelay = AL_REVERB_DEFAULT_REFLECTIONS_DELAY;
+ props.Reverb.ReflectionsPan[0] = 0.0f;
+ props.Reverb.ReflectionsPan[1] = 0.0f;
+ props.Reverb.ReflectionsPan[2] = 0.0f;
+ props.Reverb.LateReverbGain = AL_REVERB_DEFAULT_LATE_REVERB_GAIN;
+ props.Reverb.LateReverbDelay = AL_REVERB_DEFAULT_LATE_REVERB_DELAY;
+ props.Reverb.LateReverbPan[0] = 0.0f;
+ props.Reverb.LateReverbPan[1] = 0.0f;
+ props.Reverb.LateReverbPan[2] = 0.0f;
+ props.Reverb.EchoTime = 0.25f;
+ props.Reverb.EchoDepth = 0.0f;
+ props.Reverb.ModulationTime = 0.25f;
+ props.Reverb.ModulationDepth = 0.0f;
+ props.Reverb.AirAbsorptionGainHF = AL_REVERB_DEFAULT_AIR_ABSORPTION_GAINHF;
+ props.Reverb.HFReference = 5000.0f;
+ props.Reverb.LFReference = 250.0f;
+ props.Reverb.RoomRolloffFactor = AL_REVERB_DEFAULT_ROOM_ROLLOFF_FACTOR;
+ props.Reverb.DecayHFLimit = AL_REVERB_DEFAULT_DECAY_HFLIMIT;
+ return props;
+}
+
+} // namespace
+
+EffectStateFactory *ReverbStateFactory_getFactory()
+{
+ static ReverbStateFactory ReverbFactory{};
+ return &ReverbFactory;
+}
+
+EffectStateFactory *StdReverbStateFactory_getFactory()
+{
+ static StdReverbStateFactory ReverbFactory{};
+ return &ReverbFactory;
+}
diff --git a/alc/effects/vmorpher.cpp b/alc/effects/vmorpher.cpp
new file mode 100644
index 00000000..eebba3f1
--- /dev/null
+++ b/alc/effects/vmorpher.cpp
@@ -0,0 +1,430 @@
+/**
+ * OpenAL cross platform audio library
+ * Copyright (C) 2019 by Anis A. Hireche
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ * Or go to http://www.gnu.org/copyleft/lgpl.html
+ */
+
+#include "config.h"
+
+#include <cmath>
+#include <cstdlib>
+#include <algorithm>
+#include <functional>
+
+#include "alcmain.h"
+#include "alcontext.h"
+#include "alAuxEffectSlot.h"
+#include "alError.h"
+#include "alu.h"
+
+namespace {
+
+#define MAX_UPDATE_SAMPLES 128
+#define NUM_FORMANTS 4
+#define NUM_FILTERS 2
+#define Q_FACTOR 5.0f
+
+#define VOWEL_A_INDEX 0
+#define VOWEL_B_INDEX 1
+
+#define WAVEFORM_FRACBITS 24
+#define WAVEFORM_FRACONE (1<<WAVEFORM_FRACBITS)
+#define WAVEFORM_FRACMASK (WAVEFORM_FRACONE-1)
+
+inline ALfloat Sin(ALsizei index)
+{
+ constexpr ALfloat scale{al::MathDefs<float>::Tau() / ALfloat{WAVEFORM_FRACONE}};
+ return std::sin(static_cast<ALfloat>(index) * scale)*0.5f + 0.5f;
+}
+
+inline ALfloat Saw(ALsizei index)
+{
+ return static_cast<ALfloat>(index) / ALfloat{WAVEFORM_FRACONE};
+}
+
+inline ALfloat Triangle(ALsizei index)
+{
+ return std::fabs(static_cast<ALfloat>(index)*(2.0f/WAVEFORM_FRACONE) - 1.0f);
+}
+
+inline ALfloat Half(ALsizei)
+{
+ return 0.5f;
+}
+
+template<ALfloat func(ALsizei)>
+void Oscillate(ALfloat *RESTRICT dst, ALsizei index, const ALsizei step, ALsizei todo)
+{
+ for(ALsizei i{0};i < todo;i++)
+ {
+ index += step;
+ index &= WAVEFORM_FRACMASK;
+ dst[i] = func(index);
+ }
+}
+
+struct FormantFilter
+{
+ ALfloat f0norm{0.0f};
+ ALfloat fGain{1.0f};
+ ALfloat s1{0.0f};
+ ALfloat s2{0.0f};
+
+ FormantFilter() = default;
+ FormantFilter(ALfloat f0norm_, ALfloat gain) : f0norm{f0norm_}, fGain{gain} { }
+
+ inline void process(const ALfloat* samplesIn, ALfloat* samplesOut, const ALsizei numInput)
+ {
+ /* A state variable filter from a topology-preserving transform.
+ * Based on a talk given by Ivan Cohen: https://www.youtube.com/watch?v=esjHXGPyrhg
+ */
+ const ALfloat g = std::tan(al::MathDefs<float>::Pi() * f0norm);
+ const ALfloat h = 1.0f / (1 + (g / Q_FACTOR) + (g * g));
+
+ for (ALsizei i{0};i < numInput;i++)
+ {
+ const ALfloat H = h * (samplesIn[i] - (1.0f / Q_FACTOR + g) * s1 - s2);
+ const ALfloat B = g * H + s1;
+ const ALfloat L = g * B + s2;
+
+ s1 = g * H + B;
+ s2 = g * B + L;
+
+ // Apply peak and accumulate samples.
+ samplesOut[i] += B * fGain;
+ }
+ }
+
+ inline void clear()
+ {
+ s1 = 0.0f;
+ s2 = 0.0f;
+ }
+};
+
+
+struct VmorpherState final : public EffectState {
+ struct {
+ /* Effect parameters */
+ FormantFilter Formants[NUM_FILTERS][NUM_FORMANTS];
+
+ /* Effect gains for each channel */
+ ALfloat CurrentGains[MAX_OUTPUT_CHANNELS]{};
+ ALfloat TargetGains[MAX_OUTPUT_CHANNELS]{};
+ } mChans[MAX_AMBI_CHANNELS];
+
+ void (*mGetSamples)(ALfloat* RESTRICT, ALsizei, const ALsizei, ALsizei) {};
+
+ ALsizei mIndex{0};
+ ALsizei mStep{1};
+
+ /* Effects buffers */
+ ALfloat mSampleBufferA[MAX_UPDATE_SAMPLES]{};
+ ALfloat mSampleBufferB[MAX_UPDATE_SAMPLES]{};
+
+ ALboolean deviceUpdate(const ALCdevice *device) override;
+ void update(const ALCcontext *context, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target) override;
+ void process(const ALsizei samplesToDo, const FloatBufferLine *RESTRICT samplesIn, const ALsizei numInput, const al::span<FloatBufferLine> samplesOut) override;
+
+ static std::array<FormantFilter,4> getFiltersByPhoneme(ALenum phoneme, ALfloat frequency, ALfloat pitch);
+
+ DEF_NEWDEL(VmorpherState)
+};
+
+std::array<FormantFilter,4> VmorpherState::getFiltersByPhoneme(ALenum phoneme, ALfloat frequency, ALfloat pitch)
+{
+ /* Using soprano formant set of values to
+ * better match mid-range frequency space.
+ *
+ * See: https://www.classes.cs.uchicago.edu/archive/1999/spring/CS295/Computing_Resources/Csound/CsManual3.48b1.HTML/Appendices/table3.html
+ */
+ switch(phoneme)
+ {
+ case AL_VOCAL_MORPHER_PHONEME_A:
+ return {{
+ {( 800 * pitch) / frequency, 1.000000f}, /* std::pow(10.0f, 0 / 20.0f); */
+ {(1150 * pitch) / frequency, 0.501187f}, /* std::pow(10.0f, -6 / 20.0f); */
+ {(2900 * pitch) / frequency, 0.025118f}, /* std::pow(10.0f, -32 / 20.0f); */
+ {(3900 * pitch) / frequency, 0.100000f} /* std::pow(10.0f, -20 / 20.0f); */
+ }};
+ case AL_VOCAL_MORPHER_PHONEME_E:
+ return {{
+ {( 350 * pitch) / frequency, 1.000000f}, /* std::pow(10.0f, 0 / 20.0f); */
+ {(2000 * pitch) / frequency, 0.100000f}, /* std::pow(10.0f, -20 / 20.0f); */
+ {(2800 * pitch) / frequency, 0.177827f}, /* std::pow(10.0f, -15 / 20.0f); */
+ {(3600 * pitch) / frequency, 0.009999f} /* std::pow(10.0f, -40 / 20.0f); */
+ }};
+ case AL_VOCAL_MORPHER_PHONEME_I:
+ return {{
+ {( 270 * pitch) / frequency, 1.000000f}, /* std::pow(10.0f, 0 / 20.0f); */
+ {(2140 * pitch) / frequency, 0.251188f}, /* std::pow(10.0f, -12 / 20.0f); */
+ {(2950 * pitch) / frequency, 0.050118f}, /* std::pow(10.0f, -26 / 20.0f); */
+ {(3900 * pitch) / frequency, 0.050118f} /* std::pow(10.0f, -26 / 20.0f); */
+ }};
+ case AL_VOCAL_MORPHER_PHONEME_O:
+ return {{
+ {( 450 * pitch) / frequency, 1.000000f}, /* std::pow(10.0f, 0 / 20.0f); */
+ {( 800 * pitch) / frequency, 0.281838f}, /* std::pow(10.0f, -11 / 20.0f); */
+ {(2830 * pitch) / frequency, 0.079432f}, /* std::pow(10.0f, -22 / 20.0f); */
+ {(3800 * pitch) / frequency, 0.079432f} /* std::pow(10.0f, -22 / 20.0f); */
+ }};
+ case AL_VOCAL_MORPHER_PHONEME_U:
+ return {{
+ {( 325 * pitch) / frequency, 1.000000f}, /* std::pow(10.0f, 0 / 20.0f); */
+ {( 700 * pitch) / frequency, 0.158489f}, /* std::pow(10.0f, -16 / 20.0f); */
+ {(2700 * pitch) / frequency, 0.017782f}, /* std::pow(10.0f, -35 / 20.0f); */
+ {(3800 * pitch) / frequency, 0.009999f} /* std::pow(10.0f, -40 / 20.0f); */
+ }};
+ }
+ return {};
+}
+
+
+ALboolean VmorpherState::deviceUpdate(const ALCdevice* /*device*/)
+{
+ for(auto &e : mChans)
+ {
+ std::for_each(std::begin(e.Formants[VOWEL_A_INDEX]), std::end(e.Formants[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::mem_fn(&FormantFilter::clear));
+ std::fill(std::begin(e.CurrentGains), std::end(e.CurrentGains), 0.0f);
+ }
+
+ return AL_TRUE;
+}
+
+void VmorpherState::update(const ALCcontext *context, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target)
+{
+ const ALCdevice *device{context->Device};
+ const ALfloat frequency{static_cast<ALfloat>(device->Frequency)};
+ const ALfloat step{props->Vmorpher.Rate / static_cast<ALfloat>(device->Frequency)};
+ mStep = fastf2i(clampf(step*WAVEFORM_FRACONE, 0.0f, ALfloat{WAVEFORM_FRACONE-1}));
+
+ if(mStep == 0)
+ mGetSamples = Oscillate<Half>;
+ else if(props->Vmorpher.Waveform == AL_VOCAL_MORPHER_WAVEFORM_SINUSOID)
+ mGetSamples = Oscillate<Sin>;
+ else if(props->Vmorpher.Waveform == AL_VOCAL_MORPHER_WAVEFORM_SAWTOOTH)
+ mGetSamples = Oscillate<Saw>;
+ else /*if(props->Vmorpher.Waveform == AL_VOCAL_MORPHER_WAVEFORM_TRIANGLE)*/
+ mGetSamples = Oscillate<Triangle>;
+
+ const ALfloat pitchA{fastf2i(std::pow(2.0f, props->Vmorpher.PhonemeACoarseTuning*100.0f / 2400.0f)*FRACTIONONE) * (1.0f/FRACTIONONE)};
+ const ALfloat pitchB{fastf2i(std::pow(2.0f, props->Vmorpher.PhonemeBCoarseTuning*100.0f / 2400.0f)*FRACTIONONE) * (1.0f/FRACTIONONE)};
+
+ auto vowelA = getFiltersByPhoneme(props->Vmorpher.PhonemeA, frequency, pitchA);
+ auto vowelB = getFiltersByPhoneme(props->Vmorpher.PhonemeB, frequency, pitchB);
+
+ /* 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]));
+ }
+
+ mOutTarget = target.Main->Buffer;
+ for(size_t i{0u};i < slot->Wet.Buffer.size();++i)
+ {
+ auto coeffs = GetAmbiIdentityRow(i);
+ ComputePanGains(target.Main, coeffs.data(), slot->Params.Gain, mChans[i].TargetGains);
+ }
+}
+
+void VmorpherState::process(const ALsizei samplesToDo, const FloatBufferLine *RESTRICT samplesIn, const ALsizei numInput, const al::span<FloatBufferLine> samplesOut)
+{
+ /* Following the EFX specification for a conformant implementation which describes
+ * the effect as a pair of 4-band formant filters blended together using an LFO.
+ */
+ for(ALsizei base{0};base < samplesToDo;)
+ {
+ alignas(16) ALfloat lfo[MAX_UPDATE_SAMPLES];
+ const ALsizei td = mini(MAX_UPDATE_SAMPLES, samplesToDo-base);
+
+ mGetSamples(lfo, mIndex, mStep, td);
+ mIndex += (mStep * td) & WAVEFORM_FRACMASK;
+ mIndex &= WAVEFORM_FRACMASK;
+
+ ASSUME(numInput > 0);
+ for(ALsizei c{0};c < numInput;c++)
+ {
+ for (ALsizei i{0};i < td;i++)
+ {
+ mSampleBufferA[i] = 0.0f;
+ mSampleBufferB[i] = 0.0f;
+ }
+
+ auto& vowelA = mChans[c].Formants[VOWEL_A_INDEX];
+ auto& vowelB = mChans[c].Formants[VOWEL_B_INDEX];
+
+ /* Process first vowel. */
+ vowelA[0].process(&samplesIn[c][base], mSampleBufferA, td);
+ vowelA[1].process(&samplesIn[c][base], mSampleBufferA, td);
+ vowelA[2].process(&samplesIn[c][base], mSampleBufferA, td);
+ vowelA[3].process(&samplesIn[c][base], mSampleBufferA, td);
+
+ /* Process second vowel. */
+ vowelB[0].process(&samplesIn[c][base], mSampleBufferB, td);
+ vowelB[1].process(&samplesIn[c][base], mSampleBufferB, td);
+ vowelB[2].process(&samplesIn[c][base], mSampleBufferB, td);
+ vowelB[3].process(&samplesIn[c][base], mSampleBufferB, td);
+
+ alignas(16) ALfloat samplesBlended[MAX_UPDATE_SAMPLES];
+
+ for (ALsizei i{0};i < td;i++)
+ samplesBlended[i] = lerp(mSampleBufferA[i], mSampleBufferB[i], lfo[i]);
+
+ /* Now, mix the processed sound data to the output. */
+ MixSamples(samplesBlended, samplesOut, mChans[c].CurrentGains, mChans[c].TargetGains,
+ samplesToDo-base, base, td);
+ }
+
+ base += td;
+ }
+}
+
+
+void Vmorpher_setParami(EffectProps* props, ALCcontext *context, ALenum param, ALint val)
+{
+ switch(param)
+ {
+ case AL_VOCAL_MORPHER_WAVEFORM:
+ if(!(val >= AL_VOCAL_MORPHER_MIN_WAVEFORM && val <= AL_VOCAL_MORPHER_MAX_WAVEFORM))
+ SETERR_RETURN(context, AL_INVALID_VALUE,, "Vocal morpher waveform out of range");
+ props->Vmorpher.Waveform = val;
+ break;
+
+ case AL_VOCAL_MORPHER_PHONEMEA:
+ if(!(val >= AL_VOCAL_MORPHER_MIN_PHONEMEA && val <= AL_VOCAL_MORPHER_MAX_PHONEMEA))
+ SETERR_RETURN(context, AL_INVALID_VALUE,, "Vocal morpher phoneme-a out of range");
+ props->Vmorpher.PhonemeA = val;
+ break;
+
+ case AL_VOCAL_MORPHER_PHONEMEB:
+ if(!(val >= AL_VOCAL_MORPHER_MIN_PHONEMEB && val <= AL_VOCAL_MORPHER_MAX_PHONEMEB))
+ SETERR_RETURN(context, AL_INVALID_VALUE,, "Vocal morpher phoneme-b out of range");
+ props->Vmorpher.PhonemeB = val;
+ break;
+
+ case AL_VOCAL_MORPHER_PHONEMEA_COARSE_TUNING:
+ if(!(val >= AL_VOCAL_MORPHER_MIN_PHONEMEA_COARSE_TUNING && val <= AL_VOCAL_MORPHER_MAX_PHONEMEA_COARSE_TUNING))
+ SETERR_RETURN(context, AL_INVALID_VALUE,, "Vocal morpher phoneme-a coarse tuning out of range");
+ props->Vmorpher.PhonemeACoarseTuning = val;
+ break;
+
+ case AL_VOCAL_MORPHER_PHONEMEB_COARSE_TUNING:
+ if(!(val >= AL_VOCAL_MORPHER_MIN_PHONEMEB_COARSE_TUNING && val <= AL_VOCAL_MORPHER_MAX_PHONEMEB_COARSE_TUNING))
+ SETERR_RETURN(context, AL_INVALID_VALUE,, "Vocal morpher phoneme-b coarse tuning out of range");
+ props->Vmorpher.PhonemeBCoarseTuning = val;
+ break;
+
+ default:
+ alSetError(context, AL_INVALID_ENUM, "Invalid vocal morpher integer property 0x%04x", param);
+ }
+}
+void Vmorpher_setParamiv(EffectProps*, ALCcontext *context, ALenum param, const ALint*)
+{ alSetError(context, AL_INVALID_ENUM, "Invalid vocal morpher integer-vector property 0x%04x", param); }
+void Vmorpher_setParamf(EffectProps *props, ALCcontext *context, ALenum param, ALfloat val)
+{
+ switch(param)
+ {
+ case AL_VOCAL_MORPHER_RATE:
+ if(!(val >= AL_VOCAL_MORPHER_MIN_RATE && val <= AL_VOCAL_MORPHER_MAX_RATE))
+ SETERR_RETURN(context, AL_INVALID_VALUE,, "Vocal morpher rate out of range");
+ props->Vmorpher.Rate = val;
+ break;
+
+ default:
+ alSetError(context, AL_INVALID_ENUM, "Invalid vocal morpher float property 0x%04x", param);
+ }
+}
+void Vmorpher_setParamfv(EffectProps *props, ALCcontext *context, ALenum param, const ALfloat *vals)
+{ Vmorpher_setParamf(props, context, param, vals[0]); }
+
+void Vmorpher_getParami(const EffectProps* props, ALCcontext *context, ALenum param, ALint* val)
+{
+ switch(param)
+ {
+ case AL_VOCAL_MORPHER_PHONEMEA:
+ *val = props->Vmorpher.PhonemeA;
+ break;
+
+ case AL_VOCAL_MORPHER_PHONEMEB:
+ *val = props->Vmorpher.PhonemeB;
+ break;
+
+ case AL_VOCAL_MORPHER_PHONEMEA_COARSE_TUNING:
+ *val = props->Vmorpher.PhonemeACoarseTuning;
+ break;
+
+ case AL_VOCAL_MORPHER_PHONEMEB_COARSE_TUNING:
+ *val = props->Vmorpher.PhonemeBCoarseTuning;
+ break;
+
+ case AL_VOCAL_MORPHER_WAVEFORM:
+ *val = props->Vmorpher.Waveform;
+ break;
+
+ default:
+ alSetError(context, AL_INVALID_ENUM, "Invalid vocal morpher integer property 0x%04x", param);
+ }
+}
+void Vmorpher_getParamiv(const EffectProps*, ALCcontext *context, ALenum param, ALint*)
+{ alSetError(context, AL_INVALID_ENUM, "Invalid vocal morpher integer-vector property 0x%04x", param); }
+void Vmorpher_getParamf(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *val)
+{
+ switch(param)
+ {
+ case AL_VOCAL_MORPHER_RATE:
+ *val = props->Vmorpher.Rate;
+ break;
+
+ default:
+ alSetError(context, AL_INVALID_ENUM, "Invalid vocal morpher float property 0x%04x", param);
+ }
+}
+void Vmorpher_getParamfv(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *vals)
+{ Vmorpher_getParamf(props, context, param, vals); }
+
+DEFINE_ALEFFECT_VTABLE(Vmorpher);
+
+
+struct VmorpherStateFactory final : public EffectStateFactory {
+ EffectState *create() override { return new VmorpherState{}; }
+ EffectProps getDefaultProps() const noexcept override;
+ const EffectVtable *getEffectVtable() const noexcept override { return &Vmorpher_vtable; }
+};
+
+EffectProps VmorpherStateFactory::getDefaultProps() const noexcept
+{
+ EffectProps props{};
+ props.Vmorpher.Rate = AL_VOCAL_MORPHER_DEFAULT_RATE;
+ props.Vmorpher.PhonemeA = AL_VOCAL_MORPHER_DEFAULT_PHONEMEA;
+ props.Vmorpher.PhonemeB = AL_VOCAL_MORPHER_DEFAULT_PHONEMEB;
+ props.Vmorpher.PhonemeACoarseTuning = AL_VOCAL_MORPHER_DEFAULT_PHONEMEA_COARSE_TUNING;
+ props.Vmorpher.PhonemeBCoarseTuning = AL_VOCAL_MORPHER_DEFAULT_PHONEMEB_COARSE_TUNING;
+ props.Vmorpher.Waveform = AL_VOCAL_MORPHER_DEFAULT_WAVEFORM;
+ return props;
+}
+
+} // namespace
+
+EffectStateFactory *VmorpherStateFactory_getFactory()
+{
+ static VmorpherStateFactory VmorpherFactory{};
+ return &VmorpherFactory;
+}