The problem

A sine wave is not just a picture — it is a sound. Given the wave math from Graphs, generate real tones in the browser, combine two close pitches to hear beats, and shape notes with an envelope.

Tone math

A pure tone of frequency \(f\) Hz at amplitude \(A\) is:

$$ y(t) = A\sin(2\pi f t) $$

Musical pitch is logarithmic: A4 = 440 Hz and each semitone up multiplies by \(2^{1/12}\):

$$ f(n) = 440 \cdot 2^{n/12} $$

where \(n\) counts semitones from A4 (so A5 is \(n = 12\), C5 is \(n = 3\)).

Play two close frequencies together and you hear beats — a slow pulsing at the difference frequency:

$$ \sin(2\pi f_1 t) + \sin(2\pi f_2 t) = 2\cos\!\left(2\pi \tfrac{f_1-f_2}{2} t\right) \sin\!\left(2\pi \tfrac{f_1+f_2}{2} t\right) $$

Piano tuners use exactly this: the beats vanish when the strings match. Try 440 Hz + 442 Hz → 2 beats per second.

Full build (Web Audio)

var actx = new (window.AudioContext || window.webkitAudioContext)();

function tone(freq, startIn, dur, vol) {
  var osc = actx.createOscillator();
  var gain = actx.createGain();
  osc.type = 'sine';
  osc.frequency.value = freq;
  var t0 = actx.currentTime + startIn;
  // sine-shaped attack/decay envelope — no clicks
  gain.gain.setValueAtTime(0.0001, t0);
  gain.gain.exponentialRampToValueAtTime(vol, t0 + 0.03);
  gain.gain.setValueAtTime(vol, t0 + dur - 0.08);
  gain.gain.exponentialRampToValueAtTime(0.0001, t0 + dur);
  osc.connect(gain).connect(actx.destination);
  osc.start(t0);
  osc.stop(t0 + dur + 0.05);
}

function noteOffset(semitones) { return 440 * Math.pow(2, semitones / 12); }

// A major arpeggio: A4, C#5, E5, A5
[0, 4, 7, 12].forEach(function (n, i) {
  tone(noteOffset(n), i * 0.28, 0.3, 0.25);
});
print('arpeggio playing: A4 C#5 E5 A5');

// Beats demo: uncomment to hear 2 beats/sec
// tone(440, 0, 2.0, 0.2);
// tone(442, 0, 2.0, 0.2);

Browsers require a user gesture before audio starts — run this from a click (the playground’s Run button counts).

Try it

  1. Play a C major scale (offsets from A4: C5=3, D5=5, E5=7, F5=8, G5=10, A5=12, B5=14, C6=15). Verify each frequency against a tuner app.
  2. Uncomment the beats demo. Then change 442 → 443.5 and predict the new beat rate before listening.
  3. Visualize it: draw \(\sin(2\pi\cdot440t) + \sin(2\pi\cdot442t)\) on canvas for \(t\) in \([0, 1]\) second and find the 2-beat envelope in the picture.

Next: Project: Easing & Motion — sine curves that make interfaces feel alive.