C++BuilderでのVST Plugin作成法


2004.01.21 内容訂正


1.はじめに

ただでさえVST作成の日本語情報はごくわずかの中、手持ちのコンパイラがC++Builder(以下CB)であったため、なおさら情報収集に苦労しました。そこでCBでVST Pluginを作成するための留意点をまとめてみました。

自分自身、VSTもCBもよく理解しているわけではないので、誤認や不正確な部分もあるかもしれません。
間違い等を発見しましたらご指摘いただければ幸いです。
また制作、公開にあたっては全て自己責任のもとでお願いします。つまりこの文章の内容を実行したことによる不利益の責任は著者は負いません。

なお転載禁止は常識的として出版物での紹介も、上記のインターネットでの自己責任原則が理解されない恐れがあるので厳禁とします。
リンクやインターネット上での紹介記事は構いませんが、できればご一報いただければと思います。

2.準備

C++Builderの準備はもちろんですが、コンパイラ単独ではVSTは作れないのでそれ以外に必要な物を示します。

2.1 VST SDKのダウンロード

1)VST SDK2.0をDeveloper Infomationより、指示に従って、ライセンスのサインアップしてください。
2)折り返しメールがきますので、指示されたサイトよりSDKをダウンロードしてください。
3)ダウンロードしたファイルを適当なディレクトリに展開してください。
4)サイトのどこにもアナウンスされていませんが、実はSDKはバージョンアップされています。
SDK開発者のページより最新バージョンをダウンロードしてください。
※タイトルはVST GUIですが、SDK本体のバージョンアップ分も含まれています。
5)バージョンアップ分はvst2sdk\source\commonに上書きしてください。

2.2 スキル

1)CおよびC++をある程度理解すること
自分もC++を理解しているとはいえませんが、カット&トライで何とかしました。
最低限、自分で文献を調べるなどの努力は必要です。
またヘッダファイルは関数の仕様書です。コメントで仕様が書かれていることもあります。
SDKのヘッダファイルを読み、マニュアルに記述されてない情報も読み取れるぐらいのスキルは必要です。

2)音声処理のアルゴリズムと数学の知識
シンセサイザやエフェクタを1から作るには、多少なりとも音声処理のアルゴリズムや数学を理解する必要があります。
音源のソースコードがあったとしても、サンプリングレートからどのような間隔、フォーマットでホストが用意するバッファにデータを渡すかなど調整が必要になることもあります。

3)英語
SDKのマニュアルは英語で書かれています。マニュアルは一読してから取り組みましょう。
またメーリングリストでも公用語は英語なので、情報を得たいのなら自分で理解しようとする努力が必要です。
メーリングリストとそのアーカイブは貴重な情報源です。投稿しなくても構いませんのでメーリングリストに登録し、わからないことがあったら、まずマニュアルとメーリングリストのアーカイブから情報を探すことをお勧めします。

3.サンプルのコンパイル

3.1 DLL作成の準備

MakeFileがサンプルにありますがこれはVisual C++用で、CB用は用意されていません。
まずVSTのPluginはDLLファイルなので、DLLを作成できるプロジェクトを作成します。

1)CBメインウインドウのファイル(F)->新規作成(N)で新規作成ダイアログを開き、その中から、DLLウイザードを選択する。
2)DLLウイザードダイアログで、VC++スタイルを選択します。なおVCL使用はVCLを使用しない限りチェックしないでください。

3.2サンプルコードの登録

1)プロジェクトにサンプルソースを登録する。
プロジェクト(P)->プロジェクトに追加(A)で対象のサンプルソースと、vstsdk2\source\commonディレクトリにある、audioeffectx.cpp,AudioEffect.cppを登録してください。

2)インクルードパスを通す。
同じくvstsdk2\source\commonディレクトリにあるヘッダーファイルに対しパスが通るように、includeパスの設定をするか、必要なヘッダーファイルをプロジェクトに直接登録してください。

3)Unit1.cppを削除する。
Unit1.cppにはDLLMainが書かれてますが、サンプル内にDLLMainがあるのでそちらを使います。

4).defを登録する。
vstsdk2\winデイレクトリにある.defファイルをプロジェクトに登録します。

3.3 マクロの設定

サンプル内では、CBUILDERマクロにより、コンパイラ依存部を認識していますので、プロジェクトオプション内の条件にCBULDERを追加してください。

3.3 サンプルソースの修正

サンプルはVisualC++で確認されているらしく、CBではいくつか修正を必要とする箇所があります。

1).defファイルを修正する。
.def内のmainをmain=_mainに書き換えます。

2)各サンプルのSetUniqueIDの引数を4文字の文字定数からCCONSTマクロに書き換える。
ex)
SetUniqueID('ADly'); ->SetUniqueID(CCONST('A','D','l','y'));

