RSS Twitter Facebook

2014/12/25 (2014年12月 のアーカイブ)

WebAudioAPIのPeriodicWaveを波形テーブルから使う

Web Audio APIのオシレータのカスタム波形の使い方についての話です。

Web Audio APIのオシレータにはサイン波や鋸歯状波などの基本波形以外にカスタム波形というモードがあります。これを使うと自由な波形を設定できるのですが、ある意味便利、ある意味厄介な事にこの設定が通常の波形の形を指定するのではなく、倍音構成を表すPeriodicWaveと言うテーブルで指定するという仕様になっています。

Web Audio API (日本語訳) : createPeriodicWaveメソッド

オルガンのドローバーのようなものを作るには直接このテーブルの値をいじれば良いので結構音楽的ではあるのですが、逆にチップチューン的なアプリで出力波形を直接指定したい場合にはどうすれば良いのでしょうか。

このPeriodicWaveテーブルは各倍音の強さを表し、

[波形] =フーリエ変換=> [PeriodicWave] => [Oscillator] => [波形]

という関係になりますので、出したい波形をフーリエ変換する事で必要なPeriodicWaveテーブルを得る事ができます。無駄な処理をしているような気もしますが、それが仕様なので諦めましょう。
という事でフーリエ変換? と言えばFFT? どこかにライブラリあるかな? という方向に行くのも良いのですが、FFTというのはフーリエ変換の特殊な計算省略方法でしかないので、FFTなんか使わずにまともにフーリエ変換するという道もあります。

と言われてもなぁ、と思うかも知れませんが、まともなフーリエ変換なんて下の関数程度の話です。
function fourier(waveform,len){
	var real=new Float32Array(len),imag=new Float32Array(len);
	var wavlen=waveform.length;
	for(var i = 0; i < len; ++i){
		for(var j = 0; j < len; ++j){
			var wavj = j / len * wavlen;
			var d = waveform[wavj|0];
			var th = i * j / len * 2 * Math.PI;
			real[i] += Math.cos(th) * d;
			imag[i] += Math.sin(th) * d;
		}
	}
	return [real,imag];
}
この関数fourier()に任意の長さの波形そのままのテーブルwaveformと欲しいテーブルの長さlenを渡すと、periodicWaveを作るための real、imagのテーブルが返ってきます。waveformの長さと返すテーブルの長さは独立していますので、8ステップの入力に対して256の長さのテーブルを返すなど自由にできます。

なお、入力の波形は補間はせず、6-7行目あたりで階段状の波形として処理していますが、必要ならここで線形補間などを入れても良いと思います。また、振幅方向は何もスケーリングしていませんがオシレータが勝手に正規化しますので、入力するテーブルは-1~+1でも0~100でも何でもよくて各値の比率だけが重要です。出力にはreal[0]の所にDCオフセットが出てきますが、ChromeのオシレータはDCオフセットは無視します(これが仕様なのかどうかは今一明確ではありません)。気になるならreal[0]を0にしてDCオフセットを潰しておいた方が良いかも知れません。

この方法の良い所はFFTと違って変換の入出力の長さが2の累乗じゃなくても良い所で、自分が欲しい長さだけを計算する事ができます。

もちろん不利な点としてはテーブルが長くなると計算量が爆発的に多くなる所で、数K単位の長いテーブルを使いたいとか、頻繁にテーブルを作り直したいとか、他の所で既にFFTを使っているとかいう場合にはFFTを使った方が良いかも知れませんが、せいぜい数100要素程度の長さのテーブルを設定を変えた時だけ計算し直したい、というような場合にはわざわざ大きなFFTライブラリなんかを持ち込まないというのもありかなと思います。

これを使ったサンプルプログラムを下に置いてありますので触ってみてください。

http://www.g200kg.com/demo/test/fourier.html

Posted by g200kg : 2014/12/25 06:00:22