矩形波のデューティ比を変化させる

2016-11-01

ファミコンの音源は矩形波x2+三角波+ノイズという構成なんだけど、さらに矩形波にはデューティ比を変更させるという機能がある。 これをWebAudioで実現するにはどうしたらいいのかと考えていた。

仕組み

WebAudioにはオシレータで山と谷が等しい矩形波を生成することができるが、デューティ比を変更するような機能はない。 オシレータを使わずに自分で計算して作ったバッファに波形を構築してやればなんでもできるけど、面倒なのでできればしたくない。 でどうしたものかと思っていたら、実現しているサイトを見つけた: Oscilloscope and Pulse-Width control

どうやっているのかソースを見てみると、ノコギリ波を組み合わせて実現していた。 通常のノコギリ波に加えて、ゲインを反転させてデューティ比に合わせてディレイさせたものを組み合わせると、波が打ち消しあって矩形波が作り出せるようだ。

デモ

ということでデモを作ってみた:

playボタンで再生開始して、「Duty cycle」のスライダー、または数値入力でデューティ比を変更できる。 またキャンバスをドラッグで周波数とボリュームを変更できる。

ソース

内容は結構簡単で、音源となるノコギリ波を発生させるオシレータを生成し、そのオシレータから分岐させてゲインを反転させてディレイさせたものをつなげる。 そしてそれぞれを全体のボリュームをいじるゲインノードにつなぎ、最終的な出力ノードにつなげる:

//  [/|/|]
// oscillator ---------------+-> gain -> destination
// | [\|\|] [|\|\] |
// `-> inverter -> delay --'

var context = new AudioContext() // WebAudioのコンテキスト
var frequency = 220 // 鳴らす音程(周波数)
var dutyCycle = 0.5 // デューティ比
var volume = 1.0 // 音量
var oscillator = null // ノコギリ波のオシレータ
var gain = null // 音量ノード
var delay = null // デューティ比用の遅延

function createAudioNodes() {
oscillator = context.createOscillator() // オシレータ(音源)
oscillator.type = 'sawtooth' // オシレータのタイプにノコギリ波を指定

var inverter = context.createGain() // ノコギリ波から矩形波を作成するための反転ノード
inverter.gain.value = -1

delay = context.createDelay() // デューティ比再現用のディレイノード

gain = context.createGain() // 音量調整用ノード
gain.gain.value = 1

oscillator.connect(gain) // オシレータ->ゲイン
oscillator.connect(inverter) // オシレータ->反転
inverter.connect(delay) // 反転->ディレイ
delay.connect(gain) // ディレイ->ゲイン
gain.connect(destination) // ゲイン->出力
}

ユーザの操作に従って周波数やボリューム、デューティ比を変更する:

function updateAudio() {
oscillator.frequency.value = frequency // 音程更新
gain.gain.value = volume // 音量更新
delay.delayTime.value = (1.0 - dutyCycle) / frequency // デューティ比から設定すべき遅延を逆算
}

雑感

  • グラフでは赤と青のノコギリ波が打ち消し合うことを見やすくするため基準線を境に描いているが、実際にはWebAudioのノコギリ波は-1~+1の範囲
  • Web Audio Demosのページでは合成した値の基準を合わせるため、直流成分を加えている。ただ、そのゲインを 1.7*(0.5-amt) としているのがよくわからない…。
  • Web Audio Demosのページで発生している音の波形を表示するのにAudioContext.createAnalyserAnalyserNodeを作り周波数に分解し、またgetTimeDomainDataで波形そのものを取り出している。