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

シリーズのすべての投稿:
パート1.紹介とセットアップ
パート2.コードの学習
パート3. VSTおよびAU
パート4.デジタル歪み
パート5.プリセットとGUI
パート6.信号合成
パート7. MIDIメッセージの受信
パート8.仮想キーボード
パート9.封筒
パート10. GUIの改善
パート11.フィルター
パート12.低周波発振器
パート13.再設計
パート14.ポリフォニー1
パート15.ポリフォニー2
パート16.アンチエイリアス



REAPERで仮想キーボードを設定することはそれほど明白ではありません。さらに、ユーザーホストにはそのような機能がまったくない場合があります。 GUIに小さなオンスクリーンキーボードを追加しましょう。


GUI要素



WDL-OLでは、GUI要素はcontrolsと呼ばれます 。 このライブラリには、このタスクに必要なすべての機能を備えたIkeyboardControlクラスがあります。
1つの背景画像と2つの追加のスプライトを使用します。1つは黒のキーを押し、もう1つは白のキーを押します。 論理的です。すべての黒のキーは同じ形状で、白のキーは異なります。 キーを押すと、これらのスプライトがキーボードの背景画像の上に表示され、常に表示されます。
すばらしいカスタムキーを作成する場合は、 このガイドをご覧ください 。 さて、ライブラリに付属するものは次のようになります。







これらのファイルをダウンロードして、プロジェクトフォルダー/ resources / img /にドロップします。 Xcodeを使用する場合は、それらをウィンドウにドラッグしてプロジェクトに追加します。 いつものように、グラフィックの操作は、ファイル名をresource.hに追加することから始まります。 同時に、そこにいる間に、 knob.pngbackground.pngへのリンクを削除し、プロジェクトからファイル自体を削除します。

 // Unique IDs for each image resource. #define BG_ID 101 #define WHITE_KEY_ID 102 #define BLACK_KEY_ID 103 // Image resource locations for this plug. #define BG_FN "resources/img/bg.png" #define WHITE_KEY_FN "resources/img/whitekey.png" #define BLACK_KEY_FN "resources/img/blackkey.png" 


より大きなウィンドウサイズが必要:

 // GUI default dimensions #define GUI_WIDTH 434 #define GUI_HEIGHT 66 


Windowsでは、 .pngファイルをアセンブリに含めるには、 Synthesis.rcヘッダーも編集する必要があります。

 #include "resource.h" BG_ID PNG BG_FN WHITE_KEY_ID PNG WHITE_KEY_FN BLACK_KEY_ID PNG BLACK_KEY_FN 


次に、 Synthesis.hファイルのpublicセクションに、 Synthesisクラスのメンバーをいくつか追加します。

 public: // ... // Needed for the GUI keyboard: // Should return non-zero if one or more keys are playing. inline int GetNumKeys() const { return mMIDIReceiver.getNumKeys(); }; // Should return true if the specified key is playing. inline bool GetKeyStatus(int key) const { return mMIDIReceiver.getKeyStatus(key); }; static const int virtualKeyboardMinimumNoteNumber = 48; int lastVirtualKeyboardNoteNumber; 


