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