Last episode we made a computer sing, and I ended on a promise that I've been quietly excited about all week: we stopped just playing pre-made synths and asked a bolder question - what if you built the sound yourself, from the raw ingredients, deciding exactly what it is from the ground up? That's today. We're going to open up a synth, look at the guts, and learn the handful of techniques that every instrument you've ever heard is built from. Allez, roll your sleeves up, we're making sounds from scratch :-).
Here's the thing that took me years to really believe: there is no magic in a synthesizer. There's no secret sauce. Every fat bass, every glassy bell, every screaming lead is a small number of simple parts wired together in a slightly different order. Once you see the parts, you stop being a person who picks sounds and become a person who makes them. And it's the exact same shift we made way back with visuals - the moment you understood pixels and loops, you stopped downloading other people's filters and started writing your own. Same story, new sense. Let me show you the parts.
Before any code, one idea that unlocks everything. A French mathematician named Fourier worked out something genuinly wild two hundred years ago: any sound, no matter how complex - a violin, a voice, a car horn - can be broken down into a pile of plain sine waves added together. And the reverse is the good bit for us: if you add sine waves together in the right amounts, you can build any sound you want. The recipe for a timbre is just a list of frequencies and how loud each one is.
// Fourier's gift to sound designers: every timbre is a recipe of sine waves.
// a "harmonic" is a sine at an integer multiple of the base (fundamental) frequency.
const harmonics = {
fundamental: "the base pitch you actually hear. 220Hz = the note A3.",
overtone2: "440Hz - one octave up. adds body.",
overtone3: "660Hz - adds brightness & 'reediness'.",
overtone4: "880Hz - two octaves up, adds sparkle.",
// the MIX of how loud each one is = the character of the instrument.
};
The pitch you name (the "A" you press) is the fundamental. Everything stacked above it at integer multiples is an overtone or harmonic, and the particular blend of how loud each harmonic is - that, and almost nothing else, is what makes an oboe sound like an oboe and not a flute. Same note, different recipe. See where this is going?
The most direct way to use Fourier's idea is called additive synthesis: literally create one oscillator per harmonic and add them together. It's the honest, brute-force approach - old pipe organs work this way with physical pipes, and the earliest electronic synths did too. Let's stack a few sines by hand and hear a timbre appear out of nothing.
// additive: one oscillator PER harmonic, mixed into a shared output.
// stacking a fundamental + a few quieter overtones builds a richer tone.
function additiveTone(baseFreq) {
const gain = new Tone.Gain(0.15).toDestination(); // shared mixer, kept quiet
const partials = [1, 2, 3, 4, 5]; // harmonic numbers
return partials.map((n) => {
const osc = new Tone.Oscillator(baseFreq * n, "sine");
// higher harmonics get quieter - a natural 1/n rolloff sounds musical
const level = new Tone.Gain(1 / n).connect(gain);
osc.connect(level);
return osc.start();
});
}
// build a tone on A2 (110Hz). five sines, and it already sounds like an "instrument".
additiveTone(110);
Play with the amplitudes and the whole character shifts. Here's a lovely quirk worth knowing: if you keep only the odd harmonics (1, 3, 5, 7...) you get a hollow, woody sound - that's basically a clarinet. Keep all the harmonics with a gentle rolloff and it's warmer and fuller, more like a string. The recipe genuinly is the instrument.
// odd harmonics only -> hollow & woody (clarinet-ish).
// the amplitude of each odd harmonic falls off as 1/n for a square-wave-like tone.
function oddHarmonics(baseFreq, count = 6) {
const out = new Tone.Gain(0.2).toDestination();
for (let i = 0; i < count; i++) {
const n = 2 * i + 1; // 1, 3, 5, 7, ...
const osc = new Tone.Oscillator(baseFreq * n, "sine");
osc.connect(new Tone.Gain(1 / n).connect(out)).start();
}
}
Additive is beautifully pure but a bit of a faff - a rich sound might need twenty oscillators, and to make it evolve you'd have to automate all twenty amplitudes over time. Powerful, expensive, fiddly. Which is exactly why someone clever flipped the whole idea on its head.
Instead of building up from silence, subtractive synthesis starts with a sound that already has tons of harmonics - a sawtooth or a square wave - and then removes the ones you don't want with a filter. It's sculpture instead of construction: begin with a big rough block and chisel away. This is the sound of nearly every classic synth you can name, and the reason is simple - it gets you 80% of the way with about three parts.
We already met filters last episode, so this'll click fast. The classic subtractive patch is three boxes in a row: a rich oscillator, a lowpass filter, and an amplitude envelope.
// the classic subtractive patch: rich osc -> lowpass filter -> amp envelope.
// this ONE structure covers a huge range of synth sounds.
const osc = new Tone.Oscillator("C3", "sawtooth"); // harmonically rich source
const filter = new Tone.Filter(1200, "lowpass"); // carve off the top
const amp = new Tone.AmplitudeEnvelope({
attack: 0.01, decay: 0.2, sustain: 0.6, release: 0.8,
});
osc.chain(filter, amp, Tone.Destination); // wire the pipeline to the speakers
osc.start();
amp.triggerAttackRelease("2n"); // shape the note's volume over time
That sawtooth is spitting out every harmonic at once, which raw sounds harsh and buzzy. The lowpass filter tames it, and where you set the cutoff decides how bright or dark it lands. But here's the trick that makes subtractive sing: you give the filter its own envelope, so the brightness changes over the life of the note. A fast filter sweep on every note is the "pow" you hear at the start of a thousand synth basses.
// automate the FILTER cutoff with its own envelope for that classic synth "pluck".
// bright at the attack, quickly darkening - your ear reads it as "pow".
const source = new Tone.Oscillator("C2", "sawtooth").start();
const vcf = new Tone.Filter(200, "lowpass").toDestination();
source.connect(vcf);
const filterEnv = new Tone.FrequencyEnvelope({
attack: 0.005, decay: 0.15, sustain: 0.1, release: 0.2,
baseFrequency: 200, // resting (dark) cutoff
octaves: 4, // jumps UP 4 octaves at the attack, then falls back
});
filterEnv.connect(vcf.frequency);
filterEnv.triggerAttackRelease("4n"); // the cutoff snaps bright then closes down
Feel how alive that is compared to a static filter? Same three ingredients as before, but by animating the cutoff the note gets movement, a little gesture built into it. This is honestly the single most useful patch in all of synthesis - saw into a lowpass with an envelope on the cutoff. Learn just this and you can already make basses, plucks, pads, and leads.
Tone.js actually bundles this whole structure up for you as MonoSynth, so once you understand the parts, you can reach for the convenient version.
// MonoSynth = the whole subtractive patch (osc + filter + 2 envelopes) in one box.
const bass = new Tone.MonoSynth({
oscillator: { type: "sawtooth" },
filter: { type: "lowpass" },
envelope: { attack: 0.01, decay: 0.3, sustain: 0.4, release: 0.8 },
filterEnvelope: { attack: 0.01, decay: 0.2, sustain: 0.2, octaves: 3 },
}).toDestination();
bass.triggerAttackRelease("A1", "8n"); // a fat, punchy synth bass note
Same idea we just wired by hand, one tidy object. That's the pattern with all of this - understand the raw parts first, then use the shortcut with your eyes open, so when it sounds wrong you know which dial to grab.
Now for the weird, wonderful one. FM synthesis - frequency modulation - is where the sound gets bright, metallic, bell-like, glassy, all the timbres that additive and subtractive struggle to make. The idea is a bit mind-bending at first, so stay with me.
You take one oscillator - the carrier, the note you actually want to hear. Then you take a second oscillator - the modulator - and instead of listening to it, you use it to wobble the frequency of the carrier, very fast. When you vibrate one oscillator's pitch with another at audio speed, physics does something magic: brand-new frequencies appear, called sidebands, that were in neither oscillator to begin with. That's where the metallic shimmer comes from - it's frequencies born purely from the modulation.
// FM: a MODULATOR oscillator wobbles the CARRIER's frequency at audio rate.
// this births new "sideband" frequencies -> bright, metallic, bell-like tones.
const fm = new Tone.FMSynth({
harmonicity: 3, // modulator freq / carrier freq. INTEGER = harmonic/tonal.
modulationIndex: 10, // HOW HARD the modulator pushes. more = brighter/harsher.
}).toDestination();
fm.triggerAttackRelease("C4", "2n"); // a bright, glassy tone
Two dials do most of the work here, and they're worth really understanding. harmonicity is the ratio between the modulator and carrier frequencies. When it's a whole number (2, 3, 4) the sidebands line up neatly with the harmonic series and you get a musical, tonal sound. When it's a non-integer (2.4, 3.7) the sidebands land in between the harmonics and you get inharmonic, clangy, bell-and-metal sounds - which is exactly how you make a convincing bell.
// harmonicity is the whole game for FM character. integer = tonal, fractional = bell.
const fmSettings = {
warmElectricPiano: { harmonicity: 1, modulationIndex: 3 }, // soft, tonal
brightBrass: { harmonicity: 2, modulationIndex: 8 }, // punchy, harmonic
glassBell: { harmonicity: 3.5, modulationIndex: 12 }, // inharmonic clang
metallicClang: { harmonicity: 7.7, modulationIndex: 20 }, // gong-like chaos
};
// same synth, four totally different instruments, from two numbers each.
The other dial, modulationIndex, is simply how hard the modulator pushes. Low index (1-3) is subtle, just adding a little warmth and edge. Crank it toward 20 and it goes harsh and aggressive, packed with sidebands. And the beautiful part for us: if you put that index on an envelope so it starts high and drops fast, you get that classic FM electric-piano "bonk" - bright and metallic on the attack, mellowing into a soft tone as it rings out. That single trick made the sound of the entire 1980s.
// put the modulation on an envelope: bright attack, mellow tail = the FM "bonk".
const epiano = new Tone.FMSynth({
harmonicity: 1,
modulationIndex: 14,
modulationEnvelope: { attack: 0.002, decay: 0.4, sustain: 0, release: 0.2 },
envelope: { attack: 0.002, decay: 1.2, sustain: 0, release: 0.4 },
}).toDestination();
epiano.triggerAttackRelease("E4", "2n"); // that unmistakable 80s electric piano
Two quick extra tools before we build something. AM synthesis is FM's simpler cousin: instead of wobbling the carrier's frequency, you wobble its volume with another oscillator. Slow, it's a gentle tremolo (that pulsing you hear in surf-rock guitars). Fast - at audio rate - it becomes ring modulation, which gives you those eerie, robotic, dalek-ish timbres.
// AM: modulate the carrier's AMPLITUDE (volume). slow = tremolo, fast = ring mod.
const am = new Tone.AMSynth({
harmonicity: 2, // modulator freq relative to carrier - controls the ring-mod pitch
}).toDestination();
am.triggerAttackRelease("D3", "2n"); // a hollow, slightly eerie tone
And then there's the un-pitched half of synthesis: noise. Pure random signal, every frequency at once, no pitch at all. On its own it's a hiss, but filtered and shaped with an envelope it becomes the entire world of percussion and texture - snares, hi-hats, wind, rain, ocean, breath. Tone.js gives you white, pink, and brown noise (each "colour" just has a different frequency balance).
// noise + filter + envelope = percussion & texture. here: a simple snare-ish hit.
const noise = new Tone.Noise("white").start();
const nFilt = new Tone.Filter(2000, "highpass"); // thin it out, keep the "tss"
const nAmp = new Tone.AmplitudeEnvelope({
attack: 0.001, decay: 0.15, sustain: 0, release: 0.05, // a short, sharp burst
});
noise.chain(nFilt, nAmp, Tone.Destination);
nAmp.triggerAttackRelease("16n"); // tsch! a quick noise hit
That's the whole trick to drums, by the way - there's no "drum oscillator". A snare is filtered noise with a fast envelope, maybe with a short tonal blip underneath. A hi-hat is high-passed noise, even shorter. You're not finding drum sounds, you're building them out of noise the same way you build tones out of sines. Makes sense, right?
Here's the payoff that ties the whole episode together. Real synthesizers don't pick ONE of these - they layer them freely. An FM oscillator can feed into a subtractive filter, then an amplitude envelope, then reverb. A realistic piano might be a tonal oscillator layered with a tiny noise burst for the hammer "thunk". These aren't rival religions, they're building blocks that snap together, exactly like the little functions we've been composing since episode 1.
// techniques COMPOSE. here: FM source -> subtractive filter -> reverb space.
// the best of both worlds - FM's metallic character, tamed and placed in a room.
const fmSource = new Tone.FMSynth({ harmonicity: 2.5, modulationIndex: 8 });
const shaper = new Tone.Filter(1500, "lowpass"); // subtractive taming of the FM
const room = new Tone.Reverb(2.5); // put it somewhere (episode 118)
fmSource.chain(shaper, room, Tone.Destination);
fmSource.triggerAttackRelease("A3", "2n"); // metallic, but warm and roomy
Once you see it this way, sound design stops being a mystery box of presets and becomes composition - the same mindset as chaining post-processing effects on a visual, or stacking behaviours in a particle system. You've got the parts. Now you arrange them.
Time to make something real, and this one is genuinly satisfying because you'll hear three completely different instruments come out of the same handful of ideas. The rule: build each one from raw parts, then play the same little melody through all three so you can hear how much the instrument changes the feeling, even when the notes are identical.
First, a pluck using subtractive synthesis - a sawtooth through a fast filter envelope.
// INSTRUMENT 1: a bright pluck. subtractive - saw + snappy filter envelope.
const pluck = new Tone.MonoSynth({
oscillator: { type: "sawtooth" },
envelope: { attack: 0.005, decay: 0.2, sustain: 0, release: 0.2 },
filterEnvelope: { attack: 0.005, decay: 0.1, sustain: 0.1, octaves: 4 },
}).toDestination();
Second, a bell using FM - high harmonicity for that inharmonic clang, a modulation envelope for the metallic attack.
// INSTRUMENT 2: a glassy bell. FM - fractional harmonicity + modulation envelope.
const bell = new Tone.FMSynth({
harmonicity: 3.3, // fractional -> inharmonic, bell-like
modulationIndex: 14,
modulationEnvelope: { attack: 0.001, decay: 0.6, sustain: 0, release: 0.5 },
envelope: { attack: 0.001, decay: 2.0, sustain: 0, release: 1.0 },
}).toDestination();
Third, wind using filtered noise - pink noise through a sweeping bandpass, no pitch at all, pure texture.
// INSTRUMENT 3: wind. filtered noise - pink noise through a slow bandpass sweep.
const windNoise = new Tone.Noise("pink").start();
const windFilt = new Tone.Filter(500, "bandpass");
windNoise.connect(windFilt);
windFilt.toDestination();
// slowly sweep the band up and down for that breathing, gusting feel
windFilt.frequency.rampTo(1200, 4);
Now play the same phrase through the pitched two and let the wind sit underneath as a bed, and just listen to how differently they land.
// same notes, three characters. play the melody on pluck AND bell to compare.
const melody = ["C4", "E4", "G4", "B4", "A4", "G4", "E4", "C4"];
let i = 0;
const loop = new Tone.Loop((time) => {
const note = melody[i % melody.length];
pluck.triggerAttackRelease(note, "8n", time); // dry, snappy, rhythmic
bell.triggerAttackRelease(note, "8n", time + 0.02); // ringing, glassy, floaty
i++;
}, "4n");
loop.start(0);
Tone.Transport.start(); // remember: all of this behind a Tone.start() click!
Run it (behind a "click to start" button, or the browser stays stubbornly silent - we learned that the hard way last episode). Same eight notes, and the pluck sounds like a little music box while the bell sounds like a cathedral, with the wind breathing behind both. That gap between them is entirely the instrument you built, and you built all three from sines, filters, envelopes and noise. That's the whole point of today.
Push it further when you've got a minute: try giving the pluck a lowpass sweep, or layer a tiny noise burst onto the bell's attack for a "mallet" thunk, or occassionally retune the wind's bandpass to the melody note so it hums along. It's your workshop now.
MonoSynthharmonicity (integer = tonal, fractional = bell) and modulationIndex (how hard it pushes) do most of the work - put the index on an envelope and you get the classic 80s electric pianoSo that's synthesis, demystified. There is no magic in the box - just sines, filters, envelopes and noise, arranged with a bit of taste. You can now walk up to any sound and have a decent guess at how it's built, and better, you can build your own from nothing. That's a genuinly powerful place to be standing.
But notice what we've been doing this whole episode: playing single notes, or at most a little loop I handed you. We've been obsessing over what each note sounds like and completely ignoring which notes to play and when. Our melodies have been me typing ["C4", "E4", "G4"] by hand like it's 1995. That's about to change. Now that you can build instruments, the obvious next question is how to make the code choose the notes itself - to generate melody and harmony that actually sounds musical, instead of random beeps. Build your three instruments first, genuinly play with them until you've got a bell you love, and then come back - because next time we teach the computer to write the tune :-).
Sallukes! Thanks for reading.
X