The words Under construction in black text on a yellow background with diagonal black stipes surrounding it
I'm in the process of moving my site. It's still a work in progress. Please excuse the mess and broken links.

Web Audio API Tone Generator With Gain Control

TODO: Pull subtitle into page object

Notes

Debugging Stuff

I'm moving stuff around right now. All this below is helping me figure out where to put stuff

        -- title

Web Audio API Tone Generator With Gain Control

This is a basic Tone Generator wrapped in
a class. It uses the Oscillator node
from the Web Audio API. 

-- html

<div>
<button id="startStopButton">Start</button>
</div>

<div>
<input id="gainControl" type="range" min="0.0" max="1.0" step="0.01" />
</div>

-- script
-- title: Tone Generator 
-- show

class ToneGenerator {
  constructor() {
    this.audioCtx = new window.AudioContext
    this.gainNode = this.audioCtx.createGain()
    this.hertz = 440
    this.oscillator = null
    this.isPlaying = false
  }

  setGain(gain) {
    this.gainNode.gain.setTargetAtTime(
      gain, this.audioCtx.currentTime, 0.01
    )
  }

  start(gain) {
    this.setGain(gain)
    this.oscillator = this.audioCtx.createOscillator()
    this.oscillator.frequency.setValueAtTime(
      this.hertz, this.audioCtx.currentTime
    )
    this.oscillator
      .connect(this.gainNode)
      .connect(this.audioCtx.destination)
    this.oscillator.start()
  }

  stop() {
    this.oscillator.stop()
  }
}

-- script
-- title: Setup 
-- show

const toggleGenerator = (tg) => {
  if (tg.isPlaying === false) {
    const startingGain = gainControl.value
    tg.start(startingGain)
    tg.isPlaying = true 
    startStopButton.innerText = "Stop"
  } else {
    tg.stop()
    tg.isPlaying = false 
    startStopButton.innerText = "Start"
  }
}

document.addEventListener("DOMContentLoaded", () => {
  const tg = new ToneGenerator()
  startStopButton.addEventListener("click", () => { 
    toggleGenerator(tg)
  })
  gainControl.addEventListener("input", (event) => {
    tg.setGain(event.target.value)
  })
})

-- notes
-- title: Notes

- Some examples show using:  

gainNode.gain.value = ###

Doing that can cause popping/crackling 
sounds on every change. It's especially
bad using an input range slider. 

The `setTargetAtTime()`` show above prevents
that. It's sent the target gain, a startTime, 
and a timeConstant. By setting the startTime
to .currentTime() the change starts to 
take place immediately. The timeConstant defines
an "exponential approach" to the target gain. 
Using a low value like "0.01" make it happen
fast enough that it feels instant. 



-- ref
-- url: https://stackoverflow.com/a/35380694/102401

-- ref
-- url: https://developer.mozilla.org/en-US/docs/Web/API/AudioParam/setTargetAtTime

-- ref
-- url: https://webaudio.github.io/web-audio-api/#OscillatorNode

-- ref
-- url: https://developer.mozilla.org/en-US/docs/Web/API/OscillatorNode

-- ref
-- url: https://developer.mozilla.org/en-US/docs/Web/API/AudioParam

-- ref
-- url: https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API/Using_Web_Audio_API





-- categories
-- JavaScript 
-- Web Audio API
-- Snippets

-- metadata
-- date: 2023-08-31 15:01:11
-- id: 2uljwuzp
-- site: aws
-- type: snippet 
-- status: published