Talking the Talk: 会話 のための DirectX オーディオ スクリプティング ソリューション

Scott Selfon
Audio Content Consultant, Xbox Advanced Technology Group

November 2001

要約: 本ドキュメントは、Microsoft DirectMusic 用のコンテンツ作成ツールである Microsoft DirectMusic Producer のさまざまな側面を扱った一連の記事のうちの一編です。このドキュメントでは、ゲームやその他のアプリケーションの中で声による対話を実装するためのテクニックについて説明します。

はじめに

コンテンツ作成者にとっての課題の 1 つとして、会話をどのように扱うかということがあります。多くの場合は、完成した .wav ファイルを開発者に渡すだけで済みます。しかし、連結したコンテンツを利用する場合には(たとえば「このゲームではチームXとチームYが対戦します」)、開発者に正しいファイルを正しい順序で正しいタイミングで再生してもらわなくてはなりません。さらに、タイトルをローカライズする場合には、一部のコンテンツを取り替える必要があります。この場合に、ローカリゼーション チームはコードの内容を調べ、対象箇所を手作業で変更しなくてはなりません。また、プロデューサーがやってきて文の構造を変更するよう指示したり、言葉遣いやクリエイティブな変更に基づいて、コンテンツの文法上の構造を変更する必要が生じた場合にはどうすればよいでしょうか?

この分野では、Microsoft(R) DirectX(R) オーディオ スクリプティングが有効な解決方法を提供できます。DirectX オーディオ スクリプティングにより、コンテンツ作成者はコンテンツの動作を制御でき、ゲームを再コンパイルすることなく変更を実装できます。

この記事では以下のトピックを扱っています。

  • 会話の連結: 条件付きフレーズ
  • プライマリおよびセカンダリ セグメントのキューイング
  • スクリプティングと会話内のポーズ
  • スクリプティングを使ったオーディオのローカリゼーション
  • 関連情報

会話の連結: 条件付きフレーズ

ウェーブと Microsoft DirectMusic(R) セグメントの基本的な再生については、「コンテンツとコーディングのギャップの橋渡し: DirectX オーディオ スクリプティング入門」で説明しました。この記事では、複数の会話の断片をつなげて 1 つの文を作成するという、さらに興味深いテクニックを扱います。DirectX オーディオ スクリプティングにより、サウンド デザイナはこの側面を完全に制御できます。開発者やゲーム コードに影響を与えることなく、スピーチの一部を可変にしたり、一部の会話を録音し直すなどの操作を可能にします。

まず、部屋に入ってきたキャラクタに挨拶をするという基本的なシナリオを取り上げます。話を単純にするために、会話の断片 "Hello" と、部屋に入ってくる可能性のあるすべてのキャラクタ、"Jack"、"Jill"、"Joe" の名前はすでに録音してあるものとします。この例では、個々のサウンドの内容が、"Hello.wav" や "Jack.wav" のようなファイルの名前として、またスクリプト内で使用するオブジェクトの名前としても使われています。ウェーブをプロジェクトに追加し、作成しておいたスクリプト ファイルにドラッグすると、Microsoft DirectMusic Producer のプロジェクト ツリーは次のようになります。

図 1. .wav ファイルを参照しているスクリプト

さて、実装方法としては、次の 2 つの選択肢があります。

  • 開発者は、誰が部屋に入ってきたのかを判定し、SayHelloToJack や SayHelloToJill などの適切な関数を呼び出す。

- または -

  • 開発者は、変数の値を設定し、SayHello などの総称的なトリガを呼び出すことで、スクリプトに対して誰が部屋に入ってきたのかを通知する。

最初のオプションは、開発者とスクリプト作成者の両方にとって、作業量が若干増えます。一部のコードを複製しなくてはならず、新しいキャラクタを追加することに決まったら、新しいトリガを導入する必要があります。第 2 のオプションでは、スクリプト作成者の作業量が増えるだけでなく、少し後に説明するようにいくつかの問題が生じる可能性があります。次に、各オプションのコードを示します。

オプション 1: 基本的なトリガ ベースの条件付きフレーズ

次のスクリプトは、挨拶の個々のバリエーションについて、独立した関数を提供しています。


Sub SayHelloToJack
    Hello.play
    Jack.play AtFinish
End Sub
 
Sub SayHelloToJill
    Hello.play
    Jill.play AtFinish   
End Sub
 
Sub SayHelloToJoe
    Hello.play
    Joe.play AtFinish
End sub

各ルーチンでは、"Hello" のウェーブを再生した後に、具体的な名前を再生します。ここでは AtFinish フラグを使用して、前のサウンドが終了するまで待つようにしています。このフラグを何度も使用することで、多数のウェーブを連結し、キュー操作のようなことが可能になります。次に例を示します。


Sub SayHelloToJohnDoe
    Hello.play
    John.play AtFinish
    Doe.play AtFinish
End Sub

"John" と "Doe" のウェーブが存在していれば、このルーチンを実行した結果として "Hello John Doe" という挨拶が再生されます。

