ばねモデルで簡単にリアルタイム周波数解析


概要

FFTなど難しいことなしにばねモデルの共振を利用してリアルタイム周波数解析ができないだろうか?
60FPSで毎フレーム。

方法

対応する音階を固有振動数として共振を再現するクラスResonatorを作成して、
80個のインスタンスを生成する。

  class Resonator {
    constructor(parentElm, sampleRate, scale) {
      this.y = 0;
      this.v = 0;
      const f = Math.pow(2, (scale / 12) - 3) * 440 / sampleRate;
      const omega = 2 * Math.PI * f;
      this.k = 0.5;
      this.m = this.k / Math.pow(omega, 2);
      this.b = 0.5;

毎サンプルごとに以下を実行する。
元信号を基準位置としたダンパー付きのばねモデルを再現しているだけ。
これにより、サンプルが特定の周波数で変化している場合、対応する固有振動数のResonatorが共振する。

        const sample = audioData[index + i];
        const f = this.k * (sample - this.y) - this.b * this.v;
        const a = f / this.m;
        this.v += a;
        this.y += this.v;

60FPSとして、1フレームでは以下になる。48000サンプルとしたら毎フレーム800サンプルを処理する。
描画するために最大振幅を計算している。

    update(audioData, index, count) {
      let yMax = 0;
      for (let i = 0; i < count; ++i) {
        const sample = audioData[index + i];
        const f = this.k * (sample - this.y) - this.b * this.v;
        const a = f / this.m;
        this.v += a;
        this.y += this.v;
        yMax = Math.max(yMax, this.y);
      }
      this.elm.style.height = `${16 + 32 * yMax}px`;

全コード

デモ

ドミソの和音が検出できているところ。

まとめ

このような簡単な処理でもリアルタイム周波数解析が一応できる。

余談

共振した結果を実際にWeb Audioで80個のサイン波を鳴らして、毎フレーム音量を更新すると元の音楽に聞こえる。

        this.gainNode = audioCtx.createGain();
        this.gainNode.value = 0;
        this.gainNode.connect(audioCtx.destination);
        this.oscillatorNode = audioCtx.createOscillator();
        this.oscillatorNode.frequency.value = Math.pow(2, (scale / 12) - 3) * 440;
        this.oscillatorNode.connect(this.gainNode);
        this.oscillatorNode.start();
...
        this.gainNode.gain.value = yMax / 16;