2 * OpenAL cross platform audio library
3 * Copyright (C) 2018 by Raul Herraiz.
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Library General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Library General Public License for more details.
14 * You should have received a copy of the GNU Library General Public
15 * License along with this library; if not, write to the
16 * Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 * Or go to http://www.gnu.org/copyleft/lgpl.html
28 #include "al/auxeffectslot.h"
30 #include "alcontext.h"
32 #include "filters/biquad.h"
37 #define MIN_FREQ 20.0f
38 #define MAX_FREQ 2500.0f
41 struct AutowahState final
: public EffectState
{
42 /* Effect parameters */
45 ALfloat mResonanceGain
;
48 ALfloat mBandwidthNorm
;
51 /* Filter components derived from the envelope. */
58 /* Effect filters' history. */
63 /* Effect gains for each output channel */
64 ALfloat CurrentGains
[MAX_OUTPUT_CHANNELS
];
65 ALfloat TargetGains
[MAX_OUTPUT_CHANNELS
];
66 } mChans
[MAX_AMBI_CHANNELS
];
69 alignas(16) ALfloat mBufferOut
[BUFFERSIZE
];
72 ALboolean
deviceUpdate(const ALCdevice
*device
) override
;
73 void update(const ALCcontext
*context
, const ALeffectslot
*slot
, const EffectProps
*props
, const EffectTarget target
) override
;
74 void process(const size_t samplesToDo
, const al::span
<const FloatBufferLine
> samplesIn
, const al::span
<FloatBufferLine
> samplesOut
) override
;
76 DEF_NEWDEL(AutowahState
)
79 ALboolean
AutowahState::deviceUpdate(const ALCdevice
*)
81 /* (Re-)initializing parameters and clear the buffers. */
85 mResonanceGain
= 10.0f
;
87 mFreqMinNorm
= 4.5e-4f
;
88 mBandwidthNorm
= 0.05f
;
97 for(auto &chan
: mChans
)
99 std::fill(std::begin(chan
.CurrentGains
), std::end(chan
.CurrentGains
), 0.0f
);
100 chan
.Filter
.z1
= 0.0f
;
101 chan
.Filter
.z2
= 0.0f
;
107 void AutowahState::update(const ALCcontext
*context
, const ALeffectslot
*slot
, const EffectProps
*props
, const EffectTarget target
)
109 const ALCdevice
*device
{context
->mDevice
.get()};
110 const auto frequency
= static_cast<float>(device
->Frequency
);
112 const ALfloat ReleaseTime
{clampf(props
->Autowah
.ReleaseTime
, 0.001f
, 1.0f
)};
114 mAttackRate
= std::exp(-1.0f
/ (props
->Autowah
.AttackTime
*frequency
));
115 mReleaseRate
= std::exp(-1.0f
/ (ReleaseTime
*frequency
));
116 /* 0-20dB Resonance Peak gain */
117 mResonanceGain
= std::sqrt(std::log10(props
->Autowah
.Resonance
)*10.0f
/ 3.0f
);
118 mPeakGain
= 1.0f
- std::log10(props
->Autowah
.PeakGain
/AL_AUTOWAH_MAX_PEAK_GAIN
);
119 mFreqMinNorm
= MIN_FREQ
/ frequency
;
120 mBandwidthNorm
= (MAX_FREQ
-MIN_FREQ
) / frequency
;
122 mOutTarget
= target
.Main
->Buffer
;
123 for(size_t i
{0u};i
< slot
->Wet
.Buffer
.size();++i
)
125 auto coeffs
= GetAmbiIdentityRow(i
);
126 ComputePanGains(target
.Main
, coeffs
.data(), slot
->Params
.Gain
, mChans
[i
].TargetGains
);
130 void AutowahState::process(const size_t samplesToDo
, const al::span
<const FloatBufferLine
> samplesIn
, const al::span
<FloatBufferLine
> samplesOut
)
132 const ALfloat attack_rate
= mAttackRate
;
133 const ALfloat release_rate
= mReleaseRate
;
134 const ALfloat res_gain
= mResonanceGain
;
135 const ALfloat peak_gain
= mPeakGain
;
136 const ALfloat freq_min
= mFreqMinNorm
;
137 const ALfloat bandwidth
= mBandwidthNorm
;
139 ALfloat env_delay
{mEnvDelay
};
140 for(size_t i
{0u};i
< samplesToDo
;i
++)
142 ALfloat w0
, sample
, a
;
144 /* Envelope follower described on the book: Audio Effects, Theory,
145 * Implementation and Application.
147 sample
= peak_gain
* std::fabs(samplesIn
[0][i
]);
148 a
= (sample
> env_delay
) ? attack_rate
: release_rate
;
149 env_delay
= lerp(sample
, env_delay
, a
);
151 /* Calculate the cos and alpha components for this sample's filter. */
152 w0
= minf((bandwidth
*env_delay
+ freq_min
), 0.46f
) * al::MathDefs
<float>::Tau();
153 mEnv
[i
].cos_w0
= cosf(w0
);
154 mEnv
[i
].alpha
= sinf(w0
)/(2.0f
* Q_FACTOR
);
156 mEnvDelay
= env_delay
;
158 auto chandata
= std::addressof(mChans
[0]);
159 for(const auto &insamples
: samplesIn
)
161 /* This effectively inlines BiquadFilter_setParams for a peaking
162 * filter and BiquadFilter_processC. The alpha and cosine components
163 * for the filter coefficients were previously calculated with the
164 * envelope. Because the filter changes for each sample, the
165 * coefficients are transient and don't need to be held.
167 ALfloat z1
{chandata
->Filter
.z1
};
168 ALfloat z2
{chandata
->Filter
.z2
};
170 for(size_t i
{0u};i
< samplesToDo
;i
++)
172 const ALfloat alpha
= mEnv
[i
].alpha
;
173 const ALfloat cos_w0
= mEnv
[i
].cos_w0
;
174 ALfloat input
, output
;
177 b
[0] = 1.0f
+ alpha
*res_gain
;
178 b
[1] = -2.0f
* cos_w0
;
179 b
[2] = 1.0f
- alpha
*res_gain
;
180 a
[0] = 1.0f
+ alpha
/res_gain
;
181 a
[1] = -2.0f
* cos_w0
;
182 a
[2] = 1.0f
- alpha
/res_gain
;
184 input
= insamples
[i
];
185 output
= input
*(b
[0]/a
[0]) + z1
;
186 z1
= input
*(b
[1]/a
[0]) - output
*(a
[1]/a
[0]) + z2
;
187 z2
= input
*(b
[2]/a
[0]) - output
*(a
[2]/a
[0]);
188 mBufferOut
[i
] = output
;
190 chandata
->Filter
.z1
= z1
;
191 chandata
->Filter
.z2
= z2
;
193 /* Now, mix the processed sound data to the output. */
194 MixSamples({mBufferOut
, samplesToDo
}, samplesOut
, chandata
->CurrentGains
,
195 chandata
->TargetGains
, samplesToDo
, 0);
201 void Autowah_setParamf(EffectProps
*props
, ALCcontext
*context
, ALenum param
, ALfloat val
)
205 case AL_AUTOWAH_ATTACK_TIME
:
206 if(!(val
>= AL_AUTOWAH_MIN_ATTACK_TIME
&& val
<= AL_AUTOWAH_MAX_ATTACK_TIME
))
207 SETERR_RETURN(context
, AL_INVALID_VALUE
,,"Autowah attack time out of range");
208 props
->Autowah
.AttackTime
= val
;
211 case AL_AUTOWAH_RELEASE_TIME
:
212 if(!(val
>= AL_AUTOWAH_MIN_RELEASE_TIME
&& val
<= AL_AUTOWAH_MAX_RELEASE_TIME
))
213 SETERR_RETURN(context
, AL_INVALID_VALUE
,,"Autowah release time out of range");
214 props
->Autowah
.ReleaseTime
= val
;
217 case AL_AUTOWAH_RESONANCE
:
218 if(!(val
>= AL_AUTOWAH_MIN_RESONANCE
&& val
<= AL_AUTOWAH_MAX_RESONANCE
))
219 SETERR_RETURN(context
, AL_INVALID_VALUE
,,"Autowah resonance out of range");
220 props
->Autowah
.Resonance
= val
;
223 case AL_AUTOWAH_PEAK_GAIN
:
224 if(!(val
>= AL_AUTOWAH_MIN_PEAK_GAIN
&& val
<= AL_AUTOWAH_MAX_PEAK_GAIN
))
225 SETERR_RETURN(context
, AL_INVALID_VALUE
,,"Autowah peak gain out of range");
226 props
->Autowah
.PeakGain
= val
;
230 context
->setError(AL_INVALID_ENUM
, "Invalid autowah float property 0x%04x", param
);
233 void Autowah_setParamfv(EffectProps
*props
, ALCcontext
*context
, ALenum param
, const ALfloat
*vals
)
234 { Autowah_setParamf(props
, context
, param
, vals
[0]); }
236 void Autowah_setParami(EffectProps
*, ALCcontext
*context
, ALenum param
, ALint
)
237 { context
->setError(AL_INVALID_ENUM
, "Invalid autowah integer property 0x%04x", param
); }
238 void Autowah_setParamiv(EffectProps
*, ALCcontext
*context
, ALenum param
, const ALint
*)
239 { context
->setError(AL_INVALID_ENUM
, "Invalid autowah integer vector property 0x%04x", param
); }
241 void Autowah_getParamf(const EffectProps
*props
, ALCcontext
*context
, ALenum param
, ALfloat
*val
)
245 case AL_AUTOWAH_ATTACK_TIME
:
246 *val
= props
->Autowah
.AttackTime
;
249 case AL_AUTOWAH_RELEASE_TIME
:
250 *val
= props
->Autowah
.ReleaseTime
;
253 case AL_AUTOWAH_RESONANCE
:
254 *val
= props
->Autowah
.Resonance
;
257 case AL_AUTOWAH_PEAK_GAIN
:
258 *val
= props
->Autowah
.PeakGain
;
262 context
->setError(AL_INVALID_ENUM
, "Invalid autowah float property 0x%04x", param
);
266 void Autowah_getParamfv(const EffectProps
*props
, ALCcontext
*context
, ALenum param
, ALfloat
*vals
)
267 { Autowah_getParamf(props
, context
, param
, vals
); }
269 void Autowah_getParami(const EffectProps
*, ALCcontext
*context
, ALenum param
, ALint
*)
270 { context
->setError(AL_INVALID_ENUM
, "Invalid autowah integer property 0x%04x", param
); }
271 void Autowah_getParamiv(const EffectProps
*, ALCcontext
*context
, ALenum param
, ALint
*)
272 { context
->setError(AL_INVALID_ENUM
, "Invalid autowah integer vector property 0x%04x", param
); }
274 DEFINE_ALEFFECT_VTABLE(Autowah
);
277 struct AutowahStateFactory final
: public EffectStateFactory
{
278 EffectState
*create() override
{ return new AutowahState
{}; }
279 EffectProps
getDefaultProps() const noexcept override
;
280 const EffectVtable
*getEffectVtable() const noexcept override
{ return &Autowah_vtable
; }
283 EffectProps
AutowahStateFactory::getDefaultProps() const noexcept
286 props
.Autowah
.AttackTime
= AL_AUTOWAH_DEFAULT_ATTACK_TIME
;
287 props
.Autowah
.ReleaseTime
= AL_AUTOWAH_DEFAULT_RELEASE_TIME
;
288 props
.Autowah
.Resonance
= AL_AUTOWAH_DEFAULT_RESONANCE
;
289 props
.Autowah
.PeakGain
= AL_AUTOWAH_DEFAULT_PEAK_GAIN
;
295 EffectStateFactory
*AutowahStateFactory_getFactory()
297 static AutowahStateFactory AutowahFactory
{};
298 return &AutowahFactory
;