らくとあいすの備忘録

twitter : lactoice251

VRC_SpatialAudioSourceの音響特性 (SteamAudio版・2025年12月)

最近VRC_SpatialAudioSourceがSteamAudio版にアップデートされたので、同じ方法で特性を測りなおしました。 前の記事はこちらです:VRC_SpatialAudioSourceの音響特性 - らくとあいすの備忘録 前バージョンでコンテンツを作っていて、対応したい...などの方の助けになれば幸いです。

なお、今後のVRChatのアップデートで変更がある可能性があるので、要注意です。(出来れば更新します)

検証方法

前後左右上下の6つのAudioSourceから、ホワイトノイズを発することによって行う。体の向きによってAudioSourceと受聴点の関係が変わらないように、常にプレイヤーの頭に全てのAudioSourceが追従するようにU#Scriptで制御した。また、Spatializationが非有効かつSpread=0のとき、左90度の音源に対して右側での音量は0となるが、比較の分かりやすさのため、Spread=70まで上げた。これは旧バージョンの検証の差異に音源と反対側のチャネルにおいて、Spatializationが有効の時と同程度のゲインが得られるようにしたためだが、今回は新旧比較がポイントなので同じくSpread=70を採用した。

新 - 旧の比較差分

周波数についての比較

Steam Audio版

[SteamAudio版・2025年12月測定] 音源角度を変化させたときの、ホワイトノイズに対する周波数応答。(上) Spatialization非有効。(下) Spatialization有効。

旧バージョン

音源角度を変化させたときの、ホワイトノイズに対する周波数応答。(上) Spatialization非有効。(下) Spatialization有効。

比較 (差分)

SteamAudioバージョンから旧バージョンの周波数特性 [dB] を引いた値。

コメント

  • 600Hzまではほとんど前バージョンと差がないことが分かる
  • 800Hz ~ 5kHzがおさえられる (前が盛り上がっていた)
  • frontの7kHz ~11kHzが大幅に大きくなった (前は鋭いノッチがあった)
  • downの5kHzに鋭いノッチが発生 (下方の音が曇るようになった。)

距離減衰についての比較

Steam Audio版

[SteamAudio版・2025年12月測定]音源距離を変化させたときの、ホワイトノイズに対する周波数応答。

旧バージョン

音源距離を変化させたときの、ホワイトノイズに対する周波数応答。

距離による影響だけの抽出

  • やや変則的なプロットだが、次を作成した (そもそもの周波数特性の変化を除外して距離による影響だけを見たいという意図)
    • {各距離でのfront(新) - 各距離でのfront(旧)} - {1.0mでのfront(新) - 1.0mでのfront(旧)}

周波数特性の距離による影響

コメント

  • 新バージョンはかなり距離減衰がゆるくなった (カーブによる制御無しの場合)
  • 上記誤りで、下記設定のカーブによる制御が入っていました
    • Use Volume Source Volume Curve: 有効
    • Linear Rolloff, Min Distance: 0.5m, Max Distance: 40m
  • 0.1mのときに8kHzぐらいがおおきく削れるのが特徴的 (近接効果が強く表れている?)
  • 800Hz ~ 1200Hzぐらいに推移するピークがある

位相特性の比較

左右の耳の位相差を調べる。図は右チャネルの位相から左チャネルの位相を引いた位相差の時間平均を示している。これを見ると、Spatializationが非有効の場合は左右の耳の間でまったく位相差がない一方で、Spatializationが有効の場合位相差が現れているのがわかる。新旧差分は遅延秒数に換算しないとちょっとわからないが、頭部の大きさの変化による影響か多少遅延特性が変化していそう。(要追加調査)

Steam Audio版

[SteamAudio版・2025年12月測定] 音源角度を変化させたときの、左右チャネルの位相差。(上) Spatialization非有効。(下) Spatialization有効。

旧バージョン

音源角度を変化させたときの、左右チャネルの位相差。(上) Spatialization非有効。(下) Spatialization有効。

その他所感 (個人の感想)

  • 2kHzぐらいまでが周波数特性がかなりフラットになったので、DAW上で聴いている音とは近い傾向になることが多そう
  • 相対的にfront, left(耳のある側)で低域がかなりブーストされたので既存コンテンツの聴感印象としては低域のブーストを感じる
  • 下方定位がかなり特徴的になったので下の音のつかいどころは色々ありそう

アンビソニックスを利用して広がり感のあるオブジェクト音を作る in VRChat

要点

はじめに

前に書いた記事『VRの世界に音を存在させる』からはや2年近くが立ち、VRの世界の音や、音に関わる表現はますます豊かになっています。*1

このような発展の中において、オブジェクトに紐づく音については、まだまだ踏み込む余地があるのではないかと考えていました。 特に、単にオブジェクトに音がくっついているというだけではなくて、その音が世界に存在しているようにするにはどうすれば良いのかという部分についてです。

シンプルな例

複雑な状況を考える前に、まずは (Unity/VRChatの) オブジェクトに音を紐づけるシンプルな例から考えてみます。

  1. 追従先のオブジェクトを用意する
  2. オブジェクトの子にAudioSourceを配置する

これでオブジェクトに音が追従します。ですが、ここで気にしておかなければならないのはAudioSourceの設定であり、適切に立体音響再生される設定を行う必要があります。ここで立体音響再生とは、すなわち音源と自身の頭部の相対的な位置関係から決まる頭部インパルス応答 (Head-Related Impulse Response; HRIR) の効果を取り込んだ再生です。これについては前の記事と内容が重複するため詳細は割愛しますが、要点としては次の3点です。

  1. 音が左 (右) にあると左 (右) から音が大きく聴こえる : Inter Level Difference (ILD)
  2. 音が左 (右) にあると左 (右) からの音が先に聴こえる : Inter Time Difference (ITD)
  3. 音の位置に応じて頭や耳で遮蔽される周波数帯域が変化する : Spectral Cue

こうした効果を取り入れるのはなかなか難しい話ではありますが、例えばVRChatでは「VRC Spatial Audio Source」というこの効果を簡単に取り入れられるコンポーネントがあるのでありがたく使います*2

シンプルな例における課題

シンプルな例における課題は、例えば残響感のある音を得ようと思ったときに現れます。 音源にリバーブを適用してそれを単一のAudio Sourceに割り当てた場合、音はぼやけますが広がりのある音にはなりません。

  • 正面位置から再生されるドライな音源
  • 正面位置から再生される、リバーブ適用後にダウンミックスされた音源

残響音は、(少なくとも現実の空間の場合) 音源から発生した音が部屋や環境に跳ね返り、音源とは異なる様々な方向から耳に入ってくることで生じます。 従って、残響音らしい音の広がりを得るためには、反射音の方向から到来する無数の音を用意する必要があり、単一の音源だけでは再現が難しいという課題にあたります。

この課題に対して、実際に壁の反射を考慮して動的に残響を変化させる仕組みは取り入れられつつあります。

しかし、音楽等の高品質な音響が求められる用途に耐えうる残響を、リアルタイムに変化するプレイヤー位置と音源位置を考慮しながら生成することは依然として困難です。

加えて、VRChat上で音響伝搬シミュレーションを動作させるには追加の多くの課題をクリアしなければなりません。鏡像法のような単純なアルゴリズムを適用するとして、ナイーブにAudioSourceを鏡像においていく方法ではすぐに音源数の上限に達してしまいます。したがって、再生する音自体を合成して単一の音源を用いてステレオ再生することの方が比較的現実的です。プレイヤーの位置と音源位置、壁の吸音特性を考慮して、鏡像法等を用いて仮想音源の位置と適用するフィルタを決め、各方向ごとに用意した何らかのHRTFを畳み込んだ音を加算した音を作り、それをAudioSourceに流し込むなどの方法が取れると思われます。VRChat上での音響合成の例があることを踏まえると、GPUに上手く送ってやれば出来ないこともない...気がします...。

別の方法としては、HRIR適用済みの全てのオブジェクト音が混ざった状態の出力に対して最後段で残響を付加するという方法が考えられます。 これは、UnityではAudio Reverb Filterを用いて実現できます。ただし、この残響は音源方向や距離とは無関係に適用されるものであり、残響レベルを上げるほどに音の立体感が失われていくという状況に直面します。 別の問題として (これはVRChatにおける問題ですが) 利用出来るReverb (Reverb Filter) の質として、音楽用途に充分ではないという点も併せて課題となります。

Reverbの質については、VRChatで解決不可能な問題かというと考える余地はあります。IRのデータを何らかの形式で用意して、それをリアルタイムに畳み込む処理を適用することで、Audio Reverb Filterには無い、自由な残響効果を付与できると思います。ですが、そうしたものをリアルタイムにかつ複数音源に対して実現したものはまだ見たことがないので、やってみたらやっぱり困難があった...ということもあるかもしれません。

チャンネルベース (サラウンド) の利用

比較的少数の音源を用いて、広がりのある音場を作る方法として、チャンネルベースの音の利用があります。 ステレオ再生はこの最も基本的なものです。ステレオ再生では、2つの音源を用いてその音圧差や位相差をコントロールすることで、あたかも音が広がっているようにきこえたり、音源が存在しない中央から音が鳴っているように感じたりすることが出来ます。 ただし、それでも二つのスピーカーより外にある音 (たとえば後ろの壁からの反響のような音) をステレオ再生のみによって再現することは困難です。

サラウンドは、ステレオを超えてより広がりのある音を得ることをひとつの動機として導入されるものです。例えば、5.1chサラウンドでは、受聴者の正面と前方左右、後方左右の5つの音源と、低音専用の1つの音源の計6チャンネルの音源で構成されます。より音源を増やした構成では、例えば受聴者を取り囲む7つの音源、低音専用の1つの音源、上部の4つの音源を組み合わせた7.1.4chサラウンドなどがあります。

サラウンドを利用した広がりのある残響音

サラウンドを活用することで、ある方向で鳴っている音の直接音と、プレイヤーを取り囲むように到来する残響音の両方を再現することが可能になります。

  • 正面位置から再生される音に対して、サラウンドを利用した広がりのある残響音を付加*3

ただし、オブジェクト音を鳴らす手段としてサラウンドを直接利用出来ない点には注意が必要です。 これは、例えばプレイヤーの近傍を通り過ぎる飛行物から音を鳴らす状況を考えた場合、その左右が入れ替わるタイミングはプレイヤーの前を通り過ぎた瞬間であるべきですが、サラウンドでは一律してあるタイミングで左右にパンを送ることしか出来ないためこういったことは実現出来ません。 より一般的に言えば、サラウンドの音源が配置された表面上の音の再現は可能ですが、その表面を超えてプレイヤーに近づけたり、プレイヤー自身が音源の囲む空間から移動したりする状況は扱うことが出来ません。(VRの文脈でよりくだけて言えば、3DoFと6DoFの違いにおおむね対応します。)

もう一つ注意すべき点は、サラウンド音響では (多くの方式において) チャンネル数を節約するために空間的に対象ではない音源配置を採用しています。 従って、十分な音像定位の解像度が得られるのは、より密に音源が配置された方向 (すなわち前方正面) であるため、(表面の音に限ったとしても) オブジェクトを明確に定位させられる領域は限定されます。

シーンベース (アンビソニックス) の利用

シーンベースの再生方式とは、プレイヤーを取り巻く空間全体、特にプレイヤーを覆う球面上の物理的な音場を記録・再生する方式です*4 *5。 アンビソニックスは、球面調和関数展開と呼ばれる方法を用いて、球面上の音場を特定の空間的なパターンの重ね合わせとして展開します*6。これはちょうど、フーリエ変換と似た操作であり、(必ずしも正確な表現ではありませんが) 空間的な音場のパターンを空間的な周波数の低いなだらかな変動成分と、空間的な周波数の高い細かな変動成分とに分けるような操作です。展開の0番目の次数は無指向の成分であり、1番目の次数はX, Y, Z方向に対応する指向性の成分ですが、この0, 1次の成分のみを用いて球面上の音場を表現する方式をFirst Order Ambisonics (FOA) と呼びます。FOAは空間的な解像度こそ高くはありませんが、サラウンドとは異なり、全天球に対して対称的であり、全ての方向に音を配置することが可能です。したがって、解像度はぼやけていても良いが、空間全体への広がりが必要な残響音への利用は有望と考えられます。

