Oscillators
To create an oscillator, instantiate an OscillatorNode.  Once assigned to a
variable, you may adjust its pitch with the .frequency and .detune
properties: .frequency is a number in Hz; .detune is a number in cents.  In
addition, you may change the shape of the waveform by assigning the .type
property:  The default value is "sine", but you may change that to
"square", "sawtooth", or "triangle".  Once you hook up the oscillator to
a GainNode and the dac, don't forget to call its .start() method, which is
what actually tells the oscillator to start producing sound.
The .start() method takes one optional argument: the time (in seconds) after
which the oscillator should begin playing.  If you omit this argument, the
method supplies a default value of audioCtx.currentTime, which is the
equivalent of saying, "start immediately".  In general, it is good practice to
declare time offsets in relation to audioCtx.currentTime, so instead of
writing .start(2), you should write .start(audioCtx.currentTime + 2).
// Create a sawtooth wave with a frequency of 220 (aka A3).
const oscillator = audioCtx.createOscillator();
oscillator.frequency.value = 220;
oscillator.type = "sawtooth";
const oscillatorGain = audioCtx.createGain();
oscillatorGain.gain.value = 0.1;
oscillator.connect(oscillatorGain);
oscillatorGain.connect(audioCtx.destination);
// Start oscillator after 2 seconds have passed
oscillator.start(audioCtx.currentTime + 2);