バージョン

menu_open
Wwise SDK 2023.1.3
Sound Engine Object Processorプラグインの作成
警告: Object Processorは、Effect Plug-Inの上位集合のようなもので、Effect Plug-Inが実行できることは全てできるうえ、追加機能もあります。ただし、書く内容がもっと複雑になります。一度に複数のAudio Objectを処理する必要がなく、処理するオブジェクトについてオーディオ信号以外のことを知る必要がなければ、Effect Plug-Inを書いてください。 Effect Plug-inインターフェースを実装する を参照してください。

はじめに

Object Processorは、どのWwise ObjectでもEffectタブの欄に挿入されるという点で、Effect Plug-Inと似ています( Effect Plug-inインターフェースを実装する 参照)。Effect Plug-Inとの違いは、Audio Objectと一緒に使ったときに判明します。一言で言うと、Effect Plug-InはAudio Object (AkAudioObject) に左右されずにオーディオ信号を処理するだけなのに対し、Object Processorはバスを通る全てのAudio Objectを認識し、一度に処理し、それらのメタデータにアクセスすることができます。

Effectプラグインが1つの AkAudioBuffer を受信するのに対し、Object Processorは以下を提供するAkAudioObjectのインスタンスを受信します:

  • Audio Objectの信号を表す、 AkAudioBuffer インスタンスのアレイ。
  • Audio Objectのメタデータを表す、 AkAudioObject インスタンスのアレイ。

これらのコンセプトを、次のセクションで説明します。

Audio Object: コンセプト

Audio Objectの中には、モノラルまたはマルチチャンネルのオーディオ信号が入っています。WwiseでAudio Objectを一番観察しやすいのは、バスのコンフィギュレーションが Audio Objects に設定されているときです。そのようなバスを、 Audio Objectバスと呼びます。マルチチャンネルであってもなくても、 Audio Objectバスはインプットを1つのバッファにミキシングするのではなく、Audio Objectを集めて、そのメタデータを維持します。Audio ObjectバスがサポートするAudio Object数はダイナミックなのに対し、非 Audio Objectバスは、一度に1つのAudio Objectしかサポートしないと考えることができます。

Audio Objectのメタデータは主にポジショニング情報で構成され、それ以外にもカスタムメタデータのアレイがあり、カスタムメタデータはそれ自体がプラグインであり、Object Processorプラグインに使われることがあります。詳細は Audio Object Metadata を参照してください。

Object Processorを実装する

Effect Plug-Inは、 Audio Objectバスに乗せると、Audio Objectの数だけインスタンス化され、各インスタンスのライフタイムは、それがアサインされているAudio Objectのライフタイムに対応しています。実際、1つのインスタンスを再利用しながら各Audio Objectを順番に処理することはできず、理由は、オーディオが次のフレームでも継続することを保障するために必要な状態を、そのEffectが維持しているかもしれないからです。

一方、Object ProcessorはAudio Objectの数に関わらず1度だけインスタンス化され、これら全てのオブジェクトを同時に処理します。

Object Processorは、処理がIn-placeかOut-of-placeかにより、 AK::IAkInPlaceObjectPlugin または AK::IAkOutOfPlaceObjectPlugin を実装します。プラグインは、 AK::IAkPlugin::GetPluginInfo から、trueに設定された AkPluginInfo::bCanProcessObjects を返すことで、Object Processorであることを宣言します。自身がIn-placeなのか、Out-of-placeなのかを、 AkPluginInfo::bIsInPlace を設定することで宣言します。

警告: Out-of-placeのObject Processorは、バスでしかサポートされません。バスのEffect Plug-Inの場合と同様に、データを消費したり生産したりするrate(速度)を、変更できません( AkPluginInfo::bCanChangeRate をtrueにできません)。ただし、アウトプットオブジェクトや、そのチャンネルコンフィギュレーションを変更できます。

ここでは、これらのインターフェース特有の関数だけに触れています。ほかのプラグインタイプと共通のインターフェースコンポーネント( AK::IAkPlugin インターフェース)の情報については Sound Engineプラグインの作成 を参照し、Effect Plug-Inと共通の機能、例えば初期化、バイパス、モニタリングなどの情報については Effect Plug-inインターフェースを実装する を参照してください。

In-Place Object Processorを実装する

In-place Object Processorは、 AK::IAkInPlaceObjectPlugin::Execute で、Audio Objectのバッファ( AkAudioBuffer )やメタデータ( AkAudioObject )のアレイを受け取ります。全てのAudio Objectのオーディオ信号やメタデータを読んだり修正したりできますが、Audio Objectの作成や削除、チャンネルコンフィギュレーションの変更などはできません。

なお、In-place Object ProcessorがEffectとして、バスに挿入されるのではなくActor-Mixer HierarchyやInteractive Music Hierarchyのオブジェクトに挿入されると、Execute中のObject Metadataは無効となり、Metadataに対する変更は、一切保存されないので、注意してください。これを確認するためにObject Processorは、Object Keyが AK_INVALID_AUDIO_OBJECT_ID と等しいかどうかを判断し、このシナリオに効率よく対処するか、エラーとしてフラグを立てます。