アンビソニックスを併用した広がり感のあるオブジェクト音

空間上のある一点から鋭い定位の音を再生できるオブジェクトベースの音に対して、広がりのある残響音を付加する方法として、アンビソニックスを組み合わせることは良い効果が期待出来ます。これは、アンビソニックス (特に1次アンビソニックス) は、空間解像度は必ずしも高くないが、少ないチャンネル数で全天球のあらゆる方向からの音を再生出来るという点に優れているからです。実際、オブジェクトベースとアンビソニックスの組み合わせ*7 *8や、アンビソニックス形式のリバーブ*9*10も活用されつつあります。 一方、VR音楽ライブ等においてはまだ利用例が乏しく、今後活用が期待される領域だと思います。 以下では、VRChatでのタイムラインに基づくライブでの実装を例にオブジェクト音とアンビソニックスに基づく残響音の併用方法について解説します。 なお、関連するUnity/Reaper用のスクリプト等はこちらで公開しています。

基本方針

オブジェクト音とアンビソニックスに基づく残響音を併用する基本方針は次の通りです:

  • 音を発するオブジェクトの位置をUnity上で記録する
  • 記録された音源位置をDAWに取り込む
  • 対応する位置の音源を想定した残響音をアンビソニックス形式で書き出す
  • Unity (VRChat) 上でアンビソニックスとオブジェクト音 (w/ HRIR) を同時に再生する

この方式では、全てのオブジェクト音に対する残響音成分がアンビソニックス上に重ね合わされてレンダリングされることになり、オブジェクト数が増えても単一のアンビソニックスによってこれらの情報を統合することが出来ます。

Unity上での音源位置の記録

Unity上での音源位置の記録は各オブジェクトの座標等の情報を一定間隔でCSVに書き込むことによって行います。 ここでは以下のような2つのオブジェクトのアニメーションを作成し、座標を記録しました。

注意すべきポイントとして、アンビソニックスではある中心位置を仮定したときの全天球上で音をレンダリングするため、座標の記録は絶対座標ではなく、あるリスニングポイントを仮定したときの相対的な極座標 (方向+距離) で記録されることが望ましいです。実際に記録されたCSVファイルは以下のような形式です。

time,x,y,z,azimuth,elevation,distance
0.0,-3.02,2.40,0.14,0.74,0.55,0.03
0.1,-3.02,2.40,0.19,0.74,0.55,0.03
0.2,-3.00,2.40,0.43,0.73,0.55,0.03
...

元音源の分解・残響音の付加・振り分け

オブジェクト音に対する残響音を作りだす最もストレートな方法は、対象とする空間を仮定し、音源位置と受聴地点の情報をもとにその空間における残響音を計算することです。このような目的のためには、例えばIEM Room Encoderのようなプラグインを用いることが出来ます。

IEM Room Encoder

ただし、ここで元音源 (source) として適用するべき音は無響室で収録されたモノラル音源である点に注意が必要です。 多くのSFXや音楽的な音は元々響きの成分を含んだステレオの素材として作られており、このような音源素材を使う場合には上記のような方法は必ずしも適していません。

そこで今回は、響きを含んだステレオの元音源にも対応出来る方法として次の図のような処理手順を採用しています。

元音源からモノラルのオブジェクト音とアンビソニックスの残響音を作りだす
この方法では、まず元音源を任意のチャンネルのサラウンド (今回は7.1.4chを採用) に拡張します。 拡張の方法としては、追加のリバーブを付加せずに疑似的にチャンネル数を拡張出来るNugen Halo Upmixや、サラウンドリバーブの付加によってチャンネル数を拡張出来るCinematic Room等のサラウンドリバーブの利用が考えられます。 ここで得られたCenterのチャンネルをモノラルのオブジェクト音、それ以外の音をアンビソニックスに割り当てる残響音成分として分配します。 サラウンド表現からアンビソニックス表現への変換には、IEMのMulti Encoderを利用しました。
IEM Multi Encoder
この方式における利点は、ステレオの音源を使ってオブジェクト音とそのアンビソニックスでの残響音を作りだせることであり、ステレオの領域で好みのリバーブプラグインを利用することも可能になります。一方で、今回の方法はRoom Encoder等の部屋の形状に基づくシミュレーションとは異なり、特定の部屋の構造を想定していません。したがって、あくまでも適用したリバーブによって暗黙的に何らかの部屋形状や空間の大きさが規定されることになるという点には注意が必要です。さらに、アンビソニックスに割り当てられる音は必ずしも残響音と呼べるもののみになるわけではないため、音像をタイトに絞りたい場合にも適さない可能性があります。逆に、オブジェクトの音を点ではなくもう少し広がりのある大きな発音体として見せたい場合にはむしろ効果的な状況が生まれるはずです。

ReaScriptを用いたCSVを中間ファイルとするUnityとDAWの連携

音源位置に応じた残響音の再現は、前節で述べたような方法を考える場合DAW上で各種プラグインを利用して行えることが望ましいです *11。 この時、各プラグインのパラメータは音源位置に応じてオートメーションされる必要があります。CSVで記録された音源位置 (方向+距離) をDAW上のオートメーションに取り込む方法として、例えば一度MIDI CC信号に変換するなどの方法を取ることが出来ると思いますが、ここではスクリプト経由で直接CSVからオートメーションに情報を反映することにします。

スクリプト経由でのCSVの読み込みとオートメーションの作成のため、Reaper というDAWを利用します。 Reaperでは、ReaScriptと呼ばれるスクリプト機能 (lua) を用いて、各種操作を自動化出来ます。スクリプトの詳細については、こちらで公開したので割愛しますが、ポイントとなる部分をいくつか抜粋します。

CSVファイルをラインごとに読み取って、配列に格納する:

local csv_points = {}
for line in csv_file:lines() do
    local time, x, y, z, azimuth, elevation, distance = line:match("([^,]+),([^,]+),([^,]+),([^,]+),([^,]+),([^,]+),([^,]+)")
    time = tonumber(time)
    azimuth = tonumber(azimuth)
    elevation = tonumber(elevation)
    distance = tonumber(distance)
    if time and azimuth and elevation and distance then
        table.insert(csv_points, {time = time, azimuth = azimuth, elevation = elevation, distance = distance})
    end
end
csv_file:close()

配列に格納されたCSV情報 (csv_points) をある指定のトラックのエンベロープ (オートメーション) にセットする:

function EnvelopeUtils.insert_points_from_csv(envelope, csv_points, envFunc)
    for _, point in ipairs(csv_points) do
        local value = EnvelopeUtils.csv_to_env(envFunc, point.azimuth, point.elevation, point.distance)
        if value then
            reaper.InsertEnvelopePoint(envelope, point.time, value, 0, 0, false, true)
        end
    end
    reaper.Envelope_SortPoints(envelope)
end

ここで、EnvelopeUtils.csv_to_envは角度や距離の値からエンベロープの値 (0-1) に変換するための関数であり、音源種類や適用先プラグインに応じて決定されるenvFuncを引数として決定されます。envFuncの指定は具体的には次のような全体設定用csvファイルを別に用意することによって行いました。

objectName,trackType,envName,envFunc,loadScore,useScore
ClapObject,ReverbAmbi,Master azimuth angle / MultiEncoder,azimuth,0,0
ClapObject,ReverbAmbi,Master elevation angle / MultiEncoder,elevation,0,0
ClapObject,Source,Mix / ValhallaVintageVerb,dist2verb,0,0
...

このファイルでは、適用するオブジェクトの名前、そのオブジェクト用のトラックの中で、どのトラック (直接音用 / 残響音用) のどのFXパラメータにエンベロープを設定するか、そしてどのような関数で角度・距離からエンベロープ (0-1) に変換するかという情報が与えられています。例えば、角度 (azimuth) は上述のIEM MultioEncoderのMaster azimuth angleにそのまま0-1に正規化された値として入力されます。音源自体に適用されるリバーブは、dist2verbの関数に従っておおよそ距離に反比例する振幅でdry/wet比が決められます。

これらの処理を行うことで、次のように各FXのパラメータにエンベロープ (オートメーション) が適用されます。この動画では、直接音も含めてアンビソニックス上に配置したものを、binaural decodingしたステレオを再生しています。

細かい話

1. 動画中で表示しているエンベロープは主に方位角 (azimuth) ですが、エンベロープ中に不連続な点があるのがわかると思います。これは、角度が一回転する地点に対応します。このとき、その区切れ目 (360° -> 0°) で高速な逆回転が起こらないように、360°の点と0°の点を同一時刻に配置する必要があります。このため、エンベロープの値の差分がある閾値を超えた場合に、移動先の値に近い方に回り込んだ値の点を同一時刻に一つ追加するという後処理を追加しています。

2. ReaScriptはほとんどのDAW操作を自動化出来ます。したがって、今回の例においてもルーティング済みのトラックの複製・リネーム・エンベロープ適用などのオブジェクト毎の操作や、クリップ自体の配置などを自動化している他、書き出し/リスニング時のミュート設定なども全トラックに対して一括で変更出来るようにしています。

Unity (VRChat) への取り込み

作成されたモノラルのオブジェクト音と、アンビソニックスの残響音をそれぞれUnityに取り込みます。 アンビソニックスの音のVRChat向けの取り込み方法・再生方法については前回の記事の「2.3 シーンベースの立体音響」に記載したため、ここでは割愛します。記事に記載した適切な方法で、AudioClipの取り込み、AudioSourceの設定を行うことで、VRChatではアンビソニックスの音を頭部の回転に応じたBinaural (Stereo) として再生可能です。一方、オブジェクト音については、上述の「シンプルな例」と同様に単にオブジェクトに追従するAudioSourceから再生されるように設定します。

下記の動画は実際に上記の設定を行ったものをVRChat上で再生したものです。

ここでは、次の3つの設定が比較されています:

  1. 上記の方法で抽出されたモノラルのオブジェクト音とアンビソニックス形式の残響音の組み合わせ
  2. モノラルのオブジェクト音のみ
  3. バーブを適用した後モノラルにダウンミックスした音源をオブジェクト音に使用

これらの比較から、1の方法がオブジェクト音の定位感を損なわずに空間的な広がりのある音を表現出来ていることが分かります。

おわりに

本記事では、オブジェクト音を活用したVRでの音楽ライブ等の用途を主な目的として、空間的な広がり感のある残響音の付加を行う方法について解説した。 空間的な広がり感のある残響音を得るため、全天球にわたる音場を表現することの出来るアンビソニックス形式を利用した。 残響音を作りだす方法としては、Unity上で記録された音源位置を元に作り出された残響音成分を、一度サラウンド形式を介してアンビソニックス形式に変換する方式を採用した。

今回の方法では、部屋形状や材質等の考慮などの厳密なシミュレーションの代わりに音楽的に欲しいリバーブプラグインを使って作られた音を空間的に配置するという方向でのアンビソニックスの活用を行いましたが、もちろん前者のような方法が役立つ状況も多くあると思います。 また、リアルタイムに (あるいはインタラクティブに) 発生するオブジェクト音に対する残響音の付加についてもこの範疇では解決しきっていない問題です。 これらの点もふくめ、この記事で書かれている内容は単なる一例に過ぎませんが、様々なシチュエーションでオブジェクト音を活用していくための一助となれば幸いです。

ところで、現在公開中のこちらのミニパレードでは、アンビソニックスを併用した広がり感のあるオブジェクト音が使われていたりします。この記事を読んで興味を持たれた方は、9/22 (日) 23:00までにぜひ!最後に宣伝でした!

2022年の活動振り返り

