【UE5】Quartzでインタラクティブミュージック!

はじめに

第19回ぷちコン投稿作品にて、UE5のQuartz機能を利用してインタラクティブミュージックを実装しました。

UEでQuartzを利用したインタラクティブミュージックの作例は散見されつつも、案外Quartzの日本語記事が少ないように見受けられましたのでまとめました。

記事作成時のUEバージョンは5.1.1です。

4/16追記 誤字や一部リンク先のミスを修正。

おことわりとしまして、筆者の音楽の知識は浅いです。記事に誤りがあればご指摘ください。

特にBeat(拍)やBar(小節)とか4/4拍子と言われても「????」の状態でしたので、基本の音楽用語については以下記事を参考にさせていただきました。(私のような音楽センス0にもやさしく教えてくれる記事) soundquest.jp

冒頭はインタラクティブミュージックですとかQuartzを素人なりに噛み砕いた解説なので、それはいいから実装例だけ教えて!という方は「Quartz実装前の準備」から読んでください。

インタラクティブミュージックって?

インタラクティブミュージックとはゲームにおいてプレイヤーの操作で特定の場所へ近づいたり離れたり、バトルを勝ち抜いていく時のような、ゲームの展開と連動するように曲調やアレンジが変化するようなものを指すと思ってください。

このあたりは私も理解が浅いので、「ゲームメーカーズ スクランブル ゲーム音楽を彩る「インタラクティブミュージック」の進化の歴史 (講演 岩本 翔 氏)」の資料がとても参考になります。 www.docswell.com

なぜゲーム内では単純に繋げて再生できないの?

ループする曲と次のループさせる曲を繋げるだけであれば(上記スライドでいう「横の遷移」)、最初に再生する曲の長さを取得して、その時間経過後次のループミュージックを再生して繋げてその繰り返しで済むのでは?と思うのですが、

ゲームプレイ中はゲームスレッド ティックの変動が大きく、アセットロードやガベージ コレクションの処理落ちも発生しやすいという事情や、ゲームスレッドとオーディオ レンダリングスレッドのイベント タイミングが結びつけられていない(ゲームスレッド側からオーディオ再生を呼び出しても即座に応答しない場合がある)といった複合的な問題により、正確なタイミングでサウンドが開始されず音のつなぎ目が生じてしまうことがあるそうです(UE公式ドキュメントの受け売り)。

そうして発生した、ちょっとしたループの切れ目だけでインタラクティブミュージックとしては成立しなくなってしまいます。

また、手元のUE環境で再生した曲の長さを取得して、曲が終わるタイミングで再トリガーしてループさせる構成にて実際に試したところ、

単純ループ実装
ヒットストップ演出などで利用されることもある、「Set Global Time Dilation」でゲーム内速度を変化させるとその分簡単にずれてしまうことがわかりました。

そのため今流れているサウンドがどのタイミングで流れているのか把握して、次のループへ正確なタイミングで再生して繋げる処理を用意する必要がありますが、とても大変そうに思えます。

それはどうやらプロも同じ認識のようでして、上記講演と同じく岩本氏の以下note記事を引用させていただきます(記事では じーくどらむす 氏名義)。 note.com

ゼルダの伝説ではこの上さらに「ランダム」や「状況によって変化」「コード進行を違和感なく保つ」など様々な工夫が上乗せされたものですが、技術的にはどれも、音楽を時間軸でいくつかの部品に分けて、それを「次の小節」とか「8小節後」とか「部品の終わり」とかにピッタリと合わせて繋げることで実現されています。 これを実現するためには、「現在の音楽が何小節目の何拍目にあるか」を検知するシステムと、「次の音楽にキレイに繋ぐタイミング」を計算するシステム、そしてその時間に「ピッタリと重ねて次の部品を予約再生する」システムが必要なので、1.のアレンジ変化に比べてハードルが高い技術です。

とあり、やはりハードルが高い技術であることがわかります。 しかし、なんと後半の記述

「現在の音楽が何小節目の何拍目にあるか」を検知するシステム

「次の音楽にキレイに繋ぐタイミング」を計算するシステム

そしてその時間に「ピッタリと重ねて次の部品を予約再生する」システム

はUEの機能「Quartz」で比較的手軽に実現できるのです!

Quartzって?

QuartzUE4.26から追加された、インタラクティブミュージックや武器の連射などを正確なタイミングで再生するために用意されたシステムです。(仕組みの部分は私も把握出来ていないこともあり、割愛させていただきます。詳しくは下記公式ドキュメント参考にしてください…!)

docs.unrealengine.com

何ができそう?

もちろんインタラクティブミュージックや正確な発射音に加え、フォートナイトの次シーズン開始待ちのダウンタイム画面の背景で流れていたLo-Fi音楽をプロシージャル生成しているという技術情報がEpic公式の記事にまとめられています。 www.unrealengine.com

