Move some temp variables closer to where they're used
[openal-soft.git] / alc / effects / distortion.cpp
blob74cffd4a172c1f88426deabff157a4054d89a9d2
1 /**
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
21 #include "config.h"
23 #include <algorithm>
24 #include <array>
25 #include <cstdlib>
26 #include <iterator>
28 #include "alc/effects/base.h"
29 #include "almalloc.h"
30 #include "alnumbers.h"
31 #include "alnumeric.h"
32 #include "alspan.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 "core/mixer/defs.h"
41 #include "intrusive_ptr.h"
44 namespace {
46 struct DistortionState final : public EffectState {
47 /* Effect gains for each channel */
48 float mGain[MAX_OUTPUT_CHANNELS]{};
50 /* Effect parameters */
51 BiquadFilter mLowpass;
52 BiquadFilter mBandpass;
53 float mAttenuation{};
54 float mEdgeCoeff{};
56 float mBuffer[2][BufferLineSize]{};
59 void deviceUpdate(const DeviceBase *device, const Buffer &buffer) override;
60 void update(const ContextBase *context, const EffectSlot *slot, const EffectProps *props,
61 const EffectTarget target) override;
62 void process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn,
63 const al::span<FloatBufferLine> samplesOut) override;
65 DEF_NEWDEL(DistortionState)
68 void DistortionState::deviceUpdate(const DeviceBase*, const Buffer&)
70 mLowpass.clear();
71 mBandpass.clear();
74 void DistortionState::update(const ContextBase *context, const EffectSlot *slot,
75 const EffectProps *props, const EffectTarget target)
77 const DeviceBase *device{context->mDevice};
79 /* Store waveshaper edge settings. */
80 const float edge{minf(std::sin(al::numbers::pi_v<float>*0.5f * props->Distortion.Edge),
81 0.99f)};
82 mEdgeCoeff = 2.0f * edge / (1.0f-edge);
84 float cutoff{props->Distortion.LowpassCutoff};
85 /* Bandwidth value is constant in octaves. */
86 float bandwidth{(cutoff / 2.0f) / (cutoff * 0.67f)};
87 /* Divide normalized frequency by the amount of oversampling done during
88 * processing.
90 auto frequency = static_cast<float>(device->Frequency);
91 mLowpass.setParamsFromBandwidth(BiquadType::LowPass, cutoff/frequency/4.0f, 1.0f, bandwidth);
93 cutoff = props->Distortion.EQCenter;
94 /* Convert bandwidth in Hz to octaves. */
95 bandwidth = props->Distortion.EQBandwidth / (cutoff * 0.67f);
96 mBandpass.setParamsFromBandwidth(BiquadType::BandPass, cutoff/frequency/4.0f, 1.0f, bandwidth);
98 const auto coeffs = CalcDirectionCoeffs({0.0f, 0.0f, -1.0f}, 0.0f);
100 mOutTarget = target.Main->Buffer;
101 ComputePanGains(target.Main, coeffs.data(), slot->Gain*props->Distortion.Gain, mGain);
104 void DistortionState::process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut)
106 const float fc{mEdgeCoeff};
107 for(size_t base{0u};base < samplesToDo;)
109 /* Perform 4x oversampling to avoid aliasing. Oversampling greatly
110 * improves distortion quality and allows to implement lowpass and
111 * bandpass filters using high frequencies, at which classic IIR
112 * filters became unstable.
114 size_t todo{minz(BufferLineSize, (samplesToDo-base) * 4)};
116 /* Fill oversample buffer using zero stuffing. Multiply the sample by
117 * the amount of oversampling to maintain the signal's power.
119 for(size_t i{0u};i < todo;i++)
120 mBuffer[0][i] = !(i&3) ? samplesIn[0][(i>>2)+base] * 4.0f : 0.0f;
122 /* First step, do lowpass filtering of original signal. Additionally
123 * perform buffer interpolation and lowpass cutoff for oversampling
124 * (which is fortunately first step of distortion). So combine three
125 * operations into the one.
127 mLowpass.process({mBuffer[0], todo}, mBuffer[1]);
129 /* Second step, do distortion using waveshaper function to emulate
130 * signal processing during tube overdriving. Three steps of
131 * waveshaping are intended to modify waveform without boost/clipping/
132 * attenuation process.
134 auto proc_sample = [fc](float smp) -> float
136 smp = (1.0f + fc) * smp/(1.0f + fc*std::abs(smp));
137 smp = (1.0f + fc) * smp/(1.0f + fc*std::abs(smp)) * -1.0f;
138 smp = (1.0f + fc) * smp/(1.0f + fc*std::abs(smp));
139 return smp;
141 std::transform(std::begin(mBuffer[1]), std::begin(mBuffer[1])+todo, std::begin(mBuffer[0]),
142 proc_sample);
144 /* Third step, do bandpass filtering of distorted signal. */
145 mBandpass.process({mBuffer[0], todo}, mBuffer[1]);
147 todo >>= 2;
148 const float *outgains{mGain};
149 for(FloatBufferLine &output : samplesOut)
151 /* Fourth step, final, do attenuation and perform decimation,
152 * storing only one sample out of four.
154 const float gain{*(outgains++)};
155 if(!(std::fabs(gain) > GainSilenceThreshold))
156 continue;
158 for(size_t i{0u};i < todo;i++)
159 output[base+i] += gain * mBuffer[1][i*4];
162 base += todo;
167 struct DistortionStateFactory final : public EffectStateFactory {
168 al::intrusive_ptr<EffectState> create() override
169 { return al::intrusive_ptr<EffectState>{new DistortionState{}}; }
172 } // namespace
174 EffectStateFactory *DistortionStateFactory_getFactory()
176 static DistortionStateFactory DistortionFactory{};
177 return &DistortionFactory;