JS1KデモBLCK4777のせめて音を作ってる部分だけでも分かりたい

今年のAssembly 2015 1kb intro勝者のJavaScriptデモBLCK4777、わずか1023 bytesでできているとは思えない美しいデモで話題になった。

1023 bytes版のファイルはPNG bootstrapping techniqueで圧縮されているそうで見た目はバイナリである。見ても分からない。

Full archiveに入っているsafe版は非圧縮のものなので、ちゃんと見えるJavaScriptが書いてある。さらにそれを見やすく整形して置いてくれた人がいるので、それを見ると、

はい分からない。

いや、もうちょっと頑張って読もう。safe版には、上のgistに置いてあるコードの他にブートストラップ用の以下のコードが含まれている。

c = b.getContext('2d');
T = String.fromCharCode;
p = 0;
document.querySelector('button').onclick = function() {
  this.textContent = 'Starting...';
  this.disabled = true;
  setTimeout(u,1);
};

ここで変数pは0に初期化されている。function uの最初の方にあるg = p ?から始まる三項演算子は0がfalse相当なので後ろの式が採用される。後ろの式はまるで関数の引数のようなものが書いてあるが、これはコンマ演算子だ。順に実行され、最後の結果が採用される。三項演算子コンマ演算子を使えば、if文相当を短く書ける。

ちなみにJavaScriptのショートコーディングについて知りたければSuperpacking JS Demosなどの記事を読むと良い。WebGL用の長いメソッド正規表現で無理やり短くする方法とか、PNGを使ったコードの圧縮方法などが書いてある。

コードに戻ると、pに値を設定するのは一番下のAudio.playの引数部分だ。Audio.playの引数は何の効用もないはずなので単にここで値を代入しているだけ。なので、ここでpの値が設定され、requestAnimationFrame(u)で再度uが呼び出されるまでにAudio、つまりこのデモの音が生成されている。

デモ全体を把握するのはキツイので、このAudioを作るまでをせめて見てみたい。pが0である条件で通るパスだけを切り出すと、以下のコードになる。

T = String.fromCharCode;
function u() {
  audio = "RIFFdataWAVEfmt " + atob("EAAAAAEAAQAAeAAAAHgAAAEACAA") + "data";
  g = 6177;
  h = f = C = 0;
  for (; g > f; h *= f % 1 ? 1 : 0.995) {
    s = Math.pow(Math.min(f / 5457, 1), 87) + 
        Math.pow(1 - Math.min(f / 5457, 1), 8);
    if (f == [1280, 1599, 2175, 2469, 2777, 3183,
              3369, 3995, 4199, 4470, 4777, 5120][C]) {
      C++;
      h = 640;
    }
    audio += T((1 + s * 8) * Math.random() + 
               (1 - s) * (h / 45 * (f * (2 + C / 3 % 1) & 1) + 
               (C > 3) * 8 * (f * (2 + (f / 8 & 3)) % 1)) | 1);
    f += 1 / 512;
  }
  new Audio("data:Audio/WAV;base64," + btoa(audio)).play();
}

はい分からない。

いや言いたいことは分かる。最初のaudioの初期化ではwavファイルのヘッダ相当のものを作っている。atobbase64をデコードしているのはフォーマットやチャネル数を指定するためのデータで、これはリニアPCM、モノラル、サンプリングレート30720Hz、bit/sampleは8bitを示している。

なのでaudioにTString.fromCharCodeを使って数値を足しているところで8bitのサンプルごとのデータを生成している。最後のnew Audioで作ったデータをbtoabase64エンコードし音を作っているのだ。

じゃあTの後ろの面妖な式は何だ。何だ。分からん。

ちくしょう!可視化だ!

データを適当に間引いた上でc3.js使ってグラフ化してみたけど……分からん。fと比較している配列内のフレーム数のところでなんらかの変化が起きているから、ここで曲の内容を変化させているのだとは思う。変化はCをインクリメントし、hを640にすることで起こしている。式にはC > 3という部分もあるので、ある程度フレームが進んだ後のみ使われる部分もあるのだろう。hは少しずつ減衰しているように見える。

これはいわゆるbytebeatと似たような手法なのだと思う。bytebeatは単純な式から様々な音を生成することができる手法で、実装方法もごく単純にフレーム数を引数とした式の出力をリニアPCMのサンプルごとの値にするだけというもの。本当に単純なのでショートコーディングにはとても向いた方法なのだが、問題はどんな式がどんな音をだすのかさっぱり分からないこと。

それっぽい式を適当に組わせるUIがあれば、簡単に音が作れるんじゃないかと思って、前にbytebeatbank (これも音が出ます)ってのを作ったんだけど、やはり型にはまった式の組み合わせだけだとあまりバリエーションが出ないという問題があった。かといって自由な式を生成してしまうとまともな音が鳴ることはほぼ無いという別の問題が起こる。

bytebeatに限らず、こういった数式でそれっぽい音を簡単に作る方式があれば、短いコードでBGMなり効果音なりが作れて楽しいんだろうけど、そういう手法を解説しているページとか、欲しいところだ。bytebeatについては以下のページがたぶん役に立つ、んだろうけどこれを読み解くのも大変そうである。

ちなみに今回まるっとスキップした、絵を作る部分はこれにさらに輪をかけて謎の数式が乱舞しているので誰か解説して欲しい。BLCK4777の例では無いけど、短い数式や関数で驚くべきグラフィックスを作る方法については、以下のページなどが役に立つ、はず。

というわけでデモコーダーの超絶技法を知るのはとても大変そうであるという話でした。