音の鳴るブログ

鳴らないこともある

音楽用CoffeeScriptを作ろう (7) 音のでる仕組みについて

音のでる仕組みについて説明しようと思う。

Demo - CoffeeCollider

s = Synth.def (freq=440)->
  Out.ar(0, SinOsc.ar(freq))
.play()

setInterval ->
  s.set freq: Math.random() * 880 + 220
, 100

制御の部分ができていないので、これは setInterval を使っていたりする悪い例なんだけど、こういう感じでピロリラピロリラ鳴る。ポイントとしては Synth.def で音の定義をしているところと setメソッド でパラメーターを変更しているところ。実際にやっていることは 簡易版SuperCollider といった具合なんで、SuperColliderの実装についてもなんとなく分かるようになる。

Synth.def

Synth.def関数でどういう音なのかを定義する。以下、注意すべき点。

  • 直ちに関数が実行されて SynthDef オブジェクトを返す
  • 関数の引数は変換される
  • 出力するなら Out オブジェクトを使う
  • return 値は意味を持たない

「関数の引数は変換される (キリッ」って言われても意味不明だと思うので詳しく書くと上のコードはこういう感じに変換されて実行される。

s = Synth.def((freq=440)->
  Out.ar(0, SinOsc.ar(freq))
, "freq=440").play()

ちょっと雑な感じなんだけど、引数の内容が文字列として挿入するように変換されて、Synth.def関数は引数の名前、値を知ることができる。ちなみに SuperCollider は普通に関数の引数情報を取得できる。その情報をもとに OutputProxy というオブジェクトを生成して関数呼び出しに使う。つまり関数実行時の freq は 440 を指す(もうちょっと詳しく書くとパラメータを格納する配列のインデックス 0 の要素を指す) OutputProxy になっている。

関数が実行されると以下のような木が生成される。分かりにくいけど ルートノードが Out でその子が 0, SinOsc、SinOsc の子が 0, OutputProxy といった具合。

 [ 440 ] - Control - OutputProxy -+
 0 -------------------------------+ SinOsc -+
 0 -----------------------------------------+ Out 

木が生成された後は処理の順番に並び替えたリストを生成する。何かの入力になるオブジェクトは先に処理する必要があるとかそういう感じで並び替える。トポロジカルソート。

[ Control, SinOsc, Out ]

実際はどのオブジェクトのどの入力を使うとか色々情報はあるけど図では省略。OutputProxyは役目を果たして退場している。信号処理はリストを順番に処理していけば良い。あと、Out はバス(信号のたまり場みたいなところ、いっぱいある)に出力を書き込んで、最終的に 0番目と 1番目のバスの信号が耳に届く。

SynthDef を play すると リスト化された情報を使って Synth オブジェクトが生成されて音がでる。play のたびにごちゃごちゃ計算したりしない。したがって Synth.def に指定した関数は最初の一回しか実行されない。だから Synth.def 内で freq = Math.random() * 880 + 220 みたいにしても、freq の値は定数として固定される。

setメソッド

set メソッドは引数のパラメータと SynthDef で定義されているパラメータを比べて、適切なパラメーターの値を変更する。今回の例で言うと最初の 440 を格納している箇所を探し出して書き換える。

そのようにして、関数で定義した引数を名前指定して再設定できるようになっている。