目次

Wwise サウンドエンジン プラグインの作り方

Wwiseサウンドエンジン プラグインの概要

プラグインは、カスタムのDSPルーチンを、サウンドエンジンが実行する全体的な信号処理チェーンに挿入することを可能にします。このプラグインパラメータは、オーサリングツール、またはRTPCを通じてゲーム内のいずれかで制御できます (概念:リアルタイム パラメータコントロール (RTPC) を参照)。

各プラグインは二つのコンポーネントから成り立っています:

  • クロスプラットフォーム、または特定のプラットフォームに最適化して開発できる、サウンドエンジンに統合されたランタイムのコンポーネント。ゲームはこのプラグインと (プラグインの登録 を参照) 、この実装に対するリンクを登録する必要があります。
  • オーサリングツールに統合された、ユーザーインターフェイスコンポーネント。このコンポーネントは、Wwiseが読み込んだDLLにあります (Wwise プラグイン DLL の作成方法 を参照)。UIで設定したすべてのプラグインパラメータは、バンクに保存されるので、このコンポーネントはゲームで使用されません。パラメータを、RTPCを介してゲームで制御する場合には、必要な情報はまたバンクに保存されます。
Tip.gif
Tip: ランタイムコンポーネントには、static library を使用することを推奨します。こうすることで、ランタイムコンポーネントはゲームとプラグインユーザーインターフェイスの両方によりリンクされます (プラグイン ユーザーインターフェイスはWwiseが読み込むDLLです)。

パラメータ ノード インターフェイスは、サウンドエンジンのRTPCマネージャーもしくはWwiseオーサリングツールからの変更に反応し、実行中に現行のプラグインインスタンスパラメータを取得するよう、実装される必要があります。( パラメータ ノード インターフェイスの実装 を参照してください。)

サウンドエンジンに統合できるプラグインには二つの種類があります:

プラグインとこれらに関連するパラメータはサウンドエンジンが、プラグインの仕組みを介して作成します。この仕組みは、新しいパラメータのインスタンスとプラグインのインスタンスを返す、静的な生成する関数の利用が必要になります。次のコードは、これがどのように行われるかを示しています。

// Static parameter node creation function callback to be registered to the plug-in manager.
AK::IAkPluginParam * CreateMyPluginParams( AK::IAkPluginMemAlloc * in_pAllocator )
{
    return AK_PLUGIN_NEW( in_pAllocator, CAkMyPluginParams() );
}

// Static plug-in creation function callback to be registered to the plug-in manager.
AK::IAkPlugin * CreateMyPlugin( AK::IAkPluginMemAlloc * in_pAllocator )
{
    return AK_PLUGIN_NEW( in_pAllocator, CAkMyPlugin() );
}

プラグインの登録

オーディオプラグインの様々なインスタンスは、すべてPlug-in Managerが処理を行い、CompanyIDs ならびにPluginIDsの両方を使って、異なるプラグインクラスを認識します。プラグインは、ゲームで使用する前に、Plug-in Managerに登録する必要があります。登録処理で、PluginIDを引数として提供して、作成関数コールバックに結びつけます。以下のサンプルコードでは、プラグインをゲームで登録する方法を示しています。Audiokineticが提供するプラグインもまた、ゲームで使用する場合に登録する必要があります。

#include <AK/Plugin/AkMyPluginFactory.h>    // Factory headers defines plug-in IDs and declares creation function callbacks

// Demonstrates plugin registration to the plug-in manager
void RegisterPlugins()
{
    AK::SoundEngine::RegisterPlugin( AkPluginTypeSource, AKCOMPANYID_MYCOMPANY, EFFECTID_MYPLUGIN, CreateMyPlugin, CreateMyPluginParams );
    
    ...
}

あなたがプラグイン提供者で、プラグインが他のベンダーのプラグインと同じIDにならないようにしたい場合には、support@audiokinetic.com までお問い合わせください。予約された会社IDを取得できます。

Note.gif
Note: ソースプラグインのためにRegisterPlugin関数のAkPluginType引数をAkPluginTypeSource設定し、エフェクトプラグインにAkPluginTypeEffectを設定します。

Allocating/De-allocating オーディオプラグインにおけるメモリ

