Unity のネイティブオーディオプラグイン SDK は、Unity のビルトインネイティブオーディオプラグインインターフェースです。このページでは、基本的なコンセプトと複雑な使用例の両方について説明します。
まず、最新のオーディオプラグイン SDK をダウンロード してください。
ネイティブオーディオプラグインシステムは、2 つの部分から構成されます。
ネイティブ DSP (デジタル信号処理) プラグインは、C または C++ の .dll (Windows) または .dylib (OSX) として実装されます。スクリプトとは違い、これは、サポートしたい任意のプラットフォームでコンパイル可能である必要があり、場合によってはプラットフォーム固有の最適化が必要です。
C# で開発される GUI。GUI はオプションであるため、プラグイン開発は常に基本的なネイティブ DSP プラグインを作成して開始し、Unity にネイティブプラグインが公開するパラメーター表示用のデフォルトのスライダー ベースの UI を表示させます。 これは、プロジェクトを開始する場合に推奨されるアプローチです。
最初に C# GUI を .cs ファイルとして Assets/Editor フォルダーにドロップするだけで (他のエディタースクリプトと同様に) プロトタイプを作成できます。後にコードが長くなり、より適切なモジュール化と IDE サポートが必要になってきたら、適切な Visual Studio プロジェクトに移動できます。これにより、.dll ファイルにコンパイルでき、ユーザーがプロジェクトに簡単にドロップできるようになり、コードを保護しやすくなります。
ネイティブ DSP と GUI DLL は両方とも複数のプラグインを加えることができ、そのバインディングは DLL ファイル名に関係なく、プラグイン内の効果名を通してのみ発生する事に気をつけてください。
プラグイン SDK のネイティブ側は 1 つのファイル (AudioPluginInterface.h
) で構成されます。 ただし、同じ .dll 内に複数のプラグインの効果を追加するために、Unity は効果の定義とパラメーターの登録を統合された方法で処理するための追加コード (AudioPluginUtil.h
および AudioPluginUtil.cpp
) を提供します。 NativePluginDemo
プロジェクトには、ゲームを開始して、ゲーム コンテキストで役立つさまざまな種類のプラグインを表示するためのサンプルプラグインがいくつか含まれています。 このコードはパブリックドメインで入手できるため、誰でもこのコードを独自の作成の開始点として使用できます。
プラグインの開発を始めるには、プラグインのパラメーターを定義します。プラグインが含むべきすべてのパラメーターの詳細な計画を持っている必要はありませんが、ユーザー体験と参照したいさまざまなコンポーネントの大まかなアイデアを持っていると役立ちます。
Unity が提供するプラグインのサンプルには、使いやすいユーティリティ関数がたくさん含まれています。 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 関数が、そのプラグインに関連付けられた UnityAudioEffectDefinition 構造体の UnityAudioParameterDefinition 配列のエントリーを埋めます (AudioEffectPluginInterface.h
ヘッダーファイルを参照)。さらに、UnityAudioEffectDefinition
に以下の残りの項目を設定します。
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; // cheap way to calculate a sine-wave
data->c -= data->s * w;
}
return UNITY_AUDIODSP_OK;
}
上の方の GetEffectData
関数は、上で宣言した構造体の EffectData::Data に状態変数の effectdata フィールドをキャストするヘルパー関数です。
含まれる他のシンプルなプラグインは、NoiseBox プラグインです。これは、可変周波数でのホワイトノイズ、または Lofinator のプラグインの入力信号を加えて、掛け合わせ、シンプルにダウンサンプリングし、信号の量子化を行います。これらのすべては組み合わたり、ゲーム用のアニメーションのパラメーターと一緒に用いられ、携帯電話から通信状態が悪い携帯無線、壊れた拡声器まで、あらゆるものをシミュレーションすることができます。
StereoWidener は、可変遅延で mono (モノラル) や side コンポーネントにステレオ入力信号を分解し、その後、それらをステレオ効果の感覚を高めるために再結合します。
ネイティブオーディオプラグインは、プラグインのインポーターインスペクター経由で、それぞれのプラットフォームに関連付けられてなければならないので、他のネイティブまたはマネージプラグインと同じスキームを使用します。プラグインを置くためのサブディレクトリに関する詳しい情報は デスクトップ向けのプラグインのビルド を参照してください。プラットフォームの関連付けが必要なため、システムは、スタンドアロンビルドの各ビルドターゲットにどのプラグインを加えるべきかを知っています。64 ビットサポートの導入によって、同じプラットフォーム内でもこれを指定する必要があります。macOS プラグインは、ユニバーサルバイナリ形式をサポートしているため便利です。これにより、同じバンドルに 32 ビットと 64 ビットの両方のバージョンを含むことができます。
マネージコードから呼び出される Unity のネイティブプラグインは、ネイティブ DLL からインポートする関数を参照する [DllImport] 属性を使用してロードされます。しかし、ネイティブオーディオプラグインはそれと事情が異なります。なぜなら、これらは Unity がプラグインからの効果を必要とする可能性のあるミキサー アセットを作成し始める前に、ロードされる必要があるためです。エディターでは、これは問題ありません。プラグインに依存するミキサーをリロードやリビルドできるからです。しかし、スタンドアロンビルドでは、ミキサーアセットを作成する前にプラグインがロードされなければなりません。この問題を回避するために、プラグインの DLL に audioplugin
(大文字小文字を区別しない) を付けると、システムがそれを検出し、起動時に自動的にロードされるプラグインのリストに追加します。プラグイン内の定義は、Unity のミキサー内に表示される効果の名前のみを定義することに注意してください。.dll
の名前が何であっても、それが検出されるためには文字列 audioplugin
で始まる必要があります。
iOS のようなプラットフォームでは、プラグインコードは、生成された XCode プロジェクトによって作られる Unity のバイナリに静的にリンクされる必要があります。そして、プラグインのレンダリングデバイスと同じように、プラグインの登録がアプリケーションの起動コードに明示的に追加される必要があります。
macOS 上で、1 つのバンドルは、32 ビットと 64 ビットの両方のバージョンのプラグインを含むことができます。また、サイズを減らすためにそれらを分割することもできます。
このセクションでは、イコライザーやマルチバンドコンプレッションの効果など、高度なプラグインの使用例を紹介します。これらのプラグインは、上記のセクションで紹介したシンプルなプラグインよりも、はるかに多くのパラメーターを持ちます。さらに、高度なプラグインでは、パラメーター間の物理的な結合が必要であり、パラメーターを視覚化するために、上記の単純なスライダーよりもより良い方法が必要になります。例えば、イコライザーを考えてみましょう。各帯域には 3 つの異なるフィルターがあり、最終的なイコライゼーションカーブに一括して寄与します。これらの各フィルターには、周波数、Q ファクター、ゲインの 3 つのパラメーターがあり、物理的にリンクして各フィルターの形状を定義します。また、イコライザープラグインと大きな画面があれば、結果のカーブと各フィルターの寄与を表示するのに便利です。スライダーを 1 つずつ変更するのではなく、コントロール上の単純なドラッグ操作によって複数のパラメーターを同時に設定する方法でプラグインを操作します。
イコライザープラグインの GUI をカスタマイズするには、3 つのバンドをドラッグしてフィルターカーブのゲインと周波数を変更します。シフトを押しながらドラッグすると、各帯域の形状を変更できます。
要約すると、定義、初期化、非初期化、パラメータ処理は、シンプルなプラグインが使っているのと全く同じ enum ベースのメソッドに従っており、ProcessCallback
コードさえもかなり短くなっています。
適切なプラグインは、以下のように試せます。
1. Visual Studio で AudioPluginDemoGUI.sln
プロジェクトを開きます。
2. GUI コードに関連する C# クラスを探します。
Unity がネイティブプラグインの DLL をロードし、含まれるオーディオプラグインを登録した後、Unity は、登録されたプラグインの名前に一致する対応する GUI を探し始めます。これは、すべてのカスタムプラグイン GUI と同様に、IAudioEffectPluginGUI
を継承する必要がある EqualizerCustomGUI クラスの Name プロパティを通じて行われます。このクラス内の唯一の重要な関数は、bool 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 を返すと、Inspector はカスタム GUI の下にデフォルトの UI スライダーを表示します。これは GUI の開発に便利で、カスタム GUI の開発中にすべてのパラメーターを利用でき、さらに、正しいアクションを実行した結果、期待したパラメーターが変更されたことを確認できるという利点があります。
イコライザーとマルチバンドプラグインで行われる DSP 処理は、Robert Bristow Johnson 氏の優れた Audio EQ Cookbook から引用されたフィルターです。Unity の内部 API 関数を使ってカーブをプロットし、周波数応答のアンチエイリアス曲線を描画します。
イコライザーとマルチバンドのプラグインの両方ともコードを提供し、入力スペクトルと出力スペクトルを重ねてプラグインの効果を可視化します。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 は Steinberg VST や Apple AudioUnits などのような他のプラグイン SDK の上に作成されたものではありません。興味があるユーザーが、他のプラグイン SDK を Unity で使用するために、ネイティブオーディオ SDK を利用して基本ラッパーを実装するのは可能ですが、それは、厳密には Unity の開発チームによって管理されていないことに注意してください。
プラグインをホストするには、独自の課題があります。例えば、予期される呼び出し順序の複雑さを処理したり、ネイティブコードに基づくカスタム GUI ウィンドウを処理したりすることは、すぐに管理不可能になる可能性があり、サンプルコードは役に立ちません。
VST や AU プラグインや、サウンドデザインを単に模写したりテストするための効果でさえ、ロードするととても役立つ可能性があるということを理解する反面、VST/AU の使用は、数種の特定のプラットフォームに制限されるということに注意してください。Unity SDK に基づいてオーディオプラグインを書く利点は、それがソフトウェアミキシングや動的に読み込んだネイティブコードに対応しているすべてのプラットフォームで拡張可能だという点です。したがって、このアプローチは、カスタムプラグインの開発に時間を割く前に、お気に入りのツールを使用して初期のサウンドデザインを試作するための優れたソリューションとしてのみ使用してください (または、エディターでいかなる方法でもサウンドを変更しないメータリングプラグインを使用してください)。