www.youtube.com

またBeat(拍)やBar(小節)タイミングの通知が取れますので、話題のHi-Fi Rushや、音ゲー+FPSBPM: BULLETS PER MINUTEMetal: Hellsingerといったゲーム中流れるBGMのBeat(拍)にあわせて攻撃すると有利になったり、成功すると曲調も盛り上がっていく、というゲームシステムを構築することも夢じゃないと思います。

store.steampowered.com リズムゲーム歴が浅いためどれも難しいですが、つい虜になり遊んでしまうゲーム

Quartz実装前の準備

音源の入手

そして今回のような作例を実現するには前提として、数小節でループしている、もしくは楽器ごと分かれている音源を入手・購入するなり、作曲する必要があります。

というかこれが有料アセットでもあんまり見かけ無いです…。

ループを謳っているBGMアセットは多いですが、大抵はフルで最初と終端がループ用に処理されていることを指しており、数小節ループごと用意してくれているアセットはごく稀です。(どこのストアでもよいので知っていたら教えてください…!予算の許す限り買います!)

今回は記事用に商用利用も可とされているドラム・ループ音源を以下のサイトからお借りしました。 drums.kirakira-soundeffect.com

記事の後半に今回ぷちコンで利用した数小節ごとループ音源が用意されている、メタル系音楽アセットのリンクも参考に載せておきます。

数小節ごとループする音源が用意出来ない場合、一曲まるごと読み込んで指定した小節の区間でループさせたりといったことも不可能では無いのかもしれませんが、筆者のサウンドの知識が浅いため断念しました。

ざっくり触った所感として、それならばネットで検索すると「Audacity」といったサウンド編集ソフトで音の波形など確認しながら曲をループ化する方法が紹介されておりますので、そちらを習得して各ループに切り分けたほうが良さそうです。(音源は改変が許可されていること前提)

ループを無効化

さらにUE上にインポートして、利用するループ用サウンドファイルはWavなりCueなり、ループは切っておく必要があります。 これを忘れていると、ループの繋ぎごと輪唱のように曲がどんどん積み重なってしまいますので注意。

BPMの把握

またループさせる一連の曲のBPMを把握する必要もあります。 曲のBPMはたいていファイル名に記載されていることが多いですが、記載の無い場合 下記のサイトが参考になります。

ただ、たまに誤ったBPMで表示されることがあるので同一曲の別ループ音源でも確認すると良いです。

myedit.online

実際のQuartz組み込み例

前置きが長くなりましたが、実例を紹介します。

こんな感じでキーを押すと再生開始し、ループし続けるように再生(この動画ではループさせる前に次へ繋げています)、拍と小節をPrintStirngで出力もしています。

Fキーを押すと異なるループへ遷移、Zキーを押してBGMをフェードさせ終了、といった流れの簡易なインタラクティブミュージックを組むノードを紹介します。

基本的な組み方は以下のチュートリアル動画を参考にしております。

www.youtube.com

以下、blueprintUEのプレビューを置いておきます。

blueprintUEはコピペ可能なようですが当然ながらコピペする場合、イベントや変数を作成し直す必要があります。また、変数にセットされているサウンドファイルや拍子、BPMといった数値は曲に合わせて差し替える必要があります。

blueprintUEが繋がらない場合も考えられるので大きな画像のリンクも置いておきます。 SimpleQuartz.png - Google ドライブ

各部の解説

一段目ではQuartzの初期処理と再生を行っています。

Create New Clock

任意のキー(今回はVキー)を押すと実行され、再生するサウンドをセットし、Quartzシステムのクロックを作成して変数にセットしています。クロックは

オーディオ レンダリング スレッドでスケジュール設定とイベントの発生を実行するオブジェクト(公式ドキュメント)

とあり、Quartzを実行する際の基盤のようなものだと私は理解しています。

「Create New Clock」のターゲットはピンをドラッグして「QuartzSubsystemを取得」を選択すると指定出来ます。 「In Settings」に差さっているノードも「QuartzClockSettingsを作成」「QuartzTimeSignatureを作成」と打ち込んで追加します。

「QuartzTimeSignatureを作成」ノードのNumBeats(1小節内の拍数)とBeatType(音符の種類)を指定して曲のリズムを指定します。(よく楽譜の始めに書いてある拍子記号と同じようです)

今回は4/4拍子なのでNumBeats「4」、BeatType「/4」を指定しました。

曲ごとに異なるので、分からない場合は後述のPrint Stringで拍や小節のタイミングを出力して確認しながら合わせると良いかもしれません。

Set Beats Per Minute

「Set Beats Per Minute」ノードで曲のBPMを指定します。(今回は160でした)

Subscribe To Quantization Event

