オーディオプラグインの作成、パート11

シリーズのすべての投稿:
パート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.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


Source: https://habr.com/ru/post/J227791/


All Articles