音の鳴るブログ

鳴らないこともある

乱数を使う関数のテスト

function coin(x) {
  return Math.random() < x;
}

こういう関数があったとして、どうテストを書くのか。

ぱっと思いつくのはこういう感じで、Math.random 自体を上書きするやり方。

describe("coin(x)", function() {
  it("works", sinon.test(function() {
    this.stub(Math, "random", function() {
      return 0.5;
    });

    assert(coin(0.4) === false);
    assert(coin(0.5) === false);
    assert(coin(0.6) === true);
  }));
});

このやり方、たしかに間違っていないんだけど、関数のインターフェース自体を変更した方が良いと思う。

function coin(x, random) {
  random = random || Math.random;
  return random() < x;
}

これだとテストがしやすいだけじゃなく、偏った乱数生成器とかシード付きの乱数生成器を使用したりできて便利さ100万倍になる。

describe("coin(x)", function() {
  it("works", function() {
    var random = function() {
      return 0.5;
    };
    
    assert(coin(0.4, random) === false);
    assert(coin(0.5, random) === false);
    assert(coin(0.6, random) === true);
  });
});

参照

JavaScript の this が分からない

ライブラリをES6で書いて公開する所から始めよう | Web Scratch

この記事を読んで、よっしゃ僕も練習がてらライブラリのES6化するぞ!!と思って、とりあえず既存ライブラリのテストだけでもES6化しようと思って脳内コーディングをしたところ、ライブラリ作者にとって Arrow Function が意外と厄介なんじゃないかと感じた。

(実際に試したわけではないので、全体的に勘違いしている可能性があります)

例えば、ちょっと雑いけど以下のようなテストコード。

describe("test", function() {
  it("Math.random", sinon.test(function() { // <- test sandbox
    this.stub(Math, "random", function() {
      return 0.5;
    });
    assert(Math.random() === 0.5);
  }));
  // sinon.test を使えばテストが失敗しても書き換えたスタブは元に戻る
});

sinon の sandbox は this を書き変えるので Arrow Function (this が外側のスコープに束縛される) は使えない。結果 function=> が混在して嫌な感じがする。デンジャラスメル。

describe("test", ()=>{
  it("Math.random", sinon.test(function() { // <- Arrow Function が使えない
    this.stub(Math, "random", ()=>{
      return 0.5;
    });
    assert(Math.random() === 0.5);
  }));
});

これの対処は以下のどちらかになると思うのだけど。まあ後者だろうなぁと思う。

  • ライブラリ使用者が Function式 と Arrow Function を使い分ける
  • ライブラリ作者が call とか apply を使わない

sinon のインターフェースを自分なりに修正するならこんな感じにすると思う。this に頼らず、それ相当のインターフェースを引数として別途提供するみたいな感じ。

describe("test", ()=>{
  it("Math.random", sinon.test((sinon)=>{
    sinon.stub(Math, "random", ()=>{
      return 0.5;
    });
    assert(Math.random() === 0.5);
  }));
});

っていうか CoffeeScript-> みたいの、なんでないのだろう?

OSCを共有する osc-hub というのを作った

openFrameworks や Max/MSP, SuperCollider などで良く使われるメッセージングプロトコルOSC (Open Sound Control) というのがあって、どんなものかについては この辺 を参照して欲しいのだけど、通信プロトコルUDPが使われていて、基本的には A -> B というように一対一でしかメッセージを送信できない*1

そこでOSCを中継するハブのようなものがあれば、複数端末にメッセージ送信ができて便利そうなので作ってみた。頑張って作った構成図とGIFアニメが以下。

f:id:mohayonao:20141203093810p:plain

demo

使い方

具体的なコマンド等は README を参照してもらうとして、大雑把な流れを示す。

  • あらかじめ OscHubServer を立ち上げる
  • 各クライアントで OscHubClient を立ち上げて OscHubServer に接続
  • Max/MSP とかで OscHubClient に設定したポートでOSCを送受信する

用途

インターネット越しに同じMaxパッチを共有して、スクリーン共有みたいな感じでOSCだけを共有して動作を伝えるとか、まったく別々のパッチを同じコントローラで制御するとかに使えると思う。たぶん便利なのでアイデア次第でいろいろ面白いことできそうだけど、便利すぎるのですでに同じようなのがあるのかも知れない。

留意点

ネットワークを介するのでレイテンシーが発生する。

*1:ブロードキャストアドレスを使えば、同一ネットワーク内の全端末に送信できる

ウェブオーディオが使われているライブの様子を見てきた

楽×学(ラクガク)2014~私の「やる!」を興すために~ を開催します/浜松市

