シリーズのすべての投稿:
パート1.紹介とセットアップパート2.コードの学習パート3. VSTおよびAUパート4.デジタル歪みパート5.プリセットとGUIパート6.信号合成パート7. MIDIメッセージの受信パート8.仮想キーボードパート9.封筒パート10. GUIの改善パート11.フィルターパート12.低周波発振器パート13.再設計パート14.ポリフォニー1パート15.ポリフォニー2パート16.アンチエイリアス
サウンドは、変化が生じたときに興味深いものになります。 音量を変えるエンベロープジェネレーターを作成しましょう。
封筒
基本原則
頭字語
ADSR (
Attack、Decay、Sustain、Release )に慣れていない場合は、続行する前に
このウィキペディアの記事を読んでください。
本質的に、ジェネレーターは
Off、Attack、Decay、Sustain、 Releaseの 状態を持つ
状態マシンになります。 これは、ジェネレータがこれら5つの可能な状態のいずれか1つにしかならないことを示す、非常に簡潔な方法です。 エンベロープの用語では、これらの状態はステージと呼ばれます。 あるステージから別のステージへの遷移は、メンバー関数
enterStage
呼び出すことにより実行され
enterStage
。
ステージに関する重要なポイント:
- ATTACK、DECAY、およびRELEASEの各ステージの出力は、ジェネレーターによって個別に実行されます。パラメーターで指定された時間が経過すると、
enterStage
が呼び出されます - OFF状態およびSUSTAIN状態では、外部からenterStageへの呼び出しが行われるまで、無期限に保持できます。
- 攻撃、ディケイ、およびリリースは、すでに理解しているように、 時間変数ですが、SUSTAINは信号レベル変数です
- ジェネレーターは、ATTACK、DECAY、SUSTAINからRELEASEステージに進むことができます
- RELEASEステージに進むと、信号振幅は現在のレベルからゼロまで減衰し始めます
各サンプル信号に対して、ジェネレータはゼロと1の間の
double
値を生成します。 エンベロープジェネレーターから受信した現在の値は、オシレーターの出力信号と
乗算されます。 したがって、信号レベルはエンベロープによって決まります。 音でさまざまなことができるようになります。音がゆっくりと浮かんだり、急激に落ちたり、丸太のように平らになったりします。
一般に、ADSRはより高度なサウンドモデリングへの最初のアプローチです。 多くの最新のソフトウェアおよび鉄製シンセサイザーには、多数のステージを備えたエンベロープがあります。 実際、それらはすべて、これから作成するモデルの単なる拡張です。 このようなステートマシンの状態の数は5を少し超えています。
クラスEnvelopeGenerator
新しい
EnvelopeGenerator
クラスを作成し、すべてのターゲット(Mac)およびプロジェクト(Windows)に追加します。
EnvelopeGenerator.hの #define
と
#endif
間に、クラス宣言を挿入します。
#include <cmath> class EnvelopeGenerator { public: enum EnvelopeStage { ENVELOPE_STAGE_OFF = 0, ENVELOPE_STAGE_ATTACK, ENVELOPE_STAGE_DECAY, ENVELOPE_STAGE_SUSTAIN, ENVELOPE_STAGE_RELEASE, kNumEnvelopeStages }; void enterStage(EnvelopeStage newStage); double nextSample(); void setSampleRate(double newSampleRate); inline EnvelopeStage getCurrentStage() const { return currentStage; }; const double minimumLevel; EnvelopeGenerator() : minimumLevel(0.0001), currentStage(ENVELOPE_STAGE_OFF), currentLevel(minimumLevel), multiplier(1.0), sampleRate(44100.0), currentSampleIndex(0), nextStageSampleIndex(0) { stageValue[ENVELOPE_STAGE_OFF] = 0.0; stageValue[ENVELOPE_STAGE_ATTACK] = 0.01; stageValue[ENVELOPE_STAGE_DECAY] = 0.5; stageValue[ENVELOPE_STAGE_SUSTAIN] = 0.1; stageValue[ENVELOPE_STAGE_RELEASE] = 1.0; }; private: EnvelopeStage currentStage; double currentLevel; double multiplier; double sampleRate; double stageValue[kNumEnvelopeStages]; void calculateMultiplier(double startLevel, double endLevel, unsigned long long lengthInSamples); unsigned long long currentSampleIndex; unsigned long long nextStageSampleIndex; };
まず、すべてのステージで
enum
を作成します。
kNumEnvelopeStages
は、合計ステージ数を示します。 この
enum
はエンベロープクラスのネームスペースの境界内にあり、グローバルネームスペースでは外部からアクセスできないことに注意してください。
メンバー関数を実装する際に、より詳細に検討します。 信号レベルの計算はゼロでは機能しない
minimumLevel
の最小レベル
minimumLevel
必要です。 この変数を
0.001
という非常に小さな値で初期化します。
初期化リストでは、エンベロープはデフォルトで
OFF
ステージにあります。
stageValue
配列も定義済みの値で初期化されます:攻撃の100分の1秒、不況の0.5秒、静かなレベル、完全な減衰の1秒。
private currentStage
セクションでは、
private currentStage
はジェネレーターが現在どのステージにいるかを示します。
urrentLevel
は、特定の時間サンプルにおけるエンベロープのボリュームの値です。
multiplyer
器は、後で見られるように、指数関数的な減衰
multiplyer
提供します。
アタック、ディケイ、リリースの各段階では、適切なタイミングで次の段階に進むために、ジェネレーターは時間内にその位置を追跡する必要があります。 これを行うには、
currentSampleIndex
変数を使用します。
EnvelopeGenerator.cppで次の関数を追加します。
double EnvelopeGenerator::nextSample() { if (currentStage != ENVELOPE_STAGE_OFF && currentStage != ENVELOPE_STAGE_SUSTAIN) { if (currentSampleIndex == nextStageSampleIndex) { EnvelopeStage newStage = static_cast<EnvelopeStage>( (currentStage + 1) % kNumEnvelopeStages ); enterStage(newStage); } currentLevel *= multiplier; currentSampleIndex++; } return currentLevel; }
ステージATTACK、DECAYまたはRELEASEおよび
currentSampleIndex
nextStageSampleIndex
が値
nextStageSampleIndex
に達すると、
enum EnvelopeStage
次の要素に
nextStageSampleIndex
ます。
ENVELOPE_STAGE_RELEASE
後の剰余(
%
)による除算により
ENVELOPE_STAGE_RELEASE
ジェネレーターはすぐに
ENVELOPE_STAGE_OFF
に移動します。これは必要なものです。 この遷移は、
enterStage
を呼び出すことで実現され
enterStage
。
次に、
currentLevel
レベルの新しい値を計算し、
currentSampleIndex
を更新して、ジェネレーターの位置を時間的に監視します。 これらの計算は、OFFおよびSUSTAINステージでは実行されません。レベルが変更されないためです。
currentSampleIndex
と同じこと:これらの2つの段階は時間制限されていません。
時間の推移
ATTACK、DECAY、およびRELEASEでは、ジェネレーターは所定の時間内に2つの値を切り替えます。 人間の耳は、対数スケールで
ラウドネスを
知覚します。 したがって、音量の変化が耳で直線的に知覚されるためには、指数関数的に発生する必要があります。
2つのポイント間に指数曲線を作成する方法はいくつかあります。 最初に頭に浮かぶのは、各サンプルがライブラリから比較的重い
exp
関数を呼び出すことです
. , : , , .
( ):
void EnvelopeGenerator::calculateMultiplier(double startLevel, double endLevel, unsigned long long lengthInSamples) { multiplier = 1.0 + (log(endLevel) - log(startLevel)) / (lengthInSamples); }
, . , startLevel
endLevel
lengthInSamples
multiplier
, . currentLevel
. log()
.
enterStage
:
void EnvelopeGenerator::enterStage(EnvelopeStage newStage) { currentStage = newStage; currentSampleIndex = 0; if (currentStage == ENVELOPE_STAGE_OFF || currentStage == ENVELOPE_STAGE_SUSTAIN) { nextStageSampleIndex = 0; } else { nextStageSampleIndex = stageValue[currentStage] * sampleRate; } switch (newStage) { case ENVELOPE_STAGE_OFF: currentLevel = 0.0; multiplier = 1.0; break; case ENVELOPE_STAGE_ATTACK: currentLevel = minimumLevel; calculateMultiplier(currentLevel, 1.0, nextStageSampleIndex); break; case ENVELOPE_STAGE_DECAY: currentLevel = 1.0; calculateMultiplier(currentLevel, fmax(stageValue[ENVELOPE_STAGE_SUSTAIN], minimumLevel), nextStageSampleIndex); break; case ENVELOPE_STAGE_SUSTAIN: currentLevel = stageValue[ENVELOPE_STAGE_SUSTAIN]; multiplier = 1.0; break; case ENVELOPE_STAGE_RELEASE: // We could go from ATTACK/DECAY to RELEASE, // so we're not changing currentLevel here. calculateMultiplier(currentLevel, minimumLevel, nextStageSampleIndex); break; default: break; } }
currentStage
currentSampleIndex
. . , , . stageValue[currentStage]
double
( ), , , . Switch
. OFF ( , ). ATTACK minimumLevel
, multiplier
currentLevel
1.0
. DECAY stageValue[ENVELOPE_STAGE_SUSTAIN]
, fmax
, . RELEASE currentLevel
, , minimumLevel
. , RELEASE currentLevel
, . . SUSTAIN, ATTACK DECAY. , SUSTAIN , stageValue[ENVELOPE_STAGE_SUSTAIN]
, . currentLevel
.
:
void EnvelopeGenerator::setSampleRate(double newSampleRate) { sampleRate = newSampleRate; }
private
Synthesis
( ) :
EnvelopeGenerator mEnvelopeGenerator;
#include "EnvelopeGenerator.h"
.
, ProcessDoubleReplacing
Synthesis.cpp leftOutput[i]
:
// leftOutput[i] = rightOutput[i] = mOscillator.nextSample() * velocity / 127.0; if (mEnvelopeGenerator.getCurrentStage() == EnvelopeGenerator::ENVELOPE_STAGE_OFF) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); } if (mEnvelopeGenerator.getCurrentStage() == EnvelopeGenerator::ENVELOPE_STAGE_SUSTAIN) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); } leftOutput[i] = rightOutput[i] = mOscillator.nextSample() * mEnvelopeGenerator.nextSample() * velocity / 127.0;
. : OFF ATTACK, SUSTAIN RELEASE. .
mEnvelopeGenerator
. Synthesis::Reset()
:
mEnvelopeGenerator.setSampleRate(GetSampleRate());
. - — ! .
Note On/Off
, (, - , ), : — ATTACK, — RELEASE. mMIDIReceiver
note on/off, , - .
#include EnvelopeGenerator.h
MIDIReceiver.h . , Synthesis.h mEnvelopeGenerator
mMIDIReceiver
, . MIDIReceiver
enterStage
note on/off.
, . . MIDIReceiver
EnvelopeGenerator
. , MIDIReceiver
, EnvelopeGenerator
.
. Qt. , , , . , . , setText()
, . , , , . , , . ( Oscillator
, EnvelopeGenerator
, MIDIReceiver
). , . . Synthesis
.
Qt . Signals . . Signal.h GallantSignal.h , . Delegate.h GallantSignal.h ( , , “Copy items into destination group's folder”) Xcode. VisualStudio , Solution Explorer.
, MIDIReceiver
, . MIDIReceiver.h :
#include "GallantSignal.h" using Gallant::Signal2;
Signal2
, . — Signal0
Signal8
, .
public
:
Signal2< int, int > noteOn; Signal2< int, int > noteOff;
, int
. MIDIReceiver.cpp advance
:
// A key pressed later overrides any previously pressed key: if (noteNumber != mLastNoteNumber) { mLastNoteNumber = noteNumber; mLastFrequency = noteNumberToFrequency(mLastNoteNumber); mLastVelocity = velocity; // Emit a "note on" signal: noteOn(noteNumber, velocity); }
// If the last note was released, nothing should play: if (noteNumber == mLastNoteNumber) { mLastNoteNumber = -1; noteOff(noteNumber, mLastVelocity); }
— , — . mLastFrequency
-1
, : RELEASE. mLastVelocity
: , .
, , , . . .
mEnvelopeGenerator
. - onNoteOn
onNoteOff
EnvelopeGenerator
. , EnvelopeGenerator
. , . enterStage
- . - private
Synthesis
:
inline void onNoteOn(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); }; inline void onNoteOff(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); };
.
, Synthesis.cpp :
mMIDIReceiver.noteOn.Connect(this, &Synthesis::onNoteOn); mMIDIReceiver.noteOff.Connect(this, &Synthesis::onNoteOff);
— , — -.
ProcessDoubleReplacing
. if
, , .
!
. . DECAY, RELEASE, . stageValues
.
, , . GUI - :

!
.
:
martin-finke.de/blog/articles/audio-plugins-011-envelopes
. , : , , .
( ):
void EnvelopeGenerator::calculateMultiplier(double startLevel, double endLevel, unsigned long long lengthInSamples) { multiplier = 1.0 + (log(endLevel) - log(startLevel)) / (lengthInSamples); }
, . , startLevel
endLevel
lengthInSamples
multiplier
, . currentLevel
. log()
.
enterStage
:
void EnvelopeGenerator::enterStage(EnvelopeStage newStage) { currentStage = newStage; currentSampleIndex = 0; if (currentStage == ENVELOPE_STAGE_OFF || currentStage == ENVELOPE_STAGE_SUSTAIN) { nextStageSampleIndex = 0; } else { nextStageSampleIndex = stageValue[currentStage] * sampleRate; } switch (newStage) { case ENVELOPE_STAGE_OFF: currentLevel = 0.0; multiplier = 1.0; break; case ENVELOPE_STAGE_ATTACK: currentLevel = minimumLevel; calculateMultiplier(currentLevel, 1.0, nextStageSampleIndex); break; case ENVELOPE_STAGE_DECAY: currentLevel = 1.0; calculateMultiplier(currentLevel, fmax(stageValue[ENVELOPE_STAGE_SUSTAIN], minimumLevel), nextStageSampleIndex); break; case ENVELOPE_STAGE_SUSTAIN: currentLevel = stageValue[ENVELOPE_STAGE_SUSTAIN]; multiplier = 1.0; break; case ENVELOPE_STAGE_RELEASE: // We could go from ATTACK/DECAY to RELEASE, // so we're not changing currentLevel here. calculateMultiplier(currentLevel, minimumLevel, nextStageSampleIndex); break; default: break; } }
currentStage
currentSampleIndex
. . , , . stageValue[currentStage]
double
( ), , , . Switch
. OFF ( , ). ATTACK minimumLevel
, multiplier
currentLevel
1.0
. DECAY stageValue[ENVELOPE_STAGE_SUSTAIN]
, fmax
, . RELEASE currentLevel
, , minimumLevel
. , RELEASE currentLevel
, . . SUSTAIN, ATTACK DECAY. , SUSTAIN , stageValue[ENVELOPE_STAGE_SUSTAIN]
, . currentLevel
.
:
void EnvelopeGenerator::setSampleRate(double newSampleRate) { sampleRate = newSampleRate; }
private
Synthesis
( ) :
EnvelopeGenerator mEnvelopeGenerator;
#include "EnvelopeGenerator.h"
.
, ProcessDoubleReplacing
Synthesis.cpp leftOutput[i]
:
// leftOutput[i] = rightOutput[i] = mOscillator.nextSample() * velocity / 127.0; if (mEnvelopeGenerator.getCurrentStage() == EnvelopeGenerator::ENVELOPE_STAGE_OFF) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); } if (mEnvelopeGenerator.getCurrentStage() == EnvelopeGenerator::ENVELOPE_STAGE_SUSTAIN) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); } leftOutput[i] = rightOutput[i] = mOscillator.nextSample() * mEnvelopeGenerator.nextSample() * velocity / 127.0;
. : OFF ATTACK, SUSTAIN RELEASE. .
mEnvelopeGenerator
. Synthesis::Reset()
:
mEnvelopeGenerator.setSampleRate(GetSampleRate());
. - — ! .
Note On/Off
, (, - , ), : — ATTACK, — RELEASE. mMIDIReceiver
note on/off, , - .
#include EnvelopeGenerator.h
MIDIReceiver.h . , Synthesis.h mEnvelopeGenerator
mMIDIReceiver
, . MIDIReceiver
enterStage
note on/off.
, . . MIDIReceiver
EnvelopeGenerator
. , MIDIReceiver
, EnvelopeGenerator
.
. Qt. , , , . , . , setText()
, . , , , . , , . ( Oscillator
, EnvelopeGenerator
, MIDIReceiver
). , . . Synthesis
.
Qt . Signals . . Signal.h GallantSignal.h , . Delegate.h GallantSignal.h ( , , “Copy items into destination group's folder”) Xcode. VisualStudio , Solution Explorer.
, MIDIReceiver
, . MIDIReceiver.h :
#include "GallantSignal.h" using Gallant::Signal2;
Signal2
, . — Signal0
Signal8
, .
public
:
Signal2< int, int > noteOn; Signal2< int, int > noteOff;
, int
. MIDIReceiver.cpp advance
:
// A key pressed later overrides any previously pressed key: if (noteNumber != mLastNoteNumber) { mLastNoteNumber = noteNumber; mLastFrequency = noteNumberToFrequency(mLastNoteNumber); mLastVelocity = velocity; // Emit a "note on" signal: noteOn(noteNumber, velocity); }
// If the last note was released, nothing should play: if (noteNumber == mLastNoteNumber) { mLastNoteNumber = -1; noteOff(noteNumber, mLastVelocity); }
— , — . mLastFrequency
-1
, : RELEASE. mLastVelocity
: , .
, , , . . .
mEnvelopeGenerator
. - onNoteOn
onNoteOff
EnvelopeGenerator
. , EnvelopeGenerator
. , . enterStage
- . - private
Synthesis
:
inline void onNoteOn(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); }; inline void onNoteOff(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); };
.
, Synthesis.cpp :
mMIDIReceiver.noteOn.Connect(this, &Synthesis::onNoteOn); mMIDIReceiver.noteOff.Connect(this, &Synthesis::onNoteOff);
— , — -.
ProcessDoubleReplacing
. if
, , .
!
. . DECAY, RELEASE, . stageValues
.
, , . GUI - :
!
.
:
martin-finke.de/blog/articles/audio-plugins-011-envelopes
. , : , , .
( ):
void EnvelopeGenerator::calculateMultiplier(double startLevel, double endLevel, unsigned long long lengthInSamples) { multiplier = 1.0 + (log(endLevel) - log(startLevel)) / (lengthInSamples); }
, . , startLevel
endLevel
lengthInSamples
multiplier
, . currentLevel
. log()
.
enterStage
:
void EnvelopeGenerator::enterStage(EnvelopeStage newStage) { currentStage = newStage; currentSampleIndex = 0; if (currentStage == ENVELOPE_STAGE_OFF || currentStage == ENVELOPE_STAGE_SUSTAIN) { nextStageSampleIndex = 0; } else { nextStageSampleIndex = stageValue[currentStage] * sampleRate; } switch (newStage) { case ENVELOPE_STAGE_OFF: currentLevel = 0.0; multiplier = 1.0; break; case ENVELOPE_STAGE_ATTACK: currentLevel = minimumLevel; calculateMultiplier(currentLevel, 1.0, nextStageSampleIndex); break; case ENVELOPE_STAGE_DECAY: currentLevel = 1.0; calculateMultiplier(currentLevel, fmax(stageValue[ENVELOPE_STAGE_SUSTAIN], minimumLevel), nextStageSampleIndex); break; case ENVELOPE_STAGE_SUSTAIN: currentLevel = stageValue[ENVELOPE_STAGE_SUSTAIN]; multiplier = 1.0; break; case ENVELOPE_STAGE_RELEASE: // We could go from ATTACK/DECAY to RELEASE, // so we're not changing currentLevel here. calculateMultiplier(currentLevel, minimumLevel, nextStageSampleIndex); break; default: break; } }
currentStage
currentSampleIndex
. . , , . stageValue[currentStage]
double
( ), , , . Switch
. OFF ( , ). ATTACK minimumLevel
, multiplier
currentLevel
1.0
. DECAY stageValue[ENVELOPE_STAGE_SUSTAIN]
, fmax
, . RELEASE currentLevel
, , minimumLevel
. , RELEASE currentLevel
, . . SUSTAIN, ATTACK DECAY. , SUSTAIN , stageValue[ENVELOPE_STAGE_SUSTAIN]
, . currentLevel
.
:
void EnvelopeGenerator::setSampleRate(double newSampleRate) { sampleRate = newSampleRate; }
private
Synthesis
( ) :
EnvelopeGenerator mEnvelopeGenerator;
#include "EnvelopeGenerator.h"
.
, ProcessDoubleReplacing
Synthesis.cpp leftOutput[i]
:
// leftOutput[i] = rightOutput[i] = mOscillator.nextSample() * velocity / 127.0; if (mEnvelopeGenerator.getCurrentStage() == EnvelopeGenerator::ENVELOPE_STAGE_OFF) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); } if (mEnvelopeGenerator.getCurrentStage() == EnvelopeGenerator::ENVELOPE_STAGE_SUSTAIN) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); } leftOutput[i] = rightOutput[i] = mOscillator.nextSample() * mEnvelopeGenerator.nextSample() * velocity / 127.0;
. : OFF ATTACK, SUSTAIN RELEASE. .
mEnvelopeGenerator
. Synthesis::Reset()
:
mEnvelopeGenerator.setSampleRate(GetSampleRate());
. - — ! .
Note On/Off
, (, - , ), : — ATTACK, — RELEASE. mMIDIReceiver
note on/off, , - .
#include EnvelopeGenerator.h
MIDIReceiver.h . , Synthesis.h mEnvelopeGenerator
mMIDIReceiver
, . MIDIReceiver
enterStage
note on/off.
, . . MIDIReceiver
EnvelopeGenerator
. , MIDIReceiver
, EnvelopeGenerator
.
. Qt. , , , . , . , setText()
, . , , , . , , . ( Oscillator
, EnvelopeGenerator
, MIDIReceiver
). , . . Synthesis
.
Qt . Signals . . Signal.h GallantSignal.h , . Delegate.h GallantSignal.h ( , , “Copy items into destination group's folder”) Xcode. VisualStudio , Solution Explorer.
, MIDIReceiver
, . MIDIReceiver.h :
#include "GallantSignal.h" using Gallant::Signal2;
Signal2
, . — Signal0
Signal8
, .
public
:
Signal2< int, int > noteOn; Signal2< int, int > noteOff;
, int
. MIDIReceiver.cpp advance
:
// A key pressed later overrides any previously pressed key: if (noteNumber != mLastNoteNumber) { mLastNoteNumber = noteNumber; mLastFrequency = noteNumberToFrequency(mLastNoteNumber); mLastVelocity = velocity; // Emit a "note on" signal: noteOn(noteNumber, velocity); }
// If the last note was released, nothing should play: if (noteNumber == mLastNoteNumber) { mLastNoteNumber = -1; noteOff(noteNumber, mLastVelocity); }
— , — . mLastFrequency
-1
, : RELEASE. mLastVelocity
: , .
, , , . . .
mEnvelopeGenerator
. - onNoteOn
onNoteOff
EnvelopeGenerator
. , EnvelopeGenerator
. , . enterStage
- . - private
Synthesis
:
inline void onNoteOn(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); }; inline void onNoteOff(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); };
.
, Synthesis.cpp :
mMIDIReceiver.noteOn.Connect(this, &Synthesis::onNoteOn); mMIDIReceiver.noteOff.Connect(this, &Synthesis::onNoteOff);
— , — -.
ProcessDoubleReplacing
. if
, , .
!
. . DECAY, RELEASE, . stageValues
.
, , . GUI - :

!
.
:
martin-finke.de/blog/articles/audio-plugins-011-envelopes
. , : , , .
( ):
void EnvelopeGenerator::calculateMultiplier(double startLevel, double endLevel, unsigned long long lengthInSamples) { multiplier = 1.0 + (log(endLevel) - log(startLevel)) / (lengthInSamples); }
, . , startLevel
endLevel
lengthInSamples
multiplier
, . currentLevel
. log()
.
enterStage
:
void EnvelopeGenerator::enterStage(EnvelopeStage newStage) { currentStage = newStage; currentSampleIndex = 0; if (currentStage == ENVELOPE_STAGE_OFF || currentStage == ENVELOPE_STAGE_SUSTAIN) { nextStageSampleIndex = 0; } else { nextStageSampleIndex = stageValue[currentStage] * sampleRate; } switch (newStage) { case ENVELOPE_STAGE_OFF: currentLevel = 0.0; multiplier = 1.0; break; case ENVELOPE_STAGE_ATTACK: currentLevel = minimumLevel; calculateMultiplier(currentLevel, 1.0, nextStageSampleIndex); break; case ENVELOPE_STAGE_DECAY: currentLevel = 1.0; calculateMultiplier(currentLevel, fmax(stageValue[ENVELOPE_STAGE_SUSTAIN], minimumLevel), nextStageSampleIndex); break; case ENVELOPE_STAGE_SUSTAIN: currentLevel = stageValue[ENVELOPE_STAGE_SUSTAIN]; multiplier = 1.0; break; case ENVELOPE_STAGE_RELEASE: // We could go from ATTACK/DECAY to RELEASE, // so we're not changing currentLevel here. calculateMultiplier(currentLevel, minimumLevel, nextStageSampleIndex); break; default: break; } }
currentStage
currentSampleIndex
. . , , . stageValue[currentStage]
double
( ), , , . Switch
. OFF ( , ). ATTACK minimumLevel
, multiplier
currentLevel
1.0
. DECAY stageValue[ENVELOPE_STAGE_SUSTAIN]
, fmax
, . RELEASE currentLevel
, , minimumLevel
. , RELEASE currentLevel
, . . SUSTAIN, ATTACK DECAY. , SUSTAIN , stageValue[ENVELOPE_STAGE_SUSTAIN]
, . currentLevel
.
:
void EnvelopeGenerator::setSampleRate(double newSampleRate) { sampleRate = newSampleRate; }
private
Synthesis
( ) :
EnvelopeGenerator mEnvelopeGenerator;
#include "EnvelopeGenerator.h"
.
, ProcessDoubleReplacing
Synthesis.cpp leftOutput[i]
:
// leftOutput[i] = rightOutput[i] = mOscillator.nextSample() * velocity / 127.0; if (mEnvelopeGenerator.getCurrentStage() == EnvelopeGenerator::ENVELOPE_STAGE_OFF) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); } if (mEnvelopeGenerator.getCurrentStage() == EnvelopeGenerator::ENVELOPE_STAGE_SUSTAIN) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); } leftOutput[i] = rightOutput[i] = mOscillator.nextSample() * mEnvelopeGenerator.nextSample() * velocity / 127.0;
. : OFF ATTACK, SUSTAIN RELEASE. .
mEnvelopeGenerator
. Synthesis::Reset()
:
mEnvelopeGenerator.setSampleRate(GetSampleRate());
. - — ! .
Note On/Off
, (, - , ), : — ATTACK, — RELEASE. mMIDIReceiver
note on/off, , - .
#include EnvelopeGenerator.h
MIDIReceiver.h . , Synthesis.h mEnvelopeGenerator
mMIDIReceiver
, . MIDIReceiver
enterStage
note on/off.
, . . MIDIReceiver
EnvelopeGenerator
. , MIDIReceiver
, EnvelopeGenerator
.
. Qt. , , , . , . , setText()
, . , , , . , , . ( Oscillator
, EnvelopeGenerator
, MIDIReceiver
). , . . Synthesis
.
Qt . Signals . . Signal.h GallantSignal.h , . Delegate.h GallantSignal.h ( , , “Copy items into destination group's folder”) Xcode. VisualStudio , Solution Explorer.
, MIDIReceiver
, . MIDIReceiver.h :
#include "GallantSignal.h" using Gallant::Signal2;
Signal2
, . — Signal0
Signal8
, .
public
:
Signal2< int, int > noteOn; Signal2< int, int > noteOff;
, int
. MIDIReceiver.cpp advance
:
// A key pressed later overrides any previously pressed key: if (noteNumber != mLastNoteNumber) { mLastNoteNumber = noteNumber; mLastFrequency = noteNumberToFrequency(mLastNoteNumber); mLastVelocity = velocity; // Emit a "note on" signal: noteOn(noteNumber, velocity); }
// If the last note was released, nothing should play: if (noteNumber == mLastNoteNumber) { mLastNoteNumber = -1; noteOff(noteNumber, mLastVelocity); }
— , — . mLastFrequency
-1
, : RELEASE. mLastVelocity
: , .
, , , . . .
mEnvelopeGenerator
. - onNoteOn
onNoteOff
EnvelopeGenerator
. , EnvelopeGenerator
. , . enterStage
- . - private
Synthesis
:
inline void onNoteOn(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); }; inline void onNoteOff(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); };
.
, Synthesis.cpp :
mMIDIReceiver.noteOn.Connect(this, &Synthesis::onNoteOn); mMIDIReceiver.noteOff.Connect(this, &Synthesis::onNoteOff);
— , — -.
ProcessDoubleReplacing
. if
, , .
!
. . DECAY, RELEASE, . stageValues
.
, , . GUI - :

!
.
:
martin-finke.de/blog/articles/audio-plugins-011-envelopes
. , : , , .
( ):
void EnvelopeGenerator::calculateMultiplier(double startLevel, double endLevel, unsigned long long lengthInSamples) { multiplier = 1.0 + (log(endLevel) - log(startLevel)) / (lengthInSamples); }
, . , startLevel
endLevel
lengthInSamples
multiplier
, . currentLevel
. log()
.
enterStage
:
void EnvelopeGenerator::enterStage(EnvelopeStage newStage) { currentStage = newStage; currentSampleIndex = 0; if (currentStage == ENVELOPE_STAGE_OFF || currentStage == ENVELOPE_STAGE_SUSTAIN) { nextStageSampleIndex = 0; } else { nextStageSampleIndex = stageValue[currentStage] * sampleRate; } switch (newStage) { case ENVELOPE_STAGE_OFF: currentLevel = 0.0; multiplier = 1.0; break; case ENVELOPE_STAGE_ATTACK: currentLevel = minimumLevel; calculateMultiplier(currentLevel, 1.0, nextStageSampleIndex); break; case ENVELOPE_STAGE_DECAY: currentLevel = 1.0; calculateMultiplier(currentLevel, fmax(stageValue[ENVELOPE_STAGE_SUSTAIN], minimumLevel), nextStageSampleIndex); break; case ENVELOPE_STAGE_SUSTAIN: currentLevel = stageValue[ENVELOPE_STAGE_SUSTAIN]; multiplier = 1.0; break; case ENVELOPE_STAGE_RELEASE: // We could go from ATTACK/DECAY to RELEASE, // so we're not changing currentLevel here. calculateMultiplier(currentLevel, minimumLevel, nextStageSampleIndex); break; default: break; } }
currentStage
currentSampleIndex
. . , , . stageValue[currentStage]
double
( ), , , . Switch
. OFF ( , ). ATTACK minimumLevel
, multiplier
currentLevel
1.0
. DECAY stageValue[ENVELOPE_STAGE_SUSTAIN]
, fmax
, . RELEASE currentLevel
, , minimumLevel
. , RELEASE currentLevel
, . . SUSTAIN, ATTACK DECAY. , SUSTAIN , stageValue[ENVELOPE_STAGE_SUSTAIN]
, . currentLevel
.
:
void EnvelopeGenerator::setSampleRate(double newSampleRate) { sampleRate = newSampleRate; }
private
Synthesis
( ) :
EnvelopeGenerator mEnvelopeGenerator;
#include "EnvelopeGenerator.h"
.
, ProcessDoubleReplacing
Synthesis.cpp leftOutput[i]
:
// leftOutput[i] = rightOutput[i] = mOscillator.nextSample() * velocity / 127.0; if (mEnvelopeGenerator.getCurrentStage() == EnvelopeGenerator::ENVELOPE_STAGE_OFF) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); } if (mEnvelopeGenerator.getCurrentStage() == EnvelopeGenerator::ENVELOPE_STAGE_SUSTAIN) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); } leftOutput[i] = rightOutput[i] = mOscillator.nextSample() * mEnvelopeGenerator.nextSample() * velocity / 127.0;
. : OFF ATTACK, SUSTAIN RELEASE. .
mEnvelopeGenerator
. Synthesis::Reset()
:
mEnvelopeGenerator.setSampleRate(GetSampleRate());
. - — ! .
Note On/Off
, (, - , ), : — ATTACK, — RELEASE. mMIDIReceiver
note on/off, , - .
#include EnvelopeGenerator.h
MIDIReceiver.h . , Synthesis.h mEnvelopeGenerator
mMIDIReceiver
, . MIDIReceiver
enterStage
note on/off.
, . . MIDIReceiver
EnvelopeGenerator
. , MIDIReceiver
, EnvelopeGenerator
.
. Qt. , , , . , . , setText()
, . , , , . , , . ( Oscillator
, EnvelopeGenerator
, MIDIReceiver
). , . . Synthesis
.
Qt . Signals . . Signal.h GallantSignal.h , . Delegate.h GallantSignal.h ( , , “Copy items into destination group's folder”) Xcode. VisualStudio , Solution Explorer.
, MIDIReceiver
, . MIDIReceiver.h :
#include "GallantSignal.h" using Gallant::Signal2;
Signal2
, . — Signal0
Signal8
, .
public
:
Signal2< int, int > noteOn; Signal2< int, int > noteOff;
, int
. MIDIReceiver.cpp advance
:
// A key pressed later overrides any previously pressed key: if (noteNumber != mLastNoteNumber) { mLastNoteNumber = noteNumber; mLastFrequency = noteNumberToFrequency(mLastNoteNumber); mLastVelocity = velocity; // Emit a "note on" signal: noteOn(noteNumber, velocity); }
// If the last note was released, nothing should play: if (noteNumber == mLastNoteNumber) { mLastNoteNumber = -1; noteOff(noteNumber, mLastVelocity); }
— , — . mLastFrequency
-1
, : RELEASE. mLastVelocity
: , .
, , , . . .
mEnvelopeGenerator
. - onNoteOn
onNoteOff
EnvelopeGenerator
. , EnvelopeGenerator
. , . enterStage
- . - private
Synthesis
:
inline void onNoteOn(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); }; inline void onNoteOff(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); };
.
, Synthesis.cpp :
mMIDIReceiver.noteOn.Connect(this, &Synthesis::onNoteOn); mMIDIReceiver.noteOff.Connect(this, &Synthesis::onNoteOff);
— , — -.
ProcessDoubleReplacing
. if
, , .
!
. . DECAY, RELEASE, . stageValues
.
, , . GUI - :

!
.
:
martin-finke.de/blog/articles/audio-plugins-011-envelopes
. , : , , .
( ):
void EnvelopeGenerator::calculateMultiplier(double startLevel, double endLevel, unsigned long long lengthInSamples) { multiplier = 1.0 + (log(endLevel) - log(startLevel)) / (lengthInSamples); }
, . , startLevel
endLevel
lengthInSamples
multiplier
, . currentLevel
. log()
.
enterStage
:
void EnvelopeGenerator::enterStage(EnvelopeStage newStage) { currentStage = newStage; currentSampleIndex = 0; if (currentStage == ENVELOPE_STAGE_OFF || currentStage == ENVELOPE_STAGE_SUSTAIN) { nextStageSampleIndex = 0; } else { nextStageSampleIndex = stageValue[currentStage] * sampleRate; } switch (newStage) { case ENVELOPE_STAGE_OFF: currentLevel = 0.0; multiplier = 1.0; break; case ENVELOPE_STAGE_ATTACK: currentLevel = minimumLevel; calculateMultiplier(currentLevel, 1.0, nextStageSampleIndex); break; case ENVELOPE_STAGE_DECAY: currentLevel = 1.0; calculateMultiplier(currentLevel, fmax(stageValue[ENVELOPE_STAGE_SUSTAIN], minimumLevel), nextStageSampleIndex); break; case ENVELOPE_STAGE_SUSTAIN: currentLevel = stageValue[ENVELOPE_STAGE_SUSTAIN]; multiplier = 1.0; break; case ENVELOPE_STAGE_RELEASE: // We could go from ATTACK/DECAY to RELEASE, // so we're not changing currentLevel here. calculateMultiplier(currentLevel, minimumLevel, nextStageSampleIndex); break; default: break; } }
currentStage
currentSampleIndex
. . , , . stageValue[currentStage]
double
( ), , , . Switch
. OFF ( , ). ATTACK minimumLevel
, multiplier
currentLevel
1.0
. DECAY stageValue[ENVELOPE_STAGE_SUSTAIN]
, fmax
, . RELEASE currentLevel
, , minimumLevel
. , RELEASE currentLevel
, . . SUSTAIN, ATTACK DECAY. , SUSTAIN , stageValue[ENVELOPE_STAGE_SUSTAIN]
, . currentLevel
.
:
void EnvelopeGenerator::setSampleRate(double newSampleRate) { sampleRate = newSampleRate; }
private
Synthesis
( ) :
EnvelopeGenerator mEnvelopeGenerator;
#include "EnvelopeGenerator.h"
.
, ProcessDoubleReplacing
Synthesis.cpp leftOutput[i]
:
// leftOutput[i] = rightOutput[i] = mOscillator.nextSample() * velocity / 127.0; if (mEnvelopeGenerator.getCurrentStage() == EnvelopeGenerator::ENVELOPE_STAGE_OFF) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); } if (mEnvelopeGenerator.getCurrentStage() == EnvelopeGenerator::ENVELOPE_STAGE_SUSTAIN) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); } leftOutput[i] = rightOutput[i] = mOscillator.nextSample() * mEnvelopeGenerator.nextSample() * velocity / 127.0;
. : OFF ATTACK, SUSTAIN RELEASE. .
mEnvelopeGenerator
. Synthesis::Reset()
:
mEnvelopeGenerator.setSampleRate(GetSampleRate());
. - — ! .
Note On/Off
, (, - , ), : — ATTACK, — RELEASE. mMIDIReceiver
note on/off, , - .
#include EnvelopeGenerator.h
MIDIReceiver.h . , Synthesis.h mEnvelopeGenerator
mMIDIReceiver
, . MIDIReceiver
enterStage
note on/off.
, . . MIDIReceiver
EnvelopeGenerator
. , MIDIReceiver
, EnvelopeGenerator
.
. Qt. , , , . , . , setText()
, . , , , . , , . ( Oscillator
, EnvelopeGenerator
, MIDIReceiver
). , . . Synthesis
.
Qt . Signals . . Signal.h GallantSignal.h , . Delegate.h GallantSignal.h ( , , “Copy items into destination group's folder”) Xcode. VisualStudio , Solution Explorer.
, MIDIReceiver
, . MIDIReceiver.h :
#include "GallantSignal.h" using Gallant::Signal2;
Signal2
, . — Signal0
Signal8
, .
public
:
Signal2< int, int > noteOn; Signal2< int, int > noteOff;
, int
. MIDIReceiver.cpp advance
:
// A key pressed later overrides any previously pressed key: if (noteNumber != mLastNoteNumber) { mLastNoteNumber = noteNumber; mLastFrequency = noteNumberToFrequency(mLastNoteNumber); mLastVelocity = velocity; // Emit a "note on" signal: noteOn(noteNumber, velocity); }
// If the last note was released, nothing should play: if (noteNumber == mLastNoteNumber) { mLastNoteNumber = -1; noteOff(noteNumber, mLastVelocity); }
— , — . mLastFrequency
-1
, : RELEASE. mLastVelocity
: , .
, , , . . .
mEnvelopeGenerator
. - onNoteOn
onNoteOff
EnvelopeGenerator
. , EnvelopeGenerator
. , . enterStage
- . - private
Synthesis
:
inline void onNoteOn(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); }; inline void onNoteOff(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); };
.
, Synthesis.cpp :
mMIDIReceiver.noteOn.Connect(this, &Synthesis::onNoteOn); mMIDIReceiver.noteOff.Connect(this, &Synthesis::onNoteOff);
— , — -.
ProcessDoubleReplacing
. if
, , .
!
. . DECAY, RELEASE, . stageValues
.
, , . GUI - :
!
.
:
martin-finke.de/blog/articles/audio-plugins-011-envelopes
. , : , , .
( ):
void EnvelopeGenerator::calculateMultiplier(double startLevel, double endLevel, unsigned long long lengthInSamples) { multiplier = 1.0 + (log(endLevel) - log(startLevel)) / (lengthInSamples); }
, . , startLevel
endLevel
lengthInSamples
multiplier
, . currentLevel
. log()
.
enterStage
:
void EnvelopeGenerator::enterStage(EnvelopeStage newStage) { currentStage = newStage; currentSampleIndex = 0; if (currentStage == ENVELOPE_STAGE_OFF || currentStage == ENVELOPE_STAGE_SUSTAIN) { nextStageSampleIndex = 0; } else { nextStageSampleIndex = stageValue[currentStage] * sampleRate; } switch (newStage) { case ENVELOPE_STAGE_OFF: currentLevel = 0.0; multiplier = 1.0; break; case ENVELOPE_STAGE_ATTACK: currentLevel = minimumLevel; calculateMultiplier(currentLevel, 1.0, nextStageSampleIndex); break; case ENVELOPE_STAGE_DECAY: currentLevel = 1.0; calculateMultiplier(currentLevel, fmax(stageValue[ENVELOPE_STAGE_SUSTAIN], minimumLevel), nextStageSampleIndex); break; case ENVELOPE_STAGE_SUSTAIN: currentLevel = stageValue[ENVELOPE_STAGE_SUSTAIN]; multiplier = 1.0; break; case ENVELOPE_STAGE_RELEASE: // We could go from ATTACK/DECAY to RELEASE, // so we're not changing currentLevel here. calculateMultiplier(currentLevel, minimumLevel, nextStageSampleIndex); break; default: break; } }
currentStage
currentSampleIndex
. . , , . stageValue[currentStage]
double
( ), , , . Switch
. OFF ( , ). ATTACK minimumLevel
, multiplier
currentLevel
1.0
. DECAY stageValue[ENVELOPE_STAGE_SUSTAIN]
, fmax
, . RELEASE currentLevel
, , minimumLevel
. , RELEASE currentLevel
, . . SUSTAIN, ATTACK DECAY. , SUSTAIN , stageValue[ENVELOPE_STAGE_SUSTAIN]
, . currentLevel
.
:
void EnvelopeGenerator::setSampleRate(double newSampleRate) { sampleRate = newSampleRate; }
private
Synthesis
( ) :
EnvelopeGenerator mEnvelopeGenerator;
#include "EnvelopeGenerator.h"
.
, ProcessDoubleReplacing
Synthesis.cpp leftOutput[i]
:
// leftOutput[i] = rightOutput[i] = mOscillator.nextSample() * velocity / 127.0; if (mEnvelopeGenerator.getCurrentStage() == EnvelopeGenerator::ENVELOPE_STAGE_OFF) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); } if (mEnvelopeGenerator.getCurrentStage() == EnvelopeGenerator::ENVELOPE_STAGE_SUSTAIN) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); } leftOutput[i] = rightOutput[i] = mOscillator.nextSample() * mEnvelopeGenerator.nextSample() * velocity / 127.0;
. : OFF ATTACK, SUSTAIN RELEASE. .
mEnvelopeGenerator
. Synthesis::Reset()
:
mEnvelopeGenerator.setSampleRate(GetSampleRate());
. - — ! .
Note On/Off
, (, - , ), : — ATTACK, — RELEASE. mMIDIReceiver
note on/off, , - .
#include EnvelopeGenerator.h
MIDIReceiver.h . , Synthesis.h mEnvelopeGenerator
mMIDIReceiver
, . MIDIReceiver
enterStage
note on/off.
, . . MIDIReceiver
EnvelopeGenerator
. , MIDIReceiver
, EnvelopeGenerator
.
. Qt. , , , . , . , setText()
, . , , , . , , . ( Oscillator
, EnvelopeGenerator
, MIDIReceiver
). , . . Synthesis
.
Qt . Signals . . Signal.h GallantSignal.h , . Delegate.h GallantSignal.h ( , , “Copy items into destination group's folder”) Xcode. VisualStudio , Solution Explorer.
, MIDIReceiver
, . MIDIReceiver.h :
#include "GallantSignal.h" using Gallant::Signal2;
Signal2
, . — Signal0
Signal8
, .
public
:
Signal2< int, int > noteOn; Signal2< int, int > noteOff;
, int
. MIDIReceiver.cpp advance
:
// A key pressed later overrides any previously pressed key: if (noteNumber != mLastNoteNumber) { mLastNoteNumber = noteNumber; mLastFrequency = noteNumberToFrequency(mLastNoteNumber); mLastVelocity = velocity; // Emit a "note on" signal: noteOn(noteNumber, velocity); }
// If the last note was released, nothing should play: if (noteNumber == mLastNoteNumber) { mLastNoteNumber = -1; noteOff(noteNumber, mLastVelocity); }
— , — . mLastFrequency
-1
, : RELEASE. mLastVelocity
: , .
, , , . . .
mEnvelopeGenerator
. - onNoteOn
onNoteOff
EnvelopeGenerator
. , EnvelopeGenerator
. , . enterStage
- . - private
Synthesis
:
inline void onNoteOn(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); }; inline void onNoteOff(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); };
.
, Synthesis.cpp :
mMIDIReceiver.noteOn.Connect(this, &Synthesis::onNoteOn); mMIDIReceiver.noteOff.Connect(this, &Synthesis::onNoteOff);
— , — -.
ProcessDoubleReplacing
. if
, , .
!
. . DECAY, RELEASE, . stageValues
.
, , . GUI - :

!
.
:
martin-finke.de/blog/articles/audio-plugins-011-envelopes
. , : , , .
( ):
void EnvelopeGenerator::calculateMultiplier(double startLevel, double endLevel, unsigned long long lengthInSamples) { multiplier = 1.0 + (log(endLevel) - log(startLevel)) / (lengthInSamples); }
, . , startLevel
endLevel
lengthInSamples
multiplier
, . currentLevel
. log()
.
enterStage
:
void EnvelopeGenerator::enterStage(EnvelopeStage newStage) { currentStage = newStage; currentSampleIndex = 0; if (currentStage == ENVELOPE_STAGE_OFF || currentStage == ENVELOPE_STAGE_SUSTAIN) { nextStageSampleIndex = 0; } else { nextStageSampleIndex = stageValue[currentStage] * sampleRate; } switch (newStage) { case ENVELOPE_STAGE_OFF: currentLevel = 0.0; multiplier = 1.0; break; case ENVELOPE_STAGE_ATTACK: currentLevel = minimumLevel; calculateMultiplier(currentLevel, 1.0, nextStageSampleIndex); break; case ENVELOPE_STAGE_DECAY: currentLevel = 1.0; calculateMultiplier(currentLevel, fmax(stageValue[ENVELOPE_STAGE_SUSTAIN], minimumLevel), nextStageSampleIndex); break; case ENVELOPE_STAGE_SUSTAIN: currentLevel = stageValue[ENVELOPE_STAGE_SUSTAIN]; multiplier = 1.0; break; case ENVELOPE_STAGE_RELEASE: // We could go from ATTACK/DECAY to RELEASE, // so we're not changing currentLevel here. calculateMultiplier(currentLevel, minimumLevel, nextStageSampleIndex); break; default: break; } }
currentStage
currentSampleIndex
. . , , . stageValue[currentStage]
double
( ), , , . Switch
. OFF ( , ). ATTACK minimumLevel
, multiplier
currentLevel
1.0
. DECAY stageValue[ENVELOPE_STAGE_SUSTAIN]
, fmax
, . RELEASE currentLevel
, , minimumLevel
. , RELEASE currentLevel
, . . SUSTAIN, ATTACK DECAY. , SUSTAIN , stageValue[ENVELOPE_STAGE_SUSTAIN]
, . currentLevel
.
:
void EnvelopeGenerator::setSampleRate(double newSampleRate) { sampleRate = newSampleRate; }
private
Synthesis
( ) :
EnvelopeGenerator mEnvelopeGenerator;
#include "EnvelopeGenerator.h"
.
, ProcessDoubleReplacing
Synthesis.cpp leftOutput[i]
:
// leftOutput[i] = rightOutput[i] = mOscillator.nextSample() * velocity / 127.0; if (mEnvelopeGenerator.getCurrentStage() == EnvelopeGenerator::ENVELOPE_STAGE_OFF) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); } if (mEnvelopeGenerator.getCurrentStage() == EnvelopeGenerator::ENVELOPE_STAGE_SUSTAIN) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); } leftOutput[i] = rightOutput[i] = mOscillator.nextSample() * mEnvelopeGenerator.nextSample() * velocity / 127.0;
. : OFF ATTACK, SUSTAIN RELEASE. .
mEnvelopeGenerator
. Synthesis::Reset()
:
mEnvelopeGenerator.setSampleRate(GetSampleRate());
. - — ! .
Note On/Off
, (, - , ), : — ATTACK, — RELEASE. mMIDIReceiver
note on/off, , - .
#include EnvelopeGenerator.h
MIDIReceiver.h . , Synthesis.h mEnvelopeGenerator
mMIDIReceiver
, . MIDIReceiver
enterStage
note on/off.
, . . MIDIReceiver
EnvelopeGenerator
. , MIDIReceiver
, EnvelopeGenerator
.
. Qt. , , , . , . , setText()
, . , , , . , , . ( Oscillator
, EnvelopeGenerator
, MIDIReceiver
). , . . Synthesis
.
Qt . Signals . . Signal.h GallantSignal.h , . Delegate.h GallantSignal.h ( , , “Copy items into destination group's folder”) Xcode. VisualStudio , Solution Explorer.
, MIDIReceiver
, . MIDIReceiver.h :
#include "GallantSignal.h" using Gallant::Signal2;
Signal2
, . — Signal0
Signal8
, .
public
:
Signal2< int, int > noteOn; Signal2< int, int > noteOff;
, int
. MIDIReceiver.cpp advance
:
// A key pressed later overrides any previously pressed key: if (noteNumber != mLastNoteNumber) { mLastNoteNumber = noteNumber; mLastFrequency = noteNumberToFrequency(mLastNoteNumber); mLastVelocity = velocity; // Emit a "note on" signal: noteOn(noteNumber, velocity); }
// If the last note was released, nothing should play: if (noteNumber == mLastNoteNumber) { mLastNoteNumber = -1; noteOff(noteNumber, mLastVelocity); }
— , — . mLastFrequency
-1
, : RELEASE. mLastVelocity
: , .
, , , . . .
mEnvelopeGenerator
. - onNoteOn
onNoteOff
EnvelopeGenerator
. , EnvelopeGenerator
. , . enterStage
- . - private
Synthesis
:
inline void onNoteOn(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); }; inline void onNoteOff(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); };
.
, Synthesis.cpp :
mMIDIReceiver.noteOn.Connect(this, &Synthesis::onNoteOn); mMIDIReceiver.noteOff.Connect(this, &Synthesis::onNoteOff);
— , — -.
ProcessDoubleReplacing
. if
, , .
!
. . DECAY, RELEASE, . stageValues
.
, , . GUI - :

!
.
:
martin-finke.de/blog/articles/audio-plugins-011-envelopes
. , : , , .
( ):
void EnvelopeGenerator::calculateMultiplier(double startLevel, double endLevel, unsigned long long lengthInSamples) { multiplier = 1.0 + (log(endLevel) - log(startLevel)) / (lengthInSamples); }
, . , startLevel
endLevel
lengthInSamples
multiplier
, . currentLevel
. log()
.
enterStage
:
void EnvelopeGenerator::enterStage(EnvelopeStage newStage) { currentStage = newStage; currentSampleIndex = 0; if (currentStage == ENVELOPE_STAGE_OFF || currentStage == ENVELOPE_STAGE_SUSTAIN) { nextStageSampleIndex = 0; } else { nextStageSampleIndex = stageValue[currentStage] * sampleRate; } switch (newStage) { case ENVELOPE_STAGE_OFF: currentLevel = 0.0; multiplier = 1.0; break; case ENVELOPE_STAGE_ATTACK: currentLevel = minimumLevel; calculateMultiplier(currentLevel, 1.0, nextStageSampleIndex); break; case ENVELOPE_STAGE_DECAY: currentLevel = 1.0; calculateMultiplier(currentLevel, fmax(stageValue[ENVELOPE_STAGE_SUSTAIN], minimumLevel), nextStageSampleIndex); break; case ENVELOPE_STAGE_SUSTAIN: currentLevel = stageValue[ENVELOPE_STAGE_SUSTAIN]; multiplier = 1.0; break; case ENVELOPE_STAGE_RELEASE: // We could go from ATTACK/DECAY to RELEASE, // so we're not changing currentLevel here. calculateMultiplier(currentLevel, minimumLevel, nextStageSampleIndex); break; default: break; } }
currentStage
currentSampleIndex
. . , , . stageValue[currentStage]
double
( ), , , . Switch
. OFF ( , ). ATTACK minimumLevel
, multiplier
currentLevel
1.0
. DECAY stageValue[ENVELOPE_STAGE_SUSTAIN]
, fmax
, . RELEASE currentLevel
, , minimumLevel
. , RELEASE currentLevel
, . . SUSTAIN, ATTACK DECAY. , SUSTAIN , stageValue[ENVELOPE_STAGE_SUSTAIN]
, . currentLevel
.
:
void EnvelopeGenerator::setSampleRate(double newSampleRate) { sampleRate = newSampleRate; }
private
Synthesis
( ) :
EnvelopeGenerator mEnvelopeGenerator;
#include "EnvelopeGenerator.h"
.
, ProcessDoubleReplacing
Synthesis.cpp leftOutput[i]
:
// leftOutput[i] = rightOutput[i] = mOscillator.nextSample() * velocity / 127.0; if (mEnvelopeGenerator.getCurrentStage() == EnvelopeGenerator::ENVELOPE_STAGE_OFF) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); } if (mEnvelopeGenerator.getCurrentStage() == EnvelopeGenerator::ENVELOPE_STAGE_SUSTAIN) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); } leftOutput[i] = rightOutput[i] = mOscillator.nextSample() * mEnvelopeGenerator.nextSample() * velocity / 127.0;
. : OFF ATTACK, SUSTAIN RELEASE. .
mEnvelopeGenerator
. Synthesis::Reset()
:
mEnvelopeGenerator.setSampleRate(GetSampleRate());
. - — ! .
Note On/Off
, (, - , ), : — ATTACK, — RELEASE. mMIDIReceiver
note on/off, , - .
#include EnvelopeGenerator.h
MIDIReceiver.h . , Synthesis.h mEnvelopeGenerator
mMIDIReceiver
, . MIDIReceiver
enterStage
note on/off.
, . . MIDIReceiver
EnvelopeGenerator
. , MIDIReceiver
, EnvelopeGenerator
.
. Qt. , , , . , . , setText()
, . , , , . , , . ( Oscillator
, EnvelopeGenerator
, MIDIReceiver
). , . . Synthesis
.
Qt . Signals . . Signal.h GallantSignal.h , . Delegate.h GallantSignal.h ( , , “Copy items into destination group's folder”) Xcode. VisualStudio , Solution Explorer.
, MIDIReceiver
, . MIDIReceiver.h :
#include "GallantSignal.h" using Gallant::Signal2;
Signal2
, . — Signal0
Signal8
, .
public
:
Signal2< int, int > noteOn; Signal2< int, int > noteOff;
, int
. MIDIReceiver.cpp advance
:
// A key pressed later overrides any previously pressed key: if (noteNumber != mLastNoteNumber) { mLastNoteNumber = noteNumber; mLastFrequency = noteNumberToFrequency(mLastNoteNumber); mLastVelocity = velocity; // Emit a "note on" signal: noteOn(noteNumber, velocity); }
// If the last note was released, nothing should play: if (noteNumber == mLastNoteNumber) { mLastNoteNumber = -1; noteOff(noteNumber, mLastVelocity); }
— , — . mLastFrequency
-1
, : RELEASE. mLastVelocity
: , .
, , , . . .
mEnvelopeGenerator
. - onNoteOn
onNoteOff
EnvelopeGenerator
. , EnvelopeGenerator
. , . enterStage
- . - private
Synthesis
:
inline void onNoteOn(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); }; inline void onNoteOff(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); };
.
, Synthesis.cpp :
mMIDIReceiver.noteOn.Connect(this, &Synthesis::onNoteOn); mMIDIReceiver.noteOff.Connect(this, &Synthesis::onNoteOff);
— , — -.
ProcessDoubleReplacing
. if
, , .
!
. . DECAY, RELEASE, . stageValues
.
, , . GUI - :

!
.
:
martin-finke.de/blog/articles/audio-plugins-011-envelopes
. , : , , .
( ):
void EnvelopeGenerator::calculateMultiplier(double startLevel, double endLevel, unsigned long long lengthInSamples) { multiplier = 1.0 + (log(endLevel) - log(startLevel)) / (lengthInSamples); }
, . , startLevel
endLevel
lengthInSamples
multiplier
, . currentLevel
. log()
.
enterStage
:
void EnvelopeGenerator::enterStage(EnvelopeStage newStage) { currentStage = newStage; currentSampleIndex = 0; if (currentStage == ENVELOPE_STAGE_OFF || currentStage == ENVELOPE_STAGE_SUSTAIN) { nextStageSampleIndex = 0; } else { nextStageSampleIndex = stageValue[currentStage] * sampleRate; } switch (newStage) { case ENVELOPE_STAGE_OFF: currentLevel = 0.0; multiplier = 1.0; break; case ENVELOPE_STAGE_ATTACK: currentLevel = minimumLevel; calculateMultiplier(currentLevel, 1.0, nextStageSampleIndex); break; case ENVELOPE_STAGE_DECAY: currentLevel = 1.0; calculateMultiplier(currentLevel, fmax(stageValue[ENVELOPE_STAGE_SUSTAIN], minimumLevel), nextStageSampleIndex); break; case ENVELOPE_STAGE_SUSTAIN: currentLevel = stageValue[ENVELOPE_STAGE_SUSTAIN]; multiplier = 1.0; break; case ENVELOPE_STAGE_RELEASE: // We could go from ATTACK/DECAY to RELEASE, // so we're not changing currentLevel here. calculateMultiplier(currentLevel, minimumLevel, nextStageSampleIndex); break; default: break; } }
currentStage
currentSampleIndex
. . , , . stageValue[currentStage]
double
( ), , , . Switch
. OFF ( , ). ATTACK minimumLevel
, multiplier
currentLevel
1.0
. DECAY stageValue[ENVELOPE_STAGE_SUSTAIN]
, fmax
, . RELEASE currentLevel
, , minimumLevel
. , RELEASE currentLevel
, . . SUSTAIN, ATTACK DECAY. , SUSTAIN , stageValue[ENVELOPE_STAGE_SUSTAIN]
, . currentLevel
.
:
void EnvelopeGenerator::setSampleRate(double newSampleRate) { sampleRate = newSampleRate; }
private
Synthesis
( ) :
EnvelopeGenerator mEnvelopeGenerator;
#include "EnvelopeGenerator.h"
.
, ProcessDoubleReplacing
Synthesis.cpp leftOutput[i]
:
// leftOutput[i] = rightOutput[i] = mOscillator.nextSample() * velocity / 127.0; if (mEnvelopeGenerator.getCurrentStage() == EnvelopeGenerator::ENVELOPE_STAGE_OFF) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); } if (mEnvelopeGenerator.getCurrentStage() == EnvelopeGenerator::ENVELOPE_STAGE_SUSTAIN) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); } leftOutput[i] = rightOutput[i] = mOscillator.nextSample() * mEnvelopeGenerator.nextSample() * velocity / 127.0;
. : OFF ATTACK, SUSTAIN RELEASE. .
mEnvelopeGenerator
. Synthesis::Reset()
:
mEnvelopeGenerator.setSampleRate(GetSampleRate());
. - — ! .
Note On/Off
, (, - , ), : — ATTACK, — RELEASE. mMIDIReceiver
note on/off, , - .
#include EnvelopeGenerator.h
MIDIReceiver.h . , Synthesis.h mEnvelopeGenerator
mMIDIReceiver
, . MIDIReceiver
enterStage
note on/off.
, . . MIDIReceiver
EnvelopeGenerator
. , MIDIReceiver
, EnvelopeGenerator
.
. Qt. , , , . , . , setText()
, . , , , . , , . ( Oscillator
, EnvelopeGenerator
, MIDIReceiver
). , . . Synthesis
.
Qt . Signals . . Signal.h GallantSignal.h , . Delegate.h GallantSignal.h ( , , “Copy items into destination group's folder”) Xcode. VisualStudio , Solution Explorer.
, MIDIReceiver
, . MIDIReceiver.h :
#include "GallantSignal.h" using Gallant::Signal2;
Signal2
, . — Signal0
Signal8
, .
public
:
Signal2< int, int > noteOn; Signal2< int, int > noteOff;
, int
. MIDIReceiver.cpp advance
:
// A key pressed later overrides any previously pressed key: if (noteNumber != mLastNoteNumber) { mLastNoteNumber = noteNumber; mLastFrequency = noteNumberToFrequency(mLastNoteNumber); mLastVelocity = velocity; // Emit a "note on" signal: noteOn(noteNumber, velocity); }
// If the last note was released, nothing should play: if (noteNumber == mLastNoteNumber) { mLastNoteNumber = -1; noteOff(noteNumber, mLastVelocity); }
— , — . mLastFrequency
-1
, : RELEASE. mLastVelocity
: , .
, , , . . .
mEnvelopeGenerator
. - onNoteOn
onNoteOff
EnvelopeGenerator
. , EnvelopeGenerator
. , . enterStage
- . - private
Synthesis
:
inline void onNoteOn(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); }; inline void onNoteOff(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); };
.
, Synthesis.cpp :
mMIDIReceiver.noteOn.Connect(this, &Synthesis::onNoteOn); mMIDIReceiver.noteOff.Connect(this, &Synthesis::onNoteOff);
— , — -.
ProcessDoubleReplacing
. if
, , .
!
. . DECAY, RELEASE, . stageValues
.
, , . GUI - :
!
.
:
martin-finke.de/blog/articles/audio-plugins-011-envelopes
. , : , , .
( ):
void EnvelopeGenerator::calculateMultiplier(double startLevel, double endLevel, unsigned long long lengthInSamples) { multiplier = 1.0 + (log(endLevel) - log(startLevel)) / (lengthInSamples); }
, . , startLevel
endLevel
lengthInSamples
multiplier
, . currentLevel
. log()
.
enterStage
:
void EnvelopeGenerator::enterStage(EnvelopeStage newStage) { currentStage = newStage; currentSampleIndex = 0; if (currentStage == ENVELOPE_STAGE_OFF || currentStage == ENVELOPE_STAGE_SUSTAIN) { nextStageSampleIndex = 0; } else { nextStageSampleIndex = stageValue[currentStage] * sampleRate; } switch (newStage) { case ENVELOPE_STAGE_OFF: currentLevel = 0.0; multiplier = 1.0; break; case ENVELOPE_STAGE_ATTACK: currentLevel = minimumLevel; calculateMultiplier(currentLevel, 1.0, nextStageSampleIndex); break; case ENVELOPE_STAGE_DECAY: currentLevel = 1.0; calculateMultiplier(currentLevel, fmax(stageValue[ENVELOPE_STAGE_SUSTAIN], minimumLevel), nextStageSampleIndex); break; case ENVELOPE_STAGE_SUSTAIN: currentLevel = stageValue[ENVELOPE_STAGE_SUSTAIN]; multiplier = 1.0; break; case ENVELOPE_STAGE_RELEASE: // We could go from ATTACK/DECAY to RELEASE, // so we're not changing currentLevel here. calculateMultiplier(currentLevel, minimumLevel, nextStageSampleIndex); break; default: break; } }
currentStage
currentSampleIndex
. . , , . stageValue[currentStage]
double
( ), , , . Switch
. OFF ( , ). ATTACK minimumLevel
, multiplier
currentLevel
1.0
. DECAY stageValue[ENVELOPE_STAGE_SUSTAIN]
, fmax
, . RELEASE currentLevel
, , minimumLevel
. , RELEASE currentLevel
, . . SUSTAIN, ATTACK DECAY. , SUSTAIN , stageValue[ENVELOPE_STAGE_SUSTAIN]
, . currentLevel
.
:
void EnvelopeGenerator::setSampleRate(double newSampleRate) { sampleRate = newSampleRate; }
private
Synthesis
( ) :
EnvelopeGenerator mEnvelopeGenerator;
#include "EnvelopeGenerator.h"
.
, ProcessDoubleReplacing
Synthesis.cpp leftOutput[i]
:
// leftOutput[i] = rightOutput[i] = mOscillator.nextSample() * velocity / 127.0; if (mEnvelopeGenerator.getCurrentStage() == EnvelopeGenerator::ENVELOPE_STAGE_OFF) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); } if (mEnvelopeGenerator.getCurrentStage() == EnvelopeGenerator::ENVELOPE_STAGE_SUSTAIN) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); } leftOutput[i] = rightOutput[i] = mOscillator.nextSample() * mEnvelopeGenerator.nextSample() * velocity / 127.0;
. : OFF ATTACK, SUSTAIN RELEASE. .
mEnvelopeGenerator
. Synthesis::Reset()
:
mEnvelopeGenerator.setSampleRate(GetSampleRate());
. - — ! .
Note On/Off
, (, - , ), : — ATTACK, — RELEASE. mMIDIReceiver
note on/off, , - .
#include EnvelopeGenerator.h
MIDIReceiver.h . , Synthesis.h mEnvelopeGenerator
mMIDIReceiver
, . MIDIReceiver
enterStage
note on/off.
, . . MIDIReceiver
EnvelopeGenerator
. , MIDIReceiver
, EnvelopeGenerator
.
. Qt. , , , . , . , setText()
, . , , , . , , . ( Oscillator
, EnvelopeGenerator
, MIDIReceiver
). , . . Synthesis
.
Qt . Signals . . Signal.h GallantSignal.h , . Delegate.h GallantSignal.h ( , , “Copy items into destination group's folder”) Xcode. VisualStudio , Solution Explorer.
, MIDIReceiver
, . MIDIReceiver.h :
#include "GallantSignal.h" using Gallant::Signal2;
Signal2
, . — Signal0
Signal8
, .
public
:
Signal2< int, int > noteOn; Signal2< int, int > noteOff;
, int
. MIDIReceiver.cpp advance
:
// A key pressed later overrides any previously pressed key: if (noteNumber != mLastNoteNumber) { mLastNoteNumber = noteNumber; mLastFrequency = noteNumberToFrequency(mLastNoteNumber); mLastVelocity = velocity; // Emit a "note on" signal: noteOn(noteNumber, velocity); }
// If the last note was released, nothing should play: if (noteNumber == mLastNoteNumber) { mLastNoteNumber = -1; noteOff(noteNumber, mLastVelocity); }
— , — . mLastFrequency
-1
, : RELEASE. mLastVelocity
: , .
, , , . . .
mEnvelopeGenerator
. - onNoteOn
onNoteOff
EnvelopeGenerator
. , EnvelopeGenerator
. , . enterStage
- . - private
Synthesis
:
inline void onNoteOn(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); }; inline void onNoteOff(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); };
.
, Synthesis.cpp :
mMIDIReceiver.noteOn.Connect(this, &Synthesis::onNoteOn); mMIDIReceiver.noteOff.Connect(this, &Synthesis::onNoteOff);
— , — -.
ProcessDoubleReplacing
. if
, , .
!
. . DECAY, RELEASE, . stageValues
.
, , . GUI - :

!
.
:
martin-finke.de/blog/articles/audio-plugins-011-envelopes
. , : , , .
( ):
void EnvelopeGenerator::calculateMultiplier(double startLevel, double endLevel, unsigned long long lengthInSamples) { multiplier = 1.0 + (log(endLevel) - log(startLevel)) / (lengthInSamples); }
, . , startLevel
endLevel
lengthInSamples
multiplier
, . currentLevel
. log()
.
enterStage
:
void EnvelopeGenerator::enterStage(EnvelopeStage newStage) { currentStage = newStage; currentSampleIndex = 0; if (currentStage == ENVELOPE_STAGE_OFF || currentStage == ENVELOPE_STAGE_SUSTAIN) { nextStageSampleIndex = 0; } else { nextStageSampleIndex = stageValue[currentStage] * sampleRate; } switch (newStage) { case ENVELOPE_STAGE_OFF: currentLevel = 0.0; multiplier = 1.0; break; case ENVELOPE_STAGE_ATTACK: currentLevel = minimumLevel; calculateMultiplier(currentLevel, 1.0, nextStageSampleIndex); break; case ENVELOPE_STAGE_DECAY: currentLevel = 1.0; calculateMultiplier(currentLevel, fmax(stageValue[ENVELOPE_STAGE_SUSTAIN], minimumLevel), nextStageSampleIndex); break; case ENVELOPE_STAGE_SUSTAIN: currentLevel = stageValue[ENVELOPE_STAGE_SUSTAIN]; multiplier = 1.0; break; case ENVELOPE_STAGE_RELEASE: // We could go from ATTACK/DECAY to RELEASE, // so we're not changing currentLevel here. calculateMultiplier(currentLevel, minimumLevel, nextStageSampleIndex); break; default: break; } }
currentStage
currentSampleIndex
. . , , . stageValue[currentStage]
double
( ), , , . Switch
. OFF ( , ). ATTACK minimumLevel
, multiplier
currentLevel
1.0
. DECAY stageValue[ENVELOPE_STAGE_SUSTAIN]
, fmax
, . RELEASE currentLevel
, , minimumLevel
. , RELEASE currentLevel
, . . SUSTAIN, ATTACK DECAY. , SUSTAIN , stageValue[ENVELOPE_STAGE_SUSTAIN]
, . currentLevel
.
:
void EnvelopeGenerator::setSampleRate(double newSampleRate) { sampleRate = newSampleRate; }
private
Synthesis
( ) :
EnvelopeGenerator mEnvelopeGenerator;
#include "EnvelopeGenerator.h"
.
, ProcessDoubleReplacing
Synthesis.cpp leftOutput[i]
:
// leftOutput[i] = rightOutput[i] = mOscillator.nextSample() * velocity / 127.0; if (mEnvelopeGenerator.getCurrentStage() == EnvelopeGenerator::ENVELOPE_STAGE_OFF) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); } if (mEnvelopeGenerator.getCurrentStage() == EnvelopeGenerator::ENVELOPE_STAGE_SUSTAIN) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); } leftOutput[i] = rightOutput[i] = mOscillator.nextSample() * mEnvelopeGenerator.nextSample() * velocity / 127.0;
. : OFF ATTACK, SUSTAIN RELEASE. .
mEnvelopeGenerator
. Synthesis::Reset()
:
mEnvelopeGenerator.setSampleRate(GetSampleRate());
. - — ! .
Note On/Off
, (, - , ), : — ATTACK, — RELEASE. mMIDIReceiver
note on/off, , - .
#include EnvelopeGenerator.h
MIDIReceiver.h . , Synthesis.h mEnvelopeGenerator
mMIDIReceiver
, . MIDIReceiver
enterStage
note on/off.
, . . MIDIReceiver
EnvelopeGenerator
. , MIDIReceiver
, EnvelopeGenerator
.
. Qt. , , , . , . , setText()
, . , , , . , , . ( Oscillator
, EnvelopeGenerator
, MIDIReceiver
). , . . Synthesis
.
Qt . Signals . . Signal.h GallantSignal.h , . Delegate.h GallantSignal.h ( , , “Copy items into destination group's folder”) Xcode. VisualStudio , Solution Explorer.
, MIDIReceiver
, . MIDIReceiver.h :
#include "GallantSignal.h" using Gallant::Signal2;
Signal2
, . — Signal0
Signal8
, .
public
:
Signal2< int, int > noteOn; Signal2< int, int > noteOff;
, int
. MIDIReceiver.cpp advance
:
// A key pressed later overrides any previously pressed key: if (noteNumber != mLastNoteNumber) { mLastNoteNumber = noteNumber; mLastFrequency = noteNumberToFrequency(mLastNoteNumber); mLastVelocity = velocity; // Emit a "note on" signal: noteOn(noteNumber, velocity); }
// If the last note was released, nothing should play: if (noteNumber == mLastNoteNumber) { mLastNoteNumber = -1; noteOff(noteNumber, mLastVelocity); }
— , — . mLastFrequency
-1
, : RELEASE. mLastVelocity
: , .
, , , . . .
mEnvelopeGenerator
. - onNoteOn
onNoteOff
EnvelopeGenerator
. , EnvelopeGenerator
. , . enterStage
- . - private
Synthesis
:
inline void onNoteOn(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); }; inline void onNoteOff(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); };
.
, Synthesis.cpp :
mMIDIReceiver.noteOn.Connect(this, &Synthesis::onNoteOn); mMIDIReceiver.noteOff.Connect(this, &Synthesis::onNoteOff);
— , — -.
ProcessDoubleReplacing
. if
, , .
!
. . DECAY, RELEASE, . stageValues
.
, , . GUI - :
!
.
:
martin-finke.de/blog/articles/audio-plugins-011-envelopes
. , : , , .
( ):
void EnvelopeGenerator::calculateMultiplier(double startLevel, double endLevel, unsigned long long lengthInSamples) { multiplier = 1.0 + (log(endLevel) - log(startLevel)) / (lengthInSamples); }
, . , startLevel
endLevel
lengthInSamples
multiplier
, . currentLevel
. log()
.
enterStage
:
void EnvelopeGenerator::enterStage(EnvelopeStage newStage) { currentStage = newStage; currentSampleIndex = 0; if (currentStage == ENVELOPE_STAGE_OFF || currentStage == ENVELOPE_STAGE_SUSTAIN) { nextStageSampleIndex = 0; } else { nextStageSampleIndex = stageValue[currentStage] * sampleRate; } switch (newStage) { case ENVELOPE_STAGE_OFF: currentLevel = 0.0; multiplier = 1.0; break; case ENVELOPE_STAGE_ATTACK: currentLevel = minimumLevel; calculateMultiplier(currentLevel, 1.0, nextStageSampleIndex); break; case ENVELOPE_STAGE_DECAY: currentLevel = 1.0; calculateMultiplier(currentLevel, fmax(stageValue[ENVELOPE_STAGE_SUSTAIN], minimumLevel), nextStageSampleIndex); break; case ENVELOPE_STAGE_SUSTAIN: currentLevel = stageValue[ENVELOPE_STAGE_SUSTAIN]; multiplier = 1.0; break; case ENVELOPE_STAGE_RELEASE: // We could go from ATTACK/DECAY to RELEASE, // so we're not changing currentLevel here. calculateMultiplier(currentLevel, minimumLevel, nextStageSampleIndex); break; default: break; } }
currentStage
currentSampleIndex
. . , , . stageValue[currentStage]
double
( ), , , . Switch
. OFF ( , ). ATTACK minimumLevel
, multiplier
currentLevel
1.0
. DECAY stageValue[ENVELOPE_STAGE_SUSTAIN]
, fmax
, . RELEASE currentLevel
, , minimumLevel
. , RELEASE currentLevel
, . . SUSTAIN, ATTACK DECAY. , SUSTAIN , stageValue[ENVELOPE_STAGE_SUSTAIN]
, . currentLevel
.
:
void EnvelopeGenerator::setSampleRate(double newSampleRate) { sampleRate = newSampleRate; }
private
Synthesis
( ) :
EnvelopeGenerator mEnvelopeGenerator;
#include "EnvelopeGenerator.h"
.
, ProcessDoubleReplacing
Synthesis.cpp leftOutput[i]
:
// leftOutput[i] = rightOutput[i] = mOscillator.nextSample() * velocity / 127.0; if (mEnvelopeGenerator.getCurrentStage() == EnvelopeGenerator::ENVELOPE_STAGE_OFF) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); } if (mEnvelopeGenerator.getCurrentStage() == EnvelopeGenerator::ENVELOPE_STAGE_SUSTAIN) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); } leftOutput[i] = rightOutput[i] = mOscillator.nextSample() * mEnvelopeGenerator.nextSample() * velocity / 127.0;
. : OFF ATTACK, SUSTAIN RELEASE. .
mEnvelopeGenerator
. Synthesis::Reset()
:
mEnvelopeGenerator.setSampleRate(GetSampleRate());
. - — ! .
Note On/Off
, (, - , ), : — ATTACK, — RELEASE. mMIDIReceiver
note on/off, , - .
#include EnvelopeGenerator.h
MIDIReceiver.h . , Synthesis.h mEnvelopeGenerator
mMIDIReceiver
, . MIDIReceiver
enterStage
note on/off.
, . . MIDIReceiver
EnvelopeGenerator
. , MIDIReceiver
, EnvelopeGenerator
.
. Qt. , , , . , . , setText()
, . , , , . , , . ( Oscillator
, EnvelopeGenerator
, MIDIReceiver
). , . . Synthesis
.
Qt . Signals . . Signal.h GallantSignal.h , . Delegate.h GallantSignal.h ( , , “Copy items into destination group's folder”) Xcode. VisualStudio , Solution Explorer.
, MIDIReceiver
, . MIDIReceiver.h :
#include "GallantSignal.h" using Gallant::Signal2;
Signal2
, . — Signal0
Signal8
, .
public
:
Signal2< int, int > noteOn; Signal2< int, int > noteOff;
, int
. MIDIReceiver.cpp advance
:
// A key pressed later overrides any previously pressed key: if (noteNumber != mLastNoteNumber) { mLastNoteNumber = noteNumber; mLastFrequency = noteNumberToFrequency(mLastNoteNumber); mLastVelocity = velocity; // Emit a "note on" signal: noteOn(noteNumber, velocity); }
// If the last note was released, nothing should play: if (noteNumber == mLastNoteNumber) { mLastNoteNumber = -1; noteOff(noteNumber, mLastVelocity); }
— , — . mLastFrequency
-1
, : RELEASE. mLastVelocity
: , .
, , , . . .
mEnvelopeGenerator
. - onNoteOn
onNoteOff
EnvelopeGenerator
. , EnvelopeGenerator
. , . enterStage
- . - private
Synthesis
:
inline void onNoteOn(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); }; inline void onNoteOff(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); };
.
, Synthesis.cpp :
mMIDIReceiver.noteOn.Connect(this, &Synthesis::onNoteOn); mMIDIReceiver.noteOff.Connect(this, &Synthesis::onNoteOff);
— , — -.
ProcessDoubleReplacing
. if
, , .
!
. . DECAY, RELEASE, . stageValues
.
, , . GUI - :

!
.
:
martin-finke.de/blog/articles/audio-plugins-011-envelopes
. , : , , .
( ):
void EnvelopeGenerator::calculateMultiplier(double startLevel, double endLevel, unsigned long long lengthInSamples) { multiplier = 1.0 + (log(endLevel) - log(startLevel)) / (lengthInSamples); }
, . , startLevel
endLevel
lengthInSamples
multiplier
, . currentLevel
. log()
.
enterStage
:
void EnvelopeGenerator::enterStage(EnvelopeStage newStage) { currentStage = newStage; currentSampleIndex = 0; if (currentStage == ENVELOPE_STAGE_OFF || currentStage == ENVELOPE_STAGE_SUSTAIN) { nextStageSampleIndex = 0; } else { nextStageSampleIndex = stageValue[currentStage] * sampleRate; } switch (newStage) { case ENVELOPE_STAGE_OFF: currentLevel = 0.0; multiplier = 1.0; break; case ENVELOPE_STAGE_ATTACK: currentLevel = minimumLevel; calculateMultiplier(currentLevel, 1.0, nextStageSampleIndex); break; case ENVELOPE_STAGE_DECAY: currentLevel = 1.0; calculateMultiplier(currentLevel, fmax(stageValue[ENVELOPE_STAGE_SUSTAIN], minimumLevel), nextStageSampleIndex); break; case ENVELOPE_STAGE_SUSTAIN: currentLevel = stageValue[ENVELOPE_STAGE_SUSTAIN]; multiplier = 1.0; break; case ENVELOPE_STAGE_RELEASE: // We could go from ATTACK/DECAY to RELEASE, // so we're not changing currentLevel here. calculateMultiplier(currentLevel, minimumLevel, nextStageSampleIndex); break; default: break; } }
currentStage
currentSampleIndex
. . , , . stageValue[currentStage]
double
( ), , , . Switch
. OFF ( , ). ATTACK minimumLevel
, multiplier
currentLevel
1.0
. DECAY stageValue[ENVELOPE_STAGE_SUSTAIN]
, fmax
, . RELEASE currentLevel
, , minimumLevel
. , RELEASE currentLevel
, . . SUSTAIN, ATTACK DECAY. , SUSTAIN , stageValue[ENVELOPE_STAGE_SUSTAIN]
, . currentLevel
.
:
void EnvelopeGenerator::setSampleRate(double newSampleRate) { sampleRate = newSampleRate; }
private
Synthesis
( ) :
EnvelopeGenerator mEnvelopeGenerator;
#include "EnvelopeGenerator.h"
.
, ProcessDoubleReplacing
Synthesis.cpp leftOutput[i]
:
// leftOutput[i] = rightOutput[i] = mOscillator.nextSample() * velocity / 127.0; if (mEnvelopeGenerator.getCurrentStage() == EnvelopeGenerator::ENVELOPE_STAGE_OFF) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); } if (mEnvelopeGenerator.getCurrentStage() == EnvelopeGenerator::ENVELOPE_STAGE_SUSTAIN) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); } leftOutput[i] = rightOutput[i] = mOscillator.nextSample() * mEnvelopeGenerator.nextSample() * velocity / 127.0;
. : OFF ATTACK, SUSTAIN RELEASE. .
mEnvelopeGenerator
. Synthesis::Reset()
:
mEnvelopeGenerator.setSampleRate(GetSampleRate());
. - — ! .
Note On/Off
, (, - , ), : — ATTACK, — RELEASE. mMIDIReceiver
note on/off, , - .
#include EnvelopeGenerator.h
MIDIReceiver.h . , Synthesis.h mEnvelopeGenerator
mMIDIReceiver
, . MIDIReceiver
enterStage
note on/off.
, . . MIDIReceiver
EnvelopeGenerator
. , MIDIReceiver
, EnvelopeGenerator
.
. Qt. , , , . , . , setText()
, . , , , . , , . ( Oscillator
, EnvelopeGenerator
, MIDIReceiver
). , . . Synthesis
.
Qt . Signals . . Signal.h GallantSignal.h , . Delegate.h GallantSignal.h ( , , “Copy items into destination group's folder”) Xcode. VisualStudio , Solution Explorer.
, MIDIReceiver
, . MIDIReceiver.h :
#include "GallantSignal.h" using Gallant::Signal2;
Signal2
, . — Signal0
Signal8
, .
public
:
Signal2< int, int > noteOn; Signal2< int, int > noteOff;
, int
. MIDIReceiver.cpp advance
:
// A key pressed later overrides any previously pressed key: if (noteNumber != mLastNoteNumber) { mLastNoteNumber = noteNumber; mLastFrequency = noteNumberToFrequency(mLastNoteNumber); mLastVelocity = velocity; // Emit a "note on" signal: noteOn(noteNumber, velocity); }
// If the last note was released, nothing should play: if (noteNumber == mLastNoteNumber) { mLastNoteNumber = -1; noteOff(noteNumber, mLastVelocity); }
— , — . mLastFrequency
-1
, : RELEASE. mLastVelocity
: , .
, , , . . .
mEnvelopeGenerator
. - onNoteOn
onNoteOff
EnvelopeGenerator
. , EnvelopeGenerator
. , . enterStage
- . - private
Synthesis
:
inline void onNoteOn(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); }; inline void onNoteOff(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); };
.
, Synthesis.cpp :
mMIDIReceiver.noteOn.Connect(this, &Synthesis::onNoteOn); mMIDIReceiver.noteOff.Connect(this, &Synthesis::onNoteOff);
— , — -.
ProcessDoubleReplacing
. if
, , .
!
. . DECAY, RELEASE, . stageValues
.
, , . GUI - :
!
.
:
martin-finke.de/blog/articles/audio-plugins-011-envelopes
. , : , , .
( ):
void EnvelopeGenerator::calculateMultiplier(double startLevel, double endLevel, unsigned long long lengthInSamples) { multiplier = 1.0 + (log(endLevel) - log(startLevel)) / (lengthInSamples); }
, . , startLevel
endLevel
lengthInSamples
multiplier
, . currentLevel
. log()
.
enterStage
:
void EnvelopeGenerator::enterStage(EnvelopeStage newStage) { currentStage = newStage; currentSampleIndex = 0; if (currentStage == ENVELOPE_STAGE_OFF || currentStage == ENVELOPE_STAGE_SUSTAIN) { nextStageSampleIndex = 0; } else { nextStageSampleIndex = stageValue[currentStage] * sampleRate; } switch (newStage) { case ENVELOPE_STAGE_OFF: currentLevel = 0.0; multiplier = 1.0; break; case ENVELOPE_STAGE_ATTACK: currentLevel = minimumLevel; calculateMultiplier(currentLevel, 1.0, nextStageSampleIndex); break; case ENVELOPE_STAGE_DECAY: currentLevel = 1.0; calculateMultiplier(currentLevel, fmax(stageValue[ENVELOPE_STAGE_SUSTAIN], minimumLevel), nextStageSampleIndex); break; case ENVELOPE_STAGE_SUSTAIN: currentLevel = stageValue[ENVELOPE_STAGE_SUSTAIN]; multiplier = 1.0; break; case ENVELOPE_STAGE_RELEASE: // We could go from ATTACK/DECAY to RELEASE, // so we're not changing currentLevel here. calculateMultiplier(currentLevel, minimumLevel, nextStageSampleIndex); break; default: break; } }
currentStage
currentSampleIndex
. . , , . stageValue[currentStage]
double
( ), , , . Switch
. OFF ( , ). ATTACK minimumLevel
, multiplier
currentLevel
1.0
. DECAY stageValue[ENVELOPE_STAGE_SUSTAIN]
, fmax
, . RELEASE currentLevel
, , minimumLevel
. , RELEASE currentLevel
, . . SUSTAIN, ATTACK DECAY. , SUSTAIN , stageValue[ENVELOPE_STAGE_SUSTAIN]
, . currentLevel
.
:
void EnvelopeGenerator::setSampleRate(double newSampleRate) { sampleRate = newSampleRate; }
private
Synthesis
( ) :
EnvelopeGenerator mEnvelopeGenerator;
#include "EnvelopeGenerator.h"
.
, ProcessDoubleReplacing
Synthesis.cpp leftOutput[i]
:
// leftOutput[i] = rightOutput[i] = mOscillator.nextSample() * velocity / 127.0; if (mEnvelopeGenerator.getCurrentStage() == EnvelopeGenerator::ENVELOPE_STAGE_OFF) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); } if (mEnvelopeGenerator.getCurrentStage() == EnvelopeGenerator::ENVELOPE_STAGE_SUSTAIN) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); } leftOutput[i] = rightOutput[i] = mOscillator.nextSample() * mEnvelopeGenerator.nextSample() * velocity / 127.0;
. : OFF ATTACK, SUSTAIN RELEASE. .
mEnvelopeGenerator
. Synthesis::Reset()
:
mEnvelopeGenerator.setSampleRate(GetSampleRate());
. - — ! .
Note On/Off
, (, - , ), : — ATTACK, — RELEASE. mMIDIReceiver
note on/off, , - .
#include EnvelopeGenerator.h
MIDIReceiver.h . , Synthesis.h mEnvelopeGenerator
mMIDIReceiver
, . MIDIReceiver
enterStage
note on/off.
, . . MIDIReceiver
EnvelopeGenerator
. , MIDIReceiver
, EnvelopeGenerator
.
. Qt. , , , . , . , setText()
, . , , , . , , . ( Oscillator
, EnvelopeGenerator
, MIDIReceiver
). , . . Synthesis
.
Qt . Signals . . Signal.h GallantSignal.h , . Delegate.h GallantSignal.h ( , , “Copy items into destination group's folder”) Xcode. VisualStudio , Solution Explorer.
, MIDIReceiver
, . MIDIReceiver.h :
#include "GallantSignal.h" using Gallant::Signal2;
Signal2
, . — Signal0
Signal8
, .
public
:
Signal2< int, int > noteOn; Signal2< int, int > noteOff;
, int
. MIDIReceiver.cpp advance
:
// A key pressed later overrides any previously pressed key: if (noteNumber != mLastNoteNumber) { mLastNoteNumber = noteNumber; mLastFrequency = noteNumberToFrequency(mLastNoteNumber); mLastVelocity = velocity; // Emit a "note on" signal: noteOn(noteNumber, velocity); }
// If the last note was released, nothing should play: if (noteNumber == mLastNoteNumber) { mLastNoteNumber = -1; noteOff(noteNumber, mLastVelocity); }
— , — . mLastFrequency
-1
, : RELEASE. mLastVelocity
: , .
, , , . . .
mEnvelopeGenerator
. - onNoteOn
onNoteOff
EnvelopeGenerator
. , EnvelopeGenerator
. , . enterStage
- . - private
Synthesis
:
inline void onNoteOn(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); }; inline void onNoteOff(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); };
.
, Synthesis.cpp :
mMIDIReceiver.noteOn.Connect(this, &Synthesis::onNoteOn); mMIDIReceiver.noteOff.Connect(this, &Synthesis::onNoteOff);
— , — -.
ProcessDoubleReplacing
. if
, , .
!
. . DECAY, RELEASE, . stageValues
.
, , . GUI - :

!
.
:
martin-finke.de/blog/articles/audio-plugins-011-envelopes
. , : , , .
( ):
void EnvelopeGenerator::calculateMultiplier(double startLevel, double endLevel, unsigned long long lengthInSamples) { multiplier = 1.0 + (log(endLevel) - log(startLevel)) / (lengthInSamples); }
, . , startLevel
endLevel
lengthInSamples
multiplier
, . currentLevel
. log()
.
enterStage
:
void EnvelopeGenerator::enterStage(EnvelopeStage newStage) { currentStage = newStage; currentSampleIndex = 0; if (currentStage == ENVELOPE_STAGE_OFF || currentStage == ENVELOPE_STAGE_SUSTAIN) { nextStageSampleIndex = 0; } else { nextStageSampleIndex = stageValue[currentStage] * sampleRate; } switch (newStage) { case ENVELOPE_STAGE_OFF: currentLevel = 0.0; multiplier = 1.0; break; case ENVELOPE_STAGE_ATTACK: currentLevel = minimumLevel; calculateMultiplier(currentLevel, 1.0, nextStageSampleIndex); break; case ENVELOPE_STAGE_DECAY: currentLevel = 1.0; calculateMultiplier(currentLevel, fmax(stageValue[ENVELOPE_STAGE_SUSTAIN], minimumLevel), nextStageSampleIndex); break; case ENVELOPE_STAGE_SUSTAIN: currentLevel = stageValue[ENVELOPE_STAGE_SUSTAIN]; multiplier = 1.0; break; case ENVELOPE_STAGE_RELEASE: // We could go from ATTACK/DECAY to RELEASE, // so we're not changing currentLevel here. calculateMultiplier(currentLevel, minimumLevel, nextStageSampleIndex); break; default: break; } }
currentStage
currentSampleIndex
. . , , . stageValue[currentStage]
double
( ), , , . Switch
. OFF ( , ). ATTACK minimumLevel
, multiplier
currentLevel
1.0
. DECAY stageValue[ENVELOPE_STAGE_SUSTAIN]
, fmax
, . RELEASE currentLevel
, , minimumLevel
. , RELEASE currentLevel
, . . SUSTAIN, ATTACK DECAY. , SUSTAIN , stageValue[ENVELOPE_STAGE_SUSTAIN]
, . currentLevel
.
:
void EnvelopeGenerator::setSampleRate(double newSampleRate) { sampleRate = newSampleRate; }
private
Synthesis
( ) :
EnvelopeGenerator mEnvelopeGenerator;
#include "EnvelopeGenerator.h"
.
, ProcessDoubleReplacing
Synthesis.cpp leftOutput[i]
:
// leftOutput[i] = rightOutput[i] = mOscillator.nextSample() * velocity / 127.0; if (mEnvelopeGenerator.getCurrentStage() == EnvelopeGenerator::ENVELOPE_STAGE_OFF) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); } if (mEnvelopeGenerator.getCurrentStage() == EnvelopeGenerator::ENVELOPE_STAGE_SUSTAIN) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); } leftOutput[i] = rightOutput[i] = mOscillator.nextSample() * mEnvelopeGenerator.nextSample() * velocity / 127.0;
. : OFF ATTACK, SUSTAIN RELEASE. .
mEnvelopeGenerator
. Synthesis::Reset()
:
mEnvelopeGenerator.setSampleRate(GetSampleRate());
. - — ! .
Note On/Off
, (, - , ), : — ATTACK, — RELEASE. mMIDIReceiver
note on/off, , - .
#include EnvelopeGenerator.h
MIDIReceiver.h . , Synthesis.h mEnvelopeGenerator
mMIDIReceiver
, . MIDIReceiver
enterStage
note on/off.
, . . MIDIReceiver
EnvelopeGenerator
. , MIDIReceiver
, EnvelopeGenerator
.
. Qt. , , , . , . , setText()
, . , , , . , , . ( Oscillator
, EnvelopeGenerator
, MIDIReceiver
). , . . Synthesis
.
Qt . Signals . . Signal.h GallantSignal.h , . Delegate.h GallantSignal.h ( , , “Copy items into destination group's folder”) Xcode. VisualStudio , Solution Explorer.
, MIDIReceiver
, . MIDIReceiver.h :
#include "GallantSignal.h" using Gallant::Signal2;
Signal2
, . — Signal0
Signal8
, .
public
:
Signal2< int, int > noteOn; Signal2< int, int > noteOff;
, int
. MIDIReceiver.cpp advance
:
// A key pressed later overrides any previously pressed key: if (noteNumber != mLastNoteNumber) { mLastNoteNumber = noteNumber; mLastFrequency = noteNumberToFrequency(mLastNoteNumber); mLastVelocity = velocity; // Emit a "note on" signal: noteOn(noteNumber, velocity); }
// If the last note was released, nothing should play: if (noteNumber == mLastNoteNumber) { mLastNoteNumber = -1; noteOff(noteNumber, mLastVelocity); }
— , — . mLastFrequency
-1
, : RELEASE. mLastVelocity
: , .
, , , . . .
mEnvelopeGenerator
. - onNoteOn
onNoteOff
EnvelopeGenerator
. , EnvelopeGenerator
. , . enterStage
- . - private
Synthesis
:
inline void onNoteOn(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); }; inline void onNoteOff(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); };
.
, Synthesis.cpp :
mMIDIReceiver.noteOn.Connect(this, &Synthesis::onNoteOn); mMIDIReceiver.noteOff.Connect(this, &Synthesis::onNoteOff);
— , — -.
ProcessDoubleReplacing
. if
, , .
!
. . DECAY, RELEASE, . stageValues
.
, , . GUI - :
!
.
:
martin-finke.de/blog/articles/audio-plugins-011-envelopes
. , : , , .
( ):
void EnvelopeGenerator::calculateMultiplier(double startLevel, double endLevel, unsigned long long lengthInSamples) { multiplier = 1.0 + (log(endLevel) - log(startLevel)) / (lengthInSamples); }
, . , startLevel
endLevel
lengthInSamples
multiplier
, . currentLevel
. log()
.
enterStage
:
void EnvelopeGenerator::enterStage(EnvelopeStage newStage) { currentStage = newStage; currentSampleIndex = 0; if (currentStage == ENVELOPE_STAGE_OFF || currentStage == ENVELOPE_STAGE_SUSTAIN) { nextStageSampleIndex = 0; } else { nextStageSampleIndex = stageValue[currentStage] * sampleRate; } switch (newStage) { case ENVELOPE_STAGE_OFF: currentLevel = 0.0; multiplier = 1.0; break; case ENVELOPE_STAGE_ATTACK: currentLevel = minimumLevel; calculateMultiplier(currentLevel, 1.0, nextStageSampleIndex); break; case ENVELOPE_STAGE_DECAY: currentLevel = 1.0; calculateMultiplier(currentLevel, fmax(stageValue[ENVELOPE_STAGE_SUSTAIN], minimumLevel), nextStageSampleIndex); break; case ENVELOPE_STAGE_SUSTAIN: currentLevel = stageValue[ENVELOPE_STAGE_SUSTAIN]; multiplier = 1.0; break; case ENVELOPE_STAGE_RELEASE: // We could go from ATTACK/DECAY to RELEASE, // so we're not changing currentLevel here. calculateMultiplier(currentLevel, minimumLevel, nextStageSampleIndex); break; default: break; } }
currentStage
currentSampleIndex
. . , , . stageValue[currentStage]
double
( ), , , . Switch
. OFF ( , ). ATTACK minimumLevel
, multiplier
currentLevel
1.0
. DECAY stageValue[ENVELOPE_STAGE_SUSTAIN]
, fmax
, . RELEASE currentLevel
, , minimumLevel
. , RELEASE currentLevel
, . . SUSTAIN, ATTACK DECAY. , SUSTAIN , stageValue[ENVELOPE_STAGE_SUSTAIN]
, . currentLevel
.
:
void EnvelopeGenerator::setSampleRate(double newSampleRate) { sampleRate = newSampleRate; }
private
Synthesis
( ) :
EnvelopeGenerator mEnvelopeGenerator;
#include "EnvelopeGenerator.h"
.
, ProcessDoubleReplacing
Synthesis.cpp leftOutput[i]
:
// leftOutput[i] = rightOutput[i] = mOscillator.nextSample() * velocity / 127.0; if (mEnvelopeGenerator.getCurrentStage() == EnvelopeGenerator::ENVELOPE_STAGE_OFF) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); } if (mEnvelopeGenerator.getCurrentStage() == EnvelopeGenerator::ENVELOPE_STAGE_SUSTAIN) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); } leftOutput[i] = rightOutput[i] = mOscillator.nextSample() * mEnvelopeGenerator.nextSample() * velocity / 127.0;
. : OFF ATTACK, SUSTAIN RELEASE. .
mEnvelopeGenerator
. Synthesis::Reset()
:
mEnvelopeGenerator.setSampleRate(GetSampleRate());
. - — ! .
Note On/Off
, (, - , ), : — ATTACK, — RELEASE. mMIDIReceiver
note on/off, , - .
#include EnvelopeGenerator.h
MIDIReceiver.h . , Synthesis.h mEnvelopeGenerator
mMIDIReceiver
, . MIDIReceiver
enterStage
note on/off.
, . . MIDIReceiver
EnvelopeGenerator
. , MIDIReceiver
, EnvelopeGenerator
.
. Qt. , , , . , . , setText()
, . , , , . , , . ( Oscillator
, EnvelopeGenerator
, MIDIReceiver
). , . . Synthesis
.
Qt . Signals . . Signal.h GallantSignal.h , . Delegate.h GallantSignal.h ( , , “Copy items into destination group's folder”) Xcode. VisualStudio , Solution Explorer.
, MIDIReceiver
, . MIDIReceiver.h :
#include "GallantSignal.h" using Gallant::Signal2;
Signal2
, . — Signal0
Signal8
, .
public
:
Signal2< int, int > noteOn; Signal2< int, int > noteOff;
, int
. MIDIReceiver.cpp advance
:
// A key pressed later overrides any previously pressed key: if (noteNumber != mLastNoteNumber) { mLastNoteNumber = noteNumber; mLastFrequency = noteNumberToFrequency(mLastNoteNumber); mLastVelocity = velocity; // Emit a "note on" signal: noteOn(noteNumber, velocity); }
// If the last note was released, nothing should play: if (noteNumber == mLastNoteNumber) { mLastNoteNumber = -1; noteOff(noteNumber, mLastVelocity); }
— , — . mLastFrequency
-1
, : RELEASE. mLastVelocity
: , .
, , , . . .
mEnvelopeGenerator
. - onNoteOn
onNoteOff
EnvelopeGenerator
. , EnvelopeGenerator
. , . enterStage
- . - private
Synthesis
:
inline void onNoteOn(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); }; inline void onNoteOff(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); };
.
, Synthesis.cpp :
mMIDIReceiver.noteOn.Connect(this, &Synthesis::onNoteOn); mMIDIReceiver.noteOff.Connect(this, &Synthesis::onNoteOff);
— , — -.
ProcessDoubleReplacing
. if
, , .
!
. . DECAY, RELEASE, . stageValues
.
, , . GUI - :

!
.
:
martin-finke.de/blog/articles/audio-plugins-011-envelopes
. , : , , .
( ):
void EnvelopeGenerator::calculateMultiplier(double startLevel, double endLevel, unsigned long long lengthInSamples) { multiplier = 1.0 + (log(endLevel) - log(startLevel)) / (lengthInSamples); }
, . , startLevel
endLevel
lengthInSamples
multiplier
, . currentLevel
. log()
.
enterStage
:
void EnvelopeGenerator::enterStage(EnvelopeStage newStage) { currentStage = newStage; currentSampleIndex = 0; if (currentStage == ENVELOPE_STAGE_OFF || currentStage == ENVELOPE_STAGE_SUSTAIN) { nextStageSampleIndex = 0; } else { nextStageSampleIndex = stageValue[currentStage] * sampleRate; } switch (newStage) { case ENVELOPE_STAGE_OFF: currentLevel = 0.0; multiplier = 1.0; break; case ENVELOPE_STAGE_ATTACK: currentLevel = minimumLevel; calculateMultiplier(currentLevel, 1.0, nextStageSampleIndex); break; case ENVELOPE_STAGE_DECAY: currentLevel = 1.0; calculateMultiplier(currentLevel, fmax(stageValue[ENVELOPE_STAGE_SUSTAIN], minimumLevel), nextStageSampleIndex); break; case ENVELOPE_STAGE_SUSTAIN: currentLevel = stageValue[ENVELOPE_STAGE_SUSTAIN]; multiplier = 1.0; break; case ENVELOPE_STAGE_RELEASE: // We could go from ATTACK/DECAY to RELEASE, // so we're not changing currentLevel here. calculateMultiplier(currentLevel, minimumLevel, nextStageSampleIndex); break; default: break; } }
currentStage
currentSampleIndex
. . , , . stageValue[currentStage]
double
( ), , , . Switch
. OFF ( , ). ATTACK minimumLevel
, multiplier
currentLevel
1.0
. DECAY stageValue[ENVELOPE_STAGE_SUSTAIN]
, fmax
, . RELEASE currentLevel
, , minimumLevel
. , RELEASE currentLevel
, . . SUSTAIN, ATTACK DECAY. , SUSTAIN , stageValue[ENVELOPE_STAGE_SUSTAIN]
, . currentLevel
.
:
void EnvelopeGenerator::setSampleRate(double newSampleRate) { sampleRate = newSampleRate; }
private
Synthesis
( ) :
EnvelopeGenerator mEnvelopeGenerator;
#include "EnvelopeGenerator.h"
.
, ProcessDoubleReplacing
Synthesis.cpp leftOutput[i]
:
// leftOutput[i] = rightOutput[i] = mOscillator.nextSample() * velocity / 127.0; if (mEnvelopeGenerator.getCurrentStage() == EnvelopeGenerator::ENVELOPE_STAGE_OFF) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); } if (mEnvelopeGenerator.getCurrentStage() == EnvelopeGenerator::ENVELOPE_STAGE_SUSTAIN) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); } leftOutput[i] = rightOutput[i] = mOscillator.nextSample() * mEnvelopeGenerator.nextSample() * velocity / 127.0;
. : OFF ATTACK, SUSTAIN RELEASE. .
mEnvelopeGenerator
. Synthesis::Reset()
:
mEnvelopeGenerator.setSampleRate(GetSampleRate());
. - — ! .
Note On/Off
, (, - , ), : — ATTACK, — RELEASE. mMIDIReceiver
note on/off, , - .
#include EnvelopeGenerator.h
MIDIReceiver.h . , Synthesis.h mEnvelopeGenerator
mMIDIReceiver
, . MIDIReceiver
enterStage
note on/off.
, . . MIDIReceiver
EnvelopeGenerator
. , MIDIReceiver
, EnvelopeGenerator
.
. Qt. , , , . , . , setText()
, . , , , . , , . ( Oscillator
, EnvelopeGenerator
, MIDIReceiver
). , . . Synthesis
.
Qt . Signals . . Signal.h GallantSignal.h , . Delegate.h GallantSignal.h ( , , “Copy items into destination group's folder”) Xcode. VisualStudio , Solution Explorer.
, MIDIReceiver
, . MIDIReceiver.h :
#include "GallantSignal.h" using Gallant::Signal2;
Signal2
, . — Signal0
Signal8
, .
public
:
Signal2< int, int > noteOn; Signal2< int, int > noteOff;
, int
. MIDIReceiver.cpp advance
:
// A key pressed later overrides any previously pressed key: if (noteNumber != mLastNoteNumber) { mLastNoteNumber = noteNumber; mLastFrequency = noteNumberToFrequency(mLastNoteNumber); mLastVelocity = velocity; // Emit a "note on" signal: noteOn(noteNumber, velocity); }
// If the last note was released, nothing should play: if (noteNumber == mLastNoteNumber) { mLastNoteNumber = -1; noteOff(noteNumber, mLastVelocity); }
— , — . mLastFrequency
-1
, : RELEASE. mLastVelocity
: , .
, , , . . .
mEnvelopeGenerator
. - onNoteOn
onNoteOff
EnvelopeGenerator
. , EnvelopeGenerator
. , . enterStage
- . - private
Synthesis
:
inline void onNoteOn(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); }; inline void onNoteOff(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); };
.
, Synthesis.cpp :
mMIDIReceiver.noteOn.Connect(this, &Synthesis::onNoteOn); mMIDIReceiver.noteOff.Connect(this, &Synthesis::onNoteOff);
— , — -.
ProcessDoubleReplacing
. if
, , .
!
. . DECAY, RELEASE, . stageValues
.
, , . GUI - :

!
.
:
martin-finke.de/blog/articles/audio-plugins-011-envelopes
. , : , , .
( ):
void EnvelopeGenerator::calculateMultiplier(double startLevel, double endLevel, unsigned long long lengthInSamples) { multiplier = 1.0 + (log(endLevel) - log(startLevel)) / (lengthInSamples); }
, . , startLevel
endLevel
lengthInSamples
multiplier
, . currentLevel
. log()
.
enterStage
:
void EnvelopeGenerator::enterStage(EnvelopeStage newStage) { currentStage = newStage; currentSampleIndex = 0; if (currentStage == ENVELOPE_STAGE_OFF || currentStage == ENVELOPE_STAGE_SUSTAIN) { nextStageSampleIndex = 0; } else { nextStageSampleIndex = stageValue[currentStage] * sampleRate; } switch (newStage) { case ENVELOPE_STAGE_OFF: currentLevel = 0.0; multiplier = 1.0; break; case ENVELOPE_STAGE_ATTACK: currentLevel = minimumLevel; calculateMultiplier(currentLevel, 1.0, nextStageSampleIndex); break; case ENVELOPE_STAGE_DECAY: currentLevel = 1.0; calculateMultiplier(currentLevel, fmax(stageValue[ENVELOPE_STAGE_SUSTAIN], minimumLevel), nextStageSampleIndex); break; case ENVELOPE_STAGE_SUSTAIN: currentLevel = stageValue[ENVELOPE_STAGE_SUSTAIN]; multiplier = 1.0; break; case ENVELOPE_STAGE_RELEASE: // We could go from ATTACK/DECAY to RELEASE, // so we're not changing currentLevel here. calculateMultiplier(currentLevel, minimumLevel, nextStageSampleIndex); break; default: break; } }
currentStage
currentSampleIndex
. . , , . stageValue[currentStage]
double
( ), , , . Switch
. OFF ( , ). ATTACK minimumLevel
, multiplier
currentLevel
1.0
. DECAY stageValue[ENVELOPE_STAGE_SUSTAIN]
, fmax
, . RELEASE currentLevel
, , minimumLevel
. , RELEASE currentLevel
, . . SUSTAIN, ATTACK DECAY. , SUSTAIN , stageValue[ENVELOPE_STAGE_SUSTAIN]
, . currentLevel
.
:
void EnvelopeGenerator::setSampleRate(double newSampleRate) { sampleRate = newSampleRate; }
private
Synthesis
( ) :
EnvelopeGenerator mEnvelopeGenerator;
#include "EnvelopeGenerator.h"
.
, ProcessDoubleReplacing
Synthesis.cpp leftOutput[i]
:
// leftOutput[i] = rightOutput[i] = mOscillator.nextSample() * velocity / 127.0; if (mEnvelopeGenerator.getCurrentStage() == EnvelopeGenerator::ENVELOPE_STAGE_OFF) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); } if (mEnvelopeGenerator.getCurrentStage() == EnvelopeGenerator::ENVELOPE_STAGE_SUSTAIN) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); } leftOutput[i] = rightOutput[i] = mOscillator.nextSample() * mEnvelopeGenerator.nextSample() * velocity / 127.0;
. : OFF ATTACK, SUSTAIN RELEASE. .
mEnvelopeGenerator
. Synthesis::Reset()
:
mEnvelopeGenerator.setSampleRate(GetSampleRate());
. - — ! .
Note On/Off
, (, - , ), : — ATTACK, — RELEASE. mMIDIReceiver
note on/off, , - .
#include EnvelopeGenerator.h
MIDIReceiver.h . , Synthesis.h mEnvelopeGenerator
mMIDIReceiver
, . MIDIReceiver
enterStage
note on/off.
, . . MIDIReceiver
EnvelopeGenerator
. , MIDIReceiver
, EnvelopeGenerator
.
. Qt. , , , . , . , setText()
, . , , , . , , . ( Oscillator
, EnvelopeGenerator
, MIDIReceiver
). , . . Synthesis
.
Qt . Signals . . Signal.h GallantSignal.h , . Delegate.h GallantSignal.h ( , , “Copy items into destination group's folder”) Xcode. VisualStudio , Solution Explorer.
, MIDIReceiver
, . MIDIReceiver.h :
#include "GallantSignal.h" using Gallant::Signal2;
Signal2
, . — Signal0
Signal8
, .
public
:
Signal2< int, int > noteOn; Signal2< int, int > noteOff;
, int
. MIDIReceiver.cpp advance
:
// A key pressed later overrides any previously pressed key: if (noteNumber != mLastNoteNumber) { mLastNoteNumber = noteNumber; mLastFrequency = noteNumberToFrequency(mLastNoteNumber); mLastVelocity = velocity; // Emit a "note on" signal: noteOn(noteNumber, velocity); }
// If the last note was released, nothing should play: if (noteNumber == mLastNoteNumber) { mLastNoteNumber = -1; noteOff(noteNumber, mLastVelocity); }
— , — . mLastFrequency
-1
, : RELEASE. mLastVelocity
: , .
, , , . . .
mEnvelopeGenerator
. - onNoteOn
onNoteOff
EnvelopeGenerator
. , EnvelopeGenerator
. , . enterStage
- . - private
Synthesis
:
inline void onNoteOn(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); }; inline void onNoteOff(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); };
.
, Synthesis.cpp :
mMIDIReceiver.noteOn.Connect(this, &Synthesis::onNoteOn); mMIDIReceiver.noteOff.Connect(this, &Synthesis::onNoteOff);
— , — -.
ProcessDoubleReplacing
. if
, , .
!
. . DECAY, RELEASE, . stageValues
.
, , . GUI - :

!
.
:
martin-finke.de/blog/articles/audio-plugins-011-envelopes
. , : , , .
( ):
void EnvelopeGenerator::calculateMultiplier(double startLevel, double endLevel, unsigned long long lengthInSamples) { multiplier = 1.0 + (log(endLevel) - log(startLevel)) / (lengthInSamples); }
, . , startLevel
endLevel
lengthInSamples
multiplier
, . currentLevel
. log()
.
enterStage
:
void EnvelopeGenerator::enterStage(EnvelopeStage newStage) { currentStage = newStage; currentSampleIndex = 0; if (currentStage == ENVELOPE_STAGE_OFF || currentStage == ENVELOPE_STAGE_SUSTAIN) { nextStageSampleIndex = 0; } else { nextStageSampleIndex = stageValue[currentStage] * sampleRate; } switch (newStage) { case ENVELOPE_STAGE_OFF: currentLevel = 0.0; multiplier = 1.0; break; case ENVELOPE_STAGE_ATTACK: currentLevel = minimumLevel; calculateMultiplier(currentLevel, 1.0, nextStageSampleIndex); break; case ENVELOPE_STAGE_DECAY: currentLevel = 1.0; calculateMultiplier(currentLevel, fmax(stageValue[ENVELOPE_STAGE_SUSTAIN], minimumLevel), nextStageSampleIndex); break; case ENVELOPE_STAGE_SUSTAIN: currentLevel = stageValue[ENVELOPE_STAGE_SUSTAIN]; multiplier = 1.0; break; case ENVELOPE_STAGE_RELEASE: // We could go from ATTACK/DECAY to RELEASE, // so we're not changing currentLevel here. calculateMultiplier(currentLevel, minimumLevel, nextStageSampleIndex); break; default: break; } }
currentStage
currentSampleIndex
. . , , . stageValue[currentStage]
double
( ), , , . Switch
. OFF ( , ). ATTACK minimumLevel
, multiplier
currentLevel
1.0
. DECAY stageValue[ENVELOPE_STAGE_SUSTAIN]
, fmax
, . RELEASE currentLevel
, , minimumLevel
. , RELEASE currentLevel
, . . SUSTAIN, ATTACK DECAY. , SUSTAIN , stageValue[ENVELOPE_STAGE_SUSTAIN]
, . currentLevel
.
:
void EnvelopeGenerator::setSampleRate(double newSampleRate) { sampleRate = newSampleRate; }
private
Synthesis
( ) :
EnvelopeGenerator mEnvelopeGenerator;
#include "EnvelopeGenerator.h"
.
, ProcessDoubleReplacing
Synthesis.cpp leftOutput[i]
:
// leftOutput[i] = rightOutput[i] = mOscillator.nextSample() * velocity / 127.0; if (mEnvelopeGenerator.getCurrentStage() == EnvelopeGenerator::ENVELOPE_STAGE_OFF) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); } if (mEnvelopeGenerator.getCurrentStage() == EnvelopeGenerator::ENVELOPE_STAGE_SUSTAIN) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); } leftOutput[i] = rightOutput[i] = mOscillator.nextSample() * mEnvelopeGenerator.nextSample() * velocity / 127.0;
. : OFF ATTACK, SUSTAIN RELEASE. .
mEnvelopeGenerator
. Synthesis::Reset()
:
mEnvelopeGenerator.setSampleRate(GetSampleRate());
. - — ! .
Note On/Off
, (, - , ), : — ATTACK, — RELEASE. mMIDIReceiver
note on/off, , - .
#include EnvelopeGenerator.h
MIDIReceiver.h . , Synthesis.h mEnvelopeGenerator
mMIDIReceiver
, . MIDIReceiver
enterStage
note on/off.
, . . MIDIReceiver
EnvelopeGenerator
. , MIDIReceiver
, EnvelopeGenerator
.
. Qt. , , , . , . , setText()
, . , , , . , , . ( Oscillator
, EnvelopeGenerator
, MIDIReceiver
). , . . Synthesis
.
Qt . Signals . . Signal.h GallantSignal.h , . Delegate.h GallantSignal.h ( , , “Copy items into destination group's folder”) Xcode. VisualStudio , Solution Explorer.
, MIDIReceiver
, . MIDIReceiver.h :
#include "GallantSignal.h" using Gallant::Signal2;
Signal2
, . — Signal0
Signal8
, .
public
:
Signal2< int, int > noteOn; Signal2< int, int > noteOff;
, int
. MIDIReceiver.cpp advance
:
// A key pressed later overrides any previously pressed key: if (noteNumber != mLastNoteNumber) { mLastNoteNumber = noteNumber; mLastFrequency = noteNumberToFrequency(mLastNoteNumber); mLastVelocity = velocity; // Emit a "note on" signal: noteOn(noteNumber, velocity); }
// If the last note was released, nothing should play: if (noteNumber == mLastNoteNumber) { mLastNoteNumber = -1; noteOff(noteNumber, mLastVelocity); }
— , — . mLastFrequency
-1
, : RELEASE. mLastVelocity
: , .
, , , . . .
mEnvelopeGenerator
. - onNoteOn
onNoteOff
EnvelopeGenerator
. , EnvelopeGenerator
. , . enterStage
- . - private
Synthesis
:
inline void onNoteOn(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); }; inline void onNoteOff(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); };
.
, Synthesis.cpp :
mMIDIReceiver.noteOn.Connect(this, &Synthesis::onNoteOn); mMIDIReceiver.noteOff.Connect(this, &Synthesis::onNoteOff);
— , — -.
ProcessDoubleReplacing
. if
, , .
!
. . DECAY, RELEASE, . stageValues
.
, , . GUI - :
!
.
:
martin-finke.de/blog/articles/audio-plugins-011-envelopes
. , : , , .
( ):
void EnvelopeGenerator::calculateMultiplier(double startLevel, double endLevel, unsigned long long lengthInSamples) { multiplier = 1.0 + (log(endLevel) - log(startLevel)) / (lengthInSamples); }
, . , startLevel
endLevel
lengthInSamples
multiplier
, . currentLevel
. log()
.
enterStage
:
void EnvelopeGenerator::enterStage(EnvelopeStage newStage) { currentStage = newStage; currentSampleIndex = 0; if (currentStage == ENVELOPE_STAGE_OFF || currentStage == ENVELOPE_STAGE_SUSTAIN) { nextStageSampleIndex = 0; } else { nextStageSampleIndex = stageValue[currentStage] * sampleRate; } switch (newStage) { case ENVELOPE_STAGE_OFF: currentLevel = 0.0; multiplier = 1.0; break; case ENVELOPE_STAGE_ATTACK: currentLevel = minimumLevel; calculateMultiplier(currentLevel, 1.0, nextStageSampleIndex); break; case ENVELOPE_STAGE_DECAY: currentLevel = 1.0; calculateMultiplier(currentLevel, fmax(stageValue[ENVELOPE_STAGE_SUSTAIN], minimumLevel), nextStageSampleIndex); break; case ENVELOPE_STAGE_SUSTAIN: currentLevel = stageValue[ENVELOPE_STAGE_SUSTAIN]; multiplier = 1.0; break; case ENVELOPE_STAGE_RELEASE: // We could go from ATTACK/DECAY to RELEASE, // so we're not changing currentLevel here. calculateMultiplier(currentLevel, minimumLevel, nextStageSampleIndex); break; default: break; } }
currentStage
currentSampleIndex
. . , , . stageValue[currentStage]
double
( ), , , . Switch
. OFF ( , ). ATTACK minimumLevel
, multiplier
currentLevel
1.0
. DECAY stageValue[ENVELOPE_STAGE_SUSTAIN]
, fmax
, . RELEASE currentLevel
, , minimumLevel
. , RELEASE currentLevel
, . . SUSTAIN, ATTACK DECAY. , SUSTAIN , stageValue[ENVELOPE_STAGE_SUSTAIN]
, . currentLevel
.
:
void EnvelopeGenerator::setSampleRate(double newSampleRate) { sampleRate = newSampleRate; }
private
Synthesis
( ) :
EnvelopeGenerator mEnvelopeGenerator;
#include "EnvelopeGenerator.h"
.
, ProcessDoubleReplacing
Synthesis.cpp leftOutput[i]
:
// leftOutput[i] = rightOutput[i] = mOscillator.nextSample() * velocity / 127.0; if (mEnvelopeGenerator.getCurrentStage() == EnvelopeGenerator::ENVELOPE_STAGE_OFF) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); } if (mEnvelopeGenerator.getCurrentStage() == EnvelopeGenerator::ENVELOPE_STAGE_SUSTAIN) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); } leftOutput[i] = rightOutput[i] = mOscillator.nextSample() * mEnvelopeGenerator.nextSample() * velocity / 127.0;
. : OFF ATTACK, SUSTAIN RELEASE. .
mEnvelopeGenerator
. Synthesis::Reset()
:
mEnvelopeGenerator.setSampleRate(GetSampleRate());
. - — ! .
Note On/Off
, (, - , ), : — ATTACK, — RELEASE. mMIDIReceiver
note on/off, , - .
#include EnvelopeGenerator.h
MIDIReceiver.h . , Synthesis.h mEnvelopeGenerator
mMIDIReceiver
, . MIDIReceiver
enterStage
note on/off.
, . . MIDIReceiver
EnvelopeGenerator
. , MIDIReceiver
, EnvelopeGenerator
.
. Qt. , , , . , . , setText()
, . , , , . , , . ( Oscillator
, EnvelopeGenerator
, MIDIReceiver
). , . . Synthesis
.
Qt . Signals . . Signal.h GallantSignal.h , . Delegate.h GallantSignal.h ( , , “Copy items into destination group's folder”) Xcode. VisualStudio , Solution Explorer.
, MIDIReceiver
, . MIDIReceiver.h :
#include "GallantSignal.h" using Gallant::Signal2;
Signal2
, . — Signal0
Signal8
, .
public
:
Signal2< int, int > noteOn; Signal2< int, int > noteOff;
, int
. MIDIReceiver.cpp advance
:
// A key pressed later overrides any previously pressed key: if (noteNumber != mLastNoteNumber) { mLastNoteNumber = noteNumber; mLastFrequency = noteNumberToFrequency(mLastNoteNumber); mLastVelocity = velocity; // Emit a "note on" signal: noteOn(noteNumber, velocity); }
// If the last note was released, nothing should play: if (noteNumber == mLastNoteNumber) { mLastNoteNumber = -1; noteOff(noteNumber, mLastVelocity); }
— , — . mLastFrequency
-1
, : RELEASE. mLastVelocity
: , .
, , , . . .
mEnvelopeGenerator
. - onNoteOn
onNoteOff
EnvelopeGenerator
. , EnvelopeGenerator
. , . enterStage
- . - private
Synthesis
:
inline void onNoteOn(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); }; inline void onNoteOff(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); };
.
, Synthesis.cpp :
mMIDIReceiver.noteOn.Connect(this, &Synthesis::onNoteOn); mMIDIReceiver.noteOff.Connect(this, &Synthesis::onNoteOff);
— , — -.
ProcessDoubleReplacing
. if
, , .
!
. . DECAY, RELEASE, . stageValues
.
, , . GUI - :

!
.
:
martin-finke.de/blog/articles/audio-plugins-011-envelopes
. , : , , .
( ):
void EnvelopeGenerator::calculateMultiplier(double startLevel, double endLevel, unsigned long long lengthInSamples) { multiplier = 1.0 + (log(endLevel) - log(startLevel)) / (lengthInSamples); }
, . , startLevel
endLevel
lengthInSamples
multiplier
, . currentLevel
. log()
.
enterStage
:
void EnvelopeGenerator::enterStage(EnvelopeStage newStage) { currentStage = newStage; currentSampleIndex = 0; if (currentStage == ENVELOPE_STAGE_OFF || currentStage == ENVELOPE_STAGE_SUSTAIN) { nextStageSampleIndex = 0; } else { nextStageSampleIndex = stageValue[currentStage] * sampleRate; } switch (newStage) { case ENVELOPE_STAGE_OFF: currentLevel = 0.0; multiplier = 1.0; break; case ENVELOPE_STAGE_ATTACK: currentLevel = minimumLevel; calculateMultiplier(currentLevel, 1.0, nextStageSampleIndex); break; case ENVELOPE_STAGE_DECAY: currentLevel = 1.0; calculateMultiplier(currentLevel, fmax(stageValue[ENVELOPE_STAGE_SUSTAIN], minimumLevel), nextStageSampleIndex); break; case ENVELOPE_STAGE_SUSTAIN: currentLevel = stageValue[ENVELOPE_STAGE_SUSTAIN]; multiplier = 1.0; break; case ENVELOPE_STAGE_RELEASE: // We could go from ATTACK/DECAY to RELEASE, // so we're not changing currentLevel here. calculateMultiplier(currentLevel, minimumLevel, nextStageSampleIndex); break; default: break; } }
currentStage
currentSampleIndex
. . , , . stageValue[currentStage]
double
( ), , , . Switch
. OFF ( , ). ATTACK minimumLevel
, multiplier
currentLevel
1.0
. DECAY stageValue[ENVELOPE_STAGE_SUSTAIN]
, fmax
, . RELEASE currentLevel
, , minimumLevel
. , RELEASE currentLevel
, . . SUSTAIN, ATTACK DECAY. , SUSTAIN , stageValue[ENVELOPE_STAGE_SUSTAIN]
, . currentLevel
.
:
void EnvelopeGenerator::setSampleRate(double newSampleRate) { sampleRate = newSampleRate; }
private
Synthesis
( ) :
EnvelopeGenerator mEnvelopeGenerator;
#include "EnvelopeGenerator.h"
.
, ProcessDoubleReplacing
Synthesis.cpp leftOutput[i]
:
// leftOutput[i] = rightOutput[i] = mOscillator.nextSample() * velocity / 127.0; if (mEnvelopeGenerator.getCurrentStage() == EnvelopeGenerator::ENVELOPE_STAGE_OFF) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); } if (mEnvelopeGenerator.getCurrentStage() == EnvelopeGenerator::ENVELOPE_STAGE_SUSTAIN) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); } leftOutput[i] = rightOutput[i] = mOscillator.nextSample() * mEnvelopeGenerator.nextSample() * velocity / 127.0;
. : OFF ATTACK, SUSTAIN RELEASE. .
mEnvelopeGenerator
. Synthesis::Reset()
:
mEnvelopeGenerator.setSampleRate(GetSampleRate());
. - — ! .
Note On/Off
, (, - , ), : — ATTACK, — RELEASE. mMIDIReceiver
note on/off, , - .
#include EnvelopeGenerator.h
MIDIReceiver.h . , Synthesis.h mEnvelopeGenerator
mMIDIReceiver
, . MIDIReceiver
enterStage
note on/off.
, . . MIDIReceiver
EnvelopeGenerator
. , MIDIReceiver
, EnvelopeGenerator
.
. Qt. , , , . , . , setText()
, . , , , . , , . ( Oscillator
, EnvelopeGenerator
, MIDIReceiver
). , . . Synthesis
.
Qt . Signals . . Signal.h GallantSignal.h , . Delegate.h GallantSignal.h ( , , “Copy items into destination group's folder”) Xcode. VisualStudio , Solution Explorer.
, MIDIReceiver
, . MIDIReceiver.h :
#include "GallantSignal.h" using Gallant::Signal2;
Signal2
, . — Signal0
Signal8
, .
public
:
Signal2< int, int > noteOn; Signal2< int, int > noteOff;
, int
. MIDIReceiver.cpp advance
:
// A key pressed later overrides any previously pressed key: if (noteNumber != mLastNoteNumber) { mLastNoteNumber = noteNumber; mLastFrequency = noteNumberToFrequency(mLastNoteNumber); mLastVelocity = velocity; // Emit a "note on" signal: noteOn(noteNumber, velocity); }
// If the last note was released, nothing should play: if (noteNumber == mLastNoteNumber) { mLastNoteNumber = -1; noteOff(noteNumber, mLastVelocity); }
— , — . mLastFrequency
-1
, : RELEASE. mLastVelocity
: , .
, , , . . .
mEnvelopeGenerator
. - onNoteOn
onNoteOff
EnvelopeGenerator
. , EnvelopeGenerator
. , . enterStage
- . - private
Synthesis
:
inline void onNoteOn(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); }; inline void onNoteOff(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); };
.
, Synthesis.cpp :
mMIDIReceiver.noteOn.Connect(this, &Synthesis::onNoteOn); mMIDIReceiver.noteOff.Connect(this, &Synthesis::onNoteOff);
— , — -.
ProcessDoubleReplacing
. if
, , .
!
. . DECAY, RELEASE, . stageValues
.
, , . GUI - :
!
.
:
martin-finke.de/blog/articles/audio-plugins-011-envelopes
. , : , , .
( ):
void EnvelopeGenerator::calculateMultiplier(double startLevel, double endLevel, unsigned long long lengthInSamples) { multiplier = 1.0 + (log(endLevel) - log(startLevel)) / (lengthInSamples); }
, . , startLevel
endLevel
lengthInSamples
multiplier
, . currentLevel
. log()
.
enterStage
:
void EnvelopeGenerator::enterStage(EnvelopeStage newStage) { currentStage = newStage; currentSampleIndex = 0; if (currentStage == ENVELOPE_STAGE_OFF || currentStage == ENVELOPE_STAGE_SUSTAIN) { nextStageSampleIndex = 0; } else { nextStageSampleIndex = stageValue[currentStage] * sampleRate; } switch (newStage) { case ENVELOPE_STAGE_OFF: currentLevel = 0.0; multiplier = 1.0; break; case ENVELOPE_STAGE_ATTACK: currentLevel = minimumLevel; calculateMultiplier(currentLevel, 1.0, nextStageSampleIndex); break; case ENVELOPE_STAGE_DECAY: currentLevel = 1.0; calculateMultiplier(currentLevel, fmax(stageValue[ENVELOPE_STAGE_SUSTAIN], minimumLevel), nextStageSampleIndex); break; case ENVELOPE_STAGE_SUSTAIN: currentLevel = stageValue[ENVELOPE_STAGE_SUSTAIN]; multiplier = 1.0; break; case ENVELOPE_STAGE_RELEASE: // We could go from ATTACK/DECAY to RELEASE, // so we're not changing currentLevel here. calculateMultiplier(currentLevel, minimumLevel, nextStageSampleIndex); break; default: break; } }
currentStage
currentSampleIndex
. . , , . stageValue[currentStage]
double
( ), , , . Switch
. OFF ( , ). ATTACK minimumLevel
, multiplier
currentLevel
1.0
. DECAY stageValue[ENVELOPE_STAGE_SUSTAIN]
, fmax
, . RELEASE currentLevel
, , minimumLevel
. , RELEASE currentLevel
, . . SUSTAIN, ATTACK DECAY. , SUSTAIN , stageValue[ENVELOPE_STAGE_SUSTAIN]
, . currentLevel
.
:
void EnvelopeGenerator::setSampleRate(double newSampleRate) { sampleRate = newSampleRate; }
private
Synthesis
( ) :
EnvelopeGenerator mEnvelopeGenerator;
#include "EnvelopeGenerator.h"
.
, ProcessDoubleReplacing
Synthesis.cpp leftOutput[i]
:
// leftOutput[i] = rightOutput[i] = mOscillator.nextSample() * velocity / 127.0; if (mEnvelopeGenerator.getCurrentStage() == EnvelopeGenerator::ENVELOPE_STAGE_OFF) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); } if (mEnvelopeGenerator.getCurrentStage() == EnvelopeGenerator::ENVELOPE_STAGE_SUSTAIN) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); } leftOutput[i] = rightOutput[i] = mOscillator.nextSample() * mEnvelopeGenerator.nextSample() * velocity / 127.0;
. : OFF ATTACK, SUSTAIN RELEASE. .
mEnvelopeGenerator
. Synthesis::Reset()
:
mEnvelopeGenerator.setSampleRate(GetSampleRate());
. - — ! .
Note On/Off
, (, - , ), : — ATTACK, — RELEASE. mMIDIReceiver
note on/off, , - .
#include EnvelopeGenerator.h
MIDIReceiver.h . , Synthesis.h mEnvelopeGenerator
mMIDIReceiver
, . MIDIReceiver
enterStage
note on/off.
, . . MIDIReceiver
EnvelopeGenerator
. , MIDIReceiver
, EnvelopeGenerator
.
. Qt. , , , . , . , setText()
, . , , , . , , . ( Oscillator
, EnvelopeGenerator
, MIDIReceiver
). , . . Synthesis
.
Qt . Signals . . Signal.h GallantSignal.h , . Delegate.h GallantSignal.h ( , , “Copy items into destination group's folder”) Xcode. VisualStudio , Solution Explorer.
, MIDIReceiver
, . MIDIReceiver.h :
#include "GallantSignal.h" using Gallant::Signal2;
Signal2
, . — Signal0
Signal8
, .
public
:
Signal2< int, int > noteOn; Signal2< int, int > noteOff;
, int
. MIDIReceiver.cpp advance
:
// A key pressed later overrides any previously pressed key: if (noteNumber != mLastNoteNumber) { mLastNoteNumber = noteNumber; mLastFrequency = noteNumberToFrequency(mLastNoteNumber); mLastVelocity = velocity; // Emit a "note on" signal: noteOn(noteNumber, velocity); }
// If the last note was released, nothing should play: if (noteNumber == mLastNoteNumber) { mLastNoteNumber = -1; noteOff(noteNumber, mLastVelocity); }
— , — . mLastFrequency
-1
, : RELEASE. mLastVelocity
: , .
, , , . . .
mEnvelopeGenerator
. - onNoteOn
onNoteOff
EnvelopeGenerator
. , EnvelopeGenerator
. , . enterStage
- . - private
Synthesis
:
inline void onNoteOn(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); }; inline void onNoteOff(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); };
.
, Synthesis.cpp :
mMIDIReceiver.noteOn.Connect(this, &Synthesis::onNoteOn); mMIDIReceiver.noteOff.Connect(this, &Synthesis::onNoteOff);
— , — -.
ProcessDoubleReplacing
. if
, , .
!
. . DECAY, RELEASE, . stageValues
.
, , . GUI - :

!
.
:
martin-finke.de/blog/articles/audio-plugins-011-envelopes
. , : , , .
( ):
void EnvelopeGenerator::calculateMultiplier(double startLevel, double endLevel, unsigned long long lengthInSamples) { multiplier = 1.0 + (log(endLevel) - log(startLevel)) / (lengthInSamples); }
, . , startLevel
endLevel
lengthInSamples
multiplier
, . currentLevel
. log()
.
enterStage
:
void EnvelopeGenerator::enterStage(EnvelopeStage newStage) { currentStage = newStage; currentSampleIndex = 0; if (currentStage == ENVELOPE_STAGE_OFF || currentStage == ENVELOPE_STAGE_SUSTAIN) { nextStageSampleIndex = 0; } else { nextStageSampleIndex = stageValue[currentStage] * sampleRate; } switch (newStage) { case ENVELOPE_STAGE_OFF: currentLevel = 0.0; multiplier = 1.0; break; case ENVELOPE_STAGE_ATTACK: currentLevel = minimumLevel; calculateMultiplier(currentLevel, 1.0, nextStageSampleIndex); break; case ENVELOPE_STAGE_DECAY: currentLevel = 1.0; calculateMultiplier(currentLevel, fmax(stageValue[ENVELOPE_STAGE_SUSTAIN], minimumLevel), nextStageSampleIndex); break; case ENVELOPE_STAGE_SUSTAIN: currentLevel = stageValue[ENVELOPE_STAGE_SUSTAIN]; multiplier = 1.0; break; case ENVELOPE_STAGE_RELEASE: // We could go from ATTACK/DECAY to RELEASE, // so we're not changing currentLevel here. calculateMultiplier(currentLevel, minimumLevel, nextStageSampleIndex); break; default: break; } }
currentStage
currentSampleIndex
. . , , . stageValue[currentStage]
double
( ), , , . Switch
. OFF ( , ). ATTACK minimumLevel
, multiplier
currentLevel
1.0
. DECAY stageValue[ENVELOPE_STAGE_SUSTAIN]
, fmax
, . RELEASE currentLevel
, , minimumLevel
. , RELEASE currentLevel
, . . SUSTAIN, ATTACK DECAY. , SUSTAIN , stageValue[ENVELOPE_STAGE_SUSTAIN]
, . currentLevel
.
:
void EnvelopeGenerator::setSampleRate(double newSampleRate) { sampleRate = newSampleRate; }
private
Synthesis
( ) :
EnvelopeGenerator mEnvelopeGenerator;
#include "EnvelopeGenerator.h"
.
, ProcessDoubleReplacing
Synthesis.cpp leftOutput[i]
:
// leftOutput[i] = rightOutput[i] = mOscillator.nextSample() * velocity / 127.0; if (mEnvelopeGenerator.getCurrentStage() == EnvelopeGenerator::ENVELOPE_STAGE_OFF) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); } if (mEnvelopeGenerator.getCurrentStage() == EnvelopeGenerator::ENVELOPE_STAGE_SUSTAIN) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); } leftOutput[i] = rightOutput[i] = mOscillator.nextSample() * mEnvelopeGenerator.nextSample() * velocity / 127.0;
. : OFF ATTACK, SUSTAIN RELEASE. .
mEnvelopeGenerator
. Synthesis::Reset()
:
mEnvelopeGenerator.setSampleRate(GetSampleRate());
. - — ! .
Note On/Off
, (, - , ), : — ATTACK, — RELEASE. mMIDIReceiver
note on/off, , - .
#include EnvelopeGenerator.h
MIDIReceiver.h . , Synthesis.h mEnvelopeGenerator
mMIDIReceiver
, . MIDIReceiver
enterStage
note on/off.
, . . MIDIReceiver
EnvelopeGenerator
. , MIDIReceiver
, EnvelopeGenerator
.
. Qt. , , , . , . , setText()
, . , , , . , , . ( Oscillator
, EnvelopeGenerator
, MIDIReceiver
). , . . Synthesis
.
Qt . Signals . . Signal.h GallantSignal.h , . Delegate.h GallantSignal.h ( , , “Copy items into destination group's folder”) Xcode. VisualStudio , Solution Explorer.
, MIDIReceiver
, . MIDIReceiver.h :
#include "GallantSignal.h" using Gallant::Signal2;
Signal2
, . — Signal0
Signal8
, .
public
:
Signal2< int, int > noteOn; Signal2< int, int > noteOff;
, int
. MIDIReceiver.cpp advance
:
// A key pressed later overrides any previously pressed key: if (noteNumber != mLastNoteNumber) { mLastNoteNumber = noteNumber; mLastFrequency = noteNumberToFrequency(mLastNoteNumber); mLastVelocity = velocity; // Emit a "note on" signal: noteOn(noteNumber, velocity); }
// If the last note was released, nothing should play: if (noteNumber == mLastNoteNumber) { mLastNoteNumber = -1; noteOff(noteNumber, mLastVelocity); }
— , — . mLastFrequency
-1
, : RELEASE. mLastVelocity
: , .
, , , . . .
mEnvelopeGenerator
. - onNoteOn
onNoteOff
EnvelopeGenerator
. , EnvelopeGenerator
. , . enterStage
- . - private
Synthesis
:
inline void onNoteOn(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); }; inline void onNoteOff(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); };
.
, Synthesis.cpp :
mMIDIReceiver.noteOn.Connect(this, &Synthesis::onNoteOn); mMIDIReceiver.noteOff.Connect(this, &Synthesis::onNoteOff);
— , — -.
ProcessDoubleReplacing
. if
, , .
!
. . DECAY, RELEASE, . stageValues
.
, , . GUI - :
!
.
:
martin-finke.de/blog/articles/audio-plugins-011-envelopes
. , : , , .
( ):
void EnvelopeGenerator::calculateMultiplier(double startLevel, double endLevel, unsigned long long lengthInSamples) { multiplier = 1.0 + (log(endLevel) - log(startLevel)) / (lengthInSamples); }
, . , startLevel
endLevel
lengthInSamples
multiplier
, . currentLevel
. log()
.
enterStage
:
void EnvelopeGenerator::enterStage(EnvelopeStage newStage) { currentStage = newStage; currentSampleIndex = 0; if (currentStage == ENVELOPE_STAGE_OFF || currentStage == ENVELOPE_STAGE_SUSTAIN) { nextStageSampleIndex = 0; } else { nextStageSampleIndex = stageValue[currentStage] * sampleRate; } switch (newStage) { case ENVELOPE_STAGE_OFF: currentLevel = 0.0; multiplier = 1.0; break; case ENVELOPE_STAGE_ATTACK: currentLevel = minimumLevel; calculateMultiplier(currentLevel, 1.0, nextStageSampleIndex); break; case ENVELOPE_STAGE_DECAY: currentLevel = 1.0; calculateMultiplier(currentLevel, fmax(stageValue[ENVELOPE_STAGE_SUSTAIN], minimumLevel), nextStageSampleIndex); break; case ENVELOPE_STAGE_SUSTAIN: currentLevel = stageValue[ENVELOPE_STAGE_SUSTAIN]; multiplier = 1.0; break; case ENVELOPE_STAGE_RELEASE: // We could go from ATTACK/DECAY to RELEASE, // so we're not changing currentLevel here. calculateMultiplier(currentLevel, minimumLevel, nextStageSampleIndex); break; default: break; } }
currentStage
currentSampleIndex
. . , , . stageValue[currentStage]
double
( ), , , . Switch
. OFF ( , ). ATTACK minimumLevel
, multiplier
currentLevel
1.0
. DECAY stageValue[ENVELOPE_STAGE_SUSTAIN]
, fmax
, . RELEASE currentLevel
, , minimumLevel
. , RELEASE currentLevel
, . . SUSTAIN, ATTACK DECAY. , SUSTAIN , stageValue[ENVELOPE_STAGE_SUSTAIN]
, . currentLevel
.
:
void EnvelopeGenerator::setSampleRate(double newSampleRate) { sampleRate = newSampleRate; }
private
Synthesis
( ) :
EnvelopeGenerator mEnvelopeGenerator;
#include "EnvelopeGenerator.h"
.
, ProcessDoubleReplacing
Synthesis.cpp leftOutput[i]
:
// leftOutput[i] = rightOutput[i] = mOscillator.nextSample() * velocity / 127.0; if (mEnvelopeGenerator.getCurrentStage() == EnvelopeGenerator::ENVELOPE_STAGE_OFF) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); } if (mEnvelopeGenerator.getCurrentStage() == EnvelopeGenerator::ENVELOPE_STAGE_SUSTAIN) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); } leftOutput[i] = rightOutput[i] = mOscillator.nextSample() * mEnvelopeGenerator.nextSample() * velocity / 127.0;
. : OFF ATTACK, SUSTAIN RELEASE. .
mEnvelopeGenerator
. Synthesis::Reset()
:
mEnvelopeGenerator.setSampleRate(GetSampleRate());
. - — ! .
Note On/Off
, (, - , ), : — ATTACK, — RELEASE. mMIDIReceiver
note on/off, , - .
#include EnvelopeGenerator.h
MIDIReceiver.h . , Synthesis.h mEnvelopeGenerator
mMIDIReceiver
, . MIDIReceiver
enterStage
note on/off.
, . . MIDIReceiver
EnvelopeGenerator
. , MIDIReceiver
, EnvelopeGenerator
.
. Qt. , , , . , . , setText()
, . , , , . , , . ( Oscillator
, EnvelopeGenerator
, MIDIReceiver
). , . . Synthesis
.
Qt . Signals . . Signal.h GallantSignal.h , . Delegate.h GallantSignal.h ( , , “Copy items into destination group's folder”) Xcode. VisualStudio , Solution Explorer.
, MIDIReceiver
, . MIDIReceiver.h :
#include "GallantSignal.h" using Gallant::Signal2;
Signal2
, . — Signal0
Signal8
, .
public
:
Signal2< int, int > noteOn; Signal2< int, int > noteOff;
, int
. MIDIReceiver.cpp advance
:
// A key pressed later overrides any previously pressed key: if (noteNumber != mLastNoteNumber) { mLastNoteNumber = noteNumber; mLastFrequency = noteNumberToFrequency(mLastNoteNumber); mLastVelocity = velocity; // Emit a "note on" signal: noteOn(noteNumber, velocity); }
// If the last note was released, nothing should play: if (noteNumber == mLastNoteNumber) { mLastNoteNumber = -1; noteOff(noteNumber, mLastVelocity); }
— , — . mLastFrequency
-1
, : RELEASE. mLastVelocity
: , .
, , , . . .
mEnvelopeGenerator
. - onNoteOn
onNoteOff
EnvelopeGenerator
. , EnvelopeGenerator
. , . enterStage
- . - private
Synthesis
:
inline void onNoteOn(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); }; inline void onNoteOff(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); };
.
, Synthesis.cpp :
mMIDIReceiver.noteOn.Connect(this, &Synthesis::onNoteOn); mMIDIReceiver.noteOff.Connect(this, &Synthesis::onNoteOff);
— , — -.
ProcessDoubleReplacing
. if
, , .
!
. . DECAY, RELEASE, . stageValues
.
, , . GUI - :

!
.
:
martin-finke.de/blog/articles/audio-plugins-011-envelopes
. , : , , .
( ):
void EnvelopeGenerator::calculateMultiplier(double startLevel, double endLevel, unsigned long long lengthInSamples) { multiplier = 1.0 + (log(endLevel) - log(startLevel)) / (lengthInSamples); }
, . , startLevel
endLevel
lengthInSamples
multiplier
, . currentLevel
. log()
.
enterStage
:
void EnvelopeGenerator::enterStage(EnvelopeStage newStage) { currentStage = newStage; currentSampleIndex = 0; if (currentStage == ENVELOPE_STAGE_OFF || currentStage == ENVELOPE_STAGE_SUSTAIN) { nextStageSampleIndex = 0; } else { nextStageSampleIndex = stageValue[currentStage] * sampleRate; } switch (newStage) { case ENVELOPE_STAGE_OFF: currentLevel = 0.0; multiplier = 1.0; break; case ENVELOPE_STAGE_ATTACK: currentLevel = minimumLevel; calculateMultiplier(currentLevel, 1.0, nextStageSampleIndex); break; case ENVELOPE_STAGE_DECAY: currentLevel = 1.0; calculateMultiplier(currentLevel, fmax(stageValue[ENVELOPE_STAGE_SUSTAIN], minimumLevel), nextStageSampleIndex); break; case ENVELOPE_STAGE_SUSTAIN: currentLevel = stageValue[ENVELOPE_STAGE_SUSTAIN]; multiplier = 1.0; break; case ENVELOPE_STAGE_RELEASE: // We could go from ATTACK/DECAY to RELEASE, // so we're not changing currentLevel here. calculateMultiplier(currentLevel, minimumLevel, nextStageSampleIndex); break; default: break; } }
currentStage
currentSampleIndex
. . , , . stageValue[currentStage]
double
( ), , , . Switch
. OFF ( , ). ATTACK minimumLevel
, multiplier
currentLevel
1.0
. DECAY stageValue[ENVELOPE_STAGE_SUSTAIN]
, fmax
, . RELEASE currentLevel
, , minimumLevel
. , RELEASE currentLevel
, . . SUSTAIN, ATTACK DECAY. , SUSTAIN , stageValue[ENVELOPE_STAGE_SUSTAIN]
, . currentLevel
.
:
void EnvelopeGenerator::setSampleRate(double newSampleRate) { sampleRate = newSampleRate; }
private
Synthesis
( ) :
EnvelopeGenerator mEnvelopeGenerator;
#include "EnvelopeGenerator.h"
.
, ProcessDoubleReplacing
Synthesis.cpp leftOutput[i]
:
// leftOutput[i] = rightOutput[i] = mOscillator.nextSample() * velocity / 127.0; if (mEnvelopeGenerator.getCurrentStage() == EnvelopeGenerator::ENVELOPE_STAGE_OFF) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); } if (mEnvelopeGenerator.getCurrentStage() == EnvelopeGenerator::ENVELOPE_STAGE_SUSTAIN) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); } leftOutput[i] = rightOutput[i] = mOscillator.nextSample() * mEnvelopeGenerator.nextSample() * velocity / 127.0;
. : OFF ATTACK, SUSTAIN RELEASE. .
mEnvelopeGenerator
. Synthesis::Reset()
:
mEnvelopeGenerator.setSampleRate(GetSampleRate());
. - — ! .
Note On/Off
, (, - , ), : — ATTACK, — RELEASE. mMIDIReceiver
note on/off, , - .
#include EnvelopeGenerator.h
MIDIReceiver.h . , Synthesis.h mEnvelopeGenerator
mMIDIReceiver
, . MIDIReceiver
enterStage
note on/off.
, . . MIDIReceiver
EnvelopeGenerator
. , MIDIReceiver
, EnvelopeGenerator
.
. Qt. , , , . , . , setText()
, . , , , . , , . ( Oscillator
, EnvelopeGenerator
, MIDIReceiver
). , . . Synthesis
.
Qt . Signals . . Signal.h GallantSignal.h , . Delegate.h GallantSignal.h ( , , “Copy items into destination group's folder”) Xcode. VisualStudio , Solution Explorer.
, MIDIReceiver
, . MIDIReceiver.h :
#include "GallantSignal.h" using Gallant::Signal2;
Signal2
, . — Signal0
Signal8
, .
public
:
Signal2< int, int > noteOn; Signal2< int, int > noteOff;
, int
. MIDIReceiver.cpp advance
:
// A key pressed later overrides any previously pressed key: if (noteNumber != mLastNoteNumber) { mLastNoteNumber = noteNumber; mLastFrequency = noteNumberToFrequency(mLastNoteNumber); mLastVelocity = velocity; // Emit a "note on" signal: noteOn(noteNumber, velocity); }
// If the last note was released, nothing should play: if (noteNumber == mLastNoteNumber) { mLastNoteNumber = -1; noteOff(noteNumber, mLastVelocity); }
— , — . mLastFrequency
-1
, : RELEASE. mLastVelocity
: , .
, , , . . .
mEnvelopeGenerator
. - onNoteOn
onNoteOff
EnvelopeGenerator
. , EnvelopeGenerator
. , . enterStage
- . - private
Synthesis
:
inline void onNoteOn(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); }; inline void onNoteOff(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); };
.
, Synthesis.cpp :
mMIDIReceiver.noteOn.Connect(this, &Synthesis::onNoteOn); mMIDIReceiver.noteOff.Connect(this, &Synthesis::onNoteOff);
— , — -.
ProcessDoubleReplacing
. if
, , .
!
. . DECAY, RELEASE, . stageValues
.
, , . GUI - :
!
.
:
martin-finke.de/blog/articles/audio-plugins-011-envelopes
. , : , , .
( ):
void EnvelopeGenerator::calculateMultiplier(double startLevel, double endLevel, unsigned long long lengthInSamples) { multiplier = 1.0 + (log(endLevel) - log(startLevel)) / (lengthInSamples); }
, . , startLevel
endLevel
lengthInSamples
multiplier
, . currentLevel
. log()
.
enterStage
:
void EnvelopeGenerator::enterStage(EnvelopeStage newStage) { currentStage = newStage; currentSampleIndex = 0; if (currentStage == ENVELOPE_STAGE_OFF || currentStage == ENVELOPE_STAGE_SUSTAIN) { nextStageSampleIndex = 0; } else { nextStageSampleIndex = stageValue[currentStage] * sampleRate; } switch (newStage) { case ENVELOPE_STAGE_OFF: currentLevel = 0.0; multiplier = 1.0; break; case ENVELOPE_STAGE_ATTACK: currentLevel = minimumLevel; calculateMultiplier(currentLevel, 1.0, nextStageSampleIndex); break; case ENVELOPE_STAGE_DECAY: currentLevel = 1.0; calculateMultiplier(currentLevel, fmax(stageValue[ENVELOPE_STAGE_SUSTAIN], minimumLevel), nextStageSampleIndex); break; case ENVELOPE_STAGE_SUSTAIN: currentLevel = stageValue[ENVELOPE_STAGE_SUSTAIN]; multiplier = 1.0; break; case ENVELOPE_STAGE_RELEASE: // We could go from ATTACK/DECAY to RELEASE, // so we're not changing currentLevel here. calculateMultiplier(currentLevel, minimumLevel, nextStageSampleIndex); break; default: break; } }
currentStage
currentSampleIndex
. . , , . stageValue[currentStage]
double
( ), , , . Switch
. OFF ( , ). ATTACK minimumLevel
, multiplier
currentLevel
1.0
. DECAY stageValue[ENVELOPE_STAGE_SUSTAIN]
, fmax
, . RELEASE currentLevel
, , minimumLevel
. , RELEASE currentLevel
, . . SUSTAIN, ATTACK DECAY. , SUSTAIN , stageValue[ENVELOPE_STAGE_SUSTAIN]
, . currentLevel
.
:
void EnvelopeGenerator::setSampleRate(double newSampleRate) { sampleRate = newSampleRate; }
private
Synthesis
( ) :
EnvelopeGenerator mEnvelopeGenerator;
#include "EnvelopeGenerator.h"
.
, ProcessDoubleReplacing
Synthesis.cpp leftOutput[i]
:
// leftOutput[i] = rightOutput[i] = mOscillator.nextSample() * velocity / 127.0; if (mEnvelopeGenerator.getCurrentStage() == EnvelopeGenerator::ENVELOPE_STAGE_OFF) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); } if (mEnvelopeGenerator.getCurrentStage() == EnvelopeGenerator::ENVELOPE_STAGE_SUSTAIN) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); } leftOutput[i] = rightOutput[i] = mOscillator.nextSample() * mEnvelopeGenerator.nextSample() * velocity / 127.0;
. : OFF ATTACK, SUSTAIN RELEASE. .
mEnvelopeGenerator
. Synthesis::Reset()
:
mEnvelopeGenerator.setSampleRate(GetSampleRate());
. - — ! .
Note On/Off
, (, - , ), : — ATTACK, — RELEASE. mMIDIReceiver
note on/off, , - .
#include EnvelopeGenerator.h
MIDIReceiver.h . , Synthesis.h mEnvelopeGenerator
mMIDIReceiver
, . MIDIReceiver
enterStage
note on/off.
, . . MIDIReceiver
EnvelopeGenerator
. , MIDIReceiver
, EnvelopeGenerator
.
. Qt. , , , . , . , setText()
, . , , , . , , . ( Oscillator
, EnvelopeGenerator
, MIDIReceiver
). , . . Synthesis
.
Qt . Signals . . Signal.h GallantSignal.h , . Delegate.h GallantSignal.h ( , , “Copy items into destination group's folder”) Xcode. VisualStudio , Solution Explorer.
, MIDIReceiver
, . MIDIReceiver.h :
#include "GallantSignal.h" using Gallant::Signal2;
Signal2
, . — Signal0
Signal8
, .
public
:
Signal2< int, int > noteOn; Signal2< int, int > noteOff;
, int
. MIDIReceiver.cpp advance
:
// A key pressed later overrides any previously pressed key: if (noteNumber != mLastNoteNumber) { mLastNoteNumber = noteNumber; mLastFrequency = noteNumberToFrequency(mLastNoteNumber); mLastVelocity = velocity; // Emit a "note on" signal: noteOn(noteNumber, velocity); }
// If the last note was released, nothing should play: if (noteNumber == mLastNoteNumber) { mLastNoteNumber = -1; noteOff(noteNumber, mLastVelocity); }
— , — . mLastFrequency
-1
, : RELEASE. mLastVelocity
: , .
, , , . . .
mEnvelopeGenerator
. - onNoteOn
onNoteOff
EnvelopeGenerator
. , EnvelopeGenerator
. , . enterStage
- . - private
Synthesis
:
inline void onNoteOn(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); }; inline void onNoteOff(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); };
.
, Synthesis.cpp :
mMIDIReceiver.noteOn.Connect(this, &Synthesis::onNoteOn); mMIDIReceiver.noteOff.Connect(this, &Synthesis::onNoteOff);
— , — -.
ProcessDoubleReplacing
. if
, , .
!
. . DECAY, RELEASE, . stageValues
.
, , . GUI - :

!
.
:
martin-finke.de/blog/articles/audio-plugins-011-envelopes
. , : , , .
( ):
void EnvelopeGenerator::calculateMultiplier(double startLevel, double endLevel, unsigned long long lengthInSamples) { multiplier = 1.0 + (log(endLevel) - log(startLevel)) / (lengthInSamples); }
, . , startLevel
endLevel
lengthInSamples
multiplier
, . currentLevel
. log()
.
enterStage
:
void EnvelopeGenerator::enterStage(EnvelopeStage newStage) { currentStage = newStage; currentSampleIndex = 0; if (currentStage == ENVELOPE_STAGE_OFF || currentStage == ENVELOPE_STAGE_SUSTAIN) { nextStageSampleIndex = 0; } else { nextStageSampleIndex = stageValue[currentStage] * sampleRate; } switch (newStage) { case ENVELOPE_STAGE_OFF: currentLevel = 0.0; multiplier = 1.0; break; case ENVELOPE_STAGE_ATTACK: currentLevel = minimumLevel; calculateMultiplier(currentLevel, 1.0, nextStageSampleIndex); break; case ENVELOPE_STAGE_DECAY: currentLevel = 1.0; calculateMultiplier(currentLevel, fmax(stageValue[ENVELOPE_STAGE_SUSTAIN], minimumLevel), nextStageSampleIndex); break; case ENVELOPE_STAGE_SUSTAIN: currentLevel = stageValue[ENVELOPE_STAGE_SUSTAIN]; multiplier = 1.0; break; case ENVELOPE_STAGE_RELEASE: // We could go from ATTACK/DECAY to RELEASE, // so we're not changing currentLevel here. calculateMultiplier(currentLevel, minimumLevel, nextStageSampleIndex); break; default: break; } }
currentStage
currentSampleIndex
. . , , . stageValue[currentStage]
double
( ), , , . Switch
. OFF ( , ). ATTACK minimumLevel
, multiplier
currentLevel
1.0
. DECAY stageValue[ENVELOPE_STAGE_SUSTAIN]
, fmax
, . RELEASE currentLevel
, , minimumLevel
. , RELEASE currentLevel
, . . SUSTAIN, ATTACK DECAY. , SUSTAIN , stageValue[ENVELOPE_STAGE_SUSTAIN]
, . currentLevel
.
:
void EnvelopeGenerator::setSampleRate(double newSampleRate) { sampleRate = newSampleRate; }
private
Synthesis
( ) :
EnvelopeGenerator mEnvelopeGenerator;
#include "EnvelopeGenerator.h"
.
, ProcessDoubleReplacing
Synthesis.cpp leftOutput[i]
:
// leftOutput[i] = rightOutput[i] = mOscillator.nextSample() * velocity / 127.0; if (mEnvelopeGenerator.getCurrentStage() == EnvelopeGenerator::ENVELOPE_STAGE_OFF) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); } if (mEnvelopeGenerator.getCurrentStage() == EnvelopeGenerator::ENVELOPE_STAGE_SUSTAIN) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); } leftOutput[i] = rightOutput[i] = mOscillator.nextSample() * mEnvelopeGenerator.nextSample() * velocity / 127.0;
. : OFF ATTACK, SUSTAIN RELEASE. .
mEnvelopeGenerator
. Synthesis::Reset()
:
mEnvelopeGenerator.setSampleRate(GetSampleRate());
. - — ! .
Note On/Off
, (, - , ), : — ATTACK, — RELEASE. mMIDIReceiver
note on/off, , - .
#include EnvelopeGenerator.h
MIDIReceiver.h . , Synthesis.h mEnvelopeGenerator
mMIDIReceiver
, . MIDIReceiver
enterStage
note on/off.
, . . MIDIReceiver
EnvelopeGenerator
. , MIDIReceiver
, EnvelopeGenerator
.
. Qt. , , , . , . , setText()
, . , , , . , , . ( Oscillator
, EnvelopeGenerator
, MIDIReceiver
). , . . Synthesis
.
Qt . Signals . . Signal.h GallantSignal.h , . Delegate.h GallantSignal.h ( , , “Copy items into destination group's folder”) Xcode. VisualStudio , Solution Explorer.
, MIDIReceiver
, . MIDIReceiver.h :
#include "GallantSignal.h" using Gallant::Signal2;
Signal2
, . — Signal0
Signal8
, .
public
:
Signal2< int, int > noteOn; Signal2< int, int > noteOff;
, int
. MIDIReceiver.cpp advance
:
// A key pressed later overrides any previously pressed key: if (noteNumber != mLastNoteNumber) { mLastNoteNumber = noteNumber; mLastFrequency = noteNumberToFrequency(mLastNoteNumber); mLastVelocity = velocity; // Emit a "note on" signal: noteOn(noteNumber, velocity); }
// If the last note was released, nothing should play: if (noteNumber == mLastNoteNumber) { mLastNoteNumber = -1; noteOff(noteNumber, mLastVelocity); }
— , — . mLastFrequency
-1
, : RELEASE. mLastVelocity
: , .
, , , . . .
mEnvelopeGenerator
. - onNoteOn
onNoteOff
EnvelopeGenerator
. , EnvelopeGenerator
. , . enterStage
- . - private
Synthesis
:
inline void onNoteOn(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); }; inline void onNoteOff(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); };
.
, Synthesis.cpp :
mMIDIReceiver.noteOn.Connect(this, &Synthesis::onNoteOn); mMIDIReceiver.noteOff.Connect(this, &Synthesis::onNoteOff);
— , — -.
ProcessDoubleReplacing
. if
, , .
!
. . DECAY, RELEASE, . stageValues
.
, , . GUI - :
!
.
:
martin-finke.de/blog/articles/audio-plugins-011-envelopes
. , : , , .
( ):
void EnvelopeGenerator::calculateMultiplier(double startLevel, double endLevel, unsigned long long lengthInSamples) { multiplier = 1.0 + (log(endLevel) - log(startLevel)) / (lengthInSamples); }
, . , startLevel
endLevel
lengthInSamples
multiplier
, . currentLevel
. log()
.
enterStage
:
void EnvelopeGenerator::enterStage(EnvelopeStage newStage) { currentStage = newStage; currentSampleIndex = 0; if (currentStage == ENVELOPE_STAGE_OFF || currentStage == ENVELOPE_STAGE_SUSTAIN) { nextStageSampleIndex = 0; } else { nextStageSampleIndex = stageValue[currentStage] * sampleRate; } switch (newStage) { case ENVELOPE_STAGE_OFF: currentLevel = 0.0; multiplier = 1.0; break; case ENVELOPE_STAGE_ATTACK: currentLevel = minimumLevel; calculateMultiplier(currentLevel, 1.0, nextStageSampleIndex); break; case ENVELOPE_STAGE_DECAY: currentLevel = 1.0; calculateMultiplier(currentLevel, fmax(stageValue[ENVELOPE_STAGE_SUSTAIN], minimumLevel), nextStageSampleIndex); break; case ENVELOPE_STAGE_SUSTAIN: currentLevel = stageValue[ENVELOPE_STAGE_SUSTAIN]; multiplier = 1.0; break; case ENVELOPE_STAGE_RELEASE: // We could go from ATTACK/DECAY to RELEASE, // so we're not changing currentLevel here. calculateMultiplier(currentLevel, minimumLevel, nextStageSampleIndex); break; default: break; } }
currentStage
currentSampleIndex
. . , , . stageValue[currentStage]
double
( ), , , . Switch
. OFF ( , ). ATTACK minimumLevel
, multiplier
currentLevel
1.0
. DECAY stageValue[ENVELOPE_STAGE_SUSTAIN]
, fmax
, . RELEASE currentLevel
, , minimumLevel
. , RELEASE currentLevel
, . . SUSTAIN, ATTACK DECAY. , SUSTAIN , stageValue[ENVELOPE_STAGE_SUSTAIN]
, . currentLevel
.
:
void EnvelopeGenerator::setSampleRate(double newSampleRate) { sampleRate = newSampleRate; }
private
Synthesis
( ) :
EnvelopeGenerator mEnvelopeGenerator;
#include "EnvelopeGenerator.h"
.
, ProcessDoubleReplacing
Synthesis.cpp leftOutput[i]
:
// leftOutput[i] = rightOutput[i] = mOscillator.nextSample() * velocity / 127.0; if (mEnvelopeGenerator.getCurrentStage() == EnvelopeGenerator::ENVELOPE_STAGE_OFF) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); } if (mEnvelopeGenerator.getCurrentStage() == EnvelopeGenerator::ENVELOPE_STAGE_SUSTAIN) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); } leftOutput[i] = rightOutput[i] = mOscillator.nextSample() * mEnvelopeGenerator.nextSample() * velocity / 127.0;
. : OFF ATTACK, SUSTAIN RELEASE. .
mEnvelopeGenerator
. Synthesis::Reset()
:
mEnvelopeGenerator.setSampleRate(GetSampleRate());
. - — ! .
Note On/Off
, (, - , ), : — ATTACK, — RELEASE. mMIDIReceiver
note on/off, , - .
#include EnvelopeGenerator.h
MIDIReceiver.h . , Synthesis.h mEnvelopeGenerator
mMIDIReceiver
, . MIDIReceiver
enterStage
note on/off.
, . . MIDIReceiver
EnvelopeGenerator
. , MIDIReceiver
, EnvelopeGenerator
.
. Qt. , , , . , . , setText()
, . , , , . , , . ( Oscillator
, EnvelopeGenerator
, MIDIReceiver
). , . . Synthesis
.
Qt . Signals . . Signal.h GallantSignal.h , . Delegate.h GallantSignal.h ( , , “Copy items into destination group's folder”) Xcode. VisualStudio , Solution Explorer.
, MIDIReceiver
, . MIDIReceiver.h :
#include "GallantSignal.h" using Gallant::Signal2;
Signal2
, . — Signal0
Signal8
, .
public
:
Signal2< int, int > noteOn; Signal2< int, int > noteOff;
, int
. MIDIReceiver.cpp advance
:
// A key pressed later overrides any previously pressed key: if (noteNumber != mLastNoteNumber) { mLastNoteNumber = noteNumber; mLastFrequency = noteNumberToFrequency(mLastNoteNumber); mLastVelocity = velocity; // Emit a "note on" signal: noteOn(noteNumber, velocity); }
// If the last note was released, nothing should play: if (noteNumber == mLastNoteNumber) { mLastNoteNumber = -1; noteOff(noteNumber, mLastVelocity); }
— , — . mLastFrequency
-1
, : RELEASE. mLastVelocity
: , .
, , , . . .
mEnvelopeGenerator
. - onNoteOn
onNoteOff
EnvelopeGenerator
. , EnvelopeGenerator
. , . enterStage
- . - private
Synthesis
:
inline void onNoteOn(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); }; inline void onNoteOff(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); };
.
, Synthesis.cpp :
mMIDIReceiver.noteOn.Connect(this, &Synthesis::onNoteOn); mMIDIReceiver.noteOff.Connect(this, &Synthesis::onNoteOff);
— , — -.
ProcessDoubleReplacing
. if
, , .
!
. . DECAY, RELEASE, . stageValues
.
, , . GUI - :

!
.
:
martin-finke.de/blog/articles/audio-plugins-011-envelopes
. , : , , .
( ):
void EnvelopeGenerator::calculateMultiplier(double startLevel, double endLevel, unsigned long long lengthInSamples) { multiplier = 1.0 + (log(endLevel) - log(startLevel)) / (lengthInSamples); }
, . , startLevel
endLevel
lengthInSamples
multiplier
, . currentLevel
. log()
.
enterStage
:
void EnvelopeGenerator::enterStage(EnvelopeStage newStage) { currentStage = newStage; currentSampleIndex = 0; if (currentStage == ENVELOPE_STAGE_OFF || currentStage == ENVELOPE_STAGE_SUSTAIN) { nextStageSampleIndex = 0; } else { nextStageSampleIndex = stageValue[currentStage] * sampleRate; } switch (newStage) { case ENVELOPE_STAGE_OFF: currentLevel = 0.0; multiplier = 1.0; break; case ENVELOPE_STAGE_ATTACK: currentLevel = minimumLevel; calculateMultiplier(currentLevel, 1.0, nextStageSampleIndex); break; case ENVELOPE_STAGE_DECAY: currentLevel = 1.0; calculateMultiplier(currentLevel, fmax(stageValue[ENVELOPE_STAGE_SUSTAIN], minimumLevel), nextStageSampleIndex); break; case ENVELOPE_STAGE_SUSTAIN: currentLevel = stageValue[ENVELOPE_STAGE_SUSTAIN]; multiplier = 1.0; break; case ENVELOPE_STAGE_RELEASE: // We could go from ATTACK/DECAY to RELEASE, // so we're not changing currentLevel here. calculateMultiplier(currentLevel, minimumLevel, nextStageSampleIndex); break; default: break; } }
currentStage
currentSampleIndex
. . , , . stageValue[currentStage]
double
( ), , , . Switch
. OFF ( , ). ATTACK minimumLevel
, multiplier
currentLevel
1.0
. DECAY stageValue[ENVELOPE_STAGE_SUSTAIN]
, fmax
, . RELEASE currentLevel
, , minimumLevel
. , RELEASE currentLevel
, . . SUSTAIN, ATTACK DECAY. , SUSTAIN , stageValue[ENVELOPE_STAGE_SUSTAIN]
, . currentLevel
.
:
void EnvelopeGenerator::setSampleRate(double newSampleRate) { sampleRate = newSampleRate; }
private
Synthesis
( ) :
EnvelopeGenerator mEnvelopeGenerator;
#include "EnvelopeGenerator.h"
.
, ProcessDoubleReplacing
Synthesis.cpp leftOutput[i]
:
// leftOutput[i] = rightOutput[i] = mOscillator.nextSample() * velocity / 127.0; if (mEnvelopeGenerator.getCurrentStage() == EnvelopeGenerator::ENVELOPE_STAGE_OFF) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); } if (mEnvelopeGenerator.getCurrentStage() == EnvelopeGenerator::ENVELOPE_STAGE_SUSTAIN) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); } leftOutput[i] = rightOutput[i] = mOscillator.nextSample() * mEnvelopeGenerator.nextSample() * velocity / 127.0;
. : OFF ATTACK, SUSTAIN RELEASE. .
mEnvelopeGenerator
. Synthesis::Reset()
:
mEnvelopeGenerator.setSampleRate(GetSampleRate());
. - — ! .
Note On/Off
, (, - , ), : — ATTACK, — RELEASE. mMIDIReceiver
note on/off, , - .
#include EnvelopeGenerator.h
MIDIReceiver.h . , Synthesis.h mEnvelopeGenerator
mMIDIReceiver
, . MIDIReceiver
enterStage
note on/off.
, . . MIDIReceiver
EnvelopeGenerator
. , MIDIReceiver
, EnvelopeGenerator
.
. Qt. , , , . , . , setText()
, . , , , . , , . ( Oscillator
, EnvelopeGenerator
, MIDIReceiver
). , . . Synthesis
.
Qt . Signals . . Signal.h GallantSignal.h , . Delegate.h GallantSignal.h ( , , “Copy items into destination group's folder”) Xcode. VisualStudio , Solution Explorer.
, MIDIReceiver
, . MIDIReceiver.h :
#include "GallantSignal.h" using Gallant::Signal2;
Signal2
, . — Signal0
Signal8
, .
public
:
Signal2< int, int > noteOn; Signal2< int, int > noteOff;
, int
. MIDIReceiver.cpp advance
:
// A key pressed later overrides any previously pressed key: if (noteNumber != mLastNoteNumber) { mLastNoteNumber = noteNumber; mLastFrequency = noteNumberToFrequency(mLastNoteNumber); mLastVelocity = velocity; // Emit a "note on" signal: noteOn(noteNumber, velocity); }
// If the last note was released, nothing should play: if (noteNumber == mLastNoteNumber) { mLastNoteNumber = -1; noteOff(noteNumber, mLastVelocity); }
— , — . mLastFrequency
-1
, : RELEASE. mLastVelocity
: , .
, , , . . .
mEnvelopeGenerator
. - onNoteOn
onNoteOff
EnvelopeGenerator
. , EnvelopeGenerator
. , . enterStage
- . - private
Synthesis
:
inline void onNoteOn(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); }; inline void onNoteOff(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); };
.
, Synthesis.cpp :
mMIDIReceiver.noteOn.Connect(this, &Synthesis::onNoteOn); mMIDIReceiver.noteOff.Connect(this, &Synthesis::onNoteOff);
— , — -.
ProcessDoubleReplacing
. if
, , .
!
. . DECAY, RELEASE, . stageValues
.
, , . GUI - :
!
.
:
martin-finke.de/blog/articles/audio-plugins-011-envelopes
. , : , , .
( ):
void EnvelopeGenerator::calculateMultiplier(double startLevel, double endLevel, unsigned long long lengthInSamples) { multiplier = 1.0 + (log(endLevel) - log(startLevel)) / (lengthInSamples); }
, . , startLevel
endLevel
lengthInSamples
multiplier
, . currentLevel
. log()
.
enterStage
:
void EnvelopeGenerator::enterStage(EnvelopeStage newStage) { currentStage = newStage; currentSampleIndex = 0; if (currentStage == ENVELOPE_STAGE_OFF || currentStage == ENVELOPE_STAGE_SUSTAIN) { nextStageSampleIndex = 0; } else { nextStageSampleIndex = stageValue[currentStage] * sampleRate; } switch (newStage) { case ENVELOPE_STAGE_OFF: currentLevel = 0.0; multiplier = 1.0; break; case ENVELOPE_STAGE_ATTACK: currentLevel = minimumLevel; calculateMultiplier(currentLevel, 1.0, nextStageSampleIndex); break; case ENVELOPE_STAGE_DECAY: currentLevel = 1.0; calculateMultiplier(currentLevel, fmax(stageValue[ENVELOPE_STAGE_SUSTAIN], minimumLevel), nextStageSampleIndex); break; case ENVELOPE_STAGE_SUSTAIN: currentLevel = stageValue[ENVELOPE_STAGE_SUSTAIN]; multiplier = 1.0; break; case ENVELOPE_STAGE_RELEASE: // We could go from ATTACK/DECAY to RELEASE, // so we're not changing currentLevel here. calculateMultiplier(currentLevel, minimumLevel, nextStageSampleIndex); break; default: break; } }
currentStage
currentSampleIndex
. . , , . stageValue[currentStage]
double
( ), , , . Switch
. OFF ( , ). ATTACK minimumLevel
, multiplier
currentLevel
1.0
. DECAY stageValue[ENVELOPE_STAGE_SUSTAIN]
, fmax
, . RELEASE currentLevel
, , minimumLevel
. , RELEASE currentLevel
, . . SUSTAIN, ATTACK DECAY. , SUSTAIN , stageValue[ENVELOPE_STAGE_SUSTAIN]
, . currentLevel
.
:
void EnvelopeGenerator::setSampleRate(double newSampleRate) { sampleRate = newSampleRate; }
private
Synthesis
( ) :
EnvelopeGenerator mEnvelopeGenerator;
#include "EnvelopeGenerator.h"
.
, ProcessDoubleReplacing
Synthesis.cpp leftOutput[i]
:
// leftOutput[i] = rightOutput[i] = mOscillator.nextSample() * velocity / 127.0; if (mEnvelopeGenerator.getCurrentStage() == EnvelopeGenerator::ENVELOPE_STAGE_OFF) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); } if (mEnvelopeGenerator.getCurrentStage() == EnvelopeGenerator::ENVELOPE_STAGE_SUSTAIN) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); } leftOutput[i] = rightOutput[i] = mOscillator.nextSample() * mEnvelopeGenerator.nextSample() * velocity / 127.0;
. : OFF ATTACK, SUSTAIN RELEASE. .
mEnvelopeGenerator
. Synthesis::Reset()
:
mEnvelopeGenerator.setSampleRate(GetSampleRate());
. - — ! .
Note On/Off
, (, - , ), : — ATTACK, — RELEASE. mMIDIReceiver
note on/off, , - .
#include EnvelopeGenerator.h
MIDIReceiver.h . , Synthesis.h mEnvelopeGenerator
mMIDIReceiver
, . MIDIReceiver
enterStage
note on/off.
, . . MIDIReceiver
EnvelopeGenerator
. , MIDIReceiver
, EnvelopeGenerator
.
. Qt. , , , . , . , setText()
, . , , , . , , . ( Oscillator
, EnvelopeGenerator
, MIDIReceiver
). , . . Synthesis
.
Qt . Signals . . Signal.h GallantSignal.h , . Delegate.h GallantSignal.h ( , , “Copy items into destination group's folder”) Xcode. VisualStudio , Solution Explorer.
, MIDIReceiver
, . MIDIReceiver.h :
#include "GallantSignal.h" using Gallant::Signal2;
Signal2
, . — Signal0
Signal8
, .
public
:
Signal2< int, int > noteOn; Signal2< int, int > noteOff;
, int
. MIDIReceiver.cpp advance
:
// A key pressed later overrides any previously pressed key: if (noteNumber != mLastNoteNumber) { mLastNoteNumber = noteNumber; mLastFrequency = noteNumberToFrequency(mLastNoteNumber); mLastVelocity = velocity; // Emit a "note on" signal: noteOn(noteNumber, velocity); }
// If the last note was released, nothing should play: if (noteNumber == mLastNoteNumber) { mLastNoteNumber = -1; noteOff(noteNumber, mLastVelocity); }
— , — . mLastFrequency
-1
, : RELEASE. mLastVelocity
: , .
, , , . . .
mEnvelopeGenerator
. - onNoteOn
onNoteOff
EnvelopeGenerator
. , EnvelopeGenerator
. , . enterStage
- . - private
Synthesis
:
inline void onNoteOn(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); }; inline void onNoteOff(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); };
.
, Synthesis.cpp :
mMIDIReceiver.noteOn.Connect(this, &Synthesis::onNoteOn); mMIDIReceiver.noteOff.Connect(this, &Synthesis::onNoteOff);
— , — -.
ProcessDoubleReplacing
. if
, , .
!
. . DECAY, RELEASE, . stageValues
.
, , . GUI - :

!
.
:
martin-finke.de/blog/articles/audio-plugins-011-envelopes
. , : , , .
( ):
void EnvelopeGenerator::calculateMultiplier(double startLevel, double endLevel, unsigned long long lengthInSamples) { multiplier = 1.0 + (log(endLevel) - log(startLevel)) / (lengthInSamples); }
, . , startLevel
endLevel
lengthInSamples
multiplier
, . currentLevel
. log()
.
enterStage
:
void EnvelopeGenerator::enterStage(EnvelopeStage newStage) { currentStage = newStage; currentSampleIndex = 0; if (currentStage == ENVELOPE_STAGE_OFF || currentStage == ENVELOPE_STAGE_SUSTAIN) { nextStageSampleIndex = 0; } else { nextStageSampleIndex = stageValue[currentStage] * sampleRate; } switch (newStage) { case ENVELOPE_STAGE_OFF: currentLevel = 0.0; multiplier = 1.0; break; case ENVELOPE_STAGE_ATTACK: currentLevel = minimumLevel; calculateMultiplier(currentLevel, 1.0, nextStageSampleIndex); break; case ENVELOPE_STAGE_DECAY: currentLevel = 1.0; calculateMultiplier(currentLevel, fmax(stageValue[ENVELOPE_STAGE_SUSTAIN], minimumLevel), nextStageSampleIndex); break; case ENVELOPE_STAGE_SUSTAIN: currentLevel = stageValue[ENVELOPE_STAGE_SUSTAIN]; multiplier = 1.0; break; case ENVELOPE_STAGE_RELEASE: // We could go from ATTACK/DECAY to RELEASE, // so we're not changing currentLevel here. calculateMultiplier(currentLevel, minimumLevel, nextStageSampleIndex); break; default: break; } }
currentStage
currentSampleIndex
. . , , . stageValue[currentStage]
double
( ), , , . Switch
. OFF ( , ). ATTACK minimumLevel
, multiplier
currentLevel
1.0
. DECAY stageValue[ENVELOPE_STAGE_SUSTAIN]
, fmax
, . RELEASE currentLevel
, , minimumLevel
. , RELEASE currentLevel
, . . SUSTAIN, ATTACK DECAY. , SUSTAIN , stageValue[ENVELOPE_STAGE_SUSTAIN]
, . currentLevel
.
:
void EnvelopeGenerator::setSampleRate(double newSampleRate) { sampleRate = newSampleRate; }
private
Synthesis
( ) :
EnvelopeGenerator mEnvelopeGenerator;
#include "EnvelopeGenerator.h"
.
, ProcessDoubleReplacing
Synthesis.cpp leftOutput[i]
:
// leftOutput[i] = rightOutput[i] = mOscillator.nextSample() * velocity / 127.0; if (mEnvelopeGenerator.getCurrentStage() == EnvelopeGenerator::ENVELOPE_STAGE_OFF) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); } if (mEnvelopeGenerator.getCurrentStage() == EnvelopeGenerator::ENVELOPE_STAGE_SUSTAIN) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); } leftOutput[i] = rightOutput[i] = mOscillator.nextSample() * mEnvelopeGenerator.nextSample() * velocity / 127.0;
. : OFF ATTACK, SUSTAIN RELEASE. .
mEnvelopeGenerator
. Synthesis::Reset()
:
mEnvelopeGenerator.setSampleRate(GetSampleRate());
. - — ! .
Note On/Off
, (, - , ), : — ATTACK, — RELEASE. mMIDIReceiver
note on/off, , - .
#include EnvelopeGenerator.h
MIDIReceiver.h . , Synthesis.h mEnvelopeGenerator
mMIDIReceiver
, . MIDIReceiver
enterStage
note on/off.
, . . MIDIReceiver
EnvelopeGenerator
. , MIDIReceiver
, EnvelopeGenerator
.
. Qt. , , , . , . , setText()
, . , , , . , , . ( Oscillator
, EnvelopeGenerator
, MIDIReceiver
). , . . Synthesis
.
Qt . Signals . . Signal.h GallantSignal.h , . Delegate.h GallantSignal.h ( , , “Copy items into destination group's folder”) Xcode. VisualStudio , Solution Explorer.
, MIDIReceiver
, . MIDIReceiver.h :
#include "GallantSignal.h" using Gallant::Signal2;
Signal2
, . — Signal0
Signal8
, .
public
:
Signal2< int, int > noteOn; Signal2< int, int > noteOff;
, int
. MIDIReceiver.cpp advance
:
// A key pressed later overrides any previously pressed key: if (noteNumber != mLastNoteNumber) { mLastNoteNumber = noteNumber; mLastFrequency = noteNumberToFrequency(mLastNoteNumber); mLastVelocity = velocity; // Emit a "note on" signal: noteOn(noteNumber, velocity); }
// If the last note was released, nothing should play: if (noteNumber == mLastNoteNumber) { mLastNoteNumber = -1; noteOff(noteNumber, mLastVelocity); }
— , — . mLastFrequency
-1
, : RELEASE. mLastVelocity
: , .
, , , . . .
mEnvelopeGenerator
. - onNoteOn
onNoteOff
EnvelopeGenerator
. , EnvelopeGenerator
. , . enterStage
- . - private
Synthesis
:
inline void onNoteOn(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); }; inline void onNoteOff(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); };
.
, Synthesis.cpp :
mMIDIReceiver.noteOn.Connect(this, &Synthesis::onNoteOn); mMIDIReceiver.noteOff.Connect(this, &Synthesis::onNoteOff);
— , — -.
ProcessDoubleReplacing
. if
, , .
!
. . DECAY, RELEASE, . stageValues
.
, , . GUI - :

!
.
:
martin-finke.de/blog/articles/audio-plugins-011-envelopes
. , : , , .
( ):
void EnvelopeGenerator::calculateMultiplier(double startLevel, double endLevel, unsigned long long lengthInSamples) { multiplier = 1.0 + (log(endLevel) - log(startLevel)) / (lengthInSamples); }
, . , startLevel
endLevel
lengthInSamples
multiplier
, . currentLevel
. log()
.
enterStage
:
void EnvelopeGenerator::enterStage(EnvelopeStage newStage) { currentStage = newStage; currentSampleIndex = 0; if (currentStage == ENVELOPE_STAGE_OFF || currentStage == ENVELOPE_STAGE_SUSTAIN) { nextStageSampleIndex = 0; } else { nextStageSampleIndex = stageValue[currentStage] * sampleRate; } switch (newStage) { case ENVELOPE_STAGE_OFF: currentLevel = 0.0; multiplier = 1.0; break; case ENVELOPE_STAGE_ATTACK: currentLevel = minimumLevel; calculateMultiplier(currentLevel, 1.0, nextStageSampleIndex); break; case ENVELOPE_STAGE_DECAY: currentLevel = 1.0; calculateMultiplier(currentLevel, fmax(stageValue[ENVELOPE_STAGE_SUSTAIN], minimumLevel), nextStageSampleIndex); break; case ENVELOPE_STAGE_SUSTAIN: currentLevel = stageValue[ENVELOPE_STAGE_SUSTAIN]; multiplier = 1.0; break; case ENVELOPE_STAGE_RELEASE: // We could go from ATTACK/DECAY to RELEASE, // so we're not changing currentLevel here. calculateMultiplier(currentLevel, minimumLevel, nextStageSampleIndex); break; default: break; } }
currentStage
currentSampleIndex
. . , , . stageValue[currentStage]
double
( ), , , . Switch
. OFF ( , ). ATTACK minimumLevel
, multiplier
currentLevel
1.0
. DECAY stageValue[ENVELOPE_STAGE_SUSTAIN]
, fmax
, . RELEASE currentLevel
, , minimumLevel
. , RELEASE currentLevel
, . . SUSTAIN, ATTACK DECAY. , SUSTAIN , stageValue[ENVELOPE_STAGE_SUSTAIN]
, . currentLevel
.
:
void EnvelopeGenerator::setSampleRate(double newSampleRate) { sampleRate = newSampleRate; }
private
Synthesis
( ) :
EnvelopeGenerator mEnvelopeGenerator;
#include "EnvelopeGenerator.h"
.
, ProcessDoubleReplacing
Synthesis.cpp leftOutput[i]
:
// leftOutput[i] = rightOutput[i] = mOscillator.nextSample() * velocity / 127.0; if (mEnvelopeGenerator.getCurrentStage() == EnvelopeGenerator::ENVELOPE_STAGE_OFF) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); } if (mEnvelopeGenerator.getCurrentStage() == EnvelopeGenerator::ENVELOPE_STAGE_SUSTAIN) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); } leftOutput[i] = rightOutput[i] = mOscillator.nextSample() * mEnvelopeGenerator.nextSample() * velocity / 127.0;
. : OFF ATTACK, SUSTAIN RELEASE. .
mEnvelopeGenerator
. Synthesis::Reset()
:
mEnvelopeGenerator.setSampleRate(GetSampleRate());
. - — ! .
Note On/Off
, (, - , ), : — ATTACK, — RELEASE. mMIDIReceiver
note on/off, , - .
#include EnvelopeGenerator.h
MIDIReceiver.h . , Synthesis.h mEnvelopeGenerator
mMIDIReceiver
, . MIDIReceiver
enterStage
note on/off.
, . . MIDIReceiver
EnvelopeGenerator
. , MIDIReceiver
, EnvelopeGenerator
.
. Qt. , , , . , . , setText()
, . , , , . , , . ( Oscillator
, EnvelopeGenerator
, MIDIReceiver
). , . . Synthesis
.
Qt . Signals . . Signal.h GallantSignal.h , . Delegate.h GallantSignal.h ( , , “Copy items into destination group's folder”) Xcode. VisualStudio , Solution Explorer.
, MIDIReceiver
, . MIDIReceiver.h :
#include "GallantSignal.h" using Gallant::Signal2;
Signal2
, . — Signal0
Signal8
, .
public
:
Signal2< int, int > noteOn; Signal2< int, int > noteOff;
, int
. MIDIReceiver.cpp advance
:
// A key pressed later overrides any previously pressed key: if (noteNumber != mLastNoteNumber) { mLastNoteNumber = noteNumber; mLastFrequency = noteNumberToFrequency(mLastNoteNumber); mLastVelocity = velocity; // Emit a "note on" signal: noteOn(noteNumber, velocity); }
// If the last note was released, nothing should play: if (noteNumber == mLastNoteNumber) { mLastNoteNumber = -1; noteOff(noteNumber, mLastVelocity); }
— , — . mLastFrequency
-1
, : RELEASE. mLastVelocity
: , .
, , , . . .
mEnvelopeGenerator
. - onNoteOn
onNoteOff
EnvelopeGenerator
. , EnvelopeGenerator
. , . enterStage
- . - private
Synthesis
:
inline void onNoteOn(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_ATTACK); }; inline void onNoteOff(const int noteNumber, const int velocity) { mEnvelopeGenerator.enterStage(EnvelopeGenerator::ENVELOPE_STAGE_RELEASE); };
.
, Synthesis.cpp :
mMIDIReceiver.noteOn.Connect(this, &Synthesis::onNoteOn); mMIDIReceiver.noteOff.Connect(this, &Synthesis::onNoteOff);
— , — -.
ProcessDoubleReplacing
. if
, , .
!
. . DECAY, RELEASE, . stageValues
.
, , . GUI - :

!
.
:
martin-finke.de/blog/articles/audio-plugins-011-envelopes