音の鳴るブログ

鳴らないこともある

neume.js - library for web audio programming

またか。と言われてしまいそうだけど、またウェブ楽器プログラミング用のライブラリを作った。

以前に timbre.js というライブラリを作ったのだけど、それの刷新版という感じです。

特徴1: 機能性よりパフォーマンスを重視

以前は多くの動作環境をサポートするためにすべての信号処理を JavaScript で書いていたのですが、それゆえにパフォーマンスに問題があって(Issueでよく指摘された)、neume.js では IE や node.js での動作を諦めて Web Audio API の機能を大活用することでパフォーマンス問題を解決しました。デモでは three.jsgretro と一緒に動作する様子が確認できます。

特徴2: Web Audio API の面倒な部分を抽象化

Web Audio API は多機能なんだけど、部品が細かすぎて使いこなすのが難しいと思います。

例えば 1秒で減衰する三角波 880(±20)Hz を書きたい場合、素の Web Audio API だとこういう感じになると思うのですが、色々書くことが多すぎて辛い気持ちになります。

var tri    = context.createOscillator();
var lfo    = context.createOscillator();
var lfoAmp = context.createGain();
var xline  = context.createGain();

// 基準の三角波(880Hz)
tri.type = "triangle";
tri.frequency.value = 880;

// 揺らすためのサイン波(2Hz)
lfo.type = "sine";
lfo.frequency.value = 2;

// 揺らす大きさ (±20)
lfoAmp.gain.value = 20;

// 1秒の減衰
xline.gain.setValueAtTime(0.25, context.currentTime);
xline.gain.exponentialRampToValueAtTime(0.001, context.currentTime + 1);

// 各ノードを接続
lfo.connect(lfoAmp);
lfoAmp.connect(osc.frequency);
osc.connect(xline);
xline.connect(context.destination);

// オシレーターを開始
lfo.start();
osc.start();

// 一秒たったら止める
setTimeout(function() {
  lfo.stop();
  osc.stop();
  xline.disconnect();
}, 1000);

neume.js だとこういう風に書けます。これが分かりやすいと思うのにも時間がかかりそうだけど、オーディオグラフがそのままオブジェクトになっている感じです。

var Synth = new Neume(function($) {
  return $("xline", { start: 0.25, end: 0.001, dur: 1 }, 
    $("tri", { freq: $("sin", { freq: 2, mul: 20, add: 880 }) })
  ).on("end", function(e) {
    // xline が終わったら呼び出される
    this.stop(e.playbackTime);
  });
});

new Synth().start();

以下のようなオーディオグラフが生成されます。

                           +-------------------+
                           | OscillatorNode    |
                           | - type     : sine |
                           | - frequency: 2    |
                           | - detune   : 0    |
                           +-------------------+
+-----------------------+    |
| OscillatorNode        |  +------------+
| - type     : triangle |  | GainNode   |
| - frequency: 880      |--| - gain: 20 |
| - detune   : 0        |  +------------+
+-----------------------+
  |
+---------------------+
| GainNode            |
| - gain: 0.25->0.001 |
+---------------------+
  |

揺れるスピードをどんどん早くしたくなってもちょっとの修正でOK。

var Synth = new Neume(function($) {
  return $("xline", { start: 0.25, end: 0.001, dur: 1 }, 
    $("tri", {
      freq: $("sin", {
        // ここが変わった
        freq: $("xline", { start: 0.5, end: 10, dur: 20 }),
        mul: 20, add: 880
      })
    })
  ).on("end", function(e) {
    this.stop(e.playbackTime);
  });
});

new Synth().start();

どうですか?

音を作る作業にはどうしても試行錯誤が必要なので、だいたい同じようなインターフェースで適当に書いたら適切になるように工夫しました。まだ書き方によっては無駄のあるオーディオグラフが生成されたり、必要なインターフェースが足りていない等の改善点は色々あるのですが、とりあえず作業的にひと段落したので、使ってもらって様子を見たいという感じです。

ドキュメントも雑な感じになってしまったので、興味があるけど良く分からないという方はツイッター@mohayonao に聞くとか、GitHub Issue で聞くとか、関西なら呼び出しに応じられると思うので呼び出すとかしてください。

という感じです。よろしくお願いします。