こんにちは、らくとあいすです。いつもは技術記事ばかりでこういった振り返りはしていないのですが、今年はかなりめまぐるしく過ぎていってしまった感じがあったので振り返りつつ思い出してみようという感じで書き始めました。VRChatでライブをしたり、色々作ったりしている人の1例として記録に残す意味もあるかもしれません。

Typeman制作参加

今年、個人的に一番大きな出来事を挙げるのであれば、伊東ケイスケ監督作品『Typeman』への制作参加が挙げられると思います。

www.youtube.com

本作品は、VRChatを介して演者とその場で関わりあいながら物語を体験する、インタラクティブVRアニメーション、またはVR演劇です。VRサウンドエンジニアというクレジットで載せてもらっていますが、作曲の森下唯さん、レコーディング&ミキシングエンジニアの蓮尾美沙希さん (WOWOW) と共に、Typemanの音回りの制作を行っていました。もう少し具体的には、Unity上で森下さんの曲や、蓮尾さんが作成した効果音や環境音を実際に利用する部分での調整やシステム回りを担当しました。例えば、"キー"へのインタラクション、楽器的演出のためのシステム・一部アニメーション、立体音響の調整、環境音のシステム等々です。

参加の経緯としては、VR蕎麦屋タナベさんの紹介で、伊東監督にAmebient を体験頂いて、それをきっかけにお話を頂き参加することになりました。ありがたい縁でした!とはいえ、こういった大型の作品制作、まして映画祭への作品出展などはまったくの未知の領域だったので、お話をもらった段階では別の方を紹介する方が良いのではないか...と考えていたのですが。Canon for cubes など他の作品もいくつか見て頂いて、それでもということでしたので思い切って頑張ってみようということに決めました。

いざ始まってみると、制作は基本ずっと楽しく進みました。一番楽しかったのは、WOWOWのスタジオで伊東監督、森下さん、蓮尾さんはじめ制作陣が集まって音回りの作業を一気に進めた日です。(その場に一緒に居るのですが) VRChat内で話しつつ、音量感や立体音響、残響感、曲の展開のペースなど色々と調整をし、時にはスタジオで再ミックスをして頂いたり、時には伊東監督の新しいアイデアをその場で実装したり、朝から晩までてんやわんやでした。色んな人のスキルとアイデアが一つになっていく経験はとても良いものでした。

結果として、Typemanは、ヴェネチア国際映画祭へのノミネートとPremio bisato d'oro 2022(プレミオ・ビサト・ ドーロ/金鰻賞)での最優秀短編賞の受賞、多くの国際映画祭へのノミネートなどたくさんの良い評価を頂くことになりました。素晴らしい作品に関わらせて頂いて光栄でした! prtimes.jp

Beyond the Frame Festival

Typemanに関連してトークイベントに出演させて頂きました。

We met in virtual reality

Typemanとは直接関係ないのですが、映画祭繋がり。"We met in virtual reality"というJoe Hunting監督のVRChatドキュメンタリー映画に一部ピアノ演奏で出演しました。 海外コミュニティの話がメインではありますが、VRChatあるあるなどもたくさん詰まった、面白く、心温まるドキュメンタリーです。Typemanと同じく、ヴェネチア国際映画祭で上演されるなどこちらも各地の映画祭での上演が続いています。なんとU-Nextでも日本語字幕版が見られるようになりました!

EMN Records

アムニェカさん の作った、バーチャルで音楽活動する人たちの助け合い・情報交換サークルEMN Recordsに参加しました! 私の関わった今年の主な活動は、Vオリジャズコンピのアルバム制作参加と、ジャズ理論輪講の開催です。

Vオリジャズコンピ

Vオリジャズコンピとは、VTuberさんのオリジナル楽曲 (Vオリ) をジャズアレンジして、VTuberやバーチャルで活動する人を中心にしたバンドでインストカバーしたアルバムです。

www.youtube.com

私は、次の4曲にピアノで参加しています。

  • Loro/「靴と鍵と私」
  • イトイ/「Phantom sense」
  • 月宮ヤギリ/「燈色」
  • 星こにあ/「WORLD PAINTER

アルバム制作にしっかり関わるのもほぼはじめてだったので、色々と手探りでしたが、各原曲もアレンジもとても良いものだったので大変ながらも楽しく参加させてもらいました! 特に、イトイさんの「Phantom sense」は、複雑な変拍子曲でしかもピアノソロ原曲ということもあって、かなりじっくり時間をかけて取り組むことになりましたが、原曲とはまた一味違った良い作品になったと思います。ぜひぜひ、聴いてみてくださいね!

open.spotify.com

ジャズ理論輪講

ジャズ理論輪講会は、Mark Levine著の"Jazz Theory"というジャズ理論の教科書をじっくり一緒に読んでいくような会です。 もともと本は買っていたのですが、たまに参照する程度でじっくり読み通すことが出来ておらず...

という感じで呟いたところ、のむさん・アムニェカさんがすぐに賛同してくれて、EMN Recordsの企画としてやってみるという話になりました。 この輪講会は現在第21回まで8名の参加者で毎週月曜日に開催しています。内容としては、各週担当者が担当範囲を読み込んで説明・自主研究の発表などを行って、そのあとその内容についてみんなで質疑・議論するというような形です。また、不定期で輪講後に、その週の内容を触れたり触れなかったりするセッションをやったりもしています。一人で読んでいるだけでは、なかなか気づきづらい部分や、ハーモニーに対する各々の認識・聴こえ方の違い、異なるバックグラウンドの知識の交流など、毎週たくさんの刺激をもらえる回でとても楽しく続けられています。年明けからは、最後の第三部がはじまるので引き続き楽しみです!

ワールド制作

今年は、個人のワールド制作としてはあまり多くのことが出来ずに少し心残りでした。来年は、すこしこっちに重心を戻したい感じもありますね。 今年唯一制作を進めていたのが、suzukiさんの制作・公開されているワールド・システム Graphiliaを改変した、”Sound Graphilia”の制作です。元々、GraphiliaはノードベースでShaderを書く仕組みをVRChat上に構築したもので、私はこれを拡張してノードベースで音を作れる試みをしていました。今年中にPublishしたかったのですが...!来年のはやいうちには、と言う感じです。

VRプラットフォーム上でのライブ

2022年は本当に本当にたくさんのライブをした年でした。振り返ってみると、合計49ライブ...。 誘いにのって頂いた、誘ってくれたミュージシャンの方々本当にありがとうございました!

VR楽器ライブ

2021/12/28 VRミュージックフェス Vol.1  

ギリギリ2021年ですが、せっかくなので。おきゅたんbotさんの主催されているくらげビートと、音楽プロデューサー保本真吾さんプロジェクト「ジョイミューラジオ!」のコラボ企画である、「VRミュージックフェス」にVR楽器演奏で出演しました。

パフォーマンスとしては、これまでに作ったPlay with cubesと、Canon for cubes、さらにAmebientの音色 を組み合わせて、そこに観客に追従するような新しい演出を加えたものを、手元のコントローラー群で操作出来るようにしたものでした。見て頂いた方がわかりやすいと思うのでアーカイブでぜひ!

youtu.be

おきゅたんbotさんは、2018年にはじめてVRライブをした時から配信等で常に関わってくれていて、その縁もあって今回のライブにお声がけ頂きました。いつもVR楽器演奏という、なかなか伝えるのが難しいコンテンツをYouTubeでわかりやすいように伝えて頂いて大変感謝しています!

youtu.be

9/25 仮想水槽4

仮想水槽は、yukihataさんが主催されている、DJやマシンライブ、楽器演奏など多種多様なパフォーマンスと、VJの組み合わせが特徴的な音楽イベントです。

今回は、Live Codingを中心としたパフォーマンスということでお声がけ頂いていましたが、せっかくなので (そして仮想水槽の懐の広さを信じて) SonicPi Live CodingとVR楽器とピアノ演奏の組み合わせでのライブを試みました。

youtu.be

リアル側の演奏を映像に乗せた同期信号と共に送って、VR楽器の演奏周期と同期させるというアイデアキヌさんと議論していて、その実装を試みてみた回でもありました。実際には動画配信された時点での音と映像のズレなどはどうしようもなく、完全な同期はなかなか困難であるという状況ではありましたが、面白くなりそうな一端は届けられたのではないかなと思っています。また、今回kaiwareさんにVJを担当して頂いています。こんなに入り組んだ状況の中、かつ直前まで構成が確定しないような状況の中、完璧に対応して映像を合わせて頂いて本当に感謝しています...!

VR楽器自体は、 2021/12/28 VRミュージックフェス Vol.1の時に構築したものに、お客さん同士を繋いで和音を構成する線のような楽器と、そのコントローラーを加えたものを使いました。

ピアノ、Live Coding演奏ライブ (OpenMICを除く)

VRライブに考えるにあたって、実際に身をもってVRで演奏ライブをやりきってみるのが良いかなーと思っていたのもあり、今年は、ピアノでの演奏ライブを増やしてみようと思っていました。それから、SLTなどでの出演を増やしていたのですが、そこから雪だるま式にライブが増えていって大変なことになりました!ここは本当に数が多かったので一言、二言ずつにします!本当はそれぞれ話したいようなこともたくさんあるのですが...

1/22 君のピアノは。2022 at SLT

2020年に第一回の行われたピアノオンリーのライブイベントの第二回に参加しました。 ソロピアノをしっかりと弾くライブイベントはなかなかなく大変さはあるのですが、出演者ごとに違うピアノの音色を楽しめる貴重なイベントでした! また、このイベントで弾いた"Maria Cervantes"をきっかけに、4/3、9/4のEl Solに繋がっていきます...!

2/14 Jazz Live at SLT

ボーカルのあずさん、ベースののむさん、ドラムのYukihataさんとのジャズライブです。 ボーカルフロントのバンドは今まであまりやったことが無かったので、色々と手探りでしたがあずさんの力強い声に引っ張られて良いライブになりました。 そしてあずさんのバンドでは、2022年沢山ライブをすることにすることになります、たくさん誘って頂いてありがとうございました! youtu.be

2/19 Sonic Pi Live Coding at GHOSTCLUB

GHOST CLUBでSonic PiでのLive Codingを久々に行いました。VJには三日坊主さんが入ってくれて、意外とはじめての組み合わせでした。 GHOST CLUBは結構せめた音楽を演奏しても、集中して聴いたり踊ったりしてくれる環境があって本当にありがたいですね。

2/25 Solo Piano at Club Sara's buddy

VRChatのトラブルによって4/15に延期となったPiano Therapyの代わりに、急遽ソロピアノを演奏することになりました。

2/27 VR dvp 2nd session at 玉響

トラックメイカーさんがDJ的に音楽を流し、そこに即興で演奏を合わせていくという面白いスタイルのイベントでした。 私は、メリッサさんのオリジナル曲に合わせてピアノを弾いてきました。次の展開を考えながら、ギリギリ音を合わせていく感じのスリリングさが楽しかったです!

3/5 JAZZ IN VRC Part4 (ゲスト キヌさん) at SLT

2021年にサックス・リーダーの初吹音さきさん、ドラムのYukihataさんとのベースレストリオとして結成されたジャズバンドの四回目のライブでした。今回は、ゲストにキヌさんを加えて、ジャズ×ポエトリーリーディングという面白い編成に。一見突飛なようですが、良いコラボレーションになった思っています!特にGHOST ver 匣はぜひぜひ聴いてみてください。 youtu.be

3/13 VRC×REALITY合同セッション in cluster

REALITYを中心に活動されているバーチャルSAX吹きのまよさんと、初吹音さきが共同主催で開催した、セッションイベントです。VRChat、REALITYを中心に活動するミュージシャンがClusterに集まってセッションするというプラットフォーム横断的なイベントで、普段お会いしない方の演奏を聴いたりセッションしたり出来る良い機会でした!

3/14 第5回 題名のないVRC音楽会

ジェミーさん、紫陽なつさんが主催されている、VRChat上での音楽会に、ボーカルさんのあずさんと、ベースのぴゅら子さんと共に参加してきました。ドラムレスの編成でのバラード曲などは大変難しかったですが、お互いの音を感じることと、自分のタイム (≒主張) をしっかりキープしていくことを共存させるような、セッションの醍醐味を存分に楽しめるライブでした。

