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
31 #include "alc/effects/base.h"
32 #include "alnumbers.h"
33 #include "alnumeric.h"
35 #include "core/ambidefs.h"
36 #include "core/bufferline.h"
37 #include "core/context.h"
38 #include "core/cubic_tables.h"
39 #include "core/device.h"
40 #include "core/effects/base.h"
41 #include "core/effectslot.h"
42 #include "core/mixer.h"
43 #include "core/mixer/defs.h"
44 #include "core/resampler_limits.h"
45 #include "intrusive_ptr.h"
46 #include "opthelpers.h"
52 using uint
= unsigned int;
54 constexpr auto inv_sqrt2
= static_cast<float>(1.0 / al::numbers::sqrt2
);
55 constexpr auto lcoeffs_pw
= CalcDirectionCoeffs(std::array
{-1.0f
, 0.0f
, 0.0f
});
56 constexpr auto rcoeffs_pw
= CalcDirectionCoeffs(std::array
{ 1.0f
, 0.0f
, 0.0f
});
57 constexpr auto lcoeffs_nrml
= CalcDirectionCoeffs(std::array
{-inv_sqrt2
, 0.0f
, inv_sqrt2
});
58 constexpr auto rcoeffs_nrml
= CalcDirectionCoeffs(std::array
{ inv_sqrt2
, 0.0f
, inv_sqrt2
});
61 struct ChorusState final
: public EffectState
{
62 std::vector
<float> mDelayBuffer
;
67 float mLfoScale
{0.0f
};
70 /* Calculated delays to apply to the left and right outputs. */
71 std::array
<std::array
<uint
,BufferLineSize
>,2> mModDelays
{};
73 /* Temp storage for the modulated left and right outputs. */
74 alignas(16) std::array
<FloatBufferLine
,2> mBuffer
{};
76 /* Gains for left and right outputs. */
78 std::array
<float,MaxAmbiChannels
> Current
{};
79 std::array
<float,MaxAmbiChannels
> Target
{};
81 std::array
<OutGains
,2> mGains
;
83 /* effect parameters */
84 ChorusWaveform mWaveform
{};
87 float mFeedback
{0.0f
};
89 void calcTriangleDelays(const size_t todo
);
90 void calcSinusoidDelays(const size_t todo
);
92 void deviceUpdate(const DeviceBase
*device
, const float MaxDelay
);
93 void update(const ContextBase
*context
, const EffectSlot
*slot
, const ChorusWaveform waveform
,
94 const float delay
, const float depth
, const float feedback
, const float rate
,
95 int phase
, const EffectTarget target
);
97 void deviceUpdate(const DeviceBase
*device
, const BufferStorage
*) final
;
98 void update(const ContextBase
*context
, const EffectSlot
*slot
, const EffectProps
*props_
,
99 const EffectTarget target
) final
;
100 void process(const size_t samplesToDo
, const al::span
<const FloatBufferLine
> samplesIn
,
101 const al::span
<FloatBufferLine
> samplesOut
) final
;
105 void ChorusState::deviceUpdate(const DeviceBase
*Device
, const BufferStorage
*)
107 constexpr auto MaxDelay
= std::max(ChorusMaxDelay
, FlangerMaxDelay
);
108 const auto frequency
= static_cast<float>(Device
->Frequency
);
109 const size_t maxlen
{NextPowerOf2(float2uint(MaxDelay
*2.0f
*frequency
) + 1u)};
110 if(maxlen
!= mDelayBuffer
.size())
111 decltype(mDelayBuffer
)(maxlen
).swap(mDelayBuffer
);
113 std::fill(mDelayBuffer
.begin(), mDelayBuffer
.end(), 0.0f
);
114 for(auto &e
: mGains
)
116 e
.Current
.fill(0.0f
);
121 void ChorusState::update(const ContextBase
*context
, const EffectSlot
*slot
,
122 const EffectProps
*props_
, const EffectTarget target
)
124 static constexpr int mindelay
{MaxResamplerEdge
<< gCubicTable
.sTableBits
};
125 auto &props
= std::get
<ChorusProps
>(*props_
);
127 /* The LFO depth is scaled to be relative to the sample delay. Clamp the
128 * delay and depth to allow enough padding for resampling.
130 const DeviceBase
*device
{context
->mDevice
};
131 const auto frequency
= static_cast<float>(device
->Frequency
);
133 mWaveform
= props
.Waveform
;
135 mDelay
= std::max(float2int(std::round(props
.Delay
*frequency
*gCubicTable
.sTableSteps
)), mindelay
);
136 mDepth
= std::min(static_cast<float>(mDelay
)*props
.Depth
, static_cast<float>(mDelay
-mindelay
));
138 mFeedback
= props
.Feedback
;
140 /* Gains for left and right sides */
141 const bool ispairwise
{device
->mRenderMode
== RenderMode::Pairwise
};
142 const auto lcoeffs
= (!ispairwise
) ? al::span
{lcoeffs_nrml
} : al::span
{lcoeffs_pw
};
143 const auto rcoeffs
= (!ispairwise
) ? al::span
{rcoeffs_nrml
} : al::span
{rcoeffs_pw
};
145 mOutTarget
= target
.Main
->Buffer
;
146 ComputePanGains(target
.Main
, lcoeffs
, slot
->Gain
, mGains
[0].Target
);
147 ComputePanGains(target
.Main
, rcoeffs
, slot
->Gain
, mGains
[1].Target
);
149 if(!(props
.Rate
> 0.0f
))
158 /* Calculate LFO coefficient (number of samples per cycle). Limit the
159 * max range to avoid overflow when calculating the displacement.
161 static constexpr int range_limit
{std::numeric_limits
<int>::max()/360 - 180};
162 const uint lfo_range
{float2uint(std::min(std::round(frequency
/props
.Rate
), float{range_limit
}))};
164 mLfoOffset
= mLfoOffset
* lfo_range
/ mLfoRange
;
165 mLfoRange
= lfo_range
;
168 case ChorusWaveform::Triangle
:
169 mLfoScale
= 4.0f
/ static_cast<float>(mLfoRange
);
171 case ChorusWaveform::Sinusoid
:
172 mLfoScale
= al::numbers::pi_v
<float>*2.0f
/ static_cast<float>(mLfoRange
);
176 /* Calculate lfo phase displacement */
177 auto phase
= props
.Phase
;
178 if(phase
< 0) phase
+= 360;
179 mLfoDisp
= (mLfoRange
*static_cast<uint
>(phase
) + 180) / 360;
184 void ChorusState::calcTriangleDelays(const size_t todo
)
186 const uint lfo_range
{mLfoRange
};
187 const float lfo_scale
{mLfoScale
};
188 const float depth
{mDepth
};
189 const int delay
{mDelay
};
191 ASSUME(lfo_range
> 0);
194 auto gen_lfo
= [lfo_scale
,depth
,delay
](const uint offset
) -> uint
196 const float offset_norm
{static_cast<float>(offset
) * lfo_scale
};
197 return static_cast<uint
>(fastf2i((1.0f
-std::abs(2.0f
-offset_norm
)) * depth
) + delay
);
200 uint offset
{mLfoOffset
};
201 for(size_t i
{0};i
< todo
;)
203 size_t rem
{std::min(todo
-i
, size_t{lfo_range
-offset
})};
205 mModDelays
[0][i
++] = gen_lfo(offset
++);
207 if(offset
== lfo_range
)
211 offset
= (mLfoOffset
+mLfoDisp
) % lfo_range
;
212 for(size_t i
{0};i
< todo
;)
214 size_t rem
{std::min(todo
-i
, size_t{lfo_range
-offset
})};
216 mModDelays
[1][i
++] = gen_lfo(offset
++);
218 if(offset
== lfo_range
)
222 mLfoOffset
= static_cast<uint
>(mLfoOffset
+todo
) % lfo_range
;
225 void ChorusState::calcSinusoidDelays(const size_t todo
)
227 const uint lfo_range
{mLfoRange
};
228 const float lfo_scale
{mLfoScale
};
229 const float depth
{mDepth
};
230 const int delay
{mDelay
};
232 ASSUME(lfo_range
> 0);
235 auto gen_lfo
= [lfo_scale
,depth
,delay
](const uint offset
) -> uint
237 const float offset_norm
{static_cast<float>(offset
) * lfo_scale
};
238 return static_cast<uint
>(fastf2i(std::sin(offset_norm
)*depth
) + delay
);
241 uint offset
{mLfoOffset
};
242 for(size_t i
{0};i
< todo
;)
244 size_t rem
{std::min(todo
-i
, size_t{lfo_range
-offset
})};
246 mModDelays
[0][i
++] = gen_lfo(offset
++);
248 if(offset
== lfo_range
)
252 offset
= (mLfoOffset
+mLfoDisp
) % lfo_range
;
253 for(size_t i
{0};i
< todo
;)
255 size_t rem
{std::min(todo
-i
, size_t{lfo_range
-offset
})};
257 mModDelays
[1][i
++] = gen_lfo(offset
++);
259 if(offset
== lfo_range
)
263 mLfoOffset
= static_cast<uint
>(mLfoOffset
+todo
) % lfo_range
;
266 void ChorusState::process(const size_t samplesToDo
, const al::span
<const FloatBufferLine
> samplesIn
, const al::span
<FloatBufferLine
> samplesOut
)
268 const auto delaybuf
= al::span
{mDelayBuffer
};
269 const size_t bufmask
{delaybuf
.size()-1};
270 const float feedback
{mFeedback
};
271 const uint avgdelay
{(static_cast<uint
>(mDelay
) + MixerFracHalf
) >> MixerFracBits
};
272 uint offset
{mOffset
};
274 if(mWaveform
== ChorusWaveform::Sinusoid
)
275 calcSinusoidDelays(samplesToDo
);
276 else /*if(mWaveform == ChorusWaveform::Triangle)*/
277 calcTriangleDelays(samplesToDo
);
279 const auto ldelays
= al::span
{mModDelays
[0]};
280 const auto rdelays
= al::span
{mModDelays
[1]};
281 const auto lbuffer
= al::span
{mBuffer
[0]};
282 const auto rbuffer
= al::span
{mBuffer
[1]};
283 for(size_t i
{0u};i
< samplesToDo
;++i
)
285 // Feed the buffer's input first (necessary for delays < 1).
286 delaybuf
[offset
&bufmask
] = samplesIn
[0][i
];
288 // Tap for the left output.
289 size_t delay
{offset
- (ldelays
[i
] >> gCubicTable
.sTableBits
)};
290 size_t phase
{ldelays
[i
] & gCubicTable
.sTableMask
};
291 lbuffer
[i
] = delaybuf
[(delay
+1) & bufmask
]*gCubicTable
.getCoeff0(phase
) +
292 delaybuf
[(delay
) & bufmask
]*gCubicTable
.getCoeff1(phase
) +
293 delaybuf
[(delay
-1) & bufmask
]*gCubicTable
.getCoeff2(phase
) +
294 delaybuf
[(delay
-2) & bufmask
]*gCubicTable
.getCoeff3(phase
);
296 // Tap for the right output.
297 delay
= offset
- (rdelays
[i
] >> gCubicTable
.sTableBits
);
298 phase
= rdelays
[i
] & gCubicTable
.sTableMask
;
299 rbuffer
[i
] = delaybuf
[(delay
+1) & bufmask
]*gCubicTable
.getCoeff0(phase
) +
300 delaybuf
[(delay
) & bufmask
]*gCubicTable
.getCoeff1(phase
) +
301 delaybuf
[(delay
-1) & bufmask
]*gCubicTable
.getCoeff2(phase
) +
302 delaybuf
[(delay
-2) & bufmask
]*gCubicTable
.getCoeff3(phase
);
304 // Accumulate feedback from the average delay of the taps.
305 delaybuf
[offset
&bufmask
] += delaybuf
[(offset
-avgdelay
) & bufmask
] * feedback
;
309 MixSamples(lbuffer
.first(samplesToDo
), samplesOut
, mGains
[0].Current
, mGains
[0].Target
,
311 MixSamples(rbuffer
.first(samplesToDo
), samplesOut
, mGains
[1].Current
, mGains
[1].Target
,
318 struct ChorusStateFactory final
: public EffectStateFactory
{
319 al::intrusive_ptr
<EffectState
> create() override
320 { return al::intrusive_ptr
<EffectState
>{new ChorusState
{}}; }
325 EffectStateFactory
*ChorusStateFactory_getFactory()
327 static ChorusStateFactory ChorusFactory
{};
328 return &ChorusFactory
;