多角形の波アニメーション

話題になってたアニメーションが気になって、久々の JavaScript の練習って事で真似してみた。

ファイル

機能

  • 左側に多角形を表示して一定の回転速度で辺をなぞる
    • 円形と四角形と六角形。ついでに三角形を増やす
  • 多角形をなぞった時のY方向の値を波として右方向にスクロールする
  • 対応する Canvas を押されたら波形に対応する音を出力。

設計

  • HTML Canvas を種類毎に縦に並べる。
  • 始めに1024分割で各図形の波形データを計算する
  • 波形データを元に多角形をなぞる。
  • なぞったポイントの Y 座標を一定過去分覚えておいて右側に表示する。
  • Canvas のマウスイベントを補足して波形に対応する音を出力。
    • 押された場所に対応する音を出す
      • 対応する波形
      • 上下方向が音量、左右方向が音程。

実装

  • 始めに1024分割で波形データを計算する
    • 円は単純に cos, sin を x, y 座標にする
function make_wavetable_circle(waveDataX, waveDataY) {
    var theta = 0;
    var theta_delta = 2 * Math.PI / wave_length;
    for (var i = 0 ; i < wave_length ; i++) {
        waveDataX[i] = Math.cos(theta);
        waveDataY[i] = Math.sin(theta);
        theta += theta_delta;
    }
}
    • 四角形は左右上下で処理を分岐させて、移動は tan で算出
    for (var i = 0 ; i < wave_length; i++) {
        if ((theta < theta_8) || (theta > theta_8*7)) {
            // right
            x = 1;
            y = Math.tan(theta);
        } else if (theta < theta_8 * 3) {
            // top
            x = - Math.tan(theta - theta_8*2);
            y = 1;
        } else if (theta < theta_8 * 5) {
            // left
            x = - 1;
            y = - Math.tan(theta - theta_8*4);
        } else {
            // bottom
            x = Math.tan(theta - theta_8*6);
            y = - 1;
        }
        waveDataX[i] = x;
        waveDataY[i] = y;
        theta += theta_delta;
    }
    • 六角形は垂直線の座標を tan で出して、残りの5辺は回転行列で計算
function rotateXY(x, y, t) {
    var x2 = x * Math.cos(t) - y * Math.sin(t);
    var y2 = x * Math.sin(t) + y * Math.cos(t);
    return [x2, y2];
}
    • 三角形も、六角形と同じ要領で。
  • 定期的にこの波形データに対応する表示をして、アニメーションする
  • マウスイベントを補足して
    • offsetX, offsetY で場所を把握
      • 対応する音は、波形を元に AudioBufferSource で作成。
        var buf = audio_ctx.createBuffer(1, wave_length, sampleRate);
        var data = buf.getChannelData(0);
        for (var j = 0; j < wave_length ; j++) {
            data[j] = wave.waveDataY[j] * 1000;
        }
        wave.audioBuffer = buf;
      • 上下方向が音量。Gain を挟んで、その value に設定。
      • 左右方向が音程。playbackRate に周波数比を設定。
function getToneRatio(x, wave) {
    var tone = x * 2 + 20;
    return tone / (sampleRate / wave_length)
}
function getVolume(y, wave) {
    return (wave.height - y) / wave.height / 1000;
}
    • 押したまま動かした場合、場所に応じて音量と音程を設定し直す
    • 話したら音を消す。stop と disconnect 、null 代入。
      • マウスが離れても音を消す。(でないと押したままCanvasの外に出ると鳴りっぱなしになる)

ハマり

  • var のつけ忘れで for の中で読んだ先の function で i が上書きされる問題に結構ハマった。
  • canvasピクセルグリッドの左上を座標にしてるので、x, y に0.5 を足さないとpixelを跨る線を表現しようとしてボヤける。
  • NaN の値を canvas に渡すとエラーを出さずに単に描写しないだけなので、たまに困る。
  • BufferSource > Destination に直で繋いでた時はシグナル値が -1 < s < 1 でそれっぽい音が鳴ったけど、BufferSource > Gain > Destination として Gain を挟んだら -100 < s < 100 位にしないと正弦波に余計な 成分が入ってダメだった。多分、量子化ノイズ。
    • 念のため、-1000 < s < 1000 にしておいた。

課題

  • スクロールが力ずく過ぎるので、リングバッファで書き直したい。
  • 六角形、三角形を実装してたら n 角形に一般化できるの分かったので、気が向いたら作る。