Quartz の概要

サンプルを正確なタイミングで再生するためのオーディオ エンジン システム

Windows
MacOS
Linux

オーディオ レンダリングでの正確なタイミングとレイテンシーの問題

オーディオ エンジンでは、CPU のパフォーマンスのため、オーディオ サンプルは バッファ にレンダリングされ、出力ハードウェアつまりデジタル オーディオ コンバーター (DAC) に個別に送信されます。多くの理由 (CPU のキャッシュ コヒーレンシー、ハードウェア API のオーバーヘッドなど) により、これが CPU でリアルタイムにオーディオをレンダリングする唯一実現可能な方法です。

これらのバッファには一般に数百から数千ものサンプルデータが一度に含まれます。重要なのは、オーディオ エンジンのレンダリング コマンドが一般に、オーディオ バッファ レンダリングの先頭で実行されることを理解することです。例えば、コマンドは新しいサウンドをプレイして、古いものを停止し、またはボリュームやピッチなどサウンドのパラメータを変更するものです。

このため、レンダリングオーディオ バッファのサイズは直接、新コマンドが実行されるレートをコントロールします。このレートにより、発行されたコマンドで、聞こえるレイテンシーが認識されることも説明されます。例えば、爆発の VFX と爆発音を同一のゲームスレッド ティックでトリガーする場合、爆発の VFX が見えて音が聞こえるまでの間のレイテンシーは、このバッファ サイズで決まります。

つまり、48K サンプル/秒 (kHz) でレンダリングされる 2048 サンプルを含むバッファでは、聞こえるレイテンシーは最大 43 ミリ秒 (ms) になります。

レンダリング バッファ サイズによるオーディオ レンダラのこの本質的なレイテンシーの他に、ゲーム スレッドから発行されたオーディオ コマンドは、ゲーム スレッドとオーディオ スレッドの各ティック、一般的なスレッド通信オーバーヘッドのため、オーディオ エンジンに到達するまで時間がかかります。スレッド処理やゲームプレイ レイテンシーが 13 ms (実際にありえる数値) であり、48 kHz で 2048 サンプルの同じバッファを使用すると、コマンドが現在のバッファでレンダリングを開始できない場合、合計のレイテンシーは最長で 56 ms になります。

Quartz.png

バッファでレンダリングが開始された後にオーディオ リクエストが入る場合 (1)、リクエストがプレイされずに時間が経過し (2)、次のバッファの先頭 (3) で実行されます。

厄介なことに、ゲームスレッド ティックは変動が大きく (少なくともオーディオ レンダラから見た場合)、ガベージ コレクションやアセットのロードなどの間にヒッチ (処理落ち) がよく発生します。

またゲームスレッド ティックはゲームプレイやグラフィック レンダリングのタイミングを決定しますが、オーディオ レンダリングとオーディオのタイミングに関係ありません。サウンドを正確なタイミング (異なるサウンドを終了または開始する正確な時点) にプレイする必要がある場合、ゲームスレッドからこのコマンドを呼び出してもうまくいきません。ゲームスレッドとオーディオ レンダリングスレッドのイベント タイミングが結びつけられていないからです。

これらの 変動するレイテンシーオーディオ イベントのゲームスレッドへのタイミングの依存 問題は、多くのオーディオ アプリケーションでは重要ではありません。ゲームの CPU の負荷または特定プラットフォームの制約により、バッファ サイズと前もってレンダリングするバッファの数を調整することで、多くのタイミングの問題は、認識するしきい値を下回ります。

ただし、インタラクティブ ミュージック、武器の連射を正確に再現する場合、一定リズムのオーディオなど一部のオーディオ アプリケーションでは、このタイミング問題は重大なことです。

Quartz とサンプルの正確な再生

Sample Accuracy (サンプルの正確な再生) は、バッファの境界ではなく、オーディオ レンダラの任意のサンプル (バッファの途中など) から、サウンドを開始する機能です。

Quartz は、サウンド サンプルを正確にプレイすることにより、変動するレイテンシーやゲームスレッドとのタイミングが合わない問題を回避するシステムです。サンプルの正確な再生は、サウンドに対して、オーディオ バッファの先頭ではなくバッファの途中にある、任意のサンプル (時点) でオーディオをレンダリングする機能です。

