4.ここが要。実際の信号処理関連メソッド
(15)void setSampleRate(float sampleRate)
→サンプリング周波数はコレにきまりました通知
サンプリング周波数が通知されてくるだけです。 本来はこれにあわせてシンセの各種係数を再計算などするのだと 思います。 Synth1では44.1K固定なので、係数計算などは何もしてません。
サンプルと同じように、
AudioEffectX::setSampleRate (sampleRate);
を呼んでるだけです。
(16)void setBlockSize (long blockSize)
→ブロックサイズはコレにきまりました通知
ブロックサイズなるものが通知されてくるだけです。 これもサンプルと同じように、
AudioEffectX::setBlockSize (blockSize);
を呼んでるだけです。
ブロックサイズとなにか。 これは、イベント処理(processEvent)をする際に必要になる 基準サイズだと思ってもらえばいいと思います。 midiイベントの時間情報は、このサイズの範囲内に収められています。 (のはずです。) 詳細はprocessEvent()で説明します。
(17)void resume ()
→レジューム??
サンプルと同じです。
wantEvents();
これは、イベントループ関連だと思いますがここは、いじくる必要ないと思います。
(18)void process (float **inputs, float **outputs, long sampleFrames)
→信号処理をして、出力バッファに入れなさい指示その1
ここです。ここが実際の音声信号処理を行うところです。 シンセなので、入力inputsは無視です。 指示されたsampleFramesだけ、信号を生成します。 ステレオなら出力バッファは、outputs[0],outputs[1]がそれぞれLRです。 ただし、出力信号をそのままoutputs[0],[1]に書き出すのではなく、 もともと入っている信号に加算する必要があります。 なお、信号レンジは-1.0~+1.0が適正値です。
ここでは、sampleFrames回の信号処理ループを行うことになりますが、 ループ中に、適宜midiイベントを取り出して処理を進めていく必要が あります。この辺の処理が厄介なのですが、サンプルではこの辺の処理は かなり省略されています。
たとえば、ドの音がnoteオンの状態ではドの音を出力すればよいですが、 noteOffになった時からは、出力を切るかまたは、リリース用の音に切り替えて 信号を出力しないといけません。これは、noteOn/Offに限らず、ピッチベンドなど あらゆるmidiメッセージでもいえることです。 要するにMIDIの時間情報と同期を取る必要があります。 そのために、信号処理ループの中でmidiメッセージの取り出しを行う必要があるのです。 では、この同期に必要な情報はなにかというと、
a)midiイベント内の、deltaFrames
b)現在、信号処理を行っているカレントサンプルFrame
c)一連のmidiメッセージの中でのカレントmidiイベント番号
になります。b)とc)は、VSTiのフレームワークにはどうも取り入れられていないような ので、自前でメンバ変数として用意する必要があります。といっても、とても簡単なものです。 これらは、曲の先頭を0として延々続くものではなく、processEvent()が呼ばれた時に リセット=0として(基点として)、あとは、process()/processReplacing()で処理する度に、 進めればいいのです。 また、a)のdeltaFramesも、そのようにprocessEvent()が呼ばれた瞬間を基点した値になっています。 c)の一連のmidiメッセージは、processEvents()で引き渡ってきます。 上記を前提に擬似コードをかくと、 // メンバ変数
VstMidiEvent *m_events; // 一連のmidiイベント
int m_eventNum; // 一連のmidiイベントの数
int m_currentEventNo; // c)カレントのmidiイベント番号
int m_currentSample; // b)カレントサンプルFrame
void MySynth::process(float **inputs, float **outputs, long sampleframes)
{
int nRestSample=sampleframes;
VstMidiEvent *pE;
int nProcessSample;
// 信号処理ループ
while(nRestSample > 0){
// 次のイベントまでの処理できる時間を求める
if(m_currentEventNo<m_eventNum){
// 次のイベントあるので処理時間求める
pE = (VstMidiEvent*)&m_events[m_currentEventNo];
nProcessSample=pE->deltaFrames-m_currentSample;
nProcessSample=MAX(0,MIN(nRestSample,nProcessSample));
}else{
// もうイベントはないので残り全部いっきに処理
pE=NULL;
nProcessSample=nRestSample;
}
// 信号処理
int nRestSample2;
nRestSample2=nProcessSample;
while(nRestSample2>0){
// 信号処理
xxxxx;
xxxxx;
xxxxx;
nRestSample2 --;
}
m_currentSample += nProcessSample; // カレントサンプル進める
// MIDIイベント処理
if(pE){
MyProcessMIDIShortData(pE->midiData[0],pE->midiData[1],pE->midiData[2]);
m_currentEventNo++; // カレントイベント進める
}
nRestSample -= nProcessSample;
}
}
という感じです。
(19)void processReplacing (float **inputs, float **outputs, long sampleFrames)
→信号処理をして、出力バッファに入れなさい指示その2
基本的にprocess()と同じです。違いは、process()では、outputsの値に加算したのに対し、 ここでは、そのまま上書きすることです。
(20)long processEvents (VstEvents* ev)
→イベント処理
ホストから、midiメッセージが送られてきます。 正確には、VSTイベントと言われるひとつ抽象度か上のものなので、 ここから、midiイベントだけをバッファリングしておきます。 同時に、カレントサンプルなどをリセットします。 Synth1では、こんな感じです。 m_eventMaxNum; // バッファ(=m_events)のサイズ(単位はイベント数)です。
long Synth1VST::processEvents (VstEvents* ev)
{
m_currentEventNo=0; // カレントサンプルFramesリセット
m_currentSample=0; // カレンロ
m_eventNum=0; // イベント数0リセット
if(ev->numEvents>0){
if(m_eventMaxNum < ev->numEvents){
// 今のバッファサイズで小さすぎるので、サイズ拡張
if(m_events) delete m_events;
m_eventMaxNum = ev->numEvents;
m_events = new VstEvent[m_eventMaxNum];
}
// MIDIイベントをコピー
for(int i=0;i<ev->numEvents;i++){
if(ev->events[i]->type==kVstMidiType){
m_events[m_eventNum]=*(ev->events[i]);
m_eventNum++;
}
}
}
return 1; // want more
}