VSTe 何も音響処理をしない入力をそのまま出力する処理

提供:yonewiki
2023年5月5日 (金) 23:48時点におけるYo-net (トーク | 投稿記録)による版

プラグイン VSTに戻る。

概要

 まずは、基礎として何もしないエフェクト処理を作ってみたいと思います。こつこつ手作業版のプロジェクトを仕上げていくイメージですが、最初の方は自動生成したVSTプロジェクトでは既にもう準備されている部分になるので、自動生成されたプロジェクトの構造とも見比べながらやってみたいと思います。


  • 1.自動生成されたプロジェクトでも読み込まれているdllmain.cppを取り込む作業をします。


  • ソリューションエクスプローラのプロジェクト名を右クリックして表示されるメニューから[追加]-[既存の項目]を選択するか[Shift]+[Alt]+[A]キーを押します。


  • VSTのSDKの中のC:¥SDK¥VST_SDK¥vst3sdk¥public.sdk¥source¥main¥dllmain.cppファイルを選択します。
 このファイルは変更を加えませんが、エントリポイント(プログラムが起動したときに最初に呼び出される関数)となる。
BOOL WINAPI DllMain (HINSTANCE hInst, DWORD dwReason, LPVOID /*lpvReserved*/)
 という関数を含んでいます。開始関数は自分で作るのではなく、配布されているSDKセットの関数を取り込むのが普通なようです。dllmain.cppとは長い付き合いになりそうですね。


  • 2.ヘッダファイルとソースファイルの組み合わせを追加します。
  • ソリューションエクスプローラのプロジェクト名を右クリックして表示されるメニューから[追加]-[新しい項目]を選択するか[Ctrl]+[Shift]+[A]キーを押します。


表示されるダイアログで、myvst.h と myvst.cpp としましょうか?好きなファイル名でいいです。自動生成版では4つのヘッダファイルと3つのソースファイルに分割して記述されていて、
  • $(Prefix)cids.h
  • $(Prefix)controller.h
  • $(Prefix)processor.h
  • version.h
  • $(Prefix)controller.cpp
  • $(Prefix)processor.cpp
  • $(Prefix)entry.cpp

に記述されます。コツコツ版ではmyvst.hとmyvst.cppというファイル名に集約することにしたいと思います。

  • 3.myvst.h に以下のようなコードを記述します。
#pragma once

#include "pluginterfaces\base\funknown.h"
#include "public.sdk/source/vst/vstaudioeffect.h"
#include "public.sdk/source/vst/vsteditcontroller.h"
#include "public.sdk/source/main/pluginfactory.h"

namespace Steinberg {
	namespace Vst {

#define MYVST_VENDOR   "VST Company"             // VSTを提供する組織名
#define MYVST_URL      "https://wiki.yo-net.jp/" // WebサイトのURL
#define MYVST_EMAIL    "mailto:xxxx@yo-net.jp"   // メールアドレス
#define MYVST_VSTNAME  "MyVST Name"              // VSTの名前
#define MYVST_VERSION  "0"                       // VSTのバージョン
#define MYVST_SUBCATEGORIES Vst::PlugType::kFx   // VSTのサブカテゴリ

		static const FUID ProcessorUID(0xA185FDD5, 0xDA405CBE, 0xB54639A5, 0x0DD136ED);
		static const FUID ControllerUID(0x68F6FA83, 0x229451BE, 0x98647D8E, 0x25C4A242);

		class MyVSTProcessor : public AudioEffect
		{
		public:
			MyVSTProcessor();

			tresult PLUGIN_API initialize(FUnknown* context) SMTG_OVERRIDE;

			tresult PLUGIN_API process(ProcessData& data) SMTG_OVERRIDE;

			static FUnknown* createInstance(void*) { return (IAudioProcessor*)new MyVSTProcessor(); }
		};