浜松で「楽×学 2014~私の「やる!」を興すために~」というイベントが開催されて、その最期を飾る SjQ ライブパフォーマンスでウェブオーディオが使われていたので様子を見に行った。

SjQというのはこういう感じ。

【SjQ++】生演奏とコンピュータを組み合わせ、新しい即興音楽を生み出すバンド - PICK UP | KAKEHASHI

前回 、Cell というインスタレーションを手伝った流れで、そのシステムをライブ仕様に調整して使用することになった。インスタレーションでは、Cell という人工生命デバイスが環境と自身に反応しながらドローンを生成、その合間合間に音の素材をドロップする構造で、現地の音響空間を楽しむ他に、その生成された音をブラウザ経由で聴けるようになっていたのだけど、今回のライブは人工生命デバイスと人間の演奏が相互に反応しながら、生成された音の素材をお客さんのスマートフォンに送信して、演奏に合わせて光り、そして音が鳴るという仕組みを構築した。

仕組み

おおまかな構成はこういう感じ。

+-----+          +--------------+           +------------------+
| SjQ | - OSC -> | SocketServer | - WiFi -> | MobileDevice * N |
+-----+          +--------------+           +------------------+

僕は SocketServer と MobileDevice のプログラムを書いた。SocketServer は SjQ のシステムから受信したOSCメッセージをJSONに変換してブラウザに送るだけで、ブラウザは受信したメッセージに応じて、音を出したり(Web Audio API)、画面を光らせたりする(Canvas)。 音を出す部分は以前に書いたものを使用したので、実際にライブの仕様に合わせて書いたのはサーバー側、クライアント側あわせて 500 行程度。前日のスタジオ練習で雰囲気を確認していったん寝た翌朝、当日の朝に全部書き直す開発手法(development in sleeping)を採用した。

以下は抜粋したコード。

// server.js
var express = require("express");
var oscmin = require("osc-min");
var udp = require("dgram");
var http = require("http");
var socketIO = require("socket.io");

var app = express();
var server = http.createServer(app);
var io = socketIO(server);

app.use(express.static(__dirname + "/public"));

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

var numUsers = 0;
var prevOsc = {};

// お客さんの端末とのソケット接続
io.on("connection", function(socket) {
  numUsers += 1;
  console.log("connected: " + numUsers);
  
  // 接続数に応じて制御の調整を行う
  io.emit("numUsers", {
    count: numUsers, osc: prevOsc
  });

  socket.on("disconnect", function() {
    numUsers -= 1;
    console.log("disconnected: " + numUsers);

    io.emit("numUsers", {
      count: numUsers, osc: prevOsc
    });
  });

  // お客さんの全端末を強制的に音を鳴らす
  socket.on("fire", function(data) {
    io.emit("fire", data);
    console.log("fire!");
  });

});

// SjQのシステムからOSCを受信
udp.createSocket("udp4", function(data) {
  var osc = oscmin.fromBuffer(data);

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

  // お客さんの端末に送信
  io.emit("osc", osc);

  prevOsc = osc;

  console.log(JSON.stringify(osc));
}).bind(OSC_PORT);
// client.js
var socket = io();
var app = new CellLiveApp();

socket.on("osc", function(osc) {
  app.onReceiveOsc(osc);
});

socket.on("numUsers", function(data) {
  app.numUsers = data.count;
  app.setOsc(data.osc);
});

socket.on("fire", function() {
  app.fire();
});

ライブの様子

50分弱のパフォーマンスの間、サーバーに接続があったのは最大時で50台、平均して35台程度で演奏やプロジェクターで投影している映像に合わせて周りを取り囲むお客さんの端末がチカチカ光る。演奏がスパースになった瞬間にスマートフォンからの音が入り込むなど絶妙な融合ができて、パフォーマンスとそれを鑑賞するお客さんという区別が溶けて全員が当事者であるみたいな一体感というか緊張感があった。自分のiPhoneもサーバーに接続していたのだけど、自分の所有物である端末に演奏のフィードバックがダイレクトに伝わってくる感覚は非常に刺激的だった。当初、お客さん全員の端末が一気に爆発したらどうしようみたいな心配をしていたのだけど、爆発した端末がなかったのもよかった。

ウェブオーディオとアート/音楽

以前、自分で書いた内容の引用だけど。目の前で確認したことで、より強く感じた。

現地で生成される音をネットを介して体験することができるのはかなり面白くて、応用方法を色々考えるとアートや音楽と体験者との距離感の新しい選択肢になりえそう。 音楽系のメディアアートだとMax/MSPとかSuperColliderとかが良く使わていて、そういう蓄積のある技術に比べると Web Audio API はまだまだ見劣りする感じだけど、ブラウザがあれば*1専用のアプリケーションやデバイスを用意することなく誰でも手軽に体験を共有できるという点で重要なポジションを担えるんじゃないかなと思います。