3)FPUの初期化コード追加
CBのバージョン5以上(?)では取れたバグらしいのですが、FPUの初期化コードを明示する必要があるそうです。

_control87(PC_64|MCW_EM,MCW_PC|MCW_EM);
をPluginのコンストラクタなど初期化部分に追記でください。

以上でGUIを使用していないサンプルはコンパイルできるはずです。

4.シンセサイザの改造要点

サンプルのvstSynthは、矩形と鋸波のモノシンセです。シンセサイザを作るときはこれをベースにすると簡単になります。

4.1 VST SDKにおけるシンセサイザの構成

改造にあたっていじるべき部分は、パラメータのユーザーインターフェースと信号処理部、MIDIイベント処理部の3つからなります。
4.1.1 ユーザインターフェース
プログラム(パッチ)の管理とパラメータを表示、編集するためのルーチンで、vstSynthでは、vstSynth.cppに記述されています。

1.パラメータ識別子
 各パラメータは識別子によって管理され、ホストからアクセスされます。
その定義は、vstsynth.hに列挙型で定義されており、パラメータ数を増やすには、この定義に識別子を追加します。
 kNumParamsはいくつパラメータがあるかを示すもので、パラメータ識別子の列挙の最後に必ず必要です。
 よってパラメータの追加をするときは、0からkNumParamsの間に行ってください。

2.関数
パラメータ操作に関する関数を以下に示します。
プログラムのデータ構成を変更したらこれらを適宜変更する必要があります。

1)setProgram 指定されたプログラムより全てのパラメータを取得します。
2)setProgramName プログラム名を現在選択されているプログラムに格納します。
3)getProgramName プログラム名を現在選択されているプログラムより取得します。
4)getParameterLabel パラメータの単位をテキストで返します。
5)getParameterDisplay パラメータの値をテキストで返します。数値以外も可です。
6)getParameterName パラメータの名前をテキストで返します。
7)setParameter ホストからの値をパラメータと現在選択されているプログラムに格納します。
8)getParameter パラメータをホストに返します。
9)getProgramNameIndexed プログラム名を指定されたプログラムより取得します。 
10)copyProgram 現在選択されているプログラムから指定されたプログラムに内容を全てコピーします。

なおホストがパラメータとして扱う数値はfloatで0から1.0の間のみなので適宜、型変換とスケーリングが必要になります。

4.1.2 信号処理部
パラメータとノート情報より信号を生成するルーチンでシンセサイザの心臓部です。
processおよびprocessReplacingがこれにあたり、vstSynthでは、vstSynthproc.cppに記述されています。
なおprocessとprocessReplacingの違いは、元の信号に加算するかしないかの違いです。

processおよびprocessReplacingは、必要なデータの個数を要求してきます。
要求された分だけ、信号を生成(計算)しoutバッファに格納して返却してください。
なお信号の値はfloatで-1.0〜1.0の範囲です。

またMIDIイベントはデータの位置(deltaFrames)でイベントの実行位置を指定してきますので、キューを作り、そのポジションになったらMIDIイベントに対する動作を反映するようにするとより正確な演奏となります。

4.1.3 MIDIイベント処理部
MIDIデータの解釈をするルーチンで、信号処理部が音源チップならドライバーのような働きをする部分です。
processEvents がこれにあたり、vstSynthでは、vstSynthproc.cppに記述されています。

processEventsはイベント情報としてVstEvents構造体を引数に持ち、このメンバーmidiData[n]には2もしくは3バイトのMidiコマンドが格納されています。
MidiコマンドをMidi規格に従ってコマンド解釈し、信号処理部を駆動してください。
またVstEvents構造体のメンバーdeltaFramesが信号処理部のprocessに対するイベントの実行位置の情報となります。

4.2 リリースに必要なこと

自作、改造した物をリリースするにあたって必要なことがあります。

1)ソフトベンダーとしての契約を最初だけする必要があります。SDKのdocの中にある契約書'Steinberg VST PlugIns SDK Licensing Agreement'を印刷し、必要事項を記載してsteinbergに送付するだけです。
2)IDをID取得ページより取得し、関数UniqueIDの引数を変更してください。
3)ドキュメントやパッケージには、SDKに入っているLicensing Agreementに従って、Steinbergの権利表示が必要です。

5.カスタムGUI

SDKにはGUIライブラリも含まれていますが、Visual C++用のlibで配布されており、GUIサンプルは、CBではコンパイルできません。
VST2.3よりソースからVSTGUIをリコンパイルすることでGUIのサンプルもコンパイルできるようになりました。
そこで、CBのVCLを使用してカスタムGUIを実現する方法を模索しましたのでここに記します。

