1 #include "effects/builtin/moogladder4filtereffect.h"
2 #include "util/math.h"
3 
4 
5 static const double kMinCorner = 0.0003; // 13 Hz @ 44100
6 static const double kMaxCorner = 0.5; // 22050 Hz @ 44100
7 
8 // static
getId()9 QString MoogLadder4FilterEffect::getId() {
10     return "org.mixxx.effects.moogladder4filter";
11 }
12 
13 // static
getManifest()14 EffectManifestPointer MoogLadder4FilterEffect::getManifest() {
15     EffectManifestPointer pManifest(new EffectManifest());
16     pManifest->setId(getId());
17     pManifest->setName(QObject::tr("Moog Ladder 4 Filter"));
18     pManifest->setShortName(QObject::tr("Moog Filter"));
19     pManifest->setAuthor("The Mixxx Team");
20     pManifest->setVersion("1.0");
21     pManifest->setDescription(QObject::tr(
22             "A 4-pole Moog ladder filter, based on Antti Houvilainen's non linear digital implementation"));
23     pManifest->setEffectRampsFromDry(true);
24 
25     EffectManifestParameterPointer lpf = pManifest->addParameter();
26     lpf->setId("lpf");
27     lpf->setName(QObject::tr("LPF"));
28     lpf->setDescription(QObject::tr("Corner frequency ratio of the low pass filter"));
29     lpf->setControlHint(EffectManifestParameter::ControlHint::KNOB_LOGARITHMIC);
30     lpf->setSemanticHint(EffectManifestParameter::SemanticHint::UNKNOWN);
31     lpf->setUnitsHint(EffectManifestParameter::UnitsHint::UNKNOWN);
32     lpf->setDefaultLinkType(EffectManifestParameter::LinkType::LINKED_LEFT);
33     lpf->setNeutralPointOnScale(1);
34     lpf->setDefault(kMaxCorner);
35     lpf->setMinimum(kMinCorner);
36     lpf->setMaximum(kMaxCorner);
37 
38     EffectManifestParameterPointer q = pManifest->addParameter();
39     q->setId("resonance");
40     q->setName(QObject::tr("Resonance"));
41     q->setShortName(QObject::tr("Res"));
42     q->setDescription(QObject::tr("Resonance of the filters. 4 = self oscillating"));
43     q->setControlHint(EffectManifestParameter::ControlHint::KNOB_LOGARITHMIC);
44     q->setSemanticHint(EffectManifestParameter::SemanticHint::UNKNOWN);
45     q->setUnitsHint(EffectManifestParameter::UnitsHint::SAMPLERATE);
46     q->setMinimum(0.0);
47     q->setMaximum(4.0);
48     q->setDefault(1.0);
49 
50     EffectManifestParameterPointer hpf = pManifest->addParameter();
51     hpf->setId("hpf");
52     hpf->setName(QObject::tr("HPF"));
53     hpf->setDescription(QObject::tr("Corner frequency ratio of the high pass filter"));
54     hpf->setControlHint(EffectManifestParameter::ControlHint::KNOB_LOGARITHMIC);
55     hpf->setSemanticHint(EffectManifestParameter::SemanticHint::UNKNOWN);
56     hpf->setUnitsHint(EffectManifestParameter::UnitsHint::UNKNOWN);
57     hpf->setDefaultLinkType(EffectManifestParameter::LinkType::LINKED_RIGHT);
58     hpf->setNeutralPointOnScale(0.0);
59     hpf->setDefault(kMinCorner);
60     hpf->setMinimum(kMinCorner);
61     hpf->setMaximum(kMaxCorner);
62 
63     return pManifest;
64 }
65 
MoogLadder4FilterGroupState(const mixxx::EngineParameters & bufferParameters)66 MoogLadder4FilterGroupState::MoogLadder4FilterGroupState(
67         const mixxx::EngineParameters& bufferParameters)
68         : EffectState(bufferParameters),
69           m_loFreq(kMaxCorner),
70           m_resonance(0),
71           m_hiFreq(kMinCorner),
72           m_samplerate(bufferParameters.sampleRate()) {
73     m_pBuf = SampleUtil::alloc(bufferParameters.samplesPerBuffer());
74     m_pLowFilter = new EngineFilterMoogLadder4Low(
75             bufferParameters.sampleRate(),
76             m_loFreq * bufferParameters.sampleRate(), m_resonance);
77     m_pHighFilter = new EngineFilterMoogLadder4High(
78             bufferParameters.sampleRate(),
79             m_hiFreq * bufferParameters.sampleRate(), m_resonance);
80 }
81 
~MoogLadder4FilterGroupState()82 MoogLadder4FilterGroupState::~MoogLadder4FilterGroupState() {
83     SampleUtil::free(m_pBuf);
84     delete m_pLowFilter;
85     delete m_pHighFilter;
86 }
87 
MoogLadder4FilterEffect(EngineEffect * pEffect)88 MoogLadder4FilterEffect::MoogLadder4FilterEffect(EngineEffect* pEffect)
89         : m_pLPF(pEffect->getParameterById("lpf")),
90           m_pResonance(pEffect->getParameterById("resonance")),
91           m_pHPF(pEffect->getParameterById("hpf")) {
92 }
93 
~MoogLadder4FilterEffect()94 MoogLadder4FilterEffect::~MoogLadder4FilterEffect() {
95     //qDebug() << debugString() << "destroyed";
96 }
97 
processChannel(const ChannelHandle & handle,MoogLadder4FilterGroupState * pState,const CSAMPLE * pInput,CSAMPLE * pOutput,const mixxx::EngineParameters & bufferParameters,const EffectEnableState enableState,const GroupFeatureState & groupFeatures)98 void MoogLadder4FilterEffect::processChannel(
99         const ChannelHandle& handle,
100         MoogLadder4FilterGroupState* pState,
101         const CSAMPLE* pInput, CSAMPLE* pOutput,
102         const mixxx::EngineParameters& bufferParameters,
103         const EffectEnableState enableState,
104         const GroupFeatureState& groupFeatures) {
105     Q_UNUSED(handle);
106     Q_UNUSED(groupFeatures);
107 
108     double resonance = m_pResonance->value();
109     double hpf;
110     double lpf;
111     if (enableState == EffectEnableState::Disabling) {
112         // Ramp to dry, when disabling, this will ramp from dry when enabling as well
113         hpf = kMinCorner;
114         lpf = kMaxCorner;
115     } else {
116         hpf = m_pHPF->value();
117         lpf = m_pLPF->value();
118     }
119 
120     if (pState->m_loFreq != lpf ||
121             pState->m_resonance != resonance ||
122             pState->m_samplerate != bufferParameters.sampleRate()) {
123         pState->m_pLowFilter->setParameter(bufferParameters.sampleRate(),
124                 static_cast<float>(lpf * bufferParameters.sampleRate()),
125                 static_cast<float>(resonance));
126     }
127 
128     if (pState->m_hiFreq != hpf ||
129             pState->m_resonance != resonance ||
130             pState->m_samplerate != bufferParameters.sampleRate()) {
131         pState->m_pHighFilter->setParameter(bufferParameters.sampleRate(),
132                 static_cast<float>(hpf * bufferParameters.sampleRate()),
133                 static_cast<float>(resonance));
134     }
135 
136     const CSAMPLE* pLpfInput = pState->m_pBuf;
137     CSAMPLE* pHpfOutput = pState->m_pBuf;
138     if (lpf >= kMaxCorner && pState->m_loFreq >= kMaxCorner) {
139         // Lpf disabled Hpf can write directly to output
140         pHpfOutput = pOutput;
141         pLpfInput = pHpfOutput;
142     }
143 
144     if (hpf > kMinCorner) {
145         // hpf enabled, fade-in is handled in the filter when starting from pause
146         pState->m_pHighFilter->process(pInput, pHpfOutput, bufferParameters.samplesPerBuffer());
147     } else if (pState->m_hiFreq > kMinCorner) {
148         // hpf disabling
149         pState->m_pHighFilter->processAndPauseFilter(pInput,
150                 pHpfOutput, bufferParameters.samplesPerBuffer());
151     } else {
152         // paused LP uses input directly
153         pLpfInput = pInput;
154     }
155 
156     if (lpf < kMaxCorner) {
157         // lpf enabled, fade-in is handled in the filter when starting from pause
158         pState->m_pLowFilter->process(pLpfInput, pOutput, bufferParameters.samplesPerBuffer());
159     } else if (pState->m_loFreq < kMaxCorner) {
160         // hpf disabling
161         pState->m_pLowFilter->processAndPauseFilter(pLpfInput,
162                 pOutput, bufferParameters.samplesPerBuffer());
163     } else if (pLpfInput == pInput) {
164         // Both disabled
165         if (pOutput != pInput) {
166             // We need to copy pInput pOutput
167             SampleUtil::copy(pOutput, pInput, bufferParameters.samplesPerBuffer());
168         }
169     }
170 
171     pState->m_loFreq = lpf;
172     pState->m_resonance = resonance;
173     pState->m_hiFreq = hpf;
174     pState->m_samplerate = bufferParameters.sampleRate();
175 }
176