オプション 2: 変数とトリガ ベースの条件付きフレーズの組み合わせ

上記の解決方法では、スクリプトがすぐに大きくなり、サウンド デザイナにとってはコードの保守の負担が大きくなりかねません。その代わりとして、変数を使う方法があります。DirectX オーディオ スクリプティングは、前のルーチンまたは開発者によって設定された変数を使用します。変数の値を更新し、ルーチンを適切なタイミングで呼び出すように、事前に開発者との間で打ち合わせをしておく必要がありますが、新しい内容を追加するときには、1 つのウェーブといくつかの文を追加するだけで済み、開発者に与える影響は最小限で済みます。

このシナリオでは、キャラクタが部屋に入ってきたときに、ある変数 (CharacterID という名前にします) がそのキャラクタに対応する値に設定され、総称的な SayHello ルーチンが呼び出されることが、開発者との間で同意されているものとします。

スクリプトの最初の形は次のようになります。アポストロフィーで始まる行は、コードを読みやすくするためのコメントであり、コードには影響を与えません。


Dim CharacterID
' すべてのルーチンの外部で宣言され
' アプリケーションから見える変数。
' 1 = Jack、2 = Jane、3 = Joe
 
Sub SayHello
    Hello.Play
    If (CharacterID = 1) Then
        Jack.Play AtFinish
    ElseIf (CharacterID = 2) Then
        Jane.Play AtFinish
    ElseIf (CharacterID = 3) Then
        Joe.Play AtFinish
    Else
        Trace "Error - Character ID unknown"
    End If
End Sub

このコードには、スクリプティング上の重要なトピックがいくつか含まれています。IfElseIfElse、および End If は、条件文のためのキーワードです。1 つまたは複数の変数の値をチェックした上で、これらの値に基づいて何らかの動作を実行します。

また、スクリプトに組み込まれている Trace 文は、デバッグ支援ツールとなります。CharacterID が 1、2、または 3 以外の値だった場合には、このデバッグ文字列を含んだ特殊なパフォーマンス メッセージを送信します。DirectMusic Producer はメッセージ ウィンドウにこれらのトレース文を表示します。またアプリケーションは、デバッグ用にこれらの文を受け取るツールの使用も可能です。

図 2. メッセージ ウィンドウ

図 2は、上のシナリオで CharacterID が 1、2、または 3 以外の値だった場合の、DirectMusic Producer のメッセージ ウィンドウの内容を示しています。

プライマリおよびセカンダリ セグメントのキューイング

DirectMusic の重要なコンセプトとして、プライマリ セグメントは一度に 1 つしか再生できず、その上に任意の数のセカンダリ セグメントをレイヤ化できるということがあります。したがって、セグメントをプライマリ セグメントとして再生すると、それ以前に再生されていたすべてのプライマリ セグメントが、セカンダリ セグメントに影響を与えずに停止され、置き換えられます。

このことから、上記のスクリプティングの例では興味深い問題が発生します。2 人のキャラクタがほぼ同時に部屋に入ってきた場合、一方のキャラクタからのトリガは "hello" を開始し、もう一方のキャラクタからのトリガがそれを中断させることになります。ケースによっては、これが望ましい処理である場合もありますが、複数の会話(またはサウンド エフェクトなどの他のコンテンツ)を同時に聴きたいこともあります。その場合には、IsSecondary フラグを使ってセカンダリ セグメントを再生するという方法を使った方がいいでしょう。

さらに問題を複雑にするのは、デフォルトでは、セグメントのキューイングはプライマリ セグメントを基準として行われるということです。たとえば、AtFinish フラグは、プライマリ セグメントが終了したときにセグメントの再生を開始するよう指定します。しかし、複数の "Hello" のインスタンスを再生したい場合には、これをセカンダリ セグメントとして再生する必要があります。キャラクタの名前に対してキューをいつ出すべきかを知るためには、再生中のセグメントのインスタンスを追跡する必要があります。これには PlayingSegment オブジェクトという名前が付けられています。

最初のシナリオに戻って、セカンダリ セグメントを使ってJackに挨拶することにしましょう。DirectMusic パフォーマンスが他のサウンド エフェクトや音楽を同時に再生していても、このトリガによって中断されることはありません。


Sub SayHelloToJack
    Set PlayingSeg1 = Hello.play (IsSecondary)
    ' オブジェクトを等号の右側で使用する場合には、
    ' すべてのパラメータをカッコで囲む必要があります。
    ' 等号がない場合にはオプションです。
 
    Jack.play IsSecondary + AtFinish, nothing, nothing, PlayingSeg1
End Sub
 

元のルーチンにいくつかの変更が加えられていることがわかります。

まず、キューを発行するタイミングを知る目的で、現在のセカンダリ セグメントを追跡するために、PlayingSeg1 という名前の PlayingSegment オブジェクトを使用しています。

