LoginSignup
13
7

More than 5 years have passed since last update.

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

Last updated at Posted at 2016-12-07

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

はじめに

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

ヴォコーダーとは

こちら参照。

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

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

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

実際に作ってみた

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

モジュレータ

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

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

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

  • 倍音構成を求めるにあたって、基準周波数はとりあえず(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 で発表しました!

スライドはこちら

13
7
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
13
7