この記事は WebAudio Web MIDI API Advent Calendar 2016 の 8 日目の記事です。
はじめに
- WebAudio.tokyoで存在を知って登録した。
- 過去に挑戦したことあるお題。
- おもったよりしょぼい。
- なんとなくElectronで作った。
- ソースはこちら。
ヴォコーダーとは
[こちら][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
補足 : フォルマントに関して
- [フォルマント][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]
で正規化している)のようなので、ゲインに変換している(計算あってないかもしれない)。
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 で発表しました!
スライドはこちら