Concept study № 18 · how it was made
The set was built to prove one thing.
That a browser can synthesize a whole radio band — carriers, stations and the real static between them — from oscillators and filtered noise, and never touch a recording. Below: the brief, the one technique that carries it, and the three critique passes that took it from a working prototype to something worth keeping the light on for.
What we were handed
The brief
Every site in this collection begins as an art-directed brief — a fictional place with a year, a palette, a named type pairing and exactly one signature technique to execute deeply. Radio Merdeka received this:
One technique, executed deeply
The band is computed, not recorded
The whole dial is a single mixing problem. For any frequency, the set finds the nearest station and computes a signal strength — 1 when you are dead on it, falling to 0 as you drift a few kilohertz away. That one number cross-fades the station's synthesized voice against a bed of filtered white noise. Lock a station and its voice fills the room; drift, and the noise rises to take its place. Nothing is looped. The hiss you hear was generated the moment you asked for it.
Each station's "voice" is a scheduled pattern of oscillator notes — time pips, a plucked kroncong, a telegraph key still spelling merdeka, bronze gamelan, a night service that keeps announcer's hours. Every note runs through the same tiny envelope helper:
// one oscillator note, scheduled into the active station's bus function tone(t,dur,f,type,peak,a,mod){ var o=ctx.createOscillator(); o.type=type; o.frequency.value=f; var g=ctx.createGain(); g.gain.setValueAtTime(0.0001,t); g.gain.linearRampToValueAtTime(peak, t+(a||0.008)); g.gain.exponentialRampToValueAtTime(0.0001, t+dur); o.connect(g); g.connect(stationBus); // …optional FM modulator for the gamelan bells… o.start(t); o.stop(t+dur+0.06); } // the whole dial in two lines — strength cross-fades voice against static staticGain.gain.setTargetAtTime((1-curStrength)*0.085+0.004, now, 0.08); stationBus.gain.setTargetAtTime(active ? curStrength*0.55 : 0, now, 0.05);
The audio context is never created until your first tune gesture — turning the knob, dragging the glass, an arrow key. Until then the oscilloscope runs on a purely visual version of the same maths, so the instrument is alive and understandable in complete silence. That is the audio law, kept honestly.
The loop is the level
Three critique passes
The first draft is never why these pages look the way they do. Each pass shoots the real page on desktop and mobile, reads the screenshots like a hostile art director, fixes everything, and ends with one deliberate upgrade.
The dial fought its own edges
Found: the seven station names printed on the glass clipped at the band edges — "Siaran Nasional" opened as "…an Nasional" — and collided with a "kHz · medium wave" caption. On mobile all seven names overlapped into an unreadable smear. The plate was cramped on narrow screens, and the set opened on dead static, which read as broken.
Changed: inset every dial element by 4.5% so nothing touches the glass; removed the caption; hid the printed names on mobile (they live in the station log); collapsed the plate on small screens; and opened the radio already locked on Kroncong Senja at 855 kHz, so the first frame shows a working instrument.
Upgrade: signal-lock jade halo + readout turns green on lockEvery control had to answer the thumb
Found: the fixes held, but the lower deck — tuning knob, oscilloscope, signal meter, volume, mute, five presets — needed to survive the drop to a single mobile column without any control leaving thumb reach, and the oscilloscope trace read as one flat colour whether tuned or lost.
Changed: confirmed the 3-then-2 preset grid and reachable knobs at 390px, tidied the volume handler, and verified keyboard paths and visible focus on every knob, preset and button.
Upgrade: the trace colour-grades with strength — dim amber static → bright gold carrierA 1957 set has an eye
Found: the instrument was correct but missing the single most period-accurate detail a valve radio of this era carried — the magic-eye tuning indicator that told you, at a glance, when you were on station.
Changed: added an EM34-style magic eye to the brand plate. Its green phosphor wedges close as the signal locks and dim to nothing when the set is off — computed from the same strength value that drives the audio, so the whole face agrees with itself.
Upgrade: the magic-eye tuning indicatorShip gate
What it costs
- One self-contained index.html — all CSS and JS inline, zero CDN, no build step.
- No external images — the cabinet, dial, knobs, oscilloscope and static are all procedural.
- Zero console errors on the final harness run, at 390px and 1440px.
- Full keyboard operability, visible focus, AA-contrast text, and a complete page under prefers-reduced-motion.
- Page weight printed on the radio's own footer — the number is the proof.
The method that seeded this collection is the open FABLE 25 project — a design constitution, art-directed briefs, and a screenshot critique loop. We rebuilt the discipline in our own voice; the craft here is real, the brand is fiction.