すべての動的なメモリ割り当てを実行するか、提供されたメモリ割り当てインターフェイスを介してオーディオプラグイン内の割り当て解除をする必要があります。これにより、プラグインが消費ならびに解放するすべてのメモリをMemory Managerが追跡、これを特定のメモリプールから割り当てし、そしてWwiseプロファイラーで表示します。

マクロは新規/削除演算子をオーバーロードするために提供され、提供されるメモリアロケータを使用してmalloc()/free() を呼び出します。これらのマクロは、IAkPluginMemAlloc.hに提供されています。

オブジェクトを割り当てるためには AK_PLUGIN_NEW() マクロを使用し、ポインターをメモリアロケータと希望するオブジェクトタイプに渡します。このマクロは、新規に割り当てたオブジェクトにポインターを返します。対応する AK_PLUGIN_DELETE() マクロを使用して、メモリを解放します。

配列を割り当てるには、AK_PLUGIN_ALLOC()を使用します。これは、Memory Managerから要求されたバイトでのサイズを得て、割り当てられたメモリアドレスにボイドポインターを返します。対応する AK_PLUGIN_FREE() マクロを使用して、割り当てたメモリを解放します。以下のサンプルコードでは、マクロの使い方を示しています。

// Allocate single CAkMyObject
CAkMykObject * pSingleObject = AK_PLUGIN_NEW( in_pAllocator, CAkMyObject );
// Release pSingleObject
AK_PLUGIN_DELETE( in_pAllocator, pSingleObject );
// Allocate an array of uNumSamples audio samples
AkSampleType * pData = static_cast<AkSampleType *>( AK_PLUGIN_ALLOC( in_pAllocator, sizeof(AkSampleType) * uNumSamples) );
// Free array of audio samples
AK_PLUGIN_FREE( in_pAllocator, pData );

パラメータ ノード インターフェイスの実装

パラメータ ノードは主に、パラメータの読み出し、書き込みアクセスを中央に集めます。プラグイン パラメータ インターフェイスは次のメソッドで成り立っています:

AK::IAkPluginParam::Clone()

このメソッドは、パラメータインスタンスの複製を作成し、必要な内部状態変数を調整して、新しいプラグインインスタンスと共に使用する準備をします。例えば、イベントが新規再生インスタンスを作成すると、この状況が発生します。この関数は、AK_PLUGIN_NEW() マクロを使用して新規のパラメータ ノード インスタンスを返さなければなりません。多くの場合、構造体複製の呼び出しで十分です (以下のサンプルコードを参照)。パラメータ ノード内でメモリが割り当てられる場合には、ディープコピーを実施する必要があります。

// Creating a shared parameters duplicate.
AK::IAkPluginParam * CAkMyPluginParams::Clone( AK::IAkPluginMemAlloc * in_pAllocator )
{
    return AK_PLUGIN_NEW( in_pAllocator, CAkMyPluginParams(*this) );
}

AK::IAkPluginParam::Init()

この関数は、提供されたパラメータブロックでパラメータを初期化します。提供されたパラメータブロックのサイズが0の場合 (つまり、プラグインがオーサリング ツール内で使用された場合)、AKIAkPluginParam::Init() はデフォルト値を使ってパラメータ構造体を初期化します。

Tip.gif
Tip: AK::IAkPluginParam::SetParamsBlock() を呼び出して、有効な場合パラメータブロックを初期化します。
// Parameter node initialization.
AKRESULT CAkMyPluginParams::Init( AK::IAkPluginMemAlloc * in_pAllocator, void * in_pParamsBlock, AkUInt32 in_ulBlockSize )
{
    if ( in_ulBlockSize == 0)
    {
        // Init with default values if we got an invalid parameter block.
        return AK_Success;
    }

    return SetParamsBlock( in_pParamsBlock, in_ulBlockSize );
}

AK::IAkPluginParam::SetParamsBlock()

このメソッドは、Wwiseでバンクを作成した際、AKWwise::IAudioPlugin::GetBankParameters()を介して保存したパラメータブロックを使用して、一度にすべてのプラグインパラメータを設定します。このパラメータは、Wwise同等のプラグインでバンクに書き込んだのと同じ形式で読み出しをします。データはパック形式なので、変数はあるターゲットプラットフォームで要求されるデータタイプと揃わないことがあることに注意してください。AkBankReadHelpers.hで提供されるREADBANKDATAヘルパーマクロを使用して、これらのプラットフォーム特定の問題を回避します。データはアプリケーションによって適切にバイトスワットされているので、プラグインパラメータのエンディアンの問題について心配することはありません。

