1 #include "effects/builtin/threebandbiquadeqeffect.h"
2 
3 #include "effects/builtin/equalizer_util.h"
4 #include "util/math.h"
5 
6 namespace {
7 
8 // The defaults are tweaked to match the Xone:23 EQ
9 // but allow 12 dB boost instead of just 6 dB
10 static const int kStartupSamplerate = 44100;
11 static const double kMinimumFrequency = 10.0;
12 static const double kMaximumFrequency = kStartupSamplerate / 2;
13 static const double kStartupLoFreq = 50.0;
14 static const double kStartupMidFreq = 1100.0;
15 static const double kStartupHiFreq = 12000.0;
16 static const double kQBoost = 0.3;
17 static const double kQKill = 0.9;
18 static const double kQKillShelve = 0.4;
19 static const double kBoostGain = 12;
20 static const double kKillGain = -26;
21 
22 
getCenterFrequency(double low,double high)23 double getCenterFrequency(double low, double high) {
24     double scaleLow = log10(low);
25     double scaleHigh = log10(high);
26 
27     double scaleCenter = (scaleHigh - scaleLow) / 2 + scaleLow;
28     return pow(10, scaleCenter);
29 }
30 
knobValueToBiquadGainDb(double value,bool kill)31 double knobValueToBiquadGainDb (double value, bool kill) {
32     if (kill) {
33         return kKillGain;
34     }
35 
36     double ret = value - 1;
37     if (ret >= 0) {
38         ret *= kBoostGain;
39     } else {
40         ret *= -kKillGain;
41     }
42     return ret;
43 }
44 
45 } // anonymous namespace
46 
47 
48 // static
getId()49 QString ThreeBandBiquadEQEffect::getId() {
50     return "org.mixxx.effects.threebandbiquadeq";
51 }
52 
53 // static
getManifest()54 EffectManifestPointer ThreeBandBiquadEQEffect::getManifest() {
55     EffectManifestPointer pManifest(new EffectManifest());
56     pManifest->setId(getId());
57     pManifest->setName(QObject::tr("Biquad Equalizer"));
58     pManifest->setShortName(QObject::tr("BQ EQ"));
59     pManifest->setAuthor("The Mixxx Team");
60     pManifest->setVersion("1.0");
61     pManifest->setDescription(QObject::tr(
62         "A 3-band Equalizer with two biquad bell filters, a shelving high pass and kill switches.") +
63         " " + EqualizerUtil::adjustFrequencyShelvesTip());
64     pManifest->setEffectRampsFromDry(true);
65     pManifest->setIsMixingEQ(true);
66 
67     EqualizerUtil::createCommonParameters(pManifest.data(), true);
68     return pManifest;
69 }
70 
ThreeBandBiquadEQEffectGroupState(const mixxx::EngineParameters & bufferParameters)71 ThreeBandBiquadEQEffectGroupState::ThreeBandBiquadEQEffectGroupState(
72       const mixxx::EngineParameters& bufferParameters)
73         : EffectState(bufferParameters),
74           m_tempBuf(bufferParameters.samplesPerBuffer()),
75           m_oldLowBoost(0),
76           m_oldMidBoost(0),
77           m_oldHighBoost(0),
78           m_oldLowCut(0),
79           m_oldMidCut(0),
80           m_oldHighCut(0),
81           m_loFreqCorner(0),
82           m_highFreqCorner(0),
83           m_oldSampleRate(bufferParameters.sampleRate()) {
84 
85     // Initialize the filters with default parameters
86 
87     m_lowBoost = std::make_unique<EngineFilterBiquad1Peaking>(
88             bufferParameters.sampleRate() , kStartupLoFreq, kQBoost);
89     m_midBoost = std::make_unique<EngineFilterBiquad1Peaking>(
90             bufferParameters.sampleRate() , kStartupMidFreq, kQBoost);
91     m_highBoost = std::make_unique<EngineFilterBiquad1Peaking>(
92             bufferParameters.sampleRate() , kStartupHiFreq, kQBoost);
93     m_lowCut = std::make_unique<EngineFilterBiquad1Peaking>(
94             bufferParameters.sampleRate() , kStartupLoFreq, kQKill);
95     m_midCut = std::make_unique<EngineFilterBiquad1Peaking>(
96             bufferParameters.sampleRate() , kStartupMidFreq, kQKill);
97     m_highCut = std::make_unique<EngineFilterBiquad1HighShelving>(
98             bufferParameters.sampleRate() , kStartupHiFreq / 2, kQKillShelve);
99 }
100 
~ThreeBandBiquadEQEffectGroupState()101 ThreeBandBiquadEQEffectGroupState::~ThreeBandBiquadEQEffectGroupState() {
102 }
103 
setFilters(int sampleRate,double lowFreqCorner,double highFreqCorner)104 void ThreeBandBiquadEQEffectGroupState::setFilters(
105         int sampleRate, double lowFreqCorner, double highFreqCorner) {
106 
107     double lowCenter = getCenterFrequency(kMinimumFrequency, lowFreqCorner);
108     double midCenter = getCenterFrequency(lowFreqCorner, highFreqCorner);
109     double highCenter = getCenterFrequency(highFreqCorner, kMaximumFrequency);
110 
111 
112     m_lowBoost->setFrequencyCorners(
113             sampleRate, lowCenter, kQBoost, m_oldLowBoost);
114     m_midBoost->setFrequencyCorners(
115             sampleRate, midCenter, kQBoost, m_oldMidBoost);
116     m_highBoost->setFrequencyCorners(
117             sampleRate, highCenter, kQBoost, m_oldHighBoost);
118     m_lowCut->setFrequencyCorners(
119             sampleRate, lowCenter, kQKill, m_oldLowCut);
120     m_midCut->setFrequencyCorners(
121             sampleRate, midCenter, kQKill, m_oldMidCut);
122     m_highCut->setFrequencyCorners(
123             sampleRate, highCenter / 2, kQKillShelve, m_oldHighCut);
124 
125 }
126 
ThreeBandBiquadEQEffect(EngineEffect * pEffect)127 ThreeBandBiquadEQEffect::ThreeBandBiquadEQEffect(EngineEffect* pEffect)
128         : m_pPotLow(pEffect->getParameterById("low")),
129           m_pPotMid(pEffect->getParameterById("mid")),
130           m_pPotHigh(pEffect->getParameterById("high")),
131           m_pKillLow(pEffect->getParameterById("killLow")),
132           m_pKillMid(pEffect->getParameterById("killMid")),
133           m_pKillHigh(pEffect->getParameterById("killHigh")) {
134     m_pLoFreqCorner = std::make_unique<ControlProxy>("[Mixer Profile]", "LoEQFrequency");
135     m_pHiFreqCorner = std::make_unique<ControlProxy>("[Mixer Profile]", "HiEQFrequency");
136 }
137 
~ThreeBandBiquadEQEffect()138 ThreeBandBiquadEQEffect::~ThreeBandBiquadEQEffect() {
139 }
140 
processChannel(const ChannelHandle & handle,ThreeBandBiquadEQEffectGroupState * pState,const CSAMPLE * pInput,CSAMPLE * pOutput,const mixxx::EngineParameters & bufferParameters,const EffectEnableState enableState,const GroupFeatureState & groupFeatures)141 void ThreeBandBiquadEQEffect::processChannel(
142         const ChannelHandle& handle,
143         ThreeBandBiquadEQEffectGroupState* pState,
144         const CSAMPLE* pInput,
145         CSAMPLE* pOutput,
146         const mixxx::EngineParameters& bufferParameters,
147         const EffectEnableState enableState,
148         const GroupFeatureState& groupFeatures) {
149     Q_UNUSED(handle);
150     Q_UNUSED(groupFeatures);
151 
152     if (pState->m_oldSampleRate != bufferParameters.sampleRate() ||
153             (pState->m_loFreqCorner != m_pLoFreqCorner->get()) ||
154             (pState->m_highFreqCorner != m_pHiFreqCorner->get())) {
155         pState->m_loFreqCorner = m_pLoFreqCorner->get();
156         pState->m_highFreqCorner = m_pHiFreqCorner->get();
157         pState->m_oldSampleRate = bufferParameters.sampleRate();
158         pState->setFilters(bufferParameters.sampleRate(), pState->m_loFreqCorner, pState->m_highFreqCorner);
159     }
160 
161 
162     // Ramp to dry, when disabling, this will ramp from dry when enabling as well
163     double bqGainLow = 0;
164     double bqGainMid = 0;
165     double bqGainHigh = 0;
166     if (enableState != EffectEnableState::Disabling) {
167         bqGainLow = knobValueToBiquadGainDb(
168                 m_pPotLow->value(), m_pKillLow->toBool());
169         bqGainMid = knobValueToBiquadGainDb(
170                 m_pPotMid->value(), m_pKillMid->toBool());
171         bqGainHigh = knobValueToBiquadGainDb(
172                 m_pPotHigh->value(), m_pKillHigh->toBool());
173     }
174 
175     int activeFilters = 0;
176 
177     if (bqGainLow > 0.0 || pState->m_oldLowBoost > 0.0) {
178         ++activeFilters;
179     }
180     if (bqGainLow < 0.0 || pState->m_oldLowCut < 0.0) {
181         ++activeFilters;
182     }
183     if (bqGainMid > 0.0 || pState->m_oldMidBoost > 0.0) {
184         ++activeFilters;
185     }
186     if (bqGainMid < 0.0 || pState->m_oldMidCut < 0.0) {
187         ++activeFilters;
188     }
189     if (bqGainHigh > 0.0 || pState->m_oldHighBoost > 0.0) {
190         ++activeFilters;
191     }
192     if (bqGainHigh < 0.0 || pState->m_oldHighCut < 0.0) {
193         ++activeFilters;
194     }
195 
196     QVarLengthArray<const CSAMPLE*, 6> inBuffer;
197     QVarLengthArray<CSAMPLE*, 6> outBuffer;
198 
199     if (activeFilters % 2 == 0) {
200         inBuffer.append(pInput);
201         outBuffer.append(pState->m_tempBuf.data());
202 
203         inBuffer.append(pState->m_tempBuf.data());
204         outBuffer.append(pOutput);
205 
206         inBuffer.append(pOutput);
207         outBuffer.append(pState->m_tempBuf.data());
208 
209         inBuffer.append(pState->m_tempBuf.data());
210         outBuffer.append(pOutput);
211 
212         inBuffer.append(pOutput);
213         outBuffer.append(pState->m_tempBuf.data());
214 
215         inBuffer.append(pState->m_tempBuf.data());
216         outBuffer.append(pOutput);
217     }
218     else
219     {
220         inBuffer.append(pInput);
221         outBuffer.append(pOutput);
222 
223         inBuffer.append(pOutput);
224         outBuffer.append(pState->m_tempBuf.data());
225 
226         inBuffer.append(pState->m_tempBuf.data());
227         outBuffer.append(pOutput);
228 
229         inBuffer.append(pOutput);
230         outBuffer.append(pState->m_tempBuf.data());
231 
232         inBuffer.append(pState->m_tempBuf.data());
233         outBuffer.append(pOutput);
234 
235         inBuffer.append(pOutput);
236         outBuffer.append(pState->m_tempBuf.data());
237     }
238 
239     int bufIndex = 0;
240 
241     if (bqGainLow > 0.0 || pState->m_oldLowBoost > 0.0) {
242         if (bqGainLow != pState->m_oldLowBoost) {
243             double lowCenter = getCenterFrequency(
244                     kMinimumFrequency, pState->m_loFreqCorner);
245             pState->m_lowBoost->setFrequencyCorners(
246                     bufferParameters.sampleRate(), lowCenter, kQBoost, bqGainLow);
247             pState->m_oldLowBoost = bqGainLow;
248         }
249         if (bqGainLow > 0.0) {
250             pState->m_lowBoost->process(
251                     inBuffer[bufIndex], outBuffer[bufIndex], bufferParameters.samplesPerBuffer());
252         } else {
253             pState->m_lowBoost->processAndPauseFilter(
254                     inBuffer[bufIndex], outBuffer[bufIndex], bufferParameters.samplesPerBuffer());
255         }
256         ++bufIndex;
257     } else {
258         pState->m_lowBoost->pauseFilter();
259     }
260 
261     if (bqGainLow < 0.0 || pState->m_oldLowCut < 0.0) {
262         if (bqGainLow != pState->m_oldLowCut) {
263             double lowCenter = getCenterFrequency(
264                     kMinimumFrequency, pState->m_loFreqCorner);
265             pState->m_lowCut->setFrequencyCorners(
266                     bufferParameters.sampleRate(), lowCenter, kQKill, bqGainLow);
267             pState->m_oldLowCut = bqGainLow;
268         }
269         if (bqGainLow < 0.0) {
270             pState->m_lowCut->process(
271                     inBuffer[bufIndex], outBuffer[bufIndex], bufferParameters.samplesPerBuffer());
272         } else {
273             pState->m_lowCut->processAndPauseFilter(
274                     inBuffer[bufIndex], outBuffer[bufIndex], bufferParameters.samplesPerBuffer());
275         }
276         ++bufIndex;
277     } else {
278         pState->m_lowCut->pauseFilter();
279     }
280 
281     if (bqGainMid > 0.0 || pState->m_oldMidBoost > 0.0) {
282         if (bqGainMid != pState->m_oldMidBoost) {
283             double midCenter = getCenterFrequency(
284                     pState->m_loFreqCorner, pState->m_highFreqCorner);
285             pState->m_midBoost->setFrequencyCorners(
286                     bufferParameters.sampleRate(), midCenter, kQBoost, bqGainMid);
287             pState->m_oldMidBoost = bqGainMid;
288         }
289         if (bqGainMid > 0.0) {
290             pState->m_midBoost->process(
291                     inBuffer[bufIndex], outBuffer[bufIndex], bufferParameters.samplesPerBuffer());
292         } else {
293             pState->m_midBoost->processAndPauseFilter(
294                     inBuffer[bufIndex], outBuffer[bufIndex], bufferParameters.samplesPerBuffer());
295         }
296         ++bufIndex;
297     } else {
298         pState->m_midBoost->pauseFilter();
299     }
300 
301 
302     if (bqGainMid < 0.0 || pState->m_oldMidCut < 0.0) {
303         if (bqGainMid != pState->m_oldMidCut) {
304             double midCenter = getCenterFrequency(
305                     pState->m_loFreqCorner, pState->m_highFreqCorner);
306             pState->m_midCut->setFrequencyCorners(
307                     bufferParameters.sampleRate(), midCenter, kQKill, bqGainMid);
308             pState->m_oldMidCut = bqGainMid;
309         }
310         if (bqGainMid < 0.0) {
311             pState->m_midCut->process(
312                     inBuffer[bufIndex], outBuffer[bufIndex], bufferParameters.samplesPerBuffer());
313         } else {
314             pState->m_midCut->processAndPauseFilter(
315                     inBuffer[bufIndex], outBuffer[bufIndex], bufferParameters.samplesPerBuffer());
316         }
317         ++bufIndex;
318     } else {
319         pState->m_midCut->pauseFilter();
320     }
321 
322     if (bqGainHigh > 0.0 || pState->m_oldHighBoost > 0.0) {
323         if (bqGainHigh != pState->m_oldHighBoost) {
324             double highCenter = getCenterFrequency(
325                     pState->m_highFreqCorner, kMaximumFrequency);
326             pState->m_highBoost->setFrequencyCorners(
327                     bufferParameters.sampleRate(), highCenter, kQBoost, bqGainHigh);
328             pState->m_oldHighBoost = bqGainHigh;
329         }
330         if (bqGainHigh > 0.0) {
331             pState->m_highBoost->process(
332                     inBuffer[bufIndex], outBuffer[bufIndex], bufferParameters.samplesPerBuffer());
333         } else {
334             pState->m_highBoost->processAndPauseFilter(
335                     inBuffer[bufIndex], outBuffer[bufIndex], bufferParameters.samplesPerBuffer());
336         }
337         ++bufIndex;
338     } else {
339         pState->m_highBoost->pauseFilter();
340     }
341 
342     if (bqGainHigh < 0.0 || pState->m_oldHighCut < 0.0) {
343         if (bqGainHigh != pState->m_oldHighCut) {
344             double highCenter = getCenterFrequency(
345                     pState->m_highFreqCorner, kMaximumFrequency);
346             pState->m_highCut->setFrequencyCorners(
347                     bufferParameters.sampleRate(), highCenter / 2, kQKillShelve, bqGainHigh);
348             pState->m_oldHighCut = bqGainHigh;
349         }
350         if (bqGainHigh < 0.0) {
351             pState->m_highCut->process(
352                     inBuffer[bufIndex], outBuffer[bufIndex], bufferParameters.samplesPerBuffer());
353         } else {
354             pState->m_highCut->processAndPauseFilter(
355                     inBuffer[bufIndex], outBuffer[bufIndex], bufferParameters.samplesPerBuffer());
356         }
357         ++bufIndex;
358     } else {
359         pState->m_highCut->pauseFilter();
360     }
361 
362     if (activeFilters == 0) {
363         SampleUtil::copy(pOutput, pInput, bufferParameters.samplesPerBuffer());
364     }
365 
366     if (enableState == EffectEnableState::Disabling) {
367         pState->m_lowBoost->pauseFilter();
368         pState->m_midBoost->pauseFilter();
369         pState->m_highBoost->pauseFilter();
370         pState->m_lowCut->pauseFilter();
371         pState->m_midCut->pauseFilter();
372         pState->m_highCut->pauseFilter();
373     }
374 }
375