Music Theory for Programmers
Music Theory for Programmers
I’ve never been able to master a musical instrument. Every time I tried, I could mimic the sounds, but I never grasped why those sounds worked. Most music theory tutorials
ignore the fundamentalsskip the underlying physics and arithmetic that govern the system.
Because music is essentially a byproduct of mathematics, it can be computed. To truly understand it, I decided to rebuild music theory from the ground up using code.
The Roadmap
- Define sound as a numerical value
- Implement basic oscillators
- Create volume envelopes
- Explore timbre and harmonics
- Derive the concept of the octave
1. Sound is Just a Changing Number
At its core, sound is simply the oscillation of air pressure. A speaker produces this by moving a cone back and forth. In a digital environment, this is represented as a sequence of numbers indicating the cone's position, sampled times per second.
A synthesizer generates these numbers on the fly. In a web browser, you can specify the "shape" of the wave. The most basic form is the sine wave.
// Setup for browser audio
const ctx = new AudioContext();
const out = ctx.destination;
// A sine wave oscillating at 440Hz
const osc = ctx.createOscillator();
osc.frequency.value = 440;
osc.connect(out);
osc.start();
osc.stop(ctx.currentTime + 1);
The value 440 is the only musically significant part of this code. It represents the frequency of the note A, which serves as the universal "tuning fork" for the rest of the system.
2. Solving the "Click" with Envelopes
If you stop an oscillator abruptly, you'll hear a sharp click. This happens because the speaker cone is forced to snap back to center instantly from a non-zero position. To fix this, we need a second changing number to control volume.
Musicians call this an Envelope.
An envelope defines the life cycle of a note:
- Attack: The time it takes to go from silence to peak volume.
- Decay: The fall from the peak to a steady level.
By adjusting the attack, you change the character of the sound. A short attack sounds like a pluck; a long attack sounds like a bowed string. The pitch remains identical, but the perception changes.
The professional standard is the ADSR model:
- Attack
- Decay
- Sustain (the level held while the key is pressed)
- Release (the fade-out after the key is let go)
function note(freq, start = 0, length = 0.5, type = 'sine') {
const osc = ctx.createOscillator();
const gain = ctx.createGain();
osc.type = type;
osc.frequency.value = freq;
// Simple envelope: 10ms attack, then decay
gain.gain.setValueAtTime(0, ctx.currentTime + start);
gain.gain.linearRampToValueAtTime(1, ctx.currentTime + start + 0.01);
gain.gain.exponentialRampToValueAtTime(0.001, ctx.currentTime + start + length);
osc.connect(gain);
gain.connect(out);
osc.start(ctx.currentTime + start);
osc.stop(ctx.currentTime + start + length);
}
3. Timbre: The Frequencies You Didn't Ask For
A pure sine wave sounds like a medical hearing test because it contains only one frequency. Real instruments are messier. When you pluck a string at , it doesn't just vibrate as a whole; it also vibrates in halves, thirds, and quarters.
These additional frequencies are called harmonics. The primary frequency is the fundamental.
The Harmonic Series for 220Hz
| Harmonic | Multiplier | Frequency |
|---|---|---|
| 1st (Fundamental) | ||
| 2nd | ||
| 3rd | ||
| 4th | ||
| 5th |
The specific blend of these harmonics is what we call Timbre. It is why a trumpet sounds different from a violin, even when playing the same note.
The browser provides four built-in "recipes" for timbre:
['sine', 'triangle', 'square', 'sawtooth']
['sine', 'triangle', 'square', 'sawtooth'].forEach((type, i) => {
note(220, i * 0.7, 0.6, type);
});
Note: A square wave sounds "hollow" because it only contains odd-numbered harmonics.
4. The Magic of the Octave
If you play a series of notes where each frequency is exactly double the previous one, they will sound like the same note, just higher or lower.
In Western music, this interval is called an Octave. This isn't an arbitrary choice; it's rooted in the harmonic series.
If we look at the harmonics of and :
- 220Hz Series:
- 440Hz Series:
Because every harmonic of the higher note is already present in the lower note's series, the ear perceives them as having the same "color," just at a different brightness. The mathematical ratio is simply .