📜 ⬆️ ⬇️

Creating audio plugin, part 11

All posts series:
Part 1. Introduction and setup
Part 2. Learning Code
Part 3. VST and AU
Part 4. Digital Distortion
Part 5. Presets and GUI
Part 6. Signal synthesis
Part 7. Receive MIDI Messages
Part 8. Virtual Keyboard
Part 9. Envelopes
Part 10. Refinement GUI
Part 11. Filter
Part 12. Low-frequency oscillator
Part 13. Redesign
Part 14. Polyphony 1
Part 15. Polyphony 2
Part 16. Antialiasing



Today we will make a resonant filter. Filter development is a complex area, with many DSP engineers all over the world puzzling over it. We will not dive into its wilds, but create a simple low-pass filter ( Low-Pass ), band-pass ( Band Pass ) and a high pass filter ( High Pass ) based on the Paul Kellet algorithm .

We begin, as you might guess, with creating a Filter class. Remove
 #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/227791/


All Articles