tracktion-engine 3.0-10-g034fdde4aa5
Tracktion Engine — High level data model for audio applications

« « « Anklang Documentation
Loading...
Searching...
No Matches
tracktion_Equaliser.cpp
Go to the documentation of this file.
1 /*
2 ,--. ,--. ,--. ,--.
3 ,-' '-.,--.--.,--,--.,---.| |,-.,-' '-.`--' ,---. ,--,--, Copyright 2024
4 '-. .-'| .--' ,-. | .--'| /'-. .-',--.| .-. || \ Tracktion Software
5 | | | | \ '-' \ `--.| \ \ | | | |' '-' '| || | Corporation
6 `---' `--' `--`--'`---'`--'`--' `---' `--' `---' `--''--' www.tracktion.com
7
8 Tracktion Engine uses a GPL/commercial licence - see LICENCE.md for details.
9*/
10
11namespace tracktion { inline namespace engine
12{
13
15{
16public:
18 Plugin& owner, juce::Range<float> valueRangeToUse, int paramNumberToUse,
19 bool isGain_, bool isFreq_, bool isQ_)
20 : AutomatableParameter (xmlTag, name, owner, valueRangeToUse),
21 equaliser (p), paramNumber (paramNumberToUse),
22 isGain (isGain_), isFreq (isFreq_), isQ (isQ_)
23 {
24 }
25
27 {
28 notifyListenersOfDeletion();
29 }
30
31 juce::String valueToString (float value) override
32 {
33 if (isFreq)
34 return juce::String (juce::roundToInt (value)) + " Hz";
35
36 if (isGain)
37 return juce::Decibels::toString (value);
38
39 return juce::String (value, 3);
40 }
41
42 float stringToValue (const juce::String& str) override
43 {
44 if (isFreq)
45 return str.retainCharacters ("0123456789").getFloatValue();
46
47 if (isGain)
48 return dbStringToDb (str);
49
50 return str.getFloatValue();
51 }
52
53 void parameterChanged (float, bool /*byAutomation*/) override
54 {
55 equaliser.needToUpdateFilters[paramNumber] = true;
56 }
57
58 EqualiserPlugin& equaliser;
59 int paramNumber;
60 bool isGain, isFreq, isQ;
61};
62
63//==============================================================================
64const char* EqualiserPlugin::xmlTypeName = "4bandEq";
65
66EqualiserPlugin::EqualiserPlugin (PluginCreationInfo info) : Plugin (info)
67{
68 for (int i = 4; --i >= 0;)
69 needToUpdateFilters[i] = true;
70
71 addAutomatableParameter (loFreq = new EQAutomatableParameter (*this, "Low-pass freq", TRANS("Low-shelf freq"), *this, { minFreq, maxFreq }, 0, false, true, false));
72 addAutomatableParameter (loGain = new EQAutomatableParameter (*this, "Low-pass gain", TRANS("Low-shelf gain"), *this, { minGain, maxGain }, 0, true, false, false));
73 addAutomatableParameter (loQ = new EQAutomatableParameter (*this, "Low-pass Q", TRANS("Low-shelf Q"), *this, { minQ, maxQ }, 0, false, false, true));
74
75 addAutomatableParameter (midFreq1 = new EQAutomatableParameter (*this, "Mid freq 1", TRANS("Mid freq 1"), *this, { minFreq, maxFreq }, 1, false, true, false));
76 addAutomatableParameter (midGain1 = new EQAutomatableParameter (*this, "Mid gain 1", TRANS("Mid gain 1"), *this, { minGain, maxGain }, 1, true, false, false));
77 addAutomatableParameter (midQ1 = new EQAutomatableParameter (*this, "Mid Q 1", TRANS("Mid Q 1"), *this, { minQ, maxQ }, 1, false, false, true));
78
79 addAutomatableParameter (midFreq2 = new EQAutomatableParameter (*this, "Mid freq 2", TRANS("Mid freq 2"), *this, { minFreq, maxFreq }, 2, false, true, false));
80 addAutomatableParameter (midGain2 = new EQAutomatableParameter (*this, "Mid gain 2", TRANS("Mid gain 2"), *this, { minGain, maxGain }, 2, true, false, false));
81 addAutomatableParameter (midQ2 = new EQAutomatableParameter (*this, "Mid Q 2", TRANS("Mid Q 2"), *this, { minQ, maxQ }, 2, false, false, true));
82
83 addAutomatableParameter (hiFreq = new EQAutomatableParameter (*this, "High-pass freq", TRANS("High-shelf freq"), *this, { minFreq, maxFreq }, 3, false, true, false));
84 addAutomatableParameter (hiGain = new EQAutomatableParameter (*this, "High-pass gain", TRANS("High-shelf gain"), *this, { minGain, maxGain }, 3, true, false, false));
85 addAutomatableParameter (hiQ = new EQAutomatableParameter (*this, "High-pass Q", TRANS("High-shelf Q"), *this, { minQ, maxQ }, 3, false, false, true));
86
87 auto um = getUndoManager();
88
89 loFreqValue.referTo (state, IDs::loFreq, um, 80.0f);
90 loGainValue.referTo (state, IDs::loGain, um);
91 loQValue.referTo (state, IDs::loQ, um, 0.5f);
92
93 hiFreqValue.referTo (state, IDs::hiFreq, um, 17000.0f);
94 hiGainValue.referTo (state, IDs::hiGain, um);
95 hiQValue.referTo (state, IDs::hiQ, um, 0.5f);
96
97 midFreqValue1.referTo (state, IDs::midFreq1, um, 3000.0f);
98 midGainValue1.referTo (state, IDs::midGain1, um);
99 midQValue1.referTo (state, IDs::midQ1, um, 0.5f);
100
101 midFreqValue2.referTo (state, IDs::midFreq2, um, 5000.0f);
102 midGainValue2.referTo (state, IDs::midGain2, um);
103 midQValue2.referTo (state, IDs::midQ2, um, 0.5f);
104
105 phaseInvert.referTo (state, IDs::phaseInvert, um);
106
107 loFreq->attachToCurrentValue (loFreqValue);
108 loGain->attachToCurrentValue (loGainValue);
109 loQ->attachToCurrentValue (loQValue);
110
111 midFreq1->attachToCurrentValue (midFreqValue1);
112 midGain1->attachToCurrentValue (midGainValue1);
113 midQ1->attachToCurrentValue (midQValue1);
114
115 midFreq2->attachToCurrentValue (midFreqValue2);
116 midGain2->attachToCurrentValue (midGainValue2);
117 midQ2->attachToCurrentValue (midQValue2);
118
119 hiFreq->attachToCurrentValue (hiFreqValue);
120 hiGain->attachToCurrentValue (hiGainValue);
121 hiQ->attachToCurrentValue (hiQValue);
122}
123
124EqualiserPlugin::~EqualiserPlugin()
125{
126 notifyListenersOfDeletion();
127
128 loFreq->detachFromCurrentValue();
129 loGain->detachFromCurrentValue();
130 loQ->detachFromCurrentValue();
131
132 midFreq1->detachFromCurrentValue();
133 midGain1->detachFromCurrentValue();
134 midQ1->detachFromCurrentValue();
135
136 midFreq2->detachFromCurrentValue();
137 midGain2->detachFromCurrentValue();
138 midQ2->detachFromCurrentValue();
139
140 hiFreq->detachFromCurrentValue();
141 hiGain->detachFromCurrentValue();
142 hiQ->detachFromCurrentValue();
143}
144
146{
147 return getName() + "$equaliserplugin";
148}
149
150void EqualiserPlugin::resetToDefault()
151{
152 loGain->setParameter (0.0f, juce::dontSendNotification);
153 loFreq->setParameter (80.0f, juce::dontSendNotification);
154 loQ ->setParameter (0.5f, juce::dontSendNotification);
155
156 hiGain->setParameter (0.0f, juce::dontSendNotification);
157 hiFreq->setParameter (17000.0f, juce::dontSendNotification);
158 hiQ ->setParameter (0.5f, juce::dontSendNotification);
159
160 midGain1->setParameter (0.0f, juce::dontSendNotification);
161 midFreq1->setParameter (3000.0f, juce::dontSendNotification);
162 midQ1 ->setParameter (0.5f, juce::dontSendNotification);
163
164 midGain2->setParameter (0.0f, juce::dontSendNotification);
165 midFreq2->setParameter (5000.0f, juce::dontSendNotification);
166 midQ2 ->setParameter (0.5f, juce::dontSendNotification);
167
168 curveNeedsUpdating = true;
169}
170
171void EqualiserPlugin::restorePluginStateFromValueTree (const juce::ValueTree& v)
172{
173 copyPropertiesToCachedValues (v, loFreqValue, loGainValue, loQValue, hiFreqValue, hiGainValue,
174 hiQValue, midFreqValue1, midGainValue1, midQValue1, midFreqValue2,
175 midGainValue2, midQValue2, phaseInvert);
176
177 for (auto p : getAutomatableParameters())
178 p->updateFromAttachedValue();
179}
180
181void EqualiserPlugin::valueTreePropertyChanged (juce::ValueTree& parent, const juce::Identifier& prop)
182{
183 curveNeedsUpdating = true;
184 Plugin::valueTreePropertyChanged (parent, prop);
185}
186
187static float convertEQLevelToGain (double db)
188{
189 return (float) pow (10.0, db / 20.0);
190}
191
192void EqualiserPlugin::updateIIRFilters()
193{
194 const juce::ScopedLock sl (filterLock);
195
196 if (needToUpdateFilters[0])
197 {
198 needToUpdateFilters[0] = false;
199
200 auto c = juce::IIRCoefficients::makeLowShelf (lastSampleRate, loFreq->getCurrentValue(), loQ->getCurrentValue(),
201 convertEQLevelToGain (loGain->getCurrentValue()));
202
203 for (int i = EQ_CHANS; --i >= 0;)
204 low[i].setCoefficients (c);
205 }
206
207 if (needToUpdateFilters[1])
208 {
209 needToUpdateFilters[1] = false;
210
211 auto c = juce::IIRCoefficients::makePeakFilter (lastSampleRate, midFreq1->getCurrentValue(), midQ1->getCurrentValue(),
212 convertEQLevelToGain (midGain1->getCurrentValue()));
213
214 for (int i = EQ_CHANS; --i >= 0;)
215 mid1[i].setCoefficients (c);
216 }
217
218 if (needToUpdateFilters[2])
219 {
220 needToUpdateFilters[2] = false;
221
222 auto c = juce::IIRCoefficients::makePeakFilter (lastSampleRate, midFreq2->getCurrentValue(), midQ2->getCurrentValue(),
223 convertEQLevelToGain (midGain2->getCurrentValue()));
224
225 for (int i = EQ_CHANS; --i >= 0;)
226 mid2[i].setCoefficients (c);
227 }
228
229 if (needToUpdateFilters[3])
230 {
231 needToUpdateFilters[3] = false;
232
233 auto c = juce::IIRCoefficients::makeHighShelf (lastSampleRate, hiFreq->getCurrentValue(), hiQ->getCurrentValue(),
234 convertEQLevelToGain (hiGain->getCurrentValue()));
235
236 for (int i = EQ_CHANS; --i >= 0;)
237 high[i].setCoefficients (c);
238 }
239}
240
241void EqualiserPlugin::initialise (const PluginInitialisationInfo&)
242{
243 for (int i = EQ_CHANS; --i >= 0;)
244 {
245 low[i].reset();
246 mid1[i].reset();
247 mid2[i].reset();
248 high[i].reset();
249 }
250
251 if (lastSampleRate != sampleRate)
252 curveNeedsUpdating = true;
253
254 lastSampleRate = (float)sampleRate;
255
256 for (int i = 4; --i >= 0;)
257 needToUpdateFilters[i] = true;
258
259 updateIIRFilters();
260}
261
265
267{
268 if (fc.destBuffer != nullptr)
269 {
270 SCOPED_REALTIME_CHECK
271
272 const juce::ScopedLock sl (filterLock);
273
274 updateIIRFilters();
275
277
278 clearChannels (*fc.destBuffer, EQ_CHANS, -1, fc.bufferStartSample, fc.bufferNumSamples);
279
280 addAntiDenormalisationNoise (*fc.destBuffer, fc.bufferStartSample, fc.bufferNumSamples);
281
282 for (int i = std::min ((int) EQ_CHANS, fc.destBuffer->getNumChannels()); --i >= 0;)
283 {
284 auto data = fc.destBuffer->getWritePointer (i, fc.bufferStartSample);
285
286 if (loGain->getCurrentValue() != 0) low[i] .processSamples (data, fc.bufferNumSamples);
287 if (midGain1->getCurrentValue() != 0) mid1[i].processSamples (data, fc.bufferNumSamples);
288 if (midGain2->getCurrentValue() != 0) mid2[i].processSamples (data, fc.bufferNumSamples);
289 if (hiGain->getCurrentValue() != 0) high[i].processSamples (data, fc.bufferNumSamples);
290 }
291
292 if (phaseInvert)
294 }
295}
296
298{
299 if (curveNeedsUpdating)
300 {
301 updateIIRFilters();
302
303 curve.clear();
304
305 if (loGain->getCurrentValue() == 0 && midGain1->getCurrentValue() == 0
306 && midGain2->getCurrentValue() == 0 && hiGain->getCurrentValue() == 0)
307 return 0.0f;
308
309 const int sampSize = (1 << fftOrder);
310
311 float samps[sampSize * 2 + 8] = {};
312 samps[0] = 1.0f;
313
314 if (loGain->getCurrentValue() != 0) juce::IIRFilter (low[0]) .processSamples (samps, sampSize);
315 if (midGain1->getCurrentValue() != 0) juce::IIRFilter (mid1[0]).processSamples (samps, sampSize);
316 if (midGain2->getCurrentValue() != 0) juce::IIRFilter (mid2[0]).processSamples (samps, sampSize);
317 if (hiGain->getCurrentValue() != 0) juce::IIRFilter (high[0]).processSamples (samps, sampSize);
318
320
321 for (int i = 0; i < sampSize / 2;)
322 {
323 curve.addPoint (i / (float)(sampSize / 2),
324 gainToDb (juce::jlimit (0.01f, 10.0f, samps[i * 2])));
325
326 if (i < sampSize / 8)
327 ++i;
328 else if (i < sampSize / 4)
329 i += 3;
330 else
331 i += 6;
332 }
333
334 curveNeedsUpdating = false;
335 yieldGUIThread();
336 }
337
338 return curve.getY (f / (lastSampleRate / 2));
339}
340
341}} // namespace tracktion { inline namespace engine
Type * getWritePointer(int channelNumber) noexcept
int getNumChannels() const noexcept
int getNumSamples() const noexcept
void applyGain(int channel, int startSample, int numSamples, Type gain) noexcept
static String toString(Type decibels, int decimalPlaces=2, Type minusInfinityDb=Type(defaultMinusInfinitydB), bool shouldIncludeSuffix=true, StringRef customMinusInfinityString={})
static IIRCoefficients makePeakFilter(double sampleRate, double centreFrequency, double Q, float gainFactor) noexcept
static IIRCoefficients makeHighShelf(double sampleRate, double cutOffFrequency, double Q, float gainFactor) noexcept
static IIRCoefficients makeLowShelf(double sampleRate, double cutOffFrequency, double Q, float gainFactor) noexcept
void reset() noexcept
void processSamples(float *samples, int numSamples) noexcept
float getFloatValue() const noexcept
String retainCharacters(StringRef charactersToRetain) const
void performRealOnlyForwardTransform(float *inputOutputData, bool onlyCalculateNonNegativeFrequencies=false) const noexcept
void deinitialise() override
Called after play stops to release resources.
juce::String getName() const override
The name of the type, e.g.
void applyToBuffer(const PluginRenderContext &) override
Process the next block of data.
juce::String getTooltip() override
default returns the name, others can return special stuff if needed
float getDBGainAtFrequency(float f)
Finds the gain at a frequency - used to plot the EQ graph.
#define TRANS(stringLiteral)
#define jassert(expression)
typedef float
T min(T... args)
Type jlimit(Type lowerLimit, Type upperLimit, Type valueToConstrain) noexcept
dontSendNotification
int roundToInt(const FloatType value) noexcept
Passed into Plugins when they are being initialised, to give them useful contextual information that ...
pow
The context passed to plugin render methods to provide it with buffers to fill.
int bufferNumSamples
The number of samples to write into the audio buffer.
juce::AudioBuffer< float > * destBuffer
The target audio buffer which needs to be filled.
int bufferStartSample
The index of the start point in the audio buffer from which data must be written.