		class MyVSTController : public EditController
		{
		public:
			tresult PLUGIN_API initialize(FUnknown* context) SMTG_OVERRIDE;

			static FUnknown* createInstance(void*) { return (IEditController*)new MyVSTController(); }
		};
	}
}
static const FUID ProcessorUID(0xA185FDD5, 0xDA405CBE, 0xB54639A5, 0x0DD136ED);
static const FUID ControllerUID(0x68F6FA83, 0x229451BE, 0x98647D8E, 0x25C4A242);
上記の部分は独自のIDを生成する必要があります。人の真似をすると、そのVSTが使えなくなるといった迷惑がかかります。
生成するには、VisualStudioのメニューの[ツール]-[GUIDの作成]で起動されるツールを使って、[コピー]ボタンを押してIDを得ることが出来ます。
{5A927A38-9B1C-46B9-8563-23473D9C8C62}のような形式ですが、8桁ずつの区切りとして{5A927A38-9B1C46B9-85632347-3D9C8C62}のようなIDを得ます。これに4つの引数となるような0xを先頭に付与した(0x5A927A38, 0x9B1C46B9, 0x85632347, 0x3D9C8C62)のように置き換える手順で得られるのがFUIDです。
二つ目のIDを続けて得るには[新規GUID]ボタンを押します。そしてまた[コピー]ボタンです。1秒間に5000万回生成しても重複することはないとされています。
#define MYVST_VENDOR   "VST Company"             // VSTを提供する組織名
#define MYVST_URL      "https://wiki.yo-net.jp/" // WebサイトのURL
#define MYVST_EMAIL    "mailto:xxxx@yo-net.jp"   // メールアドレス
#define MYVST_VSTNAME  "MyVST Name"              // VSTの名前
#define MYVST_VERSION  "0"                       // VSTのバージョン
#define MYVST_SUBCATEGORIES Vst::PlugType::kFx   // VSTのサブカテゴリ
 この部分も独自の値を設定した方がよいでしょう。嘘をつくことになります。重複しても迷惑はあまりかからないでしょう。ただし、問い合わせの連絡がよそに行ってしまうという意味では、URLとEMAILは他人の物は使わないことは重要です。これはVSTホスト側でも取得できる値ですのでDAWによっては、この情報が閲覧できることになります。


以下の4つのヘッダファイルを読み込みます。
#include "pluginterfaces\base\funknown.h"
#include "public.sdk/source/vst/vstaudioeffect.h"
#include "public.sdk/source/vst/vsteditcontroller.h"
#include "public.sdk/source/main/pluginfactory.h"
MyVSTProcessor(AudioEffectを継承)とMyVSTController(EditControllerを継承)クラスを作ります。


ともにtresult PLUGIN_API initialize(FUnknown* context) SMTG_OVERRIDEという初期化関数を持たせます。


ともにstatic FUnknown* createInstance(void*)という関数を持たせます。オーディオクラスの戻り値は{ return (IAudioProcessor*)new MyVSTProcessor(); }として、制御クラスの戻り値は { return (IEditController*)new MyVSTController(); }とします。


オーディオクラスにはコンストラクタMyVSTProcessor::MyVSTProcessor()関数と、tresult PLUGIN_API process(ProcessData& data)関数を持たせます。


  • 4.myvst.cpp に以下のようなコードを記述します。
using namespace Steinberg;
using namespace Steinberg::Vst;

//------------------------------------------------------------------------
//  VST Plug-in Entry
//------------------------------------------------------------------------
// Windows: do not forget to include a .def file in your project to export
// GetPluginFactory function!
// Windows: エクスポートするプロジェクトに .def ファイルを含めることを忘れないでください。
// GetPluginFactory 関数!
//------------------------------------------------------------------------