3/20 Sonic Pi Live Coding at Algorave 10th Birthday Party, Twitch

Algorave 10周年イベントとして開催されたAlgorave 10th Birthday Partyに、SonicPi Live Cordingと楽器演奏の組み合わせで参加してきました。 一人10分ずつ世界中のLive Corderで24時間を繋ぐというすごいイベントでした! youtu.be

4/3 EL Sol at AWAKE

1/22に「君のピアノは」でのソロピアノでのラテン曲を聴いたtktkさんと意気投合し、2/8にSLTでデュオ演奏していたところ、EL SOL主催のcremaさん に声をかけて頂くという縁が繋がって実現したライブです。EL SOLでは、tktkさんに加えて、フルートのLazさん、ベースののむさん、ドラムのからし明太子さん、パーカッションのMaruさんを加えたラテンバンド、"LA SIESTA DE VIRTUAL"として出演しました。(主に私のせいで) なかなか難しい選曲も多く、かなり多くの合わせ練習を必要としましたが、その分とても充実したものになりました! youtu.be

4/6 azWiz at PaleBlueDotClub (EchoArena試合観戦&JazzLive)

メンバー不定のあずさんのバンドazWizとして、EchoArena JAPAN Creators (EAJC) 主催のイベント、EchoArena試合観戦&JazzLiveに出演しました。 Echo VRというのは、ディスクをお互いのコートに投げ入れることを目的とするVRゲームで、EAJCはそのプレイヤーが集まってEcho VRについての情報を発信している団体です。 本イベントはその一環として、EchoArenaの試合観戦とJazz Liveを同一のVRChatワールドで行うという面白い試みでした!

4/15 Piano Therapy Op.2 at MusicStellarLake

2/25にトラブルで延期になってしまった本イベントですが、ようやく4月に開催されました! このイベントは、AudioLinkを介して反応するパーティクル演出とピアノ演奏を組み合わせて楽しむことを目的にしたもので、MusicStellarLakeの美しいワールドの中で行われます。 なので、普段よりすこししっとりと、自分でもパーティクルの光を楽しみながら即興演奏してきました!

5/6 Lactoice & tktk duo session at GHOSTCLUB (GHOSTWEEK)

ある日Amebientにtktkさんと居たところ、たまたま居合わせたGHOSTCLUB主催の0b4k3さんと話が進んで、二人でGHOSTCLUBのイベントに出演することになりました。GHOSTCLUBの中庭の中心でライブをさせてもらえるということで、中庭のしっとりとして穏やかな感じから、GHOSTCLUBの奥の激しい感じまでを表現するようなライブ構成にしてみました。また、ライブの最後には今年アレンジさせて頂いた"ow (piano arr: lactoice)" (後述) にtktkさんを加えて演奏させて頂きました! youtu.be

5/22 azWiz at Club Sara's Buddy

azWizとして出演する今年二回目のClub Sara's Buddyでした。高層ビルのきらびやかな雰囲気の中、しっとりとしたジャズをドラムレスで演奏してきました。 youtu.be

6/19 ALLVERSE JAZZ BAND at メタソニ2, AnimeConNL

loyさん 主催の大型イベントALLVERSE WEEKENDSの中のイベントとして、メタソニ2、AnimeConNLに、ALLVERSE JAZZ BANDとして出演してきました。 ALLVERSE JAZZ BANDは、ギターのアムニェカさん、サックスの初吹音さきさん、ベースののむさんとのカルテットで、それぞれのオリジナル曲を1曲ずつ演奏するというライブでした。

8/14 Tide Tyde at RIPPLE

Tide Tydeは、完全フリーのセッションとDJ、マシンライブ、Shader Live Coding、VJを組み合わせた、Yukihataさん主催の実験的なイベントです。Yukihataさん、isagenさんの二人からはじまったフリーセッションが、段々と広がりこのライブに繋がりました。ここまでフリーなセッションというのは、VRChatの中でも外でもやったことがなく、試行錯誤の連続でしたが、全員の意図が噛み合ったり、逆に予想外の方向に飛んでいったりする感覚が楽しくすっかりハマってしまいました。

8/25 azWiz at PaleBlueDotClub, VRChat (オカムラ杯inVket)

azWizとして2回目のEchoArena JAPAN Creators (EAJC) 主催のイベント出演でした。 今回は、Vketとの連動企画ということで、EAJCのkumaさん 制作の前回よりも一層パワーアップした専用会場でのライブでした。

8/26 VR即興音楽ツアー at STYLY

VR即興音楽ツアーは、リアルメタバースプラットフォーム STYLYにアップロードされた、いくつかのシーンを巡りながら、各シーンに合わせてピアノで即興演奏をするというイベントでした。三日坊主さんに誘って頂いて出演することになったのですが、VRChatのワールドとはまた一味違った雰囲気のシーンが多く、新鮮な気持ちで楽しんで演奏出来ました。また、三日坊主さんが次にどのシーンを出すか、という読み合いも面白かったです。

8/28 azWiz at Club Sara's Buddy

azWizとして出演する今年3回目のClub Sara's Buddyでした。今回は前回のバンドに加えて、ドラムのinuさんが加わりました。決してうるさくならず、すっと雰囲気をまとめてくれるドラミングによって一層演奏しやすくなりました。 youtu.be

9/4 El Sol2 @ La Isla del Sol

4/3に開催されたラテンライブEL Solの第2回!今回は出演できなかったパーカッションのMaruさんに代わって、パーカッション・ビブラフォンみちのくさんが参加してくれました。さらにギターでアムニェカさんも加わってくれて、かなり大所帯のバンドとなりました。今回会場制作も自分達で行っていて、tktkさんがメイン部分、cremaさんがボードのデザイン、私が一部システムとカメラワークという形でした。前回に引き続き大ボリュームでしたが、とても充実感がありました! youtu.be

9/17 JAZZ IN VRC at M4L1C3 Resonate

JAZZ IN VRCの特別編のようなかたちで、音楽ライブを行っている海外コミュニティのResonance VR主催のイベントM4L1C3 Resonateに出演してきました。会場で聴こえる言葉はほとんど英語で、なかなかコミュニケーションも難しい状況ではあったのですが、演奏がはじまると絵文字や拍手で楽しんでいる様子を皆さん伝えてくれていました。

11/26 azWiz at BarContrast

この日はなんとazWizで二つもライブがありましたが、その一つ目がBarContrastでのライブでした。いつもは、あずさんの選曲でライブすることの多いazWizですが、今回はラテン系統でやってみたい曲を挙げてみたところ、全曲ラテン系のかなりアツいセットリストになりました。歌モノのラテンはずっとやってみたかったので、演奏出来て嬉しかったです!

11/26 azWiz at Roots

同日のBar Contrastのライブの後、そのままRootsでのライブとなりました。こちらはかなり大型のイベントで、同一の会場で2パラレルでライブが行われているという面白い開催形態でした! 改めて、VRChat内でライブをする人が増えているんだなあと実感しますね。セットリストはBar Contrastのものとほとんど同一ですが、さらに何曲か追加して体力を使い果たす感じのセットリストでした笑

12/10 Sheenaさんバンド クリスマスライブ at BarContrast

ボーカルのSheenaさん、ドラムのStomさん、サックスの初吹音さきさん、ベースののむさんとのバンドでした。クリスマス曲を中心に、ジャズ曲を織り交ぜながらのライブでした。いよいよ12月に入って、VRChat上であってもクリスマスライブが増えてきたりするのはなんだか面白いですね。

12/13 azWiz at Okamura Night Circus in 1952 (EchoVRオカムラエレコム杯 in Vket)

azWizとして3回目のEchoArena JAPAN Creators (EAJC) 主催のイベント出演でした。たくさん呼んで頂いてありがたい限りですね! 今回も、kumaさん制作の特別会場で、創業当時のOkamuraの工場の中にライブ会場が出現しました!また、今回のライブでは(overさん)https://twitter.com/over_keys含め鍵盤が二人居る編成で結構難しいかなと思っていたのですが、overさんの音の入れ方が絶妙で非常に厚みのある良いサウンドになりました。

12/18 Jazz Quintet at Jazz&Bar:OcLa, Cluster

3/13のVRC×REALITY合同セッションを主催されていた、サックスのまよさんリーダーのJazz QuintetでCluster上でのライブを行いました。いわゆる"スタンダード本"に載っているようなセッション定番曲ではなく、知る人ぞ知る感じのジャズ曲を中心にしたライブで、曲をしっかり頭に入れるところからなかなか大変でしたが、その分新鮮な気分でプレイ出来たように思います。 youtu.be

12/18 azWiz at Club Sara's Buddy

なんとazWizとして出演する今年4回目のClub Sara's Buddyでした。今回は、初共演のトランペットのmonetteさんが加わってくれていました。VRChatのバンドでトランペットの方と一緒に演奏するのははじめてでしたが、一層Club Sara's Buddyの雰囲気に合うようなリッチなサウンドが引き出されていて演奏していてもとても楽しかったです。 youtu.be

Open mic形式のライブ

上記のライブ以外には、SLTや玉響といったOpen mic形式の会場で突発的なライブも行っていました。これについては、共演させて頂いた方と、演奏日を並べて書かせて頂きます。 このOpen micでの共演から、上で書いたような大きめのライブに繋がったり、あらたな人とのつながりが出来たりしたことも多かったので、こういった場所の存在にはとても感謝しています!

  • 1/8 シェルさん at SLT
  • 1/15 さきさん、yukihataさん at SLT
  • 1/29 Lazさん at SLT
  • 2/8 tktkさん at SLT
  • 2/5 あずさん、のむさん、yukihataさん at SLT
  • 2/26 ぴぼさん at SLT
  • 3/12 あずさん、のむさん at SLT
  • 3/26 のむさん、さきさん at SLT
  • 4/9 シェルさん at SLT
  • 6/11 Lazさん、tktkさん at SLT
  • 7/2 ソロピアノ at SLT
  • 7/24 しーなさん、のむさん、STomさん at 玉響
  • 8/13 のむさん at 玉響
  • 8/27 さきさん、zakkさん、コジローさん、めんたいさん (V Messengers) at SLT
  • 9/10 さきさん at SLT
  • 9/17 ソロピアノ at SLT
  • 10/8 シェルさん at SLT
  • 10/22 アムニェカさん at SLT
  • 11/5 tktkさん、みちのくさん at SLT
  • 11/27 ソロピアノ at SLT
  • 12/17 まよさん、さきさん、めんたいさん at SLT

その他の活動

ow (piano arr: lactoice)

0b4k3さんの楽曲"ow"の、CUE [Archive]のためのアレンジ"ow (2022 edit)"をさらにピアノアレンジした"ow (piano arr: lactoice)"を、カセットテープ媒体に収録して頂きました。これは、元々仮想水槽2で"ow"をピアノアレンジしたところからスタートしていて、今回カセット化するにあたって、2022editだけではまだテープの時間に余りがあるということでお声がけ頂いた形です。この収録は、グランドピアノを借りて生ピアノの音色に拘って行いました。こちらも是非聴いてみてくださいね。

0b4k3.bandcamp.com

ゼロから始めるVRCピアノ生活 (ゼロピアノ)

第二回の講師として呼んで頂き、ダイアトニックコードや、2-5-1といったジャズの基本的な理論の解説と、そのピアノでの弾き方について話してきました! 第五回までの講義が終わって、1月には参加者の発表会があるみたいで楽しみです!

技術記事「VRの世界に音を存在させる」

VRChatワールド探索部 Advent Calendar 2022の記事として、VRの世界に音を存在させるという技術記事を執筆させてもらいました。結構マニアックな記事かなとも思っていたのですが、意外にも反応が大きく嬉しく思っています。未読の方はぜひぜひ。

日産アリアとめぐる環境ツアー BGM提供

以前作曲したStair54を日産アリアとめぐる環境ツアーのBGMとして利用頂きました。