Compressorは、In-place Object Processorの一例です。アルゴリズムが、全てのAudio Objectのオーディオ信号を一度に知ることに依存しているので、これはObject Processorである必要がありますが、Audio Objectのリストは変更しません。ここを通るオブジェクトのオーディオ信号を変更するだけです。

void CAkCompressorFX::Execute(
const AkAudioObjects& io_objects // Input objects and object buffers.
)
{
// Audio Objectの信号を、作業バッファ "m_estBuffer" にダウンミックスし、
// そのバッファからゲインを見積り、各Audio Objectの各サンプルに適用するゲインを計算する。
// ...
// コンプレッサゲイン( m_estBuffer のサンプル)を各Audio Objectの、各サンプルに適用する。
// 各オブジェクト用
for (AkUInt32 i = 0; i < io_objects.uNumObjects; ++i)
{
// このオブジェクトの各チャンネル用。
const AkUInt32 uNumChannels = io_objects.ppObjectBuffers[i]->NumChannels();
for (AkUInt32 j = 0; j < uNumChannels; ++j)
{
AkReal32* pInBuf = io_objects.ppObjectBuffers[i]->GetChannel(j);
const AkReal32* pInBufEnd = pInBuf + io_objects.ppObjectBuffers[i]->uValidFrames;
AkReal32* estBuf = m_estBuffer;
while (pInBuf < pInBufEnd)
{
*pInBuf *= *estBuf;
++estBuf;
++pInBuf;
}
}
}
}

もちろんCompressorは、単一のオブジェクトを処理することができ、言い換えれば、従来のチャンネルベースのバスに対応できます。つまり、以前のEffect Plug-Inの実現より優先します。

In-Place Object Processorでテールを扱う

Effectプラグインと同じく、In-place Object Processorもテールを扱うことができ、対象フレーム数分だけ、 AkAudioBuffer::eState フィールドを AK_NoMoreData から AK_DataReady に変更します。詳細は Implementing エフェクトプラグイン テール を参照してください。ここで重要なのは、Object Processorが、全てのAudio Objectのテールを個別に扱う必要があるということです。そこで、各オブジェクトをトラッキングする必要があります。

注意: Executeに送られるオーディオオブジェクトのアドレスを保存しないでください。フレーム毎に変わることがあります。オブジェクトの識別には、 AkAudioObject::key field を使うことが望ましいです。

Out-of-Place Object Processorを実装する

Out-of-place Object Processorは、インプット側とアウトプット側で、異なるAudio Objectセットを取り扱います。インプットされるAudio Objectがホストバスで決まるのに対し、アウトプットされるAudio Objectは、以下のセクションの2メソッドのうちの1つを使い、プラグインが意図的に作成します。全てのオブジェクトが、フレーム毎に AK::IAkOutOfPlaceObjectPlugin::Execute 経由でプラグインに送られます。

Out-of-Place Object Processorと、バスのコンフィギュレーション

アウトプットされるAudio Objectのチャンネルコンフィギュレーションは、プラグインが決めます。

また、非 Audio Objectバス(つまりシングルオブジェクトのバス)は、基本的に、普通の Audio Objectバスの特例であり、Audio Objectが1つしかないだけです。Object ProcessorはAudio Objectを数に関係なく受けとり、出せるので、シングルオブジェクトのバスから、動的に変化する数のAudio Objectをアウプットすることができるだけでなく、逆に従来のミキシングバスのように、 Audio ObjectバスがAudio Objectを1つだけ出すようにすることもできます。

注釈: Object Processorは、非 Audio Objectバスにインサートすると、そのバスの実際のチャンネルコンフィギュレーションを AK::IAkEffectPlugin::Init で受信します。Object Processorsは、Effect Plug-Inの上位集合なので、インサート先が Audio Objectバスでも非 Audio Objectバスでもシームレスに機能するよう、努めてください。ただし、ユーザーエラーで非 Audio Objectバスにインサートされた場合を除きます。例えば、ユーザーが ソフトウェアBinauralizer プラグインを非 Audio Objectバスにインサートしても、ダウンミックスされたオーディオには使えるポジショニング情報がないので、あまり意味がありません。このような場合は、おそらく間違いであることをユーザーに伝えた方がいいでしょう。

Out-of-Place Object Processorでテールやオブジェクトライフタイムを扱う

サウンドエンジンは各フレームの最初に、インプットとアウトプットの全てのAudio Objectの AkAudioBuffer::eState を、 AK_NoMoreData にリセットします。処理後にAudio Objectの AkAudioBuffer::eState が AK_NoMoreData に設定されたままであれば、それは破壊されます。Object Processorは、インプットとアウトプットの全てのオブジェクトが破壊されないと、破壊されません。そこで、Out-of-place Object ProcessorにインプットAudio Objectがなくても、そのままオーディオを出し続けるようにするには、1つまたは複数のアウトプットAudio Objectが消えないように、そのステートを AK_DataReady に設定すれば良いだけです。