第 2 に、Play メソッドの新しいパラメータを使用しています。AudioVBScript の最も柔軟性の高いメソッドの 1 つである Play には、4 種類の設定可能なパラメータがあります。

  • Flags タイミングの設定に最もよく使われるパラメータ。セグメントがセカンダリ セグメントなのかどうか、またその他の動作を指定します。
  • Audiopath このセグメントの再生に使用するパフォーマンス チャネル、エフェクト、およびバッファの具体的な構成。これが指定されていない場合、セグメントはデフォルトのオーディオパス上で再生されます。
  • *TransitionSegmen
    *現在再生中のセグメントからこのセグメントへのトランジションに使用するセグメント。
  • FromPlayingSegment このセグメントが基準として使用する置き換え先の PlayingSegment オブジェクト。この例では、"Hello" を再生した直後に "Jack" を再生しています。

この例では、実際には FlagsFromPlayingSegment しか使用していないので、他のパラメータはスクリプト内で nothing に設定されています。パラメータをキーワード nothing に設定するか、空のままにしておくと、そのパラメータのデフォルト値が使用されます。省略されるパラメータがリストの末尾にある場合を除き、そのパラメータをカンマで表現する必要があります。以下に示す呼び出しは等価です。


Jack.play IsSecondary + AtFinish, nothing, nothing, PlayingSeg1
Jack.play IsSecondary + AtFinish, , , PlayingSeg1
 

複数のウェーブを1つの会話の断片につなげるというアイデアに戻ると、次のようなコードが得られます。


Sub SayHelloToJohnDoe
    Set Pseg1 = Hello.play (IsSecondary)
    Set Pseg1 = John.play (IsSecondary + AtFinish, , , Pseg1)
    Set Pseg1 = Doe.play (IsSecondary + AtFinish, , , Pseg1)
End sub

このコードは、3 つのウェーブに対して順番にキューを発行し、"Hello John Doe" という文を作成します。すべてのウェーブがセカンダリ セグメントとして再生されます。

このトピックの最後の例として、オプション 2 の総称的な "SayHello" ルーチンを、セカンダリ セグメントを使うように変更します。


dim CharacterID
' PlayingSegment オブジェクトを追跡したい場合には、それ用に "dim" を
' 使用するべきです。しかし、このオブジェクトは 1 つのルーチンの中で
' しか使用されないので、ローカル宣言のままにしておきます
' (Dim文はなし)。

Sub SayHello
    Set Pseg1 = Hello.Play (IsSecondary)
    If (CharacterID = 1) then
        Set Pseg1 = Jack.Play (IsSecondary + AtFinish, , , Pseg1)
    ElseIf (CharacterID = 2) then
        Set Pseg1 = Jane.Play (IsSecondary + AtFinish, , , Pseg1)
    ElseIf (CharacterID = 3) then
        Set Pseg1 = Joe.Play (IsSecondary + AtFinish, , , Pseg1)
    Else
        Trace "Error - Character ID unknown"
    End If
End Sub

スクリプティングと会話内のポーズ

DirectX オーディオ スクリプティング ルーチンは高速で実行されるように作られており、ルーチンの途中で強制的にポーズする手段はありません。会話 フラグメントの間の、話し手の自然なポーズは、一般にはコンテンツそのものの中に組み込むべきです。会話を録音するときには、そこに含まれているポーズを残すようにします。別の方法として、ノートやウェーブ データを含んでいない、任意の長さの空のセグメントを作成し、会話 フラグメントの間でこれらにキューを発行して、ポーズを追加することもできます。

スクリプティングを使ったオーディオのローカリゼーション

ここまでで扱った概念を使用すると、複数の言語でコンテンツを提供し、プロダクトのローカライズ バージョンの中で適切な言語を再生できます。この場合には、ゲームの使用言語を追跡する変数を使うのが非常に簡単です。使用言語はゲーム中には変わらないでしょうし、たとえば変わるとしても、スクリプトはコンテンツを再生する前に変数の値をチェックすれば済みます。

次に、挨拶が 3 つの言語のうちのいずれかで再生される単純な例を示します。


Dim LanguageSetting
' 1=英語、2=スペイン語、3=フランス語。
 
Sub SayHello_Localized
    If LanguageSetting = 1 then
         Hello.play
    ElseIf LanguageSetting = 2 then
         Hola.play
    ElseIf LanguageSetting = 3 then
         Bonjour.play
    Else
         Trace "Error - unknown language setting"
    End If
End Sub

関連情報

DirectX オーディオ スクリプティング言語の詳細については、DirectMusic Producer のヘルプの「Scripting Reference」のセクションを参照してください。また、DirectX Software Development Kit (SDK) に収録されている Baseball スクリプティング サンプルも参考になります。このスクリプトを DirectMusic Producer プロジェクトにインポートし、ソース コードを見ることができます。このサンプルでは、会話の連結や、会話に影響を与える変数など、ここで扱った概念の多くが使われています。

DirectX オーディオ スクリプティング言語は Microsoft Visual Basic(R) Scripting Edition のサブセットを使用しています。この言語の詳しい説明については、MSDN Visual Basic Scripting Edition ページのリファレンス ガイドを参照してください。

このシリーズの以下の記事では、DirectMusic Producer の使い方に関する詳しい情報を提供しています。