「Subscribe To Quantization Event」では、流れているサウンドのリズムに合わせて「On Quantization Event」へ繋がったカスタムイベントへ定期的に通知を出します。

これによって今流れているサウンドが何小節目で何拍目なのかを判別し、次ループを再生させる処理を出すか分岐させることが可能です。

Quartzの場合、曲が終わる前に次の曲再生の通知を出せば、現在再生している曲の切れ目に上手く繋げてくれます。(曲のリズムを適切に設定している場合)

「In QuantizationBoundary」は選択すると以下のような項目が表示されますが、どのリズムの区切りごとイベントに通知を出すか選択することが出来ます。

今回はBeat(拍)ごと通知を出す設定にしています。

Create Sound 2D

「Create Sound 2D」でAudio Componentを作成し、後に制御できるように変数としてセットします。

Play Quantized

「Play Quantized」でサウンド量子化境界で扱えるようにします。これは次のループが再生されるタイミングがどこになるのかを、「In Quantization Boundary」といった各種設定から指定出来ます。

「In Quantization Boundary」に「QuartzQuantizationBoundaryを作成」を繋ぎます。

これはサウンドが正確に開始されるタイミングを決定する設定項目が用意されています。

主にCouting Reference Point の項目とQuantizationの組み合わせで次のループの再生が開始されるタイミングを決定出来ます。公式ドキュメント「量子化境界でのサウンドのプレイ」の項目に記載されておりますので各選択項目が何を示すのか詳しく知りたい場合はそちらを参照してください。

今回「Quantization」は「Bar(小節)」に、「Counting Reference Point」は「Transport Relative(相対トランスポート)」としています。ここでいうトランスポートとは、「曲の経過時間的な概念」という理解を私はしています。(公式ドキュメントでは「Song Counter」とあり、実際Quartzの機能で拍数や小節数をカウントしているのがこのトランスポートです。)

つまり今回の「Play Quantized」で行った設定「Quantization」を「Bar(小節)」に、「Counting Reference Point」を「Transport Relative(相対トランスポート)」は、

「クロック開始時、曲の経過時間を次の再生位置決定の基準とし、次ループの再生位置(境界)は小節の開始タイミングです。」

と指定しているようです。(この辺の理解が怪しいので誤っていたら修正します…!)

In Delegateイベント

続けて「Play Quantized」ノードの「In Delegate」に「Play Quantized」が呼ばれた際、再生時などに応じて実行されるイベントを登録します。

画像ではカスタムイベントに「Event Type」、「Name」といったピンが付いていますが、これは「Play Quantized」ノードの「In Delegate」ピンをドラッグしてCreate Event、イベントディスパッチャーを選択して、一覧から「一致するイベントを作成」で予めピンが用意されたイベントを新規作成することが出来ます。

また、「WQuartzCommandDelegateSubTypeでスイッチ」のノードでカスタムイベントの「Event Type」から分岐させます。

先の工程でカスタムイベントを作成していれば、「Event Type」をドラッグして「Switch」と検索して追加できます。

今回は分岐の「Queued」から「Play Clock」または「Resume Clock」ノードに繋げ、冒頭に変数化していた「Clock」変数をターゲットに繋げるとBGMが再生されます。

BGMのリズムで定期通知されるイベント

続いて定期通知されるイベントの処理について解説します。

画像の「MyBarEvent」という名前のカスタムイベントも、冒頭の「Subscribe To Quantization Event」ノード上の「On QuantizationEvent」ピンをドラッグしてCreate Event、イベントディスパッチャーを選択して、一覧から「一致するイベントを作成」で予めピンが用意されたイベントを新規作成出来ます。

定期通知されている拍数や小節数は先程も触れた通り「トランスポート」でカウントされているので、Print Stringで出力すると曲の拍子記号やBPMが合っているか把握し易いです。

PrintStringと「テキストをフォーマット」ノード

画像ではデバッグ用にPrintStringへ「テキストをフォーマット」というノードで拍や拍子を統合して表示させています。 Format欄のテキストボックスに以下文字列を打ち込むと「Bar」「Beat」のピンが追加されます。

bar: {bar} Beat: {beat}

(Quartzチュートリアル動画で知った機能ですが、応用が効くのでとっても便利…!) カスタムイベントと、「テキストをフォーマット」のピン同士で繋げば、再生中わかりやすく出力されます。

Branch

続いて分岐部分です。

今回お借りしたドラム音源は4拍子で16小節有りました。

そのため、画像のように16小節目の4拍目でTrueへ分岐するようにしております。

注意してほしいのが、曲の小節数を把握しておかないと曲が終わっていないのに次ループへ進んでしまい、音が被ってしまう、あるいは空白が出来てしまうという不具合が起きるのできっちり設定してあげる必要があります。(実際プレイして出力されるリズムと聴き比べる)

