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

仕組み

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

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

デモ

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

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

ソース

内容は結構簡単で、ノコギリ波を発生させるオシレータを全体のボリュームをいじるゲインノードにつなぎ、またそこから分岐させてゲインを反転させてディレイさせたものをつなげる:

  var context = new AudioContext()
  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'

    gain = context.createGain()
    gain.gain.value = 1
    oscillator.connect(gain)

    gain.connect(context.destination)

    var inverter = context.createGain()
    inverter.gain.value = -1
    gain.connect(inverter)

    delay = context.createDelay()
    inverter.connect(delay)

    delay.connect(context.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で波形そのものを取り出している。