ヴォコーダーを作ってみる


この記事は WebAudio Web MIDI API Advent Calendar 2016 の 8 日目の記事です。

はじめに

  • WebAudio.tokyoで存在を知って登録した。
  • 過去に挑戦したことあるお題。
  • おもったよりしょぼい。
  • なんとなくElectronで作った。
  • ソースはこちら

ヴォコーダーとは

こちら参照。

取り急ぎ図を書いてみた。

補足 : フォルマントに関して

リンク先にもあるように、発音している文字(母音)が同じならば周波数スペクトルが近いものとなる。
このため、同じような周波数スペクトルの波形を再現できれば、その文字(母音)が発音されているように聴こえる(はず)。
これがヴォコーダーの原理となっている。

実際に作ってみた

テスト用にスペクトル描画等も書いたけど、本筋じゃないので省く。

モジュレータ

声を入力する部分。ここでやることは以下の通り。

  • 声の入力を受け取る(現状ファイル入力のみ、フリーのサンプル音声等でやってみてください)。
  • 声の周波数スペクトル(≒倍音構成・フォルマント)を取得する。

以下、周波数スペクトル(≒倍音構成・フォルマント)を取得する部分。

  • 倍音構成を求めるにあたって、基準周波数はとりあえず(0,300)の間の最大値を利用している。
  • getFloatFrequencyDataで取れるのはデシベル(getByteFrequencyDataはさらに[0,255]で正規化している)のようなので、ゲインに変換している(計算あってないかもしれない)。
Modulator.js
    this.GetOvertone = function() {
        this.analyserNode.getFloatFrequencyData(this.spectrumData);

        var maxDb       = this.analyserNode.maxDecibels;
        var minDb       = this.analyserNode.minDecibels;
        var sampleRate  = VOCODER.audioCtx.sampleRate;
        var fftSize     = this.analyserNode.fftSize;
        var octave      = 1;
        var baseFreq    = 0;
        var baseFreqDb  = minDb;
        var overtone    = [];

        this.spectrumData.forEach(function(db, idx) {
            var nowFreq  = (sampleRate * idx) / fftSize;

            if ((0 < nowFreq && nowFreq < 300) && baseFreqDb < db) {
                baseFreqDb = db;
                baseFreq   = nowFreq;
            }
        });

        this.spectrumData.forEach(function(db, idx) {
            //var db       = (byteData * ((maxDb - minDb) / 255)) + minDb;
            var gain     = Math.pow(10, db / 20);
            var prevFreq = (sampleRate * (idx - 1)) / fftSize;
            var nowFreq  = (sampleRate * idx)       / fftSize;

            if (prevFreq < (baseFreq * octave) && (baseFreq * octave) <= nowFreq) {
                // 倍音の時のみゲインを追加していく
                overtone.push(gain);

                octave++;
            }
        });

        return { f : baseFreq, overtone : overtone };
    };

キャリア

楽器音(変調させる音)を入力する部分。ここでは出力音をオシレーターのカスタム波形で代用する。

すなわち入力は不要で、やることは以下の通り。

  • 定期的に、カスタム波形の周波数及び係数列を書き換える(変調処理の代わり)。

以下、カスタム波形の周波数・係数列を書き換える部分。

  • createPeriodicWaveに係数の配列を渡すだけで良い(比率しか見られないので正規化は不要)。
  • requestAnimationFrame内で先のGetOvertoneで取得したオブジェクトをreloadWaveに渡している。
Carrier.js
    this.reloadWave = function (obj) {
        var data = obj.overtone;
        var real = new Float32Array(data.length + 1);
        var imag = new Float32Array(data.length + 1);

        var isMute = true;

        real[0] = 0.0;
        imag[0] = 0.0;

        for (var i = 0; i < 10; i++) {
            real[i + 1] = data[i];
            imag[i + 1] = data[i];

            if (data[i] > 0.01) {
                isMute = false;
            }
        }

        if (isMute) {
            console.warn('mute');
            for (i = 0; i < 10; i++) {
                real[i + 1] = 0.0;
                imag[i + 1] = 0.0;
            }
        }

        var waveTable = VOCODER.audioCtx.createPeriodicWave(real, imag);
        this.oscillator.setPeriodicWave(waveTable);
        this.oscillator.frequency.value = obj.f;
    };

動かしてみた

  • 女声・比較的遅めのナレーション音源で試してみた。
  • 初見だと何言ってるか分からないレベル。
  • セリフ覚えていればそれっぽく感じるところは多々ある。
  • 入出力のスペクトルを描画してみると、ある周波数を超えた辺りで急激に弱くなる。

反省

  • これがヴォコーダーと言えるのか(原理を正しく理解できているのか)不安。
  • ギリギリまで手を付けれなかったため、記事のクオリティが低くなった。
  • クソコード。

WebAudio.tokyo で発表しました!

スライドはこちら