シリーズのすべての投稿:
パート1.紹介とセットアップパート2.コードの学習パート3. VSTおよびAUパート4.デジタル歪みパート5.プリセットとGUIパート6.信号合成パート7. MIDIメッセージの受信パート8.仮想キーボードパート9.封筒パート10. GUIの改善パート11.フィルターパート12.低周波発振器パート13.再設計パート14.ポリフォニー1パート15.ポリフォニー2パート16.アンチエイリアス
これまでのところ、特定の周波数で鳴るだけの一定の音波のみを生成しました。 受信したノートに応じて、MIDIメッセージに応答し、目的の周波数で波の生成をオンまたはオフにする方法を見てみましょう。
MIDIメッセージの受信
MIDI処理の基本
プラグインがホストにロードされると、リンク先のトラックからすべてのMIDIメッセージを受信します。 メモが開始および終了すると、
ProcessMidiMsg
関数がプラグインで呼び出されます。 ノートに加えて、MIDIメッセージはポルタメント情報(ピッチベンド)およびコントロールコマンド(
コントロール変更 、CCと略記)を送信できます。これらはプラグインパラメーターの自動化に使用できます。
IMidiMsg
メッセージが
ProcessMidiMsg
関数に送信されます。この関数は、形式に依存しない形式でMIDIイベントを記述します。 この説明には、オシレーターのピッチに関する情報を含む
NoteNumber
および
Velocity
パラメーターがあります。
MIDIメッセージが到着するたびに、システムは以前に満たされたオーディオバッファを既に再生しています。 MIDIメッセージを受信したときに新しいオーディオを正確に詰め込む方法はありません。 これらのイベントは、次に
ProcessDoubleReplacing
関数を呼び出す前に記憶する必要があります。 また、メッセージを受信した時間を覚えておく必要があります。そうすれば、次のバッファ充填のためにこの情報をそのまま残すことができます。
これらのタスクを実行するツールは
IMidiQueueになります。
MIDI受信者
合成プロジェクトを使用します。 バージョン管理システムを使用している場合は、プロジェクトをコミットします。 新しい
MIDIReceiverクラスを作成し、各ターゲットで
.cppがコンパイルされることを確認します。
MIDIReceiver.hで、 #define
と
#endif
間にインターフェイスを挿入します。
#pragma clang diagnostic push #pragma clang diagnostic ignored "-Wextra-tokens" #include "IPlug_include_in_plug_hdr.h" #pragma clang diagnostic pop #include "IMidiQueue.h" class MIDIReceiver { private: IMidiQueue mMidiQueue; static const int keyCount = 128; int mNumKeys;
ここで
IPlug_include_in_plug_hdr.hを有効にする必要があります。そうしないと、
IMidiQueue.hがエラーを作成します。
ご覧のとおり、MIDIメッセージキューを格納するための
private
IMidiQueue
オブジェクトがあります。 また、現在再生されているノートと、再生されているノートの数に関する情報も保存します。 プラグインはモノフォニックになる
mLast...
、3つの
mLast...
パラメーターが必要
mLast...
次の各ノートは前のノートを消し去ります(いわゆる
最後のノートの優先度 )。
noteNumberToFrequency
関数は、MIDIノートの数をヘルツ
noteNumberToFrequency
周波数に変換します。
Oscillator
クラスはノートナンバーではなく周波数で動作するため、これを使用します。
public
セクションには多数の
inline
ゲッターが含まれ、
Flush
および
Resize
を
mMidiQueue
。
Flush
の本体では
Flush
mOffset
をゼロに設定します。
mMidiQueue.Flush(nFrames)
呼び出しは、
advance
関数の前の呼び出しでこの部分のイベントをすでに処理したため、キューの先頭からサイズ
nFrames
部分を削除することを意味します。
mOffset
をゼロにすることにより、次に
mOffset
を実行するときに、キューの開始も処理することが保証されます。 括弧の後に現れる
const
は、関数
がそのクラスの不変のメンバーを変更しないことを意味します。
onMessageReceived
実装を
MIDIReceiver.cppに追加しましょう。
void MIDIReceiver::onMessageReceived(IMidiMsg* midiMessage) { IMidiMsg::EStatusMsg status = midiMessage->StatusMsg();
この関数は、MIDIメッセージを受信するたびに呼び出されます。 現在、
ノートオンメッセージと
ノートオフメッセージ(ノートの再生の開始/停止)のみに関心があり、それらを
mMidiQueue
追加し
mMidiQueue
。
次の興味深い機能は
advance
です:
void MIDIReceiver::advance() { while (!mMidiQueue.Empty()) { IMidiMsg* midiMessage = mMidiQueue.Peek(); if (midiMessage->mOffset > mOffset) break; IMidiMsg::EStatusMsg status = midiMessage->StatusMsg(); int noteNumber = midiMessage->NoteNumber(); int velocity = midiMessage->Velocity();
この関数は、オーディオバッファがいっぱいになっている間、
サンプルごとに呼び出さ
れます 。 キューにメッセージがある限り、メッセージを処理して最初から削除します(
Peek
および
Remove
を使用)。 ただし、これは、オフセット(
mOffset
)がバッファーオフセットより大きくないMIDIメッセージに対してのみ行います。 つまり、対応するサンプルの各メッセージを処理し、相対的な時間シフトをそのまま残します。
noteNumber
と
Velocity
値を読み取った後、条件付き
if
は
ノートオンと
ノートオフのメッセージを分離
します(ベロシティ値がないと
ノートオフと解釈され
ます )。 どちらの場合も、どのノートが再生され、現在再生されているノートの数を追跡します。
mLast...
値も更新され、最後のノートに優先順位が付けられます。 さらに、音の周波数を更新する必要があるのはここであるということは論理的です。 最後に、
mOffset
更新され、受信者に、このメッセージが現在バッファ内にどの程度あるかが通知されます。 別の方法で受信者にこれを伝えることもできます-引数としてオフセットを渡すことによって。
したがって、すべての着信MIDIノートのオン/オフメッセージを受信するクラスがあります。 現在再生されているノート、最後のノート、およびその頻度を追跡します。 それを使用しましょう。
MIDI受信者を使用する
開始するには、
resource.hにこれらの変更を慎重に加えます。
これらの行は、プラグインが「MIDI対応」であることをホストに伝えます。
0-1
および
0-2
は、プラグインにオーディオ入力がなく、出力が1つあることを示します。 モノ(
0-1
)、またはオーディオ入力がなく、ステレオ出力(
0-2
)があります。
次に、
Oscillator.h
後に
#include "MIDIReceiver.h"
を
Synthesis.hに追加します。 同じ場所の
public
セクションで、メンバー関数の宣言を追加します。
private
セクションに
MIDIReceiver
オブジェクトを追加します。
private:
Synthesis.cppで、次の単純な関数を記述します。
void Synthesis::ProcessMidiMsg(IMidiMsg* pMsg) { mMIDIReceiver.onMessageReceived(pMsg); }
MIDIメッセージが受信されるたびに呼び出され、受信者にメッセージを送信します。
少し整理しましょう。 上部の両方の
enums
を編集します。
enum EParams { kNumParams }; enum ELayout { kWidth = GUI_WIDTH, kHeight = GUI_HEIGHT };
そして、デフォルトのプリセットを1つだけ作成します。
void Synthesis::CreatePresets() { MakeDefaultPreset((char *) "-", kNumPrograms); }
プラグインのパラメーターを変更する場合、何もする必要はありません:
void Synthesis::OnParamChange(int paramIdx) { IMutexLock lock(this); }
インターフェイスのハンドルは、もはや役に立ちません。 コンストラクタを必要最小限に減らしましょう:
Synthesis::Synthesis(IPlugInstanceInfo instanceInfo) : IPLUG_CTOR(kNumParams, kNumPrograms, instanceInfo) { TRACE; IGraphics* pGraphics = MakeGraphics(this, kWidth, kHeight); pGraphics->AttachPanelBackground(&COLOR_RED); AttachGraphics(pGraphics); CreatePresets(); }
システムのサウンド設定が変更されると、オシレーターに新しいサンプリング周波数を伝える必要があります。
void Synthesis::Reset() { TRACE; IMutexLock lock(this); mOscillator.setSampleRate(GetSampleRate()); }
ProcessDoubleReplacing
関数はまだあります。 考えてみてください:
mMIDIReceiver.advance()
は各サンプルを実行する必要があります。 その後、MIDIレシーバーの
getLastVelocity
と
getLastFrequency
を使用して、周波数と音量を
getLastVelocity
ます。 次に、
mOscillator.setFrequency()
および
mOscillator.generate()
を呼び出して、オーディオバッファーを目的の周波数のサウンドで満たします。
generate
関数は、バッファ全体を処理するために作成されました。 MIDIレシーバーは、個別のサンプルのレベルで動作します。メッセージは、バッファー内で任意のオフセットを持つことができます。つまり、
mLastFrequency
は任意のサンプルで変更できます。 サンプルレベルでも機能するように、
Oscillator
クラスを改良する必要があります。
最初に、
generate
から
twoPI
を
twoPI
し、
private
セクション
Oscillator.hに移動します。 ここにいる間に、リンデン変数
bool
をすぐに追加して、オシレーターがミュートされている(つまり、ノートが再生されていない)かどうかを示します。
const double twoPI; bool isMuted;
初期化リストにコンストラクタを追加して、それらを初期化します。 これは次のようになります。
Oscillator() : mOscillatorMode(OSCILLATOR_MODE_SINE), mPI(2*acos(0.0)), twoPI(2 * mPI),
インラインセッターをパブリックセクションに追加します。
inline void setMuted(bool muted) { isMuted = muted; }
そして、このすぐ下に次の行を挿入します。
double nextSample();
この関数をサンプルごとに呼び出し、オシレーターからオーディオデータを受け取ります。
Oscilator.cppに次のコードを追加します。
double Oscillator::nextSample() { double value = 0.0; if(isMuted) return value; switch (mOscillatorMode) { case OSCILLATOR_MODE_SINE: value = sin(mPhase); break; case OSCILLATOR_MODE_SAW: value = 1.0 - (2.0 * mPhase / twoPI); break; case OSCILLATOR_MODE_SQUARE: if (mPhase <= mPI) { value = 1.0; } else { value = -1.0; } break; case OSCILLATOR_MODE_TRIANGLE: value = -1.0 + (2.0 * mPhase / twoPI); value = 2.0 * (fabs(value) - 0.5); break; } mPhase += mPhaseIncrement; while (mPhase >= twoPI) { mPhase -= twoPI; } return value; }
ご覧のとおり、
twoPI
使用されてい
twoPI
。 サンプルごとにこの値を計算することは冗長なので、クラスに定数として2つのpiを追加しました。
発振器が何も生成しない場合、ゼロを返します。
switch
構造はすでにおなじみですが、ここでは
for
ループを使用していません。 ここでは、バッファ全体を埋めるのではなく、バッファの単一の値を生成します。 また、同様の構造により、位相の増分を終了し、繰り返しを避けることができます。
これは、コードの柔軟性が不十分なために発生するリファクタリングの好例です。 もちろん、「バッファ」アプローチで
generate
関数を書き始める前に、1、2時間考えることができます。 しかし、この実装には1時間もかかりませんでした。 単純なアプリケーション(このような)では、アプローチを実装し、実際にコードがタスクを処理する方法を確認する方が効率的である場合があります。 ほとんどの場合、先ほど見たように、アイデア全体が正しいことがわかりました(異なる音波を計算する原理)が、問題のいくつかの側面が見落とされていました。 一方、パブリックAPIを開発している場合、後で何かを変更して、軽度の変更を加えるのは不便なので、事前に考え直す必要があります。 一般的に、状況によって異なります。
setFrequency
関数もすべてのサンプルで呼び出されます。 これは、
updateIncrement
も頻繁に呼び出されることを意味します。 しかし、まだ最適化されていません。
void Oscillator::updateIncrement() { mPhaseIncrement = mFrequency * 2 * mPI / mSampleRate; }
2 * mPI * mSampleRate
は、サンプリングレートが変更された場合にのみ変更されます。 したがって、この計算の結果は、
Oscillator::setSampleRate
内でのみ覚えて再カウントする方が適切
Oscillator::setSampleRate
。 超越的な最適化はコードを読みにくく、さらに見苦しくすることもあることを覚えておく価値があります。 特定のケースでは、基本的なモノフォニック構文を作成しているため、パフォーマンスの問題は発生しません。 ポリフォニーに到達するとき、それは別の問題になり、その後、確実に最適化されます。
これで、
Synthesis.cppの ProcessDoubleReplacing
を書き換えることができます。
void Synthesis::ProcessDoubleReplacing( double** inputs, double** outputs, int nFrames) {
forループでは、MIDIレシーバーが最初に値を更新します(
advance
が呼び出されます)。 音が鳴る(
velocity > 0
)場合、オシレーターの周波数を更新して鳴らします。 それ以外の場合は、スタブします(
nextSample
はゼロを返します)。
次に、単に
nextSample
を呼び出して値を取得し、ボリュームを変更し(
velocity
は
127
整数)、出力バッファーに結果を書き込みます。 最後に、キューの先頭を削除するために
Flush
が呼び出されます。
テスト
VSTまたはAUを実行します。 AUがホストに表示されない場合、
resource.hの
PLUG_UNIQUE_ID
を変更する必要がある場合があります。 2つのプラグインのIDが同じ場合、ホストは1つを除くすべてを無視します。
プラグインは、いくつかのMIDIデータを入力に送信する必要があります。 最も簡単な方法は、REAPERの仮想キーボードを使用することです(
[表示]→[仮想MIDIキーボード ]メニュー)。 左側にプラグインがあるトラックには、丸い赤いボタンがあります。 MIDI構成を右クリックしてMIDI構成に移動し、仮想キーボードからメッセージを受信することを選択します。
同じメニューで、
モニター入力を有効にします。 これで
仮想キーボードウィンドウに
フォーカスが置かれたので 、通常のキーボードでシンセサイザーを演奏できます。 パスワードマネージャーからユーザー名またはパスワードを入力し、どのように聞こえるかを聞きます。
MIDIキーボードを使用している場合、それを接続することにより、スタンドアロンアプリケーションをテストできます。 主なことは、正しいMIDI入力を選択することです。 音が聞こえない場合は、
〜/ Library / Application Support / Synthesis / settings.iniを削除してみてください。
この段階のプロジェクト全体は
、ここからダウンロードでき
ます 。
次回、インターフェースに素敵なキーボードを追加します:)
元の記事:
martin-finke.de/blog/articles/audio-plugins-009-receive-midi