BEGIN_FACTORY_DEF ("VST Company", 
			       "https://wiki.yo-net.jp", 
			       "mailto:xxxx@yo-net.jp")

	//---このファクトリーに含まれる最初のプラグイン-------
	// kVstAudioEffectClass コンポーネント
	DEF_CLASS2 (INLINE_UID_FROM_FUID(ProcessorUID), //FUID
				PClassInfo::kManyInstances,	// 同時に実行できる数
				kVstAudioEffectClass,	// コンポーネントカテゴリ (変更できない)
				MYVST_VSTNAME,		// プラグイン名 (独自設定に変更すべき)
				Vst::kDistributable,	// コンポーネントとコントローラーを異なるコンピューターに配布できることを意味します。
				MyVSTVSTCategory, // このプラグインのカテゴリ (独自設定に変更すべき)
				MYVST_VERSION,		// プラグインのバージョン (独自設定に変更すべき)
				kVstVersionString,		// VST3SDKのバージョン (変更しない、この定義をいつも使う)
				MyVSTProcessor::createInstance)	// 関数ポインタは、コンポーネントが実体化されるべきとき呼ばれるもの。

	// kVstComponentControllerClass コンポーネント
	DEF_CLASS2 (INLINE_UID_FROM_FUID (ControllerUID),//FUID
				PClassInfo::kManyInstances, // 同時に実行できる数
				kVstComponentControllerClass,// コントローラのクラス (変更できない)
				MYVST_VSTNAME "Controller",	// コントローラの名前 (VSTの名前にControllerを付け足したものにするべき)
				0,						// 使わない。
				"",						// 使わない
				MYVST_VERSION,		// プラグインのバージョン (独自設定に変更すべき)
				kVstVersionString,		// VST3SDKのバージョン (変更しない、この定義をいつも使う)
				MyVSTController::createInstance)// 関数ポインタは、コンポーネントが実体化されるべきとき呼ばれるもの。

	//----このファクトリに含まれるその他のプラグインの場合、最初のプラグインの場合と同様に、DEF_CLASS2 が異なります---

END_FACTORY

MyVSTProcessor::MyVSTProcessor()
{
	setControllerClass(ControllerUID);
}

tresult PLUGIN_API MyVSTProcessor::initialize(FUnknown* context)
{
	tresult result = AudioEffect::initialize(context);
	if (result == kResultTrue)
	{
		addAudioInput(STR16("AudioInput"), SpeakerArr::kStereo);
		addAudioOutput(STR16("AudioOutput"), SpeakerArr::kStereo);
	}
	return result;
}

tresult PLUGIN_API MyVSTProcessor::process(ProcessData& data)
{
	if (data.numInputs != 1 || data.numOutputs != 1)
	{
		return kResultTrue;
	}
	
	if (data.inputs[0].numChannels != 2 || data.outputs[0].numChannels != 2)
	{
		return kResultTrue;
	}

	Sample32* inL = data.inputs[0].channelBuffers32[0];
	Sample32* inR = data.inputs[0].channelBuffers32[1];
	Sample32* outL = data.outputs[0].channelBuffers32[0];
	Sample32* outR = data.outputs[0].channelBuffers32[1];

	for (int32 i = 0; i < data.numSamples; i++)
	{
		outL[i] = inL[i];
		outR[i] = inR[i];
	}

	return kResultTrue;
}

result PLUGIN_API MyVSTController::initialize(FUnknown* context)
{
	tresult result = EditController::initialize(context);

	return result;
}
  • 5.これで、一旦ビルドできます。何もしない空気のようなVSTeです。ちな、最近のバージョンから必要になっているsdk_common.libを入力ライブラリの追加で忘れていると
LNK2001 外部シンボル "public: static class Steinberg::FUID const Steinberg::IPlugView::iid" (?iid@IPlugView@Steinberg@@2VFUID@2@B) は未解決です MyVstProject C:\任意のディレクトリ\sdk.lib(vsteditcontroller.obj) 1

 というエラーが表示されます。FUIDの関数の中身がsdk_common.libの中に定義されているからだと思われます。sdk.libからsdk_common.libに移ったという感じでしょうか。


