[ Jpn | Eng ]

Main Menu



Recent

最近の記事

Search

サイト内検索:

Archive

Powered by
MTOS 5.2.2


g200kg > Web Audio API 解説 > 11.オシレーターのカスタム波形

Web Audio API 解説

2016/11/14

11.オシレーターのカスタム波形



カスタム波形の基本

Oscillatorノードで使用できる波形選択に "custom" というものがあり、これを使うとどんな波形でも自由に定義できます。ただし単純な波形のテーブルで定義するのではなく波形のスペクトラムで定義するというちょっとわかりにくい仕様になっています。簡単に言えば、倍音(ハーモニクス)の強さを指定して波形を作るという方法で、オルガンのドローバーのようなものと考えればよいかと思います。

まずハーモニクスのテーブルから createPeriodicWave()で periodicWave オブジェクトを作り、それを Oscillator に setPeriodicWave() で渡します。

Oscillator の通常の波形選択で使用する .type プロパティは setPeriodicWave()を呼び出す事で勝手に "custom" になりますが、直接ここに "custom" の値を入れる事はできません。

var real = new Float32Array(10);
var imag = new Float32Array(10);
for(var i = 0; i < 10; ++i)
	real[i] = imag[i] = 0;
imag[1] = 1;
imag[2] = 0.5;
var wavtable = audioctx.createPeriodicWave(real, imag);
osc.setPeriodicWave(wavtable);

そして波形指定の内容となる real と imag は4096以下で同じ長さの Float32Array です。ここには、任意の波形1周期分をFFTにかけた結果と同じものを設定しますが、簡単にドローバー的に使う場合には、imag の 2番目 (imag[1]) から順に値を設定します。それぞれ基準となる音、2倍音、3倍音、4倍音・・・の強さとなります。波形の大きさは最終的にピークが1の振幅になるように正規化されますので、テーブルの各値の比率だけ気にしていれば良いです。

また real[0] はDCオフセットをあらわします。ただし Chrome の実装ではこれは無視されるようです。imag[0] は使用禁止で必ず 0 にします。

FFT の定義的な所から言えば real 側のテーブルは cos() による合成、 imag 側のテーブルは sin() による合成になり、位相がずれるだけで同じ大きさの値が入っていればどちらのテーブルを使っても聴感上はほとんど同じように聴こえるのですが、波形としてはかなり違うものになります。

フーリエ解析の説明などでよく出てくる「サイン波を合成して色々な波形を作る」という例と同じように波形を作るには imag 側を使用します。

カスタム波形を使用したサンプル

ではカスタム波形を使用したサンプルです。図のような構成になっています。

<!DOCTYPE html>
<html>
<head></head>
<body>
<h1>Oscillator Custom waveform</h1>
<img src="../images/osccustom.png"/>
<hr/>
<div>
<div style="float:left;margin:5px">
<table>
<tr><td>Freq</td><td><input type="text" size="10" id="freq" value="440"/></td></tr>
<tr><td>Gain</td><td><input type="text" size="10" id="gain" value="0.5"/></td></tr>
<tr><td><br/></td></tr>
<tr><td>Harmonics</td><td>real</td><td>imag</td></tr>
<tr><td>0</td><td><input type="range" min="0" max="1" step="0.01" size="10" id="real0" value="0" onchange="Setup()"/></td><td><input type="range" min="0" max="1" step="0.01" size="10" id="imag0" value="0" onchange="Setup()"/></td></tr>
<tr><td>1</td><td><input type="range" min="0" max="1" step="0.01" size="10" id="real1" value="0" onchange="Setup()"/></td><td><input type="range" min="0" max="1" step="0.01" size="10" id="imag1" value="1" onchange="Setup()"/></td></tr>
<tr><td>2</td><td><input type="range" min="0" max="1" step="0.01" size="10" id="real2" value="0" onchange="Setup()"/></td><td><input type="range" min="0" max="1" step="0.01" size="10" id="imag2" value="0.5" onchange="Setup()"/></td></tr>
<tr><td>3</td><td><input type="range" min="0" max="1" step="0.01" size="10" id="real3" value="0" onchange="Setup()"/></td><td><input type="range" min="0" max="1" step="0.01" size="10" id="imag3" value="0" onchange="Setup()"/></td></tr>
<tr><td>4</td><td><input type="range" min="0" max="1" step="0.01" size="10" id="real4" value="0" onchange="Setup()"/></td><td><input type="range" min="0" max="1" step="0.01" size="10" id="imag4" value="0" onchange="Setup()"/></td></tr>
<tr><td>5</td><td><input type="range" min="0" max="1" step="0.01" size="10" id="real5" value="0" onchange="Setup()"/></td><td><input type="range" min="0" max="1" step="0.01" size="10" id="imag5" value="0" onchange="Setup()"/></td></tr>
<tr><td>6</td><td><input type="range" min="0" max="1" step="0.01" size="10" id="real6" value="0" onchange="Setup()"/></td><td><input type="range" min="0" max="1" step="0.01" size="10" id="imag6" value="0" onchange="Setup()"/></td></tr>
<tr><td>7</td><td><input type="range" min="0" max="1" step="0.01" size="10" id="real7" value="0" onchange="Setup()"/></td><td><input type="range" min="0" max="1" step="0.01" size="10" id="imag7" value="0" onchange="Setup()"/></td></tr>
</table>
<button onclick="Setup()">Set</button><br/>
</div>
<div style="float:left;margin-top:30px;">
<canvas id="waveform" width ="512" height="256">  </canvas>
</div>
</div>
<script type="text/javascript">

