JavaScriptのWeb Audio APIで録音してみるでHTML5のAPIを介して、

ブラウザとマイクで音を拾って、wav形式の音声データとして保存してみた。


それを踏まえた上で、

今回はマイクで拾った音を他の処理で使えるように、

音を周波数のデータとして取得してみる。


取得の前に周波数のデータとして取得する為に必要なフーリエ変換について見てみることにする。

といっても生物上がりで数学は得意ではないので、

以後の開発で使える程度の概念程度を自分用のメモとして残す。

参考:O'Reilly Japan - 行列プログラマー Pythonプログラムで学ぶ線形代数




高校数学で三角関数というものを習った。

三角関数というのはsinθ、cosθやtanθのことで

θ(シータ)と読むには角度が入り、

sinθのθに-360°から360°までの値を入れてグラフにしてみると、



こんな感じの波になる。


cosθのθに先ほどと同じ条件で値を入れてグラフ化してみると



こんな感じの波になる。


どちらもyの値が-1〜1の範囲の値になり、

2sinθや2cosθであれば-2〜2の範囲の値になり、

1/2 * sinθや1/2 * cosθであれば、-0.5〜0.5の範囲の値になる。


続いて、

sin2θをグラフ化してみると、



このように間隔が短くなった波が描写されるようになる。


三角関数の特徴からフーリエという数学者は、

これらの波を合わせればどんな複雑な波であっても数値として捉えることが出来る

ということを発見した。


具体例を示すことにしよう。

例えば、下記のような式があったとする。

2sinθ + cosθ + 3sin2θ + 5cos2θ + 4sin3θ + 2cos3θ


これをグラフ化してみると、



複雑な波のグラフが描写された!

しかも驚くべきことに、

上の式の各三角関数の前にある係数を集めて、

var data = [2, 1, 3, 5, 4, 2];

といった配列(ベクトル)のデータとして取り出すことが出来るようになった。

集合と関数


これで音といったアナログなデータをデジタルデータとして扱えるようになったわけで、

記憶媒体で音を記録できたり、ネット回線にデータを載せて送信できるようになるわけだ。

※今回は理解の為に単純にしているだけで、実際の値は今回のような単純なものではありませんでした


式のθに注目すると、左からθ、2θ、3θと順に数を大きくしている。

このように式に規則を持たせることで、

4θ、5θと式を続けることが出来るようになり、

更に複雑な周波数もデータとして捉えることが出来るようになる。

フーリエ級数 - Wikipedia


FFmpegとAudacityで動画の音声の調整に挑戦!


音声のような複雑な周波数のものを、

上記で書いたような配列(ベクトル)として取得する手法を高速フーリエ変換、通称FFTと呼ぶ。

高速フーリエ変換 - Wikipedia


波を取得した時に、

波を細かく区切って変換する必要があるといった細かいことは残っているが、

そこらへんを説明する力量はないので説明はここまでにしておく。


次回はJavaScriptのFFTを使ってみることにする。


- 続く -


追記

今回のグラフはHTML5 Canvasで書いてみた。

sinθの描写のコードは下記の通り。


<canvas id="canvas" width="600" height="200"></canvas>

<script>
    var canvas = document.querySelector("#canvas");
    var ctx = canvas.getContext("2d");

    //横軸
    ctx.beginPath();
    ctx.strokeStyle = "gray";
    ctx.moveTo(20, canvas.height / 2);
    ctx.lineTo(canvas.width - 20, canvas.height / 2);
    ctx.stroke();

    //縦軸
    ctx.beginPath();
    ctx.strokeStyle = "gray";
    ctx.moveTo(canvas.width / 2, 20);
    ctx.lineTo(canvas.width / 2, canvas.height - 20);
    ctx.stroke();

    var sliceW = (canvas.width - 40) / 720;
    var x = 20;

    for (var t = -360; t <= 360; t++) {
        var rad = t * (Math.PI / 180);
        var y = Math.sin(rad) * (-(canvas.height / 2 - 20)) + canvas.height / 2;

        ctx.beginPath();
        ctx.strokeStyle = "black";
        ctx.arc(x, y, 1, 0, Math.PI);
        ctx.stroke();

        x += sliceW;
    }

</script>