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
30 #include "alc/effects/base.h"
31 #include "alnumeric.h"
33 #include "core/ambidefs.h"
34 #include "core/bufferline.h"
35 #include "core/context.h"
36 #include "core/device.h"
37 #include "core/effects/base.h"
38 #include "core/effectslot.h"
39 #include "core/filters/biquad.h"
40 #include "core/mixer.h"
41 #include "intrusive_ptr.h"
42 #include "opthelpers.h"
48 using uint
= unsigned int;
50 constexpr float LowpassFreqRef
{5000.0f
};
52 struct EchoState final
: public EffectState
{
53 std::vector
<float> mSampleBuffer
;
55 // The echo is two tap. The delay is the number of samples from before the
57 std::array
<size_t,2> mDelayTap
{};
60 /* The panning gains for the two taps */
62 std::array
<float,MaxAmbiChannels
> Current
{};
63 std::array
<float,MaxAmbiChannels
> Target
{};
65 std::array
<OutGains
,2> mGains
;
68 float mFeedGain
{0.0f
};
70 alignas(16) std::array
<FloatBufferLine
,2> mTempBuffer
{};
72 void deviceUpdate(const DeviceBase
*device
, const BufferStorage
*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
;
79 void EchoState::deviceUpdate(const DeviceBase
*Device
, const BufferStorage
*)
81 const auto frequency
= static_cast<float>(Device
->Frequency
);
83 // Use the next power of 2 for the buffer length, so the tap offsets can be
84 // wrapped using a mask instead of a modulo
85 const uint maxlen
{NextPowerOf2(float2uint(EchoMaxDelay
*frequency
+ 0.5f
) +
86 float2uint(EchoMaxLRDelay
*frequency
+ 0.5f
))};
87 if(maxlen
!= mSampleBuffer
.size())
88 decltype(mSampleBuffer
)(maxlen
).swap(mSampleBuffer
);
90 std::fill(mSampleBuffer
.begin(), mSampleBuffer
.end(), 0.0f
);
93 std::fill(e
.Current
.begin(), e
.Current
.end(), 0.0f
);
94 std::fill(e
.Target
.begin(), e
.Target
.end(), 0.0f
);
98 void EchoState::update(const ContextBase
*context
, const EffectSlot
*slot
,
99 const EffectProps
*props_
, const EffectTarget target
)
101 auto &props
= std::get
<EchoProps
>(*props_
);
102 const DeviceBase
*device
{context
->mDevice
};
103 const auto frequency
= static_cast<float>(device
->Frequency
);
105 mDelayTap
[0] = std::max(float2uint(std::round(props
.Delay
*frequency
)), 1u);
106 mDelayTap
[1] = float2uint(std::round(props
.LRDelay
*frequency
)) + mDelayTap
[0];
108 const float gainhf
{std::max(1.0f
- props
.Damping
, 0.0625f
)}; /* Limit -24dB */
109 mFilter
.setParamsFromSlope(BiquadType::HighShelf
, LowpassFreqRef
/frequency
, gainhf
, 1.0f
);
111 mFeedGain
= props
.Feedback
;
113 /* Convert echo spread (where 0 = center, +/-1 = sides) to a 2D vector. */
114 const float x
{props
.Spread
}; /* +x = left */
115 const float z
{std::sqrt(1.0f
- x
*x
)};
117 const auto coeffs0
= CalcAmbiCoeffs( x
, 0.0f
, z
, 0.0f
);
118 const auto coeffs1
= CalcAmbiCoeffs(-x
, 0.0f
, z
, 0.0f
);
120 mOutTarget
= target
.Main
->Buffer
;
121 ComputePanGains(target
.Main
, coeffs0
, slot
->Gain
, mGains
[0].Target
);
122 ComputePanGains(target
.Main
, coeffs1
, 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 auto delaybuf
= al::span
{mSampleBuffer
};
128 const size_t mask
{delaybuf
.size()-1};
129 size_t offset
{mOffset
};
130 size_t tap1
{offset
- mDelayTap
[0]};
131 size_t tap2
{offset
- mDelayTap
[1]};
133 ASSUME(samplesToDo
> 0);
135 const BiquadFilter filter
{mFilter
};
136 auto [z1
, z2
] = mFilter
.getComponents();
137 for(size_t i
{0u};i
< samplesToDo
;)
143 size_t td
{std::min(mask
+1 - std::max(offset
, std::max(tap1
, tap2
)), samplesToDo
-i
)};
145 /* Feed the delay buffer's input first. */
146 delaybuf
[offset
] = samplesIn
[0][i
];
148 /* Get delayed output from the first and second taps. Use the
149 * second tap for feedback.
151 mTempBuffer
[0][i
] = delaybuf
[tap1
++];
152 mTempBuffer
[1][i
] = delaybuf
[tap2
++];
153 const float feedb
{mTempBuffer
[1][i
++]};
155 /* Add feedback to the delay buffer with damping and attenuation. */
156 delaybuf
[offset
++] += filter
.processOne(feedb
, z1
, z2
) * mFeedGain
;
159 mFilter
.setComponents(z1
, z2
);
162 for(size_t c
{0};c
< 2;c
++)
163 MixSamples(al::span
{mTempBuffer
[c
]}.first(samplesToDo
), samplesOut
, mGains
[c
].Current
,
164 mGains
[c
].Target
, samplesToDo
, 0);
168 struct EchoStateFactory final
: public EffectStateFactory
{
169 al::intrusive_ptr
<EffectState
> create() override
170 { return al::intrusive_ptr
<EffectState
>{new EchoState
{}}; }
175 EffectStateFactory
*EchoStateFactory_getFactory()
177 static EchoStateFactory EchoFactory
{};