youtu.be

AI ProgrammerとSonicPi

最近話題の生成系AIの一つ、AI ProgrammerでSonic Piのコードを生成させる試みで遊びました。

おわりに

こうして振り返ってみると、本当に本当に多くの人と関わらせて頂いた一年だったと思います。 関わって頂いた方、色々とお話を振ってくれた方、本当にありがとうございました!来年以降もよろしくお願いします。 来年はワールド制作にも、もう少し軸を戻していければと思っています。 それでは良いお年を!

VRの世界に音を存在させる

こんにちは。らくとあいすです。 VRChatワールド探索部 Advent Calendar 2022 12日目の記事です。

本記事では、VRChatをはじめとするVRの世界において、「音をそこに存在させる」ための技術と、そのVRChatでの利用方法を紹介します。想定する読者としては、

  • VRChatで音のあるワールドを作ってみたい人
  • VRChatのワールド探索や音楽ライブを音の視点からもっと楽しみたい人
  • 立体音響についてごく基礎的な事項を知りたい人

などになると思います。ただし私は、立体音響やゲームサウンドの専門家ではないため、いちVRChatのワールド制作者として、種々の文献等を頼りに記事を執筆しております。誤った情報がないように努めておりますが、学術的な信頼性を期待出来る水準の記事ではない点ご留意いただければと思います。

1. はじめに

現実の私たちの生活空間にはたくさんの音があります。音は、物体の振動や空気の流れによって空気が揺らされることによって発生します。揺らされた空気は波として伝播し、部屋の床や壁などの物体に反射し、共鳴し、一部は吸収されます。それらが、最終的には耳介によって耳の中に集められ、鼓膜を揺らすことで人間に認知されます。従って、音はその発生源の性質はもとより、音の発生源から鼓膜に到るまでの環境や、自身の耳の形状といった多くの情報を含んだ形で、私たちに届けられることになります。例えば、トンネルに入った時の足音の変化で、私たちは目をつぶっていてもその環境の変化をうかがい知ることが出来ます。

一方で、イヤホン、またはヘッドホンといった耳に直接取り付けるデバイスを用いた場合は、そうした現実空間の影響をほとんど受けない形で音を聴くことになります。予めデジタルデータとして記録した音の粗密を、スピーカーの振動として現実空間に再現し、スピーカーと鼓膜の間のごく短い経路を通って我々に届けられます。スピーカーやその筐体の特性を受けるため、デジタルデータそのものの形の波面が耳に届くわけではありませんが、少なくとも良く調整された一定以上の品質のヘッドホン・イヤホンでは高い再現度で音を再生出来ます。

VRChatをはじめとしたVRの世界では、ほとんどの場合イヤホンやヘッドホンから再生された音を聴くことになります。当然、VRの世界の環境の影響を、直接的に音が受けることはありません。左右2チャンネルのステレオ音源が用意されたのであれば、再生装置はそれを律儀に再生するのみで、頭をふっても、トンネルに入っても勝手に音が変わることはありません。

従って、作られたVRの世界の中で、あたかもその世界で発された音が存在しているように感じさせるためには、その状況で我々が聴く音を計算によって再現する必要があります。これは、非常にシンプルな状況であればあまり難しいことではありません。例えば、反射も吸収もない無響室のような部屋の中 (自由空間) で、ある一点から発される音 (点音源) は、距離の二乗に比例して減衰します。しかし、現実的な状況を考えると、ある地点で観測する音には、わずかに遅れて到達する無数の反射音が重ね合わされるほか、頭部での遮蔽や耳介での反射の影響を受けて変化します。音源も点音源ではなく、一般には大きさをもっています。これらの条件を近似無しに計算して聴こえる音を計算することは現実的には不可能であるので、実際には様々な近似のもとでの計算を行うことになります。

本記事では、実際にVRの世界におけるいくつかの状況を想定しながら、各状況において適切な音提示の手法について紹介します。

2. 空間に存在するように音を鳴らす手法

2.1 オブジェクトベースの立体音響

VR空間で特定のオブジェクト (例えばハンドベル) から音が鳴るような状況を考えます。この時期待される挙動は、

  • オブジェクトの見えている方向から音が聴こえてほしい
  • 音の聴こえ方は、頭の回転に追従して変わってほしい
  • 近づいていけば、今度は耳元の近くで鳴っているように聴こえてほしい

といったようなものだと思います。この挙動を再現するためにはまず、人は何故左右や上下、距離感を感じ取れるのかということを考える必要があります。

音源の水平回転方向 (方位角) の認知においては直感的にも明らかなように、左右の耳に届く音の大きさの差 (Inter level difference; ILD) が影響を及ぼします。これは、右から来る音は右耳で大きく聴こえるという、ごく単純な話です。しかし、低い音、すなわち波長の長い音については回折によって頭部を回りこんで反対側の耳に届く割合が大きいため、ILDが方向知覚にあまり役に立たなくなります。そこで活用されるもう一つの情報が、左右の耳に届く音の時間差 (Inter time difference; ITD) です。我々の耳は1ms以下の非常に小さな音の時間差を聴きとることが出来るため、左右の耳に音が届く僅かな時間差の情報を音の方向を捉えるのに活用出来ます。ここで、110Hzのサイン波について、左耳より右耳の音を10dB小さくしてILDを再現したものと、それに対してさらに右耳の音を2ms遅らせてITDを再現したものを聴いていただきます。ITDの付与により、より明確に音源方向が感じ取れることが分かると思います。

  • ILDの再現のみ

  • ILD + ITDの再現

音源の垂直回転方向 (仰角) の認知については、一般に方位角と比べて弁別が難しい。これは、我々が左右二つしか耳を持っていないために、垂直方向の音源の移動に対してはILDやITDが顕著な特徴として表れないためです。それでも、我々の頭は上下方向に対称な形をしていないため、上から来る音と下から来る音では、耳に入るまでの反射や吸収のパターンがわずかに変化します。この変化は、特定の周波数の音の吸収 (ノッチ) に現れることが知られており、スペクトル的手がかり (Spectral cue; SC) と呼ばれています*1。なお、このSCは音の前後判定にも寄与しています。音源との距離の認知については一般的にあまり容易ではありません。ただし、こちらも左右の耳に届く音の角度差によって変化するスペクトラルキューや、ILD、ITDとの関連があることが報告されています*2。また、実環境においては、遠くの音ほど部屋の反響の影響を受けて音像がぼやけるといったことも距離を認知する手がかりとなります。

VRChatでは珍しくないが、耳が4つ付いている場合は、上下方向のITDが利用出来るため、仰角知覚の精度が向上するかもしれない。写真提供:えなじーさん、モデル:翠蓮

VR空間において、音の方向や距離感を再現するためには、人間が利用している音の手掛かり (ILD、ITD、スペクトラルキュー) を、頭部トラッキングに基づいて算出される頭の角度、音源との距離等に応じて変化させれば良いと考えられる*3。そのために、現在最も良く用いられる方法が、実際に人間の耳内にマイクを装着して測定した、頭部インパルス応答 (Head-Related Impulse Response; HRIR)を利用するものです。

インパルス応答についてや、その測定方法はより詳しい文献を紹介するにとどめさせて頂きますが*4、HRIRは簡単に言えば無響室内に居る人の近くでごく短い音 (パルス音) を鳴らした時に耳内に到達する音波を測定したものです (ただし、実際の測定には短い音ではなく、Swept-sineやM系列等を用いる)。誤解を恐れずに言えば、あらゆる音は無限に短いパルス音を強さを変えながら連続的に足し合わせたようなものであり、耳に届く音はそれぞれのパルス音に対するHRIRを足し合わせたものと言えます。このような演算を、畳み込み (Convolution) と言います。

VR空間内においてある距離・角度に音源が現れた際には、事前にその距離・角度に最も近い場所で収録されたHRIRを選び出し、それを音源に畳み込むことによってその位置から到来した音の聴こえを再現出来るというわけです。とはいえ、この方法にもまだ課題はあり、例えばVR空間上で音源が発生する位置は (少なくともfloat精度程度には) 連続であり、その細かさで現実空間のHRIRを測定することは不可能です。これには、インパルス応答の補完などの手法の利用が研究されています。またHRIRは、耳や頭の形状に影響を受けるため、他人の頭部によって収録されたHRIRを用いた場合に期待する定位感が得られない場合があることが知られています。この影響の軽減のため、多くの場合複数人のHRIRを平均した応答が立体音響の生成に用いられています (おそらくVRC_SpatialAudioSourceもこの方式) *5。また、近年では耳の画像等の情報を利用して、個人に最適なHRIRを選び出す方式も登場しています*6

最後にVRChatにおいて、このHRIRに基づく立体音響を利用する方法についても触れます。これは、シーン上に配置したAudioSourceにVRC_SpatialAudioSourceコンポーネントを取り付けるだけで実現されます。ただし、以下のいくつかの設定を確認する必要があります:

  • VRC_SpatialAudioSourceコンポーネント内、Enable Spatializationにチェックを入れる
  • AudioSourceのプロパティでSpatial Blendを3Dにする

ちなみに、Enable Spatializationにチェックを入れない状態でAudioSourceのプロパティ上でSpatial Blendを3Dとした場合HRIRの畳み込みは行われず、ILDのみを用いて音の方向が提示されます。これらの比較については、過去の記事で実験結果を共有しているため必要に応じて参照して頂ければと思います。実在感のあるオブジェクトベース立体音響を目指す場合、ほとんどの場合はEnable Spatializationにチェックを入れるべきですが、次のようなケースではチェックを外す選択肢もあるかもしれません:

  • VRChat上での配信ライブ
    • TopazChatやTwitchを経由してVRChat上に音を配信する形式のライブにおいて、配信の音を最も変化なく伝える方法は、AudioSourceのSpatialBlendを2Dとしてステレオ再生することです。しかし、これでは観客の頭の運動等に音がまったく連動しないため、臨場感が削がれるかもしれません。一方で、AudioSourceのSpatialBlendを3DとしてEnable Spatializationを有効にした設定を用いた場合、「配信の音がスピーカーの方向から鳴っていてそれを実際に耳で聴いたときの音」に近いものが聴こえることになるため、配信の音そのものとは若干音が変わってしまいます。それは正しい音の変化とも言えますが、そのような変化を嫌い・かつ頭の運動に伴う音像の変化が欲しい場合については、AudioSourceのSpatialBlendを3Dにして、かつEnable Spatializationを非有効にする手もあるかもしれません。ここは演者と主催者のポリシーによるところであると思います。(切り替えUIが標準的に設けられるようになると良いかもしれない。)
  • 非常に大量の音源がある、かつ低演算量を目指す場合
    • 演算量については、検証出来ていないためたしかな事は言えませんが、HRIRの信号への畳み込みに一定のCPUリソースを食われることはたしかです。音源が大量に存在する場合、負荷はその分増大するため、低演算量を要求されるプロジェクトの場合、Enable Spatializationを非有効とすることは一つの選択肢になりうるかもしれません。(もちろん、実際のプロファイルに基づいて行われることが望ましい。)

VRC_SpatialAudioSourceの音響特性の調査。(上図) VRC_SpatialAudioSourceを用いない3DAudioSourceを頭の周囲で移動させた時の、左耳での周波数応答。(下図) VRC_SpatialAudioSourceを用いた場合での同様の測定。3DAudioSourceでは単に再生音量が変わるだけなのに対して、VRC_SpatialAudioを用いることで、周波数応答に方向に特有のノッチやピーク (SC) が出現することがわかる。(詳細は過去の記事を参照)

2.2 残響について

2.1節では、音の伝搬における頭部や耳の影響を取り込むことで、方向や距離を感じ取ることの出来る音を作り出す手法について述べました。ただし、ここでは部屋の壁や天井といった、体以外の物体による音の伝播への影響が考慮されておらず、ほとんどの場合でHRIRは無響室において収録されたものが用いられています。