// Read and parse parameter block.
AKRESULT CAkMyPluginParams::SetParamsBlock( void * in_pParamsBlock, AkUInt32 in_uBlockSize )
{
    // Read data in the order it was put in the bank
    AKRESULT eResult = AK_Success;
    AkUInt8 * pParamsBlock = (AkUInt8 *)in_pParamsBlock;
    m_fFloatParam1  = READBANKDATA( AkReal32, pParamsBlock, in_ulBlockSize );
    m_bBoolParam2 = READBANKDATA( bool, pParamsBlock, in_ulBlockSize );
    CHECKBANKDATASIZE( in_ulBlockSize, eResult );
    return eResult;
}

AK::IAkPluginParam::SetParam()

このメソッドは、単一のパラメータを一度にアップデートします。これは、パラメータ値がプラグインUI、RTPC、またはその他のいずれかで変更された際に必ず呼び出します。アップデートするパラメータはAkPluginParamIDタイプの引数で特定され、Wwise XML プラグインデスクリプションファイルで定義されたAudioEnginePropertyIDに対応します。(詳細は、Wwiseプラグイン XML 記述ファイル を参照してくださ。)

Tip.gif
Tip: XMLファイルで定義された各AudioEngineParameterIDと、AkPluginParamsIDタイプの定数変数にバインドすることを推奨します。
Note.gif
Note: RTPCパラメータをサポートするパラメータは、XMLファイルで特定したプロパティタイプが何であっても、タイプAkReal32を割り当てます。
// Parameters IDs for Wwise or RTPCs.
static const AkPluginParamID AK_MYFLOATPARAM1_ID    = 0;
static const AkPluginParamID AK_MYBOOLPARAM2_ID     = 1;

AKRESULT CAkMyPluginParams::SetParam( AkPluginParamID in_ParamID, void * in_pValue, AkUInt32 in_uParamSize )
{
        // Set parameter value.
        switch ( in_ParamID )
        {
        case AK_MYFLOATPARAM1_ID:
            m_fFloatParam1.fParam2 = *(AkReal32*)( in_pValue );
            break;
        case AK_MYBOOLPARAM2_ID:
            m_bBoolParam2 = *(bool*)( in_pValue ); // Parameter does not support RTPC
                or ...
            // Note RTPC parameters are always of type float regardless of property type in XML plugin description
            m_bBoolParam2 = (*(AkReal32*)(in_pValue)) != 0;
            break;
        
        default:
            return AK_InvalidParameter;
    }

    return AK_Success;
}

AK::IAkPluginParam::Term()

このメソッドは、パラメータ ノードが停止された時、サウンドエンジンが呼び出します。使用したすべてのメモリリソースが解放され、パラメータ ノードのインスタンスは自ら消滅します。

// Shared parameters termination.
AKRESULT CAkMyPluginParams::Term( AK::IAkPluginMemAlloc * in_pAllocator )
{
    AK_PLUGIN_DELETE( in_pAllocator, this );
    return AK_Success;
}

Communication Between Parameter Nodes and Plug-ins.

各プラグインは、パラメータ値を取得できるパラメータ ノードと関連し、これに従ってそのDSPを更新します。関連するパラメータ ノード インターフェイスは、プラグインの初期化で受け渡され、プラグインが存続する限り有効のままになります。このプラグインは、DSP処理が要求する頻度で、パラメータ ノードから情報を問い合わせできます。プラグインとその関連するパラメータ ノードの間の一定方向のみの関係のため、パラメータ ノードが問い合わせるパラメータ値への応答は、作成者の判断に任されます (つまり、アクセサメソッドの使用)。

オーディオプラグインインターフェイスの実装

オーディオプラグインを開発するには、エンジンのオーディオデータフローの中でプラグインが適切に機能できるため、特定の関数を実装する必要があります。ソースプラグインには、AKIAkSourcePluginインターフェイスから生成する必要があり、入力バッファと出力バッファを置き換えることのできるエフェクトには (つまり、アンオーダード アクセス、またはデータレートの変更の必要はありません)、 AK::IAkInPlaceEffectPluginから生成する必要があります。きちっと整理されていない実行が必要な他のエフェクトは、AKIAkOutOfPlaceEffectPlugin インターフェイスから生成する必要があります。