注意: あなたのObject Processor内のオブジェクトをトラッキングする場合は、注意が必要です。オブジェクトがサウンドエンジンによって破壊されてから、そのオブジェクトを参照するようなことがないようにしてください。
注釈: インプットオブジェクトは、 AkAudioBuffer::eState を AK_DataReady に設定すれば消えませんが、テールが不要なので、これは避けてください。
注釈: 0個のAudio ObjectをアウトプットするようなOut-of-place Object Processorは、無音をアウトプットします。

Out-of-Place Object Processingの例

Out-of-place Object Processingについて、次の3つのカノニカル事例を通してさらに探ります。

ソフトウェアBinauralizer

ソフトウェアのbinauralizerをOut-of-place Object Processorとして実装し、複数のAudio Objectを受けて、1つのステレオチャンネルコンフィギュレーションのAudio Objectとしてアウトプットすることができます。このようなプラグインは Audio Objectバスに乗せるべきですが、結果的に、このバスのアウトプットは単一のステレオ信号となります。

唯一となるステレオアウトプットオブジェクトを作成するのに便利なのは、 AK::IAkEffectPlugin::Init の、ハンドシェイクメソッド経由の方法です。

AK::IAkPluginMemAlloc* in_pAllocator,
AK::IAkPluginParam* in_pParams,
AkAudioFormat& io_rFormat)
{
m_pContext = in_pContext;
// in_rFormat.channelConfig.eConfigType will be different than AK_ChannelConfigType_Objects if the configuration of the input of the plugin is known and does not support a
// dynamic number of objects. ただしこのプラグインは Audio Objectバスでインスタンス化しないと意味がないので、ユーザーに伝えるのが得策。
// Inform the host that the output will be stereo. ホストがアウトプットオブジェクトを作成してExecuteに渡してくれる。
return AK_Success;
}

次にExecuteで:

void ObjectBinauralizerFX::Execute(
const AkAudioObjects& in_objects, ///< Input objects and object audio buffers.
const AkAudioObjects& out_objects ///< Output objects and object audio buffers.
)
{
AKASSERT(in_objects.uNumObjects > 0); // Should never be called with 0 objects if this plug-in does not force tails.
AKASSERT(out_objects.uNumObjects == 1); // Output config is a stereo channel stream.
// "Binauralize" (just mix) objects in stereo output buffer.
// For the purpose of this demonstration, instead of applying HRTF filters, let's call the built-in service to compute panning gains.
// The output object should be stereo. その2つのチャンネルをクリアする。
memset(out_objects.ppObjectBuffers[0]->GetChannel(0), 0, out_objects.ppObjectBuffers[0]->MaxFrames() * sizeof(AkReal32));
memset(out_objects.ppObjectBuffers[0]->GetChannel(1), 0, out_objects.ppObjectBuffers[0]->MaxFrames() * sizeof(AkReal32));
for (AkUInt32 i = 0; i < in_objects.uNumObjects; ++i)
{
// State management: set the output to AK_DataReady as long as one of the inputs is AK_DataReady. そうでなければ、 AK_NoMoreData と設定。
if (in_objects.ppObjectBuffers[i]->eState != AK_NoMoreData)
eState = in_objects.ppObjectBuffers[i]->eState;
// Prepare mixing matrix for this input.
const AkUInt32 uNumChannelsIn = in_objects.ppObjectBuffers[i]->NumChannels();
uNumChannelsIn,
2);
AK::SpeakerVolumes::Matrix::Zero(mx, uNumChannelsIn, 2);
// Compute panning gains and fill the mixing matrix.
m_pContext->GetMixerCtx()->ComputePositioning(
in_objects.ppObjects[i]->positioning,
out_objects.ppObjectBuffers[0]->GetChannelConfig(),
mx
);
// Using the mixing matrix, mix the channels of the ith input object into the one and only stereo output object.
AK_GET_PLUGIN_SERVICE_MIXER(m_pContext->GlobalContext())->MixNinNChannels(
in_objects.ppObjectBuffers[i],
out_objects.ppObjectBuffers[0],
1.f,
1.f,
mx, /// NOTE: To properly interpolate from frame to frame and avoid any glitch, we would need to store the previous matrix (OR positional information) for each object.
mx);
}
// Set the output object's state.
out_objects.ppObjectBuffers[0]->uValidFrames = in_objects.ppObjectBuffers[0]->MaxFrames();
out_objects.ppObjectBuffers[0]->eState = eState;
}

3D Panner

実装方法によっては、3D Pannerを、前述の ソフトウェアBinauralizer のように機能させることができます。ところが、スペーシャリゼーションを施したバーチャルマイクにそれぞれが対応するような、アウトプットAudio Objectのセットをインスタンス化した方が、より美しく実装できます。これらのAudio Objectの信号を下流のバスやデバイスによってパンニングすることになるので、バーチャルマイクのポジショニングメタデータを最大限に活用できます。

AK::IAkEffectPlugin::Init で、非オブジェクトのコンフィギュレーションを返す代わりに、意図的に AK::IAkEffectPluginContext::CreateOutputObjects を使ってアウトプットオブジェクトを作成します。

