音の鳴るブログ

鳴らないこともある

音楽用CoffeeScriptを作ろう (4) 演算子のオーバーロード

演算子のオーバーロードのしくみを実装した。

書いたコードをリンク化して共有できる機能をつけたので以下のリンクで試して結果をウェブコンソールに表示できる。ついでに試験用の機能としてシフトキーを押しながら Run すると変換された CoffeeScript のコードを確認できるようにした。

[0...10] * 10 100 + [1, 2, 3] * 11 +!![0..5] 10 / 10pi "abc" * 3

で、演算子をオーバーロードして何がしたいかというとこういうこと。

[0...10] * 10 # => [ 0, 10, 20, 30, 40, 50, 60, 70, 80, 90 ]
[ 1, 2, 3 ] + [ 100, 200, 300 ] # => [ 101, 202, 303 ]
"abc" * 3 # => "abcabcabc"
SinOsc.ar(440) * 0.5 + Noise.ar() * 0.01

特に一番下が重要で、音量を小さくしたり加算合成したりするときに二項演算子が使えるとかなり直感的になる。

しかし JavaScriptではどう頑張っても演算子のオーバーロードはできないので、以下のようにプロトタイプ関数を使った演算に書き換えて実現した。具体的には CoffeeScript.tokens でトークンに分割してから置き換えたい演算子を捜して、前後のオペランドを(括弧や関数呼び出しに注意して)探し出して、トークンを削除したり追加したり置き換えたり放置したりしている。ちなみにプロトタイプの拡張は WebWorker 内で行っているのでグローバルな領域が汚染されることはない。だからウェブコンソールで直接実行したりはできない。

10pi      // -> (10 * Math.PI) -> (10..__mul__(Math.PI))
a + b * c // -> a + (b * c)    -> a.__add__(b.__mul__(c))
-(a + b)  // -> (a + b).neg()  -> (a.__add__(b)).neg()

当然、素の CoffeeScript や JavaScript に比べるとパフォーマンスは下がるのだけど、メインのプログラムから呼ばれるサブ言語的な位置づけで音楽的な制御をして音を鳴らすための関数を呼んだりする程度では問題にはならないと思う。

オーバーロードしたのは単項演算子と四則演算だけで他はどうするか迷っている。比較演算子なんかをオーバーロードするとループのときに困りそう。あと、文字列の割り算などは未実装で、未実装のままエラーにするか文字列の分割に割り当てるかとか考えている。

ソースは結構単純。おかしな演算するときがあるのでちょこちょこ直している。

ソースコードの書き換えができてしまうと、あとはライブラリの実装だけになる。つぎは音を出す仕組みを考えようと思う。面倒くさそう。