今後の課題

いくつか反省点もあったのだけど、書くのが面倒になってきたので箇条書き。

  • 非対応端末(Androidとか)の扱いを雑にしてしまった
    • 非対応な旨を表示するとか対応可能な範囲で動作させるとかすればよかった
  • 端末スペックに応じた処理の切り替え
    • 端末に応じた最高の動作をさせたい (今回は iPhone 4s を目安に調整した)
  • WiFiネットワークの構築
    • もっと大きな規模になった場合にどうしたら良いのかさっぱり分からない

FunctionからWebWorkerを作るやつ作った

今まで Worker を作るときは

  • worker用のファイルを作る
    • 普通のやりかた
  • 1つのファイルで main と worker 用のコードを切り分ける
    • ファイルたくさん作りたくないとき
  • 文字列で worker の中身を記述して Blob 経由で URL を生成する
    • Workerの規模が小さいとき

みたいにしていたけど、3番目のやり方をもうちょっと便利にした。

使い方

バックグラウンドで動くタイマー (タブを切り替えても精度が落ちないやつ)

var timer = new WorkerBuilder(function(onmessage, postMessage) {
  // ここが worker の中身
  // 引数は無視されるのでなくても良い (lint対策)

  var t = 0;

  onmessage = function(e) {
    if (t) {
      clearInterval(t);
    }

    t = 0;

    if (typeof e.data === "number" && e.data > 0) {
      t = setInterval(function() {
        postMessage(null);
      }, e.data);
    }
  };

}).build(); // build() で Worker を生成する

// wb.createURL() で URL だけ取得することもできる


// あとは普通の Worker として使える
timer.onmessage = function() {
  console.log("!");
};

timer.postMessage(1000);

仕組み

単純に関数を文字列化して中身を取り出す -> Blob -> URL.createURL しているだけです。

用途

Web Audio APIに将来的に導入される AudioWorkerNode はノード毎にファイルを用意させるみたいな仕様っぽいので、そんな面倒なこと絶対にしたくない気持ちで作った。動かせる環境がないので確認できないけど以下のように使えると思う。

var bitcrusher_worker = new WorkerBuilder(function(onaudioprocess) {
  var phaser = 0;
  var lastDataValue = 0;

  onaudioprocess = function (e) {
    for (var channel = 0; channel < e.inputBuffers.length; channel++) {
      var inputBuffer = e.inputBuffers[channel];
      var outputBuffer = e.outputBuffers[channel];
      var bufferLength = inputBuffer.length;
      var bitsArray = e.parameters.bits;
      var frequencyReductionArray = e.parameters.frequencyReduction;

      for (var i = 0; i < bufferLength; i++) {
        var bits = bitsArray ? bitsArray[i] : 8;
        var frequencyReduction = frequencyReductionArray ? frequencyReductionArray[i] : 0.5;

        var step = Math.pow(1 / 2, bits);
        phaser += frequencyReduction;
        if (phaser >= 1.0) {
          phaser -= 1.0;
          lastDataValue = step * Math.floor(inputBuffer[i] / step + 0.5);
        }
        outputBuffer[i] = lastDataValue;
      }
    }
  };
});

var bitcrusherNode = audioContext.createAudioWorker(bitcrusher_worker.createURL(), 1, 1);

// Custom parameter - number of bits to crush down to - default 8
bitcrusherNode.addParameter( "bits", 8 );

// Custom parameter - frequency reduction, 0-1, default 0.5
bitcrusherNode.addParameter( "frequencyReduction", 0.5 );

クソコードは世界を救う

以前に書いた一部のマニアには垂涎もののウェブアプリがFirefoxで動いていなかったので修正した。簡単に内容を説明するとWeb Audio APIのAudioParamの遷移をグラフ化するやつです。

グラフの値を取得するにあたって 1 だけを出力する AudioBufferSource を GainNode に接続して使っていたのだけど、どうもバッファのデータが反映されていないらしく、 0 を出力 → GainNode を通しても 0 のままとなっていたっぽい。

+--------------------+
| AudioBufferSource  | * 常に 1 を出力
| - buffer: [ 1, 1 ] |
+--------------------+
  |
+-----------+
| GainNode  | * 入力と x を積算して出力
| - gain: x |   = AudioParam の値の遷移が分かる
+-----------+
  |
+--------------------------+
| audioContext.destination |
+--------------------------+

修正前 (WebKit系なら動くけどFirefoxでは動いてなかった)

bufSrc = audioContext.createBufferSource()