Synthesis.cppの初期化リストに、lastVirtualKeyboardNoteNumberを追加します。

 Synthesis::Synthesis(IPlugInstanceInfo instanceInfo) : IPLUG_CTOR(kNumParams, kNumPrograms, instanceInfo), lastVirtualKeyboardNoteNumber(virtualKeyboardMinimumNoteNumber - 1) { // ... } 


演奏されたノートのソースがホストの場合、プラグインキーボードで押された状態で表示されるはずです。 キーボードはgetNumKeysgetKeyStatusを呼び出して、どのキーが押されたかを調べます。 前回、これらの機能をMIDIReceiver実装MIDIReceiverであるため、 MIDIReceiver

プライベートセクションでは、次の行も追加する必要があります。

 IControl* mVirtualKeyboard; void processVirtualKeyboard(); 


IControlクラスは、すべてのGUIコントロールの基本クラスです。 IkeyboardControlIkeyboardControlオブジェクトを宣言することはできません。これは、 .hファイルには「不明」 IkeyboardControlです。 したがって、ポインターを使用する必要があります。 IKeyboardControl.hには 、「プラグインのクラスを宣言した後にこのヘッダーを追加(#include)する必要があるため、プラグインのメインの.cppファイルに追加するのが最善です」というコメントがあります。
状況を明確にするために、 Synthesis.cppを見てみましょう。 #include resource.h前に#include "IKeyboardControl.h"を追加します。
次に、コンストラクタを変更します。

 Synthesis::Synthesis(IPlugInstanceInfo instanceInfo) : IPLUG_CTOR(kNumParams, kNumPrograms, instanceInfo), lastVirtualKeyboardNoteNumber(virtualKeyboardMinimumNoteNumber - 1) { TRACE; IGraphics* pGraphics = MakeGraphics(this, kWidth, kHeight); pGraphics->AttachBackground(BG_ID, BG_FN); IBitmap whiteKeyImage = pGraphics->LoadIBitmap(WHITE_KEY_ID, WHITE_KEY_FN, 6); IBitmap blackKeyImage = pGraphics->LoadIBitmap(BLACK_KEY_ID, BLACK_KEY_FN); // C# D# F# G# A# int keyCoordinates[12] = { 0, 7, 12, 20, 24, 36, 43, 48, 56, 60, 69, 72 }; mVirtualKeyboard = new IKeyboardControl(this, kKeybX, kKeybY, virtualKeyboardMinimumNoteNumber, /* octaves: */ 5, &whiteKeyImage, &blackKeyImage, keyCoordinates); pGraphics->AttachControl(mVirtualKeyboard); AttachGraphics(pGraphics); CreatePresets(); } 


背景画像を添付すると興味深いことが始まります。 最初に、押された黒と白のキーをIbitmapオブジェクトの形式でIbitmapます。 LoadIBitmap6 )関数の3番目の引数は、 whitekeys.pngに6つのフレームが含まれていることをグラフィックスシステムに伝えます。

デフォルトでは、pRegularKeysには6つの画像(C / F、D、E / B、G、A、上部C)が含まれている必要がありますが、pSharpKeyには1つの画像(すべてのフラット/シャープ)しか含まれていません。
-IKeyboardControl.h

keyCoordinates配列は、システムに左境界線を基準とした各キーのオフセットを伝えます。 このアクションは1オクターブのみで実行する必要があり、 IKeyboardControl他のすべてのオクターブのオフセットを計算します。
大まかに言えば、次の行では、新しいオブジェクトnew IKeyboardControlを初期化し、名前mVirtualKeyboardを割り当てます。 多くの情報を送信します。

興味深いことに、仮想キーボードオブジェクトはbg.pngファイルの存在さえも知りません。 彼はただ彼を必要としません、すべてがそのように機能します。 キーボード画像は背景画像の一部になることができ、 IkeyboardControlコンストラクターに渡すためだけにこのピースをカットする必要があるため、これはプラスです。

C ++でプログラミングの経験がある場合は、条件反射が発生するはずです。コンストラクターにはnewが含まれているため、 delete mVirtualKeyboard必要があります。 ただし、これを行ってからトラックからプラグインを削除すると、 ランタイム例外がポップアップします。 理由は、呼び出しが行われるときです

 pGraphics->AttachControl(mVirtualKeyboard); 

メモリ管理をグラフィックシステムに移行し、このメモリ領域の管理は私たちの責任ではなくなりました。 deleteを使用して、既に空きメモリ領域の固定を解除します。

次に、 CreatePresets関数の本体を削除します。

 void Synthesis::CreatePresets() { } 


そして、kKeybXとkKeybYをELayoutに追加します。

 enum ELayout { kWidth = GUI_WIDTH, kHeight = GUI_HEIGHT, kKeybX = 1, kKeybY = 0 }; 


パフォーマンス上の理由から、 IKeyboardControlはそれ自体を再描画しません。 これはグラフィックプログラミングの一般的な方法です。GUI要素を「ダーティ」としてマークします。つまり、そのイメージは次の再描画サイクルでのみ更新されます。 IKeyboardControl.h 、特にOnMouseDownOnMouseUpを見ると、 mKeymKey割り当てられており、( Drawではなく) SetDirty関数がSetDirtyていることがSetDirtyます。 SetDirtyIControlクラスのメンバー関数です(その実装はそれぞれIControl.cppにあります)。 このコントロールのmDirtyパラメーターmDirtytrueに設定しtrue 。 再描画サイクルごとに、グラフィックシステムはmDirtytrueであるすべてのGUI要素を再描画しtrue 。 グラフィックシステムのこの側面を理解することが重要なので、このような詳細を掘り下げました。

外部MIDIメッセージへの応答



これまでのところ、キーボードをクリックすると汚れています。 mMIDIReceiverからmMIDIReceiverデータを受信しますが、外部MIDIデータも受信する必要があります。 mVirtualKeyboardmMIDIReceiverはお互いについて何も知らないので、 Synthesis.cppで ProcessMidiMsgを編集しましょう。

 void Synthesis::ProcessMidiMsg(IMidiMsg* pMsg) { mMIDIReceiver.onMessageReceived(pMsg); mVirtualKeyboard->SetDirty(); } 


最初に、 mMIDIReceiverは受信したMIDIデータに従ってmLast...メンバーを更新します。 その後、 mVirtualKeyboardはダーティとしてマークされます。 次に、次の再描画サイクルで、 DrawmVirtualKeyboardに対してmVirtualKeyboardれ、次にGetNumKeysおよびGetKeyStatusを呼び出します。 最初は複雑に思えるかもしれませんが、実際には、冗長性と不要な動きを避ける透明で明確に構造化された設計です。
仮想キーボードは外部MIDIメッセージに応答し、押されたキーを正しく描画します。

キーボード応答



ホストに組み込まれた仮想キーボードの押下にキーボードを強制的に応答させ、MIDIメッセージを生成し、受信者mMIDIReceiver送信します。
forループの直前にこの呼び出しをProcessDoubleReplacing追加します。

 processVirtualKeyboard(); 


そして適切な関数を書きます:

 void Synthesis::processVirtualKeyboard() { IKeyboardControl* virtualKeyboard = (IKeyboardControl*) mVirtualKeyboard; int virtualKeyboardNoteNumber = virtualKeyboard->GetKey() + virtualKeyboardMinimumNoteNumber; if(lastVirtualKeyboardNoteNumber >= virtualKeyboardMinimumNoteNumber && virtualKeyboardNoteNumber != lastVirtualKeyboardNoteNumber) { // The note number has changed from a valid key to something else (valid key or nothing). Release the valid key: IMidiMsg midiMessage; midiMessage.MakeNoteOffMsg(lastVirtualKeyboardNoteNumber, 0); mMIDIReceiver.onMessageReceived(&midiMessage); } if (virtualKeyboardNoteNumber >= virtualKeyboardMinimumNoteNumber && virtualKeyboardNoteNumber != lastVirtualKeyboardNoteNumber) { // A valid key is pressed that wasn't pressed the previous call. Send a "note on" message to the MIDI receiver: IMidiMsg midiMessage; midiMessage.MakeNoteOnMsg(virtualKeyboardNoteNumber, virtualKeyboard->GetVelocity(), 0); mMIDIReceiver.onMessageReceived(&midiMessage); } lastVirtualKeyboardNoteNumber = virtualKeyboardNoteNumber; } 


GetKeyは、押されたキーに対応するノート番号を提供します。 IKeyboardControlはマルチタッチをサポートしていないため、一度に1つのキーしか押すことができません。 最初のifは、もう押されていないキー(ある場合)をリリースします。 この関数はmBlockSizeサンプルごとにmBlockSizeれるため、2番目のifは、このクリックに対してメッセージのメモが 1つだけ生成されるようにします( mBlockSizeサンプルごとではありません)。 関数が呼び出されるたびにこれらの「繰り返しクリック」を避けるために、 lastVirtualKeyboardNoteNumberの値を覚えています。

行こう!



シンセを再び起動する準備ができました! すべてが正しく行われていれば、彼のキーボードで演奏できます。 そして、仮想ホストキーボードまたは他の接続されたMIDIソースの使用がプラグインキーボードに表示されます(順番に、最後に押されたキーが表示されます)。 確かに、音はこの最後の1つのキーにのみ対応します。 ポリフォニーについては少し後で説明します。
クラシックシンセサイザーサウンドで、お友達に自慢したり、お気に入りのベートーベンを演奏したりできます。 今だけ音は一種の「木」であり、キーを押して放すとクリック音が聞こえます。 これは、洞が生成される場合に特に顕著です。 そのため、封筒を追加する必要があります。 これは次の投稿で行います。

この段階のプロジェクトファイルは、ここからダウンロードできます

元の記事:
martin-finke.de/blog/articles/audio-plugins-010-virtual-keyboard

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


All Articles