マクロを展開すると以下のような関数になります。
BEGIN_FACTORY_DEFマクロ部分は以下のようになります。
__declspec (dllexport) IPluginFactory* __stdcall GetPluginFactory () {
  if (!gPluginFactory) { 
    static PFactoryInfo factoryInfo (第1引数(ベンダー), 第二引数(URL), 第三引数(EMAIL), Vst::kDefaultFactoryFlags);
    gPluginFactory = new CPluginFactory (factoryInfo);


DEF_CLASS2マクロ部分は以下のようになります。
{ 
      TUID lcid = {
        (::Steinberg::int8)(((::Steinberg::uint32)(Steinberg::Vst::ProcessorUID.getLong1 ()) & 0x000000FF) ),
        (::Steinberg::int8)(((::Steinberg::uint32)(Steinberg::Vst::ProcessorUID.getLong1 ()) & 0x0000FF00) >> 8),
        (::Steinberg::int8)(((::Steinberg::uint32)(Steinberg::Vst::ProcessorUID.getLong1 ()) & 0x00FF0000) >> 16),
        (::Steinberg::int8)(((::Steinberg::uint32)(Steinberg::Vst::ProcessorUID.getLong1 ()) & 0xFF000000) >> 24),
        (::Steinberg::int8)(((::Steinberg::uint32)(Steinberg::Vst::ProcessorUID.getLong2 ()) & 0x00FF0000) >> 16),
        (::Steinberg::int8)(((::Steinberg::uint32)(Steinberg::Vst::ProcessorUID.getLong2 ()) & 0xFF000000) >> 24),
        (::Steinberg::int8)(((::Steinberg::uint32)(Steinberg::Vst::ProcessorUID.getLong2 ()) & 0x000000FF) ),
        (::Steinberg::int8)(((::Steinberg::uint32)(Steinberg::Vst::ProcessorUID.getLong2 ()) & 0x0000FF00) >> 8),
        (::Steinberg::int8)(((::Steinberg::uint32)(Steinberg::Vst::ProcessorUID.getLong3 ()) & 0xFF000000) >> 24),
        (::Steinberg::int8)(((::Steinberg::uint32)(Steinberg::Vst::ProcessorUID.getLong3 ()) & 0x00FF0000) >> 16),
        (::Steinberg::int8)(((::Steinberg::uint32)(Steinberg::Vst::ProcessorUID.getLong3 ()) & 0x0000FF00) >> 8),
        (::Steinberg::int8)(((::Steinberg::uint32)(Steinberg::Vst::ProcessorUID.getLong3 ()) & 0x000000FF) ),
        (::Steinberg::int8)(((::Steinberg::uint32)(Steinberg::Vst::ProcessorUID.getLong4 ()) & 0xFF000000) >> 24),
        (::Steinberg::int8)(((::Steinberg::uint32)(Steinberg::Vst::ProcessorUID.getLong4 ()) & 0x00FF0000) >> 16),
        (::Steinberg::int8)(((::Steinberg::uint32)(Steinberg::Vst::ProcessorUID.getLong4 ()) & 0x0000FF00) >> 8),
        (::Steinberg::int8)(((::Steinberg::uint32)(Steinberg::Vst::ProcessorUID.getLong4 ()) & 0x000000FF) )
      };
      static PClassInfo2 componentClass (lcid,PClassInfo::kManyInstances,"Audio Module Class", VSTプラグイン名,Vst::kDistributable, サブカテゴリ, 0, VSTプラグインのバージョン, VSTSDKのバージョン); 
      gPluginFactory->registerClass (&componentClass,Steinberg::Vst::MyVSTProcessor::createInstance);
    }

    {
      TUID lcid = {
        (::Steinberg::int8)(((::Steinberg::uint32)(Steinberg::Vst::ControllerUID.getLong1 ()) & 0x000000FF) ),
        (::Steinberg::int8)(((::Steinberg::uint32)(Steinberg::Vst::ControllerUID.getLong1 ()) & 0x0000FF00) >> 8),
        (::Steinberg::int8)(((::Steinberg::uint32)(Steinberg::Vst::ControllerUID.getLong1 ()) & 0x00FF0000) >> 16),
        (::Steinberg::int8)(((::Steinberg::uint32)(Steinberg::Vst::ControllerUID.getLong1 ()) & 0xFF000000) >> 24),
        (::Steinberg::int8)(((::Steinberg::uint32)(Steinberg::Vst::ControllerUID.getLong2 ()) & 0x00FF0000) >> 16),
        (::Steinberg::int8)(((::Steinberg::uint32)(Steinberg::Vst::ControllerUID.getLong2 ()) & 0xFF000000) >> 24),
        (::Steinberg::int8)(((::Steinberg::uint32)(Steinberg::Vst::ControllerUID.getLong2 ()) & 0x000000FF) ),
        (::Steinberg::int8)(((::Steinberg::uint32)(Steinberg::Vst::ControllerUID.getLong2 ()) & 0x0000FF00) >> 8),
        (::Steinberg::int8)(((::Steinberg::uint32)(Steinberg::Vst::ControllerUID.getLong3 ()) & 0xFF000000) >> 24),
        (::Steinberg::int8)(((::Steinberg::uint32)(Steinberg::Vst::ControllerUID.getLong3 ()) & 0x00FF0000) >> 16),
        (::Steinberg::int8)(((::Steinberg::uint32)(Steinberg::Vst::ControllerUID.getLong3 ()) & 0x0000FF00) >> 8),
        (::Steinberg::int8)(((::Steinberg::uint32)(Steinberg::Vst::ControllerUID.getLong3 ()) & 0x000000FF) ),
        (::Steinberg::int8)(((::Steinberg::uint32)(Steinberg::Vst::ControllerUID.getLong4 ()) & 0xFF000000) >> 24),
        (::Steinberg::int8)(((::Steinberg::uint32)(Steinberg::Vst::ControllerUID.getLong4 ()) & 0x00FF0000) >> 16),
        (::Steinberg::int8)(((::Steinberg::uint32)(Steinberg::Vst::ControllerUID.getLong4 ()) & 0x0000FF00) >> 8),
        (::Steinberg::int8)(((::Steinberg::uint32)(Steinberg::Vst::ControllerUID.getLong4 ()) & 0x000000FF) ) 
      };
      static PClassInfo2 componentClass (lcid, PClassInfo::kManyInstances, "Component Controller Class", VSTプラグイン名 " Controller", 0, "", 0, VSTプラグインのバージョン, VSTSDKのバージョン);
      gPluginFactory->registerClass (&componentClass,Steinberg::Vst::MyVSTController::createInstance);
    }


END_FACTORYマクロ部分は以下のようになります。
  }
  else{ 
    gPluginFactory->addRef ();
  }
  return gPluginFactory;
}
 あー、__declspec (dllexport) IPluginFactory* __stdcall GetPluginFactory () {…}


 ってあるのでこれをC言語の名前マングリングを使ってエクスポートさせれば*.defファイルで書き出す関数の指定をする必要は無さそうだな。惜しい。この関数が定義されている場所にextern "C"ってつければ良さそう。


