Ruby1.9のlambdaをアロー演算子に。Hash#perlish!

Ruby1.9ではlambdaを->で書けるようになりました。

lambda { |x| x+1 }  # これと
-> x { x+1 }        # これは等価

lambdaと->は文法上の扱いが異なります。

p(lambda{})   # => <Proc:0x9696dc@(irb):1 (lambda)>
plambda{}     # NoMethodError
p(->{})       # => <Proc:0x95e29c@(irb):2 (lambda)>
p->{}         # => <Proc:0x95bea0@(irb):3 (lambda)>

要するに->{}はデリミタ不要ということです。楽しいですね。
ところで、p->{}なんて形はPerlのリファレンスにしか見えません。なのでPerl風のアクセスができるようにしてみましょう。

$binding = binding

class Hash
  def perlish!(bind = $binding)
    id = self.object_id
    that = self
    bind.eval("local_variables").each{|sym|
      if id == bind.eval("#{sym.to_s}.object_id")
        Object.__send__(:define_method, sym, -> x { that[x.call] })
        break;
      end
    }
  end
end

こんなスクリプトを実行すると、

h = {"key" => :value}
h["key"]     # => :value

h.perlish!

p h->{"key"} # => :value
p h.class    # => Hash

Perlのリファレンスみたいな感じでHashにアクセスできるようになります{"def"}みたいなことはできない。">*1。楽しいですね。

*1:文法の制約からリファレンスを引けるのは一段まで。h->{"abc"}->{"def"}みたいなことはできない。

WebブラウザとGoogle Waveで動くシーケンサ、Anzutoneリリース!


http://anzutone.appspot.com/
読み方は「あんずとーん」です。

これはなに?

SafariFirefoxで動くミュージックシーケンサです*1
非常にシンプルな機能しか備えていませんが、「みんなで作曲できます」。

みんなで作曲できる

上記サイトのAnzutoneは通常版ですが、Google Waveで動くバージョンもあります。
テンプレートは以下に。
Anzutone Gadgetのテンプレート

このガジェットはAnzutoneをGoogle Waveでも動くようにしたものです。
通常版との違いは「みんなで作曲できる」ことです。ぜひお試しください。

ただ、非常に横幅の広いガジェットなので取り扱いには注意してください。

ソースとか

オープンソースです。MITライセンス。
Githubホスティングしてます。

今後の展望

ノープランです。
いくつか追加したい機能はあるのですが、明日から大学でコミットが少なくなるのでなんとも言えません。
Google Wave API Challengeには応募しようと思っていますが、あれってガジェットもOKなんでしょうか?

技術的なこと

Web WorkersをユーザコードのSandboxとして使うアイディア*2jQuery UI 1.8のハマリどころ、
audioタグの振る舞いがブラウザ・OSによって全く違うこと、などなど言いたいことはたくさんあるので後日別エントリにまとめます。


あと、僕はFlashとかサーブレットとか書けないので全部JavaScriptの実装になってます。

制作の経緯

通常版のAnzutoneは春休みの総まとめで作ったようなもの*3なんですが、Google Wave版は別の動機があります。

今のGoogle Waveの先、そこにはネット上のありとあらゆるドキュメントがリアルタイムでユーザーに編集され正しい姿を目指して常に形を変え続ける、そういう未来があります。どうでしょう、そう考えるとわくわくしてきませんか?

http://d.hatena.ne.jp/technohippy/20091130#1259592204

このエントリを読んだから、僕はGoogle Waveのガジェットを作りました。


僕には3つのPと言われても、あんまりピンと来ないけれど、
色々なものをみんなで一緒に作れるようになったら、すごく楽しいですよね。

*1:Google Chromeでは動きません。

*2:我ながらこのアイディアは素晴らしい。

*3:Webブラウザで動画編集→波形処理→波形再生ときて、シーケンサ作れるじゃん。作るか。作った。という流れ。

JavaScriptで波をつくろう。リアルタイム波形生成&再生

前のエントリでこんなことを書きました。

JavaScriptで波形データを読み書きすることができる。しかし再生するのは難しい。
HTML5のaudioタグとData URIを組み合わせればできないこともないが、コストが大きすぎる。

コストが大きいのは音声ファイルが大きいからです。50MBある波形をいちいち変換してられません。
でも小さい波形ならできるかもしれない! ということでやってみました。