static const int k_uNumObjectsOut = 6;
AK::IAkPluginMemAlloc* in_pAllocator,
AK::IAkPluginParam* in_pParams,
AkAudioFormat& in_rFormat
)
{
// Create output objects.
// Desired channel configuration for all new objects: mono.
AkChannelConfig channelConfig;
// AkAudioObjects::uNumObjects, the number of objects to create.
// AkAudioObjects::ppObjectBuffers, Returned array of pointers to the object buffers newly created, allocated by the caller. それらが不要であれば、nullptrを渡す。
// AkAudioObjects::ppObjects, Returned array of pointers to the objects newly created, allocated by the caller. それらが不要であれば、nullptrを渡す。
AkAudioObject* ppObjects[k_uNumObjectsOut];
AkAudioObjects outputObjects;
outputObjects.uNumObjects = k_uNumObjectsOut;
outputObjects.ppObjects = ppObjects;
outputObjects.ppObjectBuffers = nullptr; // not needed.
AKRESULT res = in_pContext->CreateOutputObjects(
channelConfig,
outputObjects
);
if (res == AK_Success)
{
// Set output objects' 3D positions as if they were laid out as a 5.1 config around the listener.
// FL
ppObjects[0]->positioning.threeD.xform.SetPosition(-0.707f, 0.f, 0.707f);
// Store the objects' keys so we can later retrieve them (see helper below).
m_objectKeys[0] = ppObjects[0]->key;
// FR
//...
}
}
// Helper function: find an object having key in_key in array in_objects.
static AkUInt32 FindOutputObjectIdx(AkAudioObjectID in_key, AkAudioObject** in_objects)
{
for (int i = 0; i < k_uNumObjectsOut; i++)
{
if (in_objects[i]->key == in_key)
return i;
}
AKASSERT(false);
return -1;
}
void Ak3DPannerFX::Execute(
const AkAudioObjects& in_objects, // Input objects and object audio buffers.
const AkAudioObjects& out_objects // Output objects and object audio buffers.
)
{
// Compute panning of each object into a temp buffer tempBuffer.
// ...
// Copy each channel of the temp buffer to its corresponding output object.
AKASSERT(k_uNumObjectsOut == out_objects.uNumObjects);
for (int i = 0; i < k_uNumObjectsOut; i++)
{
// Find corresponding output object.
// In Execute, the order of objects is not reliable. 上で定義したヘルパーを使い、アウトプットオブジェクトのアレイにある、各オブジェクトをサーチすることが必要。
AkUInt32 idx = FindOutputObjectIdx(m_objectKeys[i], out_objects.ppObjects);
// Copy the ith temp buffer's channel into the proper output object.
memcpy(out_objects.ppObjectBuffers[idx]->GetChannel(0), tempBuffer.GetChannel(i), tempBuffer.uValidFrames * sizeof(AkReal32));
// Set the output object's state to avoid garbage collection by the host.
out_objects.ppObjectBuffers[idx]->uValidFrames = tempBuffer.uValidFrames;
out_objects.ppObjectBuffers[idx]->eState = tempBuffer.eState;
}
}

上記の例を見て、なぜ (-0.707f, 0.f, 0.707f) がフロントレフトを表すのか、疑問に思われたかもしれません。詳細は 3D Transformationについて を参照してください。

Particle Generator

Particle Generatorは、インプットされた1つのAudio Objectに対してN個のアウトプットAudio Objectを作成し、該当オブジェクトのポジションの周りに、ランダムに配置します。この種のObject ProcessorはInitでオブジェクトを作成することができず、オブジェクトをExecuteでダイナミックに作成して、トラッキングして、インプットオブジェクトのトラッキングをします。もしインプットオブジェクトのステートが AK_NoMoreData であれば、対応するアウトプットオブジェクトのステートも、 AK_NoMoreData に設定する必要があります。そうすれば、サウンドエンジンが、ガベージを確実に収集します。