Quartz1.png

Quartz には、オーディオ呼び出しのタイミングをバッファの途中に設定する方法があり、次のバッファの先頭にオーディオ レンダリングを遅延させることはありません。

オーディオ バッファの先頭でサウンドをレンダリングするのではなく、Quartz は、バッファ サイズ、ゲームスレッドのタイミングまたは変動するレイテンシーの影響を受けないように、期待する音楽理論の値 (小節や拍)、または時間の値 (秒) に基づいてプレイするサウンドをキューに入れます。

ダイナミック ミュージック システムの作成から、サブマシンガンの連射などリズムのある、タイミングが重要なサウンドの制御まで、多くの応用例があります。

Quartz の仕組み

Quartz では、オーディオ レンダリングの特定のサンプルでプレイ コマンドを実行する PlayQuantized() を使用するサウンドのスケジュール設定により、オーディオ エンジンに前もって通知できます。定刻前にスケジュール設定することにより、遅延を考慮します。つまり、遅延がないかのように、キューにあるサウンドをプレイするため、サウンドを正確に計算されたサンプルでレンダリングできます。

バッファ サイズはオーディオ バッファ コールバック サイズ (略して コールバック サイズ) とも呼ばれます。

Quartz2.png

A はゲーム スレッドを表します。X 軸の区切りはゲーム フレーム ティックを表し、一方 B はオーディオ レンダリング スレッドを表し、X 軸の区切りはレンダリング オーディオ バッファを表します。上と下の X 軸はタイミングを合わせています。 (1) ゲーム スレッドが PlayQuantized() を呼び出すとき、指定した量子化境界 (次の四分音符など) でサウンドをプレイすることをリクエストします。オーディオ レンダリング スレッドで最も近いオーディオ出力サンプルを計算するからです。(2) Quartz は汎用 Quantized Command の形式のリクエストを発行し、将来の時点でオーディオをレンダリングするサウンドをキューに入れます。このリクエストは、オーディオ レンダリング スレッドで、計算された量になるまで、複数のオーディオ バッファに保持されます (3)。計算されたバッファでサウンドをレンダリングするタイミングになると、Quartz はバッファの先頭でレンダリングしないように、整数遅延でオーディオをずらし (4)、四分音符で正確にプレイするために必要なサンプルで出力バッファの途中でレンダリングします (5)。

Quartz は ブループリント でも動作します。ブループリントにはこれらの量子化コマンドとタイミング イベントを利用する方法があり、次に オーディオ ミキサー で発生する量子化アクションに同期して、ゲームプレイ ロジックをトリガーします。

Quartz のメイン コンポーネント

Quartz Clock

クロック は、オーディオ レンダリング スレッドでスケジュール設定とイベントの発生を実行するオブジェクトです。クロックは Quartz サブシステム で作成され、ブループリントで Clock Handles を使用して変更されます。それぞれのクロックには Quartz Metronome があります。

Quartz Metronome

メトロノーム は、オーディオ レンダリング スレッド オブジェクトです。時間の経過を追跡し、到着コマンドを (BPM (拍/分) と拍子記号 (タイム シグネチャ) などユーザー指定情報から) 実行する必要がある時点を決定します。

ゲームプレイ ロジックは、音楽を開始する時点の通知を受けるため、メトロノームのイベントにサブスクライブにできます。

Quartz Clock Handle

Clock Handleアクティブ クロック のゲームプレイ側プロキシです。これは Quartz サブシステム から取得され、オーディオ ミキサーで動作するクロックをコントロールするために使用されます。

Quartz サブシステム

Clock Handle が特定クロックの機能にアクセスできる一方、Quartz サブシステムは特定クロックに関連しない機能にアクセスできます。これに含まれるのは、クロックの作成と取得、クロックが存在することを確認、表示、レイテンシー情報のクエリです。

AudioComponent:PlayQuantized()

オーディオ コンポーネントに追加された新規関数で、指定クロック、指定 Quantization Boundary でサウンドをプレイする方法が用意されています。

Quartz クロックの作成と初期化

