RSS Twitter Facebook
g200kg > Web Audio API 解説 > 03.オシレーターの使い方

Web Audio API 解説

2019/01/19

03.オシレーターの使い方



パラメータの指定

オシレーターには周波数の指定や波形の指定などのパラメータがありますので、これらを設定できるようにしてみます。HTML 上には波形、周波数、レベルを設定する UI があります。

ノードの接続は osc => gain => destination となっていて、type と freq は osc を、level は gain をコントロールします。


<!DOCTYPE html>
<html>
<body>
<h1>Oscillator</h1>
<button onclick="Play()">Play</button><br/>
<table>
<tr><th>Type</th><td><select id="type" onchange="Setup()"><option value="sine">Sine</option><option value="square">Square</option><option value="sawtooth">SawTooth</option><option value="triangle">Triangle</option></select></td></tr>
<tr><th>Freq(Hz)</th><td><input type="range" min="50" max="3000" id="freq" value="440" oninput="Setup()"/></td><td id="freqdisp">1000</td></tr>
<tr><th>Level</th><td><input type="range" min="0" max="1" step="0.01" value="0.5" id="level" value="1" oninput="Setup()"/></td><td id="leveldisp">0.5</td></tr>
</table>
<script>

var playing = false;
function Play(){
    if(playing)
        return;
    audioctx = new AudioContext();
    osc = new OscillatorNode(audioctx);
    gain = new GainNode(audioctx);
    osc.connect(gain).connect(audioctx.destination);
    osc.start();
    playing = true;
}
function Setup() {
    var type = document.getElementById("type").value;
    var freq = parseFloat(document.getElementById("freq").value);
    var level = parseFloat(document.getElementById("level").value);
    document.getElementById("freqdisp").innerHTML = freq;
    document.getElementById("leveldisp").innerHTML = level;

    osc.type = type;
    osc.frequency.value = freq;
    gain.gain.value = level;
}

</script>
</body>
</html>

なお、波形の指定(type)の内、5番目の "custom" は単純に波形を指定するのではなく、倍音のスペクトラムを指定するという凝った仕様になっているのですが、使い方が違いますのでこちらを参照してください。⇒ オシレータのカスタム波形

テストページ:オシレーターの使い方

パラメータの多くは AudioParam というオブジェクトになっていて、直接値を代入するのではなく .value プロパティに値を設定します。ただしパラメータによっては AudioParam ではなく、直接値を入れるものもありますので 仕様書 で確認が必要です (16.各ノードのパラメータ一覧に一覧にしてありますので参照してください)


オシレーターのパラメータおよびメソッド

パラメータ単位デフォルト値値の範囲内容
type文字列-"sine""sine"
"square"
"sawtooth"
"triangle"
"custom"
波形の指定
"sine" : サイン波
"square" : 矩形波
"sawtooth":鋸歯状波
"triangle":三角波
"custom":カスタム(直接指定できない)
frequencyAudioParamHz440-ナイキスト周波数

+ナイキスト周波数
周波数の指定。範囲の「ナイキスト周波数」はサンプリング周波数の1/2で、大抵のブラウザ実装では22050となります。周波数に負の値を設定すると位相が反転します。
このパラメータは a-rate です。
detuneAudioParamセント0約 -153600 ~ 約 +153600デチューン。本来の周波数からずらします。単位の「セント」は半音の 1/100を表し、1200で 1 オクターブとなります。
このパラメータは a-rate です。
start(when)
旧:noteOn
関数---再生開始。whenは開始時刻(秒)
stop(when)
旧:noteOff
関数---再生停止。whenは停止時刻(秒)
setPeriodicWave
(PeriodicWave)
関数---カスタム波形の指定


オシレーターのstart() / stop()

最初の例ではオシレータを作って start() する事で信号を出しましたが音を止める時には stop() というメソッドを呼び出します。しかし、ここで誤解されやすそうな仕様があって、一度 stop() して止めたオシレータはもう二度と音を出す事はできません。

これはワンショットサンプルなどの音源として使う AudioBufferSource でも同じで一度 start() して再生し終わるか stop() で止めるともうその AudioBufferSource は使えなくなります。というのも Oscillator や AudioBufferSource は使い捨てで 1 音出す度にどんどん新しいオブジェクトを作っては再生して捨てる、というような使い方を想定しているんですね。MIDI で言う NoteOn / NoteOff の組がひとつのオブジェクトに相当しているような感じです。AudioBufferSource はともかく「Oscillator」という名前は誤解を招きやすいとは思いますが。

もちろん作成したオシレータを再生しっぱなしで確保しておいて後ろに繋げた Gain ノードでゲートする事はできます。アプリケーションにもよりますが、Oscillator を使ってシンセサイザーのようなものを作ろうとする場合はそういう形が多くなるのではないかと思います。


オシレーターのタイミング制御

オシレータを動かす際に今までは start() という関数を呼び出していましたが、この関数は引数を取る事ができ、動作を開始する時刻を指定できます。 時刻が過去の場合、例えば 0 を指定すると関数が呼び出されたら即時に動作を開始する事を意味していますが、現在の時刻はオーディオコンテキストのプロパティ .currentTime にありますのでこれを基準にあらかじめ動作の時刻を指定する事ができます。例えば、start(audioctx.currentTime + 1) ならば今から1秒後にオシレータが作動します。

実際のアプリ開発で実用的なものを作ろうとした場合、リズムがヨタらない演奏をするには時刻を指定する事が推奨されます。というのもこの時刻による制御によらないとすると Javascript の setInterval() などでタイミングを決める事になりますが、これだと処理が重くなって発音が遅れたりすることが普通に起こります。

下の例では Setup() が呼ばれた時にあらかじめ必要なオシレータと時刻を設定してしまいます。0.5秒ごとに0.1秒の長さの音を出すオシレータを10個作っています。基準となる時刻はSetup()が呼ばれた時の時刻 t0 で常にここからの計算になっている事が重要です。



<!DOCTYPE html>
<html>
<body>
<h1>Oscillator Timing Control</h1>
<br/>
<button onclick="Start()">Start</button><br/>
<script>
function Start() {
    var audioctx = new AudioContext();
    var t0 = audioctx.currentTime;
    for(var i = 0; i < 10; ++i) {
        var osc = new OscillatorNode(audioctx);
        osc.connect(audioctx.destination);
        osc.start(t0 + i * 0.5 + 0.5);
        osc.stop(t0 + i * 0.5 + 0.6);
    }
}
</script>
</body>
</html>
テストページ:オシレーターのタイミング制御

もっと実用的な実装にするには

なお、このようにあらかじめタイミングを予約する事ができるからと言って、あまりに大量の予約を詰め込む事は推奨されていません。発音の予定を組んでしまった分はユーザーの操作に反応できなくなっちゃいますしね。

じゃあどうするかと言うと、この記事「A Tale of Two Clocks - Scheduling Web Audio with Precision」に詳しいのですが、setInterval()やsetTimeout()のようなタイマーである程度発音すべき情報を先読みしつつ、ちょっと先の予定までstart()のスケジューリング機能で予定を組む、という合わせ技が必要になります。大体数十mSec間隔程度のタイマーで予定を組みながら動作する、というのがWeb Audio APIが想定している動作のようです。




g200kg