注意: Out-of-place Object Processorが AK::IAkEffectPluginContext::CreateOutputObjects をExecute内からコールしても、 out_objects で渡されるアウトプットオブジェクトを確実にアクセスできるとは限りません。その場合は、 AK::IAkEffectPluginContext::GetOutputObjects を使う必要があります。
// The plugin needs to maintain a map of input object keys to generated objects. Like so:
struct GeneratedObjects
{
AkUInt32 numObjs;
AkAudioObjectID apObjectKeys[AK_MAX_GENERATED_OBJECTS];
int index; /// We use an index mark each output object as "visited" and map them to input objects (index in the array) at the same time.
};
void ParticleGeneratorFX::Execute(
const AkAudioObjects& in_objects, ///< Input objects and object audio buffers.
const AkAudioObjects& out_objects ///< Output objects and object audio buffers.
)
{
AKASSERT(in_objects.uNumObjects > 0); // Should never be called with 0 objects if this plug-in supports no tail.
// Object bookkeeping.
for (AkUInt32 i = 0; i < in_objects.uNumObjects; ++i)
{
// Find this object in our map.
AkAudioObject * inobj = in_objects.ppObjects[i];
GeneratedObjects * pEntry = m_mapInObjsToOutObjs.Exists(inobj->key);
if (pEntry)
pEntry->index = i; // Found. あとで使えるようにインデックスをメモする。
else
{
// New. Create a new entry and new associated output objects.
pEntry = m_mapInObjsToOutObjs.AddInput(inobj->key);
if (pEntry)
{
AkUInt32 numObjsOut = 1;
{
// If "3D".
// Create between one and AK_MAX_GENERATED_OBJECTS output objects.
AkReal32 fRandom = rand() / ((AkReal32)RAND_MAX);
numObjsOut = (AkUInt32)(fRandom * (AK_MAX_GENERATED_OBJECTS - 1) + 1.f);
}
// Else just create one object.
AkAudioObject ** arNewObjects = (AkAudioObject**)AkAlloca(numObjsOut * sizeof(AkAudioObject*));
AkAudioObjects outputObjects;
outputObjects.uNumObjects = numObjsOut;
outputObjects.ppObjectBuffers = nullptr;
outputObjects.ppObjects = arNewObjects;
if (m_pContext->CreateOutputObjects(in_objects.ppObjectBuffers[i]->GetChannelConfig(), outputObjects) == AK_Success)
{
for (AkUInt32 iObj = 0; iObj < numObjsOut; iObj++)
{
AkAudioObject * pObject = arNewObjects[iObj];
pEntry->apObjectKeys[iObj] = pObject->key;
// Copy the input object's positional metadata, but randomize the actual position.
pObject->positioning.threeD = inobj->positioning.threeD;
// Randomize position and assign to output object.
/// NOTE: By randomizing position now at object creation time and not updating it with inobj->positioning, particles will remain fixed with the listener's head throughout their
/// existence. あるいは、マップにオフセットを格納し、フレーム毎に、それを inobj に適用する方法もある。
AkVector pos = ComputeRandomPosition(inobj->positioning.threeD.xform.Position());
}
pEntry->numObjs = numObjsOut;
pEntry->index = i;
}
}
}
}
// Copy input objects' signal to corresponding output objects. 途中でオブジェクトのガベージコレクション(こちら側で)。
// We cannot use out_objects because we changed the collection of objects during this call!代わりに GetOutputObjects を使う。
// First, query the number of objects.
AkAudioObjects outputObjects;
outputObjects.uNumObjects = 0;
outputObjects.ppObjectBuffers = nullptr;
outputObjects.ppObjects = nullptr;
m_pContext->GetOutputObjects(outputObjects);
if (outputObjects.uNumObjects > 0)
{
// Allocate arrays on the stack and retrieve the output objects.
AkAudioBuffer ** buffersOut = (AkAudioBuffer **)AkAlloca(outputObjects.uNumObjects * sizeof(AkAudioBuffer*));
AkAudioObject ** objectsOut = (AkAudioObject **)AkAlloca(outputObjects.uNumObjects * sizeof(AkAudioObject*));
m_pContext->GetOutputObjects(outputObjects);
// Iterate through our internal map.
while (it != m_mapInObjsToOutObjs.End())
{
// Has the input object been passed to Execute?
if ((*it).pUserData->index >= 0)
{
// Yes. そのシグナルを、関連する各アウトプットオブジェクトにコピーする。
AkAudioBuffer* inbuf = in_objects.ppObjectBuffers[(*it).pUserData->index];
const AkUInt32 uNumChannels = inbuf->NumChannels();
for (AkUInt32 out = 0; out < (*it).pUserData->numObjs; out++)
{
// Find output object.
AkAudioBuffer * pBufferOut = nullptr;
AkAudioObject * pObjOut = nullptr;
for (AkUInt32 i = 0; i < outputObjects.uNumObjects; i++)
{
if (objectsOut[i]->key == (*it).pUserData->apObjectKeys[out])
{
pBufferOut = buffersOut[i];
pObjOut = objectsOut[i];
break;
}
}
if (pObjOut)
{
// Copy each channel.
for (AkUInt32 j = 0; j < uNumChannels; ++j)
{
AkReal32* pInBuf = inbuf->GetChannel(j);
AkReal32* outBuf = pBufferOut->GetChannel(j);
memcpy(outBuf, pInBuf, inbuf->uValidFrames * sizeof(AkReal32));
}
// Copy state.
pBufferOut->uValidFrames = inbuf->uValidFrames;
pBufferOut->eState = inbuf->eState;
// Also, since there is a clear association of input objects to output objects, let's propagate the associated input object's custom metadata to the output.
pObjOut->arCustomMetadata.Copy(in_objects.ppObjects[(*it).pUserData->index]->arCustomMetadata);
}
}
(*it).pUserData->index = -1; // "clear" index for next frame.
++it;
}
else
{
// Destroy stale objects.
// Output objects are collected by the host if we don't set their eState explicitly to AK_DataReady.
// However, here we need to get rid of them on our side otherwise our map would grow indefinitely.
it = m_mapInObjsToOutObjs.EraseSwap(it);
}
}
}
}

Audio Objectに名前をアサインする

オーサリングツールのAudio Object Profilerでは、Audio Objectの名前に、元となったWwise Objectの名前が付けられます。このため、Out-of-place Object Processorのアウトプットオブジェクトの名前は、どれもホストバスの名前が付きます。プロファイリングをしやすくするために、適切であれば、アウトプットオブジェクトの名前を AkAudioObject::SetName を使って付けることを推奨します。