#include "public.sdk/source/main/pluginfactory.h"
#include "pluginterfaces\base\ipluginbase.h"
を呼び出していて、その中の最後のところで


extern "C"
{
	SMTG_EXPORT_SYMBOL Steinberg::IPluginFactory* PLUGIN_API GetPluginFactory ();
	typedef Steinberg::IPluginFactory* (PLUGIN_API *GetFactoryProc) ();
}


 と定義されているので、BEGIN_FACTORY_DEFに名前空間を設定せずルートに置いていれば、宣言部の参照がされて、きちんと名前修飾マングリングが解除された状態で.vst3が生成される。DEFファイルはいらないね。


 オーディオクラスのコンストラクタではsetControllerClass(ControllerUID);という処理を記述します。引数にコントロールクラスのFUIDを保持している変数を指定します。これでオーディオクラスとコントロールクラスとの関係が関連づけられます。必須の命令です。


 オーディオクラスの初期化関数の中ではまず最初に
AudioEffect::initialize(context);
 で初期化をし、初期化に成功したら、addAudioInput関数とaddAudioOutput関数を実行します。
addAudioInput(STR16("AudioInput"), SpeakerArr::kStereo);とaddAudioOutput()は同じ意味の引数をとります。
第一引数はu"AudioInput"としているのと同じ意味です。UTF-16のユニコードリテラルというものです。
第二引数にはSpeakerArr::kStereo(ステレオ)の他、SpeakerArr::kMono(モノラル)、SpeakerArr::k51(5.1ch)があります。
第三引数は省略可能でkMainが規定値で、kAuxとすることもできます。(サイドチェイン入力を意味します。)
第四引数も省略可能でBusInfo::kDefaultActiveのみ指定できます。


 AddAudioInputを呼び出す都度、DAWから受け取った音声波形情報をProcessData&型のdata配列を継承元のAudioEffectクラスがProcess関数の引数として受け取ることが出来ます。1回目のAddAudioInputによって、受け取れるように構築される入力がdata.inputs[0]となります。


 tresult PLUGIN_API MyVSTProcessor::process(ProcessData& data)関数にいよいよ本格的な波形処理を書いていき、data.outputsを書き出します。


 最初は、入力と出力を一つだけ受け取っているかを確認します。もしdata.numinputsが1以外ならif文の中身を実行して即時に関数を終了します。


