2 * OpenAL cross platform audio library
3 * Copyright (C) 2009 by Chris Robinson.
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
29 #include "alc/effects/base.h"
31 #include "alnumeric.h"
33 #include "core/bufferline.h"
34 #include "core/context.h"
35 #include "core/devformat.h"
36 #include "core/device.h"
37 #include "core/effectslot.h"
38 #include "core/filters/biquad.h"
39 #include "core/mixer.h"
40 #include "intrusive_ptr.h"
41 #include "opthelpers.h"
47 using uint
= unsigned int;
49 constexpr float LowpassFreqRef
{5000.0f
};
51 struct EchoState final
: public EffectState
{
52 al::vector
<float,16> mSampleBuffer
;
54 // The echo is two tap. The delay is the number of samples from before the
61 /* The panning gains for the two taps */
63 float Current
[MaxAmbiChannels
]{};
64 float Target
[MaxAmbiChannels
]{};
68 float mFeedGain
{0.0f
};
70 alignas(16) float mTempBuffer
[2][BufferLineSize
];
72 void deviceUpdate(const DeviceBase
*device
, const Buffer
&buffer
) override
;
73 void update(const ContextBase
*context
, const EffectSlot
*slot
, const EffectProps
*props
,
74 const EffectTarget target
) override
;
75 void process(const size_t samplesToDo
, const al::span
<const FloatBufferLine
> samplesIn
,
76 const al::span
<FloatBufferLine
> samplesOut
) override
;
81 void EchoState::deviceUpdate(const DeviceBase
*Device
, const Buffer
&)
83 const auto frequency
= static_cast<float>(Device
->Frequency
);
85 // Use the next power of 2 for the buffer length, so the tap offsets can be
86 // wrapped using a mask instead of a modulo
87 const uint maxlen
{NextPowerOf2(float2uint(EchoMaxDelay
*frequency
+ 0.5f
) +
88 float2uint(EchoMaxLRDelay
*frequency
+ 0.5f
))};
89 if(maxlen
!= mSampleBuffer
.size())
90 al::vector
<float,16>(maxlen
).swap(mSampleBuffer
);
92 std::fill(mSampleBuffer
.begin(), mSampleBuffer
.end(), 0.0f
);
95 std::fill(std::begin(e
.Current
), std::end(e
.Current
), 0.0f
);
96 std::fill(std::begin(e
.Target
), std::end(e
.Target
), 0.0f
);
100 void EchoState::update(const ContextBase
*context
, const EffectSlot
*slot
,
101 const EffectProps
*props
, const EffectTarget target
)
103 const DeviceBase
*device
{context
->mDevice
};
104 const auto frequency
= static_cast<float>(device
->Frequency
);
106 mTap
[0].delay
= maxu(float2uint(props
->Echo
.Delay
*frequency
+ 0.5f
), 1);
107 mTap
[1].delay
= float2uint(props
->Echo
.LRDelay
*frequency
+ 0.5f
) + mTap
[0].delay
;
109 const float gainhf
{maxf(1.0f
- props
->Echo
.Damping
, 0.0625f
)}; /* Limit -24dB */
110 mFilter
.setParamsFromSlope(BiquadType::HighShelf
, LowpassFreqRef
/frequency
, gainhf
, 1.0f
);
112 mFeedGain
= props
->Echo
.Feedback
;
114 /* Convert echo spread (where 0 = center, +/-1 = sides) to angle. */
115 const float angle
{std::asin(props
->Echo
.Spread
)};
117 const auto coeffs0
= CalcAngleCoeffs(-angle
, 0.0f
, 0.0f
);
118 const auto coeffs1
= CalcAngleCoeffs( angle
, 0.0f
, 0.0f
);
120 mOutTarget
= target
.Main
->Buffer
;
121 ComputePanGains(target
.Main
, coeffs0
.data(), slot
->Gain
, mGains
[0].Target
);
122 ComputePanGains(target
.Main
, coeffs1
.data(), slot
->Gain
, mGains
[1].Target
);
125 void EchoState::process(const size_t samplesToDo
, const al::span
<const FloatBufferLine
> samplesIn
, const al::span
<FloatBufferLine
> samplesOut
)
127 const size_t mask
{mSampleBuffer
.size()-1};
128 float *RESTRICT delaybuf
{mSampleBuffer
.data()};
129 size_t offset
{mOffset
};
130 size_t tap1
{offset
- mTap
[0].delay
};
131 size_t tap2
{offset
- mTap
[1].delay
};
134 ASSUME(samplesToDo
> 0);
136 const BiquadFilter filter
{mFilter
};
137 std::tie(z1
, z2
) = mFilter
.getComponents();
138 for(size_t i
{0u};i
< samplesToDo
;)
144 size_t td
{minz(mask
+1 - maxz(offset
, maxz(tap1
, tap2
)), samplesToDo
-i
)};
146 /* Feed the delay buffer's input first. */
147 delaybuf
[offset
] = samplesIn
[0][i
];
149 /* Get delayed output from the first and second taps. Use the
150 * second tap for feedback.
152 mTempBuffer
[0][i
] = delaybuf
[tap1
++];
153 mTempBuffer
[1][i
] = delaybuf
[tap2
++];
154 const float feedb
{mTempBuffer
[1][i
++]};
156 /* Add feedback to the delay buffer with damping and attenuation. */
157 delaybuf
[offset
++] += filter
.processOne(feedb
, z1
, z2
) * mFeedGain
;
160 mFilter
.setComponents(z1
, z2
);
163 for(size_t c
{0};c
< 2;c
++)
164 MixSamples({mTempBuffer
[c
], samplesToDo
}, samplesOut
, mGains
[c
].Current
, mGains
[c
].Target
,
169 struct EchoStateFactory final
: public EffectStateFactory
{
170 al::intrusive_ptr
<EffectState
> create() override
171 { return al::intrusive_ptr
<EffectState
>{new EchoState
{}}; }
176 EffectStateFactory
*EchoStateFactory_getFactory()
178 static EchoStateFactory EchoFactory
{};