音の鳴るブログ

鳴らないこともある

お焚き上げ2015

年末なので今年書いた npm モジュールをまとめてみます。


OscMsg v0.3.0

OSC のメッセージを JSON にデコード/エンコードするライブラリ。去年からちょくちょく OSC を使って Node.js と openFrameworks や Max/MSP と連携する案件を手伝ったりしていたので非常に重宝した。似たようなライブラリに node-osc-min というのがあるのだけど、壊れたデータを渡したときに例外が出るのとかブラウザ対応がいまいち気に入らなかったので自分で作った。とはいえブラウザで使うことはないなーという印象。


WebAudioScheduler v1.0.0

Web Audio API を使うときに必読の この記事 をベースに作ったスケジューリングライブラリ。時間と関数とパラメータを登録しておくと良いタイミングで呼び出してくれる。Web Audio API を使うときはこれがないと何もできないというレベルで重宝した。名前に反して Node.js でも動くので、そちらでも大活躍。(Web Audio API で使うときは AudioContext#currentTime の時間を指定、そうでないときは Date.now() / 1000 の時間を指定できる)


ADSREnvelope v1.0.0

Web Audio API で ADSRエンベロープを作るのが意外と難しいので、それだけのために作った。ADSR の各パラメータを設定すれば、それに応じた AudioParam 向けのメソッドを生成してくれたり、x秒時点の値を計算してくれたりする。後者の機能は Node.js でも動くので、そちらでも大活躍。デモサイトではエンベロープを可視化したりできる。


Ciseaux v0.4.0

Web Audio API の AudioBuffer を切ったり貼ったり編集できるやつ。がーっと編集して最後に WebWorker でレンダリングするみたいな少し凝った構成になっていて、作って満足した感があった。wav の読み込み/書き出しのみなら Node.js でも動く。


InlineWorker v1.0.0

github.com

関数からWebWorkerを作るやつ。上のライブラリで使っている。Node.js でも動く。ここに書いたやつ。


SeqEmitter v1.2.0

簡単にシーケンサーを作るためのベースモジュール。楽譜情報のイテレーター (timeという要素を返すオブジェクトを返すやつ) を渡すと良いタイミングでイベントとして発火してくれる。


IntervalIterator v1.0.0

上のモジュールで使っているやつ。time という要素を持つオブジェクトを返すイテレーターをラップして、1秒単位とかでまとめて返してくれるイテレータ


MIDIKeyboard v0.2.1

MIDI キーボードの演奏情報をイベントエミッターの API で受信できるやつ。Web MIDI API と Node.js で使える。


LaunchControl v0.4.3

Novation LaunchControl の捜査情報をイベントエミッターの API で受信できるやつ。Web MIDI API と Node.js で使える。


MIDIDevice v0.4.1

上記の2つのベースになっているモジュール。Web MIDI API と node-midi の差を吸収するみたいなやつ。ここに書いたように親クラスを切り替えて継承して使う。


他にもあったと思うけど忘れた。という感じ。

だいぶ前に作ったライブラリが未だに人気があるので、来年はこれをやり直そうかなと思っていて、さいこうの仕様を考えながら年越しをしたいと思います。

Google Chrome の右上の名前をだるくなくした

Google Chrome の右上に本名が表示され続けてだるい。前は簡単に消せたけど今回のは黒い画面で何か入力しないといけないとか意味不明で困っていたのだけど、名前のところをクリックすると編集可能っぽいので、できるだけだるくない名前にした。

f:id:mohayonao:20150729061252p:plain

f:id:mohayonao:20150729061302p:plain

f:id:mohayonao:20150729061317p:plain

f:id:mohayonao:20150729061847p:plain

f:id:mohayonao:20150729061852p:plain

Web Music ハッカソン で Tシャツ をもらいました

Web Music ハッカソン で WebSocket で演奏情報を分散配信して複数のブラウザでひとつの音楽を演奏するシーケンサを作って、頑張った賞みたいなので LittleBits のTシャツをもらいました。

コンセプト的なやつ

f:id:mohayonao:20150726201422p:plain

  • サーバーがある
  • サーバーは音楽の演奏情報を持っている
  • MIDIコントローラー等でサーバーの演奏情報を操作できる
  • たくさんのクライアントがある (10-50台くらいを想定), WebSocket で接続
  • それぞれのクライアントにバラバラに演奏情報 (JSON) を送信する
  • 空間全体でひとつの音楽になる

経緯

去年からちょくちょくウェブオーディオを使ったインスタレーションとかライブを手伝っているのですが、そこで得られたノウハウが全然形として残っていなかったので整理したかったのと、あと今後のために試しておきたかったこととか React とか flux とかの最新のトレンドとかを全部ぶっこんでみたかった。

当日のハック

諸事情あってかなりの張り切りモードになっていたせいで事前に準備しすぎて、あとは細かい調整するくらいしかできない状態だったのだけど、当日一緒になった方々に機能を追加してもらえたりハッカソンぽい感じで作業できた。僕は最強JSビルド環境厨みたいなところがあって、何も考えずに Node.js で ES6 で React で Flux (しかも自分で実装したやつ) で npm run scripts を使っていてかなり制限のきつい状況だったのだけど、うまい具合に対応してもらえてよかった。

スマートフォンの向きでシンセリードのフィルターのかかり具合を制御できる機能を追加してくれた

1x1 の Canvas がチカチカするだけだった画面に 波形 と スペクトラム を表示してくれた

ありがとうございました!

デモ

会場のWiFiが想定外に悪くて全然できなかった。どこか10台以上のクライアントを使ってデモさせてくれるところないですかね?

一台のPCで9つのブラウザウインドウを開いて実行している画面。違う音が出ているので画面の表示はバラバラ、同時に聞くとひとつの音楽になる。本来はこれをハッカソン参加者のそれぞれのPCで実行するつもりだった。

f:id:mohayonao:20150727102503p:plain

一応サーバーがなくても動くスタンドアロン版も用意しているので、雰囲気をつかむだけなら試せる。こっちはめっちゃ React ってる。演奏情報のメッセージングをサーバーを介するかクライアント単体で完結させるかだけなのでベースのコードはほとんど共有できている。

http://mohayonao.github.io/web-music-hackathon-04/

f:id:mohayonao:20150727103701p:plain

懇親会

  • @sascacci さんと楽器の手触り感の気持ちよさの話
  • @aike1000 さんと開発環境とかテストの話
  • @g200kg さんのGPUを使った信号処理の話

あと、今度 YAMAHA から発売される reface はスピーカーがついてて良いみたいなこととか DX7II の思い出みたいなことを話していた気がする。

WebMusicハッカソンで使えそうな俺俺ライブラリ 5選

7月25日に WebMusicハッカソン #4 @kyoto というのがあるのですが、ちょうど良い機会なので僕が作ったウェブ音楽用簡単超絶便利ライブラリを紹介したいと思います。


WEB AUDIO SCHEDULER

2 つの時計のお話 - Web Audio の正確なスケジューリングについて - HTML5 Rocks

Web Audio で必須な割に難しいのがスケジュール管理で、いちおう上の記事のやり方が推奨なのですが、そのままやるとかなり面倒くさい。のだけど、このライブラリを使うと面倒な部分は気にせず、いつ何をしたいのかを書くだけで良くなる。以下は簡単なメトロノームの例。

var gcguard = [];
var audioContext = new AudioContext();
var scheduler = new WebAudioScheduler({ context: audioContext });

function metronome(e) {
  // e.playbackTime が WebAudio 的な時間
  // 0.5秒間隔で ticktack 関数の呼び出しを引数付きで登録
  scheduler.insert(e.playbackTime + 0.000, ticktack, [ 880, 1.00 ]);
  scheduler.insert(e.playbackTime + 0.500, ticktack, [ 440, 0.05 ]);
  scheduler.insert(e.playbackTime + 1.000, ticktack, [ 440, 0.05 ]);
  scheduler.insert(e.playbackTime + 1.500, ticktack, [ 440, 0.05 ]);
  // 2秒後にこの関数を呼び出ししなおす
  scheduler.insert(e.playbackTime + 2.000, metronome);
}

function ticktack(e, frequency, duration) {
  var playbackTime = e.playbackTime;
  var osc = audioContext.createOscillator();
  var amp = audioContext.createGain();
  var t0 = playbackTime;
  var t1 = t0 + duration;

  osc.frequency.value = frequency;
  amp.gain.setValueAtTime(0.4, t0);
  amp.gain.linearRampToValueAtTime(0, t1);
  
  osc.start(t0);
  osc.stop(t1);

  osc.onended = function() {
    osc.disconnect();
    amp.disconnect();
    gcguard.splice(gcguard.indexOf(osc), 1);
  };
  gcguard.push(osc);

  osc.connect(amp);
  amp.connect(audioContext.destination);
}

scheduler.start(metronome);

WEB AUDIO API SHIM

JavaScript - Web Audioの新しいAPIについてざっくり解説 - Qiita

Web Audio の新しい API のポリフィル。StereoPannerNode でオートパンったり、getFloatTimeDomainData で きれいな波形を描画 したり、promise-based API でモダンなプログラミングができるようになる。light版がおすすめ。以下は fetch API からの簡単なオートパンの例。

var gcguard = [];

fetch("amen.wav").then(function(res) {
  return res.arrayBuffer();
}).then(function(audioData) {
  return audioContext.decodeAudioData(audioData);
}).then(function(buffer) {
  var bufSrc = audioContext.createBufferSource();
  var panLFO = audioContext.createOscillator();
  var panner = audioContext.createStereoPanner();
  var t0 = audioContext.currentTime;
  var t1 = t0 + 30;

  bufSrc.buffer = buffer;
  bufSrc.loop = true;
  panLFO.frequency.value = 2;
  
  bufSrc.start(t0);
  panLFO.start(t0);
  bufSrc.stop(t1);
  panLFO.stop(t1);

  bufSrc.onended = function() {
    bufSrc.disconnect();
    panLFO.disconnect();
    panner.disconnect();
    gcguard.splice(gcguard.indexOf(bufSrc), 1);
  };
  gcguard.push(bufSrc);
  
  bufSrc.connect(panner);
  panLFO.connect(panner.pan);
  panner.connect(audioContext.destination);
});

MIDI KEYBOARD

MIDIキーボードの演奏情報をイベントエミッターなAPIで受信できるやつ。Web MIDI API はもちろん、Node.js でも動くのでサーバーサイドのコントローラーとしても使える。以下は M-AUDIO の Keystation Mini 32 を開いて、演奏情報をダンプする簡単な例。

// 開きたいデバイスの名前を指定して new する
var midiKey = new MIDIKeyboard("Keystation Mini 32");

midiKey.open().catch(function(e) {
  console.error(e);
});

midiKey.on("message", function(e) {
  console.log("dataType  : " + e.dataType);
  console.log("noteNumber: " + e.noteNumber);
  console.log("velocity  : " + e.velocity);
  console.log("value     : " + e.value);
  console.log("channel   : " + e.channel);
});

MIDIキーボードの名前は以下のように取得できる。

MIDIKeyboard.requestDeviceNames().then(function(devies) {
  console.log(devices);
});

MIDI DEVICE

MIDIキーボード以外も使いたい人向け、継承するなりして _onmidimessage を上書きすれば簡単にMIDIコントローラーのライブラリが作れる。上の MIDI キーボード以外に Novation LAUNCH CONTROL 用のがあるので参考になりそう。Web MIDI API, Node.js 以外にテスト用インターフェースもあるので CI とか実デバイスがない状態でも使えたりもする。以下はなんか適当で簡単な例。

var midiDevice = new MIDIDevice("Super MIDI Controller");

midiDevice.open().catch(function(e) {
  console.error(e);
});

midiDevice._onmidimessage = function(e) {
  if (e.data[0] & 0x90 === 0x90) {
    this.emit("noteOn");
  }
};

midiDevice.bang = function() {
  this.send([ 0x90, 0x64, 0x64 ]);
};

OSC MSG

ブラウザでOSCを読み書きするライブラリ。もともと osc-min というライブラリを使っていたのだけど、browserify するとサイズが大きくなるのと、壊れた OSC を受信したときに例外を出すのが困るので自分で書いた。基本的なAPIは osc-min と互換性があるし、もちろん Node.js でも使える。以下は Max/MSPとブラウザでの簡単で意味のないやり取りの例。

f:id:mohayonao:20150707210301p:plain

var path = require("path");
var express = require("express");
var socketIO = require("socket.io");
var http = require("http");
var dgram = require("dgram");
var app = express();
var server = http.createServer(app);
var webSocket = socketIO(server);
var oscSocket = dgram.createSocket("udp4");

app.use(express.static(path.join(__dirname, "./public")));

server.listen(8000, function() {
  console.log("Listening HTTP on port %d", server.address().port);
});

webSocket.on("connect", function(socket) {
  // ブラウザから Max/MSP に転送
  socket.on("/osc", function(buffer) {
    oscSocket.send(buffer, 0, buffer.length, 7401, "127.0.0.1");
  });
});

// Max/MSP からブラウザに転送
oscSocket.on("message", function(buffer) {
  webSocket.emit("/osc", buffer);
});

oscSocket.bind(7400, function() {
  console.log("Listening OSC on port %d", oscSocket.address().port);
});
<button id="bang">BANG</button>
<script src="/socket.io/socket.io.js"></script>
<script src="/osc-msg.js"></script>
<script>
window.onload = function() {
  var socket = io();

  function receiveOSC(buffer) {
    var msg = OscMsg.fromBuffer(buffer);

    if (msg.elements) {
      msg = msg.elements[0];
    }

    msg.args = msg.args.map(function(value) {
      return value.value;
    });

    console.log(JSON.stringify(msg));
  }

  // WebSocket 経由で Max/MSP に送信
  function sendOSC(msg) {
    socket.emit("/osc", OscMsg.toBuffer(msg));
  }

  function bang() {
     sendOSC({
      address: "/noteOn",
      args: [
        { type: "integer", value: ((Math.random() * 24) + 48)|0 },
        Math.random() * 128, // 型を指定しない数値は Float になる
      ],
    });
  }

  // Max/MSP から WebSocket経由で受信
  socket.on("/osc", receiveOSC);

  document.getElementById("bang").onclick = bang;
}
</script>

CISEAUX

AudioBuffer を切り貼り編集するやつ。分割したり逆回転させたり重ねたり色々できる 。wavファイルなら Node.js でも使える。以下は AudioBuffer を 100 分割して stutter して適当に並び替える簡単な例。

var gcguard = [];

Ciseaux.from("amen.wav").then(function(tape) {
  // 100分割
  var tapes = tape.split(100).map(function(tape) {
    // それぞれを3回繰り返し
    return tape.repeat(3);
  });
  
  // くっつけてちょっと早くする
  tape = Ciseaux.concat(_.shuffle(tapes)).pitch(1.5);

  return tape.render();
}).then(function(buffer) {
  var bufSrc = audioContext.createBufferSource();
  var t0 = audioContext.currentTime;
  var t1 = t0 + 30;

  bufSrc.buffer = buffer;
  bufSrc.loop = true;

  bufSrc.start(t0);
  bufSrc.stop(t1);

  bufSrc.onended = function() {
    bufSrc.disconnect();
    gcguard.splice(gcguard.indexOf(bufSrc), 1);
  };
  gcguard.push(bufSrc);

  bufSrc.connect(audioContext.destination);
});

STEREO ANALYSER NODE

AnalyserNode のステレオ版。getFloatFrequencyData などのメソッドが左右 2つ指定できる。以下はオーディオファイルを読み込んでからの左右の周波数スペクトラムを表示する簡単な例。

var gcguard = {};

fetch("amen.wav").fetch(function(res) {
  return res.arrayBuffer();
}).then(function(audioData) {
  return new Promise(function(resolve, reject) {
    audioContext.decodeData(audioData, resolve, reject);
  });
}).then(function(buffer) {
  var bufSrc = audioContext.createBufferSource();
  var analyser = new StereoAnalyserNode(audioContext);
  var t0 = audioContext.currentTime;
  var t1 = t0 + 30;
  var timerId = 0;

  bufSrc.buffer = buffer;
  bufSrc.loop = true;

  bufSrc.start(t0);
  bufSrc.stop(t1);

  bufSrc.onended = function() {
    bufSrc.disconnect();
    analyser.disconnect();
    clearInterval(timerId);
    gcguard.splice(gcguard.indexOf(bufSrc), 1);
  };
  gcguard.push(bufSrc);

  var L = new Float32Array(analyser.frequencyBinCount);
  var R = new Float32Array(analyser.frequencyBinCount);

  timerId = setInterval(function() {
    analyser.getFloatFrequencyData(L, R);
    drawSpectrum(L, R);
  }, 100);

  bufSrc.connect(analyser);
  analyser.connect(audioContext.destination);
});

MML EMITTER

MMLで書いた演奏情報を良い感じのタイミングでイベント発火してくれるやつ。便利なので良いのだけど、変な機能があったりコードが酷いのでもうちょっと綺麗な感じに破壊的に書き直ししたい。以下はドレミファソラシドを演奏する簡単な例。

var gcguard = [];
var mml = new MMLEmitter(audioContext, "t120 l8 $ cdef gab<c c>bag fedc; t120 l2 o3 $ ccee>aa<dd");

mml.tracks[0].on("note", noteOn);
mml.tracks[1].on("note", noteOn);

function noteOn(e) {
  var frequency= e.frequency;
  var duration = e.duration;
  var playbackTime = e.playbackTime;
  var osc = audioContext.createOscillator();
  var amp = audioContext.createGain();
  var t0 = playbackTime;
  var t1 = t0 + duration;

  osc.frequency.value = frequency;
  amp.gain.setValueAtTime(0.4, t0);
  amp.gain.linearRampToValueAtTime(0, t1);
    
  osc.start(t0);
  osc.stop(t1);

  osc.onended = function() {
    osc.disconnect();
    amp.disconnect();
    gcguard.splice(gcguard.indexOf(osc), 1);
  };
  gcguard.push(osc);
  
  osc.connect(amp);
  amp.connect(audioContext.destination);
}

JS クイズ

Q: 以下のテストをパスしてください

function assert(cond) {
  if (!cond) throw new Error("failed!!");
}

// Foo と Bar は異なる
assert(Foo !== Bar);

// new Foo() は Foo / Bar のインスタンスである
assert(new Foo() instanceof Foo);
assert(new Foo() instanceof Bar);

// new Bar() も Foo / Bar のインスタンスである
assert(new Bar() instanceof Foo);
assert(new Bar() instanceof Bar);

A: https://jsfiddle.net/oz2sfb56/

JavaScriptの小銭リテラルが便利

リテラルとは

コンピュータプログラムのソースコードなどで、特定のデータ型による値を直接表記する際の書式。また、そのような書式に従って記載された値。

JavaScriptにも数値や文字列、オブジェクト、正規表現などのリテラル表現があるのだけど、意外と知られていないものに 小銭リテラル がある。小銭リテラルはその名のとおり小銭を表現する書式で仕様では以下のように定義されている。

KozeniLiteral ::
  DecimalIntegerLiteral KozeniParts(opt)

KozeniParts ::
  KozeniPart
  KozeniParts KozeniPart

KozeniPart ::
  KozeniDelimiter KozeniDigits

KozeniDigits ::
  DecimalDigit DecimalDigit DecimalDigit

KozeniDelimiter ::
  ,

使用例

変数(price)には小銭の部分だけが代入されるので余計な演算をすることなく小銭を取得できる。

var price = 1,234,567; // 小銭リテラル

console.log("小銭は " + price + "円です");
// => 小銭は 567 円です

試しに数値リテラルで同じことをする例も載せるが複雑な演算子を必要としない小銭リテラルの方が明らかにシンプルで便利なのが分かると思う。

var price = 1234567; // 数値リテラル

console.log("小銭は " + (price % 1000) + "円です");
// => 小銭は 567 円です

このように便利な小銭リテラルだけど、エッジすぎる機能なのか残念なことに現時点ではきちんと実装されておらず非常にバギーなので、実装が安定するまでは使うのを控えた方がよさそう。

var price = 1,234,067; // 小銭リテラル

console.log("小銭は " + price + "円です");
// => 小銭は 55 円です (12円の損)

確認されているバグ的なやつ

  • 小銭が少なくなる場合がある
  • strict mode だと SyntaxError になる場合がある
  • 定義されていない書式でも動作することがある

参照

ECMA-262 11.14 コンマ演算子(,)