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

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

[webaudio]:http://qiita.com/advent-calendar/2016/webaudio

# はじめに
- WebAudio.tokyoで存在を知って登録した。
- 過去に挑戦したことあるお題。
- おもったよりしょぼい。
- なんとなくElectronで作った。
- ソースは[こちら](https://github.com/Thought-Nibbler/Vocoder)。

# ヴォコーダーとは

[こちら][Vocoder]参照。
[Vocoder]:https://ja.wikipedia.org/wiki/%E3%83%B4%E3%82%A9%E3%82%B3%E3%83%BC%E3%83%80%E3%83%BC

取り急ぎ図を書いてみた。
![vocoder.png](https://qiita-image-store.s3.amazonaws.com/0/36612/2acdb422-b5a0-14f2-358f-a5b18df97f76.png)

## 補足 : フォルマントに関して
- [フォルマント][formant]
[formant]:https://ja.wikipedia.org/wiki/%E3%83%95%E3%82%A9%E3%83%AB%E3%83%9E%E3%83%B3%E3%83%88

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

# 実際に作ってみた

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

## モジュレータ

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

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

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

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

```js: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`に渡している。

```js: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 で発表しました！

スライドは[こちら](http://qiita.com/Thought_Nibbler/private/081ee0bcf08b2898563b)