部屋の影響を取り入れるためのもっとも直接的な方法は、音源から耳に届くまでのあらゆる音の反射経路を取り出し、それらの経路において耳に届くまでの時間と減衰・吸収を求めることです。これは、部屋形状がごくシンプルな場合にはある程度現実的な計算量で計算可能であり、例えば「鏡像法」というアルゴリズムが良く知られています。鏡像法や、Unity上での鏡像法のシミュレーションについては、よしたかさんによるこちらの講演資料に詳しいです*7

ただし、鏡像法等の方法によって、逐次に音源から人までの反射経路と遅延を計算することは非常にコストが高く、部屋形状が複雑であったり音源が複数個となった場合はさらに困難です*8。そこで多くの場合は (頭部の場合と同様) 室内の物体の音への影響をインパルス応答として測定 (または生成)し、このインパルス応答を音源に畳み込むことによって、残響 (リバーブ) を表現します。このような方式のリバーブをコンボリュージョン・リバーブと呼びます。

VRChat(Unity) において利用出来るリバーブもコンボリュージョン・リバーブです。これは、AudioSourceにAudioReverbFilterを取り付けるか、Audio Listenerを取り付けたMain CameraにAudioReverbFilterを取り付けることで適用可能です。前者は、取り付けたAudioSourceのみへのリバーブ、後者は聴こえる全ての音へのリバーブとなります。

ちなみに、VRChatにおいてPost processing stack (PPS) とMainCameraに取り付けるリバーブを併用する場合、VRC Scene DescriptorのReference Cameraとして設定したPost-process layerを設定したCameraに、AudioReverbFilterを追加してしまうと、この効果が反映されないという問題がある。この場合は、「Main Camera」という名前のCameraを一つ用意してこちらに、AudioListenerとAudioReverbFilterを取り付け、Reference Cameraとして設定するPPS付きのCameraにはAudioListenerやAudioReverbFilterを取り付けないようにする必要がある。

UnityのAudioReverbFilterでは、プロパティからコンボリュージョン・リバーブの設定を変更出来ます。これは、部屋の形状や壁面の反射率に合わせてリバーブをコントロールするために用いることが出来ます。プロパティの詳細は公式ドキュメントに記載されている通りですが、ここでもそれぞれの設定項目について簡単にまとめておきます。

  • Reverb Preset: プリセットとしていくつかの環境を想定したリバーブのバラメータが用意されています。"User"を選択した場合は、下記パラメータを用いて自分でリバーブの調整を行うことが可能となります。
  • Dry Level: 元信号 (リバーブを適用しない信号) のレベルです。
  • Room: リバーブを適用した信号のレベルです。
    • ところで、ドキュメントには"Room effect level at low frequencies"と記載されているが、一方でRoom LFにも"Room effect low-frequency level"と記載されています。こちらは、"at low frequencies"ではなく全体のレベルなのではないかと思っているのですが、実際のところは不明です。
  • Room HF (LF): リバーブを適用した信号のレベルの高音 (低音) 域成分のレベルです。
  • HF (LF) Reference: 高音域 (低音域) として設定する周波数の範囲を決めます。
  • Decay Time: 低音域での残響減衰時間です (何Hzを基準にしているかは不明。おそらく一般的には500Hzなど?)
  • Decay HFRatio: 低音域にたいする高音域の残響減衰時間の比です
    • 一般に現実空間であれば、高音域の方が物質に吸収されやすいため、残響成分では低音域が大きくなりやすい。 したがって、Decay HFRatioを1.0以上の値にするのは、物理的には考えづらい状況です。(そのような効果を狙うのであれば1.0を超えても良いと思います。)
  • Reflections Level: 初期反射のレベル
  • Reflections Delay: 初期反射の遅延時間
  • Reverb Level: 後部残響のレベル
  • Reverb Delay: 後部残響の遅延時間
    • 反射音として到来する音のうちはじめ (の方) にやってくる音を初期反射、後の方にやってくる音を後部残響といいます
    • 初期反射の遅延時間が大きい (= 直接音が到来してから反射音が到来するまでの時間が長い) ということは、音源との距離に対して部屋のサイズが大きいことを意味します

2.3 シーンベースの立体音響

ある程度遠方に、非常に多くの音源があるような場合を考えます。例えば、森の中で木々の葉がこすれたり、街中で無数の人が話したり歩いたり、雨が降っていたりといった状況が当てはまります。この状況は、2.1のオブジェクトベースの立体音響において非常に多数の音源が存在するケースとも言えるが、雨粒一つ一つの音に対してHRIRの畳み込み演算を行い立体音響を生成することは現実的ではありません。

このようなケースで現状 (VRChatワールドとして) 最も良く用いられている手段は、おそらく(Spatial blendを2Dとして、Enable Spatializationを非有効にしたAudioSourceを用いて) ステレオ音源を2D音源として再生する方式だと思います。この方法では、当然頭部の回転やプレイヤーの移動によって音が変化することはありませんが、雨音のように方向感のあまりない音に対しては多くの場合十分と言えます。ただし、建物の中から雨の降る屋外に移動したり、森を抜けて草原に出たりといったように、環境音がプレイヤーの動きによって動的に変化することが期待される状況は十分にあり得ます。このようなケースには、2DのAudioSourceの音量や、Low Pass Filter (LPF) 等の設定をプレイヤー位置に応じて変化させることで、ある程度対応可能です。2D AudioSourceの制御による環境音の遷移については過去の記事にも書いているため興味があれば参照して頂ければと思います。

より臨場感ある環境音の表現を得るために立体音響を導入するための手法としては、アンビソニックスの利用が挙げられます。アンビソニックスの詳細についてはより詳しい文献*9 を紹介するにとどめますが、ごく簡略化して言えば (同一球面上に配置された) 多数のマイクロフォンを利用して、観測地点からみた全天球上の音場を記録するフォーマットです。もう少し言えば全天球上の音場を、球面調和関数の各成分に展開して記録するものです。アンビソニックスの現在最も普及している形式は、一次アンビソニックス (First Order Ambisonics; FOA) と呼ばれるものです。これは、球面調和関数展開の1次までの項を利用するもので、無指向W (0次) と、X, Y, Z (1次) の4つのチャンネルから構成されます。アンビソニックス形式で記録された全天球の音場は、後から回転して再生することが可能です。アンビソニックスを利用すれば、VR空間においてプレイヤーの頭部回転に対して音場を逆方向に回転させることで、頭部の動きに追従した立体的な環境音を再生することが可能となります。ただし、アンビソニックスはあくまでも全天球上の音場を記録しているに過ぎないため、プレイヤーの並進運動に対応することは出来ません。従って、6DoFのVRコンテンツに用いる場合においては、プレイヤーのごく近くで鳴っているような音にはあまり適さない点に注意が必要です。

VRChatでのアンビソニックス形式の音の再生方法については、こちらの動画 が詳しいですが、簡単に日本語で手順を書いておきます:

  1. アンビソニックス形式の音源を用意する
  2. Unityのプロジェクトにimport
  3. Edit>Project settings>Audio>Ambisonic Decoder Plugin>Oculus Spatializerを選択します
  4. アンビソニックス音源のimport settingsでAmbisonicsにチェック、Load TypeをStreamingに変更しからApplyします
  5. シーンにAudio Sourceを設置し、Spatial blendを2D、VRC_SpatialAudioSourceのEnable Spatializationを非有効にします
  6. 上記AudioSourceのAudioClipとしてアンビソニックス音源を設定します
  7. アンビソニックス音源の正面方向を向けたい方向に、AudioSourceのZ軸を向けます

以上でアンビソニックス形式の音の再生が可能となり、実際にVRChatワールド内において頭を動かすことで音が変化することが確認出来ます。

3. さいごに

本記事では、主にVRChatで音のあるワールドを作る人に向けて、立体音響にまつわる基礎的な事項を紹介しました。非常に奥の深い分野で、書ききれていないところ、知識の至らないところ多々あったかとは思いますが、より詳細な文献に至る前段階として、少しでも理解の助けになれば幸いです。また、ワールドを探索する人にとっても、この記事がワールドを音の観点からより楽しめるようなきっかけとなれば幸いです。

*1:K. Iida+, "Median plane localization using a parametric model of the head-related transfer function based on spectral cues", Applied Acoustics vol. 68, 2007, URL: http://www.iida-lab.it-chiba.ac.jp/literature/research.papers/14.median.plane.localization.using~.aa-2007.pdf

*2:金 海永 他 (2000)、拡張輻輳角モデルを用いた音像距離定位の制御 日本バーチャルリアリティ学会論文誌 vol.5 No.3、URL: https://www.jstage.jst.go.jp/article/tvrsj/5/3/5_KJ00007553639/_pdf/-char/ja

*3:谷幸雄 (2017)、頭部伝達関数による音像定位 日本音響学会誌73巻3号 小特集—頭部伝達関数とその応用—、URL: https://www.jstage.jst.go.jp/article/jasj/73/3/73_173/_pdf

*4:河原英紀 (2020)、インパルス応答の基本概念 日本音響学会誌 76 巻 3 号、URL: https://www.jstage.jst.go.jp/article/jasj/76/3/76_148/_pdf

*5:平均HRIRを用いた場合、当然そのデータの平均から外れた頭部・耳の形状の場合は立体音響の質が下がってしまう。おそらく、ほとんどの日本人にとってVRChat上での音は十分な立体音響として聴こえていないのではないかと思う。実際私も、VRChat上で上下や前後の判定を行うことは結構難しいと感じた。

*6:SONY 360 Reality Audio: https://www.sony.jp/headphone/special/360_Reality_Audio/

*7:吉高 弘俊、VR音響のための鏡像法による距離減衰の再現、URL: https://learning.unity3d.jp/2121/

*8:とはいえ、近年は計算機パワーの増大に伴って動的に変化するようなリバーブについても導入がはじまっているらしい

*9:西村竜一 (2014)、特集記事「アンビソニックス」 映像情報メディア学会誌 Vol. 68 No. 8、 URL: https://www.jstage.jst.go.jp/article/itej/68/8/68_616/_pdf

SonicPiをMIDIコントローラで制御する話

こんにちは。

久し振りにSonicPiでライブをしたのですが、今回はMIDIコントローラを初導入しました。

SonicPiでのMIDIコンからの入力の受け方について少しまとめておきたいと思います。 本記事は、程度SonicPiに慣れ親しんでいる方を想定しています。

環境

  • SonicPi for Win x64 v3.3.1
  • DJ TechTools MIDI FIGHTER TWISTER
    • 16個のロータリーエンコーダのついたMIDIバイス。ノブを押し込むことでもMIDI信号を送信出来る。

SonicPiでのMIDIの受け方

Sonic Piでは接続されているMIDIバイスからの入力を、Time Stateと呼ばれる共有メモリに保持しています。

ここは、良くわかってないで書きますが、(SonicPiのTutorialによれば) Time Stateはグローバル変数と似ているがスレッドセーフであるとのことです。 例えば、複数スレッド (ループ) 間で共通の値 (例えばBPM) にアクセスしたいとき、グローバル変数ではそれがあるスレッドで書き換えられると他のスレッドに影響を及ぼしますが、Time Stateの変更はスレッド間で独立に行われるため、determinism guaranteesであるということみたいです。

Time Stateは、シンボル (コロン+文字列) または文字列によって名前付けられる。MIDI入力は次のような文字列でTime Stateに格納されます:

"/midi:[MIDIデバイス名]:[MIDIチャネル]/[MIDIイベント]"

MIDIバイスの名称は、環境設定>入出力>MIDIポートから確認出来ます*1。 格納されたMIDI Time Stateは、get または syncによって取得することが出来ます:

note, velocity = get (or sync) "/midi:[MIDIデバイス名]:[MIDIチャネル]/[MIDIイベント]"

getによる取得は、MIDI Time Stateにその時保持されている値を取り出すもので、syncによる取得はMIDI Time Stateが新しい入力によって更新されたときにその値を取得するものです。 また、MIDI Time Stateの名前はアスタリスク ( * ) によるワイルドカードを用いて指定することが出来ます。 これは例えば、MIDIイベントとMIDIチャネルを "*"に置き換えることによってあるMIDIバイスから入力される信号を全て受け取るといったように利用することが出来ます。