window.AudioContext = window.webkitAudioContext||window.AudioContext;
var audioctx = new AudioContext();

var osc = audioctx.createOscillator();
var gain = audioctx.createGain();
var cap = audioctx.createScriptProcessor(1024, 1, 1);
cap.onaudioprocess = Capture;

var freq = 440;
var synccount = 0;
var capturebuf = new Array(512);
var capindex = 0;

osc.connect(gain);
gain.connect(cap);
cap.connect(audioctx.destination);

var tablen = 8;
var real = new Float32Array(tablen);
var imag = new Float32Array(tablen);
var start = 0;

function Setup() {
	if(start == 0) {
		osc.start(0);
		start = 1;
	}
	osc.start(0);
	for(i = 0; i < tablen; i++) { // make Harmonics
		real[i] = parseFloat(document.getElementById("real"+i).value);
		imag[i] = parseFloat(document.getElementById("imag"+i).value);
	}
	var waveTable = audioctx.createPeriodicWave(real, imag);	//create waveTable
	osc.setPeriodicWave(waveTable);	//set to Oscillator
	freq = parseFloat(document.getElementById("freq").value);
	osc.frequency.value = freq;
	gain.gain.value = parseFloat(document.getElementById("gain").value);
}

/////////////for Display
function Capture(ev) {
	var inbuf = ev.inputBuffer.getChannelData(0);
	var outbuf = ev.outputBuffer.getChannelData(0);
	var period = audioctx.sampleRate / freq;
	for(var i = 0; i < 1024; ++i) {
		outbuf[i] = inbuf[i];
		if(capindex < 512) capturebuf[capindex++] = inbuf[i];
		if(++synccount >= period) {		// for Display Sync
			synccount -= period;
			if(capindex >= 512) capindex = 0;
		}
	}
}

function DrawGraph() {
	var canvas = document.getElementById("waveform");
	var ctx = canvas.getContext('2d');
	ctx.fillStyle = "#222222";
	ctx.fillRect(0, 0, 512, 512);
	ctx.fillStyle = "#00ff44";
	ctx.fillRect(0, 128, 512, 1);
	for(var i = 0; i < 512; ++i) {
		var v = 128 - capturebuf[i] * 128;
		ctx.fillRect(i, v, 1, 128 - v);
	}
}
setInterval(DrawGraph, 100);


</script>

</body>
</html>

ちょっと長ったらしくなっていますが、実際にオシレーターから出てくる波形を確認したかったので、ScriptProcessor で生データをキャプチャーして画面が流れないように同期を取りつつ表示するという小細工を行っています。

テストページ:オシレータのカスタム波形




g200kg