4.音と映像とインターフェイスの連携

前章3. SiON と DisplayObject の連携の続きです.後半では,インタラクティブな音生成Flashにおいて重要になると考えられる,音の遅延といかに上手に付き合うか,について少し考えてみようと思います.

インターフェイスとの連携

クリックやキー入力といったユーザーアクションに反応して音を鳴らす場合,delayとquantize を0に設定して最速で音を生成します.音の合成は関数呼び出し後の最初のストリーミング時に行われるため,発音の遅延は一定ではありません.
ユーザーアクションに対応して"音楽と同期した"音を鳴らす場合は,delay と quantize で同期タイミングを設定して呼び出します(Synchronized Sequence).この場合,音楽に同期させるための遅延が余計に発生します.この"音楽に同期させるための遅延"は,SiMMLTrack::trackStartDelay プロパティで取得できます.
これらの遅延された発音のタイミングは Event Trigger によって取得します.NOTE_ON_STREAM イベントハンドラで SiONTrackEvent::frameTriggerDelay プロパティを参照するか,NOTE_ON_FRAME イベントハンドラを使用してください.
ユーザーアクションによる音と音楽と映像を連動させる場合は,ユーザーアクション→音楽と同期して音生成→映像リアクションと3段階の遅延が発生します.SiON では,Synchronized Sequence と Event Trigger によってこれらの遅延を処理します.

var driver:SiONDriver = new SiONDriver();
driver.addEventListener(SiONTrackEvent.NOTE_ON_FRAME, _noteOnFrame);
addEventListener(Event.CLICK, _onClick);
driver.play();
...

// クリックで音生成,
private function _onClick(event:Event) : void {
    // delay=0(第4), quant=4(第5) で四分音符に同期 (Synchronized Sequence).
    // eventTriggerID=100 (第7),noteOnType=1 (第8)で,NOTE_ON_FRAME イベントを発行(Event Trigger).
    driver.noteOn(60, null, 4,  0, 4,  0,  100, 1, 0);
}

// 音が発音されるタイミングでNOTE_ON_FRAMEイベントが発生.
private function _noteOnFrame(trackEvent:SiONTrackEvent) : void {
    // eventTriggerIDで発行元をチェック
    if (trackEvent.eventTriggerID == 100) {
        (音に合わせた演出)
    }
}

視覚刺激と聴覚刺激

一般的に視覚は聴覚に比べ時間分解能が低い事が知られています.このため,視覚刺激が聴覚刺激に対して近いタイミングで起きると,視覚刺激は聴覚刺激に引きずられて同じタイミングだと錯覚します(感覚的な問題で文献によっても様々ですが約100ms程度は吸収される?).
SiON における DisplayObject との同期は不完全で,理論上約±20ms(60fpsで2フレーム程度)のバラつきが発生します.このバラつきを補正する事は技術的に可能ですが,そのために一律50msの遅延が必要になるため,現状では入れていません.
ビートに対して音を同期させる場合は厳密に合わせる必要がありますが,絵とビートの同期はそれ程しっかり合わせる必要はありません.この事を念頭に置いておくと,どうしても発生してしまう音の遅延と上手に付き合えるんじゃないかなと思います.

遅延描画と先行判定

例えば Box2D などの物理エンジンで衝突判定に対して音をつける事を考えます.基本的にはユーザーアクションと同様,衝突時に最速で合成させます.この遅延は,一応,上述の錯覚の範囲内に入ります.ただし,バッファサイズ=2048では計算上70±20ms程度の遅延が考えられるためギリギリと言えます.
この音に Synchronized Sequence を設定して音楽に同期させます.すると,上述のように視覚が聴覚刺激に引きずられるため,ボールが壁に当たるタイミングがあたかも音楽にあってるかのように見えます.しかし,この場合の遅延は,錯覚で吸収される範囲を超える事があり,音が遅れて聞こえてしまいます.
● - wonderfl build flash online
このデモは SiON の実装実験中に作ったもので SiON も Box2D も使っていませんが,ランダムなタイミングに発生する床とボールの衝突音をビートに同期させています.クリックでボールを1つづつ落とすと,時々床衝突に対して音が遅れて感じる事があると思います.これは音のズレが錯覚の範囲を超えているためと考えられます.
このデモでは最大200ms程度の遅延が起きていると考えられます.この 200ms という数字は,70±20ms の最速でも存在する遅延と,最大110msのビート同期による遅延を足した大体の値です.つまり 200ms = 50msの固定遅延+150msの変動遅延 と捕らえる事ができます.
もし,この変動遅延150msの中心のタイミングで映像上ボールが床に衝突すれば,バラつきは±75msとなり,理論上,音が衝突と一緒に起きていると錯覚できる範囲に収まることになります.このようなタイミングに衝突を合わせるは,50+75=125ms前に衝突が分かっていれば良い事になります.
これは60fpsで7フレーム程度レンダリングしておいて遅れて映像を出す,または現在の速度と位置から衝突予想時間を概算する事で実現できます.ユーザーアクションに対する反応はこうは行きませんが,あらかじめある程度決まった物理法則などの場合,こういった方法で遅延を打ち消す方法があります.
((♪)) - wonderfl build flash online
このデモは,上述のように音の生成を床との衝突より数フレーム前に行っています.実際にはある程度の速度である程度床に近づいたら音を生成する,という非常に適当な近似を行っているのですが,前のデモよりも音の遅延が少なくなったと感じれると思います.
現実に125ms先読みを行うと,音が前のめり過ぎて違和感を感じます.映像より若干音が遅いほうが自然に感じるようです.ここら辺は個人の感覚で微調整するのが良いと思います.

遅延演出

例えば ホーミングレーザーを撃って敵に着弾し爆発する際の音をつける事を考えます(つまりRez).この爆発音を音楽に同期させる場合,ホーミングレーザーの着弾時間を微調整する事で遅延を消す事ができます.
具体的には,ホーミングレーザーを撃った時点でホーミングレーザーの着弾時間分のdelayと同期 quantize を設定しておいて爆発音を生成してしまいます.この際,実際に生じる遅延時間をSiMMLTrack::trackStartDelayで取得してホーミングレーザーの軌道を計算する事で,爆発音が遅延なしに音楽に同期しているように見せることができます.
また,シューティングゲームで機体の爆発を音楽に同期させるような場合は,例えば小爆発してからタイムラグがあって破壊,のような演出を行えば,遅延をそのままタイムラグに当てはめればごまかす事ができます.これは拙作ゲーム"Quantized Blaze"で使った同期方法です(http://www.vector.co.jp/soft/dl/win95/game/se397592.html).自機被弾の演出が比較的分かりやすいと思います.
このように,遅延を積極的に演出に組み込む事でも,遅延を感じさせないインタラクティブな音表現が可能となると思います.

遅延を緩和するユーザーインタフェイス

例えば,キーボードで鍵盤楽器を模した場合,遅延の影響で打鍵から発音にはタイムラグが生じます.実際の鍵盤楽器はキーを押した瞬間に音がなりますから,このタイムラグは鍵盤楽器を弾いたことのある人なら不快なものとなる可能性があります.
一方,マウスのクリックで音が鳴る場合,同様にタイムラグが発生しますが,クリックはボタンを離した瞬間に決定される場合も多いです.このためタイムラグはそれほど気にならないかもしれません.
このように遅延はそのユーザーアクションの種類によっても感じ方が大きく変わってきます.映像リアクションの見せ方次第でも感じ方は大きく変わってくると思います.