True分岐後次のループ再生のため1行目の「CreateSound2D」の地点へイベントをトリガーしてあげます。

Reset Transport Quantized

「Reset Transport Quantized」でトランスポートをリセットしてまた拍数と小節数を0からカウントするようにします。

こちらの「In Quantization Boundary」に繋いだ「QuartzQuantizationBoundaryを作成」の設定は「Play Quantized」ノードに繋いでいるものと同一で問題ないようです。

この状態で再生すると、以下動画のように「Bar」「Beat」を出力しつつ、ループして再生されるはずです。

別の曲が流れるように簡易設定

ループは完成したので、次は別の曲を指定してみましょう。

といっても画像の通り、キーを押したらSound Componentの変数に曲をセットするだけの単純なものです。

これだけで再生中にキーを押して次の曲へ、また次の曲中に別キーを押して別の曲へ、16小節終わったあとに切り変わっているのがわかります。

また、動画上では行っていないですが、QuartzでBGMを処理していることにより、冒頭で上げた「Set Global Time Dilation」でゲーム内速度を変化させても問題なく曲のループを繋げてくれます。

音楽停止

最後に、音楽停止時の処理です。

Stop Clockでクロックを停止して定期通知のイベントなどQuartzの動作を停止させます。Quartzで再生を開始した場合、こちらを指定しないとサウンドが流れ続けてしまいます。

FadeOutは通常のサウンド用ノードです。指定した時間でサウンドをフェードアウトして停止します。

念のため記載しておきますと、「は有効である・は有効でない」のノードは変数を配置して右クリックで「検証済みゲットに変換」でその変数が有効かどうか判別して分岐出来ます。「Is Valid」ノードの短縮版のようなものです。

これで動画のようにQuartzで再生されたサウンドを停止することが出来ます。

余談

動画ではマネキンがリズムを取っていますが、定期通知される「MyBarEvent」にそういった動きのAnimationMontageを繋いで再生しているだけです。(ABP側での調整などはリンク先のドキュメントを参考にしてください。)

複数のループ曲を扱う場合

また、ぷちコンの作品では複数のループを切り替える必要があるため、データアセットに同一曲の一連のループ音源をすべて登録してBPMや拍子記号の情報も予め用意し、

曲ごと配列化したものを現在再生したいループ曲番号(ボスの撃破回数などで変化)で取得して曲ごとの拍子記号やBPMサウンドアセットを割り当てるように組んでいます。

うまくいかないとき

筆者が躓いた箇所を記載しておきます。

曲が重なって聞こえる

曲にループがかかっているかもしれないので、WavやCueファイルのループを確認しましょう。

次のループへの分岐条件となっている小節や拍の数がちゃんと合っているかも確認。

大本の再生開始が多重に実行されていないかも確認(念のため「DoOnce」を挟むと良い)

曲が大きめに聞こえる、「Stop Clock」や「Stop」指定しても止まらない

「Subscribe To Quantization Event」に繋いでいるイベントの、次の曲への分岐条件を適切に指定しないと拍の度に次の曲を流す処理が飛んでしまい、多重に再生されてしまいます。

また多重に流れているため「Stop Clock」や音声の「Stop」も複数にかかっておらず停止しないようです。

↓失敗例

曲のつなぎ目がハッキリわかってしまう

曲の小節や拍数、BPMがちゃんと合っているか確認してください。

また、曲のほうもコンテンツブラウザのサムネイルで見た波形の冒頭や終端に空白がないかなど確認し、あればサウンド編集ソフトで修正を試みましょう。

またPrintStringで出力されている数値とサウンドのリズムが合っているか、実際に試聴しながら確認してみてください。

クラッシュする

私も原因よくわかっていないのですが、「QuartzTimeSignatureを作成」のBeat Typeを試しにいろいろ設定してプレイするとループ分岐時にクラッシュしました。

ここは一旦保存してから触るなど慎重に扱ったほうが良いみたいです。

まとめ

これだけ強力なインタラクティブミュージックを実装可能な機能、てっきりUE5から追加されたとばかり思っていたのですが、4.26から追加されていましたね…!

変拍子の場合とか、岩本氏がスライドで触れていた「縦の遷移」をやる場合の実装なども実例を紹介したいとは思いますが、いかんせん音源が…!

以上ではありますが、読まれた方の参考になれば幸いです。それでは~!

参考アセット

数小節ごとのループ素材入りメタル系音源です!(セール時に購入しました)

おそらくこの販売者様の他音源も数小節ごとループ化されていると思われます。

marketplace-website-node-launcher-prod.ol.epicgames.com

marketplace-website-node-launcher-prod.ol.epicgames.com

marketplace-website-node-launcher-prod.ol.epicgames.com