基本的なアイディア

  1. 波形データをつくる(数値の配列)
  2. 波形をバイナリ列に変換する
  3. バイナリ列にWAVヘッダを付加する
  4. Base64エンコード
  5. audioタグのsrc属性に指定
  6. audioを再生

つくったもの

http://yanagiatool.appspot.com/jsaudio/mmltest.html

シンプルなMMLプレイヤーです。JavaScript + HTML5
ベロシティとかループとかはありませんが、かえるのうたは演奏できます。

演奏に使う波形は下のテキストボックスで制御できます。


実際のコードとかはgithubに。

大雑把なしくみ解説

「リアルタイムレンダリング」のほうは音符ひとつにつきWAVファイルをひとつ生成し、タイマーでスケジューリングして鳴らしてます。
WAVファイルは再生前に"作り置き"するのではなく、再生中に動的に生成しています。

「オフラインレンダリング」は再生前に全てのWAVファイルを生成、JavaScriptでそれらを合成してひとつの大きなWAVにしてからaudioタグに渡してます。

もう少し細かなしくみ解説

「リアルタイムレンダリング」はレンダリングコールバックを定義して、setIntervalで定期的に呼び出しています。

var Timer;

function play(){ // ボタンが押されるとこの関数が呼ばれる
  ... // 再生開始の準備
  Timer = setInterval(renderBar,  // レンダリングコールバック
			     1000 * ((60.0 / Score.bpm) / (16 / 4))); // 16分音符の長さ
}


コールバックの中で波形とaudioタグを生成して再生バッファに突っ込みます。音符ひとつにつきaudioタグひとつ。

var renderBuffer = [];

function renderBar(){  // レンダリングコールバック
  var note;
  ... // 音符のコレクションから「今発音すべき音符」を探索してくる

  var signal = createSignal(note.duration, note.pitch);  // 波形を動的に生成
  var url = convertToURL(signal); // 波形にWAVEヘッダを付与、Base64エンコード
  var audio = new Audio(url); // audio要素を生成
 
  document.getElementById("anywhere").appendChild(audio); // audio要素をhtmlに追加。この時点でurlからロードがはじまる
  renderBuffer.push(audio); // すぐには再生できないので、一旦バッファに貯めておく

  ... // 他に発音すべき音符がないか確認

  setTimeout(playBuffer, 10);  // 再生バッファを再生
}

function playBuffer(){
  for(var i = 0; i < renderBuffer.length; i++){
    renderBuffer[i].play();
  }
  renderBuffer = [];  // バッファをクリア
}


また、このアプローチだと再生が終わったaudioタグは不要になるので、適当なタイミングで要素を削除するコードも差し込みます。

  audio.pause();
  document.getElementById("anywhere").removeChild(audio); // HTMLからの参照を切る。これでGCされる

波形の生成について

今回使用している波形のフォーマットは以下の通りです。

  • サンプリングレート 44.1kHz
  • 量子化ビット 8bit
  • モノラル

ページのユーザーコードは上のフォーマットの数値配列を返却することを期待しています。
数値配列は以下のコードでunsigned char(8bit)のバイナリに変換できます。

  var signal = userSignal;
  var binary = "";
  for(var i = 0; i < signal.length; i++){
    binary += String.fromCharCode(signal[i]);
  }
}

波形をData URIに変換する

波形バイナリにWAVヘッダを付加する

以下のコードで、44100Hzの8bitモノラルな波形バイナリにヘッダを付加できます。

  var signals = "波形のバイナリ";
  var header;

  header = "WAVEfmt " + String.fromCharCode(16, 0, 0, 0);
  header += String.fromCharCode(1, 0); // format id
  header += String.fromCharCode(1, 0); // channels
  header += String.fromCharCode(68, 172, 0, 0); // sampling rate
  header += String.fromCharCode(68, 172, 0, 0); // byte/sec
  header += String.fromCharCode(1, 0); // block size
  header += String.fromCharCode(8, 0); // byte/sample
  header += "data";		       // data chunk label

  var siglen = signals.length;
  var sigsize;

  sigsize = String.fromCharCode((siglen >> 0 & 0xFF),
				(siglen >> 8 & 0xFF),
				(siglen >> 16 & 0xFF),
				(siglen >> 24 & 0xFF));

  header += sigsize;

  var wavlen = header.length + signals.length;
  var riff = "RIFF";
  
  riff += String.fromCharCode((wavlen >> 0 & 0xFF),
			      (wavlen >> 8 & 0xFF),
			      (wavlen >> 16 & 0xFF),
			      (wavlen >> 24 & 0xFF));
 
 wavefile = riff + header + signals;