以下にサンプル ワークフローを示します。

  1. クロックを作成するには、Create New ClockQuartz サブシステム で呼び出します。

  2. Clock NameTime Signature を指定します。

  3. 戻り値をブループリント変数として格納します。(同じ名前のクロックがすでに存在する場合、既存のクロックのハンドルを返します)。

  4. クロックの Tick RateClock Handle で以下の関数のいずれかを呼び出すことにより設定します。

    • Beats per Minute (以下の図で設定)

    • Milliseconds per Tick

    • Ticks per Second

    • Thirty-second Notes per Minute

ステップ 4 の呼び出しは交換可能で、利便性を考慮してバリエーションがあります。注意が必要なのは、それぞれに対応する getter があることです。

量子化境界でのサウンドのプレイ

量子化境界でサウンドをプレイするには、Play QuantizedAudio Component で呼び出し、Clock Handle と Quantization Boundary を指定します。

Quartz Quantization Boundary は、サウンドを開始する正確なタイミングをオーディオ ミキサーに指示する方法です。これはスケジュール関連の Quartz 関数のいくつかで使用されます。この構造体に複数のオプションがあります。

Quantization はスケジュール設定で使用する時間値です。可能な継続時間を示します。

持続時間

説明

Bar (小節)

拍子記号により自動で決まります。

Beat (拍)

デフォルトは拍子記号の分母ですが、オーバーライドできます。

1/32

三十二分音符は利用可能な最小の持続時間です。

1/16、(付点) 1/16、1/16 (三連符)

十六分音符、付点十六分音符、十六分三連符

1/8、(付点) 1/8、1/8 (三連符)

八分音符、付点八分音符、八分三連符

1/4, (付点) 1/4, 1/4 (三連符)

四分音符、付点四分音符、四分三連符

Half、(付点) Half、Half (三連符)

二分音符、付点二分音符、二分三連符

Whole、(付点) Whole、Whole (三連符)

全音符、付点全音符、全三連符。全音符は利用できる最長の持続時間です。

Tick

ティックは最小値、三十二分音符と同じです。

Multiplier は、サウンドをプレイするまでの Quantization 時間値の数です。これは選択した Quantization 時間値の乗数です。

Counting Reference Point はイベントのスケジュールを設定するために使用する参照ポイントです。異なるユースケースでは、異なる参照ポイントを使用します。

  • Bar Relative — 現在の小節の最初からです。例えば、ドラム マシンでは

    • Quantization を Beat に、Multiplier を 2.0 にすると、次の Beat 2 (小節の 2 番目の拍) を発生させるイベントのスケジュールを設定します。

    • クロックが Beat 1 (1 小節に 4 拍) に設定されると、イベントは次の拍でトリガーされます。

    • クロックが Beat 3 (4 拍の第 3 拍) に設定されると、イベントは次の小節の Beat 2 でトリガーされます。

  • Transport Relative — クロックの開始時、トランスポートが最後にリセットされた時点を基準にします。例えば、4 小節の境界でステムをトリガーするとき

    • これは Transport (曲番号) を基準にクリップ/ステムをトリガーするために使用します。

    • クロックが Bar 6 にある場合、イベントが Bar 9 (トランスポートは Bar 1 からカウント) でトリガーされます。

  • Current Time Relative — 現在の時刻を基準にします。例えば、すぐに音楽がわかるように、スティンガーをトリガーするとき

    • Quantization を Beat にして、Multiplier を 2.0 にすると、次の次の強拍にイベントのスケジュールを設定します。

    • クロックが Beat 1 (4 拍の第 1 拍) にある場合、イベントは Beat 3 でトリガーされます。

    • クロックが Beat 3 (4 拍の第 3 拍) にある場合、イベントは次の小節の Beat 1 でトリガーされます。

Quartz では、小節が全音に対応するとは限らず、拍が四分音符に対応するとは限りません。詳細については、「拍子記号とパルスのオーバーライド」を参照してください。

コマンド イベントへのサブスクライブ

ゲームプレイ ロジックを量子化イベント (プレイするようにスケジュール設定したサウンドの開始など) に同期するとき、特定コマンドのライフ サイクルの異なる段階で通知できます。

これを実行するには、これらの種類の関数で In Delegate 引数のピンからドラッグして、カスタム イベント を作成します。