例えば、前述の 3D Panner でオブジェクトを作成するときに、名前を以下のように付けることができます:

// FL
ppObjects[0]->SetName(in_pAllocator, "FL");
//...

Audio Object Metadata

AkAudioObject struct は、オブジェクトパイプライン全体にあるAudio Objectのオーディオバッファに沿って流れる、全てのAudio Objectメタデータを網羅しています。以下の3つのカテゴリに分けることができます:

Positioning Metadata

Audio Objectの AkAudioObject::positioning には、発生元となったサウンドのポジショニングデータが入っています。 AkAudioObject::positioning.behavioral が、Wwise Objectにある全ての該当ポジショニング設定を保持しています。例えば、スピーカーパンニングを使うサウンドであれば、 AkAudioObject::positioning.behavioral.panType にパンナータイプが1つ設定され、 panLRpanBFpanDU がパンナーのポジションとなります。

もしスペーシャリゼーションモードが3Dであれば( AK_SpatializationMode_PositionOnly または AK_SpatializationMode_PositionAndOrientation )、AkAudioObject::positioning.threeD に、3Dポジションに関連する全てのデータが含まれます:

  • AkAudioObject::positioning.threeD.xform は基本的に関連ゲームオブジェクトのポジションを継承しますが、3Dオートメーションなど、各サウンドの3Dポジション設定によっては、それを変更したりオーバーライドしたりできます。
  • AkAudioObject::positioning.threeD.spreadAkAudioObject::positioning.threeD.focus は、一般的にAttenuation Curveから算出されます。

累積ゲインのメタデータ

Audio Objectには、ソースのVolumeや、バスからのゲインの変化や、バス間の接続の変化など、上流で適用された累積ゲインが伴います。EffectやObject Processorのないシンプルなシナリオでは、Audio Objectのゲインは、最終的にスピーカーベッドにミックスダウンされるまで、またはAudio Objectとしてシステム出力に送られるまで、オーディオ信号に適用されないことを意味します。これにより、Audio Objectのオーディオ信号に対し、1フレームに何度もゲインを追加するようなことを回避でき、特にGame Objectのポジションが追加されたり削除されたりすることでAudio Objectが作成され破壊される場合などでも、滑らかに変化するオーディオミックスが生まれます。

このメタデータの対応はObject Processorで任意であり、有効にするには IAkPlugin::GetPluginInfo のObject Processorの実装で、 IAkPluginInfo::bUsesGainAttribute をtrueに設定します。 bUsesGainAttribute をfalseのままにすると、Executeに渡す全てのオーディオバッファに、実行前に適用されたAudio Objectの累積ゲインが伴い、Object Processorに渡されるゲインは中立的となります。しかし bUsesGainAttribute がtrueに設定されると、オーディオバッファは修正されることなく、累積ゲインは非ユニット値になる可能性があります。これにより、Object Processorがゲインを必要に応じて認識することができ、必要に応じてAudio Objectの累積ゲインを変更することもできます。

Audio Objectの累積ゲインを変更する場合は、値が AkRamp であり、あるフレームのfNext値と次のフレームのfPrev値の連続性が、サウンドエンジンによって自動的に処理されないのことに注意してください。つまり、Object Processorが、あるフレームでfNextを変更しようとした場合、次のフレームのfPrevにも同じ変更を適用する必要があります。これを適切にに管理しないと、オーディオパイプラインのほかの部分でAudio Objectのゲインを消費する必要があるときに、オーディオのグリッチや、オーディオ信号の途切れが発生する可能性があります。

3D Transformationについて

スペーシャリゼーションモード AkAudioObject::positioning.behavioral.spatMode が3Dであれば( AK_SpatializationMode_PositionOnly または AK_SpatializationMode_PositionAndOrientation )、3Dポジションは、それが所属するバスに関連付けられているゲームオブジェクト(リスナー)に相対するように、トランスフォーム(変換と回転)されます。例えば、サウンドのポジションが (2, 0, 0) であれば、 (10, 0, 0) のポジションにあるリスナーと関連付けられた Audio Objectバスでそのサウンドを処理すると、出てくるAudio Objectのポジションは、 (-8, 0, 0) となります。このバスにあるObject Processorは、そのAudio Objectが (-8, 0, 0) に配置されているものとして、とらえます。

Wwiseの座標システムは、左手系で、 AkCommonDefs.h で定義した通り、デフォルトのオリエンテーションのフロントベクトルはZ方向を指し、トップベクトルはY方向を指します。

/// Default listener transform.
#define AK_DEFAULT_LISTENER_POSITION_X (0.0f) // at coordinate system's origin
#define AK_DEFAULT_LISTENER_POSITION_Y (0.0f)
#define AK_DEFAULT_LISTENER_POSITION_Z (0.0f)
#define AK_DEFAULT_LISTENER_FRONT_X (0.0f) // looking toward Z,
#define AK_DEFAULT_LISTENER_FRONT_Y (0.0f)
#define AK_DEFAULT_LISTENER_FRONT_Z (1.0f)
#define AK_DEFAULT_TOP_X (0.0f) // top towards Y
#define AK_DEFAULT_TOP_Y (1.0f)
#define AK_DEFAULT_TOP_Z (0.0f)