このコードは

WAV ファイルフォーマット
http://www.kk.iij4u.or.jp/~kondo/wave/

を参考にして作成しました。thanks!

バイナリをBase64エンコード

楽をしたいので変換モジュールを探してきます。
今回は弾さんのbase64.jsを使いました。

javascript - Yet Another Base64 transcoder
http://blog.livedoor.jp/dankogai/archives/51067688.html

thanks!

エンコードした文字列に "data:audio/wav;base64" を付加

concatするだけです。

これだけの知識があれば上のサンプルと同じものがつくれます!

やったね!

つくりながら思ったこと

リアルタイムレンダリングは厳しい

audioタグはこういう用途で使われることを想定していないようで、どのブラウザでもいまいち綺麗に鳴りません。
試した中ではFirefoxが一番うまくいっていましたが、アクティビティモニタでスレッド数を確認すると楽しいことに*1
MacSafariはプロセス間通信しまくりでダメダメでした。Appleは「Flashなんて必要ない」と言う前にaudioタグを改善するべきです。

オフラインレンダリングは使える

ブラウザのaudioタグがダメダメなら全部JavaScriptでやってしまえばいい、というのがオフラインレンダリング
個人的な感触では、これ、使えます。いけます。
リアルタイムでやるよりも(波形合成のコストがある分)やや重いですが、オフラインならaudioタグに触る必要がないのでWeb Workersで処理できます。
少なくともユーザーにリアルタイムだと勘違いさせる程度の処理速度は実現できそうな感じです。

HTML5すごい! で終わらせたくない

こういう感じのエントリを書くと、「HTML5すげー、JavaScriptすげー」みたいな反応をよく聞きます。
でもそれだけで終わらせるのはもったいないです。
僕はJavaScript歴4ヶ月のビギナーですが、上のプログラムを6時間で書くことができました。
もしあなたが僕よりも長くJavaScriptを使っているのなら、より良いものをより短い時間で作ることができるかもしれません。
僕の知らないAjaxの世界で、もっと面白いことができるのかもしれません。

まとめ

Canvas、Video、AudioとJavaScriptでマルチメディア処理をする技術は出揃いました。
あとはクリエイターのアイディアと、少しのコーディングで"波"をつくることができます。
楽しくて、面白くて、みんなとつながる、大きな"波"をつくることができます。


準備はすべて整いました。
JavaScriptで、"波"をつくりましょう。


*1:どうやらaudioタグの数だけスレッドを生成しているようです。

JavaScriptでwavファイルを読み込んで波形表示するサンプル

動画でリッチなことできるんなら音声でもできるんじゃね? と思ったので書いてみました。

http://yanagiatool.appspot.com/jsaudio/load.html (FireFox3.6系列のみ)

ローカルからwavファイルを画面にドラッグアンドドロップしてみてください。ゆっくり波形が表示されます。
読み込めるwavファイルの形式は16bitステレオのみです。

仕組み

File APIを使ってます。
FileReaderでファイルの中身をStringとして読み込んで、ヘッダをチェックして、データ部を適当にエンディアン変換しながら表示してます。

File APIの使い方やバイナリの扱い方などは

W3C File APIを使ってJavaScriptでファイル加工 - しばそんノート
http://d.hatena.ne.jp/shibason/20100111/1263191021

が参考になりました。thx!

作ってて思ったこと

JavaScriptだと、波形と音声が繋がらないなーと。
今回作ったスクリプトは波形のデータを全部配列に持っていて、それを直接編集することができます。
でも加工した波形データを再生するうまい手段がHTML5/JavaScriptにはなくて*1、こういうことをやる旨みって少ないのかなあと思ったりしました。

var speaker = new SoundOutputUnit();
speaker.renderCallback = function(time){
 ...
}

speaker.start();

みたいなことができるようになると、すごく面白くなるとは思うんですが。

*1:audioタグにdataスキームで渡すという方法はあるけど、50MBのwavを毎回出力するわけにはいかないし。

Webブラウザで動画編集!

html5で videoの任意のフレームをcanvasに描画するメモ - 超自己満足プログラミング
http://d.hatena.ne.jp/favril/20100225/1267099197

昨日この記事を見て、これはすごい! すごいすごい! ってなって、これができるんならWebブラウザだけで動画編集できるんじゃね? と思ったりしたので、ざっくり作ってみました。