このカスタム イベントは多様な理由で複数回呼び出されることがあります。この画像の例では、イベント タイプ EQuantizationCommandDelegateSubType 列挙型を次の各イベントに応答するように分割できます。

イベント タイプ

説明

Failed To Queue

コマンドはオーディオ レンダリング スレッドに入力できませんでした (Play Quantized の並行処理に失敗した可能性がある)。

Queued

コマンドがオーディオ レンダリング スレッドで受け取り、実行を待機しています (並行処理を通過)

Canceled

実行前にオーディオ レンダリング スレッドでコマンドがキャンセルされました (プレイが始まる前にオーディオ コンポーネントで停止が呼び出された可能性がある)。

About to Start

コマンドの実行が可能な状態です (Started と同じゲームプレイ フレームで現在呼び出されるが、将来は、スレッド間のレイテンシーを相殺できるほど十分早く呼び出される予定)。

Started

コマンドはオーディオ レンダリング スレッドで実行されています。

メトロノーム イベントへのサブスクライブ

Quantized Command ライフ サイクルの多様な段階にサブスクライブしてゲームプレイをオーディオ レンダリングと同期する他に、Quantization Duration が発生するたびに通知することもできます。

次の例では、bar 通知にサブスクライブします。イベントが発生するたびに、次の小節でサウンドをプレイするようにスケジュール設定します。

単一イベントですべての Quantization Duration にアクセスする場合、同時にすべての Quantization Events にサブスクライブして、Quantization Type 列挙型で切り替えられます。

拍子記号とパルスのオーバーライド

Quartz では拍子記号と拍を細かくコントロールできます。前の「Quartz クロックの作成と初期化」で示したように、クロックを作成するとき、拍子記号 を指定する必要があります。

拍子記号に 3 つのフィールドがあります。最初の 2 つで通常の拍子記号を表します。

  • Num Beats: 小節ごとの拍 (Beat Types で指定したタイプ) の数、拍子記号の 分子 です。

  • Beat Type: 拍の継続時間を表す列挙型で、拍子記号の 分母 です。使用可能な値は次のとおりです。

    列挙型

    /2

    二分音符

    /4

    四分音符

    /8

    八分音符

    /16

    十六分音符

    /32

    三十二分音符

  • Optional Pulse Override、3 番目のフィールドには、小節全体で拍の継続時間を指定する方法を入力します。Optional Pulse Override を入力しない場合、拍子記号の Beat Type がデフォルトで設定されます。

Bar Quantization Boundary は拍子記号を直感的にコントロールするものです。4/4 拍子では、一小節に 4 個の四分音符、12/8 拍子では、一小節に 12 個の八分音符の長さになります。

Pulse Override 引数は実際にはパルスオーバーライドの配列です。各エントリで、指定 Duration で期待される Number of Pulses を指定し、それから配列の次のエントリに移ります。以下の例を参照してください。

この拍子記号は 7/8 です。Optional Pulse Override を指定しないと、Beat Quantization Boundary は /8 (八分音符) Quantization Boundary と同じです。

ただし Quartz では、拍 (パルス) の長さを正確に指定できます。この例では、配列に 2 つのエントリがあり、[Details (詳細)] パネルの [Default Value (デフォルト値)] に示されています。

OptionalPulseOverride_Values.png

0 値 では

  • Number Of Pulses (パルス数):2

  • Pulse Duration (パルス持続時間):1/4

初めの 2 拍は四分音符の長さです。

1 値 では

  • Number Of Pulses (パルス数):1

  • ulse Duration: (付点) 四分音符

これは最後の拍が付点四分音符の長さです。

これによりカウントは (1, 2) (1, 2) (1, 2, 3) になります。太字の 1 は拍をとる八分音符です。ブループリントで Beat Quantization Boundary にサブスクライブしている場合、ここでこのイベントが発生します。

両エントリを切り替えると、(1, 2, 3) (1, 2) (1, 2) になります。

値が小節より短い継続時間になる場合、最後のエントリは正しい長さになるまで複製されます。

小節より長い継続時間になる値が入力された場合、リストは切り捨てられ、次の小節の強拍でリストの先頭から開始されます。

Unreal Engine のドキュメントを改善するために協力をお願いします!どのような改善を望んでいるかご意見をお聞かせください。
調査に参加する
閉じる