※VST2.3よりVST GUIのソースコードが公開されています。
しかしこのコードはCBのエラーチェックが厳しいためかそのままではエラーが出ます。
ですが、大半のエラーはキャストに関するエラーで適切なキャストに修正(キャストでごまかす??)することでコンパイルが通せます。
下記方法はVCLで強引にHWNDを取得するなどしているためか動作が不安定な点があります。
VCLを使用するよりは、VSTGUIライブラリをコンパイルして使用することをお勧めします。
 

5.1 準備

3章と同様にDLL作成の準備をしますが、DLLウイザードで、VCL使用にチェックを入れます。
そうするとプロジェクトに自動的に、メインウインドウとしてクラスTForm1が追加されます。
なお3章の時と異なり、Unit1.cppは、メインウインドウのクラスが書かれているので削除しないでください。
3章と同様Unit1.cppを削除します。その後、ファイル(F)->フォームの新規作成(F)でメインウインドウ(TForm1)を生成します。
この時にTForm1のクラスが書かれたUnit1.cppが新たに自動作成されます。
なお以前のUnit1.cppにはDLLmainが記述されていたので、別途DLLmainが記述されたソースを、SDKのサンプルを参考に別ファイルで作成してください。

5.2 AEffEditor派生クラス

カスタムGUIは、ホストがアクセスするGUIのクラスとしてAEffEditorの派生クラスが必要です。
筆者作成のソースVOPMEditcppをサンプルとして示します。

1)AEffEditor::open
 Editorオープン時の関数で引数 ptrはウインドウハンドル(HWND)そのものです。
 ここで後述のコンストラクタTForm1((HWND)ptr)でnewします。
 最後のTForm::Showでウインドウを表示します。

2)AEffEditor::close
 Editorクローズ時の関数です。ウインドウをnewで生成しているので、deleteしてください。

5.3 Unit1(class TForm1)ソースの変更

TFormは通常、VCLの起動ルーチンよりTComponentを引数としてコンストラクタが呼ばれますが、VSTは通常のハンドル(HWND)によりウインドウ生成をします。
そこで、TFormのコンストラクタを引数HWNDを持つものでオーバーロードします。
CBのオンラインヘルプのTForm::TFormに説明があるのでそちらを参照してください。
なおVOPMEdit.cppでは、HWND以外にも引数がありますが、諸般の事情により追加したものであり、常に必要なものではありません。

5.4 TForm1の注意事項

これで通常のCBらしいビジュアル開発が可能になりますが、いくつか制約やはまった点がありますので以下に示します。

1)Top,Leftは、コンストラクタで(0,0)にしておかないと、ホストの用意したウインドウ上にうまく描画されません。

2)オブジェクトインスペクタでTForm1のBorderStyleをbsNoneにしないと、タイトルバーがホストが用意しているものの下にも表示されてしまいます。

3)子コントロールのクラスを動的に生成するにはTComponentをNULLとしてでなければできません。
コントロールを子にしたい場合は、コントロールクラス生成後、TForm::InsertControlで手作業でコントロールクラスを子として登録する必要があります。

4)イベントメッセージの一部はホストでキャッチされて、メインウインドウやその子コントロールにはディスパッチされません。

おもにGUIに必要となるMouseWheelとKey入力はSDK2.1より拡張され使用できるようになりました。
マニュアルには書いてないので記します。

なおホストによってはこれらの機能に対応してないので注意が必要です。
※Cubase VST32 5.1は対応してましたが、Cubasis VST3.0は対応してませんでした。

 1.MouseWheel
 AudioEffectXの派生クラスにvenderSpecificメンバーを以下のようにオバーロードします。
 なおこのときeditorクラスのメンバーonWheelがイベント応答関数になります。

long VOPM::vendorSpecific (long lArg1, long lArg2, void* ptrArg, float floatArg)
{
//MouseWhell Enable
        if (editor && lArg1 ==CCONST( 's','t','C','A') && lArg2 == CCONST('W','h','e','e'))
        {
                return editor->onWheel (floatArg) == true ? 1 : 0;

        }
        else
        {
                return AudioEffectX::vendorSpecific (lArg1, lArg2, ptrArg, floatArg);
        }
}

※VOPMはAudioEffectXの派生クラス

 2.Key入力
 AEffEditor::onKeyDown (VstKeyCode &Key)がSDK2.1で追加されたキーイベント応答関数です。
 Key.characterが文字コードになります。
 
5)その他
 マニュアルにはない機能、拡張された機能は、ヘッダーファイルやメーリングリストに情報があることがあります。



Copyright (c)2002 Sam
sam_kb@yahoo.co.jp