http://yanagiatool.appspot.com/jsvideo/player.html (MacSafari4で動作確認。QuickTime + ChromeならWindowsでもいけるかも?)

あそびかた

「動画を読み込む」ボタンを押すと、テキストボックスに入ってるurlの動画を読み込みます。「state」って書いてあるところが「stand by」になったら「再生 / 停止」ボタンを押してください。
あとは再生しながらスライダーを動かしたりすると、なんだか楽しい感じになります。

(※ Google Chromeだと透過度パラメータが反映されないようです。)


テキストボックスにはデフォルトで
http://www.youtube.com/demo/google_main.mp4
を入れてますが、

ウリ

HTML5 + JavaScriptのみの実装です。

作ってて思ったこと

Webブラウザで動画編集」は幻想じゃない

HTML5JavaScriptだけでも編集ソフト作れますね。今回はUIつけてませんが、カット編集もできます。アニメーションもできます。
作ってる最中は「パフォーマンス的に厳しいかな?」と思ってましたが、軽いコーデックの動画ならElisとほぼ同じ速度で動きます。
編集結果の書き出しは、連番画像をbase64で出力するとか、最終的なレンダリングはサーバー側で行うとかすればできそうですね。

編集ソフトをWebアプリとして作る意味

編集ソフトがWebとつながることで、Webの資源に簡単にアクセスできるようになります。
今までローカルにファイルを保存しないとできなかった、「zoomeとニコニコの動画を使ってマッシュアップ」みたいなことがwebブラウザだけでできるようになります。
Flickrから画像を選んで、YouTubeから音楽を選んで、スライドショーの動画を作成」みたいなこともwebブラウザだけでできるようになります。
これってすごく面白いことだと思いませんか?

動画編集ソフトウェアを簡単に作れる時代がやってきた

今まで、動画を扱うソフトウェアを作るのはすごく面倒でした。
「よし作るか!」という気になっても、まずは画像処理、音声処理、動画処理、資源管理、描画処理、その他もろもろのライブラリとかフレームワークとかを探すところから始めないといけません。使うライブラリが決まったらライセンスに矛盾がないか確認して、リンクして、動かしてみて、「えっ、このフレームワークってGCと一緒に使えないの?」みたいなことがあったりして、とにかく面倒です。


でも、今ならHTML5JavaScriptだけで動画を扱うソフトウェアを書けます。
もしあなたがJavaScriptプログラマなら、今すぐに動画を扱うソフトウェアを作ることができます。


面白い未来が、楽しい未来が、すぐそこまで来ている気がします。

Youtubeに解説動画を上げていない理由

いろんなところで聞かれたので書いておきます。


Youtubeに動画を上げていない理由は以下の2つです。

  • まだα版(そこまでオープンにするつもりはない)
  • まだα版(10.6専用のはずなのに10.5で動く)


β版をリリースするタイミングできちんと紹介動画を作ればいいかなって思ってます。

追記

α版の「使い方動画」があって「紹介動画」が無い理由も同じ。その時点で大きくアピールするつもりがなかったから、紹介動画を作っていない。

Core Imageフィルタで平行移動がしたい

考えたけどできなかったという話。

Core Imageフィルタで平行移動がしたい


こういう画像から

こういう画像を作りたい。

フィルタでできると何がうれしいの?

平行移動した画像を別のフィルタの入力にできる。
普通に平行移動すると、

[ciContext drawImage:image atPoint:offset/* 平行移動 */ fromRect:contextRect];

こんな感じになっちゃって、平行移動済みの画像をあれこれできない。
glReadPixelsとかで読み戻せばできるけど、パフォーマンスがすごく悪くなる。

アフィン変換フィルタで平行移動できそう。でもできない。

CIAffineTransformっていう画像にアフィン変換を施すCore Imageフィルタがあるけど、これは拡大縮小と回転しかできない。

負の方向にクロップするとできそう。でもできない。

CICropフィルタの入力に負の値を与えるとそれっぽい画像を返してくれる。でもこっちが想定しているスケールにならない。

いちどNSImageに変換すればできる。

CIImageをNSImageとかに変換して平行移動してCIImageに戻せばできる。でもいちいちメインメモリに読み戻すのはパフォーマンス的にNG。

自分でCore Imageフィルタを書けばできる。

当たり前だけどできる。でも、よくよく考えたら処理にほとんど必要ないピクセルのためにVRAMを食いつぶすのはよくないよね。

わかったこと

Core Imageフィルタで平行移動しようとしてはいけない。