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

前提として、Maxはある程度かけることを想定している。
その上でこちらのgetting started をとりあえず一通り目を通すとよい。

RNBOからweb向けにexportする方法はこちら を参考にするとよい。
exportすると、3種類のファイルが吐き出される。
-
patch.export.json
- 本体。このjsonファイルを
@rnbo/js
に与えると、なぜかwebで動くようになる。 - このjsonファイルの中に wasmっぽいものがあるので、それを直接実行しているらしい。
- 本体。このjsonファイルを
-
dependencies.json
- 依存しているバッファーなどを記述する場所。あとでもう少し説明する。
-
patch.export.license
- ライセンスについて書かれたファイル

で、ここからはWeb側の話をする。
CDN等を使うこともできるが、普通はnpmを使うので、その前提で読んでほしい。
npm install @rnbo/js
これでnpmパッケージがインストールできるが、この際に、書き出したRNBOデバイスのRNBOバージョンと一致している必要がある。
一致していない場合、ランタイムでエラーが起きるが、
下図のように親切にどのバージョンを入れればいいのかが出てくれる。
なので上図の場合、パッチャーのバージョンに合わせて、
npm install @rnbo/js@0.16.0
とすればよい

ところで、Webの開発に明るくない人向けに、先に断りを入れておかなければいけない話がある。
それは、ブラウザというものは開発者が自由にバカスカと音を出していい場所ではない ということである。
どういうことかというと、「ページを開いただけでユーザーが何もしていないのに音が出る」ということは多くのブラウザで 仕組み上できない。
しようとしても、The AudioContext was not allowed to start. It must be resumed (or created) after a user gesture on the page
というエラーが出て、音がならない。
この辺り、この記事に結構詳しく書かれているが、
ようするに、ユーザーがなにがしかの操作(クリックなど)をした後にのみ、音を鳴らすことが許される。
なので、ここから先の処理は、ユーザー起因のUIからのイベント発火の後に実行されなければいけない。

ここでは、簡単にページ内にボタンがあり、それが押下された時に onClick
という関数が実行されることとする。
async function onClick() {
//ここに処理を飼いていく
}
まず必要なことは、AudioContextの初期化だ。
AudioContext
は Web Audio APIの一部で、Web Audio APIの力によってRNBOがWebで動いている。
const ctx = new AudioContext();
await ctx.resume(); // 音を鳴らすことを許す
// ↑この行はなくても動く場合がある

次に、先にexportしたRNBOパッチをどこかしらから取得する必要がある。
最悪、ソースコード内にjsonをハードコーディングしたって死にはしないが、とても行儀が悪いので、
どこかしらのサーバーに置いておきランタイムで取り寄せるのが一般的であると思われる。
const rawPatcher = await fetch("/path/to/patch.export.json"); // これでhttpリクエストを投げることができる
const patcher = await rawPatcher.json();

つぎはRNBOデバイスの初期化処理をする
モジュールのimportについては、viteやparcelなどのモジュールバンドラーを使っていれば、勝手にやってくれるし、昨今の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);
これで音が出る。

ところで、AudioContext.destination
はデフォルトでは、OSの出力先に設定されているオーディオデバイスが選択されるようであるが、どうやら最近のChromeのリリースによって、新しいChromeなら選択的にセットすることができるようになったらしい。
これは地味に嬉しいことで、オーディオ出力先選択UIで出力先を選択できるようにすれば、
たとえば Loopback のような仮想オーディオデバイスを選択し、
Web上で動くRNBOデバイス -> Loopback -> DAW のように音を直接流すことができる。
そして、それをするためにわざわざOSのオーディオ設定を切り替えなくてよいのは、結構嬉しいことな気がする。

さて、パッチによっては、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のパスと一致している必要があることに注意

あとからRNBOデバイス内で使っているバッファーを書き換える方法
dependencies.json
にあらかじめ指定したファイルしかバッファーとして使えないのかというと、そんなことはない。
await device.setDataBuffer("buf_sample", audioBuf);
このような形で、差し替えたいバッファー名(パッチで使われているバッファー名に対応する)と、
AudioBuffer
型であるデータを突っ込めば、いつでもバッファーは差し替えられる。
この AudioBuffer
はどのように作るのかというと、
const audioBuf = await context.decodeAudioData(arrayBuf);
このように、ArrayBuffer をもとにして作ることができる。
ArrayBuffer
とはJSの世界で用いられる、データバッファーを示すためのジェネリックなオブジェクトであり、色々な方法で作ることができる。
可能な方法の一例でいうと、
- ユーザーが選択したファイルを読んで作る (
File
) - ブラウザがアクセスできるオーディオ端末から録音して作る
- JSで命令的に1byteずつ詰めてつくる

ここからハンズオン会
これでsvelteアプリをつくる
npm create vite@latest single-motion-granular-2 --template svelte