Last time I left you performing a live set, mutating loops mid-song, and I ended with something that had genuinly been nagging at me. We spent this whole long audio arc making music - notes that land on a grid, scales that can't sound wrong, chords that resolve, beats that groove. Pleasant, tidy, gridded. And then I typed one lopsided euclidean rhythm, heard that almost-wrong limp, and couldn't stop thinking about all the sound that isn't music. The fridge hum. Rain on a window. A raw buzz that's somehow more interesting than any polite chord. So today we walk off the edge of "music" completely, and into sound art - sound treated as a material in its own right, the way a sculptor treats clay. Allez, no scales today :-).
I want to be honest up front: this is the strangest episode of the audio arc, and probably the most freeing. There's no "right note" to hit, no beat to lock to, nothing that can go out of tune because nothing's in a key to begin with. That scares people. It also means the only thing that matters is whether the sound itself is interesting - the texture, the space, the duration, the surprise. Sound art is what happens when you stop asking "is this musical?" and start asking "is this worth listening to?" Different question entirely. And it turns out code is a ridiculously good tool for it, because a lot of sound art is about systems and time and chance - which is exactly what we've been building all along.
Before any code, let me give you the two people whose ideas we're about to turn into JavaScript, because knowing why this exists changes how you write it.
First: John Cage, 1952, a piece called 4'33". A pianist walks on stage, sits down, opens the lid, and plays... nothing. For four minutes and thirty-three seconds. No notes. The "music" is whatever you hear in the room while you wait - a cough, a chair creaking, traffic outside, your own heartbeat getting loud in the silence. Cage's whole point was that there is no such thing as silence - the moment you stop making sound on purpose, you notice the sound that was always there. That single idea cracked the whole field open.
Second: R. Murray Schafer, a Canadian composer who in the 1970s coined the word soundscape and started a thing called acoustic ecology - the study of the sounds of a place as if they were a landscape you could compose with. A forest, a harbour, a train station: each has its own sonic signature, and Schafer taught people to listen to them as art. Field recording - going out with a mic and capturing a place - grew straight out of that.
Hold those two ideas - silence is full and a place is a composition - because everything we build today is one of them, in code.
Let's begin where Cage did: with silence, but framed. The frame is the whole trick. A silence you stumble into is just a gap. A silence you announce - "the piece has begun, and it lasts four minutes and thirty-three seconds" - becomes a container that makes you listen. So the smallest possible sound-art program isn't one that makes noise. It's one that makes a frame around listening.
// 4'33" as code. we make NO sound - we frame a duration and ask you to listen.
// the "piece" is whatever the room does while the timer runs.
function fourThirtyThree(onTick, onDone) {
const total = 4 * 60 + 33; // 273 seconds, Cage's exact length
let elapsed = 0;
const id = setInterval(() => {
elapsed++;
onTick(elapsed, total); // e.g. update a "now listening" display
if (elapsed >= total) { clearInterval(id); onDone(); }
}, 1000);
return () => clearInterval(id); // hand back a "stop" so the frame can be closed
}
Notice there's not a single oscillator in there. That's the point, and it's a genuinly useful thing to sit with as a coder: sometimes the artwork is the structure around the content, not the content. The empty onTick is where you'd draw a slowly filling bar, or the words "movement 1 of 3" - anything that tells the listener this emptiness is on purpose. Makes sense, right? Silence you're told to notice is a completely different experience from silence you happen to be in.
Right, now we make actual sound, but the ugliest sound in the book - noise. In music, noise is the enemy: hiss, static, the thing you try to remove. In sound art it's the raw clay, and there are several flavours of it, each with its own character. Let me build the most basic one by hand first, because you should see that noise is just... random numbers played as samples.
// white noise, built from scratch: fill a buffer with random samples in -1..1.
// this IS noise. it's just random numbers, played 44100 of them a second.
const ctx = new (window.AudioContext || window.webkitAudioContext)();
function makeWhiteNoise(seconds) {
const len = ctx.sampleRate * seconds;
const buffer = ctx.createBuffer(1, len, ctx.sampleRate);
const data = buffer.getChannelData(0);
for (let i = 0; i < len; i++) data[i] = Math.random() * 2 - 1; // -1..1, flat + harsh
return buffer;
}
That's white noise - equal energy at every frequency, which to our ears sounds thin and hissy, like TV static. But there's a whole family, and the differences matter enormously for how a texture feels. White is harsh and bright. Pink noise rolls off the highs so it sounds warmer and more natural - it's the closest thing to rain or a waterfall. Brown (or red) noise rolls off even harder, a deep rumble like distant surf or wind. Tone.js gives us all three for free, so let me swap my hand-rolled buffer for the real toolkit.
// the noise family, via Tone.js. each "colour" is a different tilt of energy across frequency.
const white = new Tone.Noise("white"); // harsh, bright, TV static
const pink = new Tone.Noise("pink"); // warm, natural - rain, a waterfall
const brown = new Tone.Noise("brown"); // deep rumble - distant surf, heavy wind
// brown noise is my go-to for a "bed" you can sit under for ten minutes without fatigue.
pink.toDestination().start(); // fill the room with a soft, endless hiss
Play brown noise for thirty seconds with your eyes shut and tell me it doesn't feel like standing near the sea. That's the thing sound art keeps teaching you: our brains desperately want to hear noise as a place. We can't help it. And that instinct is the raw material you get to shape.
Raw noise is boring after ten seconds - it just sits there. The art is in shaping it over time so it breathes and changes. The single most powerful tool for that is a filter - the same idea from our synthesis episodes, a gate that only lets certain frequencies through. Sweep a filter slowly across a noise source and the flat hiss turns into something that moves, like wind picking up and dying down.
// noise + a slowly sweeping filter = wind. the filter decides which frequencies survive.
const source = new Tone.Noise("pink").start();
const filter = new Tone.Filter(400, "bandpass"); // only let a narrow band through
source.connect(filter);
filter.toDestination();
// an LFO (slow oscillator) drifts the filter's centre up and down over 20 seconds.
const drift = new Tone.LFO("0.05hz", 200, 1200); // 0.05hz = one sweep every 20s, very slow
drift.connect(filter.frequency).start(); // now the "wind" rises and falls on its own
That 0.05hz is doing a lot of work - it's a sub-audio rate, way too slow to hear as a pitch, so instead you hear it as change over time. This is a huge mental shift from the music arc: there, our fastest interesting timescale was a sixteenth note. Here, our slowest interesting timescale might be ten minutes. Sound art lives in the long durations, where a change so gradual you can't point to the moment it happened is exactly the effect you want. See where this is going? We're stretching time way out.
Now Schafer's idea - a place as a composition. This is where sound art gets genuinely magical, because you leave your synthesizer entirely and go record the world. Your phone is a field recorder. Point it at a rainstorm, a busy market, a humming fridge, a playground, and you've captured a sonic photograph of a place. Then you bring it into code and treat it as material - the same way we treated a sample back in episode 125.
// a field recording is just an audio file. load it and it becomes material to sculpt.
const field = new Tone.Player({
url: "rain-on-window.mp3", // YOUR recording - a place, captured
loop: true, // loop it so the "place" never ends
autostart: true,
}).toDestination();
field.volume.value = -8; // sit it back a little, like ambience should
That alone - a looping field recording, quietly filling a room - is already a valid piece of sound art. People sell exactly this as "ambient" records. But we're coders, so of course we don't leave it alone. The interesting move is to process the place, to take the raw recording and pull it apart. And the tool we already know for pulling a sound into tiny fragments and rearranging them is granular synthesis, straight from episode 125.
// granular processing of a field recording (episode 125). we shatter the "place" into
// grains and scatter them, so a 4-second rain clip becomes an endless, ever-shifting cloud.
const grains = new Tone.GrainPlayer({
url: "rain-on-window.mp3",
loop: true,
grainSize: 0.12, // 120ms fragments
overlap: 0.08, // grains overlap so it's a smooth cloud, not a stutter
playbackRate: 0.6, // slow it down - rain becomes a vast, slow-motion downpour
detune: 0,
}).toDestination();
grains.start();
Drop playbackRate to 0.6 and rain stops being rain - it becomes this huge, slow, cavernous texture, unmistakably made of rain but no longer literally it. That gap - between a sound you recognise and what you've done to it - is one of sound art's favourite places to live. The listener half-recognises the world and half-doesn't, and that little uncanny wobble is the whole hook.
There's one more classic sound-art form I have to show you, because it's the opposite of everything the music arc taught: the drone. A drone is a single, sustained tone that just... holds. For minutes. No melody, no rhythm, no change of chord - one sound, examined. It sounds like it should be boring, and it is the furthest thing from it, because when a tone holds long enough your ear starts hearing inside it - the tiny beatings, the overtones, the way it interacts with the room.
The trick to a drone that's alive rather than dead is to stack a few oscillators very slightly detuned from each other. When two tones are a hair apart in frequency, they interfere and produce a slow beating - a gentle wobble in loudness as they drift in and out of phase. That beating is what makes a drone shimmer.
// a living drone: several oscillators, very slightly detuned, beating against each other.
function drone(freq) {
const detunes = [-7, -3, 0, 4, 8]; // cents - tiny, sub-semitone offsets
const gain = new Tone.Gain(0.15).toDestination();
return detunes.map((cents) => {
const osc = new Tone.Oscillator(freq, "sawtooth");
osc.detune.value = cents; // each voice a hair off the others -> slow beating
osc.connect(gain);
osc.start();
return osc;
});
}
drone(55); // a low A. sit with it. the "wobble" you hear is the detuned voices beating.
Those numbers - -7 to 8 cents, hundredths of a semitone - are deliberately tiny. Wider detuning would sound like an out-of-tune chord; this narrow spread instead reads as one thick tone that's quietly alive. And a drone is the perfect canvas for the slow filter sweep from earlier - put a filter on the drone's output, drift it over minutes, and the tone slowly opens and closes like something breathing. That's a durational piece in about fifteen lines.
Now let's put it together the sound-art way, which is deliberately unlike how we composed music. No transport, no bars, no scale. Instead: a bed (a continuous noise or drone texture), and events that happen at irregular, unpredictable times over the top - a distant clang, a bird, a fragment of the field recording surfacing and sinking again. The events don't land on a beat. They land whenever they land, and that irregularity is what makes it feel like a real place instead of a machine.
To schedule irregular events, we ditch the metronome and use random gaps between them - a longer gap means a sparser, lonelier soundscape.
// events at IRREGULAR times - no grid. random gaps make it feel like a real place, not a machine.
function scheduleEvent(fire) {
const gap = 2 + Math.random() * 8; // somewhere between 2 and 10 seconds - unpredictable
setTimeout(() => {
fire(); // make one sound...
scheduleEvent(fire); // ...then schedule the next, with a fresh random gap
}, gap * 1000);
}
Each event can be almost anything. Let me make the events little bell-like clangs at random pitches, panned to random positions in the stereo field (episode 124's spatial thinking), so they feel like they're happening around you in a space rather than flat in front.
// each event: a soft metallic ping, at a random pitch, placed randomly in the stereo space.
function pingEvent() {
const panner = new Tone.Panner(Math.random() * 2 - 1).toDestination(); // -1 left .. +1 right
const metal = new Tone.MetalSynth({
envelope: { attack: 0.001, decay: 1.4, release: 0.2 },
harmonicity: 3.1 + Math.random() * 4, // randomise the timbre so no two are alike
}).connect(panner);
const freq = 120 + Math.random() * 600; // no scale - ANY frequency is allowed here
metal.triggerAttackRelease(freq, "8n", undefined, 0.3);
}
See that freq = 120 + Math.random() * 600? In the music arc that line would've been a crime - I'd have snapped it to a scale so it couldn't sound wrong. Here I want it off the grid, any frequency, because a real place doesn't tune its bells to A minor. Letting go of the scale is genuinely the hardest habit to break after all those music episodes, and it's the whole point of today.
One thing that separates flat, amateur sound art from the stuff that gives you chills is space - a sense that the sounds are happening somewhere, a room, a cathedral, a valley. We touched this in episode 124, but for soundscapes reverb isn't a polish effect, it's structural. A long, huge reverb is the place. So let me wrap the whole soundscape in a big reverberant space and let every event bloom into it.
// a big shared reverb IS the "place". everything blooms into the same space.
const space = new Tone.Reverb({
decay: 12, // a long tail - a huge, cathedral-like room
preDelay: 0.05,
wet: 0.6,
}).toDestination();
// now route the bed and the events THROUGH the space instead of straight to the speakers.
const bed = new Tone.Noise("brown").connect(space);
bed.volume.value = -22; // the brown-noise bed, way back, the "air" of the room
bed.start();
Route your drone, your filtered noise, and every ping into that one shared reverb and suddenly they all inhabit the same room - which is what makes a bunch of separate sounds cohere into a single place. That shared-space trick is doing the same job the shared state object did in our composition back in episode 128: it's the one thing in the middle that makes everything feel like it belongs together.
Last idea, and it's the one that most separates sound art from music: duration. Music episodes worked in seconds and minutes. Sound-art installations often run for hours, or forever, and the structure is so slow you experience it more like weather than like a song. To write that, you don't want a bar-based director - you want an incredibly slow drift of a few global parameters, so gradual that no listener could ever point to the moment something changed.
// a durational "conductor" - not bars, but a glacial drift. change is too slow to notice
// happening, yet the piece is completely different ten minutes apart.
const world = { density: 0.5, brightness: 0.3, depth: 0.5 };
function evolve() {
// nudge each parameter by a tiny random step - a random walk (episode 12's noise thinking).
world.density = clamp(world.density + (Math.random() - 0.5) * 0.04);
world.brightness = clamp(world.brightness + (Math.random() - 0.5) * 0.03);
world.depth = clamp(world.depth + (Math.random() - 0.5) * 0.03);
space.wet.rampTo(world.depth, 20); // the ROOM slowly changes size over 20 seconds
setTimeout(evolve, 15000); // re-evaluate only every 15 seconds - glacial
}
function clamp(v) { return Math.max(0, Math.min(1, v)); }
evolve();
That's a random walk - the same wandering-value idea from our Perlin noise episode (episode 12), except now it's steering a whole sonic world instead of a wiggle on screen. Wire world.density into how often scheduleEvent fires, and world.brightness into that filter sweep, and you've got a soundscape that genuinly never repeats and never sits still - but changes so slowly it feels like a living place you happen to be sitting in, not a track that's playing at you. Leave it running while you work. Come back in ten minutes. It'll be somewhere else, and you'll have no idea when it left.
Here's the brief, and it's the loosest one I've ever given you, on purpose. Compose a place. Not a song - a place. Pick somewhere real or imagined: a rainy tram stop, the inside of a cave, a spaceship at idle, the sea at 3am. Then build it out of today's parts: a bed (brown noise, or a drone, or a looping field recording of somewhere near you), a big shared reverb that is the space, and irregular events pinging in and out at random times and random positions. No scale. No beat. No grid. If you catch yourself snapping a frequency to a note, stop - let it be off, let it be weird.
Then do the hard thing: give it duration. Add the glacial evolve random walk so the place drifts. And once it's running, walk away from your computer for five real minutes and just live in the room with it the way Cage made his audience live in that silence. Does it feel like a place? Does it change without you noticing it change? Does a bit of it ever surprise you, even though you wrote it? If yes - if you built a system whose sound you'd actually choose to sit inside - you've made sound art. Record a good long stretch of it with MediaRecorder (episode 20) so you can keep the runs that come out haunting.
And here's the thing I want you to notice while you build it. You didn't learn a single new technique today. Noise, filters, granular, drones, random timing, reverb, a random walk - every one of those we already had. What changed was the intent: we pointed the exact same tools at a completely different question. That's a bigger lesson than any one trick, and it's exactly the ground I want to stand on next.
Look back over this whole arc - all the audio, all the visuals, the generative systems way back at the start. We've written the same handful of ideas over and over: noise, lerp, state, random walks, shared objects in the middle, loops reading variables. We keep rebuilding them from scratch each episode. And every serious creative coder eventually hits the same wall I've been quietly circling: you get tired of rewriting your own noise function and your own lerp and your own particle for the hundredth time, and you start wanting your own set of tools - a little personal toolkit that's yours, shaped exactly the way your hands work, that you reach for every time you start something new. That itch - the leap from writing sketches to building the thing you write sketches with - is where we go next. Make your place first. Sit in it. Then come back, because it's time we stopped starting from zero every time :-).
4'33" - silence is never empty, and framing a silence makes you hear the room - and Schafer's soundscape - a place has a sonic signature you can compose withSo that's us off the edge of music entirely, and honestly I think it's the episode that ties the whole arc's philosophy together. You can make sound that isn't trying to be pretty or gridded or resolved - sound that's just interesting, a place you'd sit inside - and it's built from the exact same noise-and-lerp-and-state bricks we've used since the very beginning. That's the quiet superpower of creative coding: a small set of ideas, pointed with taste at whatever question you feel like asking today. Go build somewhere I'd want to sit :-).
Sallukes! Thanks for reading.
X