パート1.紹介とセットアップ
パート2.コードの学習
パート3. VSTおよびAU
パート4.デジタル歪み
パート5.プリセットとGUI
パート6.信号合成
パート7. MIDIメッセージの受信
パート8.仮想キーボード
パート9.封筒
パート10. GUIの改善
パート11.フィルター
パート12.低周波発振器
パート13.再設計
パート14.ポリフォニー1
パート15.ポリフォニー2
パート16.アンチエイリアス
今日は、共振フィルターを作成します。 フィルタ設計は複雑な領域であり、世界中の多くのDSPエンジニアが困惑しています。 ジャングルに飛び込むのではなく、 Paul Kellet アルゴリズムに基づいて単純なローパスフィルター ( Low-Pass )、 バンドパス ( Band-Pass )、およびハイパスフィルター ( High-Pass )を作成します
ご想像のとおり、
Filter
クラスを作成することから始めましょう。 削除する
#include Filter.h ( ) :
class Filter { public: enum FilterMode { FILTER_MODE_LOWPASS = 0, FILTER_MODE_HIGHPASS, FILTER_MODE_BANDPASS, kNumFilterModes }; Filter() : cutoff(0.99), resonance(0.0), mode(FILTER_MODE_LOWPASS), buf0(0.0), buf1(0.0) { calculateFeedbackAmount(); }; double process(double inputValue); inline void setCutoff(double newCutoff) { cutoff = newCutoff; calculateFeedbackAmount(); }; inline void setResonance(double newResonance) { resonance = newResonance; calculateFeedbackAmount(); }; inline void setFilterMode(FilterMode newMode) { mode = newMode; } private: double cutoff; double resonance; FilterMode mode; double feedbackAmount; inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - cutoff); } double buf0; double buf1; };
private
cutoff
resonance
. Mode
(Lowpass, Highpass, Bandpass). feedbackAmount
, buf0
buf1
, .
( calculateFeedbackAmount()
). process
. feedbackAmount
cutoff
resonance
, calculateFeedbackAmount
.
Filter.cpp :
// By Paul Kellett // http://www.musicdsp.org/showone.php?id=29 double Filter::process(double inputValue) { buf0 += cutoff * (inputValue - buf0); buf1 += cutoff * (buf0 - buf1); switch (mode) { case FILTER_MODE_LOWPASS: return buf1; case FILTER_MODE_HIGHPASS: return inputValue - buf0; case FILTER_MODE_BANDPASS: return buf0 - buf1; default: return 0.0; } }
, ? , . « » , (. . 6 ). buf0
buf1
: . inputValue
, - buf0
( ). . 6 / 12 /. switch
, buf1
. , , buf0
, 12, 6 /, .
case
FILTER_MODE_HIGHPASS
. buf0
. inputValue
buf0
, . buf1
, .
case
FILTER_MODE_BANDPASS
, buf0 - buf1
. buf0
, buf1
. , .
: , .
buf1
. ( Infinite Impulse Response , IIR ). , . , , .
, , . , , , , . , NI Massive, « ». , , . , ( Massive ). «» , , , AAS Ultra Analog, , , , . , , , , NI Reaktor. , — , .. .
. GUI. bg.png ( “Move to trash” Xcode), :
filtermode.png knob_small.png ( , 50 50 ) bg.png ( )
ID resource.h :
// Unique IDs for each image resource. #define BG_ID 101 #define WHITE_KEY_ID 102 #define BLACK_KEY_ID 103 #define WAVEFORM_ID 104 #define KNOB_ID 105 #define KNOB_SMALL_ID 106 #define FILTERMODE_ID 107 // Image resource locations for this plug. #define BG_FN "resources/img/bg.png" #define WHITE_KEY_FN "resources/img/whitekey.png" #define BLACK_KEY_FN "resources/img/blackkey.png" #define WAVEFORM_FN "resources/img/waveform.png" #define KNOB_FN "resources/img/knob.png" #define KNOB_SMALL_FN "resources/img/knob_small.png" #define FILTERMODE_FN "resources/img/filtermode.png"
Synthesis.rc :
#include "resource.h" BG_ID PNG BG_FN WHITE_KEY_ID PNG WHITE_KEY_FN BLACK_KEY_ID PNG BLACK_KEY_FN WAVEFORM_ID PNG WAVEFORM_FN KNOB_ID PNG KNOB_FN KNOB_SMALL_ID PNG KNOB_SMALL_FN FILTERMODE_ID PNG FILTERMODE_FN
#include "Filter.h"
Synthesis.h private
:
Filter mFilter;
EParams
Synthesis.cpp :
enum EParams { mWaveform = 0, mAttack, mDecay, mSustain, mRelease, mFilterMode, mFilterCutoff, mFilterResonance, mFilterAttack, mFilterDecay, mFilterSustain, mFilterRelease, mFilterEnvelopeAmount, kNumParams };
:
pGraphics->AttachControl(new ISwitchControl(this, 24, 38, mWaveform, &waveformBitmap));
. (Lowpass, Highpass, Bandpass). AttachGraphics(pGraphics)
:
GetParam(mFilterMode)->InitEnum("Filter Mode", Filter::FILTER_MODE_LOWPASS, Filter::kNumFilterModes); IBitmap filtermodeBitmap = pGraphics->LoadIBitmap(FILTERMODE_ID, FILTERMODE_FN, 3); pGraphics->AttachControl(new ISwitchControl(this, 24, 123, mFilterMode, &filtermodeBitmap));
. . knob_small.png :
// Knobs for filter cutoff and resonance IBitmap smallKnobBitmap = pGraphics->LoadIBitmap(KNOB_SMALL_ID, KNOB_SMALL_FN, 64); // Cutoff knob: GetParam(mFilterCutoff)->InitDouble("Cutoff", 0.99, 0.01, 0.99, 0.001); GetParam(mFilterCutoff)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 5, 177, mFilterCutoff, &smallKnobBitmap)); // Resonance knob: GetParam(mFilterResonance)->InitDouble("Resonance", 0.01, 0.01, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 61, 177, mFilterResonance, &smallKnobBitmap));
, 1.0
, calculateFeedbackAmount
, .
ProcessDoubleReplacing
:
leftOutput[i] = rightOutput[i] = mFilter.process(mOscillator.nextSample() * mEnvelopeGenerator.nextSample() * velocity / 127.0);
. switch
Synthesis::OnParamChange
:
case mFilterCutoff: mFilter.setCutoff(GetParam(paramIdx)->Value()); break; case mFilterResonance: mFilter.setResonance(GetParam(paramIdx)->Value()); break; case mFilterMode: mFilter.setFilterMode(static_cast<Filter::FilterMode>(GetParam(paramIdx)->Int())); break;
. - .
— . , , .
:
buf0 += cutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1));
, , ( buf0 – buf1
), feedbackAmount
. calculateFeedbackAmount
feedbackAmount
resonance
, . . , .
, . , , . , , .
-12 / -24 /
! 24 . switch
Filter::process
:
buf2 += cutoff * (buf1 - buf2); buf3 += cutoff * (buf2 – buf3);
: , , . , , , ( ). switch
:
switch (mode) { case FILTER_MODE_LOWPASS: return buf3; case FILTER_MODE_HIGHPASS: return inputValue - buf3; case FILTER_MODE_BANDPASS: return buf0 - buf3; default: return 0.0; }
buf1
buf3
– . -24 /. buf2
buf3
, private
Filter.h :
double buf2; double buf3;
, buf0
c buf1
:
Filter() : // ... buf0(0.0), buf1(0.0), buf2(0.0), buf3(0.0) // ...
, , : . .
?
. , , .
- cutoff
. . cutoffMod
, , cutoff
. #include private
:
double cutoffMod;
:
Filter() : cutoff(0.99), resonance(0.01), cutoffMod(0.0), // ...
. private
:
inline double getCalculatedCutoff() const { return fmax(fmin(cutoff + cutoffMod, 0.99), 0.01); };
calculateFeedbackAmount
:
inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - getCalculatedCutoff()); }
cutoffMod
. feedbackAmount
, :
inline void setCutoffMod(double newCutoffMod) { cutoffMod = newCutoffMod; calculateFeedbackAmount(); }
, . Filter::process
:
if (inputValue == 0.0) return inputValue; double calculatedCutoff = getCalculatedCutoff(); buf0 += calculatedCutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1)); buf1 += calculatedCutoff * (buf0 - buf1); buf2 += calculatedCutoff * (buf1 - buf2); buf3 += calculatedCutoff * (buf2 – buf3);
, . , , . . cutoff
calculatedCutoff
-.
, ( setCutoffMod
), Synthesis
, , . , cutoffMod
. filterEnvelopeAmount
-1
+1
. GUI.
private
Synthesis.h :
EnvelopeGenerator mFilterEnvelopeGenerator; double filterEnvelopeAmount;
MIDI , onNoteOn
onNoteOff
:
inline void onNoteOn(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); }; inline void onNoteOff(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); };
. ProcessDoubleReplacing
:
mFilter.setCutoffMod(mFilterEnvelopeGenerator.nextSample() * filterEnvelopeAmount);
, nextSample
filterEnvelopeAmount
cutoffMod
. filterEnvelopeAmount
:
Synthesis::Synthesis(IPlugInstanceInfo instanceInfo) : IPLUG_CTOR(kNumParams, kNumPrograms, instanceInfo), lastVirtualKeyboardNoteNumber(virtualKeyboardMinimumNoteNumber - 1), filterEnvelopeAmount(0.0) { // ... }
Synthesis::Reset
:
mFilterEnvelopeGenerator.setSampleRate(GetSampleRate());
EParams
, . AttachGraphics
:
// Knobs for filter envelope // Attack knob GetParam(mFilterAttack)->InitDouble("Filter Env Attack", 0.01, 0.01, 10.0, 0.001); GetParam(mFilterAttack)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 139, 178, mFilterAttack, &smallKnobBitmap)); // Decay knob: GetParam(mFilterDecay)->InitDouble("Filter Env Decay", 0.5, 0.01, 15.0, 0.001); GetParam(mFilterDecay)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 195, 178, mFilterDecay, &smallKnobBitmap)); // Sustain knob: GetParam(mFilterSustain)->InitDouble("Filter Env Sustain", 0.1, 0.001, 1.0, 0.001); GetParam(mFilterSustain)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 251, 178, mFilterSustain, &smallKnobBitmap)); // Release knob: GetParam(mFilterRelease)->InitDouble("Filter Env Release", 1.0, 0.001, 15.0, 0.001); GetParam(mFilterRelease)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 307, 178, mFilterRelease, &smallKnobBitmap)); // Filter envelope amount knob: GetParam(mFilterEnvelopeAmount)->InitDouble("Filter Env Amount", 0.0, -1.0, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 363, 178, mFilterEnvelopeAmount, &smallKnobBitmap));
, smallKnobBitmap
. . , . Synthesis::OnParamChange
switch
:
case mFilterAttack: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK, GetParam(paramIdx)->Value()); break; case mFilterDecay: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_DECAY, GetParam(paramIdx)->Value()); break; case mFilterSustain: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_SUSTAIN, GetParam(paramIdx)->Value()); break; case mFilterRelease: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE, GetParam(paramIdx)->Value()); break; case mFilterEnvelopeAmount: filterEnvelopeAmount = GetParam(paramIdx)->Value(); break;
!
! (- C1), :
EnvelopeGenerator
. . !
.
:)
:
martin-finke.de/blog/articles/audio-plugins-013-filter
#include Filter.h ( ) :
class Filter { public: enum FilterMode { FILTER_MODE_LOWPASS = 0, FILTER_MODE_HIGHPASS, FILTER_MODE_BANDPASS, kNumFilterModes }; Filter() : cutoff(0.99), resonance(0.0), mode(FILTER_MODE_LOWPASS), buf0(0.0), buf1(0.0) { calculateFeedbackAmount(); }; double process(double inputValue); inline void setCutoff(double newCutoff) { cutoff = newCutoff; calculateFeedbackAmount(); }; inline void setResonance(double newResonance) { resonance = newResonance; calculateFeedbackAmount(); }; inline void setFilterMode(FilterMode newMode) { mode = newMode; } private: double cutoff; double resonance; FilterMode mode; double feedbackAmount; inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - cutoff); } double buf0; double buf1; };
private
cutoff
resonance
. Mode
(Lowpass, Highpass, Bandpass). feedbackAmount
, buf0
buf1
, .
( calculateFeedbackAmount()
). process
. feedbackAmount
cutoff
resonance
, calculateFeedbackAmount
.
Filter.cpp :
// By Paul Kellett // http://www.musicdsp.org/showone.php?id=29 double Filter::process(double inputValue) { buf0 += cutoff * (inputValue - buf0); buf1 += cutoff * (buf0 - buf1); switch (mode) { case FILTER_MODE_LOWPASS: return buf1; case FILTER_MODE_HIGHPASS: return inputValue - buf0; case FILTER_MODE_BANDPASS: return buf0 - buf1; default: return 0.0; } }
, ? , . « » , (. . 6 ). buf0
buf1
: . inputValue
, - buf0
( ). . 6 / 12 /. switch
, buf1
. , , buf0
, 12, 6 /, .
case
FILTER_MODE_HIGHPASS
. buf0
. inputValue
buf0
, . buf1
, .
case
FILTER_MODE_BANDPASS
, buf0 - buf1
. buf0
, buf1
. , .
: , .
buf1
. ( Infinite Impulse Response , IIR ). , . , , .
, , . , , , , . , NI Massive, « ». , , . , ( Massive ). «» , , , AAS Ultra Analog, , , , . , , , , NI Reaktor. , — , .. .
. GUI. bg.png ( “Move to trash” Xcode), :
filtermode.png knob_small.png ( , 50 50 ) bg.png ( )
ID resource.h :
// Unique IDs for each image resource. #define BG_ID 101 #define WHITE_KEY_ID 102 #define BLACK_KEY_ID 103 #define WAVEFORM_ID 104 #define KNOB_ID 105 #define KNOB_SMALL_ID 106 #define FILTERMODE_ID 107 // Image resource locations for this plug. #define BG_FN "resources/img/bg.png" #define WHITE_KEY_FN "resources/img/whitekey.png" #define BLACK_KEY_FN "resources/img/blackkey.png" #define WAVEFORM_FN "resources/img/waveform.png" #define KNOB_FN "resources/img/knob.png" #define KNOB_SMALL_FN "resources/img/knob_small.png" #define FILTERMODE_FN "resources/img/filtermode.png"
Synthesis.rc :
#include "resource.h" BG_ID PNG BG_FN WHITE_KEY_ID PNG WHITE_KEY_FN BLACK_KEY_ID PNG BLACK_KEY_FN WAVEFORM_ID PNG WAVEFORM_FN KNOB_ID PNG KNOB_FN KNOB_SMALL_ID PNG KNOB_SMALL_FN FILTERMODE_ID PNG FILTERMODE_FN
#include "Filter.h"
Synthesis.h private
:
Filter mFilter;
EParams
Synthesis.cpp :
enum EParams { mWaveform = 0, mAttack, mDecay, mSustain, mRelease, mFilterMode, mFilterCutoff, mFilterResonance, mFilterAttack, mFilterDecay, mFilterSustain, mFilterRelease, mFilterEnvelopeAmount, kNumParams };
:
pGraphics->AttachControl(new ISwitchControl(this, 24, 38, mWaveform, &waveformBitmap));
. (Lowpass, Highpass, Bandpass). AttachGraphics(pGraphics)
:
GetParam(mFilterMode)->InitEnum("Filter Mode", Filter::FILTER_MODE_LOWPASS, Filter::kNumFilterModes); IBitmap filtermodeBitmap = pGraphics->LoadIBitmap(FILTERMODE_ID, FILTERMODE_FN, 3); pGraphics->AttachControl(new ISwitchControl(this, 24, 123, mFilterMode, &filtermodeBitmap));
. . knob_small.png :
// Knobs for filter cutoff and resonance IBitmap smallKnobBitmap = pGraphics->LoadIBitmap(KNOB_SMALL_ID, KNOB_SMALL_FN, 64); // Cutoff knob: GetParam(mFilterCutoff)->InitDouble("Cutoff", 0.99, 0.01, 0.99, 0.001); GetParam(mFilterCutoff)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 5, 177, mFilterCutoff, &smallKnobBitmap)); // Resonance knob: GetParam(mFilterResonance)->InitDouble("Resonance", 0.01, 0.01, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 61, 177, mFilterResonance, &smallKnobBitmap));
, 1.0
, calculateFeedbackAmount
, .
ProcessDoubleReplacing
:
leftOutput[i] = rightOutput[i] = mFilter.process(mOscillator.nextSample() * mEnvelopeGenerator.nextSample() * velocity / 127.0);
. switch
Synthesis::OnParamChange
:
case mFilterCutoff: mFilter.setCutoff(GetParam(paramIdx)->Value()); break; case mFilterResonance: mFilter.setResonance(GetParam(paramIdx)->Value()); break; case mFilterMode: mFilter.setFilterMode(static_cast<Filter::FilterMode>(GetParam(paramIdx)->Int())); break;
. - .
— . , , .
:
buf0 += cutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1));
, , ( buf0 – buf1
), feedbackAmount
. calculateFeedbackAmount
feedbackAmount
resonance
, . . , .
, . , , . , , .
-12 / -24 /
! 24 . switch
Filter::process
:
buf2 += cutoff * (buf1 - buf2); buf3 += cutoff * (buf2 – buf3);
: , , . , , , ( ). switch
:
switch (mode) { case FILTER_MODE_LOWPASS: return buf3; case FILTER_MODE_HIGHPASS: return inputValue - buf3; case FILTER_MODE_BANDPASS: return buf0 - buf3; default: return 0.0; }
buf1
buf3
– . -24 /. buf2
buf3
, private
Filter.h :
double buf2; double buf3;
, buf0
c buf1
:
Filter() : // ... buf0(0.0), buf1(0.0), buf2(0.0), buf3(0.0) // ...
, , : . .
?
. , , .
- cutoff
. . cutoffMod
, , cutoff
. #include private
:
double cutoffMod;
:
Filter() : cutoff(0.99), resonance(0.01), cutoffMod(0.0), // ...
. private
:
inline double getCalculatedCutoff() const { return fmax(fmin(cutoff + cutoffMod, 0.99), 0.01); };
calculateFeedbackAmount
:
inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - getCalculatedCutoff()); }
cutoffMod
. feedbackAmount
, :
inline void setCutoffMod(double newCutoffMod) { cutoffMod = newCutoffMod; calculateFeedbackAmount(); }
, . Filter::process
:
if (inputValue == 0.0) return inputValue; double calculatedCutoff = getCalculatedCutoff(); buf0 += calculatedCutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1)); buf1 += calculatedCutoff * (buf0 - buf1); buf2 += calculatedCutoff * (buf1 - buf2); buf3 += calculatedCutoff * (buf2 – buf3);
, . , , . . cutoff
calculatedCutoff
-.
, ( setCutoffMod
), Synthesis
, , . , cutoffMod
. filterEnvelopeAmount
-1
+1
. GUI.
private
Synthesis.h :
EnvelopeGenerator mFilterEnvelopeGenerator; double filterEnvelopeAmount;
MIDI , onNoteOn
onNoteOff
:
inline void onNoteOn(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); }; inline void onNoteOff(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); };
. ProcessDoubleReplacing
:
mFilter.setCutoffMod(mFilterEnvelopeGenerator.nextSample() * filterEnvelopeAmount);
, nextSample
filterEnvelopeAmount
cutoffMod
. filterEnvelopeAmount
:
Synthesis::Synthesis(IPlugInstanceInfo instanceInfo) : IPLUG_CTOR(kNumParams, kNumPrograms, instanceInfo), lastVirtualKeyboardNoteNumber(virtualKeyboardMinimumNoteNumber - 1), filterEnvelopeAmount(0.0) { // ... }
Synthesis::Reset
:
mFilterEnvelopeGenerator.setSampleRate(GetSampleRate());
EParams
, . AttachGraphics
:
// Knobs for filter envelope // Attack knob GetParam(mFilterAttack)->InitDouble("Filter Env Attack", 0.01, 0.01, 10.0, 0.001); GetParam(mFilterAttack)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 139, 178, mFilterAttack, &smallKnobBitmap)); // Decay knob: GetParam(mFilterDecay)->InitDouble("Filter Env Decay", 0.5, 0.01, 15.0, 0.001); GetParam(mFilterDecay)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 195, 178, mFilterDecay, &smallKnobBitmap)); // Sustain knob: GetParam(mFilterSustain)->InitDouble("Filter Env Sustain", 0.1, 0.001, 1.0, 0.001); GetParam(mFilterSustain)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 251, 178, mFilterSustain, &smallKnobBitmap)); // Release knob: GetParam(mFilterRelease)->InitDouble("Filter Env Release", 1.0, 0.001, 15.0, 0.001); GetParam(mFilterRelease)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 307, 178, mFilterRelease, &smallKnobBitmap)); // Filter envelope amount knob: GetParam(mFilterEnvelopeAmount)->InitDouble("Filter Env Amount", 0.0, -1.0, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 363, 178, mFilterEnvelopeAmount, &smallKnobBitmap));
, smallKnobBitmap
. . , . Synthesis::OnParamChange
switch
:
case mFilterAttack: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK, GetParam(paramIdx)->Value()); break; case mFilterDecay: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_DECAY, GetParam(paramIdx)->Value()); break; case mFilterSustain: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_SUSTAIN, GetParam(paramIdx)->Value()); break; case mFilterRelease: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE, GetParam(paramIdx)->Value()); break; case mFilterEnvelopeAmount: filterEnvelopeAmount = GetParam(paramIdx)->Value(); break;
!
! (- C1), :
EnvelopeGenerator
. . !
.
:)
:
martin-finke.de/blog/articles/audio-plugins-013-filter
#include Filter.h ( ) :
class Filter { public: enum FilterMode { FILTER_MODE_LOWPASS = 0, FILTER_MODE_HIGHPASS, FILTER_MODE_BANDPASS, kNumFilterModes }; Filter() : cutoff(0.99), resonance(0.0), mode(FILTER_MODE_LOWPASS), buf0(0.0), buf1(0.0) { calculateFeedbackAmount(); }; double process(double inputValue); inline void setCutoff(double newCutoff) { cutoff = newCutoff; calculateFeedbackAmount(); }; inline void setResonance(double newResonance) { resonance = newResonance; calculateFeedbackAmount(); }; inline void setFilterMode(FilterMode newMode) { mode = newMode; } private: double cutoff; double resonance; FilterMode mode; double feedbackAmount; inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - cutoff); } double buf0; double buf1; };
private
cutoff
resonance
. Mode
(Lowpass, Highpass, Bandpass). feedbackAmount
, buf0
buf1
, .
( calculateFeedbackAmount()
). process
. feedbackAmount
cutoff
resonance
, calculateFeedbackAmount
.
Filter.cpp :
// By Paul Kellett // http://www.musicdsp.org/showone.php?id=29 double Filter::process(double inputValue) { buf0 += cutoff * (inputValue - buf0); buf1 += cutoff * (buf0 - buf1); switch (mode) { case FILTER_MODE_LOWPASS: return buf1; case FILTER_MODE_HIGHPASS: return inputValue - buf0; case FILTER_MODE_BANDPASS: return buf0 - buf1; default: return 0.0; } }
, ? , . « » , (. . 6 ). buf0
buf1
: . inputValue
, - buf0
( ). . 6 / 12 /. switch
, buf1
. , , buf0
, 12, 6 /, .
case
FILTER_MODE_HIGHPASS
. buf0
. inputValue
buf0
, . buf1
, .
case
FILTER_MODE_BANDPASS
, buf0 - buf1
. buf0
, buf1
. , .
: , .
buf1
. ( Infinite Impulse Response , IIR ). , . , , .
, , . , , , , . , NI Massive, « ». , , . , ( Massive ). «» , , , AAS Ultra Analog, , , , . , , , , NI Reaktor. , — , .. .
. GUI. bg.png ( “Move to trash” Xcode), :
filtermode.png knob_small.png ( , 50 50 ) bg.png ( )
ID resource.h :
// Unique IDs for each image resource. #define BG_ID 101 #define WHITE_KEY_ID 102 #define BLACK_KEY_ID 103 #define WAVEFORM_ID 104 #define KNOB_ID 105 #define KNOB_SMALL_ID 106 #define FILTERMODE_ID 107 // Image resource locations for this plug. #define BG_FN "resources/img/bg.png" #define WHITE_KEY_FN "resources/img/whitekey.png" #define BLACK_KEY_FN "resources/img/blackkey.png" #define WAVEFORM_FN "resources/img/waveform.png" #define KNOB_FN "resources/img/knob.png" #define KNOB_SMALL_FN "resources/img/knob_small.png" #define FILTERMODE_FN "resources/img/filtermode.png"
Synthesis.rc :
#include "resource.h" BG_ID PNG BG_FN WHITE_KEY_ID PNG WHITE_KEY_FN BLACK_KEY_ID PNG BLACK_KEY_FN WAVEFORM_ID PNG WAVEFORM_FN KNOB_ID PNG KNOB_FN KNOB_SMALL_ID PNG KNOB_SMALL_FN FILTERMODE_ID PNG FILTERMODE_FN
#include "Filter.h"
Synthesis.h private
:
Filter mFilter;
EParams
Synthesis.cpp :
enum EParams { mWaveform = 0, mAttack, mDecay, mSustain, mRelease, mFilterMode, mFilterCutoff, mFilterResonance, mFilterAttack, mFilterDecay, mFilterSustain, mFilterRelease, mFilterEnvelopeAmount, kNumParams };
:
pGraphics->AttachControl(new ISwitchControl(this, 24, 38, mWaveform, &waveformBitmap));
. (Lowpass, Highpass, Bandpass). AttachGraphics(pGraphics)
:
GetParam(mFilterMode)->InitEnum("Filter Mode", Filter::FILTER_MODE_LOWPASS, Filter::kNumFilterModes); IBitmap filtermodeBitmap = pGraphics->LoadIBitmap(FILTERMODE_ID, FILTERMODE_FN, 3); pGraphics->AttachControl(new ISwitchControl(this, 24, 123, mFilterMode, &filtermodeBitmap));
. . knob_small.png :
// Knobs for filter cutoff and resonance IBitmap smallKnobBitmap = pGraphics->LoadIBitmap(KNOB_SMALL_ID, KNOB_SMALL_FN, 64); // Cutoff knob: GetParam(mFilterCutoff)->InitDouble("Cutoff", 0.99, 0.01, 0.99, 0.001); GetParam(mFilterCutoff)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 5, 177, mFilterCutoff, &smallKnobBitmap)); // Resonance knob: GetParam(mFilterResonance)->InitDouble("Resonance", 0.01, 0.01, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 61, 177, mFilterResonance, &smallKnobBitmap));
, 1.0
, calculateFeedbackAmount
, .
ProcessDoubleReplacing
:
leftOutput[i] = rightOutput[i] = mFilter.process(mOscillator.nextSample() * mEnvelopeGenerator.nextSample() * velocity / 127.0);
. switch
Synthesis::OnParamChange
:
case mFilterCutoff: mFilter.setCutoff(GetParam(paramIdx)->Value()); break; case mFilterResonance: mFilter.setResonance(GetParam(paramIdx)->Value()); break; case mFilterMode: mFilter.setFilterMode(static_cast<Filter::FilterMode>(GetParam(paramIdx)->Int())); break;
. - .
— . , , .
:
buf0 += cutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1));
, , ( buf0 – buf1
), feedbackAmount
. calculateFeedbackAmount
feedbackAmount
resonance
, . . , .
, . , , . , , .
-12 / -24 /
! 24 . switch
Filter::process
:
buf2 += cutoff * (buf1 - buf2); buf3 += cutoff * (buf2 – buf3);
: , , . , , , ( ). switch
:
switch (mode) { case FILTER_MODE_LOWPASS: return buf3; case FILTER_MODE_HIGHPASS: return inputValue - buf3; case FILTER_MODE_BANDPASS: return buf0 - buf3; default: return 0.0; }
buf1
buf3
– . -24 /. buf2
buf3
, private
Filter.h :
double buf2; double buf3;
, buf0
c buf1
:
Filter() : // ... buf0(0.0), buf1(0.0), buf2(0.0), buf3(0.0) // ...
, , : . .
?
. , , .
- cutoff
. . cutoffMod
, , cutoff
. #include private
:
double cutoffMod;
:
Filter() : cutoff(0.99), resonance(0.01), cutoffMod(0.0), // ...
. private
:
inline double getCalculatedCutoff() const { return fmax(fmin(cutoff + cutoffMod, 0.99), 0.01); };
calculateFeedbackAmount
:
inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - getCalculatedCutoff()); }
cutoffMod
. feedbackAmount
, :
inline void setCutoffMod(double newCutoffMod) { cutoffMod = newCutoffMod; calculateFeedbackAmount(); }
, . Filter::process
:
if (inputValue == 0.0) return inputValue; double calculatedCutoff = getCalculatedCutoff(); buf0 += calculatedCutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1)); buf1 += calculatedCutoff * (buf0 - buf1); buf2 += calculatedCutoff * (buf1 - buf2); buf3 += calculatedCutoff * (buf2 – buf3);
, . , , . . cutoff
calculatedCutoff
-.
, ( setCutoffMod
), Synthesis
, , . , cutoffMod
. filterEnvelopeAmount
-1
+1
. GUI.
private
Synthesis.h :
EnvelopeGenerator mFilterEnvelopeGenerator; double filterEnvelopeAmount;
MIDI , onNoteOn
onNoteOff
:
inline void onNoteOn(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); }; inline void onNoteOff(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); };
. ProcessDoubleReplacing
:
mFilter.setCutoffMod(mFilterEnvelopeGenerator.nextSample() * filterEnvelopeAmount);
, nextSample
filterEnvelopeAmount
cutoffMod
. filterEnvelopeAmount
:
Synthesis::Synthesis(IPlugInstanceInfo instanceInfo) : IPLUG_CTOR(kNumParams, kNumPrograms, instanceInfo), lastVirtualKeyboardNoteNumber(virtualKeyboardMinimumNoteNumber - 1), filterEnvelopeAmount(0.0) { // ... }
Synthesis::Reset
:
mFilterEnvelopeGenerator.setSampleRate(GetSampleRate());
EParams
, . AttachGraphics
:
// Knobs for filter envelope // Attack knob GetParam(mFilterAttack)->InitDouble("Filter Env Attack", 0.01, 0.01, 10.0, 0.001); GetParam(mFilterAttack)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 139, 178, mFilterAttack, &smallKnobBitmap)); // Decay knob: GetParam(mFilterDecay)->InitDouble("Filter Env Decay", 0.5, 0.01, 15.0, 0.001); GetParam(mFilterDecay)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 195, 178, mFilterDecay, &smallKnobBitmap)); // Sustain knob: GetParam(mFilterSustain)->InitDouble("Filter Env Sustain", 0.1, 0.001, 1.0, 0.001); GetParam(mFilterSustain)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 251, 178, mFilterSustain, &smallKnobBitmap)); // Release knob: GetParam(mFilterRelease)->InitDouble("Filter Env Release", 1.0, 0.001, 15.0, 0.001); GetParam(mFilterRelease)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 307, 178, mFilterRelease, &smallKnobBitmap)); // Filter envelope amount knob: GetParam(mFilterEnvelopeAmount)->InitDouble("Filter Env Amount", 0.0, -1.0, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 363, 178, mFilterEnvelopeAmount, &smallKnobBitmap));
, smallKnobBitmap
. . , . Synthesis::OnParamChange
switch
:
case mFilterAttack: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK, GetParam(paramIdx)->Value()); break; case mFilterDecay: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_DECAY, GetParam(paramIdx)->Value()); break; case mFilterSustain: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_SUSTAIN, GetParam(paramIdx)->Value()); break; case mFilterRelease: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE, GetParam(paramIdx)->Value()); break; case mFilterEnvelopeAmount: filterEnvelopeAmount = GetParam(paramIdx)->Value(); break;
!
! (- C1), :
EnvelopeGenerator
. . !
.
:)
:
martin-finke.de/blog/articles/audio-plugins-013-filter
#include Filter.h ( ) :
class Filter { public: enum FilterMode { FILTER_MODE_LOWPASS = 0, FILTER_MODE_HIGHPASS, FILTER_MODE_BANDPASS, kNumFilterModes }; Filter() : cutoff(0.99), resonance(0.0), mode(FILTER_MODE_LOWPASS), buf0(0.0), buf1(0.0) { calculateFeedbackAmount(); }; double process(double inputValue); inline void setCutoff(double newCutoff) { cutoff = newCutoff; calculateFeedbackAmount(); }; inline void setResonance(double newResonance) { resonance = newResonance; calculateFeedbackAmount(); }; inline void setFilterMode(FilterMode newMode) { mode = newMode; } private: double cutoff; double resonance; FilterMode mode; double feedbackAmount; inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - cutoff); } double buf0; double buf1; };
private
cutoff
resonance
. Mode
(Lowpass, Highpass, Bandpass). feedbackAmount
, buf0
buf1
, .
( calculateFeedbackAmount()
). process
. feedbackAmount
cutoff
resonance
, calculateFeedbackAmount
.
Filter.cpp :
// By Paul Kellett // http://www.musicdsp.org/showone.php?id=29 double Filter::process(double inputValue) { buf0 += cutoff * (inputValue - buf0); buf1 += cutoff * (buf0 - buf1); switch (mode) { case FILTER_MODE_LOWPASS: return buf1; case FILTER_MODE_HIGHPASS: return inputValue - buf0; case FILTER_MODE_BANDPASS: return buf0 - buf1; default: return 0.0; } }
, ? , . « » , (. . 6 ). buf0
buf1
: . inputValue
, - buf0
( ). . 6 / 12 /. switch
, buf1
. , , buf0
, 12, 6 /, .
case
FILTER_MODE_HIGHPASS
. buf0
. inputValue
buf0
, . buf1
, .
case
FILTER_MODE_BANDPASS
, buf0 - buf1
. buf0
, buf1
. , .
: , .
buf1
. ( Infinite Impulse Response , IIR ). , . , , .
, , . , , , , . , NI Massive, « ». , , . , ( Massive ). «» , , , AAS Ultra Analog, , , , . , , , , NI Reaktor. , — , .. .
. GUI. bg.png ( “Move to trash” Xcode), :
filtermode.png knob_small.png ( , 50 50 ) bg.png ( )
ID resource.h :
// Unique IDs for each image resource. #define BG_ID 101 #define WHITE_KEY_ID 102 #define BLACK_KEY_ID 103 #define WAVEFORM_ID 104 #define KNOB_ID 105 #define KNOB_SMALL_ID 106 #define FILTERMODE_ID 107 // Image resource locations for this plug. #define BG_FN "resources/img/bg.png" #define WHITE_KEY_FN "resources/img/whitekey.png" #define BLACK_KEY_FN "resources/img/blackkey.png" #define WAVEFORM_FN "resources/img/waveform.png" #define KNOB_FN "resources/img/knob.png" #define KNOB_SMALL_FN "resources/img/knob_small.png" #define FILTERMODE_FN "resources/img/filtermode.png"
Synthesis.rc :
#include "resource.h" BG_ID PNG BG_FN WHITE_KEY_ID PNG WHITE_KEY_FN BLACK_KEY_ID PNG BLACK_KEY_FN WAVEFORM_ID PNG WAVEFORM_FN KNOB_ID PNG KNOB_FN KNOB_SMALL_ID PNG KNOB_SMALL_FN FILTERMODE_ID PNG FILTERMODE_FN
#include "Filter.h"
Synthesis.h private
:
Filter mFilter;
EParams
Synthesis.cpp :
enum EParams { mWaveform = 0, mAttack, mDecay, mSustain, mRelease, mFilterMode, mFilterCutoff, mFilterResonance, mFilterAttack, mFilterDecay, mFilterSustain, mFilterRelease, mFilterEnvelopeAmount, kNumParams };
:
pGraphics->AttachControl(new ISwitchControl(this, 24, 38, mWaveform, &waveformBitmap));
. (Lowpass, Highpass, Bandpass). AttachGraphics(pGraphics)
:
GetParam(mFilterMode)->InitEnum("Filter Mode", Filter::FILTER_MODE_LOWPASS, Filter::kNumFilterModes); IBitmap filtermodeBitmap = pGraphics->LoadIBitmap(FILTERMODE_ID, FILTERMODE_FN, 3); pGraphics->AttachControl(new ISwitchControl(this, 24, 123, mFilterMode, &filtermodeBitmap));
. . knob_small.png :
// Knobs for filter cutoff and resonance IBitmap smallKnobBitmap = pGraphics->LoadIBitmap(KNOB_SMALL_ID, KNOB_SMALL_FN, 64); // Cutoff knob: GetParam(mFilterCutoff)->InitDouble("Cutoff", 0.99, 0.01, 0.99, 0.001); GetParam(mFilterCutoff)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 5, 177, mFilterCutoff, &smallKnobBitmap)); // Resonance knob: GetParam(mFilterResonance)->InitDouble("Resonance", 0.01, 0.01, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 61, 177, mFilterResonance, &smallKnobBitmap));
, 1.0
, calculateFeedbackAmount
, .
ProcessDoubleReplacing
:
leftOutput[i] = rightOutput[i] = mFilter.process(mOscillator.nextSample() * mEnvelopeGenerator.nextSample() * velocity / 127.0);
. switch
Synthesis::OnParamChange
:
case mFilterCutoff: mFilter.setCutoff(GetParam(paramIdx)->Value()); break; case mFilterResonance: mFilter.setResonance(GetParam(paramIdx)->Value()); break; case mFilterMode: mFilter.setFilterMode(static_cast<Filter::FilterMode>(GetParam(paramIdx)->Int())); break;
. - .
— . , , .
:
buf0 += cutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1));
, , ( buf0 – buf1
), feedbackAmount
. calculateFeedbackAmount
feedbackAmount
resonance
, . . , .
, . , , . , , .
-12 / -24 /
! 24 . switch
Filter::process
:
buf2 += cutoff * (buf1 - buf2); buf3 += cutoff * (buf2 – buf3);
: , , . , , , ( ). switch
:
switch (mode) { case FILTER_MODE_LOWPASS: return buf3; case FILTER_MODE_HIGHPASS: return inputValue - buf3; case FILTER_MODE_BANDPASS: return buf0 - buf3; default: return 0.0; }
buf1
buf3
– . -24 /. buf2
buf3
, private
Filter.h :
double buf2; double buf3;
, buf0
c buf1
:
Filter() : // ... buf0(0.0), buf1(0.0), buf2(0.0), buf3(0.0) // ...
, , : . .
?
. , , .
- cutoff
. . cutoffMod
, , cutoff
. #include private
:
double cutoffMod;
:
Filter() : cutoff(0.99), resonance(0.01), cutoffMod(0.0), // ...
. private
:
inline double getCalculatedCutoff() const { return fmax(fmin(cutoff + cutoffMod, 0.99), 0.01); };
calculateFeedbackAmount
:
inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - getCalculatedCutoff()); }
cutoffMod
. feedbackAmount
, :
inline void setCutoffMod(double newCutoffMod) { cutoffMod = newCutoffMod; calculateFeedbackAmount(); }
, . Filter::process
:
if (inputValue == 0.0) return inputValue; double calculatedCutoff = getCalculatedCutoff(); buf0 += calculatedCutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1)); buf1 += calculatedCutoff * (buf0 - buf1); buf2 += calculatedCutoff * (buf1 - buf2); buf3 += calculatedCutoff * (buf2 – buf3);
, . , , . . cutoff
calculatedCutoff
-.
, ( setCutoffMod
), Synthesis
, , . , cutoffMod
. filterEnvelopeAmount
-1
+1
. GUI.
private
Synthesis.h :
EnvelopeGenerator mFilterEnvelopeGenerator; double filterEnvelopeAmount;
MIDI , onNoteOn
onNoteOff
:
inline void onNoteOn(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); }; inline void onNoteOff(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); };
. ProcessDoubleReplacing
:
mFilter.setCutoffMod(mFilterEnvelopeGenerator.nextSample() * filterEnvelopeAmount);
, nextSample
filterEnvelopeAmount
cutoffMod
. filterEnvelopeAmount
:
Synthesis::Synthesis(IPlugInstanceInfo instanceInfo) : IPLUG_CTOR(kNumParams, kNumPrograms, instanceInfo), lastVirtualKeyboardNoteNumber(virtualKeyboardMinimumNoteNumber - 1), filterEnvelopeAmount(0.0) { // ... }
Synthesis::Reset
:
mFilterEnvelopeGenerator.setSampleRate(GetSampleRate());
EParams
, . AttachGraphics
:
// Knobs for filter envelope // Attack knob GetParam(mFilterAttack)->InitDouble("Filter Env Attack", 0.01, 0.01, 10.0, 0.001); GetParam(mFilterAttack)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 139, 178, mFilterAttack, &smallKnobBitmap)); // Decay knob: GetParam(mFilterDecay)->InitDouble("Filter Env Decay", 0.5, 0.01, 15.0, 0.001); GetParam(mFilterDecay)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 195, 178, mFilterDecay, &smallKnobBitmap)); // Sustain knob: GetParam(mFilterSustain)->InitDouble("Filter Env Sustain", 0.1, 0.001, 1.0, 0.001); GetParam(mFilterSustain)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 251, 178, mFilterSustain, &smallKnobBitmap)); // Release knob: GetParam(mFilterRelease)->InitDouble("Filter Env Release", 1.0, 0.001, 15.0, 0.001); GetParam(mFilterRelease)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 307, 178, mFilterRelease, &smallKnobBitmap)); // Filter envelope amount knob: GetParam(mFilterEnvelopeAmount)->InitDouble("Filter Env Amount", 0.0, -1.0, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 363, 178, mFilterEnvelopeAmount, &smallKnobBitmap));
, smallKnobBitmap
. . , . Synthesis::OnParamChange
switch
:
case mFilterAttack: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK, GetParam(paramIdx)->Value()); break; case mFilterDecay: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_DECAY, GetParam(paramIdx)->Value()); break; case mFilterSustain: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_SUSTAIN, GetParam(paramIdx)->Value()); break; case mFilterRelease: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE, GetParam(paramIdx)->Value()); break; case mFilterEnvelopeAmount: filterEnvelopeAmount = GetParam(paramIdx)->Value(); break;
!
! (- C1), :
EnvelopeGenerator
. . !
.
:)
:
martin-finke.de/blog/articles/audio-plugins-013-filter
#include Filter.h ( ) :
class Filter { public: enum FilterMode { FILTER_MODE_LOWPASS = 0, FILTER_MODE_HIGHPASS, FILTER_MODE_BANDPASS, kNumFilterModes }; Filter() : cutoff(0.99), resonance(0.0), mode(FILTER_MODE_LOWPASS), buf0(0.0), buf1(0.0) { calculateFeedbackAmount(); }; double process(double inputValue); inline void setCutoff(double newCutoff) { cutoff = newCutoff; calculateFeedbackAmount(); }; inline void setResonance(double newResonance) { resonance = newResonance; calculateFeedbackAmount(); }; inline void setFilterMode(FilterMode newMode) { mode = newMode; } private: double cutoff; double resonance; FilterMode mode; double feedbackAmount; inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - cutoff); } double buf0; double buf1; };
private
cutoff
resonance
. Mode
(Lowpass, Highpass, Bandpass). feedbackAmount
, buf0
buf1
, .
( calculateFeedbackAmount()
). process
. feedbackAmount
cutoff
resonance
, calculateFeedbackAmount
.
Filter.cpp :
// By Paul Kellett // http://www.musicdsp.org/showone.php?id=29 double Filter::process(double inputValue) { buf0 += cutoff * (inputValue - buf0); buf1 += cutoff * (buf0 - buf1); switch (mode) { case FILTER_MODE_LOWPASS: return buf1; case FILTER_MODE_HIGHPASS: return inputValue - buf0; case FILTER_MODE_BANDPASS: return buf0 - buf1; default: return 0.0; } }
, ? , . « » , (. . 6 ). buf0
buf1
: . inputValue
, - buf0
( ). . 6 / 12 /. switch
, buf1
. , , buf0
, 12, 6 /, .
case
FILTER_MODE_HIGHPASS
. buf0
. inputValue
buf0
, . buf1
, .
case
FILTER_MODE_BANDPASS
, buf0 - buf1
. buf0
, buf1
. , .
: , .
buf1
. ( Infinite Impulse Response , IIR ). , . , , .
, , . , , , , . , NI Massive, « ». , , . , ( Massive ). «» , , , AAS Ultra Analog, , , , . , , , , NI Reaktor. , — , .. .
. GUI. bg.png ( “Move to trash” Xcode), :
filtermode.png knob_small.png ( , 50 50 ) bg.png ( )
ID resource.h :
// Unique IDs for each image resource. #define BG_ID 101 #define WHITE_KEY_ID 102 #define BLACK_KEY_ID 103 #define WAVEFORM_ID 104 #define KNOB_ID 105 #define KNOB_SMALL_ID 106 #define FILTERMODE_ID 107 // Image resource locations for this plug. #define BG_FN "resources/img/bg.png" #define WHITE_KEY_FN "resources/img/whitekey.png" #define BLACK_KEY_FN "resources/img/blackkey.png" #define WAVEFORM_FN "resources/img/waveform.png" #define KNOB_FN "resources/img/knob.png" #define KNOB_SMALL_FN "resources/img/knob_small.png" #define FILTERMODE_FN "resources/img/filtermode.png"
Synthesis.rc :
#include "resource.h" BG_ID PNG BG_FN WHITE_KEY_ID PNG WHITE_KEY_FN BLACK_KEY_ID PNG BLACK_KEY_FN WAVEFORM_ID PNG WAVEFORM_FN KNOB_ID PNG KNOB_FN KNOB_SMALL_ID PNG KNOB_SMALL_FN FILTERMODE_ID PNG FILTERMODE_FN
#include "Filter.h"
Synthesis.h private
:
Filter mFilter;
EParams
Synthesis.cpp :
enum EParams { mWaveform = 0, mAttack, mDecay, mSustain, mRelease, mFilterMode, mFilterCutoff, mFilterResonance, mFilterAttack, mFilterDecay, mFilterSustain, mFilterRelease, mFilterEnvelopeAmount, kNumParams };
:
pGraphics->AttachControl(new ISwitchControl(this, 24, 38, mWaveform, &waveformBitmap));
. (Lowpass, Highpass, Bandpass). AttachGraphics(pGraphics)
:
GetParam(mFilterMode)->InitEnum("Filter Mode", Filter::FILTER_MODE_LOWPASS, Filter::kNumFilterModes); IBitmap filtermodeBitmap = pGraphics->LoadIBitmap(FILTERMODE_ID, FILTERMODE_FN, 3); pGraphics->AttachControl(new ISwitchControl(this, 24, 123, mFilterMode, &filtermodeBitmap));
. . knob_small.png :
// Knobs for filter cutoff and resonance IBitmap smallKnobBitmap = pGraphics->LoadIBitmap(KNOB_SMALL_ID, KNOB_SMALL_FN, 64); // Cutoff knob: GetParam(mFilterCutoff)->InitDouble("Cutoff", 0.99, 0.01, 0.99, 0.001); GetParam(mFilterCutoff)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 5, 177, mFilterCutoff, &smallKnobBitmap)); // Resonance knob: GetParam(mFilterResonance)->InitDouble("Resonance", 0.01, 0.01, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 61, 177, mFilterResonance, &smallKnobBitmap));
, 1.0
, calculateFeedbackAmount
, .
ProcessDoubleReplacing
:
leftOutput[i] = rightOutput[i] = mFilter.process(mOscillator.nextSample() * mEnvelopeGenerator.nextSample() * velocity / 127.0);
. switch
Synthesis::OnParamChange
:
case mFilterCutoff: mFilter.setCutoff(GetParam(paramIdx)->Value()); break; case mFilterResonance: mFilter.setResonance(GetParam(paramIdx)->Value()); break; case mFilterMode: mFilter.setFilterMode(static_cast<Filter::FilterMode>(GetParam(paramIdx)->Int())); break;
. - .
— . , , .
:
buf0 += cutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1));
, , ( buf0 – buf1
), feedbackAmount
. calculateFeedbackAmount
feedbackAmount
resonance
, . . , .
, . , , . , , .
-12 / -24 /
! 24 . switch
Filter::process
:
buf2 += cutoff * (buf1 - buf2); buf3 += cutoff * (buf2 – buf3);
: , , . , , , ( ). switch
:
switch (mode) { case FILTER_MODE_LOWPASS: return buf3; case FILTER_MODE_HIGHPASS: return inputValue - buf3; case FILTER_MODE_BANDPASS: return buf0 - buf3; default: return 0.0; }
buf1
buf3
– . -24 /. buf2
buf3
, private
Filter.h :
double buf2; double buf3;
, buf0
c buf1
:
Filter() : // ... buf0(0.0), buf1(0.0), buf2(0.0), buf3(0.0) // ...
, , : . .
?
. , , .
- cutoff
. . cutoffMod
, , cutoff
. #include private
:
double cutoffMod;
:
Filter() : cutoff(0.99), resonance(0.01), cutoffMod(0.0), // ...
. private
:
inline double getCalculatedCutoff() const { return fmax(fmin(cutoff + cutoffMod, 0.99), 0.01); };
calculateFeedbackAmount
:
inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - getCalculatedCutoff()); }
cutoffMod
. feedbackAmount
, :
inline void setCutoffMod(double newCutoffMod) { cutoffMod = newCutoffMod; calculateFeedbackAmount(); }
, . Filter::process
:
if (inputValue == 0.0) return inputValue; double calculatedCutoff = getCalculatedCutoff(); buf0 += calculatedCutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1)); buf1 += calculatedCutoff * (buf0 - buf1); buf2 += calculatedCutoff * (buf1 - buf2); buf3 += calculatedCutoff * (buf2 – buf3);
, . , , . . cutoff
calculatedCutoff
-.
, ( setCutoffMod
), Synthesis
, , . , cutoffMod
. filterEnvelopeAmount
-1
+1
. GUI.
private
Synthesis.h :
EnvelopeGenerator mFilterEnvelopeGenerator; double filterEnvelopeAmount;
MIDI , onNoteOn
onNoteOff
:
inline void onNoteOn(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); }; inline void onNoteOff(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); };
. ProcessDoubleReplacing
:
mFilter.setCutoffMod(mFilterEnvelopeGenerator.nextSample() * filterEnvelopeAmount);
, nextSample
filterEnvelopeAmount
cutoffMod
. filterEnvelopeAmount
:
Synthesis::Synthesis(IPlugInstanceInfo instanceInfo) : IPLUG_CTOR(kNumParams, kNumPrograms, instanceInfo), lastVirtualKeyboardNoteNumber(virtualKeyboardMinimumNoteNumber - 1), filterEnvelopeAmount(0.0) { // ... }
Synthesis::Reset
:
mFilterEnvelopeGenerator.setSampleRate(GetSampleRate());
EParams
, . AttachGraphics
:
// Knobs for filter envelope // Attack knob GetParam(mFilterAttack)->InitDouble("Filter Env Attack", 0.01, 0.01, 10.0, 0.001); GetParam(mFilterAttack)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 139, 178, mFilterAttack, &smallKnobBitmap)); // Decay knob: GetParam(mFilterDecay)->InitDouble("Filter Env Decay", 0.5, 0.01, 15.0, 0.001); GetParam(mFilterDecay)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 195, 178, mFilterDecay, &smallKnobBitmap)); // Sustain knob: GetParam(mFilterSustain)->InitDouble("Filter Env Sustain", 0.1, 0.001, 1.0, 0.001); GetParam(mFilterSustain)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 251, 178, mFilterSustain, &smallKnobBitmap)); // Release knob: GetParam(mFilterRelease)->InitDouble("Filter Env Release", 1.0, 0.001, 15.0, 0.001); GetParam(mFilterRelease)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 307, 178, mFilterRelease, &smallKnobBitmap)); // Filter envelope amount knob: GetParam(mFilterEnvelopeAmount)->InitDouble("Filter Env Amount", 0.0, -1.0, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 363, 178, mFilterEnvelopeAmount, &smallKnobBitmap));
, smallKnobBitmap
. . , . Synthesis::OnParamChange
switch
:
case mFilterAttack: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK, GetParam(paramIdx)->Value()); break; case mFilterDecay: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_DECAY, GetParam(paramIdx)->Value()); break; case mFilterSustain: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_SUSTAIN, GetParam(paramIdx)->Value()); break; case mFilterRelease: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE, GetParam(paramIdx)->Value()); break; case mFilterEnvelopeAmount: filterEnvelopeAmount = GetParam(paramIdx)->Value(); break;
!
! (- C1), :
EnvelopeGenerator
. . !
.
:)
:
martin-finke.de/blog/articles/audio-plugins-013-filter
#include Filter.h ( ) :
class Filter { public: enum FilterMode { FILTER_MODE_LOWPASS = 0, FILTER_MODE_HIGHPASS, FILTER_MODE_BANDPASS, kNumFilterModes }; Filter() : cutoff(0.99), resonance(0.0), mode(FILTER_MODE_LOWPASS), buf0(0.0), buf1(0.0) { calculateFeedbackAmount(); }; double process(double inputValue); inline void setCutoff(double newCutoff) { cutoff = newCutoff; calculateFeedbackAmount(); }; inline void setResonance(double newResonance) { resonance = newResonance; calculateFeedbackAmount(); }; inline void setFilterMode(FilterMode newMode) { mode = newMode; } private: double cutoff; double resonance; FilterMode mode; double feedbackAmount; inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - cutoff); } double buf0; double buf1; };
private
cutoff
resonance
.
Mode
(Lowpass, Highpass, Bandpass).
feedbackAmount
,
buf0
buf1
, .
(
calculateFeedbackAmount()
).
process
.
feedbackAmount
cutoff
resonance
,
calculateFeedbackAmount
.
Filter.cpp :
// By Paul Kellett // http://www.musicdsp.org/showone.php?id=29 double Filter::process(double inputValue) { buf0 += cutoff * (inputValue - buf0); buf1 += cutoff * (buf0 - buf1); switch (mode) { case FILTER_MODE_LOWPASS: return buf1; case FILTER_MODE_HIGHPASS: return inputValue - buf0; case FILTER_MODE_BANDPASS: return buf0 - buf1; default: return 0.0; } }
, ? , . « » , (. . 6 ).
buf0
buf1
: .
inputValue
, -
buf0
( ). . 6 / 12 /.
switch
,
buf1
. , ,
buf0
, 12, 6 /, .
case
FILTER_MODE_HIGHPASS
.
buf0
.
inputValue
buf0
, .
buf1
, .
case
FILTER_MODE_BANDPASS
,
buf0 - buf1
.
buf0
,
buf1
. , .
: , .
buf1
. ( Infinite Impulse Response , IIR ). , . , , .
, , . , , , , . , NI Massive, « ». , , . , ( Massive ). «» , , , AAS Ultra Analog, , , , . , , , , NI Reaktor. , — , .. .
. GUI. bg.png ( “Move to trash” Xcode), :
filtermode.png knob_small.png ( , 50 50 ) bg.png ( )
ID resource.h :
// Unique IDs for each image resource. #define BG_ID 101 #define WHITE_KEY_ID 102 #define BLACK_KEY_ID 103 #define WAVEFORM_ID 104 #define KNOB_ID 105 #define KNOB_SMALL_ID 106 #define FILTERMODE_ID 107 // Image resource locations for this plug. #define BG_FN "resources/img/bg.png" #define WHITE_KEY_FN "resources/img/whitekey.png" #define BLACK_KEY_FN "resources/img/blackkey.png" #define WAVEFORM_FN "resources/img/waveform.png" #define KNOB_FN "resources/img/knob.png" #define KNOB_SMALL_FN "resources/img/knob_small.png" #define FILTERMODE_FN "resources/img/filtermode.png"
Synthesis.rc :
#include "resource.h" BG_ID PNG BG_FN WHITE_KEY_ID PNG WHITE_KEY_FN BLACK_KEY_ID PNG BLACK_KEY_FN WAVEFORM_ID PNG WAVEFORM_FN KNOB_ID PNG KNOB_FN KNOB_SMALL_ID PNG KNOB_SMALL_FN FILTERMODE_ID PNG FILTERMODE_FN
#include "Filter.h"
Synthesis.h
private
:
Filter mFilter;
EParams
Synthesis.cpp :
enum EParams { mWaveform = 0, mAttack, mDecay, mSustain, mRelease, mFilterMode, mFilterCutoff, mFilterResonance, mFilterAttack, mFilterDecay, mFilterSustain, mFilterRelease, mFilterEnvelopeAmount, kNumParams };
:
pGraphics->AttachControl(new ISwitchControl(this, 24, 38, mWaveform, &waveformBitmap));
. (Lowpass, Highpass, Bandpass).
AttachGraphics(pGraphics)
:
GetParam(mFilterMode)->InitEnum("Filter Mode", Filter::FILTER_MODE_LOWPASS, Filter::kNumFilterModes); IBitmap filtermodeBitmap = pGraphics->LoadIBitmap(FILTERMODE_ID, FILTERMODE_FN, 3); pGraphics->AttachControl(new ISwitchControl(this, 24, 123, mFilterMode, &filtermodeBitmap));
. . knob_small.png :
// Knobs for filter cutoff and resonance IBitmap smallKnobBitmap = pGraphics->LoadIBitmap(KNOB_SMALL_ID, KNOB_SMALL_FN, 64); // Cutoff knob: GetParam(mFilterCutoff)->InitDouble("Cutoff", 0.99, 0.01, 0.99, 0.001); GetParam(mFilterCutoff)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 5, 177, mFilterCutoff, &smallKnobBitmap)); // Resonance knob: GetParam(mFilterResonance)->InitDouble("Resonance", 0.01, 0.01, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 61, 177, mFilterResonance, &smallKnobBitmap));
,
1.0
,
calculateFeedbackAmount
, .
ProcessDoubleReplacing
:
leftOutput[i] = rightOutput[i] = mFilter.process(mOscillator.nextSample() * mEnvelopeGenerator.nextSample() * velocity / 127.0);
.
switch
Synthesis::OnParamChange
:
case mFilterCutoff: mFilter.setCutoff(GetParam(paramIdx)->Value()); break; case mFilterResonance: mFilter.setResonance(GetParam(paramIdx)->Value()); break; case mFilterMode: mFilter.setFilterMode(static_cast<Filter::FilterMode>(GetParam(paramIdx)->Int())); break;
. - .
— . , , .
:
buf0 += cutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1));
, , (
buf0 – buf1
),
feedbackAmount
.
calculateFeedbackAmount
feedbackAmount
resonance
, . . , .
, . , , . , , .
-12 / -24 /
! 24 .
switch
Filter::process
:
buf2 += cutoff * (buf1 - buf2); buf3 += cutoff * (buf2 – buf3);
: , , . , , , ( ).
switch
:
switch (mode) { case FILTER_MODE_LOWPASS: return buf3; case FILTER_MODE_HIGHPASS: return inputValue - buf3; case FILTER_MODE_BANDPASS: return buf0 - buf3; default: return 0.0; }
buf1
buf3
– . -24 /.
buf2
buf3
,
private
Filter.h :
double buf2; double buf3;
,
buf0
c
buf1
:
Filter() : // ... buf0(0.0), buf1(0.0), buf2(0.0), buf3(0.0) // ...
, , : . .
?
. , , .
-
cutoff
. .
cutoffMod
, ,
cutoff
.
#include private
:
double cutoffMod;
:
Filter() : cutoff(0.99), resonance(0.01), cutoffMod(0.0), // ...
. private
:
inline double getCalculatedCutoff() const { return fmax(fmin(cutoff + cutoffMod, 0.99), 0.01); };
calculateFeedbackAmount
:
inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - getCalculatedCutoff()); }
cutoffMod
. feedbackAmount
, :
inline void setCutoffMod(double newCutoffMod) { cutoffMod = newCutoffMod; calculateFeedbackAmount(); }
, . Filter::process
:
if (inputValue == 0.0) return inputValue; double calculatedCutoff = getCalculatedCutoff(); buf0 += calculatedCutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1)); buf1 += calculatedCutoff * (buf0 - buf1); buf2 += calculatedCutoff * (buf1 - buf2); buf3 += calculatedCutoff * (buf2 – buf3);
, . , , . . cutoff
calculatedCutoff
-.
, ( setCutoffMod
), Synthesis
, , . , cutoffMod
. filterEnvelopeAmount
-1
+1
. GUI.
private
Synthesis.h :
EnvelopeGenerator mFilterEnvelopeGenerator; double filterEnvelopeAmount;
MIDI , onNoteOn
onNoteOff
:
inline void onNoteOn(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); }; inline void onNoteOff(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); };
. ProcessDoubleReplacing
:
mFilter.setCutoffMod(mFilterEnvelopeGenerator.nextSample() * filterEnvelopeAmount);
, nextSample
filterEnvelopeAmount
cutoffMod
. filterEnvelopeAmount
:
Synthesis::Synthesis(IPlugInstanceInfo instanceInfo) : IPLUG_CTOR(kNumParams, kNumPrograms, instanceInfo), lastVirtualKeyboardNoteNumber(virtualKeyboardMinimumNoteNumber - 1), filterEnvelopeAmount(0.0) { // ... }
Synthesis::Reset
:
mFilterEnvelopeGenerator.setSampleRate(GetSampleRate());
EParams
, . AttachGraphics
:
// Knobs for filter envelope // Attack knob GetParam(mFilterAttack)->InitDouble("Filter Env Attack", 0.01, 0.01, 10.0, 0.001); GetParam(mFilterAttack)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 139, 178, mFilterAttack, &smallKnobBitmap)); // Decay knob: GetParam(mFilterDecay)->InitDouble("Filter Env Decay", 0.5, 0.01, 15.0, 0.001); GetParam(mFilterDecay)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 195, 178, mFilterDecay, &smallKnobBitmap)); // Sustain knob: GetParam(mFilterSustain)->InitDouble("Filter Env Sustain", 0.1, 0.001, 1.0, 0.001); GetParam(mFilterSustain)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 251, 178, mFilterSustain, &smallKnobBitmap)); // Release knob: GetParam(mFilterRelease)->InitDouble("Filter Env Release", 1.0, 0.001, 15.0, 0.001); GetParam(mFilterRelease)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 307, 178, mFilterRelease, &smallKnobBitmap)); // Filter envelope amount knob: GetParam(mFilterEnvelopeAmount)->InitDouble("Filter Env Amount", 0.0, -1.0, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 363, 178, mFilterEnvelopeAmount, &smallKnobBitmap));
, smallKnobBitmap
. . , . Synthesis::OnParamChange
switch
:
case mFilterAttack: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK, GetParam(paramIdx)->Value()); break; case mFilterDecay: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_DECAY, GetParam(paramIdx)->Value()); break; case mFilterSustain: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_SUSTAIN, GetParam(paramIdx)->Value()); break; case mFilterRelease: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE, GetParam(paramIdx)->Value()); break; case mFilterEnvelopeAmount: filterEnvelopeAmount = GetParam(paramIdx)->Value(); break;
!
! (- C1), :
EnvelopeGenerator
. . !
.
:)
:
martin-finke.de/blog/articles/audio-plugins-013-filter
#include Filter.h ( ) :
class Filter { public: enum FilterMode { FILTER_MODE_LOWPASS = 0, FILTER_MODE_HIGHPASS, FILTER_MODE_BANDPASS, kNumFilterModes }; Filter() : cutoff(0.99), resonance(0.0), mode(FILTER_MODE_LOWPASS), buf0(0.0), buf1(0.0) { calculateFeedbackAmount(); }; double process(double inputValue); inline void setCutoff(double newCutoff) { cutoff = newCutoff; calculateFeedbackAmount(); }; inline void setResonance(double newResonance) { resonance = newResonance; calculateFeedbackAmount(); }; inline void setFilterMode(FilterMode newMode) { mode = newMode; } private: double cutoff; double resonance; FilterMode mode; double feedbackAmount; inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - cutoff); } double buf0; double buf1; };
private
cutoff
resonance
. Mode
(Lowpass, Highpass, Bandpass). feedbackAmount
, buf0
buf1
, .
( calculateFeedbackAmount()
). process
. feedbackAmount
cutoff
resonance
, calculateFeedbackAmount
.
Filter.cpp :
// By Paul Kellett // http://www.musicdsp.org/showone.php?id=29 double Filter::process(double inputValue) { buf0 += cutoff * (inputValue - buf0); buf1 += cutoff * (buf0 - buf1); switch (mode) { case FILTER_MODE_LOWPASS: return buf1; case FILTER_MODE_HIGHPASS: return inputValue - buf0; case FILTER_MODE_BANDPASS: return buf0 - buf1; default: return 0.0; } }
, ? , . « » , (. . 6 ). buf0
buf1
: . inputValue
, - buf0
( ). . 6 / 12 /. switch
, buf1
. , , buf0
, 12, 6 /, .
case
FILTER_MODE_HIGHPASS
. buf0
. inputValue
buf0
, . buf1
, .
case
FILTER_MODE_BANDPASS
, buf0 - buf1
. buf0
, buf1
. , .
: , .
buf1
. ( Infinite Impulse Response , IIR ). , . , , .
, , . , , , , . , NI Massive, « ». , , . , ( Massive ). «» , , , AAS Ultra Analog, , , , . , , , , NI Reaktor. , — , .. .
. GUI. bg.png ( “Move to trash” Xcode), :
filtermode.png knob_small.png ( , 50 50 ) bg.png ( )
ID resource.h :
// Unique IDs for each image resource. #define BG_ID 101 #define WHITE_KEY_ID 102 #define BLACK_KEY_ID 103 #define WAVEFORM_ID 104 #define KNOB_ID 105 #define KNOB_SMALL_ID 106 #define FILTERMODE_ID 107 // Image resource locations for this plug. #define BG_FN "resources/img/bg.png" #define WHITE_KEY_FN "resources/img/whitekey.png" #define BLACK_KEY_FN "resources/img/blackkey.png" #define WAVEFORM_FN "resources/img/waveform.png" #define KNOB_FN "resources/img/knob.png" #define KNOB_SMALL_FN "resources/img/knob_small.png" #define FILTERMODE_FN "resources/img/filtermode.png"
Synthesis.rc :
#include "resource.h" BG_ID PNG BG_FN WHITE_KEY_ID PNG WHITE_KEY_FN BLACK_KEY_ID PNG BLACK_KEY_FN WAVEFORM_ID PNG WAVEFORM_FN KNOB_ID PNG KNOB_FN KNOB_SMALL_ID PNG KNOB_SMALL_FN FILTERMODE_ID PNG FILTERMODE_FN
#include "Filter.h"
Synthesis.h private
:
Filter mFilter;
EParams
Synthesis.cpp :
enum EParams { mWaveform = 0, mAttack, mDecay, mSustain, mRelease, mFilterMode, mFilterCutoff, mFilterResonance, mFilterAttack, mFilterDecay, mFilterSustain, mFilterRelease, mFilterEnvelopeAmount, kNumParams };
:
pGraphics->AttachControl(new ISwitchControl(this, 24, 38, mWaveform, &waveformBitmap));
. (Lowpass, Highpass, Bandpass). AttachGraphics(pGraphics)
:
GetParam(mFilterMode)->InitEnum("Filter Mode", Filter::FILTER_MODE_LOWPASS, Filter::kNumFilterModes); IBitmap filtermodeBitmap = pGraphics->LoadIBitmap(FILTERMODE_ID, FILTERMODE_FN, 3); pGraphics->AttachControl(new ISwitchControl(this, 24, 123, mFilterMode, &filtermodeBitmap));
. . knob_small.png :
// Knobs for filter cutoff and resonance IBitmap smallKnobBitmap = pGraphics->LoadIBitmap(KNOB_SMALL_ID, KNOB_SMALL_FN, 64); // Cutoff knob: GetParam(mFilterCutoff)->InitDouble("Cutoff", 0.99, 0.01, 0.99, 0.001); GetParam(mFilterCutoff)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 5, 177, mFilterCutoff, &smallKnobBitmap)); // Resonance knob: GetParam(mFilterResonance)->InitDouble("Resonance", 0.01, 0.01, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 61, 177, mFilterResonance, &smallKnobBitmap));
, 1.0
, calculateFeedbackAmount
, .
ProcessDoubleReplacing
:
leftOutput[i] = rightOutput[i] = mFilter.process(mOscillator.nextSample() * mEnvelopeGenerator.nextSample() * velocity / 127.0);
. switch
Synthesis::OnParamChange
:
case mFilterCutoff: mFilter.setCutoff(GetParam(paramIdx)->Value()); break; case mFilterResonance: mFilter.setResonance(GetParam(paramIdx)->Value()); break; case mFilterMode: mFilter.setFilterMode(static_cast<Filter::FilterMode>(GetParam(paramIdx)->Int())); break;
. - .
— . , , .
:
buf0 += cutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1));
, , ( buf0 – buf1
), feedbackAmount
. calculateFeedbackAmount
feedbackAmount
resonance
, . . , .
, . , , . , , .
-12 / -24 /
! 24 . switch
Filter::process
:
buf2 += cutoff * (buf1 - buf2); buf3 += cutoff * (buf2 – buf3);
: , , . , , , ( ). switch
:
switch (mode) { case FILTER_MODE_LOWPASS: return buf3; case FILTER_MODE_HIGHPASS: return inputValue - buf3; case FILTER_MODE_BANDPASS: return buf0 - buf3; default: return 0.0; }
buf1
buf3
– . -24 /. buf2
buf3
, private
Filter.h :
double buf2; double buf3;
, buf0
c buf1
:
Filter() : // ... buf0(0.0), buf1(0.0), buf2(0.0), buf3(0.0) // ...
, , : . .
?
. , , .
- cutoff
. . cutoffMod
, , cutoff
. #include private
:
double cutoffMod;
:
Filter() : cutoff(0.99), resonance(0.01), cutoffMod(0.0), // ...
. private
:
inline double getCalculatedCutoff() const { return fmax(fmin(cutoff + cutoffMod, 0.99), 0.01); };
calculateFeedbackAmount
:
inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - getCalculatedCutoff()); }
cutoffMod
. feedbackAmount
, :
inline void setCutoffMod(double newCutoffMod) { cutoffMod = newCutoffMod; calculateFeedbackAmount(); }
, . Filter::process
:
if (inputValue == 0.0) return inputValue; double calculatedCutoff = getCalculatedCutoff(); buf0 += calculatedCutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1)); buf1 += calculatedCutoff * (buf0 - buf1); buf2 += calculatedCutoff * (buf1 - buf2); buf3 += calculatedCutoff * (buf2 – buf3);
, . , , . . cutoff
calculatedCutoff
-.
, ( setCutoffMod
), Synthesis
, , . , cutoffMod
. filterEnvelopeAmount
-1
+1
. GUI.
private
Synthesis.h :
EnvelopeGenerator mFilterEnvelopeGenerator; double filterEnvelopeAmount;
MIDI , onNoteOn
onNoteOff
:
inline void onNoteOn(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); }; inline void onNoteOff(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); };
. ProcessDoubleReplacing
:
mFilter.setCutoffMod(mFilterEnvelopeGenerator.nextSample() * filterEnvelopeAmount);
, nextSample
filterEnvelopeAmount
cutoffMod
. filterEnvelopeAmount
:
Synthesis::Synthesis(IPlugInstanceInfo instanceInfo) : IPLUG_CTOR(kNumParams, kNumPrograms, instanceInfo), lastVirtualKeyboardNoteNumber(virtualKeyboardMinimumNoteNumber - 1), filterEnvelopeAmount(0.0) { // ... }
Synthesis::Reset
:
mFilterEnvelopeGenerator.setSampleRate(GetSampleRate());
EParams
, . AttachGraphics
:
// Knobs for filter envelope // Attack knob GetParam(mFilterAttack)->InitDouble("Filter Env Attack", 0.01, 0.01, 10.0, 0.001); GetParam(mFilterAttack)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 139, 178, mFilterAttack, &smallKnobBitmap)); // Decay knob: GetParam(mFilterDecay)->InitDouble("Filter Env Decay", 0.5, 0.01, 15.0, 0.001); GetParam(mFilterDecay)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 195, 178, mFilterDecay, &smallKnobBitmap)); // Sustain knob: GetParam(mFilterSustain)->InitDouble("Filter Env Sustain", 0.1, 0.001, 1.0, 0.001); GetParam(mFilterSustain)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 251, 178, mFilterSustain, &smallKnobBitmap)); // Release knob: GetParam(mFilterRelease)->InitDouble("Filter Env Release", 1.0, 0.001, 15.0, 0.001); GetParam(mFilterRelease)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 307, 178, mFilterRelease, &smallKnobBitmap)); // Filter envelope amount knob: GetParam(mFilterEnvelopeAmount)->InitDouble("Filter Env Amount", 0.0, -1.0, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 363, 178, mFilterEnvelopeAmount, &smallKnobBitmap));
, smallKnobBitmap
. . , . Synthesis::OnParamChange
switch
:
case mFilterAttack: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK, GetParam(paramIdx)->Value()); break; case mFilterDecay: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_DECAY, GetParam(paramIdx)->Value()); break; case mFilterSustain: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_SUSTAIN, GetParam(paramIdx)->Value()); break; case mFilterRelease: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE, GetParam(paramIdx)->Value()); break; case mFilterEnvelopeAmount: filterEnvelopeAmount = GetParam(paramIdx)->Value(); break;
!
! (- C1), :
EnvelopeGenerator
. . !
.
:)
:
martin-finke.de/blog/articles/audio-plugins-013-filter
#include Filter.h ( ) :
class Filter { public: enum FilterMode { FILTER_MODE_LOWPASS = 0, FILTER_MODE_HIGHPASS, FILTER_MODE_BANDPASS, kNumFilterModes }; Filter() : cutoff(0.99), resonance(0.0), mode(FILTER_MODE_LOWPASS), buf0(0.0), buf1(0.0) { calculateFeedbackAmount(); }; double process(double inputValue); inline void setCutoff(double newCutoff) { cutoff = newCutoff; calculateFeedbackAmount(); }; inline void setResonance(double newResonance) { resonance = newResonance; calculateFeedbackAmount(); }; inline void setFilterMode(FilterMode newMode) { mode = newMode; } private: double cutoff; double resonance; FilterMode mode; double feedbackAmount; inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - cutoff); } double buf0; double buf1; };
private
cutoff
resonance
. Mode
(Lowpass, Highpass, Bandpass). feedbackAmount
, buf0
buf1
, .
( calculateFeedbackAmount()
). process
. feedbackAmount
cutoff
resonance
, calculateFeedbackAmount
.
Filter.cpp :
// By Paul Kellett // http://www.musicdsp.org/showone.php?id=29 double Filter::process(double inputValue) { buf0 += cutoff * (inputValue - buf0); buf1 += cutoff * (buf0 - buf1); switch (mode) { case FILTER_MODE_LOWPASS: return buf1; case FILTER_MODE_HIGHPASS: return inputValue - buf0; case FILTER_MODE_BANDPASS: return buf0 - buf1; default: return 0.0; } }
, ? , . « » , (. . 6 ). buf0
buf1
: . inputValue
, - buf0
( ). . 6 / 12 /. switch
, buf1
. , , buf0
, 12, 6 /, .
case
FILTER_MODE_HIGHPASS
. buf0
. inputValue
buf0
, . buf1
, .
case
FILTER_MODE_BANDPASS
, buf0 - buf1
. buf0
, buf1
. , .
: , .
buf1
. ( Infinite Impulse Response , IIR ). , . , , .
, , . , , , , . , NI Massive, « ». , , . , ( Massive ). «» , , , AAS Ultra Analog, , , , . , , , , NI Reaktor. , — , .. .
. GUI. bg.png ( “Move to trash” Xcode), :
filtermode.png knob_small.png ( , 50 50 ) bg.png ( )
ID resource.h :
// Unique IDs for each image resource. #define BG_ID 101 #define WHITE_KEY_ID 102 #define BLACK_KEY_ID 103 #define WAVEFORM_ID 104 #define KNOB_ID 105 #define KNOB_SMALL_ID 106 #define FILTERMODE_ID 107 // Image resource locations for this plug. #define BG_FN "resources/img/bg.png" #define WHITE_KEY_FN "resources/img/whitekey.png" #define BLACK_KEY_FN "resources/img/blackkey.png" #define WAVEFORM_FN "resources/img/waveform.png" #define KNOB_FN "resources/img/knob.png" #define KNOB_SMALL_FN "resources/img/knob_small.png" #define FILTERMODE_FN "resources/img/filtermode.png"
Synthesis.rc :
#include "resource.h" BG_ID PNG BG_FN WHITE_KEY_ID PNG WHITE_KEY_FN BLACK_KEY_ID PNG BLACK_KEY_FN WAVEFORM_ID PNG WAVEFORM_FN KNOB_ID PNG KNOB_FN KNOB_SMALL_ID PNG KNOB_SMALL_FN FILTERMODE_ID PNG FILTERMODE_FN
#include "Filter.h"
Synthesis.h private
:
Filter mFilter;
EParams
Synthesis.cpp :
enum EParams { mWaveform = 0, mAttack, mDecay, mSustain, mRelease, mFilterMode, mFilterCutoff, mFilterResonance, mFilterAttack, mFilterDecay, mFilterSustain, mFilterRelease, mFilterEnvelopeAmount, kNumParams };
:
pGraphics->AttachControl(new ISwitchControl(this, 24, 38, mWaveform, &waveformBitmap));
. (Lowpass, Highpass, Bandpass). AttachGraphics(pGraphics)
:
GetParam(mFilterMode)->InitEnum("Filter Mode", Filter::FILTER_MODE_LOWPASS, Filter::kNumFilterModes); IBitmap filtermodeBitmap = pGraphics->LoadIBitmap(FILTERMODE_ID, FILTERMODE_FN, 3); pGraphics->AttachControl(new ISwitchControl(this, 24, 123, mFilterMode, &filtermodeBitmap));
. . knob_small.png :
// Knobs for filter cutoff and resonance IBitmap smallKnobBitmap = pGraphics->LoadIBitmap(KNOB_SMALL_ID, KNOB_SMALL_FN, 64); // Cutoff knob: GetParam(mFilterCutoff)->InitDouble("Cutoff", 0.99, 0.01, 0.99, 0.001); GetParam(mFilterCutoff)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 5, 177, mFilterCutoff, &smallKnobBitmap)); // Resonance knob: GetParam(mFilterResonance)->InitDouble("Resonance", 0.01, 0.01, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 61, 177, mFilterResonance, &smallKnobBitmap));
, 1.0
, calculateFeedbackAmount
, .
ProcessDoubleReplacing
:
leftOutput[i] = rightOutput[i] = mFilter.process(mOscillator.nextSample() * mEnvelopeGenerator.nextSample() * velocity / 127.0);
. switch
Synthesis::OnParamChange
:
case mFilterCutoff: mFilter.setCutoff(GetParam(paramIdx)->Value()); break; case mFilterResonance: mFilter.setResonance(GetParam(paramIdx)->Value()); break; case mFilterMode: mFilter.setFilterMode(static_cast<Filter::FilterMode>(GetParam(paramIdx)->Int())); break;
. - .
— . , , .
:
buf0 += cutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1));
, , ( buf0 – buf1
), feedbackAmount
. calculateFeedbackAmount
feedbackAmount
resonance
, . . , .
, . , , . , , .
-12 / -24 /
! 24 . switch
Filter::process
:
buf2 += cutoff * (buf1 - buf2); buf3 += cutoff * (buf2 – buf3);
: , , . , , , ( ). switch
:
switch (mode) { case FILTER_MODE_LOWPASS: return buf3; case FILTER_MODE_HIGHPASS: return inputValue - buf3; case FILTER_MODE_BANDPASS: return buf0 - buf3; default: return 0.0; }
buf1
buf3
– . -24 /. buf2
buf3
, private
Filter.h :
double buf2; double buf3;
, buf0
c buf1
:
Filter() : // ... buf0(0.0), buf1(0.0), buf2(0.0), buf3(0.0) // ...
, , : . .
?
. , , .
- cutoff
. . cutoffMod
, , cutoff
. #include private
:
double cutoffMod;
:
Filter() : cutoff(0.99), resonance(0.01), cutoffMod(0.0), // ...
. private
:
inline double getCalculatedCutoff() const { return fmax(fmin(cutoff + cutoffMod, 0.99), 0.01); };
calculateFeedbackAmount
:
inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - getCalculatedCutoff()); }
cutoffMod
. feedbackAmount
, :
inline void setCutoffMod(double newCutoffMod) { cutoffMod = newCutoffMod; calculateFeedbackAmount(); }
, . Filter::process
:
if (inputValue == 0.0) return inputValue; double calculatedCutoff = getCalculatedCutoff(); buf0 += calculatedCutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1)); buf1 += calculatedCutoff * (buf0 - buf1); buf2 += calculatedCutoff * (buf1 - buf2); buf3 += calculatedCutoff * (buf2 – buf3);
, . , , . . cutoff
calculatedCutoff
-.
, ( setCutoffMod
), Synthesis
, , . , cutoffMod
. filterEnvelopeAmount
-1
+1
. GUI.
private
Synthesis.h :
EnvelopeGenerator mFilterEnvelopeGenerator; double filterEnvelopeAmount;
MIDI , onNoteOn
onNoteOff
:
inline void onNoteOn(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); }; inline void onNoteOff(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); };
. ProcessDoubleReplacing
:
mFilter.setCutoffMod(mFilterEnvelopeGenerator.nextSample() * filterEnvelopeAmount);
, nextSample
filterEnvelopeAmount
cutoffMod
. filterEnvelopeAmount
:
Synthesis::Synthesis(IPlugInstanceInfo instanceInfo) : IPLUG_CTOR(kNumParams, kNumPrograms, instanceInfo), lastVirtualKeyboardNoteNumber(virtualKeyboardMinimumNoteNumber - 1), filterEnvelopeAmount(0.0) { // ... }
Synthesis::Reset
:
mFilterEnvelopeGenerator.setSampleRate(GetSampleRate());
EParams
, . AttachGraphics
:
// Knobs for filter envelope // Attack knob GetParam(mFilterAttack)->InitDouble("Filter Env Attack", 0.01, 0.01, 10.0, 0.001); GetParam(mFilterAttack)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 139, 178, mFilterAttack, &smallKnobBitmap)); // Decay knob: GetParam(mFilterDecay)->InitDouble("Filter Env Decay", 0.5, 0.01, 15.0, 0.001); GetParam(mFilterDecay)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 195, 178, mFilterDecay, &smallKnobBitmap)); // Sustain knob: GetParam(mFilterSustain)->InitDouble("Filter Env Sustain", 0.1, 0.001, 1.0, 0.001); GetParam(mFilterSustain)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 251, 178, mFilterSustain, &smallKnobBitmap)); // Release knob: GetParam(mFilterRelease)->InitDouble("Filter Env Release", 1.0, 0.001, 15.0, 0.001); GetParam(mFilterRelease)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 307, 178, mFilterRelease, &smallKnobBitmap)); // Filter envelope amount knob: GetParam(mFilterEnvelopeAmount)->InitDouble("Filter Env Amount", 0.0, -1.0, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 363, 178, mFilterEnvelopeAmount, &smallKnobBitmap));
, smallKnobBitmap
. . , . Synthesis::OnParamChange
switch
:
case mFilterAttack: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK, GetParam(paramIdx)->Value()); break; case mFilterDecay: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_DECAY, GetParam(paramIdx)->Value()); break; case mFilterSustain: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_SUSTAIN, GetParam(paramIdx)->Value()); break; case mFilterRelease: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE, GetParam(paramIdx)->Value()); break; case mFilterEnvelopeAmount: filterEnvelopeAmount = GetParam(paramIdx)->Value(); break;
!
! (- C1), :
EnvelopeGenerator
. . !
.
:)
:
martin-finke.de/blog/articles/audio-plugins-013-filter
#include Filter.h ( ) :
class Filter { public: enum FilterMode { FILTER_MODE_LOWPASS = 0, FILTER_MODE_HIGHPASS, FILTER_MODE_BANDPASS, kNumFilterModes }; Filter() : cutoff(0.99), resonance(0.0), mode(FILTER_MODE_LOWPASS), buf0(0.0), buf1(0.0) { calculateFeedbackAmount(); }; double process(double inputValue); inline void setCutoff(double newCutoff) { cutoff = newCutoff; calculateFeedbackAmount(); }; inline void setResonance(double newResonance) { resonance = newResonance; calculateFeedbackAmount(); }; inline void setFilterMode(FilterMode newMode) { mode = newMode; } private: double cutoff; double resonance; FilterMode mode; double feedbackAmount; inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - cutoff); } double buf0; double buf1; };
private
cutoff
resonance
.
Mode
(Lowpass, Highpass, Bandpass).
feedbackAmount
,
buf0
buf1
, .
(
calculateFeedbackAmount()
).
process
.
feedbackAmount
cutoff
resonance
,
calculateFeedbackAmount
.
Filter.cpp :
// By Paul Kellett // http://www.musicdsp.org/showone.php?id=29 double Filter::process(double inputValue) { buf0 += cutoff * (inputValue - buf0); buf1 += cutoff * (buf0 - buf1); switch (mode) { case FILTER_MODE_LOWPASS: return buf1; case FILTER_MODE_HIGHPASS: return inputValue - buf0; case FILTER_MODE_BANDPASS: return buf0 - buf1; default: return 0.0; } }
, ? , . « » , (. . 6 ).
buf0
buf1
: .
inputValue
, -
buf0
( ). . 6 / 12 /.
switch
,
buf1
. , ,
buf0
, 12, 6 /, .
case
FILTER_MODE_HIGHPASS
.
buf0
.
inputValue
buf0
, .
buf1
, .
case
FILTER_MODE_BANDPASS
,
buf0 - buf1
.
buf0
,
buf1
. , .
: , .
buf1
. ( Infinite Impulse Response , IIR ). , . , , .
, , . , , , , . , NI Massive, « ». , , . , ( Massive ). «» , , , AAS Ultra Analog, , , , . , , , , NI Reaktor. , — , .. .
. GUI. bg.png ( “Move to trash” Xcode), :
filtermode.png knob_small.png ( , 50 50 ) bg.png ( )
ID resource.h :
// Unique IDs for each image resource. #define BG_ID 101 #define WHITE_KEY_ID 102 #define BLACK_KEY_ID 103 #define WAVEFORM_ID 104 #define KNOB_ID 105 #define KNOB_SMALL_ID 106 #define FILTERMODE_ID 107 // Image resource locations for this plug. #define BG_FN "resources/img/bg.png" #define WHITE_KEY_FN "resources/img/whitekey.png" #define BLACK_KEY_FN "resources/img/blackkey.png" #define WAVEFORM_FN "resources/img/waveform.png" #define KNOB_FN "resources/img/knob.png" #define KNOB_SMALL_FN "resources/img/knob_small.png" #define FILTERMODE_FN "resources/img/filtermode.png"
Synthesis.rc :
#include "resource.h" BG_ID PNG BG_FN WHITE_KEY_ID PNG WHITE_KEY_FN BLACK_KEY_ID PNG BLACK_KEY_FN WAVEFORM_ID PNG WAVEFORM_FN KNOB_ID PNG KNOB_FN KNOB_SMALL_ID PNG KNOB_SMALL_FN FILTERMODE_ID PNG FILTERMODE_FN
#include "Filter.h"
Synthesis.h
private
:
Filter mFilter;
EParams
Synthesis.cpp :
enum EParams { mWaveform = 0, mAttack, mDecay, mSustain, mRelease, mFilterMode, mFilterCutoff, mFilterResonance, mFilterAttack, mFilterDecay, mFilterSustain, mFilterRelease, mFilterEnvelopeAmount, kNumParams };
:
pGraphics->AttachControl(new ISwitchControl(this, 24, 38, mWaveform, &waveformBitmap));
. (Lowpass, Highpass, Bandpass).
AttachGraphics(pGraphics)
:
GetParam(mFilterMode)->InitEnum("Filter Mode", Filter::FILTER_MODE_LOWPASS, Filter::kNumFilterModes); IBitmap filtermodeBitmap = pGraphics->LoadIBitmap(FILTERMODE_ID, FILTERMODE_FN, 3); pGraphics->AttachControl(new ISwitchControl(this, 24, 123, mFilterMode, &filtermodeBitmap));
. . knob_small.png :
// Knobs for filter cutoff and resonance IBitmap smallKnobBitmap = pGraphics->LoadIBitmap(KNOB_SMALL_ID, KNOB_SMALL_FN, 64); // Cutoff knob: GetParam(mFilterCutoff)->InitDouble("Cutoff", 0.99, 0.01, 0.99, 0.001); GetParam(mFilterCutoff)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 5, 177, mFilterCutoff, &smallKnobBitmap)); // Resonance knob: GetParam(mFilterResonance)->InitDouble("Resonance", 0.01, 0.01, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 61, 177, mFilterResonance, &smallKnobBitmap));
,
1.0
,
calculateFeedbackAmount
, .
ProcessDoubleReplacing
:
leftOutput[i] = rightOutput[i] = mFilter.process(mOscillator.nextSample() * mEnvelopeGenerator.nextSample() * velocity / 127.0);
.
switch
Synthesis::OnParamChange
:
case mFilterCutoff: mFilter.setCutoff(GetParam(paramIdx)->Value()); break; case mFilterResonance: mFilter.setResonance(GetParam(paramIdx)->Value()); break; case mFilterMode: mFilter.setFilterMode(static_cast<Filter::FilterMode>(GetParam(paramIdx)->Int())); break;
. - .
— . , , .
:
buf0 += cutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1));
, , (
buf0 – buf1
),
feedbackAmount
.
calculateFeedbackAmount
feedbackAmount
resonance
, . . , .
, . , , . , , .
-12 / -24 /
! 24 .
switch
Filter::process
:
buf2 += cutoff * (buf1 - buf2); buf3 += cutoff * (buf2 – buf3);
: , , . , , , ( ).
switch
:
switch (mode) { case FILTER_MODE_LOWPASS: return buf3; case FILTER_MODE_HIGHPASS: return inputValue - buf3; case FILTER_MODE_BANDPASS: return buf0 - buf3; default: return 0.0; }
buf1
buf3
– . -24 /.
buf2
buf3
,
private
Filter.h :
double buf2; double buf3;
,
buf0
c
buf1
:
Filter() : // ... buf0(0.0), buf1(0.0), buf2(0.0), buf3(0.0) // ...
, , : . .
?
. , , .
-
cutoff
. .
cutoffMod
, ,
cutoff
.
#include private
:
double cutoffMod;
:
Filter() : cutoff(0.99), resonance(0.01), cutoffMod(0.0), // ...
. private
:
inline double getCalculatedCutoff() const { return fmax(fmin(cutoff + cutoffMod, 0.99), 0.01); };
calculateFeedbackAmount
:
inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - getCalculatedCutoff()); }
cutoffMod
. feedbackAmount
, :
inline void setCutoffMod(double newCutoffMod) { cutoffMod = newCutoffMod; calculateFeedbackAmount(); }
, . Filter::process
:
if (inputValue == 0.0) return inputValue; double calculatedCutoff = getCalculatedCutoff(); buf0 += calculatedCutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1)); buf1 += calculatedCutoff * (buf0 - buf1); buf2 += calculatedCutoff * (buf1 - buf2); buf3 += calculatedCutoff * (buf2 – buf3);
, . , , . . cutoff
calculatedCutoff
-.
, ( setCutoffMod
), Synthesis
, , . , cutoffMod
. filterEnvelopeAmount
-1
+1
. GUI.
private
Synthesis.h :
EnvelopeGenerator mFilterEnvelopeGenerator; double filterEnvelopeAmount;
MIDI , onNoteOn
onNoteOff
:
inline void onNoteOn(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); }; inline void onNoteOff(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); };
. ProcessDoubleReplacing
:
mFilter.setCutoffMod(mFilterEnvelopeGenerator.nextSample() * filterEnvelopeAmount);
, nextSample
filterEnvelopeAmount
cutoffMod
. filterEnvelopeAmount
:
Synthesis::Synthesis(IPlugInstanceInfo instanceInfo) : IPLUG_CTOR(kNumParams, kNumPrograms, instanceInfo), lastVirtualKeyboardNoteNumber(virtualKeyboardMinimumNoteNumber - 1), filterEnvelopeAmount(0.0) { // ... }
Synthesis::Reset
:
mFilterEnvelopeGenerator.setSampleRate(GetSampleRate());
EParams
, . AttachGraphics
:
// Knobs for filter envelope // Attack knob GetParam(mFilterAttack)->InitDouble("Filter Env Attack", 0.01, 0.01, 10.0, 0.001); GetParam(mFilterAttack)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 139, 178, mFilterAttack, &smallKnobBitmap)); // Decay knob: GetParam(mFilterDecay)->InitDouble("Filter Env Decay", 0.5, 0.01, 15.0, 0.001); GetParam(mFilterDecay)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 195, 178, mFilterDecay, &smallKnobBitmap)); // Sustain knob: GetParam(mFilterSustain)->InitDouble("Filter Env Sustain", 0.1, 0.001, 1.0, 0.001); GetParam(mFilterSustain)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 251, 178, mFilterSustain, &smallKnobBitmap)); // Release knob: GetParam(mFilterRelease)->InitDouble("Filter Env Release", 1.0, 0.001, 15.0, 0.001); GetParam(mFilterRelease)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 307, 178, mFilterRelease, &smallKnobBitmap)); // Filter envelope amount knob: GetParam(mFilterEnvelopeAmount)->InitDouble("Filter Env Amount", 0.0, -1.0, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 363, 178, mFilterEnvelopeAmount, &smallKnobBitmap));
, smallKnobBitmap
. . , . Synthesis::OnParamChange
switch
:
case mFilterAttack: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK, GetParam(paramIdx)->Value()); break; case mFilterDecay: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_DECAY, GetParam(paramIdx)->Value()); break; case mFilterSustain: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_SUSTAIN, GetParam(paramIdx)->Value()); break; case mFilterRelease: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE, GetParam(paramIdx)->Value()); break; case mFilterEnvelopeAmount: filterEnvelopeAmount = GetParam(paramIdx)->Value(); break;
!
! (- C1), :
EnvelopeGenerator
. . !
.
:)
:
martin-finke.de/blog/articles/audio-plugins-013-filter
#include Filter.h ( ) :
class Filter { public: enum FilterMode { FILTER_MODE_LOWPASS = 0, FILTER_MODE_HIGHPASS, FILTER_MODE_BANDPASS, kNumFilterModes }; Filter() : cutoff(0.99), resonance(0.0), mode(FILTER_MODE_LOWPASS), buf0(0.0), buf1(0.0) { calculateFeedbackAmount(); }; double process(double inputValue); inline void setCutoff(double newCutoff) { cutoff = newCutoff; calculateFeedbackAmount(); }; inline void setResonance(double newResonance) { resonance = newResonance; calculateFeedbackAmount(); }; inline void setFilterMode(FilterMode newMode) { mode = newMode; } private: double cutoff; double resonance; FilterMode mode; double feedbackAmount; inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - cutoff); } double buf0; double buf1; };
private
cutoff
resonance
. Mode
(Lowpass, Highpass, Bandpass). feedbackAmount
, buf0
buf1
, .
( calculateFeedbackAmount()
). process
. feedbackAmount
cutoff
resonance
, calculateFeedbackAmount
.
Filter.cpp :
// By Paul Kellett // http://www.musicdsp.org/showone.php?id=29 double Filter::process(double inputValue) { buf0 += cutoff * (inputValue - buf0); buf1 += cutoff * (buf0 - buf1); switch (mode) { case FILTER_MODE_LOWPASS: return buf1; case FILTER_MODE_HIGHPASS: return inputValue - buf0; case FILTER_MODE_BANDPASS: return buf0 - buf1; default: return 0.0; } }
, ? , . « » , (. . 6 ). buf0
buf1
: . inputValue
, - buf0
( ). . 6 / 12 /. switch
, buf1
. , , buf0
, 12, 6 /, .
case
FILTER_MODE_HIGHPASS
. buf0
. inputValue
buf0
, . buf1
, .
case
FILTER_MODE_BANDPASS
, buf0 - buf1
. buf0
, buf1
. , .
: , .
buf1
. ( Infinite Impulse Response , IIR ). , . , , .
, , . , , , , . , NI Massive, « ». , , . , ( Massive ). «» , , , AAS Ultra Analog, , , , . , , , , NI Reaktor. , — , .. .
. GUI. bg.png ( “Move to trash” Xcode), :
filtermode.png knob_small.png ( , 50 50 ) bg.png ( )
ID resource.h :
// Unique IDs for each image resource. #define BG_ID 101 #define WHITE_KEY_ID 102 #define BLACK_KEY_ID 103 #define WAVEFORM_ID 104 #define KNOB_ID 105 #define KNOB_SMALL_ID 106 #define FILTERMODE_ID 107 // Image resource locations for this plug. #define BG_FN "resources/img/bg.png" #define WHITE_KEY_FN "resources/img/whitekey.png" #define BLACK_KEY_FN "resources/img/blackkey.png" #define WAVEFORM_FN "resources/img/waveform.png" #define KNOB_FN "resources/img/knob.png" #define KNOB_SMALL_FN "resources/img/knob_small.png" #define FILTERMODE_FN "resources/img/filtermode.png"
Synthesis.rc :
#include "resource.h" BG_ID PNG BG_FN WHITE_KEY_ID PNG WHITE_KEY_FN BLACK_KEY_ID PNG BLACK_KEY_FN WAVEFORM_ID PNG WAVEFORM_FN KNOB_ID PNG KNOB_FN KNOB_SMALL_ID PNG KNOB_SMALL_FN FILTERMODE_ID PNG FILTERMODE_FN
#include "Filter.h"
Synthesis.h private
:
Filter mFilter;
EParams
Synthesis.cpp :
enum EParams { mWaveform = 0, mAttack, mDecay, mSustain, mRelease, mFilterMode, mFilterCutoff, mFilterResonance, mFilterAttack, mFilterDecay, mFilterSustain, mFilterRelease, mFilterEnvelopeAmount, kNumParams };
:
pGraphics->AttachControl(new ISwitchControl(this, 24, 38, mWaveform, &waveformBitmap));
. (Lowpass, Highpass, Bandpass). AttachGraphics(pGraphics)
:
GetParam(mFilterMode)->InitEnum("Filter Mode", Filter::FILTER_MODE_LOWPASS, Filter::kNumFilterModes); IBitmap filtermodeBitmap = pGraphics->LoadIBitmap(FILTERMODE_ID, FILTERMODE_FN, 3); pGraphics->AttachControl(new ISwitchControl(this, 24, 123, mFilterMode, &filtermodeBitmap));
. . knob_small.png :
// Knobs for filter cutoff and resonance IBitmap smallKnobBitmap = pGraphics->LoadIBitmap(KNOB_SMALL_ID, KNOB_SMALL_FN, 64); // Cutoff knob: GetParam(mFilterCutoff)->InitDouble("Cutoff", 0.99, 0.01, 0.99, 0.001); GetParam(mFilterCutoff)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 5, 177, mFilterCutoff, &smallKnobBitmap)); // Resonance knob: GetParam(mFilterResonance)->InitDouble("Resonance", 0.01, 0.01, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 61, 177, mFilterResonance, &smallKnobBitmap));
, 1.0
, calculateFeedbackAmount
, .
ProcessDoubleReplacing
:
leftOutput[i] = rightOutput[i] = mFilter.process(mOscillator.nextSample() * mEnvelopeGenerator.nextSample() * velocity / 127.0);
. switch
Synthesis::OnParamChange
:
case mFilterCutoff: mFilter.setCutoff(GetParam(paramIdx)->Value()); break; case mFilterResonance: mFilter.setResonance(GetParam(paramIdx)->Value()); break; case mFilterMode: mFilter.setFilterMode(static_cast<Filter::FilterMode>(GetParam(paramIdx)->Int())); break;
. - .
— . , , .
:
buf0 += cutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1));
, , ( buf0 – buf1
), feedbackAmount
. calculateFeedbackAmount
feedbackAmount
resonance
, . . , .
, . , , . , , .
-12 / -24 /
! 24 . switch
Filter::process
:
buf2 += cutoff * (buf1 - buf2); buf3 += cutoff * (buf2 – buf3);
: , , . , , , ( ). switch
:
switch (mode) { case FILTER_MODE_LOWPASS: return buf3; case FILTER_MODE_HIGHPASS: return inputValue - buf3; case FILTER_MODE_BANDPASS: return buf0 - buf3; default: return 0.0; }
buf1
buf3
– . -24 /. buf2
buf3
, private
Filter.h :
double buf2; double buf3;
, buf0
c buf1
:
Filter() : // ... buf0(0.0), buf1(0.0), buf2(0.0), buf3(0.0) // ...
, , : . .
?
. , , .
- cutoff
. . cutoffMod
, , cutoff
. #include private
:
double cutoffMod;
:
Filter() : cutoff(0.99), resonance(0.01), cutoffMod(0.0), // ...
. private
:
inline double getCalculatedCutoff() const { return fmax(fmin(cutoff + cutoffMod, 0.99), 0.01); };
calculateFeedbackAmount
:
inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - getCalculatedCutoff()); }
cutoffMod
. feedbackAmount
, :
inline void setCutoffMod(double newCutoffMod) { cutoffMod = newCutoffMod; calculateFeedbackAmount(); }
, . Filter::process
:
if (inputValue == 0.0) return inputValue; double calculatedCutoff = getCalculatedCutoff(); buf0 += calculatedCutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1)); buf1 += calculatedCutoff * (buf0 - buf1); buf2 += calculatedCutoff * (buf1 - buf2); buf3 += calculatedCutoff * (buf2 – buf3);
, . , , . . cutoff
calculatedCutoff
-.
, ( setCutoffMod
), Synthesis
, , . , cutoffMod
. filterEnvelopeAmount
-1
+1
. GUI.
private
Synthesis.h :
EnvelopeGenerator mFilterEnvelopeGenerator; double filterEnvelopeAmount;
MIDI , onNoteOn
onNoteOff
:
inline void onNoteOn(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); }; inline void onNoteOff(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); };
. ProcessDoubleReplacing
:
mFilter.setCutoffMod(mFilterEnvelopeGenerator.nextSample() * filterEnvelopeAmount);
, nextSample
filterEnvelopeAmount
cutoffMod
. filterEnvelopeAmount
:
Synthesis::Synthesis(IPlugInstanceInfo instanceInfo) : IPLUG_CTOR(kNumParams, kNumPrograms, instanceInfo), lastVirtualKeyboardNoteNumber(virtualKeyboardMinimumNoteNumber - 1), filterEnvelopeAmount(0.0) { // ... }
Synthesis::Reset
:
mFilterEnvelopeGenerator.setSampleRate(GetSampleRate());
EParams
, . AttachGraphics
:
// Knobs for filter envelope // Attack knob GetParam(mFilterAttack)->InitDouble("Filter Env Attack", 0.01, 0.01, 10.0, 0.001); GetParam(mFilterAttack)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 139, 178, mFilterAttack, &smallKnobBitmap)); // Decay knob: GetParam(mFilterDecay)->InitDouble("Filter Env Decay", 0.5, 0.01, 15.0, 0.001); GetParam(mFilterDecay)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 195, 178, mFilterDecay, &smallKnobBitmap)); // Sustain knob: GetParam(mFilterSustain)->InitDouble("Filter Env Sustain", 0.1, 0.001, 1.0, 0.001); GetParam(mFilterSustain)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 251, 178, mFilterSustain, &smallKnobBitmap)); // Release knob: GetParam(mFilterRelease)->InitDouble("Filter Env Release", 1.0, 0.001, 15.0, 0.001); GetParam(mFilterRelease)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 307, 178, mFilterRelease, &smallKnobBitmap)); // Filter envelope amount knob: GetParam(mFilterEnvelopeAmount)->InitDouble("Filter Env Amount", 0.0, -1.0, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 363, 178, mFilterEnvelopeAmount, &smallKnobBitmap));
, smallKnobBitmap
. . , . Synthesis::OnParamChange
switch
:
case mFilterAttack: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK, GetParam(paramIdx)->Value()); break; case mFilterDecay: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_DECAY, GetParam(paramIdx)->Value()); break; case mFilterSustain: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_SUSTAIN, GetParam(paramIdx)->Value()); break; case mFilterRelease: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE, GetParam(paramIdx)->Value()); break; case mFilterEnvelopeAmount: filterEnvelopeAmount = GetParam(paramIdx)->Value(); break;
!
! (- C1), :
EnvelopeGenerator
. . !
.
:)
:
martin-finke.de/blog/articles/audio-plugins-013-filter
-
#include Filter.h ( ) :
class Filter { public: enum FilterMode { FILTER_MODE_LOWPASS = 0, FILTER_MODE_HIGHPASS, FILTER_MODE_BANDPASS, kNumFilterModes }; Filter() : cutoff(0.99), resonance(0.0), mode(FILTER_MODE_LOWPASS), buf0(0.0), buf1(0.0) { calculateFeedbackAmount(); }; double process(double inputValue); inline void setCutoff(double newCutoff) { cutoff = newCutoff; calculateFeedbackAmount(); }; inline void setResonance(double newResonance) { resonance = newResonance; calculateFeedbackAmount(); }; inline void setFilterMode(FilterMode newMode) { mode = newMode; } private: double cutoff; double resonance; FilterMode mode; double feedbackAmount; inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - cutoff); } double buf0; double buf1; };
private
cutoff
resonance
.Mode
(Lowpass, Highpass, Bandpass).feedbackAmount
,buf0
buf1
, .
(calculateFeedbackAmount()
).process
.feedbackAmount
cutoff
resonance
,calculateFeedbackAmount
.
Filter.cpp :
// By Paul Kellett // http://www.musicdsp.org/showone.php?id=29 double Filter::process(double inputValue) { buf0 += cutoff * (inputValue - buf0); buf1 += cutoff * (buf0 - buf1); switch (mode) { case FILTER_MODE_LOWPASS: return buf1; case FILTER_MODE_HIGHPASS: return inputValue - buf0; case FILTER_MODE_BANDPASS: return buf0 - buf1; default: return 0.0; } }
, ? , . « » , (. . 6 ).buf0
buf1
: .inputValue
, -buf0
( ). . 6 / 12 /.switch
,buf1
. , ,buf0
, 12, 6 /, .
case
FILTER_MODE_HIGHPASS
.buf0
.inputValue
buf0
, .buf1
, .
case
FILTER_MODE_BANDPASS
,buf0 - buf1
.buf0
,buf1
. , .
: , .
buf1
. ( Infinite Impulse Response , IIR ). , . , , .
, , . , , , , . , NI Massive, « ». , , . , ( Massive ). «» , , , AAS Ultra Analog, , , , . , , , , NI Reaktor. , — , .. .
. GUI. bg.png ( “Move to trash” Xcode), :
filtermode.png knob_small.png ( , 50 50 ) bg.png ( )
ID resource.h :
// Unique IDs for each image resource. #define BG_ID 101 #define WHITE_KEY_ID 102 #define BLACK_KEY_ID 103 #define WAVEFORM_ID 104 #define KNOB_ID 105 #define KNOB_SMALL_ID 106 #define FILTERMODE_ID 107 // Image resource locations for this plug. #define BG_FN "resources/img/bg.png" #define WHITE_KEY_FN "resources/img/whitekey.png" #define BLACK_KEY_FN "resources/img/blackkey.png" #define WAVEFORM_FN "resources/img/waveform.png" #define KNOB_FN "resources/img/knob.png" #define KNOB_SMALL_FN "resources/img/knob_small.png" #define FILTERMODE_FN "resources/img/filtermode.png"
Synthesis.rc :
#include "resource.h" BG_ID PNG BG_FN WHITE_KEY_ID PNG WHITE_KEY_FN BLACK_KEY_ID PNG BLACK_KEY_FN WAVEFORM_ID PNG WAVEFORM_FN KNOB_ID PNG KNOB_FN KNOB_SMALL_ID PNG KNOB_SMALL_FN FILTERMODE_ID PNG FILTERMODE_FN
#include "Filter.h"
Synthesis.hprivate
:
Filter mFilter;
EParams
Synthesis.cpp :
enum EParams { mWaveform = 0, mAttack, mDecay, mSustain, mRelease, mFilterMode, mFilterCutoff, mFilterResonance, mFilterAttack, mFilterDecay, mFilterSustain, mFilterRelease, mFilterEnvelopeAmount, kNumParams };
:
pGraphics->AttachControl(new ISwitchControl(this, 24, 38, mWaveform, &waveformBitmap));
. (Lowpass, Highpass, Bandpass).AttachGraphics(pGraphics)
:
GetParam(mFilterMode)->InitEnum("Filter Mode", Filter::FILTER_MODE_LOWPASS, Filter::kNumFilterModes); IBitmap filtermodeBitmap = pGraphics->LoadIBitmap(FILTERMODE_ID, FILTERMODE_FN, 3); pGraphics->AttachControl(new ISwitchControl(this, 24, 123, mFilterMode, &filtermodeBitmap));
. . knob_small.png :
// Knobs for filter cutoff and resonance IBitmap smallKnobBitmap = pGraphics->LoadIBitmap(KNOB_SMALL_ID, KNOB_SMALL_FN, 64); // Cutoff knob: GetParam(mFilterCutoff)->InitDouble("Cutoff", 0.99, 0.01, 0.99, 0.001); GetParam(mFilterCutoff)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 5, 177, mFilterCutoff, &smallKnobBitmap)); // Resonance knob: GetParam(mFilterResonance)->InitDouble("Resonance", 0.01, 0.01, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 61, 177, mFilterResonance, &smallKnobBitmap));
,1.0
,calculateFeedbackAmount
, .
ProcessDoubleReplacing
:
leftOutput[i] = rightOutput[i] = mFilter.process(mOscillator.nextSample() * mEnvelopeGenerator.nextSample() * velocity / 127.0);
.switch
Synthesis::OnParamChange
:
case mFilterCutoff: mFilter.setCutoff(GetParam(paramIdx)->Value()); break; case mFilterResonance: mFilter.setResonance(GetParam(paramIdx)->Value()); break; case mFilterMode: mFilter.setFilterMode(static_cast<Filter::FilterMode>(GetParam(paramIdx)->Int())); break;
. - .
— . , , .
:
buf0 += cutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1));
, , (buf0 – buf1
),feedbackAmount
.calculateFeedbackAmount
feedbackAmount
resonance
, . . , .
, . , , . , , .
-12 / -24 /
! 24 .switch
Filter::process
:
buf2 += cutoff * (buf1 - buf2); buf3 += cutoff * (buf2 – buf3);
: , , . , , , ( ).switch
:
switch (mode) { case FILTER_MODE_LOWPASS: return buf3; case FILTER_MODE_HIGHPASS: return inputValue - buf3; case FILTER_MODE_BANDPASS: return buf0 - buf3; default: return 0.0; }
buf1
buf3
– . -24 /.buf2
buf3
,private
Filter.h :
double buf2; double buf3;
,buf0
cbuf1
:
Filter() : // ... buf0(0.0), buf1(0.0), buf2(0.0), buf3(0.0) // ...
, , : . .
?
. , , .
-cutoff
. .cutoffMod
, ,cutoff
.#include private
:
double cutoffMod;
:
Filter() : cutoff(0.99), resonance(0.01), cutoffMod(0.0), // ...
.private
:
inline double getCalculatedCutoff() const { return fmax(fmin(cutoff + cutoffMod, 0.99), 0.01); };
calculateFeedbackAmount
:
inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - getCalculatedCutoff()); }
cutoffMod
.feedbackAmount
, :
inline void setCutoffMod(double newCutoffMod) { cutoffMod = newCutoffMod; calculateFeedbackAmount(); }
, .Filter::process
:
if (inputValue == 0.0) return inputValue; double calculatedCutoff = getCalculatedCutoff(); buf0 += calculatedCutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1)); buf1 += calculatedCutoff * (buf0 - buf1); buf2 += calculatedCutoff * (buf1 - buf2); buf3 += calculatedCutoff * (buf2 – buf3);
, . , , . .cutoff
calculatedCutoff
-.
, (setCutoffMod
),Synthesis
, , . ,cutoffMod
.filterEnvelopeAmount
-1
+1
. GUI.
private
Synthesis.h :
EnvelopeGenerator mFilterEnvelopeGenerator; double filterEnvelopeAmount;
MIDI ,onNoteOn
onNoteOff
:
inline void onNoteOn(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); }; inline void onNoteOff(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); };
.ProcessDoubleReplacing
:
mFilter.setCutoffMod(mFilterEnvelopeGenerator.nextSample() * filterEnvelopeAmount);
,nextSample
filterEnvelopeAmount
cutoffMod
.filterEnvelopeAmount
:
Synthesis::Synthesis(IPlugInstanceInfo instanceInfo) : IPLUG_CTOR(kNumParams, kNumPrograms, instanceInfo), lastVirtualKeyboardNoteNumber(virtualKeyboardMinimumNoteNumber - 1), filterEnvelopeAmount(0.0) { // ... }
Synthesis::Reset
:
mFilterEnvelopeGenerator.setSampleRate(GetSampleRate());
EParams
, .AttachGraphics
:
// Knobs for filter envelope // Attack knob GetParam(mFilterAttack)->InitDouble("Filter Env Attack", 0.01, 0.01, 10.0, 0.001); GetParam(mFilterAttack)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 139, 178, mFilterAttack, &smallKnobBitmap)); // Decay knob: GetParam(mFilterDecay)->InitDouble("Filter Env Decay", 0.5, 0.01, 15.0, 0.001); GetParam(mFilterDecay)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 195, 178, mFilterDecay, &smallKnobBitmap)); // Sustain knob: GetParam(mFilterSustain)->InitDouble("Filter Env Sustain", 0.1, 0.001, 1.0, 0.001); GetParam(mFilterSustain)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 251, 178, mFilterSustain, &smallKnobBitmap)); // Release knob: GetParam(mFilterRelease)->InitDouble("Filter Env Release", 1.0, 0.001, 15.0, 0.001); GetParam(mFilterRelease)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 307, 178, mFilterRelease, &smallKnobBitmap)); // Filter envelope amount knob: GetParam(mFilterEnvelopeAmount)->InitDouble("Filter Env Amount", 0.0, -1.0, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 363, 178, mFilterEnvelopeAmount, &smallKnobBitmap));
,smallKnobBitmap
. . , .Synthesis::OnParamChange
switch
:
case mFilterAttack: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK, GetParam(paramIdx)->Value()); break; case mFilterDecay: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_DECAY, GetParam(paramIdx)->Value()); break; case mFilterSustain: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_SUSTAIN, GetParam(paramIdx)->Value()); break; case mFilterRelease: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE, GetParam(paramIdx)->Value()); break; case mFilterEnvelopeAmount: filterEnvelopeAmount = GetParam(paramIdx)->Value(); break;
!
! (- C1), :
EnvelopeGenerator
. . !
.
:)
:
martin-finke.de/blog/articles/audio-plugins-013-filter
-
#include Filter.h ( ) :
class Filter { public: enum FilterMode { FILTER_MODE_LOWPASS = 0, FILTER_MODE_HIGHPASS, FILTER_MODE_BANDPASS, kNumFilterModes }; Filter() : cutoff(0.99), resonance(0.0), mode(FILTER_MODE_LOWPASS), buf0(0.0), buf1(0.0) { calculateFeedbackAmount(); }; double process(double inputValue); inline void setCutoff(double newCutoff) { cutoff = newCutoff; calculateFeedbackAmount(); }; inline void setResonance(double newResonance) { resonance = newResonance; calculateFeedbackAmount(); }; inline void setFilterMode(FilterMode newMode) { mode = newMode; } private: double cutoff; double resonance; FilterMode mode; double feedbackAmount; inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - cutoff); } double buf0; double buf1; };
private
cutoff
resonance
.Mode
(Lowpass, Highpass, Bandpass).feedbackAmount
,buf0
buf1
, .
(calculateFeedbackAmount()
).process
.feedbackAmount
cutoff
resonance
,calculateFeedbackAmount
.
Filter.cpp :
// By Paul Kellett // http://www.musicdsp.org/showone.php?id=29 double Filter::process(double inputValue) { buf0 += cutoff * (inputValue - buf0); buf1 += cutoff * (buf0 - buf1); switch (mode) { case FILTER_MODE_LOWPASS: return buf1; case FILTER_MODE_HIGHPASS: return inputValue - buf0; case FILTER_MODE_BANDPASS: return buf0 - buf1; default: return 0.0; } }
, ? , . « » , (. . 6 ).buf0
buf1
: .inputValue
, -buf0
( ). . 6 / 12 /.switch
,buf1
. , ,buf0
, 12, 6 /, .
case
FILTER_MODE_HIGHPASS
.buf0
.inputValue
buf0
, .buf1
, .
case
FILTER_MODE_BANDPASS
,buf0 - buf1
.buf0
,buf1
. , .
: , .
buf1
. ( Infinite Impulse Response , IIR ). , . , , .
, , . , , , , . , NI Massive, « ». , , . , ( Massive ). «» , , , AAS Ultra Analog, , , , . , , , , NI Reaktor. , — , .. .
. GUI. bg.png ( “Move to trash” Xcode), :
filtermode.png knob_small.png ( , 50 50 ) bg.png ( )
ID resource.h :
// Unique IDs for each image resource. #define BG_ID 101 #define WHITE_KEY_ID 102 #define BLACK_KEY_ID 103 #define WAVEFORM_ID 104 #define KNOB_ID 105 #define KNOB_SMALL_ID 106 #define FILTERMODE_ID 107 // Image resource locations for this plug. #define BG_FN "resources/img/bg.png" #define WHITE_KEY_FN "resources/img/whitekey.png" #define BLACK_KEY_FN "resources/img/blackkey.png" #define WAVEFORM_FN "resources/img/waveform.png" #define KNOB_FN "resources/img/knob.png" #define KNOB_SMALL_FN "resources/img/knob_small.png" #define FILTERMODE_FN "resources/img/filtermode.png"
Synthesis.rc :
#include "resource.h" BG_ID PNG BG_FN WHITE_KEY_ID PNG WHITE_KEY_FN BLACK_KEY_ID PNG BLACK_KEY_FN WAVEFORM_ID PNG WAVEFORM_FN KNOB_ID PNG KNOB_FN KNOB_SMALL_ID PNG KNOB_SMALL_FN FILTERMODE_ID PNG FILTERMODE_FN
#include "Filter.h"
Synthesis.hprivate
:
Filter mFilter;
EParams
Synthesis.cpp :
enum EParams { mWaveform = 0, mAttack, mDecay, mSustain, mRelease, mFilterMode, mFilterCutoff, mFilterResonance, mFilterAttack, mFilterDecay, mFilterSustain, mFilterRelease, mFilterEnvelopeAmount, kNumParams };
:
pGraphics->AttachControl(new ISwitchControl(this, 24, 38, mWaveform, &waveformBitmap));
. (Lowpass, Highpass, Bandpass).AttachGraphics(pGraphics)
:
GetParam(mFilterMode)->InitEnum("Filter Mode", Filter::FILTER_MODE_LOWPASS, Filter::kNumFilterModes); IBitmap filtermodeBitmap = pGraphics->LoadIBitmap(FILTERMODE_ID, FILTERMODE_FN, 3); pGraphics->AttachControl(new ISwitchControl(this, 24, 123, mFilterMode, &filtermodeBitmap));
. . knob_small.png :
// Knobs for filter cutoff and resonance IBitmap smallKnobBitmap = pGraphics->LoadIBitmap(KNOB_SMALL_ID, KNOB_SMALL_FN, 64); // Cutoff knob: GetParam(mFilterCutoff)->InitDouble("Cutoff", 0.99, 0.01, 0.99, 0.001); GetParam(mFilterCutoff)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 5, 177, mFilterCutoff, &smallKnobBitmap)); // Resonance knob: GetParam(mFilterResonance)->InitDouble("Resonance", 0.01, 0.01, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 61, 177, mFilterResonance, &smallKnobBitmap));
,1.0
,calculateFeedbackAmount
, .
ProcessDoubleReplacing
:
leftOutput[i] = rightOutput[i] = mFilter.process(mOscillator.nextSample() * mEnvelopeGenerator.nextSample() * velocity / 127.0);
.switch
Synthesis::OnParamChange
:
case mFilterCutoff: mFilter.setCutoff(GetParam(paramIdx)->Value()); break; case mFilterResonance: mFilter.setResonance(GetParam(paramIdx)->Value()); break; case mFilterMode: mFilter.setFilterMode(static_cast<Filter::FilterMode>(GetParam(paramIdx)->Int())); break;
. - .
— . , , .
:
buf0 += cutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1));
, , (buf0 – buf1
),feedbackAmount
.calculateFeedbackAmount
feedbackAmount
resonance
, . . , .
, . , , . , , .
-12 / -24 /
! 24 .switch
Filter::process
:
buf2 += cutoff * (buf1 - buf2); buf3 += cutoff * (buf2 – buf3);
: , , . , , , ( ).switch
:
switch (mode) { case FILTER_MODE_LOWPASS: return buf3; case FILTER_MODE_HIGHPASS: return inputValue - buf3; case FILTER_MODE_BANDPASS: return buf0 - buf3; default: return 0.0; }
buf1
buf3
– . -24 /.buf2
buf3
,private
Filter.h :
double buf2; double buf3;
,buf0
cbuf1
:
Filter() : // ... buf0(0.0), buf1(0.0), buf2(0.0), buf3(0.0) // ...
, , : . .
?
. , , .
-cutoff
. .cutoffMod
, ,cutoff
.#include private
:
double cutoffMod;
:
Filter() : cutoff(0.99), resonance(0.01), cutoffMod(0.0), // ...
.private
:
inline double getCalculatedCutoff() const { return fmax(fmin(cutoff + cutoffMod, 0.99), 0.01); };
calculateFeedbackAmount
:
inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - getCalculatedCutoff()); }
cutoffMod
.feedbackAmount
, :
inline void setCutoffMod(double newCutoffMod) { cutoffMod = newCutoffMod; calculateFeedbackAmount(); }
, .Filter::process
:
if (inputValue == 0.0) return inputValue; double calculatedCutoff = getCalculatedCutoff(); buf0 += calculatedCutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1)); buf1 += calculatedCutoff * (buf0 - buf1); buf2 += calculatedCutoff * (buf1 - buf2); buf3 += calculatedCutoff * (buf2 – buf3);
, . , , . .cutoff
calculatedCutoff
-.
, (setCutoffMod
),Synthesis
, , . ,cutoffMod
.filterEnvelopeAmount
-1
+1
. GUI.
private
Synthesis.h :
EnvelopeGenerator mFilterEnvelopeGenerator; double filterEnvelopeAmount;
MIDI ,onNoteOn
onNoteOff
:
inline void onNoteOn(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); }; inline void onNoteOff(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); };
.ProcessDoubleReplacing
:
mFilter.setCutoffMod(mFilterEnvelopeGenerator.nextSample() * filterEnvelopeAmount);
,nextSample
filterEnvelopeAmount
cutoffMod
.filterEnvelopeAmount
:
Synthesis::Synthesis(IPlugInstanceInfo instanceInfo) : IPLUG_CTOR(kNumParams, kNumPrograms, instanceInfo), lastVirtualKeyboardNoteNumber(virtualKeyboardMinimumNoteNumber - 1), filterEnvelopeAmount(0.0) { // ... }
Synthesis::Reset
:
mFilterEnvelopeGenerator.setSampleRate(GetSampleRate());
EParams
, .AttachGraphics
:
// Knobs for filter envelope // Attack knob GetParam(mFilterAttack)->InitDouble("Filter Env Attack", 0.01, 0.01, 10.0, 0.001); GetParam(mFilterAttack)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 139, 178, mFilterAttack, &smallKnobBitmap)); // Decay knob: GetParam(mFilterDecay)->InitDouble("Filter Env Decay", 0.5, 0.01, 15.0, 0.001); GetParam(mFilterDecay)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 195, 178, mFilterDecay, &smallKnobBitmap)); // Sustain knob: GetParam(mFilterSustain)->InitDouble("Filter Env Sustain", 0.1, 0.001, 1.0, 0.001); GetParam(mFilterSustain)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 251, 178, mFilterSustain, &smallKnobBitmap)); // Release knob: GetParam(mFilterRelease)->InitDouble("Filter Env Release", 1.0, 0.001, 15.0, 0.001); GetParam(mFilterRelease)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 307, 178, mFilterRelease, &smallKnobBitmap)); // Filter envelope amount knob: GetParam(mFilterEnvelopeAmount)->InitDouble("Filter Env Amount", 0.0, -1.0, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 363, 178, mFilterEnvelopeAmount, &smallKnobBitmap));
,smallKnobBitmap
. . , .Synthesis::OnParamChange
switch
:
case mFilterAttack: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK, GetParam(paramIdx)->Value()); break; case mFilterDecay: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_DECAY, GetParam(paramIdx)->Value()); break; case mFilterSustain: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_SUSTAIN, GetParam(paramIdx)->Value()); break; case mFilterRelease: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE, GetParam(paramIdx)->Value()); break; case mFilterEnvelopeAmount: filterEnvelopeAmount = GetParam(paramIdx)->Value(); break;
!
! (- C1), :
EnvelopeGenerator
. . !
.
:)
:
martin-finke.de/blog/articles/audio-plugins-013-filter
-
#include Filter.h ( ) :
class Filter { public: enum FilterMode { FILTER_MODE_LOWPASS = 0, FILTER_MODE_HIGHPASS, FILTER_MODE_BANDPASS, kNumFilterModes }; Filter() : cutoff(0.99), resonance(0.0), mode(FILTER_MODE_LOWPASS), buf0(0.0), buf1(0.0) { calculateFeedbackAmount(); }; double process(double inputValue); inline void setCutoff(double newCutoff) { cutoff = newCutoff; calculateFeedbackAmount(); }; inline void setResonance(double newResonance) { resonance = newResonance; calculateFeedbackAmount(); }; inline void setFilterMode(FilterMode newMode) { mode = newMode; } private: double cutoff; double resonance; FilterMode mode; double feedbackAmount; inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - cutoff); } double buf0; double buf1; };
private
cutoff
resonance
.Mode
(Lowpass, Highpass, Bandpass).feedbackAmount
,buf0
buf1
, .
(calculateFeedbackAmount()
).process
.feedbackAmount
cutoff
resonance
,calculateFeedbackAmount
.
Filter.cpp :
// By Paul Kellett // http://www.musicdsp.org/showone.php?id=29 double Filter::process(double inputValue) { buf0 += cutoff * (inputValue - buf0); buf1 += cutoff * (buf0 - buf1); switch (mode) { case FILTER_MODE_LOWPASS: return buf1; case FILTER_MODE_HIGHPASS: return inputValue - buf0; case FILTER_MODE_BANDPASS: return buf0 - buf1; default: return 0.0; } }
, ? , . « » , (. . 6 ).buf0
buf1
: .inputValue
, -buf0
( ). . 6 / 12 /.switch
,buf1
. , ,buf0
, 12, 6 /, .
case
FILTER_MODE_HIGHPASS
.buf0
.inputValue
buf0
, .buf1
, .
case
FILTER_MODE_BANDPASS
,buf0 - buf1
.buf0
,buf1
. , .
: , .
buf1
. ( Infinite Impulse Response , IIR ). , . , , .
, , . , , , , . , NI Massive, « ». , , . , ( Massive ). «» , , , AAS Ultra Analog, , , , . , , , , NI Reaktor. , — , .. .
. GUI. bg.png ( “Move to trash” Xcode), :
filtermode.png knob_small.png ( , 50 50 ) bg.png ( )
ID resource.h :
// Unique IDs for each image resource. #define BG_ID 101 #define WHITE_KEY_ID 102 #define BLACK_KEY_ID 103 #define WAVEFORM_ID 104 #define KNOB_ID 105 #define KNOB_SMALL_ID 106 #define FILTERMODE_ID 107 // Image resource locations for this plug. #define BG_FN "resources/img/bg.png" #define WHITE_KEY_FN "resources/img/whitekey.png" #define BLACK_KEY_FN "resources/img/blackkey.png" #define WAVEFORM_FN "resources/img/waveform.png" #define KNOB_FN "resources/img/knob.png" #define KNOB_SMALL_FN "resources/img/knob_small.png" #define FILTERMODE_FN "resources/img/filtermode.png"
Synthesis.rc :
#include "resource.h" BG_ID PNG BG_FN WHITE_KEY_ID PNG WHITE_KEY_FN BLACK_KEY_ID PNG BLACK_KEY_FN WAVEFORM_ID PNG WAVEFORM_FN KNOB_ID PNG KNOB_FN KNOB_SMALL_ID PNG KNOB_SMALL_FN FILTERMODE_ID PNG FILTERMODE_FN
#include "Filter.h"
Synthesis.hprivate
:
Filter mFilter;
EParams
Synthesis.cpp :
enum EParams { mWaveform = 0, mAttack, mDecay, mSustain, mRelease, mFilterMode, mFilterCutoff, mFilterResonance, mFilterAttack, mFilterDecay, mFilterSustain, mFilterRelease, mFilterEnvelopeAmount, kNumParams };
:
pGraphics->AttachControl(new ISwitchControl(this, 24, 38, mWaveform, &waveformBitmap));
. (Lowpass, Highpass, Bandpass).AttachGraphics(pGraphics)
:
GetParam(mFilterMode)->InitEnum("Filter Mode", Filter::FILTER_MODE_LOWPASS, Filter::kNumFilterModes); IBitmap filtermodeBitmap = pGraphics->LoadIBitmap(FILTERMODE_ID, FILTERMODE_FN, 3); pGraphics->AttachControl(new ISwitchControl(this, 24, 123, mFilterMode, &filtermodeBitmap));
. . knob_small.png :
// Knobs for filter cutoff and resonance IBitmap smallKnobBitmap = pGraphics->LoadIBitmap(KNOB_SMALL_ID, KNOB_SMALL_FN, 64); // Cutoff knob: GetParam(mFilterCutoff)->InitDouble("Cutoff", 0.99, 0.01, 0.99, 0.001); GetParam(mFilterCutoff)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 5, 177, mFilterCutoff, &smallKnobBitmap)); // Resonance knob: GetParam(mFilterResonance)->InitDouble("Resonance", 0.01, 0.01, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 61, 177, mFilterResonance, &smallKnobBitmap));
,1.0
,calculateFeedbackAmount
, .
ProcessDoubleReplacing
:
leftOutput[i] = rightOutput[i] = mFilter.process(mOscillator.nextSample() * mEnvelopeGenerator.nextSample() * velocity / 127.0);
.switch
Synthesis::OnParamChange
:
case mFilterCutoff: mFilter.setCutoff(GetParam(paramIdx)->Value()); break; case mFilterResonance: mFilter.setResonance(GetParam(paramIdx)->Value()); break; case mFilterMode: mFilter.setFilterMode(static_cast<Filter::FilterMode>(GetParam(paramIdx)->Int())); break;
. - .
— . , , .
:
buf0 += cutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1));
, , (buf0 – buf1
),feedbackAmount
.calculateFeedbackAmount
feedbackAmount
resonance
, . . , .
, . , , . , , .
-12 / -24 /
! 24 .switch
Filter::process
:
buf2 += cutoff * (buf1 - buf2); buf3 += cutoff * (buf2 – buf3);
: , , . , , , ( ).switch
:
switch (mode) { case FILTER_MODE_LOWPASS: return buf3; case FILTER_MODE_HIGHPASS: return inputValue - buf3; case FILTER_MODE_BANDPASS: return buf0 - buf3; default: return 0.0; }
buf1
buf3
– . -24 /.buf2
buf3
,private
Filter.h :
double buf2; double buf3;
,buf0
cbuf1
:
Filter() : // ... buf0(0.0), buf1(0.0), buf2(0.0), buf3(0.0) // ...
, , : . .
?
. , , .
-cutoff
. .cutoffMod
, ,cutoff
.#include private
:
double cutoffMod;
:
Filter() : cutoff(0.99), resonance(0.01), cutoffMod(0.0), // ...
.private
:
inline double getCalculatedCutoff() const { return fmax(fmin(cutoff + cutoffMod, 0.99), 0.01); };
calculateFeedbackAmount
:
inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - getCalculatedCutoff()); }
cutoffMod
.feedbackAmount
, :
inline void setCutoffMod(double newCutoffMod) { cutoffMod = newCutoffMod; calculateFeedbackAmount(); }
, .Filter::process
:
if (inputValue == 0.0) return inputValue; double calculatedCutoff = getCalculatedCutoff(); buf0 += calculatedCutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1)); buf1 += calculatedCutoff * (buf0 - buf1); buf2 += calculatedCutoff * (buf1 - buf2); buf3 += calculatedCutoff * (buf2 – buf3);
, . , , . .cutoff
calculatedCutoff
-.
, (setCutoffMod
),Synthesis
, , . ,cutoffMod
.filterEnvelopeAmount
-1
+1
. GUI.
private
Synthesis.h :
EnvelopeGenerator mFilterEnvelopeGenerator; double filterEnvelopeAmount;
MIDI ,onNoteOn
onNoteOff
:
inline void onNoteOn(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); }; inline void onNoteOff(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); };
.ProcessDoubleReplacing
:
mFilter.setCutoffMod(mFilterEnvelopeGenerator.nextSample() * filterEnvelopeAmount);
,nextSample
filterEnvelopeAmount
cutoffMod
.filterEnvelopeAmount
:
Synthesis::Synthesis(IPlugInstanceInfo instanceInfo) : IPLUG_CTOR(kNumParams, kNumPrograms, instanceInfo), lastVirtualKeyboardNoteNumber(virtualKeyboardMinimumNoteNumber - 1), filterEnvelopeAmount(0.0) { // ... }
Synthesis::Reset
:
mFilterEnvelopeGenerator.setSampleRate(GetSampleRate());
EParams
, .AttachGraphics
:
// Knobs for filter envelope // Attack knob GetParam(mFilterAttack)->InitDouble("Filter Env Attack", 0.01, 0.01, 10.0, 0.001); GetParam(mFilterAttack)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 139, 178, mFilterAttack, &smallKnobBitmap)); // Decay knob: GetParam(mFilterDecay)->InitDouble("Filter Env Decay", 0.5, 0.01, 15.0, 0.001); GetParam(mFilterDecay)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 195, 178, mFilterDecay, &smallKnobBitmap)); // Sustain knob: GetParam(mFilterSustain)->InitDouble("Filter Env Sustain", 0.1, 0.001, 1.0, 0.001); GetParam(mFilterSustain)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 251, 178, mFilterSustain, &smallKnobBitmap)); // Release knob: GetParam(mFilterRelease)->InitDouble("Filter Env Release", 1.0, 0.001, 15.0, 0.001); GetParam(mFilterRelease)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 307, 178, mFilterRelease, &smallKnobBitmap)); // Filter envelope amount knob: GetParam(mFilterEnvelopeAmount)->InitDouble("Filter Env Amount", 0.0, -1.0, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 363, 178, mFilterEnvelopeAmount, &smallKnobBitmap));
,smallKnobBitmap
. . , .Synthesis::OnParamChange
switch
:
case mFilterAttack: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK, GetParam(paramIdx)->Value()); break; case mFilterDecay: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_DECAY, GetParam(paramIdx)->Value()); break; case mFilterSustain: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_SUSTAIN, GetParam(paramIdx)->Value()); break; case mFilterRelease: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE, GetParam(paramIdx)->Value()); break; case mFilterEnvelopeAmount: filterEnvelopeAmount = GetParam(paramIdx)->Value(); break;
!
! (- C1), :
EnvelopeGenerator
. . !
.
:)
:
martin-finke.de/blog/articles/audio-plugins-013-filter
#include Filter.h ( ) :
class Filter { public: enum FilterMode { FILTER_MODE_LOWPASS = 0, FILTER_MODE_HIGHPASS, FILTER_MODE_BANDPASS, kNumFilterModes }; Filter() : cutoff(0.99), resonance(0.0), mode(FILTER_MODE_LOWPASS), buf0(0.0), buf1(0.0) { calculateFeedbackAmount(); }; double process(double inputValue); inline void setCutoff(double newCutoff) { cutoff = newCutoff; calculateFeedbackAmount(); }; inline void setResonance(double newResonance) { resonance = newResonance; calculateFeedbackAmount(); }; inline void setFilterMode(FilterMode newMode) { mode = newMode; } private: double cutoff; double resonance; FilterMode mode; double feedbackAmount; inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - cutoff); } double buf0; double buf1; };
private
cutoff
resonance
. Mode
(Lowpass, Highpass, Bandpass). feedbackAmount
, buf0
buf1
, .
( calculateFeedbackAmount()
). process
. feedbackAmount
cutoff
resonance
, calculateFeedbackAmount
.
Filter.cpp :
// By Paul Kellett // http://www.musicdsp.org/showone.php?id=29 double Filter::process(double inputValue) { buf0 += cutoff * (inputValue - buf0); buf1 += cutoff * (buf0 - buf1); switch (mode) { case FILTER_MODE_LOWPASS: return buf1; case FILTER_MODE_HIGHPASS: return inputValue - buf0; case FILTER_MODE_BANDPASS: return buf0 - buf1; default: return 0.0; } }
, ? , . « » , (. . 6 ). buf0
buf1
: . inputValue
, - buf0
( ). . 6 / 12 /. switch
, buf1
. , , buf0
, 12, 6 /, .
case
FILTER_MODE_HIGHPASS
. buf0
. inputValue
buf0
, . buf1
, .
case
FILTER_MODE_BANDPASS
, buf0 - buf1
. buf0
, buf1
. , .
: , .
buf1
. ( Infinite Impulse Response , IIR ). , . , , .
, , . , , , , . , NI Massive, « ». , , . , ( Massive ). «» , , , AAS Ultra Analog, , , , . , , , , NI Reaktor. , — , .. .
. GUI. bg.png ( “Move to trash” Xcode), :
filtermode.png knob_small.png ( , 50 50 ) bg.png ( )
ID resource.h :
// Unique IDs for each image resource. #define BG_ID 101 #define WHITE_KEY_ID 102 #define BLACK_KEY_ID 103 #define WAVEFORM_ID 104 #define KNOB_ID 105 #define KNOB_SMALL_ID 106 #define FILTERMODE_ID 107 // Image resource locations for this plug. #define BG_FN "resources/img/bg.png" #define WHITE_KEY_FN "resources/img/whitekey.png" #define BLACK_KEY_FN "resources/img/blackkey.png" #define WAVEFORM_FN "resources/img/waveform.png" #define KNOB_FN "resources/img/knob.png" #define KNOB_SMALL_FN "resources/img/knob_small.png" #define FILTERMODE_FN "resources/img/filtermode.png"
Synthesis.rc :
#include "resource.h" BG_ID PNG BG_FN WHITE_KEY_ID PNG WHITE_KEY_FN BLACK_KEY_ID PNG BLACK_KEY_FN WAVEFORM_ID PNG WAVEFORM_FN KNOB_ID PNG KNOB_FN KNOB_SMALL_ID PNG KNOB_SMALL_FN FILTERMODE_ID PNG FILTERMODE_FN
#include "Filter.h"
Synthesis.h private
:
Filter mFilter;
EParams
Synthesis.cpp :
enum EParams { mWaveform = 0, mAttack, mDecay, mSustain, mRelease, mFilterMode, mFilterCutoff, mFilterResonance, mFilterAttack, mFilterDecay, mFilterSustain, mFilterRelease, mFilterEnvelopeAmount, kNumParams };
:
pGraphics->AttachControl(new ISwitchControl(this, 24, 38, mWaveform, &waveformBitmap));
. (Lowpass, Highpass, Bandpass). AttachGraphics(pGraphics)
:
GetParam(mFilterMode)->InitEnum("Filter Mode", Filter::FILTER_MODE_LOWPASS, Filter::kNumFilterModes); IBitmap filtermodeBitmap = pGraphics->LoadIBitmap(FILTERMODE_ID, FILTERMODE_FN, 3); pGraphics->AttachControl(new ISwitchControl(this, 24, 123, mFilterMode, &filtermodeBitmap));
. . knob_small.png :
// Knobs for filter cutoff and resonance IBitmap smallKnobBitmap = pGraphics->LoadIBitmap(KNOB_SMALL_ID, KNOB_SMALL_FN, 64); // Cutoff knob: GetParam(mFilterCutoff)->InitDouble("Cutoff", 0.99, 0.01, 0.99, 0.001); GetParam(mFilterCutoff)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 5, 177, mFilterCutoff, &smallKnobBitmap)); // Resonance knob: GetParam(mFilterResonance)->InitDouble("Resonance", 0.01, 0.01, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 61, 177, mFilterResonance, &smallKnobBitmap));
, 1.0
, calculateFeedbackAmount
, .
ProcessDoubleReplacing
:
leftOutput[i] = rightOutput[i] = mFilter.process(mOscillator.nextSample() * mEnvelopeGenerator.nextSample() * velocity / 127.0);
. switch
Synthesis::OnParamChange
:
case mFilterCutoff: mFilter.setCutoff(GetParam(paramIdx)->Value()); break; case mFilterResonance: mFilter.setResonance(GetParam(paramIdx)->Value()); break; case mFilterMode: mFilter.setFilterMode(static_cast<Filter::FilterMode>(GetParam(paramIdx)->Int())); break;
. - .
— . , , .
:
buf0 += cutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1));
, , ( buf0 – buf1
), feedbackAmount
. calculateFeedbackAmount
feedbackAmount
resonance
, . . , .
, . , , . , , .
-12 / -24 /
! 24 . switch
Filter::process
:
buf2 += cutoff * (buf1 - buf2); buf3 += cutoff * (buf2 – buf3);
: , , . , , , ( ). switch
:
switch (mode) { case FILTER_MODE_LOWPASS: return buf3; case FILTER_MODE_HIGHPASS: return inputValue - buf3; case FILTER_MODE_BANDPASS: return buf0 - buf3; default: return 0.0; }
buf1
buf3
– . -24 /. buf2
buf3
, private
Filter.h :
double buf2; double buf3;
, buf0
c buf1
:
Filter() : // ... buf0(0.0), buf1(0.0), buf2(0.0), buf3(0.0) // ...
, , : . .
?
. , , .
- cutoff
. . cutoffMod
, , cutoff
. #include private
:
double cutoffMod;
:
Filter() : cutoff(0.99), resonance(0.01), cutoffMod(0.0), // ...
. private
:
inline double getCalculatedCutoff() const { return fmax(fmin(cutoff + cutoffMod, 0.99), 0.01); };
calculateFeedbackAmount
:
inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - getCalculatedCutoff()); }
cutoffMod
. feedbackAmount
, :
inline void setCutoffMod(double newCutoffMod) { cutoffMod = newCutoffMod; calculateFeedbackAmount(); }
, . Filter::process
:
if (inputValue == 0.0) return inputValue; double calculatedCutoff = getCalculatedCutoff(); buf0 += calculatedCutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1)); buf1 += calculatedCutoff * (buf0 - buf1); buf2 += calculatedCutoff * (buf1 - buf2); buf3 += calculatedCutoff * (buf2 – buf3);
, . , , . . cutoff
calculatedCutoff
-.
, ( setCutoffMod
), Synthesis
, , . , cutoffMod
. filterEnvelopeAmount
-1
+1
. GUI.
private
Synthesis.h :
EnvelopeGenerator mFilterEnvelopeGenerator; double filterEnvelopeAmount;
MIDI , onNoteOn
onNoteOff
:
inline void onNoteOn(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); }; inline void onNoteOff(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); };
. ProcessDoubleReplacing
:
mFilter.setCutoffMod(mFilterEnvelopeGenerator.nextSample() * filterEnvelopeAmount);
, nextSample
filterEnvelopeAmount
cutoffMod
. filterEnvelopeAmount
:
Synthesis::Synthesis(IPlugInstanceInfo instanceInfo) : IPLUG_CTOR(kNumParams, kNumPrograms, instanceInfo), lastVirtualKeyboardNoteNumber(virtualKeyboardMinimumNoteNumber - 1), filterEnvelopeAmount(0.0) { // ... }
Synthesis::Reset
:
mFilterEnvelopeGenerator.setSampleRate(GetSampleRate());
EParams
, . AttachGraphics
:
// Knobs for filter envelope // Attack knob GetParam(mFilterAttack)->InitDouble("Filter Env Attack", 0.01, 0.01, 10.0, 0.001); GetParam(mFilterAttack)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 139, 178, mFilterAttack, &smallKnobBitmap)); // Decay knob: GetParam(mFilterDecay)->InitDouble("Filter Env Decay", 0.5, 0.01, 15.0, 0.001); GetParam(mFilterDecay)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 195, 178, mFilterDecay, &smallKnobBitmap)); // Sustain knob: GetParam(mFilterSustain)->InitDouble("Filter Env Sustain", 0.1, 0.001, 1.0, 0.001); GetParam(mFilterSustain)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 251, 178, mFilterSustain, &smallKnobBitmap)); // Release knob: GetParam(mFilterRelease)->InitDouble("Filter Env Release", 1.0, 0.001, 15.0, 0.001); GetParam(mFilterRelease)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 307, 178, mFilterRelease, &smallKnobBitmap)); // Filter envelope amount knob: GetParam(mFilterEnvelopeAmount)->InitDouble("Filter Env Amount", 0.0, -1.0, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 363, 178, mFilterEnvelopeAmount, &smallKnobBitmap));
, smallKnobBitmap
. . , . Synthesis::OnParamChange
switch
:
case mFilterAttack: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK, GetParam(paramIdx)->Value()); break; case mFilterDecay: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_DECAY, GetParam(paramIdx)->Value()); break; case mFilterSustain: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_SUSTAIN, GetParam(paramIdx)->Value()); break; case mFilterRelease: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE, GetParam(paramIdx)->Value()); break; case mFilterEnvelopeAmount: filterEnvelopeAmount = GetParam(paramIdx)->Value(); break;
!
! (- C1), :
EnvelopeGenerator
. . !
.
:)
:
martin-finke.de/blog/articles/audio-plugins-013-filter
#include Filter.h ( ) :
class Filter { public: enum FilterMode { FILTER_MODE_LOWPASS = 0, FILTER_MODE_HIGHPASS, FILTER_MODE_BANDPASS, kNumFilterModes }; Filter() : cutoff(0.99), resonance(0.0), mode(FILTER_MODE_LOWPASS), buf0(0.0), buf1(0.0) { calculateFeedbackAmount(); }; double process(double inputValue); inline void setCutoff(double newCutoff) { cutoff = newCutoff; calculateFeedbackAmount(); }; inline void setResonance(double newResonance) { resonance = newResonance; calculateFeedbackAmount(); }; inline void setFilterMode(FilterMode newMode) { mode = newMode; } private: double cutoff; double resonance; FilterMode mode; double feedbackAmount; inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - cutoff); } double buf0; double buf1; };
private
cutoff
resonance
. Mode
(Lowpass, Highpass, Bandpass). feedbackAmount
, buf0
buf1
, .
( calculateFeedbackAmount()
). process
. feedbackAmount
cutoff
resonance
, calculateFeedbackAmount
.
Filter.cpp :
// By Paul Kellett // http://www.musicdsp.org/showone.php?id=29 double Filter::process(double inputValue) { buf0 += cutoff * (inputValue - buf0); buf1 += cutoff * (buf0 - buf1); switch (mode) { case FILTER_MODE_LOWPASS: return buf1; case FILTER_MODE_HIGHPASS: return inputValue - buf0; case FILTER_MODE_BANDPASS: return buf0 - buf1; default: return 0.0; } }
, ? , . « » , (. . 6 ). buf0
buf1
: . inputValue
, - buf0
( ). . 6 / 12 /. switch
, buf1
. , , buf0
, 12, 6 /, .
case
FILTER_MODE_HIGHPASS
. buf0
. inputValue
buf0
, . buf1
, .
case
FILTER_MODE_BANDPASS
, buf0 - buf1
. buf0
, buf1
. , .
: , .
buf1
. ( Infinite Impulse Response , IIR ). , . , , .
, , . , , , , . , NI Massive, « ». , , . , ( Massive ). «» , , , AAS Ultra Analog, , , , . , , , , NI Reaktor. , — , .. .
. GUI. bg.png ( “Move to trash” Xcode), :
filtermode.png knob_small.png ( , 50 50 ) bg.png ( )
ID resource.h :
// Unique IDs for each image resource. #define BG_ID 101 #define WHITE_KEY_ID 102 #define BLACK_KEY_ID 103 #define WAVEFORM_ID 104 #define KNOB_ID 105 #define KNOB_SMALL_ID 106 #define FILTERMODE_ID 107 // Image resource locations for this plug. #define BG_FN "resources/img/bg.png" #define WHITE_KEY_FN "resources/img/whitekey.png" #define BLACK_KEY_FN "resources/img/blackkey.png" #define WAVEFORM_FN "resources/img/waveform.png" #define KNOB_FN "resources/img/knob.png" #define KNOB_SMALL_FN "resources/img/knob_small.png" #define FILTERMODE_FN "resources/img/filtermode.png"
Synthesis.rc :
#include "resource.h" BG_ID PNG BG_FN WHITE_KEY_ID PNG WHITE_KEY_FN BLACK_KEY_ID PNG BLACK_KEY_FN WAVEFORM_ID PNG WAVEFORM_FN KNOB_ID PNG KNOB_FN KNOB_SMALL_ID PNG KNOB_SMALL_FN FILTERMODE_ID PNG FILTERMODE_FN
#include "Filter.h"
Synthesis.h private
:
Filter mFilter;
EParams
Synthesis.cpp :
enum EParams { mWaveform = 0, mAttack, mDecay, mSustain, mRelease, mFilterMode, mFilterCutoff, mFilterResonance, mFilterAttack, mFilterDecay, mFilterSustain, mFilterRelease, mFilterEnvelopeAmount, kNumParams };
:
pGraphics->AttachControl(new ISwitchControl(this, 24, 38, mWaveform, &waveformBitmap));
. (Lowpass, Highpass, Bandpass). AttachGraphics(pGraphics)
:
GetParam(mFilterMode)->InitEnum("Filter Mode", Filter::FILTER_MODE_LOWPASS, Filter::kNumFilterModes); IBitmap filtermodeBitmap = pGraphics->LoadIBitmap(FILTERMODE_ID, FILTERMODE_FN, 3); pGraphics->AttachControl(new ISwitchControl(this, 24, 123, mFilterMode, &filtermodeBitmap));
. . knob_small.png :
// Knobs for filter cutoff and resonance IBitmap smallKnobBitmap = pGraphics->LoadIBitmap(KNOB_SMALL_ID, KNOB_SMALL_FN, 64); // Cutoff knob: GetParam(mFilterCutoff)->InitDouble("Cutoff", 0.99, 0.01, 0.99, 0.001); GetParam(mFilterCutoff)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 5, 177, mFilterCutoff, &smallKnobBitmap)); // Resonance knob: GetParam(mFilterResonance)->InitDouble("Resonance", 0.01, 0.01, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 61, 177, mFilterResonance, &smallKnobBitmap));
, 1.0
, calculateFeedbackAmount
, .
ProcessDoubleReplacing
:
leftOutput[i] = rightOutput[i] = mFilter.process(mOscillator.nextSample() * mEnvelopeGenerator.nextSample() * velocity / 127.0);
. switch
Synthesis::OnParamChange
:
case mFilterCutoff: mFilter.setCutoff(GetParam(paramIdx)->Value()); break; case mFilterResonance: mFilter.setResonance(GetParam(paramIdx)->Value()); break; case mFilterMode: mFilter.setFilterMode(static_cast<Filter::FilterMode>(GetParam(paramIdx)->Int())); break;
. - .
— . , , .
:
buf0 += cutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1));
, , ( buf0 – buf1
), feedbackAmount
. calculateFeedbackAmount
feedbackAmount
resonance
, . . , .
, . , , . , , .
-12 / -24 /
! 24 . switch
Filter::process
:
buf2 += cutoff * (buf1 - buf2); buf3 += cutoff * (buf2 – buf3);
: , , . , , , ( ). switch
:
switch (mode) { case FILTER_MODE_LOWPASS: return buf3; case FILTER_MODE_HIGHPASS: return inputValue - buf3; case FILTER_MODE_BANDPASS: return buf0 - buf3; default: return 0.0; }
buf1
buf3
– . -24 /. buf2
buf3
, private
Filter.h :
double buf2; double buf3;
, buf0
c buf1
:
Filter() : // ... buf0(0.0), buf1(0.0), buf2(0.0), buf3(0.0) // ...
, , : . .
?
. , , .
- cutoff
. . cutoffMod
, , cutoff
. #include private
:
double cutoffMod;
:
Filter() : cutoff(0.99), resonance(0.01), cutoffMod(0.0), // ...
. private
:
inline double getCalculatedCutoff() const { return fmax(fmin(cutoff + cutoffMod, 0.99), 0.01); };
calculateFeedbackAmount
:
inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - getCalculatedCutoff()); }
cutoffMod
. feedbackAmount
, :
inline void setCutoffMod(double newCutoffMod) { cutoffMod = newCutoffMod; calculateFeedbackAmount(); }
, . Filter::process
:
if (inputValue == 0.0) return inputValue; double calculatedCutoff = getCalculatedCutoff(); buf0 += calculatedCutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1)); buf1 += calculatedCutoff * (buf0 - buf1); buf2 += calculatedCutoff * (buf1 - buf2); buf3 += calculatedCutoff * (buf2 – buf3);
, . , , . . cutoff
calculatedCutoff
-.
, ( setCutoffMod
), Synthesis
, , . , cutoffMod
. filterEnvelopeAmount
-1
+1
. GUI.
private
Synthesis.h :
EnvelopeGenerator mFilterEnvelopeGenerator; double filterEnvelopeAmount;
MIDI , onNoteOn
onNoteOff
:
inline void onNoteOn(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); }; inline void onNoteOff(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); };
. ProcessDoubleReplacing
:
mFilter.setCutoffMod(mFilterEnvelopeGenerator.nextSample() * filterEnvelopeAmount);
, nextSample
filterEnvelopeAmount
cutoffMod
. filterEnvelopeAmount
:
Synthesis::Synthesis(IPlugInstanceInfo instanceInfo) : IPLUG_CTOR(kNumParams, kNumPrograms, instanceInfo), lastVirtualKeyboardNoteNumber(virtualKeyboardMinimumNoteNumber - 1), filterEnvelopeAmount(0.0) { // ... }
Synthesis::Reset
:
mFilterEnvelopeGenerator.setSampleRate(GetSampleRate());
EParams
, . AttachGraphics
:
// Knobs for filter envelope // Attack knob GetParam(mFilterAttack)->InitDouble("Filter Env Attack", 0.01, 0.01, 10.0, 0.001); GetParam(mFilterAttack)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 139, 178, mFilterAttack, &smallKnobBitmap)); // Decay knob: GetParam(mFilterDecay)->InitDouble("Filter Env Decay", 0.5, 0.01, 15.0, 0.001); GetParam(mFilterDecay)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 195, 178, mFilterDecay, &smallKnobBitmap)); // Sustain knob: GetParam(mFilterSustain)->InitDouble("Filter Env Sustain", 0.1, 0.001, 1.0, 0.001); GetParam(mFilterSustain)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 251, 178, mFilterSustain, &smallKnobBitmap)); // Release knob: GetParam(mFilterRelease)->InitDouble("Filter Env Release", 1.0, 0.001, 15.0, 0.001); GetParam(mFilterRelease)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 307, 178, mFilterRelease, &smallKnobBitmap)); // Filter envelope amount knob: GetParam(mFilterEnvelopeAmount)->InitDouble("Filter Env Amount", 0.0, -1.0, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 363, 178, mFilterEnvelopeAmount, &smallKnobBitmap));
, smallKnobBitmap
. . , . Synthesis::OnParamChange
switch
:
case mFilterAttack: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK, GetParam(paramIdx)->Value()); break; case mFilterDecay: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_DECAY, GetParam(paramIdx)->Value()); break; case mFilterSustain: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_SUSTAIN, GetParam(paramIdx)->Value()); break; case mFilterRelease: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE, GetParam(paramIdx)->Value()); break; case mFilterEnvelopeAmount: filterEnvelopeAmount = GetParam(paramIdx)->Value(); break;
!
! (- C1), :
EnvelopeGenerator
. . !
.
:)
:
martin-finke.de/blog/articles/audio-plugins-013-filter
#include Filter.h ( ) :
class Filter { public: enum FilterMode { FILTER_MODE_LOWPASS = 0, FILTER_MODE_HIGHPASS, FILTER_MODE_BANDPASS, kNumFilterModes }; Filter() : cutoff(0.99), resonance(0.0), mode(FILTER_MODE_LOWPASS), buf0(0.0), buf1(0.0) { calculateFeedbackAmount(); }; double process(double inputValue); inline void setCutoff(double newCutoff) { cutoff = newCutoff; calculateFeedbackAmount(); }; inline void setResonance(double newResonance) { resonance = newResonance; calculateFeedbackAmount(); }; inline void setFilterMode(FilterMode newMode) { mode = newMode; } private: double cutoff; double resonance; FilterMode mode; double feedbackAmount; inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - cutoff); } double buf0; double buf1; };
private
cutoff
resonance
. Mode
(Lowpass, Highpass, Bandpass). feedbackAmount
, buf0
buf1
, .
( calculateFeedbackAmount()
). process
. feedbackAmount
cutoff
resonance
, calculateFeedbackAmount
.
Filter.cpp :
// By Paul Kellett // http://www.musicdsp.org/showone.php?id=29 double Filter::process(double inputValue) { buf0 += cutoff * (inputValue - buf0); buf1 += cutoff * (buf0 - buf1); switch (mode) { case FILTER_MODE_LOWPASS: return buf1; case FILTER_MODE_HIGHPASS: return inputValue - buf0; case FILTER_MODE_BANDPASS: return buf0 - buf1; default: return 0.0; } }
, ? , . « » , (. . 6 ). buf0
buf1
: . inputValue
, - buf0
( ). . 6 / 12 /. switch
, buf1
. , , buf0
, 12, 6 /, .
case
FILTER_MODE_HIGHPASS
. buf0
. inputValue
buf0
, . buf1
, .
case
FILTER_MODE_BANDPASS
, buf0 - buf1
. buf0
, buf1
. , .
: , .
buf1
. ( Infinite Impulse Response , IIR ). , . , , .
, , . , , , , . , NI Massive, « ». , , . , ( Massive ). «» , , , AAS Ultra Analog, , , , . , , , , NI Reaktor. , — , .. .
. GUI. bg.png ( “Move to trash” Xcode), :
filtermode.png knob_small.png ( , 50 50 ) bg.png ( )
ID resource.h :
// Unique IDs for each image resource. #define BG_ID 101 #define WHITE_KEY_ID 102 #define BLACK_KEY_ID 103 #define WAVEFORM_ID 104 #define KNOB_ID 105 #define KNOB_SMALL_ID 106 #define FILTERMODE_ID 107 // Image resource locations for this plug. #define BG_FN "resources/img/bg.png" #define WHITE_KEY_FN "resources/img/whitekey.png" #define BLACK_KEY_FN "resources/img/blackkey.png" #define WAVEFORM_FN "resources/img/waveform.png" #define KNOB_FN "resources/img/knob.png" #define KNOB_SMALL_FN "resources/img/knob_small.png" #define FILTERMODE_FN "resources/img/filtermode.png"
Synthesis.rc :
#include "resource.h" BG_ID PNG BG_FN WHITE_KEY_ID PNG WHITE_KEY_FN BLACK_KEY_ID PNG BLACK_KEY_FN WAVEFORM_ID PNG WAVEFORM_FN KNOB_ID PNG KNOB_FN KNOB_SMALL_ID PNG KNOB_SMALL_FN FILTERMODE_ID PNG FILTERMODE_FN
#include "Filter.h"
Synthesis.h private
:
Filter mFilter;
EParams
Synthesis.cpp :
enum EParams { mWaveform = 0, mAttack, mDecay, mSustain, mRelease, mFilterMode, mFilterCutoff, mFilterResonance, mFilterAttack, mFilterDecay, mFilterSustain, mFilterRelease, mFilterEnvelopeAmount, kNumParams };
:
pGraphics->AttachControl(new ISwitchControl(this, 24, 38, mWaveform, &waveformBitmap));
. (Lowpass, Highpass, Bandpass). AttachGraphics(pGraphics)
:
GetParam(mFilterMode)->InitEnum("Filter Mode", Filter::FILTER_MODE_LOWPASS, Filter::kNumFilterModes); IBitmap filtermodeBitmap = pGraphics->LoadIBitmap(FILTERMODE_ID, FILTERMODE_FN, 3); pGraphics->AttachControl(new ISwitchControl(this, 24, 123, mFilterMode, &filtermodeBitmap));
. . knob_small.png :
// Knobs for filter cutoff and resonance IBitmap smallKnobBitmap = pGraphics->LoadIBitmap(KNOB_SMALL_ID, KNOB_SMALL_FN, 64); // Cutoff knob: GetParam(mFilterCutoff)->InitDouble("Cutoff", 0.99, 0.01, 0.99, 0.001); GetParam(mFilterCutoff)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 5, 177, mFilterCutoff, &smallKnobBitmap)); // Resonance knob: GetParam(mFilterResonance)->InitDouble("Resonance", 0.01, 0.01, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 61, 177, mFilterResonance, &smallKnobBitmap));
, 1.0
, calculateFeedbackAmount
, .
ProcessDoubleReplacing
:
leftOutput[i] = rightOutput[i] = mFilter.process(mOscillator.nextSample() * mEnvelopeGenerator.nextSample() * velocity / 127.0);
. switch
Synthesis::OnParamChange
:
case mFilterCutoff: mFilter.setCutoff(GetParam(paramIdx)->Value()); break; case mFilterResonance: mFilter.setResonance(GetParam(paramIdx)->Value()); break; case mFilterMode: mFilter.setFilterMode(static_cast<Filter::FilterMode>(GetParam(paramIdx)->Int())); break;
. - .
— . , , .
:
buf0 += cutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1));
, , ( buf0 – buf1
), feedbackAmount
. calculateFeedbackAmount
feedbackAmount
resonance
, . . , .
, . , , . , , .
-12 / -24 /
! 24 . switch
Filter::process
:
buf2 += cutoff * (buf1 - buf2); buf3 += cutoff * (buf2 – buf3);
: , , . , , , ( ). switch
:
switch (mode) { case FILTER_MODE_LOWPASS: return buf3; case FILTER_MODE_HIGHPASS: return inputValue - buf3; case FILTER_MODE_BANDPASS: return buf0 - buf3; default: return 0.0; }
buf1
buf3
– . -24 /. buf2
buf3
, private
Filter.h :
double buf2; double buf3;
, buf0
c buf1
:
Filter() : // ... buf0(0.0), buf1(0.0), buf2(0.0), buf3(0.0) // ...
, , : . .
?
. , , .
- cutoff
. . cutoffMod
, , cutoff
. #include private
:
double cutoffMod;
:
Filter() : cutoff(0.99), resonance(0.01), cutoffMod(0.0), // ...
. private
:
inline double getCalculatedCutoff() const { return fmax(fmin(cutoff + cutoffMod, 0.99), 0.01); };
calculateFeedbackAmount
:
inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - getCalculatedCutoff()); }
cutoffMod
. feedbackAmount
, :
inline void setCutoffMod(double newCutoffMod) { cutoffMod = newCutoffMod; calculateFeedbackAmount(); }
, . Filter::process
:
if (inputValue == 0.0) return inputValue; double calculatedCutoff = getCalculatedCutoff(); buf0 += calculatedCutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1)); buf1 += calculatedCutoff * (buf0 - buf1); buf2 += calculatedCutoff * (buf1 - buf2); buf3 += calculatedCutoff * (buf2 – buf3);
, . , , . . cutoff
calculatedCutoff
-.
, ( setCutoffMod
), Synthesis
, , . , cutoffMod
. filterEnvelopeAmount
-1
+1
. GUI.
private
Synthesis.h :
EnvelopeGenerator mFilterEnvelopeGenerator; double filterEnvelopeAmount;
MIDI , onNoteOn
onNoteOff
:
inline void onNoteOn(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); }; inline void onNoteOff(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); };
. ProcessDoubleReplacing
:
mFilter.setCutoffMod(mFilterEnvelopeGenerator.nextSample() * filterEnvelopeAmount);
, nextSample
filterEnvelopeAmount
cutoffMod
. filterEnvelopeAmount
:
Synthesis::Synthesis(IPlugInstanceInfo instanceInfo) : IPLUG_CTOR(kNumParams, kNumPrograms, instanceInfo), lastVirtualKeyboardNoteNumber(virtualKeyboardMinimumNoteNumber - 1), filterEnvelopeAmount(0.0) { // ... }
Synthesis::Reset
:
mFilterEnvelopeGenerator.setSampleRate(GetSampleRate());
EParams
, . AttachGraphics
:
// Knobs for filter envelope // Attack knob GetParam(mFilterAttack)->InitDouble("Filter Env Attack", 0.01, 0.01, 10.0, 0.001); GetParam(mFilterAttack)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 139, 178, mFilterAttack, &smallKnobBitmap)); // Decay knob: GetParam(mFilterDecay)->InitDouble("Filter Env Decay", 0.5, 0.01, 15.0, 0.001); GetParam(mFilterDecay)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 195, 178, mFilterDecay, &smallKnobBitmap)); // Sustain knob: GetParam(mFilterSustain)->InitDouble("Filter Env Sustain", 0.1, 0.001, 1.0, 0.001); GetParam(mFilterSustain)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 251, 178, mFilterSustain, &smallKnobBitmap)); // Release knob: GetParam(mFilterRelease)->InitDouble("Filter Env Release", 1.0, 0.001, 15.0, 0.001); GetParam(mFilterRelease)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 307, 178, mFilterRelease, &smallKnobBitmap)); // Filter envelope amount knob: GetParam(mFilterEnvelopeAmount)->InitDouble("Filter Env Amount", 0.0, -1.0, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 363, 178, mFilterEnvelopeAmount, &smallKnobBitmap));
, smallKnobBitmap
. . , . Synthesis::OnParamChange
switch
:
case mFilterAttack: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK, GetParam(paramIdx)->Value()); break; case mFilterDecay: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_DECAY, GetParam(paramIdx)->Value()); break; case mFilterSustain: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_SUSTAIN, GetParam(paramIdx)->Value()); break; case mFilterRelease: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE, GetParam(paramIdx)->Value()); break; case mFilterEnvelopeAmount: filterEnvelopeAmount = GetParam(paramIdx)->Value(); break;
!
! (- C1), :
EnvelopeGenerator
. . !
.
:)
:
martin-finke.de/blog/articles/audio-plugins-013-filter
#include Filter.h ( ) :
class Filter { public: enum FilterMode { FILTER_MODE_LOWPASS = 0, FILTER_MODE_HIGHPASS, FILTER_MODE_BANDPASS, kNumFilterModes }; Filter() : cutoff(0.99), resonance(0.0), mode(FILTER_MODE_LOWPASS), buf0(0.0), buf1(0.0) { calculateFeedbackAmount(); }; double process(double inputValue); inline void setCutoff(double newCutoff) { cutoff = newCutoff; calculateFeedbackAmount(); }; inline void setResonance(double newResonance) { resonance = newResonance; calculateFeedbackAmount(); }; inline void setFilterMode(FilterMode newMode) { mode = newMode; } private: double cutoff; double resonance; FilterMode mode; double feedbackAmount; inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - cutoff); } double buf0; double buf1; };
private
cutoff
resonance
. Mode
(Lowpass, Highpass, Bandpass). feedbackAmount
, buf0
buf1
, .
( calculateFeedbackAmount()
). process
. feedbackAmount
cutoff
resonance
, calculateFeedbackAmount
.
Filter.cpp :
// By Paul Kellett // http://www.musicdsp.org/showone.php?id=29 double Filter::process(double inputValue) { buf0 += cutoff * (inputValue - buf0); buf1 += cutoff * (buf0 - buf1); switch (mode) { case FILTER_MODE_LOWPASS: return buf1; case FILTER_MODE_HIGHPASS: return inputValue - buf0; case FILTER_MODE_BANDPASS: return buf0 - buf1; default: return 0.0; } }
, ? , . « » , (. . 6 ). buf0
buf1
: . inputValue
, - buf0
( ). . 6 / 12 /. switch
, buf1
. , , buf0
, 12, 6 /, .
case
FILTER_MODE_HIGHPASS
. buf0
. inputValue
buf0
, . buf1
, .
case
FILTER_MODE_BANDPASS
, buf0 - buf1
. buf0
, buf1
. , .
: , .
buf1
. ( Infinite Impulse Response , IIR ). , . , , .
, , . , , , , . , NI Massive, « ». , , . , ( Massive ). «» , , , AAS Ultra Analog, , , , . , , , , NI Reaktor. , — , .. .
. GUI. bg.png ( “Move to trash” Xcode), :
filtermode.png knob_small.png ( , 50 50 ) bg.png ( )
ID resource.h :
// Unique IDs for each image resource. #define BG_ID 101 #define WHITE_KEY_ID 102 #define BLACK_KEY_ID 103 #define WAVEFORM_ID 104 #define KNOB_ID 105 #define KNOB_SMALL_ID 106 #define FILTERMODE_ID 107 // Image resource locations for this plug. #define BG_FN "resources/img/bg.png" #define WHITE_KEY_FN "resources/img/whitekey.png" #define BLACK_KEY_FN "resources/img/blackkey.png" #define WAVEFORM_FN "resources/img/waveform.png" #define KNOB_FN "resources/img/knob.png" #define KNOB_SMALL_FN "resources/img/knob_small.png" #define FILTERMODE_FN "resources/img/filtermode.png"
Synthesis.rc :
#include "resource.h" BG_ID PNG BG_FN WHITE_KEY_ID PNG WHITE_KEY_FN BLACK_KEY_ID PNG BLACK_KEY_FN WAVEFORM_ID PNG WAVEFORM_FN KNOB_ID PNG KNOB_FN KNOB_SMALL_ID PNG KNOB_SMALL_FN FILTERMODE_ID PNG FILTERMODE_FN
#include "Filter.h"
Synthesis.h private
:
Filter mFilter;
EParams
Synthesis.cpp :
enum EParams { mWaveform = 0, mAttack, mDecay, mSustain, mRelease, mFilterMode, mFilterCutoff, mFilterResonance, mFilterAttack, mFilterDecay, mFilterSustain, mFilterRelease, mFilterEnvelopeAmount, kNumParams };
:
pGraphics->AttachControl(new ISwitchControl(this, 24, 38, mWaveform, &waveformBitmap));
. (Lowpass, Highpass, Bandpass). AttachGraphics(pGraphics)
:
GetParam(mFilterMode)->InitEnum("Filter Mode", Filter::FILTER_MODE_LOWPASS, Filter::kNumFilterModes); IBitmap filtermodeBitmap = pGraphics->LoadIBitmap(FILTERMODE_ID, FILTERMODE_FN, 3); pGraphics->AttachControl(new ISwitchControl(this, 24, 123, mFilterMode, &filtermodeBitmap));
. . knob_small.png :
// Knobs for filter cutoff and resonance IBitmap smallKnobBitmap = pGraphics->LoadIBitmap(KNOB_SMALL_ID, KNOB_SMALL_FN, 64); // Cutoff knob: GetParam(mFilterCutoff)->InitDouble("Cutoff", 0.99, 0.01, 0.99, 0.001); GetParam(mFilterCutoff)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 5, 177, mFilterCutoff, &smallKnobBitmap)); // Resonance knob: GetParam(mFilterResonance)->InitDouble("Resonance", 0.01, 0.01, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 61, 177, mFilterResonance, &smallKnobBitmap));
, 1.0
, calculateFeedbackAmount
, .
ProcessDoubleReplacing
:
leftOutput[i] = rightOutput[i] = mFilter.process(mOscillator.nextSample() * mEnvelopeGenerator.nextSample() * velocity / 127.0);
. switch
Synthesis::OnParamChange
:
case mFilterCutoff: mFilter.setCutoff(GetParam(paramIdx)->Value()); break; case mFilterResonance: mFilter.setResonance(GetParam(paramIdx)->Value()); break; case mFilterMode: mFilter.setFilterMode(static_cast<Filter::FilterMode>(GetParam(paramIdx)->Int())); break;
. - .
— . , , .
:
buf0 += cutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1));
, , ( buf0 – buf1
), feedbackAmount
. calculateFeedbackAmount
feedbackAmount
resonance
, . . , .
, . , , . , , .
-12 / -24 /
! 24 . switch
Filter::process
:
buf2 += cutoff * (buf1 - buf2); buf3 += cutoff * (buf2 – buf3);
: , , . , , , ( ). switch
:
switch (mode) { case FILTER_MODE_LOWPASS: return buf3; case FILTER_MODE_HIGHPASS: return inputValue - buf3; case FILTER_MODE_BANDPASS: return buf0 - buf3; default: return 0.0; }
buf1
buf3
– . -24 /. buf2
buf3
, private
Filter.h :
double buf2; double buf3;
, buf0
c buf1
:
Filter() : // ... buf0(0.0), buf1(0.0), buf2(0.0), buf3(0.0) // ...
, , : . .
?
. , , .
- cutoff
. . cutoffMod
, , cutoff
. #include private
:
double cutoffMod;
:
Filter() : cutoff(0.99), resonance(0.01), cutoffMod(0.0), // ...
. private
:
inline double getCalculatedCutoff() const { return fmax(fmin(cutoff + cutoffMod, 0.99), 0.01); };
calculateFeedbackAmount
:
inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - getCalculatedCutoff()); }
cutoffMod
. feedbackAmount
, :
inline void setCutoffMod(double newCutoffMod) { cutoffMod = newCutoffMod; calculateFeedbackAmount(); }
, . Filter::process
:
if (inputValue == 0.0) return inputValue; double calculatedCutoff = getCalculatedCutoff(); buf0 += calculatedCutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1)); buf1 += calculatedCutoff * (buf0 - buf1); buf2 += calculatedCutoff * (buf1 - buf2); buf3 += calculatedCutoff * (buf2 – buf3);
, . , , . . cutoff
calculatedCutoff
-.
, ( setCutoffMod
), Synthesis
, , . , cutoffMod
. filterEnvelopeAmount
-1
+1
. GUI.
private
Synthesis.h :
EnvelopeGenerator mFilterEnvelopeGenerator; double filterEnvelopeAmount;
MIDI , onNoteOn
onNoteOff
:
inline void onNoteOn(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); }; inline void onNoteOff(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); };
. ProcessDoubleReplacing
:
mFilter.setCutoffMod(mFilterEnvelopeGenerator.nextSample() * filterEnvelopeAmount);
, nextSample
filterEnvelopeAmount
cutoffMod
. filterEnvelopeAmount
:
Synthesis::Synthesis(IPlugInstanceInfo instanceInfo) : IPLUG_CTOR(kNumParams, kNumPrograms, instanceInfo), lastVirtualKeyboardNoteNumber(virtualKeyboardMinimumNoteNumber - 1), filterEnvelopeAmount(0.0) { // ... }
Synthesis::Reset
:
mFilterEnvelopeGenerator.setSampleRate(GetSampleRate());
EParams
, . AttachGraphics
:
// Knobs for filter envelope // Attack knob GetParam(mFilterAttack)->InitDouble("Filter Env Attack", 0.01, 0.01, 10.0, 0.001); GetParam(mFilterAttack)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 139, 178, mFilterAttack, &smallKnobBitmap)); // Decay knob: GetParam(mFilterDecay)->InitDouble("Filter Env Decay", 0.5, 0.01, 15.0, 0.001); GetParam(mFilterDecay)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 195, 178, mFilterDecay, &smallKnobBitmap)); // Sustain knob: GetParam(mFilterSustain)->InitDouble("Filter Env Sustain", 0.1, 0.001, 1.0, 0.001); GetParam(mFilterSustain)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 251, 178, mFilterSustain, &smallKnobBitmap)); // Release knob: GetParam(mFilterRelease)->InitDouble("Filter Env Release", 1.0, 0.001, 15.0, 0.001); GetParam(mFilterRelease)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 307, 178, mFilterRelease, &smallKnobBitmap)); // Filter envelope amount knob: GetParam(mFilterEnvelopeAmount)->InitDouble("Filter Env Amount", 0.0, -1.0, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 363, 178, mFilterEnvelopeAmount, &smallKnobBitmap));
, smallKnobBitmap
. . , . Synthesis::OnParamChange
switch
:
case mFilterAttack: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK, GetParam(paramIdx)->Value()); break; case mFilterDecay: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_DECAY, GetParam(paramIdx)->Value()); break; case mFilterSustain: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_SUSTAIN, GetParam(paramIdx)->Value()); break; case mFilterRelease: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE, GetParam(paramIdx)->Value()); break; case mFilterEnvelopeAmount: filterEnvelopeAmount = GetParam(paramIdx)->Value(); break;
!
! (- C1), :
EnvelopeGenerator
. . !
.
:)
:
martin-finke.de/blog/articles/audio-plugins-013-filter
#include Filter.h ( ) :
class Filter { public: enum FilterMode { FILTER_MODE_LOWPASS = 0, FILTER_MODE_HIGHPASS, FILTER_MODE_BANDPASS, kNumFilterModes }; Filter() : cutoff(0.99), resonance(0.0), mode(FILTER_MODE_LOWPASS), buf0(0.0), buf1(0.0) { calculateFeedbackAmount(); }; double process(double inputValue); inline void setCutoff(double newCutoff) { cutoff = newCutoff; calculateFeedbackAmount(); }; inline void setResonance(double newResonance) { resonance = newResonance; calculateFeedbackAmount(); }; inline void setFilterMode(FilterMode newMode) { mode = newMode; } private: double cutoff; double resonance; FilterMode mode; double feedbackAmount; inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - cutoff); } double buf0; double buf1; };
private
cutoff
resonance
. Mode
(Lowpass, Highpass, Bandpass). feedbackAmount
, buf0
buf1
, .
( calculateFeedbackAmount()
). process
. feedbackAmount
cutoff
resonance
, calculateFeedbackAmount
.
Filter.cpp :
// By Paul Kellett // http://www.musicdsp.org/showone.php?id=29 double Filter::process(double inputValue) { buf0 += cutoff * (inputValue - buf0); buf1 += cutoff * (buf0 - buf1); switch (mode) { case FILTER_MODE_LOWPASS: return buf1; case FILTER_MODE_HIGHPASS: return inputValue - buf0; case FILTER_MODE_BANDPASS: return buf0 - buf1; default: return 0.0; } }
, ? , . « » , (. . 6 ). buf0
buf1
: . inputValue
, - buf0
( ). . 6 / 12 /. switch
, buf1
. , , buf0
, 12, 6 /, .
case
FILTER_MODE_HIGHPASS
. buf0
. inputValue
buf0
, . buf1
, .
case
FILTER_MODE_BANDPASS
, buf0 - buf1
. buf0
, buf1
. , .
: , .
buf1
. ( Infinite Impulse Response , IIR ). , . , , .
, , . , , , , . , NI Massive, « ». , , . , ( Massive ). «» , , , AAS Ultra Analog, , , , . , , , , NI Reaktor. , — , .. .
. GUI. bg.png ( “Move to trash” Xcode), :
filtermode.png knob_small.png ( , 50 50 ) bg.png ( )
ID resource.h :
// Unique IDs for each image resource. #define BG_ID 101 #define WHITE_KEY_ID 102 #define BLACK_KEY_ID 103 #define WAVEFORM_ID 104 #define KNOB_ID 105 #define KNOB_SMALL_ID 106 #define FILTERMODE_ID 107 // Image resource locations for this plug. #define BG_FN "resources/img/bg.png" #define WHITE_KEY_FN "resources/img/whitekey.png" #define BLACK_KEY_FN "resources/img/blackkey.png" #define WAVEFORM_FN "resources/img/waveform.png" #define KNOB_FN "resources/img/knob.png" #define KNOB_SMALL_FN "resources/img/knob_small.png" #define FILTERMODE_FN "resources/img/filtermode.png"
Synthesis.rc :
#include "resource.h" BG_ID PNG BG_FN WHITE_KEY_ID PNG WHITE_KEY_FN BLACK_KEY_ID PNG BLACK_KEY_FN WAVEFORM_ID PNG WAVEFORM_FN KNOB_ID PNG KNOB_FN KNOB_SMALL_ID PNG KNOB_SMALL_FN FILTERMODE_ID PNG FILTERMODE_FN
#include "Filter.h"
Synthesis.h private
:
Filter mFilter;
EParams
Synthesis.cpp :
enum EParams { mWaveform = 0, mAttack, mDecay, mSustain, mRelease, mFilterMode, mFilterCutoff, mFilterResonance, mFilterAttack, mFilterDecay, mFilterSustain, mFilterRelease, mFilterEnvelopeAmount, kNumParams };
:
pGraphics->AttachControl(new ISwitchControl(this, 24, 38, mWaveform, &waveformBitmap));
. (Lowpass, Highpass, Bandpass). AttachGraphics(pGraphics)
:
GetParam(mFilterMode)->InitEnum("Filter Mode", Filter::FILTER_MODE_LOWPASS, Filter::kNumFilterModes); IBitmap filtermodeBitmap = pGraphics->LoadIBitmap(FILTERMODE_ID, FILTERMODE_FN, 3); pGraphics->AttachControl(new ISwitchControl(this, 24, 123, mFilterMode, &filtermodeBitmap));
. . knob_small.png :
// Knobs for filter cutoff and resonance IBitmap smallKnobBitmap = pGraphics->LoadIBitmap(KNOB_SMALL_ID, KNOB_SMALL_FN, 64); // Cutoff knob: GetParam(mFilterCutoff)->InitDouble("Cutoff", 0.99, 0.01, 0.99, 0.001); GetParam(mFilterCutoff)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 5, 177, mFilterCutoff, &smallKnobBitmap)); // Resonance knob: GetParam(mFilterResonance)->InitDouble("Resonance", 0.01, 0.01, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 61, 177, mFilterResonance, &smallKnobBitmap));
, 1.0
, calculateFeedbackAmount
, .
ProcessDoubleReplacing
:
leftOutput[i] = rightOutput[i] = mFilter.process(mOscillator.nextSample() * mEnvelopeGenerator.nextSample() * velocity / 127.0);
. switch
Synthesis::OnParamChange
:
case mFilterCutoff: mFilter.setCutoff(GetParam(paramIdx)->Value()); break; case mFilterResonance: mFilter.setResonance(GetParam(paramIdx)->Value()); break; case mFilterMode: mFilter.setFilterMode(static_cast<Filter::FilterMode>(GetParam(paramIdx)->Int())); break;
. - .
— . , , .
:
buf0 += cutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1));
, , ( buf0 – buf1
), feedbackAmount
. calculateFeedbackAmount
feedbackAmount
resonance
, . . , .
, . , , . , , .
-12 / -24 /
! 24 . switch
Filter::process
:
buf2 += cutoff * (buf1 - buf2); buf3 += cutoff * (buf2 – buf3);
: , , . , , , ( ). switch
:
switch (mode) { case FILTER_MODE_LOWPASS: return buf3; case FILTER_MODE_HIGHPASS: return inputValue - buf3; case FILTER_MODE_BANDPASS: return buf0 - buf3; default: return 0.0; }
buf1
buf3
– . -24 /. buf2
buf3
, private
Filter.h :
double buf2; double buf3;
, buf0
c buf1
:
Filter() : // ... buf0(0.0), buf1(0.0), buf2(0.0), buf3(0.0) // ...
, , : . .
?
. , , .
- cutoff
. . cutoffMod
, , cutoff
. #include private
:
double cutoffMod;
:
Filter() : cutoff(0.99), resonance(0.01), cutoffMod(0.0), // ...
. private
:
inline double getCalculatedCutoff() const { return fmax(fmin(cutoff + cutoffMod, 0.99), 0.01); };
calculateFeedbackAmount
:
inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - getCalculatedCutoff()); }
cutoffMod
. feedbackAmount
, :
inline void setCutoffMod(double newCutoffMod) { cutoffMod = newCutoffMod; calculateFeedbackAmount(); }
, . Filter::process
:
if (inputValue == 0.0) return inputValue; double calculatedCutoff = getCalculatedCutoff(); buf0 += calculatedCutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1)); buf1 += calculatedCutoff * (buf0 - buf1); buf2 += calculatedCutoff * (buf1 - buf2); buf3 += calculatedCutoff * (buf2 – buf3);
, . , , . . cutoff
calculatedCutoff
-.
, ( setCutoffMod
), Synthesis
, , . , cutoffMod
. filterEnvelopeAmount
-1
+1
. GUI.
private
Synthesis.h :
EnvelopeGenerator mFilterEnvelopeGenerator; double filterEnvelopeAmount;
MIDI , onNoteOn
onNoteOff
:
inline void onNoteOn(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); }; inline void onNoteOff(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); };
. ProcessDoubleReplacing
:
mFilter.setCutoffMod(mFilterEnvelopeGenerator.nextSample() * filterEnvelopeAmount);
, nextSample
filterEnvelopeAmount
cutoffMod
. filterEnvelopeAmount
:
Synthesis::Synthesis(IPlugInstanceInfo instanceInfo) : IPLUG_CTOR(kNumParams, kNumPrograms, instanceInfo), lastVirtualKeyboardNoteNumber(virtualKeyboardMinimumNoteNumber - 1), filterEnvelopeAmount(0.0) { // ... }
Synthesis::Reset
:
mFilterEnvelopeGenerator.setSampleRate(GetSampleRate());
EParams
, . AttachGraphics
:
// Knobs for filter envelope // Attack knob GetParam(mFilterAttack)->InitDouble("Filter Env Attack", 0.01, 0.01, 10.0, 0.001); GetParam(mFilterAttack)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 139, 178, mFilterAttack, &smallKnobBitmap)); // Decay knob: GetParam(mFilterDecay)->InitDouble("Filter Env Decay", 0.5, 0.01, 15.0, 0.001); GetParam(mFilterDecay)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 195, 178, mFilterDecay, &smallKnobBitmap)); // Sustain knob: GetParam(mFilterSustain)->InitDouble("Filter Env Sustain", 0.1, 0.001, 1.0, 0.001); GetParam(mFilterSustain)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 251, 178, mFilterSustain, &smallKnobBitmap)); // Release knob: GetParam(mFilterRelease)->InitDouble("Filter Env Release", 1.0, 0.001, 15.0, 0.001); GetParam(mFilterRelease)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 307, 178, mFilterRelease, &smallKnobBitmap)); // Filter envelope amount knob: GetParam(mFilterEnvelopeAmount)->InitDouble("Filter Env Amount", 0.0, -1.0, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 363, 178, mFilterEnvelopeAmount, &smallKnobBitmap));
, smallKnobBitmap
. . , . Synthesis::OnParamChange
switch
:
case mFilterAttack: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK, GetParam(paramIdx)->Value()); break; case mFilterDecay: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_DECAY, GetParam(paramIdx)->Value()); break; case mFilterSustain: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_SUSTAIN, GetParam(paramIdx)->Value()); break; case mFilterRelease: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE, GetParam(paramIdx)->Value()); break; case mFilterEnvelopeAmount: filterEnvelopeAmount = GetParam(paramIdx)->Value(); break;
!
! (- C1), :
EnvelopeGenerator
. . !
.
:)
:
martin-finke.de/blog/articles/audio-plugins-013-filter
#include Filter.h ( ) :
class Filter { public: enum FilterMode { FILTER_MODE_LOWPASS = 0, FILTER_MODE_HIGHPASS, FILTER_MODE_BANDPASS, kNumFilterModes }; Filter() : cutoff(0.99), resonance(0.0), mode(FILTER_MODE_LOWPASS), buf0(0.0), buf1(0.0) { calculateFeedbackAmount(); }; double process(double inputValue); inline void setCutoff(double newCutoff) { cutoff = newCutoff; calculateFeedbackAmount(); }; inline void setResonance(double newResonance) { resonance = newResonance; calculateFeedbackAmount(); }; inline void setFilterMode(FilterMode newMode) { mode = newMode; } private: double cutoff; double resonance; FilterMode mode; double feedbackAmount; inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - cutoff); } double buf0; double buf1; };
private
cutoff
resonance
. Mode
(Lowpass, Highpass, Bandpass). feedbackAmount
, buf0
buf1
, .
( calculateFeedbackAmount()
). process
. feedbackAmount
cutoff
resonance
, calculateFeedbackAmount
.
Filter.cpp :
// By Paul Kellett // http://www.musicdsp.org/showone.php?id=29 double Filter::process(double inputValue) { buf0 += cutoff * (inputValue - buf0); buf1 += cutoff * (buf0 - buf1); switch (mode) { case FILTER_MODE_LOWPASS: return buf1; case FILTER_MODE_HIGHPASS: return inputValue - buf0; case FILTER_MODE_BANDPASS: return buf0 - buf1; default: return 0.0; } }
, ? , . « » , (. . 6 ). buf0
buf1
: . inputValue
, - buf0
( ). . 6 / 12 /. switch
, buf1
. , , buf0
, 12, 6 /, .
case
FILTER_MODE_HIGHPASS
. buf0
. inputValue
buf0
, . buf1
, .
case
FILTER_MODE_BANDPASS
, buf0 - buf1
. buf0
, buf1
. , .
: , .
buf1
. ( Infinite Impulse Response , IIR ). , . , , .
, , . , , , , . , NI Massive, « ». , , . , ( Massive ). «» , , , AAS Ultra Analog, , , , . , , , , NI Reaktor. , — , .. .
. GUI. bg.png ( “Move to trash” Xcode), :
filtermode.png knob_small.png ( , 50 50 ) bg.png ( )
ID resource.h :
// Unique IDs for each image resource. #define BG_ID 101 #define WHITE_KEY_ID 102 #define BLACK_KEY_ID 103 #define WAVEFORM_ID 104 #define KNOB_ID 105 #define KNOB_SMALL_ID 106 #define FILTERMODE_ID 107 // Image resource locations for this plug. #define BG_FN "resources/img/bg.png" #define WHITE_KEY_FN "resources/img/whitekey.png" #define BLACK_KEY_FN "resources/img/blackkey.png" #define WAVEFORM_FN "resources/img/waveform.png" #define KNOB_FN "resources/img/knob.png" #define KNOB_SMALL_FN "resources/img/knob_small.png" #define FILTERMODE_FN "resources/img/filtermode.png"
Synthesis.rc :
#include "resource.h" BG_ID PNG BG_FN WHITE_KEY_ID PNG WHITE_KEY_FN BLACK_KEY_ID PNG BLACK_KEY_FN WAVEFORM_ID PNG WAVEFORM_FN KNOB_ID PNG KNOB_FN KNOB_SMALL_ID PNG KNOB_SMALL_FN FILTERMODE_ID PNG FILTERMODE_FN
#include "Filter.h"
Synthesis.h private
:
Filter mFilter;
EParams
Synthesis.cpp :
enum EParams { mWaveform = 0, mAttack, mDecay, mSustain, mRelease, mFilterMode, mFilterCutoff, mFilterResonance, mFilterAttack, mFilterDecay, mFilterSustain, mFilterRelease, mFilterEnvelopeAmount, kNumParams };
:
pGraphics->AttachControl(new ISwitchControl(this, 24, 38, mWaveform, &waveformBitmap));
. (Lowpass, Highpass, Bandpass). AttachGraphics(pGraphics)
:
GetParam(mFilterMode)->InitEnum("Filter Mode", Filter::FILTER_MODE_LOWPASS, Filter::kNumFilterModes); IBitmap filtermodeBitmap = pGraphics->LoadIBitmap(FILTERMODE_ID, FILTERMODE_FN, 3); pGraphics->AttachControl(new ISwitchControl(this, 24, 123, mFilterMode, &filtermodeBitmap));
. . knob_small.png :
// Knobs for filter cutoff and resonance IBitmap smallKnobBitmap = pGraphics->LoadIBitmap(KNOB_SMALL_ID, KNOB_SMALL_FN, 64); // Cutoff knob: GetParam(mFilterCutoff)->InitDouble("Cutoff", 0.99, 0.01, 0.99, 0.001); GetParam(mFilterCutoff)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 5, 177, mFilterCutoff, &smallKnobBitmap)); // Resonance knob: GetParam(mFilterResonance)->InitDouble("Resonance", 0.01, 0.01, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 61, 177, mFilterResonance, &smallKnobBitmap));
, 1.0
, calculateFeedbackAmount
, .
ProcessDoubleReplacing
:
leftOutput[i] = rightOutput[i] = mFilter.process(mOscillator.nextSample() * mEnvelopeGenerator.nextSample() * velocity / 127.0);
. switch
Synthesis::OnParamChange
:
case mFilterCutoff: mFilter.setCutoff(GetParam(paramIdx)->Value()); break; case mFilterResonance: mFilter.setResonance(GetParam(paramIdx)->Value()); break; case mFilterMode: mFilter.setFilterMode(static_cast<Filter::FilterMode>(GetParam(paramIdx)->Int())); break;
. - .
— . , , .
:
buf0 += cutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1));
, , ( buf0 – buf1
), feedbackAmount
. calculateFeedbackAmount
feedbackAmount
resonance
, . . , .
, . , , . , , .
-12 / -24 /
! 24 . switch
Filter::process
:
buf2 += cutoff * (buf1 - buf2); buf3 += cutoff * (buf2 – buf3);
: , , . , , , ( ). switch
:
switch (mode) { case FILTER_MODE_LOWPASS: return buf3; case FILTER_MODE_HIGHPASS: return inputValue - buf3; case FILTER_MODE_BANDPASS: return buf0 - buf3; default: return 0.0; }
buf1
buf3
– . -24 /. buf2
buf3
, private
Filter.h :
double buf2; double buf3;
, buf0
c buf1
:
Filter() : // ... buf0(0.0), buf1(0.0), buf2(0.0), buf3(0.0) // ...
, , : . .
?
. , , .
- cutoff
. . cutoffMod
, , cutoff
. #include private
:
double cutoffMod;
:
Filter() : cutoff(0.99), resonance(0.01), cutoffMod(0.0), // ...
. private
:
inline double getCalculatedCutoff() const { return fmax(fmin(cutoff + cutoffMod, 0.99), 0.01); };
calculateFeedbackAmount
:
inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - getCalculatedCutoff()); }
cutoffMod
. feedbackAmount
, :
inline void setCutoffMod(double newCutoffMod) { cutoffMod = newCutoffMod; calculateFeedbackAmount(); }
, . Filter::process
:
if (inputValue == 0.0) return inputValue; double calculatedCutoff = getCalculatedCutoff(); buf0 += calculatedCutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1)); buf1 += calculatedCutoff * (buf0 - buf1); buf2 += calculatedCutoff * (buf1 - buf2); buf3 += calculatedCutoff * (buf2 – buf3);
, . , , . . cutoff
calculatedCutoff
-.
, ( setCutoffMod
), Synthesis
, , . , cutoffMod
. filterEnvelopeAmount
-1
+1
. GUI.
private
Synthesis.h :
EnvelopeGenerator mFilterEnvelopeGenerator; double filterEnvelopeAmount;
MIDI , onNoteOn
onNoteOff
:
inline void onNoteOn(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); }; inline void onNoteOff(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); };
. ProcessDoubleReplacing
:
mFilter.setCutoffMod(mFilterEnvelopeGenerator.nextSample() * filterEnvelopeAmount);
, nextSample
filterEnvelopeAmount
cutoffMod
. filterEnvelopeAmount
:
Synthesis::Synthesis(IPlugInstanceInfo instanceInfo) : IPLUG_CTOR(kNumParams, kNumPrograms, instanceInfo), lastVirtualKeyboardNoteNumber(virtualKeyboardMinimumNoteNumber - 1), filterEnvelopeAmount(0.0) { // ... }
Synthesis::Reset
:
mFilterEnvelopeGenerator.setSampleRate(GetSampleRate());
EParams
, . AttachGraphics
:
// Knobs for filter envelope // Attack knob GetParam(mFilterAttack)->InitDouble("Filter Env Attack", 0.01, 0.01, 10.0, 0.001); GetParam(mFilterAttack)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 139, 178, mFilterAttack, &smallKnobBitmap)); // Decay knob: GetParam(mFilterDecay)->InitDouble("Filter Env Decay", 0.5, 0.01, 15.0, 0.001); GetParam(mFilterDecay)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 195, 178, mFilterDecay, &smallKnobBitmap)); // Sustain knob: GetParam(mFilterSustain)->InitDouble("Filter Env Sustain", 0.1, 0.001, 1.0, 0.001); GetParam(mFilterSustain)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 251, 178, mFilterSustain, &smallKnobBitmap)); // Release knob: GetParam(mFilterRelease)->InitDouble("Filter Env Release", 1.0, 0.001, 15.0, 0.001); GetParam(mFilterRelease)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 307, 178, mFilterRelease, &smallKnobBitmap)); // Filter envelope amount knob: GetParam(mFilterEnvelopeAmount)->InitDouble("Filter Env Amount", 0.0, -1.0, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 363, 178, mFilterEnvelopeAmount, &smallKnobBitmap));
, smallKnobBitmap
. . , . Synthesis::OnParamChange
switch
:
case mFilterAttack: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK, GetParam(paramIdx)->Value()); break; case mFilterDecay: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_DECAY, GetParam(paramIdx)->Value()); break; case mFilterSustain: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_SUSTAIN, GetParam(paramIdx)->Value()); break; case mFilterRelease: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE, GetParam(paramIdx)->Value()); break; case mFilterEnvelopeAmount: filterEnvelopeAmount = GetParam(paramIdx)->Value(); break;
!
! (- C1), :
EnvelopeGenerator
. . !
.
:)
:
martin-finke.de/blog/articles/audio-plugins-013-filter
#include Filter.h ( ) :
class Filter { public: enum FilterMode { FILTER_MODE_LOWPASS = 0, FILTER_MODE_HIGHPASS, FILTER_MODE_BANDPASS, kNumFilterModes }; Filter() : cutoff(0.99), resonance(0.0), mode(FILTER_MODE_LOWPASS), buf0(0.0), buf1(0.0) { calculateFeedbackAmount(); }; double process(double inputValue); inline void setCutoff(double newCutoff) { cutoff = newCutoff; calculateFeedbackAmount(); }; inline void setResonance(double newResonance) { resonance = newResonance; calculateFeedbackAmount(); }; inline void setFilterMode(FilterMode newMode) { mode = newMode; } private: double cutoff; double resonance; FilterMode mode; double feedbackAmount; inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - cutoff); } double buf0; double buf1; };
private
cutoff
resonance
. Mode
(Lowpass, Highpass, Bandpass). feedbackAmount
, buf0
buf1
, .
( calculateFeedbackAmount()
). process
. feedbackAmount
cutoff
resonance
, calculateFeedbackAmount
.
Filter.cpp :
// By Paul Kellett // http://www.musicdsp.org/showone.php?id=29 double Filter::process(double inputValue) { buf0 += cutoff * (inputValue - buf0); buf1 += cutoff * (buf0 - buf1); switch (mode) { case FILTER_MODE_LOWPASS: return buf1; case FILTER_MODE_HIGHPASS: return inputValue - buf0; case FILTER_MODE_BANDPASS: return buf0 - buf1; default: return 0.0; } }
, ? , . « » , (. . 6 ). buf0
buf1
: . inputValue
, - buf0
( ). . 6 / 12 /. switch
, buf1
. , , buf0
, 12, 6 /, .
case
FILTER_MODE_HIGHPASS
. buf0
. inputValue
buf0
, . buf1
, .
case
FILTER_MODE_BANDPASS
, buf0 - buf1
. buf0
, buf1
. , .
: , .
buf1
. ( Infinite Impulse Response , IIR ). , . , , .
, , . , , , , . , NI Massive, « ». , , . , ( Massive ). «» , , , AAS Ultra Analog, , , , . , , , , NI Reaktor. , — , .. .
. GUI. bg.png ( “Move to trash” Xcode), :
filtermode.png knob_small.png ( , 50 50 ) bg.png ( )
ID resource.h :
// Unique IDs for each image resource. #define BG_ID 101 #define WHITE_KEY_ID 102 #define BLACK_KEY_ID 103 #define WAVEFORM_ID 104 #define KNOB_ID 105 #define KNOB_SMALL_ID 106 #define FILTERMODE_ID 107 // Image resource locations for this plug. #define BG_FN "resources/img/bg.png" #define WHITE_KEY_FN "resources/img/whitekey.png" #define BLACK_KEY_FN "resources/img/blackkey.png" #define WAVEFORM_FN "resources/img/waveform.png" #define KNOB_FN "resources/img/knob.png" #define KNOB_SMALL_FN "resources/img/knob_small.png" #define FILTERMODE_FN "resources/img/filtermode.png"
Synthesis.rc :
#include "resource.h" BG_ID PNG BG_FN WHITE_KEY_ID PNG WHITE_KEY_FN BLACK_KEY_ID PNG BLACK_KEY_FN WAVEFORM_ID PNG WAVEFORM_FN KNOB_ID PNG KNOB_FN KNOB_SMALL_ID PNG KNOB_SMALL_FN FILTERMODE_ID PNG FILTERMODE_FN
#include "Filter.h"
Synthesis.h private
:
Filter mFilter;
EParams
Synthesis.cpp :
enum EParams { mWaveform = 0, mAttack, mDecay, mSustain, mRelease, mFilterMode, mFilterCutoff, mFilterResonance, mFilterAttack, mFilterDecay, mFilterSustain, mFilterRelease, mFilterEnvelopeAmount, kNumParams };
:
pGraphics->AttachControl(new ISwitchControl(this, 24, 38, mWaveform, &waveformBitmap));
. (Lowpass, Highpass, Bandpass). AttachGraphics(pGraphics)
:
GetParam(mFilterMode)->InitEnum("Filter Mode", Filter::FILTER_MODE_LOWPASS, Filter::kNumFilterModes); IBitmap filtermodeBitmap = pGraphics->LoadIBitmap(FILTERMODE_ID, FILTERMODE_FN, 3); pGraphics->AttachControl(new ISwitchControl(this, 24, 123, mFilterMode, &filtermodeBitmap));
. . knob_small.png :
// Knobs for filter cutoff and resonance IBitmap smallKnobBitmap = pGraphics->LoadIBitmap(KNOB_SMALL_ID, KNOB_SMALL_FN, 64); // Cutoff knob: GetParam(mFilterCutoff)->InitDouble("Cutoff", 0.99, 0.01, 0.99, 0.001); GetParam(mFilterCutoff)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 5, 177, mFilterCutoff, &smallKnobBitmap)); // Resonance knob: GetParam(mFilterResonance)->InitDouble("Resonance", 0.01, 0.01, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 61, 177, mFilterResonance, &smallKnobBitmap));
, 1.0
, calculateFeedbackAmount
, .
ProcessDoubleReplacing
:
leftOutput[i] = rightOutput[i] = mFilter.process(mOscillator.nextSample() * mEnvelopeGenerator.nextSample() * velocity / 127.0);
. switch
Synthesis::OnParamChange
:
case mFilterCutoff: mFilter.setCutoff(GetParam(paramIdx)->Value()); break; case mFilterResonance: mFilter.setResonance(GetParam(paramIdx)->Value()); break; case mFilterMode: mFilter.setFilterMode(static_cast<Filter::FilterMode>(GetParam(paramIdx)->Int())); break;
. - .
— . , , .
:
buf0 += cutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1));
, , ( buf0 – buf1
), feedbackAmount
. calculateFeedbackAmount
feedbackAmount
resonance
, . . , .
, . , , . , , .
-12 / -24 /
! 24 . switch
Filter::process
:
buf2 += cutoff * (buf1 - buf2); buf3 += cutoff * (buf2 – buf3);
: , , . , , , ( ). switch
:
switch (mode) { case FILTER_MODE_LOWPASS: return buf3; case FILTER_MODE_HIGHPASS: return inputValue - buf3; case FILTER_MODE_BANDPASS: return buf0 - buf3; default: return 0.0; }
buf1
buf3
– . -24 /. buf2
buf3
, private
Filter.h :
double buf2; double buf3;
, buf0
c buf1
:
Filter() : // ... buf0(0.0), buf1(0.0), buf2(0.0), buf3(0.0) // ...
, , : . .
?
. , , .
- cutoff
. . cutoffMod
, , cutoff
. #include private
:
double cutoffMod;
:
Filter() : cutoff(0.99), resonance(0.01), cutoffMod(0.0), // ...
. private
:
inline double getCalculatedCutoff() const { return fmax(fmin(cutoff + cutoffMod, 0.99), 0.01); };
calculateFeedbackAmount
:
inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - getCalculatedCutoff()); }
cutoffMod
. feedbackAmount
, :
inline void setCutoffMod(double newCutoffMod) { cutoffMod = newCutoffMod; calculateFeedbackAmount(); }
, . Filter::process
:
if (inputValue == 0.0) return inputValue; double calculatedCutoff = getCalculatedCutoff(); buf0 += calculatedCutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1)); buf1 += calculatedCutoff * (buf0 - buf1); buf2 += calculatedCutoff * (buf1 - buf2); buf3 += calculatedCutoff * (buf2 – buf3);
, . , , . . cutoff
calculatedCutoff
-.
, ( setCutoffMod
), Synthesis
, , . , cutoffMod
. filterEnvelopeAmount
-1
+1
. GUI.
private
Synthesis.h :
EnvelopeGenerator mFilterEnvelopeGenerator; double filterEnvelopeAmount;
MIDI , onNoteOn
onNoteOff
:
inline void onNoteOn(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); }; inline void onNoteOff(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); };
. ProcessDoubleReplacing
:
mFilter.setCutoffMod(mFilterEnvelopeGenerator.nextSample() * filterEnvelopeAmount);
, nextSample
filterEnvelopeAmount
cutoffMod
. filterEnvelopeAmount
:
Synthesis::Synthesis(IPlugInstanceInfo instanceInfo) : IPLUG_CTOR(kNumParams, kNumPrograms, instanceInfo), lastVirtualKeyboardNoteNumber(virtualKeyboardMinimumNoteNumber - 1), filterEnvelopeAmount(0.0) { // ... }
Synthesis::Reset
:
mFilterEnvelopeGenerator.setSampleRate(GetSampleRate());
EParams
, . AttachGraphics
:
// Knobs for filter envelope // Attack knob GetParam(mFilterAttack)->InitDouble("Filter Env Attack", 0.01, 0.01, 10.0, 0.001); GetParam(mFilterAttack)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 139, 178, mFilterAttack, &smallKnobBitmap)); // Decay knob: GetParam(mFilterDecay)->InitDouble("Filter Env Decay", 0.5, 0.01, 15.0, 0.001); GetParam(mFilterDecay)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 195, 178, mFilterDecay, &smallKnobBitmap)); // Sustain knob: GetParam(mFilterSustain)->InitDouble("Filter Env Sustain", 0.1, 0.001, 1.0, 0.001); GetParam(mFilterSustain)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 251, 178, mFilterSustain, &smallKnobBitmap)); // Release knob: GetParam(mFilterRelease)->InitDouble("Filter Env Release", 1.0, 0.001, 15.0, 0.001); GetParam(mFilterRelease)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 307, 178, mFilterRelease, &smallKnobBitmap)); // Filter envelope amount knob: GetParam(mFilterEnvelopeAmount)->InitDouble("Filter Env Amount", 0.0, -1.0, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 363, 178, mFilterEnvelopeAmount, &smallKnobBitmap));
, smallKnobBitmap
. . , . Synthesis::OnParamChange
switch
:
case mFilterAttack: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK, GetParam(paramIdx)->Value()); break; case mFilterDecay: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_DECAY, GetParam(paramIdx)->Value()); break; case mFilterSustain: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_SUSTAIN, GetParam(paramIdx)->Value()); break; case mFilterRelease: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE, GetParam(paramIdx)->Value()); break; case mFilterEnvelopeAmount: filterEnvelopeAmount = GetParam(paramIdx)->Value(); break;
!
! (- C1), :
EnvelopeGenerator
. . !
.
:)
:
martin-finke.de/blog/articles/audio-plugins-013-filter
#include Filter.h ( ) :
class Filter { public: enum FilterMode { FILTER_MODE_LOWPASS = 0, FILTER_MODE_HIGHPASS, FILTER_MODE_BANDPASS, kNumFilterModes }; Filter() : cutoff(0.99), resonance(0.0), mode(FILTER_MODE_LOWPASS), buf0(0.0), buf1(0.0) { calculateFeedbackAmount(); }; double process(double inputValue); inline void setCutoff(double newCutoff) { cutoff = newCutoff; calculateFeedbackAmount(); }; inline void setResonance(double newResonance) { resonance = newResonance; calculateFeedbackAmount(); }; inline void setFilterMode(FilterMode newMode) { mode = newMode; } private: double cutoff; double resonance; FilterMode mode; double feedbackAmount; inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - cutoff); } double buf0; double buf1; };
private
cutoff
resonance
. Mode
(Lowpass, Highpass, Bandpass). feedbackAmount
, buf0
buf1
, .
( calculateFeedbackAmount()
). process
. feedbackAmount
cutoff
resonance
, calculateFeedbackAmount
.
Filter.cpp :
// By Paul Kellett // http://www.musicdsp.org/showone.php?id=29 double Filter::process(double inputValue) { buf0 += cutoff * (inputValue - buf0); buf1 += cutoff * (buf0 - buf1); switch (mode) { case FILTER_MODE_LOWPASS: return buf1; case FILTER_MODE_HIGHPASS: return inputValue - buf0; case FILTER_MODE_BANDPASS: return buf0 - buf1; default: return 0.0; } }
, ? , . « » , (. . 6 ). buf0
buf1
: . inputValue
, - buf0
( ). . 6 / 12 /. switch
, buf1
. , , buf0
, 12, 6 /, .
case
FILTER_MODE_HIGHPASS
. buf0
. inputValue
buf0
, . buf1
, .
case
FILTER_MODE_BANDPASS
, buf0 - buf1
. buf0
, buf1
. , .
: , .
buf1
. ( Infinite Impulse Response , IIR ). , . , , .
, , . , , , , . , NI Massive, « ». , , . , ( Massive ). «» , , , AAS Ultra Analog, , , , . , , , , NI Reaktor. , — , .. .
. GUI. bg.png ( “Move to trash” Xcode), :
filtermode.png knob_small.png ( , 50 50 ) bg.png ( )
ID resource.h :
// Unique IDs for each image resource. #define BG_ID 101 #define WHITE_KEY_ID 102 #define BLACK_KEY_ID 103 #define WAVEFORM_ID 104 #define KNOB_ID 105 #define KNOB_SMALL_ID 106 #define FILTERMODE_ID 107 // Image resource locations for this plug. #define BG_FN "resources/img/bg.png" #define WHITE_KEY_FN "resources/img/whitekey.png" #define BLACK_KEY_FN "resources/img/blackkey.png" #define WAVEFORM_FN "resources/img/waveform.png" #define KNOB_FN "resources/img/knob.png" #define KNOB_SMALL_FN "resources/img/knob_small.png" #define FILTERMODE_FN "resources/img/filtermode.png"
Synthesis.rc :
#include "resource.h" BG_ID PNG BG_FN WHITE_KEY_ID PNG WHITE_KEY_FN BLACK_KEY_ID PNG BLACK_KEY_FN WAVEFORM_ID PNG WAVEFORM_FN KNOB_ID PNG KNOB_FN KNOB_SMALL_ID PNG KNOB_SMALL_FN FILTERMODE_ID PNG FILTERMODE_FN
#include "Filter.h"
Synthesis.h private
:
Filter mFilter;
EParams
Synthesis.cpp :
enum EParams { mWaveform = 0, mAttack, mDecay, mSustain, mRelease, mFilterMode, mFilterCutoff, mFilterResonance, mFilterAttack, mFilterDecay, mFilterSustain, mFilterRelease, mFilterEnvelopeAmount, kNumParams };
:
pGraphics->AttachControl(new ISwitchControl(this, 24, 38, mWaveform, &waveformBitmap));
. (Lowpass, Highpass, Bandpass). AttachGraphics(pGraphics)
:
GetParam(mFilterMode)->InitEnum("Filter Mode", Filter::FILTER_MODE_LOWPASS, Filter::kNumFilterModes); IBitmap filtermodeBitmap = pGraphics->LoadIBitmap(FILTERMODE_ID, FILTERMODE_FN, 3); pGraphics->AttachControl(new ISwitchControl(this, 24, 123, mFilterMode, &filtermodeBitmap));
. . knob_small.png :
// Knobs for filter cutoff and resonance IBitmap smallKnobBitmap = pGraphics->LoadIBitmap(KNOB_SMALL_ID, KNOB_SMALL_FN, 64); // Cutoff knob: GetParam(mFilterCutoff)->InitDouble("Cutoff", 0.99, 0.01, 0.99, 0.001); GetParam(mFilterCutoff)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 5, 177, mFilterCutoff, &smallKnobBitmap)); // Resonance knob: GetParam(mFilterResonance)->InitDouble("Resonance", 0.01, 0.01, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 61, 177, mFilterResonance, &smallKnobBitmap));
, 1.0
, calculateFeedbackAmount
, .
ProcessDoubleReplacing
:
leftOutput[i] = rightOutput[i] = mFilter.process(mOscillator.nextSample() * mEnvelopeGenerator.nextSample() * velocity / 127.0);
. switch
Synthesis::OnParamChange
:
case mFilterCutoff: mFilter.setCutoff(GetParam(paramIdx)->Value()); break; case mFilterResonance: mFilter.setResonance(GetParam(paramIdx)->Value()); break; case mFilterMode: mFilter.setFilterMode(static_cast<Filter::FilterMode>(GetParam(paramIdx)->Int())); break;
. - .
— . , , .
:
buf0 += cutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1));
, , ( buf0 – buf1
), feedbackAmount
. calculateFeedbackAmount
feedbackAmount
resonance
, . . , .
, . , , . , , .
-12 / -24 /
! 24 . switch
Filter::process
:
buf2 += cutoff * (buf1 - buf2); buf3 += cutoff * (buf2 – buf3);
: , , . , , , ( ). switch
:
switch (mode) { case FILTER_MODE_LOWPASS: return buf3; case FILTER_MODE_HIGHPASS: return inputValue - buf3; case FILTER_MODE_BANDPASS: return buf0 - buf3; default: return 0.0; }
buf1
buf3
– . -24 /. buf2
buf3
, private
Filter.h :
double buf2; double buf3;
, buf0
c buf1
:
Filter() : // ... buf0(0.0), buf1(0.0), buf2(0.0), buf3(0.0) // ...
, , : . .
?
. , , .
- cutoff
. . cutoffMod
, , cutoff
. #include private
:
double cutoffMod;
:
Filter() : cutoff(0.99), resonance(0.01), cutoffMod(0.0), // ...
. private
:
inline double getCalculatedCutoff() const { return fmax(fmin(cutoff + cutoffMod, 0.99), 0.01); };
calculateFeedbackAmount
:
inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - getCalculatedCutoff()); }
cutoffMod
. feedbackAmount
, :
inline void setCutoffMod(double newCutoffMod) { cutoffMod = newCutoffMod; calculateFeedbackAmount(); }
, . Filter::process
:
if (inputValue == 0.0) return inputValue; double calculatedCutoff = getCalculatedCutoff(); buf0 += calculatedCutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1)); buf1 += calculatedCutoff * (buf0 - buf1); buf2 += calculatedCutoff * (buf1 - buf2); buf3 += calculatedCutoff * (buf2 – buf3);
, . , , . . cutoff
calculatedCutoff
-.
, ( setCutoffMod
), Synthesis
, , . , cutoffMod
. filterEnvelopeAmount
-1
+1
. GUI.
private
Synthesis.h :
EnvelopeGenerator mFilterEnvelopeGenerator; double filterEnvelopeAmount;
MIDI , onNoteOn
onNoteOff
:
inline void onNoteOn(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); }; inline void onNoteOff(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); };
. ProcessDoubleReplacing
:
mFilter.setCutoffMod(mFilterEnvelopeGenerator.nextSample() * filterEnvelopeAmount);
, nextSample
filterEnvelopeAmount
cutoffMod
. filterEnvelopeAmount
:
Synthesis::Synthesis(IPlugInstanceInfo instanceInfo) : IPLUG_CTOR(kNumParams, kNumPrograms, instanceInfo), lastVirtualKeyboardNoteNumber(virtualKeyboardMinimumNoteNumber - 1), filterEnvelopeAmount(0.0) { // ... }
Synthesis::Reset
:
mFilterEnvelopeGenerator.setSampleRate(GetSampleRate());
EParams
, . AttachGraphics
:
// Knobs for filter envelope // Attack knob GetParam(mFilterAttack)->InitDouble("Filter Env Attack", 0.01, 0.01, 10.0, 0.001); GetParam(mFilterAttack)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 139, 178, mFilterAttack, &smallKnobBitmap)); // Decay knob: GetParam(mFilterDecay)->InitDouble("Filter Env Decay", 0.5, 0.01, 15.0, 0.001); GetParam(mFilterDecay)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 195, 178, mFilterDecay, &smallKnobBitmap)); // Sustain knob: GetParam(mFilterSustain)->InitDouble("Filter Env Sustain", 0.1, 0.001, 1.0, 0.001); GetParam(mFilterSustain)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 251, 178, mFilterSustain, &smallKnobBitmap)); // Release knob: GetParam(mFilterRelease)->InitDouble("Filter Env Release", 1.0, 0.001, 15.0, 0.001); GetParam(mFilterRelease)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 307, 178, mFilterRelease, &smallKnobBitmap)); // Filter envelope amount knob: GetParam(mFilterEnvelopeAmount)->InitDouble("Filter Env Amount", 0.0, -1.0, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 363, 178, mFilterEnvelopeAmount, &smallKnobBitmap));
, smallKnobBitmap
. . , . Synthesis::OnParamChange
switch
:
case mFilterAttack: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK, GetParam(paramIdx)->Value()); break; case mFilterDecay: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_DECAY, GetParam(paramIdx)->Value()); break; case mFilterSustain: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_SUSTAIN, GetParam(paramIdx)->Value()); break; case mFilterRelease: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE, GetParam(paramIdx)->Value()); break; case mFilterEnvelopeAmount: filterEnvelopeAmount = GetParam(paramIdx)->Value(); break;
!
! (- C1), :
EnvelopeGenerator
. . !
.
:)
:
martin-finke.de/blog/articles/audio-plugins-013-filter
#include Filter.h ( ) :
class Filter { public: enum FilterMode { FILTER_MODE_LOWPASS = 0, FILTER_MODE_HIGHPASS, FILTER_MODE_BANDPASS, kNumFilterModes }; Filter() : cutoff(0.99), resonance(0.0), mode(FILTER_MODE_LOWPASS), buf0(0.0), buf1(0.0) { calculateFeedbackAmount(); }; double process(double inputValue); inline void setCutoff(double newCutoff) { cutoff = newCutoff; calculateFeedbackAmount(); }; inline void setResonance(double newResonance) { resonance = newResonance; calculateFeedbackAmount(); }; inline void setFilterMode(FilterMode newMode) { mode = newMode; } private: double cutoff; double resonance; FilterMode mode; double feedbackAmount; inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - cutoff); } double buf0; double buf1; };
private
cutoff
resonance
. Mode
(Lowpass, Highpass, Bandpass). feedbackAmount
, buf0
buf1
, .
( calculateFeedbackAmount()
). process
. feedbackAmount
cutoff
resonance
, calculateFeedbackAmount
.
Filter.cpp :
// By Paul Kellett // http://www.musicdsp.org/showone.php?id=29 double Filter::process(double inputValue) { buf0 += cutoff * (inputValue - buf0); buf1 += cutoff * (buf0 - buf1); switch (mode) { case FILTER_MODE_LOWPASS: return buf1; case FILTER_MODE_HIGHPASS: return inputValue - buf0; case FILTER_MODE_BANDPASS: return buf0 - buf1; default: return 0.0; } }
, ? , . « » , (. . 6 ). buf0
buf1
: . inputValue
, - buf0
( ). . 6 / 12 /. switch
, buf1
. , , buf0
, 12, 6 /, .
case
FILTER_MODE_HIGHPASS
. buf0
. inputValue
buf0
, . buf1
, .
case
FILTER_MODE_BANDPASS
, buf0 - buf1
. buf0
, buf1
. , .
: , .
buf1
. ( Infinite Impulse Response , IIR ). , . , , .
, , . , , , , . , NI Massive, « ». , , . , ( Massive ). «» , , , AAS Ultra Analog, , , , . , , , , NI Reaktor. , — , .. .
. GUI. bg.png ( “Move to trash” Xcode), :
filtermode.png knob_small.png ( , 50 50 ) bg.png ( )
ID resource.h :
// Unique IDs for each image resource. #define BG_ID 101 #define WHITE_KEY_ID 102 #define BLACK_KEY_ID 103 #define WAVEFORM_ID 104 #define KNOB_ID 105 #define KNOB_SMALL_ID 106 #define FILTERMODE_ID 107 // Image resource locations for this plug. #define BG_FN "resources/img/bg.png" #define WHITE_KEY_FN "resources/img/whitekey.png" #define BLACK_KEY_FN "resources/img/blackkey.png" #define WAVEFORM_FN "resources/img/waveform.png" #define KNOB_FN "resources/img/knob.png" #define KNOB_SMALL_FN "resources/img/knob_small.png" #define FILTERMODE_FN "resources/img/filtermode.png"
Synthesis.rc :
#include "resource.h" BG_ID PNG BG_FN WHITE_KEY_ID PNG WHITE_KEY_FN BLACK_KEY_ID PNG BLACK_KEY_FN WAVEFORM_ID PNG WAVEFORM_FN KNOB_ID PNG KNOB_FN KNOB_SMALL_ID PNG KNOB_SMALL_FN FILTERMODE_ID PNG FILTERMODE_FN
#include "Filter.h"
Synthesis.h private
:
Filter mFilter;
EParams
Synthesis.cpp :
enum EParams { mWaveform = 0, mAttack, mDecay, mSustain, mRelease, mFilterMode, mFilterCutoff, mFilterResonance, mFilterAttack, mFilterDecay, mFilterSustain, mFilterRelease, mFilterEnvelopeAmount, kNumParams };
:
pGraphics->AttachControl(new ISwitchControl(this, 24, 38, mWaveform, &waveformBitmap));
. (Lowpass, Highpass, Bandpass). AttachGraphics(pGraphics)
:
GetParam(mFilterMode)->InitEnum("Filter Mode", Filter::FILTER_MODE_LOWPASS, Filter::kNumFilterModes); IBitmap filtermodeBitmap = pGraphics->LoadIBitmap(FILTERMODE_ID, FILTERMODE_FN, 3); pGraphics->AttachControl(new ISwitchControl(this, 24, 123, mFilterMode, &filtermodeBitmap));
. . knob_small.png :
// Knobs for filter cutoff and resonance IBitmap smallKnobBitmap = pGraphics->LoadIBitmap(KNOB_SMALL_ID, KNOB_SMALL_FN, 64); // Cutoff knob: GetParam(mFilterCutoff)->InitDouble("Cutoff", 0.99, 0.01, 0.99, 0.001); GetParam(mFilterCutoff)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 5, 177, mFilterCutoff, &smallKnobBitmap)); // Resonance knob: GetParam(mFilterResonance)->InitDouble("Resonance", 0.01, 0.01, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 61, 177, mFilterResonance, &smallKnobBitmap));
, 1.0
, calculateFeedbackAmount
, .
ProcessDoubleReplacing
:
leftOutput[i] = rightOutput[i] = mFilter.process(mOscillator.nextSample() * mEnvelopeGenerator.nextSample() * velocity / 127.0);
. switch
Synthesis::OnParamChange
:
case mFilterCutoff: mFilter.setCutoff(GetParam(paramIdx)->Value()); break; case mFilterResonance: mFilter.setResonance(GetParam(paramIdx)->Value()); break; case mFilterMode: mFilter.setFilterMode(static_cast<Filter::FilterMode>(GetParam(paramIdx)->Int())); break;
. - .
— . , , .
:
buf0 += cutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1));
, , ( buf0 – buf1
), feedbackAmount
. calculateFeedbackAmount
feedbackAmount
resonance
, . . , .
, . , , . , , .
-12 / -24 /
! 24 . switch
Filter::process
:
buf2 += cutoff * (buf1 - buf2); buf3 += cutoff * (buf2 – buf3);
: , , . , , , ( ). switch
:
switch (mode) { case FILTER_MODE_LOWPASS: return buf3; case FILTER_MODE_HIGHPASS: return inputValue - buf3; case FILTER_MODE_BANDPASS: return buf0 - buf3; default: return 0.0; }
buf1
buf3
– . -24 /. buf2
buf3
, private
Filter.h :
double buf2; double buf3;
, buf0
c buf1
:
Filter() : // ... buf0(0.0), buf1(0.0), buf2(0.0), buf3(0.0) // ...
, , : . .
?
. , , .
- cutoff
. . cutoffMod
, , cutoff
. #include private
:
double cutoffMod;
:
Filter() : cutoff(0.99), resonance(0.01), cutoffMod(0.0), // ...
. private
:
inline double getCalculatedCutoff() const { return fmax(fmin(cutoff + cutoffMod, 0.99), 0.01); };
calculateFeedbackAmount
:
inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - getCalculatedCutoff()); }
cutoffMod
. feedbackAmount
, :
inline void setCutoffMod(double newCutoffMod) { cutoffMod = newCutoffMod; calculateFeedbackAmount(); }
, . Filter::process
:
if (inputValue == 0.0) return inputValue; double calculatedCutoff = getCalculatedCutoff(); buf0 += calculatedCutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1)); buf1 += calculatedCutoff * (buf0 - buf1); buf2 += calculatedCutoff * (buf1 - buf2); buf3 += calculatedCutoff * (buf2 – buf3);
, . , , . . cutoff
calculatedCutoff
-.
, ( setCutoffMod
), Synthesis
, , . , cutoffMod
. filterEnvelopeAmount
-1
+1
. GUI.
private
Synthesis.h :
EnvelopeGenerator mFilterEnvelopeGenerator; double filterEnvelopeAmount;
MIDI , onNoteOn
onNoteOff
:
inline void onNoteOn(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); }; inline void onNoteOff(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); };
. ProcessDoubleReplacing
:
mFilter.setCutoffMod(mFilterEnvelopeGenerator.nextSample() * filterEnvelopeAmount);
, nextSample
filterEnvelopeAmount
cutoffMod
. filterEnvelopeAmount
:
Synthesis::Synthesis(IPlugInstanceInfo instanceInfo) : IPLUG_CTOR(kNumParams, kNumPrograms, instanceInfo), lastVirtualKeyboardNoteNumber(virtualKeyboardMinimumNoteNumber - 1), filterEnvelopeAmount(0.0) { // ... }
Synthesis::Reset
:
mFilterEnvelopeGenerator.setSampleRate(GetSampleRate());
EParams
, . AttachGraphics
:
// Knobs for filter envelope // Attack knob GetParam(mFilterAttack)->InitDouble("Filter Env Attack", 0.01, 0.01, 10.0, 0.001); GetParam(mFilterAttack)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 139, 178, mFilterAttack, &smallKnobBitmap)); // Decay knob: GetParam(mFilterDecay)->InitDouble("Filter Env Decay", 0.5, 0.01, 15.0, 0.001); GetParam(mFilterDecay)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 195, 178, mFilterDecay, &smallKnobBitmap)); // Sustain knob: GetParam(mFilterSustain)->InitDouble("Filter Env Sustain", 0.1, 0.001, 1.0, 0.001); GetParam(mFilterSustain)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 251, 178, mFilterSustain, &smallKnobBitmap)); // Release knob: GetParam(mFilterRelease)->InitDouble("Filter Env Release", 1.0, 0.001, 15.0, 0.001); GetParam(mFilterRelease)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 307, 178, mFilterRelease, &smallKnobBitmap)); // Filter envelope amount knob: GetParam(mFilterEnvelopeAmount)->InitDouble("Filter Env Amount", 0.0, -1.0, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 363, 178, mFilterEnvelopeAmount, &smallKnobBitmap));
, smallKnobBitmap
. . , . Synthesis::OnParamChange
switch
:
case mFilterAttack: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK, GetParam(paramIdx)->Value()); break; case mFilterDecay: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_DECAY, GetParam(paramIdx)->Value()); break; case mFilterSustain: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_SUSTAIN, GetParam(paramIdx)->Value()); break; case mFilterRelease: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE, GetParam(paramIdx)->Value()); break; case mFilterEnvelopeAmount: filterEnvelopeAmount = GetParam(paramIdx)->Value(); break;
!
! (- C1), :
EnvelopeGenerator
. . !
.
:)
:
martin-finke.de/blog/articles/audio-plugins-013-filter
#include Filter.h ( ) :
class Filter { public: enum FilterMode { FILTER_MODE_LOWPASS = 0, FILTER_MODE_HIGHPASS, FILTER_MODE_BANDPASS, kNumFilterModes }; Filter() : cutoff(0.99), resonance(0.0), mode(FILTER_MODE_LOWPASS), buf0(0.0), buf1(0.0) { calculateFeedbackAmount(); }; double process(double inputValue); inline void setCutoff(double newCutoff) { cutoff = newCutoff; calculateFeedbackAmount(); }; inline void setResonance(double newResonance) { resonance = newResonance; calculateFeedbackAmount(); }; inline void setFilterMode(FilterMode newMode) { mode = newMode; } private: double cutoff; double resonance; FilterMode mode; double feedbackAmount; inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - cutoff); } double buf0; double buf1; };
private
cutoff
resonance
. Mode
(Lowpass, Highpass, Bandpass). feedbackAmount
, buf0
buf1
, .
( calculateFeedbackAmount()
). process
. feedbackAmount
cutoff
resonance
, calculateFeedbackAmount
.
Filter.cpp :
// By Paul Kellett // http://www.musicdsp.org/showone.php?id=29 double Filter::process(double inputValue) { buf0 += cutoff * (inputValue - buf0); buf1 += cutoff * (buf0 - buf1); switch (mode) { case FILTER_MODE_LOWPASS: return buf1; case FILTER_MODE_HIGHPASS: return inputValue - buf0; case FILTER_MODE_BANDPASS: return buf0 - buf1; default: return 0.0; } }
, ? , . « » , (. . 6 ). buf0
buf1
: . inputValue
, - buf0
( ). . 6 / 12 /. switch
, buf1
. , , buf0
, 12, 6 /, .
case
FILTER_MODE_HIGHPASS
. buf0
. inputValue
buf0
, . buf1
, .
case
FILTER_MODE_BANDPASS
, buf0 - buf1
. buf0
, buf1
. , .
: , .
buf1
. ( Infinite Impulse Response , IIR ). , . , , .
, , . , , , , . , NI Massive, « ». , , . , ( Massive ). «» , , , AAS Ultra Analog, , , , . , , , , NI Reaktor. , — , .. .
. GUI. bg.png ( “Move to trash” Xcode), :
filtermode.png knob_small.png ( , 50 50 ) bg.png ( )
ID resource.h :
// Unique IDs for each image resource. #define BG_ID 101 #define WHITE_KEY_ID 102 #define BLACK_KEY_ID 103 #define WAVEFORM_ID 104 #define KNOB_ID 105 #define KNOB_SMALL_ID 106 #define FILTERMODE_ID 107 // Image resource locations for this plug. #define BG_FN "resources/img/bg.png" #define WHITE_KEY_FN "resources/img/whitekey.png" #define BLACK_KEY_FN "resources/img/blackkey.png" #define WAVEFORM_FN "resources/img/waveform.png" #define KNOB_FN "resources/img/knob.png" #define KNOB_SMALL_FN "resources/img/knob_small.png" #define FILTERMODE_FN "resources/img/filtermode.png"
Synthesis.rc :
#include "resource.h" BG_ID PNG BG_FN WHITE_KEY_ID PNG WHITE_KEY_FN BLACK_KEY_ID PNG BLACK_KEY_FN WAVEFORM_ID PNG WAVEFORM_FN KNOB_ID PNG KNOB_FN KNOB_SMALL_ID PNG KNOB_SMALL_FN FILTERMODE_ID PNG FILTERMODE_FN
#include "Filter.h"
Synthesis.h private
:
Filter mFilter;
EParams
Synthesis.cpp :
enum EParams { mWaveform = 0, mAttack, mDecay, mSustain, mRelease, mFilterMode, mFilterCutoff, mFilterResonance, mFilterAttack, mFilterDecay, mFilterSustain, mFilterRelease, mFilterEnvelopeAmount, kNumParams };
:
pGraphics->AttachControl(new ISwitchControl(this, 24, 38, mWaveform, &waveformBitmap));
. (Lowpass, Highpass, Bandpass). AttachGraphics(pGraphics)
:
GetParam(mFilterMode)->InitEnum("Filter Mode", Filter::FILTER_MODE_LOWPASS, Filter::kNumFilterModes); IBitmap filtermodeBitmap = pGraphics->LoadIBitmap(FILTERMODE_ID, FILTERMODE_FN, 3); pGraphics->AttachControl(new ISwitchControl(this, 24, 123, mFilterMode, &filtermodeBitmap));
. . knob_small.png :
// Knobs for filter cutoff and resonance IBitmap smallKnobBitmap = pGraphics->LoadIBitmap(KNOB_SMALL_ID, KNOB_SMALL_FN, 64); // Cutoff knob: GetParam(mFilterCutoff)->InitDouble("Cutoff", 0.99, 0.01, 0.99, 0.001); GetParam(mFilterCutoff)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 5, 177, mFilterCutoff, &smallKnobBitmap)); // Resonance knob: GetParam(mFilterResonance)->InitDouble("Resonance", 0.01, 0.01, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 61, 177, mFilterResonance, &smallKnobBitmap));
, 1.0
, calculateFeedbackAmount
, .
ProcessDoubleReplacing
:
leftOutput[i] = rightOutput[i] = mFilter.process(mOscillator.nextSample() * mEnvelopeGenerator.nextSample() * velocity / 127.0);
. switch
Synthesis::OnParamChange
:
case mFilterCutoff: mFilter.setCutoff(GetParam(paramIdx)->Value()); break; case mFilterResonance: mFilter.setResonance(GetParam(paramIdx)->Value()); break; case mFilterMode: mFilter.setFilterMode(static_cast<Filter::FilterMode>(GetParam(paramIdx)->Int())); break;
. - .
— . , , .
:
buf0 += cutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1));
, , ( buf0 – buf1
), feedbackAmount
. calculateFeedbackAmount
feedbackAmount
resonance
, . . , .
, . , , . , , .
-12 / -24 /
! 24 . switch
Filter::process
:
buf2 += cutoff * (buf1 - buf2); buf3 += cutoff * (buf2 – buf3);
: , , . , , , ( ). switch
:
switch (mode) { case FILTER_MODE_LOWPASS: return buf3; case FILTER_MODE_HIGHPASS: return inputValue - buf3; case FILTER_MODE_BANDPASS: return buf0 - buf3; default: return 0.0; }
buf1
buf3
– . -24 /. buf2
buf3
, private
Filter.h :
double buf2; double buf3;
, buf0
c buf1
:
Filter() : // ... buf0(0.0), buf1(0.0), buf2(0.0), buf3(0.0) // ...
, , : . .
?
. , , .
- cutoff
. . cutoffMod
, , cutoff
. #include private
:
double cutoffMod;
:
Filter() : cutoff(0.99), resonance(0.01), cutoffMod(0.0), // ...
. private
:
inline double getCalculatedCutoff() const { return fmax(fmin(cutoff + cutoffMod, 0.99), 0.01); };
calculateFeedbackAmount
:
inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - getCalculatedCutoff()); }
cutoffMod
. feedbackAmount
, :
inline void setCutoffMod(double newCutoffMod) { cutoffMod = newCutoffMod; calculateFeedbackAmount(); }
, . Filter::process
:
if (inputValue == 0.0) return inputValue; double calculatedCutoff = getCalculatedCutoff(); buf0 += calculatedCutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1)); buf1 += calculatedCutoff * (buf0 - buf1); buf2 += calculatedCutoff * (buf1 - buf2); buf3 += calculatedCutoff * (buf2 – buf3);
, . , , . . cutoff
calculatedCutoff
-.
, ( setCutoffMod
), Synthesis
, , . , cutoffMod
. filterEnvelopeAmount
-1
+1
. GUI.
private
Synthesis.h :
EnvelopeGenerator mFilterEnvelopeGenerator; double filterEnvelopeAmount;
MIDI , onNoteOn
onNoteOff
:
inline void onNoteOn(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); }; inline void onNoteOff(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); };
. ProcessDoubleReplacing
:
mFilter.setCutoffMod(mFilterEnvelopeGenerator.nextSample() * filterEnvelopeAmount);
, nextSample
filterEnvelopeAmount
cutoffMod
. filterEnvelopeAmount
:
Synthesis::Synthesis(IPlugInstanceInfo instanceInfo) : IPLUG_CTOR(kNumParams, kNumPrograms, instanceInfo), lastVirtualKeyboardNoteNumber(virtualKeyboardMinimumNoteNumber - 1), filterEnvelopeAmount(0.0) { // ... }
Synthesis::Reset
:
mFilterEnvelopeGenerator.setSampleRate(GetSampleRate());
EParams
, . AttachGraphics
:
// Knobs for filter envelope // Attack knob GetParam(mFilterAttack)->InitDouble("Filter Env Attack", 0.01, 0.01, 10.0, 0.001); GetParam(mFilterAttack)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 139, 178, mFilterAttack, &smallKnobBitmap)); // Decay knob: GetParam(mFilterDecay)->InitDouble("Filter Env Decay", 0.5, 0.01, 15.0, 0.001); GetParam(mFilterDecay)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 195, 178, mFilterDecay, &smallKnobBitmap)); // Sustain knob: GetParam(mFilterSustain)->InitDouble("Filter Env Sustain", 0.1, 0.001, 1.0, 0.001); GetParam(mFilterSustain)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 251, 178, mFilterSustain, &smallKnobBitmap)); // Release knob: GetParam(mFilterRelease)->InitDouble("Filter Env Release", 1.0, 0.001, 15.0, 0.001); GetParam(mFilterRelease)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 307, 178, mFilterRelease, &smallKnobBitmap)); // Filter envelope amount knob: GetParam(mFilterEnvelopeAmount)->InitDouble("Filter Env Amount", 0.0, -1.0, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 363, 178, mFilterEnvelopeAmount, &smallKnobBitmap));
, smallKnobBitmap
. . , . Synthesis::OnParamChange
switch
:
case mFilterAttack: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK, GetParam(paramIdx)->Value()); break; case mFilterDecay: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_DECAY, GetParam(paramIdx)->Value()); break; case mFilterSustain: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_SUSTAIN, GetParam(paramIdx)->Value()); break; case mFilterRelease: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE, GetParam(paramIdx)->Value()); break; case mFilterEnvelopeAmount: filterEnvelopeAmount = GetParam(paramIdx)->Value(); break;
!
! (- C1), :
EnvelopeGenerator
. . !
.
:)
:
martin-finke.de/blog/articles/audio-plugins-013-filter
#include Filter.h ( ) :
class Filter { public: enum FilterMode { FILTER_MODE_LOWPASS = 0, FILTER_MODE_HIGHPASS, FILTER_MODE_BANDPASS, kNumFilterModes }; Filter() : cutoff(0.99), resonance(0.0), mode(FILTER_MODE_LOWPASS), buf0(0.0), buf1(0.0) { calculateFeedbackAmount(); }; double process(double inputValue); inline void setCutoff(double newCutoff) { cutoff = newCutoff; calculateFeedbackAmount(); }; inline void setResonance(double newResonance) { resonance = newResonance; calculateFeedbackAmount(); }; inline void setFilterMode(FilterMode newMode) { mode = newMode; } private: double cutoff; double resonance; FilterMode mode; double feedbackAmount; inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - cutoff); } double buf0; double buf1; };
private
cutoff
resonance
. Mode
(Lowpass, Highpass, Bandpass). feedbackAmount
, buf0
buf1
, .
( calculateFeedbackAmount()
). process
. feedbackAmount
cutoff
resonance
, calculateFeedbackAmount
.
Filter.cpp :
// By Paul Kellett // http://www.musicdsp.org/showone.php?id=29 double Filter::process(double inputValue) { buf0 += cutoff * (inputValue - buf0); buf1 += cutoff * (buf0 - buf1); switch (mode) { case FILTER_MODE_LOWPASS: return buf1; case FILTER_MODE_HIGHPASS: return inputValue - buf0; case FILTER_MODE_BANDPASS: return buf0 - buf1; default: return 0.0; } }
, ? , . « » , (. . 6 ). buf0
buf1
: . inputValue
, - buf0
( ). . 6 / 12 /. switch
, buf1
. , , buf0
, 12, 6 /, .
case
FILTER_MODE_HIGHPASS
. buf0
. inputValue
buf0
, . buf1
, .
case
FILTER_MODE_BANDPASS
, buf0 - buf1
. buf0
, buf1
. , .
: , .
buf1
. ( Infinite Impulse Response , IIR ). , . , , .
, , . , , , , . , NI Massive, « ». , , . , ( Massive ). «» , , , AAS Ultra Analog, , , , . , , , , NI Reaktor. , — , .. .
. GUI. bg.png ( “Move to trash” Xcode), :
filtermode.png knob_small.png ( , 50 50 ) bg.png ( )
ID resource.h :
// Unique IDs for each image resource. #define BG_ID 101 #define WHITE_KEY_ID 102 #define BLACK_KEY_ID 103 #define WAVEFORM_ID 104 #define KNOB_ID 105 #define KNOB_SMALL_ID 106 #define FILTERMODE_ID 107 // Image resource locations for this plug. #define BG_FN "resources/img/bg.png" #define WHITE_KEY_FN "resources/img/whitekey.png" #define BLACK_KEY_FN "resources/img/blackkey.png" #define WAVEFORM_FN "resources/img/waveform.png" #define KNOB_FN "resources/img/knob.png" #define KNOB_SMALL_FN "resources/img/knob_small.png" #define FILTERMODE_FN "resources/img/filtermode.png"
Synthesis.rc :
#include "resource.h" BG_ID PNG BG_FN WHITE_KEY_ID PNG WHITE_KEY_FN BLACK_KEY_ID PNG BLACK_KEY_FN WAVEFORM_ID PNG WAVEFORM_FN KNOB_ID PNG KNOB_FN KNOB_SMALL_ID PNG KNOB_SMALL_FN FILTERMODE_ID PNG FILTERMODE_FN
#include "Filter.h"
Synthesis.h private
:
Filter mFilter;
EParams
Synthesis.cpp :
enum EParams { mWaveform = 0, mAttack, mDecay, mSustain, mRelease, mFilterMode, mFilterCutoff, mFilterResonance, mFilterAttack, mFilterDecay, mFilterSustain, mFilterRelease, mFilterEnvelopeAmount, kNumParams };
:
pGraphics->AttachControl(new ISwitchControl(this, 24, 38, mWaveform, &waveformBitmap));
. (Lowpass, Highpass, Bandpass). AttachGraphics(pGraphics)
:
GetParam(mFilterMode)->InitEnum("Filter Mode", Filter::FILTER_MODE_LOWPASS, Filter::kNumFilterModes); IBitmap filtermodeBitmap = pGraphics->LoadIBitmap(FILTERMODE_ID, FILTERMODE_FN, 3); pGraphics->AttachControl(new ISwitchControl(this, 24, 123, mFilterMode, &filtermodeBitmap));
. . knob_small.png :
// Knobs for filter cutoff and resonance IBitmap smallKnobBitmap = pGraphics->LoadIBitmap(KNOB_SMALL_ID, KNOB_SMALL_FN, 64); // Cutoff knob: GetParam(mFilterCutoff)->InitDouble("Cutoff", 0.99, 0.01, 0.99, 0.001); GetParam(mFilterCutoff)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 5, 177, mFilterCutoff, &smallKnobBitmap)); // Resonance knob: GetParam(mFilterResonance)->InitDouble("Resonance", 0.01, 0.01, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 61, 177, mFilterResonance, &smallKnobBitmap));
, 1.0
, calculateFeedbackAmount
, .
ProcessDoubleReplacing
:
leftOutput[i] = rightOutput[i] = mFilter.process(mOscillator.nextSample() * mEnvelopeGenerator.nextSample() * velocity / 127.0);
. switch
Synthesis::OnParamChange
:
case mFilterCutoff: mFilter.setCutoff(GetParam(paramIdx)->Value()); break; case mFilterResonance: mFilter.setResonance(GetParam(paramIdx)->Value()); break; case mFilterMode: mFilter.setFilterMode(static_cast<Filter::FilterMode>(GetParam(paramIdx)->Int())); break;
. - .
— . , , .
:
buf0 += cutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1));
, , ( buf0 – buf1
), feedbackAmount
. calculateFeedbackAmount
feedbackAmount
resonance
, . . , .
, . , , . , , .
-12 / -24 /
! 24 . switch
Filter::process
:
buf2 += cutoff * (buf1 - buf2); buf3 += cutoff * (buf2 – buf3);
: , , . , , , ( ). switch
:
switch (mode) { case FILTER_MODE_LOWPASS: return buf3; case FILTER_MODE_HIGHPASS: return inputValue - buf3; case FILTER_MODE_BANDPASS: return buf0 - buf3; default: return 0.0; }
buf1
buf3
– . -24 /. buf2
buf3
, private
Filter.h :
double buf2; double buf3;
, buf0
c buf1
:
Filter() : // ... buf0(0.0), buf1(0.0), buf2(0.0), buf3(0.0) // ...
, , : . .
?
. , , .
- cutoff
. . cutoffMod
, , cutoff
. #include private
:
double cutoffMod;
:
Filter() : cutoff(0.99), resonance(0.01), cutoffMod(0.0), // ...
. private
:
inline double getCalculatedCutoff() const { return fmax(fmin(cutoff + cutoffMod, 0.99), 0.01); };
calculateFeedbackAmount
:
inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - getCalculatedCutoff()); }
cutoffMod
. feedbackAmount
, :
inline void setCutoffMod(double newCutoffMod) { cutoffMod = newCutoffMod; calculateFeedbackAmount(); }
, . Filter::process
:
if (inputValue == 0.0) return inputValue; double calculatedCutoff = getCalculatedCutoff(); buf0 += calculatedCutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1)); buf1 += calculatedCutoff * (buf0 - buf1); buf2 += calculatedCutoff * (buf1 - buf2); buf3 += calculatedCutoff * (buf2 – buf3);
, . , , . . cutoff
calculatedCutoff
-.
, ( setCutoffMod
), Synthesis
, , . , cutoffMod
. filterEnvelopeAmount
-1
+1
. GUI.
private
Synthesis.h :
EnvelopeGenerator mFilterEnvelopeGenerator; double filterEnvelopeAmount;
MIDI , onNoteOn
onNoteOff
:
inline void onNoteOn(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); }; inline void onNoteOff(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); };
. ProcessDoubleReplacing
:
mFilter.setCutoffMod(mFilterEnvelopeGenerator.nextSample() * filterEnvelopeAmount);
, nextSample
filterEnvelopeAmount
cutoffMod
. filterEnvelopeAmount
:
Synthesis::Synthesis(IPlugInstanceInfo instanceInfo) : IPLUG_CTOR(kNumParams, kNumPrograms, instanceInfo), lastVirtualKeyboardNoteNumber(virtualKeyboardMinimumNoteNumber - 1), filterEnvelopeAmount(0.0) { // ... }
Synthesis::Reset
:
mFilterEnvelopeGenerator.setSampleRate(GetSampleRate());
EParams
, . AttachGraphics
:
// Knobs for filter envelope // Attack knob GetParam(mFilterAttack)->InitDouble("Filter Env Attack", 0.01, 0.01, 10.0, 0.001); GetParam(mFilterAttack)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 139, 178, mFilterAttack, &smallKnobBitmap)); // Decay knob: GetParam(mFilterDecay)->InitDouble("Filter Env Decay", 0.5, 0.01, 15.0, 0.001); GetParam(mFilterDecay)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 195, 178, mFilterDecay, &smallKnobBitmap)); // Sustain knob: GetParam(mFilterSustain)->InitDouble("Filter Env Sustain", 0.1, 0.001, 1.0, 0.001); GetParam(mFilterSustain)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 251, 178, mFilterSustain, &smallKnobBitmap)); // Release knob: GetParam(mFilterRelease)->InitDouble("Filter Env Release", 1.0, 0.001, 15.0, 0.001); GetParam(mFilterRelease)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 307, 178, mFilterRelease, &smallKnobBitmap)); // Filter envelope amount knob: GetParam(mFilterEnvelopeAmount)->InitDouble("Filter Env Amount", 0.0, -1.0, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 363, 178, mFilterEnvelopeAmount, &smallKnobBitmap));
, smallKnobBitmap
. . , . Synthesis::OnParamChange
switch
:
case mFilterAttack: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK, GetParam(paramIdx)->Value()); break; case mFilterDecay: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_DECAY, GetParam(paramIdx)->Value()); break; case mFilterSustain: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_SUSTAIN, GetParam(paramIdx)->Value()); break; case mFilterRelease: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE, GetParam(paramIdx)->Value()); break; case mFilterEnvelopeAmount: filterEnvelopeAmount = GetParam(paramIdx)->Value(); break;
!
! (- C1), :
EnvelopeGenerator
. . !
.
:)
:
martin-finke.de/blog/articles/audio-plugins-013-filter
#include Filter.h ( ) :
class Filter { public: enum FilterMode { FILTER_MODE_LOWPASS = 0, FILTER_MODE_HIGHPASS, FILTER_MODE_BANDPASS, kNumFilterModes }; Filter() : cutoff(0.99), resonance(0.0), mode(FILTER_MODE_LOWPASS), buf0(0.0), buf1(0.0) { calculateFeedbackAmount(); }; double process(double inputValue); inline void setCutoff(double newCutoff) { cutoff = newCutoff; calculateFeedbackAmount(); }; inline void setResonance(double newResonance) { resonance = newResonance; calculateFeedbackAmount(); }; inline void setFilterMode(FilterMode newMode) { mode = newMode; } private: double cutoff; double resonance; FilterMode mode; double feedbackAmount; inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - cutoff); } double buf0; double buf1; };
private
cutoff
resonance
. Mode
(Lowpass, Highpass, Bandpass). feedbackAmount
, buf0
buf1
, .
( calculateFeedbackAmount()
). process
. feedbackAmount
cutoff
resonance
, calculateFeedbackAmount
.
Filter.cpp :
// By Paul Kellett // http://www.musicdsp.org/showone.php?id=29 double Filter::process(double inputValue) { buf0 += cutoff * (inputValue - buf0); buf1 += cutoff * (buf0 - buf1); switch (mode) { case FILTER_MODE_LOWPASS: return buf1; case FILTER_MODE_HIGHPASS: return inputValue - buf0; case FILTER_MODE_BANDPASS: return buf0 - buf1; default: return 0.0; } }
, ? , . « » , (. . 6 ). buf0
buf1
: . inputValue
, - buf0
( ). . 6 / 12 /. switch
, buf1
. , , buf0
, 12, 6 /, .
case
FILTER_MODE_HIGHPASS
. buf0
. inputValue
buf0
, . buf1
, .
case
FILTER_MODE_BANDPASS
, buf0 - buf1
. buf0
, buf1
. , .
: , .
buf1
. ( Infinite Impulse Response , IIR ). , . , , .
, , . , , , , . , NI Massive, « ». , , . , ( Massive ). «» , , , AAS Ultra Analog, , , , . , , , , NI Reaktor. , — , .. .
. GUI. bg.png ( “Move to trash” Xcode), :
filtermode.png knob_small.png ( , 50 50 ) bg.png ( )
ID resource.h :
// Unique IDs for each image resource. #define BG_ID 101 #define WHITE_KEY_ID 102 #define BLACK_KEY_ID 103 #define WAVEFORM_ID 104 #define KNOB_ID 105 #define KNOB_SMALL_ID 106 #define FILTERMODE_ID 107 // Image resource locations for this plug. #define BG_FN "resources/img/bg.png" #define WHITE_KEY_FN "resources/img/whitekey.png" #define BLACK_KEY_FN "resources/img/blackkey.png" #define WAVEFORM_FN "resources/img/waveform.png" #define KNOB_FN "resources/img/knob.png" #define KNOB_SMALL_FN "resources/img/knob_small.png" #define FILTERMODE_FN "resources/img/filtermode.png"
Synthesis.rc :
#include "resource.h" BG_ID PNG BG_FN WHITE_KEY_ID PNG WHITE_KEY_FN BLACK_KEY_ID PNG BLACK_KEY_FN WAVEFORM_ID PNG WAVEFORM_FN KNOB_ID PNG KNOB_FN KNOB_SMALL_ID PNG KNOB_SMALL_FN FILTERMODE_ID PNG FILTERMODE_FN
#include "Filter.h"
Synthesis.h private
:
Filter mFilter;
EParams
Synthesis.cpp :
enum EParams { mWaveform = 0, mAttack, mDecay, mSustain, mRelease, mFilterMode, mFilterCutoff, mFilterResonance, mFilterAttack, mFilterDecay, mFilterSustain, mFilterRelease, mFilterEnvelopeAmount, kNumParams };
:
pGraphics->AttachControl(new ISwitchControl(this, 24, 38, mWaveform, &waveformBitmap));
. (Lowpass, Highpass, Bandpass). AttachGraphics(pGraphics)
:
GetParam(mFilterMode)->InitEnum("Filter Mode", Filter::FILTER_MODE_LOWPASS, Filter::kNumFilterModes); IBitmap filtermodeBitmap = pGraphics->LoadIBitmap(FILTERMODE_ID, FILTERMODE_FN, 3); pGraphics->AttachControl(new ISwitchControl(this, 24, 123, mFilterMode, &filtermodeBitmap));
. . knob_small.png :
// Knobs for filter cutoff and resonance IBitmap smallKnobBitmap = pGraphics->LoadIBitmap(KNOB_SMALL_ID, KNOB_SMALL_FN, 64); // Cutoff knob: GetParam(mFilterCutoff)->InitDouble("Cutoff", 0.99, 0.01, 0.99, 0.001); GetParam(mFilterCutoff)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 5, 177, mFilterCutoff, &smallKnobBitmap)); // Resonance knob: GetParam(mFilterResonance)->InitDouble("Resonance", 0.01, 0.01, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 61, 177, mFilterResonance, &smallKnobBitmap));
, 1.0
, calculateFeedbackAmount
, .
ProcessDoubleReplacing
:
leftOutput[i] = rightOutput[i] = mFilter.process(mOscillator.nextSample() * mEnvelopeGenerator.nextSample() * velocity / 127.0);
. switch
Synthesis::OnParamChange
:
case mFilterCutoff: mFilter.setCutoff(GetParam(paramIdx)->Value()); break; case mFilterResonance: mFilter.setResonance(GetParam(paramIdx)->Value()); break; case mFilterMode: mFilter.setFilterMode(static_cast<Filter::FilterMode>(GetParam(paramIdx)->Int())); break;
. - .
— . , , .
:
buf0 += cutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1));
, , ( buf0 – buf1
), feedbackAmount
. calculateFeedbackAmount
feedbackAmount
resonance
, . . , .
, . , , . , , .
-12 / -24 /
! 24 . switch
Filter::process
:
buf2 += cutoff * (buf1 - buf2); buf3 += cutoff * (buf2 – buf3);
: , , . , , , ( ). switch
:
switch (mode) { case FILTER_MODE_LOWPASS: return buf3; case FILTER_MODE_HIGHPASS: return inputValue - buf3; case FILTER_MODE_BANDPASS: return buf0 - buf3; default: return 0.0; }
buf1
buf3
– . -24 /. buf2
buf3
, private
Filter.h :
double buf2; double buf3;
, buf0
c buf1
:
Filter() : // ... buf0(0.0), buf1(0.0), buf2(0.0), buf3(0.0) // ...
, , : . .
?
. , , .
- cutoff
. . cutoffMod
, , cutoff
. #include private
:
double cutoffMod;
:
Filter() : cutoff(0.99), resonance(0.01), cutoffMod(0.0), // ...
. private
:
inline double getCalculatedCutoff() const { return fmax(fmin(cutoff + cutoffMod, 0.99), 0.01); };
calculateFeedbackAmount
:
inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - getCalculatedCutoff()); }
cutoffMod
. feedbackAmount
, :
inline void setCutoffMod(double newCutoffMod) { cutoffMod = newCutoffMod; calculateFeedbackAmount(); }
, . Filter::process
:
if (inputValue == 0.0) return inputValue; double calculatedCutoff = getCalculatedCutoff(); buf0 += calculatedCutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1)); buf1 += calculatedCutoff * (buf0 - buf1); buf2 += calculatedCutoff * (buf1 - buf2); buf3 += calculatedCutoff * (buf2 – buf3);
, . , , . . cutoff
calculatedCutoff
-.
, ( setCutoffMod
), Synthesis
, , . , cutoffMod
. filterEnvelopeAmount
-1
+1
. GUI.
private
Synthesis.h :
EnvelopeGenerator mFilterEnvelopeGenerator; double filterEnvelopeAmount;
MIDI , onNoteOn
onNoteOff
:
inline void onNoteOn(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); }; inline void onNoteOff(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); };
. ProcessDoubleReplacing
:
mFilter.setCutoffMod(mFilterEnvelopeGenerator.nextSample() * filterEnvelopeAmount);
, nextSample
filterEnvelopeAmount
cutoffMod
. filterEnvelopeAmount
:
Synthesis::Synthesis(IPlugInstanceInfo instanceInfo) : IPLUG_CTOR(kNumParams, kNumPrograms, instanceInfo), lastVirtualKeyboardNoteNumber(virtualKeyboardMinimumNoteNumber - 1), filterEnvelopeAmount(0.0) { // ... }
Synthesis::Reset
:
mFilterEnvelopeGenerator.setSampleRate(GetSampleRate());
EParams
, . AttachGraphics
:
// Knobs for filter envelope // Attack knob GetParam(mFilterAttack)->InitDouble("Filter Env Attack", 0.01, 0.01, 10.0, 0.001); GetParam(mFilterAttack)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 139, 178, mFilterAttack, &smallKnobBitmap)); // Decay knob: GetParam(mFilterDecay)->InitDouble("Filter Env Decay", 0.5, 0.01, 15.0, 0.001); GetParam(mFilterDecay)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 195, 178, mFilterDecay, &smallKnobBitmap)); // Sustain knob: GetParam(mFilterSustain)->InitDouble("Filter Env Sustain", 0.1, 0.001, 1.0, 0.001); GetParam(mFilterSustain)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 251, 178, mFilterSustain, &smallKnobBitmap)); // Release knob: GetParam(mFilterRelease)->InitDouble("Filter Env Release", 1.0, 0.001, 15.0, 0.001); GetParam(mFilterRelease)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 307, 178, mFilterRelease, &smallKnobBitmap)); // Filter envelope amount knob: GetParam(mFilterEnvelopeAmount)->InitDouble("Filter Env Amount", 0.0, -1.0, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 363, 178, mFilterEnvelopeAmount, &smallKnobBitmap));
, smallKnobBitmap
. . , . Synthesis::OnParamChange
switch
:
case mFilterAttack: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK, GetParam(paramIdx)->Value()); break; case mFilterDecay: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_DECAY, GetParam(paramIdx)->Value()); break; case mFilterSustain: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_SUSTAIN, GetParam(paramIdx)->Value()); break; case mFilterRelease: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE, GetParam(paramIdx)->Value()); break; case mFilterEnvelopeAmount: filterEnvelopeAmount = GetParam(paramIdx)->Value(); break;
!
! (- C1), :
EnvelopeGenerator
. . !
.
:)
:
martin-finke.de/blog/articles/audio-plugins-013-filter
#include Filter.h ( ) :
class Filter { public: enum FilterMode { FILTER_MODE_LOWPASS = 0, FILTER_MODE_HIGHPASS, FILTER_MODE_BANDPASS, kNumFilterModes }; Filter() : cutoff(0.99), resonance(0.0), mode(FILTER_MODE_LOWPASS), buf0(0.0), buf1(0.0) { calculateFeedbackAmount(); }; double process(double inputValue); inline void setCutoff(double newCutoff) { cutoff = newCutoff; calculateFeedbackAmount(); }; inline void setResonance(double newResonance) { resonance = newResonance; calculateFeedbackAmount(); }; inline void setFilterMode(FilterMode newMode) { mode = newMode; } private: double cutoff; double resonance; FilterMode mode; double feedbackAmount; inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - cutoff); } double buf0; double buf1; };
private
cutoff
resonance
. Mode
(Lowpass, Highpass, Bandpass). feedbackAmount
, buf0
buf1
, .
( calculateFeedbackAmount()
). process
. feedbackAmount
cutoff
resonance
, calculateFeedbackAmount
.
Filter.cpp :
// By Paul Kellett // http://www.musicdsp.org/showone.php?id=29 double Filter::process(double inputValue) { buf0 += cutoff * (inputValue - buf0); buf1 += cutoff * (buf0 - buf1); switch (mode) { case FILTER_MODE_LOWPASS: return buf1; case FILTER_MODE_HIGHPASS: return inputValue - buf0; case FILTER_MODE_BANDPASS: return buf0 - buf1; default: return 0.0; } }
, ? , . « » , (. . 6 ). buf0
buf1
: . inputValue
, - buf0
( ). . 6 / 12 /. switch
, buf1
. , , buf0
, 12, 6 /, .
case
FILTER_MODE_HIGHPASS
. buf0
. inputValue
buf0
, . buf1
, .
case
FILTER_MODE_BANDPASS
, buf0 - buf1
. buf0
, buf1
. , .
: , .
buf1
. ( Infinite Impulse Response , IIR ). , . , , .
, , . , , , , . , NI Massive, « ». , , . , ( Massive ). «» , , , AAS Ultra Analog, , , , . , , , , NI Reaktor. , — , .. .
. GUI. bg.png ( “Move to trash” Xcode), :
filtermode.png knob_small.png ( , 50 50 ) bg.png ( )
ID resource.h :
// Unique IDs for each image resource. #define BG_ID 101 #define WHITE_KEY_ID 102 #define BLACK_KEY_ID 103 #define WAVEFORM_ID 104 #define KNOB_ID 105 #define KNOB_SMALL_ID 106 #define FILTERMODE_ID 107 // Image resource locations for this plug. #define BG_FN "resources/img/bg.png" #define WHITE_KEY_FN "resources/img/whitekey.png" #define BLACK_KEY_FN "resources/img/blackkey.png" #define WAVEFORM_FN "resources/img/waveform.png" #define KNOB_FN "resources/img/knob.png" #define KNOB_SMALL_FN "resources/img/knob_small.png" #define FILTERMODE_FN "resources/img/filtermode.png"
Synthesis.rc :
#include "resource.h" BG_ID PNG BG_FN WHITE_KEY_ID PNG WHITE_KEY_FN BLACK_KEY_ID PNG BLACK_KEY_FN WAVEFORM_ID PNG WAVEFORM_FN KNOB_ID PNG KNOB_FN KNOB_SMALL_ID PNG KNOB_SMALL_FN FILTERMODE_ID PNG FILTERMODE_FN
#include "Filter.h"
Synthesis.h private
:
Filter mFilter;
EParams
Synthesis.cpp :
enum EParams { mWaveform = 0, mAttack, mDecay, mSustain, mRelease, mFilterMode, mFilterCutoff, mFilterResonance, mFilterAttack, mFilterDecay, mFilterSustain, mFilterRelease, mFilterEnvelopeAmount, kNumParams };
:
pGraphics->AttachControl(new ISwitchControl(this, 24, 38, mWaveform, &waveformBitmap));
. (Lowpass, Highpass, Bandpass). AttachGraphics(pGraphics)
:
GetParam(mFilterMode)->InitEnum("Filter Mode", Filter::FILTER_MODE_LOWPASS, Filter::kNumFilterModes); IBitmap filtermodeBitmap = pGraphics->LoadIBitmap(FILTERMODE_ID, FILTERMODE_FN, 3); pGraphics->AttachControl(new ISwitchControl(this, 24, 123, mFilterMode, &filtermodeBitmap));
. . knob_small.png :
// Knobs for filter cutoff and resonance IBitmap smallKnobBitmap = pGraphics->LoadIBitmap(KNOB_SMALL_ID, KNOB_SMALL_FN, 64); // Cutoff knob: GetParam(mFilterCutoff)->InitDouble("Cutoff", 0.99, 0.01, 0.99, 0.001); GetParam(mFilterCutoff)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 5, 177, mFilterCutoff, &smallKnobBitmap)); // Resonance knob: GetParam(mFilterResonance)->InitDouble("Resonance", 0.01, 0.01, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 61, 177, mFilterResonance, &smallKnobBitmap));
, 1.0
, calculateFeedbackAmount
, .
ProcessDoubleReplacing
:
leftOutput[i] = rightOutput[i] = mFilter.process(mOscillator.nextSample() * mEnvelopeGenerator.nextSample() * velocity / 127.0);
. switch
Synthesis::OnParamChange
:
case mFilterCutoff: mFilter.setCutoff(GetParam(paramIdx)->Value()); break; case mFilterResonance: mFilter.setResonance(GetParam(paramIdx)->Value()); break; case mFilterMode: mFilter.setFilterMode(static_cast<Filter::FilterMode>(GetParam(paramIdx)->Int())); break;
. - .
— . , , .
:
buf0 += cutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1));
, , ( buf0 – buf1
), feedbackAmount
. calculateFeedbackAmount
feedbackAmount
resonance
, . . , .
, . , , . , , .
-12 / -24 /
! 24 . switch
Filter::process
:
buf2 += cutoff * (buf1 - buf2); buf3 += cutoff * (buf2 – buf3);
: , , . , , , ( ). switch
:
switch (mode) { case FILTER_MODE_LOWPASS: return buf3; case FILTER_MODE_HIGHPASS: return inputValue - buf3; case FILTER_MODE_BANDPASS: return buf0 - buf3; default: return 0.0; }
buf1
buf3
– . -24 /. buf2
buf3
, private
Filter.h :
double buf2; double buf3;
, buf0
c buf1
:
Filter() : // ... buf0(0.0), buf1(0.0), buf2(0.0), buf3(0.0) // ...
, , : . .
?
. , , .
- cutoff
. . cutoffMod
, , cutoff
. #include private
:
double cutoffMod;
:
Filter() : cutoff(0.99), resonance(0.01), cutoffMod(0.0), // ...
. private
:
inline double getCalculatedCutoff() const { return fmax(fmin(cutoff + cutoffMod, 0.99), 0.01); };
calculateFeedbackAmount
:
inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - getCalculatedCutoff()); }
cutoffMod
. feedbackAmount
, :
inline void setCutoffMod(double newCutoffMod) { cutoffMod = newCutoffMod; calculateFeedbackAmount(); }
, . Filter::process
:
if (inputValue == 0.0) return inputValue; double calculatedCutoff = getCalculatedCutoff(); buf0 += calculatedCutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1)); buf1 += calculatedCutoff * (buf0 - buf1); buf2 += calculatedCutoff * (buf1 - buf2); buf3 += calculatedCutoff * (buf2 – buf3);
, . , , . . cutoff
calculatedCutoff
-.
, ( setCutoffMod
), Synthesis
, , . , cutoffMod
. filterEnvelopeAmount
-1
+1
. GUI.
private
Synthesis.h :
EnvelopeGenerator mFilterEnvelopeGenerator; double filterEnvelopeAmount;
MIDI , onNoteOn
onNoteOff
:
inline void onNoteOn(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); }; inline void onNoteOff(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); };
. ProcessDoubleReplacing
:
mFilter.setCutoffMod(mFilterEnvelopeGenerator.nextSample() * filterEnvelopeAmount);
, nextSample
filterEnvelopeAmount
cutoffMod
. filterEnvelopeAmount
:
Synthesis::Synthesis(IPlugInstanceInfo instanceInfo) : IPLUG_CTOR(kNumParams, kNumPrograms, instanceInfo), lastVirtualKeyboardNoteNumber(virtualKeyboardMinimumNoteNumber - 1), filterEnvelopeAmount(0.0) { // ... }
Synthesis::Reset
:
mFilterEnvelopeGenerator.setSampleRate(GetSampleRate());
EParams
, . AttachGraphics
:
// Knobs for filter envelope // Attack knob GetParam(mFilterAttack)->InitDouble("Filter Env Attack", 0.01, 0.01, 10.0, 0.001); GetParam(mFilterAttack)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 139, 178, mFilterAttack, &smallKnobBitmap)); // Decay knob: GetParam(mFilterDecay)->InitDouble("Filter Env Decay", 0.5, 0.01, 15.0, 0.001); GetParam(mFilterDecay)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 195, 178, mFilterDecay, &smallKnobBitmap)); // Sustain knob: GetParam(mFilterSustain)->InitDouble("Filter Env Sustain", 0.1, 0.001, 1.0, 0.001); GetParam(mFilterSustain)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 251, 178, mFilterSustain, &smallKnobBitmap)); // Release knob: GetParam(mFilterRelease)->InitDouble("Filter Env Release", 1.0, 0.001, 15.0, 0.001); GetParam(mFilterRelease)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 307, 178, mFilterRelease, &smallKnobBitmap)); // Filter envelope amount knob: GetParam(mFilterEnvelopeAmount)->InitDouble("Filter Env Amount", 0.0, -1.0, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 363, 178, mFilterEnvelopeAmount, &smallKnobBitmap));
, smallKnobBitmap
. . , . Synthesis::OnParamChange
switch
:
case mFilterAttack: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK, GetParam(paramIdx)->Value()); break; case mFilterDecay: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_DECAY, GetParam(paramIdx)->Value()); break; case mFilterSustain: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_SUSTAIN, GetParam(paramIdx)->Value()); break; case mFilterRelease: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE, GetParam(paramIdx)->Value()); break; case mFilterEnvelopeAmount: filterEnvelopeAmount = GetParam(paramIdx)->Value(); break;
!
! (- C1), :
EnvelopeGenerator
. . !
.
:)
:
martin-finke.de/blog/articles/audio-plugins-013-filter
#include Filter.h ( ) :
class Filter { public: enum FilterMode { FILTER_MODE_LOWPASS = 0, FILTER_MODE_HIGHPASS, FILTER_MODE_BANDPASS, kNumFilterModes }; Filter() : cutoff(0.99), resonance(0.0), mode(FILTER_MODE_LOWPASS), buf0(0.0), buf1(0.0) { calculateFeedbackAmount(); }; double process(double inputValue); inline void setCutoff(double newCutoff) { cutoff = newCutoff; calculateFeedbackAmount(); }; inline void setResonance(double newResonance) { resonance = newResonance; calculateFeedbackAmount(); }; inline void setFilterMode(FilterMode newMode) { mode = newMode; } private: double cutoff; double resonance; FilterMode mode; double feedbackAmount; inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - cutoff); } double buf0; double buf1; };
private
cutoff
resonance
. Mode
(Lowpass, Highpass, Bandpass). feedbackAmount
, buf0
buf1
, .
( calculateFeedbackAmount()
). process
. feedbackAmount
cutoff
resonance
, calculateFeedbackAmount
.
Filter.cpp :
// By Paul Kellett // http://www.musicdsp.org/showone.php?id=29 double Filter::process(double inputValue) { buf0 += cutoff * (inputValue - buf0); buf1 += cutoff * (buf0 - buf1); switch (mode) { case FILTER_MODE_LOWPASS: return buf1; case FILTER_MODE_HIGHPASS: return inputValue - buf0; case FILTER_MODE_BANDPASS: return buf0 - buf1; default: return 0.0; } }
, ? , . « » , (. . 6 ). buf0
buf1
: . inputValue
, - buf0
( ). . 6 / 12 /. switch
, buf1
. , , buf0
, 12, 6 /, .
case
FILTER_MODE_HIGHPASS
. buf0
. inputValue
buf0
, . buf1
, .
case
FILTER_MODE_BANDPASS
, buf0 - buf1
. buf0
, buf1
. , .
: , .
buf1
. ( Infinite Impulse Response , IIR ). , . , , .
, , . , , , , . , NI Massive, « ». , , . , ( Massive ). «» , , , AAS Ultra Analog, , , , . , , , , NI Reaktor. , — , .. .
. GUI. bg.png ( “Move to trash” Xcode), :
filtermode.png knob_small.png ( , 50 50 ) bg.png ( )
ID resource.h :
// Unique IDs for each image resource. #define BG_ID 101 #define WHITE_KEY_ID 102 #define BLACK_KEY_ID 103 #define WAVEFORM_ID 104 #define KNOB_ID 105 #define KNOB_SMALL_ID 106 #define FILTERMODE_ID 107 // Image resource locations for this plug. #define BG_FN "resources/img/bg.png" #define WHITE_KEY_FN "resources/img/whitekey.png" #define BLACK_KEY_FN "resources/img/blackkey.png" #define WAVEFORM_FN "resources/img/waveform.png" #define KNOB_FN "resources/img/knob.png" #define KNOB_SMALL_FN "resources/img/knob_small.png" #define FILTERMODE_FN "resources/img/filtermode.png"
Synthesis.rc :
#include "resource.h" BG_ID PNG BG_FN WHITE_KEY_ID PNG WHITE_KEY_FN BLACK_KEY_ID PNG BLACK_KEY_FN WAVEFORM_ID PNG WAVEFORM_FN KNOB_ID PNG KNOB_FN KNOB_SMALL_ID PNG KNOB_SMALL_FN FILTERMODE_ID PNG FILTERMODE_FN
#include "Filter.h"
Synthesis.h private
:
Filter mFilter;
EParams
Synthesis.cpp :
enum EParams { mWaveform = 0, mAttack, mDecay, mSustain, mRelease, mFilterMode, mFilterCutoff, mFilterResonance, mFilterAttack, mFilterDecay, mFilterSustain, mFilterRelease, mFilterEnvelopeAmount, kNumParams };
:
pGraphics->AttachControl(new ISwitchControl(this, 24, 38, mWaveform, &waveformBitmap));
. (Lowpass, Highpass, Bandpass). AttachGraphics(pGraphics)
:
GetParam(mFilterMode)->InitEnum("Filter Mode", Filter::FILTER_MODE_LOWPASS, Filter::kNumFilterModes); IBitmap filtermodeBitmap = pGraphics->LoadIBitmap(FILTERMODE_ID, FILTERMODE_FN, 3); pGraphics->AttachControl(new ISwitchControl(this, 24, 123, mFilterMode, &filtermodeBitmap));
. . knob_small.png :
// Knobs for filter cutoff and resonance IBitmap smallKnobBitmap = pGraphics->LoadIBitmap(KNOB_SMALL_ID, KNOB_SMALL_FN, 64); // Cutoff knob: GetParam(mFilterCutoff)->InitDouble("Cutoff", 0.99, 0.01, 0.99, 0.001); GetParam(mFilterCutoff)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 5, 177, mFilterCutoff, &smallKnobBitmap)); // Resonance knob: GetParam(mFilterResonance)->InitDouble("Resonance", 0.01, 0.01, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 61, 177, mFilterResonance, &smallKnobBitmap));
, 1.0
, calculateFeedbackAmount
, .
ProcessDoubleReplacing
:
leftOutput[i] = rightOutput[i] = mFilter.process(mOscillator.nextSample() * mEnvelopeGenerator.nextSample() * velocity / 127.0);
. switch
Synthesis::OnParamChange
:
case mFilterCutoff: mFilter.setCutoff(GetParam(paramIdx)->Value()); break; case mFilterResonance: mFilter.setResonance(GetParam(paramIdx)->Value()); break; case mFilterMode: mFilter.setFilterMode(static_cast<Filter::FilterMode>(GetParam(paramIdx)->Int())); break;
. - .
— . , , .
:
buf0 += cutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1));
, , ( buf0 – buf1
), feedbackAmount
. calculateFeedbackAmount
feedbackAmount
resonance
, . . , .
, . , , . , , .
-12 / -24 /
! 24 . switch
Filter::process
:
buf2 += cutoff * (buf1 - buf2); buf3 += cutoff * (buf2 – buf3);
: , , . , , , ( ). switch
:
switch (mode) { case FILTER_MODE_LOWPASS: return buf3; case FILTER_MODE_HIGHPASS: return inputValue - buf3; case FILTER_MODE_BANDPASS: return buf0 - buf3; default: return 0.0; }
buf1
buf3
– . -24 /. buf2
buf3
, private
Filter.h :
double buf2; double buf3;
, buf0
c buf1
:
Filter() : // ... buf0(0.0), buf1(0.0), buf2(0.0), buf3(0.0) // ...
, , : . .
?
. , , .
- cutoff
. . cutoffMod
, , cutoff
. #include private
:
double cutoffMod;
:
Filter() : cutoff(0.99), resonance(0.01), cutoffMod(0.0), // ...
. private
:
inline double getCalculatedCutoff() const { return fmax(fmin(cutoff + cutoffMod, 0.99), 0.01); };
calculateFeedbackAmount
:
inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - getCalculatedCutoff()); }
cutoffMod
. feedbackAmount
, :
inline void setCutoffMod(double newCutoffMod) { cutoffMod = newCutoffMod; calculateFeedbackAmount(); }
, . Filter::process
:
if (inputValue == 0.0) return inputValue; double calculatedCutoff = getCalculatedCutoff(); buf0 += calculatedCutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1)); buf1 += calculatedCutoff * (buf0 - buf1); buf2 += calculatedCutoff * (buf1 - buf2); buf3 += calculatedCutoff * (buf2 – buf3);
, . , , . . cutoff
calculatedCutoff
-.
, ( setCutoffMod
), Synthesis
, , . , cutoffMod
. filterEnvelopeAmount
-1
+1
. GUI.
private
Synthesis.h :
EnvelopeGenerator mFilterEnvelopeGenerator; double filterEnvelopeAmount;
MIDI , onNoteOn
onNoteOff
:
inline void onNoteOn(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); }; inline void onNoteOff(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); };
. ProcessDoubleReplacing
:
mFilter.setCutoffMod(mFilterEnvelopeGenerator.nextSample() * filterEnvelopeAmount);
, nextSample
filterEnvelopeAmount
cutoffMod
. filterEnvelopeAmount
:
Synthesis::Synthesis(IPlugInstanceInfo instanceInfo) : IPLUG_CTOR(kNumParams, kNumPrograms, instanceInfo), lastVirtualKeyboardNoteNumber(virtualKeyboardMinimumNoteNumber - 1), filterEnvelopeAmount(0.0) { // ... }
Synthesis::Reset
:
mFilterEnvelopeGenerator.setSampleRate(GetSampleRate());
EParams
, . AttachGraphics
:
// Knobs for filter envelope // Attack knob GetParam(mFilterAttack)->InitDouble("Filter Env Attack", 0.01, 0.01, 10.0, 0.001); GetParam(mFilterAttack)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 139, 178, mFilterAttack, &smallKnobBitmap)); // Decay knob: GetParam(mFilterDecay)->InitDouble("Filter Env Decay", 0.5, 0.01, 15.0, 0.001); GetParam(mFilterDecay)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 195, 178, mFilterDecay, &smallKnobBitmap)); // Sustain knob: GetParam(mFilterSustain)->InitDouble("Filter Env Sustain", 0.1, 0.001, 1.0, 0.001); GetParam(mFilterSustain)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 251, 178, mFilterSustain, &smallKnobBitmap)); // Release knob: GetParam(mFilterRelease)->InitDouble("Filter Env Release", 1.0, 0.001, 15.0, 0.001); GetParam(mFilterRelease)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 307, 178, mFilterRelease, &smallKnobBitmap)); // Filter envelope amount knob: GetParam(mFilterEnvelopeAmount)->InitDouble("Filter Env Amount", 0.0, -1.0, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 363, 178, mFilterEnvelopeAmount, &smallKnobBitmap));
, smallKnobBitmap
. . , . Synthesis::OnParamChange
switch
:
case mFilterAttack: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK, GetParam(paramIdx)->Value()); break; case mFilterDecay: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_DECAY, GetParam(paramIdx)->Value()); break; case mFilterSustain: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_SUSTAIN, GetParam(paramIdx)->Value()); break; case mFilterRelease: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE, GetParam(paramIdx)->Value()); break; case mFilterEnvelopeAmount: filterEnvelopeAmount = GetParam(paramIdx)->Value(); break;
!
! (- C1), :
EnvelopeGenerator
. . !
.
:)
:
martin-finke.de/blog/articles/audio-plugins-013-filter
#include Filter.h ( ) :
class Filter { public: enum FilterMode { FILTER_MODE_LOWPASS = 0, FILTER_MODE_HIGHPASS, FILTER_MODE_BANDPASS, kNumFilterModes }; Filter() : cutoff(0.99), resonance(0.0), mode(FILTER_MODE_LOWPASS), buf0(0.0), buf1(0.0) { calculateFeedbackAmount(); }; double process(double inputValue); inline void setCutoff(double newCutoff) { cutoff = newCutoff; calculateFeedbackAmount(); }; inline void setResonance(double newResonance) { resonance = newResonance; calculateFeedbackAmount(); }; inline void setFilterMode(FilterMode newMode) { mode = newMode; } private: double cutoff; double resonance; FilterMode mode; double feedbackAmount; inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - cutoff); } double buf0; double buf1; };
private
cutoff
resonance
. Mode
(Lowpass, Highpass, Bandpass). feedbackAmount
, buf0
buf1
, .
( calculateFeedbackAmount()
). process
. feedbackAmount
cutoff
resonance
, calculateFeedbackAmount
.
Filter.cpp :
// By Paul Kellett // http://www.musicdsp.org/showone.php?id=29 double Filter::process(double inputValue) { buf0 += cutoff * (inputValue - buf0); buf1 += cutoff * (buf0 - buf1); switch (mode) { case FILTER_MODE_LOWPASS: return buf1; case FILTER_MODE_HIGHPASS: return inputValue - buf0; case FILTER_MODE_BANDPASS: return buf0 - buf1; default: return 0.0; } }
, ? , . « » , (. . 6 ). buf0
buf1
: . inputValue
, - buf0
( ). . 6 / 12 /. switch
, buf1
. , , buf0
, 12, 6 /, .
case
FILTER_MODE_HIGHPASS
. buf0
. inputValue
buf0
, . buf1
, .
case
FILTER_MODE_BANDPASS
, buf0 - buf1
. buf0
, buf1
. , .
: , .
buf1
. ( Infinite Impulse Response , IIR ). , . , , .
, , . , , , , . , NI Massive, « ». , , . , ( Massive ). «» , , , AAS Ultra Analog, , , , . , , , , NI Reaktor. , — , .. .
. GUI. bg.png ( “Move to trash” Xcode), :
filtermode.png knob_small.png ( , 50 50 ) bg.png ( )
ID resource.h :
// Unique IDs for each image resource. #define BG_ID 101 #define WHITE_KEY_ID 102 #define BLACK_KEY_ID 103 #define WAVEFORM_ID 104 #define KNOB_ID 105 #define KNOB_SMALL_ID 106 #define FILTERMODE_ID 107 // Image resource locations for this plug. #define BG_FN "resources/img/bg.png" #define WHITE_KEY_FN "resources/img/whitekey.png" #define BLACK_KEY_FN "resources/img/blackkey.png" #define WAVEFORM_FN "resources/img/waveform.png" #define KNOB_FN "resources/img/knob.png" #define KNOB_SMALL_FN "resources/img/knob_small.png" #define FILTERMODE_FN "resources/img/filtermode.png"
Synthesis.rc :
#include "resource.h" BG_ID PNG BG_FN WHITE_KEY_ID PNG WHITE_KEY_FN BLACK_KEY_ID PNG BLACK_KEY_FN WAVEFORM_ID PNG WAVEFORM_FN KNOB_ID PNG KNOB_FN KNOB_SMALL_ID PNG KNOB_SMALL_FN FILTERMODE_ID PNG FILTERMODE_FN
#include "Filter.h"
Synthesis.h private
:
Filter mFilter;
EParams
Synthesis.cpp :
enum EParams { mWaveform = 0, mAttack, mDecay, mSustain, mRelease, mFilterMode, mFilterCutoff, mFilterResonance, mFilterAttack, mFilterDecay, mFilterSustain, mFilterRelease, mFilterEnvelopeAmount, kNumParams };
:
pGraphics->AttachControl(new ISwitchControl(this, 24, 38, mWaveform, &waveformBitmap));
. (Lowpass, Highpass, Bandpass). AttachGraphics(pGraphics)
:
GetParam(mFilterMode)->InitEnum("Filter Mode", Filter::FILTER_MODE_LOWPASS, Filter::kNumFilterModes); IBitmap filtermodeBitmap = pGraphics->LoadIBitmap(FILTERMODE_ID, FILTERMODE_FN, 3); pGraphics->AttachControl(new ISwitchControl(this, 24, 123, mFilterMode, &filtermodeBitmap));
. . knob_small.png :
// Knobs for filter cutoff and resonance IBitmap smallKnobBitmap = pGraphics->LoadIBitmap(KNOB_SMALL_ID, KNOB_SMALL_FN, 64); // Cutoff knob: GetParam(mFilterCutoff)->InitDouble("Cutoff", 0.99, 0.01, 0.99, 0.001); GetParam(mFilterCutoff)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 5, 177, mFilterCutoff, &smallKnobBitmap)); // Resonance knob: GetParam(mFilterResonance)->InitDouble("Resonance", 0.01, 0.01, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 61, 177, mFilterResonance, &smallKnobBitmap));
, 1.0
, calculateFeedbackAmount
, .
ProcessDoubleReplacing
:
leftOutput[i] = rightOutput[i] = mFilter.process(mOscillator.nextSample() * mEnvelopeGenerator.nextSample() * velocity / 127.0);
. switch
Synthesis::OnParamChange
:
case mFilterCutoff: mFilter.setCutoff(GetParam(paramIdx)->Value()); break; case mFilterResonance: mFilter.setResonance(GetParam(paramIdx)->Value()); break; case mFilterMode: mFilter.setFilterMode(static_cast<Filter::FilterMode>(GetParam(paramIdx)->Int())); break;
. - .
— . , , .
:
buf0 += cutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1));
, , ( buf0 – buf1
), feedbackAmount
. calculateFeedbackAmount
feedbackAmount
resonance
, . . , .
, . , , . , , .
-12 / -24 /
! 24 . switch
Filter::process
:
buf2 += cutoff * (buf1 - buf2); buf3 += cutoff * (buf2 – buf3);
: , , . , , , ( ). switch
:
switch (mode) { case FILTER_MODE_LOWPASS: return buf3; case FILTER_MODE_HIGHPASS: return inputValue - buf3; case FILTER_MODE_BANDPASS: return buf0 - buf3; default: return 0.0; }
buf1
buf3
– . -24 /. buf2
buf3
, private
Filter.h :
double buf2; double buf3;
, buf0
c buf1
:
Filter() : // ... buf0(0.0), buf1(0.0), buf2(0.0), buf3(0.0) // ...
, , : . .
?
. , , .
- cutoff
. . cutoffMod
, , cutoff
. #include private
:
double cutoffMod;
:
Filter() : cutoff(0.99), resonance(0.01), cutoffMod(0.0), // ...
. private
:
inline double getCalculatedCutoff() const { return fmax(fmin(cutoff + cutoffMod, 0.99), 0.01); };
calculateFeedbackAmount
:
inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - getCalculatedCutoff()); }
cutoffMod
. feedbackAmount
, :
inline void setCutoffMod(double newCutoffMod) { cutoffMod = newCutoffMod; calculateFeedbackAmount(); }
, . Filter::process
:
if (inputValue == 0.0) return inputValue; double calculatedCutoff = getCalculatedCutoff(); buf0 += calculatedCutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1)); buf1 += calculatedCutoff * (buf0 - buf1); buf2 += calculatedCutoff * (buf1 - buf2); buf3 += calculatedCutoff * (buf2 – buf3);
, . , , . . cutoff
calculatedCutoff
-.
, ( setCutoffMod
), Synthesis
, , . , cutoffMod
. filterEnvelopeAmount
-1
+1
. GUI.
private
Synthesis.h :
EnvelopeGenerator mFilterEnvelopeGenerator; double filterEnvelopeAmount;
MIDI , onNoteOn
onNoteOff
:
inline void onNoteOn(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); }; inline void onNoteOff(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); };
. ProcessDoubleReplacing
:
mFilter.setCutoffMod(mFilterEnvelopeGenerator.nextSample() * filterEnvelopeAmount);
, nextSample
filterEnvelopeAmount
cutoffMod
. filterEnvelopeAmount
:
Synthesis::Synthesis(IPlugInstanceInfo instanceInfo) : IPLUG_CTOR(kNumParams, kNumPrograms, instanceInfo), lastVirtualKeyboardNoteNumber(virtualKeyboardMinimumNoteNumber - 1), filterEnvelopeAmount(0.0) { // ... }
Synthesis::Reset
:
mFilterEnvelopeGenerator.setSampleRate(GetSampleRate());
EParams
, . AttachGraphics
:
// Knobs for filter envelope // Attack knob GetParam(mFilterAttack)->InitDouble("Filter Env Attack", 0.01, 0.01, 10.0, 0.001); GetParam(mFilterAttack)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 139, 178, mFilterAttack, &smallKnobBitmap)); // Decay knob: GetParam(mFilterDecay)->InitDouble("Filter Env Decay", 0.5, 0.01, 15.0, 0.001); GetParam(mFilterDecay)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 195, 178, mFilterDecay, &smallKnobBitmap)); // Sustain knob: GetParam(mFilterSustain)->InitDouble("Filter Env Sustain", 0.1, 0.001, 1.0, 0.001); GetParam(mFilterSustain)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 251, 178, mFilterSustain, &smallKnobBitmap)); // Release knob: GetParam(mFilterRelease)->InitDouble("Filter Env Release", 1.0, 0.001, 15.0, 0.001); GetParam(mFilterRelease)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 307, 178, mFilterRelease, &smallKnobBitmap)); // Filter envelope amount knob: GetParam(mFilterEnvelopeAmount)->InitDouble("Filter Env Amount", 0.0, -1.0, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 363, 178, mFilterEnvelopeAmount, &smallKnobBitmap));
, smallKnobBitmap
. . , . Synthesis::OnParamChange
switch
:
case mFilterAttack: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK, GetParam(paramIdx)->Value()); break; case mFilterDecay: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_DECAY, GetParam(paramIdx)->Value()); break; case mFilterSustain: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_SUSTAIN, GetParam(paramIdx)->Value()); break; case mFilterRelease: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE, GetParam(paramIdx)->Value()); break; case mFilterEnvelopeAmount: filterEnvelopeAmount = GetParam(paramIdx)->Value(); break;
!
! (- C1), :
EnvelopeGenerator
. . !
.
:)
:
martin-finke.de/blog/articles/audio-plugins-013-filter
#include Filter.h ( ) :
class Filter { public: enum FilterMode { FILTER_MODE_LOWPASS = 0, FILTER_MODE_HIGHPASS, FILTER_MODE_BANDPASS, kNumFilterModes }; Filter() : cutoff(0.99), resonance(0.0), mode(FILTER_MODE_LOWPASS), buf0(0.0), buf1(0.0) { calculateFeedbackAmount(); }; double process(double inputValue); inline void setCutoff(double newCutoff) { cutoff = newCutoff; calculateFeedbackAmount(); }; inline void setResonance(double newResonance) { resonance = newResonance; calculateFeedbackAmount(); }; inline void setFilterMode(FilterMode newMode) { mode = newMode; } private: double cutoff; double resonance; FilterMode mode; double feedbackAmount; inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - cutoff); } double buf0; double buf1; };
private
cutoff
resonance
. Mode
(Lowpass, Highpass, Bandpass). feedbackAmount
, buf0
buf1
, .
( calculateFeedbackAmount()
). process
. feedbackAmount
cutoff
resonance
, calculateFeedbackAmount
.
Filter.cpp :
// By Paul Kellett // http://www.musicdsp.org/showone.php?id=29 double Filter::process(double inputValue) { buf0 += cutoff * (inputValue - buf0); buf1 += cutoff * (buf0 - buf1); switch (mode) { case FILTER_MODE_LOWPASS: return buf1; case FILTER_MODE_HIGHPASS: return inputValue - buf0; case FILTER_MODE_BANDPASS: return buf0 - buf1; default: return 0.0; } }
, ? , . « » , (. . 6 ). buf0
buf1
: . inputValue
, - buf0
( ). . 6 / 12 /. switch
, buf1
. , , buf0
, 12, 6 /, .
case
FILTER_MODE_HIGHPASS
. buf0
. inputValue
buf0
, . buf1
, .
case
FILTER_MODE_BANDPASS
, buf0 - buf1
. buf0
, buf1
. , .
: , .
buf1
. ( Infinite Impulse Response , IIR ). , . , , .
, , . , , , , . , NI Massive, « ». , , . , ( Massive ). «» , , , AAS Ultra Analog, , , , . , , , , NI Reaktor. , — , .. .
. GUI. bg.png ( “Move to trash” Xcode), :
filtermode.png knob_small.png ( , 50 50 ) bg.png ( )
ID resource.h :
// Unique IDs for each image resource. #define BG_ID 101 #define WHITE_KEY_ID 102 #define BLACK_KEY_ID 103 #define WAVEFORM_ID 104 #define KNOB_ID 105 #define KNOB_SMALL_ID 106 #define FILTERMODE_ID 107 // Image resource locations for this plug. #define BG_FN "resources/img/bg.png" #define WHITE_KEY_FN "resources/img/whitekey.png" #define BLACK_KEY_FN "resources/img/blackkey.png" #define WAVEFORM_FN "resources/img/waveform.png" #define KNOB_FN "resources/img/knob.png" #define KNOB_SMALL_FN "resources/img/knob_small.png" #define FILTERMODE_FN "resources/img/filtermode.png"
Synthesis.rc :
#include "resource.h" BG_ID PNG BG_FN WHITE_KEY_ID PNG WHITE_KEY_FN BLACK_KEY_ID PNG BLACK_KEY_FN WAVEFORM_ID PNG WAVEFORM_FN KNOB_ID PNG KNOB_FN KNOB_SMALL_ID PNG KNOB_SMALL_FN FILTERMODE_ID PNG FILTERMODE_FN
#include "Filter.h"
Synthesis.h private
:
Filter mFilter;
EParams
Synthesis.cpp :
enum EParams { mWaveform = 0, mAttack, mDecay, mSustain, mRelease, mFilterMode, mFilterCutoff, mFilterResonance, mFilterAttack, mFilterDecay, mFilterSustain, mFilterRelease, mFilterEnvelopeAmount, kNumParams };
:
pGraphics->AttachControl(new ISwitchControl(this, 24, 38, mWaveform, &waveformBitmap));
. (Lowpass, Highpass, Bandpass). AttachGraphics(pGraphics)
:
GetParam(mFilterMode)->InitEnum("Filter Mode", Filter::FILTER_MODE_LOWPASS, Filter::kNumFilterModes); IBitmap filtermodeBitmap = pGraphics->LoadIBitmap(FILTERMODE_ID, FILTERMODE_FN, 3); pGraphics->AttachControl(new ISwitchControl(this, 24, 123, mFilterMode, &filtermodeBitmap));
. . knob_small.png :
// Knobs for filter cutoff and resonance IBitmap smallKnobBitmap = pGraphics->LoadIBitmap(KNOB_SMALL_ID, KNOB_SMALL_FN, 64); // Cutoff knob: GetParam(mFilterCutoff)->InitDouble("Cutoff", 0.99, 0.01, 0.99, 0.001); GetParam(mFilterCutoff)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 5, 177, mFilterCutoff, &smallKnobBitmap)); // Resonance knob: GetParam(mFilterResonance)->InitDouble("Resonance", 0.01, 0.01, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 61, 177, mFilterResonance, &smallKnobBitmap));
, 1.0
, calculateFeedbackAmount
, .
ProcessDoubleReplacing
:
leftOutput[i] = rightOutput[i] = mFilter.process(mOscillator.nextSample() * mEnvelopeGenerator.nextSample() * velocity / 127.0);
. switch
Synthesis::OnParamChange
:
case mFilterCutoff: mFilter.setCutoff(GetParam(paramIdx)->Value()); break; case mFilterResonance: mFilter.setResonance(GetParam(paramIdx)->Value()); break; case mFilterMode: mFilter.setFilterMode(static_cast<Filter::FilterMode>(GetParam(paramIdx)->Int())); break;
. - .
— . , , .
:
buf0 += cutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1));
, , ( buf0 – buf1
), feedbackAmount
. calculateFeedbackAmount
feedbackAmount
resonance
, . . , .
, . , , . , , .
-12 / -24 /
! 24 . switch
Filter::process
:
buf2 += cutoff * (buf1 - buf2); buf3 += cutoff * (buf2 – buf3);
: , , . , , , ( ). switch
:
switch (mode) { case FILTER_MODE_LOWPASS: return buf3; case FILTER_MODE_HIGHPASS: return inputValue - buf3; case FILTER_MODE_BANDPASS: return buf0 - buf3; default: return 0.0; }
buf1
buf3
– . -24 /. buf2
buf3
, private
Filter.h :
double buf2; double buf3;
, buf0
c buf1
:
Filter() : // ... buf0(0.0), buf1(0.0), buf2(0.0), buf3(0.0) // ...
, , : . .
?
. , , .
- cutoff
. . cutoffMod
, , cutoff
. #include private
:
double cutoffMod;
:
Filter() : cutoff(0.99), resonance(0.01), cutoffMod(0.0), // ...
. private
:
inline double getCalculatedCutoff() const { return fmax(fmin(cutoff + cutoffMod, 0.99), 0.01); };
calculateFeedbackAmount
:
inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - getCalculatedCutoff()); }
cutoffMod
. feedbackAmount
, :
inline void setCutoffMod(double newCutoffMod) { cutoffMod = newCutoffMod; calculateFeedbackAmount(); }
, . Filter::process
:
if (inputValue == 0.0) return inputValue; double calculatedCutoff = getCalculatedCutoff(); buf0 += calculatedCutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1)); buf1 += calculatedCutoff * (buf0 - buf1); buf2 += calculatedCutoff * (buf1 - buf2); buf3 += calculatedCutoff * (buf2 – buf3);
, . , , . . cutoff
calculatedCutoff
-.
, ( setCutoffMod
), Synthesis
, , . , cutoffMod
. filterEnvelopeAmount
-1
+1
. GUI.
private
Synthesis.h :
EnvelopeGenerator mFilterEnvelopeGenerator; double filterEnvelopeAmount;
MIDI , onNoteOn
onNoteOff
:
inline void onNoteOn(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); }; inline void onNoteOff(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); };
. ProcessDoubleReplacing
:
mFilter.setCutoffMod(mFilterEnvelopeGenerator.nextSample() * filterEnvelopeAmount);
, nextSample
filterEnvelopeAmount
cutoffMod
. filterEnvelopeAmount
:
Synthesis::Synthesis(IPlugInstanceInfo instanceInfo) : IPLUG_CTOR(kNumParams, kNumPrograms, instanceInfo), lastVirtualKeyboardNoteNumber(virtualKeyboardMinimumNoteNumber - 1), filterEnvelopeAmount(0.0) { // ... }
Synthesis::Reset
:
mFilterEnvelopeGenerator.setSampleRate(GetSampleRate());
EParams
, . AttachGraphics
:
// Knobs for filter envelope // Attack knob GetParam(mFilterAttack)->InitDouble("Filter Env Attack", 0.01, 0.01, 10.0, 0.001); GetParam(mFilterAttack)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 139, 178, mFilterAttack, &smallKnobBitmap)); // Decay knob: GetParam(mFilterDecay)->InitDouble("Filter Env Decay", 0.5, 0.01, 15.0, 0.001); GetParam(mFilterDecay)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 195, 178, mFilterDecay, &smallKnobBitmap)); // Sustain knob: GetParam(mFilterSustain)->InitDouble("Filter Env Sustain", 0.1, 0.001, 1.0, 0.001); GetParam(mFilterSustain)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 251, 178, mFilterSustain, &smallKnobBitmap)); // Release knob: GetParam(mFilterRelease)->InitDouble("Filter Env Release", 1.0, 0.001, 15.0, 0.001); GetParam(mFilterRelease)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 307, 178, mFilterRelease, &smallKnobBitmap)); // Filter envelope amount knob: GetParam(mFilterEnvelopeAmount)->InitDouble("Filter Env Amount", 0.0, -1.0, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 363, 178, mFilterEnvelopeAmount, &smallKnobBitmap));
, smallKnobBitmap
. . , . Synthesis::OnParamChange
switch
:
case mFilterAttack: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK, GetParam(paramIdx)->Value()); break; case mFilterDecay: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_DECAY, GetParam(paramIdx)->Value()); break; case mFilterSustain: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_SUSTAIN, GetParam(paramIdx)->Value()); break; case mFilterRelease: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE, GetParam(paramIdx)->Value()); break; case mFilterEnvelopeAmount: filterEnvelopeAmount = GetParam(paramIdx)->Value(); break;
!
! (- C1), :
EnvelopeGenerator
. . !
.
:)
:
martin-finke.de/blog/articles/audio-plugins-013-filter
#include Filter.h ( ) :
class Filter { public: enum FilterMode { FILTER_MODE_LOWPASS = 0, FILTER_MODE_HIGHPASS, FILTER_MODE_BANDPASS, kNumFilterModes }; Filter() : cutoff(0.99), resonance(0.0), mode(FILTER_MODE_LOWPASS), buf0(0.0), buf1(0.0) { calculateFeedbackAmount(); }; double process(double inputValue); inline void setCutoff(double newCutoff) { cutoff = newCutoff; calculateFeedbackAmount(); }; inline void setResonance(double newResonance) { resonance = newResonance; calculateFeedbackAmount(); }; inline void setFilterMode(FilterMode newMode) { mode = newMode; } private: double cutoff; double resonance; FilterMode mode; double feedbackAmount; inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - cutoff); } double buf0; double buf1; };
private
cutoff
resonance
. Mode
(Lowpass, Highpass, Bandpass). feedbackAmount
, buf0
buf1
, .
( calculateFeedbackAmount()
). process
. feedbackAmount
cutoff
resonance
, calculateFeedbackAmount
.
Filter.cpp :
// By Paul Kellett // http://www.musicdsp.org/showone.php?id=29 double Filter::process(double inputValue) { buf0 += cutoff * (inputValue - buf0); buf1 += cutoff * (buf0 - buf1); switch (mode) { case FILTER_MODE_LOWPASS: return buf1; case FILTER_MODE_HIGHPASS: return inputValue - buf0; case FILTER_MODE_BANDPASS: return buf0 - buf1; default: return 0.0; } }
, ? , . « » , (. . 6 ). buf0
buf1
: . inputValue
, - buf0
( ). . 6 / 12 /. switch
, buf1
. , , buf0
, 12, 6 /, .
case
FILTER_MODE_HIGHPASS
. buf0
. inputValue
buf0
, . buf1
, .
case
FILTER_MODE_BANDPASS
, buf0 - buf1
. buf0
, buf1
. , .
: , .
buf1
. ( Infinite Impulse Response , IIR ). , . , , .
, , . , , , , . , NI Massive, « ». , , . , ( Massive ). «» , , , AAS Ultra Analog, , , , . , , , , NI Reaktor. , — , .. .
. GUI. bg.png ( “Move to trash” Xcode), :
filtermode.png knob_small.png ( , 50 50 ) bg.png ( )
ID resource.h :
// Unique IDs for each image resource. #define BG_ID 101 #define WHITE_KEY_ID 102 #define BLACK_KEY_ID 103 #define WAVEFORM_ID 104 #define KNOB_ID 105 #define KNOB_SMALL_ID 106 #define FILTERMODE_ID 107 // Image resource locations for this plug. #define BG_FN "resources/img/bg.png" #define WHITE_KEY_FN "resources/img/whitekey.png" #define BLACK_KEY_FN "resources/img/blackkey.png" #define WAVEFORM_FN "resources/img/waveform.png" #define KNOB_FN "resources/img/knob.png" #define KNOB_SMALL_FN "resources/img/knob_small.png" #define FILTERMODE_FN "resources/img/filtermode.png"
Synthesis.rc :
#include "resource.h" BG_ID PNG BG_FN WHITE_KEY_ID PNG WHITE_KEY_FN BLACK_KEY_ID PNG BLACK_KEY_FN WAVEFORM_ID PNG WAVEFORM_FN KNOB_ID PNG KNOB_FN KNOB_SMALL_ID PNG KNOB_SMALL_FN FILTERMODE_ID PNG FILTERMODE_FN
#include "Filter.h"
Synthesis.h private
:
Filter mFilter;
EParams
Synthesis.cpp :
enum EParams { mWaveform = 0, mAttack, mDecay, mSustain, mRelease, mFilterMode, mFilterCutoff, mFilterResonance, mFilterAttack, mFilterDecay, mFilterSustain, mFilterRelease, mFilterEnvelopeAmount, kNumParams };
:
pGraphics->AttachControl(new ISwitchControl(this, 24, 38, mWaveform, &waveformBitmap));
. (Lowpass, Highpass, Bandpass). AttachGraphics(pGraphics)
:
GetParam(mFilterMode)->InitEnum("Filter Mode", Filter::FILTER_MODE_LOWPASS, Filter::kNumFilterModes); IBitmap filtermodeBitmap = pGraphics->LoadIBitmap(FILTERMODE_ID, FILTERMODE_FN, 3); pGraphics->AttachControl(new ISwitchControl(this, 24, 123, mFilterMode, &filtermodeBitmap));
. . knob_small.png :
// Knobs for filter cutoff and resonance IBitmap smallKnobBitmap = pGraphics->LoadIBitmap(KNOB_SMALL_ID, KNOB_SMALL_FN, 64); // Cutoff knob: GetParam(mFilterCutoff)->InitDouble("Cutoff", 0.99, 0.01, 0.99, 0.001); GetParam(mFilterCutoff)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 5, 177, mFilterCutoff, &smallKnobBitmap)); // Resonance knob: GetParam(mFilterResonance)->InitDouble("Resonance", 0.01, 0.01, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 61, 177, mFilterResonance, &smallKnobBitmap));
, 1.0
, calculateFeedbackAmount
, .
ProcessDoubleReplacing
:
leftOutput[i] = rightOutput[i] = mFilter.process(mOscillator.nextSample() * mEnvelopeGenerator.nextSample() * velocity / 127.0);
. switch
Synthesis::OnParamChange
:
case mFilterCutoff: mFilter.setCutoff(GetParam(paramIdx)->Value()); break; case mFilterResonance: mFilter.setResonance(GetParam(paramIdx)->Value()); break; case mFilterMode: mFilter.setFilterMode(static_cast<Filter::FilterMode>(GetParam(paramIdx)->Int())); break;
. - .
— . , , .
:
buf0 += cutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1));
, , ( buf0 – buf1
), feedbackAmount
. calculateFeedbackAmount
feedbackAmount
resonance
, . . , .
, . , , . , , .
-12 / -24 /
! 24 . switch
Filter::process
:
buf2 += cutoff * (buf1 - buf2); buf3 += cutoff * (buf2 – buf3);
: , , . , , , ( ). switch
:
switch (mode) { case FILTER_MODE_LOWPASS: return buf3; case FILTER_MODE_HIGHPASS: return inputValue - buf3; case FILTER_MODE_BANDPASS: return buf0 - buf3; default: return 0.0; }
buf1
buf3
– . -24 /. buf2
buf3
, private
Filter.h :
double buf2; double buf3;
, buf0
c buf1
:
Filter() : // ... buf0(0.0), buf1(0.0), buf2(0.0), buf3(0.0) // ...
, , : . .
?
. , , .
- cutoff
. . cutoffMod
, , cutoff
. #include private
:
double cutoffMod;
:
Filter() : cutoff(0.99), resonance(0.01), cutoffMod(0.0), // ...
. private
:
inline double getCalculatedCutoff() const { return fmax(fmin(cutoff + cutoffMod, 0.99), 0.01); };
calculateFeedbackAmount
:
inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - getCalculatedCutoff()); }
cutoffMod
. feedbackAmount
, :
inline void setCutoffMod(double newCutoffMod) { cutoffMod = newCutoffMod; calculateFeedbackAmount(); }
, . Filter::process
:
if (inputValue == 0.0) return inputValue; double calculatedCutoff = getCalculatedCutoff(); buf0 += calculatedCutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1)); buf1 += calculatedCutoff * (buf0 - buf1); buf2 += calculatedCutoff * (buf1 - buf2); buf3 += calculatedCutoff * (buf2 – buf3);
, . , , . . cutoff
calculatedCutoff
-.
, ( setCutoffMod
), Synthesis
, , . , cutoffMod
. filterEnvelopeAmount
-1
+1
. GUI.
private
Synthesis.h :
EnvelopeGenerator mFilterEnvelopeGenerator; double filterEnvelopeAmount;
MIDI , onNoteOn
onNoteOff
:
inline void onNoteOn(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); }; inline void onNoteOff(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); };
. ProcessDoubleReplacing
:
mFilter.setCutoffMod(mFilterEnvelopeGenerator.nextSample() * filterEnvelopeAmount);
, nextSample
filterEnvelopeAmount
cutoffMod
. filterEnvelopeAmount
:
Synthesis::Synthesis(IPlugInstanceInfo instanceInfo) : IPLUG_CTOR(kNumParams, kNumPrograms, instanceInfo), lastVirtualKeyboardNoteNumber(virtualKeyboardMinimumNoteNumber - 1), filterEnvelopeAmount(0.0) { // ... }
Synthesis::Reset
:
mFilterEnvelopeGenerator.setSampleRate(GetSampleRate());
EParams
, . AttachGraphics
:
// Knobs for filter envelope // Attack knob GetParam(mFilterAttack)->InitDouble("Filter Env Attack", 0.01, 0.01, 10.0, 0.001); GetParam(mFilterAttack)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 139, 178, mFilterAttack, &smallKnobBitmap)); // Decay knob: GetParam(mFilterDecay)->InitDouble("Filter Env Decay", 0.5, 0.01, 15.0, 0.001); GetParam(mFilterDecay)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 195, 178, mFilterDecay, &smallKnobBitmap)); // Sustain knob: GetParam(mFilterSustain)->InitDouble("Filter Env Sustain", 0.1, 0.001, 1.0, 0.001); GetParam(mFilterSustain)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 251, 178, mFilterSustain, &smallKnobBitmap)); // Release knob: GetParam(mFilterRelease)->InitDouble("Filter Env Release", 1.0, 0.001, 15.0, 0.001); GetParam(mFilterRelease)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 307, 178, mFilterRelease, &smallKnobBitmap)); // Filter envelope amount knob: GetParam(mFilterEnvelopeAmount)->InitDouble("Filter Env Amount", 0.0, -1.0, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 363, 178, mFilterEnvelopeAmount, &smallKnobBitmap));
, smallKnobBitmap
. . , . Synthesis::OnParamChange
switch
:
case mFilterAttack: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK, GetParam(paramIdx)->Value()); break; case mFilterDecay: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_DECAY, GetParam(paramIdx)->Value()); break; case mFilterSustain: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_SUSTAIN, GetParam(paramIdx)->Value()); break; case mFilterRelease: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE, GetParam(paramIdx)->Value()); break; case mFilterEnvelopeAmount: filterEnvelopeAmount = GetParam(paramIdx)->Value(); break;
!
! (- C1), :
EnvelopeGenerator
. . !
.
:)
:
martin-finke.de/blog/articles/audio-plugins-013-filter
#include Filter.h ( ) :
class Filter { public: enum FilterMode { FILTER_MODE_LOWPASS = 0, FILTER_MODE_HIGHPASS, FILTER_MODE_BANDPASS, kNumFilterModes }; Filter() : cutoff(0.99), resonance(0.0), mode(FILTER_MODE_LOWPASS), buf0(0.0), buf1(0.0) { calculateFeedbackAmount(); }; double process(double inputValue); inline void setCutoff(double newCutoff) { cutoff = newCutoff; calculateFeedbackAmount(); }; inline void setResonance(double newResonance) { resonance = newResonance; calculateFeedbackAmount(); }; inline void setFilterMode(FilterMode newMode) { mode = newMode; } private: double cutoff; double resonance; FilterMode mode; double feedbackAmount; inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - cutoff); } double buf0; double buf1; };
private
cutoff
resonance
. Mode
(Lowpass, Highpass, Bandpass). feedbackAmount
, buf0
buf1
, .
( calculateFeedbackAmount()
). process
. feedbackAmount
cutoff
resonance
, calculateFeedbackAmount
.
Filter.cpp :
// By Paul Kellett // http://www.musicdsp.org/showone.php?id=29 double Filter::process(double inputValue) { buf0 += cutoff * (inputValue - buf0); buf1 += cutoff * (buf0 - buf1); switch (mode) { case FILTER_MODE_LOWPASS: return buf1; case FILTER_MODE_HIGHPASS: return inputValue - buf0; case FILTER_MODE_BANDPASS: return buf0 - buf1; default: return 0.0; } }
, ? , . « » , (. . 6 ). buf0
buf1
: . inputValue
, - buf0
( ). . 6 / 12 /. switch
, buf1
. , , buf0
, 12, 6 /, .
case
FILTER_MODE_HIGHPASS
. buf0
. inputValue
buf0
, . buf1
, .
case
FILTER_MODE_BANDPASS
, buf0 - buf1
. buf0
, buf1
. , .
: , .
buf1
. ( Infinite Impulse Response , IIR ). , . , , .
, , . , , , , . , NI Massive, « ». , , . , ( Massive ). «» , , , AAS Ultra Analog, , , , . , , , , NI Reaktor. , — , .. .
. GUI. bg.png ( “Move to trash” Xcode), :
filtermode.png knob_small.png ( , 50 50 ) bg.png ( )
ID resource.h :
// Unique IDs for each image resource. #define BG_ID 101 #define WHITE_KEY_ID 102 #define BLACK_KEY_ID 103 #define WAVEFORM_ID 104 #define KNOB_ID 105 #define KNOB_SMALL_ID 106 #define FILTERMODE_ID 107 // Image resource locations for this plug. #define BG_FN "resources/img/bg.png" #define WHITE_KEY_FN "resources/img/whitekey.png" #define BLACK_KEY_FN "resources/img/blackkey.png" #define WAVEFORM_FN "resources/img/waveform.png" #define KNOB_FN "resources/img/knob.png" #define KNOB_SMALL_FN "resources/img/knob_small.png" #define FILTERMODE_FN "resources/img/filtermode.png"
Synthesis.rc :
#include "resource.h" BG_ID PNG BG_FN WHITE_KEY_ID PNG WHITE_KEY_FN BLACK_KEY_ID PNG BLACK_KEY_FN WAVEFORM_ID PNG WAVEFORM_FN KNOB_ID PNG KNOB_FN KNOB_SMALL_ID PNG KNOB_SMALL_FN FILTERMODE_ID PNG FILTERMODE_FN
#include "Filter.h"
Synthesis.h private
:
Filter mFilter;
EParams
Synthesis.cpp :
enum EParams { mWaveform = 0, mAttack, mDecay, mSustain, mRelease, mFilterMode, mFilterCutoff, mFilterResonance, mFilterAttack, mFilterDecay, mFilterSustain, mFilterRelease, mFilterEnvelopeAmount, kNumParams };
:
pGraphics->AttachControl(new ISwitchControl(this, 24, 38, mWaveform, &waveformBitmap));
. (Lowpass, Highpass, Bandpass). AttachGraphics(pGraphics)
:
GetParam(mFilterMode)->InitEnum("Filter Mode", Filter::FILTER_MODE_LOWPASS, Filter::kNumFilterModes); IBitmap filtermodeBitmap = pGraphics->LoadIBitmap(FILTERMODE_ID, FILTERMODE_FN, 3); pGraphics->AttachControl(new ISwitchControl(this, 24, 123, mFilterMode, &filtermodeBitmap));
. . knob_small.png :
// Knobs for filter cutoff and resonance IBitmap smallKnobBitmap = pGraphics->LoadIBitmap(KNOB_SMALL_ID, KNOB_SMALL_FN, 64); // Cutoff knob: GetParam(mFilterCutoff)->InitDouble("Cutoff", 0.99, 0.01, 0.99, 0.001); GetParam(mFilterCutoff)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 5, 177, mFilterCutoff, &smallKnobBitmap)); // Resonance knob: GetParam(mFilterResonance)->InitDouble("Resonance", 0.01, 0.01, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 61, 177, mFilterResonance, &smallKnobBitmap));
, 1.0
, calculateFeedbackAmount
, .
ProcessDoubleReplacing
:
leftOutput[i] = rightOutput[i] = mFilter.process(mOscillator.nextSample() * mEnvelopeGenerator.nextSample() * velocity / 127.0);
. switch
Synthesis::OnParamChange
:
case mFilterCutoff: mFilter.setCutoff(GetParam(paramIdx)->Value()); break; case mFilterResonance: mFilter.setResonance(GetParam(paramIdx)->Value()); break; case mFilterMode: mFilter.setFilterMode(static_cast<Filter::FilterMode>(GetParam(paramIdx)->Int())); break;
. - .
— . , , .
:
buf0 += cutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1));
, , ( buf0 – buf1
), feedbackAmount
. calculateFeedbackAmount
feedbackAmount
resonance
, . . , .
, . , , . , , .
-12 / -24 /
! 24 . switch
Filter::process
:
buf2 += cutoff * (buf1 - buf2); buf3 += cutoff * (buf2 – buf3);
: , , . , , , ( ). switch
:
switch (mode) { case FILTER_MODE_LOWPASS: return buf3; case FILTER_MODE_HIGHPASS: return inputValue - buf3; case FILTER_MODE_BANDPASS: return buf0 - buf3; default: return 0.0; }
buf1
buf3
– . -24 /. buf2
buf3
, private
Filter.h :
double buf2; double buf3;
, buf0
c buf1
:
Filter() : // ... buf0(0.0), buf1(0.0), buf2(0.0), buf3(0.0) // ...
, , : . .
?
. , , .
- cutoff
. . cutoffMod
, , cutoff
. #include private
:
double cutoffMod;
:
Filter() : cutoff(0.99), resonance(0.01), cutoffMod(0.0), // ...
. private
:
inline double getCalculatedCutoff() const { return fmax(fmin(cutoff + cutoffMod, 0.99), 0.01); };
calculateFeedbackAmount
:
inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - getCalculatedCutoff()); }
cutoffMod
. feedbackAmount
, :
inline void setCutoffMod(double newCutoffMod) { cutoffMod = newCutoffMod; calculateFeedbackAmount(); }
, . Filter::process
:
if (inputValue == 0.0) return inputValue; double calculatedCutoff = getCalculatedCutoff(); buf0 += calculatedCutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1)); buf1 += calculatedCutoff * (buf0 - buf1); buf2 += calculatedCutoff * (buf1 - buf2); buf3 += calculatedCutoff * (buf2 – buf3);
, . , , . . cutoff
calculatedCutoff
-.
, ( setCutoffMod
), Synthesis
, , . , cutoffMod
. filterEnvelopeAmount
-1
+1
. GUI.
private
Synthesis.h :
EnvelopeGenerator mFilterEnvelopeGenerator; double filterEnvelopeAmount;
MIDI , onNoteOn
onNoteOff
:
inline void onNoteOn(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); }; inline void onNoteOff(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); };
. ProcessDoubleReplacing
:
mFilter.setCutoffMod(mFilterEnvelopeGenerator.nextSample() * filterEnvelopeAmount);
, nextSample
filterEnvelopeAmount
cutoffMod
. filterEnvelopeAmount
:
Synthesis::Synthesis(IPlugInstanceInfo instanceInfo) : IPLUG_CTOR(kNumParams, kNumPrograms, instanceInfo), lastVirtualKeyboardNoteNumber(virtualKeyboardMinimumNoteNumber - 1), filterEnvelopeAmount(0.0) { // ... }
Synthesis::Reset
:
mFilterEnvelopeGenerator.setSampleRate(GetSampleRate());
EParams
, . AttachGraphics
:
// Knobs for filter envelope // Attack knob GetParam(mFilterAttack)->InitDouble("Filter Env Attack", 0.01, 0.01, 10.0, 0.001); GetParam(mFilterAttack)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 139, 178, mFilterAttack, &smallKnobBitmap)); // Decay knob: GetParam(mFilterDecay)->InitDouble("Filter Env Decay", 0.5, 0.01, 15.0, 0.001); GetParam(mFilterDecay)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 195, 178, mFilterDecay, &smallKnobBitmap)); // Sustain knob: GetParam(mFilterSustain)->InitDouble("Filter Env Sustain", 0.1, 0.001, 1.0, 0.001); GetParam(mFilterSustain)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 251, 178, mFilterSustain, &smallKnobBitmap)); // Release knob: GetParam(mFilterRelease)->InitDouble("Filter Env Release", 1.0, 0.001, 15.0, 0.001); GetParam(mFilterRelease)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 307, 178, mFilterRelease, &smallKnobBitmap)); // Filter envelope amount knob: GetParam(mFilterEnvelopeAmount)->InitDouble("Filter Env Amount", 0.0, -1.0, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 363, 178, mFilterEnvelopeAmount, &smallKnobBitmap));
, smallKnobBitmap
. . , . Synthesis::OnParamChange
switch
:
case mFilterAttack: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK, GetParam(paramIdx)->Value()); break; case mFilterDecay: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_DECAY, GetParam(paramIdx)->Value()); break; case mFilterSustain: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_SUSTAIN, GetParam(paramIdx)->Value()); break; case mFilterRelease: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE, GetParam(paramIdx)->Value()); break; case mFilterEnvelopeAmount: filterEnvelopeAmount = GetParam(paramIdx)->Value(); break;
!
! (- C1), :
EnvelopeGenerator
. . !
.
:)
:
martin-finke.de/blog/articles/audio-plugins-013-filter
#include Filter.h ( ) :
class Filter { public: enum FilterMode { FILTER_MODE_LOWPASS = 0, FILTER_MODE_HIGHPASS, FILTER_MODE_BANDPASS, kNumFilterModes }; Filter() : cutoff(0.99), resonance(0.0), mode(FILTER_MODE_LOWPASS), buf0(0.0), buf1(0.0) { calculateFeedbackAmount(); }; double process(double inputValue); inline void setCutoff(double newCutoff) { cutoff = newCutoff; calculateFeedbackAmount(); }; inline void setResonance(double newResonance) { resonance = newResonance; calculateFeedbackAmount(); }; inline void setFilterMode(FilterMode newMode) { mode = newMode; } private: double cutoff; double resonance; FilterMode mode; double feedbackAmount; inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - cutoff); } double buf0; double buf1; };
private
cutoff
resonance
. Mode
(Lowpass, Highpass, Bandpass). feedbackAmount
, buf0
buf1
, .
( calculateFeedbackAmount()
). process
. feedbackAmount
cutoff
resonance
, calculateFeedbackAmount
.
Filter.cpp :
// By Paul Kellett // http://www.musicdsp.org/showone.php?id=29 double Filter::process(double inputValue) { buf0 += cutoff * (inputValue - buf0); buf1 += cutoff * (buf0 - buf1); switch (mode) { case FILTER_MODE_LOWPASS: return buf1; case FILTER_MODE_HIGHPASS: return inputValue - buf0; case FILTER_MODE_BANDPASS: return buf0 - buf1; default: return 0.0; } }
, ? , . « » , (. . 6 ). buf0
buf1
: . inputValue
, - buf0
( ). . 6 / 12 /. switch
, buf1
. , , buf0
, 12, 6 /, .
case
FILTER_MODE_HIGHPASS
. buf0
. inputValue
buf0
, . buf1
, .
case
FILTER_MODE_BANDPASS
, buf0 - buf1
. buf0
, buf1
. , .
: , .
buf1
. ( Infinite Impulse Response , IIR ). , . , , .
, , . , , , , . , NI Massive, « ». , , . , ( Massive ). «» , , , AAS Ultra Analog, , , , . , , , , NI Reaktor. , — , .. .
. GUI. bg.png ( “Move to trash” Xcode), :
filtermode.png knob_small.png ( , 50 50 ) bg.png ( )
ID resource.h :
// Unique IDs for each image resource. #define BG_ID 101 #define WHITE_KEY_ID 102 #define BLACK_KEY_ID 103 #define WAVEFORM_ID 104 #define KNOB_ID 105 #define KNOB_SMALL_ID 106 #define FILTERMODE_ID 107 // Image resource locations for this plug. #define BG_FN "resources/img/bg.png" #define WHITE_KEY_FN "resources/img/whitekey.png" #define BLACK_KEY_FN "resources/img/blackkey.png" #define WAVEFORM_FN "resources/img/waveform.png" #define KNOB_FN "resources/img/knob.png" #define KNOB_SMALL_FN "resources/img/knob_small.png" #define FILTERMODE_FN "resources/img/filtermode.png"
Synthesis.rc :
#include "resource.h" BG_ID PNG BG_FN WHITE_KEY_ID PNG WHITE_KEY_FN BLACK_KEY_ID PNG BLACK_KEY_FN WAVEFORM_ID PNG WAVEFORM_FN KNOB_ID PNG KNOB_FN KNOB_SMALL_ID PNG KNOB_SMALL_FN FILTERMODE_ID PNG FILTERMODE_FN
#include "Filter.h"
Synthesis.h private
:
Filter mFilter;
EParams
Synthesis.cpp :
enum EParams { mWaveform = 0, mAttack, mDecay, mSustain, mRelease, mFilterMode, mFilterCutoff, mFilterResonance, mFilterAttack, mFilterDecay, mFilterSustain, mFilterRelease, mFilterEnvelopeAmount, kNumParams };
:
pGraphics->AttachControl(new ISwitchControl(this, 24, 38, mWaveform, &waveformBitmap));
. (Lowpass, Highpass, Bandpass). AttachGraphics(pGraphics)
:
GetParam(mFilterMode)->InitEnum("Filter Mode", Filter::FILTER_MODE_LOWPASS, Filter::kNumFilterModes); IBitmap filtermodeBitmap = pGraphics->LoadIBitmap(FILTERMODE_ID, FILTERMODE_FN, 3); pGraphics->AttachControl(new ISwitchControl(this, 24, 123, mFilterMode, &filtermodeBitmap));
. . knob_small.png :
// Knobs for filter cutoff and resonance IBitmap smallKnobBitmap = pGraphics->LoadIBitmap(KNOB_SMALL_ID, KNOB_SMALL_FN, 64); // Cutoff knob: GetParam(mFilterCutoff)->InitDouble("Cutoff", 0.99, 0.01, 0.99, 0.001); GetParam(mFilterCutoff)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 5, 177, mFilterCutoff, &smallKnobBitmap)); // Resonance knob: GetParam(mFilterResonance)->InitDouble("Resonance", 0.01, 0.01, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 61, 177, mFilterResonance, &smallKnobBitmap));
, 1.0
, calculateFeedbackAmount
, .
ProcessDoubleReplacing
:
leftOutput[i] = rightOutput[i] = mFilter.process(mOscillator.nextSample() * mEnvelopeGenerator.nextSample() * velocity / 127.0);
. switch
Synthesis::OnParamChange
:
case mFilterCutoff: mFilter.setCutoff(GetParam(paramIdx)->Value()); break; case mFilterResonance: mFilter.setResonance(GetParam(paramIdx)->Value()); break; case mFilterMode: mFilter.setFilterMode(static_cast<Filter::FilterMode>(GetParam(paramIdx)->Int())); break;
. - .
— . , , .
:
buf0 += cutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1));
, , ( buf0 – buf1
), feedbackAmount
. calculateFeedbackAmount
feedbackAmount
resonance
, . . , .
, . , , . , , .
-12 / -24 /
! 24 . switch
Filter::process
:
buf2 += cutoff * (buf1 - buf2); buf3 += cutoff * (buf2 – buf3);
: , , . , , , ( ). switch
:
switch (mode) { case FILTER_MODE_LOWPASS: return buf3; case FILTER_MODE_HIGHPASS: return inputValue - buf3; case FILTER_MODE_BANDPASS: return buf0 - buf3; default: return 0.0; }
buf1
buf3
– . -24 /. buf2
buf3
, private
Filter.h :
double buf2; double buf3;
, buf0
c buf1
:
Filter() : // ... buf0(0.0), buf1(0.0), buf2(0.0), buf3(0.0) // ...
, , : . .
?
. , , .
- cutoff
. . cutoffMod
, , cutoff
. #include private
:
double cutoffMod;
:
Filter() : cutoff(0.99), resonance(0.01), cutoffMod(0.0), // ...
. private
:
inline double getCalculatedCutoff() const { return fmax(fmin(cutoff + cutoffMod, 0.99), 0.01); };
calculateFeedbackAmount
:
inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - getCalculatedCutoff()); }
cutoffMod
. feedbackAmount
, :
inline void setCutoffMod(double newCutoffMod) { cutoffMod = newCutoffMod; calculateFeedbackAmount(); }
, . Filter::process
:
if (inputValue == 0.0) return inputValue; double calculatedCutoff = getCalculatedCutoff(); buf0 += calculatedCutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1)); buf1 += calculatedCutoff * (buf0 - buf1); buf2 += calculatedCutoff * (buf1 - buf2); buf3 += calculatedCutoff * (buf2 – buf3);
, . , , . . cutoff
calculatedCutoff
-.
, ( setCutoffMod
), Synthesis
, , . , cutoffMod
. filterEnvelopeAmount
-1
+1
. GUI.
private
Synthesis.h :
EnvelopeGenerator mFilterEnvelopeGenerator; double filterEnvelopeAmount;
MIDI , onNoteOn
onNoteOff
:
inline void onNoteOn(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); }; inline void onNoteOff(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); };
. ProcessDoubleReplacing
:
mFilter.setCutoffMod(mFilterEnvelopeGenerator.nextSample() * filterEnvelopeAmount);
, nextSample
filterEnvelopeAmount
cutoffMod
. filterEnvelopeAmount
:
Synthesis::Synthesis(IPlugInstanceInfo instanceInfo) : IPLUG_CTOR(kNumParams, kNumPrograms, instanceInfo), lastVirtualKeyboardNoteNumber(virtualKeyboardMinimumNoteNumber - 1), filterEnvelopeAmount(0.0) { // ... }
Synthesis::Reset
:
mFilterEnvelopeGenerator.setSampleRate(GetSampleRate());
EParams
, . AttachGraphics
:
// Knobs for filter envelope // Attack knob GetParam(mFilterAttack)->InitDouble("Filter Env Attack", 0.01, 0.01, 10.0, 0.001); GetParam(mFilterAttack)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 139, 178, mFilterAttack, &smallKnobBitmap)); // Decay knob: GetParam(mFilterDecay)->InitDouble("Filter Env Decay", 0.5, 0.01, 15.0, 0.001); GetParam(mFilterDecay)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 195, 178, mFilterDecay, &smallKnobBitmap)); // Sustain knob: GetParam(mFilterSustain)->InitDouble("Filter Env Sustain", 0.1, 0.001, 1.0, 0.001); GetParam(mFilterSustain)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 251, 178, mFilterSustain, &smallKnobBitmap)); // Release knob: GetParam(mFilterRelease)->InitDouble("Filter Env Release", 1.0, 0.001, 15.0, 0.001); GetParam(mFilterRelease)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 307, 178, mFilterRelease, &smallKnobBitmap)); // Filter envelope amount knob: GetParam(mFilterEnvelopeAmount)->InitDouble("Filter Env Amount", 0.0, -1.0, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 363, 178, mFilterEnvelopeAmount, &smallKnobBitmap));
, smallKnobBitmap
. . , . Synthesis::OnParamChange
switch
:
case mFilterAttack: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK, GetParam(paramIdx)->Value()); break; case mFilterDecay: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_DECAY, GetParam(paramIdx)->Value()); break; case mFilterSustain: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_SUSTAIN, GetParam(paramIdx)->Value()); break; case mFilterRelease: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE, GetParam(paramIdx)->Value()); break; case mFilterEnvelopeAmount: filterEnvelopeAmount = GetParam(paramIdx)->Value(); break;
!
! (- C1), :
EnvelopeGenerator
. . !
.
:)
:
martin-finke.de/blog/articles/audio-plugins-013-filter
#include Filter.h ( ) :
class Filter { public: enum FilterMode { FILTER_MODE_LOWPASS = 0, FILTER_MODE_HIGHPASS, FILTER_MODE_BANDPASS, kNumFilterModes }; Filter() : cutoff(0.99), resonance(0.0), mode(FILTER_MODE_LOWPASS), buf0(0.0), buf1(0.0) { calculateFeedbackAmount(); }; double process(double inputValue); inline void setCutoff(double newCutoff) { cutoff = newCutoff; calculateFeedbackAmount(); }; inline void setResonance(double newResonance) { resonance = newResonance; calculateFeedbackAmount(); }; inline void setFilterMode(FilterMode newMode) { mode = newMode; } private: double cutoff; double resonance; FilterMode mode; double feedbackAmount; inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - cutoff); } double buf0; double buf1; };
private
cutoff
resonance
. Mode
(Lowpass, Highpass, Bandpass). feedbackAmount
, buf0
buf1
, .
( calculateFeedbackAmount()
). process
. feedbackAmount
cutoff
resonance
, calculateFeedbackAmount
.
Filter.cpp :
// By Paul Kellett // http://www.musicdsp.org/showone.php?id=29 double Filter::process(double inputValue) { buf0 += cutoff * (inputValue - buf0); buf1 += cutoff * (buf0 - buf1); switch (mode) { case FILTER_MODE_LOWPASS: return buf1; case FILTER_MODE_HIGHPASS: return inputValue - buf0; case FILTER_MODE_BANDPASS: return buf0 - buf1; default: return 0.0; } }
, ? , . « » , (. . 6 ). buf0
buf1
: . inputValue
, - buf0
( ). . 6 / 12 /. switch
, buf1
. , , buf0
, 12, 6 /, .
case
FILTER_MODE_HIGHPASS
. buf0
. inputValue
buf0
, . buf1
, .
case
FILTER_MODE_BANDPASS
, buf0 - buf1
. buf0
, buf1
. , .
: , .
buf1
. ( Infinite Impulse Response , IIR ). , . , , .
, , . , , , , . , NI Massive, « ». , , . , ( Massive ). «» , , , AAS Ultra Analog, , , , . , , , , NI Reaktor. , — , .. .
. GUI. bg.png ( “Move to trash” Xcode), :
filtermode.png knob_small.png ( , 50 50 ) bg.png ( )
ID resource.h :
// Unique IDs for each image resource. #define BG_ID 101 #define WHITE_KEY_ID 102 #define BLACK_KEY_ID 103 #define WAVEFORM_ID 104 #define KNOB_ID 105 #define KNOB_SMALL_ID 106 #define FILTERMODE_ID 107 // Image resource locations for this plug. #define BG_FN "resources/img/bg.png" #define WHITE_KEY_FN "resources/img/whitekey.png" #define BLACK_KEY_FN "resources/img/blackkey.png" #define WAVEFORM_FN "resources/img/waveform.png" #define KNOB_FN "resources/img/knob.png" #define KNOB_SMALL_FN "resources/img/knob_small.png" #define FILTERMODE_FN "resources/img/filtermode.png"
Synthesis.rc :
#include "resource.h" BG_ID PNG BG_FN WHITE_KEY_ID PNG WHITE_KEY_FN BLACK_KEY_ID PNG BLACK_KEY_FN WAVEFORM_ID PNG WAVEFORM_FN KNOB_ID PNG KNOB_FN KNOB_SMALL_ID PNG KNOB_SMALL_FN FILTERMODE_ID PNG FILTERMODE_FN
#include "Filter.h"
Synthesis.h private
:
Filter mFilter;
EParams
Synthesis.cpp :
enum EParams { mWaveform = 0, mAttack, mDecay, mSustain, mRelease, mFilterMode, mFilterCutoff, mFilterResonance, mFilterAttack, mFilterDecay, mFilterSustain, mFilterRelease, mFilterEnvelopeAmount, kNumParams };
:
pGraphics->AttachControl(new ISwitchControl(this, 24, 38, mWaveform, &waveformBitmap));
. (Lowpass, Highpass, Bandpass). AttachGraphics(pGraphics)
:
GetParam(mFilterMode)->InitEnum("Filter Mode", Filter::FILTER_MODE_LOWPASS, Filter::kNumFilterModes); IBitmap filtermodeBitmap = pGraphics->LoadIBitmap(FILTERMODE_ID, FILTERMODE_FN, 3); pGraphics->AttachControl(new ISwitchControl(this, 24, 123, mFilterMode, &filtermodeBitmap));
. . knob_small.png :
// Knobs for filter cutoff and resonance IBitmap smallKnobBitmap = pGraphics->LoadIBitmap(KNOB_SMALL_ID, KNOB_SMALL_FN, 64); // Cutoff knob: GetParam(mFilterCutoff)->InitDouble("Cutoff", 0.99, 0.01, 0.99, 0.001); GetParam(mFilterCutoff)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 5, 177, mFilterCutoff, &smallKnobBitmap)); // Resonance knob: GetParam(mFilterResonance)->InitDouble("Resonance", 0.01, 0.01, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 61, 177, mFilterResonance, &smallKnobBitmap));
, 1.0
, calculateFeedbackAmount
, .
ProcessDoubleReplacing
:
leftOutput[i] = rightOutput[i] = mFilter.process(mOscillator.nextSample() * mEnvelopeGenerator.nextSample() * velocity / 127.0);
. switch
Synthesis::OnParamChange
:
case mFilterCutoff: mFilter.setCutoff(GetParam(paramIdx)->Value()); break; case mFilterResonance: mFilter.setResonance(GetParam(paramIdx)->Value()); break; case mFilterMode: mFilter.setFilterMode(static_cast<Filter::FilterMode>(GetParam(paramIdx)->Int())); break;
. - .
— . , , .
:
buf0 += cutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1));
, , ( buf0 – buf1
), feedbackAmount
. calculateFeedbackAmount
feedbackAmount
resonance
, . . , .
, . , , . , , .
-12 / -24 /
! 24 . switch
Filter::process
:
buf2 += cutoff * (buf1 - buf2); buf3 += cutoff * (buf2 – buf3);
: , , . , , , ( ). switch
:
switch (mode) { case FILTER_MODE_LOWPASS: return buf3; case FILTER_MODE_HIGHPASS: return inputValue - buf3; case FILTER_MODE_BANDPASS: return buf0 - buf3; default: return 0.0; }
buf1
buf3
– . -24 /. buf2
buf3
, private
Filter.h :
double buf2; double buf3;
, buf0
c buf1
:
Filter() : // ... buf0(0.0), buf1(0.0), buf2(0.0), buf3(0.0) // ...
, , : . .
?
. , , .
- cutoff
. . cutoffMod
, , cutoff
. #include private
:
double cutoffMod;
:
Filter() : cutoff(0.99), resonance(0.01), cutoffMod(0.0), // ...
. private
:
inline double getCalculatedCutoff() const { return fmax(fmin(cutoff + cutoffMod, 0.99), 0.01); };
calculateFeedbackAmount
:
inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - getCalculatedCutoff()); }
cutoffMod
. feedbackAmount
, :
inline void setCutoffMod(double newCutoffMod) { cutoffMod = newCutoffMod; calculateFeedbackAmount(); }
, . Filter::process
:
if (inputValue == 0.0) return inputValue; double calculatedCutoff = getCalculatedCutoff(); buf0 += calculatedCutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1)); buf1 += calculatedCutoff * (buf0 - buf1); buf2 += calculatedCutoff * (buf1 - buf2); buf3 += calculatedCutoff * (buf2 – buf3);
, . , , . . cutoff
calculatedCutoff
-.
, ( setCutoffMod
), Synthesis
, , . , cutoffMod
. filterEnvelopeAmount
-1
+1
. GUI.
private
Synthesis.h :
EnvelopeGenerator mFilterEnvelopeGenerator; double filterEnvelopeAmount;
MIDI , onNoteOn
onNoteOff
:
inline void onNoteOn(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); }; inline void onNoteOff(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); };
. ProcessDoubleReplacing
:
mFilter.setCutoffMod(mFilterEnvelopeGenerator.nextSample() * filterEnvelopeAmount);
, nextSample
filterEnvelopeAmount
cutoffMod
. filterEnvelopeAmount
:
Synthesis::Synthesis(IPlugInstanceInfo instanceInfo) : IPLUG_CTOR(kNumParams, kNumPrograms, instanceInfo), lastVirtualKeyboardNoteNumber(virtualKeyboardMinimumNoteNumber - 1), filterEnvelopeAmount(0.0) { // ... }
Synthesis::Reset
:
mFilterEnvelopeGenerator.setSampleRate(GetSampleRate());
EParams
, . AttachGraphics
:
// Knobs for filter envelope // Attack knob GetParam(mFilterAttack)->InitDouble("Filter Env Attack", 0.01, 0.01, 10.0, 0.001); GetParam(mFilterAttack)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 139, 178, mFilterAttack, &smallKnobBitmap)); // Decay knob: GetParam(mFilterDecay)->InitDouble("Filter Env Decay", 0.5, 0.01, 15.0, 0.001); GetParam(mFilterDecay)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 195, 178, mFilterDecay, &smallKnobBitmap)); // Sustain knob: GetParam(mFilterSustain)->InitDouble("Filter Env Sustain", 0.1, 0.001, 1.0, 0.001); GetParam(mFilterSustain)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 251, 178, mFilterSustain, &smallKnobBitmap)); // Release knob: GetParam(mFilterRelease)->InitDouble("Filter Env Release", 1.0, 0.001, 15.0, 0.001); GetParam(mFilterRelease)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 307, 178, mFilterRelease, &smallKnobBitmap)); // Filter envelope amount knob: GetParam(mFilterEnvelopeAmount)->InitDouble("Filter Env Amount", 0.0, -1.0, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 363, 178, mFilterEnvelopeAmount, &smallKnobBitmap));
, smallKnobBitmap
. . , . Synthesis::OnParamChange
switch
:
case mFilterAttack: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK, GetParam(paramIdx)->Value()); break; case mFilterDecay: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_DECAY, GetParam(paramIdx)->Value()); break; case mFilterSustain: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_SUSTAIN, GetParam(paramIdx)->Value()); break; case mFilterRelease: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE, GetParam(paramIdx)->Value()); break; case mFilterEnvelopeAmount: filterEnvelopeAmount = GetParam(paramIdx)->Value(); break;
!
! (- C1), :
EnvelopeGenerator
. . !
.
:)
:
martin-finke.de/blog/articles/audio-plugins-013-filter
#include Filter.h ( ) :
class Filter { public: enum FilterMode { FILTER_MODE_LOWPASS = 0, FILTER_MODE_HIGHPASS, FILTER_MODE_BANDPASS, kNumFilterModes }; Filter() : cutoff(0.99), resonance(0.0), mode(FILTER_MODE_LOWPASS), buf0(0.0), buf1(0.0) { calculateFeedbackAmount(); }; double process(double inputValue); inline void setCutoff(double newCutoff) { cutoff = newCutoff; calculateFeedbackAmount(); }; inline void setResonance(double newResonance) { resonance = newResonance; calculateFeedbackAmount(); }; inline void setFilterMode(FilterMode newMode) { mode = newMode; } private: double cutoff; double resonance; FilterMode mode; double feedbackAmount; inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - cutoff); } double buf0; double buf1; };
private
cutoff
resonance
.
Mode
(Lowpass, Highpass, Bandpass).
feedbackAmount
,
buf0
buf1
, .
(
calculateFeedbackAmount()
).
process
.
feedbackAmount
cutoff
resonance
,
calculateFeedbackAmount
.
Filter.cpp :
// By Paul Kellett // http://www.musicdsp.org/showone.php?id=29 double Filter::process(double inputValue) { buf0 += cutoff * (inputValue - buf0); buf1 += cutoff * (buf0 - buf1); switch (mode) { case FILTER_MODE_LOWPASS: return buf1; case FILTER_MODE_HIGHPASS: return inputValue - buf0; case FILTER_MODE_BANDPASS: return buf0 - buf1; default: return 0.0; } }
, ? , . « » , (. . 6 ).
buf0
buf1
: .
inputValue
, -
buf0
( ). . 6 / 12 /.
switch
,
buf1
. , ,
buf0
, 12, 6 /, .
case
FILTER_MODE_HIGHPASS
.
buf0
.
inputValue
buf0
, .
buf1
, .
case
FILTER_MODE_BANDPASS
,
buf0 - buf1
.
buf0
,
buf1
. , .
: , .
buf1
. ( Infinite Impulse Response , IIR ). , . , , .
, , . , , , , . , NI Massive, « ». , , . , ( Massive ). «» , , , AAS Ultra Analog, , , , . , , , , NI Reaktor. , — , .. .
. GUI. bg.png ( “Move to trash” Xcode), :
filtermode.png knob_small.png ( , 50 50 ) bg.png ( )
ID resource.h :
// Unique IDs for each image resource. #define BG_ID 101 #define WHITE_KEY_ID 102 #define BLACK_KEY_ID 103 #define WAVEFORM_ID 104 #define KNOB_ID 105 #define KNOB_SMALL_ID 106 #define FILTERMODE_ID 107 // Image resource locations for this plug. #define BG_FN "resources/img/bg.png" #define WHITE_KEY_FN "resources/img/whitekey.png" #define BLACK_KEY_FN "resources/img/blackkey.png" #define WAVEFORM_FN "resources/img/waveform.png" #define KNOB_FN "resources/img/knob.png" #define KNOB_SMALL_FN "resources/img/knob_small.png" #define FILTERMODE_FN "resources/img/filtermode.png"
Synthesis.rc :
#include "resource.h" BG_ID PNG BG_FN WHITE_KEY_ID PNG WHITE_KEY_FN BLACK_KEY_ID PNG BLACK_KEY_FN WAVEFORM_ID PNG WAVEFORM_FN KNOB_ID PNG KNOB_FN KNOB_SMALL_ID PNG KNOB_SMALL_FN FILTERMODE_ID PNG FILTERMODE_FN
#include "Filter.h"
Synthesis.h
private
:
Filter mFilter;
EParams
Synthesis.cpp :
enum EParams { mWaveform = 0, mAttack, mDecay, mSustain, mRelease, mFilterMode, mFilterCutoff, mFilterResonance, mFilterAttack, mFilterDecay, mFilterSustain, mFilterRelease, mFilterEnvelopeAmount, kNumParams };
:
pGraphics->AttachControl(new ISwitchControl(this, 24, 38, mWaveform, &waveformBitmap));
. (Lowpass, Highpass, Bandpass).
AttachGraphics(pGraphics)
:
GetParam(mFilterMode)->InitEnum("Filter Mode", Filter::FILTER_MODE_LOWPASS, Filter::kNumFilterModes); IBitmap filtermodeBitmap = pGraphics->LoadIBitmap(FILTERMODE_ID, FILTERMODE_FN, 3); pGraphics->AttachControl(new ISwitchControl(this, 24, 123, mFilterMode, &filtermodeBitmap));
. . knob_small.png :
// Knobs for filter cutoff and resonance IBitmap smallKnobBitmap = pGraphics->LoadIBitmap(KNOB_SMALL_ID, KNOB_SMALL_FN, 64); // Cutoff knob: GetParam(mFilterCutoff)->InitDouble("Cutoff", 0.99, 0.01, 0.99, 0.001); GetParam(mFilterCutoff)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 5, 177, mFilterCutoff, &smallKnobBitmap)); // Resonance knob: GetParam(mFilterResonance)->InitDouble("Resonance", 0.01, 0.01, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 61, 177, mFilterResonance, &smallKnobBitmap));
,
1.0
,
calculateFeedbackAmount
, .
ProcessDoubleReplacing
:
leftOutput[i] = rightOutput[i] = mFilter.process(mOscillator.nextSample() * mEnvelopeGenerator.nextSample() * velocity / 127.0);
.
switch
Synthesis::OnParamChange
:
case mFilterCutoff: mFilter.setCutoff(GetParam(paramIdx)->Value()); break; case mFilterResonance: mFilter.setResonance(GetParam(paramIdx)->Value()); break; case mFilterMode: mFilter.setFilterMode(static_cast<Filter::FilterMode>(GetParam(paramIdx)->Int())); break;
. - .
— . , , .
:
buf0 += cutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1));
, , (
buf0 – buf1
),
feedbackAmount
.
calculateFeedbackAmount
feedbackAmount
resonance
, . . , .
, . , , . , , .
-12 / -24 /
! 24 .
switch
Filter::process
:
buf2 += cutoff * (buf1 - buf2); buf3 += cutoff * (buf2 – buf3);
: , , . , , , ( ).
switch
:
switch (mode) { case FILTER_MODE_LOWPASS: return buf3; case FILTER_MODE_HIGHPASS: return inputValue - buf3; case FILTER_MODE_BANDPASS: return buf0 - buf3; default: return 0.0; }
buf1
buf3
– . -24 /.
buf2
buf3
,
private
Filter.h :
double buf2; double buf3;
,
buf0
c
buf1
:
Filter() : // ... buf0(0.0), buf1(0.0), buf2(0.0), buf3(0.0) // ...
, , : . .
?
. , , .
-
cutoff
. .
cutoffMod
, ,
cutoff
.
#include private
:
double cutoffMod;
:
Filter() : cutoff(0.99), resonance(0.01), cutoffMod(0.0), // ...
. private
:
inline double getCalculatedCutoff() const { return fmax(fmin(cutoff + cutoffMod, 0.99), 0.01); };
calculateFeedbackAmount
:
inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - getCalculatedCutoff()); }
cutoffMod
. feedbackAmount
, :
inline void setCutoffMod(double newCutoffMod) { cutoffMod = newCutoffMod; calculateFeedbackAmount(); }
, . Filter::process
:
if (inputValue == 0.0) return inputValue; double calculatedCutoff = getCalculatedCutoff(); buf0 += calculatedCutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1)); buf1 += calculatedCutoff * (buf0 - buf1); buf2 += calculatedCutoff * (buf1 - buf2); buf3 += calculatedCutoff * (buf2 – buf3);
, . , , . . cutoff
calculatedCutoff
-.
, ( setCutoffMod
), Synthesis
, , . , cutoffMod
. filterEnvelopeAmount
-1
+1
. GUI.
private
Synthesis.h :
EnvelopeGenerator mFilterEnvelopeGenerator; double filterEnvelopeAmount;
MIDI , onNoteOn
onNoteOff
:
inline void onNoteOn(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); }; inline void onNoteOff(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); };
. ProcessDoubleReplacing
:
mFilter.setCutoffMod(mFilterEnvelopeGenerator.nextSample() * filterEnvelopeAmount);
, nextSample
filterEnvelopeAmount
cutoffMod
. filterEnvelopeAmount
:
Synthesis::Synthesis(IPlugInstanceInfo instanceInfo) : IPLUG_CTOR(kNumParams, kNumPrograms, instanceInfo), lastVirtualKeyboardNoteNumber(virtualKeyboardMinimumNoteNumber - 1), filterEnvelopeAmount(0.0) { // ... }
Synthesis::Reset
:
mFilterEnvelopeGenerator.setSampleRate(GetSampleRate());
EParams
, . AttachGraphics
:
// Knobs for filter envelope // Attack knob GetParam(mFilterAttack)->InitDouble("Filter Env Attack", 0.01, 0.01, 10.0, 0.001); GetParam(mFilterAttack)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 139, 178, mFilterAttack, &smallKnobBitmap)); // Decay knob: GetParam(mFilterDecay)->InitDouble("Filter Env Decay", 0.5, 0.01, 15.0, 0.001); GetParam(mFilterDecay)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 195, 178, mFilterDecay, &smallKnobBitmap)); // Sustain knob: GetParam(mFilterSustain)->InitDouble("Filter Env Sustain", 0.1, 0.001, 1.0, 0.001); GetParam(mFilterSustain)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 251, 178, mFilterSustain, &smallKnobBitmap)); // Release knob: GetParam(mFilterRelease)->InitDouble("Filter Env Release", 1.0, 0.001, 15.0, 0.001); GetParam(mFilterRelease)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 307, 178, mFilterRelease, &smallKnobBitmap)); // Filter envelope amount knob: GetParam(mFilterEnvelopeAmount)->InitDouble("Filter Env Amount", 0.0, -1.0, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 363, 178, mFilterEnvelopeAmount, &smallKnobBitmap));
, smallKnobBitmap
. . , . Synthesis::OnParamChange
switch
:
case mFilterAttack: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK, GetParam(paramIdx)->Value()); break; case mFilterDecay: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_DECAY, GetParam(paramIdx)->Value()); break; case mFilterSustain: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_SUSTAIN, GetParam(paramIdx)->Value()); break; case mFilterRelease: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE, GetParam(paramIdx)->Value()); break; case mFilterEnvelopeAmount: filterEnvelopeAmount = GetParam(paramIdx)->Value(); break;
!
! (- C1), :
EnvelopeGenerator
. . !
.
:)
:
martin-finke.de/blog/articles/audio-plugins-013-filter
#include Filter.h ( ) :
class Filter { public: enum FilterMode { FILTER_MODE_LOWPASS = 0, FILTER_MODE_HIGHPASS, FILTER_MODE_BANDPASS, kNumFilterModes }; Filter() : cutoff(0.99), resonance(0.0), mode(FILTER_MODE_LOWPASS), buf0(0.0), buf1(0.0) { calculateFeedbackAmount(); }; double process(double inputValue); inline void setCutoff(double newCutoff) { cutoff = newCutoff; calculateFeedbackAmount(); }; inline void setResonance(double newResonance) { resonance = newResonance; calculateFeedbackAmount(); }; inline void setFilterMode(FilterMode newMode) { mode = newMode; } private: double cutoff; double resonance; FilterMode mode; double feedbackAmount; inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - cutoff); } double buf0; double buf1; };
private
cutoff
resonance
. Mode
(Lowpass, Highpass, Bandpass). feedbackAmount
, buf0
buf1
, .
( calculateFeedbackAmount()
). process
. feedbackAmount
cutoff
resonance
, calculateFeedbackAmount
.
Filter.cpp :
// By Paul Kellett // http://www.musicdsp.org/showone.php?id=29 double Filter::process(double inputValue) { buf0 += cutoff * (inputValue - buf0); buf1 += cutoff * (buf0 - buf1); switch (mode) { case FILTER_MODE_LOWPASS: return buf1; case FILTER_MODE_HIGHPASS: return inputValue - buf0; case FILTER_MODE_BANDPASS: return buf0 - buf1; default: return 0.0; } }
, ? , . « » , (. . 6 ). buf0
buf1
: . inputValue
, - buf0
( ). . 6 / 12 /. switch
, buf1
. , , buf0
, 12, 6 /, .
case
FILTER_MODE_HIGHPASS
. buf0
. inputValue
buf0
, . buf1
, .
case
FILTER_MODE_BANDPASS
, buf0 - buf1
. buf0
, buf1
. , .
: , .
buf1
. ( Infinite Impulse Response , IIR ). , . , , .
, , . , , , , . , NI Massive, « ». , , . , ( Massive ). «» , , , AAS Ultra Analog, , , , . , , , , NI Reaktor. , — , .. .
. GUI. bg.png ( “Move to trash” Xcode), :
filtermode.png knob_small.png ( , 50 50 ) bg.png ( )
ID resource.h :
// Unique IDs for each image resource. #define BG_ID 101 #define WHITE_KEY_ID 102 #define BLACK_KEY_ID 103 #define WAVEFORM_ID 104 #define KNOB_ID 105 #define KNOB_SMALL_ID 106 #define FILTERMODE_ID 107 // Image resource locations for this plug. #define BG_FN "resources/img/bg.png" #define WHITE_KEY_FN "resources/img/whitekey.png" #define BLACK_KEY_FN "resources/img/blackkey.png" #define WAVEFORM_FN "resources/img/waveform.png" #define KNOB_FN "resources/img/knob.png" #define KNOB_SMALL_FN "resources/img/knob_small.png" #define FILTERMODE_FN "resources/img/filtermode.png"
Synthesis.rc :
#include "resource.h" BG_ID PNG BG_FN WHITE_KEY_ID PNG WHITE_KEY_FN BLACK_KEY_ID PNG BLACK_KEY_FN WAVEFORM_ID PNG WAVEFORM_FN KNOB_ID PNG KNOB_FN KNOB_SMALL_ID PNG KNOB_SMALL_FN FILTERMODE_ID PNG FILTERMODE_FN
#include "Filter.h"
Synthesis.h private
:
Filter mFilter;
EParams
Synthesis.cpp :
enum EParams { mWaveform = 0, mAttack, mDecay, mSustain, mRelease, mFilterMode, mFilterCutoff, mFilterResonance, mFilterAttack, mFilterDecay, mFilterSustain, mFilterRelease, mFilterEnvelopeAmount, kNumParams };
:
pGraphics->AttachControl(new ISwitchControl(this, 24, 38, mWaveform, &waveformBitmap));
. (Lowpass, Highpass, Bandpass). AttachGraphics(pGraphics)
:
GetParam(mFilterMode)->InitEnum("Filter Mode", Filter::FILTER_MODE_LOWPASS, Filter::kNumFilterModes); IBitmap filtermodeBitmap = pGraphics->LoadIBitmap(FILTERMODE_ID, FILTERMODE_FN, 3); pGraphics->AttachControl(new ISwitchControl(this, 24, 123, mFilterMode, &filtermodeBitmap));
. . knob_small.png :
// Knobs for filter cutoff and resonance IBitmap smallKnobBitmap = pGraphics->LoadIBitmap(KNOB_SMALL_ID, KNOB_SMALL_FN, 64); // Cutoff knob: GetParam(mFilterCutoff)->InitDouble("Cutoff", 0.99, 0.01, 0.99, 0.001); GetParam(mFilterCutoff)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 5, 177, mFilterCutoff, &smallKnobBitmap)); // Resonance knob: GetParam(mFilterResonance)->InitDouble("Resonance", 0.01, 0.01, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 61, 177, mFilterResonance, &smallKnobBitmap));
, 1.0
, calculateFeedbackAmount
, .
ProcessDoubleReplacing
:
leftOutput[i] = rightOutput[i] = mFilter.process(mOscillator.nextSample() * mEnvelopeGenerator.nextSample() * velocity / 127.0);
. switch
Synthesis::OnParamChange
:
case mFilterCutoff: mFilter.setCutoff(GetParam(paramIdx)->Value()); break; case mFilterResonance: mFilter.setResonance(GetParam(paramIdx)->Value()); break; case mFilterMode: mFilter.setFilterMode(static_cast<Filter::FilterMode>(GetParam(paramIdx)->Int())); break;
. - .
— . , , .
:
buf0 += cutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1));
, , ( buf0 – buf1
), feedbackAmount
. calculateFeedbackAmount
feedbackAmount
resonance
, . . , .
, . , , . , , .
-12 / -24 /
! 24 . switch
Filter::process
:
buf2 += cutoff * (buf1 - buf2); buf3 += cutoff * (buf2 – buf3);
: , , . , , , ( ). switch
:
switch (mode) { case FILTER_MODE_LOWPASS: return buf3; case FILTER_MODE_HIGHPASS: return inputValue - buf3; case FILTER_MODE_BANDPASS: return buf0 - buf3; default: return 0.0; }
buf1
buf3
– . -24 /. buf2
buf3
, private
Filter.h :
double buf2; double buf3;
, buf0
c buf1
:
Filter() : // ... buf0(0.0), buf1(0.0), buf2(0.0), buf3(0.0) // ...
, , : . .
?
. , , .
- cutoff
. . cutoffMod
, , cutoff
. #include private
:
double cutoffMod;
:
Filter() : cutoff(0.99), resonance(0.01), cutoffMod(0.0), // ...
. private
:
inline double getCalculatedCutoff() const { return fmax(fmin(cutoff + cutoffMod, 0.99), 0.01); };
calculateFeedbackAmount
:
inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - getCalculatedCutoff()); }
cutoffMod
. feedbackAmount
, :
inline void setCutoffMod(double newCutoffMod) { cutoffMod = newCutoffMod; calculateFeedbackAmount(); }
, . Filter::process
:
if (inputValue == 0.0) return inputValue; double calculatedCutoff = getCalculatedCutoff(); buf0 += calculatedCutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1)); buf1 += calculatedCutoff * (buf0 - buf1); buf2 += calculatedCutoff * (buf1 - buf2); buf3 += calculatedCutoff * (buf2 – buf3);
, . , , . . cutoff
calculatedCutoff
-.
, ( setCutoffMod
), Synthesis
, , . , cutoffMod
. filterEnvelopeAmount
-1
+1
. GUI.
private
Synthesis.h :
EnvelopeGenerator mFilterEnvelopeGenerator; double filterEnvelopeAmount;
MIDI , onNoteOn
onNoteOff
:
inline void onNoteOn(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); }; inline void onNoteOff(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); };
. ProcessDoubleReplacing
:
mFilter.setCutoffMod(mFilterEnvelopeGenerator.nextSample() * filterEnvelopeAmount);
, nextSample
filterEnvelopeAmount
cutoffMod
. filterEnvelopeAmount
:
Synthesis::Synthesis(IPlugInstanceInfo instanceInfo) : IPLUG_CTOR(kNumParams, kNumPrograms, instanceInfo), lastVirtualKeyboardNoteNumber(virtualKeyboardMinimumNoteNumber - 1), filterEnvelopeAmount(0.0) { // ... }
Synthesis::Reset
:
mFilterEnvelopeGenerator.setSampleRate(GetSampleRate());
EParams
, . AttachGraphics
:
// Knobs for filter envelope // Attack knob GetParam(mFilterAttack)->InitDouble("Filter Env Attack", 0.01, 0.01, 10.0, 0.001); GetParam(mFilterAttack)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 139, 178, mFilterAttack, &smallKnobBitmap)); // Decay knob: GetParam(mFilterDecay)->InitDouble("Filter Env Decay", 0.5, 0.01, 15.0, 0.001); GetParam(mFilterDecay)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 195, 178, mFilterDecay, &smallKnobBitmap)); // Sustain knob: GetParam(mFilterSustain)->InitDouble("Filter Env Sustain", 0.1, 0.001, 1.0, 0.001); GetParam(mFilterSustain)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 251, 178, mFilterSustain, &smallKnobBitmap)); // Release knob: GetParam(mFilterRelease)->InitDouble("Filter Env Release", 1.0, 0.001, 15.0, 0.001); GetParam(mFilterRelease)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 307, 178, mFilterRelease, &smallKnobBitmap)); // Filter envelope amount knob: GetParam(mFilterEnvelopeAmount)->InitDouble("Filter Env Amount", 0.0, -1.0, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 363, 178, mFilterEnvelopeAmount, &smallKnobBitmap));
, smallKnobBitmap
. . , . Synthesis::OnParamChange
switch
:
case mFilterAttack: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK, GetParam(paramIdx)->Value()); break; case mFilterDecay: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_DECAY, GetParam(paramIdx)->Value()); break; case mFilterSustain: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_SUSTAIN, GetParam(paramIdx)->Value()); break; case mFilterRelease: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE, GetParam(paramIdx)->Value()); break; case mFilterEnvelopeAmount: filterEnvelopeAmount = GetParam(paramIdx)->Value(); break;
!
! (- C1), :
EnvelopeGenerator
. . !
.
:)
:
martin-finke.de/blog/articles/audio-plugins-013-filter
#include Filter.h ( ) :
class Filter { public: enum FilterMode { FILTER_MODE_LOWPASS = 0, FILTER_MODE_HIGHPASS, FILTER_MODE_BANDPASS, kNumFilterModes }; Filter() : cutoff(0.99), resonance(0.0), mode(FILTER_MODE_LOWPASS), buf0(0.0), buf1(0.0) { calculateFeedbackAmount(); }; double process(double inputValue); inline void setCutoff(double newCutoff) { cutoff = newCutoff; calculateFeedbackAmount(); }; inline void setResonance(double newResonance) { resonance = newResonance; calculateFeedbackAmount(); }; inline void setFilterMode(FilterMode newMode) { mode = newMode; } private: double cutoff; double resonance; FilterMode mode; double feedbackAmount; inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - cutoff); } double buf0; double buf1; };
private
cutoff
resonance
. Mode
(Lowpass, Highpass, Bandpass). feedbackAmount
, buf0
buf1
, .
( calculateFeedbackAmount()
). process
. feedbackAmount
cutoff
resonance
, calculateFeedbackAmount
.
Filter.cpp :
// By Paul Kellett // http://www.musicdsp.org/showone.php?id=29 double Filter::process(double inputValue) { buf0 += cutoff * (inputValue - buf0); buf1 += cutoff * (buf0 - buf1); switch (mode) { case FILTER_MODE_LOWPASS: return buf1; case FILTER_MODE_HIGHPASS: return inputValue - buf0; case FILTER_MODE_BANDPASS: return buf0 - buf1; default: return 0.0; } }
, ? , . « » , (. . 6 ). buf0
buf1
: . inputValue
, - buf0
( ). . 6 / 12 /. switch
, buf1
. , , buf0
, 12, 6 /, .
case
FILTER_MODE_HIGHPASS
. buf0
. inputValue
buf0
, . buf1
, .
case
FILTER_MODE_BANDPASS
, buf0 - buf1
. buf0
, buf1
. , .
: , .
buf1
. ( Infinite Impulse Response , IIR ). , . , , .
, , . , , , , . , NI Massive, « ». , , . , ( Massive ). «» , , , AAS Ultra Analog, , , , . , , , , NI Reaktor. , — , .. .
. GUI. bg.png ( “Move to trash” Xcode), :
filtermode.png knob_small.png ( , 50 50 ) bg.png ( )
ID resource.h :
// Unique IDs for each image resource. #define BG_ID 101 #define WHITE_KEY_ID 102 #define BLACK_KEY_ID 103 #define WAVEFORM_ID 104 #define KNOB_ID 105 #define KNOB_SMALL_ID 106 #define FILTERMODE_ID 107 // Image resource locations for this plug. #define BG_FN "resources/img/bg.png" #define WHITE_KEY_FN "resources/img/whitekey.png" #define BLACK_KEY_FN "resources/img/blackkey.png" #define WAVEFORM_FN "resources/img/waveform.png" #define KNOB_FN "resources/img/knob.png" #define KNOB_SMALL_FN "resources/img/knob_small.png" #define FILTERMODE_FN "resources/img/filtermode.png"
Synthesis.rc :
#include "resource.h" BG_ID PNG BG_FN WHITE_KEY_ID PNG WHITE_KEY_FN BLACK_KEY_ID PNG BLACK_KEY_FN WAVEFORM_ID PNG WAVEFORM_FN KNOB_ID PNG KNOB_FN KNOB_SMALL_ID PNG KNOB_SMALL_FN FILTERMODE_ID PNG FILTERMODE_FN
#include "Filter.h"
Synthesis.h private
:
Filter mFilter;
EParams
Synthesis.cpp :
enum EParams { mWaveform = 0, mAttack, mDecay, mSustain, mRelease, mFilterMode, mFilterCutoff, mFilterResonance, mFilterAttack, mFilterDecay, mFilterSustain, mFilterRelease, mFilterEnvelopeAmount, kNumParams };
:
pGraphics->AttachControl(new ISwitchControl(this, 24, 38, mWaveform, &waveformBitmap));
. (Lowpass, Highpass, Bandpass). AttachGraphics(pGraphics)
:
GetParam(mFilterMode)->InitEnum("Filter Mode", Filter::FILTER_MODE_LOWPASS, Filter::kNumFilterModes); IBitmap filtermodeBitmap = pGraphics->LoadIBitmap(FILTERMODE_ID, FILTERMODE_FN, 3); pGraphics->AttachControl(new ISwitchControl(this, 24, 123, mFilterMode, &filtermodeBitmap));
. . knob_small.png :
// Knobs for filter cutoff and resonance IBitmap smallKnobBitmap = pGraphics->LoadIBitmap(KNOB_SMALL_ID, KNOB_SMALL_FN, 64); // Cutoff knob: GetParam(mFilterCutoff)->InitDouble("Cutoff", 0.99, 0.01, 0.99, 0.001); GetParam(mFilterCutoff)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 5, 177, mFilterCutoff, &smallKnobBitmap)); // Resonance knob: GetParam(mFilterResonance)->InitDouble("Resonance", 0.01, 0.01, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 61, 177, mFilterResonance, &smallKnobBitmap));
, 1.0
, calculateFeedbackAmount
, .
ProcessDoubleReplacing
:
leftOutput[i] = rightOutput[i] = mFilter.process(mOscillator.nextSample() * mEnvelopeGenerator.nextSample() * velocity / 127.0);
. switch
Synthesis::OnParamChange
:
case mFilterCutoff: mFilter.setCutoff(GetParam(paramIdx)->Value()); break; case mFilterResonance: mFilter.setResonance(GetParam(paramIdx)->Value()); break; case mFilterMode: mFilter.setFilterMode(static_cast<Filter::FilterMode>(GetParam(paramIdx)->Int())); break;
. - .
— . , , .
:
buf0 += cutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1));
, , ( buf0 – buf1
), feedbackAmount
. calculateFeedbackAmount
feedbackAmount
resonance
, . . , .
, . , , . , , .
-12 / -24 /
! 24 . switch
Filter::process
:
buf2 += cutoff * (buf1 - buf2); buf3 += cutoff * (buf2 – buf3);
: , , . , , , ( ). switch
:
switch (mode) { case FILTER_MODE_LOWPASS: return buf3; case FILTER_MODE_HIGHPASS: return inputValue - buf3; case FILTER_MODE_BANDPASS: return buf0 - buf3; default: return 0.0; }
buf1
buf3
– . -24 /. buf2
buf3
, private
Filter.h :
double buf2; double buf3;
, buf0
c buf1
:
Filter() : // ... buf0(0.0), buf1(0.0), buf2(0.0), buf3(0.0) // ...
, , : . .
?
. , , .
- cutoff
. . cutoffMod
, , cutoff
. #include private
:
double cutoffMod;
:
Filter() : cutoff(0.99), resonance(0.01), cutoffMod(0.0), // ...
. private
:
inline double getCalculatedCutoff() const { return fmax(fmin(cutoff + cutoffMod, 0.99), 0.01); };
calculateFeedbackAmount
:
inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - getCalculatedCutoff()); }
cutoffMod
. feedbackAmount
, :
inline void setCutoffMod(double newCutoffMod) { cutoffMod = newCutoffMod; calculateFeedbackAmount(); }
, . Filter::process
:
if (inputValue == 0.0) return inputValue; double calculatedCutoff = getCalculatedCutoff(); buf0 += calculatedCutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1)); buf1 += calculatedCutoff * (buf0 - buf1); buf2 += calculatedCutoff * (buf1 - buf2); buf3 += calculatedCutoff * (buf2 – buf3);
, . , , . . cutoff
calculatedCutoff
-.
, ( setCutoffMod
), Synthesis
, , . , cutoffMod
. filterEnvelopeAmount
-1
+1
. GUI.
private
Synthesis.h :
EnvelopeGenerator mFilterEnvelopeGenerator; double filterEnvelopeAmount;
MIDI , onNoteOn
onNoteOff
:
inline void onNoteOn(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); }; inline void onNoteOff(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); };
. ProcessDoubleReplacing
:
mFilter.setCutoffMod(mFilterEnvelopeGenerator.nextSample() * filterEnvelopeAmount);
, nextSample
filterEnvelopeAmount
cutoffMod
. filterEnvelopeAmount
:
Synthesis::Synthesis(IPlugInstanceInfo instanceInfo) : IPLUG_CTOR(kNumParams, kNumPrograms, instanceInfo), lastVirtualKeyboardNoteNumber(virtualKeyboardMinimumNoteNumber - 1), filterEnvelopeAmount(0.0) { // ... }
Synthesis::Reset
:
mFilterEnvelopeGenerator.setSampleRate(GetSampleRate());
EParams
, . AttachGraphics
:
// Knobs for filter envelope // Attack knob GetParam(mFilterAttack)->InitDouble("Filter Env Attack", 0.01, 0.01, 10.0, 0.001); GetParam(mFilterAttack)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 139, 178, mFilterAttack, &smallKnobBitmap)); // Decay knob: GetParam(mFilterDecay)->InitDouble("Filter Env Decay", 0.5, 0.01, 15.0, 0.001); GetParam(mFilterDecay)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 195, 178, mFilterDecay, &smallKnobBitmap)); // Sustain knob: GetParam(mFilterSustain)->InitDouble("Filter Env Sustain", 0.1, 0.001, 1.0, 0.001); GetParam(mFilterSustain)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 251, 178, mFilterSustain, &smallKnobBitmap)); // Release knob: GetParam(mFilterRelease)->InitDouble("Filter Env Release", 1.0, 0.001, 15.0, 0.001); GetParam(mFilterRelease)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 307, 178, mFilterRelease, &smallKnobBitmap)); // Filter envelope amount knob: GetParam(mFilterEnvelopeAmount)->InitDouble("Filter Env Amount", 0.0, -1.0, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 363, 178, mFilterEnvelopeAmount, &smallKnobBitmap));
, smallKnobBitmap
. . , . Synthesis::OnParamChange
switch
:
case mFilterAttack: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK, GetParam(paramIdx)->Value()); break; case mFilterDecay: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_DECAY, GetParam(paramIdx)->Value()); break; case mFilterSustain: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_SUSTAIN, GetParam(paramIdx)->Value()); break; case mFilterRelease: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE, GetParam(paramIdx)->Value()); break; case mFilterEnvelopeAmount: filterEnvelopeAmount = GetParam(paramIdx)->Value(); break;
!
! (- C1), :
EnvelopeGenerator
. . !
.
:)
:
martin-finke.de/blog/articles/audio-plugins-013-filter
#include Filter.h ( ) :
class Filter { public: enum FilterMode { FILTER_MODE_LOWPASS = 0, FILTER_MODE_HIGHPASS, FILTER_MODE_BANDPASS, kNumFilterModes }; Filter() : cutoff(0.99), resonance(0.0), mode(FILTER_MODE_LOWPASS), buf0(0.0), buf1(0.0) { calculateFeedbackAmount(); }; double process(double inputValue); inline void setCutoff(double newCutoff) { cutoff = newCutoff; calculateFeedbackAmount(); }; inline void setResonance(double newResonance) { resonance = newResonance; calculateFeedbackAmount(); }; inline void setFilterMode(FilterMode newMode) { mode = newMode; } private: double cutoff; double resonance; FilterMode mode; double feedbackAmount; inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - cutoff); } double buf0; double buf1; };
private
cutoff
resonance
. Mode
(Lowpass, Highpass, Bandpass). feedbackAmount
, buf0
buf1
, .
( calculateFeedbackAmount()
). process
. feedbackAmount
cutoff
resonance
, calculateFeedbackAmount
.
Filter.cpp :
// By Paul Kellett // http://www.musicdsp.org/showone.php?id=29 double Filter::process(double inputValue) { buf0 += cutoff * (inputValue - buf0); buf1 += cutoff * (buf0 - buf1); switch (mode) { case FILTER_MODE_LOWPASS: return buf1; case FILTER_MODE_HIGHPASS: return inputValue - buf0; case FILTER_MODE_BANDPASS: return buf0 - buf1; default: return 0.0; } }
, ? , . « » , (. . 6 ). buf0
buf1
: . inputValue
, - buf0
( ). . 6 / 12 /. switch
, buf1
. , , buf0
, 12, 6 /, .
case
FILTER_MODE_HIGHPASS
. buf0
. inputValue
buf0
, . buf1
, .
case
FILTER_MODE_BANDPASS
, buf0 - buf1
. buf0
, buf1
. , .
: , .
buf1
. ( Infinite Impulse Response , IIR ). , . , , .
, , . , , , , . , NI Massive, « ». , , . , ( Massive ). «» , , , AAS Ultra Analog, , , , . , , , , NI Reaktor. , — , .. .
. GUI. bg.png ( “Move to trash” Xcode), :
filtermode.png knob_small.png ( , 50 50 ) bg.png ( )
ID resource.h :
// Unique IDs for each image resource. #define BG_ID 101 #define WHITE_KEY_ID 102 #define BLACK_KEY_ID 103 #define WAVEFORM_ID 104 #define KNOB_ID 105 #define KNOB_SMALL_ID 106 #define FILTERMODE_ID 107 // Image resource locations for this plug. #define BG_FN "resources/img/bg.png" #define WHITE_KEY_FN "resources/img/whitekey.png" #define BLACK_KEY_FN "resources/img/blackkey.png" #define WAVEFORM_FN "resources/img/waveform.png" #define KNOB_FN "resources/img/knob.png" #define KNOB_SMALL_FN "resources/img/knob_small.png" #define FILTERMODE_FN "resources/img/filtermode.png"
Synthesis.rc :
#include "resource.h" BG_ID PNG BG_FN WHITE_KEY_ID PNG WHITE_KEY_FN BLACK_KEY_ID PNG BLACK_KEY_FN WAVEFORM_ID PNG WAVEFORM_FN KNOB_ID PNG KNOB_FN KNOB_SMALL_ID PNG KNOB_SMALL_FN FILTERMODE_ID PNG FILTERMODE_FN
#include "Filter.h"
Synthesis.h private
:
Filter mFilter;
EParams
Synthesis.cpp :
enum EParams { mWaveform = 0, mAttack, mDecay, mSustain, mRelease, mFilterMode, mFilterCutoff, mFilterResonance, mFilterAttack, mFilterDecay, mFilterSustain, mFilterRelease, mFilterEnvelopeAmount, kNumParams };
:
pGraphics->AttachControl(new ISwitchControl(this, 24, 38, mWaveform, &waveformBitmap));
. (Lowpass, Highpass, Bandpass). AttachGraphics(pGraphics)
:
GetParam(mFilterMode)->InitEnum("Filter Mode", Filter::FILTER_MODE_LOWPASS, Filter::kNumFilterModes); IBitmap filtermodeBitmap = pGraphics->LoadIBitmap(FILTERMODE_ID, FILTERMODE_FN, 3); pGraphics->AttachControl(new ISwitchControl(this, 24, 123, mFilterMode, &filtermodeBitmap));
. . knob_small.png :
// Knobs for filter cutoff and resonance IBitmap smallKnobBitmap = pGraphics->LoadIBitmap(KNOB_SMALL_ID, KNOB_SMALL_FN, 64); // Cutoff knob: GetParam(mFilterCutoff)->InitDouble("Cutoff", 0.99, 0.01, 0.99, 0.001); GetParam(mFilterCutoff)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 5, 177, mFilterCutoff, &smallKnobBitmap)); // Resonance knob: GetParam(mFilterResonance)->InitDouble("Resonance", 0.01, 0.01, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 61, 177, mFilterResonance, &smallKnobBitmap));
, 1.0
, calculateFeedbackAmount
, .
ProcessDoubleReplacing
:
leftOutput[i] = rightOutput[i] = mFilter.process(mOscillator.nextSample() * mEnvelopeGenerator.nextSample() * velocity / 127.0);
. switch
Synthesis::OnParamChange
:
case mFilterCutoff: mFilter.setCutoff(GetParam(paramIdx)->Value()); break; case mFilterResonance: mFilter.setResonance(GetParam(paramIdx)->Value()); break; case mFilterMode: mFilter.setFilterMode(static_cast<Filter::FilterMode>(GetParam(paramIdx)->Int())); break;
. - .
— . , , .
:
buf0 += cutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1));
, , ( buf0 – buf1
), feedbackAmount
. calculateFeedbackAmount
feedbackAmount
resonance
, . . , .
, . , , . , , .
-12 / -24 /
! 24 . switch
Filter::process
:
buf2 += cutoff * (buf1 - buf2); buf3 += cutoff * (buf2 – buf3);
: , , . , , , ( ). switch
:
switch (mode) { case FILTER_MODE_LOWPASS: return buf3; case FILTER_MODE_HIGHPASS: return inputValue - buf3; case FILTER_MODE_BANDPASS: return buf0 - buf3; default: return 0.0; }
buf1
buf3
– . -24 /. buf2
buf3
, private
Filter.h :
double buf2; double buf3;
, buf0
c buf1
:
Filter() : // ... buf0(0.0), buf1(0.0), buf2(0.0), buf3(0.0) // ...
, , : . .
?
. , , .
- cutoff
. . cutoffMod
, , cutoff
. #include private
:
double cutoffMod;
:
Filter() : cutoff(0.99), resonance(0.01), cutoffMod(0.0), // ...
. private
:
inline double getCalculatedCutoff() const { return fmax(fmin(cutoff + cutoffMod, 0.99), 0.01); };
calculateFeedbackAmount
:
inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - getCalculatedCutoff()); }
cutoffMod
. feedbackAmount
, :
inline void setCutoffMod(double newCutoffMod) { cutoffMod = newCutoffMod; calculateFeedbackAmount(); }
, . Filter::process
:
if (inputValue == 0.0) return inputValue; double calculatedCutoff = getCalculatedCutoff(); buf0 += calculatedCutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1)); buf1 += calculatedCutoff * (buf0 - buf1); buf2 += calculatedCutoff * (buf1 - buf2); buf3 += calculatedCutoff * (buf2 – buf3);
, . , , . . cutoff
calculatedCutoff
-.
, ( setCutoffMod
), Synthesis
, , . , cutoffMod
. filterEnvelopeAmount
-1
+1
. GUI.
private
Synthesis.h :
EnvelopeGenerator mFilterEnvelopeGenerator; double filterEnvelopeAmount;
MIDI , onNoteOn
onNoteOff
:
inline void onNoteOn(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); }; inline void onNoteOff(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); };
. ProcessDoubleReplacing
:
mFilter.setCutoffMod(mFilterEnvelopeGenerator.nextSample() * filterEnvelopeAmount);
, nextSample
filterEnvelopeAmount
cutoffMod
. filterEnvelopeAmount
:
Synthesis::Synthesis(IPlugInstanceInfo instanceInfo) : IPLUG_CTOR(kNumParams, kNumPrograms, instanceInfo), lastVirtualKeyboardNoteNumber(virtualKeyboardMinimumNoteNumber - 1), filterEnvelopeAmount(0.0) { // ... }
Synthesis::Reset
:
mFilterEnvelopeGenerator.setSampleRate(GetSampleRate());
EParams
, . AttachGraphics
:
// Knobs for filter envelope // Attack knob GetParam(mFilterAttack)->InitDouble("Filter Env Attack", 0.01, 0.01, 10.0, 0.001); GetParam(mFilterAttack)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 139, 178, mFilterAttack, &smallKnobBitmap)); // Decay knob: GetParam(mFilterDecay)->InitDouble("Filter Env Decay", 0.5, 0.01, 15.0, 0.001); GetParam(mFilterDecay)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 195, 178, mFilterDecay, &smallKnobBitmap)); // Sustain knob: GetParam(mFilterSustain)->InitDouble("Filter Env Sustain", 0.1, 0.001, 1.0, 0.001); GetParam(mFilterSustain)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 251, 178, mFilterSustain, &smallKnobBitmap)); // Release knob: GetParam(mFilterRelease)->InitDouble("Filter Env Release", 1.0, 0.001, 15.0, 0.001); GetParam(mFilterRelease)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 307, 178, mFilterRelease, &smallKnobBitmap)); // Filter envelope amount knob: GetParam(mFilterEnvelopeAmount)->InitDouble("Filter Env Amount", 0.0, -1.0, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 363, 178, mFilterEnvelopeAmount, &smallKnobBitmap));
, smallKnobBitmap
. . , . Synthesis::OnParamChange
switch
:
case mFilterAttack: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK, GetParam(paramIdx)->Value()); break; case mFilterDecay: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_DECAY, GetParam(paramIdx)->Value()); break; case mFilterSustain: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_SUSTAIN, GetParam(paramIdx)->Value()); break; case mFilterRelease: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE, GetParam(paramIdx)->Value()); break; case mFilterEnvelopeAmount: filterEnvelopeAmount = GetParam(paramIdx)->Value(); break;
!
! (- C1), :
EnvelopeGenerator
. . !
.
:)
:
martin-finke.de/blog/articles/audio-plugins-013-filter
#include Filter.h ( ) :
class Filter { public: enum FilterMode { FILTER_MODE_LOWPASS = 0, FILTER_MODE_HIGHPASS, FILTER_MODE_BANDPASS, kNumFilterModes }; Filter() : cutoff(0.99), resonance(0.0), mode(FILTER_MODE_LOWPASS), buf0(0.0), buf1(0.0) { calculateFeedbackAmount(); }; double process(double inputValue); inline void setCutoff(double newCutoff) { cutoff = newCutoff; calculateFeedbackAmount(); }; inline void setResonance(double newResonance) { resonance = newResonance; calculateFeedbackAmount(); }; inline void setFilterMode(FilterMode newMode) { mode = newMode; } private: double cutoff; double resonance; FilterMode mode; double feedbackAmount; inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - cutoff); } double buf0; double buf1; };
private
cutoff
resonance
. Mode
(Lowpass, Highpass, Bandpass). feedbackAmount
, buf0
buf1
, .
( calculateFeedbackAmount()
). process
. feedbackAmount
cutoff
resonance
, calculateFeedbackAmount
.
Filter.cpp :
// By Paul Kellett // http://www.musicdsp.org/showone.php?id=29 double Filter::process(double inputValue) { buf0 += cutoff * (inputValue - buf0); buf1 += cutoff * (buf0 - buf1); switch (mode) { case FILTER_MODE_LOWPASS: return buf1; case FILTER_MODE_HIGHPASS: return inputValue - buf0; case FILTER_MODE_BANDPASS: return buf0 - buf1; default: return 0.0; } }
, ? , . « » , (. . 6 ). buf0
buf1
: . inputValue
, - buf0
( ). . 6 / 12 /. switch
, buf1
. , , buf0
, 12, 6 /, .
case
FILTER_MODE_HIGHPASS
. buf0
. inputValue
buf0
, . buf1
, .
case
FILTER_MODE_BANDPASS
, buf0 - buf1
. buf0
, buf1
. , .
: , .
buf1
. ( Infinite Impulse Response , IIR ). , . , , .
, , . , , , , . , NI Massive, « ». , , . , ( Massive ). «» , , , AAS Ultra Analog, , , , . , , , , NI Reaktor. , — , .. .
. GUI. bg.png ( “Move to trash” Xcode), :
filtermode.png knob_small.png ( , 50 50 ) bg.png ( )
ID resource.h :
// Unique IDs for each image resource. #define BG_ID 101 #define WHITE_KEY_ID 102 #define BLACK_KEY_ID 103 #define WAVEFORM_ID 104 #define KNOB_ID 105 #define KNOB_SMALL_ID 106 #define FILTERMODE_ID 107 // Image resource locations for this plug. #define BG_FN "resources/img/bg.png" #define WHITE_KEY_FN "resources/img/whitekey.png" #define BLACK_KEY_FN "resources/img/blackkey.png" #define WAVEFORM_FN "resources/img/waveform.png" #define KNOB_FN "resources/img/knob.png" #define KNOB_SMALL_FN "resources/img/knob_small.png" #define FILTERMODE_FN "resources/img/filtermode.png"
Synthesis.rc :
#include "resource.h" BG_ID PNG BG_FN WHITE_KEY_ID PNG WHITE_KEY_FN BLACK_KEY_ID PNG BLACK_KEY_FN WAVEFORM_ID PNG WAVEFORM_FN KNOB_ID PNG KNOB_FN KNOB_SMALL_ID PNG KNOB_SMALL_FN FILTERMODE_ID PNG FILTERMODE_FN
#include "Filter.h"
Synthesis.h private
:
Filter mFilter;
EParams
Synthesis.cpp :
enum EParams { mWaveform = 0, mAttack, mDecay, mSustain, mRelease, mFilterMode, mFilterCutoff, mFilterResonance, mFilterAttack, mFilterDecay, mFilterSustain, mFilterRelease, mFilterEnvelopeAmount, kNumParams };
:
pGraphics->AttachControl(new ISwitchControl(this, 24, 38, mWaveform, &waveformBitmap));
. (Lowpass, Highpass, Bandpass). AttachGraphics(pGraphics)
:
GetParam(mFilterMode)->InitEnum("Filter Mode", Filter::FILTER_MODE_LOWPASS, Filter::kNumFilterModes); IBitmap filtermodeBitmap = pGraphics->LoadIBitmap(FILTERMODE_ID, FILTERMODE_FN, 3); pGraphics->AttachControl(new ISwitchControl(this, 24, 123, mFilterMode, &filtermodeBitmap));
. . knob_small.png :
// Knobs for filter cutoff and resonance IBitmap smallKnobBitmap = pGraphics->LoadIBitmap(KNOB_SMALL_ID, KNOB_SMALL_FN, 64); // Cutoff knob: GetParam(mFilterCutoff)->InitDouble("Cutoff", 0.99, 0.01, 0.99, 0.001); GetParam(mFilterCutoff)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 5, 177, mFilterCutoff, &smallKnobBitmap)); // Resonance knob: GetParam(mFilterResonance)->InitDouble("Resonance", 0.01, 0.01, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 61, 177, mFilterResonance, &smallKnobBitmap));
, 1.0
, calculateFeedbackAmount
, .
ProcessDoubleReplacing
:
leftOutput[i] = rightOutput[i] = mFilter.process(mOscillator.nextSample() * mEnvelopeGenerator.nextSample() * velocity / 127.0);
. switch
Synthesis::OnParamChange
:
case mFilterCutoff: mFilter.setCutoff(GetParam(paramIdx)->Value()); break; case mFilterResonance: mFilter.setResonance(GetParam(paramIdx)->Value()); break; case mFilterMode: mFilter.setFilterMode(static_cast<Filter::FilterMode>(GetParam(paramIdx)->Int())); break;
. - .
— . , , .
:
buf0 += cutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1));
, , ( buf0 – buf1
), feedbackAmount
. calculateFeedbackAmount
feedbackAmount
resonance
, . . , .
, . , , . , , .
-12 / -24 /
! 24 . switch
Filter::process
:
buf2 += cutoff * (buf1 - buf2); buf3 += cutoff * (buf2 – buf3);
: , , . , , , ( ). switch
:
switch (mode) { case FILTER_MODE_LOWPASS: return buf3; case FILTER_MODE_HIGHPASS: return inputValue - buf3; case FILTER_MODE_BANDPASS: return buf0 - buf3; default: return 0.0; }
buf1
buf3
– . -24 /. buf2
buf3
, private
Filter.h :
double buf2; double buf3;
, buf0
c buf1
:
Filter() : // ... buf0(0.0), buf1(0.0), buf2(0.0), buf3(0.0) // ...
, , : . .
?
. , , .
- cutoff
. . cutoffMod
, , cutoff
. #include private
:
double cutoffMod;
:
Filter() : cutoff(0.99), resonance(0.01), cutoffMod(0.0), // ...
. private
:
inline double getCalculatedCutoff() const { return fmax(fmin(cutoff + cutoffMod, 0.99), 0.01); };
calculateFeedbackAmount
:
inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - getCalculatedCutoff()); }
cutoffMod
. feedbackAmount
, :
inline void setCutoffMod(double newCutoffMod) { cutoffMod = newCutoffMod; calculateFeedbackAmount(); }
, . Filter::process
:
if (inputValue == 0.0) return inputValue; double calculatedCutoff = getCalculatedCutoff(); buf0 += calculatedCutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1)); buf1 += calculatedCutoff * (buf0 - buf1); buf2 += calculatedCutoff * (buf1 - buf2); buf3 += calculatedCutoff * (buf2 – buf3);
, . , , . . cutoff
calculatedCutoff
-.
, ( setCutoffMod
), Synthesis
, , . , cutoffMod
. filterEnvelopeAmount
-1
+1
. GUI.
private
Synthesis.h :
EnvelopeGenerator mFilterEnvelopeGenerator; double filterEnvelopeAmount;
MIDI , onNoteOn
onNoteOff
:
inline void onNoteOn(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); }; inline void onNoteOff(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); };
. ProcessDoubleReplacing
:
mFilter.setCutoffMod(mFilterEnvelopeGenerator.nextSample() * filterEnvelopeAmount);
, nextSample
filterEnvelopeAmount
cutoffMod
. filterEnvelopeAmount
:
Synthesis::Synthesis(IPlugInstanceInfo instanceInfo) : IPLUG_CTOR(kNumParams, kNumPrograms, instanceInfo), lastVirtualKeyboardNoteNumber(virtualKeyboardMinimumNoteNumber - 1), filterEnvelopeAmount(0.0) { // ... }
Synthesis::Reset
:
mFilterEnvelopeGenerator.setSampleRate(GetSampleRate());
EParams
, . AttachGraphics
:
// Knobs for filter envelope // Attack knob GetParam(mFilterAttack)->InitDouble("Filter Env Attack", 0.01, 0.01, 10.0, 0.001); GetParam(mFilterAttack)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 139, 178, mFilterAttack, &smallKnobBitmap)); // Decay knob: GetParam(mFilterDecay)->InitDouble("Filter Env Decay", 0.5, 0.01, 15.0, 0.001); GetParam(mFilterDecay)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 195, 178, mFilterDecay, &smallKnobBitmap)); // Sustain knob: GetParam(mFilterSustain)->InitDouble("Filter Env Sustain", 0.1, 0.001, 1.0, 0.001); GetParam(mFilterSustain)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 251, 178, mFilterSustain, &smallKnobBitmap)); // Release knob: GetParam(mFilterRelease)->InitDouble("Filter Env Release", 1.0, 0.001, 15.0, 0.001); GetParam(mFilterRelease)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 307, 178, mFilterRelease, &smallKnobBitmap)); // Filter envelope amount knob: GetParam(mFilterEnvelopeAmount)->InitDouble("Filter Env Amount", 0.0, -1.0, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 363, 178, mFilterEnvelopeAmount, &smallKnobBitmap));
, smallKnobBitmap
. . , . Synthesis::OnParamChange
switch
:
case mFilterAttack: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK, GetParam(paramIdx)->Value()); break; case mFilterDecay: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_DECAY, GetParam(paramIdx)->Value()); break; case mFilterSustain: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_SUSTAIN, GetParam(paramIdx)->Value()); break; case mFilterRelease: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE, GetParam(paramIdx)->Value()); break; case mFilterEnvelopeAmount: filterEnvelopeAmount = GetParam(paramIdx)->Value(); break;
!
! (- C1), :
EnvelopeGenerator
. . !
.
:)
:
martin-finke.de/blog/articles/audio-plugins-013-filter
#include Filter.h ( ) :
class Filter { public: enum FilterMode { FILTER_MODE_LOWPASS = 0, FILTER_MODE_HIGHPASS, FILTER_MODE_BANDPASS, kNumFilterModes }; Filter() : cutoff(0.99), resonance(0.0), mode(FILTER_MODE_LOWPASS), buf0(0.0), buf1(0.0) { calculateFeedbackAmount(); }; double process(double inputValue); inline void setCutoff(double newCutoff) { cutoff = newCutoff; calculateFeedbackAmount(); }; inline void setResonance(double newResonance) { resonance = newResonance; calculateFeedbackAmount(); }; inline void setFilterMode(FilterMode newMode) { mode = newMode; } private: double cutoff; double resonance; FilterMode mode; double feedbackAmount; inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - cutoff); } double buf0; double buf1; };
private
cutoff
resonance
.
Mode
(Lowpass, Highpass, Bandpass).
feedbackAmount
,
buf0
buf1
, .
(
calculateFeedbackAmount()
).
process
.
feedbackAmount
cutoff
resonance
,
calculateFeedbackAmount
.
Filter.cpp :
// By Paul Kellett // http://www.musicdsp.org/showone.php?id=29 double Filter::process(double inputValue) { buf0 += cutoff * (inputValue - buf0); buf1 += cutoff * (buf0 - buf1); switch (mode) { case FILTER_MODE_LOWPASS: return buf1; case FILTER_MODE_HIGHPASS: return inputValue - buf0; case FILTER_MODE_BANDPASS: return buf0 - buf1; default: return 0.0; } }
, ? , . « » , (. . 6 ).
buf0
buf1
: .
inputValue
, -
buf0
( ). . 6 / 12 /.
switch
,
buf1
. , ,
buf0
, 12, 6 /, .
case
FILTER_MODE_HIGHPASS
.
buf0
.
inputValue
buf0
, .
buf1
, .
case
FILTER_MODE_BANDPASS
,
buf0 - buf1
.
buf0
,
buf1
. , .
: , .
buf1
. ( Infinite Impulse Response , IIR ). , . , , .
, , . , , , , . , NI Massive, « ». , , . , ( Massive ). «» , , , AAS Ultra Analog, , , , . , , , , NI Reaktor. , — , .. .
. GUI. bg.png ( “Move to trash” Xcode), :
filtermode.png knob_small.png ( , 50 50 ) bg.png ( )
ID resource.h :
// Unique IDs for each image resource. #define BG_ID 101 #define WHITE_KEY_ID 102 #define BLACK_KEY_ID 103 #define WAVEFORM_ID 104 #define KNOB_ID 105 #define KNOB_SMALL_ID 106 #define FILTERMODE_ID 107 // Image resource locations for this plug. #define BG_FN "resources/img/bg.png" #define WHITE_KEY_FN "resources/img/whitekey.png" #define BLACK_KEY_FN "resources/img/blackkey.png" #define WAVEFORM_FN "resources/img/waveform.png" #define KNOB_FN "resources/img/knob.png" #define KNOB_SMALL_FN "resources/img/knob_small.png" #define FILTERMODE_FN "resources/img/filtermode.png"
Synthesis.rc :
#include "resource.h" BG_ID PNG BG_FN WHITE_KEY_ID PNG WHITE_KEY_FN BLACK_KEY_ID PNG BLACK_KEY_FN WAVEFORM_ID PNG WAVEFORM_FN KNOB_ID PNG KNOB_FN KNOB_SMALL_ID PNG KNOB_SMALL_FN FILTERMODE_ID PNG FILTERMODE_FN
#include "Filter.h"
Synthesis.h
private
:
Filter mFilter;
EParams
Synthesis.cpp :
enum EParams { mWaveform = 0, mAttack, mDecay, mSustain, mRelease, mFilterMode, mFilterCutoff, mFilterResonance, mFilterAttack, mFilterDecay, mFilterSustain, mFilterRelease, mFilterEnvelopeAmount, kNumParams };
:
pGraphics->AttachControl(new ISwitchControl(this, 24, 38, mWaveform, &waveformBitmap));
. (Lowpass, Highpass, Bandpass).
AttachGraphics(pGraphics)
:
GetParam(mFilterMode)->InitEnum("Filter Mode", Filter::FILTER_MODE_LOWPASS, Filter::kNumFilterModes); IBitmap filtermodeBitmap = pGraphics->LoadIBitmap(FILTERMODE_ID, FILTERMODE_FN, 3); pGraphics->AttachControl(new ISwitchControl(this, 24, 123, mFilterMode, &filtermodeBitmap));
. . knob_small.png :
// Knobs for filter cutoff and resonance IBitmap smallKnobBitmap = pGraphics->LoadIBitmap(KNOB_SMALL_ID, KNOB_SMALL_FN, 64); // Cutoff knob: GetParam(mFilterCutoff)->InitDouble("Cutoff", 0.99, 0.01, 0.99, 0.001); GetParam(mFilterCutoff)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 5, 177, mFilterCutoff, &smallKnobBitmap)); // Resonance knob: GetParam(mFilterResonance)->InitDouble("Resonance", 0.01, 0.01, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 61, 177, mFilterResonance, &smallKnobBitmap));
,
1.0
,
calculateFeedbackAmount
, .
ProcessDoubleReplacing
:
leftOutput[i] = rightOutput[i] = mFilter.process(mOscillator.nextSample() * mEnvelopeGenerator.nextSample() * velocity / 127.0);
.
switch
Synthesis::OnParamChange
:
case mFilterCutoff: mFilter.setCutoff(GetParam(paramIdx)->Value()); break; case mFilterResonance: mFilter.setResonance(GetParam(paramIdx)->Value()); break; case mFilterMode: mFilter.setFilterMode(static_cast<Filter::FilterMode>(GetParam(paramIdx)->Int())); break;
. - .
— . , , .
:
buf0 += cutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1));
, , (
buf0 – buf1
),
feedbackAmount
.
calculateFeedbackAmount
feedbackAmount
resonance
, . . , .
, . , , . , , .
-12 / -24 /
! 24 .
switch
Filter::process
:
buf2 += cutoff * (buf1 - buf2); buf3 += cutoff * (buf2 – buf3);
: , , . , , , ( ).
switch
:
switch (mode) { case FILTER_MODE_LOWPASS: return buf3; case FILTER_MODE_HIGHPASS: return inputValue - buf3; case FILTER_MODE_BANDPASS: return buf0 - buf3; default: return 0.0; }
buf1
buf3
– . -24 /.
buf2
buf3
,
private
Filter.h :
double buf2; double buf3;
,
buf0
c
buf1
:
Filter() : // ... buf0(0.0), buf1(0.0), buf2(0.0), buf3(0.0) // ...
, , : . .
?
. , , .
-
cutoff
. .
cutoffMod
, ,
cutoff
.
#include private
:
double cutoffMod;
:
Filter() : cutoff(0.99), resonance(0.01), cutoffMod(0.0), // ...
. private
:
inline double getCalculatedCutoff() const { return fmax(fmin(cutoff + cutoffMod, 0.99), 0.01); };
calculateFeedbackAmount
:
inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - getCalculatedCutoff()); }
cutoffMod
. feedbackAmount
, :
inline void setCutoffMod(double newCutoffMod) { cutoffMod = newCutoffMod; calculateFeedbackAmount(); }
, . Filter::process
:
if (inputValue == 0.0) return inputValue; double calculatedCutoff = getCalculatedCutoff(); buf0 += calculatedCutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1)); buf1 += calculatedCutoff * (buf0 - buf1); buf2 += calculatedCutoff * (buf1 - buf2); buf3 += calculatedCutoff * (buf2 – buf3);
, . , , . . cutoff
calculatedCutoff
-.
, ( setCutoffMod
), Synthesis
, , . , cutoffMod
. filterEnvelopeAmount
-1
+1
. GUI.
private
Synthesis.h :
EnvelopeGenerator mFilterEnvelopeGenerator; double filterEnvelopeAmount;
MIDI , onNoteOn
onNoteOff
:
inline void onNoteOn(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); }; inline void onNoteOff(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); };
. ProcessDoubleReplacing
:
mFilter.setCutoffMod(mFilterEnvelopeGenerator.nextSample() * filterEnvelopeAmount);
, nextSample
filterEnvelopeAmount
cutoffMod
. filterEnvelopeAmount
:
Synthesis::Synthesis(IPlugInstanceInfo instanceInfo) : IPLUG_CTOR(kNumParams, kNumPrograms, instanceInfo), lastVirtualKeyboardNoteNumber(virtualKeyboardMinimumNoteNumber - 1), filterEnvelopeAmount(0.0) { // ... }
Synthesis::Reset
:
mFilterEnvelopeGenerator.setSampleRate(GetSampleRate());
EParams
, . AttachGraphics
:
// Knobs for filter envelope // Attack knob GetParam(mFilterAttack)->InitDouble("Filter Env Attack", 0.01, 0.01, 10.0, 0.001); GetParam(mFilterAttack)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 139, 178, mFilterAttack, &smallKnobBitmap)); // Decay knob: GetParam(mFilterDecay)->InitDouble("Filter Env Decay", 0.5, 0.01, 15.0, 0.001); GetParam(mFilterDecay)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 195, 178, mFilterDecay, &smallKnobBitmap)); // Sustain knob: GetParam(mFilterSustain)->InitDouble("Filter Env Sustain", 0.1, 0.001, 1.0, 0.001); GetParam(mFilterSustain)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 251, 178, mFilterSustain, &smallKnobBitmap)); // Release knob: GetParam(mFilterRelease)->InitDouble("Filter Env Release", 1.0, 0.001, 15.0, 0.001); GetParam(mFilterRelease)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 307, 178, mFilterRelease, &smallKnobBitmap)); // Filter envelope amount knob: GetParam(mFilterEnvelopeAmount)->InitDouble("Filter Env Amount", 0.0, -1.0, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 363, 178, mFilterEnvelopeAmount, &smallKnobBitmap));
, smallKnobBitmap
. . , . Synthesis::OnParamChange
switch
:
case mFilterAttack: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK, GetParam(paramIdx)->Value()); break; case mFilterDecay: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_DECAY, GetParam(paramIdx)->Value()); break; case mFilterSustain: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_SUSTAIN, GetParam(paramIdx)->Value()); break; case mFilterRelease: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE, GetParam(paramIdx)->Value()); break; case mFilterEnvelopeAmount: filterEnvelopeAmount = GetParam(paramIdx)->Value(); break;
!
! (- C1), :
EnvelopeGenerator
. . !
.
:)
:
martin-finke.de/blog/articles/audio-plugins-013-filter
#include Filter.h ( ) :
class Filter { public: enum FilterMode { FILTER_MODE_LOWPASS = 0, FILTER_MODE_HIGHPASS, FILTER_MODE_BANDPASS, kNumFilterModes }; Filter() : cutoff(0.99), resonance(0.0), mode(FILTER_MODE_LOWPASS), buf0(0.0), buf1(0.0) { calculateFeedbackAmount(); }; double process(double inputValue); inline void setCutoff(double newCutoff) { cutoff = newCutoff; calculateFeedbackAmount(); }; inline void setResonance(double newResonance) { resonance = newResonance; calculateFeedbackAmount(); }; inline void setFilterMode(FilterMode newMode) { mode = newMode; } private: double cutoff; double resonance; FilterMode mode; double feedbackAmount; inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - cutoff); } double buf0; double buf1; };
private
cutoff
resonance
. Mode
(Lowpass, Highpass, Bandpass). feedbackAmount
, buf0
buf1
, .
( calculateFeedbackAmount()
). process
. feedbackAmount
cutoff
resonance
, calculateFeedbackAmount
.
Filter.cpp :
// By Paul Kellett // http://www.musicdsp.org/showone.php?id=29 double Filter::process(double inputValue) { buf0 += cutoff * (inputValue - buf0); buf1 += cutoff * (buf0 - buf1); switch (mode) { case FILTER_MODE_LOWPASS: return buf1; case FILTER_MODE_HIGHPASS: return inputValue - buf0; case FILTER_MODE_BANDPASS: return buf0 - buf1; default: return 0.0; } }
, ? , . « » , (. . 6 ). buf0
buf1
: . inputValue
, - buf0
( ). . 6 / 12 /. switch
, buf1
. , , buf0
, 12, 6 /, .
case
FILTER_MODE_HIGHPASS
. buf0
. inputValue
buf0
, . buf1
, .
case
FILTER_MODE_BANDPASS
, buf0 - buf1
. buf0
, buf1
. , .
: , .
buf1
. ( Infinite Impulse Response , IIR ). , . , , .
, , . , , , , . , NI Massive, « ». , , . , ( Massive ). «» , , , AAS Ultra Analog, , , , . , , , , NI Reaktor. , — , .. .
. GUI. bg.png ( “Move to trash” Xcode), :
filtermode.png knob_small.png ( , 50 50 ) bg.png ( )
ID resource.h :
// Unique IDs for each image resource. #define BG_ID 101 #define WHITE_KEY_ID 102 #define BLACK_KEY_ID 103 #define WAVEFORM_ID 104 #define KNOB_ID 105 #define KNOB_SMALL_ID 106 #define FILTERMODE_ID 107 // Image resource locations for this plug. #define BG_FN "resources/img/bg.png" #define WHITE_KEY_FN "resources/img/whitekey.png" #define BLACK_KEY_FN "resources/img/blackkey.png" #define WAVEFORM_FN "resources/img/waveform.png" #define KNOB_FN "resources/img/knob.png" #define KNOB_SMALL_FN "resources/img/knob_small.png" #define FILTERMODE_FN "resources/img/filtermode.png"
Synthesis.rc :
#include "resource.h" BG_ID PNG BG_FN WHITE_KEY_ID PNG WHITE_KEY_FN BLACK_KEY_ID PNG BLACK_KEY_FN WAVEFORM_ID PNG WAVEFORM_FN KNOB_ID PNG KNOB_FN KNOB_SMALL_ID PNG KNOB_SMALL_FN FILTERMODE_ID PNG FILTERMODE_FN
#include "Filter.h"
Synthesis.h private
:
Filter mFilter;
EParams
Synthesis.cpp :
enum EParams { mWaveform = 0, mAttack, mDecay, mSustain, mRelease, mFilterMode, mFilterCutoff, mFilterResonance, mFilterAttack, mFilterDecay, mFilterSustain, mFilterRelease, mFilterEnvelopeAmount, kNumParams };
:
pGraphics->AttachControl(new ISwitchControl(this, 24, 38, mWaveform, &waveformBitmap));
. (Lowpass, Highpass, Bandpass). AttachGraphics(pGraphics)
:
GetParam(mFilterMode)->InitEnum("Filter Mode", Filter::FILTER_MODE_LOWPASS, Filter::kNumFilterModes); IBitmap filtermodeBitmap = pGraphics->LoadIBitmap(FILTERMODE_ID, FILTERMODE_FN, 3); pGraphics->AttachControl(new ISwitchControl(this, 24, 123, mFilterMode, &filtermodeBitmap));
. . knob_small.png :
// Knobs for filter cutoff and resonance IBitmap smallKnobBitmap = pGraphics->LoadIBitmap(KNOB_SMALL_ID, KNOB_SMALL_FN, 64); // Cutoff knob: GetParam(mFilterCutoff)->InitDouble("Cutoff", 0.99, 0.01, 0.99, 0.001); GetParam(mFilterCutoff)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 5, 177, mFilterCutoff, &smallKnobBitmap)); // Resonance knob: GetParam(mFilterResonance)->InitDouble("Resonance", 0.01, 0.01, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 61, 177, mFilterResonance, &smallKnobBitmap));
, 1.0
, calculateFeedbackAmount
, .
ProcessDoubleReplacing
:
leftOutput[i] = rightOutput[i] = mFilter.process(mOscillator.nextSample() * mEnvelopeGenerator.nextSample() * velocity / 127.0);
. switch
Synthesis::OnParamChange
:
case mFilterCutoff: mFilter.setCutoff(GetParam(paramIdx)->Value()); break; case mFilterResonance: mFilter.setResonance(GetParam(paramIdx)->Value()); break; case mFilterMode: mFilter.setFilterMode(static_cast<Filter::FilterMode>(GetParam(paramIdx)->Int())); break;
. - .
— . , , .
:
buf0 += cutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1));
, , ( buf0 – buf1
), feedbackAmount
. calculateFeedbackAmount
feedbackAmount
resonance
, . . , .
, . , , . , , .
-12 / -24 /
! 24 . switch
Filter::process
:
buf2 += cutoff * (buf1 - buf2); buf3 += cutoff * (buf2 – buf3);
: , , . , , , ( ). switch
:
switch (mode) { case FILTER_MODE_LOWPASS: return buf3; case FILTER_MODE_HIGHPASS: return inputValue - buf3; case FILTER_MODE_BANDPASS: return buf0 - buf3; default: return 0.0; }
buf1
buf3
– . -24 /. buf2
buf3
, private
Filter.h :
double buf2; double buf3;
, buf0
c buf1
:
Filter() : // ... buf0(0.0), buf1(0.0), buf2(0.0), buf3(0.0) // ...
, , : . .
?
. , , .
- cutoff
. . cutoffMod
, , cutoff
. #include private
:
double cutoffMod;
:
Filter() : cutoff(0.99), resonance(0.01), cutoffMod(0.0), // ...
. private
:
inline double getCalculatedCutoff() const { return fmax(fmin(cutoff + cutoffMod, 0.99), 0.01); };
calculateFeedbackAmount
:
inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - getCalculatedCutoff()); }
cutoffMod
. feedbackAmount
, :
inline void setCutoffMod(double newCutoffMod) { cutoffMod = newCutoffMod; calculateFeedbackAmount(); }
, . Filter::process
:
if (inputValue == 0.0) return inputValue; double calculatedCutoff = getCalculatedCutoff(); buf0 += calculatedCutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1)); buf1 += calculatedCutoff * (buf0 - buf1); buf2 += calculatedCutoff * (buf1 - buf2); buf3 += calculatedCutoff * (buf2 – buf3);
, . , , . . cutoff
calculatedCutoff
-.
, ( setCutoffMod
), Synthesis
, , . , cutoffMod
. filterEnvelopeAmount
-1
+1
. GUI.
private
Synthesis.h :
EnvelopeGenerator mFilterEnvelopeGenerator; double filterEnvelopeAmount;
MIDI , onNoteOn
onNoteOff
:
inline void onNoteOn(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); }; inline void onNoteOff(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); };
. ProcessDoubleReplacing
:
mFilter.setCutoffMod(mFilterEnvelopeGenerator.nextSample() * filterEnvelopeAmount);
, nextSample
filterEnvelopeAmount
cutoffMod
. filterEnvelopeAmount
:
Synthesis::Synthesis(IPlugInstanceInfo instanceInfo) : IPLUG_CTOR(kNumParams, kNumPrograms, instanceInfo), lastVirtualKeyboardNoteNumber(virtualKeyboardMinimumNoteNumber - 1), filterEnvelopeAmount(0.0) { // ... }
Synthesis::Reset
:
mFilterEnvelopeGenerator.setSampleRate(GetSampleRate());
EParams
, . AttachGraphics
:
// Knobs for filter envelope // Attack knob GetParam(mFilterAttack)->InitDouble("Filter Env Attack", 0.01, 0.01, 10.0, 0.001); GetParam(mFilterAttack)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 139, 178, mFilterAttack, &smallKnobBitmap)); // Decay knob: GetParam(mFilterDecay)->InitDouble("Filter Env Decay", 0.5, 0.01, 15.0, 0.001); GetParam(mFilterDecay)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 195, 178, mFilterDecay, &smallKnobBitmap)); // Sustain knob: GetParam(mFilterSustain)->InitDouble("Filter Env Sustain", 0.1, 0.001, 1.0, 0.001); GetParam(mFilterSustain)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 251, 178, mFilterSustain, &smallKnobBitmap)); // Release knob: GetParam(mFilterRelease)->InitDouble("Filter Env Release", 1.0, 0.001, 15.0, 0.001); GetParam(mFilterRelease)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 307, 178, mFilterRelease, &smallKnobBitmap)); // Filter envelope amount knob: GetParam(mFilterEnvelopeAmount)->InitDouble("Filter Env Amount", 0.0, -1.0, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 363, 178, mFilterEnvelopeAmount, &smallKnobBitmap));
, smallKnobBitmap
. . , . Synthesis::OnParamChange
switch
:
case mFilterAttack: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK, GetParam(paramIdx)->Value()); break; case mFilterDecay: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_DECAY, GetParam(paramIdx)->Value()); break; case mFilterSustain: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_SUSTAIN, GetParam(paramIdx)->Value()); break; case mFilterRelease: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE, GetParam(paramIdx)->Value()); break; case mFilterEnvelopeAmount: filterEnvelopeAmount = GetParam(paramIdx)->Value(); break;
!
! (- C1), :
EnvelopeGenerator
. . !
.
:)
:
martin-finke.de/blog/articles/audio-plugins-013-filter
#include Filter.h ( ) :
class Filter { public: enum FilterMode { FILTER_MODE_LOWPASS = 0, FILTER_MODE_HIGHPASS, FILTER_MODE_BANDPASS, kNumFilterModes }; Filter() : cutoff(0.99), resonance(0.0), mode(FILTER_MODE_LOWPASS), buf0(0.0), buf1(0.0) { calculateFeedbackAmount(); }; double process(double inputValue); inline void setCutoff(double newCutoff) { cutoff = newCutoff; calculateFeedbackAmount(); }; inline void setResonance(double newResonance) { resonance = newResonance; calculateFeedbackAmount(); }; inline void setFilterMode(FilterMode newMode) { mode = newMode; } private: double cutoff; double resonance; FilterMode mode; double feedbackAmount; inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - cutoff); } double buf0; double buf1; };
private
cutoff
resonance
. Mode
(Lowpass, Highpass, Bandpass). feedbackAmount
, buf0
buf1
, .
( calculateFeedbackAmount()
). process
. feedbackAmount
cutoff
resonance
, calculateFeedbackAmount
.
Filter.cpp :
// By Paul Kellett // http://www.musicdsp.org/showone.php?id=29 double Filter::process(double inputValue) { buf0 += cutoff * (inputValue - buf0); buf1 += cutoff * (buf0 - buf1); switch (mode) { case FILTER_MODE_LOWPASS: return buf1; case FILTER_MODE_HIGHPASS: return inputValue - buf0; case FILTER_MODE_BANDPASS: return buf0 - buf1; default: return 0.0; } }
, ? , . « » , (. . 6 ). buf0
buf1
: . inputValue
, - buf0
( ). . 6 / 12 /. switch
, buf1
. , , buf0
, 12, 6 /, .
case
FILTER_MODE_HIGHPASS
. buf0
. inputValue
buf0
, . buf1
, .
case
FILTER_MODE_BANDPASS
, buf0 - buf1
. buf0
, buf1
. , .
: , .
buf1
. ( Infinite Impulse Response , IIR ). , . , , .
, , . , , , , . , NI Massive, « ». , , . , ( Massive ). «» , , , AAS Ultra Analog, , , , . , , , , NI Reaktor. , — , .. .
. GUI. bg.png ( “Move to trash” Xcode), :
filtermode.png knob_small.png ( , 50 50 ) bg.png ( )
ID resource.h :
// Unique IDs for each image resource. #define BG_ID 101 #define WHITE_KEY_ID 102 #define BLACK_KEY_ID 103 #define WAVEFORM_ID 104 #define KNOB_ID 105 #define KNOB_SMALL_ID 106 #define FILTERMODE_ID 107 // Image resource locations for this plug. #define BG_FN "resources/img/bg.png" #define WHITE_KEY_FN "resources/img/whitekey.png" #define BLACK_KEY_FN "resources/img/blackkey.png" #define WAVEFORM_FN "resources/img/waveform.png" #define KNOB_FN "resources/img/knob.png" #define KNOB_SMALL_FN "resources/img/knob_small.png" #define FILTERMODE_FN "resources/img/filtermode.png"
Synthesis.rc :
#include "resource.h" BG_ID PNG BG_FN WHITE_KEY_ID PNG WHITE_KEY_FN BLACK_KEY_ID PNG BLACK_KEY_FN WAVEFORM_ID PNG WAVEFORM_FN KNOB_ID PNG KNOB_FN KNOB_SMALL_ID PNG KNOB_SMALL_FN FILTERMODE_ID PNG FILTERMODE_FN
#include "Filter.h"
Synthesis.h private
:
Filter mFilter;
EParams
Synthesis.cpp :
enum EParams { mWaveform = 0, mAttack, mDecay, mSustain, mRelease, mFilterMode, mFilterCutoff, mFilterResonance, mFilterAttack, mFilterDecay, mFilterSustain, mFilterRelease, mFilterEnvelopeAmount, kNumParams };
:
pGraphics->AttachControl(new ISwitchControl(this, 24, 38, mWaveform, &waveformBitmap));
. (Lowpass, Highpass, Bandpass). AttachGraphics(pGraphics)
:
GetParam(mFilterMode)->InitEnum("Filter Mode", Filter::FILTER_MODE_LOWPASS, Filter::kNumFilterModes); IBitmap filtermodeBitmap = pGraphics->LoadIBitmap(FILTERMODE_ID, FILTERMODE_FN, 3); pGraphics->AttachControl(new ISwitchControl(this, 24, 123, mFilterMode, &filtermodeBitmap));
. . knob_small.png :
// Knobs for filter cutoff and resonance IBitmap smallKnobBitmap = pGraphics->LoadIBitmap(KNOB_SMALL_ID, KNOB_SMALL_FN, 64); // Cutoff knob: GetParam(mFilterCutoff)->InitDouble("Cutoff", 0.99, 0.01, 0.99, 0.001); GetParam(mFilterCutoff)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 5, 177, mFilterCutoff, &smallKnobBitmap)); // Resonance knob: GetParam(mFilterResonance)->InitDouble("Resonance", 0.01, 0.01, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 61, 177, mFilterResonance, &smallKnobBitmap));
, 1.0
, calculateFeedbackAmount
, .
ProcessDoubleReplacing
:
leftOutput[i] = rightOutput[i] = mFilter.process(mOscillator.nextSample() * mEnvelopeGenerator.nextSample() * velocity / 127.0);
. switch
Synthesis::OnParamChange
:
case mFilterCutoff: mFilter.setCutoff(GetParam(paramIdx)->Value()); break; case mFilterResonance: mFilter.setResonance(GetParam(paramIdx)->Value()); break; case mFilterMode: mFilter.setFilterMode(static_cast<Filter::FilterMode>(GetParam(paramIdx)->Int())); break;
. - .
— . , , .
:
buf0 += cutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1));
, , ( buf0 – buf1
), feedbackAmount
. calculateFeedbackAmount
feedbackAmount
resonance
, . . , .
, . , , . , , .
-12 / -24 /
! 24 . switch
Filter::process
:
buf2 += cutoff * (buf1 - buf2); buf3 += cutoff * (buf2 – buf3);
: , , . , , , ( ). switch
:
switch (mode) { case FILTER_MODE_LOWPASS: return buf3; case FILTER_MODE_HIGHPASS: return inputValue - buf3; case FILTER_MODE_BANDPASS: return buf0 - buf3; default: return 0.0; }
buf1
buf3
– . -24 /. buf2
buf3
, private
Filter.h :
double buf2; double buf3;
, buf0
c buf1
:
Filter() : // ... buf0(0.0), buf1(0.0), buf2(0.0), buf3(0.0) // ...
, , : . .
?
. , , .
- cutoff
. . cutoffMod
, , cutoff
. #include private
:
double cutoffMod;
:
Filter() : cutoff(0.99), resonance(0.01), cutoffMod(0.0), // ...
. private
:
inline double getCalculatedCutoff() const { return fmax(fmin(cutoff + cutoffMod, 0.99), 0.01); };
calculateFeedbackAmount
:
inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - getCalculatedCutoff()); }
cutoffMod
. feedbackAmount
, :
inline void setCutoffMod(double newCutoffMod) { cutoffMod = newCutoffMod; calculateFeedbackAmount(); }
, . Filter::process
:
if (inputValue == 0.0) return inputValue; double calculatedCutoff = getCalculatedCutoff(); buf0 += calculatedCutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1)); buf1 += calculatedCutoff * (buf0 - buf1); buf2 += calculatedCutoff * (buf1 - buf2); buf3 += calculatedCutoff * (buf2 – buf3);
, . , , . . cutoff
calculatedCutoff
-.
, ( setCutoffMod
), Synthesis
, , . , cutoffMod
. filterEnvelopeAmount
-1
+1
. GUI.
private
Synthesis.h :
EnvelopeGenerator mFilterEnvelopeGenerator; double filterEnvelopeAmount;
MIDI , onNoteOn
onNoteOff
:
inline void onNoteOn(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); }; inline void onNoteOff(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); };
. ProcessDoubleReplacing
:
mFilter.setCutoffMod(mFilterEnvelopeGenerator.nextSample() * filterEnvelopeAmount);
, nextSample
filterEnvelopeAmount
cutoffMod
. filterEnvelopeAmount
:
Synthesis::Synthesis(IPlugInstanceInfo instanceInfo) : IPLUG_CTOR(kNumParams, kNumPrograms, instanceInfo), lastVirtualKeyboardNoteNumber(virtualKeyboardMinimumNoteNumber - 1), filterEnvelopeAmount(0.0) { // ... }
Synthesis::Reset
:
mFilterEnvelopeGenerator.setSampleRate(GetSampleRate());
EParams
, . AttachGraphics
:
// Knobs for filter envelope // Attack knob GetParam(mFilterAttack)->InitDouble("Filter Env Attack", 0.01, 0.01, 10.0, 0.001); GetParam(mFilterAttack)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 139, 178, mFilterAttack, &smallKnobBitmap)); // Decay knob: GetParam(mFilterDecay)->InitDouble("Filter Env Decay", 0.5, 0.01, 15.0, 0.001); GetParam(mFilterDecay)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 195, 178, mFilterDecay, &smallKnobBitmap)); // Sustain knob: GetParam(mFilterSustain)->InitDouble("Filter Env Sustain", 0.1, 0.001, 1.0, 0.001); GetParam(mFilterSustain)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 251, 178, mFilterSustain, &smallKnobBitmap)); // Release knob: GetParam(mFilterRelease)->InitDouble("Filter Env Release", 1.0, 0.001, 15.0, 0.001); GetParam(mFilterRelease)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 307, 178, mFilterRelease, &smallKnobBitmap)); // Filter envelope amount knob: GetParam(mFilterEnvelopeAmount)->InitDouble("Filter Env Amount", 0.0, -1.0, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 363, 178, mFilterEnvelopeAmount, &smallKnobBitmap));
, smallKnobBitmap
. . , . Synthesis::OnParamChange
switch
:
case mFilterAttack: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK, GetParam(paramIdx)->Value()); break; case mFilterDecay: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_DECAY, GetParam(paramIdx)->Value()); break; case mFilterSustain: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_SUSTAIN, GetParam(paramIdx)->Value()); break; case mFilterRelease: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE, GetParam(paramIdx)->Value()); break; case mFilterEnvelopeAmount: filterEnvelopeAmount = GetParam(paramIdx)->Value(); break;
!
! (- C1), :
EnvelopeGenerator
. . !
.
:)
:
martin-finke.de/blog/articles/audio-plugins-013-filter
#include Filter.h ( ) :
class Filter { public: enum FilterMode { FILTER_MODE_LOWPASS = 0, FILTER_MODE_HIGHPASS, FILTER_MODE_BANDPASS, kNumFilterModes }; Filter() : cutoff(0.99), resonance(0.0), mode(FILTER_MODE_LOWPASS), buf0(0.0), buf1(0.0) { calculateFeedbackAmount(); }; double process(double inputValue); inline void setCutoff(double newCutoff) { cutoff = newCutoff; calculateFeedbackAmount(); }; inline void setResonance(double newResonance) { resonance = newResonance; calculateFeedbackAmount(); }; inline void setFilterMode(FilterMode newMode) { mode = newMode; } private: double cutoff; double resonance; FilterMode mode; double feedbackAmount; inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - cutoff); } double buf0; double buf1; };
private
cutoff
resonance
. Mode
(Lowpass, Highpass, Bandpass). feedbackAmount
, buf0
buf1
, .
( calculateFeedbackAmount()
). process
. feedbackAmount
cutoff
resonance
, calculateFeedbackAmount
.
Filter.cpp :
// By Paul Kellett // http://www.musicdsp.org/showone.php?id=29 double Filter::process(double inputValue) { buf0 += cutoff * (inputValue - buf0); buf1 += cutoff * (buf0 - buf1); switch (mode) { case FILTER_MODE_LOWPASS: return buf1; case FILTER_MODE_HIGHPASS: return inputValue - buf0; case FILTER_MODE_BANDPASS: return buf0 - buf1; default: return 0.0; } }
, ? , . « » , (. . 6 ). buf0
buf1
: . inputValue
, - buf0
( ). . 6 / 12 /. switch
, buf1
. , , buf0
, 12, 6 /, .
case
FILTER_MODE_HIGHPASS
. buf0
. inputValue
buf0
, . buf1
, .
case
FILTER_MODE_BANDPASS
, buf0 - buf1
. buf0
, buf1
. , .
: , .
buf1
. ( Infinite Impulse Response , IIR ). , . , , .
, , . , , , , . , NI Massive, « ». , , . , ( Massive ). «» , , , AAS Ultra Analog, , , , . , , , , NI Reaktor. , — , .. .
. GUI. bg.png ( “Move to trash” Xcode), :
filtermode.png knob_small.png ( , 50 50 ) bg.png ( )
ID resource.h :
// Unique IDs for each image resource. #define BG_ID 101 #define WHITE_KEY_ID 102 #define BLACK_KEY_ID 103 #define WAVEFORM_ID 104 #define KNOB_ID 105 #define KNOB_SMALL_ID 106 #define FILTERMODE_ID 107 // Image resource locations for this plug. #define BG_FN "resources/img/bg.png" #define WHITE_KEY_FN "resources/img/whitekey.png" #define BLACK_KEY_FN "resources/img/blackkey.png" #define WAVEFORM_FN "resources/img/waveform.png" #define KNOB_FN "resources/img/knob.png" #define KNOB_SMALL_FN "resources/img/knob_small.png" #define FILTERMODE_FN "resources/img/filtermode.png"
Synthesis.rc :
#include "resource.h" BG_ID PNG BG_FN WHITE_KEY_ID PNG WHITE_KEY_FN BLACK_KEY_ID PNG BLACK_KEY_FN WAVEFORM_ID PNG WAVEFORM_FN KNOB_ID PNG KNOB_FN KNOB_SMALL_ID PNG KNOB_SMALL_FN FILTERMODE_ID PNG FILTERMODE_FN
#include "Filter.h"
Synthesis.h private
:
Filter mFilter;
EParams
Synthesis.cpp :
enum EParams { mWaveform = 0, mAttack, mDecay, mSustain, mRelease, mFilterMode, mFilterCutoff, mFilterResonance, mFilterAttack, mFilterDecay, mFilterSustain, mFilterRelease, mFilterEnvelopeAmount, kNumParams };
:
pGraphics->AttachControl(new ISwitchControl(this, 24, 38, mWaveform, &waveformBitmap));
. (Lowpass, Highpass, Bandpass). AttachGraphics(pGraphics)
:
GetParam(mFilterMode)->InitEnum("Filter Mode", Filter::FILTER_MODE_LOWPASS, Filter::kNumFilterModes); IBitmap filtermodeBitmap = pGraphics->LoadIBitmap(FILTERMODE_ID, FILTERMODE_FN, 3); pGraphics->AttachControl(new ISwitchControl(this, 24, 123, mFilterMode, &filtermodeBitmap));
. . knob_small.png :
// Knobs for filter cutoff and resonance IBitmap smallKnobBitmap = pGraphics->LoadIBitmap(KNOB_SMALL_ID, KNOB_SMALL_FN, 64); // Cutoff knob: GetParam(mFilterCutoff)->InitDouble("Cutoff", 0.99, 0.01, 0.99, 0.001); GetParam(mFilterCutoff)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 5, 177, mFilterCutoff, &smallKnobBitmap)); // Resonance knob: GetParam(mFilterResonance)->InitDouble("Resonance", 0.01, 0.01, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 61, 177, mFilterResonance, &smallKnobBitmap));
, 1.0
, calculateFeedbackAmount
, .
ProcessDoubleReplacing
:
leftOutput[i] = rightOutput[i] = mFilter.process(mOscillator.nextSample() * mEnvelopeGenerator.nextSample() * velocity / 127.0);
. switch
Synthesis::OnParamChange
:
case mFilterCutoff: mFilter.setCutoff(GetParam(paramIdx)->Value()); break; case mFilterResonance: mFilter.setResonance(GetParam(paramIdx)->Value()); break; case mFilterMode: mFilter.setFilterMode(static_cast<Filter::FilterMode>(GetParam(paramIdx)->Int())); break;
. - .
— . , , .
:
buf0 += cutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1));
, , ( buf0 – buf1
), feedbackAmount
. calculateFeedbackAmount
feedbackAmount
resonance
, . . , .
, . , , . , , .
-12 / -24 /
! 24 . switch
Filter::process
:
buf2 += cutoff * (buf1 - buf2); buf3 += cutoff * (buf2 – buf3);
: , , . , , , ( ). switch
:
switch (mode) { case FILTER_MODE_LOWPASS: return buf3; case FILTER_MODE_HIGHPASS: return inputValue - buf3; case FILTER_MODE_BANDPASS: return buf0 - buf3; default: return 0.0; }
buf1
buf3
– . -24 /. buf2
buf3
, private
Filter.h :
double buf2; double buf3;
, buf0
c buf1
:
Filter() : // ... buf0(0.0), buf1(0.0), buf2(0.0), buf3(0.0) // ...
, , : . .
?
. , , .
- cutoff
. . cutoffMod
, , cutoff
. #include private
:
double cutoffMod;
:
Filter() : cutoff(0.99), resonance(0.01), cutoffMod(0.0), // ...
. private
:
inline double getCalculatedCutoff() const { return fmax(fmin(cutoff + cutoffMod, 0.99), 0.01); };
calculateFeedbackAmount
:
inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - getCalculatedCutoff()); }
cutoffMod
. feedbackAmount
, :
inline void setCutoffMod(double newCutoffMod) { cutoffMod = newCutoffMod; calculateFeedbackAmount(); }
, . Filter::process
:
if (inputValue == 0.0) return inputValue; double calculatedCutoff = getCalculatedCutoff(); buf0 += calculatedCutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1)); buf1 += calculatedCutoff * (buf0 - buf1); buf2 += calculatedCutoff * (buf1 - buf2); buf3 += calculatedCutoff * (buf2 – buf3);
, . , , . . cutoff
calculatedCutoff
-.
, ( setCutoffMod
), Synthesis
, , . , cutoffMod
. filterEnvelopeAmount
-1
+1
. GUI.
private
Synthesis.h :
EnvelopeGenerator mFilterEnvelopeGenerator; double filterEnvelopeAmount;
MIDI , onNoteOn
onNoteOff
:
inline void onNoteOn(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); }; inline void onNoteOff(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); };
. ProcessDoubleReplacing
:
mFilter.setCutoffMod(mFilterEnvelopeGenerator.nextSample() * filterEnvelopeAmount);
, nextSample
filterEnvelopeAmount
cutoffMod
. filterEnvelopeAmount
:
Synthesis::Synthesis(IPlugInstanceInfo instanceInfo) : IPLUG_CTOR(kNumParams, kNumPrograms, instanceInfo), lastVirtualKeyboardNoteNumber(virtualKeyboardMinimumNoteNumber - 1), filterEnvelopeAmount(0.0) { // ... }
Synthesis::Reset
:
mFilterEnvelopeGenerator.setSampleRate(GetSampleRate());
EParams
, . AttachGraphics
:
// Knobs for filter envelope // Attack knob GetParam(mFilterAttack)->InitDouble("Filter Env Attack", 0.01, 0.01, 10.0, 0.001); GetParam(mFilterAttack)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 139, 178, mFilterAttack, &smallKnobBitmap)); // Decay knob: GetParam(mFilterDecay)->InitDouble("Filter Env Decay", 0.5, 0.01, 15.0, 0.001); GetParam(mFilterDecay)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 195, 178, mFilterDecay, &smallKnobBitmap)); // Sustain knob: GetParam(mFilterSustain)->InitDouble("Filter Env Sustain", 0.1, 0.001, 1.0, 0.001); GetParam(mFilterSustain)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 251, 178, mFilterSustain, &smallKnobBitmap)); // Release knob: GetParam(mFilterRelease)->InitDouble("Filter Env Release", 1.0, 0.001, 15.0, 0.001); GetParam(mFilterRelease)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 307, 178, mFilterRelease, &smallKnobBitmap)); // Filter envelope amount knob: GetParam(mFilterEnvelopeAmount)->InitDouble("Filter Env Amount", 0.0, -1.0, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 363, 178, mFilterEnvelopeAmount, &smallKnobBitmap));
, smallKnobBitmap
. . , . Synthesis::OnParamChange
switch
:
case mFilterAttack: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK, GetParam(paramIdx)->Value()); break; case mFilterDecay: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_DECAY, GetParam(paramIdx)->Value()); break; case mFilterSustain: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_SUSTAIN, GetParam(paramIdx)->Value()); break; case mFilterRelease: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE, GetParam(paramIdx)->Value()); break; case mFilterEnvelopeAmount: filterEnvelopeAmount = GetParam(paramIdx)->Value(); break;
!
! (- C1), :
EnvelopeGenerator
. . !
.
:)
:
martin-finke.de/blog/articles/audio-plugins-013-filter
#include Filter.h ( ) :
class Filter { public: enum FilterMode { FILTER_MODE_LOWPASS = 0, FILTER_MODE_HIGHPASS, FILTER_MODE_BANDPASS, kNumFilterModes }; Filter() : cutoff(0.99), resonance(0.0), mode(FILTER_MODE_LOWPASS), buf0(0.0), buf1(0.0) { calculateFeedbackAmount(); }; double process(double inputValue); inline void setCutoff(double newCutoff) { cutoff = newCutoff; calculateFeedbackAmount(); }; inline void setResonance(double newResonance) { resonance = newResonance; calculateFeedbackAmount(); }; inline void setFilterMode(FilterMode newMode) { mode = newMode; } private: double cutoff; double resonance; FilterMode mode; double feedbackAmount; inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - cutoff); } double buf0; double buf1; };
private
cutoff
resonance
. Mode
(Lowpass, Highpass, Bandpass). feedbackAmount
, buf0
buf1
, .
( calculateFeedbackAmount()
). process
. feedbackAmount
cutoff
resonance
, calculateFeedbackAmount
.
Filter.cpp :
// By Paul Kellett // http://www.musicdsp.org/showone.php?id=29 double Filter::process(double inputValue) { buf0 += cutoff * (inputValue - buf0); buf1 += cutoff * (buf0 - buf1); switch (mode) { case FILTER_MODE_LOWPASS: return buf1; case FILTER_MODE_HIGHPASS: return inputValue - buf0; case FILTER_MODE_BANDPASS: return buf0 - buf1; default: return 0.0; } }
, ? , . « » , (. . 6 ). buf0
buf1
: . inputValue
, - buf0
( ). . 6 / 12 /. switch
, buf1
. , , buf0
, 12, 6 /, .
case
FILTER_MODE_HIGHPASS
. buf0
. inputValue
buf0
, . buf1
, .
case
FILTER_MODE_BANDPASS
, buf0 - buf1
. buf0
, buf1
. , .
: , .
buf1
. ( Infinite Impulse Response , IIR ). , . , , .
, , . , , , , . , NI Massive, « ». , , . , ( Massive ). «» , , , AAS Ultra Analog, , , , . , , , , NI Reaktor. , — , .. .
. GUI. bg.png ( “Move to trash” Xcode), :
filtermode.png knob_small.png ( , 50 50 ) bg.png ( )
ID resource.h :
// Unique IDs for each image resource. #define BG_ID 101 #define WHITE_KEY_ID 102 #define BLACK_KEY_ID 103 #define WAVEFORM_ID 104 #define KNOB_ID 105 #define KNOB_SMALL_ID 106 #define FILTERMODE_ID 107 // Image resource locations for this plug. #define BG_FN "resources/img/bg.png" #define WHITE_KEY_FN "resources/img/whitekey.png" #define BLACK_KEY_FN "resources/img/blackkey.png" #define WAVEFORM_FN "resources/img/waveform.png" #define KNOB_FN "resources/img/knob.png" #define KNOB_SMALL_FN "resources/img/knob_small.png" #define FILTERMODE_FN "resources/img/filtermode.png"
Synthesis.rc :
#include "resource.h" BG_ID PNG BG_FN WHITE_KEY_ID PNG WHITE_KEY_FN BLACK_KEY_ID PNG BLACK_KEY_FN WAVEFORM_ID PNG WAVEFORM_FN KNOB_ID PNG KNOB_FN KNOB_SMALL_ID PNG KNOB_SMALL_FN FILTERMODE_ID PNG FILTERMODE_FN
#include "Filter.h"
Synthesis.h private
:
Filter mFilter;
EParams
Synthesis.cpp :
enum EParams { mWaveform = 0, mAttack, mDecay, mSustain, mRelease, mFilterMode, mFilterCutoff, mFilterResonance, mFilterAttack, mFilterDecay, mFilterSustain, mFilterRelease, mFilterEnvelopeAmount, kNumParams };
:
pGraphics->AttachControl(new ISwitchControl(this, 24, 38, mWaveform, &waveformBitmap));
. (Lowpass, Highpass, Bandpass). AttachGraphics(pGraphics)
:
GetParam(mFilterMode)->InitEnum("Filter Mode", Filter::FILTER_MODE_LOWPASS, Filter::kNumFilterModes); IBitmap filtermodeBitmap = pGraphics->LoadIBitmap(FILTERMODE_ID, FILTERMODE_FN, 3); pGraphics->AttachControl(new ISwitchControl(this, 24, 123, mFilterMode, &filtermodeBitmap));
. . knob_small.png :
// Knobs for filter cutoff and resonance IBitmap smallKnobBitmap = pGraphics->LoadIBitmap(KNOB_SMALL_ID, KNOB_SMALL_FN, 64); // Cutoff knob: GetParam(mFilterCutoff)->InitDouble("Cutoff", 0.99, 0.01, 0.99, 0.001); GetParam(mFilterCutoff)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 5, 177, mFilterCutoff, &smallKnobBitmap)); // Resonance knob: GetParam(mFilterResonance)->InitDouble("Resonance", 0.01, 0.01, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 61, 177, mFilterResonance, &smallKnobBitmap));
, 1.0
, calculateFeedbackAmount
, .
ProcessDoubleReplacing
:
leftOutput[i] = rightOutput[i] = mFilter.process(mOscillator.nextSample() * mEnvelopeGenerator.nextSample() * velocity / 127.0);
. switch
Synthesis::OnParamChange
:
case mFilterCutoff: mFilter.setCutoff(GetParam(paramIdx)->Value()); break; case mFilterResonance: mFilter.setResonance(GetParam(paramIdx)->Value()); break; case mFilterMode: mFilter.setFilterMode(static_cast<Filter::FilterMode>(GetParam(paramIdx)->Int())); break;
. - .
— . , , .
:
buf0 += cutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1));
, , ( buf0 – buf1
), feedbackAmount
. calculateFeedbackAmount
feedbackAmount
resonance
, . . , .
, . , , . , , .
-12 / -24 /
! 24 . switch
Filter::process
:
buf2 += cutoff * (buf1 - buf2); buf3 += cutoff * (buf2 – buf3);
: , , . , , , ( ). switch
:
switch (mode) { case FILTER_MODE_LOWPASS: return buf3; case FILTER_MODE_HIGHPASS: return inputValue - buf3; case FILTER_MODE_BANDPASS: return buf0 - buf3; default: return 0.0; }
buf1
buf3
– . -24 /. buf2
buf3
, private
Filter.h :
double buf2; double buf3;
, buf0
c buf1
:
Filter() : // ... buf0(0.0), buf1(0.0), buf2(0.0), buf3(0.0) // ...
, , : . .
?
. , , .
- cutoff
. . cutoffMod
, , cutoff
. #include private
:
double cutoffMod;
:
Filter() : cutoff(0.99), resonance(0.01), cutoffMod(0.0), // ...
. private
:
inline double getCalculatedCutoff() const { return fmax(fmin(cutoff + cutoffMod, 0.99), 0.01); };
calculateFeedbackAmount
:
inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - getCalculatedCutoff()); }
cutoffMod
. feedbackAmount
, :
inline void setCutoffMod(double newCutoffMod) { cutoffMod = newCutoffMod; calculateFeedbackAmount(); }
, . Filter::process
:
if (inputValue == 0.0) return inputValue; double calculatedCutoff = getCalculatedCutoff(); buf0 += calculatedCutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1)); buf1 += calculatedCutoff * (buf0 - buf1); buf2 += calculatedCutoff * (buf1 - buf2); buf3 += calculatedCutoff * (buf2 – buf3);
, . , , . . cutoff
calculatedCutoff
-.
, ( setCutoffMod
), Synthesis
, , . , cutoffMod
. filterEnvelopeAmount
-1
+1
. GUI.
private
Synthesis.h :
EnvelopeGenerator mFilterEnvelopeGenerator; double filterEnvelopeAmount;
MIDI , onNoteOn
onNoteOff
:
inline void onNoteOn(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); }; inline void onNoteOff(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); };
. ProcessDoubleReplacing
:
mFilter.setCutoffMod(mFilterEnvelopeGenerator.nextSample() * filterEnvelopeAmount);
, nextSample
filterEnvelopeAmount
cutoffMod
. filterEnvelopeAmount
:
Synthesis::Synthesis(IPlugInstanceInfo instanceInfo) : IPLUG_CTOR(kNumParams, kNumPrograms, instanceInfo), lastVirtualKeyboardNoteNumber(virtualKeyboardMinimumNoteNumber - 1), filterEnvelopeAmount(0.0) { // ... }
Synthesis::Reset
:
mFilterEnvelopeGenerator.setSampleRate(GetSampleRate());
EParams
, . AttachGraphics
:
// Knobs for filter envelope // Attack knob GetParam(mFilterAttack)->InitDouble("Filter Env Attack", 0.01, 0.01, 10.0, 0.001); GetParam(mFilterAttack)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 139, 178, mFilterAttack, &smallKnobBitmap)); // Decay knob: GetParam(mFilterDecay)->InitDouble("Filter Env Decay", 0.5, 0.01, 15.0, 0.001); GetParam(mFilterDecay)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 195, 178, mFilterDecay, &smallKnobBitmap)); // Sustain knob: GetParam(mFilterSustain)->InitDouble("Filter Env Sustain", 0.1, 0.001, 1.0, 0.001); GetParam(mFilterSustain)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 251, 178, mFilterSustain, &smallKnobBitmap)); // Release knob: GetParam(mFilterRelease)->InitDouble("Filter Env Release", 1.0, 0.001, 15.0, 0.001); GetParam(mFilterRelease)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 307, 178, mFilterRelease, &smallKnobBitmap)); // Filter envelope amount knob: GetParam(mFilterEnvelopeAmount)->InitDouble("Filter Env Amount", 0.0, -1.0, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 363, 178, mFilterEnvelopeAmount, &smallKnobBitmap));
, smallKnobBitmap
. . , . Synthesis::OnParamChange
switch
:
case mFilterAttack: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK, GetParam(paramIdx)->Value()); break; case mFilterDecay: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_DECAY, GetParam(paramIdx)->Value()); break; case mFilterSustain: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_SUSTAIN, GetParam(paramIdx)->Value()); break; case mFilterRelease: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE, GetParam(paramIdx)->Value()); break; case mFilterEnvelopeAmount: filterEnvelopeAmount = GetParam(paramIdx)->Value(); break;
!
! (- C1), :
EnvelopeGenerator
. . !
.
:)
:
martin-finke.de/blog/articles/audio-plugins-013-filter
#include Filter.h ( ) :
class Filter { public: enum FilterMode { FILTER_MODE_LOWPASS = 0, FILTER_MODE_HIGHPASS, FILTER_MODE_BANDPASS, kNumFilterModes }; Filter() : cutoff(0.99), resonance(0.0), mode(FILTER_MODE_LOWPASS), buf0(0.0), buf1(0.0) { calculateFeedbackAmount(); }; double process(double inputValue); inline void setCutoff(double newCutoff) { cutoff = newCutoff; calculateFeedbackAmount(); }; inline void setResonance(double newResonance) { resonance = newResonance; calculateFeedbackAmount(); }; inline void setFilterMode(FilterMode newMode) { mode = newMode; } private: double cutoff; double resonance; FilterMode mode; double feedbackAmount; inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - cutoff); } double buf0; double buf1; };
private
cutoff
resonance
. Mode
(Lowpass, Highpass, Bandpass). feedbackAmount
, buf0
buf1
, .
( calculateFeedbackAmount()
). process
. feedbackAmount
cutoff
resonance
, calculateFeedbackAmount
.
Filter.cpp :
// By Paul Kellett // http://www.musicdsp.org/showone.php?id=29 double Filter::process(double inputValue) { buf0 += cutoff * (inputValue - buf0); buf1 += cutoff * (buf0 - buf1); switch (mode) { case FILTER_MODE_LOWPASS: return buf1; case FILTER_MODE_HIGHPASS: return inputValue - buf0; case FILTER_MODE_BANDPASS: return buf0 - buf1; default: return 0.0; } }
, ? , . « » , (. . 6 ). buf0
buf1
: . inputValue
, - buf0
( ). . 6 / 12 /. switch
, buf1
. , , buf0
, 12, 6 /, .
case
FILTER_MODE_HIGHPASS
. buf0
. inputValue
buf0
, . buf1
, .
case
FILTER_MODE_BANDPASS
, buf0 - buf1
. buf0
, buf1
. , .
: , .
buf1
. ( Infinite Impulse Response , IIR ). , . , , .
, , . , , , , . , NI Massive, « ». , , . , ( Massive ). «» , , , AAS Ultra Analog, , , , . , , , , NI Reaktor. , — , .. .
. GUI. bg.png ( “Move to trash” Xcode), :
filtermode.png knob_small.png ( , 50 50 ) bg.png ( )
ID resource.h :
// Unique IDs for each image resource. #define BG_ID 101 #define WHITE_KEY_ID 102 #define BLACK_KEY_ID 103 #define WAVEFORM_ID 104 #define KNOB_ID 105 #define KNOB_SMALL_ID 106 #define FILTERMODE_ID 107 // Image resource locations for this plug. #define BG_FN "resources/img/bg.png" #define WHITE_KEY_FN "resources/img/whitekey.png" #define BLACK_KEY_FN "resources/img/blackkey.png" #define WAVEFORM_FN "resources/img/waveform.png" #define KNOB_FN "resources/img/knob.png" #define KNOB_SMALL_FN "resources/img/knob_small.png" #define FILTERMODE_FN "resources/img/filtermode.png"
Synthesis.rc :
#include "resource.h" BG_ID PNG BG_FN WHITE_KEY_ID PNG WHITE_KEY_FN BLACK_KEY_ID PNG BLACK_KEY_FN WAVEFORM_ID PNG WAVEFORM_FN KNOB_ID PNG KNOB_FN KNOB_SMALL_ID PNG KNOB_SMALL_FN FILTERMODE_ID PNG FILTERMODE_FN
#include "Filter.h"
Synthesis.h private
:
Filter mFilter;
EParams
Synthesis.cpp :
enum EParams { mWaveform = 0, mAttack, mDecay, mSustain, mRelease, mFilterMode, mFilterCutoff, mFilterResonance, mFilterAttack, mFilterDecay, mFilterSustain, mFilterRelease, mFilterEnvelopeAmount, kNumParams };
:
pGraphics->AttachControl(new ISwitchControl(this, 24, 38, mWaveform, &waveformBitmap));
. (Lowpass, Highpass, Bandpass). AttachGraphics(pGraphics)
:
GetParam(mFilterMode)->InitEnum("Filter Mode", Filter::FILTER_MODE_LOWPASS, Filter::kNumFilterModes); IBitmap filtermodeBitmap = pGraphics->LoadIBitmap(FILTERMODE_ID, FILTERMODE_FN, 3); pGraphics->AttachControl(new ISwitchControl(this, 24, 123, mFilterMode, &filtermodeBitmap));
. . knob_small.png :
// Knobs for filter cutoff and resonance IBitmap smallKnobBitmap = pGraphics->LoadIBitmap(KNOB_SMALL_ID, KNOB_SMALL_FN, 64); // Cutoff knob: GetParam(mFilterCutoff)->InitDouble("Cutoff", 0.99, 0.01, 0.99, 0.001); GetParam(mFilterCutoff)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 5, 177, mFilterCutoff, &smallKnobBitmap)); // Resonance knob: GetParam(mFilterResonance)->InitDouble("Resonance", 0.01, 0.01, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 61, 177, mFilterResonance, &smallKnobBitmap));
, 1.0
, calculateFeedbackAmount
, .
ProcessDoubleReplacing
:
leftOutput[i] = rightOutput[i] = mFilter.process(mOscillator.nextSample() * mEnvelopeGenerator.nextSample() * velocity / 127.0);
. switch
Synthesis::OnParamChange
:
case mFilterCutoff: mFilter.setCutoff(GetParam(paramIdx)->Value()); break; case mFilterResonance: mFilter.setResonance(GetParam(paramIdx)->Value()); break; case mFilterMode: mFilter.setFilterMode(static_cast<Filter::FilterMode>(GetParam(paramIdx)->Int())); break;
. - .
— . , , .
:
buf0 += cutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1));
, , ( buf0 – buf1
), feedbackAmount
. calculateFeedbackAmount
feedbackAmount
resonance
, . . , .
, . , , . , , .
-12 / -24 /
! 24 . switch
Filter::process
:
buf2 += cutoff * (buf1 - buf2); buf3 += cutoff * (buf2 – buf3);
: , , . , , , ( ). switch
:
switch (mode) { case FILTER_MODE_LOWPASS: return buf3; case FILTER_MODE_HIGHPASS: return inputValue - buf3; case FILTER_MODE_BANDPASS: return buf0 - buf3; default: return 0.0; }
buf1
buf3
– . -24 /. buf2
buf3
, private
Filter.h :
double buf2; double buf3;
, buf0
c buf1
:
Filter() : // ... buf0(0.0), buf1(0.0), buf2(0.0), buf3(0.0) // ...
, , : . .
?
. , , .
- cutoff
. . cutoffMod
, , cutoff
. #include private
:
double cutoffMod;
:
Filter() : cutoff(0.99), resonance(0.01), cutoffMod(0.0), // ...
. private
:
inline double getCalculatedCutoff() const { return fmax(fmin(cutoff + cutoffMod, 0.99), 0.01); };
calculateFeedbackAmount
:
inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - getCalculatedCutoff()); }
cutoffMod
. feedbackAmount
, :
inline void setCutoffMod(double newCutoffMod) { cutoffMod = newCutoffMod; calculateFeedbackAmount(); }
, . Filter::process
:
if (inputValue == 0.0) return inputValue; double calculatedCutoff = getCalculatedCutoff(); buf0 += calculatedCutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1)); buf1 += calculatedCutoff * (buf0 - buf1); buf2 += calculatedCutoff * (buf1 - buf2); buf3 += calculatedCutoff * (buf2 – buf3);
, . , , . . cutoff
calculatedCutoff
-.
, ( setCutoffMod
), Synthesis
, , . , cutoffMod
. filterEnvelopeAmount
-1
+1
. GUI.
private
Synthesis.h :
EnvelopeGenerator mFilterEnvelopeGenerator; double filterEnvelopeAmount;
MIDI , onNoteOn
onNoteOff
:
inline void onNoteOn(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); }; inline void onNoteOff(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); };
. ProcessDoubleReplacing
:
mFilter.setCutoffMod(mFilterEnvelopeGenerator.nextSample() * filterEnvelopeAmount);
, nextSample
filterEnvelopeAmount
cutoffMod
. filterEnvelopeAmount
:
Synthesis::Synthesis(IPlugInstanceInfo instanceInfo) : IPLUG_CTOR(kNumParams, kNumPrograms, instanceInfo), lastVirtualKeyboardNoteNumber(virtualKeyboardMinimumNoteNumber - 1), filterEnvelopeAmount(0.0) { // ... }
Synthesis::Reset
:
mFilterEnvelopeGenerator.setSampleRate(GetSampleRate());
EParams
, . AttachGraphics
:
// Knobs for filter envelope // Attack knob GetParam(mFilterAttack)->InitDouble("Filter Env Attack", 0.01, 0.01, 10.0, 0.001); GetParam(mFilterAttack)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 139, 178, mFilterAttack, &smallKnobBitmap)); // Decay knob: GetParam(mFilterDecay)->InitDouble("Filter Env Decay", 0.5, 0.01, 15.0, 0.001); GetParam(mFilterDecay)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 195, 178, mFilterDecay, &smallKnobBitmap)); // Sustain knob: GetParam(mFilterSustain)->InitDouble("Filter Env Sustain", 0.1, 0.001, 1.0, 0.001); GetParam(mFilterSustain)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 251, 178, mFilterSustain, &smallKnobBitmap)); // Release knob: GetParam(mFilterRelease)->InitDouble("Filter Env Release", 1.0, 0.001, 15.0, 0.001); GetParam(mFilterRelease)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 307, 178, mFilterRelease, &smallKnobBitmap)); // Filter envelope amount knob: GetParam(mFilterEnvelopeAmount)->InitDouble("Filter Env Amount", 0.0, -1.0, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 363, 178, mFilterEnvelopeAmount, &smallKnobBitmap));
, smallKnobBitmap
. . , . Synthesis::OnParamChange
switch
:
case mFilterAttack: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK, GetParam(paramIdx)->Value()); break; case mFilterDecay: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_DECAY, GetParam(paramIdx)->Value()); break; case mFilterSustain: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_SUSTAIN, GetParam(paramIdx)->Value()); break; case mFilterRelease: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE, GetParam(paramIdx)->Value()); break; case mFilterEnvelopeAmount: filterEnvelopeAmount = GetParam(paramIdx)->Value(); break;
!
! (- C1), :
EnvelopeGenerator
. . !
.
:)
:
martin-finke.de/blog/articles/audio-plugins-013-filter
#include Filter.h ( ) :
class Filter { public: enum FilterMode { FILTER_MODE_LOWPASS = 0, FILTER_MODE_HIGHPASS, FILTER_MODE_BANDPASS, kNumFilterModes }; Filter() : cutoff(0.99), resonance(0.0), mode(FILTER_MODE_LOWPASS), buf0(0.0), buf1(0.0) { calculateFeedbackAmount(); }; double process(double inputValue); inline void setCutoff(double newCutoff) { cutoff = newCutoff; calculateFeedbackAmount(); }; inline void setResonance(double newResonance) { resonance = newResonance; calculateFeedbackAmount(); }; inline void setFilterMode(FilterMode newMode) { mode = newMode; } private: double cutoff; double resonance; FilterMode mode; double feedbackAmount; inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - cutoff); } double buf0; double buf1; };
private
cutoff
resonance
. Mode
(Lowpass, Highpass, Bandpass). feedbackAmount
, buf0
buf1
, .
( calculateFeedbackAmount()
). process
. feedbackAmount
cutoff
resonance
, calculateFeedbackAmount
.
Filter.cpp :
// By Paul Kellett // http://www.musicdsp.org/showone.php?id=29 double Filter::process(double inputValue) { buf0 += cutoff * (inputValue - buf0); buf1 += cutoff * (buf0 - buf1); switch (mode) { case FILTER_MODE_LOWPASS: return buf1; case FILTER_MODE_HIGHPASS: return inputValue - buf0; case FILTER_MODE_BANDPASS: return buf0 - buf1; default: return 0.0; } }
, ? , . « » , (. . 6 ). buf0
buf1
: . inputValue
, - buf0
( ). . 6 / 12 /. switch
, buf1
. , , buf0
, 12, 6 /, .
case
FILTER_MODE_HIGHPASS
. buf0
. inputValue
buf0
, . buf1
, .
case
FILTER_MODE_BANDPASS
, buf0 - buf1
. buf0
, buf1
. , .
: , .
buf1
. ( Infinite Impulse Response , IIR ). , . , , .
, , . , , , , . , NI Massive, « ». , , . , ( Massive ). «» , , , AAS Ultra Analog, , , , . , , , , NI Reaktor. , — , .. .
. GUI. bg.png ( “Move to trash” Xcode), :
filtermode.png knob_small.png ( , 50 50 ) bg.png ( )
ID resource.h :
// Unique IDs for each image resource. #define BG_ID 101 #define WHITE_KEY_ID 102 #define BLACK_KEY_ID 103 #define WAVEFORM_ID 104 #define KNOB_ID 105 #define KNOB_SMALL_ID 106 #define FILTERMODE_ID 107 // Image resource locations for this plug. #define BG_FN "resources/img/bg.png" #define WHITE_KEY_FN "resources/img/whitekey.png" #define BLACK_KEY_FN "resources/img/blackkey.png" #define WAVEFORM_FN "resources/img/waveform.png" #define KNOB_FN "resources/img/knob.png" #define KNOB_SMALL_FN "resources/img/knob_small.png" #define FILTERMODE_FN "resources/img/filtermode.png"
Synthesis.rc :
#include "resource.h" BG_ID PNG BG_FN WHITE_KEY_ID PNG WHITE_KEY_FN BLACK_KEY_ID PNG BLACK_KEY_FN WAVEFORM_ID PNG WAVEFORM_FN KNOB_ID PNG KNOB_FN KNOB_SMALL_ID PNG KNOB_SMALL_FN FILTERMODE_ID PNG FILTERMODE_FN
#include "Filter.h"
Synthesis.h private
:
Filter mFilter;
EParams
Synthesis.cpp :
enum EParams { mWaveform = 0, mAttack, mDecay, mSustain, mRelease, mFilterMode, mFilterCutoff, mFilterResonance, mFilterAttack, mFilterDecay, mFilterSustain, mFilterRelease, mFilterEnvelopeAmount, kNumParams };
:
pGraphics->AttachControl(new ISwitchControl(this, 24, 38, mWaveform, &waveformBitmap));
. (Lowpass, Highpass, Bandpass). AttachGraphics(pGraphics)
:
GetParam(mFilterMode)->InitEnum("Filter Mode", Filter::FILTER_MODE_LOWPASS, Filter::kNumFilterModes); IBitmap filtermodeBitmap = pGraphics->LoadIBitmap(FILTERMODE_ID, FILTERMODE_FN, 3); pGraphics->AttachControl(new ISwitchControl(this, 24, 123, mFilterMode, &filtermodeBitmap));
. . knob_small.png :
// Knobs for filter cutoff and resonance IBitmap smallKnobBitmap = pGraphics->LoadIBitmap(KNOB_SMALL_ID, KNOB_SMALL_FN, 64); // Cutoff knob: GetParam(mFilterCutoff)->InitDouble("Cutoff", 0.99, 0.01, 0.99, 0.001); GetParam(mFilterCutoff)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 5, 177, mFilterCutoff, &smallKnobBitmap)); // Resonance knob: GetParam(mFilterResonance)->InitDouble("Resonance", 0.01, 0.01, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 61, 177, mFilterResonance, &smallKnobBitmap));
, 1.0
, calculateFeedbackAmount
, .
ProcessDoubleReplacing
:
leftOutput[i] = rightOutput[i] = mFilter.process(mOscillator.nextSample() * mEnvelopeGenerator.nextSample() * velocity / 127.0);
. switch
Synthesis::OnParamChange
:
case mFilterCutoff: mFilter.setCutoff(GetParam(paramIdx)->Value()); break; case mFilterResonance: mFilter.setResonance(GetParam(paramIdx)->Value()); break; case mFilterMode: mFilter.setFilterMode(static_cast<Filter::FilterMode>(GetParam(paramIdx)->Int())); break;
. - .
— . , , .
:
buf0 += cutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1));
, , ( buf0 – buf1
), feedbackAmount
. calculateFeedbackAmount
feedbackAmount
resonance
, . . , .
, . , , . , , .
-12 / -24 /
! 24 . switch
Filter::process
:
buf2 += cutoff * (buf1 - buf2); buf3 += cutoff * (buf2 – buf3);
: , , . , , , ( ). switch
:
switch (mode) { case FILTER_MODE_LOWPASS: return buf3; case FILTER_MODE_HIGHPASS: return inputValue - buf3; case FILTER_MODE_BANDPASS: return buf0 - buf3; default: return 0.0; }
buf1
buf3
– . -24 /. buf2
buf3
, private
Filter.h :
double buf2; double buf3;
, buf0
c buf1
:
Filter() : // ... buf0(0.0), buf1(0.0), buf2(0.0), buf3(0.0) // ...
, , : . .
?
. , , .
- cutoff
. . cutoffMod
, , cutoff
. #include private
:
double cutoffMod;
:
Filter() : cutoff(0.99), resonance(0.01), cutoffMod(0.0), // ...
. private
:
inline double getCalculatedCutoff() const { return fmax(fmin(cutoff + cutoffMod, 0.99), 0.01); };
calculateFeedbackAmount
:
inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - getCalculatedCutoff()); }
cutoffMod
. feedbackAmount
, :
inline void setCutoffMod(double newCutoffMod) { cutoffMod = newCutoffMod; calculateFeedbackAmount(); }
, . Filter::process
:
if (inputValue == 0.0) return inputValue; double calculatedCutoff = getCalculatedCutoff(); buf0 += calculatedCutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1)); buf1 += calculatedCutoff * (buf0 - buf1); buf2 += calculatedCutoff * (buf1 - buf2); buf3 += calculatedCutoff * (buf2 – buf3);
, . , , . . cutoff
calculatedCutoff
-.
, ( setCutoffMod
), Synthesis
, , . , cutoffMod
. filterEnvelopeAmount
-1
+1
. GUI.
private
Synthesis.h :
EnvelopeGenerator mFilterEnvelopeGenerator; double filterEnvelopeAmount;
MIDI , onNoteOn
onNoteOff
:
inline void onNoteOn(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); }; inline void onNoteOff(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); };
. ProcessDoubleReplacing
:
mFilter.setCutoffMod(mFilterEnvelopeGenerator.nextSample() * filterEnvelopeAmount);
, nextSample
filterEnvelopeAmount
cutoffMod
. filterEnvelopeAmount
:
Synthesis::Synthesis(IPlugInstanceInfo instanceInfo) : IPLUG_CTOR(kNumParams, kNumPrograms, instanceInfo), lastVirtualKeyboardNoteNumber(virtualKeyboardMinimumNoteNumber - 1), filterEnvelopeAmount(0.0) { // ... }
Synthesis::Reset
:
mFilterEnvelopeGenerator.setSampleRate(GetSampleRate());
EParams
, . AttachGraphics
:
// Knobs for filter envelope // Attack knob GetParam(mFilterAttack)->InitDouble("Filter Env Attack", 0.01, 0.01, 10.0, 0.001); GetParam(mFilterAttack)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 139, 178, mFilterAttack, &smallKnobBitmap)); // Decay knob: GetParam(mFilterDecay)->InitDouble("Filter Env Decay", 0.5, 0.01, 15.0, 0.001); GetParam(mFilterDecay)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 195, 178, mFilterDecay, &smallKnobBitmap)); // Sustain knob: GetParam(mFilterSustain)->InitDouble("Filter Env Sustain", 0.1, 0.001, 1.0, 0.001); GetParam(mFilterSustain)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 251, 178, mFilterSustain, &smallKnobBitmap)); // Release knob: GetParam(mFilterRelease)->InitDouble("Filter Env Release", 1.0, 0.001, 15.0, 0.001); GetParam(mFilterRelease)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 307, 178, mFilterRelease, &smallKnobBitmap)); // Filter envelope amount knob: GetParam(mFilterEnvelopeAmount)->InitDouble("Filter Env Amount", 0.0, -1.0, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 363, 178, mFilterEnvelopeAmount, &smallKnobBitmap));
, smallKnobBitmap
. . , . Synthesis::OnParamChange
switch
:
case mFilterAttack: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK, GetParam(paramIdx)->Value()); break; case mFilterDecay: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_DECAY, GetParam(paramIdx)->Value()); break; case mFilterSustain: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_SUSTAIN, GetParam(paramIdx)->Value()); break; case mFilterRelease: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE, GetParam(paramIdx)->Value()); break; case mFilterEnvelopeAmount: filterEnvelopeAmount = GetParam(paramIdx)->Value(); break;
!
! (- C1), :
EnvelopeGenerator
. . !
.
:)
:
martin-finke.de/blog/articles/audio-plugins-013-filter
#include Filter.h ( ) :
class Filter { public: enum FilterMode { FILTER_MODE_LOWPASS = 0, FILTER_MODE_HIGHPASS, FILTER_MODE_BANDPASS, kNumFilterModes }; Filter() : cutoff(0.99), resonance(0.0), mode(FILTER_MODE_LOWPASS), buf0(0.0), buf1(0.0) { calculateFeedbackAmount(); }; double process(double inputValue); inline void setCutoff(double newCutoff) { cutoff = newCutoff; calculateFeedbackAmount(); }; inline void setResonance(double newResonance) { resonance = newResonance; calculateFeedbackAmount(); }; inline void setFilterMode(FilterMode newMode) { mode = newMode; } private: double cutoff; double resonance; FilterMode mode; double feedbackAmount; inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - cutoff); } double buf0; double buf1; };
private
cutoff
resonance
. Mode
(Lowpass, Highpass, Bandpass). feedbackAmount
, buf0
buf1
, .
( calculateFeedbackAmount()
). process
. feedbackAmount
cutoff
resonance
, calculateFeedbackAmount
.
Filter.cpp :
// By Paul Kellett // http://www.musicdsp.org/showone.php?id=29 double Filter::process(double inputValue) { buf0 += cutoff * (inputValue - buf0); buf1 += cutoff * (buf0 - buf1); switch (mode) { case FILTER_MODE_LOWPASS: return buf1; case FILTER_MODE_HIGHPASS: return inputValue - buf0; case FILTER_MODE_BANDPASS: return buf0 - buf1; default: return 0.0; } }
, ? , . « » , (. . 6 ). buf0
buf1
: . inputValue
, - buf0
( ). . 6 / 12 /. switch
, buf1
. , , buf0
, 12, 6 /, .
case
FILTER_MODE_HIGHPASS
. buf0
. inputValue
buf0
, . buf1
, .
case
FILTER_MODE_BANDPASS
, buf0 - buf1
. buf0
, buf1
. , .
: , .
buf1
. ( Infinite Impulse Response , IIR ). , . , , .
, , . , , , , . , NI Massive, « ». , , . , ( Massive ). «» , , , AAS Ultra Analog, , , , . , , , , NI Reaktor. , — , .. .
. GUI. bg.png ( “Move to trash” Xcode), :
filtermode.png knob_small.png ( , 50 50 ) bg.png ( )
ID resource.h :
// Unique IDs for each image resource. #define BG_ID 101 #define WHITE_KEY_ID 102 #define BLACK_KEY_ID 103 #define WAVEFORM_ID 104 #define KNOB_ID 105 #define KNOB_SMALL_ID 106 #define FILTERMODE_ID 107 // Image resource locations for this plug. #define BG_FN "resources/img/bg.png" #define WHITE_KEY_FN "resources/img/whitekey.png" #define BLACK_KEY_FN "resources/img/blackkey.png" #define WAVEFORM_FN "resources/img/waveform.png" #define KNOB_FN "resources/img/knob.png" #define KNOB_SMALL_FN "resources/img/knob_small.png" #define FILTERMODE_FN "resources/img/filtermode.png"
Synthesis.rc :
#include "resource.h" BG_ID PNG BG_FN WHITE_KEY_ID PNG WHITE_KEY_FN BLACK_KEY_ID PNG BLACK_KEY_FN WAVEFORM_ID PNG WAVEFORM_FN KNOB_ID PNG KNOB_FN KNOB_SMALL_ID PNG KNOB_SMALL_FN FILTERMODE_ID PNG FILTERMODE_FN
#include "Filter.h"
Synthesis.h private
:
Filter mFilter;
EParams
Synthesis.cpp :
enum EParams { mWaveform = 0, mAttack, mDecay, mSustain, mRelease, mFilterMode, mFilterCutoff, mFilterResonance, mFilterAttack, mFilterDecay, mFilterSustain, mFilterRelease, mFilterEnvelopeAmount, kNumParams };
:
pGraphics->AttachControl(new ISwitchControl(this, 24, 38, mWaveform, &waveformBitmap));
. (Lowpass, Highpass, Bandpass). AttachGraphics(pGraphics)
:
GetParam(mFilterMode)->InitEnum("Filter Mode", Filter::FILTER_MODE_LOWPASS, Filter::kNumFilterModes); IBitmap filtermodeBitmap = pGraphics->LoadIBitmap(FILTERMODE_ID, FILTERMODE_FN, 3); pGraphics->AttachControl(new ISwitchControl(this, 24, 123, mFilterMode, &filtermodeBitmap));
. . knob_small.png :
// Knobs for filter cutoff and resonance IBitmap smallKnobBitmap = pGraphics->LoadIBitmap(KNOB_SMALL_ID, KNOB_SMALL_FN, 64); // Cutoff knob: GetParam(mFilterCutoff)->InitDouble("Cutoff", 0.99, 0.01, 0.99, 0.001); GetParam(mFilterCutoff)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 5, 177, mFilterCutoff, &smallKnobBitmap)); // Resonance knob: GetParam(mFilterResonance)->InitDouble("Resonance", 0.01, 0.01, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 61, 177, mFilterResonance, &smallKnobBitmap));
, 1.0
, calculateFeedbackAmount
, .
ProcessDoubleReplacing
:
leftOutput[i] = rightOutput[i] = mFilter.process(mOscillator.nextSample() * mEnvelopeGenerator.nextSample() * velocity / 127.0);
. switch
Synthesis::OnParamChange
:
case mFilterCutoff: mFilter.setCutoff(GetParam(paramIdx)->Value()); break; case mFilterResonance: mFilter.setResonance(GetParam(paramIdx)->Value()); break; case mFilterMode: mFilter.setFilterMode(static_cast<Filter::FilterMode>(GetParam(paramIdx)->Int())); break;
. - .
— . , , .
:
buf0 += cutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1));
, , ( buf0 – buf1
), feedbackAmount
. calculateFeedbackAmount
feedbackAmount
resonance
, . . , .
, . , , . , , .
-12 / -24 /
! 24 . switch
Filter::process
:
buf2 += cutoff * (buf1 - buf2); buf3 += cutoff * (buf2 – buf3);
: , , . , , , ( ). switch
:
switch (mode) { case FILTER_MODE_LOWPASS: return buf3; case FILTER_MODE_HIGHPASS: return inputValue - buf3; case FILTER_MODE_BANDPASS: return buf0 - buf3; default: return 0.0; }
buf1
buf3
– . -24 /. buf2
buf3
, private
Filter.h :
double buf2; double buf3;
, buf0
c buf1
:
Filter() : // ... buf0(0.0), buf1(0.0), buf2(0.0), buf3(0.0) // ...
, , : . .
?
. , , .
- cutoff
. . cutoffMod
, , cutoff
. #include private
:
double cutoffMod;
:
Filter() : cutoff(0.99), resonance(0.01), cutoffMod(0.0), // ...
. private
:
inline double getCalculatedCutoff() const { return fmax(fmin(cutoff + cutoffMod, 0.99), 0.01); };
calculateFeedbackAmount
:
inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - getCalculatedCutoff()); }
cutoffMod
. feedbackAmount
, :
inline void setCutoffMod(double newCutoffMod) { cutoffMod = newCutoffMod; calculateFeedbackAmount(); }
, . Filter::process
:
if (inputValue == 0.0) return inputValue; double calculatedCutoff = getCalculatedCutoff(); buf0 += calculatedCutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1)); buf1 += calculatedCutoff * (buf0 - buf1); buf2 += calculatedCutoff * (buf1 - buf2); buf3 += calculatedCutoff * (buf2 – buf3);
, . , , . . cutoff
calculatedCutoff
-.
, ( setCutoffMod
), Synthesis
, , . , cutoffMod
. filterEnvelopeAmount
-1
+1
. GUI.
private
Synthesis.h :
EnvelopeGenerator mFilterEnvelopeGenerator; double filterEnvelopeAmount;
MIDI , onNoteOn
onNoteOff
:
inline void onNoteOn(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); }; inline void onNoteOff(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); };
. ProcessDoubleReplacing
:
mFilter.setCutoffMod(mFilterEnvelopeGenerator.nextSample() * filterEnvelopeAmount);
, nextSample
filterEnvelopeAmount
cutoffMod
. filterEnvelopeAmount
:
Synthesis::Synthesis(IPlugInstanceInfo instanceInfo) : IPLUG_CTOR(kNumParams, kNumPrograms, instanceInfo), lastVirtualKeyboardNoteNumber(virtualKeyboardMinimumNoteNumber - 1), filterEnvelopeAmount(0.0) { // ... }
Synthesis::Reset
:
mFilterEnvelopeGenerator.setSampleRate(GetSampleRate());
EParams
, . AttachGraphics
:
// Knobs for filter envelope // Attack knob GetParam(mFilterAttack)->InitDouble("Filter Env Attack", 0.01, 0.01, 10.0, 0.001); GetParam(mFilterAttack)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 139, 178, mFilterAttack, &smallKnobBitmap)); // Decay knob: GetParam(mFilterDecay)->InitDouble("Filter Env Decay", 0.5, 0.01, 15.0, 0.001); GetParam(mFilterDecay)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 195, 178, mFilterDecay, &smallKnobBitmap)); // Sustain knob: GetParam(mFilterSustain)->InitDouble("Filter Env Sustain", 0.1, 0.001, 1.0, 0.001); GetParam(mFilterSustain)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 251, 178, mFilterSustain, &smallKnobBitmap)); // Release knob: GetParam(mFilterRelease)->InitDouble("Filter Env Release", 1.0, 0.001, 15.0, 0.001); GetParam(mFilterRelease)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 307, 178, mFilterRelease, &smallKnobBitmap)); // Filter envelope amount knob: GetParam(mFilterEnvelopeAmount)->InitDouble("Filter Env Amount", 0.0, -1.0, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 363, 178, mFilterEnvelopeAmount, &smallKnobBitmap));
, smallKnobBitmap
. . , . Synthesis::OnParamChange
switch
:
case mFilterAttack: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK, GetParam(paramIdx)->Value()); break; case mFilterDecay: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_DECAY, GetParam(paramIdx)->Value()); break; case mFilterSustain: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_SUSTAIN, GetParam(paramIdx)->Value()); break; case mFilterRelease: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE, GetParam(paramIdx)->Value()); break; case mFilterEnvelopeAmount: filterEnvelopeAmount = GetParam(paramIdx)->Value(); break;
!
! (- C1), :
EnvelopeGenerator
. . !
.
:)
:
martin-finke.de/blog/articles/audio-plugins-013-filter
#include Filter.h ( ) :
class Filter { public: enum FilterMode { FILTER_MODE_LOWPASS = 0, FILTER_MODE_HIGHPASS, FILTER_MODE_BANDPASS, kNumFilterModes }; Filter() : cutoff(0.99), resonance(0.0), mode(FILTER_MODE_LOWPASS), buf0(0.0), buf1(0.0) { calculateFeedbackAmount(); }; double process(double inputValue); inline void setCutoff(double newCutoff) { cutoff = newCutoff; calculateFeedbackAmount(); }; inline void setResonance(double newResonance) { resonance = newResonance; calculateFeedbackAmount(); }; inline void setFilterMode(FilterMode newMode) { mode = newMode; } private: double cutoff; double resonance; FilterMode mode; double feedbackAmount; inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - cutoff); } double buf0; double buf1; };
private
cutoff
resonance
. Mode
(Lowpass, Highpass, Bandpass). feedbackAmount
, buf0
buf1
, .
( calculateFeedbackAmount()
). process
. feedbackAmount
cutoff
resonance
, calculateFeedbackAmount
.
Filter.cpp :
// By Paul Kellett // http://www.musicdsp.org/showone.php?id=29 double Filter::process(double inputValue) { buf0 += cutoff * (inputValue - buf0); buf1 += cutoff * (buf0 - buf1); switch (mode) { case FILTER_MODE_LOWPASS: return buf1; case FILTER_MODE_HIGHPASS: return inputValue - buf0; case FILTER_MODE_BANDPASS: return buf0 - buf1; default: return 0.0; } }
, ? , . « » , (. . 6 ). buf0
buf1
: . inputValue
, - buf0
( ). . 6 / 12 /. switch
, buf1
. , , buf0
, 12, 6 /, .
case
FILTER_MODE_HIGHPASS
. buf0
. inputValue
buf0
, . buf1
, .
case
FILTER_MODE_BANDPASS
, buf0 - buf1
. buf0
, buf1
. , .
: , .
buf1
. ( Infinite Impulse Response , IIR ). , . , , .
, , . , , , , . , NI Massive, « ». , , . , ( Massive ). «» , , , AAS Ultra Analog, , , , . , , , , NI Reaktor. , — , .. .
. GUI. bg.png ( “Move to trash” Xcode), :
filtermode.png knob_small.png ( , 50 50 ) bg.png ( )
ID resource.h :
// Unique IDs for each image resource. #define BG_ID 101 #define WHITE_KEY_ID 102 #define BLACK_KEY_ID 103 #define WAVEFORM_ID 104 #define KNOB_ID 105 #define KNOB_SMALL_ID 106 #define FILTERMODE_ID 107 // Image resource locations for this plug. #define BG_FN "resources/img/bg.png" #define WHITE_KEY_FN "resources/img/whitekey.png" #define BLACK_KEY_FN "resources/img/blackkey.png" #define WAVEFORM_FN "resources/img/waveform.png" #define KNOB_FN "resources/img/knob.png" #define KNOB_SMALL_FN "resources/img/knob_small.png" #define FILTERMODE_FN "resources/img/filtermode.png"
Synthesis.rc :
#include "resource.h" BG_ID PNG BG_FN WHITE_KEY_ID PNG WHITE_KEY_FN BLACK_KEY_ID PNG BLACK_KEY_FN WAVEFORM_ID PNG WAVEFORM_FN KNOB_ID PNG KNOB_FN KNOB_SMALL_ID PNG KNOB_SMALL_FN FILTERMODE_ID PNG FILTERMODE_FN
#include "Filter.h"
Synthesis.h private
:
Filter mFilter;
EParams
Synthesis.cpp :
enum EParams { mWaveform = 0, mAttack, mDecay, mSustain, mRelease, mFilterMode, mFilterCutoff, mFilterResonance, mFilterAttack, mFilterDecay, mFilterSustain, mFilterRelease, mFilterEnvelopeAmount, kNumParams };
:
pGraphics->AttachControl(new ISwitchControl(this, 24, 38, mWaveform, &waveformBitmap));
. (Lowpass, Highpass, Bandpass). AttachGraphics(pGraphics)
:
GetParam(mFilterMode)->InitEnum("Filter Mode", Filter::FILTER_MODE_LOWPASS, Filter::kNumFilterModes); IBitmap filtermodeBitmap = pGraphics->LoadIBitmap(FILTERMODE_ID, FILTERMODE_FN, 3); pGraphics->AttachControl(new ISwitchControl(this, 24, 123, mFilterMode, &filtermodeBitmap));
. . knob_small.png :
// Knobs for filter cutoff and resonance IBitmap smallKnobBitmap = pGraphics->LoadIBitmap(KNOB_SMALL_ID, KNOB_SMALL_FN, 64); // Cutoff knob: GetParam(mFilterCutoff)->InitDouble("Cutoff", 0.99, 0.01, 0.99, 0.001); GetParam(mFilterCutoff)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 5, 177, mFilterCutoff, &smallKnobBitmap)); // Resonance knob: GetParam(mFilterResonance)->InitDouble("Resonance", 0.01, 0.01, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 61, 177, mFilterResonance, &smallKnobBitmap));
, 1.0
, calculateFeedbackAmount
, .
ProcessDoubleReplacing
:
leftOutput[i] = rightOutput[i] = mFilter.process(mOscillator.nextSample() * mEnvelopeGenerator.nextSample() * velocity / 127.0);
. switch
Synthesis::OnParamChange
:
case mFilterCutoff: mFilter.setCutoff(GetParam(paramIdx)->Value()); break; case mFilterResonance: mFilter.setResonance(GetParam(paramIdx)->Value()); break; case mFilterMode: mFilter.setFilterMode(static_cast<Filter::FilterMode>(GetParam(paramIdx)->Int())); break;
. - .
— . , , .
:
buf0 += cutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1));
, , ( buf0 – buf1
), feedbackAmount
. calculateFeedbackAmount
feedbackAmount
resonance
, . . , .
, . , , . , , .
-12 / -24 /
! 24 . switch
Filter::process
:
buf2 += cutoff * (buf1 - buf2); buf3 += cutoff * (buf2 – buf3);
: , , . , , , ( ). switch
:
switch (mode) { case FILTER_MODE_LOWPASS: return buf3; case FILTER_MODE_HIGHPASS: return inputValue - buf3; case FILTER_MODE_BANDPASS: return buf0 - buf3; default: return 0.0; }
buf1
buf3
– . -24 /. buf2
buf3
, private
Filter.h :
double buf2; double buf3;
, buf0
c buf1
:
Filter() : // ... buf0(0.0), buf1(0.0), buf2(0.0), buf3(0.0) // ...
, , : . .
?
. , , .
- cutoff
. . cutoffMod
, , cutoff
. #include private
:
double cutoffMod;
:
Filter() : cutoff(0.99), resonance(0.01), cutoffMod(0.0), // ...
. private
:
inline double getCalculatedCutoff() const { return fmax(fmin(cutoff + cutoffMod, 0.99), 0.01); };
calculateFeedbackAmount
:
inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - getCalculatedCutoff()); }
cutoffMod
. feedbackAmount
, :
inline void setCutoffMod(double newCutoffMod) { cutoffMod = newCutoffMod; calculateFeedbackAmount(); }
, . Filter::process
:
if (inputValue == 0.0) return inputValue; double calculatedCutoff = getCalculatedCutoff(); buf0 += calculatedCutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1)); buf1 += calculatedCutoff * (buf0 - buf1); buf2 += calculatedCutoff * (buf1 - buf2); buf3 += calculatedCutoff * (buf2 – buf3);
, . , , . . cutoff
calculatedCutoff
-.
, ( setCutoffMod
), Synthesis
, , . , cutoffMod
. filterEnvelopeAmount
-1
+1
. GUI.
private
Synthesis.h :
EnvelopeGenerator mFilterEnvelopeGenerator; double filterEnvelopeAmount;
MIDI , onNoteOn
onNoteOff
:
inline void onNoteOn(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); }; inline void onNoteOff(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); };
. ProcessDoubleReplacing
:
mFilter.setCutoffMod(mFilterEnvelopeGenerator.nextSample() * filterEnvelopeAmount);
, nextSample
filterEnvelopeAmount
cutoffMod
. filterEnvelopeAmount
:
Synthesis::Synthesis(IPlugInstanceInfo instanceInfo) : IPLUG_CTOR(kNumParams, kNumPrograms, instanceInfo), lastVirtualKeyboardNoteNumber(virtualKeyboardMinimumNoteNumber - 1), filterEnvelopeAmount(0.0) { // ... }
Synthesis::Reset
:
mFilterEnvelopeGenerator.setSampleRate(GetSampleRate());
EParams
, . AttachGraphics
:
// Knobs for filter envelope // Attack knob GetParam(mFilterAttack)->InitDouble("Filter Env Attack", 0.01, 0.01, 10.0, 0.001); GetParam(mFilterAttack)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 139, 178, mFilterAttack, &smallKnobBitmap)); // Decay knob: GetParam(mFilterDecay)->InitDouble("Filter Env Decay", 0.5, 0.01, 15.0, 0.001); GetParam(mFilterDecay)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 195, 178, mFilterDecay, &smallKnobBitmap)); // Sustain knob: GetParam(mFilterSustain)->InitDouble("Filter Env Sustain", 0.1, 0.001, 1.0, 0.001); GetParam(mFilterSustain)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 251, 178, mFilterSustain, &smallKnobBitmap)); // Release knob: GetParam(mFilterRelease)->InitDouble("Filter Env Release", 1.0, 0.001, 15.0, 0.001); GetParam(mFilterRelease)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 307, 178, mFilterRelease, &smallKnobBitmap)); // Filter envelope amount knob: GetParam(mFilterEnvelopeAmount)->InitDouble("Filter Env Amount", 0.0, -1.0, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 363, 178, mFilterEnvelopeAmount, &smallKnobBitmap));
, smallKnobBitmap
. . , . Synthesis::OnParamChange
switch
:
case mFilterAttack: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK, GetParam(paramIdx)->Value()); break; case mFilterDecay: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_DECAY, GetParam(paramIdx)->Value()); break; case mFilterSustain: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_SUSTAIN, GetParam(paramIdx)->Value()); break; case mFilterRelease: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE, GetParam(paramIdx)->Value()); break; case mFilterEnvelopeAmount: filterEnvelopeAmount = GetParam(paramIdx)->Value(); break;
!
! (- C1), :
EnvelopeGenerator
. . !
.
:)
:
martin-finke.de/blog/articles/audio-plugins-013-filter
#include Filter.h ( ) :
class Filter { public: enum FilterMode { FILTER_MODE_LOWPASS = 0, FILTER_MODE_HIGHPASS, FILTER_MODE_BANDPASS, kNumFilterModes }; Filter() : cutoff(0.99), resonance(0.0), mode(FILTER_MODE_LOWPASS), buf0(0.0), buf1(0.0) { calculateFeedbackAmount(); }; double process(double inputValue); inline void setCutoff(double newCutoff) { cutoff = newCutoff; calculateFeedbackAmount(); }; inline void setResonance(double newResonance) { resonance = newResonance; calculateFeedbackAmount(); }; inline void setFilterMode(FilterMode newMode) { mode = newMode; } private: double cutoff; double resonance; FilterMode mode; double feedbackAmount; inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - cutoff); } double buf0; double buf1; };
private
cutoff
resonance
. Mode
(Lowpass, Highpass, Bandpass). feedbackAmount
, buf0
buf1
, .
( calculateFeedbackAmount()
). process
. feedbackAmount
cutoff
resonance
, calculateFeedbackAmount
.
Filter.cpp :
// By Paul Kellett // http://www.musicdsp.org/showone.php?id=29 double Filter::process(double inputValue) { buf0 += cutoff * (inputValue - buf0); buf1 += cutoff * (buf0 - buf1); switch (mode) { case FILTER_MODE_LOWPASS: return buf1; case FILTER_MODE_HIGHPASS: return inputValue - buf0; case FILTER_MODE_BANDPASS: return buf0 - buf1; default: return 0.0; } }
, ? , . « » , (. . 6 ). buf0
buf1
: . inputValue
, - buf0
( ). . 6 / 12 /. switch
, buf1
. , , buf0
, 12, 6 /, .
case
FILTER_MODE_HIGHPASS
. buf0
. inputValue
buf0
, . buf1
, .
case
FILTER_MODE_BANDPASS
, buf0 - buf1
. buf0
, buf1
. , .
: , .
buf1
. ( Infinite Impulse Response , IIR ). , . , , .
, , . , , , , . , NI Massive, « ». , , . , ( Massive ). «» , , , AAS Ultra Analog, , , , . , , , , NI Reaktor. , — , .. .
. GUI. bg.png ( “Move to trash” Xcode), :
filtermode.png knob_small.png ( , 50 50 ) bg.png ( )
ID resource.h :
// Unique IDs for each image resource. #define BG_ID 101 #define WHITE_KEY_ID 102 #define BLACK_KEY_ID 103 #define WAVEFORM_ID 104 #define KNOB_ID 105 #define KNOB_SMALL_ID 106 #define FILTERMODE_ID 107 // Image resource locations for this plug. #define BG_FN "resources/img/bg.png" #define WHITE_KEY_FN "resources/img/whitekey.png" #define BLACK_KEY_FN "resources/img/blackkey.png" #define WAVEFORM_FN "resources/img/waveform.png" #define KNOB_FN "resources/img/knob.png" #define KNOB_SMALL_FN "resources/img/knob_small.png" #define FILTERMODE_FN "resources/img/filtermode.png"
Synthesis.rc :
#include "resource.h" BG_ID PNG BG_FN WHITE_KEY_ID PNG WHITE_KEY_FN BLACK_KEY_ID PNG BLACK_KEY_FN WAVEFORM_ID PNG WAVEFORM_FN KNOB_ID PNG KNOB_FN KNOB_SMALL_ID PNG KNOB_SMALL_FN FILTERMODE_ID PNG FILTERMODE_FN
#include "Filter.h"
Synthesis.h private
:
Filter mFilter;
EParams
Synthesis.cpp :
enum EParams { mWaveform = 0, mAttack, mDecay, mSustain, mRelease, mFilterMode, mFilterCutoff, mFilterResonance, mFilterAttack, mFilterDecay, mFilterSustain, mFilterRelease, mFilterEnvelopeAmount, kNumParams };
:
pGraphics->AttachControl(new ISwitchControl(this, 24, 38, mWaveform, &waveformBitmap));
. (Lowpass, Highpass, Bandpass). AttachGraphics(pGraphics)
:
GetParam(mFilterMode)->InitEnum("Filter Mode", Filter::FILTER_MODE_LOWPASS, Filter::kNumFilterModes); IBitmap filtermodeBitmap = pGraphics->LoadIBitmap(FILTERMODE_ID, FILTERMODE_FN, 3); pGraphics->AttachControl(new ISwitchControl(this, 24, 123, mFilterMode, &filtermodeBitmap));
. . knob_small.png :
// Knobs for filter cutoff and resonance IBitmap smallKnobBitmap = pGraphics->LoadIBitmap(KNOB_SMALL_ID, KNOB_SMALL_FN, 64); // Cutoff knob: GetParam(mFilterCutoff)->InitDouble("Cutoff", 0.99, 0.01, 0.99, 0.001); GetParam(mFilterCutoff)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 5, 177, mFilterCutoff, &smallKnobBitmap)); // Resonance knob: GetParam(mFilterResonance)->InitDouble("Resonance", 0.01, 0.01, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 61, 177, mFilterResonance, &smallKnobBitmap));
, 1.0
, calculateFeedbackAmount
, .
ProcessDoubleReplacing
:
leftOutput[i] = rightOutput[i] = mFilter.process(mOscillator.nextSample() * mEnvelopeGenerator.nextSample() * velocity / 127.0);
. switch
Synthesis::OnParamChange
:
case mFilterCutoff: mFilter.setCutoff(GetParam(paramIdx)->Value()); break; case mFilterResonance: mFilter.setResonance(GetParam(paramIdx)->Value()); break; case mFilterMode: mFilter.setFilterMode(static_cast<Filter::FilterMode>(GetParam(paramIdx)->Int())); break;
. - .
— . , , .
:
buf0 += cutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1));
, , ( buf0 – buf1
), feedbackAmount
. calculateFeedbackAmount
feedbackAmount
resonance
, . . , .
, . , , . , , .
-12 / -24 /
! 24 . switch
Filter::process
:
buf2 += cutoff * (buf1 - buf2); buf3 += cutoff * (buf2 – buf3);
: , , . , , , ( ). switch
:
switch (mode) { case FILTER_MODE_LOWPASS: return buf3; case FILTER_MODE_HIGHPASS: return inputValue - buf3; case FILTER_MODE_BANDPASS: return buf0 - buf3; default: return 0.0; }
buf1
buf3
– . -24 /. buf2
buf3
, private
Filter.h :
double buf2; double buf3;
, buf0
c buf1
:
Filter() : // ... buf0(0.0), buf1(0.0), buf2(0.0), buf3(0.0) // ...
, , : . .
?
. , , .
- cutoff
. . cutoffMod
, , cutoff
. #include private
:
double cutoffMod;
:
Filter() : cutoff(0.99), resonance(0.01), cutoffMod(0.0), // ...
. private
:
inline double getCalculatedCutoff() const { return fmax(fmin(cutoff + cutoffMod, 0.99), 0.01); };
calculateFeedbackAmount
:
inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - getCalculatedCutoff()); }
cutoffMod
. feedbackAmount
, :
inline void setCutoffMod(double newCutoffMod) { cutoffMod = newCutoffMod; calculateFeedbackAmount(); }
, . Filter::process
:
if (inputValue == 0.0) return inputValue; double calculatedCutoff = getCalculatedCutoff(); buf0 += calculatedCutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1)); buf1 += calculatedCutoff * (buf0 - buf1); buf2 += calculatedCutoff * (buf1 - buf2); buf3 += calculatedCutoff * (buf2 – buf3);
, . , , . . cutoff
calculatedCutoff
-.
, ( setCutoffMod
), Synthesis
, , . , cutoffMod
. filterEnvelopeAmount
-1
+1
. GUI.
private
Synthesis.h :
EnvelopeGenerator mFilterEnvelopeGenerator; double filterEnvelopeAmount;
MIDI , onNoteOn
onNoteOff
:
inline void onNoteOn(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); }; inline void onNoteOff(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); };
. ProcessDoubleReplacing
:
mFilter.setCutoffMod(mFilterEnvelopeGenerator.nextSample() * filterEnvelopeAmount);
, nextSample
filterEnvelopeAmount
cutoffMod
. filterEnvelopeAmount
:
Synthesis::Synthesis(IPlugInstanceInfo instanceInfo) : IPLUG_CTOR(kNumParams, kNumPrograms, instanceInfo), lastVirtualKeyboardNoteNumber(virtualKeyboardMinimumNoteNumber - 1), filterEnvelopeAmount(0.0) { // ... }
Synthesis::Reset
:
mFilterEnvelopeGenerator.setSampleRate(GetSampleRate());
EParams
, . AttachGraphics
:
// Knobs for filter envelope // Attack knob GetParam(mFilterAttack)->InitDouble("Filter Env Attack", 0.01, 0.01, 10.0, 0.001); GetParam(mFilterAttack)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 139, 178, mFilterAttack, &smallKnobBitmap)); // Decay knob: GetParam(mFilterDecay)->InitDouble("Filter Env Decay", 0.5, 0.01, 15.0, 0.001); GetParam(mFilterDecay)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 195, 178, mFilterDecay, &smallKnobBitmap)); // Sustain knob: GetParam(mFilterSustain)->InitDouble("Filter Env Sustain", 0.1, 0.001, 1.0, 0.001); GetParam(mFilterSustain)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 251, 178, mFilterSustain, &smallKnobBitmap)); // Release knob: GetParam(mFilterRelease)->InitDouble("Filter Env Release", 1.0, 0.001, 15.0, 0.001); GetParam(mFilterRelease)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 307, 178, mFilterRelease, &smallKnobBitmap)); // Filter envelope amount knob: GetParam(mFilterEnvelopeAmount)->InitDouble("Filter Env Amount", 0.0, -1.0, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 363, 178, mFilterEnvelopeAmount, &smallKnobBitmap));
, smallKnobBitmap
. . , . Synthesis::OnParamChange
switch
:
case mFilterAttack: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK, GetParam(paramIdx)->Value()); break; case mFilterDecay: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_DECAY, GetParam(paramIdx)->Value()); break; case mFilterSustain: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_SUSTAIN, GetParam(paramIdx)->Value()); break; case mFilterRelease: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE, GetParam(paramIdx)->Value()); break; case mFilterEnvelopeAmount: filterEnvelopeAmount = GetParam(paramIdx)->Value(); break;
!
! (- C1), :
EnvelopeGenerator
. . !
.
:)
:
martin-finke.de/blog/articles/audio-plugins-013-filter
#include Filter.h ( ) :
class Filter { public: enum FilterMode { FILTER_MODE_LOWPASS = 0, FILTER_MODE_HIGHPASS, FILTER_MODE_BANDPASS, kNumFilterModes }; Filter() : cutoff(0.99), resonance(0.0), mode(FILTER_MODE_LOWPASS), buf0(0.0), buf1(0.0) { calculateFeedbackAmount(); }; double process(double inputValue); inline void setCutoff(double newCutoff) { cutoff = newCutoff; calculateFeedbackAmount(); }; inline void setResonance(double newResonance) { resonance = newResonance; calculateFeedbackAmount(); }; inline void setFilterMode(FilterMode newMode) { mode = newMode; } private: double cutoff; double resonance; FilterMode mode; double feedbackAmount; inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - cutoff); } double buf0; double buf1; };
private
cutoff
resonance
. Mode
(Lowpass, Highpass, Bandpass). feedbackAmount
, buf0
buf1
, .
( calculateFeedbackAmount()
). process
. feedbackAmount
cutoff
resonance
, calculateFeedbackAmount
.
Filter.cpp :
// By Paul Kellett // http://www.musicdsp.org/showone.php?id=29 double Filter::process(double inputValue) { buf0 += cutoff * (inputValue - buf0); buf1 += cutoff * (buf0 - buf1); switch (mode) { case FILTER_MODE_LOWPASS: return buf1; case FILTER_MODE_HIGHPASS: return inputValue - buf0; case FILTER_MODE_BANDPASS: return buf0 - buf1; default: return 0.0; } }
, ? , . « » , (. . 6 ). buf0
buf1
: . inputValue
, - buf0
( ). . 6 / 12 /. switch
, buf1
. , , buf0
, 12, 6 /, .
case
FILTER_MODE_HIGHPASS
. buf0
. inputValue
buf0
, . buf1
, .
case
FILTER_MODE_BANDPASS
, buf0 - buf1
. buf0
, buf1
. , .
: , .
buf1
. ( Infinite Impulse Response , IIR ). , . , , .
, , . , , , , . , NI Massive, « ». , , . , ( Massive ). «» , , , AAS Ultra Analog, , , , . , , , , NI Reaktor. , — , .. .
. GUI. bg.png ( “Move to trash” Xcode), :
filtermode.png knob_small.png ( , 50 50 ) bg.png ( )
ID resource.h :
// Unique IDs for each image resource. #define BG_ID 101 #define WHITE_KEY_ID 102 #define BLACK_KEY_ID 103 #define WAVEFORM_ID 104 #define KNOB_ID 105 #define KNOB_SMALL_ID 106 #define FILTERMODE_ID 107 // Image resource locations for this plug. #define BG_FN "resources/img/bg.png" #define WHITE_KEY_FN "resources/img/whitekey.png" #define BLACK_KEY_FN "resources/img/blackkey.png" #define WAVEFORM_FN "resources/img/waveform.png" #define KNOB_FN "resources/img/knob.png" #define KNOB_SMALL_FN "resources/img/knob_small.png" #define FILTERMODE_FN "resources/img/filtermode.png"
Synthesis.rc :
#include "resource.h" BG_ID PNG BG_FN WHITE_KEY_ID PNG WHITE_KEY_FN BLACK_KEY_ID PNG BLACK_KEY_FN WAVEFORM_ID PNG WAVEFORM_FN KNOB_ID PNG KNOB_FN KNOB_SMALL_ID PNG KNOB_SMALL_FN FILTERMODE_ID PNG FILTERMODE_FN
#include "Filter.h"
Synthesis.h private
:
Filter mFilter;
EParams
Synthesis.cpp :
enum EParams { mWaveform = 0, mAttack, mDecay, mSustain, mRelease, mFilterMode, mFilterCutoff, mFilterResonance, mFilterAttack, mFilterDecay, mFilterSustain, mFilterRelease, mFilterEnvelopeAmount, kNumParams };
:
pGraphics->AttachControl(new ISwitchControl(this, 24, 38, mWaveform, &waveformBitmap));
. (Lowpass, Highpass, Bandpass). AttachGraphics(pGraphics)
:
GetParam(mFilterMode)->InitEnum("Filter Mode", Filter::FILTER_MODE_LOWPASS, Filter::kNumFilterModes); IBitmap filtermodeBitmap = pGraphics->LoadIBitmap(FILTERMODE_ID, FILTERMODE_FN, 3); pGraphics->AttachControl(new ISwitchControl(this, 24, 123, mFilterMode, &filtermodeBitmap));
. . knob_small.png :
// Knobs for filter cutoff and resonance IBitmap smallKnobBitmap = pGraphics->LoadIBitmap(KNOB_SMALL_ID, KNOB_SMALL_FN, 64); // Cutoff knob: GetParam(mFilterCutoff)->InitDouble("Cutoff", 0.99, 0.01, 0.99, 0.001); GetParam(mFilterCutoff)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 5, 177, mFilterCutoff, &smallKnobBitmap)); // Resonance knob: GetParam(mFilterResonance)->InitDouble("Resonance", 0.01, 0.01, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 61, 177, mFilterResonance, &smallKnobBitmap));
, 1.0
, calculateFeedbackAmount
, .
ProcessDoubleReplacing
:
leftOutput[i] = rightOutput[i] = mFilter.process(mOscillator.nextSample() * mEnvelopeGenerator.nextSample() * velocity / 127.0);
. switch
Synthesis::OnParamChange
:
case mFilterCutoff: mFilter.setCutoff(GetParam(paramIdx)->Value()); break; case mFilterResonance: mFilter.setResonance(GetParam(paramIdx)->Value()); break; case mFilterMode: mFilter.setFilterMode(static_cast<Filter::FilterMode>(GetParam(paramIdx)->Int())); break;
. - .
— . , , .
:
buf0 += cutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1));
, , ( buf0 – buf1
), feedbackAmount
. calculateFeedbackAmount
feedbackAmount
resonance
, . . , .
, . , , . , , .
-12 / -24 /
! 24 . switch
Filter::process
:
buf2 += cutoff * (buf1 - buf2); buf3 += cutoff * (buf2 – buf3);
: , , . , , , ( ). switch
:
switch (mode) { case FILTER_MODE_LOWPASS: return buf3; case FILTER_MODE_HIGHPASS: return inputValue - buf3; case FILTER_MODE_BANDPASS: return buf0 - buf3; default: return 0.0; }
buf1
buf3
– . -24 /. buf2
buf3
, private
Filter.h :
double buf2; double buf3;
, buf0
c buf1
:
Filter() : // ... buf0(0.0), buf1(0.0), buf2(0.0), buf3(0.0) // ...
, , : . .
?
. , , .
- cutoff
. . cutoffMod
, , cutoff
. #include private
:
double cutoffMod;
:
Filter() : cutoff(0.99), resonance(0.01), cutoffMod(0.0), // ...
. private
:
inline double getCalculatedCutoff() const { return fmax(fmin(cutoff + cutoffMod, 0.99), 0.01); };
calculateFeedbackAmount
:
inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - getCalculatedCutoff()); }
cutoffMod
. feedbackAmount
, :
inline void setCutoffMod(double newCutoffMod) { cutoffMod = newCutoffMod; calculateFeedbackAmount(); }
, . Filter::process
:
if (inputValue == 0.0) return inputValue; double calculatedCutoff = getCalculatedCutoff(); buf0 += calculatedCutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1)); buf1 += calculatedCutoff * (buf0 - buf1); buf2 += calculatedCutoff * (buf1 - buf2); buf3 += calculatedCutoff * (buf2 – buf3);
, . , , . . cutoff
calculatedCutoff
-.
, ( setCutoffMod
), Synthesis
, , . , cutoffMod
. filterEnvelopeAmount
-1
+1
. GUI.
private
Synthesis.h :
EnvelopeGenerator mFilterEnvelopeGenerator; double filterEnvelopeAmount;
MIDI , onNoteOn
onNoteOff
:
inline void onNoteOn(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); }; inline void onNoteOff(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); };
. ProcessDoubleReplacing
:
mFilter.setCutoffMod(mFilterEnvelopeGenerator.nextSample() * filterEnvelopeAmount);
, nextSample
filterEnvelopeAmount
cutoffMod
. filterEnvelopeAmount
:
Synthesis::Synthesis(IPlugInstanceInfo instanceInfo) : IPLUG_CTOR(kNumParams, kNumPrograms, instanceInfo), lastVirtualKeyboardNoteNumber(virtualKeyboardMinimumNoteNumber - 1), filterEnvelopeAmount(0.0) { // ... }
Synthesis::Reset
:
mFilterEnvelopeGenerator.setSampleRate(GetSampleRate());
EParams
, . AttachGraphics
:
// Knobs for filter envelope // Attack knob GetParam(mFilterAttack)->InitDouble("Filter Env Attack", 0.01, 0.01, 10.0, 0.001); GetParam(mFilterAttack)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 139, 178, mFilterAttack, &smallKnobBitmap)); // Decay knob: GetParam(mFilterDecay)->InitDouble("Filter Env Decay", 0.5, 0.01, 15.0, 0.001); GetParam(mFilterDecay)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 195, 178, mFilterDecay, &smallKnobBitmap)); // Sustain knob: GetParam(mFilterSustain)->InitDouble("Filter Env Sustain", 0.1, 0.001, 1.0, 0.001); GetParam(mFilterSustain)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 251, 178, mFilterSustain, &smallKnobBitmap)); // Release knob: GetParam(mFilterRelease)->InitDouble("Filter Env Release", 1.0, 0.001, 15.0, 0.001); GetParam(mFilterRelease)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 307, 178, mFilterRelease, &smallKnobBitmap)); // Filter envelope amount knob: GetParam(mFilterEnvelopeAmount)->InitDouble("Filter Env Amount", 0.0, -1.0, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 363, 178, mFilterEnvelopeAmount, &smallKnobBitmap));
, smallKnobBitmap
. . , . Synthesis::OnParamChange
switch
:
case mFilterAttack: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK, GetParam(paramIdx)->Value()); break; case mFilterDecay: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_DECAY, GetParam(paramIdx)->Value()); break; case mFilterSustain: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_SUSTAIN, GetParam(paramIdx)->Value()); break; case mFilterRelease: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE, GetParam(paramIdx)->Value()); break; case mFilterEnvelopeAmount: filterEnvelopeAmount = GetParam(paramIdx)->Value(); break;
!
! (- C1), :
EnvelopeGenerator
. . !
.
:)
:
martin-finke.de/blog/articles/audio-plugins-013-filter
#include Filter.h ( ) :
class Filter { public: enum FilterMode { FILTER_MODE_LOWPASS = 0, FILTER_MODE_HIGHPASS, FILTER_MODE_BANDPASS, kNumFilterModes }; Filter() : cutoff(0.99), resonance(0.0), mode(FILTER_MODE_LOWPASS), buf0(0.0), buf1(0.0) { calculateFeedbackAmount(); }; double process(double inputValue); inline void setCutoff(double newCutoff) { cutoff = newCutoff; calculateFeedbackAmount(); }; inline void setResonance(double newResonance) { resonance = newResonance; calculateFeedbackAmount(); }; inline void setFilterMode(FilterMode newMode) { mode = newMode; } private: double cutoff; double resonance; FilterMode mode; double feedbackAmount; inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - cutoff); } double buf0; double buf1; };
private
cutoff
resonance
.
Mode
(Lowpass, Highpass, Bandpass).
feedbackAmount
,
buf0
buf1
, .
(
calculateFeedbackAmount()
).
process
.
feedbackAmount
cutoff
resonance
,
calculateFeedbackAmount
.
Filter.cpp :
// By Paul Kellett // http://www.musicdsp.org/showone.php?id=29 double Filter::process(double inputValue) { buf0 += cutoff * (inputValue - buf0); buf1 += cutoff * (buf0 - buf1); switch (mode) { case FILTER_MODE_LOWPASS: return buf1; case FILTER_MODE_HIGHPASS: return inputValue - buf0; case FILTER_MODE_BANDPASS: return buf0 - buf1; default: return 0.0; } }
, ? , . « » , (. . 6 ).
buf0
buf1
: .
inputValue
, -
buf0
( ). . 6 / 12 /.
switch
,
buf1
. , ,
buf0
, 12, 6 /, .
case
FILTER_MODE_HIGHPASS
.
buf0
.
inputValue
buf0
, .
buf1
, .
case
FILTER_MODE_BANDPASS
,
buf0 - buf1
.
buf0
,
buf1
. , .
: , .
buf1
. ( Infinite Impulse Response , IIR ). , . , , .
, , . , , , , . , NI Massive, « ». , , . , ( Massive ). «» , , , AAS Ultra Analog, , , , . , , , , NI Reaktor. , — , .. .
. GUI. bg.png ( “Move to trash” Xcode), :
filtermode.png knob_small.png ( , 50 50 ) bg.png ( )
ID resource.h :
// Unique IDs for each image resource. #define BG_ID 101 #define WHITE_KEY_ID 102 #define BLACK_KEY_ID 103 #define WAVEFORM_ID 104 #define KNOB_ID 105 #define KNOB_SMALL_ID 106 #define FILTERMODE_ID 107 // Image resource locations for this plug. #define BG_FN "resources/img/bg.png" #define WHITE_KEY_FN "resources/img/whitekey.png" #define BLACK_KEY_FN "resources/img/blackkey.png" #define WAVEFORM_FN "resources/img/waveform.png" #define KNOB_FN "resources/img/knob.png" #define KNOB_SMALL_FN "resources/img/knob_small.png" #define FILTERMODE_FN "resources/img/filtermode.png"
Synthesis.rc :
#include "resource.h" BG_ID PNG BG_FN WHITE_KEY_ID PNG WHITE_KEY_FN BLACK_KEY_ID PNG BLACK_KEY_FN WAVEFORM_ID PNG WAVEFORM_FN KNOB_ID PNG KNOB_FN KNOB_SMALL_ID PNG KNOB_SMALL_FN FILTERMODE_ID PNG FILTERMODE_FN
#include "Filter.h"
Synthesis.h
private
:
Filter mFilter;
EParams
Synthesis.cpp :
enum EParams { mWaveform = 0, mAttack, mDecay, mSustain, mRelease, mFilterMode, mFilterCutoff, mFilterResonance, mFilterAttack, mFilterDecay, mFilterSustain, mFilterRelease, mFilterEnvelopeAmount, kNumParams };
:
pGraphics->AttachControl(new ISwitchControl(this, 24, 38, mWaveform, &waveformBitmap));
. (Lowpass, Highpass, Bandpass).
AttachGraphics(pGraphics)
:
GetParam(mFilterMode)->InitEnum("Filter Mode", Filter::FILTER_MODE_LOWPASS, Filter::kNumFilterModes); IBitmap filtermodeBitmap = pGraphics->LoadIBitmap(FILTERMODE_ID, FILTERMODE_FN, 3); pGraphics->AttachControl(new ISwitchControl(this, 24, 123, mFilterMode, &filtermodeBitmap));
. . knob_small.png :
// Knobs for filter cutoff and resonance IBitmap smallKnobBitmap = pGraphics->LoadIBitmap(KNOB_SMALL_ID, KNOB_SMALL_FN, 64); // Cutoff knob: GetParam(mFilterCutoff)->InitDouble("Cutoff", 0.99, 0.01, 0.99, 0.001); GetParam(mFilterCutoff)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 5, 177, mFilterCutoff, &smallKnobBitmap)); // Resonance knob: GetParam(mFilterResonance)->InitDouble("Resonance", 0.01, 0.01, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 61, 177, mFilterResonance, &smallKnobBitmap));
,
1.0
,
calculateFeedbackAmount
, .
ProcessDoubleReplacing
:
leftOutput[i] = rightOutput[i] = mFilter.process(mOscillator.nextSample() * mEnvelopeGenerator.nextSample() * velocity / 127.0);
.
switch
Synthesis::OnParamChange
:
case mFilterCutoff: mFilter.setCutoff(GetParam(paramIdx)->Value()); break; case mFilterResonance: mFilter.setResonance(GetParam(paramIdx)->Value()); break; case mFilterMode: mFilter.setFilterMode(static_cast<Filter::FilterMode>(GetParam(paramIdx)->Int())); break;
. - .
— . , , .
:
buf0 += cutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1));
, , (
buf0 – buf1
),
feedbackAmount
.
calculateFeedbackAmount
feedbackAmount
resonance
, . . , .
, . , , . , , .
-12 / -24 /
! 24 .
switch
Filter::process
:
buf2 += cutoff * (buf1 - buf2); buf3 += cutoff * (buf2 – buf3);
: , , . , , , ( ).
switch
:
switch (mode) { case FILTER_MODE_LOWPASS: return buf3; case FILTER_MODE_HIGHPASS: return inputValue - buf3; case FILTER_MODE_BANDPASS: return buf0 - buf3; default: return 0.0; }
buf1
buf3
– . -24 /.
buf2
buf3
,
private
Filter.h :
double buf2; double buf3;
,
buf0
c
buf1
:
Filter() : // ... buf0(0.0), buf1(0.0), buf2(0.0), buf3(0.0) // ...
, , : . .
?
. , , .
-
cutoff
. .
cutoffMod
, ,
cutoff
.
#include private
:
double cutoffMod;
:
Filter() : cutoff(0.99), resonance(0.01), cutoffMod(0.0), // ...
. private
:
inline double getCalculatedCutoff() const { return fmax(fmin(cutoff + cutoffMod, 0.99), 0.01); };
calculateFeedbackAmount
:
inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - getCalculatedCutoff()); }
cutoffMod
. feedbackAmount
, :
inline void setCutoffMod(double newCutoffMod) { cutoffMod = newCutoffMod; calculateFeedbackAmount(); }
, . Filter::process
:
if (inputValue == 0.0) return inputValue; double calculatedCutoff = getCalculatedCutoff(); buf0 += calculatedCutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1)); buf1 += calculatedCutoff * (buf0 - buf1); buf2 += calculatedCutoff * (buf1 - buf2); buf3 += calculatedCutoff * (buf2 – buf3);
, . , , . . cutoff
calculatedCutoff
-.
, ( setCutoffMod
), Synthesis
, , . , cutoffMod
. filterEnvelopeAmount
-1
+1
. GUI.
private
Synthesis.h :
EnvelopeGenerator mFilterEnvelopeGenerator; double filterEnvelopeAmount;
MIDI , onNoteOn
onNoteOff
:
inline void onNoteOn(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); }; inline void onNoteOff(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); };
. ProcessDoubleReplacing
:
mFilter.setCutoffMod(mFilterEnvelopeGenerator.nextSample() * filterEnvelopeAmount);
, nextSample
filterEnvelopeAmount
cutoffMod
. filterEnvelopeAmount
:
Synthesis::Synthesis(IPlugInstanceInfo instanceInfo) : IPLUG_CTOR(kNumParams, kNumPrograms, instanceInfo), lastVirtualKeyboardNoteNumber(virtualKeyboardMinimumNoteNumber - 1), filterEnvelopeAmount(0.0) { // ... }
Synthesis::Reset
:
mFilterEnvelopeGenerator.setSampleRate(GetSampleRate());
EParams
, . AttachGraphics
:
// Knobs for filter envelope // Attack knob GetParam(mFilterAttack)->InitDouble("Filter Env Attack", 0.01, 0.01, 10.0, 0.001); GetParam(mFilterAttack)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 139, 178, mFilterAttack, &smallKnobBitmap)); // Decay knob: GetParam(mFilterDecay)->InitDouble("Filter Env Decay", 0.5, 0.01, 15.0, 0.001); GetParam(mFilterDecay)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 195, 178, mFilterDecay, &smallKnobBitmap)); // Sustain knob: GetParam(mFilterSustain)->InitDouble("Filter Env Sustain", 0.1, 0.001, 1.0, 0.001); GetParam(mFilterSustain)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 251, 178, mFilterSustain, &smallKnobBitmap)); // Release knob: GetParam(mFilterRelease)->InitDouble("Filter Env Release", 1.0, 0.001, 15.0, 0.001); GetParam(mFilterRelease)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 307, 178, mFilterRelease, &smallKnobBitmap)); // Filter envelope amount knob: GetParam(mFilterEnvelopeAmount)->InitDouble("Filter Env Amount", 0.0, -1.0, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 363, 178, mFilterEnvelopeAmount, &smallKnobBitmap));
, smallKnobBitmap
. . , . Synthesis::OnParamChange
switch
:
case mFilterAttack: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK, GetParam(paramIdx)->Value()); break; case mFilterDecay: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_DECAY, GetParam(paramIdx)->Value()); break; case mFilterSustain: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_SUSTAIN, GetParam(paramIdx)->Value()); break; case mFilterRelease: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE, GetParam(paramIdx)->Value()); break; case mFilterEnvelopeAmount: filterEnvelopeAmount = GetParam(paramIdx)->Value(); break;
!
! (- C1), :
EnvelopeGenerator
. . !
.
:)
:
martin-finke.de/blog/articles/audio-plugins-013-filter
#include Filter.h ( ) :
class Filter { public: enum FilterMode { FILTER_MODE_LOWPASS = 0, FILTER_MODE_HIGHPASS, FILTER_MODE_BANDPASS, kNumFilterModes }; Filter() : cutoff(0.99), resonance(0.0), mode(FILTER_MODE_LOWPASS), buf0(0.0), buf1(0.0) { calculateFeedbackAmount(); }; double process(double inputValue); inline void setCutoff(double newCutoff) { cutoff = newCutoff; calculateFeedbackAmount(); }; inline void setResonance(double newResonance) { resonance = newResonance; calculateFeedbackAmount(); }; inline void setFilterMode(FilterMode newMode) { mode = newMode; } private: double cutoff; double resonance; FilterMode mode; double feedbackAmount; inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - cutoff); } double buf0; double buf1; };
private
cutoff
resonance
. Mode
(Lowpass, Highpass, Bandpass). feedbackAmount
, buf0
buf1
, .
( calculateFeedbackAmount()
). process
. feedbackAmount
cutoff
resonance
, calculateFeedbackAmount
.
Filter.cpp :
// By Paul Kellett // http://www.musicdsp.org/showone.php?id=29 double Filter::process(double inputValue) { buf0 += cutoff * (inputValue - buf0); buf1 += cutoff * (buf0 - buf1); switch (mode) { case FILTER_MODE_LOWPASS: return buf1; case FILTER_MODE_HIGHPASS: return inputValue - buf0; case FILTER_MODE_BANDPASS: return buf0 - buf1; default: return 0.0; } }
, ? , . « » , (. . 6 ). buf0
buf1
: . inputValue
, - buf0
( ). . 6 / 12 /. switch
, buf1
. , , buf0
, 12, 6 /, .
case
FILTER_MODE_HIGHPASS
. buf0
. inputValue
buf0
, . buf1
, .
case
FILTER_MODE_BANDPASS
, buf0 - buf1
. buf0
, buf1
. , .
: , .
buf1
. ( Infinite Impulse Response , IIR ). , . , , .
, , . , , , , . , NI Massive, « ». , , . , ( Massive ). «» , , , AAS Ultra Analog, , , , . , , , , NI Reaktor. , — , .. .
. GUI. bg.png ( “Move to trash” Xcode), :
filtermode.png knob_small.png ( , 50 50 ) bg.png ( )
ID resource.h :
// Unique IDs for each image resource. #define BG_ID 101 #define WHITE_KEY_ID 102 #define BLACK_KEY_ID 103 #define WAVEFORM_ID 104 #define KNOB_ID 105 #define KNOB_SMALL_ID 106 #define FILTERMODE_ID 107 // Image resource locations for this plug. #define BG_FN "resources/img/bg.png" #define WHITE_KEY_FN "resources/img/whitekey.png" #define BLACK_KEY_FN "resources/img/blackkey.png" #define WAVEFORM_FN "resources/img/waveform.png" #define KNOB_FN "resources/img/knob.png" #define KNOB_SMALL_FN "resources/img/knob_small.png" #define FILTERMODE_FN "resources/img/filtermode.png"
Synthesis.rc :
#include "resource.h" BG_ID PNG BG_FN WHITE_KEY_ID PNG WHITE_KEY_FN BLACK_KEY_ID PNG BLACK_KEY_FN WAVEFORM_ID PNG WAVEFORM_FN KNOB_ID PNG KNOB_FN KNOB_SMALL_ID PNG KNOB_SMALL_FN FILTERMODE_ID PNG FILTERMODE_FN
#include "Filter.h"
Synthesis.h private
:
Filter mFilter;
EParams
Synthesis.cpp :
enum EParams { mWaveform = 0, mAttack, mDecay, mSustain, mRelease, mFilterMode, mFilterCutoff, mFilterResonance, mFilterAttack, mFilterDecay, mFilterSustain, mFilterRelease, mFilterEnvelopeAmount, kNumParams };
:
pGraphics->AttachControl(new ISwitchControl(this, 24, 38, mWaveform, &waveformBitmap));
. (Lowpass, Highpass, Bandpass). AttachGraphics(pGraphics)
:
GetParam(mFilterMode)->InitEnum("Filter Mode", Filter::FILTER_MODE_LOWPASS, Filter::kNumFilterModes); IBitmap filtermodeBitmap = pGraphics->LoadIBitmap(FILTERMODE_ID, FILTERMODE_FN, 3); pGraphics->AttachControl(new ISwitchControl(this, 24, 123, mFilterMode, &filtermodeBitmap));
. . knob_small.png :
// Knobs for filter cutoff and resonance IBitmap smallKnobBitmap = pGraphics->LoadIBitmap(KNOB_SMALL_ID, KNOB_SMALL_FN, 64); // Cutoff knob: GetParam(mFilterCutoff)->InitDouble("Cutoff", 0.99, 0.01, 0.99, 0.001); GetParam(mFilterCutoff)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 5, 177, mFilterCutoff, &smallKnobBitmap)); // Resonance knob: GetParam(mFilterResonance)->InitDouble("Resonance", 0.01, 0.01, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 61, 177, mFilterResonance, &smallKnobBitmap));
, 1.0
, calculateFeedbackAmount
, .
ProcessDoubleReplacing
:
leftOutput[i] = rightOutput[i] = mFilter.process(mOscillator.nextSample() * mEnvelopeGenerator.nextSample() * velocity / 127.0);
. switch
Synthesis::OnParamChange
:
case mFilterCutoff: mFilter.setCutoff(GetParam(paramIdx)->Value()); break; case mFilterResonance: mFilter.setResonance(GetParam(paramIdx)->Value()); break; case mFilterMode: mFilter.setFilterMode(static_cast<Filter::FilterMode>(GetParam(paramIdx)->Int())); break;
. - .
— . , , .
:
buf0 += cutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1));
, , ( buf0 – buf1
), feedbackAmount
. calculateFeedbackAmount
feedbackAmount
resonance
, . . , .
, . , , . , , .
-12 / -24 /
! 24 . switch
Filter::process
:
buf2 += cutoff * (buf1 - buf2); buf3 += cutoff * (buf2 – buf3);
: , , . , , , ( ). switch
:
switch (mode) { case FILTER_MODE_LOWPASS: return buf3; case FILTER_MODE_HIGHPASS: return inputValue - buf3; case FILTER_MODE_BANDPASS: return buf0 - buf3; default: return 0.0; }
buf1
buf3
– . -24 /. buf2
buf3
, private
Filter.h :
double buf2; double buf3;
, buf0
c buf1
:
Filter() : // ... buf0(0.0), buf1(0.0), buf2(0.0), buf3(0.0) // ...
, , : . .
?
. , , .
- cutoff
. . cutoffMod
, , cutoff
. #include private
:
double cutoffMod;
:
Filter() : cutoff(0.99), resonance(0.01), cutoffMod(0.0), // ...
. private
:
inline double getCalculatedCutoff() const { return fmax(fmin(cutoff + cutoffMod, 0.99), 0.01); };
calculateFeedbackAmount
:
inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - getCalculatedCutoff()); }
cutoffMod
. feedbackAmount
, :
inline void setCutoffMod(double newCutoffMod) { cutoffMod = newCutoffMod; calculateFeedbackAmount(); }
, . Filter::process
:
if (inputValue == 0.0) return inputValue; double calculatedCutoff = getCalculatedCutoff(); buf0 += calculatedCutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1)); buf1 += calculatedCutoff * (buf0 - buf1); buf2 += calculatedCutoff * (buf1 - buf2); buf3 += calculatedCutoff * (buf2 – buf3);
, . , , . . cutoff
calculatedCutoff
-.
, ( setCutoffMod
), Synthesis
, , . , cutoffMod
. filterEnvelopeAmount
-1
+1
. GUI.
private
Synthesis.h :
EnvelopeGenerator mFilterEnvelopeGenerator; double filterEnvelopeAmount;
MIDI , onNoteOn
onNoteOff
:
inline void onNoteOn(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); }; inline void onNoteOff(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); };
. ProcessDoubleReplacing
:
mFilter.setCutoffMod(mFilterEnvelopeGenerator.nextSample() * filterEnvelopeAmount);
, nextSample
filterEnvelopeAmount
cutoffMod
. filterEnvelopeAmount
:
Synthesis::Synthesis(IPlugInstanceInfo instanceInfo) : IPLUG_CTOR(kNumParams, kNumPrograms, instanceInfo), lastVirtualKeyboardNoteNumber(virtualKeyboardMinimumNoteNumber - 1), filterEnvelopeAmount(0.0) { // ... }
Synthesis::Reset
:
mFilterEnvelopeGenerator.setSampleRate(GetSampleRate());
EParams
, . AttachGraphics
:
// Knobs for filter envelope // Attack knob GetParam(mFilterAttack)->InitDouble("Filter Env Attack", 0.01, 0.01, 10.0, 0.001); GetParam(mFilterAttack)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 139, 178, mFilterAttack, &smallKnobBitmap)); // Decay knob: GetParam(mFilterDecay)->InitDouble("Filter Env Decay", 0.5, 0.01, 15.0, 0.001); GetParam(mFilterDecay)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 195, 178, mFilterDecay, &smallKnobBitmap)); // Sustain knob: GetParam(mFilterSustain)->InitDouble("Filter Env Sustain", 0.1, 0.001, 1.0, 0.001); GetParam(mFilterSustain)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 251, 178, mFilterSustain, &smallKnobBitmap)); // Release knob: GetParam(mFilterRelease)->InitDouble("Filter Env Release", 1.0, 0.001, 15.0, 0.001); GetParam(mFilterRelease)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 307, 178, mFilterRelease, &smallKnobBitmap)); // Filter envelope amount knob: GetParam(mFilterEnvelopeAmount)->InitDouble("Filter Env Amount", 0.0, -1.0, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 363, 178, mFilterEnvelopeAmount, &smallKnobBitmap));
, smallKnobBitmap
. . , . Synthesis::OnParamChange
switch
:
case mFilterAttack: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK, GetParam(paramIdx)->Value()); break; case mFilterDecay: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_DECAY, GetParam(paramIdx)->Value()); break; case mFilterSustain: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_SUSTAIN, GetParam(paramIdx)->Value()); break; case mFilterRelease: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE, GetParam(paramIdx)->Value()); break; case mFilterEnvelopeAmount: filterEnvelopeAmount = GetParam(paramIdx)->Value(); break;
!
! (- C1), :
EnvelopeGenerator
. . !
.
:)
:
martin-finke.de/blog/articles/audio-plugins-013-filter
#include Filter.h ( ) :
class Filter { public: enum FilterMode { FILTER_MODE_LOWPASS = 0, FILTER_MODE_HIGHPASS, FILTER_MODE_BANDPASS, kNumFilterModes }; Filter() : cutoff(0.99), resonance(0.0), mode(FILTER_MODE_LOWPASS), buf0(0.0), buf1(0.0) { calculateFeedbackAmount(); }; double process(double inputValue); inline void setCutoff(double newCutoff) { cutoff = newCutoff; calculateFeedbackAmount(); }; inline void setResonance(double newResonance) { resonance = newResonance; calculateFeedbackAmount(); }; inline void setFilterMode(FilterMode newMode) { mode = newMode; } private: double cutoff; double resonance; FilterMode mode; double feedbackAmount; inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - cutoff); } double buf0; double buf1; };
private
cutoff
resonance
. Mode
(Lowpass, Highpass, Bandpass). feedbackAmount
, buf0
buf1
, .
( calculateFeedbackAmount()
). process
. feedbackAmount
cutoff
resonance
, calculateFeedbackAmount
.
Filter.cpp :
// By Paul Kellett // http://www.musicdsp.org/showone.php?id=29 double Filter::process(double inputValue) { buf0 += cutoff * (inputValue - buf0); buf1 += cutoff * (buf0 - buf1); switch (mode) { case FILTER_MODE_LOWPASS: return buf1; case FILTER_MODE_HIGHPASS: return inputValue - buf0; case FILTER_MODE_BANDPASS: return buf0 - buf1; default: return 0.0; } }
, ? , . « » , (. . 6 ). buf0
buf1
: . inputValue
, - buf0
( ). . 6 / 12 /. switch
, buf1
. , , buf0
, 12, 6 /, .
case
FILTER_MODE_HIGHPASS
. buf0
. inputValue
buf0
, . buf1
, .
case
FILTER_MODE_BANDPASS
, buf0 - buf1
. buf0
, buf1
. , .
: , .
buf1
. ( Infinite Impulse Response , IIR ). , . , , .
, , . , , , , . , NI Massive, « ». , , . , ( Massive ). «» , , , AAS Ultra Analog, , , , . , , , , NI Reaktor. , — , .. .
. GUI. bg.png ( “Move to trash” Xcode), :
filtermode.png knob_small.png ( , 50 50 ) bg.png ( )
ID resource.h :
// Unique IDs for each image resource. #define BG_ID 101 #define WHITE_KEY_ID 102 #define BLACK_KEY_ID 103 #define WAVEFORM_ID 104 #define KNOB_ID 105 #define KNOB_SMALL_ID 106 #define FILTERMODE_ID 107 // Image resource locations for this plug. #define BG_FN "resources/img/bg.png" #define WHITE_KEY_FN "resources/img/whitekey.png" #define BLACK_KEY_FN "resources/img/blackkey.png" #define WAVEFORM_FN "resources/img/waveform.png" #define KNOB_FN "resources/img/knob.png" #define KNOB_SMALL_FN "resources/img/knob_small.png" #define FILTERMODE_FN "resources/img/filtermode.png"
Synthesis.rc :
#include "resource.h" BG_ID PNG BG_FN WHITE_KEY_ID PNG WHITE_KEY_FN BLACK_KEY_ID PNG BLACK_KEY_FN WAVEFORM_ID PNG WAVEFORM_FN KNOB_ID PNG KNOB_FN KNOB_SMALL_ID PNG KNOB_SMALL_FN FILTERMODE_ID PNG FILTERMODE_FN
#include "Filter.h"
Synthesis.h private
:
Filter mFilter;
EParams
Synthesis.cpp :
enum EParams { mWaveform = 0, mAttack, mDecay, mSustain, mRelease, mFilterMode, mFilterCutoff, mFilterResonance, mFilterAttack, mFilterDecay, mFilterSustain, mFilterRelease, mFilterEnvelopeAmount, kNumParams };
:
pGraphics->AttachControl(new ISwitchControl(this, 24, 38, mWaveform, &waveformBitmap));
. (Lowpass, Highpass, Bandpass). AttachGraphics(pGraphics)
:
GetParam(mFilterMode)->InitEnum("Filter Mode", Filter::FILTER_MODE_LOWPASS, Filter::kNumFilterModes); IBitmap filtermodeBitmap = pGraphics->LoadIBitmap(FILTERMODE_ID, FILTERMODE_FN, 3); pGraphics->AttachControl(new ISwitchControl(this, 24, 123, mFilterMode, &filtermodeBitmap));
. . knob_small.png :
// Knobs for filter cutoff and resonance IBitmap smallKnobBitmap = pGraphics->LoadIBitmap(KNOB_SMALL_ID, KNOB_SMALL_FN, 64); // Cutoff knob: GetParam(mFilterCutoff)->InitDouble("Cutoff", 0.99, 0.01, 0.99, 0.001); GetParam(mFilterCutoff)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 5, 177, mFilterCutoff, &smallKnobBitmap)); // Resonance knob: GetParam(mFilterResonance)->InitDouble("Resonance", 0.01, 0.01, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 61, 177, mFilterResonance, &smallKnobBitmap));
, 1.0
, calculateFeedbackAmount
, .
ProcessDoubleReplacing
:
leftOutput[i] = rightOutput[i] = mFilter.process(mOscillator.nextSample() * mEnvelopeGenerator.nextSample() * velocity / 127.0);
. switch
Synthesis::OnParamChange
:
case mFilterCutoff: mFilter.setCutoff(GetParam(paramIdx)->Value()); break; case mFilterResonance: mFilter.setResonance(GetParam(paramIdx)->Value()); break; case mFilterMode: mFilter.setFilterMode(static_cast<Filter::FilterMode>(GetParam(paramIdx)->Int())); break;
. - .
— . , , .
:
buf0 += cutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1));
, , ( buf0 – buf1
), feedbackAmount
. calculateFeedbackAmount
feedbackAmount
resonance
, . . , .
, . , , . , , .
-12 / -24 /
! 24 . switch
Filter::process
:
buf2 += cutoff * (buf1 - buf2); buf3 += cutoff * (buf2 – buf3);
: , , . , , , ( ). switch
:
switch (mode) { case FILTER_MODE_LOWPASS: return buf3; case FILTER_MODE_HIGHPASS: return inputValue - buf3; case FILTER_MODE_BANDPASS: return buf0 - buf3; default: return 0.0; }
buf1
buf3
– . -24 /. buf2
buf3
, private
Filter.h :
double buf2; double buf3;
, buf0
c buf1
:
Filter() : // ... buf0(0.0), buf1(0.0), buf2(0.0), buf3(0.0) // ...
, , : . .
?
. , , .
- cutoff
. . cutoffMod
, , cutoff
. #include private
:
double cutoffMod;
:
Filter() : cutoff(0.99), resonance(0.01), cutoffMod(0.0), // ...
. private
:
inline double getCalculatedCutoff() const { return fmax(fmin(cutoff + cutoffMod, 0.99), 0.01); };
calculateFeedbackAmount
:
inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - getCalculatedCutoff()); }
cutoffMod
. feedbackAmount
, :
inline void setCutoffMod(double newCutoffMod) { cutoffMod = newCutoffMod; calculateFeedbackAmount(); }
, . Filter::process
:
if (inputValue == 0.0) return inputValue; double calculatedCutoff = getCalculatedCutoff(); buf0 += calculatedCutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1)); buf1 += calculatedCutoff * (buf0 - buf1); buf2 += calculatedCutoff * (buf1 - buf2); buf3 += calculatedCutoff * (buf2 – buf3);
, . , , . . cutoff
calculatedCutoff
-.
, ( setCutoffMod
), Synthesis
, , . , cutoffMod
. filterEnvelopeAmount
-1
+1
. GUI.
private
Synthesis.h :
EnvelopeGenerator mFilterEnvelopeGenerator; double filterEnvelopeAmount;
MIDI , onNoteOn
onNoteOff
:
inline void onNoteOn(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); }; inline void onNoteOff(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); };
. ProcessDoubleReplacing
:
mFilter.setCutoffMod(mFilterEnvelopeGenerator.nextSample() * filterEnvelopeAmount);
, nextSample
filterEnvelopeAmount
cutoffMod
. filterEnvelopeAmount
:
Synthesis::Synthesis(IPlugInstanceInfo instanceInfo) : IPLUG_CTOR(kNumParams, kNumPrograms, instanceInfo), lastVirtualKeyboardNoteNumber(virtualKeyboardMinimumNoteNumber - 1), filterEnvelopeAmount(0.0) { // ... }
Synthesis::Reset
:
mFilterEnvelopeGenerator.setSampleRate(GetSampleRate());
EParams
, . AttachGraphics
:
// Knobs for filter envelope // Attack knob GetParam(mFilterAttack)->InitDouble("Filter Env Attack", 0.01, 0.01, 10.0, 0.001); GetParam(mFilterAttack)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 139, 178, mFilterAttack, &smallKnobBitmap)); // Decay knob: GetParam(mFilterDecay)->InitDouble("Filter Env Decay", 0.5, 0.01, 15.0, 0.001); GetParam(mFilterDecay)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 195, 178, mFilterDecay, &smallKnobBitmap)); // Sustain knob: GetParam(mFilterSustain)->InitDouble("Filter Env Sustain", 0.1, 0.001, 1.0, 0.001); GetParam(mFilterSustain)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 251, 178, mFilterSustain, &smallKnobBitmap)); // Release knob: GetParam(mFilterRelease)->InitDouble("Filter Env Release", 1.0, 0.001, 15.0, 0.001); GetParam(mFilterRelease)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 307, 178, mFilterRelease, &smallKnobBitmap)); // Filter envelope amount knob: GetParam(mFilterEnvelopeAmount)->InitDouble("Filter Env Amount", 0.0, -1.0, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 363, 178, mFilterEnvelopeAmount, &smallKnobBitmap));
, smallKnobBitmap
. . , . Synthesis::OnParamChange
switch
:
case mFilterAttack: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK, GetParam(paramIdx)->Value()); break; case mFilterDecay: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_DECAY, GetParam(paramIdx)->Value()); break; case mFilterSustain: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_SUSTAIN, GetParam(paramIdx)->Value()); break; case mFilterRelease: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE, GetParam(paramIdx)->Value()); break; case mFilterEnvelopeAmount: filterEnvelopeAmount = GetParam(paramIdx)->Value(); break;
!
! (- C1), :
EnvelopeGenerator
. . !
.
:)
:
martin-finke.de/blog/articles/audio-plugins-013-filter
#include Filter.h ( ) :
class Filter { public: enum FilterMode { FILTER_MODE_LOWPASS = 0, FILTER_MODE_HIGHPASS, FILTER_MODE_BANDPASS, kNumFilterModes }; Filter() : cutoff(0.99), resonance(0.0), mode(FILTER_MODE_LOWPASS), buf0(0.0), buf1(0.0) { calculateFeedbackAmount(); }; double process(double inputValue); inline void setCutoff(double newCutoff) { cutoff = newCutoff; calculateFeedbackAmount(); }; inline void setResonance(double newResonance) { resonance = newResonance; calculateFeedbackAmount(); }; inline void setFilterMode(FilterMode newMode) { mode = newMode; } private: double cutoff; double resonance; FilterMode mode; double feedbackAmount; inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - cutoff); } double buf0; double buf1; };
private
cutoff
resonance
. Mode
(Lowpass, Highpass, Bandpass). feedbackAmount
, buf0
buf1
, .
( calculateFeedbackAmount()
). process
. feedbackAmount
cutoff
resonance
, calculateFeedbackAmount
.
Filter.cpp :
// By Paul Kellett // http://www.musicdsp.org/showone.php?id=29 double Filter::process(double inputValue) { buf0 += cutoff * (inputValue - buf0); buf1 += cutoff * (buf0 - buf1); switch (mode) { case FILTER_MODE_LOWPASS: return buf1; case FILTER_MODE_HIGHPASS: return inputValue - buf0; case FILTER_MODE_BANDPASS: return buf0 - buf1; default: return 0.0; } }
, ? , . « » , (. . 6 ). buf0
buf1
: . inputValue
, - buf0
( ). . 6 / 12 /. switch
, buf1
. , , buf0
, 12, 6 /, .
case
FILTER_MODE_HIGHPASS
. buf0
. inputValue
buf0
, . buf1
, .
case
FILTER_MODE_BANDPASS
, buf0 - buf1
. buf0
, buf1
. , .
: , .
buf1
. ( Infinite Impulse Response , IIR ). , . , , .
, , . , , , , . , NI Massive, « ». , , . , ( Massive ). «» , , , AAS Ultra Analog, , , , . , , , , NI Reaktor. , — , .. .
. GUI. bg.png ( “Move to trash” Xcode), :
filtermode.png knob_small.png ( , 50 50 ) bg.png ( )
ID resource.h :
// Unique IDs for each image resource. #define BG_ID 101 #define WHITE_KEY_ID 102 #define BLACK_KEY_ID 103 #define WAVEFORM_ID 104 #define KNOB_ID 105 #define KNOB_SMALL_ID 106 #define FILTERMODE_ID 107 // Image resource locations for this plug. #define BG_FN "resources/img/bg.png" #define WHITE_KEY_FN "resources/img/whitekey.png" #define BLACK_KEY_FN "resources/img/blackkey.png" #define WAVEFORM_FN "resources/img/waveform.png" #define KNOB_FN "resources/img/knob.png" #define KNOB_SMALL_FN "resources/img/knob_small.png" #define FILTERMODE_FN "resources/img/filtermode.png"
Synthesis.rc :
#include "resource.h" BG_ID PNG BG_FN WHITE_KEY_ID PNG WHITE_KEY_FN BLACK_KEY_ID PNG BLACK_KEY_FN WAVEFORM_ID PNG WAVEFORM_FN KNOB_ID PNG KNOB_FN KNOB_SMALL_ID PNG KNOB_SMALL_FN FILTERMODE_ID PNG FILTERMODE_FN
#include "Filter.h"
Synthesis.h private
:
Filter mFilter;
EParams
Synthesis.cpp :
enum EParams { mWaveform = 0, mAttack, mDecay, mSustain, mRelease, mFilterMode, mFilterCutoff, mFilterResonance, mFilterAttack, mFilterDecay, mFilterSustain, mFilterRelease, mFilterEnvelopeAmount, kNumParams };
:
pGraphics->AttachControl(new ISwitchControl(this, 24, 38, mWaveform, &waveformBitmap));
. (Lowpass, Highpass, Bandpass). AttachGraphics(pGraphics)
:
GetParam(mFilterMode)->InitEnum("Filter Mode", Filter::FILTER_MODE_LOWPASS, Filter::kNumFilterModes); IBitmap filtermodeBitmap = pGraphics->LoadIBitmap(FILTERMODE_ID, FILTERMODE_FN, 3); pGraphics->AttachControl(new ISwitchControl(this, 24, 123, mFilterMode, &filtermodeBitmap));
. . knob_small.png :
// Knobs for filter cutoff and resonance IBitmap smallKnobBitmap = pGraphics->LoadIBitmap(KNOB_SMALL_ID, KNOB_SMALL_FN, 64); // Cutoff knob: GetParam(mFilterCutoff)->InitDouble("Cutoff", 0.99, 0.01, 0.99, 0.001); GetParam(mFilterCutoff)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 5, 177, mFilterCutoff, &smallKnobBitmap)); // Resonance knob: GetParam(mFilterResonance)->InitDouble("Resonance", 0.01, 0.01, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 61, 177, mFilterResonance, &smallKnobBitmap));
, 1.0
, calculateFeedbackAmount
, .
ProcessDoubleReplacing
:
leftOutput[i] = rightOutput[i] = mFilter.process(mOscillator.nextSample() * mEnvelopeGenerator.nextSample() * velocity / 127.0);
. switch
Synthesis::OnParamChange
:
case mFilterCutoff: mFilter.setCutoff(GetParam(paramIdx)->Value()); break; case mFilterResonance: mFilter.setResonance(GetParam(paramIdx)->Value()); break; case mFilterMode: mFilter.setFilterMode(static_cast<Filter::FilterMode>(GetParam(paramIdx)->Int())); break;
. - .
— . , , .
:
buf0 += cutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1));
, , ( buf0 – buf1
), feedbackAmount
. calculateFeedbackAmount
feedbackAmount
resonance
, . . , .
, . , , . , , .
-12 / -24 /
! 24 . switch
Filter::process
:
buf2 += cutoff * (buf1 - buf2); buf3 += cutoff * (buf2 – buf3);
: , , . , , , ( ). switch
:
switch (mode) { case FILTER_MODE_LOWPASS: return buf3; case FILTER_MODE_HIGHPASS: return inputValue - buf3; case FILTER_MODE_BANDPASS: return buf0 - buf3; default: return 0.0; }
buf1
buf3
– . -24 /. buf2
buf3
, private
Filter.h :
double buf2; double buf3;
, buf0
c buf1
:
Filter() : // ... buf0(0.0), buf1(0.0), buf2(0.0), buf3(0.0) // ...
, , : . .
?
. , , .
- cutoff
. . cutoffMod
, , cutoff
. #include private
:
double cutoffMod;
:
Filter() : cutoff(0.99), resonance(0.01), cutoffMod(0.0), // ...
. private
:
inline double getCalculatedCutoff() const { return fmax(fmin(cutoff + cutoffMod, 0.99), 0.01); };
calculateFeedbackAmount
:
inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - getCalculatedCutoff()); }
cutoffMod
. feedbackAmount
, :
inline void setCutoffMod(double newCutoffMod) { cutoffMod = newCutoffMod; calculateFeedbackAmount(); }
, . Filter::process
:
if (inputValue == 0.0) return inputValue; double calculatedCutoff = getCalculatedCutoff(); buf0 += calculatedCutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1)); buf1 += calculatedCutoff * (buf0 - buf1); buf2 += calculatedCutoff * (buf1 - buf2); buf3 += calculatedCutoff * (buf2 – buf3);
, . , , . . cutoff
calculatedCutoff
-.
, ( setCutoffMod
), Synthesis
, , . , cutoffMod
. filterEnvelopeAmount
-1
+1
. GUI.
private
Synthesis.h :
EnvelopeGenerator mFilterEnvelopeGenerator; double filterEnvelopeAmount;
MIDI , onNoteOn
onNoteOff
:
inline void onNoteOn(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); }; inline void onNoteOff(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); };
. ProcessDoubleReplacing
:
mFilter.setCutoffMod(mFilterEnvelopeGenerator.nextSample() * filterEnvelopeAmount);
, nextSample
filterEnvelopeAmount
cutoffMod
. filterEnvelopeAmount
:
Synthesis::Synthesis(IPlugInstanceInfo instanceInfo) : IPLUG_CTOR(kNumParams, kNumPrograms, instanceInfo), lastVirtualKeyboardNoteNumber(virtualKeyboardMinimumNoteNumber - 1), filterEnvelopeAmount(0.0) { // ... }
Synthesis::Reset
:
mFilterEnvelopeGenerator.setSampleRate(GetSampleRate());
EParams
, . AttachGraphics
:
// Knobs for filter envelope // Attack knob GetParam(mFilterAttack)->InitDouble("Filter Env Attack", 0.01, 0.01, 10.0, 0.001); GetParam(mFilterAttack)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 139, 178, mFilterAttack, &smallKnobBitmap)); // Decay knob: GetParam(mFilterDecay)->InitDouble("Filter Env Decay", 0.5, 0.01, 15.0, 0.001); GetParam(mFilterDecay)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 195, 178, mFilterDecay, &smallKnobBitmap)); // Sustain knob: GetParam(mFilterSustain)->InitDouble("Filter Env Sustain", 0.1, 0.001, 1.0, 0.001); GetParam(mFilterSustain)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 251, 178, mFilterSustain, &smallKnobBitmap)); // Release knob: GetParam(mFilterRelease)->InitDouble("Filter Env Release", 1.0, 0.001, 15.0, 0.001); GetParam(mFilterRelease)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 307, 178, mFilterRelease, &smallKnobBitmap)); // Filter envelope amount knob: GetParam(mFilterEnvelopeAmount)->InitDouble("Filter Env Amount", 0.0, -1.0, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 363, 178, mFilterEnvelopeAmount, &smallKnobBitmap));
, smallKnobBitmap
. . , . Synthesis::OnParamChange
switch
:
case mFilterAttack: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK, GetParam(paramIdx)->Value()); break; case mFilterDecay: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_DECAY, GetParam(paramIdx)->Value()); break; case mFilterSustain: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_SUSTAIN, GetParam(paramIdx)->Value()); break; case mFilterRelease: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE, GetParam(paramIdx)->Value()); break; case mFilterEnvelopeAmount: filterEnvelopeAmount = GetParam(paramIdx)->Value(); break;
!
! (- C1), :
EnvelopeGenerator
. . !
.
:)
:
martin-finke.de/blog/articles/audio-plugins-013-filter
#include Filter.h ( ) :
class Filter { public: enum FilterMode { FILTER_MODE_LOWPASS = 0, FILTER_MODE_HIGHPASS, FILTER_MODE_BANDPASS, kNumFilterModes }; Filter() : cutoff(0.99), resonance(0.0), mode(FILTER_MODE_LOWPASS), buf0(0.0), buf1(0.0) { calculateFeedbackAmount(); }; double process(double inputValue); inline void setCutoff(double newCutoff) { cutoff = newCutoff; calculateFeedbackAmount(); }; inline void setResonance(double newResonance) { resonance = newResonance; calculateFeedbackAmount(); }; inline void setFilterMode(FilterMode newMode) { mode = newMode; } private: double cutoff; double resonance; FilterMode mode; double feedbackAmount; inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - cutoff); } double buf0; double buf1; };
private
cutoff
resonance
. Mode
(Lowpass, Highpass, Bandpass). feedbackAmount
, buf0
buf1
, .
( calculateFeedbackAmount()
). process
. feedbackAmount
cutoff
resonance
, calculateFeedbackAmount
.
Filter.cpp :
// By Paul Kellett // http://www.musicdsp.org/showone.php?id=29 double Filter::process(double inputValue) { buf0 += cutoff * (inputValue - buf0); buf1 += cutoff * (buf0 - buf1); switch (mode) { case FILTER_MODE_LOWPASS: return buf1; case FILTER_MODE_HIGHPASS: return inputValue - buf0; case FILTER_MODE_BANDPASS: return buf0 - buf1; default: return 0.0; } }
, ? , . « » , (. . 6 ). buf0
buf1
: . inputValue
, - buf0
( ). . 6 / 12 /. switch
, buf1
. , , buf0
, 12, 6 /, .
case
FILTER_MODE_HIGHPASS
. buf0
. inputValue
buf0
, . buf1
, .
case
FILTER_MODE_BANDPASS
, buf0 - buf1
. buf0
, buf1
. , .
: , .
buf1
. ( Infinite Impulse Response , IIR ). , . , , .
, , . , , , , . , NI Massive, « ». , , . , ( Massive ). «» , , , AAS Ultra Analog, , , , . , , , , NI Reaktor. , — , .. .
. GUI. bg.png ( “Move to trash” Xcode), :
filtermode.png knob_small.png ( , 50 50 ) bg.png ( )
ID resource.h :
// Unique IDs for each image resource. #define BG_ID 101 #define WHITE_KEY_ID 102 #define BLACK_KEY_ID 103 #define WAVEFORM_ID 104 #define KNOB_ID 105 #define KNOB_SMALL_ID 106 #define FILTERMODE_ID 107 // Image resource locations for this plug. #define BG_FN "resources/img/bg.png" #define WHITE_KEY_FN "resources/img/whitekey.png" #define BLACK_KEY_FN "resources/img/blackkey.png" #define WAVEFORM_FN "resources/img/waveform.png" #define KNOB_FN "resources/img/knob.png" #define KNOB_SMALL_FN "resources/img/knob_small.png" #define FILTERMODE_FN "resources/img/filtermode.png"
Synthesis.rc :
#include "resource.h" BG_ID PNG BG_FN WHITE_KEY_ID PNG WHITE_KEY_FN BLACK_KEY_ID PNG BLACK_KEY_FN WAVEFORM_ID PNG WAVEFORM_FN KNOB_ID PNG KNOB_FN KNOB_SMALL_ID PNG KNOB_SMALL_FN FILTERMODE_ID PNG FILTERMODE_FN
#include "Filter.h"
Synthesis.h private
:
Filter mFilter;
EParams
Synthesis.cpp :
enum EParams { mWaveform = 0, mAttack, mDecay, mSustain, mRelease, mFilterMode, mFilterCutoff, mFilterResonance, mFilterAttack, mFilterDecay, mFilterSustain, mFilterRelease, mFilterEnvelopeAmount, kNumParams };
:
pGraphics->AttachControl(new ISwitchControl(this, 24, 38, mWaveform, &waveformBitmap));
. (Lowpass, Highpass, Bandpass). AttachGraphics(pGraphics)
:
GetParam(mFilterMode)->InitEnum("Filter Mode", Filter::FILTER_MODE_LOWPASS, Filter::kNumFilterModes); IBitmap filtermodeBitmap = pGraphics->LoadIBitmap(FILTERMODE_ID, FILTERMODE_FN, 3); pGraphics->AttachControl(new ISwitchControl(this, 24, 123, mFilterMode, &filtermodeBitmap));
. . knob_small.png :
// Knobs for filter cutoff and resonance IBitmap smallKnobBitmap = pGraphics->LoadIBitmap(KNOB_SMALL_ID, KNOB_SMALL_FN, 64); // Cutoff knob: GetParam(mFilterCutoff)->InitDouble("Cutoff", 0.99, 0.01, 0.99, 0.001); GetParam(mFilterCutoff)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 5, 177, mFilterCutoff, &smallKnobBitmap)); // Resonance knob: GetParam(mFilterResonance)->InitDouble("Resonance", 0.01, 0.01, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 61, 177, mFilterResonance, &smallKnobBitmap));
, 1.0
, calculateFeedbackAmount
, .
ProcessDoubleReplacing
:
leftOutput[i] = rightOutput[i] = mFilter.process(mOscillator.nextSample() * mEnvelopeGenerator.nextSample() * velocity / 127.0);
. switch
Synthesis::OnParamChange
:
case mFilterCutoff: mFilter.setCutoff(GetParam(paramIdx)->Value()); break; case mFilterResonance: mFilter.setResonance(GetParam(paramIdx)->Value()); break; case mFilterMode: mFilter.setFilterMode(static_cast<Filter::FilterMode>(GetParam(paramIdx)->Int())); break;
. - .
— . , , .
:
buf0 += cutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1));
, , ( buf0 – buf1
), feedbackAmount
. calculateFeedbackAmount
feedbackAmount
resonance
, . . , .
, . , , . , , .
-12 / -24 /
! 24 . switch
Filter::process
:
buf2 += cutoff * (buf1 - buf2); buf3 += cutoff * (buf2 – buf3);
: , , . , , , ( ). switch
:
switch (mode) { case FILTER_MODE_LOWPASS: return buf3; case FILTER_MODE_HIGHPASS: return inputValue - buf3; case FILTER_MODE_BANDPASS: return buf0 - buf3; default: return 0.0; }
buf1
buf3
– . -24 /. buf2
buf3
, private
Filter.h :
double buf2; double buf3;
, buf0
c buf1
:
Filter() : // ... buf0(0.0), buf1(0.0), buf2(0.0), buf3(0.0) // ...
, , : . .
?
. , , .
- cutoff
. . cutoffMod
, , cutoff
. #include private
:
double cutoffMod;
:
Filter() : cutoff(0.99), resonance(0.01), cutoffMod(0.0), // ...
. private
:
inline double getCalculatedCutoff() const { return fmax(fmin(cutoff + cutoffMod, 0.99), 0.01); };
calculateFeedbackAmount
:
inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - getCalculatedCutoff()); }
cutoffMod
. feedbackAmount
, :
inline void setCutoffMod(double newCutoffMod) { cutoffMod = newCutoffMod; calculateFeedbackAmount(); }
, . Filter::process
:
if (inputValue == 0.0) return inputValue; double calculatedCutoff = getCalculatedCutoff(); buf0 += calculatedCutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1)); buf1 += calculatedCutoff * (buf0 - buf1); buf2 += calculatedCutoff * (buf1 - buf2); buf3 += calculatedCutoff * (buf2 – buf3);
, . , , . . cutoff
calculatedCutoff
-.
, ( setCutoffMod
), Synthesis
, , . , cutoffMod
. filterEnvelopeAmount
-1
+1
. GUI.
private
Synthesis.h :
EnvelopeGenerator mFilterEnvelopeGenerator; double filterEnvelopeAmount;
MIDI , onNoteOn
onNoteOff
:
inline void onNoteOn(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); }; inline void onNoteOff(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); };
. ProcessDoubleReplacing
:
mFilter.setCutoffMod(mFilterEnvelopeGenerator.nextSample() * filterEnvelopeAmount);
, nextSample
filterEnvelopeAmount
cutoffMod
. filterEnvelopeAmount
:
Synthesis::Synthesis(IPlugInstanceInfo instanceInfo) : IPLUG_CTOR(kNumParams, kNumPrograms, instanceInfo), lastVirtualKeyboardNoteNumber(virtualKeyboardMinimumNoteNumber - 1), filterEnvelopeAmount(0.0) { // ... }
Synthesis::Reset
:
mFilterEnvelopeGenerator.setSampleRate(GetSampleRate());
EParams
, . AttachGraphics
:
// Knobs for filter envelope // Attack knob GetParam(mFilterAttack)->InitDouble("Filter Env Attack", 0.01, 0.01, 10.0, 0.001); GetParam(mFilterAttack)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 139, 178, mFilterAttack, &smallKnobBitmap)); // Decay knob: GetParam(mFilterDecay)->InitDouble("Filter Env Decay", 0.5, 0.01, 15.0, 0.001); GetParam(mFilterDecay)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 195, 178, mFilterDecay, &smallKnobBitmap)); // Sustain knob: GetParam(mFilterSustain)->InitDouble("Filter Env Sustain", 0.1, 0.001, 1.0, 0.001); GetParam(mFilterSustain)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 251, 178, mFilterSustain, &smallKnobBitmap)); // Release knob: GetParam(mFilterRelease)->InitDouble("Filter Env Release", 1.0, 0.001, 15.0, 0.001); GetParam(mFilterRelease)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 307, 178, mFilterRelease, &smallKnobBitmap)); // Filter envelope amount knob: GetParam(mFilterEnvelopeAmount)->InitDouble("Filter Env Amount", 0.0, -1.0, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 363, 178, mFilterEnvelopeAmount, &smallKnobBitmap));
, smallKnobBitmap
. . , . Synthesis::OnParamChange
switch
:
case mFilterAttack: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK, GetParam(paramIdx)->Value()); break; case mFilterDecay: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_DECAY, GetParam(paramIdx)->Value()); break; case mFilterSustain: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_SUSTAIN, GetParam(paramIdx)->Value()); break; case mFilterRelease: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE, GetParam(paramIdx)->Value()); break; case mFilterEnvelopeAmount: filterEnvelopeAmount = GetParam(paramIdx)->Value(); break;
!
! (- C1), :
EnvelopeGenerator
. . !
.
:)
:
martin-finke.de/blog/articles/audio-plugins-013-filter
#include Filter.h ( ) :
class Filter { public: enum FilterMode { FILTER_MODE_LOWPASS = 0, FILTER_MODE_HIGHPASS, FILTER_MODE_BANDPASS, kNumFilterModes }; Filter() : cutoff(0.99), resonance(0.0), mode(FILTER_MODE_LOWPASS), buf0(0.0), buf1(0.0) { calculateFeedbackAmount(); }; double process(double inputValue); inline void setCutoff(double newCutoff) { cutoff = newCutoff; calculateFeedbackAmount(); }; inline void setResonance(double newResonance) { resonance = newResonance; calculateFeedbackAmount(); }; inline void setFilterMode(FilterMode newMode) { mode = newMode; } private: double cutoff; double resonance; FilterMode mode; double feedbackAmount; inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - cutoff); } double buf0; double buf1; };
private
cutoff
resonance
. Mode
(Lowpass, Highpass, Bandpass). feedbackAmount
, buf0
buf1
, .
( calculateFeedbackAmount()
). process
. feedbackAmount
cutoff
resonance
, calculateFeedbackAmount
.
Filter.cpp :
// By Paul Kellett // http://www.musicdsp.org/showone.php?id=29 double Filter::process(double inputValue) { buf0 += cutoff * (inputValue - buf0); buf1 += cutoff * (buf0 - buf1); switch (mode) { case FILTER_MODE_LOWPASS: return buf1; case FILTER_MODE_HIGHPASS: return inputValue - buf0; case FILTER_MODE_BANDPASS: return buf0 - buf1; default: return 0.0; } }
, ? , . « » , (. . 6 ). buf0
buf1
: . inputValue
, - buf0
( ). . 6 / 12 /. switch
, buf1
. , , buf0
, 12, 6 /, .
case
FILTER_MODE_HIGHPASS
. buf0
. inputValue
buf0
, . buf1
, .
case
FILTER_MODE_BANDPASS
, buf0 - buf1
. buf0
, buf1
. , .
: , .
buf1
. ( Infinite Impulse Response , IIR ). , . , , .
, , . , , , , . , NI Massive, « ». , , . , ( Massive ). «» , , , AAS Ultra Analog, , , , . , , , , NI Reaktor. , — , .. .
. GUI. bg.png ( “Move to trash” Xcode), :
filtermode.png knob_small.png ( , 50 50 ) bg.png ( )
ID resource.h :
// Unique IDs for each image resource. #define BG_ID 101 #define WHITE_KEY_ID 102 #define BLACK_KEY_ID 103 #define WAVEFORM_ID 104 #define KNOB_ID 105 #define KNOB_SMALL_ID 106 #define FILTERMODE_ID 107 // Image resource locations for this plug. #define BG_FN "resources/img/bg.png" #define WHITE_KEY_FN "resources/img/whitekey.png" #define BLACK_KEY_FN "resources/img/blackkey.png" #define WAVEFORM_FN "resources/img/waveform.png" #define KNOB_FN "resources/img/knob.png" #define KNOB_SMALL_FN "resources/img/knob_small.png" #define FILTERMODE_FN "resources/img/filtermode.png"
Synthesis.rc :
#include "resource.h" BG_ID PNG BG_FN WHITE_KEY_ID PNG WHITE_KEY_FN BLACK_KEY_ID PNG BLACK_KEY_FN WAVEFORM_ID PNG WAVEFORM_FN KNOB_ID PNG KNOB_FN KNOB_SMALL_ID PNG KNOB_SMALL_FN FILTERMODE_ID PNG FILTERMODE_FN
#include "Filter.h"
Synthesis.h private
:
Filter mFilter;
EParams
Synthesis.cpp :
enum EParams { mWaveform = 0, mAttack, mDecay, mSustain, mRelease, mFilterMode, mFilterCutoff, mFilterResonance, mFilterAttack, mFilterDecay, mFilterSustain, mFilterRelease, mFilterEnvelopeAmount, kNumParams };
:
pGraphics->AttachControl(new ISwitchControl(this, 24, 38, mWaveform, &waveformBitmap));
. (Lowpass, Highpass, Bandpass). AttachGraphics(pGraphics)
:
GetParam(mFilterMode)->InitEnum("Filter Mode", Filter::FILTER_MODE_LOWPASS, Filter::kNumFilterModes); IBitmap filtermodeBitmap = pGraphics->LoadIBitmap(FILTERMODE_ID, FILTERMODE_FN, 3); pGraphics->AttachControl(new ISwitchControl(this, 24, 123, mFilterMode, &filtermodeBitmap));
. . knob_small.png :
// Knobs for filter cutoff and resonance IBitmap smallKnobBitmap = pGraphics->LoadIBitmap(KNOB_SMALL_ID, KNOB_SMALL_FN, 64); // Cutoff knob: GetParam(mFilterCutoff)->InitDouble("Cutoff", 0.99, 0.01, 0.99, 0.001); GetParam(mFilterCutoff)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 5, 177, mFilterCutoff, &smallKnobBitmap)); // Resonance knob: GetParam(mFilterResonance)->InitDouble("Resonance", 0.01, 0.01, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 61, 177, mFilterResonance, &smallKnobBitmap));
, 1.0
, calculateFeedbackAmount
, .
ProcessDoubleReplacing
:
leftOutput[i] = rightOutput[i] = mFilter.process(mOscillator.nextSample() * mEnvelopeGenerator.nextSample() * velocity / 127.0);
. switch
Synthesis::OnParamChange
:
case mFilterCutoff: mFilter.setCutoff(GetParam(paramIdx)->Value()); break; case mFilterResonance: mFilter.setResonance(GetParam(paramIdx)->Value()); break; case mFilterMode: mFilter.setFilterMode(static_cast<Filter::FilterMode>(GetParam(paramIdx)->Int())); break;
. - .
— . , , .
:
buf0 += cutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1));
, , ( buf0 – buf1
), feedbackAmount
. calculateFeedbackAmount
feedbackAmount
resonance
, . . , .
, . , , . , , .
-12 / -24 /
! 24 . switch
Filter::process
:
buf2 += cutoff * (buf1 - buf2); buf3 += cutoff * (buf2 – buf3);
: , , . , , , ( ). switch
:
switch (mode) { case FILTER_MODE_LOWPASS: return buf3; case FILTER_MODE_HIGHPASS: return inputValue - buf3; case FILTER_MODE_BANDPASS: return buf0 - buf3; default: return 0.0; }
buf1
buf3
– . -24 /. buf2
buf3
, private
Filter.h :
double buf2; double buf3;
, buf0
c buf1
:
Filter() : // ... buf0(0.0), buf1(0.0), buf2(0.0), buf3(0.0) // ...
, , : . .
?
. , , .
- cutoff
. . cutoffMod
, , cutoff
. #include private
:
double cutoffMod;
:
Filter() : cutoff(0.99), resonance(0.01), cutoffMod(0.0), // ...
. private
:
inline double getCalculatedCutoff() const { return fmax(fmin(cutoff + cutoffMod, 0.99), 0.01); };
calculateFeedbackAmount
:
inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - getCalculatedCutoff()); }
cutoffMod
. feedbackAmount
, :
inline void setCutoffMod(double newCutoffMod) { cutoffMod = newCutoffMod; calculateFeedbackAmount(); }
, . Filter::process
:
if (inputValue == 0.0) return inputValue; double calculatedCutoff = getCalculatedCutoff(); buf0 += calculatedCutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1)); buf1 += calculatedCutoff * (buf0 - buf1); buf2 += calculatedCutoff * (buf1 - buf2); buf3 += calculatedCutoff * (buf2 – buf3);
, . , , . . cutoff
calculatedCutoff
-.
, ( setCutoffMod
), Synthesis
, , . , cutoffMod
. filterEnvelopeAmount
-1
+1
. GUI.
private
Synthesis.h :
EnvelopeGenerator mFilterEnvelopeGenerator; double filterEnvelopeAmount;
MIDI , onNoteOn
onNoteOff
:
inline void onNoteOn(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); }; inline void onNoteOff(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); };
. ProcessDoubleReplacing
:
mFilter.setCutoffMod(mFilterEnvelopeGenerator.nextSample() * filterEnvelopeAmount);
, nextSample
filterEnvelopeAmount
cutoffMod
. filterEnvelopeAmount
:
Synthesis::Synthesis(IPlugInstanceInfo instanceInfo) : IPLUG_CTOR(kNumParams, kNumPrograms, instanceInfo), lastVirtualKeyboardNoteNumber(virtualKeyboardMinimumNoteNumber - 1), filterEnvelopeAmount(0.0) { // ... }
Synthesis::Reset
:
mFilterEnvelopeGenerator.setSampleRate(GetSampleRate());
EParams
, . AttachGraphics
:
// Knobs for filter envelope // Attack knob GetParam(mFilterAttack)->InitDouble("Filter Env Attack", 0.01, 0.01, 10.0, 0.001); GetParam(mFilterAttack)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 139, 178, mFilterAttack, &smallKnobBitmap)); // Decay knob: GetParam(mFilterDecay)->InitDouble("Filter Env Decay", 0.5, 0.01, 15.0, 0.001); GetParam(mFilterDecay)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 195, 178, mFilterDecay, &smallKnobBitmap)); // Sustain knob: GetParam(mFilterSustain)->InitDouble("Filter Env Sustain", 0.1, 0.001, 1.0, 0.001); GetParam(mFilterSustain)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 251, 178, mFilterSustain, &smallKnobBitmap)); // Release knob: GetParam(mFilterRelease)->InitDouble("Filter Env Release", 1.0, 0.001, 15.0, 0.001); GetParam(mFilterRelease)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 307, 178, mFilterRelease, &smallKnobBitmap)); // Filter envelope amount knob: GetParam(mFilterEnvelopeAmount)->InitDouble("Filter Env Amount", 0.0, -1.0, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 363, 178, mFilterEnvelopeAmount, &smallKnobBitmap));
, smallKnobBitmap
. . , . Synthesis::OnParamChange
switch
:
case mFilterAttack: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK, GetParam(paramIdx)->Value()); break; case mFilterDecay: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_DECAY, GetParam(paramIdx)->Value()); break; case mFilterSustain: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_SUSTAIN, GetParam(paramIdx)->Value()); break; case mFilterRelease: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE, GetParam(paramIdx)->Value()); break; case mFilterEnvelopeAmount: filterEnvelopeAmount = GetParam(paramIdx)->Value(); break;
!
! (- C1), :
EnvelopeGenerator
. . !
.
:)
:
martin-finke.de/blog/articles/audio-plugins-013-filter
#include Filter.h ( ) :
class Filter { public: enum FilterMode { FILTER_MODE_LOWPASS = 0, FILTER_MODE_HIGHPASS, FILTER_MODE_BANDPASS, kNumFilterModes }; Filter() : cutoff(0.99), resonance(0.0), mode(FILTER_MODE_LOWPASS), buf0(0.0), buf1(0.0) { calculateFeedbackAmount(); }; double process(double inputValue); inline void setCutoff(double newCutoff) { cutoff = newCutoff; calculateFeedbackAmount(); }; inline void setResonance(double newResonance) { resonance = newResonance; calculateFeedbackAmount(); }; inline void setFilterMode(FilterMode newMode) { mode = newMode; } private: double cutoff; double resonance; FilterMode mode; double feedbackAmount; inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - cutoff); } double buf0; double buf1; };
private
cutoff
resonance
. Mode
(Lowpass, Highpass, Bandpass). feedbackAmount
, buf0
buf1
, .
( calculateFeedbackAmount()
). process
. feedbackAmount
cutoff
resonance
, calculateFeedbackAmount
.
Filter.cpp :
// By Paul Kellett // http://www.musicdsp.org/showone.php?id=29 double Filter::process(double inputValue) { buf0 += cutoff * (inputValue - buf0); buf1 += cutoff * (buf0 - buf1); switch (mode) { case FILTER_MODE_LOWPASS: return buf1; case FILTER_MODE_HIGHPASS: return inputValue - buf0; case FILTER_MODE_BANDPASS: return buf0 - buf1; default: return 0.0; } }
, ? , . « » , (. . 6 ). buf0
buf1
: . inputValue
, - buf0
( ). . 6 / 12 /. switch
, buf1
. , , buf0
, 12, 6 /, .
case
FILTER_MODE_HIGHPASS
. buf0
. inputValue
buf0
, . buf1
, .
case
FILTER_MODE_BANDPASS
, buf0 - buf1
. buf0
, buf1
. , .
: , .
buf1
. ( Infinite Impulse Response , IIR ). , . , , .
, , . , , , , . , NI Massive, « ». , , . , ( Massive ). «» , , , AAS Ultra Analog, , , , . , , , , NI Reaktor. , — , .. .
. GUI. bg.png ( “Move to trash” Xcode), :
filtermode.png knob_small.png ( , 50 50 ) bg.png ( )
ID resource.h :
// Unique IDs for each image resource. #define BG_ID 101 #define WHITE_KEY_ID 102 #define BLACK_KEY_ID 103 #define WAVEFORM_ID 104 #define KNOB_ID 105 #define KNOB_SMALL_ID 106 #define FILTERMODE_ID 107 // Image resource locations for this plug. #define BG_FN "resources/img/bg.png" #define WHITE_KEY_FN "resources/img/whitekey.png" #define BLACK_KEY_FN "resources/img/blackkey.png" #define WAVEFORM_FN "resources/img/waveform.png" #define KNOB_FN "resources/img/knob.png" #define KNOB_SMALL_FN "resources/img/knob_small.png" #define FILTERMODE_FN "resources/img/filtermode.png"
Synthesis.rc :
#include "resource.h" BG_ID PNG BG_FN WHITE_KEY_ID PNG WHITE_KEY_FN BLACK_KEY_ID PNG BLACK_KEY_FN WAVEFORM_ID PNG WAVEFORM_FN KNOB_ID PNG KNOB_FN KNOB_SMALL_ID PNG KNOB_SMALL_FN FILTERMODE_ID PNG FILTERMODE_FN
#include "Filter.h"
Synthesis.h private
:
Filter mFilter;
EParams
Synthesis.cpp :
enum EParams { mWaveform = 0, mAttack, mDecay, mSustain, mRelease, mFilterMode, mFilterCutoff, mFilterResonance, mFilterAttack, mFilterDecay, mFilterSustain, mFilterRelease, mFilterEnvelopeAmount, kNumParams };
:
pGraphics->AttachControl(new ISwitchControl(this, 24, 38, mWaveform, &waveformBitmap));
. (Lowpass, Highpass, Bandpass). AttachGraphics(pGraphics)
:
GetParam(mFilterMode)->InitEnum("Filter Mode", Filter::FILTER_MODE_LOWPASS, Filter::kNumFilterModes); IBitmap filtermodeBitmap = pGraphics->LoadIBitmap(FILTERMODE_ID, FILTERMODE_FN, 3); pGraphics->AttachControl(new ISwitchControl(this, 24, 123, mFilterMode, &filtermodeBitmap));
. . knob_small.png :
// Knobs for filter cutoff and resonance IBitmap smallKnobBitmap = pGraphics->LoadIBitmap(KNOB_SMALL_ID, KNOB_SMALL_FN, 64); // Cutoff knob: GetParam(mFilterCutoff)->InitDouble("Cutoff", 0.99, 0.01, 0.99, 0.001); GetParam(mFilterCutoff)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 5, 177, mFilterCutoff, &smallKnobBitmap)); // Resonance knob: GetParam(mFilterResonance)->InitDouble("Resonance", 0.01, 0.01, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 61, 177, mFilterResonance, &smallKnobBitmap));
, 1.0
, calculateFeedbackAmount
, .
ProcessDoubleReplacing
:
leftOutput[i] = rightOutput[i] = mFilter.process(mOscillator.nextSample() * mEnvelopeGenerator.nextSample() * velocity / 127.0);
. switch
Synthesis::OnParamChange
:
case mFilterCutoff: mFilter.setCutoff(GetParam(paramIdx)->Value()); break; case mFilterResonance: mFilter.setResonance(GetParam(paramIdx)->Value()); break; case mFilterMode: mFilter.setFilterMode(static_cast<Filter::FilterMode>(GetParam(paramIdx)->Int())); break;
. - .
— . , , .
:
buf0 += cutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1));
, , ( buf0 – buf1
), feedbackAmount
. calculateFeedbackAmount
feedbackAmount
resonance
, . . , .
, . , , . , , .
-12 / -24 /
! 24 . switch
Filter::process
:
buf2 += cutoff * (buf1 - buf2); buf3 += cutoff * (buf2 – buf3);
: , , . , , , ( ). switch
:
switch (mode) { case FILTER_MODE_LOWPASS: return buf3; case FILTER_MODE_HIGHPASS: return inputValue - buf3; case FILTER_MODE_BANDPASS: return buf0 - buf3; default: return 0.0; }
buf1
buf3
– . -24 /. buf2
buf3
, private
Filter.h :
double buf2; double buf3;
, buf0
c buf1
:
Filter() : // ... buf0(0.0), buf1(0.0), buf2(0.0), buf3(0.0) // ...
, , : . .
?
. , , .
- cutoff
. . cutoffMod
, , cutoff
. #include private
:
double cutoffMod;
:
Filter() : cutoff(0.99), resonance(0.01), cutoffMod(0.0), // ...
. private
:
inline double getCalculatedCutoff() const { return fmax(fmin(cutoff + cutoffMod, 0.99), 0.01); };
calculateFeedbackAmount
:
inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - getCalculatedCutoff()); }
cutoffMod
. feedbackAmount
, :
inline void setCutoffMod(double newCutoffMod) { cutoffMod = newCutoffMod; calculateFeedbackAmount(); }
, . Filter::process
:
if (inputValue == 0.0) return inputValue; double calculatedCutoff = getCalculatedCutoff(); buf0 += calculatedCutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1)); buf1 += calculatedCutoff * (buf0 - buf1); buf2 += calculatedCutoff * (buf1 - buf2); buf3 += calculatedCutoff * (buf2 – buf3);
, . , , . . cutoff
calculatedCutoff
-.
, ( setCutoffMod
), Synthesis
, , . , cutoffMod
. filterEnvelopeAmount
-1
+1
. GUI.
private
Synthesis.h :
EnvelopeGenerator mFilterEnvelopeGenerator; double filterEnvelopeAmount;
MIDI , onNoteOn
onNoteOff
:
inline void onNoteOn(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); }; inline void onNoteOff(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); };
. ProcessDoubleReplacing
:
mFilter.setCutoffMod(mFilterEnvelopeGenerator.nextSample() * filterEnvelopeAmount);
, nextSample
filterEnvelopeAmount
cutoffMod
. filterEnvelopeAmount
:
Synthesis::Synthesis(IPlugInstanceInfo instanceInfo) : IPLUG_CTOR(kNumParams, kNumPrograms, instanceInfo), lastVirtualKeyboardNoteNumber(virtualKeyboardMinimumNoteNumber - 1), filterEnvelopeAmount(0.0) { // ... }
Synthesis::Reset
:
mFilterEnvelopeGenerator.setSampleRate(GetSampleRate());
EParams
, . AttachGraphics
:
// Knobs for filter envelope // Attack knob GetParam(mFilterAttack)->InitDouble("Filter Env Attack", 0.01, 0.01, 10.0, 0.001); GetParam(mFilterAttack)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 139, 178, mFilterAttack, &smallKnobBitmap)); // Decay knob: GetParam(mFilterDecay)->InitDouble("Filter Env Decay", 0.5, 0.01, 15.0, 0.001); GetParam(mFilterDecay)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 195, 178, mFilterDecay, &smallKnobBitmap)); // Sustain knob: GetParam(mFilterSustain)->InitDouble("Filter Env Sustain", 0.1, 0.001, 1.0, 0.001); GetParam(mFilterSustain)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 251, 178, mFilterSustain, &smallKnobBitmap)); // Release knob: GetParam(mFilterRelease)->InitDouble("Filter Env Release", 1.0, 0.001, 15.0, 0.001); GetParam(mFilterRelease)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 307, 178, mFilterRelease, &smallKnobBitmap)); // Filter envelope amount knob: GetParam(mFilterEnvelopeAmount)->InitDouble("Filter Env Amount", 0.0, -1.0, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 363, 178, mFilterEnvelopeAmount, &smallKnobBitmap));
, smallKnobBitmap
. . , . Synthesis::OnParamChange
switch
:
case mFilterAttack: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK, GetParam(paramIdx)->Value()); break; case mFilterDecay: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_DECAY, GetParam(paramIdx)->Value()); break; case mFilterSustain: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_SUSTAIN, GetParam(paramIdx)->Value()); break; case mFilterRelease: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE, GetParam(paramIdx)->Value()); break; case mFilterEnvelopeAmount: filterEnvelopeAmount = GetParam(paramIdx)->Value(); break;
!
! (- C1), :
EnvelopeGenerator
. . !
.
:)
:
martin-finke.de/blog/articles/audio-plugins-013-filter
#include Filter.h ( ) :
class Filter { public: enum FilterMode { FILTER_MODE_LOWPASS = 0, FILTER_MODE_HIGHPASS, FILTER_MODE_BANDPASS, kNumFilterModes }; Filter() : cutoff(0.99), resonance(0.0), mode(FILTER_MODE_LOWPASS), buf0(0.0), buf1(0.0) { calculateFeedbackAmount(); }; double process(double inputValue); inline void setCutoff(double newCutoff) { cutoff = newCutoff; calculateFeedbackAmount(); }; inline void setResonance(double newResonance) { resonance = newResonance; calculateFeedbackAmount(); }; inline void setFilterMode(FilterMode newMode) { mode = newMode; } private: double cutoff; double resonance; FilterMode mode; double feedbackAmount; inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - cutoff); } double buf0; double buf1; };
private
cutoff
resonance
. Mode
(Lowpass, Highpass, Bandpass). feedbackAmount
, buf0
buf1
, .
( calculateFeedbackAmount()
). process
. feedbackAmount
cutoff
resonance
, calculateFeedbackAmount
.
Filter.cpp :
// By Paul Kellett // http://www.musicdsp.org/showone.php?id=29 double Filter::process(double inputValue) { buf0 += cutoff * (inputValue - buf0); buf1 += cutoff * (buf0 - buf1); switch (mode) { case FILTER_MODE_LOWPASS: return buf1; case FILTER_MODE_HIGHPASS: return inputValue - buf0; case FILTER_MODE_BANDPASS: return buf0 - buf1; default: return 0.0; } }
, ? , . « » , (. . 6 ). buf0
buf1
: . inputValue
, - buf0
( ). . 6 / 12 /. switch
, buf1
. , , buf0
, 12, 6 /, .
case
FILTER_MODE_HIGHPASS
. buf0
. inputValue
buf0
, . buf1
, .
case
FILTER_MODE_BANDPASS
, buf0 - buf1
. buf0
, buf1
. , .
: , .
buf1
. ( Infinite Impulse Response , IIR ). , . , , .
, , . , , , , . , NI Massive, « ». , , . , ( Massive ). «» , , , AAS Ultra Analog, , , , . , , , , NI Reaktor. , — , .. .
. GUI. bg.png ( “Move to trash” Xcode), :
filtermode.png knob_small.png ( , 50 50 ) bg.png ( )
ID resource.h :
// Unique IDs for each image resource. #define BG_ID 101 #define WHITE_KEY_ID 102 #define BLACK_KEY_ID 103 #define WAVEFORM_ID 104 #define KNOB_ID 105 #define KNOB_SMALL_ID 106 #define FILTERMODE_ID 107 // Image resource locations for this plug. #define BG_FN "resources/img/bg.png" #define WHITE_KEY_FN "resources/img/whitekey.png" #define BLACK_KEY_FN "resources/img/blackkey.png" #define WAVEFORM_FN "resources/img/waveform.png" #define KNOB_FN "resources/img/knob.png" #define KNOB_SMALL_FN "resources/img/knob_small.png" #define FILTERMODE_FN "resources/img/filtermode.png"
Synthesis.rc :
#include "resource.h" BG_ID PNG BG_FN WHITE_KEY_ID PNG WHITE_KEY_FN BLACK_KEY_ID PNG BLACK_KEY_FN WAVEFORM_ID PNG WAVEFORM_FN KNOB_ID PNG KNOB_FN KNOB_SMALL_ID PNG KNOB_SMALL_FN FILTERMODE_ID PNG FILTERMODE_FN
#include "Filter.h"
Synthesis.h private
:
Filter mFilter;
EParams
Synthesis.cpp :
enum EParams { mWaveform = 0, mAttack, mDecay, mSustain, mRelease, mFilterMode, mFilterCutoff, mFilterResonance, mFilterAttack, mFilterDecay, mFilterSustain, mFilterRelease, mFilterEnvelopeAmount, kNumParams };
:
pGraphics->AttachControl(new ISwitchControl(this, 24, 38, mWaveform, &waveformBitmap));
. (Lowpass, Highpass, Bandpass). AttachGraphics(pGraphics)
:
GetParam(mFilterMode)->InitEnum("Filter Mode", Filter::FILTER_MODE_LOWPASS, Filter::kNumFilterModes); IBitmap filtermodeBitmap = pGraphics->LoadIBitmap(FILTERMODE_ID, FILTERMODE_FN, 3); pGraphics->AttachControl(new ISwitchControl(this, 24, 123, mFilterMode, &filtermodeBitmap));
. . knob_small.png :
// Knobs for filter cutoff and resonance IBitmap smallKnobBitmap = pGraphics->LoadIBitmap(KNOB_SMALL_ID, KNOB_SMALL_FN, 64); // Cutoff knob: GetParam(mFilterCutoff)->InitDouble("Cutoff", 0.99, 0.01, 0.99, 0.001); GetParam(mFilterCutoff)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 5, 177, mFilterCutoff, &smallKnobBitmap)); // Resonance knob: GetParam(mFilterResonance)->InitDouble("Resonance", 0.01, 0.01, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 61, 177, mFilterResonance, &smallKnobBitmap));
, 1.0
, calculateFeedbackAmount
, .
ProcessDoubleReplacing
:
leftOutput[i] = rightOutput[i] = mFilter.process(mOscillator.nextSample() * mEnvelopeGenerator.nextSample() * velocity / 127.0);
. switch
Synthesis::OnParamChange
:
case mFilterCutoff: mFilter.setCutoff(GetParam(paramIdx)->Value()); break; case mFilterResonance: mFilter.setResonance(GetParam(paramIdx)->Value()); break; case mFilterMode: mFilter.setFilterMode(static_cast<Filter::FilterMode>(GetParam(paramIdx)->Int())); break;
. - .
— . , , .
:
buf0 += cutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1));
, , ( buf0 – buf1
), feedbackAmount
. calculateFeedbackAmount
feedbackAmount
resonance
, . . , .
, . , , . , , .
-12 / -24 /
! 24 . switch
Filter::process
:
buf2 += cutoff * (buf1 - buf2); buf3 += cutoff * (buf2 – buf3);
: , , . , , , ( ). switch
:
switch (mode) { case FILTER_MODE_LOWPASS: return buf3; case FILTER_MODE_HIGHPASS: return inputValue - buf3; case FILTER_MODE_BANDPASS: return buf0 - buf3; default: return 0.0; }
buf1
buf3
– . -24 /. buf2
buf3
, private
Filter.h :
double buf2; double buf3;
, buf0
c buf1
:
Filter() : // ... buf0(0.0), buf1(0.0), buf2(0.0), buf3(0.0) // ...
, , : . .
?
. , , .
- cutoff
. . cutoffMod
, , cutoff
. #include private
:
double cutoffMod;
:
Filter() : cutoff(0.99), resonance(0.01), cutoffMod(0.0), // ...
. private
:
inline double getCalculatedCutoff() const { return fmax(fmin(cutoff + cutoffMod, 0.99), 0.01); };
calculateFeedbackAmount
:
inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - getCalculatedCutoff()); }
cutoffMod
. feedbackAmount
, :
inline void setCutoffMod(double newCutoffMod) { cutoffMod = newCutoffMod; calculateFeedbackAmount(); }
, . Filter::process
:
if (inputValue == 0.0) return inputValue; double calculatedCutoff = getCalculatedCutoff(); buf0 += calculatedCutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1)); buf1 += calculatedCutoff * (buf0 - buf1); buf2 += calculatedCutoff * (buf1 - buf2); buf3 += calculatedCutoff * (buf2 – buf3);
, . , , . . cutoff
calculatedCutoff
-.
, ( setCutoffMod
), Synthesis
, , . , cutoffMod
. filterEnvelopeAmount
-1
+1
. GUI.
private
Synthesis.h :
EnvelopeGenerator mFilterEnvelopeGenerator; double filterEnvelopeAmount;
MIDI , onNoteOn
onNoteOff
:
inline void onNoteOn(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); }; inline void onNoteOff(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); };
. ProcessDoubleReplacing
:
mFilter.setCutoffMod(mFilterEnvelopeGenerator.nextSample() * filterEnvelopeAmount);
, nextSample
filterEnvelopeAmount
cutoffMod
. filterEnvelopeAmount
:
Synthesis::Synthesis(IPlugInstanceInfo instanceInfo) : IPLUG_CTOR(kNumParams, kNumPrograms, instanceInfo), lastVirtualKeyboardNoteNumber(virtualKeyboardMinimumNoteNumber - 1), filterEnvelopeAmount(0.0) { // ... }
Synthesis::Reset
:
mFilterEnvelopeGenerator.setSampleRate(GetSampleRate());
EParams
, . AttachGraphics
:
// Knobs for filter envelope // Attack knob GetParam(mFilterAttack)->InitDouble("Filter Env Attack", 0.01, 0.01, 10.0, 0.001); GetParam(mFilterAttack)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 139, 178, mFilterAttack, &smallKnobBitmap)); // Decay knob: GetParam(mFilterDecay)->InitDouble("Filter Env Decay", 0.5, 0.01, 15.0, 0.001); GetParam(mFilterDecay)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 195, 178, mFilterDecay, &smallKnobBitmap)); // Sustain knob: GetParam(mFilterSustain)->InitDouble("Filter Env Sustain", 0.1, 0.001, 1.0, 0.001); GetParam(mFilterSustain)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 251, 178, mFilterSustain, &smallKnobBitmap)); // Release knob: GetParam(mFilterRelease)->InitDouble("Filter Env Release", 1.0, 0.001, 15.0, 0.001); GetParam(mFilterRelease)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 307, 178, mFilterRelease, &smallKnobBitmap)); // Filter envelope amount knob: GetParam(mFilterEnvelopeAmount)->InitDouble("Filter Env Amount", 0.0, -1.0, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 363, 178, mFilterEnvelopeAmount, &smallKnobBitmap));
, smallKnobBitmap
. . , . Synthesis::OnParamChange
switch
:
case mFilterAttack: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK, GetParam(paramIdx)->Value()); break; case mFilterDecay: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_DECAY, GetParam(paramIdx)->Value()); break; case mFilterSustain: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_SUSTAIN, GetParam(paramIdx)->Value()); break; case mFilterRelease: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE, GetParam(paramIdx)->Value()); break; case mFilterEnvelopeAmount: filterEnvelopeAmount = GetParam(paramIdx)->Value(); break;
!
! (- C1), :
EnvelopeGenerator
. . !
.
:)
:
martin-finke.de/blog/articles/audio-plugins-013-filter
#include Filter.h ( ) :
class Filter { public: enum FilterMode { FILTER_MODE_LOWPASS = 0, FILTER_MODE_HIGHPASS, FILTER_MODE_BANDPASS, kNumFilterModes }; Filter() : cutoff(0.99), resonance(0.0), mode(FILTER_MODE_LOWPASS), buf0(0.0), buf1(0.0) { calculateFeedbackAmount(); }; double process(double inputValue); inline void setCutoff(double newCutoff) { cutoff = newCutoff; calculateFeedbackAmount(); }; inline void setResonance(double newResonance) { resonance = newResonance; calculateFeedbackAmount(); }; inline void setFilterMode(FilterMode newMode) { mode = newMode; } private: double cutoff; double resonance; FilterMode mode; double feedbackAmount; inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - cutoff); } double buf0; double buf1; };
private
cutoff
resonance
. Mode
(Lowpass, Highpass, Bandpass). feedbackAmount
, buf0
buf1
, .
( calculateFeedbackAmount()
). process
. feedbackAmount
cutoff
resonance
, calculateFeedbackAmount
.
Filter.cpp :
// By Paul Kellett // http://www.musicdsp.org/showone.php?id=29 double Filter::process(double inputValue) { buf0 += cutoff * (inputValue - buf0); buf1 += cutoff * (buf0 - buf1); switch (mode) { case FILTER_MODE_LOWPASS: return buf1; case FILTER_MODE_HIGHPASS: return inputValue - buf0; case FILTER_MODE_BANDPASS: return buf0 - buf1; default: return 0.0; } }
, ? , . « » , (. . 6 ). buf0
buf1
: . inputValue
, - buf0
( ). . 6 / 12 /. switch
, buf1
. , , buf0
, 12, 6 /, .
case
FILTER_MODE_HIGHPASS
. buf0
. inputValue
buf0
, . buf1
, .
case
FILTER_MODE_BANDPASS
, buf0 - buf1
. buf0
, buf1
. , .
: , .
buf1
. ( Infinite Impulse Response , IIR ). , . , , .
, , . , , , , . , NI Massive, « ». , , . , ( Massive ). «» , , , AAS Ultra Analog, , , , . , , , , NI Reaktor. , — , .. .
. GUI. bg.png ( “Move to trash” Xcode), :
filtermode.png knob_small.png ( , 50 50 ) bg.png ( )
ID resource.h :
// Unique IDs for each image resource. #define BG_ID 101 #define WHITE_KEY_ID 102 #define BLACK_KEY_ID 103 #define WAVEFORM_ID 104 #define KNOB_ID 105 #define KNOB_SMALL_ID 106 #define FILTERMODE_ID 107 // Image resource locations for this plug. #define BG_FN "resources/img/bg.png" #define WHITE_KEY_FN "resources/img/whitekey.png" #define BLACK_KEY_FN "resources/img/blackkey.png" #define WAVEFORM_FN "resources/img/waveform.png" #define KNOB_FN "resources/img/knob.png" #define KNOB_SMALL_FN "resources/img/knob_small.png" #define FILTERMODE_FN "resources/img/filtermode.png"
Synthesis.rc :
#include "resource.h" BG_ID PNG BG_FN WHITE_KEY_ID PNG WHITE_KEY_FN BLACK_KEY_ID PNG BLACK_KEY_FN WAVEFORM_ID PNG WAVEFORM_FN KNOB_ID PNG KNOB_FN KNOB_SMALL_ID PNG KNOB_SMALL_FN FILTERMODE_ID PNG FILTERMODE_FN
#include "Filter.h"
Synthesis.h private
:
Filter mFilter;
EParams
Synthesis.cpp :
enum EParams { mWaveform = 0, mAttack, mDecay, mSustain, mRelease, mFilterMode, mFilterCutoff, mFilterResonance, mFilterAttack, mFilterDecay, mFilterSustain, mFilterRelease, mFilterEnvelopeAmount, kNumParams };
:
pGraphics->AttachControl(new ISwitchControl(this, 24, 38, mWaveform, &waveformBitmap));
. (Lowpass, Highpass, Bandpass). AttachGraphics(pGraphics)
:
GetParam(mFilterMode)->InitEnum("Filter Mode", Filter::FILTER_MODE_LOWPASS, Filter::kNumFilterModes); IBitmap filtermodeBitmap = pGraphics->LoadIBitmap(FILTERMODE_ID, FILTERMODE_FN, 3); pGraphics->AttachControl(new ISwitchControl(this, 24, 123, mFilterMode, &filtermodeBitmap));
. . knob_small.png :
// Knobs for filter cutoff and resonance IBitmap smallKnobBitmap = pGraphics->LoadIBitmap(KNOB_SMALL_ID, KNOB_SMALL_FN, 64); // Cutoff knob: GetParam(mFilterCutoff)->InitDouble("Cutoff", 0.99, 0.01, 0.99, 0.001); GetParam(mFilterCutoff)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 5, 177, mFilterCutoff, &smallKnobBitmap)); // Resonance knob: GetParam(mFilterResonance)->InitDouble("Resonance", 0.01, 0.01, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 61, 177, mFilterResonance, &smallKnobBitmap));
, 1.0
, calculateFeedbackAmount
, .
ProcessDoubleReplacing
:
leftOutput[i] = rightOutput[i] = mFilter.process(mOscillator.nextSample() * mEnvelopeGenerator.nextSample() * velocity / 127.0);
. switch
Synthesis::OnParamChange
:
case mFilterCutoff: mFilter.setCutoff(GetParam(paramIdx)->Value()); break; case mFilterResonance: mFilter.setResonance(GetParam(paramIdx)->Value()); break; case mFilterMode: mFilter.setFilterMode(static_cast<Filter::FilterMode>(GetParam(paramIdx)->Int())); break;
. - .
— . , , .
:
buf0 += cutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1));
, , ( buf0 – buf1
), feedbackAmount
. calculateFeedbackAmount
feedbackAmount
resonance
, . . , .
, . , , . , , .
-12 / -24 /
! 24 . switch
Filter::process
:
buf2 += cutoff * (buf1 - buf2); buf3 += cutoff * (buf2 – buf3);
: , , . , , , ( ). switch
:
switch (mode) { case FILTER_MODE_LOWPASS: return buf3; case FILTER_MODE_HIGHPASS: return inputValue - buf3; case FILTER_MODE_BANDPASS: return buf0 - buf3; default: return 0.0; }
buf1
buf3
– . -24 /. buf2
buf3
, private
Filter.h :
double buf2; double buf3;
, buf0
c buf1
:
Filter() : // ... buf0(0.0), buf1(0.0), buf2(0.0), buf3(0.0) // ...
, , : . .
?
. , , .
- cutoff
. . cutoffMod
, , cutoff
. #include private
:
double cutoffMod;
:
Filter() : cutoff(0.99), resonance(0.01), cutoffMod(0.0), // ...
. private
:
inline double getCalculatedCutoff() const { return fmax(fmin(cutoff + cutoffMod, 0.99), 0.01); };
calculateFeedbackAmount
:
inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - getCalculatedCutoff()); }
cutoffMod
. feedbackAmount
, :
inline void setCutoffMod(double newCutoffMod) { cutoffMod = newCutoffMod; calculateFeedbackAmount(); }
, . Filter::process
:
if (inputValue == 0.0) return inputValue; double calculatedCutoff = getCalculatedCutoff(); buf0 += calculatedCutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1)); buf1 += calculatedCutoff * (buf0 - buf1); buf2 += calculatedCutoff * (buf1 - buf2); buf3 += calculatedCutoff * (buf2 – buf3);
, . , , . . cutoff
calculatedCutoff
-.
, ( setCutoffMod
), Synthesis
, , . , cutoffMod
. filterEnvelopeAmount
-1
+1
. GUI.
private
Synthesis.h :
EnvelopeGenerator mFilterEnvelopeGenerator; double filterEnvelopeAmount;
MIDI , onNoteOn
onNoteOff
:
inline void onNoteOn(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); }; inline void onNoteOff(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); };
. ProcessDoubleReplacing
:
mFilter.setCutoffMod(mFilterEnvelopeGenerator.nextSample() * filterEnvelopeAmount);
, nextSample
filterEnvelopeAmount
cutoffMod
. filterEnvelopeAmount
:
Synthesis::Synthesis(IPlugInstanceInfo instanceInfo) : IPLUG_CTOR(kNumParams, kNumPrograms, instanceInfo), lastVirtualKeyboardNoteNumber(virtualKeyboardMinimumNoteNumber - 1), filterEnvelopeAmount(0.0) { // ... }
Synthesis::Reset
:
mFilterEnvelopeGenerator.setSampleRate(GetSampleRate());
EParams
, . AttachGraphics
:
// Knobs for filter envelope // Attack knob GetParam(mFilterAttack)->InitDouble("Filter Env Attack", 0.01, 0.01, 10.0, 0.001); GetParam(mFilterAttack)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 139, 178, mFilterAttack, &smallKnobBitmap)); // Decay knob: GetParam(mFilterDecay)->InitDouble("Filter Env Decay", 0.5, 0.01, 15.0, 0.001); GetParam(mFilterDecay)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 195, 178, mFilterDecay, &smallKnobBitmap)); // Sustain knob: GetParam(mFilterSustain)->InitDouble("Filter Env Sustain", 0.1, 0.001, 1.0, 0.001); GetParam(mFilterSustain)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 251, 178, mFilterSustain, &smallKnobBitmap)); // Release knob: GetParam(mFilterRelease)->InitDouble("Filter Env Release", 1.0, 0.001, 15.0, 0.001); GetParam(mFilterRelease)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 307, 178, mFilterRelease, &smallKnobBitmap)); // Filter envelope amount knob: GetParam(mFilterEnvelopeAmount)->InitDouble("Filter Env Amount", 0.0, -1.0, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 363, 178, mFilterEnvelopeAmount, &smallKnobBitmap));
, smallKnobBitmap
. . , . Synthesis::OnParamChange
switch
:
case mFilterAttack: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK, GetParam(paramIdx)->Value()); break; case mFilterDecay: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_DECAY, GetParam(paramIdx)->Value()); break; case mFilterSustain: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_SUSTAIN, GetParam(paramIdx)->Value()); break; case mFilterRelease: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE, GetParam(paramIdx)->Value()); break; case mFilterEnvelopeAmount: filterEnvelopeAmount = GetParam(paramIdx)->Value(); break;
!
! (- C1), :
EnvelopeGenerator
. . !
.
:)
:
martin-finke.de/blog/articles/audio-plugins-013-filter
#include Filter.h ( ) :
class Filter { public: enum FilterMode { FILTER_MODE_LOWPASS = 0, FILTER_MODE_HIGHPASS, FILTER_MODE_BANDPASS, kNumFilterModes }; Filter() : cutoff(0.99), resonance(0.0), mode(FILTER_MODE_LOWPASS), buf0(0.0), buf1(0.0) { calculateFeedbackAmount(); }; double process(double inputValue); inline void setCutoff(double newCutoff) { cutoff = newCutoff; calculateFeedbackAmount(); }; inline void setResonance(double newResonance) { resonance = newResonance; calculateFeedbackAmount(); }; inline void setFilterMode(FilterMode newMode) { mode = newMode; } private: double cutoff; double resonance; FilterMode mode; double feedbackAmount; inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - cutoff); } double buf0; double buf1; };
private
cutoff
resonance
. Mode
(Lowpass, Highpass, Bandpass). feedbackAmount
, buf0
buf1
, .
( calculateFeedbackAmount()
). process
. feedbackAmount
cutoff
resonance
, calculateFeedbackAmount
.
Filter.cpp :
// By Paul Kellett // http://www.musicdsp.org/showone.php?id=29 double Filter::process(double inputValue) { buf0 += cutoff * (inputValue - buf0); buf1 += cutoff * (buf0 - buf1); switch (mode) { case FILTER_MODE_LOWPASS: return buf1; case FILTER_MODE_HIGHPASS: return inputValue - buf0; case FILTER_MODE_BANDPASS: return buf0 - buf1; default: return 0.0; } }
, ? , . « » , (. . 6 ). buf0
buf1
: . inputValue
, - buf0
( ). . 6 / 12 /. switch
, buf1
. , , buf0
, 12, 6 /, .
case
FILTER_MODE_HIGHPASS
. buf0
. inputValue
buf0
, . buf1
, .
case
FILTER_MODE_BANDPASS
, buf0 - buf1
. buf0
, buf1
. , .
: , .
buf1
. ( Infinite Impulse Response , IIR ). , . , , .
, , . , , , , . , NI Massive, « ». , , . , ( Massive ). «» , , , AAS Ultra Analog, , , , . , , , , NI Reaktor. , — , .. .
. GUI. bg.png ( “Move to trash” Xcode), :
filtermode.png knob_small.png ( , 50 50 ) bg.png ( )
ID resource.h :
// Unique IDs for each image resource. #define BG_ID 101 #define WHITE_KEY_ID 102 #define BLACK_KEY_ID 103 #define WAVEFORM_ID 104 #define KNOB_ID 105 #define KNOB_SMALL_ID 106 #define FILTERMODE_ID 107 // Image resource locations for this plug. #define BG_FN "resources/img/bg.png" #define WHITE_KEY_FN "resources/img/whitekey.png" #define BLACK_KEY_FN "resources/img/blackkey.png" #define WAVEFORM_FN "resources/img/waveform.png" #define KNOB_FN "resources/img/knob.png" #define KNOB_SMALL_FN "resources/img/knob_small.png" #define FILTERMODE_FN "resources/img/filtermode.png"
Synthesis.rc :
#include "resource.h" BG_ID PNG BG_FN WHITE_KEY_ID PNG WHITE_KEY_FN BLACK_KEY_ID PNG BLACK_KEY_FN WAVEFORM_ID PNG WAVEFORM_FN KNOB_ID PNG KNOB_FN KNOB_SMALL_ID PNG KNOB_SMALL_FN FILTERMODE_ID PNG FILTERMODE_FN
#include "Filter.h"
Synthesis.h private
:
Filter mFilter;
EParams
Synthesis.cpp :
enum EParams { mWaveform = 0, mAttack, mDecay, mSustain, mRelease, mFilterMode, mFilterCutoff, mFilterResonance, mFilterAttack, mFilterDecay, mFilterSustain, mFilterRelease, mFilterEnvelopeAmount, kNumParams };
:
pGraphics->AttachControl(new ISwitchControl(this, 24, 38, mWaveform, &waveformBitmap));
. (Lowpass, Highpass, Bandpass). AttachGraphics(pGraphics)
:
GetParam(mFilterMode)->InitEnum("Filter Mode", Filter::FILTER_MODE_LOWPASS, Filter::kNumFilterModes); IBitmap filtermodeBitmap = pGraphics->LoadIBitmap(FILTERMODE_ID, FILTERMODE_FN, 3); pGraphics->AttachControl(new ISwitchControl(this, 24, 123, mFilterMode, &filtermodeBitmap));
. . knob_small.png :
// Knobs for filter cutoff and resonance IBitmap smallKnobBitmap = pGraphics->LoadIBitmap(KNOB_SMALL_ID, KNOB_SMALL_FN, 64); // Cutoff knob: GetParam(mFilterCutoff)->InitDouble("Cutoff", 0.99, 0.01, 0.99, 0.001); GetParam(mFilterCutoff)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 5, 177, mFilterCutoff, &smallKnobBitmap)); // Resonance knob: GetParam(mFilterResonance)->InitDouble("Resonance", 0.01, 0.01, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 61, 177, mFilterResonance, &smallKnobBitmap));
, 1.0
, calculateFeedbackAmount
, .
ProcessDoubleReplacing
:
leftOutput[i] = rightOutput[i] = mFilter.process(mOscillator.nextSample() * mEnvelopeGenerator.nextSample() * velocity / 127.0);
. switch
Synthesis::OnParamChange
:
case mFilterCutoff: mFilter.setCutoff(GetParam(paramIdx)->Value()); break; case mFilterResonance: mFilter.setResonance(GetParam(paramIdx)->Value()); break; case mFilterMode: mFilter.setFilterMode(static_cast<Filter::FilterMode>(GetParam(paramIdx)->Int())); break;
. - .
— . , , .
:
buf0 += cutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1));
, , ( buf0 – buf1
), feedbackAmount
. calculateFeedbackAmount
feedbackAmount
resonance
, . . , .
, . , , . , , .
-12 / -24 /
! 24 . switch
Filter::process
:
buf2 += cutoff * (buf1 - buf2); buf3 += cutoff * (buf2 – buf3);
: , , . , , , ( ). switch
:
switch (mode) { case FILTER_MODE_LOWPASS: return buf3; case FILTER_MODE_HIGHPASS: return inputValue - buf3; case FILTER_MODE_BANDPASS: return buf0 - buf3; default: return 0.0; }
buf1
buf3
– . -24 /. buf2
buf3
, private
Filter.h :
double buf2; double buf3;
, buf0
c buf1
:
Filter() : // ... buf0(0.0), buf1(0.0), buf2(0.0), buf3(0.0) // ...
, , : . .
?
. , , .
- cutoff
. . cutoffMod
, , cutoff
. #include private
:
double cutoffMod;
:
Filter() : cutoff(0.99), resonance(0.01), cutoffMod(0.0), // ...
. private
:
inline double getCalculatedCutoff() const { return fmax(fmin(cutoff + cutoffMod, 0.99), 0.01); };
calculateFeedbackAmount
:
inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - getCalculatedCutoff()); }
cutoffMod
. feedbackAmount
, :
inline void setCutoffMod(double newCutoffMod) { cutoffMod = newCutoffMod; calculateFeedbackAmount(); }
, . Filter::process
:
if (inputValue == 0.0) return inputValue; double calculatedCutoff = getCalculatedCutoff(); buf0 += calculatedCutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1)); buf1 += calculatedCutoff * (buf0 - buf1); buf2 += calculatedCutoff * (buf1 - buf2); buf3 += calculatedCutoff * (buf2 – buf3);
, . , , . . cutoff
calculatedCutoff
-.
, ( setCutoffMod
), Synthesis
, , . , cutoffMod
. filterEnvelopeAmount
-1
+1
. GUI.
private
Synthesis.h :
EnvelopeGenerator mFilterEnvelopeGenerator; double filterEnvelopeAmount;
MIDI , onNoteOn
onNoteOff
:
inline void onNoteOn(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); }; inline void onNoteOff(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); };
. ProcessDoubleReplacing
:
mFilter.setCutoffMod(mFilterEnvelopeGenerator.nextSample() * filterEnvelopeAmount);
, nextSample
filterEnvelopeAmount
cutoffMod
. filterEnvelopeAmount
:
Synthesis::Synthesis(IPlugInstanceInfo instanceInfo) : IPLUG_CTOR(kNumParams, kNumPrograms, instanceInfo), lastVirtualKeyboardNoteNumber(virtualKeyboardMinimumNoteNumber - 1), filterEnvelopeAmount(0.0) { // ... }
Synthesis::Reset
:
mFilterEnvelopeGenerator.setSampleRate(GetSampleRate());
EParams
, . AttachGraphics
:
// Knobs for filter envelope // Attack knob GetParam(mFilterAttack)->InitDouble("Filter Env Attack", 0.01, 0.01, 10.0, 0.001); GetParam(mFilterAttack)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 139, 178, mFilterAttack, &smallKnobBitmap)); // Decay knob: GetParam(mFilterDecay)->InitDouble("Filter Env Decay", 0.5, 0.01, 15.0, 0.001); GetParam(mFilterDecay)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 195, 178, mFilterDecay, &smallKnobBitmap)); // Sustain knob: GetParam(mFilterSustain)->InitDouble("Filter Env Sustain", 0.1, 0.001, 1.0, 0.001); GetParam(mFilterSustain)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 251, 178, mFilterSustain, &smallKnobBitmap)); // Release knob: GetParam(mFilterRelease)->InitDouble("Filter Env Release", 1.0, 0.001, 15.0, 0.001); GetParam(mFilterRelease)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 307, 178, mFilterRelease, &smallKnobBitmap)); // Filter envelope amount knob: GetParam(mFilterEnvelopeAmount)->InitDouble("Filter Env Amount", 0.0, -1.0, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 363, 178, mFilterEnvelopeAmount, &smallKnobBitmap));
, smallKnobBitmap
. . , . Synthesis::OnParamChange
switch
:
case mFilterAttack: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK, GetParam(paramIdx)->Value()); break; case mFilterDecay: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_DECAY, GetParam(paramIdx)->Value()); break; case mFilterSustain: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_SUSTAIN, GetParam(paramIdx)->Value()); break; case mFilterRelease: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE, GetParam(paramIdx)->Value()); break; case mFilterEnvelopeAmount: filterEnvelopeAmount = GetParam(paramIdx)->Value(); break;
!
! (- C1), :
EnvelopeGenerator
. . !
.
:)
:
martin-finke.de/blog/articles/audio-plugins-013-filter
#include Filter.h ( ) :
class Filter { public: enum FilterMode { FILTER_MODE_LOWPASS = 0, FILTER_MODE_HIGHPASS, FILTER_MODE_BANDPASS, kNumFilterModes }; Filter() : cutoff(0.99), resonance(0.0), mode(FILTER_MODE_LOWPASS), buf0(0.0), buf1(0.0) { calculateFeedbackAmount(); }; double process(double inputValue); inline void setCutoff(double newCutoff) { cutoff = newCutoff; calculateFeedbackAmount(); }; inline void setResonance(double newResonance) { resonance = newResonance; calculateFeedbackAmount(); }; inline void setFilterMode(FilterMode newMode) { mode = newMode; } private: double cutoff; double resonance; FilterMode mode; double feedbackAmount; inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - cutoff); } double buf0; double buf1; };
private
cutoff
resonance
. Mode
(Lowpass, Highpass, Bandpass). feedbackAmount
, buf0
buf1
, .
( calculateFeedbackAmount()
). process
. feedbackAmount
cutoff
resonance
, calculateFeedbackAmount
.
Filter.cpp :
// By Paul Kellett // http://www.musicdsp.org/showone.php?id=29 double Filter::process(double inputValue) { buf0 += cutoff * (inputValue - buf0); buf1 += cutoff * (buf0 - buf1); switch (mode) { case FILTER_MODE_LOWPASS: return buf1; case FILTER_MODE_HIGHPASS: return inputValue - buf0; case FILTER_MODE_BANDPASS: return buf0 - buf1; default: return 0.0; } }
, ? , . « » , (. . 6 ). buf0
buf1
: . inputValue
, - buf0
( ). . 6 / 12 /. switch
, buf1
. , , buf0
, 12, 6 /, .
case
FILTER_MODE_HIGHPASS
. buf0
. inputValue
buf0
, . buf1
, .
case
FILTER_MODE_BANDPASS
, buf0 - buf1
. buf0
, buf1
. , .
: , .
buf1
. ( Infinite Impulse Response , IIR ). , . , , .
, , . , , , , . , NI Massive, « ». , , . , ( Massive ). «» , , , AAS Ultra Analog, , , , . , , , , NI Reaktor. , — , .. .
. GUI. bg.png ( “Move to trash” Xcode), :
filtermode.png knob_small.png ( , 50 50 ) bg.png ( )
ID resource.h :
// Unique IDs for each image resource. #define BG_ID 101 #define WHITE_KEY_ID 102 #define BLACK_KEY_ID 103 #define WAVEFORM_ID 104 #define KNOB_ID 105 #define KNOB_SMALL_ID 106 #define FILTERMODE_ID 107 // Image resource locations for this plug. #define BG_FN "resources/img/bg.png" #define WHITE_KEY_FN "resources/img/whitekey.png" #define BLACK_KEY_FN "resources/img/blackkey.png" #define WAVEFORM_FN "resources/img/waveform.png" #define KNOB_FN "resources/img/knob.png" #define KNOB_SMALL_FN "resources/img/knob_small.png" #define FILTERMODE_FN "resources/img/filtermode.png"
Synthesis.rc :
#include "resource.h" BG_ID PNG BG_FN WHITE_KEY_ID PNG WHITE_KEY_FN BLACK_KEY_ID PNG BLACK_KEY_FN WAVEFORM_ID PNG WAVEFORM_FN KNOB_ID PNG KNOB_FN KNOB_SMALL_ID PNG KNOB_SMALL_FN FILTERMODE_ID PNG FILTERMODE_FN
#include "Filter.h"
Synthesis.h private
:
Filter mFilter;
EParams
Synthesis.cpp :
enum EParams { mWaveform = 0, mAttack, mDecay, mSustain, mRelease, mFilterMode, mFilterCutoff, mFilterResonance, mFilterAttack, mFilterDecay, mFilterSustain, mFilterRelease, mFilterEnvelopeAmount, kNumParams };
:
pGraphics->AttachControl(new ISwitchControl(this, 24, 38, mWaveform, &waveformBitmap));
. (Lowpass, Highpass, Bandpass). AttachGraphics(pGraphics)
:
GetParam(mFilterMode)->InitEnum("Filter Mode", Filter::FILTER_MODE_LOWPASS, Filter::kNumFilterModes); IBitmap filtermodeBitmap = pGraphics->LoadIBitmap(FILTERMODE_ID, FILTERMODE_FN, 3); pGraphics->AttachControl(new ISwitchControl(this, 24, 123, mFilterMode, &filtermodeBitmap));
. . knob_small.png :
// Knobs for filter cutoff and resonance IBitmap smallKnobBitmap = pGraphics->LoadIBitmap(KNOB_SMALL_ID, KNOB_SMALL_FN, 64); // Cutoff knob: GetParam(mFilterCutoff)->InitDouble("Cutoff", 0.99, 0.01, 0.99, 0.001); GetParam(mFilterCutoff)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 5, 177, mFilterCutoff, &smallKnobBitmap)); // Resonance knob: GetParam(mFilterResonance)->InitDouble("Resonance", 0.01, 0.01, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 61, 177, mFilterResonance, &smallKnobBitmap));
, 1.0
, calculateFeedbackAmount
, .
ProcessDoubleReplacing
:
leftOutput[i] = rightOutput[i] = mFilter.process(mOscillator.nextSample() * mEnvelopeGenerator.nextSample() * velocity / 127.0);
. switch
Synthesis::OnParamChange
:
case mFilterCutoff: mFilter.setCutoff(GetParam(paramIdx)->Value()); break; case mFilterResonance: mFilter.setResonance(GetParam(paramIdx)->Value()); break; case mFilterMode: mFilter.setFilterMode(static_cast<Filter::FilterMode>(GetParam(paramIdx)->Int())); break;
. - .
— . , , .
:
buf0 += cutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1));
, , ( buf0 – buf1
), feedbackAmount
. calculateFeedbackAmount
feedbackAmount
resonance
, . . , .
, . , , . , , .
-12 / -24 /
! 24 . switch
Filter::process
:
buf2 += cutoff * (buf1 - buf2); buf3 += cutoff * (buf2 – buf3);
: , , . , , , ( ). switch
:
switch (mode) { case FILTER_MODE_LOWPASS: return buf3; case FILTER_MODE_HIGHPASS: return inputValue - buf3; case FILTER_MODE_BANDPASS: return buf0 - buf3; default: return 0.0; }
buf1
buf3
– . -24 /. buf2
buf3
, private
Filter.h :
double buf2; double buf3;
, buf0
c buf1
:
Filter() : // ... buf0(0.0), buf1(0.0), buf2(0.0), buf3(0.0) // ...
, , : . .
?
. , , .
- cutoff
. . cutoffMod
, , cutoff
. #include private
:
double cutoffMod;
:
Filter() : cutoff(0.99), resonance(0.01), cutoffMod(0.0), // ...
. private
:
inline double getCalculatedCutoff() const { return fmax(fmin(cutoff + cutoffMod, 0.99), 0.01); };
calculateFeedbackAmount
:
inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - getCalculatedCutoff()); }
cutoffMod
. feedbackAmount
, :
inline void setCutoffMod(double newCutoffMod) { cutoffMod = newCutoffMod; calculateFeedbackAmount(); }
, . Filter::process
:
if (inputValue == 0.0) return inputValue; double calculatedCutoff = getCalculatedCutoff(); buf0 += calculatedCutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1)); buf1 += calculatedCutoff * (buf0 - buf1); buf2 += calculatedCutoff * (buf1 - buf2); buf3 += calculatedCutoff * (buf2 – buf3);
, . , , . . cutoff
calculatedCutoff
-.
, ( setCutoffMod
), Synthesis
, , . , cutoffMod
. filterEnvelopeAmount
-1
+1
. GUI.
private
Synthesis.h :
EnvelopeGenerator mFilterEnvelopeGenerator; double filterEnvelopeAmount;
MIDI , onNoteOn
onNoteOff
:
inline void onNoteOn(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); }; inline void onNoteOff(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); };
. ProcessDoubleReplacing
:
mFilter.setCutoffMod(mFilterEnvelopeGenerator.nextSample() * filterEnvelopeAmount);
, nextSample
filterEnvelopeAmount
cutoffMod
. filterEnvelopeAmount
:
Synthesis::Synthesis(IPlugInstanceInfo instanceInfo) : IPLUG_CTOR(kNumParams, kNumPrograms, instanceInfo), lastVirtualKeyboardNoteNumber(virtualKeyboardMinimumNoteNumber - 1), filterEnvelopeAmount(0.0) { // ... }
Synthesis::Reset
:
mFilterEnvelopeGenerator.setSampleRate(GetSampleRate());
EParams
, . AttachGraphics
:
// Knobs for filter envelope // Attack knob GetParam(mFilterAttack)->InitDouble("Filter Env Attack", 0.01, 0.01, 10.0, 0.001); GetParam(mFilterAttack)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 139, 178, mFilterAttack, &smallKnobBitmap)); // Decay knob: GetParam(mFilterDecay)->InitDouble("Filter Env Decay", 0.5, 0.01, 15.0, 0.001); GetParam(mFilterDecay)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 195, 178, mFilterDecay, &smallKnobBitmap)); // Sustain knob: GetParam(mFilterSustain)->InitDouble("Filter Env Sustain", 0.1, 0.001, 1.0, 0.001); GetParam(mFilterSustain)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 251, 178, mFilterSustain, &smallKnobBitmap)); // Release knob: GetParam(mFilterRelease)->InitDouble("Filter Env Release", 1.0, 0.001, 15.0, 0.001); GetParam(mFilterRelease)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 307, 178, mFilterRelease, &smallKnobBitmap)); // Filter envelope amount knob: GetParam(mFilterEnvelopeAmount)->InitDouble("Filter Env Amount", 0.0, -1.0, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 363, 178, mFilterEnvelopeAmount, &smallKnobBitmap));
, smallKnobBitmap
. . , . Synthesis::OnParamChange
switch
:
case mFilterAttack: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK, GetParam(paramIdx)->Value()); break; case mFilterDecay: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_DECAY, GetParam(paramIdx)->Value()); break; case mFilterSustain: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_SUSTAIN, GetParam(paramIdx)->Value()); break; case mFilterRelease: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE, GetParam(paramIdx)->Value()); break; case mFilterEnvelopeAmount: filterEnvelopeAmount = GetParam(paramIdx)->Value(); break;
!
! (- C1), :
EnvelopeGenerator
. . !
.
:)
:
martin-finke.de/blog/articles/audio-plugins-013-filter
#include Filter.h ( ) :
class Filter { public: enum FilterMode { FILTER_MODE_LOWPASS = 0, FILTER_MODE_HIGHPASS, FILTER_MODE_BANDPASS, kNumFilterModes }; Filter() : cutoff(0.99), resonance(0.0), mode(FILTER_MODE_LOWPASS), buf0(0.0), buf1(0.0) { calculateFeedbackAmount(); }; double process(double inputValue); inline void setCutoff(double newCutoff) { cutoff = newCutoff; calculateFeedbackAmount(); }; inline void setResonance(double newResonance) { resonance = newResonance; calculateFeedbackAmount(); }; inline void setFilterMode(FilterMode newMode) { mode = newMode; } private: double cutoff; double resonance; FilterMode mode; double feedbackAmount; inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - cutoff); } double buf0; double buf1; };
private
cutoff
resonance
. Mode
(Lowpass, Highpass, Bandpass). feedbackAmount
, buf0
buf1
, .
( calculateFeedbackAmount()
). process
. feedbackAmount
cutoff
resonance
, calculateFeedbackAmount
.
Filter.cpp :
// By Paul Kellett // http://www.musicdsp.org/showone.php?id=29 double Filter::process(double inputValue) { buf0 += cutoff * (inputValue - buf0); buf1 += cutoff * (buf0 - buf1); switch (mode) { case FILTER_MODE_LOWPASS: return buf1; case FILTER_MODE_HIGHPASS: return inputValue - buf0; case FILTER_MODE_BANDPASS: return buf0 - buf1; default: return 0.0; } }
, ? , . « » , (. . 6 ). buf0
buf1
: . inputValue
, - buf0
( ). . 6 / 12 /. switch
, buf1
. , , buf0
, 12, 6 /, .
case
FILTER_MODE_HIGHPASS
. buf0
. inputValue
buf0
, . buf1
, .
case
FILTER_MODE_BANDPASS
, buf0 - buf1
. buf0
, buf1
. , .
: , .
buf1
. ( Infinite Impulse Response , IIR ). , . , , .
, , . , , , , . , NI Massive, « ». , , . , ( Massive ). «» , , , AAS Ultra Analog, , , , . , , , , NI Reaktor. , — , .. .
. GUI. bg.png ( “Move to trash” Xcode), :
filtermode.png knob_small.png ( , 50 50 ) bg.png ( )
ID resource.h :
// Unique IDs for each image resource. #define BG_ID 101 #define WHITE_KEY_ID 102 #define BLACK_KEY_ID 103 #define WAVEFORM_ID 104 #define KNOB_ID 105 #define KNOB_SMALL_ID 106 #define FILTERMODE_ID 107 // Image resource locations for this plug. #define BG_FN "resources/img/bg.png" #define WHITE_KEY_FN "resources/img/whitekey.png" #define BLACK_KEY_FN "resources/img/blackkey.png" #define WAVEFORM_FN "resources/img/waveform.png" #define KNOB_FN "resources/img/knob.png" #define KNOB_SMALL_FN "resources/img/knob_small.png" #define FILTERMODE_FN "resources/img/filtermode.png"
Synthesis.rc :
#include "resource.h" BG_ID PNG BG_FN WHITE_KEY_ID PNG WHITE_KEY_FN BLACK_KEY_ID PNG BLACK_KEY_FN WAVEFORM_ID PNG WAVEFORM_FN KNOB_ID PNG KNOB_FN KNOB_SMALL_ID PNG KNOB_SMALL_FN FILTERMODE_ID PNG FILTERMODE_FN
#include "Filter.h"
Synthesis.h private
:
Filter mFilter;
EParams
Synthesis.cpp :
enum EParams { mWaveform = 0, mAttack, mDecay, mSustain, mRelease, mFilterMode, mFilterCutoff, mFilterResonance, mFilterAttack, mFilterDecay, mFilterSustain, mFilterRelease, mFilterEnvelopeAmount, kNumParams };
:
pGraphics->AttachControl(new ISwitchControl(this, 24, 38, mWaveform, &waveformBitmap));
. (Lowpass, Highpass, Bandpass). AttachGraphics(pGraphics)
:
GetParam(mFilterMode)->InitEnum("Filter Mode", Filter::FILTER_MODE_LOWPASS, Filter::kNumFilterModes); IBitmap filtermodeBitmap = pGraphics->LoadIBitmap(FILTERMODE_ID, FILTERMODE_FN, 3); pGraphics->AttachControl(new ISwitchControl(this, 24, 123, mFilterMode, &filtermodeBitmap));
. . knob_small.png :
// Knobs for filter cutoff and resonance IBitmap smallKnobBitmap = pGraphics->LoadIBitmap(KNOB_SMALL_ID, KNOB_SMALL_FN, 64); // Cutoff knob: GetParam(mFilterCutoff)->InitDouble("Cutoff", 0.99, 0.01, 0.99, 0.001); GetParam(mFilterCutoff)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 5, 177, mFilterCutoff, &smallKnobBitmap)); // Resonance knob: GetParam(mFilterResonance)->InitDouble("Resonance", 0.01, 0.01, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 61, 177, mFilterResonance, &smallKnobBitmap));
, 1.0
, calculateFeedbackAmount
, .
ProcessDoubleReplacing
:
leftOutput[i] = rightOutput[i] = mFilter.process(mOscillator.nextSample() * mEnvelopeGenerator.nextSample() * velocity / 127.0);
. switch
Synthesis::OnParamChange
:
case mFilterCutoff: mFilter.setCutoff(GetParam(paramIdx)->Value()); break; case mFilterResonance: mFilter.setResonance(GetParam(paramIdx)->Value()); break; case mFilterMode: mFilter.setFilterMode(static_cast<Filter::FilterMode>(GetParam(paramIdx)->Int())); break;
. - .
— . , , .
:
buf0 += cutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1));
, , ( buf0 – buf1
), feedbackAmount
. calculateFeedbackAmount
feedbackAmount
resonance
, . . , .
, . , , . , , .
-12 / -24 /
! 24 . switch
Filter::process
:
buf2 += cutoff * (buf1 - buf2); buf3 += cutoff * (buf2 – buf3);
: , , . , , , ( ). switch
:
switch (mode) { case FILTER_MODE_LOWPASS: return buf3; case FILTER_MODE_HIGHPASS: return inputValue - buf3; case FILTER_MODE_BANDPASS: return buf0 - buf3; default: return 0.0; }
buf1
buf3
– . -24 /. buf2
buf3
, private
Filter.h :
double buf2; double buf3;
, buf0
c buf1
:
Filter() : // ... buf0(0.0), buf1(0.0), buf2(0.0), buf3(0.0) // ...
, , : . .
?
. , , .
- cutoff
. . cutoffMod
, , cutoff
. #include private
:
double cutoffMod;
:
Filter() : cutoff(0.99), resonance(0.01), cutoffMod(0.0), // ...
. private
:
inline double getCalculatedCutoff() const { return fmax(fmin(cutoff + cutoffMod, 0.99), 0.01); };
calculateFeedbackAmount
:
inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - getCalculatedCutoff()); }
cutoffMod
. feedbackAmount
, :
inline void setCutoffMod(double newCutoffMod) { cutoffMod = newCutoffMod; calculateFeedbackAmount(); }
, . Filter::process
:
if (inputValue == 0.0) return inputValue; double calculatedCutoff = getCalculatedCutoff(); buf0 += calculatedCutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1)); buf1 += calculatedCutoff * (buf0 - buf1); buf2 += calculatedCutoff * (buf1 - buf2); buf3 += calculatedCutoff * (buf2 – buf3);
, . , , . . cutoff
calculatedCutoff
-.
, ( setCutoffMod
), Synthesis
, , . , cutoffMod
. filterEnvelopeAmount
-1
+1
. GUI.
private
Synthesis.h :
EnvelopeGenerator mFilterEnvelopeGenerator; double filterEnvelopeAmount;
MIDI , onNoteOn
onNoteOff
:
inline void onNoteOn(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); }; inline void onNoteOff(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); };
. ProcessDoubleReplacing
:
mFilter.setCutoffMod(mFilterEnvelopeGenerator.nextSample() * filterEnvelopeAmount);
, nextSample
filterEnvelopeAmount
cutoffMod
. filterEnvelopeAmount
:
Synthesis::Synthesis(IPlugInstanceInfo instanceInfo) : IPLUG_CTOR(kNumParams, kNumPrograms, instanceInfo), lastVirtualKeyboardNoteNumber(virtualKeyboardMinimumNoteNumber - 1), filterEnvelopeAmount(0.0) { // ... }
Synthesis::Reset
:
mFilterEnvelopeGenerator.setSampleRate(GetSampleRate());
EParams
, . AttachGraphics
:
// Knobs for filter envelope // Attack knob GetParam(mFilterAttack)->InitDouble("Filter Env Attack", 0.01, 0.01, 10.0, 0.001); GetParam(mFilterAttack)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 139, 178, mFilterAttack, &smallKnobBitmap)); // Decay knob: GetParam(mFilterDecay)->InitDouble("Filter Env Decay", 0.5, 0.01, 15.0, 0.001); GetParam(mFilterDecay)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 195, 178, mFilterDecay, &smallKnobBitmap)); // Sustain knob: GetParam(mFilterSustain)->InitDouble("Filter Env Sustain", 0.1, 0.001, 1.0, 0.001); GetParam(mFilterSustain)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 251, 178, mFilterSustain, &smallKnobBitmap)); // Release knob: GetParam(mFilterRelease)->InitDouble("Filter Env Release", 1.0, 0.001, 15.0, 0.001); GetParam(mFilterRelease)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 307, 178, mFilterRelease, &smallKnobBitmap)); // Filter envelope amount knob: GetParam(mFilterEnvelopeAmount)->InitDouble("Filter Env Amount", 0.0, -1.0, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 363, 178, mFilterEnvelopeAmount, &smallKnobBitmap));
, smallKnobBitmap
. . , . Synthesis::OnParamChange
switch
:
case mFilterAttack: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK, GetParam(paramIdx)->Value()); break; case mFilterDecay: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_DECAY, GetParam(paramIdx)->Value()); break; case mFilterSustain: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_SUSTAIN, GetParam(paramIdx)->Value()); break; case mFilterRelease: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE, GetParam(paramIdx)->Value()); break; case mFilterEnvelopeAmount: filterEnvelopeAmount = GetParam(paramIdx)->Value(); break;
!
! (- C1), :
EnvelopeGenerator
. . !
.
:)
:
martin-finke.de/blog/articles/audio-plugins-013-filter
#include Filter.h ( ) :
class Filter { public: enum FilterMode { FILTER_MODE_LOWPASS = 0, FILTER_MODE_HIGHPASS, FILTER_MODE_BANDPASS, kNumFilterModes }; Filter() : cutoff(0.99), resonance(0.0), mode(FILTER_MODE_LOWPASS), buf0(0.0), buf1(0.0) { calculateFeedbackAmount(); }; double process(double inputValue); inline void setCutoff(double newCutoff) { cutoff = newCutoff; calculateFeedbackAmount(); }; inline void setResonance(double newResonance) { resonance = newResonance; calculateFeedbackAmount(); }; inline void setFilterMode(FilterMode newMode) { mode = newMode; } private: double cutoff; double resonance; FilterMode mode; double feedbackAmount; inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - cutoff); } double buf0; double buf1; };
private
cutoff
resonance
. Mode
(Lowpass, Highpass, Bandpass). feedbackAmount
, buf0
buf1
, .
( calculateFeedbackAmount()
). process
. feedbackAmount
cutoff
resonance
, calculateFeedbackAmount
.
Filter.cpp :
// By Paul Kellett // http://www.musicdsp.org/showone.php?id=29 double Filter::process(double inputValue) { buf0 += cutoff * (inputValue - buf0); buf1 += cutoff * (buf0 - buf1); switch (mode) { case FILTER_MODE_LOWPASS: return buf1; case FILTER_MODE_HIGHPASS: return inputValue - buf0; case FILTER_MODE_BANDPASS: return buf0 - buf1; default: return 0.0; } }
, ? , . « » , (. . 6 ). buf0
buf1
: . inputValue
, - buf0
( ). . 6 / 12 /. switch
, buf1
. , , buf0
, 12, 6 /, .
case
FILTER_MODE_HIGHPASS
. buf0
. inputValue
buf0
, . buf1
, .
case
FILTER_MODE_BANDPASS
, buf0 - buf1
. buf0
, buf1
. , .
: , .
buf1
. ( Infinite Impulse Response , IIR ). , . , , .
, , . , , , , . , NI Massive, « ». , , . , ( Massive ). «» , , , AAS Ultra Analog, , , , . , , , , NI Reaktor. , — , .. .
. GUI. bg.png ( “Move to trash” Xcode), :
filtermode.png knob_small.png ( , 50 50 ) bg.png ( )
ID resource.h :
// Unique IDs for each image resource. #define BG_ID 101 #define WHITE_KEY_ID 102 #define BLACK_KEY_ID 103 #define WAVEFORM_ID 104 #define KNOB_ID 105 #define KNOB_SMALL_ID 106 #define FILTERMODE_ID 107 // Image resource locations for this plug. #define BG_FN "resources/img/bg.png" #define WHITE_KEY_FN "resources/img/whitekey.png" #define BLACK_KEY_FN "resources/img/blackkey.png" #define WAVEFORM_FN "resources/img/waveform.png" #define KNOB_FN "resources/img/knob.png" #define KNOB_SMALL_FN "resources/img/knob_small.png" #define FILTERMODE_FN "resources/img/filtermode.png"
Synthesis.rc :
#include "resource.h" BG_ID PNG BG_FN WHITE_KEY_ID PNG WHITE_KEY_FN BLACK_KEY_ID PNG BLACK_KEY_FN WAVEFORM_ID PNG WAVEFORM_FN KNOB_ID PNG KNOB_FN KNOB_SMALL_ID PNG KNOB_SMALL_FN FILTERMODE_ID PNG FILTERMODE_FN
#include "Filter.h"
Synthesis.h private
:
Filter mFilter;
EParams
Synthesis.cpp :
enum EParams { mWaveform = 0, mAttack, mDecay, mSustain, mRelease, mFilterMode, mFilterCutoff, mFilterResonance, mFilterAttack, mFilterDecay, mFilterSustain, mFilterRelease, mFilterEnvelopeAmount, kNumParams };
:
pGraphics->AttachControl(new ISwitchControl(this, 24, 38, mWaveform, &waveformBitmap));
. (Lowpass, Highpass, Bandpass). AttachGraphics(pGraphics)
:
GetParam(mFilterMode)->InitEnum("Filter Mode", Filter::FILTER_MODE_LOWPASS, Filter::kNumFilterModes); IBitmap filtermodeBitmap = pGraphics->LoadIBitmap(FILTERMODE_ID, FILTERMODE_FN, 3); pGraphics->AttachControl(new ISwitchControl(this, 24, 123, mFilterMode, &filtermodeBitmap));
. . knob_small.png :
// Knobs for filter cutoff and resonance IBitmap smallKnobBitmap = pGraphics->LoadIBitmap(KNOB_SMALL_ID, KNOB_SMALL_FN, 64); // Cutoff knob: GetParam(mFilterCutoff)->InitDouble("Cutoff", 0.99, 0.01, 0.99, 0.001); GetParam(mFilterCutoff)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 5, 177, mFilterCutoff, &smallKnobBitmap)); // Resonance knob: GetParam(mFilterResonance)->InitDouble("Resonance", 0.01, 0.01, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 61, 177, mFilterResonance, &smallKnobBitmap));
, 1.0
, calculateFeedbackAmount
, .
ProcessDoubleReplacing
:
leftOutput[i] = rightOutput[i] = mFilter.process(mOscillator.nextSample() * mEnvelopeGenerator.nextSample() * velocity / 127.0);
. switch
Synthesis::OnParamChange
:
case mFilterCutoff: mFilter.setCutoff(GetParam(paramIdx)->Value()); break; case mFilterResonance: mFilter.setResonance(GetParam(paramIdx)->Value()); break; case mFilterMode: mFilter.setFilterMode(static_cast<Filter::FilterMode>(GetParam(paramIdx)->Int())); break;
. - .
— . , , .
:
buf0 += cutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1));
, , ( buf0 – buf1
), feedbackAmount
. calculateFeedbackAmount
feedbackAmount
resonance
, . . , .
, . , , . , , .
-12 / -24 /
! 24 . switch
Filter::process
:
buf2 += cutoff * (buf1 - buf2); buf3 += cutoff * (buf2 – buf3);
: , , . , , , ( ). switch
:
switch (mode) { case FILTER_MODE_LOWPASS: return buf3; case FILTER_MODE_HIGHPASS: return inputValue - buf3; case FILTER_MODE_BANDPASS: return buf0 - buf3; default: return 0.0; }
buf1
buf3
– . -24 /. buf2
buf3
, private
Filter.h :
double buf2; double buf3;
, buf0
c buf1
:
Filter() : // ... buf0(0.0), buf1(0.0), buf2(0.0), buf3(0.0) // ...
, , : . .
?
. , , .
- cutoff
. . cutoffMod
, , cutoff
. #include private
:
double cutoffMod;
:
Filter() : cutoff(0.99), resonance(0.01), cutoffMod(0.0), // ...
. private
:
inline double getCalculatedCutoff() const { return fmax(fmin(cutoff + cutoffMod, 0.99), 0.01); };
calculateFeedbackAmount
:
inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - getCalculatedCutoff()); }
cutoffMod
. feedbackAmount
, :
inline void setCutoffMod(double newCutoffMod) { cutoffMod = newCutoffMod; calculateFeedbackAmount(); }
, . Filter::process
:
if (inputValue == 0.0) return inputValue; double calculatedCutoff = getCalculatedCutoff(); buf0 += calculatedCutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1)); buf1 += calculatedCutoff * (buf0 - buf1); buf2 += calculatedCutoff * (buf1 - buf2); buf3 += calculatedCutoff * (buf2 – buf3);
, . , , . . cutoff
calculatedCutoff
-.
, ( setCutoffMod
), Synthesis
, , . , cutoffMod
. filterEnvelopeAmount
-1
+1
. GUI.
private
Synthesis.h :
EnvelopeGenerator mFilterEnvelopeGenerator; double filterEnvelopeAmount;
MIDI , onNoteOn
onNoteOff
:
inline void onNoteOn(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); }; inline void onNoteOff(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); };
. ProcessDoubleReplacing
:
mFilter.setCutoffMod(mFilterEnvelopeGenerator.nextSample() * filterEnvelopeAmount);
, nextSample
filterEnvelopeAmount
cutoffMod
. filterEnvelopeAmount
:
Synthesis::Synthesis(IPlugInstanceInfo instanceInfo) : IPLUG_CTOR(kNumParams, kNumPrograms, instanceInfo), lastVirtualKeyboardNoteNumber(virtualKeyboardMinimumNoteNumber - 1), filterEnvelopeAmount(0.0) { // ... }
Synthesis::Reset
:
mFilterEnvelopeGenerator.setSampleRate(GetSampleRate());
EParams
, . AttachGraphics
:
// Knobs for filter envelope // Attack knob GetParam(mFilterAttack)->InitDouble("Filter Env Attack", 0.01, 0.01, 10.0, 0.001); GetParam(mFilterAttack)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 139, 178, mFilterAttack, &smallKnobBitmap)); // Decay knob: GetParam(mFilterDecay)->InitDouble("Filter Env Decay", 0.5, 0.01, 15.0, 0.001); GetParam(mFilterDecay)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 195, 178, mFilterDecay, &smallKnobBitmap)); // Sustain knob: GetParam(mFilterSustain)->InitDouble("Filter Env Sustain", 0.1, 0.001, 1.0, 0.001); GetParam(mFilterSustain)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 251, 178, mFilterSustain, &smallKnobBitmap)); // Release knob: GetParam(mFilterRelease)->InitDouble("Filter Env Release", 1.0, 0.001, 15.0, 0.001); GetParam(mFilterRelease)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 307, 178, mFilterRelease, &smallKnobBitmap)); // Filter envelope amount knob: GetParam(mFilterEnvelopeAmount)->InitDouble("Filter Env Amount", 0.0, -1.0, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 363, 178, mFilterEnvelopeAmount, &smallKnobBitmap));
, smallKnobBitmap
. . , . Synthesis::OnParamChange
switch
:
case mFilterAttack: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK, GetParam(paramIdx)->Value()); break; case mFilterDecay: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_DECAY, GetParam(paramIdx)->Value()); break; case mFilterSustain: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_SUSTAIN, GetParam(paramIdx)->Value()); break; case mFilterRelease: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE, GetParam(paramIdx)->Value()); break; case mFilterEnvelopeAmount: filterEnvelopeAmount = GetParam(paramIdx)->Value(); break;
!
! (- C1), :
EnvelopeGenerator
. . !
.
:)
:
martin-finke.de/blog/articles/audio-plugins-013-filter
#include Filter.h ( ) :
class Filter { public: enum FilterMode { FILTER_MODE_LOWPASS = 0, FILTER_MODE_HIGHPASS, FILTER_MODE_BANDPASS, kNumFilterModes }; Filter() : cutoff(0.99), resonance(0.0), mode(FILTER_MODE_LOWPASS), buf0(0.0), buf1(0.0) { calculateFeedbackAmount(); }; double process(double inputValue); inline void setCutoff(double newCutoff) { cutoff = newCutoff; calculateFeedbackAmount(); }; inline void setResonance(double newResonance) { resonance = newResonance; calculateFeedbackAmount(); }; inline void setFilterMode(FilterMode newMode) { mode = newMode; } private: double cutoff; double resonance; FilterMode mode; double feedbackAmount; inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - cutoff); } double buf0; double buf1; };
private
cutoff
resonance
. Mode
(Lowpass, Highpass, Bandpass). feedbackAmount
, buf0
buf1
, .
( calculateFeedbackAmount()
). process
. feedbackAmount
cutoff
resonance
, calculateFeedbackAmount
.
Filter.cpp :
// By Paul Kellett // http://www.musicdsp.org/showone.php?id=29 double Filter::process(double inputValue) { buf0 += cutoff * (inputValue - buf0); buf1 += cutoff * (buf0 - buf1); switch (mode) { case FILTER_MODE_LOWPASS: return buf1; case FILTER_MODE_HIGHPASS: return inputValue - buf0; case FILTER_MODE_BANDPASS: return buf0 - buf1; default: return 0.0; } }
, ? , . « » , (. . 6 ). buf0
buf1
: . inputValue
, - buf0
( ). . 6 / 12 /. switch
, buf1
. , , buf0
, 12, 6 /, .
case
FILTER_MODE_HIGHPASS
. buf0
. inputValue
buf0
, . buf1
, .
case
FILTER_MODE_BANDPASS
, buf0 - buf1
. buf0
, buf1
. , .
: , .
buf1
. ( Infinite Impulse Response , IIR ). , . , , .
, , . , , , , . , NI Massive, « ». , , . , ( Massive ). «» , , , AAS Ultra Analog, , , , . , , , , NI Reaktor. , — , .. .
. GUI. bg.png ( “Move to trash” Xcode), :
filtermode.png knob_small.png ( , 50 50 ) bg.png ( )
ID resource.h :
// Unique IDs for each image resource. #define BG_ID 101 #define WHITE_KEY_ID 102 #define BLACK_KEY_ID 103 #define WAVEFORM_ID 104 #define KNOB_ID 105 #define KNOB_SMALL_ID 106 #define FILTERMODE_ID 107 // Image resource locations for this plug. #define BG_FN "resources/img/bg.png" #define WHITE_KEY_FN "resources/img/whitekey.png" #define BLACK_KEY_FN "resources/img/blackkey.png" #define WAVEFORM_FN "resources/img/waveform.png" #define KNOB_FN "resources/img/knob.png" #define KNOB_SMALL_FN "resources/img/knob_small.png" #define FILTERMODE_FN "resources/img/filtermode.png"
Synthesis.rc :
#include "resource.h" BG_ID PNG BG_FN WHITE_KEY_ID PNG WHITE_KEY_FN BLACK_KEY_ID PNG BLACK_KEY_FN WAVEFORM_ID PNG WAVEFORM_FN KNOB_ID PNG KNOB_FN KNOB_SMALL_ID PNG KNOB_SMALL_FN FILTERMODE_ID PNG FILTERMODE_FN
#include "Filter.h"
Synthesis.h private
:
Filter mFilter;
EParams
Synthesis.cpp :
enum EParams { mWaveform = 0, mAttack, mDecay, mSustain, mRelease, mFilterMode, mFilterCutoff, mFilterResonance, mFilterAttack, mFilterDecay, mFilterSustain, mFilterRelease, mFilterEnvelopeAmount, kNumParams };
:
pGraphics->AttachControl(new ISwitchControl(this, 24, 38, mWaveform, &waveformBitmap));
. (Lowpass, Highpass, Bandpass). AttachGraphics(pGraphics)
:
GetParam(mFilterMode)->InitEnum("Filter Mode", Filter::FILTER_MODE_LOWPASS, Filter::kNumFilterModes); IBitmap filtermodeBitmap = pGraphics->LoadIBitmap(FILTERMODE_ID, FILTERMODE_FN, 3); pGraphics->AttachControl(new ISwitchControl(this, 24, 123, mFilterMode, &filtermodeBitmap));
. . knob_small.png :
// Knobs for filter cutoff and resonance IBitmap smallKnobBitmap = pGraphics->LoadIBitmap(KNOB_SMALL_ID, KNOB_SMALL_FN, 64); // Cutoff knob: GetParam(mFilterCutoff)->InitDouble("Cutoff", 0.99, 0.01, 0.99, 0.001); GetParam(mFilterCutoff)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 5, 177, mFilterCutoff, &smallKnobBitmap)); // Resonance knob: GetParam(mFilterResonance)->InitDouble("Resonance", 0.01, 0.01, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 61, 177, mFilterResonance, &smallKnobBitmap));
, 1.0
, calculateFeedbackAmount
, .
ProcessDoubleReplacing
:
leftOutput[i] = rightOutput[i] = mFilter.process(mOscillator.nextSample() * mEnvelopeGenerator.nextSample() * velocity / 127.0);
. switch
Synthesis::OnParamChange
:
case mFilterCutoff: mFilter.setCutoff(GetParam(paramIdx)->Value()); break; case mFilterResonance: mFilter.setResonance(GetParam(paramIdx)->Value()); break; case mFilterMode: mFilter.setFilterMode(static_cast<Filter::FilterMode>(GetParam(paramIdx)->Int())); break;
. - .
— . , , .
:
buf0 += cutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1));
, , ( buf0 – buf1
), feedbackAmount
. calculateFeedbackAmount
feedbackAmount
resonance
, . . , .
, . , , . , , .
-12 / -24 /
! 24 . switch
Filter::process
:
buf2 += cutoff * (buf1 - buf2); buf3 += cutoff * (buf2 – buf3);
: , , . , , , ( ). switch
:
switch (mode) { case FILTER_MODE_LOWPASS: return buf3; case FILTER_MODE_HIGHPASS: return inputValue - buf3; case FILTER_MODE_BANDPASS: return buf0 - buf3; default: return 0.0; }
buf1
buf3
– . -24 /. buf2
buf3
, private
Filter.h :
double buf2; double buf3;
, buf0
c buf1
:
Filter() : // ... buf0(0.0), buf1(0.0), buf2(0.0), buf3(0.0) // ...
, , : . .
?
. , , .
- cutoff
. . cutoffMod
, , cutoff
. #include private
:
double cutoffMod;
:
Filter() : cutoff(0.99), resonance(0.01), cutoffMod(0.0), // ...
. private
:
inline double getCalculatedCutoff() const { return fmax(fmin(cutoff + cutoffMod, 0.99), 0.01); };
calculateFeedbackAmount
:
inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - getCalculatedCutoff()); }
cutoffMod
. feedbackAmount
, :
inline void setCutoffMod(double newCutoffMod) { cutoffMod = newCutoffMod; calculateFeedbackAmount(); }
, . Filter::process
:
if (inputValue == 0.0) return inputValue; double calculatedCutoff = getCalculatedCutoff(); buf0 += calculatedCutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1)); buf1 += calculatedCutoff * (buf0 - buf1); buf2 += calculatedCutoff * (buf1 - buf2); buf3 += calculatedCutoff * (buf2 – buf3);
, . , , . . cutoff
calculatedCutoff
-.
, ( setCutoffMod
), Synthesis
, , . , cutoffMod
. filterEnvelopeAmount
-1
+1
. GUI.
private
Synthesis.h :
EnvelopeGenerator mFilterEnvelopeGenerator; double filterEnvelopeAmount;
MIDI , onNoteOn
onNoteOff
:
inline void onNoteOn(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); }; inline void onNoteOff(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); };
. ProcessDoubleReplacing
:
mFilter.setCutoffMod(mFilterEnvelopeGenerator.nextSample() * filterEnvelopeAmount);
, nextSample
filterEnvelopeAmount
cutoffMod
. filterEnvelopeAmount
:
Synthesis::Synthesis(IPlugInstanceInfo instanceInfo) : IPLUG_CTOR(kNumParams, kNumPrograms, instanceInfo), lastVirtualKeyboardNoteNumber(virtualKeyboardMinimumNoteNumber - 1), filterEnvelopeAmount(0.0) { // ... }
Synthesis::Reset
:
mFilterEnvelopeGenerator.setSampleRate(GetSampleRate());
EParams
, . AttachGraphics
:
// Knobs for filter envelope // Attack knob GetParam(mFilterAttack)->InitDouble("Filter Env Attack", 0.01, 0.01, 10.0, 0.001); GetParam(mFilterAttack)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 139, 178, mFilterAttack, &smallKnobBitmap)); // Decay knob: GetParam(mFilterDecay)->InitDouble("Filter Env Decay", 0.5, 0.01, 15.0, 0.001); GetParam(mFilterDecay)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 195, 178, mFilterDecay, &smallKnobBitmap)); // Sustain knob: GetParam(mFilterSustain)->InitDouble("Filter Env Sustain", 0.1, 0.001, 1.0, 0.001); GetParam(mFilterSustain)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 251, 178, mFilterSustain, &smallKnobBitmap)); // Release knob: GetParam(mFilterRelease)->InitDouble("Filter Env Release", 1.0, 0.001, 15.0, 0.001); GetParam(mFilterRelease)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 307, 178, mFilterRelease, &smallKnobBitmap)); // Filter envelope amount knob: GetParam(mFilterEnvelopeAmount)->InitDouble("Filter Env Amount", 0.0, -1.0, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 363, 178, mFilterEnvelopeAmount, &smallKnobBitmap));
, smallKnobBitmap
. . , . Synthesis::OnParamChange
switch
:
case mFilterAttack: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK, GetParam(paramIdx)->Value()); break; case mFilterDecay: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_DECAY, GetParam(paramIdx)->Value()); break; case mFilterSustain: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_SUSTAIN, GetParam(paramIdx)->Value()); break; case mFilterRelease: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE, GetParam(paramIdx)->Value()); break; case mFilterEnvelopeAmount: filterEnvelopeAmount = GetParam(paramIdx)->Value(); break;
!
! (- C1), :
EnvelopeGenerator
. . !
.
:)
:
martin-finke.de/blog/articles/audio-plugins-013-filter
#include Filter.h ( ) :
class Filter { public: enum FilterMode { FILTER_MODE_LOWPASS = 0, FILTER_MODE_HIGHPASS, FILTER_MODE_BANDPASS, kNumFilterModes }; Filter() : cutoff(0.99), resonance(0.0), mode(FILTER_MODE_LOWPASS), buf0(0.0), buf1(0.0) { calculateFeedbackAmount(); }; double process(double inputValue); inline void setCutoff(double newCutoff) { cutoff = newCutoff; calculateFeedbackAmount(); }; inline void setResonance(double newResonance) { resonance = newResonance; calculateFeedbackAmount(); }; inline void setFilterMode(FilterMode newMode) { mode = newMode; } private: double cutoff; double resonance; FilterMode mode; double feedbackAmount; inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - cutoff); } double buf0; double buf1; };
private
cutoff
resonance
. Mode
(Lowpass, Highpass, Bandpass). feedbackAmount
, buf0
buf1
, .
( calculateFeedbackAmount()
). process
. feedbackAmount
cutoff
resonance
, calculateFeedbackAmount
.
Filter.cpp :
// By Paul Kellett // http://www.musicdsp.org/showone.php?id=29 double Filter::process(double inputValue) { buf0 += cutoff * (inputValue - buf0); buf1 += cutoff * (buf0 - buf1); switch (mode) { case FILTER_MODE_LOWPASS: return buf1; case FILTER_MODE_HIGHPASS: return inputValue - buf0; case FILTER_MODE_BANDPASS: return buf0 - buf1; default: return 0.0; } }
, ? , . « » , (. . 6 ). buf0
buf1
: . inputValue
, - buf0
( ). . 6 / 12 /. switch
, buf1
. , , buf0
, 12, 6 /, .
case
FILTER_MODE_HIGHPASS
. buf0
. inputValue
buf0
, . buf1
, .
case
FILTER_MODE_BANDPASS
, buf0 - buf1
. buf0
, buf1
. , .
: , .
buf1
. ( Infinite Impulse Response , IIR ). , . , , .
, , . , , , , . , NI Massive, « ». , , . , ( Massive ). «» , , , AAS Ultra Analog, , , , . , , , , NI Reaktor. , — , .. .
. GUI. bg.png ( “Move to trash” Xcode), :
filtermode.png knob_small.png ( , 50 50 ) bg.png ( )
ID resource.h :
// Unique IDs for each image resource. #define BG_ID 101 #define WHITE_KEY_ID 102 #define BLACK_KEY_ID 103 #define WAVEFORM_ID 104 #define KNOB_ID 105 #define KNOB_SMALL_ID 106 #define FILTERMODE_ID 107 // Image resource locations for this plug. #define BG_FN "resources/img/bg.png" #define WHITE_KEY_FN "resources/img/whitekey.png" #define BLACK_KEY_FN "resources/img/blackkey.png" #define WAVEFORM_FN "resources/img/waveform.png" #define KNOB_FN "resources/img/knob.png" #define KNOB_SMALL_FN "resources/img/knob_small.png" #define FILTERMODE_FN "resources/img/filtermode.png"
Synthesis.rc :
#include "resource.h" BG_ID PNG BG_FN WHITE_KEY_ID PNG WHITE_KEY_FN BLACK_KEY_ID PNG BLACK_KEY_FN WAVEFORM_ID PNG WAVEFORM_FN KNOB_ID PNG KNOB_FN KNOB_SMALL_ID PNG KNOB_SMALL_FN FILTERMODE_ID PNG FILTERMODE_FN
#include "Filter.h"
Synthesis.h private
:
Filter mFilter;
EParams
Synthesis.cpp :
enum EParams { mWaveform = 0, mAttack, mDecay, mSustain, mRelease, mFilterMode, mFilterCutoff, mFilterResonance, mFilterAttack, mFilterDecay, mFilterSustain, mFilterRelease, mFilterEnvelopeAmount, kNumParams };
:
pGraphics->AttachControl(new ISwitchControl(this, 24, 38, mWaveform, &waveformBitmap));
. (Lowpass, Highpass, Bandpass). AttachGraphics(pGraphics)
:
GetParam(mFilterMode)->InitEnum("Filter Mode", Filter::FILTER_MODE_LOWPASS, Filter::kNumFilterModes); IBitmap filtermodeBitmap = pGraphics->LoadIBitmap(FILTERMODE_ID, FILTERMODE_FN, 3); pGraphics->AttachControl(new ISwitchControl(this, 24, 123, mFilterMode, &filtermodeBitmap));
. . knob_small.png :
// Knobs for filter cutoff and resonance IBitmap smallKnobBitmap = pGraphics->LoadIBitmap(KNOB_SMALL_ID, KNOB_SMALL_FN, 64); // Cutoff knob: GetParam(mFilterCutoff)->InitDouble("Cutoff", 0.99, 0.01, 0.99, 0.001); GetParam(mFilterCutoff)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 5, 177, mFilterCutoff, &smallKnobBitmap)); // Resonance knob: GetParam(mFilterResonance)->InitDouble("Resonance", 0.01, 0.01, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 61, 177, mFilterResonance, &smallKnobBitmap));
, 1.0
, calculateFeedbackAmount
, .
ProcessDoubleReplacing
:
leftOutput[i] = rightOutput[i] = mFilter.process(mOscillator.nextSample() * mEnvelopeGenerator.nextSample() * velocity / 127.0);
. switch
Synthesis::OnParamChange
:
case mFilterCutoff: mFilter.setCutoff(GetParam(paramIdx)->Value()); break; case mFilterResonance: mFilter.setResonance(GetParam(paramIdx)->Value()); break; case mFilterMode: mFilter.setFilterMode(static_cast<Filter::FilterMode>(GetParam(paramIdx)->Int())); break;
. - .
— . , , .
:
buf0 += cutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1));
, , ( buf0 – buf1
), feedbackAmount
. calculateFeedbackAmount
feedbackAmount
resonance
, . . , .
, . , , . , , .
-12 / -24 /
! 24 . switch
Filter::process
:
buf2 += cutoff * (buf1 - buf2); buf3 += cutoff * (buf2 – buf3);
: , , . , , , ( ). switch
:
switch (mode) { case FILTER_MODE_LOWPASS: return buf3; case FILTER_MODE_HIGHPASS: return inputValue - buf3; case FILTER_MODE_BANDPASS: return buf0 - buf3; default: return 0.0; }
buf1
buf3
– . -24 /. buf2
buf3
, private
Filter.h :
double buf2; double buf3;
, buf0
c buf1
:
Filter() : // ... buf0(0.0), buf1(0.0), buf2(0.0), buf3(0.0) // ...
, , : . .
?
. , , .
- cutoff
. . cutoffMod
, , cutoff
. #include private
:
double cutoffMod;
:
Filter() : cutoff(0.99), resonance(0.01), cutoffMod(0.0), // ...
. private
:
inline double getCalculatedCutoff() const { return fmax(fmin(cutoff + cutoffMod, 0.99), 0.01); };
calculateFeedbackAmount
:
inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - getCalculatedCutoff()); }
cutoffMod
. feedbackAmount
, :
inline void setCutoffMod(double newCutoffMod) { cutoffMod = newCutoffMod; calculateFeedbackAmount(); }
, . Filter::process
:
if (inputValue == 0.0) return inputValue; double calculatedCutoff = getCalculatedCutoff(); buf0 += calculatedCutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1)); buf1 += calculatedCutoff * (buf0 - buf1); buf2 += calculatedCutoff * (buf1 - buf2); buf3 += calculatedCutoff * (buf2 – buf3);
, . , , . . cutoff
calculatedCutoff
-.
, ( setCutoffMod
), Synthesis
, , . , cutoffMod
. filterEnvelopeAmount
-1
+1
. GUI.
private
Synthesis.h :
EnvelopeGenerator mFilterEnvelopeGenerator; double filterEnvelopeAmount;
MIDI , onNoteOn
onNoteOff
:
inline void onNoteOn(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); }; inline void onNoteOff(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); };
. ProcessDoubleReplacing
:
mFilter.setCutoffMod(mFilterEnvelopeGenerator.nextSample() * filterEnvelopeAmount);
, nextSample
filterEnvelopeAmount
cutoffMod
. filterEnvelopeAmount
:
Synthesis::Synthesis(IPlugInstanceInfo instanceInfo) : IPLUG_CTOR(kNumParams, kNumPrograms, instanceInfo), lastVirtualKeyboardNoteNumber(virtualKeyboardMinimumNoteNumber - 1), filterEnvelopeAmount(0.0) { // ... }
Synthesis::Reset
:
mFilterEnvelopeGenerator.setSampleRate(GetSampleRate());
EParams
, . AttachGraphics
:
// Knobs for filter envelope // Attack knob GetParam(mFilterAttack)->InitDouble("Filter Env Attack", 0.01, 0.01, 10.0, 0.001); GetParam(mFilterAttack)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 139, 178, mFilterAttack, &smallKnobBitmap)); // Decay knob: GetParam(mFilterDecay)->InitDouble("Filter Env Decay", 0.5, 0.01, 15.0, 0.001); GetParam(mFilterDecay)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 195, 178, mFilterDecay, &smallKnobBitmap)); // Sustain knob: GetParam(mFilterSustain)->InitDouble("Filter Env Sustain", 0.1, 0.001, 1.0, 0.001); GetParam(mFilterSustain)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 251, 178, mFilterSustain, &smallKnobBitmap)); // Release knob: GetParam(mFilterRelease)->InitDouble("Filter Env Release", 1.0, 0.001, 15.0, 0.001); GetParam(mFilterRelease)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 307, 178, mFilterRelease, &smallKnobBitmap)); // Filter envelope amount knob: GetParam(mFilterEnvelopeAmount)->InitDouble("Filter Env Amount", 0.0, -1.0, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 363, 178, mFilterEnvelopeAmount, &smallKnobBitmap));
, smallKnobBitmap
. . , . Synthesis::OnParamChange
switch
:
case mFilterAttack: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK, GetParam(paramIdx)->Value()); break; case mFilterDecay: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_DECAY, GetParam(paramIdx)->Value()); break; case mFilterSustain: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_SUSTAIN, GetParam(paramIdx)->Value()); break; case mFilterRelease: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE, GetParam(paramIdx)->Value()); break; case mFilterEnvelopeAmount: filterEnvelopeAmount = GetParam(paramIdx)->Value(); break;
!
! (- C1), :
EnvelopeGenerator
. . !
.
:)
:
martin-finke.de/blog/articles/audio-plugins-013-filter
#include Filter.h ( ) :
class Filter { public: enum FilterMode { FILTER_MODE_LOWPASS = 0, FILTER_MODE_HIGHPASS, FILTER_MODE_BANDPASS, kNumFilterModes }; Filter() : cutoff(0.99), resonance(0.0), mode(FILTER_MODE_LOWPASS), buf0(0.0), buf1(0.0) { calculateFeedbackAmount(); }; double process(double inputValue); inline void setCutoff(double newCutoff) { cutoff = newCutoff; calculateFeedbackAmount(); }; inline void setResonance(double newResonance) { resonance = newResonance; calculateFeedbackAmount(); }; inline void setFilterMode(FilterMode newMode) { mode = newMode; } private: double cutoff; double resonance; FilterMode mode; double feedbackAmount; inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - cutoff); } double buf0; double buf1; };
private
cutoff
resonance
. Mode
(Lowpass, Highpass, Bandpass). feedbackAmount
, buf0
buf1
, .
( calculateFeedbackAmount()
). process
. feedbackAmount
cutoff
resonance
, calculateFeedbackAmount
.
Filter.cpp :
// By Paul Kellett // http://www.musicdsp.org/showone.php?id=29 double Filter::process(double inputValue) { buf0 += cutoff * (inputValue - buf0); buf1 += cutoff * (buf0 - buf1); switch (mode) { case FILTER_MODE_LOWPASS: return buf1; case FILTER_MODE_HIGHPASS: return inputValue - buf0; case FILTER_MODE_BANDPASS: return buf0 - buf1; default: return 0.0; } }
, ? , . « » , (. . 6 ). buf0
buf1
: . inputValue
, - buf0
( ). . 6 / 12 /. switch
, buf1
. , , buf0
, 12, 6 /, .
case
FILTER_MODE_HIGHPASS
. buf0
. inputValue
buf0
, . buf1
, .
case
FILTER_MODE_BANDPASS
, buf0 - buf1
. buf0
, buf1
. , .
: , .
buf1
. ( Infinite Impulse Response , IIR ). , . , , .
, , . , , , , . , NI Massive, « ». , , . , ( Massive ). «» , , , AAS Ultra Analog, , , , . , , , , NI Reaktor. , — , .. .
. GUI. bg.png ( “Move to trash” Xcode), :
filtermode.png knob_small.png ( , 50 50 ) bg.png ( )
ID resource.h :
// Unique IDs for each image resource. #define BG_ID 101 #define WHITE_KEY_ID 102 #define BLACK_KEY_ID 103 #define WAVEFORM_ID 104 #define KNOB_ID 105 #define KNOB_SMALL_ID 106 #define FILTERMODE_ID 107 // Image resource locations for this plug. #define BG_FN "resources/img/bg.png" #define WHITE_KEY_FN "resources/img/whitekey.png" #define BLACK_KEY_FN "resources/img/blackkey.png" #define WAVEFORM_FN "resources/img/waveform.png" #define KNOB_FN "resources/img/knob.png" #define KNOB_SMALL_FN "resources/img/knob_small.png" #define FILTERMODE_FN "resources/img/filtermode.png"
Synthesis.rc :
#include "resource.h" BG_ID PNG BG_FN WHITE_KEY_ID PNG WHITE_KEY_FN BLACK_KEY_ID PNG BLACK_KEY_FN WAVEFORM_ID PNG WAVEFORM_FN KNOB_ID PNG KNOB_FN KNOB_SMALL_ID PNG KNOB_SMALL_FN FILTERMODE_ID PNG FILTERMODE_FN
#include "Filter.h"
Synthesis.h private
:
Filter mFilter;
EParams
Synthesis.cpp :
enum EParams { mWaveform = 0, mAttack, mDecay, mSustain, mRelease, mFilterMode, mFilterCutoff, mFilterResonance, mFilterAttack, mFilterDecay, mFilterSustain, mFilterRelease, mFilterEnvelopeAmount, kNumParams };
:
pGraphics->AttachControl(new ISwitchControl(this, 24, 38, mWaveform, &waveformBitmap));
. (Lowpass, Highpass, Bandpass). AttachGraphics(pGraphics)
:
GetParam(mFilterMode)->InitEnum("Filter Mode", Filter::FILTER_MODE_LOWPASS, Filter::kNumFilterModes); IBitmap filtermodeBitmap = pGraphics->LoadIBitmap(FILTERMODE_ID, FILTERMODE_FN, 3); pGraphics->AttachControl(new ISwitchControl(this, 24, 123, mFilterMode, &filtermodeBitmap));
. . knob_small.png :
// Knobs for filter cutoff and resonance IBitmap smallKnobBitmap = pGraphics->LoadIBitmap(KNOB_SMALL_ID, KNOB_SMALL_FN, 64); // Cutoff knob: GetParam(mFilterCutoff)->InitDouble("Cutoff", 0.99, 0.01, 0.99, 0.001); GetParam(mFilterCutoff)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 5, 177, mFilterCutoff, &smallKnobBitmap)); // Resonance knob: GetParam(mFilterResonance)->InitDouble("Resonance", 0.01, 0.01, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 61, 177, mFilterResonance, &smallKnobBitmap));
, 1.0
, calculateFeedbackAmount
, .
ProcessDoubleReplacing
:
leftOutput[i] = rightOutput[i] = mFilter.process(mOscillator.nextSample() * mEnvelopeGenerator.nextSample() * velocity / 127.0);
. switch
Synthesis::OnParamChange
:
case mFilterCutoff: mFilter.setCutoff(GetParam(paramIdx)->Value()); break; case mFilterResonance: mFilter.setResonance(GetParam(paramIdx)->Value()); break; case mFilterMode: mFilter.setFilterMode(static_cast<Filter::FilterMode>(GetParam(paramIdx)->Int())); break;
. - .
— . , , .
:
buf0 += cutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1));
, , ( buf0 – buf1
), feedbackAmount
. calculateFeedbackAmount
feedbackAmount
resonance
, . . , .
, . , , . , , .
-12 / -24 /
! 24 . switch
Filter::process
:
buf2 += cutoff * (buf1 - buf2); buf3 += cutoff * (buf2 – buf3);
: , , . , , , ( ). switch
:
switch (mode) { case FILTER_MODE_LOWPASS: return buf3; case FILTER_MODE_HIGHPASS: return inputValue - buf3; case FILTER_MODE_BANDPASS: return buf0 - buf3; default: return 0.0; }
buf1
buf3
– . -24 /. buf2
buf3
, private
Filter.h :
double buf2; double buf3;
, buf0
c buf1
:
Filter() : // ... buf0(0.0), buf1(0.0), buf2(0.0), buf3(0.0) // ...
, , : . .
?
. , , .
- cutoff
. . cutoffMod
, , cutoff
. #include private
:
double cutoffMod;
:
Filter() : cutoff(0.99), resonance(0.01), cutoffMod(0.0), // ...
. private
:
inline double getCalculatedCutoff() const { return fmax(fmin(cutoff + cutoffMod, 0.99), 0.01); };
calculateFeedbackAmount
:
inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - getCalculatedCutoff()); }
cutoffMod
. feedbackAmount
, :
inline void setCutoffMod(double newCutoffMod) { cutoffMod = newCutoffMod; calculateFeedbackAmount(); }
, . Filter::process
:
if (inputValue == 0.0) return inputValue; double calculatedCutoff = getCalculatedCutoff(); buf0 += calculatedCutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1)); buf1 += calculatedCutoff * (buf0 - buf1); buf2 += calculatedCutoff * (buf1 - buf2); buf3 += calculatedCutoff * (buf2 – buf3);
, . , , . . cutoff
calculatedCutoff
-.
, ( setCutoffMod
), Synthesis
, , . , cutoffMod
. filterEnvelopeAmount
-1
+1
. GUI.
private
Synthesis.h :
EnvelopeGenerator mFilterEnvelopeGenerator; double filterEnvelopeAmount;
MIDI , onNoteOn
onNoteOff
:
inline void onNoteOn(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); }; inline void onNoteOff(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); };
. ProcessDoubleReplacing
:
mFilter.setCutoffMod(mFilterEnvelopeGenerator.nextSample() * filterEnvelopeAmount);
, nextSample
filterEnvelopeAmount
cutoffMod
. filterEnvelopeAmount
:
Synthesis::Synthesis(IPlugInstanceInfo instanceInfo) : IPLUG_CTOR(kNumParams, kNumPrograms, instanceInfo), lastVirtualKeyboardNoteNumber(virtualKeyboardMinimumNoteNumber - 1), filterEnvelopeAmount(0.0) { // ... }
Synthesis::Reset
:
mFilterEnvelopeGenerator.setSampleRate(GetSampleRate());
EParams
, . AttachGraphics
:
// Knobs for filter envelope // Attack knob GetParam(mFilterAttack)->InitDouble("Filter Env Attack", 0.01, 0.01, 10.0, 0.001); GetParam(mFilterAttack)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 139, 178, mFilterAttack, &smallKnobBitmap)); // Decay knob: GetParam(mFilterDecay)->InitDouble("Filter Env Decay", 0.5, 0.01, 15.0, 0.001); GetParam(mFilterDecay)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 195, 178, mFilterDecay, &smallKnobBitmap)); // Sustain knob: GetParam(mFilterSustain)->InitDouble("Filter Env Sustain", 0.1, 0.001, 1.0, 0.001); GetParam(mFilterSustain)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 251, 178, mFilterSustain, &smallKnobBitmap)); // Release knob: GetParam(mFilterRelease)->InitDouble("Filter Env Release", 1.0, 0.001, 15.0, 0.001); GetParam(mFilterRelease)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 307, 178, mFilterRelease, &smallKnobBitmap)); // Filter envelope amount knob: GetParam(mFilterEnvelopeAmount)->InitDouble("Filter Env Amount", 0.0, -1.0, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 363, 178, mFilterEnvelopeAmount, &smallKnobBitmap));
, smallKnobBitmap
. . , . Synthesis::OnParamChange
switch
:
case mFilterAttack: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK, GetParam(paramIdx)->Value()); break; case mFilterDecay: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_DECAY, GetParam(paramIdx)->Value()); break; case mFilterSustain: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_SUSTAIN, GetParam(paramIdx)->Value()); break; case mFilterRelease: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE, GetParam(paramIdx)->Value()); break; case mFilterEnvelopeAmount: filterEnvelopeAmount = GetParam(paramIdx)->Value(); break;
!
! (- C1), :
EnvelopeGenerator
. . !
.
:)
:
martin-finke.de/blog/articles/audio-plugins-013-filter
#include Filter.h ( ) :
class Filter { public: enum FilterMode { FILTER_MODE_LOWPASS = 0, FILTER_MODE_HIGHPASS, FILTER_MODE_BANDPASS, kNumFilterModes }; Filter() : cutoff(0.99), resonance(0.0), mode(FILTER_MODE_LOWPASS), buf0(0.0), buf1(0.0) { calculateFeedbackAmount(); }; double process(double inputValue); inline void setCutoff(double newCutoff) { cutoff = newCutoff; calculateFeedbackAmount(); }; inline void setResonance(double newResonance) { resonance = newResonance; calculateFeedbackAmount(); }; inline void setFilterMode(FilterMode newMode) { mode = newMode; } private: double cutoff; double resonance; FilterMode mode; double feedbackAmount; inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - cutoff); } double buf0; double buf1; };
private
cutoff
resonance
. Mode
(Lowpass, Highpass, Bandpass). feedbackAmount
, buf0
buf1
, .
( calculateFeedbackAmount()
). process
. feedbackAmount
cutoff
resonance
, calculateFeedbackAmount
.
Filter.cpp :
// By Paul Kellett // http://www.musicdsp.org/showone.php?id=29 double Filter::process(double inputValue) { buf0 += cutoff * (inputValue - buf0); buf1 += cutoff * (buf0 - buf1); switch (mode) { case FILTER_MODE_LOWPASS: return buf1; case FILTER_MODE_HIGHPASS: return inputValue - buf0; case FILTER_MODE_BANDPASS: return buf0 - buf1; default: return 0.0; } }
, ? , . « » , (. . 6 ). buf0
buf1
: . inputValue
, - buf0
( ). . 6 / 12 /. switch
, buf1
. , , buf0
, 12, 6 /, .
case
FILTER_MODE_HIGHPASS
. buf0
. inputValue
buf0
, . buf1
, .
case
FILTER_MODE_BANDPASS
, buf0 - buf1
. buf0
, buf1
. , .
: , .
buf1
. ( Infinite Impulse Response , IIR ). , . , , .
, , . , , , , . , NI Massive, « ». , , . , ( Massive ). «» , , , AAS Ultra Analog, , , , . , , , , NI Reaktor. , — , .. .
. GUI. bg.png ( “Move to trash” Xcode), :
filtermode.png knob_small.png ( , 50 50 ) bg.png ( )
ID resource.h :
// Unique IDs for each image resource. #define BG_ID 101 #define WHITE_KEY_ID 102 #define BLACK_KEY_ID 103 #define WAVEFORM_ID 104 #define KNOB_ID 105 #define KNOB_SMALL_ID 106 #define FILTERMODE_ID 107 // Image resource locations for this plug. #define BG_FN "resources/img/bg.png" #define WHITE_KEY_FN "resources/img/whitekey.png" #define BLACK_KEY_FN "resources/img/blackkey.png" #define WAVEFORM_FN "resources/img/waveform.png" #define KNOB_FN "resources/img/knob.png" #define KNOB_SMALL_FN "resources/img/knob_small.png" #define FILTERMODE_FN "resources/img/filtermode.png"
Synthesis.rc :
#include "resource.h" BG_ID PNG BG_FN WHITE_KEY_ID PNG WHITE_KEY_FN BLACK_KEY_ID PNG BLACK_KEY_FN WAVEFORM_ID PNG WAVEFORM_FN KNOB_ID PNG KNOB_FN KNOB_SMALL_ID PNG KNOB_SMALL_FN FILTERMODE_ID PNG FILTERMODE_FN
#include "Filter.h"
Synthesis.h private
:
Filter mFilter;
EParams
Synthesis.cpp :
enum EParams { mWaveform = 0, mAttack, mDecay, mSustain, mRelease, mFilterMode, mFilterCutoff, mFilterResonance, mFilterAttack, mFilterDecay, mFilterSustain, mFilterRelease, mFilterEnvelopeAmount, kNumParams };
:
pGraphics->AttachControl(new ISwitchControl(this, 24, 38, mWaveform, &waveformBitmap));
. (Lowpass, Highpass, Bandpass). AttachGraphics(pGraphics)
:
GetParam(mFilterMode)->InitEnum("Filter Mode", Filter::FILTER_MODE_LOWPASS, Filter::kNumFilterModes); IBitmap filtermodeBitmap = pGraphics->LoadIBitmap(FILTERMODE_ID, FILTERMODE_FN, 3); pGraphics->AttachControl(new ISwitchControl(this, 24, 123, mFilterMode, &filtermodeBitmap));
. . knob_small.png :
// Knobs for filter cutoff and resonance IBitmap smallKnobBitmap = pGraphics->LoadIBitmap(KNOB_SMALL_ID, KNOB_SMALL_FN, 64); // Cutoff knob: GetParam(mFilterCutoff)->InitDouble("Cutoff", 0.99, 0.01, 0.99, 0.001); GetParam(mFilterCutoff)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 5, 177, mFilterCutoff, &smallKnobBitmap)); // Resonance knob: GetParam(mFilterResonance)->InitDouble("Resonance", 0.01, 0.01, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 61, 177, mFilterResonance, &smallKnobBitmap));
, 1.0
, calculateFeedbackAmount
, .
ProcessDoubleReplacing
:
leftOutput[i] = rightOutput[i] = mFilter.process(mOscillator.nextSample() * mEnvelopeGenerator.nextSample() * velocity / 127.0);
. switch
Synthesis::OnParamChange
:
case mFilterCutoff: mFilter.setCutoff(GetParam(paramIdx)->Value()); break; case mFilterResonance: mFilter.setResonance(GetParam(paramIdx)->Value()); break; case mFilterMode: mFilter.setFilterMode(static_cast<Filter::FilterMode>(GetParam(paramIdx)->Int())); break;
. - .
— . , , .
:
buf0 += cutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1));
, , ( buf0 – buf1
), feedbackAmount
. calculateFeedbackAmount
feedbackAmount
resonance
, . . , .
, . , , . , , .
-12 / -24 /
! 24 . switch
Filter::process
:
buf2 += cutoff * (buf1 - buf2); buf3 += cutoff * (buf2 – buf3);
: , , . , , , ( ). switch
:
switch (mode) { case FILTER_MODE_LOWPASS: return buf3; case FILTER_MODE_HIGHPASS: return inputValue - buf3; case FILTER_MODE_BANDPASS: return buf0 - buf3; default: return 0.0; }
buf1
buf3
– . -24 /. buf2
buf3
, private
Filter.h :
double buf2; double buf3;
, buf0
c buf1
:
Filter() : // ... buf0(0.0), buf1(0.0), buf2(0.0), buf3(0.0) // ...
, , : . .
?
. , , .
- cutoff
. . cutoffMod
, , cutoff
. #include private
:
double cutoffMod;
:
Filter() : cutoff(0.99), resonance(0.01), cutoffMod(0.0), // ...
. private
:
inline double getCalculatedCutoff() const { return fmax(fmin(cutoff + cutoffMod, 0.99), 0.01); };
calculateFeedbackAmount
:
inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - getCalculatedCutoff()); }
cutoffMod
. feedbackAmount
, :
inline void setCutoffMod(double newCutoffMod) { cutoffMod = newCutoffMod; calculateFeedbackAmount(); }
, . Filter::process
:
if (inputValue == 0.0) return inputValue; double calculatedCutoff = getCalculatedCutoff(); buf0 += calculatedCutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1)); buf1 += calculatedCutoff * (buf0 - buf1); buf2 += calculatedCutoff * (buf1 - buf2); buf3 += calculatedCutoff * (buf2 – buf3);
, . , , . . cutoff
calculatedCutoff
-.
, ( setCutoffMod
), Synthesis
, , . , cutoffMod
. filterEnvelopeAmount
-1
+1
. GUI.
private
Synthesis.h :
EnvelopeGenerator mFilterEnvelopeGenerator; double filterEnvelopeAmount;
MIDI , onNoteOn
onNoteOff
:
inline void onNoteOn(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); }; inline void onNoteOff(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); };
. ProcessDoubleReplacing
:
mFilter.setCutoffMod(mFilterEnvelopeGenerator.nextSample() * filterEnvelopeAmount);
, nextSample
filterEnvelopeAmount
cutoffMod
. filterEnvelopeAmount
:
Synthesis::Synthesis(IPlugInstanceInfo instanceInfo) : IPLUG_CTOR(kNumParams, kNumPrograms, instanceInfo), lastVirtualKeyboardNoteNumber(virtualKeyboardMinimumNoteNumber - 1), filterEnvelopeAmount(0.0) { // ... }
Synthesis::Reset
:
mFilterEnvelopeGenerator.setSampleRate(GetSampleRate());
EParams
, . AttachGraphics
:
// Knobs for filter envelope // Attack knob GetParam(mFilterAttack)->InitDouble("Filter Env Attack", 0.01, 0.01, 10.0, 0.001); GetParam(mFilterAttack)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 139, 178, mFilterAttack, &smallKnobBitmap)); // Decay knob: GetParam(mFilterDecay)->InitDouble("Filter Env Decay", 0.5, 0.01, 15.0, 0.001); GetParam(mFilterDecay)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 195, 178, mFilterDecay, &smallKnobBitmap)); // Sustain knob: GetParam(mFilterSustain)->InitDouble("Filter Env Sustain", 0.1, 0.001, 1.0, 0.001); GetParam(mFilterSustain)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 251, 178, mFilterSustain, &smallKnobBitmap)); // Release knob: GetParam(mFilterRelease)->InitDouble("Filter Env Release", 1.0, 0.001, 15.0, 0.001); GetParam(mFilterRelease)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 307, 178, mFilterRelease, &smallKnobBitmap)); // Filter envelope amount knob: GetParam(mFilterEnvelopeAmount)->InitDouble("Filter Env Amount", 0.0, -1.0, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 363, 178, mFilterEnvelopeAmount, &smallKnobBitmap));
, smallKnobBitmap
. . , . Synthesis::OnParamChange
switch
:
case mFilterAttack: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK, GetParam(paramIdx)->Value()); break; case mFilterDecay: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_DECAY, GetParam(paramIdx)->Value()); break; case mFilterSustain: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_SUSTAIN, GetParam(paramIdx)->Value()); break; case mFilterRelease: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE, GetParam(paramIdx)->Value()); break; case mFilterEnvelopeAmount: filterEnvelopeAmount = GetParam(paramIdx)->Value(); break;
!
! (- C1), :
EnvelopeGenerator
. . !
.
:)
:
martin-finke.de/blog/articles/audio-plugins-013-filter
#include Filter.h ( ) :
class Filter { public: enum FilterMode { FILTER_MODE_LOWPASS = 0, FILTER_MODE_HIGHPASS, FILTER_MODE_BANDPASS, kNumFilterModes }; Filter() : cutoff(0.99), resonance(0.0), mode(FILTER_MODE_LOWPASS), buf0(0.0), buf1(0.0) { calculateFeedbackAmount(); }; double process(double inputValue); inline void setCutoff(double newCutoff) { cutoff = newCutoff; calculateFeedbackAmount(); }; inline void setResonance(double newResonance) { resonance = newResonance; calculateFeedbackAmount(); }; inline void setFilterMode(FilterMode newMode) { mode = newMode; } private: double cutoff; double resonance; FilterMode mode; double feedbackAmount; inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - cutoff); } double buf0; double buf1; };
private
cutoff
resonance
. Mode
(Lowpass, Highpass, Bandpass). feedbackAmount
, buf0
buf1
, .
( calculateFeedbackAmount()
). process
. feedbackAmount
cutoff
resonance
, calculateFeedbackAmount
.
Filter.cpp :
// By Paul Kellett // http://www.musicdsp.org/showone.php?id=29 double Filter::process(double inputValue) { buf0 += cutoff * (inputValue - buf0); buf1 += cutoff * (buf0 - buf1); switch (mode) { case FILTER_MODE_LOWPASS: return buf1; case FILTER_MODE_HIGHPASS: return inputValue - buf0; case FILTER_MODE_BANDPASS: return buf0 - buf1; default: return 0.0; } }
, ? , . « » , (. . 6 ). buf0
buf1
: . inputValue
, - buf0
( ). . 6 / 12 /. switch
, buf1
. , , buf0
, 12, 6 /, .
case
FILTER_MODE_HIGHPASS
. buf0
. inputValue
buf0
, . buf1
, .
case
FILTER_MODE_BANDPASS
, buf0 - buf1
. buf0
, buf1
. , .
: , .
buf1
. ( Infinite Impulse Response , IIR ). , . , , .
, , . , , , , . , NI Massive, « ». , , . , ( Massive ). «» , , , AAS Ultra Analog, , , , . , , , , NI Reaktor. , — , .. .
. GUI. bg.png ( “Move to trash” Xcode), :
filtermode.png knob_small.png ( , 50 50 ) bg.png ( )
ID resource.h :
// Unique IDs for each image resource. #define BG_ID 101 #define WHITE_KEY_ID 102 #define BLACK_KEY_ID 103 #define WAVEFORM_ID 104 #define KNOB_ID 105 #define KNOB_SMALL_ID 106 #define FILTERMODE_ID 107 // Image resource locations for this plug. #define BG_FN "resources/img/bg.png" #define WHITE_KEY_FN "resources/img/whitekey.png" #define BLACK_KEY_FN "resources/img/blackkey.png" #define WAVEFORM_FN "resources/img/waveform.png" #define KNOB_FN "resources/img/knob.png" #define KNOB_SMALL_FN "resources/img/knob_small.png" #define FILTERMODE_FN "resources/img/filtermode.png"
Synthesis.rc :
#include "resource.h" BG_ID PNG BG_FN WHITE_KEY_ID PNG WHITE_KEY_FN BLACK_KEY_ID PNG BLACK_KEY_FN WAVEFORM_ID PNG WAVEFORM_FN KNOB_ID PNG KNOB_FN KNOB_SMALL_ID PNG KNOB_SMALL_FN FILTERMODE_ID PNG FILTERMODE_FN
#include "Filter.h"
Synthesis.h private
:
Filter mFilter;
EParams
Synthesis.cpp :
enum EParams { mWaveform = 0, mAttack, mDecay, mSustain, mRelease, mFilterMode, mFilterCutoff, mFilterResonance, mFilterAttack, mFilterDecay, mFilterSustain, mFilterRelease, mFilterEnvelopeAmount, kNumParams };
:
pGraphics->AttachControl(new ISwitchControl(this, 24, 38, mWaveform, &waveformBitmap));
. (Lowpass, Highpass, Bandpass). AttachGraphics(pGraphics)
:
GetParam(mFilterMode)->InitEnum("Filter Mode", Filter::FILTER_MODE_LOWPASS, Filter::kNumFilterModes); IBitmap filtermodeBitmap = pGraphics->LoadIBitmap(FILTERMODE_ID, FILTERMODE_FN, 3); pGraphics->AttachControl(new ISwitchControl(this, 24, 123, mFilterMode, &filtermodeBitmap));
. . knob_small.png :
// Knobs for filter cutoff and resonance IBitmap smallKnobBitmap = pGraphics->LoadIBitmap(KNOB_SMALL_ID, KNOB_SMALL_FN, 64); // Cutoff knob: GetParam(mFilterCutoff)->InitDouble("Cutoff", 0.99, 0.01, 0.99, 0.001); GetParam(mFilterCutoff)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 5, 177, mFilterCutoff, &smallKnobBitmap)); // Resonance knob: GetParam(mFilterResonance)->InitDouble("Resonance", 0.01, 0.01, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 61, 177, mFilterResonance, &smallKnobBitmap));
, 1.0
, calculateFeedbackAmount
, .
ProcessDoubleReplacing
:
leftOutput[i] = rightOutput[i] = mFilter.process(mOscillator.nextSample() * mEnvelopeGenerator.nextSample() * velocity / 127.0);
. switch
Synthesis::OnParamChange
:
case mFilterCutoff: mFilter.setCutoff(GetParam(paramIdx)->Value()); break; case mFilterResonance: mFilter.setResonance(GetParam(paramIdx)->Value()); break; case mFilterMode: mFilter.setFilterMode(static_cast<Filter::FilterMode>(GetParam(paramIdx)->Int())); break;
. - .
— . , , .
:
buf0 += cutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1));
, , ( buf0 – buf1
), feedbackAmount
. calculateFeedbackAmount
feedbackAmount
resonance
, . . , .
, . , , . , , .
-12 / -24 /
! 24 . switch
Filter::process
:
buf2 += cutoff * (buf1 - buf2); buf3 += cutoff * (buf2 – buf3);
: , , . , , , ( ). switch
:
switch (mode) { case FILTER_MODE_LOWPASS: return buf3; case FILTER_MODE_HIGHPASS: return inputValue - buf3; case FILTER_MODE_BANDPASS: return buf0 - buf3; default: return 0.0; }
buf1
buf3
– . -24 /. buf2
buf3
, private
Filter.h :
double buf2; double buf3;
, buf0
c buf1
:
Filter() : // ... buf0(0.0), buf1(0.0), buf2(0.0), buf3(0.0) // ...
, , : . .
?
. , , .
- cutoff
. . cutoffMod
, , cutoff
. #include private
:
double cutoffMod;
:
Filter() : cutoff(0.99), resonance(0.01), cutoffMod(0.0), // ...
. private
:
inline double getCalculatedCutoff() const { return fmax(fmin(cutoff + cutoffMod, 0.99), 0.01); };
calculateFeedbackAmount
:
inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - getCalculatedCutoff()); }
cutoffMod
. feedbackAmount
, :
inline void setCutoffMod(double newCutoffMod) { cutoffMod = newCutoffMod; calculateFeedbackAmount(); }
, . Filter::process
:
if (inputValue == 0.0) return inputValue; double calculatedCutoff = getCalculatedCutoff(); buf0 += calculatedCutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1)); buf1 += calculatedCutoff * (buf0 - buf1); buf2 += calculatedCutoff * (buf1 - buf2); buf3 += calculatedCutoff * (buf2 – buf3);
, . , , . . cutoff
calculatedCutoff
-.
, ( setCutoffMod
), Synthesis
, , . , cutoffMod
. filterEnvelopeAmount
-1
+1
. GUI.
private
Synthesis.h :
EnvelopeGenerator mFilterEnvelopeGenerator; double filterEnvelopeAmount;
MIDI , onNoteOn
onNoteOff
:
inline void onNoteOn(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); }; inline void onNoteOff(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); };
. ProcessDoubleReplacing
:
mFilter.setCutoffMod(mFilterEnvelopeGenerator.nextSample() * filterEnvelopeAmount);
, nextSample
filterEnvelopeAmount
cutoffMod
. filterEnvelopeAmount
:
Synthesis::Synthesis(IPlugInstanceInfo instanceInfo) : IPLUG_CTOR(kNumParams, kNumPrograms, instanceInfo), lastVirtualKeyboardNoteNumber(virtualKeyboardMinimumNoteNumber - 1), filterEnvelopeAmount(0.0) { // ... }
Synthesis::Reset
:
mFilterEnvelopeGenerator.setSampleRate(GetSampleRate());
EParams
, . AttachGraphics
:
// Knobs for filter envelope // Attack knob GetParam(mFilterAttack)->InitDouble("Filter Env Attack", 0.01, 0.01, 10.0, 0.001); GetParam(mFilterAttack)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 139, 178, mFilterAttack, &smallKnobBitmap)); // Decay knob: GetParam(mFilterDecay)->InitDouble("Filter Env Decay", 0.5, 0.01, 15.0, 0.001); GetParam(mFilterDecay)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 195, 178, mFilterDecay, &smallKnobBitmap)); // Sustain knob: GetParam(mFilterSustain)->InitDouble("Filter Env Sustain", 0.1, 0.001, 1.0, 0.001); GetParam(mFilterSustain)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 251, 178, mFilterSustain, &smallKnobBitmap)); // Release knob: GetParam(mFilterRelease)->InitDouble("Filter Env Release", 1.0, 0.001, 15.0, 0.001); GetParam(mFilterRelease)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 307, 178, mFilterRelease, &smallKnobBitmap)); // Filter envelope amount knob: GetParam(mFilterEnvelopeAmount)->InitDouble("Filter Env Amount", 0.0, -1.0, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 363, 178, mFilterEnvelopeAmount, &smallKnobBitmap));
, smallKnobBitmap
. . , . Synthesis::OnParamChange
switch
:
case mFilterAttack: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK, GetParam(paramIdx)->Value()); break; case mFilterDecay: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_DECAY, GetParam(paramIdx)->Value()); break; case mFilterSustain: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_SUSTAIN, GetParam(paramIdx)->Value()); break; case mFilterRelease: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE, GetParam(paramIdx)->Value()); break; case mFilterEnvelopeAmount: filterEnvelopeAmount = GetParam(paramIdx)->Value(); break;
!
! (- C1), :
EnvelopeGenerator
. . !
.
:)
:
martin-finke.de/blog/articles/audio-plugins-013-filter
#include Filter.h ( ) :
class Filter { public: enum FilterMode { FILTER_MODE_LOWPASS = 0, FILTER_MODE_HIGHPASS, FILTER_MODE_BANDPASS, kNumFilterModes }; Filter() : cutoff(0.99), resonance(0.0), mode(FILTER_MODE_LOWPASS), buf0(0.0), buf1(0.0) { calculateFeedbackAmount(); }; double process(double inputValue); inline void setCutoff(double newCutoff) { cutoff = newCutoff; calculateFeedbackAmount(); }; inline void setResonance(double newResonance) { resonance = newResonance; calculateFeedbackAmount(); }; inline void setFilterMode(FilterMode newMode) { mode = newMode; } private: double cutoff; double resonance; FilterMode mode; double feedbackAmount; inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - cutoff); } double buf0; double buf1; };
private
cutoff
resonance
. Mode
(Lowpass, Highpass, Bandpass). feedbackAmount
, buf0
buf1
, .
( calculateFeedbackAmount()
). process
. feedbackAmount
cutoff
resonance
, calculateFeedbackAmount
.
Filter.cpp :
// By Paul Kellett // http://www.musicdsp.org/showone.php?id=29 double Filter::process(double inputValue) { buf0 += cutoff * (inputValue - buf0); buf1 += cutoff * (buf0 - buf1); switch (mode) { case FILTER_MODE_LOWPASS: return buf1; case FILTER_MODE_HIGHPASS: return inputValue - buf0; case FILTER_MODE_BANDPASS: return buf0 - buf1; default: return 0.0; } }
, ? , . « » , (. . 6 ). buf0
buf1
: . inputValue
, - buf0
( ). . 6 / 12 /. switch
, buf1
. , , buf0
, 12, 6 /, .
case
FILTER_MODE_HIGHPASS
. buf0
. inputValue
buf0
, . buf1
, .
case
FILTER_MODE_BANDPASS
, buf0 - buf1
. buf0
, buf1
. , .
: , .
buf1
. ( Infinite Impulse Response , IIR ). , . , , .
, , . , , , , . , NI Massive, « ». , , . , ( Massive ). «» , , , AAS Ultra Analog, , , , . , , , , NI Reaktor. , — , .. .
. GUI. bg.png ( “Move to trash” Xcode), :
filtermode.png knob_small.png ( , 50 50 ) bg.png ( )
ID resource.h :
// Unique IDs for each image resource. #define BG_ID 101 #define WHITE_KEY_ID 102 #define BLACK_KEY_ID 103 #define WAVEFORM_ID 104 #define KNOB_ID 105 #define KNOB_SMALL_ID 106 #define FILTERMODE_ID 107 // Image resource locations for this plug. #define BG_FN "resources/img/bg.png" #define WHITE_KEY_FN "resources/img/whitekey.png" #define BLACK_KEY_FN "resources/img/blackkey.png" #define WAVEFORM_FN "resources/img/waveform.png" #define KNOB_FN "resources/img/knob.png" #define KNOB_SMALL_FN "resources/img/knob_small.png" #define FILTERMODE_FN "resources/img/filtermode.png"
Synthesis.rc :
#include "resource.h" BG_ID PNG BG_FN WHITE_KEY_ID PNG WHITE_KEY_FN BLACK_KEY_ID PNG BLACK_KEY_FN WAVEFORM_ID PNG WAVEFORM_FN KNOB_ID PNG KNOB_FN KNOB_SMALL_ID PNG KNOB_SMALL_FN FILTERMODE_ID PNG FILTERMODE_FN
#include "Filter.h"
Synthesis.h private
:
Filter mFilter;
EParams
Synthesis.cpp :
enum EParams { mWaveform = 0, mAttack, mDecay, mSustain, mRelease, mFilterMode, mFilterCutoff, mFilterResonance, mFilterAttack, mFilterDecay, mFilterSustain, mFilterRelease, mFilterEnvelopeAmount, kNumParams };
:
pGraphics->AttachControl(new ISwitchControl(this, 24, 38, mWaveform, &waveformBitmap));
. (Lowpass, Highpass, Bandpass). AttachGraphics(pGraphics)
:
GetParam(mFilterMode)->InitEnum("Filter Mode", Filter::FILTER_MODE_LOWPASS, Filter::kNumFilterModes); IBitmap filtermodeBitmap = pGraphics->LoadIBitmap(FILTERMODE_ID, FILTERMODE_FN, 3); pGraphics->AttachControl(new ISwitchControl(this, 24, 123, mFilterMode, &filtermodeBitmap));
. . knob_small.png :
// Knobs for filter cutoff and resonance IBitmap smallKnobBitmap = pGraphics->LoadIBitmap(KNOB_SMALL_ID, KNOB_SMALL_FN, 64); // Cutoff knob: GetParam(mFilterCutoff)->InitDouble("Cutoff", 0.99, 0.01, 0.99, 0.001); GetParam(mFilterCutoff)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 5, 177, mFilterCutoff, &smallKnobBitmap)); // Resonance knob: GetParam(mFilterResonance)->InitDouble("Resonance", 0.01, 0.01, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 61, 177, mFilterResonance, &smallKnobBitmap));
, 1.0
, calculateFeedbackAmount
, .
ProcessDoubleReplacing
:
leftOutput[i] = rightOutput[i] = mFilter.process(mOscillator.nextSample() * mEnvelopeGenerator.nextSample() * velocity / 127.0);
. switch
Synthesis::OnParamChange
:
case mFilterCutoff: mFilter.setCutoff(GetParam(paramIdx)->Value()); break; case mFilterResonance: mFilter.setResonance(GetParam(paramIdx)->Value()); break; case mFilterMode: mFilter.setFilterMode(static_cast<Filter::FilterMode>(GetParam(paramIdx)->Int())); break;
. - .
— . , , .
:
buf0 += cutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1));
, , ( buf0 – buf1
), feedbackAmount
. calculateFeedbackAmount
feedbackAmount
resonance
, . . , .
, . , , . , , .
-12 / -24 /
! 24 . switch
Filter::process
:
buf2 += cutoff * (buf1 - buf2); buf3 += cutoff * (buf2 – buf3);
: , , . , , , ( ). switch
:
switch (mode) { case FILTER_MODE_LOWPASS: return buf3; case FILTER_MODE_HIGHPASS: return inputValue - buf3; case FILTER_MODE_BANDPASS: return buf0 - buf3; default: return 0.0; }
buf1
buf3
– . -24 /. buf2
buf3
, private
Filter.h :
double buf2; double buf3;
, buf0
c buf1
:
Filter() : // ... buf0(0.0), buf1(0.0), buf2(0.0), buf3(0.0) // ...
, , : . .
?
. , , .
- cutoff
. . cutoffMod
, , cutoff
. #include private
:
double cutoffMod;
:
Filter() : cutoff(0.99), resonance(0.01), cutoffMod(0.0), // ...
. private
:
inline double getCalculatedCutoff() const { return fmax(fmin(cutoff + cutoffMod, 0.99), 0.01); };
calculateFeedbackAmount
:
inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - getCalculatedCutoff()); }
cutoffMod
. feedbackAmount
, :
inline void setCutoffMod(double newCutoffMod) { cutoffMod = newCutoffMod; calculateFeedbackAmount(); }
, . Filter::process
:
if (inputValue == 0.0) return inputValue; double calculatedCutoff = getCalculatedCutoff(); buf0 += calculatedCutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1)); buf1 += calculatedCutoff * (buf0 - buf1); buf2 += calculatedCutoff * (buf1 - buf2); buf3 += calculatedCutoff * (buf2 – buf3);
, . , , . . cutoff
calculatedCutoff
-.
, ( setCutoffMod
), Synthesis
, , . , cutoffMod
. filterEnvelopeAmount
-1
+1
. GUI.
private
Synthesis.h :
EnvelopeGenerator mFilterEnvelopeGenerator; double filterEnvelopeAmount;
MIDI , onNoteOn
onNoteOff
:
inline void onNoteOn(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); }; inline void onNoteOff(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); };
. ProcessDoubleReplacing
:
mFilter.setCutoffMod(mFilterEnvelopeGenerator.nextSample() * filterEnvelopeAmount);
, nextSample
filterEnvelopeAmount
cutoffMod
. filterEnvelopeAmount
:
Synthesis::Synthesis(IPlugInstanceInfo instanceInfo) : IPLUG_CTOR(kNumParams, kNumPrograms, instanceInfo), lastVirtualKeyboardNoteNumber(virtualKeyboardMinimumNoteNumber - 1), filterEnvelopeAmount(0.0) { // ... }
Synthesis::Reset
:
mFilterEnvelopeGenerator.setSampleRate(GetSampleRate());
EParams
, . AttachGraphics
:
// Knobs for filter envelope // Attack knob GetParam(mFilterAttack)->InitDouble("Filter Env Attack", 0.01, 0.01, 10.0, 0.001); GetParam(mFilterAttack)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 139, 178, mFilterAttack, &smallKnobBitmap)); // Decay knob: GetParam(mFilterDecay)->InitDouble("Filter Env Decay", 0.5, 0.01, 15.0, 0.001); GetParam(mFilterDecay)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 195, 178, mFilterDecay, &smallKnobBitmap)); // Sustain knob: GetParam(mFilterSustain)->InitDouble("Filter Env Sustain", 0.1, 0.001, 1.0, 0.001); GetParam(mFilterSustain)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 251, 178, mFilterSustain, &smallKnobBitmap)); // Release knob: GetParam(mFilterRelease)->InitDouble("Filter Env Release", 1.0, 0.001, 15.0, 0.001); GetParam(mFilterRelease)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 307, 178, mFilterRelease, &smallKnobBitmap)); // Filter envelope amount knob: GetParam(mFilterEnvelopeAmount)->InitDouble("Filter Env Amount", 0.0, -1.0, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 363, 178, mFilterEnvelopeAmount, &smallKnobBitmap));
, smallKnobBitmap
. . , . Synthesis::OnParamChange
switch
:
case mFilterAttack: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK, GetParam(paramIdx)->Value()); break; case mFilterDecay: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_DECAY, GetParam(paramIdx)->Value()); break; case mFilterSustain: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_SUSTAIN, GetParam(paramIdx)->Value()); break; case mFilterRelease: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE, GetParam(paramIdx)->Value()); break; case mFilterEnvelopeAmount: filterEnvelopeAmount = GetParam(paramIdx)->Value(); break;
!
! (- C1), :
EnvelopeGenerator
. . !
.
:)
:
martin-finke.de/blog/articles/audio-plugins-013-filter
#include Filter.h ( ) :
class Filter { public: enum FilterMode { FILTER_MODE_LOWPASS = 0, FILTER_MODE_HIGHPASS, FILTER_MODE_BANDPASS, kNumFilterModes }; Filter() : cutoff(0.99), resonance(0.0), mode(FILTER_MODE_LOWPASS), buf0(0.0), buf1(0.0) { calculateFeedbackAmount(); }; double process(double inputValue); inline void setCutoff(double newCutoff) { cutoff = newCutoff; calculateFeedbackAmount(); }; inline void setResonance(double newResonance) { resonance = newResonance; calculateFeedbackAmount(); }; inline void setFilterMode(FilterMode newMode) { mode = newMode; } private: double cutoff; double resonance; FilterMode mode; double feedbackAmount; inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - cutoff); } double buf0; double buf1; };
private
cutoff
resonance
. Mode
(Lowpass, Highpass, Bandpass). feedbackAmount
, buf0
buf1
, .
( calculateFeedbackAmount()
). process
. feedbackAmount
cutoff
resonance
, calculateFeedbackAmount
.
Filter.cpp :
// By Paul Kellett // http://www.musicdsp.org/showone.php?id=29 double Filter::process(double inputValue) { buf0 += cutoff * (inputValue - buf0); buf1 += cutoff * (buf0 - buf1); switch (mode) { case FILTER_MODE_LOWPASS: return buf1; case FILTER_MODE_HIGHPASS: return inputValue - buf0; case FILTER_MODE_BANDPASS: return buf0 - buf1; default: return 0.0; } }
, ? , . « » , (. . 6 ). buf0
buf1
: . inputValue
, - buf0
( ). . 6 / 12 /. switch
, buf1
. , , buf0
, 12, 6 /, .
case
FILTER_MODE_HIGHPASS
. buf0
. inputValue
buf0
, . buf1
, .
case
FILTER_MODE_BANDPASS
, buf0 - buf1
. buf0
, buf1
. , .
: , .
buf1
. ( Infinite Impulse Response , IIR ). , . , , .
, , . , , , , . , NI Massive, « ». , , . , ( Massive ). «» , , , AAS Ultra Analog, , , , . , , , , NI Reaktor. , — , .. .
. GUI. bg.png ( “Move to trash” Xcode), :
filtermode.png knob_small.png ( , 50 50 ) bg.png ( )
ID resource.h :
// Unique IDs for each image resource. #define BG_ID 101 #define WHITE_KEY_ID 102 #define BLACK_KEY_ID 103 #define WAVEFORM_ID 104 #define KNOB_ID 105 #define KNOB_SMALL_ID 106 #define FILTERMODE_ID 107 // Image resource locations for this plug. #define BG_FN "resources/img/bg.png" #define WHITE_KEY_FN "resources/img/whitekey.png" #define BLACK_KEY_FN "resources/img/blackkey.png" #define WAVEFORM_FN "resources/img/waveform.png" #define KNOB_FN "resources/img/knob.png" #define KNOB_SMALL_FN "resources/img/knob_small.png" #define FILTERMODE_FN "resources/img/filtermode.png"
Synthesis.rc :
#include "resource.h" BG_ID PNG BG_FN WHITE_KEY_ID PNG WHITE_KEY_FN BLACK_KEY_ID PNG BLACK_KEY_FN WAVEFORM_ID PNG WAVEFORM_FN KNOB_ID PNG KNOB_FN KNOB_SMALL_ID PNG KNOB_SMALL_FN FILTERMODE_ID PNG FILTERMODE_FN
#include "Filter.h"
Synthesis.h private
:
Filter mFilter;
EParams
Synthesis.cpp :
enum EParams { mWaveform = 0, mAttack, mDecay, mSustain, mRelease, mFilterMode, mFilterCutoff, mFilterResonance, mFilterAttack, mFilterDecay, mFilterSustain, mFilterRelease, mFilterEnvelopeAmount, kNumParams };
:
pGraphics->AttachControl(new ISwitchControl(this, 24, 38, mWaveform, &waveformBitmap));
. (Lowpass, Highpass, Bandpass). AttachGraphics(pGraphics)
:
GetParam(mFilterMode)->InitEnum("Filter Mode", Filter::FILTER_MODE_LOWPASS, Filter::kNumFilterModes); IBitmap filtermodeBitmap = pGraphics->LoadIBitmap(FILTERMODE_ID, FILTERMODE_FN, 3); pGraphics->AttachControl(new ISwitchControl(this, 24, 123, mFilterMode, &filtermodeBitmap));
. . knob_small.png :
// Knobs for filter cutoff and resonance IBitmap smallKnobBitmap = pGraphics->LoadIBitmap(KNOB_SMALL_ID, KNOB_SMALL_FN, 64); // Cutoff knob: GetParam(mFilterCutoff)->InitDouble("Cutoff", 0.99, 0.01, 0.99, 0.001); GetParam(mFilterCutoff)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 5, 177, mFilterCutoff, &smallKnobBitmap)); // Resonance knob: GetParam(mFilterResonance)->InitDouble("Resonance", 0.01, 0.01, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 61, 177, mFilterResonance, &smallKnobBitmap));
, 1.0
, calculateFeedbackAmount
, .
ProcessDoubleReplacing
:
leftOutput[i] = rightOutput[i] = mFilter.process(mOscillator.nextSample() * mEnvelopeGenerator.nextSample() * velocity / 127.0);
. switch
Synthesis::OnParamChange
:
case mFilterCutoff: mFilter.setCutoff(GetParam(paramIdx)->Value()); break; case mFilterResonance: mFilter.setResonance(GetParam(paramIdx)->Value()); break; case mFilterMode: mFilter.setFilterMode(static_cast<Filter::FilterMode>(GetParam(paramIdx)->Int())); break;
. - .
— . , , .
:
buf0 += cutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1));
, , ( buf0 – buf1
), feedbackAmount
. calculateFeedbackAmount
feedbackAmount
resonance
, . . , .
, . , , . , , .
-12 / -24 /
! 24 . switch
Filter::process
:
buf2 += cutoff * (buf1 - buf2); buf3 += cutoff * (buf2 – buf3);
: , , . , , , ( ). switch
:
switch (mode) { case FILTER_MODE_LOWPASS: return buf3; case FILTER_MODE_HIGHPASS: return inputValue - buf3; case FILTER_MODE_BANDPASS: return buf0 - buf3; default: return 0.0; }
buf1
buf3
– . -24 /. buf2
buf3
, private
Filter.h :
double buf2; double buf3;
, buf0
c buf1
:
Filter() : // ... buf0(0.0), buf1(0.0), buf2(0.0), buf3(0.0) // ...
, , : . .
?
. , , .
- cutoff
. . cutoffMod
, , cutoff
. #include private
:
double cutoffMod;
:
Filter() : cutoff(0.99), resonance(0.01), cutoffMod(0.0), // ...
. private
:
inline double getCalculatedCutoff() const { return fmax(fmin(cutoff + cutoffMod, 0.99), 0.01); };
calculateFeedbackAmount
:
inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - getCalculatedCutoff()); }
cutoffMod
. feedbackAmount
, :
inline void setCutoffMod(double newCutoffMod) { cutoffMod = newCutoffMod; calculateFeedbackAmount(); }
, . Filter::process
:
if (inputValue == 0.0) return inputValue; double calculatedCutoff = getCalculatedCutoff(); buf0 += calculatedCutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1)); buf1 += calculatedCutoff * (buf0 - buf1); buf2 += calculatedCutoff * (buf1 - buf2); buf3 += calculatedCutoff * (buf2 – buf3);
, . , , . . cutoff
calculatedCutoff
-.
, ( setCutoffMod
), Synthesis
, , . , cutoffMod
. filterEnvelopeAmount
-1
+1
. GUI.
private
Synthesis.h :
EnvelopeGenerator mFilterEnvelopeGenerator; double filterEnvelopeAmount;
MIDI , onNoteOn
onNoteOff
:
inline void onNoteOn(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); }; inline void onNoteOff(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); };
. ProcessDoubleReplacing
:
mFilter.setCutoffMod(mFilterEnvelopeGenerator.nextSample() * filterEnvelopeAmount);
, nextSample
filterEnvelopeAmount
cutoffMod
. filterEnvelopeAmount
:
Synthesis::Synthesis(IPlugInstanceInfo instanceInfo) : IPLUG_CTOR(kNumParams, kNumPrograms, instanceInfo), lastVirtualKeyboardNoteNumber(virtualKeyboardMinimumNoteNumber - 1), filterEnvelopeAmount(0.0) { // ... }
Synthesis::Reset
:
mFilterEnvelopeGenerator.setSampleRate(GetSampleRate());
EParams
, . AttachGraphics
:
// Knobs for filter envelope // Attack knob GetParam(mFilterAttack)->InitDouble("Filter Env Attack", 0.01, 0.01, 10.0, 0.001); GetParam(mFilterAttack)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 139, 178, mFilterAttack, &smallKnobBitmap)); // Decay knob: GetParam(mFilterDecay)->InitDouble("Filter Env Decay", 0.5, 0.01, 15.0, 0.001); GetParam(mFilterDecay)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 195, 178, mFilterDecay, &smallKnobBitmap)); // Sustain knob: GetParam(mFilterSustain)->InitDouble("Filter Env Sustain", 0.1, 0.001, 1.0, 0.001); GetParam(mFilterSustain)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 251, 178, mFilterSustain, &smallKnobBitmap)); // Release knob: GetParam(mFilterRelease)->InitDouble("Filter Env Release", 1.0, 0.001, 15.0, 0.001); GetParam(mFilterRelease)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 307, 178, mFilterRelease, &smallKnobBitmap)); // Filter envelope amount knob: GetParam(mFilterEnvelopeAmount)->InitDouble("Filter Env Amount", 0.0, -1.0, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 363, 178, mFilterEnvelopeAmount, &smallKnobBitmap));
, smallKnobBitmap
. . , . Synthesis::OnParamChange
switch
:
case mFilterAttack: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK, GetParam(paramIdx)->Value()); break; case mFilterDecay: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_DECAY, GetParam(paramIdx)->Value()); break; case mFilterSustain: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_SUSTAIN, GetParam(paramIdx)->Value()); break; case mFilterRelease: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE, GetParam(paramIdx)->Value()); break; case mFilterEnvelopeAmount: filterEnvelopeAmount = GetParam(paramIdx)->Value(); break;
!
! (- C1), :
EnvelopeGenerator
. . !
.
:)
:
martin-finke.de/blog/articles/audio-plugins-013-filter
#include Filter.h ( ) :
class Filter { public: enum FilterMode { FILTER_MODE_LOWPASS = 0, FILTER_MODE_HIGHPASS, FILTER_MODE_BANDPASS, kNumFilterModes }; Filter() : cutoff(0.99), resonance(0.0), mode(FILTER_MODE_LOWPASS), buf0(0.0), buf1(0.0) { calculateFeedbackAmount(); }; double process(double inputValue); inline void setCutoff(double newCutoff) { cutoff = newCutoff; calculateFeedbackAmount(); }; inline void setResonance(double newResonance) { resonance = newResonance; calculateFeedbackAmount(); }; inline void setFilterMode(FilterMode newMode) { mode = newMode; } private: double cutoff; double resonance; FilterMode mode; double feedbackAmount; inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - cutoff); } double buf0; double buf1; };
private
cutoff
resonance
. Mode
(Lowpass, Highpass, Bandpass). feedbackAmount
, buf0
buf1
, .
( calculateFeedbackAmount()
). process
. feedbackAmount
cutoff
resonance
, calculateFeedbackAmount
.
Filter.cpp :
// By Paul Kellett // http://www.musicdsp.org/showone.php?id=29 double Filter::process(double inputValue) { buf0 += cutoff * (inputValue - buf0); buf1 += cutoff * (buf0 - buf1); switch (mode) { case FILTER_MODE_LOWPASS: return buf1; case FILTER_MODE_HIGHPASS: return inputValue - buf0; case FILTER_MODE_BANDPASS: return buf0 - buf1; default: return 0.0; } }
, ? , . « » , (. . 6 ). buf0
buf1
: . inputValue
, - buf0
( ). . 6 / 12 /. switch
, buf1
. , , buf0
, 12, 6 /, .
case
FILTER_MODE_HIGHPASS
. buf0
. inputValue
buf0
, . buf1
, .
case
FILTER_MODE_BANDPASS
, buf0 - buf1
. buf0
, buf1
. , .
: , .
buf1
. ( Infinite Impulse Response , IIR ). , . , , .
, , . , , , , . , NI Massive, « ». , , . , ( Massive ). «» , , , AAS Ultra Analog, , , , . , , , , NI Reaktor. , — , .. .
. GUI. bg.png ( “Move to trash” Xcode), :
filtermode.png knob_small.png ( , 50 50 ) bg.png ( )
ID resource.h :
// Unique IDs for each image resource. #define BG_ID 101 #define WHITE_KEY_ID 102 #define BLACK_KEY_ID 103 #define WAVEFORM_ID 104 #define KNOB_ID 105 #define KNOB_SMALL_ID 106 #define FILTERMODE_ID 107 // Image resource locations for this plug. #define BG_FN "resources/img/bg.png" #define WHITE_KEY_FN "resources/img/whitekey.png" #define BLACK_KEY_FN "resources/img/blackkey.png" #define WAVEFORM_FN "resources/img/waveform.png" #define KNOB_FN "resources/img/knob.png" #define KNOB_SMALL_FN "resources/img/knob_small.png" #define FILTERMODE_FN "resources/img/filtermode.png"
Synthesis.rc :
#include "resource.h" BG_ID PNG BG_FN WHITE_KEY_ID PNG WHITE_KEY_FN BLACK_KEY_ID PNG BLACK_KEY_FN WAVEFORM_ID PNG WAVEFORM_FN KNOB_ID PNG KNOB_FN KNOB_SMALL_ID PNG KNOB_SMALL_FN FILTERMODE_ID PNG FILTERMODE_FN
#include "Filter.h"
Synthesis.h private
:
Filter mFilter;
EParams
Synthesis.cpp :
enum EParams { mWaveform = 0, mAttack, mDecay, mSustain, mRelease, mFilterMode, mFilterCutoff, mFilterResonance, mFilterAttack, mFilterDecay, mFilterSustain, mFilterRelease, mFilterEnvelopeAmount, kNumParams };
:
pGraphics->AttachControl(new ISwitchControl(this, 24, 38, mWaveform, &waveformBitmap));
. (Lowpass, Highpass, Bandpass). AttachGraphics(pGraphics)
:
GetParam(mFilterMode)->InitEnum("Filter Mode", Filter::FILTER_MODE_LOWPASS, Filter::kNumFilterModes); IBitmap filtermodeBitmap = pGraphics->LoadIBitmap(FILTERMODE_ID, FILTERMODE_FN, 3); pGraphics->AttachControl(new ISwitchControl(this, 24, 123, mFilterMode, &filtermodeBitmap));
. . knob_small.png :
// Knobs for filter cutoff and resonance IBitmap smallKnobBitmap = pGraphics->LoadIBitmap(KNOB_SMALL_ID, KNOB_SMALL_FN, 64); // Cutoff knob: GetParam(mFilterCutoff)->InitDouble("Cutoff", 0.99, 0.01, 0.99, 0.001); GetParam(mFilterCutoff)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 5, 177, mFilterCutoff, &smallKnobBitmap)); // Resonance knob: GetParam(mFilterResonance)->InitDouble("Resonance", 0.01, 0.01, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 61, 177, mFilterResonance, &smallKnobBitmap));
, 1.0
, calculateFeedbackAmount
, .
ProcessDoubleReplacing
:
leftOutput[i] = rightOutput[i] = mFilter.process(mOscillator.nextSample() * mEnvelopeGenerator.nextSample() * velocity / 127.0);
. switch
Synthesis::OnParamChange
:
case mFilterCutoff: mFilter.setCutoff(GetParam(paramIdx)->Value()); break; case mFilterResonance: mFilter.setResonance(GetParam(paramIdx)->Value()); break; case mFilterMode: mFilter.setFilterMode(static_cast<Filter::FilterMode>(GetParam(paramIdx)->Int())); break;
. - .
— . , , .
:
buf0 += cutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1));
, , ( buf0 – buf1
), feedbackAmount
. calculateFeedbackAmount
feedbackAmount
resonance
, . . , .
, . , , . , , .
-12 / -24 /
! 24 . switch
Filter::process
:
buf2 += cutoff * (buf1 - buf2); buf3 += cutoff * (buf2 – buf3);
: , , . , , , ( ). switch
:
switch (mode) { case FILTER_MODE_LOWPASS: return buf3; case FILTER_MODE_HIGHPASS: return inputValue - buf3; case FILTER_MODE_BANDPASS: return buf0 - buf3; default: return 0.0; }
buf1
buf3
– . -24 /. buf2
buf3
, private
Filter.h :
double buf2; double buf3;
, buf0
c buf1
:
Filter() : // ... buf0(0.0), buf1(0.0), buf2(0.0), buf3(0.0) // ...
, , : . .
?
. , , .
- cutoff
. . cutoffMod
, , cutoff
. #include private
:
double cutoffMod;
:
Filter() : cutoff(0.99), resonance(0.01), cutoffMod(0.0), // ...
. private
:
inline double getCalculatedCutoff() const { return fmax(fmin(cutoff + cutoffMod, 0.99), 0.01); };
calculateFeedbackAmount
:
inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - getCalculatedCutoff()); }
cutoffMod
. feedbackAmount
, :
inline void setCutoffMod(double newCutoffMod) { cutoffMod = newCutoffMod; calculateFeedbackAmount(); }
, . Filter::process
:
if (inputValue == 0.0) return inputValue; double calculatedCutoff = getCalculatedCutoff(); buf0 += calculatedCutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1)); buf1 += calculatedCutoff * (buf0 - buf1); buf2 += calculatedCutoff * (buf1 - buf2); buf3 += calculatedCutoff * (buf2 – buf3);
, . , , . . cutoff
calculatedCutoff
-.
, ( setCutoffMod
), Synthesis
, , . , cutoffMod
. filterEnvelopeAmount
-1
+1
. GUI.
private
Synthesis.h :
EnvelopeGenerator mFilterEnvelopeGenerator; double filterEnvelopeAmount;
MIDI , onNoteOn
onNoteOff
:
inline void onNoteOn(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); }; inline void onNoteOff(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); };
. ProcessDoubleReplacing
:
mFilter.setCutoffMod(mFilterEnvelopeGenerator.nextSample() * filterEnvelopeAmount);
, nextSample
filterEnvelopeAmount
cutoffMod
. filterEnvelopeAmount
:
Synthesis::Synthesis(IPlugInstanceInfo instanceInfo) : IPLUG_CTOR(kNumParams, kNumPrograms, instanceInfo), lastVirtualKeyboardNoteNumber(virtualKeyboardMinimumNoteNumber - 1), filterEnvelopeAmount(0.0) { // ... }
Synthesis::Reset
:
mFilterEnvelopeGenerator.setSampleRate(GetSampleRate());
EParams
, . AttachGraphics
:
// Knobs for filter envelope // Attack knob GetParam(mFilterAttack)->InitDouble("Filter Env Attack", 0.01, 0.01, 10.0, 0.001); GetParam(mFilterAttack)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 139, 178, mFilterAttack, &smallKnobBitmap)); // Decay knob: GetParam(mFilterDecay)->InitDouble("Filter Env Decay", 0.5, 0.01, 15.0, 0.001); GetParam(mFilterDecay)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 195, 178, mFilterDecay, &smallKnobBitmap)); // Sustain knob: GetParam(mFilterSustain)->InitDouble("Filter Env Sustain", 0.1, 0.001, 1.0, 0.001); GetParam(mFilterSustain)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 251, 178, mFilterSustain, &smallKnobBitmap)); // Release knob: GetParam(mFilterRelease)->InitDouble("Filter Env Release", 1.0, 0.001, 15.0, 0.001); GetParam(mFilterRelease)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 307, 178, mFilterRelease, &smallKnobBitmap)); // Filter envelope amount knob: GetParam(mFilterEnvelopeAmount)->InitDouble("Filter Env Amount", 0.0, -1.0, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 363, 178, mFilterEnvelopeAmount, &smallKnobBitmap));
, smallKnobBitmap
. . , . Synthesis::OnParamChange
switch
:
case mFilterAttack: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK, GetParam(paramIdx)->Value()); break; case mFilterDecay: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_DECAY, GetParam(paramIdx)->Value()); break; case mFilterSustain: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_SUSTAIN, GetParam(paramIdx)->Value()); break; case mFilterRelease: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE, GetParam(paramIdx)->Value()); break; case mFilterEnvelopeAmount: filterEnvelopeAmount = GetParam(paramIdx)->Value(); break;
!
! (- C1), :
EnvelopeGenerator
. . !
.
:)
:
martin-finke.de/blog/articles/audio-plugins-013-filter
#include Filter.h ( ) :
class Filter { public: enum FilterMode { FILTER_MODE_LOWPASS = 0, FILTER_MODE_HIGHPASS, FILTER_MODE_BANDPASS, kNumFilterModes }; Filter() : cutoff(0.99), resonance(0.0), mode(FILTER_MODE_LOWPASS), buf0(0.0), buf1(0.0) { calculateFeedbackAmount(); }; double process(double inputValue); inline void setCutoff(double newCutoff) { cutoff = newCutoff; calculateFeedbackAmount(); }; inline void setResonance(double newResonance) { resonance = newResonance; calculateFeedbackAmount(); }; inline void setFilterMode(FilterMode newMode) { mode = newMode; } private: double cutoff; double resonance; FilterMode mode; double feedbackAmount; inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - cutoff); } double buf0; double buf1; };
private
cutoff
resonance
. Mode
(Lowpass, Highpass, Bandpass). feedbackAmount
, buf0
buf1
, .
( calculateFeedbackAmount()
). process
. feedbackAmount
cutoff
resonance
, calculateFeedbackAmount
.
Filter.cpp :
// By Paul Kellett // http://www.musicdsp.org/showone.php?id=29 double Filter::process(double inputValue) { buf0 += cutoff * (inputValue - buf0); buf1 += cutoff * (buf0 - buf1); switch (mode) { case FILTER_MODE_LOWPASS: return buf1; case FILTER_MODE_HIGHPASS: return inputValue - buf0; case FILTER_MODE_BANDPASS: return buf0 - buf1; default: return 0.0; } }
, ? , . « » , (. . 6 ). buf0
buf1
: . inputValue
, - buf0
( ). . 6 / 12 /. switch
, buf1
. , , buf0
, 12, 6 /, .
case
FILTER_MODE_HIGHPASS
. buf0
. inputValue
buf0
, . buf1
, .
case
FILTER_MODE_BANDPASS
, buf0 - buf1
. buf0
, buf1
. , .
: , .
buf1
. ( Infinite Impulse Response , IIR ). , . , , .
, , . , , , , . , NI Massive, « ». , , . , ( Massive ). «» , , , AAS Ultra Analog, , , , . , , , , NI Reaktor. , — , .. .
. GUI. bg.png ( “Move to trash” Xcode), :
filtermode.png knob_small.png ( , 50 50 ) bg.png ( )
ID resource.h :
// Unique IDs for each image resource. #define BG_ID 101 #define WHITE_KEY_ID 102 #define BLACK_KEY_ID 103 #define WAVEFORM_ID 104 #define KNOB_ID 105 #define KNOB_SMALL_ID 106 #define FILTERMODE_ID 107 // Image resource locations for this plug. #define BG_FN "resources/img/bg.png" #define WHITE_KEY_FN "resources/img/whitekey.png" #define BLACK_KEY_FN "resources/img/blackkey.png" #define WAVEFORM_FN "resources/img/waveform.png" #define KNOB_FN "resources/img/knob.png" #define KNOB_SMALL_FN "resources/img/knob_small.png" #define FILTERMODE_FN "resources/img/filtermode.png"
Synthesis.rc :
#include "resource.h" BG_ID PNG BG_FN WHITE_KEY_ID PNG WHITE_KEY_FN BLACK_KEY_ID PNG BLACK_KEY_FN WAVEFORM_ID PNG WAVEFORM_FN KNOB_ID PNG KNOB_FN KNOB_SMALL_ID PNG KNOB_SMALL_FN FILTERMODE_ID PNG FILTERMODE_FN
#include "Filter.h"
Synthesis.h private
:
Filter mFilter;
EParams
Synthesis.cpp :
enum EParams { mWaveform = 0, mAttack, mDecay, mSustain, mRelease, mFilterMode, mFilterCutoff, mFilterResonance, mFilterAttack, mFilterDecay, mFilterSustain, mFilterRelease, mFilterEnvelopeAmount, kNumParams };
:
pGraphics->AttachControl(new ISwitchControl(this, 24, 38, mWaveform, &waveformBitmap));
. (Lowpass, Highpass, Bandpass). AttachGraphics(pGraphics)
:
GetParam(mFilterMode)->InitEnum("Filter Mode", Filter::FILTER_MODE_LOWPASS, Filter::kNumFilterModes); IBitmap filtermodeBitmap = pGraphics->LoadIBitmap(FILTERMODE_ID, FILTERMODE_FN, 3); pGraphics->AttachControl(new ISwitchControl(this, 24, 123, mFilterMode, &filtermodeBitmap));
. . knob_small.png :
// Knobs for filter cutoff and resonance IBitmap smallKnobBitmap = pGraphics->LoadIBitmap(KNOB_SMALL_ID, KNOB_SMALL_FN, 64); // Cutoff knob: GetParam(mFilterCutoff)->InitDouble("Cutoff", 0.99, 0.01, 0.99, 0.001); GetParam(mFilterCutoff)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 5, 177, mFilterCutoff, &smallKnobBitmap)); // Resonance knob: GetParam(mFilterResonance)->InitDouble("Resonance", 0.01, 0.01, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 61, 177, mFilterResonance, &smallKnobBitmap));
, 1.0
, calculateFeedbackAmount
, .
ProcessDoubleReplacing
:
leftOutput[i] = rightOutput[i] = mFilter.process(mOscillator.nextSample() * mEnvelopeGenerator.nextSample() * velocity / 127.0);
. switch
Synthesis::OnParamChange
:
case mFilterCutoff: mFilter.setCutoff(GetParam(paramIdx)->Value()); break; case mFilterResonance: mFilter.setResonance(GetParam(paramIdx)->Value()); break; case mFilterMode: mFilter.setFilterMode(static_cast<Filter::FilterMode>(GetParam(paramIdx)->Int())); break;
. - .
— . , , .
:
buf0 += cutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1));
, , ( buf0 – buf1
), feedbackAmount
. calculateFeedbackAmount
feedbackAmount
resonance
, . . , .
, . , , . , , .
-12 / -24 /
! 24 . switch
Filter::process
:
buf2 += cutoff * (buf1 - buf2); buf3 += cutoff * (buf2 – buf3);
: , , . , , , ( ). switch
:
switch (mode) { case FILTER_MODE_LOWPASS: return buf3; case FILTER_MODE_HIGHPASS: return inputValue - buf3; case FILTER_MODE_BANDPASS: return buf0 - buf3; default: return 0.0; }
buf1
buf3
– . -24 /. buf2
buf3
, private
Filter.h :
double buf2; double buf3;
, buf0
c buf1
:
Filter() : // ... buf0(0.0), buf1(0.0), buf2(0.0), buf3(0.0) // ...
, , : . .
?
. , , .
- cutoff
. . cutoffMod
, , cutoff
. #include private
:
double cutoffMod;
:
Filter() : cutoff(0.99), resonance(0.01), cutoffMod(0.0), // ...
. private
:
inline double getCalculatedCutoff() const { return fmax(fmin(cutoff + cutoffMod, 0.99), 0.01); };
calculateFeedbackAmount
:
inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - getCalculatedCutoff()); }
cutoffMod
. feedbackAmount
, :
inline void setCutoffMod(double newCutoffMod) { cutoffMod = newCutoffMod; calculateFeedbackAmount(); }
, . Filter::process
:
if (inputValue == 0.0) return inputValue; double calculatedCutoff = getCalculatedCutoff(); buf0 += calculatedCutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1)); buf1 += calculatedCutoff * (buf0 - buf1); buf2 += calculatedCutoff * (buf1 - buf2); buf3 += calculatedCutoff * (buf2 – buf3);
, . , , . . cutoff
calculatedCutoff
-.
, ( setCutoffMod
), Synthesis
, , . , cutoffMod
. filterEnvelopeAmount
-1
+1
. GUI.
private
Synthesis.h :
EnvelopeGenerator mFilterEnvelopeGenerator; double filterEnvelopeAmount;
MIDI , onNoteOn
onNoteOff
:
inline void onNoteOn(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); }; inline void onNoteOff(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); };
. ProcessDoubleReplacing
:
mFilter.setCutoffMod(mFilterEnvelopeGenerator.nextSample() * filterEnvelopeAmount);
, nextSample
filterEnvelopeAmount
cutoffMod
. filterEnvelopeAmount
:
Synthesis::Synthesis(IPlugInstanceInfo instanceInfo) : IPLUG_CTOR(kNumParams, kNumPrograms, instanceInfo), lastVirtualKeyboardNoteNumber(virtualKeyboardMinimumNoteNumber - 1), filterEnvelopeAmount(0.0) { // ... }
Synthesis::Reset
:
mFilterEnvelopeGenerator.setSampleRate(GetSampleRate());
EParams
, . AttachGraphics
:
// Knobs for filter envelope // Attack knob GetParam(mFilterAttack)->InitDouble("Filter Env Attack", 0.01, 0.01, 10.0, 0.001); GetParam(mFilterAttack)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 139, 178, mFilterAttack, &smallKnobBitmap)); // Decay knob: GetParam(mFilterDecay)->InitDouble("Filter Env Decay", 0.5, 0.01, 15.0, 0.001); GetParam(mFilterDecay)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 195, 178, mFilterDecay, &smallKnobBitmap)); // Sustain knob: GetParam(mFilterSustain)->InitDouble("Filter Env Sustain", 0.1, 0.001, 1.0, 0.001); GetParam(mFilterSustain)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 251, 178, mFilterSustain, &smallKnobBitmap)); // Release knob: GetParam(mFilterRelease)->InitDouble("Filter Env Release", 1.0, 0.001, 15.0, 0.001); GetParam(mFilterRelease)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 307, 178, mFilterRelease, &smallKnobBitmap)); // Filter envelope amount knob: GetParam(mFilterEnvelopeAmount)->InitDouble("Filter Env Amount", 0.0, -1.0, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 363, 178, mFilterEnvelopeAmount, &smallKnobBitmap));
, smallKnobBitmap
. . , . Synthesis::OnParamChange
switch
:
case mFilterAttack: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK, GetParam(paramIdx)->Value()); break; case mFilterDecay: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_DECAY, GetParam(paramIdx)->Value()); break; case mFilterSustain: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_SUSTAIN, GetParam(paramIdx)->Value()); break; case mFilterRelease: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE, GetParam(paramIdx)->Value()); break; case mFilterEnvelopeAmount: filterEnvelopeAmount = GetParam(paramIdx)->Value(); break;
!
! (- C1), :
EnvelopeGenerator
. . !
.
:)
:
martin-finke.de/blog/articles/audio-plugins-013-filter
#include Filter.h ( ) :
class Filter { public: enum FilterMode { FILTER_MODE_LOWPASS = 0, FILTER_MODE_HIGHPASS, FILTER_MODE_BANDPASS, kNumFilterModes }; Filter() : cutoff(0.99), resonance(0.0), mode(FILTER_MODE_LOWPASS), buf0(0.0), buf1(0.0) { calculateFeedbackAmount(); }; double process(double inputValue); inline void setCutoff(double newCutoff) { cutoff = newCutoff; calculateFeedbackAmount(); }; inline void setResonance(double newResonance) { resonance = newResonance; calculateFeedbackAmount(); }; inline void setFilterMode(FilterMode newMode) { mode = newMode; } private: double cutoff; double resonance; FilterMode mode; double feedbackAmount; inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - cutoff); } double buf0; double buf1; };
private
cutoff
resonance
. Mode
(Lowpass, Highpass, Bandpass). feedbackAmount
, buf0
buf1
, .
( calculateFeedbackAmount()
). process
. feedbackAmount
cutoff
resonance
, calculateFeedbackAmount
.
Filter.cpp :
// By Paul Kellett // http://www.musicdsp.org/showone.php?id=29 double Filter::process(double inputValue) { buf0 += cutoff * (inputValue - buf0); buf1 += cutoff * (buf0 - buf1); switch (mode) { case FILTER_MODE_LOWPASS: return buf1; case FILTER_MODE_HIGHPASS: return inputValue - buf0; case FILTER_MODE_BANDPASS: return buf0 - buf1; default: return 0.0; } }
, ? , . « » , (. . 6 ). buf0
buf1
: . inputValue
, - buf0
( ). . 6 / 12 /. switch
, buf1
. , , buf0
, 12, 6 /, .
case
FILTER_MODE_HIGHPASS
. buf0
. inputValue
buf0
, . buf1
, .
case
FILTER_MODE_BANDPASS
, buf0 - buf1
. buf0
, buf1
. , .
: , .
buf1
. ( Infinite Impulse Response , IIR ). , . , , .
, , . , , , , . , NI Massive, « ». , , . , ( Massive ). «» , , , AAS Ultra Analog, , , , . , , , , NI Reaktor. , — , .. .
. GUI. bg.png ( “Move to trash” Xcode), :
filtermode.png knob_small.png ( , 50 50 ) bg.png ( )
ID resource.h :
// Unique IDs for each image resource. #define BG_ID 101 #define WHITE_KEY_ID 102 #define BLACK_KEY_ID 103 #define WAVEFORM_ID 104 #define KNOB_ID 105 #define KNOB_SMALL_ID 106 #define FILTERMODE_ID 107 // Image resource locations for this plug. #define BG_FN "resources/img/bg.png" #define WHITE_KEY_FN "resources/img/whitekey.png" #define BLACK_KEY_FN "resources/img/blackkey.png" #define WAVEFORM_FN "resources/img/waveform.png" #define KNOB_FN "resources/img/knob.png" #define KNOB_SMALL_FN "resources/img/knob_small.png" #define FILTERMODE_FN "resources/img/filtermode.png"
Synthesis.rc :
#include "resource.h" BG_ID PNG BG_FN WHITE_KEY_ID PNG WHITE_KEY_FN BLACK_KEY_ID PNG BLACK_KEY_FN WAVEFORM_ID PNG WAVEFORM_FN KNOB_ID PNG KNOB_FN KNOB_SMALL_ID PNG KNOB_SMALL_FN FILTERMODE_ID PNG FILTERMODE_FN
#include "Filter.h"
Synthesis.h private
:
Filter mFilter;
EParams
Synthesis.cpp :
enum EParams { mWaveform = 0, mAttack, mDecay, mSustain, mRelease, mFilterMode, mFilterCutoff, mFilterResonance, mFilterAttack, mFilterDecay, mFilterSustain, mFilterRelease, mFilterEnvelopeAmount, kNumParams };
:
pGraphics->AttachControl(new ISwitchControl(this, 24, 38, mWaveform, &waveformBitmap));
. (Lowpass, Highpass, Bandpass). AttachGraphics(pGraphics)
:
GetParam(mFilterMode)->InitEnum("Filter Mode", Filter::FILTER_MODE_LOWPASS, Filter::kNumFilterModes); IBitmap filtermodeBitmap = pGraphics->LoadIBitmap(FILTERMODE_ID, FILTERMODE_FN, 3); pGraphics->AttachControl(new ISwitchControl(this, 24, 123, mFilterMode, &filtermodeBitmap));
. . knob_small.png :
// Knobs for filter cutoff and resonance IBitmap smallKnobBitmap = pGraphics->LoadIBitmap(KNOB_SMALL_ID, KNOB_SMALL_FN, 64); // Cutoff knob: GetParam(mFilterCutoff)->InitDouble("Cutoff", 0.99, 0.01, 0.99, 0.001); GetParam(mFilterCutoff)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 5, 177, mFilterCutoff, &smallKnobBitmap)); // Resonance knob: GetParam(mFilterResonance)->InitDouble("Resonance", 0.01, 0.01, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 61, 177, mFilterResonance, &smallKnobBitmap));
, 1.0
, calculateFeedbackAmount
, .
ProcessDoubleReplacing
:
leftOutput[i] = rightOutput[i] = mFilter.process(mOscillator.nextSample() * mEnvelopeGenerator.nextSample() * velocity / 127.0);
. switch
Synthesis::OnParamChange
:
case mFilterCutoff: mFilter.setCutoff(GetParam(paramIdx)->Value()); break; case mFilterResonance: mFilter.setResonance(GetParam(paramIdx)->Value()); break; case mFilterMode: mFilter.setFilterMode(static_cast<Filter::FilterMode>(GetParam(paramIdx)->Int())); break;
. - .
— . , , .
:
buf0 += cutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1));
, , ( buf0 – buf1
), feedbackAmount
. calculateFeedbackAmount
feedbackAmount
resonance
, . . , .
, . , , . , , .
-12 / -24 /
! 24 . switch
Filter::process
:
buf2 += cutoff * (buf1 - buf2); buf3 += cutoff * (buf2 – buf3);
: , , . , , , ( ). switch
:
switch (mode) { case FILTER_MODE_LOWPASS: return buf3; case FILTER_MODE_HIGHPASS: return inputValue - buf3; case FILTER_MODE_BANDPASS: return buf0 - buf3; default: return 0.0; }
buf1
buf3
– . -24 /. buf2
buf3
, private
Filter.h :
double buf2; double buf3;
, buf0
c buf1
:
Filter() : // ... buf0(0.0), buf1(0.0), buf2(0.0), buf3(0.0) // ...
, , : . .
?
. , , .
- cutoff
. . cutoffMod
, , cutoff
. #include private
:
double cutoffMod;
:
Filter() : cutoff(0.99), resonance(0.01), cutoffMod(0.0), // ...
. private
:
inline double getCalculatedCutoff() const { return fmax(fmin(cutoff + cutoffMod, 0.99), 0.01); };
calculateFeedbackAmount
:
inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - getCalculatedCutoff()); }
cutoffMod
. feedbackAmount
, :
inline void setCutoffMod(double newCutoffMod) { cutoffMod = newCutoffMod; calculateFeedbackAmount(); }
, . Filter::process
:
if (inputValue == 0.0) return inputValue; double calculatedCutoff = getCalculatedCutoff(); buf0 += calculatedCutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1)); buf1 += calculatedCutoff * (buf0 - buf1); buf2 += calculatedCutoff * (buf1 - buf2); buf3 += calculatedCutoff * (buf2 – buf3);
, . , , . . cutoff
calculatedCutoff
-.
, ( setCutoffMod
), Synthesis
, , . , cutoffMod
. filterEnvelopeAmount
-1
+1
. GUI.
private
Synthesis.h :
EnvelopeGenerator mFilterEnvelopeGenerator; double filterEnvelopeAmount;
MIDI , onNoteOn
onNoteOff
:
inline void onNoteOn(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); }; inline void onNoteOff(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); };
. ProcessDoubleReplacing
:
mFilter.setCutoffMod(mFilterEnvelopeGenerator.nextSample() * filterEnvelopeAmount);
, nextSample
filterEnvelopeAmount
cutoffMod
. filterEnvelopeAmount
:
Synthesis::Synthesis(IPlugInstanceInfo instanceInfo) : IPLUG_CTOR(kNumParams, kNumPrograms, instanceInfo), lastVirtualKeyboardNoteNumber(virtualKeyboardMinimumNoteNumber - 1), filterEnvelopeAmount(0.0) { // ... }
Synthesis::Reset
:
mFilterEnvelopeGenerator.setSampleRate(GetSampleRate());
EParams
, . AttachGraphics
:
// Knobs for filter envelope // Attack knob GetParam(mFilterAttack)->InitDouble("Filter Env Attack", 0.01, 0.01, 10.0, 0.001); GetParam(mFilterAttack)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 139, 178, mFilterAttack, &smallKnobBitmap)); // Decay knob: GetParam(mFilterDecay)->InitDouble("Filter Env Decay", 0.5, 0.01, 15.0, 0.001); GetParam(mFilterDecay)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 195, 178, mFilterDecay, &smallKnobBitmap)); // Sustain knob: GetParam(mFilterSustain)->InitDouble("Filter Env Sustain", 0.1, 0.001, 1.0, 0.001); GetParam(mFilterSustain)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 251, 178, mFilterSustain, &smallKnobBitmap)); // Release knob: GetParam(mFilterRelease)->InitDouble("Filter Env Release", 1.0, 0.001, 15.0, 0.001); GetParam(mFilterRelease)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 307, 178, mFilterRelease, &smallKnobBitmap)); // Filter envelope amount knob: GetParam(mFilterEnvelopeAmount)->InitDouble("Filter Env Amount", 0.0, -1.0, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 363, 178, mFilterEnvelopeAmount, &smallKnobBitmap));
, smallKnobBitmap
. . , . Synthesis::OnParamChange
switch
:
case mFilterAttack: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK, GetParam(paramIdx)->Value()); break; case mFilterDecay: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_DECAY, GetParam(paramIdx)->Value()); break; case mFilterSustain: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_SUSTAIN, GetParam(paramIdx)->Value()); break; case mFilterRelease: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE, GetParam(paramIdx)->Value()); break; case mFilterEnvelopeAmount: filterEnvelopeAmount = GetParam(paramIdx)->Value(); break;
!
! (- C1), :
EnvelopeGenerator
. . !
.
:)
:
martin-finke.de/blog/articles/audio-plugins-013-filter
#include Filter.h ( ) :
class Filter { public: enum FilterMode { FILTER_MODE_LOWPASS = 0, FILTER_MODE_HIGHPASS, FILTER_MODE_BANDPASS, kNumFilterModes }; Filter() : cutoff(0.99), resonance(0.0), mode(FILTER_MODE_LOWPASS), buf0(0.0), buf1(0.0) { calculateFeedbackAmount(); }; double process(double inputValue); inline void setCutoff(double newCutoff) { cutoff = newCutoff; calculateFeedbackAmount(); }; inline void setResonance(double newResonance) { resonance = newResonance; calculateFeedbackAmount(); }; inline void setFilterMode(FilterMode newMode) { mode = newMode; } private: double cutoff; double resonance; FilterMode mode; double feedbackAmount; inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - cutoff); } double buf0; double buf1; };
private
cutoff
resonance
. Mode
(Lowpass, Highpass, Bandpass). feedbackAmount
, buf0
buf1
, .
( calculateFeedbackAmount()
). process
. feedbackAmount
cutoff
resonance
, calculateFeedbackAmount
.
Filter.cpp :
// By Paul Kellett // http://www.musicdsp.org/showone.php?id=29 double Filter::process(double inputValue) { buf0 += cutoff * (inputValue - buf0); buf1 += cutoff * (buf0 - buf1); switch (mode) { case FILTER_MODE_LOWPASS: return buf1; case FILTER_MODE_HIGHPASS: return inputValue - buf0; case FILTER_MODE_BANDPASS: return buf0 - buf1; default: return 0.0; } }
, ? , . « » , (. . 6 ). buf0
buf1
: . inputValue
, - buf0
( ). . 6 / 12 /. switch
, buf1
. , , buf0
, 12, 6 /, .
case
FILTER_MODE_HIGHPASS
. buf0
. inputValue
buf0
, . buf1
, .
case
FILTER_MODE_BANDPASS
, buf0 - buf1
. buf0
, buf1
. , .
: , .
buf1
. ( Infinite Impulse Response , IIR ). , . , , .
, , . , , , , . , NI Massive, « ». , , . , ( Massive ). «» , , , AAS Ultra Analog, , , , . , , , , NI Reaktor. , — , .. .
. GUI. bg.png ( “Move to trash” Xcode), :
filtermode.png knob_small.png ( , 50 50 ) bg.png ( )
ID resource.h :
// Unique IDs for each image resource. #define BG_ID 101 #define WHITE_KEY_ID 102 #define BLACK_KEY_ID 103 #define WAVEFORM_ID 104 #define KNOB_ID 105 #define KNOB_SMALL_ID 106 #define FILTERMODE_ID 107 // Image resource locations for this plug. #define BG_FN "resources/img/bg.png" #define WHITE_KEY_FN "resources/img/whitekey.png" #define BLACK_KEY_FN "resources/img/blackkey.png" #define WAVEFORM_FN "resources/img/waveform.png" #define KNOB_FN "resources/img/knob.png" #define KNOB_SMALL_FN "resources/img/knob_small.png" #define FILTERMODE_FN "resources/img/filtermode.png"
Synthesis.rc :
#include "resource.h" BG_ID PNG BG_FN WHITE_KEY_ID PNG WHITE_KEY_FN BLACK_KEY_ID PNG BLACK_KEY_FN WAVEFORM_ID PNG WAVEFORM_FN KNOB_ID PNG KNOB_FN KNOB_SMALL_ID PNG KNOB_SMALL_FN FILTERMODE_ID PNG FILTERMODE_FN
#include "Filter.h"
Synthesis.h private
:
Filter mFilter;
EParams
Synthesis.cpp :
enum EParams { mWaveform = 0, mAttack, mDecay, mSustain, mRelease, mFilterMode, mFilterCutoff, mFilterResonance, mFilterAttack, mFilterDecay, mFilterSustain, mFilterRelease, mFilterEnvelopeAmount, kNumParams };
:
pGraphics->AttachControl(new ISwitchControl(this, 24, 38, mWaveform, &waveformBitmap));
. (Lowpass, Highpass, Bandpass). AttachGraphics(pGraphics)
:
GetParam(mFilterMode)->InitEnum("Filter Mode", Filter::FILTER_MODE_LOWPASS, Filter::kNumFilterModes); IBitmap filtermodeBitmap = pGraphics->LoadIBitmap(FILTERMODE_ID, FILTERMODE_FN, 3); pGraphics->AttachControl(new ISwitchControl(this, 24, 123, mFilterMode, &filtermodeBitmap));
. . knob_small.png :
// Knobs for filter cutoff and resonance IBitmap smallKnobBitmap = pGraphics->LoadIBitmap(KNOB_SMALL_ID, KNOB_SMALL_FN, 64); // Cutoff knob: GetParam(mFilterCutoff)->InitDouble("Cutoff", 0.99, 0.01, 0.99, 0.001); GetParam(mFilterCutoff)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 5, 177, mFilterCutoff, &smallKnobBitmap)); // Resonance knob: GetParam(mFilterResonance)->InitDouble("Resonance", 0.01, 0.01, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 61, 177, mFilterResonance, &smallKnobBitmap));
, 1.0
, calculateFeedbackAmount
, .
ProcessDoubleReplacing
:
leftOutput[i] = rightOutput[i] = mFilter.process(mOscillator.nextSample() * mEnvelopeGenerator.nextSample() * velocity / 127.0);
. switch
Synthesis::OnParamChange
:
case mFilterCutoff: mFilter.setCutoff(GetParam(paramIdx)->Value()); break; case mFilterResonance: mFilter.setResonance(GetParam(paramIdx)->Value()); break; case mFilterMode: mFilter.setFilterMode(static_cast<Filter::FilterMode>(GetParam(paramIdx)->Int())); break;
. - .
— . , , .
:
buf0 += cutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1));
, , ( buf0 – buf1
), feedbackAmount
. calculateFeedbackAmount
feedbackAmount
resonance
, . . , .
, . , , . , , .
-12 / -24 /
! 24 . switch
Filter::process
:
buf2 += cutoff * (buf1 - buf2); buf3 += cutoff * (buf2 – buf3);
: , , . , , , ( ). switch
:
switch (mode) { case FILTER_MODE_LOWPASS: return buf3; case FILTER_MODE_HIGHPASS: return inputValue - buf3; case FILTER_MODE_BANDPASS: return buf0 - buf3; default: return 0.0; }
buf1
buf3
– . -24 /. buf2
buf3
, private
Filter.h :
double buf2; double buf3;
, buf0
c buf1
:
Filter() : // ... buf0(0.0), buf1(0.0), buf2(0.0), buf3(0.0) // ...
, , : . .
?
. , , .
- cutoff
. . cutoffMod
, , cutoff
. #include private
:
double cutoffMod;
:
Filter() : cutoff(0.99), resonance(0.01), cutoffMod(0.0), // ...
. private
:
inline double getCalculatedCutoff() const { return fmax(fmin(cutoff + cutoffMod, 0.99), 0.01); };
calculateFeedbackAmount
:
inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - getCalculatedCutoff()); }
cutoffMod
. feedbackAmount
, :
inline void setCutoffMod(double newCutoffMod) { cutoffMod = newCutoffMod; calculateFeedbackAmount(); }
, . Filter::process
:
if (inputValue == 0.0) return inputValue; double calculatedCutoff = getCalculatedCutoff(); buf0 += calculatedCutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1)); buf1 += calculatedCutoff * (buf0 - buf1); buf2 += calculatedCutoff * (buf1 - buf2); buf3 += calculatedCutoff * (buf2 – buf3);
, . , , . . cutoff
calculatedCutoff
-.
, ( setCutoffMod
), Synthesis
, , . , cutoffMod
. filterEnvelopeAmount
-1
+1
. GUI.
private
Synthesis.h :
EnvelopeGenerator mFilterEnvelopeGenerator; double filterEnvelopeAmount;
MIDI , onNoteOn
onNoteOff
:
inline void onNoteOn(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); }; inline void onNoteOff(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); };
. ProcessDoubleReplacing
:
mFilter.setCutoffMod(mFilterEnvelopeGenerator.nextSample() * filterEnvelopeAmount);
, nextSample
filterEnvelopeAmount
cutoffMod
. filterEnvelopeAmount
:
Synthesis::Synthesis(IPlugInstanceInfo instanceInfo) : IPLUG_CTOR(kNumParams, kNumPrograms, instanceInfo), lastVirtualKeyboardNoteNumber(virtualKeyboardMinimumNoteNumber - 1), filterEnvelopeAmount(0.0) { // ... }
Synthesis::Reset
:
mFilterEnvelopeGenerator.setSampleRate(GetSampleRate());
EParams
, . AttachGraphics
:
// Knobs for filter envelope // Attack knob GetParam(mFilterAttack)->InitDouble("Filter Env Attack", 0.01, 0.01, 10.0, 0.001); GetParam(mFilterAttack)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 139, 178, mFilterAttack, &smallKnobBitmap)); // Decay knob: GetParam(mFilterDecay)->InitDouble("Filter Env Decay", 0.5, 0.01, 15.0, 0.001); GetParam(mFilterDecay)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 195, 178, mFilterDecay, &smallKnobBitmap)); // Sustain knob: GetParam(mFilterSustain)->InitDouble("Filter Env Sustain", 0.1, 0.001, 1.0, 0.001); GetParam(mFilterSustain)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 251, 178, mFilterSustain, &smallKnobBitmap)); // Release knob: GetParam(mFilterRelease)->InitDouble("Filter Env Release", 1.0, 0.001, 15.0, 0.001); GetParam(mFilterRelease)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 307, 178, mFilterRelease, &smallKnobBitmap)); // Filter envelope amount knob: GetParam(mFilterEnvelopeAmount)->InitDouble("Filter Env Amount", 0.0, -1.0, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 363, 178, mFilterEnvelopeAmount, &smallKnobBitmap));
, smallKnobBitmap
. . , . Synthesis::OnParamChange
switch
:
case mFilterAttack: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK, GetParam(paramIdx)->Value()); break; case mFilterDecay: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_DECAY, GetParam(paramIdx)->Value()); break; case mFilterSustain: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_SUSTAIN, GetParam(paramIdx)->Value()); break; case mFilterRelease: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE, GetParam(paramIdx)->Value()); break; case mFilterEnvelopeAmount: filterEnvelopeAmount = GetParam(paramIdx)->Value(); break;
!
! (- C1), :
EnvelopeGenerator
. . !
.
:)
:
martin-finke.de/blog/articles/audio-plugins-013-filter
#include Filter.h ( ) :
class Filter { public: enum FilterMode { FILTER_MODE_LOWPASS = 0, FILTER_MODE_HIGHPASS, FILTER_MODE_BANDPASS, kNumFilterModes }; Filter() : cutoff(0.99), resonance(0.0), mode(FILTER_MODE_LOWPASS), buf0(0.0), buf1(0.0) { calculateFeedbackAmount(); }; double process(double inputValue); inline void setCutoff(double newCutoff) { cutoff = newCutoff; calculateFeedbackAmount(); }; inline void setResonance(double newResonance) { resonance = newResonance; calculateFeedbackAmount(); }; inline void setFilterMode(FilterMode newMode) { mode = newMode; } private: double cutoff; double resonance; FilterMode mode; double feedbackAmount; inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - cutoff); } double buf0; double buf1; };
private
cutoff
resonance
. Mode
(Lowpass, Highpass, Bandpass). feedbackAmount
, buf0
buf1
, .
( calculateFeedbackAmount()
). process
. feedbackAmount
cutoff
resonance
, calculateFeedbackAmount
.
Filter.cpp :
// By Paul Kellett // http://www.musicdsp.org/showone.php?id=29 double Filter::process(double inputValue) { buf0 += cutoff * (inputValue - buf0); buf1 += cutoff * (buf0 - buf1); switch (mode) { case FILTER_MODE_LOWPASS: return buf1; case FILTER_MODE_HIGHPASS: return inputValue - buf0; case FILTER_MODE_BANDPASS: return buf0 - buf1; default: return 0.0; } }
, ? , . « » , (. . 6 ). buf0
buf1
: . inputValue
, - buf0
( ). . 6 / 12 /. switch
, buf1
. , , buf0
, 12, 6 /, .
case
FILTER_MODE_HIGHPASS
. buf0
. inputValue
buf0
, . buf1
, .
case
FILTER_MODE_BANDPASS
, buf0 - buf1
. buf0
, buf1
. , .
: , .
buf1
. ( Infinite Impulse Response , IIR ). , . , , .
, , . , , , , . , NI Massive, « ». , , . , ( Massive ). «» , , , AAS Ultra Analog, , , , . , , , , NI Reaktor. , — , .. .
. GUI. bg.png ( “Move to trash” Xcode), :
filtermode.png knob_small.png ( , 50 50 ) bg.png ( )
ID resource.h :
// Unique IDs for each image resource. #define BG_ID 101 #define WHITE_KEY_ID 102 #define BLACK_KEY_ID 103 #define WAVEFORM_ID 104 #define KNOB_ID 105 #define KNOB_SMALL_ID 106 #define FILTERMODE_ID 107 // Image resource locations for this plug. #define BG_FN "resources/img/bg.png" #define WHITE_KEY_FN "resources/img/whitekey.png" #define BLACK_KEY_FN "resources/img/blackkey.png" #define WAVEFORM_FN "resources/img/waveform.png" #define KNOB_FN "resources/img/knob.png" #define KNOB_SMALL_FN "resources/img/knob_small.png" #define FILTERMODE_FN "resources/img/filtermode.png"
Synthesis.rc :
#include "resource.h" BG_ID PNG BG_FN WHITE_KEY_ID PNG WHITE_KEY_FN BLACK_KEY_ID PNG BLACK_KEY_FN WAVEFORM_ID PNG WAVEFORM_FN KNOB_ID PNG KNOB_FN KNOB_SMALL_ID PNG KNOB_SMALL_FN FILTERMODE_ID PNG FILTERMODE_FN
#include "Filter.h"
Synthesis.h private
:
Filter mFilter;
EParams
Synthesis.cpp :
enum EParams { mWaveform = 0, mAttack, mDecay, mSustain, mRelease, mFilterMode, mFilterCutoff, mFilterResonance, mFilterAttack, mFilterDecay, mFilterSustain, mFilterRelease, mFilterEnvelopeAmount, kNumParams };
:
pGraphics->AttachControl(new ISwitchControl(this, 24, 38, mWaveform, &waveformBitmap));
. (Lowpass, Highpass, Bandpass). AttachGraphics(pGraphics)
:
GetParam(mFilterMode)->InitEnum("Filter Mode", Filter::FILTER_MODE_LOWPASS, Filter::kNumFilterModes); IBitmap filtermodeBitmap = pGraphics->LoadIBitmap(FILTERMODE_ID, FILTERMODE_FN, 3); pGraphics->AttachControl(new ISwitchControl(this, 24, 123, mFilterMode, &filtermodeBitmap));
. . knob_small.png :
// Knobs for filter cutoff and resonance IBitmap smallKnobBitmap = pGraphics->LoadIBitmap(KNOB_SMALL_ID, KNOB_SMALL_FN, 64); // Cutoff knob: GetParam(mFilterCutoff)->InitDouble("Cutoff", 0.99, 0.01, 0.99, 0.001); GetParam(mFilterCutoff)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 5, 177, mFilterCutoff, &smallKnobBitmap)); // Resonance knob: GetParam(mFilterResonance)->InitDouble("Resonance", 0.01, 0.01, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 61, 177, mFilterResonance, &smallKnobBitmap));
, 1.0
, calculateFeedbackAmount
, .
ProcessDoubleReplacing
:
leftOutput[i] = rightOutput[i] = mFilter.process(mOscillator.nextSample() * mEnvelopeGenerator.nextSample() * velocity / 127.0);
. switch
Synthesis::OnParamChange
:
case mFilterCutoff: mFilter.setCutoff(GetParam(paramIdx)->Value()); break; case mFilterResonance: mFilter.setResonance(GetParam(paramIdx)->Value()); break; case mFilterMode: mFilter.setFilterMode(static_cast<Filter::FilterMode>(GetParam(paramIdx)->Int())); break;
. - .
— . , , .
:
buf0 += cutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1));
, , ( buf0 – buf1
), feedbackAmount
. calculateFeedbackAmount
feedbackAmount
resonance
, . . , .
, . , , . , , .
-12 / -24 /
! 24 . switch
Filter::process
:
buf2 += cutoff * (buf1 - buf2); buf3 += cutoff * (buf2 – buf3);
: , , . , , , ( ). switch
:
switch (mode) { case FILTER_MODE_LOWPASS: return buf3; case FILTER_MODE_HIGHPASS: return inputValue - buf3; case FILTER_MODE_BANDPASS: return buf0 - buf3; default: return 0.0; }
buf1
buf3
– . -24 /. buf2
buf3
, private
Filter.h :
double buf2; double buf3;
, buf0
c buf1
:
Filter() : // ... buf0(0.0), buf1(0.0), buf2(0.0), buf3(0.0) // ...
, , : . .
?
. , , .
- cutoff
. . cutoffMod
, , cutoff
. #include private
:
double cutoffMod;
:
Filter() : cutoff(0.99), resonance(0.01), cutoffMod(0.0), // ...
. private
:
inline double getCalculatedCutoff() const { return fmax(fmin(cutoff + cutoffMod, 0.99), 0.01); };
calculateFeedbackAmount
:
inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - getCalculatedCutoff()); }
cutoffMod
. feedbackAmount
, :
inline void setCutoffMod(double newCutoffMod) { cutoffMod = newCutoffMod; calculateFeedbackAmount(); }
, . Filter::process
:
if (inputValue == 0.0) return inputValue; double calculatedCutoff = getCalculatedCutoff(); buf0 += calculatedCutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1)); buf1 += calculatedCutoff * (buf0 - buf1); buf2 += calculatedCutoff * (buf1 - buf2); buf3 += calculatedCutoff * (buf2 – buf3);
, . , , . . cutoff
calculatedCutoff
-.
, ( setCutoffMod
), Synthesis
, , . , cutoffMod
. filterEnvelopeAmount
-1
+1
. GUI.
private
Synthesis.h :
EnvelopeGenerator mFilterEnvelopeGenerator; double filterEnvelopeAmount;
MIDI , onNoteOn
onNoteOff
:
inline void onNoteOn(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); }; inline void onNoteOff(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); };
. ProcessDoubleReplacing
:
mFilter.setCutoffMod(mFilterEnvelopeGenerator.nextSample() * filterEnvelopeAmount);
, nextSample
filterEnvelopeAmount
cutoffMod
. filterEnvelopeAmount
:
Synthesis::Synthesis(IPlugInstanceInfo instanceInfo) : IPLUG_CTOR(kNumParams, kNumPrograms, instanceInfo), lastVirtualKeyboardNoteNumber(virtualKeyboardMinimumNoteNumber - 1), filterEnvelopeAmount(0.0) { // ... }
Synthesis::Reset
:
mFilterEnvelopeGenerator.setSampleRate(GetSampleRate());
EParams
, . AttachGraphics
:
// Knobs for filter envelope // Attack knob GetParam(mFilterAttack)->InitDouble("Filter Env Attack", 0.01, 0.01, 10.0, 0.001); GetParam(mFilterAttack)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 139, 178, mFilterAttack, &smallKnobBitmap)); // Decay knob: GetParam(mFilterDecay)->InitDouble("Filter Env Decay", 0.5, 0.01, 15.0, 0.001); GetParam(mFilterDecay)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 195, 178, mFilterDecay, &smallKnobBitmap)); // Sustain knob: GetParam(mFilterSustain)->InitDouble("Filter Env Sustain", 0.1, 0.001, 1.0, 0.001); GetParam(mFilterSustain)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 251, 178, mFilterSustain, &smallKnobBitmap)); // Release knob: GetParam(mFilterRelease)->InitDouble("Filter Env Release", 1.0, 0.001, 15.0, 0.001); GetParam(mFilterRelease)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 307, 178, mFilterRelease, &smallKnobBitmap)); // Filter envelope amount knob: GetParam(mFilterEnvelopeAmount)->InitDouble("Filter Env Amount", 0.0, -1.0, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 363, 178, mFilterEnvelopeAmount, &smallKnobBitmap));
, smallKnobBitmap
. . , . Synthesis::OnParamChange
switch
:
case mFilterAttack: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK, GetParam(paramIdx)->Value()); break; case mFilterDecay: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_DECAY, GetParam(paramIdx)->Value()); break; case mFilterSustain: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_SUSTAIN, GetParam(paramIdx)->Value()); break; case mFilterRelease: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE, GetParam(paramIdx)->Value()); break; case mFilterEnvelopeAmount: filterEnvelopeAmount = GetParam(paramIdx)->Value(); break;
!
! (- C1), :
EnvelopeGenerator
. . !
.
:)
:
martin-finke.de/blog/articles/audio-plugins-013-filter
#include Filter.h ( ) :
class Filter { public: enum FilterMode { FILTER_MODE_LOWPASS = 0, FILTER_MODE_HIGHPASS, FILTER_MODE_BANDPASS, kNumFilterModes }; Filter() : cutoff(0.99), resonance(0.0), mode(FILTER_MODE_LOWPASS), buf0(0.0), buf1(0.0) { calculateFeedbackAmount(); }; double process(double inputValue); inline void setCutoff(double newCutoff) { cutoff = newCutoff; calculateFeedbackAmount(); }; inline void setResonance(double newResonance) { resonance = newResonance; calculateFeedbackAmount(); }; inline void setFilterMode(FilterMode newMode) { mode = newMode; } private: double cutoff; double resonance; FilterMode mode; double feedbackAmount; inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - cutoff); } double buf0; double buf1; };
private
cutoff
resonance
. Mode
(Lowpass, Highpass, Bandpass). feedbackAmount
, buf0
buf1
, .
( calculateFeedbackAmount()
). process
. feedbackAmount
cutoff
resonance
, calculateFeedbackAmount
.
Filter.cpp :
// By Paul Kellett // http://www.musicdsp.org/showone.php?id=29 double Filter::process(double inputValue) { buf0 += cutoff * (inputValue - buf0); buf1 += cutoff * (buf0 - buf1); switch (mode) { case FILTER_MODE_LOWPASS: return buf1; case FILTER_MODE_HIGHPASS: return inputValue - buf0; case FILTER_MODE_BANDPASS: return buf0 - buf1; default: return 0.0; } }
, ? , . « » , (. . 6 ). buf0
buf1
: . inputValue
, - buf0
( ). . 6 / 12 /. switch
, buf1
. , , buf0
, 12, 6 /, .
case
FILTER_MODE_HIGHPASS
. buf0
. inputValue
buf0
, . buf1
, .
case
FILTER_MODE_BANDPASS
, buf0 - buf1
. buf0
, buf1
. , .
: , .
buf1
. ( Infinite Impulse Response , IIR ). , . , , .
, , . , , , , . , NI Massive, « ». , , . , ( Massive ). «» , , , AAS Ultra Analog, , , , . , , , , NI Reaktor. , — , .. .
. GUI. bg.png ( “Move to trash” Xcode), :
filtermode.png knob_small.png ( , 50 50 ) bg.png ( )
ID resource.h :
// Unique IDs for each image resource. #define BG_ID 101 #define WHITE_KEY_ID 102 #define BLACK_KEY_ID 103 #define WAVEFORM_ID 104 #define KNOB_ID 105 #define KNOB_SMALL_ID 106 #define FILTERMODE_ID 107 // Image resource locations for this plug. #define BG_FN "resources/img/bg.png" #define WHITE_KEY_FN "resources/img/whitekey.png" #define BLACK_KEY_FN "resources/img/blackkey.png" #define WAVEFORM_FN "resources/img/waveform.png" #define KNOB_FN "resources/img/knob.png" #define KNOB_SMALL_FN "resources/img/knob_small.png" #define FILTERMODE_FN "resources/img/filtermode.png"
Synthesis.rc :
#include "resource.h" BG_ID PNG BG_FN WHITE_KEY_ID PNG WHITE_KEY_FN BLACK_KEY_ID PNG BLACK_KEY_FN WAVEFORM_ID PNG WAVEFORM_FN KNOB_ID PNG KNOB_FN KNOB_SMALL_ID PNG KNOB_SMALL_FN FILTERMODE_ID PNG FILTERMODE_FN
#include "Filter.h"
Synthesis.h private
:
Filter mFilter;
EParams
Synthesis.cpp :
enum EParams { mWaveform = 0, mAttack, mDecay, mSustain, mRelease, mFilterMode, mFilterCutoff, mFilterResonance, mFilterAttack, mFilterDecay, mFilterSustain, mFilterRelease, mFilterEnvelopeAmount, kNumParams };
:
pGraphics->AttachControl(new ISwitchControl(this, 24, 38, mWaveform, &waveformBitmap));
. (Lowpass, Highpass, Bandpass). AttachGraphics(pGraphics)
:
GetParam(mFilterMode)->InitEnum("Filter Mode", Filter::FILTER_MODE_LOWPASS, Filter::kNumFilterModes); IBitmap filtermodeBitmap = pGraphics->LoadIBitmap(FILTERMODE_ID, FILTERMODE_FN, 3); pGraphics->AttachControl(new ISwitchControl(this, 24, 123, mFilterMode, &filtermodeBitmap));
. . knob_small.png :
// Knobs for filter cutoff and resonance IBitmap smallKnobBitmap = pGraphics->LoadIBitmap(KNOB_SMALL_ID, KNOB_SMALL_FN, 64); // Cutoff knob: GetParam(mFilterCutoff)->InitDouble("Cutoff", 0.99, 0.01, 0.99, 0.001); GetParam(mFilterCutoff)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 5, 177, mFilterCutoff, &smallKnobBitmap)); // Resonance knob: GetParam(mFilterResonance)->InitDouble("Resonance", 0.01, 0.01, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 61, 177, mFilterResonance, &smallKnobBitmap));
, 1.0
, calculateFeedbackAmount
, .
ProcessDoubleReplacing
:
leftOutput[i] = rightOutput[i] = mFilter.process(mOscillator.nextSample() * mEnvelopeGenerator.nextSample() * velocity / 127.0);
. switch
Synthesis::OnParamChange
:
case mFilterCutoff: mFilter.setCutoff(GetParam(paramIdx)->Value()); break; case mFilterResonance: mFilter.setResonance(GetParam(paramIdx)->Value()); break; case mFilterMode: mFilter.setFilterMode(static_cast<Filter::FilterMode>(GetParam(paramIdx)->Int())); break;
. - .
— . , , .
:
buf0 += cutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1));
, , ( buf0 – buf1
), feedbackAmount
. calculateFeedbackAmount
feedbackAmount
resonance
, . . , .
, . , , . , , .
-12 / -24 /
! 24 . switch
Filter::process
:
buf2 += cutoff * (buf1 - buf2); buf3 += cutoff * (buf2 – buf3);
: , , . , , , ( ). switch
:
switch (mode) { case FILTER_MODE_LOWPASS: return buf3; case FILTER_MODE_HIGHPASS: return inputValue - buf3; case FILTER_MODE_BANDPASS: return buf0 - buf3; default: return 0.0; }
buf1
buf3
– . -24 /. buf2
buf3
, private
Filter.h :
double buf2; double buf3;
, buf0
c buf1
:
Filter() : // ... buf0(0.0), buf1(0.0), buf2(0.0), buf3(0.0) // ...
, , : . .
?
. , , .
- cutoff
. . cutoffMod
, , cutoff
. #include private
:
double cutoffMod;
:
Filter() : cutoff(0.99), resonance(0.01), cutoffMod(0.0), // ...
. private
:
inline double getCalculatedCutoff() const { return fmax(fmin(cutoff + cutoffMod, 0.99), 0.01); };
calculateFeedbackAmount
:
inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - getCalculatedCutoff()); }
cutoffMod
. feedbackAmount
, :
inline void setCutoffMod(double newCutoffMod) { cutoffMod = newCutoffMod; calculateFeedbackAmount(); }
, . Filter::process
:
if (inputValue == 0.0) return inputValue; double calculatedCutoff = getCalculatedCutoff(); buf0 += calculatedCutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1)); buf1 += calculatedCutoff * (buf0 - buf1); buf2 += calculatedCutoff * (buf1 - buf2); buf3 += calculatedCutoff * (buf2 – buf3);
, . , , . . cutoff
calculatedCutoff
-.
, ( setCutoffMod
), Synthesis
, , . , cutoffMod
. filterEnvelopeAmount
-1
+1
. GUI.
private
Synthesis.h :
EnvelopeGenerator mFilterEnvelopeGenerator; double filterEnvelopeAmount;
MIDI , onNoteOn
onNoteOff
:
inline void onNoteOn(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); }; inline void onNoteOff(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); };
. ProcessDoubleReplacing
:
mFilter.setCutoffMod(mFilterEnvelopeGenerator.nextSample() * filterEnvelopeAmount);
, nextSample
filterEnvelopeAmount
cutoffMod
. filterEnvelopeAmount
:
Synthesis::Synthesis(IPlugInstanceInfo instanceInfo) : IPLUG_CTOR(kNumParams, kNumPrograms, instanceInfo), lastVirtualKeyboardNoteNumber(virtualKeyboardMinimumNoteNumber - 1), filterEnvelopeAmount(0.0) { // ... }
Synthesis::Reset
:
mFilterEnvelopeGenerator.setSampleRate(GetSampleRate());
EParams
, . AttachGraphics
:
// Knobs for filter envelope // Attack knob GetParam(mFilterAttack)->InitDouble("Filter Env Attack", 0.01, 0.01, 10.0, 0.001); GetParam(mFilterAttack)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 139, 178, mFilterAttack, &smallKnobBitmap)); // Decay knob: GetParam(mFilterDecay)->InitDouble("Filter Env Decay", 0.5, 0.01, 15.0, 0.001); GetParam(mFilterDecay)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 195, 178, mFilterDecay, &smallKnobBitmap)); // Sustain knob: GetParam(mFilterSustain)->InitDouble("Filter Env Sustain", 0.1, 0.001, 1.0, 0.001); GetParam(mFilterSustain)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 251, 178, mFilterSustain, &smallKnobBitmap)); // Release knob: GetParam(mFilterRelease)->InitDouble("Filter Env Release", 1.0, 0.001, 15.0, 0.001); GetParam(mFilterRelease)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 307, 178, mFilterRelease, &smallKnobBitmap)); // Filter envelope amount knob: GetParam(mFilterEnvelopeAmount)->InitDouble("Filter Env Amount", 0.0, -1.0, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 363, 178, mFilterEnvelopeAmount, &smallKnobBitmap));
, smallKnobBitmap
. . , . Synthesis::OnParamChange
switch
:
case mFilterAttack: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK, GetParam(paramIdx)->Value()); break; case mFilterDecay: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_DECAY, GetParam(paramIdx)->Value()); break; case mFilterSustain: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_SUSTAIN, GetParam(paramIdx)->Value()); break; case mFilterRelease: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE, GetParam(paramIdx)->Value()); break; case mFilterEnvelopeAmount: filterEnvelopeAmount = GetParam(paramIdx)->Value(); break;
!
! (- C1), :
EnvelopeGenerator
. . !
.
:)
:
martin-finke.de/blog/articles/audio-plugins-013-filter
#include Filter.h ( ) :
class Filter { public: enum FilterMode { FILTER_MODE_LOWPASS = 0, FILTER_MODE_HIGHPASS, FILTER_MODE_BANDPASS, kNumFilterModes }; Filter() : cutoff(0.99), resonance(0.0), mode(FILTER_MODE_LOWPASS), buf0(0.0), buf1(0.0) { calculateFeedbackAmount(); }; double process(double inputValue); inline void setCutoff(double newCutoff) { cutoff = newCutoff; calculateFeedbackAmount(); }; inline void setResonance(double newResonance) { resonance = newResonance; calculateFeedbackAmount(); }; inline void setFilterMode(FilterMode newMode) { mode = newMode; } private: double cutoff; double resonance; FilterMode mode; double feedbackAmount; inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - cutoff); } double buf0; double buf1; };
private
cutoff
resonance
. Mode
(Lowpass, Highpass, Bandpass). feedbackAmount
, buf0
buf1
, .
( calculateFeedbackAmount()
). process
. feedbackAmount
cutoff
resonance
, calculateFeedbackAmount
.
Filter.cpp :
// By Paul Kellett // http://www.musicdsp.org/showone.php?id=29 double Filter::process(double inputValue) { buf0 += cutoff * (inputValue - buf0); buf1 += cutoff * (buf0 - buf1); switch (mode) { case FILTER_MODE_LOWPASS: return buf1; case FILTER_MODE_HIGHPASS: return inputValue - buf0; case FILTER_MODE_BANDPASS: return buf0 - buf1; default: return 0.0; } }
, ? , . « » , (. . 6 ). buf0
buf1
: . inputValue
, - buf0
( ). . 6 / 12 /. switch
, buf1
. , , buf0
, 12, 6 /, .
case
FILTER_MODE_HIGHPASS
. buf0
. inputValue
buf0
, . buf1
, .
case
FILTER_MODE_BANDPASS
, buf0 - buf1
. buf0
, buf1
. , .
: , .
buf1
. ( Infinite Impulse Response , IIR ). , . , , .
, , . , , , , . , NI Massive, « ». , , . , ( Massive ). «» , , , AAS Ultra Analog, , , , . , , , , NI Reaktor. , — , .. .
. GUI. bg.png ( “Move to trash” Xcode), :
filtermode.png knob_small.png ( , 50 50 ) bg.png ( )
ID resource.h :
// Unique IDs for each image resource. #define BG_ID 101 #define WHITE_KEY_ID 102 #define BLACK_KEY_ID 103 #define WAVEFORM_ID 104 #define KNOB_ID 105 #define KNOB_SMALL_ID 106 #define FILTERMODE_ID 107 // Image resource locations for this plug. #define BG_FN "resources/img/bg.png" #define WHITE_KEY_FN "resources/img/whitekey.png" #define BLACK_KEY_FN "resources/img/blackkey.png" #define WAVEFORM_FN "resources/img/waveform.png" #define KNOB_FN "resources/img/knob.png" #define KNOB_SMALL_FN "resources/img/knob_small.png" #define FILTERMODE_FN "resources/img/filtermode.png"
Synthesis.rc :
#include "resource.h" BG_ID PNG BG_FN WHITE_KEY_ID PNG WHITE_KEY_FN BLACK_KEY_ID PNG BLACK_KEY_FN WAVEFORM_ID PNG WAVEFORM_FN KNOB_ID PNG KNOB_FN KNOB_SMALL_ID PNG KNOB_SMALL_FN FILTERMODE_ID PNG FILTERMODE_FN
#include "Filter.h"
Synthesis.h private
:
Filter mFilter;
EParams
Synthesis.cpp :
enum EParams { mWaveform = 0, mAttack, mDecay, mSustain, mRelease, mFilterMode, mFilterCutoff, mFilterResonance, mFilterAttack, mFilterDecay, mFilterSustain, mFilterRelease, mFilterEnvelopeAmount, kNumParams };
:
pGraphics->AttachControl(new ISwitchControl(this, 24, 38, mWaveform, &waveformBitmap));
. (Lowpass, Highpass, Bandpass). AttachGraphics(pGraphics)
:
GetParam(mFilterMode)->InitEnum("Filter Mode", Filter::FILTER_MODE_LOWPASS, Filter::kNumFilterModes); IBitmap filtermodeBitmap = pGraphics->LoadIBitmap(FILTERMODE_ID, FILTERMODE_FN, 3); pGraphics->AttachControl(new ISwitchControl(this, 24, 123, mFilterMode, &filtermodeBitmap));
. . knob_small.png :
// Knobs for filter cutoff and resonance IBitmap smallKnobBitmap = pGraphics->LoadIBitmap(KNOB_SMALL_ID, KNOB_SMALL_FN, 64); // Cutoff knob: GetParam(mFilterCutoff)->InitDouble("Cutoff", 0.99, 0.01, 0.99, 0.001); GetParam(mFilterCutoff)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 5, 177, mFilterCutoff, &smallKnobBitmap)); // Resonance knob: GetParam(mFilterResonance)->InitDouble("Resonance", 0.01, 0.01, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 61, 177, mFilterResonance, &smallKnobBitmap));
, 1.0
, calculateFeedbackAmount
, .
ProcessDoubleReplacing
:
leftOutput[i] = rightOutput[i] = mFilter.process(mOscillator.nextSample() * mEnvelopeGenerator.nextSample() * velocity / 127.0);
. switch
Synthesis::OnParamChange
:
case mFilterCutoff: mFilter.setCutoff(GetParam(paramIdx)->Value()); break; case mFilterResonance: mFilter.setResonance(GetParam(paramIdx)->Value()); break; case mFilterMode: mFilter.setFilterMode(static_cast<Filter::FilterMode>(GetParam(paramIdx)->Int())); break;
. - .
— . , , .
:
buf0 += cutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1));
, , ( buf0 – buf1
), feedbackAmount
. calculateFeedbackAmount
feedbackAmount
resonance
, . . , .
, . , , . , , .
-12 / -24 /
! 24 . switch
Filter::process
:
buf2 += cutoff * (buf1 - buf2); buf3 += cutoff * (buf2 – buf3);
: , , . , , , ( ). switch
:
switch (mode) { case FILTER_MODE_LOWPASS: return buf3; case FILTER_MODE_HIGHPASS: return inputValue - buf3; case FILTER_MODE_BANDPASS: return buf0 - buf3; default: return 0.0; }
buf1
buf3
– . -24 /. buf2
buf3
, private
Filter.h :
double buf2; double buf3;
, buf0
c buf1
:
Filter() : // ... buf0(0.0), buf1(0.0), buf2(0.0), buf3(0.0) // ...
, , : . .
?
. , , .
- cutoff
. . cutoffMod
, , cutoff
. #include private
:
double cutoffMod;
:
Filter() : cutoff(0.99), resonance(0.01), cutoffMod(0.0), // ...
. private
:
inline double getCalculatedCutoff() const { return fmax(fmin(cutoff + cutoffMod, 0.99), 0.01); };
calculateFeedbackAmount
:
inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - getCalculatedCutoff()); }
cutoffMod
. feedbackAmount
, :
inline void setCutoffMod(double newCutoffMod) { cutoffMod = newCutoffMod; calculateFeedbackAmount(); }
, . Filter::process
:
if (inputValue == 0.0) return inputValue; double calculatedCutoff = getCalculatedCutoff(); buf0 += calculatedCutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1)); buf1 += calculatedCutoff * (buf0 - buf1); buf2 += calculatedCutoff * (buf1 - buf2); buf3 += calculatedCutoff * (buf2 – buf3);
, . , , . . cutoff
calculatedCutoff
-.
, ( setCutoffMod
), Synthesis
, , . , cutoffMod
. filterEnvelopeAmount
-1
+1
. GUI.
private
Synthesis.h :
EnvelopeGenerator mFilterEnvelopeGenerator; double filterEnvelopeAmount;
MIDI , onNoteOn
onNoteOff
:
inline void onNoteOn(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); }; inline void onNoteOff(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); };
. ProcessDoubleReplacing
:
mFilter.setCutoffMod(mFilterEnvelopeGenerator.nextSample() * filterEnvelopeAmount);
, nextSample
filterEnvelopeAmount
cutoffMod
. filterEnvelopeAmount
:
Synthesis::Synthesis(IPlugInstanceInfo instanceInfo) : IPLUG_CTOR(kNumParams, kNumPrograms, instanceInfo), lastVirtualKeyboardNoteNumber(virtualKeyboardMinimumNoteNumber - 1), filterEnvelopeAmount(0.0) { // ... }
Synthesis::Reset
:
mFilterEnvelopeGenerator.setSampleRate(GetSampleRate());
EParams
, . AttachGraphics
:
// Knobs for filter envelope // Attack knob GetParam(mFilterAttack)->InitDouble("Filter Env Attack", 0.01, 0.01, 10.0, 0.001); GetParam(mFilterAttack)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 139, 178, mFilterAttack, &smallKnobBitmap)); // Decay knob: GetParam(mFilterDecay)->InitDouble("Filter Env Decay", 0.5, 0.01, 15.0, 0.001); GetParam(mFilterDecay)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 195, 178, mFilterDecay, &smallKnobBitmap)); // Sustain knob: GetParam(mFilterSustain)->InitDouble("Filter Env Sustain", 0.1, 0.001, 1.0, 0.001); GetParam(mFilterSustain)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 251, 178, mFilterSustain, &smallKnobBitmap)); // Release knob: GetParam(mFilterRelease)->InitDouble("Filter Env Release", 1.0, 0.001, 15.0, 0.001); GetParam(mFilterRelease)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 307, 178, mFilterRelease, &smallKnobBitmap)); // Filter envelope amount knob: GetParam(mFilterEnvelopeAmount)->InitDouble("Filter Env Amount", 0.0, -1.0, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 363, 178, mFilterEnvelopeAmount, &smallKnobBitmap));
, smallKnobBitmap
. . , . Synthesis::OnParamChange
switch
:
case mFilterAttack: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK, GetParam(paramIdx)->Value()); break; case mFilterDecay: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_DECAY, GetParam(paramIdx)->Value()); break; case mFilterSustain: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_SUSTAIN, GetParam(paramIdx)->Value()); break; case mFilterRelease: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE, GetParam(paramIdx)->Value()); break; case mFilterEnvelopeAmount: filterEnvelopeAmount = GetParam(paramIdx)->Value(); break;
!
! (- C1), :
EnvelopeGenerator
. . !
.
:)
:
martin-finke.de/blog/articles/audio-plugins-013-filter
#include Filter.h ( ) :
class Filter { public: enum FilterMode { FILTER_MODE_LOWPASS = 0, FILTER_MODE_HIGHPASS, FILTER_MODE_BANDPASS, kNumFilterModes }; Filter() : cutoff(0.99), resonance(0.0), mode(FILTER_MODE_LOWPASS), buf0(0.0), buf1(0.0) { calculateFeedbackAmount(); }; double process(double inputValue); inline void setCutoff(double newCutoff) { cutoff = newCutoff; calculateFeedbackAmount(); }; inline void setResonance(double newResonance) { resonance = newResonance; calculateFeedbackAmount(); }; inline void setFilterMode(FilterMode newMode) { mode = newMode; } private: double cutoff; double resonance; FilterMode mode; double feedbackAmount; inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - cutoff); } double buf0; double buf1; };
private
cutoff
resonance
. Mode
(Lowpass, Highpass, Bandpass). feedbackAmount
, buf0
buf1
, .
( calculateFeedbackAmount()
). process
. feedbackAmount
cutoff
resonance
, calculateFeedbackAmount
.
Filter.cpp :
// By Paul Kellett // http://www.musicdsp.org/showone.php?id=29 double Filter::process(double inputValue) { buf0 += cutoff * (inputValue - buf0); buf1 += cutoff * (buf0 - buf1); switch (mode) { case FILTER_MODE_LOWPASS: return buf1; case FILTER_MODE_HIGHPASS: return inputValue - buf0; case FILTER_MODE_BANDPASS: return buf0 - buf1; default: return 0.0; } }
, ? , . « » , (. . 6 ). buf0
buf1
: . inputValue
, - buf0
( ). . 6 / 12 /. switch
, buf1
. , , buf0
, 12, 6 /, .
case
FILTER_MODE_HIGHPASS
. buf0
. inputValue
buf0
, . buf1
, .
case
FILTER_MODE_BANDPASS
, buf0 - buf1
. buf0
, buf1
. , .
: , .
buf1
. ( Infinite Impulse Response , IIR ). , . , , .
, , . , , , , . , NI Massive, « ». , , . , ( Massive ). «» , , , AAS Ultra Analog, , , , . , , , , NI Reaktor. , — , .. .
. GUI. bg.png ( “Move to trash” Xcode), :
filtermode.png knob_small.png ( , 50 50 ) bg.png ( )
ID resource.h :
// Unique IDs for each image resource. #define BG_ID 101 #define WHITE_KEY_ID 102 #define BLACK_KEY_ID 103 #define WAVEFORM_ID 104 #define KNOB_ID 105 #define KNOB_SMALL_ID 106 #define FILTERMODE_ID 107 // Image resource locations for this plug. #define BG_FN "resources/img/bg.png" #define WHITE_KEY_FN "resources/img/whitekey.png" #define BLACK_KEY_FN "resources/img/blackkey.png" #define WAVEFORM_FN "resources/img/waveform.png" #define KNOB_FN "resources/img/knob.png" #define KNOB_SMALL_FN "resources/img/knob_small.png" #define FILTERMODE_FN "resources/img/filtermode.png"
Synthesis.rc :
#include "resource.h" BG_ID PNG BG_FN WHITE_KEY_ID PNG WHITE_KEY_FN BLACK_KEY_ID PNG BLACK_KEY_FN WAVEFORM_ID PNG WAVEFORM_FN KNOB_ID PNG KNOB_FN KNOB_SMALL_ID PNG KNOB_SMALL_FN FILTERMODE_ID PNG FILTERMODE_FN
#include "Filter.h"
Synthesis.h private
:
Filter mFilter;
EParams
Synthesis.cpp :
enum EParams { mWaveform = 0, mAttack, mDecay, mSustain, mRelease, mFilterMode, mFilterCutoff, mFilterResonance, mFilterAttack, mFilterDecay, mFilterSustain, mFilterRelease, mFilterEnvelopeAmount, kNumParams };
:
pGraphics->AttachControl(new ISwitchControl(this, 24, 38, mWaveform, &waveformBitmap));
. (Lowpass, Highpass, Bandpass). AttachGraphics(pGraphics)
:
GetParam(mFilterMode)->InitEnum("Filter Mode", Filter::FILTER_MODE_LOWPASS, Filter::kNumFilterModes); IBitmap filtermodeBitmap = pGraphics->LoadIBitmap(FILTERMODE_ID, FILTERMODE_FN, 3); pGraphics->AttachControl(new ISwitchControl(this, 24, 123, mFilterMode, &filtermodeBitmap));
. . knob_small.png :
// Knobs for filter cutoff and resonance IBitmap smallKnobBitmap = pGraphics->LoadIBitmap(KNOB_SMALL_ID, KNOB_SMALL_FN, 64); // Cutoff knob: GetParam(mFilterCutoff)->InitDouble("Cutoff", 0.99, 0.01, 0.99, 0.001); GetParam(mFilterCutoff)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 5, 177, mFilterCutoff, &smallKnobBitmap)); // Resonance knob: GetParam(mFilterResonance)->InitDouble("Resonance", 0.01, 0.01, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 61, 177, mFilterResonance, &smallKnobBitmap));
, 1.0
, calculateFeedbackAmount
, .
ProcessDoubleReplacing
:
leftOutput[i] = rightOutput[i] = mFilter.process(mOscillator.nextSample() * mEnvelopeGenerator.nextSample() * velocity / 127.0);
. switch
Synthesis::OnParamChange
:
case mFilterCutoff: mFilter.setCutoff(GetParam(paramIdx)->Value()); break; case mFilterResonance: mFilter.setResonance(GetParam(paramIdx)->Value()); break; case mFilterMode: mFilter.setFilterMode(static_cast<Filter::FilterMode>(GetParam(paramIdx)->Int())); break;
. - .
— . , , .
:
buf0 += cutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1));
, , ( buf0 – buf1
), feedbackAmount
. calculateFeedbackAmount
feedbackAmount
resonance
, . . , .
, . , , . , , .
-12 / -24 /
! 24 . switch
Filter::process
:
buf2 += cutoff * (buf1 - buf2); buf3 += cutoff * (buf2 – buf3);
: , , . , , , ( ). switch
:
switch (mode) { case FILTER_MODE_LOWPASS: return buf3; case FILTER_MODE_HIGHPASS: return inputValue - buf3; case FILTER_MODE_BANDPASS: return buf0 - buf3; default: return 0.0; }
buf1
buf3
– . -24 /. buf2
buf3
, private
Filter.h :
double buf2; double buf3;
, buf0
c buf1
:
Filter() : // ... buf0(0.0), buf1(0.0), buf2(0.0), buf3(0.0) // ...
, , : . .
?
. , , .
- cutoff
. . cutoffMod
, , cutoff
. #include private
:
double cutoffMod;
:
Filter() : cutoff(0.99), resonance(0.01), cutoffMod(0.0), // ...
. private
:
inline double getCalculatedCutoff() const { return fmax(fmin(cutoff + cutoffMod, 0.99), 0.01); };
calculateFeedbackAmount
:
inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - getCalculatedCutoff()); }
cutoffMod
. feedbackAmount
, :
inline void setCutoffMod(double newCutoffMod) { cutoffMod = newCutoffMod; calculateFeedbackAmount(); }
, . Filter::process
:
if (inputValue == 0.0) return inputValue; double calculatedCutoff = getCalculatedCutoff(); buf0 += calculatedCutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1)); buf1 += calculatedCutoff * (buf0 - buf1); buf2 += calculatedCutoff * (buf1 - buf2); buf3 += calculatedCutoff * (buf2 – buf3);
, . , , . . cutoff
calculatedCutoff
-.
, ( setCutoffMod
), Synthesis
, , . , cutoffMod
. filterEnvelopeAmount
-1
+1
. GUI.
private
Synthesis.h :
EnvelopeGenerator mFilterEnvelopeGenerator; double filterEnvelopeAmount;
MIDI , onNoteOn
onNoteOff
:
inline void onNoteOn(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); }; inline void onNoteOff(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); };
. ProcessDoubleReplacing
:
mFilter.setCutoffMod(mFilterEnvelopeGenerator.nextSample() * filterEnvelopeAmount);
, nextSample
filterEnvelopeAmount
cutoffMod
. filterEnvelopeAmount
:
Synthesis::Synthesis(IPlugInstanceInfo instanceInfo) : IPLUG_CTOR(kNumParams, kNumPrograms, instanceInfo), lastVirtualKeyboardNoteNumber(virtualKeyboardMinimumNoteNumber - 1), filterEnvelopeAmount(0.0) { // ... }
Synthesis::Reset
:
mFilterEnvelopeGenerator.setSampleRate(GetSampleRate());
EParams
, . AttachGraphics
:
// Knobs for filter envelope // Attack knob GetParam(mFilterAttack)->InitDouble("Filter Env Attack", 0.01, 0.01, 10.0, 0.001); GetParam(mFilterAttack)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 139, 178, mFilterAttack, &smallKnobBitmap)); // Decay knob: GetParam(mFilterDecay)->InitDouble("Filter Env Decay", 0.5, 0.01, 15.0, 0.001); GetParam(mFilterDecay)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 195, 178, mFilterDecay, &smallKnobBitmap)); // Sustain knob: GetParam(mFilterSustain)->InitDouble("Filter Env Sustain", 0.1, 0.001, 1.0, 0.001); GetParam(mFilterSustain)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 251, 178, mFilterSustain, &smallKnobBitmap)); // Release knob: GetParam(mFilterRelease)->InitDouble("Filter Env Release", 1.0, 0.001, 15.0, 0.001); GetParam(mFilterRelease)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 307, 178, mFilterRelease, &smallKnobBitmap)); // Filter envelope amount knob: GetParam(mFilterEnvelopeAmount)->InitDouble("Filter Env Amount", 0.0, -1.0, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 363, 178, mFilterEnvelopeAmount, &smallKnobBitmap));
, smallKnobBitmap
. . , . Synthesis::OnParamChange
switch
:
case mFilterAttack: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK, GetParam(paramIdx)->Value()); break; case mFilterDecay: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_DECAY, GetParam(paramIdx)->Value()); break; case mFilterSustain: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_SUSTAIN, GetParam(paramIdx)->Value()); break; case mFilterRelease: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE, GetParam(paramIdx)->Value()); break; case mFilterEnvelopeAmount: filterEnvelopeAmount = GetParam(paramIdx)->Value(); break;
!
! (- C1), :
EnvelopeGenerator
. . !
.
:)
:
martin-finke.de/blog/articles/audio-plugins-013-filter
#include Filter.h ( ) :
class Filter { public: enum FilterMode { FILTER_MODE_LOWPASS = 0, FILTER_MODE_HIGHPASS, FILTER_MODE_BANDPASS, kNumFilterModes }; Filter() : cutoff(0.99), resonance(0.0), mode(FILTER_MODE_LOWPASS), buf0(0.0), buf1(0.0) { calculateFeedbackAmount(); }; double process(double inputValue); inline void setCutoff(double newCutoff) { cutoff = newCutoff; calculateFeedbackAmount(); }; inline void setResonance(double newResonance) { resonance = newResonance; calculateFeedbackAmount(); }; inline void setFilterMode(FilterMode newMode) { mode = newMode; } private: double cutoff; double resonance; FilterMode mode; double feedbackAmount; inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - cutoff); } double buf0; double buf1; };
private
cutoff
resonance
. Mode
(Lowpass, Highpass, Bandpass). feedbackAmount
, buf0
buf1
, .
( calculateFeedbackAmount()
). process
. feedbackAmount
cutoff
resonance
, calculateFeedbackAmount
.
Filter.cpp :
// By Paul Kellett // http://www.musicdsp.org/showone.php?id=29 double Filter::process(double inputValue) { buf0 += cutoff * (inputValue - buf0); buf1 += cutoff * (buf0 - buf1); switch (mode) { case FILTER_MODE_LOWPASS: return buf1; case FILTER_MODE_HIGHPASS: return inputValue - buf0; case FILTER_MODE_BANDPASS: return buf0 - buf1; default: return 0.0; } }
, ? , . « » , (. . 6 ). buf0
buf1
: . inputValue
, - buf0
( ). . 6 / 12 /. switch
, buf1
. , , buf0
, 12, 6 /, .
case
FILTER_MODE_HIGHPASS
. buf0
. inputValue
buf0
, . buf1
, .
case
FILTER_MODE_BANDPASS
, buf0 - buf1
. buf0
, buf1
. , .
: , .
buf1
. ( Infinite Impulse Response , IIR ). , . , , .
, , . , , , , . , NI Massive, « ». , , . , ( Massive ). «» , , , AAS Ultra Analog, , , , . , , , , NI Reaktor. , — , .. .
. GUI. bg.png ( “Move to trash” Xcode), :
filtermode.png knob_small.png ( , 50 50 ) bg.png ( )
ID resource.h :
// Unique IDs for each image resource. #define BG_ID 101 #define WHITE_KEY_ID 102 #define BLACK_KEY_ID 103 #define WAVEFORM_ID 104 #define KNOB_ID 105 #define KNOB_SMALL_ID 106 #define FILTERMODE_ID 107 // Image resource locations for this plug. #define BG_FN "resources/img/bg.png" #define WHITE_KEY_FN "resources/img/whitekey.png" #define BLACK_KEY_FN "resources/img/blackkey.png" #define WAVEFORM_FN "resources/img/waveform.png" #define KNOB_FN "resources/img/knob.png" #define KNOB_SMALL_FN "resources/img/knob_small.png" #define FILTERMODE_FN "resources/img/filtermode.png"
Synthesis.rc :
#include "resource.h" BG_ID PNG BG_FN WHITE_KEY_ID PNG WHITE_KEY_FN BLACK_KEY_ID PNG BLACK_KEY_FN WAVEFORM_ID PNG WAVEFORM_FN KNOB_ID PNG KNOB_FN KNOB_SMALL_ID PNG KNOB_SMALL_FN FILTERMODE_ID PNG FILTERMODE_FN
#include "Filter.h"
Synthesis.h private
:
Filter mFilter;
EParams
Synthesis.cpp :
enum EParams { mWaveform = 0, mAttack, mDecay, mSustain, mRelease, mFilterMode, mFilterCutoff, mFilterResonance, mFilterAttack, mFilterDecay, mFilterSustain, mFilterRelease, mFilterEnvelopeAmount, kNumParams };
:
pGraphics->AttachControl(new ISwitchControl(this, 24, 38, mWaveform, &waveformBitmap));
. (Lowpass, Highpass, Bandpass). AttachGraphics(pGraphics)
:
GetParam(mFilterMode)->InitEnum("Filter Mode", Filter::FILTER_MODE_LOWPASS, Filter::kNumFilterModes); IBitmap filtermodeBitmap = pGraphics->LoadIBitmap(FILTERMODE_ID, FILTERMODE_FN, 3); pGraphics->AttachControl(new ISwitchControl(this, 24, 123, mFilterMode, &filtermodeBitmap));
. . knob_small.png :
// Knobs for filter cutoff and resonance IBitmap smallKnobBitmap = pGraphics->LoadIBitmap(KNOB_SMALL_ID, KNOB_SMALL_FN, 64); // Cutoff knob: GetParam(mFilterCutoff)->InitDouble("Cutoff", 0.99, 0.01, 0.99, 0.001); GetParam(mFilterCutoff)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 5, 177, mFilterCutoff, &smallKnobBitmap)); // Resonance knob: GetParam(mFilterResonance)->InitDouble("Resonance", 0.01, 0.01, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 61, 177, mFilterResonance, &smallKnobBitmap));
, 1.0
, calculateFeedbackAmount
, .
ProcessDoubleReplacing
:
leftOutput[i] = rightOutput[i] = mFilter.process(mOscillator.nextSample() * mEnvelopeGenerator.nextSample() * velocity / 127.0);
. switch
Synthesis::OnParamChange
:
case mFilterCutoff: mFilter.setCutoff(GetParam(paramIdx)->Value()); break; case mFilterResonance: mFilter.setResonance(GetParam(paramIdx)->Value()); break; case mFilterMode: mFilter.setFilterMode(static_cast<Filter::FilterMode>(GetParam(paramIdx)->Int())); break;
. - .
— . , , .
:
buf0 += cutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1));
, , ( buf0 – buf1
), feedbackAmount
. calculateFeedbackAmount
feedbackAmount
resonance
, . . , .
, . , , . , , .
-12 / -24 /
! 24 . switch
Filter::process
:
buf2 += cutoff * (buf1 - buf2); buf3 += cutoff * (buf2 – buf3);
: , , . , , , ( ). switch
:
switch (mode) { case FILTER_MODE_LOWPASS: return buf3; case FILTER_MODE_HIGHPASS: return inputValue - buf3; case FILTER_MODE_BANDPASS: return buf0 - buf3; default: return 0.0; }
buf1
buf3
– . -24 /. buf2
buf3
, private
Filter.h :
double buf2; double buf3;
, buf0
c buf1
:
Filter() : // ... buf0(0.0), buf1(0.0), buf2(0.0), buf3(0.0) // ...
, , : . .
?
. , , .
- cutoff
. . cutoffMod
, , cutoff
. #include private
:
double cutoffMod;
:
Filter() : cutoff(0.99), resonance(0.01), cutoffMod(0.0), // ...
. private
:
inline double getCalculatedCutoff() const { return fmax(fmin(cutoff + cutoffMod, 0.99), 0.01); };
calculateFeedbackAmount
:
inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - getCalculatedCutoff()); }
cutoffMod
. feedbackAmount
, :
inline void setCutoffMod(double newCutoffMod) { cutoffMod = newCutoffMod; calculateFeedbackAmount(); }
, . Filter::process
:
if (inputValue == 0.0) return inputValue; double calculatedCutoff = getCalculatedCutoff(); buf0 += calculatedCutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1)); buf1 += calculatedCutoff * (buf0 - buf1); buf2 += calculatedCutoff * (buf1 - buf2); buf3 += calculatedCutoff * (buf2 – buf3);
, . , , . . cutoff
calculatedCutoff
-.
, ( setCutoffMod
), Synthesis
, , . , cutoffMod
. filterEnvelopeAmount
-1
+1
. GUI.
private
Synthesis.h :
EnvelopeGenerator mFilterEnvelopeGenerator; double filterEnvelopeAmount;
MIDI , onNoteOn
onNoteOff
:
inline void onNoteOn(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); }; inline void onNoteOff(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); };
. ProcessDoubleReplacing
:
mFilter.setCutoffMod(mFilterEnvelopeGenerator.nextSample() * filterEnvelopeAmount);
, nextSample
filterEnvelopeAmount
cutoffMod
. filterEnvelopeAmount
:
Synthesis::Synthesis(IPlugInstanceInfo instanceInfo) : IPLUG_CTOR(kNumParams, kNumPrograms, instanceInfo), lastVirtualKeyboardNoteNumber(virtualKeyboardMinimumNoteNumber - 1), filterEnvelopeAmount(0.0) { // ... }
Synthesis::Reset
:
mFilterEnvelopeGenerator.setSampleRate(GetSampleRate());
EParams
, . AttachGraphics
:
// Knobs for filter envelope // Attack knob GetParam(mFilterAttack)->InitDouble("Filter Env Attack", 0.01, 0.01, 10.0, 0.001); GetParam(mFilterAttack)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 139, 178, mFilterAttack, &smallKnobBitmap)); // Decay knob: GetParam(mFilterDecay)->InitDouble("Filter Env Decay", 0.5, 0.01, 15.0, 0.001); GetParam(mFilterDecay)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 195, 178, mFilterDecay, &smallKnobBitmap)); // Sustain knob: GetParam(mFilterSustain)->InitDouble("Filter Env Sustain", 0.1, 0.001, 1.0, 0.001); GetParam(mFilterSustain)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 251, 178, mFilterSustain, &smallKnobBitmap)); // Release knob: GetParam(mFilterRelease)->InitDouble("Filter Env Release", 1.0, 0.001, 15.0, 0.001); GetParam(mFilterRelease)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 307, 178, mFilterRelease, &smallKnobBitmap)); // Filter envelope amount knob: GetParam(mFilterEnvelopeAmount)->InitDouble("Filter Env Amount", 0.0, -1.0, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 363, 178, mFilterEnvelopeAmount, &smallKnobBitmap));
, smallKnobBitmap
. . , . Synthesis::OnParamChange
switch
:
case mFilterAttack: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK, GetParam(paramIdx)->Value()); break; case mFilterDecay: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_DECAY, GetParam(paramIdx)->Value()); break; case mFilterSustain: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_SUSTAIN, GetParam(paramIdx)->Value()); break; case mFilterRelease: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE, GetParam(paramIdx)->Value()); break; case mFilterEnvelopeAmount: filterEnvelopeAmount = GetParam(paramIdx)->Value(); break;
!
! (- C1), :
EnvelopeGenerator
. . !
.
:)
:
martin-finke.de/blog/articles/audio-plugins-013-filter
#include Filter.h ( ) :
class Filter { public: enum FilterMode { FILTER_MODE_LOWPASS = 0, FILTER_MODE_HIGHPASS, FILTER_MODE_BANDPASS, kNumFilterModes }; Filter() : cutoff(0.99), resonance(0.0), mode(FILTER_MODE_LOWPASS), buf0(0.0), buf1(0.0) { calculateFeedbackAmount(); }; double process(double inputValue); inline void setCutoff(double newCutoff) { cutoff = newCutoff; calculateFeedbackAmount(); }; inline void setResonance(double newResonance) { resonance = newResonance; calculateFeedbackAmount(); }; inline void setFilterMode(FilterMode newMode) { mode = newMode; } private: double cutoff; double resonance; FilterMode mode; double feedbackAmount; inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - cutoff); } double buf0; double buf1; };
private
cutoff
resonance
. Mode
(Lowpass, Highpass, Bandpass). feedbackAmount
, buf0
buf1
, .
( calculateFeedbackAmount()
). process
. feedbackAmount
cutoff
resonance
, calculateFeedbackAmount
.
Filter.cpp :
// By Paul Kellett // http://www.musicdsp.org/showone.php?id=29 double Filter::process(double inputValue) { buf0 += cutoff * (inputValue - buf0); buf1 += cutoff * (buf0 - buf1); switch (mode) { case FILTER_MODE_LOWPASS: return buf1; case FILTER_MODE_HIGHPASS: return inputValue - buf0; case FILTER_MODE_BANDPASS: return buf0 - buf1; default: return 0.0; } }
, ? , . « » , (. . 6 ). buf0
buf1
: . inputValue
, - buf0
( ). . 6 / 12 /. switch
, buf1
. , , buf0
, 12, 6 /, .
case
FILTER_MODE_HIGHPASS
. buf0
. inputValue
buf0
, . buf1
, .
case
FILTER_MODE_BANDPASS
, buf0 - buf1
. buf0
, buf1
. , .
: , .
buf1
. ( Infinite Impulse Response , IIR ). , . , , .
, , . , , , , . , NI Massive, « ». , , . , ( Massive ). «» , , , AAS Ultra Analog, , , , . , , , , NI Reaktor. , — , .. .
. GUI. bg.png ( “Move to trash” Xcode), :
filtermode.png knob_small.png ( , 50 50 ) bg.png ( )
ID resource.h :
// Unique IDs for each image resource. #define BG_ID 101 #define WHITE_KEY_ID 102 #define BLACK_KEY_ID 103 #define WAVEFORM_ID 104 #define KNOB_ID 105 #define KNOB_SMALL_ID 106 #define FILTERMODE_ID 107 // Image resource locations for this plug. #define BG_FN "resources/img/bg.png" #define WHITE_KEY_FN "resources/img/whitekey.png" #define BLACK_KEY_FN "resources/img/blackkey.png" #define WAVEFORM_FN "resources/img/waveform.png" #define KNOB_FN "resources/img/knob.png" #define KNOB_SMALL_FN "resources/img/knob_small.png" #define FILTERMODE_FN "resources/img/filtermode.png"
Synthesis.rc :
#include "resource.h" BG_ID PNG BG_FN WHITE_KEY_ID PNG WHITE_KEY_FN BLACK_KEY_ID PNG BLACK_KEY_FN WAVEFORM_ID PNG WAVEFORM_FN KNOB_ID PNG KNOB_FN KNOB_SMALL_ID PNG KNOB_SMALL_FN FILTERMODE_ID PNG FILTERMODE_FN
#include "Filter.h"
Synthesis.h private
:
Filter mFilter;
EParams
Synthesis.cpp :
enum EParams { mWaveform = 0, mAttack, mDecay, mSustain, mRelease, mFilterMode, mFilterCutoff, mFilterResonance, mFilterAttack, mFilterDecay, mFilterSustain, mFilterRelease, mFilterEnvelopeAmount, kNumParams };
:
pGraphics->AttachControl(new ISwitchControl(this, 24, 38, mWaveform, &waveformBitmap));
. (Lowpass, Highpass, Bandpass). AttachGraphics(pGraphics)
:
GetParam(mFilterMode)->InitEnum("Filter Mode", Filter::FILTER_MODE_LOWPASS, Filter::kNumFilterModes); IBitmap filtermodeBitmap = pGraphics->LoadIBitmap(FILTERMODE_ID, FILTERMODE_FN, 3); pGraphics->AttachControl(new ISwitchControl(this, 24, 123, mFilterMode, &filtermodeBitmap));
. . knob_small.png :
// Knobs for filter cutoff and resonance IBitmap smallKnobBitmap = pGraphics->LoadIBitmap(KNOB_SMALL_ID, KNOB_SMALL_FN, 64); // Cutoff knob: GetParam(mFilterCutoff)->InitDouble("Cutoff", 0.99, 0.01, 0.99, 0.001); GetParam(mFilterCutoff)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 5, 177, mFilterCutoff, &smallKnobBitmap)); // Resonance knob: GetParam(mFilterResonance)->InitDouble("Resonance", 0.01, 0.01, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 61, 177, mFilterResonance, &smallKnobBitmap));
, 1.0
, calculateFeedbackAmount
, .
ProcessDoubleReplacing
:
leftOutput[i] = rightOutput[i] = mFilter.process(mOscillator.nextSample() * mEnvelopeGenerator.nextSample() * velocity / 127.0);
. switch
Synthesis::OnParamChange
:
case mFilterCutoff: mFilter.setCutoff(GetParam(paramIdx)->Value()); break; case mFilterResonance: mFilter.setResonance(GetParam(paramIdx)->Value()); break; case mFilterMode: mFilter.setFilterMode(static_cast<Filter::FilterMode>(GetParam(paramIdx)->Int())); break;
. - .
— . , , .
:
buf0 += cutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1));
, , ( buf0 – buf1
), feedbackAmount
. calculateFeedbackAmount
feedbackAmount
resonance
, . . , .
, . , , . , , .
-12 / -24 /
! 24 . switch
Filter::process
:
buf2 += cutoff * (buf1 - buf2); buf3 += cutoff * (buf2 – buf3);
: , , . , , , ( ). switch
:
switch (mode) { case FILTER_MODE_LOWPASS: return buf3; case FILTER_MODE_HIGHPASS: return inputValue - buf3; case FILTER_MODE_BANDPASS: return buf0 - buf3; default: return 0.0; }
buf1
buf3
– . -24 /. buf2
buf3
, private
Filter.h :
double buf2; double buf3;
, buf0
c buf1
:
Filter() : // ... buf0(0.0), buf1(0.0), buf2(0.0), buf3(0.0) // ...
, , : . .
?
. , , .
- cutoff
. . cutoffMod
, , cutoff
. #include private
:
double cutoffMod;
:
Filter() : cutoff(0.99), resonance(0.01), cutoffMod(0.0), // ...
. private
:
inline double getCalculatedCutoff() const { return fmax(fmin(cutoff + cutoffMod, 0.99), 0.01); };
calculateFeedbackAmount
:
inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - getCalculatedCutoff()); }
cutoffMod
. feedbackAmount
, :
inline void setCutoffMod(double newCutoffMod) { cutoffMod = newCutoffMod; calculateFeedbackAmount(); }
, . Filter::process
:
if (inputValue == 0.0) return inputValue; double calculatedCutoff = getCalculatedCutoff(); buf0 += calculatedCutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1)); buf1 += calculatedCutoff * (buf0 - buf1); buf2 += calculatedCutoff * (buf1 - buf2); buf3 += calculatedCutoff * (buf2 – buf3);
, . , , . . cutoff
calculatedCutoff
-.
, ( setCutoffMod
), Synthesis
, , . , cutoffMod
. filterEnvelopeAmount
-1
+1
. GUI.
private
Synthesis.h :
EnvelopeGenerator mFilterEnvelopeGenerator; double filterEnvelopeAmount;
MIDI , onNoteOn
onNoteOff
:
inline void onNoteOn(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); }; inline void onNoteOff(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); };
. ProcessDoubleReplacing
:
mFilter.setCutoffMod(mFilterEnvelopeGenerator.nextSample() * filterEnvelopeAmount);
, nextSample
filterEnvelopeAmount
cutoffMod
. filterEnvelopeAmount
:
Synthesis::Synthesis(IPlugInstanceInfo instanceInfo) : IPLUG_CTOR(kNumParams, kNumPrograms, instanceInfo), lastVirtualKeyboardNoteNumber(virtualKeyboardMinimumNoteNumber - 1), filterEnvelopeAmount(0.0) { // ... }
Synthesis::Reset
:
mFilterEnvelopeGenerator.setSampleRate(GetSampleRate());
EParams
, . AttachGraphics
:
// Knobs for filter envelope // Attack knob GetParam(mFilterAttack)->InitDouble("Filter Env Attack", 0.01, 0.01, 10.0, 0.001); GetParam(mFilterAttack)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 139, 178, mFilterAttack, &smallKnobBitmap)); // Decay knob: GetParam(mFilterDecay)->InitDouble("Filter Env Decay", 0.5, 0.01, 15.0, 0.001); GetParam(mFilterDecay)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 195, 178, mFilterDecay, &smallKnobBitmap)); // Sustain knob: GetParam(mFilterSustain)->InitDouble("Filter Env Sustain", 0.1, 0.001, 1.0, 0.001); GetParam(mFilterSustain)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 251, 178, mFilterSustain, &smallKnobBitmap)); // Release knob: GetParam(mFilterRelease)->InitDouble("Filter Env Release", 1.0, 0.001, 15.0, 0.001); GetParam(mFilterRelease)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 307, 178, mFilterRelease, &smallKnobBitmap)); // Filter envelope amount knob: GetParam(mFilterEnvelopeAmount)->InitDouble("Filter Env Amount", 0.0, -1.0, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 363, 178, mFilterEnvelopeAmount, &smallKnobBitmap));
, smallKnobBitmap
. . , . Synthesis::OnParamChange
switch
:
case mFilterAttack: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK, GetParam(paramIdx)->Value()); break; case mFilterDecay: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_DECAY, GetParam(paramIdx)->Value()); break; case mFilterSustain: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_SUSTAIN, GetParam(paramIdx)->Value()); break; case mFilterRelease: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE, GetParam(paramIdx)->Value()); break; case mFilterEnvelopeAmount: filterEnvelopeAmount = GetParam(paramIdx)->Value(); break;
!
! (- C1), :
EnvelopeGenerator
. . !
.
:)
:
martin-finke.de/blog/articles/audio-plugins-013-filter
#include Filter.h ( ) :
class Filter { public: enum FilterMode { FILTER_MODE_LOWPASS = 0, FILTER_MODE_HIGHPASS, FILTER_MODE_BANDPASS, kNumFilterModes }; Filter() : cutoff(0.99), resonance(0.0), mode(FILTER_MODE_LOWPASS), buf0(0.0), buf1(0.0) { calculateFeedbackAmount(); }; double process(double inputValue); inline void setCutoff(double newCutoff) { cutoff = newCutoff; calculateFeedbackAmount(); }; inline void setResonance(double newResonance) { resonance = newResonance; calculateFeedbackAmount(); }; inline void setFilterMode(FilterMode newMode) { mode = newMode; } private: double cutoff; double resonance; FilterMode mode; double feedbackAmount; inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - cutoff); } double buf0; double buf1; };
private
cutoff
resonance
. Mode
(Lowpass, Highpass, Bandpass). feedbackAmount
, buf0
buf1
, .
( calculateFeedbackAmount()
). process
. feedbackAmount
cutoff
resonance
, calculateFeedbackAmount
.
Filter.cpp :
// By Paul Kellett // http://www.musicdsp.org/showone.php?id=29 double Filter::process(double inputValue) { buf0 += cutoff * (inputValue - buf0); buf1 += cutoff * (buf0 - buf1); switch (mode) { case FILTER_MODE_LOWPASS: return buf1; case FILTER_MODE_HIGHPASS: return inputValue - buf0; case FILTER_MODE_BANDPASS: return buf0 - buf1; default: return 0.0; } }
, ? , . « » , (. . 6 ). buf0
buf1
: . inputValue
, - buf0
( ). . 6 / 12 /. switch
, buf1
. , , buf0
, 12, 6 /, .
case
FILTER_MODE_HIGHPASS
. buf0
. inputValue
buf0
, . buf1
, .
case
FILTER_MODE_BANDPASS
, buf0 - buf1
. buf0
, buf1
. , .
: , .
buf1
. ( Infinite Impulse Response , IIR ). , . , , .
, , . , , , , . , NI Massive, « ». , , . , ( Massive ). «» , , , AAS Ultra Analog, , , , . , , , , NI Reaktor. , — , .. .
. GUI. bg.png ( “Move to trash” Xcode), :
filtermode.png knob_small.png ( , 50 50 ) bg.png ( )
ID resource.h :
// Unique IDs for each image resource. #define BG_ID 101 #define WHITE_KEY_ID 102 #define BLACK_KEY_ID 103 #define WAVEFORM_ID 104 #define KNOB_ID 105 #define KNOB_SMALL_ID 106 #define FILTERMODE_ID 107 // Image resource locations for this plug. #define BG_FN "resources/img/bg.png" #define WHITE_KEY_FN "resources/img/whitekey.png" #define BLACK_KEY_FN "resources/img/blackkey.png" #define WAVEFORM_FN "resources/img/waveform.png" #define KNOB_FN "resources/img/knob.png" #define KNOB_SMALL_FN "resources/img/knob_small.png" #define FILTERMODE_FN "resources/img/filtermode.png"
Synthesis.rc :
#include "resource.h" BG_ID PNG BG_FN WHITE_KEY_ID PNG WHITE_KEY_FN BLACK_KEY_ID PNG BLACK_KEY_FN WAVEFORM_ID PNG WAVEFORM_FN KNOB_ID PNG KNOB_FN KNOB_SMALL_ID PNG KNOB_SMALL_FN FILTERMODE_ID PNG FILTERMODE_FN
#include "Filter.h"
Synthesis.h private
:
Filter mFilter;
EParams
Synthesis.cpp :
enum EParams { mWaveform = 0, mAttack, mDecay, mSustain, mRelease, mFilterMode, mFilterCutoff, mFilterResonance, mFilterAttack, mFilterDecay, mFilterSustain, mFilterRelease, mFilterEnvelopeAmount, kNumParams };
:
pGraphics->AttachControl(new ISwitchControl(this, 24, 38, mWaveform, &waveformBitmap));
. (Lowpass, Highpass, Bandpass). AttachGraphics(pGraphics)
:
GetParam(mFilterMode)->InitEnum("Filter Mode", Filter::FILTER_MODE_LOWPASS, Filter::kNumFilterModes); IBitmap filtermodeBitmap = pGraphics->LoadIBitmap(FILTERMODE_ID, FILTERMODE_FN, 3); pGraphics->AttachControl(new ISwitchControl(this, 24, 123, mFilterMode, &filtermodeBitmap));
. . knob_small.png :
// Knobs for filter cutoff and resonance IBitmap smallKnobBitmap = pGraphics->LoadIBitmap(KNOB_SMALL_ID, KNOB_SMALL_FN, 64); // Cutoff knob: GetParam(mFilterCutoff)->InitDouble("Cutoff", 0.99, 0.01, 0.99, 0.001); GetParam(mFilterCutoff)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 5, 177, mFilterCutoff, &smallKnobBitmap)); // Resonance knob: GetParam(mFilterResonance)->InitDouble("Resonance", 0.01, 0.01, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 61, 177, mFilterResonance, &smallKnobBitmap));
, 1.0
, calculateFeedbackAmount
, .
ProcessDoubleReplacing
:
leftOutput[i] = rightOutput[i] = mFilter.process(mOscillator.nextSample() * mEnvelopeGenerator.nextSample() * velocity / 127.0);
. switch
Synthesis::OnParamChange
:
case mFilterCutoff: mFilter.setCutoff(GetParam(paramIdx)->Value()); break; case mFilterResonance: mFilter.setResonance(GetParam(paramIdx)->Value()); break; case mFilterMode: mFilter.setFilterMode(static_cast<Filter::FilterMode>(GetParam(paramIdx)->Int())); break;
. - .
— . , , .
:
buf0 += cutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1));
, , ( buf0 – buf1
), feedbackAmount
. calculateFeedbackAmount
feedbackAmount
resonance
, . . , .
, . , , . , , .
-12 / -24 /
! 24 . switch
Filter::process
:
buf2 += cutoff * (buf1 - buf2); buf3 += cutoff * (buf2 – buf3);
: , , . , , , ( ). switch
:
switch (mode) { case FILTER_MODE_LOWPASS: return buf3; case FILTER_MODE_HIGHPASS: return inputValue - buf3; case FILTER_MODE_BANDPASS: return buf0 - buf3; default: return 0.0; }
buf1
buf3
– . -24 /. buf2
buf3
, private
Filter.h :
double buf2; double buf3;
, buf0
c buf1
:
Filter() : // ... buf0(0.0), buf1(0.0), buf2(0.0), buf3(0.0) // ...
, , : . .
?
. , , .
- cutoff
. . cutoffMod
, , cutoff
. #include private
:
double cutoffMod;
:
Filter() : cutoff(0.99), resonance(0.01), cutoffMod(0.0), // ...
. private
:
inline double getCalculatedCutoff() const { return fmax(fmin(cutoff + cutoffMod, 0.99), 0.01); };
calculateFeedbackAmount
:
inline void calculateFeedbackAmount() { feedbackAmount = resonance + resonance/(1.0 - getCalculatedCutoff()); }
cutoffMod
. feedbackAmount
, :
inline void setCutoffMod(double newCutoffMod) { cutoffMod = newCutoffMod; calculateFeedbackAmount(); }
, . Filter::process
:
if (inputValue == 0.0) return inputValue; double calculatedCutoff = getCalculatedCutoff(); buf0 += calculatedCutoff * (inputValue - buf0 + feedbackAmount * (buf0 - buf1)); buf1 += calculatedCutoff * (buf0 - buf1); buf2 += calculatedCutoff * (buf1 - buf2); buf3 += calculatedCutoff * (buf2 – buf3);
, . , , . . cutoff
calculatedCutoff
-.
, ( setCutoffMod
), Synthesis
, , . , cutoffMod
. filterEnvelopeAmount
-1
+1
. GUI.
private
Synthesis.h :
EnvelopeGenerator mFilterEnvelopeGenerator; double filterEnvelopeAmount;
MIDI , onNoteOn
onNoteOff
:
inline void onNoteOn(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); }; inline void onNoteOff(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); mFilterEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); };
. ProcessDoubleReplacing
:
mFilter.setCutoffMod(mFilterEnvelopeGenerator.nextSample() * filterEnvelopeAmount);
, nextSample
filterEnvelopeAmount
cutoffMod
. filterEnvelopeAmount
:
Synthesis::Synthesis(IPlugInstanceInfo instanceInfo) : IPLUG_CTOR(kNumParams, kNumPrograms, instanceInfo), lastVirtualKeyboardNoteNumber(virtualKeyboardMinimumNoteNumber - 1), filterEnvelopeAmount(0.0) { // ... }
Synthesis::Reset
:
mFilterEnvelopeGenerator.setSampleRate(GetSampleRate());
EParams
, . AttachGraphics
:
// Knobs for filter envelope // Attack knob GetParam(mFilterAttack)->InitDouble("Filter Env Attack", 0.01, 0.01, 10.0, 0.001); GetParam(mFilterAttack)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 139, 178, mFilterAttack, &smallKnobBitmap)); // Decay knob: GetParam(mFilterDecay)->InitDouble("Filter Env Decay", 0.5, 0.01, 15.0, 0.001); GetParam(mFilterDecay)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 195, 178, mFilterDecay, &smallKnobBitmap)); // Sustain knob: GetParam(mFilterSustain)->InitDouble("Filter Env Sustain", 0.1, 0.001, 1.0, 0.001); GetParam(mFilterSustain)->SetShape(2); pGraphics->AttachControl(new IKnobMultiControl(this, 251, 178, mFilterSustain, &smallKnobBitmap)); // Release knob: GetParam(mFilterRelease)->InitDouble("Filter Env Release", 1.0, 0.001, 15.0, 0.001); GetParam(mFilterRelease)->SetShape(3); pGraphics->AttachControl(new IKnobMultiControl(this, 307, 178, mFilterRelease, &smallKnobBitmap)); // Filter envelope amount knob: GetParam(mFilterEnvelopeAmount)->InitDouble("Filter Env Amount", 0.0, -1.0, 1.0, 0.001); pGraphics->AttachControl(new IKnobMultiControl(this, 363, 178, mFilterEnvelopeAmount, &smallKnobBitmap));
, smallKnobBitmap
. . , . Synthesis::OnParamChange
switch
:
case mFilterAttack: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK, GetParam(paramIdx)->Value()); break; case mFilterDecay: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_DECAY, GetParam(paramIdx)->Value()); break; case mFilterSustain: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_SUSTAIN, GetParam(paramIdx)->Value()); break; case mFilterRelease: mFilterEnvelopeGenerator.setStageValue(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE, GetParam(paramIdx)->Value()); break; case mFilterEnvelopeAmount: filterEnvelopeAmount = GetParam(paramIdx)->Value(); break;
!
! (- C1), :
EnvelopeGenerator
. . !
.
:)
:
martin-finke.de/blog/articles/audio-plugins-013-filter