例えば、 ソフトウェアBinauralizer では、インプットオブジェクトがプラグインに到達する前に回転されるので、正の数Zはリスナーのフロント、正の数Xはリスナーの右となります。この例でサービス AK::IAkMixerPluginContext::ComputePositioning がリスナーのオリエンテーションを使わないのも同じ理由で、デフォルトのオリエンテーションをとります。

そうすると、上の 3D Panner の例で、 (-0.707, 0, 0.707) は、ホストバスに関連付けられたゲームオブジェクトの、フロントレフト方向45度を指します。

Custom Metadataプラグイン

Object Processorは、Audio Objectにアタッチされているカスタムメタデータをアクセスできます。

カスタムメタデータは、パラメータのセットだけで成立するプラグインの一種です。オーサリング側で、メタデータプラグインを好きなWwise Objectに追加でき、ShareSetがサポートされます。サウンドエンジンで、 AK::IAkPluginParam インターフェースを実装します。

フレームごとにAudio Objectは、該当があれば、その発生元となったサウンドにアタッチされているメタデータプラグインを全て収集し、それを自身の AkAudioObject::arCustomMetadata アレイに追加します。次に、訪問する全てのバスにアタッチされている、全てのメタデータプラグインを収集します。Object Processorは、In-placeでもOut-of-placeでも、このアレイを読むことができます。もちろん、プラグインの内容を解釈するには、そのプラグインを認識している必要があります。

Object Processorの実装者が、コンパニオン(仲間)のメタデータプラグインを1つ以上書いて、それをユーザーがWwise Objectに追加することができます。

例えば、前述のSoftware Binauralizer( ソフトウェアBinauralizer 参照)で、Passthrough(通過)モードを設定し、特定のサウンドにHRTFフィルタを適用させない場合を考えます。その場合はコンパニオンとなるメタデータプラグインとして ObjectBinauralizerMetadata を作成し、Passthroughという名前のブール型プロパティを付けます。ユーザーは、HRTFの適用を外したいWwise Objectに、このプラグインを追加します。次に、あなたのObject Processorの、 Execute() で:

for (AkUInt32 i = 0; i < in_objects.uNumObjects; ++i)
{
// Assume that Passthrough Mode is false if there isn't an ObjectBinauralizerMetadata metadata on the Audio Object.
bool bPassthrough = false;
// Search for it.
for (AkAudioObject::ArrayCustomMetadata::Iterator it = in_objects.ppObjects[i]->arCustomMetadata.Begin(); it != in_objects.ppObjects[i]->arCustomMetadata.End(); ++it)
{
if ((*it).pluginID == AKMAKECLASSID(AkPluginTypeMetadata, MYCOMPANYID, OBJECT_BINAURALIZER_METADATA_ID))
{
// The metadata plugin ID matches the type we are looking for. これで、 AK::IAkPluginParam を既知のタイプに安全にキャストできる。
ObjectBinauralizerMetadata * pMetadata = (ObjectBinauralizerMetadata*)(*it).pParam;
bPassthrough = pMetadata->bPassthrough;
}
}
// Do something with bPassthrough
// ...
}
注釈: Audio Objectは、訪問した全てのWwise Objectからメタデータプラグインを収集するので、同じプラグインタイプの、複数のインスタンスが、1つのAudio Objectで見つかることがあります。これが発生したときの方針は、あなたが判断し、ユーザーに通知してください。
@ AK_UnsupportedChannelConfig
Channel configuration is not supported in the current execution context.
Definition: AkTypes.h:260
#define AK_SPEAKER_SETUP_MONO
1.0 setup channel mask
AkSampleType * GetChannel(AkUInt32 in_uIndex)
Definition: AkCommonDefs.h:575
AkPositioningData positioning
Positioning data for deferred 3D rendering.
Definition: AkCommonDefs.h:324
AkForceInline AkChannelConfig GetChannelConfig() const
Definition: AkCommonDefs.h:502
AkUInt64 AkAudioObjectID
Audio Object ID
Definition: AkTypes.h:170
AKRESULT Copy(const AkArray< T, ARG_T, TAlloc, TGrowBy, TMovePolicy > &in_rSource)
Definition: AkArray.h:865
AkAudioObjectID key
Unique ID, local to a given bus. Only the lower 56 of 64 bits are used for the object itself....
Definition: AkCommonDefs.h:322
const AkVector & Position() const
Get position vector.
Definition: AkTypes.h:620
AkForceInline AkUInt32 NumChannels() const
Get the number of channels.
Definition: AkCommonDefs.h:491
AKRESULT
Standard function call result.
Definition: AkTypes.h:213
AkUInt32 uChannelMask
Channel mask (configuration).
AkForceInline AkUInt32 GetRequiredSize(AkUInt32 in_uNumChannelsIn, AkUInt32 in_uNumChannelsOut)
Compute size (in bytes) required for given channel configurations.
@ AK_NoMoreData
No more data is available from the source.
Definition: AkTypes.h:226
#define AkAllocaSIMD(_size_)
AkChannelConfig channelConfig
Channel configuration.
Definition: AkCommonDefs.h:66
AkForceInline void Zero(MatrixPtr in_pVolumes, AkUInt32 in_uNumChannelsIn, AkUInt32 in_uNumChannelsOut)
Clear matrix.
AKSOUNDENGINE_API AKRESULT Init(const AkCommSettings &in_settings)
@ AK_ChannelConfigType_Objects
Object-based configurations.
float AkReal32
32-bit floating point
@ AK_Success
The operation was successful.
Definition: AkTypes.h:215
AKRESULT eState
Execution status
Definition: AkCommonDefs.h:650
#define AK_GET_PLUGIN_SERVICE_MIXER(plugin_ctx)
Definition: IAkPlugin.h:1752
AkUInt16 uValidFrames
Number of valid sample frames in the audio buffer
Definition: AkCommonDefs.h:656
AKRESULT SetName(AK::IAkPluginMemAlloc *in_pAllocator, const char *in_szName)
Definition: AkCommonDefs.h:407
#define AKMAKECLASSID(in_pluginType, in_companyID, in_pluginID)
Definition: AkCommonDefs.h:190
USER_DATA * Exists(KEY in_key)
Returns the user data associated with given input context. Returns NULL if none found.
AkAudioBuffer ** ppObjectBuffers
Array of pointers to audio object buffers.
Definition: AkCommonDefs.h:669
USER_DATA * AddInput(KEY in_key)
Adds an input with new user data.
#define AKASSERT(Condition)
Definition: AkAssert.h:67
AkUInt32 uNumObjects
Number of audio objects.
Definition: AkCommonDefs.h:668
ArrayCustomMetadata arCustomMetadata
Array of custom metadata, gathered from visited objects. Note: any custom metadata is expected to exi...
Definition: AkCommonDefs.h:354
#define AkAlloca(_size_)
Stack allocations.
AkBehavioralPositioningData behavioral
Positioning data inherited from sound structures and mix busses.
Definition: AkCommonDefs.h:291
AkArray< AkInputMapSlot< KEY, USER_DATA >, const AkInputMapSlot< KEY, USER_DATA > &, AkPluginArrayAllocator >::Iterator EraseSwap(typename AkArray< AkInputMapSlot< KEY, USER_DATA >, const AkInputMapSlot< KEY, USER_DATA > &, AkPluginArrayAllocator >::Iterator &in_rIter)
Erase the specified iterator in the array. but it does not guarantee the ordering in the array.
Iterator Begin() const
Returns the iterator to the first item of the array, will be End() if the array is empty.
Definition: AkArray.h:344
Ak3dData threeD
3D data used for 3D spatialization.
Definition: AkCommonDefs.h:290
void SetPosition(const AkVector &in_position)
Set position.
Definition: AkTypes.h:678
A collection of audio objects. Encapsulates the audio data and metadata of each audio object in separ...
Definition: AkCommonDefs.h:661
AkForceInline void SetStandard(AkUInt32 in_uChannelMask)
Set channel config as a standard configuration specified with given channel mask.
AkReal32 * MatrixPtr
Volume matrix. Access each input channel vector with AK::SpeakerVolumes::Matrix::GetChannel().
@ AK_SpatializationMode_None
No spatialization
Definition: AkTypes.h:1218
uint32_t AkUInt32
Unsigned 32-bit integer
AkUInt32 eConfigType
Channel config type (AkChannelConfigType).
virtual AKRESULT CreateOutputObjects(AkChannelConfig in_channelConfig, AkAudioObjects &io_objects)=0
3D vector for some operations in 3D space. Typically intended only for localized calculations due to ...
Definition: AkTypes.h:444
#define AK_SPEAKER_SETUP_STEREO
2.0 setup channel mask
AkMixerInputMap: Map of inputs (identified with AK::IAkMixerInputContext *) to user-defined blocks of...
Defines the parameters of an audio buffer format.
Definition: AkCommonDefs.h:63
@ AK_SpatializationMode_PositionAndOrientation
Spatialization based on both emitter position and emitter orientation.
Definition: AkTypes.h:1220
AkTransform xform
Object position / orientation.
Definition: AkCommonDefs.h:257
AkAudioObject ** ppObjects
Array of pointers to audio objects.
Definition: AkCommonDefs.h:670
Ak3DSpatializationMode spatMode
3D spatialization mode.
Definition: AkCommonDefs.h:282
@ AkPluginTypeMetadata
Metadata plug-in: applies object-based processing to audio data
Definition: AkTypes.h:1259
AkForceInline AkUInt16 MaxFrames() const
Definition: AkCommonDefs.h:643

このページはお役に立ちましたか?

サポートは必要ですか?

ご質問や問題、ご不明点はございますか?お気軽にお問い合わせください。

サポートページをご確認ください

あなたのプロジェクトについて教えてください。ご不明な点はありませんか。

プロジェクトを登録していただくことで、ご利用開始のサポートをいたします。

Wwiseからはじめよう