JavaScriptでリアルタイムに音を出すときに簡単便利なやつつくった
先日作成した「関西電気保安協会リズムマシーン」と「ONE-LINER-ORCHESTRA」が
で使えるようになりました。
どちらも pico-player.js っていう拙作ライブラリを使っているんだけど、普通に便利なので紹介します。
https://gist.github.com/1342081
CoffeeScriptで書いてコンパイルしています。もうJavaScript書けなくなってきました。
2011/11/08追記
別タブ選択時に音が途切れる減少を解消しました。
関連記事: http://d.hatena.ne.jp/mohayonao/20111108/1320756534
2011/11/07 追記
CoffeeScriptで書いた分は無駄が多いので、JavaScriptで書き直したバージョン更新しました。
具体的にどんなことをしているのかは以前に書いた記事が参考になるかも知れません。
JavaScriptでリアルタイムに音を鳴らす方法を3つほど - つまみ食う
使い方
これだけ、非常に簡単。
// ブラウザに応じたプレイヤーオブジェクトを生成 var player = pico.getplayer(options); // 引数はオブジェクトで渡す。以下はデフォルト値。 options = { samplerate: 44100, // サンプリングレート channel: 1, // チャンネル数 duration: 40, // ストリームの分割時間(ミリ秒) slice: 8 // ストリームセルの分割数 }; // ジェネレータ(後述)を引数に play player.play(generator); // 再生中? player.isPlaying(); // 音を止める player.stop(); // プレイヤーの種類 ["WebKitPlayer", "MozPlayer", "HTML5AudioPlayer"] console.log( player.gettype() );
playerは以下の不変プロパティを持つ
player.SAMPLERATE // サンプリングレート player.CHANNEL // チャンネル数 player.STREAM_FULL_SIZE // ストリームのサイズ player.STREAM_CELL_SIZE // ストリームセルのサイズ player.STREAM_CELL_COUNT // ストリームセルの分割数 player.PLAY_INTERVAL // ストリームの分割時間(ミリ秒)
この中で気にしないといけないのは、サンプリングレートとストリームのサイズくらいであとはおまけ。
あと、オブジェクト生成時の引数と実際に生成されたオブジェクトのプロパティは異なるので注意が必要。たとえば、Chrome(Web Audio API)はサンプリングレート固定なので、起動時の引数は無視されるし、Opera(HTMLAudioElementを大量生成)は他の2つに比べて無理やり処理している関係でストリームの分割時間が長め(400ミリ秒以上)に設定される。このへんは十分に検証できないので調整必要かも。
ジェネレータ
- ジェネレータは実際に音のシグナルを出力するためのオブジェクト
- player.play(generator) を実行すると、generator.next が定期的に呼ばれる
- generator.next は player.STREAM_FULL_SIZE の大きさの Float32Array (-1.0 <= signal <= +1.0)を返す
- チャンネル数が 2 のときは [L, R, L, R, .. ] という配列を返せばよい
以下はウェーブテーブル方式のジェネレータのサンプル。
あらかじめ音の波形の配列を用意しておいて、配列の要素を飛ばす大きさで音の高さをコントロールしてる。
var TABLE_SIZE = 1024; var sinetable = new Float32Array(TABLE_SIZE); for (var i = 0; i < TABLE_SIZE; i++) { sinetable[i] = Math.sin(2 * Math.PI * (i / TABLE_SIZE)); } var ToneGenerator = function(player, wavelet, frequency) { this.wavelet = wavelet; this.frequency = frequency; this.phase = 0; this.phaseStep = TABLE_SIZE * frequency / player.SAMPLERATE; this.stream_full_size = player.STREAM_FULL_SIZE; }; ToneGenerator.prototype.next = function() { var wavlet = this.wavelet; var phase = this.phase; var phaseStep = this.phaseStep; var table_size = TABLE_SIZE; var stream = new Float32Array(this.stream_full_size); for (var i = 0, imax = this.stream_full_size; i < imax; i++) { stream[i] = wavelet[(phase|0) % table_size]; phase += phaseStep; } this.phase = phase; return stream; }; // 440Hzのサイン波を再生して 2秒後に止まる var player = pico.getplayer(); var gen = new ToneGenerator(player, sinetable, 440); player.play(gen); setTimeout(function() { player.stop(); }, 2000);
ONE-LINER-ORCHESTRAでは上記のジェネレータみたいなやつを複数管理して加算合成して返すジェネレータを player に与えて複数の音を出している。大量の音を合成している例
上記のサンプルはここで確認できる。
pico-player.js / pico-player.coffee
ちょっと便利
オプション引数に slice を与えると STREAM_CELL_SIZE というのが計算される。
STREAM_CELL_SIZE は STREAM_FULL_SIZE/slice で計算される値で、player 自体の動作には何の影響もないんだけど、あると便利なので入れている。
たとえばモノフォニックなアルペジエータを作っているとき、音程の切り替わるサンプル数は
sample = samplecount = (60/BPM) * SAMPLERATE * (4/LENGTH)
で計算できるので、samplecount-- していって 0 になったら、音程を切り替えれば良いんだけど、1サンプルごとに if で条件判定するのは非効率で、ある程度大雑把(耳では判別できない)に処理したほうが計算量が減って良い。音量とか低位とかも同じ。その大雑把な数字に使えるのが STREAM_CELL_SIZE。
var samplerate = player.SAMPLERATE; var stream_cell_size = player.STREAM_CELL_SIZE; var stream = new Float32Array(player.STREAM_FULL_SIZE); var k = 0; for (var i = 0, imax = player.STREAM_CELL_COUNT; i < imax; i++) { samplecount -= STREAM_CELL_SIZE; if (samplecount <= 0) { // 音程を切り替えるための処理 // phaseStep = TABLE_SIZE * new_frequency / samplerate; // みたいなの samplecount += sample; } for (var j = 0; j < stream_cell_size; j++) { stream[k++] = wavelet[(phase|0)&TABLE_SIZE]; phase += phaseStep; } } return stream;
ブラウザ戦争の結果、JavaScript がいくら高速になったといってもDSP処理は結構重いので計算を省略するのが重要で、最初の例のサイン波も再生時には Math.sin みたいな複雑な計算はリアルタイムに行わないような工夫をしたり、STREAM_CELL_SIZEを使った例みたいに処理単位を減らしたり地道な努力が必要っぽい。
メモ
・Operaでは Float32Array が使えないっぽい (pico-player.js内で偽Float32Arrayをでっち上げている)
・Operaと同じやり方はSafariでは固まる。ナゾ。
・iOS, Androidもダメ。
・上記の例で phaseStep は毎回計算しているけど、 MIDIノート番号の細かい版みたいなのを用意すると効率よい。
# 半音をresolution個に分割した音番号->phseStepのテーブルをつくる def calc_steps(size=8192, resolution=32): center = size >> 1 def calcStep(i): freq = 440.0 * ((2.0**(1.0/(12*resolution)))**(i-center)) return TABLE_LENGTH * freq / SAMPLERATE return tuple(calcStep(i) for i in xrange(size))
関西電気保安協会リズムマシーンの生存戦略について
KSDN-808II 関西電気保安協会リズムマシーン
http://ksdn808.herokuapp.com
機能追加しました。最初のバージョンと比べると以下のことができるようになった。
- テンポが(たぶん)正確に
- 以前のやつ、なぜかループ内でループ変数を書き換えていてテンポがおかくなっていたのを直した。
- Operaでも再生できるようになった
- wavデータをリアルタイムに作って再生している。リアルタイム感ひくい。
- ドラム音のピッチを変更できるようにした
- 波形データ配列のインデックスを捜査ステップを変えているだけ。
- ドラムキット、ボーカルセットを変更できるようにした
- データを作るのが面倒くさくなったので、とりあえずドラム2種類と「生存戦略」を追加。
- 矢印キーでエディター内を移動
- viスタイルの移動方法だったけど、僕はvi使いじゃなくて分からなくなるので矢印でも動けるようにした。
僕も「非常に短いコードから音楽を生成して再生するやつ」つくった
非常に短いコードから音楽を生成して再生するやつ - hitode909のダイアリー
http://d.hatena.ne.jp/hitode909/20111024/1319204646
これを見て面白かったので僕もやってみた。
ONE-LINER-ORCHESTRA
http://mohayonao.herokuapp.com/one-liner-orchestra
同じものを作ってもしょうがないので、いくつか機能をつけたら、たったひとつの式で全てを生み出す無骨さが無くなった代わりに非常に複雑な音楽が簡単に作れるようになった。
非常に長い周期のノコギリ波はフェードイン/アウトっぽいし、三角波はオートパンっぽい、それらのパラメータも同じような式で操作できたら面白そう。それをターミナルのようなインターフェイスで式(コマンド)を次々に入力していくことで、複数トラックを制御すれば...!!というものです。
Chrome(Web Audio API)とFirefox(Audio Data API)ハイブリッドに動く。
たまにブラウザが落ちる.. そうなったらごめんなさい。
リファレンス
システムコマンド
start | プレイヤーの開始 |
stop | プレイヤーの停止 |
ミキサーコマンド
add (exp) | トラックを追加 (addは省略可) |
del [id] | トラックを削除 |
sel (id) | トラックを選択 |
ch (id) | トラックを選択 (selと同じ) |
mute [id] | ミュート |
solo [id] | ソロ |
[id]は省略可能、その場合は選択中のトラックに適用される
トラックコマンド
vol (exp) | ボリューム1を設定 |
amp (exp) | ボリューム2を設定 |
pan (exp) | パンを設定 |
pitch [num] | ピッチシフトを設定 (1=1オクターブ, 省略時は0と同じ) |
filter (NONE,LP,HP,BP,BR) | フィルタを設定 |
cutoff (exp) | カットオフを設定 |
res (exp) | レゾナンスを設定 |
famp (exp) | フィルターデプスを設定 |
exp の部分は t<<1 とかの式を入れられる。単純に数値でもOK。128が真ん中。
つくった曲
冒頭デモのスコア
t*(((t>>12)|(t>>8))&(63&(t>>4))) filter HP cutoff t<<1|t>>5 pan t<<3 t<<1|t>>5 pitch 4 filter LP res t<<1|t>>7 cutoff t<<2 (t<<1)/(~t&(1<<(t&15))) pan 224 del 1 del 0 amp 64 amp 32 amp 16 amp 8 del stop
ビート感あるやつ
(t>>4)&((t<<5)|(Math.sin(t)*3000)) filter LP cutoff t<<1|t>>5 res 224 t<<5|t>>2 pan t<<1|t>>6 pitch 1 amp t
id:hitode909 のモチーフを使った曲
t&(t<<(t/800)) | (t-16000)*((t-16000)<<((t-16000)/800)) | (t-32000)&((t-32000)<<((t-32000)/800)) (Math.sin(t/((t>>10)%7+2))*0.7 + Math.sin(t/((t>>11)%4+3)) + Math.sin(t/((t>>13)%6+4)))*30+128 ch 0 amp t<<5 pan (t>>2)|(t&0x0f0)<<2 filter BP ch 1 pan 96 (t<<5)|(t>>2)|(t<<3) amp 64 pan 240
参考
countercomplex: Algorithmic symphonies from one line of code -- how and why?
http://countercomplex.blogspot.com/2011/10/algorithmic-symphonies-from-one-line-of.html
1行のコードからアルゴリズム交響曲 - どのように、そしてなぜ? - 閉村観光
http://d.hatena.ne.jp/miujun/20111003
非常に短いコードから音楽を生成して再生するやつ - hitode909のダイアリー
http://d.hatena.ne.jp/hitode909/20111024/1319204646
Pythonのインタプリタを楽器にしてしまう簡単な方法。 - 蟲!虫!蟲! - #!/usr/bin/bugrammer
http://bugrammer.g.hatena.ne.jp/nisemono_san/20111022/1319289787
JavaScriptで画期的なリズムマシーンをつくった
誇大広告です。
2日ほど前に「おまえはアホか」を作って音程だけなんだけど言葉のイントネーションで遊ぶのって面白いと思って、そもそも面白いイントネーションのものを素材にして別のものを作ってみた。
KSDN-808
http://ksdn808.herokuapp.com/
KSDN-808はJavaScriptで作られた4つのドラム音と関西電気保安協会が鳴る実用的なリズムマシーンです。あなたの作成したクールなリズムトラックを保存して共有することができます。無料です。
Web Audio API と Audio Data API を使っていて、Chrome と Firefox で動く。
画像が悪くてよく見えないけど、デモ動画も作ってみた。全部キーボードで操作している。
デモソング
http://ksdn808.herokuapp.com/U0Sw7yAZ
http://ksdn808.herokuapp.com/U0SfBFae
http://ksdn808.herokuapp.com/U0S+CY8r
ソースコードとか
heroku + node.js/CoffeeScript + mongoDB という構成。使いやすいので気に入ってる。
ソースコードはGitHubに置いた。
https://github.com/mohayonao/ksdn808
メモ
・ChromeはWeb Audio API、FirefoxはAudio Data APIを使ってリアルタイムに処理
・Web Audio APIはサンプリングレートを変えられない (48kHzだった)
・Chromeでは音が変、Firefoxの音は格好よい(素材のサンプルレートと揃えているため?)
・Firefoxでは表示と音がずれる(どんどんずれる)
・テンポが計算よりはやい(分かる程度微妙にはやい)
・別タブを選択すると変になって格好よい
・おまけでフィルタもつけてみた
・UIけっこういけてると思う
・よく考えたら関西ローカルなネタだわ
・関西電気保安協会はKSDNじゃなくてKSDHやった..
参考にしたもの
「おまえはアホか」をつくった
カラフル鍵盤をクリックすれば様々な音程で喋りますし、あなたのオペレーションを保存したり再生することも出来ますのでアホな友達にアホと伝えることができてとても便利。無料です。
おまアホサンプル
http://hotbros.herokuapp.com/300SpaV
http://hotbros.herokuapp.com/300klBYE
ソースコードとか
heroku + node.js/CoffeeScript + mongoDB という構成。
ソースコードはGitHubに置いた。
https://github.com/mohayonao/hotbros
サーバー、クライアントどちらもCoffeeScriptで書いて、それぞれJavaScriptにコンパイルして動かしている。wcオプションで自動コンパイルするようにすれば楽で良い。
$ coffee -wc app.coffee public/javascripts/main.coffee
ついでにサーバー側のスクリプトはnode-devで更新するたびに再起動するようにしておくと楽できる。
$ node-dev app.js
技術っぽいこと
・リアルタイムにAudioオブジェクトを作って再生
・事前に各音のwaveデータを用意しておいてfloatの配列で持っておく
・配列のインデックスを位相として捉えて位相の増分(配列をどう捜査するか)で音程を出す
・位相が2ずつ増えるなら元の音の1オクターブ高くなるし、0.5ずつ増えるなら1オクターブ低くなる
・1.5ずつ増えるなら5度上の音だし、0.75ずつ増えると4度下の音になる
・原音から離れるにつれて不自然さが大きくなる
・音程によって持続時間が異なることになるけど気にしない
・本当は配列の真ん中あたりをループするなりして長さを揃うようにするのがベター
参考にしたもの
node.jsのプログラムを自動で再起動してくれるnode-devコマンド
http://d.hatena.ne.jp/replication/20110224/1298474534
JavaScriptで音を鳴らす方法を3つほど
http://d.hatena.ne.jp/mohayonao/20110808/1312803835
おまえはアホか
http://fc2.in/s60195
DevQuiz2011のコードを晒してみる
やったのはウォーミングアップ、Web Game、Go!とスライドパズル。スコアが120.8だった。
Web Game
https://gist.github.com/1210841#file_web_game.js
Chrome Extensionとかダルそうやなーと思いながら、HTMLソースを見ると普通に色の情報が書いてあったので目視で10問くらい解いた後、カードが増えてきたのでブックマークレットを書いた。
Go!
https://gist.github.com/1210841#file_golang.go
Goが出始めたときにインストールしたっきり触ってなかったけど問題が簡単そうだったので選んだ。でもコンパイルの仕方がわからなかったり、リファレンスどおりやってもコンパイルできなかったりかなり悩むはめに...
このページがなかったらクリアできなかった
http://golang.jp/install#releases
スライドパズル
https://gist.github.com/1210841#file_slidepuzzle.py
幅優先探索でちょっとやってみたら全然解けなかったので、ちゃんと調べようと思ったけど面倒くさくなったので適当に優先順位つけるようにしたら、思ったよりは回答してたのでそれでいいやってなった。夜に動かす→仕事行く→帰ったら止める。みたいなやりかたで2,3回動かした。
やってみて
一応ボーダーは超えてるっぽいけど、プログラム書く友達とかいないしそういうイベント行ったことないので怖いです。