受け取ったMIDI入力を使用出来る形にする

基本方針は次の通りです:

Note On/Off

Note On/Offは今回スイッチ/トグル的な情報の取得 (例えば全ミュートや、乱数の更新) と、手でSFXを鳴らすために使いました。 下記のコードのように、Note On/Offでそれぞれスレッドを用意してそれぞれはON/OFFの入力をsyncで待ちます。そして、やってきたMIDI信号のNoteに応じて処理の振り分けを行います。n==1の場合の処理は、ON/OFFで連動していてONが押されてからOFFまでの間mute=trueとする処理。また、n==11の時はONが押されたら即座にサンプルを再生します。

live_loop:midion do
  use_real_time
  n,v = sync "/midi:midi_fighter_twister_2:1/note_on"
  if n==1
    mute = true
  end
[中略]
  if n==11
    sample fx2s,rand_i(fx2N)
  end
end

live_loop:midioff do
  use_real_time
  n,v = sync "/midi:midi_fighter_twister_2:1/note_off"
  if n==1
    mute = false
  end
end

Control Change

Control changeは、連続的な値の変化 (サンプルを鳴らす頻度、乱雑さ、リバーブの深さ、LPFなど) のために使いました。 ただし、SonicPiでは一度鳴った後の音に対して音を連続的に変化させることが出来ないことには注意が必要で、あくまでも各音を鳴らす時の設定を変化させるというところにとどまります。 Control changeも、MIDI ON/OFFと同じように受け取れますが、複数のノブを同時に回して同一チャネルにCC信号を送ったときMIDI ON/OFFと同じような書き方だと片方が取れなくなるのではないかと思うので [未検証]、今回は各16個のノブを回したときに別のチャネルにControl change信号が送られるようにし、それぞれを独立に受信します。 ただし、この場合に下記のようにsyncを使ってしまうと、ch1のControl change信号が来ないとそれ以降の信号が受け取れないようになってしまう。

live_loop:midicc do
  use_real_time
  _,v1 = sync "/midi:midi_fighter_twister_2:1/control_change"
  _,v2 = sync "/midi:midi_fighter_twister_2:2/control_change"
  _,v3 = sync "/midi:midi_fighter_twister_2:3/control_change"
end

そこで今回はやや無駄があるようにも思われるがgetによって信号を受信することにしました。ただし、getの場合は受信を待ってはくれないので別途sleepが必要となる:

live_loop:midicc do
  use_real_time
  _,v1 = get "/midi:midi_fighter_twister_2:1/control_change"
  _,v2 = get "/midi:midi_fighter_twister_2:2/control_change"
  [中略]
 if v1 != nil
    rbd = v1/127.0
  end  
  if v2 != nil
    rhh = v2/127.0
  end
  [中略]
  sleep 0.3
end

受け取ったMIDI入力を元にリズムや音を作る

今回はリズムの生成アルゴリズムの変数とエフェクトのパラメータの部分に主にMIDI信号を利用した。 明確な機能というよりは、お気持ち的に割り当てている変数が多いですが...下記が実際に行った機能割り当てです。

f:id:Raku_Phys:20211128174420p:plain
MIDIコンへの機能割り当て

ざっくりと各項目について書いていきます。

Control changeのパラメータ

各サンプルの頻度

今回のリズム生成の基本的な方針は、6つのサンプルグループと2つのシンセのいずれかを時間単位あたりひとつだけ鳴らすというもので、この8つのグループから各拍にどれを選択するかという頻度を8つのノブでコントロールします。例えば、1,2番目のノブだけを振り切るとバスドラとハイハットだけが鳴るといったような感じです。

1ループの音数

時間単位あたりひとつだけ音を鳴らすという方針の上では、各拍の音は独立、つまりループという概念は生まれませんが音楽的まとまりの導入(とエフェクトの負荷軽減)のため、いくつかの拍を1まとまりとして「1ループ」という概念を導入しています。ここでは、1ループに含まれる音の数を変更します。

ループのまとまり感

前項のようにループを導入してもそのままでは、各音は完全に独立です。 今回は音楽的なまとまりをかなりヒューリスティックに導入していて、この「まとまり感」変数をあげていくと、ループ先頭でバスドラが鳴りやすく、ループ中間でハットが鳴りやすくなり、逆にそれ以外の拍では鳴りづらくなっていきます。ループの音数が4のときは普通の四つ打ちに近づいていく感じですね。ループの音数を5や7にして変拍子を作り出すことも出来ます。

音程の広がり

今回音程のある楽器として、ベース、単音のシンセ、コードの三つが導入されていますがこれらのうちシンセとコードについて、ある割合でオクターブジャンプするようになっています。その割合をここでコントロールします。

サンプルの種類幅

各リズムサンプルグループ (バスドラ、ハイハット、クラップ、タム、パーカッション、グリッチ系) は、それぞれ複数のサンプルを持っています。この中からどの程度のバリエーションを取り出すかという部分がこの変数です。サンプルの順番として、おとなしめのものが前半に、激しめのものが後半にくるようにしておきます。

HPF/LPF cutoff

サンプルに対するHigh pass, Low pass filterのカットオフです。 ここで、他のエフェクトはループ単位でかけていますが、このエフェクトだけは各拍毎に更新しています。

バーブMIX

バーブのdry/wet割合です。今回はもともと深めのリバーブをかけておいて、dry/wetのmixだけでリバーブをコントロールすることにしています。

PAN

グローバルなPAN振りです。もともと各サンプルはある程度の乱数幅を持ってそれぞれPANが振られていますが、このノブを回すとその乱数幅が収束していって、振り切った時にはL/Rに偏るようになっています。

Note On/Offのパラメータ

MUTE

全ミュートです。ただし、SonicPiは既に鳴らした音をミュート出来ないので、実際には次の拍以降に鳴るはずの音を鳴らさないということをしています。このスイッチは押している間ずっとミュートで、NoteOffを受信したときにミュートを解除するという動作にしています。

ループ内seed固定

1ループごとに固定のseedを設定しなおすかどうかのスイッチです。1ループごとにset_random_seed [seed値]でseedを設定しなおすと、各ループではまったく同じ音が繰り返されることになります。これによって、例えば、1ループの音数が16の時は1小節のリズムパターンのように振舞います。逆にseedを設定しないときは、ループ間ではまったく別の音が鳴ることになります。

seed変更

これは、前項のループ内seed固定が有効な時に効果があります。ループ内seed固定をしているそのseedをこのボタンを押すたびに変更します。これによって、ループパターンを変更して好みのものを「ガチャ」出来るようになります。

Bass ON

これは主にエンコーダが足りないためにBassだけ独立させているだけですが、Bassはバスドラと連動させた動きが多いので、鳴らし方・頻度はバスドラの頻度パラメータに紐づけて、鳴らすかどうかをこのスイッチで決めています。

エフェクトON/OFF

今回は、bitcrusherと、ixi_technoの二つのエフェクトを用意しています。エフェクトのパラメータをいじるのではなく、単にON/OFFをするスイッチです。

SFX

その他のNote ON/OFFと違って楽器的な振舞いをします。ボタンを押したタイミングで、性質の違う3つのSFXグループからランダムなサンプルを選んでそれぞれ再生されます。

時間単位

最小時間単位の長さを変更します。例えば、最小時間単位が1のときに比べて1/4の時は4倍のテンポになるといった感じです。

まとめ

Sonic PiをMIDIコンで制御するために、MIDI入力を受け取る方法とそれを使った実際の制御方法例を示しました。 別の使い方としては、シンセサイザーのように各シンセのパラメータを変更したりするようにも使っている人が多いようですね *2。 最後に、参考までに全体コードを載せておきます。コメントも何もないのでかなり見づらいかもしれませんが... 改変等はご自由にどうぞ!

use_bpm 138

load_synthdefs "C:/Synth/my-synth"
set_volume! 5
bds = "C:/SPS/gc1127/bd/"
bdN = 2
hhs = "C:/SPS/gc1127/hh/"
hhN = 2
claps = "C:/SPS/gc1127/clap/"
clapN = 3
toms = "C:/SPS/gc1127/tom/"
tomN = 6
percs = "C:/SPS/gc1127/perc/"
percN = 54
glitchs = "C:/SPS/gc1127/glitch/"
glitchN = 30
fx1s = "C:/SPS/gc1127/fx1/"
fx1N = 3
fx2s = "C:/SPS/gc1127/fx2/"
fx2N = 3
revs = "C:/SPS/gc1127/rev/"
revN = 4
Ns = [3,4,5,6,7,11,16]
Ms = [0.25,0.5,3.0/4.0,1.0]
key = -4
bsl = [:C3,:C3,:C2,:C2,:C2,:C2,:C2,:C2,:C2,:C1,:C1,:C1,:C1,:Db1,:Db2,:G1,:G2,:Db3].ring
synl = [:C2,:C3,:C3,:F3,:G3,:Bb3,:C4,:Db4,:Eb4,:F4,:G4,:Bb4,:C5].ring
cdl = [:C4,:C4,:Eb4,:F4,:C5]

mute = false
fixseed = false
withbs = false
seed = 0
rbd = 0
rhh = 0
rclap = 0
rtom = 0
rperc = 0
rglitch = 0
rsyn = 0
rcd = 0
octr = 0.0
width = 0.6
gpan = 0.0
acc = 0
var = 0.0
rev = 0.0
hpfco = 0.0
lpfco = 0.0
N = 4
M = 1
ixi = 0
bit = 0

set_mixer_control! hpf:0,hpf_slide:0.5
set_mixer_control! lpf:127,lpf_slide:0


live_loop:metro do
  sleep 4
end

live_loop:loop1,sync: :metro do
  F = 4.0 - var*4.0
  fbd = 1.0
  fhh = 1.0
  it = 0
  if fixseed
    use_random_seed seed
  end
  with_fx :reverb,room:rev,mix:0.2+rev*0.8 do
    with_fx :ixi_techno,res:0.9,mix:ixi do
      with_fx :bitcrusher,bits:5,mix:bit do
        N.times do
          with_fx :rhpf,cutoff:hpfco,res:0.9 do
            with_fx :rlpf,cutoff:lpfco,res:0.9 do
              if not mute
                oct = 12*(5.0*octr*rand-1.0).to_i
                frbd = rbd
                frhh = rhh
                if it==0
                  frbd = rbd*(1.0 + 50.0*acc)
                else
                  frbd = rbd/(1.0 + acc*20.0)
                end
                if it==(N/2.0).to_i
                  frhh = rhh*(1.0 + 50.0*acc)
                else
                  frhh = rhh/(1.0 + acc*20.0)
                end

                rsum = frbd+frhh+rclap+rtom+rperc+rglitch+rsyn+rcd
                if rand<rsum
                  r = rsum*rand
                  if r<frbd
                    sample bds,(bdN*rand**F).to_i,amp:0.75,pan:gpan
                    with_synth:sine do
                      play 30,amp:0.75,pan:gpan
                    end
                    if withbs
                      with_synth :tb303 do
                        play bsl.choose+0+key,amp:0.4,release:0.3+0.1*rand,cutoff:60+70*rand if rand<0.8*(1.0 - acc)
                      end
                    end
                  elsif r<frbd+frhh
                    sample hhs,(hhN*rand**F).to_i,amp:0.75,pan:(gpan + 0.2 + (rand**0.5 - 0.2)*width).clamp(1)
                  elsif r<frbd+frhh+rclap
                    sample claps,(clapN*rand**F).to_i,amp:0.75,pan:(gpan + -0.2 + (0.2 - rand**0.5)*width).clamp(1),rate:1.0 + 2.0*(rand-0.5)*var*var
                  elsif r<frbd+frhh+rclap+rtom
                    sample toms,(tomN*rand**F).to_i,amp:0.9+0.2*rand,pan:(gpan + -0.4 + (0.4 - rand**0.5)*width).clamp(1)
                  elsif r<frbd+frhh+rclap+rtom+rperc
                    sample percs,(percN*rand**F).to_i,amp:0.3+0.8*rand*rand,pan:(gpan + width*(rand**0.5-0.5)*2.0).clamp(1),rate:1.0 + 3.0*(rand-0.5)*var*var
                  elsif r<frbd+frhh+rclap+rtom+rperc+rglitch
                    sample glitchs,(glitchN*rand).to_i,amp:0.3+0.8*rand*rand,pan:(gpan + width*(rand**0.5-0.5)*1.5).clamp(1),rate:1.0 + 4.0*(rand-0.5)*var*var
                  elsif r<frbd+frhh+rclap+rtom+rperc+rglitch+rsyn
                    with_synth :myssaw do
                      play synl.choose+0+key+oct,release:0.3+0.13*rand,amp:0.15+0.3*rand
                    end
                  elsif r<frbd+frhh+rclap+rtom+rperc+rglitch+rsyn+rcd
                    with_synth :dtri do
                      play chord(cdl.choose+key+oct,'m7'),release:0.2,amp:1.5
                    end
                  end
                end
                if withbs
                  with_synth :tb303 do
                    play bsl.choose+0+key,amp:0.3+rand*0.22,release:0.18+0.1*rand,cutoff:40+70*rand if rand<acc*rbd
                  end
                end
                with_synth :myssaw do
                  play synl.choose+0+key+oct,release:0.2+0.04*rand,amp:0.15+0.15*rand if rand<rsyn
                end

              end
              it += 1
              sleep 0.25/M
            end
          end
        end
      end
    end
  end