プラグインのライフサイクルは、常にAKIAkPlugin::Init() 関数の呼び出しから始まり、すぐその後にAKIAkPlugin::Reset()の呼び出しに続きます。プラグインが多くのデータ出力を必要とする限り、AKIAkPlugin::Execute() が新しいバッファと共に呼び出されます。プラグインがもう必要でない場合には、AKIAkPlugin::Term() を呼び出します。

AK::IAkPlugin::Term()

このメソッドは、プラグインが停止した時に呼び出します。AKIAkPlugin::Term() は、プラグインが使用したすべてのメモリリソースを解放し、プラグインインスタンスを削除しなければなりません。

AKRESULT CAkMyPlugin::Term( AK::IAkPluginMemAlloc * in_pAllocator )
{
    if ( m_pMyDelayLine != NULL )
    {
        AK_PLUGIN_FREE( in_pAllocator, m_pMyDelayLine );
        m_pMyDelayLine = NULL;
    }

    AK_PLUGIN_DELETE( in_pAllocator, this );
    return AK_Success;
}

AK::IAkPlugin::Reset()

リセットメソッドは、プラグインの状態を初期化し、新しい無関係のオーディオコンテンツ用意する準備をします。サウンドエンジンパイプランは、初期化のすぐ後およびオブジェクトの状態がリセットを必要とする場合にいつでも、AKIAkPlugin::Reset() を呼び出します。通常はすべてのメモリ割り当てが初期化の段階で実施されますが、ディレイラインと例のサンプルカウントはAKIAkPlugin::Reset()でクリアする必要があります。

// Reset internal state of plug-in
AKRESULT CAkMyPlugin::Reset( )
{
    // Reset delay line
    if ( m_pMyDelayLine != NULL )
        memset( m_pMyDelayLine, 0, m_uNumDelaySamples * sizeof(AkSampleType) );
    return AK_Success;
}

AK::IAkPlugin::GetPluginInfo()

このプラグイン情報問い合わせの仕組みは、サウンドエンジンがプラグインについての情報を必要とする場合に使用します。AkPluginInfo 構造体に正しい情報を入力して、実装されているプラグインのタイプを説明 (例えば、ソースまたはエフェクト)、そのバッファ使用のスキーム (たとえば、in placeなど)、および処理モード (例えば、同期など)。

Note.gif

Note: エフェクトプラグインは、すべてのプラットフォームで同期である必要がありますが、PlayStation3はその限りではありません (非同期のSPUベースの実装をする必要があります)。

Note.gif
Note: ソースプラグインは、PlayStation3以外のプラットフォームで同期する必要があり、PlayStation3 プラットフォームでは、AkPluginInfo構造体で指定するように、同期 (PPUベース)、または非同期 (SPUベース) にすることが可能です。
// Effect info query from sound engine.
AKRESULT CAkMyPlugin::GetPluginInfo( AkPluginInfo & out_rPluginInfo )
{
    out_rPluginInfo.eType = AkPluginTypeSource; // Source plug-in.
    out_rPluginInfo.bIsInPlace = true;          // In-place plug-in.
    out_rPluginInfo.bIsAsynchronous = false;    // Synchronous plug-in.
    return AK_Success;
}

AkAudioBuffer構造体を使用するデータにアクセスする

オーディオ データ バッファは、AkAudioBuffer構造体へのポインターを介して、実行時にプラグインに渡されます。プラグインに渡されるすべてのオーディオ バッファは、固定の形式を使用します。ソフトウェアエッフェクトをサポートするプラットフォームでは、オーディオ バッファのチャンネルはインターリーブされておらず、すべてのサンプルは、48 kHzサンプルレートで実行される、(-1.f,1.f)レンジで正規化された32ビット浮動小数点です。

Note.gif
Note: Wii source plug-ins use 16 bit signed integer values [-32768,32767].

AkAudioBuffer構造体は、 インターリーブならびにデインターリーブされたデータの両方にアクセスする方法を提供します。各チャンネルバッファ (AkAudioBuffer::uValidFrames) で有効な、いくつかのサンプルフレームを特定するフィールドと、それらのバッファが含むことのできる最大数のサンプルフレーム (AkAudioBuffer::MaxFrames()が返す)を含みます。

