OSCILLA ← Back to the synth

Guide · Service manual

How to build
a website you
can play

OSCILLA's landing page does not describe the product, it is the product: a working subtractive synthesizer in the hero section. Web Audio API, one script, zero dependencies. Here is the wiring diagram.

01

The signal chain

Web Audio works like a modular synth: you create nodes and patch them with cables. OSCILLA's entire architecture is nine nodes:

key press │ ▼ oscillator (sine/square/saw/tri) │ ▼ voice gain ◄── envelope: 8ms attack, variable release │ ▼ lowpass filter ◄── Cutoff slider │ ├────────────► dry ────────┐ │ ▼ └► delay (310ms) ► echo ► master ► analyser ► speakers ▲ │ └────┘ feedback 35%

Every held key gets its own oscillator and envelope (that is all polyphony is), and they all pour into one shared filter and echo. The analyser node at the end is the interesting one: it lets the page read the waveform that is actually playing, which powers everything in the next two sections.

02

An honest oscilloscope

Sixty times a second, the scope asks the analyser for the current waveform and draws it as a chunky line in the selected wave's color:

analyser.getByteTimeDomainData(timeData);
for (let i = 0; i < n; i++) {
  const v = (timeData[i] - 128) / 128;   // -1 .. 1
  lineTo(x(i), H/2 + v * H * 0.42);
}

No fakery is the whole point: turn the cutoff down and you watch the square wave's corners physically round off, because a lowpass filter removing high frequencies is the rounding of corners. The scope teaches synthesis by accident.

03

Springs that listen

The console physically reacts to loudness. Each frame we compute the RMS (average loudness) of the signal and treat it as a target for a small spring simulation:

const accel = k * (target - pos) - damping * vel;
vel += accel * dt;
pos += vel * dt;
console.style.transform = `scale(${1 + pos * 0.014})`;

A spring, not a CSS transition, because sound is physical. Strike a chord and the console jumps then settles with a wobble; hold a quiet note and it just leans. Motion that is caused by the thing it decorates always reads as alive; scheduled motion reads as decoration.

Under prefers-reduced-motion the spring is off, the marquee stops, and the scope (which is information, not decoration) keeps drawing.

04

The toy-shop design system

The visual language is a toy: thick 3px ink borders, hard offset shadows (no blur, like stickers), four block colors, and Bricolage Grotesque shouting in uppercase. The trick that ties the page to the instrument: the accent color everywhere, the title's period, slider thumbs, held keys, the power dot, is the selected waveform's color, driven by one CSS variable.

:root { --wave: #2F5BFF; }        /* sine = cobalt */
root.style.setProperty('--wave', WAVE_COLORS[wave]);
Paper
#FAF6EE
Ink
#16130E
Tomato · SQR
#FF4B2E
Cobalt · SIN
#2F5BFF
Sun · SAW
#FFC53D
Mint · TRI
#35C58C

Type: Bricolage Grotesque (display), Schibsted Grotesk (body), Azeret Mono (values and labels). All served locally as woff2.

05

Build your own

  1. Wait for the gesture. Browsers refuse audio before a user interaction. Create the AudioContext lazily inside the first pointer or key event, never on load.
  2. One oscillator per held key is enough. Polyphony sounds hard; it is a Map from key to oscillator.
  3. Envelope with setTargetAtTime. Never set gain instantly, that clicks. An 8 ms attack and an exponential release make everything feel physical.
  4. Put an analyser at the end and treat it as the page's nervous system: scope, springs, glows, anything can read it.
  5. Let the instrument own the accent color. One CSS variable driven by state makes the whole page feel wired to the sound.
  6. QWERTY is an instrument. Map a home-row octave (A W S E D F T G Y H U J K), ignore key repeats, and kill all notes on window blur.