end

live_loop:midion do
  use_real_time
  n,v = sync "/midi:midi_fighter_twister_2:1/note_on"
  if n==1
    mute = true
  end
  if n==2
    fixseed = !fixseed
  end
  if n==3
    seed = seed + 7
  end
  if n==4
    withbs = !withbs
  end
  if n==5
    bit = 1.0 - bit
  end
  if n==6
    ixi = 1.0 - ixi
  end
  if n==9
    sample revs,rand_i(revN),amp:1.5
  end
  if n==10
    sample fx1s,rand_i(fx1N)
  end
  if n==11
    sample fx2s,rand_i(fx2N)
  end

  if n>=13
    M = Ms[n-13]
  end
end

live_loop:midioff do
  use_real_time
  n,v = sync "/midi:midi_fighter_twister_2:1/note_off"
  if n==1
    mute = false
  end
end

live_loop:getmidicc do
  use_real_time
  _,v1 = get "/midi:midi_fighter_twister_2:1/control_change"
  _,v2 = get "/midi:midi_fighter_twister_2:2/control_change"
  _,v3 = get "/midi:midi_fighter_twister_2:3/control_change"
  _,v4 = get "/midi:midi_fighter_twister_2:4/control_change"
  _,v5 = get "/midi:midi_fighter_twister_2:5/control_change"
  _,v6 = get "/midi:midi_fighter_twister_2:6/control_change"
  _,v7 = get "/midi:midi_fighter_twister_2:7/control_change"
  _,v8 = get "/midi:midi_fighter_twister_2:8/control_change"
  _,v9 = get "/midi:midi_fighter_twister_2:9/control_change"
  _,v10 = get "/midi:midi_fighter_twister_2:10/control_change"
  _,v11 = get "/midi:midi_fighter_twister_2:11/control_change"
  _,v12 = get "/midi:midi_fighter_twister_2:12/control_change"
  _,v13 = get "/midi:midi_fighter_twister_2:13/control_change"
  _,v14 = get "/midi:midi_fighter_twister_2:14/control_change"
  _,v15 = get "/midi:midi_fighter_twister_2:15/control_change"
  _,v16 = get "/midi:midi_fighter_twister_2:16/control_change"
  if v1 != nil
    rbd = v1/127.0
  end

  if v2 != nil
    rhh = v2/127.0
  end

  if v3 != nil
    rclap = v3/127.0
  end

  if v4 != nil
    rtom = v4/127.0
  end

  if v5 != nil
    rperc = v5/127.0
  end

  if v6 != nil
    rglitch = v6/127.0
  end

  if v7 != nil
    rsyn = v7/127.0
  end

  if v8 != nil
    rcd = v8/127.0
  end

  if v9 != nil
    N = Ns[(7.0*(v9-1.0)/127).to_i]
  end

  if v10 != nil
    acc = v10/127.0
  end

  if v11 != nil
    octr = v11/127.0
  end

  if v12 != nil
    var = v12/127.0
  end  

  if v13 != nil
    hpfco = v13
  end

  if v14 != nil
    rev = v14/127.0
  end

  if v15 != nil
    gpan = 2.0 * (v15/127.0 - 0.5)
  end

  if v16 != nil
    lpfco = v16
  end
  sleep 0.3
end

*1:MIDIバイスを入力した順番によって、名称の末尾の数字が変更されうるので注意が必要です。

*2:Nikoさんとか寝る前さんとか

VRC_SpatialAudioSourceの音響特性

※2025年12月現在VRC_SpatialAudioSourceの仕様が変わったため下記の測定結果は古い情報です。新しい情報はこちらの記事に書きました。

VRC_SpatialAudioSourceの音響特性 (SteamAudio版・2025年12月) - らくとあいすの備忘録

下記アーカイブとして...


またまた、VRC_SpatialAudioSourceの話。 毎回もう使わないって言ってる気がするけど、代替を考えるためには良く知る必要がある...。 今回は、そもそもVRC_SpatialAudioSourceってどのぐらいちゃんと「Spatialization」してるんだろうかという話。 ドキュメントには逆二乗減衰しか書いてないので本当にわからない。

角度に対する応答の検証

Head-related transfer function (HRTF) の畳み込みなどのちゃんとしたSpatializationの効果が入っているとすると、前後左右上下で周波数特性に差が出るはず。さらに左寄りまたは右寄りの位置の音源に対しては、波が左右の耳に到達するまでの時間差から左右位相差が現れるはず。これらを確かめるため、VRChatで記録したデータからVRC_SpatialAudioSource有/無の場合を比較する。

実験は前後左右上下の6つのAudioSourceから、ホワイトノイズを発することによって行う。体の向きによってAudioSourceと受聴点の関係が変わらないように、常にプレイヤーの頭に全てのAudioSourceが追従するようにU#Scriptで制御した。また、Spatializationが非有効かつSpread=0のとき、左90度の音源に対して右側での音量は0となるが、比較の分かりやすさのため、Spread=70まで上げて、音源と反対側のチャネルにおいて、Spatializationが有効の時と同程度のゲインが得られるようにした。

周波数特性

まずは周波数特性から。図は左チャネルについて2D-Audioに対する各周波数帯域でのゲイン比を示す。 Spatializationが非有効の場合、レベル差は現れるものの周波数特性は全く変化しない。一方、Spatializationが有効の場合、1000Hzより上の帯域で音源方向によって異なる周波数特性が見られる。特に、4000Hzから10000Hz付近に見られる大きなノッチが特徴的だがこれはスペクトラルキューと呼ばれるもので、人はこの高域に表れるノッチやピークの変化から音源の方向を捉えていることが知られている。

ホワイトノイズに対する周波数応答。(上) Spatialization非有効。(下) Spatialization有効。

位相差

次に、左右の耳の位相差を調べる。図は右チャネルの位相から左チャネルの位相を引いた位相差の時間平均を示している。これを見ると、Spatializationが非有効の場合は左右の耳の間でまったく位相差がない一方で、Spatializationが有効の場合位相差が現れているのがわかる。ここでは、位相差の物理的妥当性までは検討しないが、少なくともLeft/Rightで位相が逆転していることはもっともらしい。こうした、低域における左右耳の位相差も、人が音源方向を認知する上で重要であることが知られている。

左右チャネルの位相差。(上) Spatialization非有効。(下) Spatialization有効。なお、高域の位相差は空間エイリアシングの影響で、意味を持たないため1000Hzまでを示している。

距離に対する応答の検証

距離による周波数特性の変化を調べる。実験は、プレイヤーの正面方向、0.1, 0.5, 1.0, 3.0, 10.0, 30.0mの6地点に配置したAudioSourceから発したホワイトノイズを記録することによって行う。

周波数特性

図は、音源距離を変化させたときの、周波数応答を示している。ごく近接では高域のノッチが浅くなりフラットな周波数分布となっているのに対して、離れた距離では高域に深いノッチが現れることがわかる。逆に3m以降ではほとんど周波数特性は変化していない。これは頭部の大きさに対して十分離れているためと考えられる。

音源距離を変化させたときの、ホワイトノイズに対する周波数応答。

まとめと感想

今回は、そもそもVRC_SpatialAudioSourceってどのぐらいちゃんと「Spatialization」してるんだろうか、ということでVRC_SpatialAudioSourceの角度と距離に関する応答を調べた。 あまり信用していなかったが、やってみるとそれなりにちゃんとHRTFをシミュレートしているらしいことがわかった。 これでなんでドキュメントに逆二乗則ですとしか書いてないのか理解できない。

全然ダメだったら代替を考えるかと思ったが、こうデータを見ると使わないのは勿体ないと思わざるを得ないので悩ましい...。特に位相差を手で入れるのはかなり困難...。 あと、今回色々と音を比較していて思ったのは、もしSpatializationを有効にするのであればそれを前提として音作りをする必要がありそうということ。 イヤホンで聴いて音を作ると、つい耳に馴染むように外から聴こえてきたような音を作ってしまいがちだが、それをしてしまうと二重で頭部の影響を受けたようなものになってしまって音がぼやけてしまうかもしれない。音を作る時は、Spatializer的なプラグインを入れながら最終的な聴こえを確認しつつ進めるのが良さそうだと思う。

VRC_SpatialAudioSourceのクリックノイズ問題

今回もVRChatでの立体音響等について.次のツイートのように,VRC_SpatialAudioSourceの"Spatialization"を有効にしたときにだけ音の鳴り始めでプツっとなる問題を検証.

結論

親オブジェクトRigidbodyコンポーネントが付いている時に、その子オブジェクトとして配置されたVRC_SpatialAudioSource付きのAudioSourceUdon経由で鳴らすと、音の鳴り始めにクリックノイズがのる。

検証

Time.deltaTimeで時間を測って、AudioSourceを1秒毎に鳴らすU#ScriptでAudioSourceを鳴らす。 片方 (左側) は直置きのAudioSource、もう片方 (右側) は親オブジェクトにIs Kinematicのrigid bodyを付けた。 結果は動画のように、rigid bodyを付けた方だけ2回に1回程度の頻度でクリックノイズが乗った。

using UdonSharp;
using UnityEngine;
using VRC.SDKBase;
using VRC.Udon;

public class Test_spatialaudio : UdonSharpBehaviour
{
    public AudioClip clip;
    public AudioSource source;
    private float t;
    private void Update()
    {
        if (t > 1.0f)
        {
            source.PlayOneShot(clip);
            t = 0.0f;
        }
        t += Time.deltaTime;
    }
}

解決策

AudioSourceの親オブジェクトにRigidbodyを付けるのは、Pickupオブジェクトから音を鳴らすなど、音をあるオブジェクトの動きに追従させたい時。 なので、追従を親子関係ではなく、Script経由で行えば、親オブジェクトにRigidbodyを置くことを回避し、クリックノイズを避けることが出来る。

さいごに

VRC_SpatialAudioSource、ひとまず目に見えた問題は回避されましたが他にもどこに地雷が潜んでいるのかわからないのでちょっともう触りたくないですね...。

一応検証に使ったワールドを置いておきます。

https://vrchat.com/home/launch?worldId=wrld_095d2450-a9b3-482e-ac7a-8552a1e804e3&instanceId=77423~private(usr_08f6abeb-183b-463e-b355-53978f61b24d)~canRequestInvite~region(us)~nonce(e81eb2a7-2ab0-474b-8e19-def0946c6fc2)