2 * OpenAL cross platform audio library
3 * Copyright (C) 2013 by Mike Gorchak
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
33 #include "al/auxeffectslot.h"
35 #include "alcontext.h"
37 #include "alnumeric.h"
41 #include "effects/base.h"
42 #include "math_defs.h"
43 #include "opthelpers.h"
49 static_assert(AL_CHORUS_WAVEFORM_SINUSOID
== AL_FLANGER_WAVEFORM_SINUSOID
, "Chorus/Flanger waveform value mismatch");
50 static_assert(AL_CHORUS_WAVEFORM_TRIANGLE
== AL_FLANGER_WAVEFORM_TRIANGLE
, "Chorus/Flanger waveform value mismatch");
57 #define MAX_UPDATE_SAMPLES 256
59 struct ChorusState final
: public EffectState
{
60 al::vector
<float,16> mSampleBuffer
;
65 float mLfoScale
{0.0f
};
68 /* Gains for left and right sides */
70 float Current
[MAX_OUTPUT_CHANNELS
]{};
71 float Target
[MAX_OUTPUT_CHANNELS
]{};
74 /* effect parameters */
78 float mFeedback
{0.0f
};
80 void getTriangleDelays(ALuint (*delays
)[MAX_UPDATE_SAMPLES
], const size_t todo
);
81 void getSinusoidDelays(ALuint (*delays
)[MAX_UPDATE_SAMPLES
], const size_t todo
);
83 void deviceUpdate(const ALCdevice
*device
) override
;
84 void update(const ALCcontext
*context
, const ALeffectslot
*slot
, const EffectProps
*props
, const EffectTarget target
) override
;
85 void process(const size_t samplesToDo
, const al::span
<const FloatBufferLine
> samplesIn
, const al::span
<FloatBufferLine
> samplesOut
) override
;
87 DEF_NEWDEL(ChorusState
)
90 void ChorusState::deviceUpdate(const ALCdevice
*Device
)
92 constexpr float max_delay
{maxf(AL_CHORUS_MAX_DELAY
, AL_FLANGER_MAX_DELAY
)};
94 const auto frequency
= static_cast<float>(Device
->Frequency
);
95 const size_t maxlen
{NextPowerOf2(float2uint(max_delay
*2.0f
*frequency
) + 1u)};
96 if(maxlen
!= mSampleBuffer
.size())
97 al::vector
<float,16>(maxlen
).swap(mSampleBuffer
);
99 std::fill(mSampleBuffer
.begin(), mSampleBuffer
.end(), 0.0f
);
100 for(auto &e
: mGains
)
102 std::fill(std::begin(e
.Current
), std::end(e
.Current
), 0.0f
);
103 std::fill(std::begin(e
.Target
), std::end(e
.Target
), 0.0f
);
107 void ChorusState::update(const ALCcontext
*Context
, const ALeffectslot
*Slot
, const EffectProps
*props
, const EffectTarget target
)
109 constexpr ALsizei mindelay
{(MAX_RESAMPLER_PADDING
>>1) << FRACTIONBITS
};
111 switch(props
->Chorus
.Waveform
)
113 case AL_CHORUS_WAVEFORM_TRIANGLE
:
114 mWaveform
= WaveForm::Triangle
;
116 case AL_CHORUS_WAVEFORM_SINUSOID
:
117 mWaveform
= WaveForm::Sinusoid
;
121 /* The LFO depth is scaled to be relative to the sample delay. Clamp the
122 * delay and depth to allow enough padding for resampling.
124 const ALCdevice
*device
{Context
->mDevice
.get()};
125 const auto frequency
= static_cast<float>(device
->Frequency
);
127 mDelay
= maxi(float2int(props
->Chorus
.Delay
*frequency
*FRACTIONONE
+ 0.5f
), mindelay
);
128 mDepth
= minf(props
->Chorus
.Depth
* static_cast<float>(mDelay
),
129 static_cast<float>(mDelay
- mindelay
));
131 mFeedback
= props
->Chorus
.Feedback
;
133 /* Gains for left and right sides */
134 const auto lcoeffs
= CalcDirectionCoeffs({-1.0f
, 0.0f
, 0.0f
}, 0.0f
);
135 const auto rcoeffs
= CalcDirectionCoeffs({ 1.0f
, 0.0f
, 0.0f
}, 0.0f
);
137 mOutTarget
= target
.Main
->Buffer
;
138 ComputePanGains(target
.Main
, lcoeffs
.data(), Slot
->Params
.Gain
, mGains
[0].Target
);
139 ComputePanGains(target
.Main
, rcoeffs
.data(), Slot
->Params
.Gain
, mGains
[1].Target
);
141 float rate
{props
->Chorus
.Rate
};
151 /* Calculate LFO coefficient (number of samples per cycle). Limit the
152 * max range to avoid overflow when calculating the displacement.
154 ALuint lfo_range
{float2uint(minf(frequency
/rate
+ 0.5f
, float{INT_MAX
/360 - 180}))};
156 mLfoOffset
= mLfoOffset
* lfo_range
/ mLfoRange
;
157 mLfoRange
= lfo_range
;
160 case WaveForm::Triangle
:
161 mLfoScale
= 4.0f
/ static_cast<float>(mLfoRange
);
163 case WaveForm::Sinusoid
:
164 mLfoScale
= al::MathDefs
<float>::Tau() / static_cast<float>(mLfoRange
);
168 /* Calculate lfo phase displacement */
169 int phase
{props
->Chorus
.Phase
};
170 if(phase
< 0) phase
= 360 + phase
;
171 mLfoDisp
= (mLfoRange
*static_cast<ALuint
>(phase
) + 180) / 360;
176 void ChorusState::getTriangleDelays(ALuint (*delays
)[MAX_UPDATE_SAMPLES
], const size_t todo
)
178 const ALuint lfo_range
{mLfoRange
};
179 const float lfo_scale
{mLfoScale
};
180 const float depth
{mDepth
};
181 const int delay
{mDelay
};
183 ASSUME(lfo_range
> 0);
186 ALuint offset
{mLfoOffset
};
187 auto gen_lfo
= [&offset
,lfo_range
,lfo_scale
,depth
,delay
]() -> ALuint
189 offset
= (offset
+1)%lfo_range
;
190 const float offset_norm
{static_cast<float>(offset
) * lfo_scale
};
191 return static_cast<ALuint
>(fastf2i((1.0f
-std::abs(2.0f
-offset_norm
)) * depth
) + delay
);
193 std::generate_n(delays
[0], todo
, gen_lfo
);
195 offset
= (mLfoOffset
+mLfoDisp
) % lfo_range
;
196 std::generate_n(delays
[1], todo
, gen_lfo
);
198 mLfoOffset
= static_cast<ALuint
>(mLfoOffset
+todo
) % lfo_range
;
201 void ChorusState::getSinusoidDelays(ALuint (*delays
)[MAX_UPDATE_SAMPLES
], const size_t todo
)
203 const ALuint lfo_range
{mLfoRange
};
204 const float lfo_scale
{mLfoScale
};
205 const float depth
{mDepth
};
206 const int delay
{mDelay
};
208 ASSUME(lfo_range
> 0);
211 ALuint offset
{mLfoOffset
};
212 auto gen_lfo
= [&offset
,lfo_range
,lfo_scale
,depth
,delay
]() -> ALuint
214 offset
= (offset
+1)%lfo_range
;
215 const float offset_norm
{static_cast<float>(offset
) * lfo_scale
};
216 return static_cast<ALuint
>(fastf2i(std::sin(offset_norm
)*depth
) + delay
);
218 std::generate_n(delays
[0], todo
, gen_lfo
);
220 offset
= (mLfoOffset
+mLfoDisp
) % lfo_range
;
221 std::generate_n(delays
[1], todo
, gen_lfo
);
223 mLfoOffset
= static_cast<ALuint
>(mLfoOffset
+todo
) % lfo_range
;
226 void ChorusState::process(const size_t samplesToDo
, const al::span
<const FloatBufferLine
> samplesIn
, const al::span
<FloatBufferLine
> samplesOut
)
228 const size_t bufmask
{mSampleBuffer
.size()-1};
229 const float feedback
{mFeedback
};
230 const ALuint avgdelay
{(static_cast<ALuint
>(mDelay
) + (FRACTIONONE
>>1)) >> FRACTIONBITS
};
231 float *RESTRICT delaybuf
{mSampleBuffer
.data()};
232 ALuint offset
{mOffset
};
234 for(size_t base
{0u};base
< samplesToDo
;)
236 const size_t todo
{minz(MAX_UPDATE_SAMPLES
, samplesToDo
-base
)};
238 ALuint moddelays
[2][MAX_UPDATE_SAMPLES
];
239 if(mWaveform
== WaveForm::Sinusoid
)
240 getSinusoidDelays(moddelays
, todo
);
241 else /*if(mWaveform == WaveForm::Triangle)*/
242 getTriangleDelays(moddelays
, todo
);
244 alignas(16) float temps
[2][MAX_UPDATE_SAMPLES
];
245 for(size_t i
{0u};i
< todo
;++i
)
247 // Feed the buffer's input first (necessary for delays < 1).
248 delaybuf
[offset
&bufmask
] = samplesIn
[0][base
+i
];
250 // Tap for the left output.
251 ALuint delay
{offset
- (moddelays
[0][i
]>>FRACTIONBITS
)};
252 float mu
{static_cast<float>(moddelays
[0][i
]&FRACTIONMASK
) * (1.0f
/FRACTIONONE
)};
253 temps
[0][i
] = cubic(delaybuf
[(delay
+1) & bufmask
], delaybuf
[(delay
) & bufmask
],
254 delaybuf
[(delay
-1) & bufmask
], delaybuf
[(delay
-2) & bufmask
], mu
);
256 // Tap for the right output.
257 delay
= offset
- (moddelays
[1][i
]>>FRACTIONBITS
);
258 mu
= static_cast<float>(moddelays
[1][i
]&FRACTIONMASK
) * (1.0f
/FRACTIONONE
);
259 temps
[1][i
] = cubic(delaybuf
[(delay
+1) & bufmask
], delaybuf
[(delay
) & bufmask
],
260 delaybuf
[(delay
-1) & bufmask
], delaybuf
[(delay
-2) & bufmask
], mu
);
262 // Accumulate feedback from the average delay of the taps.
263 delaybuf
[offset
&bufmask
] += delaybuf
[(offset
-avgdelay
) & bufmask
] * feedback
;
267 for(ALsizei c
{0};c
< 2;++c
)
268 MixSamples({temps
[c
], todo
}, samplesOut
, mGains
[c
].Current
, mGains
[c
].Target
,
269 samplesToDo
-base
, base
);
278 void Chorus_setParami(EffectProps
*props
, ALenum param
, int val
)
282 case AL_CHORUS_WAVEFORM
:
283 if(!(val
>= AL_CHORUS_MIN_WAVEFORM
&& val
<= AL_CHORUS_MAX_WAVEFORM
))
284 throw effect_exception
{AL_INVALID_VALUE
, "Invalid chorus waveform"};
285 props
->Chorus
.Waveform
= val
;
288 case AL_CHORUS_PHASE
:
289 if(!(val
>= AL_CHORUS_MIN_PHASE
&& val
<= AL_CHORUS_MAX_PHASE
))
290 throw effect_exception
{AL_INVALID_VALUE
, "Chorus phase out of range"};
291 props
->Chorus
.Phase
= val
;
295 throw effect_exception
{AL_INVALID_ENUM
, "Invalid chorus integer property 0x%04x", param
};
298 void Chorus_setParamiv(EffectProps
*props
, ALenum param
, const int *vals
)
299 { Chorus_setParami(props
, param
, vals
[0]); }
300 void Chorus_setParamf(EffectProps
*props
, ALenum param
, float val
)
305 if(!(val
>= AL_CHORUS_MIN_RATE
&& val
<= AL_CHORUS_MAX_RATE
))
306 throw effect_exception
{AL_INVALID_VALUE
, "Chorus rate out of range"};
307 props
->Chorus
.Rate
= val
;
310 case AL_CHORUS_DEPTH
:
311 if(!(val
>= AL_CHORUS_MIN_DEPTH
&& val
<= AL_CHORUS_MAX_DEPTH
))
312 throw effect_exception
{AL_INVALID_VALUE
, "Chorus depth out of range"};
313 props
->Chorus
.Depth
= val
;
316 case AL_CHORUS_FEEDBACK
:
317 if(!(val
>= AL_CHORUS_MIN_FEEDBACK
&& val
<= AL_CHORUS_MAX_FEEDBACK
))
318 throw effect_exception
{AL_INVALID_VALUE
, "Chorus feedback out of range"};
319 props
->Chorus
.Feedback
= val
;
322 case AL_CHORUS_DELAY
:
323 if(!(val
>= AL_CHORUS_MIN_DELAY
&& val
<= AL_CHORUS_MAX_DELAY
))
324 throw effect_exception
{AL_INVALID_VALUE
, "Chorus delay out of range"};
325 props
->Chorus
.Delay
= val
;
329 throw effect_exception
{AL_INVALID_ENUM
, "Invalid chorus float property 0x%04x", param
};
332 void Chorus_setParamfv(EffectProps
*props
, ALenum param
, const float *vals
)
333 { Chorus_setParamf(props
, param
, vals
[0]); }
335 void Chorus_getParami(const EffectProps
*props
, ALenum param
, int *val
)
339 case AL_CHORUS_WAVEFORM
:
340 *val
= props
->Chorus
.Waveform
;
343 case AL_CHORUS_PHASE
:
344 *val
= props
->Chorus
.Phase
;
348 throw effect_exception
{AL_INVALID_ENUM
, "Invalid chorus integer property 0x%04x", param
};
351 void Chorus_getParamiv(const EffectProps
*props
, ALenum param
, int *vals
)
352 { Chorus_getParami(props
, param
, vals
); }
353 void Chorus_getParamf(const EffectProps
*props
, ALenum param
, float *val
)
358 *val
= props
->Chorus
.Rate
;
361 case AL_CHORUS_DEPTH
:
362 *val
= props
->Chorus
.Depth
;
365 case AL_CHORUS_FEEDBACK
:
366 *val
= props
->Chorus
.Feedback
;
369 case AL_CHORUS_DELAY
:
370 *val
= props
->Chorus
.Delay
;
374 throw effect_exception
{AL_INVALID_ENUM
, "Invalid chorus float property 0x%04x", param
};
377 void Chorus_getParamfv(const EffectProps
*props
, ALenum param
, float *vals
)
378 { Chorus_getParamf(props
, param
, vals
); }
380 DEFINE_ALEFFECT_VTABLE(Chorus
);
383 struct ChorusStateFactory final
: public EffectStateFactory
{
384 EffectState
*create() override
{ return new ChorusState
{}; }
385 EffectProps
getDefaultProps() const noexcept override
;
386 const EffectVtable
*getEffectVtable() const noexcept override
{ return &Chorus_vtable
; }
389 EffectProps
ChorusStateFactory::getDefaultProps() const noexcept
392 props
.Chorus
.Waveform
= AL_CHORUS_DEFAULT_WAVEFORM
;
393 props
.Chorus
.Phase
= AL_CHORUS_DEFAULT_PHASE
;
394 props
.Chorus
.Rate
= AL_CHORUS_DEFAULT_RATE
;
395 props
.Chorus
.Depth
= AL_CHORUS_DEFAULT_DEPTH
;
396 props
.Chorus
.Feedback
= AL_CHORUS_DEFAULT_FEEDBACK
;
397 props
.Chorus
.Delay
= AL_CHORUS_DEFAULT_DELAY
;
402 void Flanger_setParami(EffectProps
*props
, ALenum param
, int val
)
406 case AL_FLANGER_WAVEFORM
:
407 if(!(val
>= AL_FLANGER_MIN_WAVEFORM
&& val
<= AL_FLANGER_MAX_WAVEFORM
))
408 throw effect_exception
{AL_INVALID_VALUE
, "Invalid flanger waveform"};
409 props
->Chorus
.Waveform
= val
;
412 case AL_FLANGER_PHASE
:
413 if(!(val
>= AL_FLANGER_MIN_PHASE
&& val
<= AL_FLANGER_MAX_PHASE
))
414 throw effect_exception
{AL_INVALID_VALUE
, "Flanger phase out of range"};
415 props
->Chorus
.Phase
= val
;
419 throw effect_exception
{AL_INVALID_ENUM
, "Invalid flanger integer property 0x%04x", param
};
422 void Flanger_setParamiv(EffectProps
*props
, ALenum param
, const int *vals
)
423 { Flanger_setParami(props
, param
, vals
[0]); }
424 void Flanger_setParamf(EffectProps
*props
, ALenum param
, float val
)
428 case AL_FLANGER_RATE
:
429 if(!(val
>= AL_FLANGER_MIN_RATE
&& val
<= AL_FLANGER_MAX_RATE
))
430 throw effect_exception
{AL_INVALID_VALUE
, "Flanger rate out of range"};
431 props
->Chorus
.Rate
= val
;
434 case AL_FLANGER_DEPTH
:
435 if(!(val
>= AL_FLANGER_MIN_DEPTH
&& val
<= AL_FLANGER_MAX_DEPTH
))
436 throw effect_exception
{AL_INVALID_VALUE
, "Flanger depth out of range"};
437 props
->Chorus
.Depth
= val
;
440 case AL_FLANGER_FEEDBACK
:
441 if(!(val
>= AL_FLANGER_MIN_FEEDBACK
&& val
<= AL_FLANGER_MAX_FEEDBACK
))
442 throw effect_exception
{AL_INVALID_VALUE
, "Flanger feedback out of range"};
443 props
->Chorus
.Feedback
= val
;
446 case AL_FLANGER_DELAY
:
447 if(!(val
>= AL_FLANGER_MIN_DELAY
&& val
<= AL_FLANGER_MAX_DELAY
))
448 throw effect_exception
{AL_INVALID_VALUE
, "Flanger delay out of range"};
449 props
->Chorus
.Delay
= val
;
453 throw effect_exception
{AL_INVALID_ENUM
, "Invalid flanger float property 0x%04x", param
};
456 void Flanger_setParamfv(EffectProps
*props
, ALenum param
, const float *vals
)
457 { Flanger_setParamf(props
, param
, vals
[0]); }
459 void Flanger_getParami(const EffectProps
*props
, ALenum param
, int *val
)
463 case AL_FLANGER_WAVEFORM
:
464 *val
= props
->Chorus
.Waveform
;
467 case AL_FLANGER_PHASE
:
468 *val
= props
->Chorus
.Phase
;
472 throw effect_exception
{AL_INVALID_ENUM
, "Invalid flanger integer property 0x%04x", param
};
475 void Flanger_getParamiv(const EffectProps
*props
, ALenum param
, int *vals
)
476 { Flanger_getParami(props
, param
, vals
); }
477 void Flanger_getParamf(const EffectProps
*props
, ALenum param
, float *val
)
481 case AL_FLANGER_RATE
:
482 *val
= props
->Chorus
.Rate
;
485 case AL_FLANGER_DEPTH
:
486 *val
= props
->Chorus
.Depth
;
489 case AL_FLANGER_FEEDBACK
:
490 *val
= props
->Chorus
.Feedback
;
493 case AL_FLANGER_DELAY
:
494 *val
= props
->Chorus
.Delay
;
498 throw effect_exception
{AL_INVALID_ENUM
, "Invalid flanger float property 0x%04x", param
};
501 void Flanger_getParamfv(const EffectProps
*props
, ALenum param
, float *vals
)
502 { Flanger_getParamf(props
, param
, vals
); }
504 DEFINE_ALEFFECT_VTABLE(Flanger
);
507 /* Flanger is basically a chorus with a really short delay. They can both use
508 * the same processing functions, so piggyback flanger on the chorus functions.
510 struct FlangerStateFactory final
: public EffectStateFactory
{
511 EffectState
*create() override
{ return new ChorusState
{}; }
512 EffectProps
getDefaultProps() const noexcept override
;
513 const EffectVtable
*getEffectVtable() const noexcept override
{ return &Flanger_vtable
; }
516 EffectProps
FlangerStateFactory::getDefaultProps() const noexcept
519 props
.Chorus
.Waveform
= AL_FLANGER_DEFAULT_WAVEFORM
;
520 props
.Chorus
.Phase
= AL_FLANGER_DEFAULT_PHASE
;
521 props
.Chorus
.Rate
= AL_FLANGER_DEFAULT_RATE
;
522 props
.Chorus
.Depth
= AL_FLANGER_DEFAULT_DEPTH
;
523 props
.Chorus
.Feedback
= AL_FLANGER_DEFAULT_FEEDBACK
;
524 props
.Chorus
.Delay
= AL_FLANGER_DEFAULT_DELAY
;
530 EffectStateFactory
*ChorusStateFactory_getFactory()
532 static ChorusStateFactory ChorusFactory
{};
533 return &ChorusFactory
;
536 EffectStateFactory
*FlangerStateFactory_getFactory()
538 static FlangerStateFactory FlangerFactory
{};
539 return &FlangerFactory
;