bufSrc.buffer = audioContext.createBuffer(1, 2, 44100)
bufSrc.buffer.getChannelData(0).set [ 1, 1 ]

bufSrc.loop = true

修正後 (コメントと1行追加)

bufSrc = audioContext.createBufferSource()

bufSrc.buffer = audioContext.createBuffer(1, 2, 44100)
bufSrc.buffer.getChannelData(0).set [ 1, 1 ]

# 不安だからもう一度
bufSrc.buffer = bufSrc.buffer

bufSrc.loop = true

どうも bufSrc.buffer に AudioBuffer が代入された時点でバッファの内容がコピーされて、それ以降の変更がC++側に無視されている気がする。ので、再代入することで変更を反映させた。JITコンパイラが賢くなると再代入の行自体が無視されてやっぱり動かなくなるかも知れない。

この変が該当箇所だと思うけど良く分からなかった。


次回のクソコード選手権にエントリーして、散々馬鹿にされたいと思います。

ウェブオーディオが使われているインスタレーションの様子を見てきた

今、奈良で 町家の芸術祭 はならぁと というイベントが開催されていて、そこで展示されているインスタレーション作品にウェブオーディオが使われているので様子を見に行った。SjQの魚住勇太さんの作品で詳しくはここに書いてある。

http://yutauozumi.com/cell/details/

時計仕掛けの生命をイメージして設置された装置。装置は部屋の気温や光といった環境と反応しながら、音を紡ぎ、空間を満たして行く。その動きや音は、刻々と変化していく。装置の内部にはいくつかのアームが設置されている。それらのひとつひとつが、単なる部品ではなく、個体として、互いに反応する動きを見せる。その様子は、分ごと、時間ごと、日ごとに、変化する。それは常時、音に変換されることで、一つの流れをもったサウンドスケープとしてリアルタイムで奏でられる。

会場は複数の人工生命デバイスがカチャカチャと反応しながら音楽を作り出す空間になっていて、人工生命デバイス自身の状態や空間の環境を元に音楽パラメーターを生成して、それをサーバー経由のブラウザでリアルタイムに処理してサウンド化しているということなので相当にコンピューティングなことをしているのだけど、現地ではそう見えなくて機械と生物が曖昧に溶け合うというような感じで心地よかった。音自体は以下のサイトで現地で鳴っているものがリアルタイムに聴けて充分楽しめるのだけど、やっぱり音は場であるし、実際に人工生命デバイスが音楽を生成している現場というのは緊張感があって断然良いので、みなさん現地まで見に行けば良いと思います。

奈良は遠いという方はこのサイトを上手く使うと簡単に来られます。

語り

ウェブオーディオ部分を担当したエンジニアの人によると、作家さんからMax/MSP等で綿密に作り込まれた音のイメージを伝えられて、そのイメージを壊すことなくWeb Audio APIで再現させることが求められたけど、Web Audio APIだけではやりたいことがなかなかできなくて結構ガツガツ書いたということで、うわー、そうとう辛そうだなと思ったし、実際辛かったし、体重が3kgくらい増えたけど、疲れた時のハッピーターン250%は格別です。

という感じで、フロントの部分は僕が書かせてもらった。事前にたまたま neume.js というウェブオーディオライブラリを作っていたので、それを使っている。

ただ、ライブラリは早い段階での試作には強力でも、要求に合わせてキチキチにチューニングしようとすると拡張用コードを結構書く必要があったり、チューニング作業はWeb Audio APIの仕様とWebKitの実装とライブラリの実装とアプリケーションの性質とやりたいことのバランスを考える必要があったりと、まだまだノウハウが足りないのを実感した。夢の中でウェブオーディオ仙人みたいな人に弟子入りして色々教えてもらいながらどうにか完成させたという感じ。このあたりは色々あるのでそのうち書けたら書きたい。

ウェブオーディオとアート/音楽

現地で生成される音をネットを介して体験することができるのはかなり面白くて、応用方法を色々考えるとアートや音楽と体験者との距離感の新しい選択肢になりえそう。 音楽系のメディアアートだとMax/MSPとかSuperColliderとかが良く使わていて、そういう蓄積のある技術に比べると Web Audio API はまだまだ見劣りする感じだけど、ブラウザがあれば*1専用のアプリケーションやデバイスを用意することなく誰でも手軽に体験を共有できるという点で重要なポジションを担えるんじゃないかなと思います。

ところでカレーの情報です

会場の近くのカフェ、居心地もカレーも最高だったので合わせて寄りたいところです。

f:id:mohayonao:20141104134907j:plain

ミジンコブンコ

*1:IEに対応していないとかあるけども