このページでは、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 は実際は 1 つのファイル (AudioPluginInterface.h) からできていますが、同じ DLL 内に複数のプラグインエフェクトを含めることが簡単にできます。シンプルな統合された方法 (AudioPluginUtil.h と AudioPluginUtil.cpp) で、エフェクト定義とパラメーター登録の制御をサポートするコードを追加しました。NativePluginDemo プロジェクトには、役に立つ多くのサンプルプラグインが含まれており、ゲームコンテキストで役立つさまざまな種類のプラグインを紹介します。このコードはパブリックドメインになっているので、開発の出発点としてコードを自由に使ってください。
プラグインの開発は、プラグインに必要なパラメータの定義から始めます。開発を始める前に、プラグインが配置するすべてのパラメータの詳細なマスタープランを持っている必要はありません。しかし、ユーザーにどんな経験をしてもらいたいか、どのコンポーネントが必要になってくるかについての大まかなアイデアを持っていると良いでしょう。
提供するプラグインのサンプルには、使いやすいユーティリティ関数がたくさん含まれています。 “Ring Modulator” プラグイン例を見てみましょう。このシンプルなプラグインは、正弦波で入力される信号を掛け合わせます。特に、周波数が異なる複数のリングモジュレーション効果が連鎖すると、ラジオノイズ/壊れた受信機のようなエフェクトを与えます。
サンプルプラグインでパラメータを処理するための基本的なスキームは、それらを enum 値として定義することです。これは、便利さと簡潔さの両方のために、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 呼び出しの数字は、最小値、最大値、デフォルト値、そして表示のみに使用するスケーリング係数が続きます。すなわち、パーセント値の場合は、実際の値は
0 から 1 で、表示されるときは 100 で積算されます。このためのカスタム GUI コードはありませんが、前述したように Unity はこれらの基本的なパラメーター定義からデフォルトの GUI を生成します。未定義のパラメーターをチェックするシステムはありません。そのため、AudioPluginUtil システムは、宣言されたすべての enum 値 (P_NUM
は除く) は、対応するパラメーター定義と一致すると想定します。
舞台裏では RegisterParameter 関数は、そのプラグインに関連付けられている (“AudioEffectPluginInterface.h” を参照) UnityAudioEffectDefinition 構造体の UnityAudioParameterDefinition 配列のエントリに記入します。UnityAudioEffectDefinition で設定される必要がある残り部分は関数へのコールバックで、プラグインの初期化 (CreateCallback)、パラメーターの設定/取得 (SetFloatParameterCallback/ UnityAudioEffect_GetFloatParameterCallback
)、実際の処理 (UnityAudioEffect_ProcessCallback
) を行い、 終了後、最終的にプラグインのインスタンスを破棄します (UnityAudioEffect_ReleaseCallback
)。
同じ DLL で複数のプラグインを持つことを簡単にするために、各プラグインは、独自の名前空間に収納されています。コールバック関数のための特定の命名規則が使用され、例えば、DEFINE_EFFECT
と DECLARE_EFFECT
マクロは UnityAudioEffectDefinition 構造体に記入できます。内部では、すべてのエフェクトの定義は、ライブラリ UnityGetAudioEffectDefinitions の唯一のエントリポイントでポインターが返される配列に格納されます。
これは、VST や AudioUnits など他のプラグイン形式や、Unity オーディオプラグインインターフェースからマップするブリッジプラグインを開発したい場合、知っておくと便利です。その場合、ロード時にパラメーターの記述を設定する、より動的な方法を開発する必要があります。
次は、プラグインのインスタンスのデータです。プラグイン例では、EffectData 構造体にすべてのデータを置きます。これの割り当ては、ミキサー内のプラグインのインスタンスごとに呼び出される対応した CreateCallback で発生する必要があります。この簡単な例では、すべてのチャネルに掛け合わせた 1 つの正弦波があり、他のより高度なプラグインは、入力チャンネル毎に追加データを割り当てる必要があります。
struct EffectData
{
struct Data
{
float p[P_NUM]; // パラメーター
float s; // オシレーターの正弦出力
float c; // オシレーターの余弦出力
};
union
{
Data data;
unsigned char pad[(sizeof(Data) + 15) & ~15];
};
};
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; // 正弦波を計算する負担の軽い方法
data->c -= data->s * w;
}
return UNITY_AUDIODSP_OK;
}
上の方の GetEffectData 関数は、上で宣言した構造体の EffectData::Data に状態変数の effectdata フィールドをキャストするヘルパー関数です。
含まれる他のシンプルなプラグインは、NoiseBox プラグインです。これは、可変周波数でのホワイトノイズ、または Lofinator のプラグインの入力信号を加えて、掛け合わせ、シンプルにダウンサンプリングし、信号の量子化を行います。これらのすべては組み合わたり、ゲーム用のアニメーションのパラメーターと一緒に用いられ、携帯電話から通信状態が悪い携帯無線、壊れた拡声器まで、あらゆるものをシミュレーションすることができます。
StereoWidener は、可変遅延で mono(モノラル) や side コンポーネントにステレオ入力信号を分解し、その後、それらをステレオ効果の感じを高めるために再結合します。
ネイティブオーディオプラグインは、プラグインのインポーターインスペクター経由で、それぞれのプラットフォームに関連付けられてなければならないので、他のネイティブまたはマネージプラグインと同じスキームを使用します。プラグインを置くためのサブディレクトリに関する詳しい情報は こちらのページ を参照してください。プラットフォームの関連付けが必要なため、システムは、スタンドアロンビルドの各ビルドターゲットにどのプラグインを含むべきかを知っています。64 ビットサポートの導入によって、同じプラットフォーム内でもこれを指定する必要があります。Universal Binary 形式は、同じバンドル内に 32 ビットと 64 ビットの両方のバリアントを含むのを許可しています。
マネージコードから呼び出される Unity のネイティブプラグインは、ネイティブ DLL からインポートする関数を参照している [DllImport] 属性を使用してロードされます。しかし、ネイティブオーディオプラグインの場合、事情が違います。ここで起こる特別な問題は、プラグインからエフェクトを必要とするかもしれないミキサーアセットの作成が開始される前にオーディオプラグインをロードする必要があるということです。エディターでは、これは問題ありません。プラグインに依存するミキサーをリロードやリビルドできるからです。しかし、スタンドアロンビルドでは、ミキサーアセットを作成する前にプラグインがロードされなければなりません。この問題を解決するには、現在のやり方は、プラグインの DLL に “audioplugin” (大文字小文字を区別しない) を付けることです。そのため、システムはこれを検出することができ、自動的に起動時にロードされるプラグインのリストに追加します。Unity のミキサー内に表示されるエフェクトの名前を定義するのは、プラグイン内の定義のみです。DLL は何でも呼び出すことができますが、検出されるためには、文字列が “audioplugin” で始まる必要があります。
iOS のようなプラットフォームには、プラグインコードは、作成された XCode プロジェクトによって生成される Unity のバイナリに静的にリンクされる必要があります。そして、そこに (プラグインのレンダリングデバイスと同じように) プラグインの登録をアプリケーションの起動コードに明示的に追加する必要があります。
OSX上で1つのバンドルには、32ビットおよび64ビット版の両方のプラグインを含むことができます。また、サイズを保存するためにそれらを分割することができます。
今度は、もう少し高度な応用例を見てみましょう。イコライゼーションとマルチバンド圧縮のためのエフェクトです。このようなプラグインは、前述したシンプルなプラグインよりも、はるかにパラメーター数が多く、パラメーター間にいくつかの物理的な結合があります。このような場合、パラメーターの可視化をするために、単にたくさんのシンプルなスライダーを使うよりも上手い方法が必要です。インスタンスのためのイコライザーを考えてみましょう。各帯域には、まとまって最終的なイコライゼーションカーブに影響する 3 つの異なるフィルターがあります。このフィルターはそれぞれ、周波数、Q 値、ゲインの 3 つのパラメーターを持ち、これらのパラメーターは物理的に関連付けられ、各フィルターの形状を定義します。そのため、イコライザープラグインが結果の曲線を示す大きなディスプレイを持ち、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 とネイティブコード間のもっとも重要な連携です。また、以下の関数を使用してパラメーター “NAME” の最小値、最大値、デフォルト値をクエリし、ネイティブと UI コード内のこれらの定義と重複するのを避けることもできます。
plugin.GetFloatParameterInfo("NAME", out minVal, out maxVal, out defVal);
独自の OnGUI 関数が true を返す場合には、インスペクターのカスタム GUI 下にデフォルトの UI スライダーが表示されます。これもまた、独自のカスタム GUI を開発のために便利です。なぜなら、独自のカスタム GUI の開発中にすべてのパラメーターが使用可能で、すべてのパラメータを持っていて、正しいアクションを実行すると、期待通りパラメーターが変更されることを簡単に確認できるからです。
ここでは、イコライザーとマルチバンドのプラグインで発生している DSP 処理の詳細については説明しません。興味がある人のために述べると、フィルターは Robert Bristow Johnson の優れた Audio EQ Cookbook から使用し、カーブを作成するために、Unity はいくつかの内部API関数を使用して周波数応答のアンチエイリアシングカーブを描きます。
もう 1 つ言及すると、イコライザーとマルチバンドのプラグインの両方が、入力スペクトルと出力スペクトルを重ねてプラグインの効果を可視化するコードも提供することです。これは興味深い問題を提起します。GIU コードはオーディオ処理よりもずっと低い更新レート ( フレームレート) で実行され、オーディオストリームにアクセスできません。それでは、このデータをどのように読み取れば良いでしょうか。 このために、ネイティブコードには特別な関数があります。
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 パラメーターが ProcessCallback で読み取られて、曲内の位置を決定するということを指摘しておきます。このカウンターはグローバルなサンプル位置なので、サンプルで指定された各音符の長さでそれを除算し、この除算の余りがゼロになるたびに音符イベントをシンセサイザーエンジンに送信します。こうすることで、すべてのプラグインエフェクトが同じサンプルベースの時計に同期したままになります。例えば、そのようなエフェクトを通して既知のテンポで音楽を再生する場合は、タイミング情報を使用して音楽にテンポが同期されるフィルターエフェクトを適用したり、音楽を遅延させたりできます。
ネイティブのオーディオプラグイン SDK は Spatialization SDK の基礎で、オーディオソースごとにインスタンス化するカスタムの立体化エフェクトの開発を可能にします。 詳しい情報は Audio Spatializer SDK を参照してください。
これは、ちょうど、サウンドシステムの一部を高性能のネイティブコードへと向かうための努力の第一歩にすぎません。これを Unity の他の部分にも統合してエフェクトをミキサーの外部で使用できるようにするとともに、SDK を拡張して float 以外のパラメータータイプをサポートし、より優れたデフォルトの GUI やバイナリデータの保存をサポートする予定です。
独自のプラグインを作成して楽しんでください。アセットストアでそれらを拝見するのが楽しみです。
設計に多くの類似持性はありますが、Unity のネイティブのオーディオ SDK は Steinberg VST や Apple AudioUnits など他のプラグイン SDK の上に作成されたものではありません。興味があるユーザーが、他のプラグイン SDK を Unity で使用するために、ネイティブオーディオ SDK を利用して基本ラッパーを実装するのはたやすいことでしょう。しかし、それは、Unity の開発チームが意図していることではありません。どんなプラグインの適切なホスティングも、あっという間にとても複雑になります。予想される呼び出し順序の複雑さへの対処や、ネイティブコードの急速な増大によるカスタム GUI 画面の処理などが急速に拡大し、コード例としてあまり役立ちません。
VST や AU プラグインや、サウンドデザインを単に模写したりテストするためのエフェクトでさえ、ロードするととても役立つ可能性があるということを理解する反面、VST/AU の使用は、数種の特定のプラットフォームに制限されるということに注意してください。Unity SDK に基づいてオーディオプラグインを書く有効性は、すべてのプラットフォームで拡張可能で、ソフトウェアミキシングや動的に読み込んだネイティブコードに対応しているということです。とはいえ、カスタムプラグインの開発に時間を費やすことを決める前に、早期のサウンドデザインを好みのツールで模写するのに有効なケースもあります (または、単に、サウンドが変わらないエディター内で、プラグインを試すのに使用できます)。何かよい解決案のあるひとは、やってみてください。