All articles
8 min read

The Sound of Growing: How Web Audio Creates Living Soundscapes

web audiocreative codingsound artgenerative music

Open most websites and they're silent. Text, images, animation — all visual. The browser has a full-featured audio synthesizer built in, and almost nobody uses it. On Lumitree, some of the most memorable micro-worlds are the ones you hear before you understand them.

This article is about how we build sound gardens: interactive, generative soundscapes that run entirely in the browser using the Web Audio API. No audio files. No samples. No external libraries. Just oscillators, filters, and math — all synthesized in real time from nothing.

The Web Audio API in 60 seconds

The Web Audio API gives you a modular synthesis environment inside any browser. You build audio by connecting nodes in a graph, exactly like patching cables in a hardware synthesizer:

const ctx = new AudioContext();

// Create an oscillator (sound source)
const osc = ctx.createOscillator();
osc.type = 'sine';
osc.frequency.value = 440; // A4

// Create a gain node (volume control)
const gain = ctx.createGain();
gain.gain.value = 0.3;

// Connect: oscillator → gain → speakers
osc.connect(gain);
gain.connect(ctx.destination);
osc.start();

That's a complete instrument in eight lines. A sine wave at 440Hz, played at 30% volume. From here, everything is composition.

From oscillator to ecosystem

A single tone is boring. A sound garden needs layers, movement, and unpredictability. Here's how we build complexity from simple parts:

Detuned unison. Play the same note on three oscillators, each slightly detuned. The phase interference creates a rich, shimmering texture — the same trick analog synthesizers have used since the 1970s.

function createChoir(ctx, freq, count = 3) {
  const voices = [];
  for (let i = 0; i < count; i++) {
    const osc = ctx.createOscillator();
    osc.type = 'sine';
    osc.frequency.value = freq * (1 + (i - 1) * 0.003);
    voices.push(osc);
  }
  return voices;
}

LFO modulation. A low-frequency oscillator (LFO) connected to a parameter creates slow, organic movement. Connect one to pitch and you get vibrato. Connect one to volume and you get tremolo. Connect one to a filter cutoff and the sound breathes.

const lfo = ctx.createOscillator();
lfo.frequency.value = 0.2; // One cycle every 5 seconds
const lfoGain = ctx.createGain();
lfoGain.gain.value = 5; // Modulation depth in Hz
lfo.connect(lfoGain);
lfoGain.connect(osc.frequency); // Slow pitch wobble
lfo.start();

Convolution reverb from noise. You can create reverb without an impulse response file — generate a noise burst, shape its amplitude envelope with an exponential decay, and use it as a ConvolverNode impulse response. The result sounds like a physical space, synthesized from nothing.

Generative composition

The sound gardens on Lumitree don't play a fixed composition. They generate music in real time using simple rules that produce complex results.

Pentatonic safety net. If you constrain random note selection to a pentatonic scale, everything sounds harmonious by accident. There are no dissonant intervals in the pentatonic scale — every combination works. This is the secret behind wind chimes: random timing, guaranteed harmony.

// C pentatonic: C D E G A
const scale = [0, 2, 4, 7, 9];
const octave = 3 + Math.floor(Math.random() * 3);
const note = scale[Math.floor(Math.random() * scale.length)];
const freq = 440 * Math.pow(2, (note + octave * 12 - 69) / 12);

Temporal patterns. Rather than playing notes at fixed intervals, we schedule them using Poisson-distributed random delays. This creates a natural, organic rhythm — like rain falling on a roof. Sometimes two notes arrive almost together. Sometimes there's a long pause. The silence is part of the composition.

Reactive density. In interactive sound gardens, the visitor's mouse position or scroll depth controls the density and intensity of the soundscape. Move to the center and the garden blooms with sound. Move to the edges and it thins to a whisper. The visitor conducts without knowing it.

The envelope: why sounds feel alive

The difference between a mechanical beep and a living tone is the envelope — how a sound starts, sustains, and fades. The classic ADSR envelope (Attack, Decay, Sustain, Release) gives every note a shape:

function playNote(ctx, freq, duration = 2) {
  const osc = ctx.createOscillator();
  const gain = ctx.createGain();
  const now = ctx.currentTime;

  osc.frequency.value = freq;
  gain.gain.setValueAtTime(0, now);
  gain.gain.linearRampToValueAtTime(0.3, now + 0.05);  // Attack
  gain.gain.exponentialRampToValueAtTime(0.1, now + 0.3); // Decay
  gain.gain.setValueAtTime(0.1, now + duration - 0.5);    // Sustain
  gain.gain.exponentialRampToValueAtTime(0.001, now + duration); // Release

  osc.connect(gain);
  gain.connect(ctx.destination);
  osc.start(now);
  osc.stop(now + duration);
}

A fast attack and slow release sounds like a bell — bright onset, long tail. A slow attack with no sustain sounds like a swell, like something breathing in. By varying envelopes across voices, the sound garden fills with different characters: some percussive, some ambient, some ghostly.

Spatial sound: where the music lives

The Web Audio API includes a PannerNode that positions sound in 3D space. In a sound garden, each sound source has a location. As the visitor moves through the space, sounds grow louder or softer, shift left or right. The result is less like listening to music and more like walking through a forest where every tree is singing a different note.

Even simple stereo panning creates a sense of space. Random pan positions for each note, combined with different reverb amounts, produce a wide, immersive field from a mono synthesis engine.

The 50KB challenge for audio

Audio code is remarkably compact. A complete generative sound garden — with multiple voice types, scale-based note selection, envelope shaping, reverb, and interactivity — typically fits in 2-4KB of JavaScript. Compare that to the smallest useful audio sample file (a single piano note as a compressed MP3 is about 15KB) and the advantage of synthesis becomes clear.

Synthesis isn't just smaller. It's infinitely variable. A sample plays the same way every time. A synthesized note is born fresh each time it's triggered — with slightly different detuning, slightly different timing, slightly different resonance. The sound garden never repeats. It's alive in a way that recorded audio can never be.

Why sound matters for web art

Vision dominates the web, but sound creates presence. A visual animation is something you look at. A visual animation with generative sound is something you're inside. The sound makes the world feel like it exists independently of you — it was playing before you arrived, and it'll keep playing after you leave.

Sound also creates emotional tone faster than any other medium. A low drone with slow modulation instantly communicates vastness. High, bright arpeggios feel like sunrise. A single sustained note with a slow filter sweep is melancholy without a word being spoken. The sound garden doesn't describe a mood — it produces one.

The most common reaction we see from visitors who encounter their first sound garden is surprise. Not at the sound itself, but at the fact that a browser tab is doing this. That surprise — the realization that the web platform is far more capable than most websites suggest — is part of what Lumitree is about.

Try it yourself

If you want to experiment with Web Audio synthesis:

  • Start with one oscillator. Get it playing, then add a gain node. Then add an envelope. Each addition teaches you something about the API.
  • Use pentatonic scales. Random notes in a pentatonic scale always sound good. It removes the "wrong note" problem and lets you focus on timbre and timing.
  • Schedule everything with AudioContext.currentTime. Don't use setTimeout for audio timing — it's too imprecise. The audio clock is sample-accurate.
  • Layer three quiet sounds instead of one loud sound. Multiple soft voices create richness. One loud voice creates a headache.
  • Always start with user interaction. Browsers require a user gesture before audio can play. Add a "click to enter" or respond to the first mouse move. Never autoplay.

Visit lumitree.art and look for the branches tagged as sound gardens. Turn your volume up. The tree is singing.