data.numInputs != 1 || data.numOutputs != 1
 で入力と出力の数が1以外かを確認しています。


 次にステレオ音声を受け取っているかチェックし、data.inputs[0].numChannelsが2以外ならif文の中身を実行して即時に関数を終了します。
data.inputs[0].numChannels != 2 || data.outputs[0].numChannels != 2


 で入力と出力の形式がステレオ形式以外かを確認しています。


 ここから、入力を取り出し、出力を作る処理をします。入力を取得するには Sample32* inL = data.inputs[0].channelBuffers32[0];のようにして、Sample32型のポインタ変数inLに入力のステレオ左チャンネルを取り出します。Sample32は、float型と同じ形式の型で小数点ありの数を保持するもので、ポインタ変数とすることで、配列値を扱うことになります。同様にして


Sample32* inL = data.inputs[0].channelBuffers32[0];
Sample32* inR = data.inputs[0].channelBuffers32[1];
Sample32* outL = data.outputs[0].channelBuffers32[0];
Sample32* outR = data.outputs[0].channelBuffers32[1];


 とすることで、ポインタの取得が出来ています。


 後は、int32 i = 0; i < data.numSamples; i++で繰り返し、サンプルの数だけ配列値があるので、その範囲の値をinからoutにそのまま設定するべく以下のように処理しています。int32はint32_tでint型です。新しい型が出てきても慌てることはありません。基本的にはプログラムで格納する値は整数か小数(浮動小数点数)かポインタ(32bitあるいは64bitの16進数で表した方が理解しやすい値)や文字列のいずれかです。ちょっと難しい計算をする場合もあるかな程度で考えていいでしょう。それでも管理人自身はプログラムの迷宮に迷い込みやすいですけど。配列の先頭にポインタを合わせるだけの処理になります。何もしないとはこういうことですね。


outL[i] = inL[i];
outR[i] = inR[i];


 result PLUGIN_API MyVSTController::initialize(FUnknown* context)はEditControllerクラスを継承していて、その初期化ではEditController::initialize(context);を実行しています。