Zenn
Open13

RNBOをWebに導入するコツなど

yuichkunyuichkun

前提として、Maxはある程度かけることを想定している。

その上でこちらのgetting started をとりあえず一通り目を通すとよい。

yuichkunyuichkun

RNBOからweb向けにexportする方法はこちら を参考にするとよい。

exportすると、3種類のファイルが吐き出される。

  • patch.export.json
    • 本体。このjsonファイルを @rnbo/js に与えると、なぜかwebで動くようになる。
    • このjsonファイルの中に wasmっぽいものがあるので、それを直接実行しているらしい。
  • dependencies.json
    • 依存しているバッファーなどを記述する場所。あとでもう少し説明する。
  • patch.export.license
    • ライセンスについて書かれたファイル
yuichkunyuichkun

で、ここからはWeb側の話をする。

CDN等を使うこともできるが、普通はnpmを使うので、その前提で読んでほしい。

npm install @rnbo/js

これでnpmパッケージがインストールできるが、この際に、書き出したRNBOデバイスのRNBOバージョンと一致している必要がある。
一致していない場合、ランタイムでエラーが起きるが、
下図のように親切にどのバージョンを入れればいいのかが出てくれる。

なので上図の場合、パッチャーのバージョンに合わせて、

npm install @rnbo/js@0.16.0

とすればよい

yuichkunyuichkun

ところで、Webの開発に明るくない人向けに、先に断りを入れておかなければいけない話がある。

それは、ブラウザというものは開発者が自由にバカスカと音を出していい場所ではない ということである。

どういうことかというと、「ページを開いただけでユーザーが何もしていないのに音が出る」ということは多くのブラウザで 仕組み上できない。

しようとしても、The AudioContext was not allowed to start. It must be resumed (or created) after a user gesture on the page というエラーが出て、音がならない。

この辺り、この記事に結構詳しく書かれているが、
ようするに、ユーザーがなにがしかの操作(クリックなど)をした後にのみ、音を鳴らすことが許される。

なので、ここから先の処理は、ユーザー起因のUIからのイベント発火の後に実行されなければいけない。

yuichkunyuichkun

ここでは、簡単にページ内にボタンがあり、それが押下された時に onClick という関数が実行されることとする。

async function onClick() {
  //ここに処理を飼いていく
}

まず必要なことは、AudioContextの初期化だ。
AudioContextWeb Audio APIの一部で、Web Audio APIの力によってRNBOがWebで動いている。

const ctx = new AudioContext();
await ctx.resume(); // 音を鳴らすことを許す
// ↑この行はなくても動く場合がある
yuichkunyuichkun

次に、先にexportしたRNBOパッチをどこかしらから取得する必要がある。

最悪、ソースコード内にjsonをハードコーディングしたって死にはしないが、とても行儀が悪いので、
どこかしらのサーバーに置いておきランタイムで取り寄せるのが一般的であると思われる。

const rawPatcher = await fetch("/path/to/patch.export.json"); // これでhttpリクエストを投げることができる
const patcher = await rawPatcher.json();
yuichkunyuichkun

つぎはRNBOデバイスの初期化処理をする

モジュールのimportについては、viteparcelなどのモジュールバンドラーを使っていれば、勝手にやってくれるし、昨今のSPAフレームワークはだいたいout-of-the-boxでそういうのが入っているので、気にせずに import しよう

import { createDevice } from "@rnbo/js"

const device = await createDevice({ context, patcher });

これで実はもう あなたのRNBOデバイスはWeb上に生きている! 🎉

が、このままでは音がならない。
なぜか?

RNBOが動いていても、そこから出る音がどこにも接続されていないからだ。

出力先デバイスについては、AudioContext が持っているので

device.node.connect(context.destination);

これで音が出る。

yuichkunyuichkun

ところで、AudioContext.destination はデフォルトでは、OSの出力先に設定されているオーディオデバイスが選択されるようであるが、どうやら最近のChromeのリリースによって、新しいChromeなら選択的にセットすることができるようになったらしい。

https://developer.chrome.com/blog/audiocontext-setsinkid/

これは地味に嬉しいことで、オーディオ出力先選択UIで出力先を選択できるようにすれば、
たとえば Loopback のような仮想オーディオデバイスを選択し、
Web上で動くRNBOデバイス -> Loopback -> DAW のように音を直接流すことができる。

そして、それをするためにわざわざOSのオーディオ設定を切り替えなくてよいのは、結構嬉しいことな気がする。

yuichkunyuichkun

さて、パッチによっては、bufferなどの依存リソースを必要としているものもあるだろう。

これを有効にするためには

// export時に生成されたdependenciesのmapのようなファイルを取得する
const dependencies = await (await fetch("dependencies.json")).json();
await device.loadDataBufferDependencies(dependencies); // bufferをロードする

ちなみにこの dependencies.json は下記のような感じのファイルになっている

[
  {
    "id": "buf_sample",
    "file": "Plaits_20200805_10.wav"
  },
  {
    "id": "buf_win",
    "file": "sinwindowExp055_48000.wav"
  }
]

そして、この file プロパティに指定されているファイルを読みにいくので、ここのパスがサーバーにおいてあるリソースのURLのパスと一致している必要があることに注意

yuichkunyuichkun

あとからRNBOデバイス内で使っているバッファーを書き換える方法

dependencies.json にあらかじめ指定したファイルしかバッファーとして使えないのかというと、そんなことはない。

await device.setDataBuffer("buf_sample", audioBuf);

このような形で、差し替えたいバッファー名(パッチで使われているバッファー名に対応する)と、
AudioBuffer 型であるデータを突っ込めば、いつでもバッファーは差し替えられる。

この AudioBuffer はどのように作るのかというと、

const audioBuf = await context.decodeAudioData(arrayBuf);

このように、ArrayBuffer をもとにして作ることができる。

ArrayBuffer とはJSの世界で用いられる、データバッファーを示すためのジェネリックなオブジェクトであり、色々な方法で作ることができる。
可能な方法の一例でいうと、

  • ユーザーが選択したファイルを読んで作る (File)
  • ブラウザがアクセスできるオーディオ端末から録音して作る
  • JSで命令的に1byteずつ詰めてつくる
yuichkunyuichkun

ここからハンズオン会

これでsvelteアプリをつくる

npm create vite@latest single-motion-granular-2 --template svelte
ログインするとコメントできます