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