このドキュメントでは、Unity 5.0に組み込まれたネイティブオーディオプラグインインターフェースについて記述します。いくつかの具体的なプラグイン例について、先へ進むにつれてより複雑なものを見ていきます。この方法により、非常に基本的な概念から始めて、ドキュメントの終わりの方では、より複雑な使用例をご紹介します。
まず最初に、こちらから最新のオーディオプラグイン SDK をダウンロードしてください。
ネイティブオーディオプラグインシステムは、2つの部分から構成されます。
ネイティブ DSP(デジタル信号処理)プラグインは、C または C++の.dll(Windows)または.dylib の(OSX)として実装されます。スクリプトとは違い、パフォーマンス上の高い要求のために、あなたは、プラットフォーム固有の最適化と、および、サポートしたい任意のプラットフォーム用にコンパイルする必要があります。
C# で開発される GUI。GUI は任意であることに注意してください。そのため、常に基本的なネイティブ DSP プラグインを作成することから、プラグイン開発を始める事ができます。ネイティブプラグインで公開している、パラメータを設定するためのデフォルトのスライダーベース UI を Unity で表示してみましょう。どのようなプロジェクトでもブートストラップするため、このアプローチをお勧めします。
最初は、 C# による GUI の原型を Assets/Editor フォルダーにドロップするだけで、 .cs ファイルとして作成できることを覚えておいてください(他のエディタースクリプトと全く同じです)。コードが伸び始め、よりよいモジュール化とよりよい IDE サポートが必要になり始めたら、後から適切な MonoDevelop のプロジェクトに移動することができます。これによって .dll にコンパイルすることが可能となるため、ユーザーがプロジェクトにドロップする事が容易になり、またコードを保護する事もできます。
また、ネイティブ DSP と GUI の DLL は両方とも複数のプラグインを含めることができ、そのバインディングは DLL 名に関係なく、プラグイン内のエフェクト名を通してのみ発生する事に気をつけてください。
ネイティブな面では、プラグイン SDK は実際は一つのファイル (AudioPluginInterface.h) からできていますが、同じ DLL 内に複数のプラグインエフェクトを持つことが簡単にできます。私たちは、シンプルな統合された方法 (AudioPluginUtil.h と AudioPluginUtil.cpp) で、エフェクト定義とパラメータ登録の制御をサポートするコードを追加しました。NativePluginDemo プロジェクトには、あなたの手助けになる多くのサンプルプラグインが含まれていることを覚えておいてください。ゲームの状況にあわせるのに便利な、様々な異なるプラグインタイプを見ることができます。このコードはパブリックドメインになっているので、あなた自身による創造のための出発点として、このコードを遠慮なく使ってください。
プラグインの開発は、あなたのプラグインが必要なパラメータの定義から始めます。あなたは、スタートする前に、プラグインが配置するすべてのパラメータの詳細なマスタープランを持っている必要はありません。しかし、それはユーザーにどんな経験をしてもらいたいか、どのコンポーネントが必要になってくるかについての大まかなアイデアを持っていると役立ちます。
私たちが提供しているサンプルのプラグインには、使いやすいユーティリティ関数がたくさん含まれています。 “Ring Modulator” プラグイン例を見てみましょう。このシンプルなプラグインは、サイン波で入力される信号を掛け合わせます。特に、複数の周波数で連鎖させた複数のリングモジュレーションのエフェクトは、ラジオノイズ/壊れた受信機のようなエフェクトを与えます。
例のプラグインのパラメータを扱うための基本的なスキームは、利便性と簡潔さの両方のために float の配列へのインデックスとして使用する列挙値としてそれらを定義することです。
enum Param
{
P_FREQ,
P_MIX,
P_NUM
};
int InternalRegisterEffectDefinition(UnityAudioEffectDefinition& definition)
{
int numparams = P_NUM;
definition.paramdefs = new UnityAudioParameterDefinition [numparams];
RegisterParameter(definition, "Frequency", "Hz",
0.0f, kMaxSampleRate, 1000.0f,
1.0f, 3.0f,
P_FREQ);
RegisterParameter(definition, "Mix amount", "%",
0.0f, 1.0f, 0.5f,
100.0f, 1.0f,
P_MIX);
return numparams;
}
RegisterParameter が呼び出す数字は、表示のみに使用するスケーリング係数が続く最小値、最大値およびデフォルト値です。すなわち、表示されたとき100でスケーリングされるパーセンテージ値で、実際の値は0から1になります。このためのカスタム GUI コードはありませんが、前に別述したように Unity はこれらの基本的なパラメータ定義からデフォルトの GUI を生成します。未定義のパラメータをチェックしないことに注意してください。AudioPluginUtil システムは、宣言されたすべての列挙型の値( P_NUM
除く)は、対応するパラメータ定義とマッチしていると予想しています。
舞台裏では RegisterParameter 関数 (“AudioEffectPluginInterface.h”を参照)は、そのプラグインに関連付けられている UnityAudioEffectDefinition 構造体の UnityAudioParameterDefinition 配列のエントリに記入します。UnityAudioEffectDefinition で設定される必要がある残りはプラグイン(CreateCallback)を具体例をあげて示すことを取り扱う機能へのコールバック(CreateCallback)です。setting/getting パラメータ(SetFloatParameterCallback/ UnityAudioEffect_GetFloatParameterCallback
)、実際の処理を行う(UnityAudioEffect_ProcessCallback
)、終了後、最終的にはプラグインのインスタンスを破棄する(UnityAudioEffect_ReleaseCallback
)があります。
同じ DLL で複数のプラグインを持つことを簡単にするために、各プラグインは、独自のネームスペースに収納されており、DEFINE_EFFECTと
DECLARE_EFFECT` マクロが UnityAudioEffectDefinition 構造を記入することができるように、コールバック関数のための特定の命名規則が使われます。フードの下に、すべてのエフェクトの定義はポインタがライブラリ UnityGetAudioEffectDefinitions の唯一のエントリポイントで返さされた配列に格納されます。
これは、あなたが、VST や AudioUnits など他のプラグイン形式から、または、Unity オーディオプラグインインターフェースからマップするブリッジプラグインを開発したい場合、知っておくと便利です。その場合、ロード時にパラメータの記述を設定する、よりダイナミックな方法を開発する必要があります。
次は、プラグインのインスタンスのためのデータです。プラグイン例では、EffectData 構造体にこのすべてを置きます。これの割り当ては、ミキサー内のプラグインのインスタンスごとに呼び出され、対応する CreateCallback に起こる必要があります。この簡単な例では、すべてのチャネルに掛け合わせられた一つのサイン波があると、他のより高度なプラグインは、入力チャンネル毎の追加データを割り当てる必要があります。
struct EffectData
{
struct Data
{
float p[P_NUM]; // Parameters
float s; // Sine output of oscillator
float c; // Cosine output of oscillator
};
union
{
Data data;
unsigned char pad[(sizeof(Data) + 15) & ~15];
};
};
ちょっと待ってください。共用体とパディングとはなんのことでしょうか?さて、プレイステーション3のためのプラグインを開発しない限り、あなたはこれを無視することができ、ちょうど “Data” 構造の中のメンバーに集中することができます。しかし、プラグインをこのプラットホームに移植したい場合に備えて、あなたは PS3 の上で、SPUs の上で信号処理がなされ、メイン CPU と SPU の間に前後のデータを転送するために、データを16バイトの境界線に整列させることが必要であることに気づいてなければなりません。そして、このように、プラグインデータの実際の Data 構造が16で割り切れない場合があるとしても、EffectData 構造のサイズが16の倍数であることを、“pad” 配列は確認します。この規制は、コードを見ると少し鬱になりますが、結局は、すべてのプラットフォームで維持するために、唯一の共有コードになるという利益のためです。例で示される方法に従えば、あなたのコードを PS3に移植することが簡単です。
UNITY_AUDIODSP_RESULT UNITY_AUDIODSP_CALLBACK CreateCallback(
UnityAudioEffectState* state)
{
EffectData* effectdata = new EffectData;
memset(effectdata, 0, sizeof(EffectData));
effectdata->data.c = 1.0f;
state->effectdata = effectdata;
InitParametersFromDefinitions(
InternalRegisterEffectDefinition, effectdata->data.p);
return UNITY_AUDIODSP_OK;
}
UnityAudioEffectState には、サンプリングレート、(タイミング)処理されるサンプル、またはプラグインがバイパスされているか、すべてのコールバック関数にするか、など、ホストからさまざまなデータが含まれます。
そして、もちろん、プラグインインスタンスをフリーにすることに該当する機能でもあります:
UNITY_AUDIODSP_RESULT UNITY_AUDIODSP_CALLBACK ReleaseCallback(
UnityAudioEffectState* state)
{
EffectData::Data* data = &state->GetEffectData<EffectData>()->data;
delete data;
return UNITY_AUDIODSP_OK;
}
オーディオの主な処理は ProcessCallback で起こります:
UNITY_AUDIODSP_RESULT UNITY_AUDIODSP_CALLBACK ProcessCallback(
UnityAudioEffectState* state,
float* inbuffer, float* outbuffer,
unsigned int length,
int inchannels, int outchannels)
{
EffectData::Data* data = &state->GetEffectData<EffectData>()->data;
float w = 2.0f * sinf(kPI * data->p[P_FREQ] / state->samplerate);
for(unsigned int n = 0; n < length; n++)
{
for(int i = 0; i < outchannels; i++)
{
outbuffer[n * outchannels + i] =
inbuffer[n * outchannels + i] *
(1.0f - data->p[P_MIX] + data->p[P_MIX] * data->s);
}
data->s += data->c * w; // cheap way to calculate a sine-wave
data->c -= data->s * w;
}
return UNITY_AUDIODSP_OK;
}
私たちは、このリストで、この関数から PS3特定の部分を取り除いてきました。トップの GetEffectData 関数は、我々が上記で宣言した構造体 EffectData::data に状態変数の effectdata フィールドをキャストするヘルパー関数です。
含まれる他のシンプルなプラグインは、NoiseBox プラグインです。これは、可変周波数でのホワイトノイズ、または Lofinator のプラグインの入力信号を加えて、掛け合わせ、シンプルにダウンサンプリングし、信号の量子化を行います。これらのすべてを組み合わせ用いて、携帯電話、通信状態が悪い携帯無線、壊れた拡声器など何かをためにゲームドリブンアニメーションパラメータを使用してシミュレートすることができます。
StereoWidener は、可変遅延で mono(モノラル) や side コンポーネントにステレオ入力信号を分解し、その後、それらをステレオ効果の感じを高めるために再結合します。
ネイティブオーディオプラグインは、それらはプラグインのインポーターインスペクター経由で、それぞれのプラットフォームに関連付けられてなければならず、他のネイティブまたは管理プラグインと同じスキームを使用します。あなたは デスクトップ向け のプラグインを配置して、サブフォルダーの詳細を読むことができます。プラットフォームの関連付けが必要です。そのため、スタンドアロンビルドで各ビルドターゲットに含めるプラグインをシステムは知っています。64ビットサポートの導入でもプラットフォーム内で指定する必要があります。ユニバーサルバイナリフォーマットは、同じバンドル内に32ビットおよび64ビットの両方のバリアントを含むのを許可しています。
マネージコードから呼び出される Unity のネイティブプラグインは、ネイティブ DLL からインポートする関数を参照している [DllImport] 属性を使用してロードされます。しかし、ネイティブオーディオプラグインの場合、事情が違います。ここで起こる特別な問題は、プラグインからエフェクトを必要とするかもしれないミキサーアセットを、Unity が作成を開始する前にオーディオプラグインをロードする必要があるということです。エディターでは、これは問題ありません。プラグインに依存するミキサーをリロードやリビルドできるからです。しかし、しかし、ミキサーアセットを作成する前に、スタンドアロンビルドでプラグインがロードされなければなりません。この問題を解決するには、現在のやり方は、プラグイン“audioplugin” (大文字小文字を区別しない) の DLL の前に付けることです。そのため、システムは、これを検出することができ、自動的に起動時にロードされるプラグインのリストに追加されます。Unity のミキサーの中に示されるエフェクトの名前を定義し、プラグイン内部でのみ定義することを忘れないでください。DLL は、何かを呼び出すことができますが、検出されるために文字列が、“audioplugin” で始まる必要があります。
iOS のようなプラットフォーム用のプラグインコードは、ジェネレートされた Xcode プロジェクトによって生成される Unity バイナリに静的にリンクされる必要があります。そして、そこに - レンダリングデバイスプラグインと同じように - プラグインの登録をアプリの起動コードに明示的に追加する必要があります。
OSX上で1つのバンドルには、32ビットおよび64ビット版の両方のプラグインを含むことができます。また、サイズを保存するためにそれらを分割することができます。
今度は、もう少し高度な応用例を見てみましょう:イコライゼーションおよびマルチバンド圧縮のためのエフェクトです。このようなプラグインは、前節で示したシンプルなプラグインよりも、はるかに多いパラメータ数を持っており、パラメータ間にいくつかの物理的な結合があります。それは、パラメータを視覚化するために、たくさんのシンプルなスライダーよりも、よりよい方法が必要です。インスタンスのためのイコライザーを考えてみましょう: 各バンドは、寄り集まって最終的にイコライゼーションカーブに貢献する3つの異なるフィルタを持ち、このフィルタそれぞれは、物理的にリンクされ、各フィルタの形状を定義している3つのパラメータ、周波数、Q 値およびゲインを持っています。だから、イコライザープラグインが結果の曲線を示す素敵な大きなディスプレイを持っている場合、個々のフィルタが寄与する、一度に1つのスライダーを変更する制御の代わりに簡単なドラッグ操作で複数のパラメータを同時に設定する方法で操作することができ、ユーザーに大いに役立ちます。
Equalizer プラグインのカスタム GUI。フィルタカーブのゲインと周波数を変更するには3つのバンドをドラッグします。各バンドの形状を変更するためにシフトボタンを押しながらドラッグしてください。
だから、もう一度、定義、初期化、初期化解除およびパラメータ処理は、単純なプラグインが使用する正確な同じ列挙型ベースの方法に従います。さらに ProcessCallback コードはかなり短いです。さて、タイムはネイティブコードを見て停止し、MonoDevelop で AudioPluginDemoGUI.sln プロジェクトを開きます。ここにあなたは、GUI コードのための associated C# クラスを見つけるでしょう。それが動作する方法は簡単です: いったん、Unity はネイティブプラグインの DLL をロードし、含まれるオーディオプラグインを登録したならば、それは登録されたプラグインの名前と一致する対応する GUI を探して起動します。これは、すべてのカスタムプラグインの GUI のように、IAudioEffectPluginGUI から継承する必要があり、EqualizerCustomGUI クラスの Name プロパティーを介して行われます。このクラス内に一つだけ重要な機能があります。ブール OnGUI(IAudioEffectPlugin plugin) 関数です。IAudioEffectPlugin プラグイン引数をを通して、ネイティブプラグインが定義したパラメータを読み書きするために、この関数はネイティブプラグインのハンドルを取得します。だから、パラメータを読むために、それをコールします:
plugin.GetFloatParameter("MasterGain", out masterGain);
どれかパラメータが見つかった場合、true を返し、それを設定し、それをコールします:
plugin.SetFloatParameter("MasterGain", masterGain);
どのパラメータが存在する場合にも true を返します。そして、それは基本的に GUI とネイティブコード間のもっとも重要な結合です。また、関数を使用することができます。
plugin.GetFloatParameterInfo("NAME", out minVal, out maxVal, out defVal);
クエリパラメータ “NAME” にとって、ネイティブと UI コードでこれらの最小値、最大値およびデフォルト値の複製定義を避けなければなりません。あなたの OnGUI 関数が true を返す場合には、インスペクターがカスタム GUI 下のデフォルトの UI のスライダーが表示されることに注意してください。これは、あなたのカスタム GUI を開発している間、使用可能なすべてのパラメータを持っていて、あなたの GUI 開発をブートストラップするために、再度便利で、それに対して正しいアクションの実行が期待されるようにパラメータに変化をもたらす簡単なチェックする方法を持っています。
私たちは、ここのイコライザおよびマルチバンドのプラグインの中で起こっている DSP 処理の詳細について説明しません。興味のある方のために、Robert Bristow-Johnson の名著 “Audio EQ Cookbook” からフィルタを取り上げます。曲線をプロットする Unity は、周波数応答のためのアンチエイリアス曲線を描くためにいくつかの内部 API 関数を提供します。
もう一つ言及することは、イコライザおよびマルチバンド両方のプラグインに興味深いポイントが表示され、プラグインのエフェクトを可視化するために、入出力のスペクトルをオーバーレイするためのコードが提供されることです: GUI コードは、オーディオ処理よりもはるかに低い更新速度(フレームレート)で実行し、オーディオストリームへのアクセス権を持っていないので、どのように、このデータを読んでいるのでしょうか? このために、ネイティブコードで特別な関数があります:
UNITY_AUDIODSP_RESULT UNITY_AUDIODSP_CALLBACK GetFloatParameterCallback(
UnityAudioEffectState* state,
int index,
float* value,
char *valuestr)
{
EffectData::Data* data = &state->GetEffectData<EffectData>()->data;
if(index >= P_NUM)
return UNITY_AUDIODSP_ERR_UNSUPPORTED;
if(value != NULL)
*value = data->p[index];
if(valuestr != NULL)
valuestr[0] = 0;
return UNITY_AUDIODSP_OK;
}
これは単に、ネイティブプラグインからの浮動小数点データの配列を読むことができます。リそのデータが何であれ、プラグインシステムはクエストが大量に UI またはネイティブコードを遅くしない限り、気にしません。イコライザおよびマルチバンドコードについて、プラグインから入出力データを供給し、戻って、スペクトルを取り戻すことが容易になる FFTAnalyzer と呼ばれるユーティリティクラスがあります。このスペクトルデータは GetFloatBufferCallback によって再度サンプリングされて、C# の UI コードに渡されます。データが再サンプリングされる必要がある理由は、GetFloatBufferCallback がリクエストされるサンプルの数を返す間、FFTAnalyzer が一定の周波数分解能で解析を実行させ、それはデータを表示しているビューの幅によって決まります。最小限の量の DSP コードの非常に簡単なプラグインについて、あなたも CorrelationMeter のプラグインを見てみるかもしれません。それは、単に信号が“どうやってステレオ化する”を示すために、右チャネルの振幅に対して左チャンネルの振幅をプロットします。
左: CorrelationMeter プラグインのカスタム GUI。
右: オーバーレイスペクトラム解析とイコライザーGUI(緑のカーブがソース、赤は処理後)。
この時点で、私たちはまた、イコライザおよびマルチバンドエフェクトの両方が意図的にシンプルかつ最適化されずに保持していることを指摘したいと思います。しかし、私たちは、彼らがプラグインシステムでサポートされている複雑な UI のもっとよい例を提供すると思います。関連するプラットホーム特有の最適化を行う際に、明らかに多くの仕事がまだあります。たくさんのパラメータを調整して、本当に正しく感じさせ、音楽的な方法などに対応させるため…等々。私たちはまた、単にプラグインの Unity の標準レパートリーを増やす都合のためにいくつかの点で Unity の組み込みプラグインとして、これらのエフェクトのいくつかを実装する場合があります。しかし、私たちは読者の皆さんが、本当に素晴らしいプラグインを作成するために挑戦することを心から願っています。– 何ともいえないけども、結局、彼らはいくつかの点で、組み込みプラグインとして終わるかもしれない。;-)
コンボリューションリバーブのプラグイン例。インパルス応答は、ランダムノイズを減衰させます。 パラメータによって定義されています。これは、生産プラグインとして、デモンストレーション目的のためだけです。 ユーザが任意に記録されたインパルスをロードできるようにしなければなりません。基本となるコンボリューションアルゴリズム。 それでも同じことのままです。
3つの異なる時間スケールでレベルを測定するラウドネスモニタリングツールの例。 また、単にデモの目的は現代の音量標準化に準拠したモニタツールの構築を開始する位置づけです。 カーブレンダリングコードは Unity に組み込まれています。
いくつかの楽しい演習の時間です。なぜそれを処理するのではなく、音を生み出すためにプラグインシステムを使用しないのですか?アシッドトランス ― このジャンルを定めた主なシンセの一部のいくつかの簡単なクローン - を聞く人々には、精通している必要がある、いくつかの簡単なベースラインとドラムシンセサイザーを実行してみましょう。Plugin_TeeBee.cpp と Plugin_TeeDee.cpp を見てください。これらの単純なシンセだけで、ランダムなリアクションパターンを生成し、合成エンジンでフィルター、エンベロープを微調整るなどのためにいくつかのパラメータを持っています。ここでも、私たちはここでそれらの詳細については説明しませんが、ちょうど state->dsptick パラメータは “song” の中の位置を決定するために、ProcessCallback で読まれることを指摘しておきます。このカウンタは、グローバルなサンプルのポジションです。私たちはただのサンプルで指定された各音符の長さでそれを分割し、この除算が 0 余りを持つたびに、合成エンジンにノートイベントを発生させます。このように、すべてのプラグインのエフェクトは同じサンプルベースクロックに同期してとどまります。あなたが例えば、このようなエフェクトを通して、既知のテンポであらかじめ録音してある音楽を再生したい場合は、音楽の上で tempo-synchronized フィルタエフェクト、または遅延を適用するためにタイミング情報を使うことができます。
ネイティブオーディオプラグイン SDK は Spatialization SDK の基礎で、オーディオソースごとにインスタンス化するカスタム立体化(Spatialization)エフェクトの開発を可能にします。 詳しい情報はAudio Spatializer SDK を参照してください。
これは、ちょうど、高性能ネイティブコードにサウンドシステムの一部を切り開くための努力のスタートです。Unity の他の部分で、ミキサーの外側で使用可能なをエフェクトを作ること、SDK を拡張し、よりよいデフォルトの GUI のサポートを持つフロート以外のパラメータタイプをサポートすること、バイナリデータを格納することを統合する計画があります。
独自のプラグインを作成するたくさんの楽しみををどうぞ。アセットストアでそれらを見せてほしいです。;-)
設計に多くの類維持性はありますが、Unity のネイティブオーディオ SDK は Steinberg VST や Apple AudioUnits など他のプラグイン SDK より優位に立ってビルドされたものではありません。興味があるユーザーが、他のプラグイン SDK を Unity で使用するために、ネイティブオーディオ SDK を利用して基本ラッパーを実装するのはたやすいことでしょう。しかし、それは、Unity の開発チームが意図していることではありません。どんなプラグインの適切なホスティングも、あっという間にとても複雑になります。予想される不満の錯綜にかかわったり、ネイティブコードが急速に拡張されたことによってカスタム GUI 画面を扱うなど、コード例としてあまり役立たなくなってしまいます。
VST や AU プラグインや、サウンドデザインを単に模写したりテストするためのエフェクトでさえ、ロードするととても役立つ可能性があるということを理解する反面、VST/AU の使用は、数種の特定のプラットフォームに制限されるということに注意してください。Unity SDK に基づいてオーディオプラグインを書く有効性は、すべてのプラットフォームで拡張可能で、ソフトウェアミキシングや動的に読み込んだネイティブコードに対応しているということです。とはいえ、カスタムプラグインの開発に時間を費やすことを決める前に、早期のサウンドデザインを好みのツールで模写するのに有効なケースもあります (または、単に、サウンドが変わらないエディター内で、プラグインを試すのに使用できます)。何かよい解決案のあるひとは、やってみてください。