信号処理をするときに良さそうなデータ構造について
JavaScriptでつくっているサウンドプログラミング用のライブラリ ( timbre ) にデモを追加した。wavファイルを読み込めるようになって、フィルターをLFOで制御したり歪ませたりできるようになった。
凝ったUIを作りたかったけど疲れたので省略したのだけど、よく考えたらウェブコンソールをUIにすれば良いと思ってそこから多少の操作が出来るようにした。Chromeでやるのが推奨。(Firefoxでやると巨大なデータを表示しようとしてクラッシュする。参照)
003. load wav files and decode
http://mohayonao.github.com/timbre/examples/003_loadwavfiles.html
ドラムループとピアノの音を読み込んでランダムなメロディーと合わせて演奏しているだけだけど、結構聴いていられると思う。
表題の件
まだ試行錯誤していて仕様が固まっていなくて自分でもきちんと整理できていないので、とりあえず今のところどういう風に動作しているのか書く。
(* (rLPF (sin 0.5 0 100 1400).kr() 0.8 (pulse 1200 0 0.5)) (adsr 50 1000)).play()
これを実行すると、
1200Hzの矩形波にローパスフィルタをかけた音が 50msec で立ちあがり 1000msec で減衰する。ローパスフィルタのレゾナンスは 0.8 で、カットオフ周波数は 1400Hz を中心に2秒周期(0.5Hz)で上下 100Hz 変化する。
このとき、システムの中はこんな感じになっている。
SoundSystem に登録されている(dacs配列に格納されている) dacオブジェクトを起点にグラフを辿って信号処理を行い、その結果を加算合成したものを Web Audio API 等を通じて出力する。
図のとおり Max/MSP にそっくりで、 S式風の記述はそれを表現するのに適している。実際は大幅に修正が必要で似ても似つかないものになりそうだけど、timbreの最初のアイデアはここにあった。Max/MSPと異なるのはアウトレットは必ず1つしかなく、インレットは信号のみを受けつけるものが制限なく使える。*オブジェクトに4つ入力すると全部を積算したものを出力し、ローパスフィルタなら加算した後、フィルタをかける。
だいたいオブジェクトである
図の丸四角で囲ってあるのは信号処理用のオブジェクトで信号処理用メソッドを呼び出すと128サンプル分の結果を返す。数字だけ書いてあるオブジェクトは呼び出すと128サンプル分の同じ数字を返す。すべてのオブジェクトが同じフォーマットで出力するので、好きなように組み合わせて使える。
オーディオレイトとコントロールレイト
図中に赤と青で色わけした。青いオブジェクトはコントロールレイト(kr)で動作する。コントロールレイトで動作する場合、計算を大幅に省略するので処理効率が良くなる。具体的には128サンプルごとに計算する。エンベロープやLFOなど 1サンプルごとに計算する必要がないものはコントロールレイトで動作させる。1サンプルごとに計算する必要がある場合、オーディオレイト(ar)で動作させる。大体のオブジェクトは固定だけど、一部のオブジェクトは切り換えができる。切り換えるのには SuperCollider にならって ar(), kr() メソッドを使う。
playメソッド
図には dac とあるのに、コードにはない。
実は最初のコードをきちんと書くと下記のようになる。
(dac (* (rLPF (sin 0.5 0 100 1400).kr() 0.8 (pulse 1200 0 0.5)) (adsr 50 1000))).on()
playメソッドは、(なければ)自動でdacをつくって自分を登録し、on()を呼び出す。
SuperColliderのチュートリアルを見て、便利そうだったので真似て追加したのだけど、適当に追加したから後でトラブルがあるかもしれない。
仕様が定まっていないのでサンプルコードを作りながら適宜修正しているのだけど、だいたいこんな感じで動いている。グラフでやるのは非常に簡単で良いと思う。インターフェイスは必ずしもグラフである必要はないけれど、それでも実際のデータ構造と近いところで考えられるので、やっぱりグラフが良いと思う。