AkAudioBuffer構造体はまた、データに存在するチャンネルを定義するバッファチャンネルマスクを含みます。チャンネル数が必要な場合には、AkAudioBufferNumChannels()を使用します。

インターリーブされたデータの取得。

プラグインは、AkAudioBufferGetInterleavedData()を介してインターリーブしたデータのバッファにアクセスできます。ソースプラグインのみがインターリーブされたデータにアクセス、出力できます。これを行うには、初期化の間にサウンドエンジンを正しく準備する必要があります (AK::IAkSourcePlugin::Init() を参照)。サウンドエンジンは、データを適切にネイティブのパイプライン形式に変換するために、処理したDSPをインスタンス化します。

Tip.gif
Tip: ソースプラグインが既ににサウンドエンジンのネイティブ形式であるデータを出力した場合、より良いパフォーマンスが得られます。

デインターリーブされたデータの取得。

プラグインは、AkAudioBufferGetChannel()を介して個々のデインターリーブされたチャンネルにアクセスします。データタイプは常に、サウンドエンジンのネイティブ形式 (AkSampleType) に一致します。次のコード例は、処理のためにデインターリーブされたチャンネルを取得する方法を示しています。

// Process all channels independently.
void CAkMyPlugin::Execute( AkAudioBuffer * io_pBuffer )     // Input/Output buffer (processing is done in place).
{
    AkUInt32 uNumChannels = io_pBuffer->NumChannels();
    
    for ( AkUInt32 uChan=0; uChan<uNumChannels; uChan++ )
    {
        AkSampleType * pChannel = io_pBuffer->GetChannel( uChan );
        // Process data of pChannel...
    }
}
Caution.gif
Caution: プラグインは、各チャンネルのバッファがメモリで隣接しているのが当然とみなしてはなりません。

チェンネルの配列

チャンネルは次の順番で並んでいます: 左前、右前、中央、左後ろ、右後ろ、および最後にLFEとなります。低域効果音 (Low-Frequency channel: LFE) は、常に 最後に配置されるので、DSPが処理を要求する数だけ、別に処理することができます。プラグインは、 AkAudioBuffer::HasLFE()と共にオーディオバッファにLFEチャンネルが存在する場合に、問い合わせできます。AkAudioBufferGetLFE()を呼び出すことで、直接LFEチャンネルにアクセスできます。次のコードは、LFEチャンネルを別に扱う2つの異なる方法を示しています。

void CAkMyPlugin::Execute( AkAudioBuffer * io_pBuffer )     // Input/Output buffer (processing is done in place).
{
    // Get LFE channel with GetLFE().
    AkSampleType * pLFE = io_pBuffer->GetLFE();
    if ( pLFE )
    {
        // Process data of pLFE...
    }

    OR ...
    
    // Get LFE channel with GetChannel().
    if ( io_pBuffer->HasLFE() )
    {
        AkSampleType * pLFE = io_pBuffer->GetChannel( io_pBuffer->NumChannels() - 1 );
        // Process data of pLFE...
    }
}

LFEにないチャンネルに特定の処理と適用したい場合には、AkCommonDefs.h のチャンネルインデックス定義を使用する必要があります。 For example, if you want to process only the center channel of a 5.x configuration, you would do the following:

// Process only the center channel of a 5.x configuration.
void CAkMyPlugin::Execute( AkAudioBuffer * io_pBuffer )     // Input/Output buffer (processing is done in place).
{
    // Query for specific channel configuration.
    if ( io_pBuffer->GetChannelMask() == AK_SPEAKER_SETUP_5 || io_pBuffer->GetChannelMask() == AK_SPEAKER_SETUP_5POINT1 )
    {
        // Access channel using the index defined for the appropriate configuration.
        AkSampleType * pCenter = io_pBuffer->GetChannel( AK_IDX_SETUP_5_CENTER );
        // Process data of pCenter...
    }
}
Tip.gif
Tip: チャンネルインデックス定義は、設定が N.0 または N.1のいずれのからも独立していることに注意してください。これは、LFE チャンネルは常に最後にあるからです (チャンネル設定がLFEでない場合には、AK_IDX_SETUP_N_LFE は使用しません)。

詳細については、以下のセクションを参照してください: