← DP-303 DP-303 Design Notes
Design Notes

DP-303

Bass Line Synthesizer & Pattern Library

DP-303 is a browser-native TB-303 bass synthesizer and curated pattern library. Every sound is generated in real time using the Web Audio API: no audio files stream from the server, no plugins are required, and the entire app loads as a single HTML file with inlined assets. This document explains the decisions behind the audio engine, the TR-909 drum section, the pattern data model, and the visual design.

The TB-303 Voice

The original Roland TB-303 Bass Line (1981) produced its sound through a deceptively simple analog signal chain: a single oscillator running sawtooth or square wave, fed through an 18 dB/octave resonant lowpass filter (the CEM 3394 chip), shaped by a VCA envelope, with a separate accent circuit and a portamento (slide) mechanism that carried pitch between triggered notes.

DP-303 replicates that chain in the Web Audio API without external libraries:

OscillatorNode
WaveShaperNode
drive / soft clip
BiquadFilterNode
lowpass, 18 dB/oct
GainNode
VCA envelope
GainNode
mix / level
DynamicsCompressor
limiter
ParameterRangeNotes
Cutoff 200 Hz — 4 kHz Filter frequency at rest. The envelope modulates upward on every triggered note.
Resonance 1 — 25 BiquadFilter Q. At high values the filter self-oscillates, producing the acid scream.
Env Mod 0.2× — 4× Multiplier applied to the envelope peak. Controls how far the filter opens on attack.
Decay 10 ms — 400 ms Exponential filter envelope decay. Short decay = percussive stab; long = slow bloom.
Accent 1× — 3× Per-step gain boost. Accented steps also receive an extended envelope peak, matching 303 behavior.
Drive 0 — 1 WaveShaperNode with a tanh-based soft-clip curve. 0 = clean; 1 = saturated distortion pre-filter.

Slide is implemented by scheduling a linearRampToValueAtTime on the oscillator frequency across the full step duration when a slide flag is set on a step. This produces the portamento glide between notes that defines the 303 sound, without needing a separate portamento node.

Waveform switching between sawtooth and square is live: clicking the selector updates osc.type on the running OscillatorNode with no restart or click. The original 303 required a physical switch; here it's instant.

Lookahead Scheduler

Web Audio event scheduling is sample-accurate, but JavaScript timers are not. The standard approach is a lookahead scheduler: a setTimeout loop runs every 25 ms, inspects the audio clock, and schedules any notes that fall within the next 100 ms. This decouples the imprecise JS event loop from the precise audio graph.

// From sequencer.js const LOOKAHEAD_MS = 100; const SCHEDULE_INTERVAL_MS = 25; // Each tick: schedule all steps within the lookahead window while (this.nextStepTime < ctx.currentTime + 0.1) { this._scheduleStep(this.currentStep, this.nextStepTime); this._scheduleDrumStep(this.drumStep, this.nextStepTime); this.nextStepTime += this._stepDuration(); }

A consequence of lookahead scheduling: any code that reads a Web Audio parameter value with .value during scheduling will get the current value, not the future scheduled value. This caused the original hat choke bug (see the drums section below) and is worth understanding when extending the engine.

The UI step indicator fires via a setTimeout calculated from the difference between the scheduled audio time and the current wall clock. It will drift slightly with system load but is accurate enough for visual feedback.

The TR-909 Section

The drum sounds are sample-based, sourced from the TR-909 ROM sample archive at hyperreal.org. Four voices are used: kick (BTAAADA), snare (STATASA), closed hat (HHCDA), and open hat (HHODA). These are the maximum-parameter samples from the 909's original D/A converter — longest decay, most open tone, giving the most material for the envelope shaping done in software.

Each voice is controlled by 2 to 4 normalized (0–1) parameters that map to audio values:

VoiceControlsImplementation
Kick Tune, Level, Attack, Decay Playback rate shift (±1 octave via pow(2, x)), gain envelope with configurable attack ramp and exponential decay.
Snare Tune, Level, Tone, Snappy Playback rate, lowpass BiquadFilter (800–4000 Hz for Tone), exponential decay length controlled by Snappy.
Closed Hat Level, Decay Short exponential decay (10–130 ms). Routes to the CH bus. Chokes the OH bus on trigger.
Open Hat Level, Decay Longer exponential decay (100–700 ms). Routes to the OH bus. Restores OH bus gain on trigger.

Hat choke is the behavior on real hardware where triggering the closed hat silences the open hat immediately. The implementation uses two persistent GainNodes as buses: _chBus for closed hat output, _ohBus for open hat output. When a closed hat fires, it schedules a 4 ms linear ramp on _ohBus.gain to zero. When an open hat fires, it cancels any scheduled values and restores _ohBus.gain to 1.

Earlier attempts at choke tracked individual source nodes and called .stop() on them, or read gain.value during lookahead. Both fail: stopping a source mid-playback causes a click, and gain.value returns the current value even when future values are scheduled on the parameter. The bus GainNode approach works because cancelScheduledValues + setValueAtTime + linearRampToValueAtTime are all sample-accurate scheduled operations on the audio thread.

Lo-Fi Bundling

The original TR-909 WAV files are 44.1 kHz, 16-bit mono — about 125 KB total. Loading them requires four separate HTTP requests and a brief wait before the first beat. To eliminate the load wait and add character, both a lo-fi and hi-fi version are bundled directly into a JavaScript module as base64 data URIs.

ModeSample RateBit DepthTotal Size
Lo-Fi (default) 11,025 Hz 8-bit unsigned ~16 KB WAV / ~22 KB base64
Hi-Fi 44,100 Hz 16-bit PCM ~125 KB WAV / ~167 KB base64

Both sets are decoded into AudioBuffers at startup via the Web Audio decodeAudioData API. Switching between lo-fi and hi-fi is instantaneous mid-playback because both buffer sets are held in memory. The lo-fi mode is the default: at 11 kHz the samples have a gritty, vintage character that suits the 303 aesthetic, and the 8x smaller decode payload means the kit is ready before the first bar plays.

11,025 Hz is exactly 1/4 of CD quality (44,100 Hz) and was the sample rate of the original Sound Blaster cards. It introduces a Nyquist ceiling at 5,512 Hz, rolling off high-frequency content that gives the drums a warmer, more lo-fi presence — appropriate for a genre that emerged from drum machines with 8-bit D/A converters.

Pattern Model

Each pattern is a plain JavaScript object. Steps are variable-length arrays; the sequencer loops them independently of the 16-step drum grid, creating polyrhythm automatically when the pattern length is not a power of two.

// A step has five fields: { pitch: 'G', // note name: C, C#, D, D#, E, F, F#, G, G#, A, A#, B octave: -1, // -1 (down), 0 (mid), +1 (up) gate: true, // true = triggered note; false = rest accent: false, // gain boost + extended envelope peak slide: true, // portamento glide into this note from the previous }

Frequency is computed as: midiToFrequency(pitchToMidi(pitch) + (octave * 12)). The base octave is MIDI 36 (C2, ~65 Hz) — the 303 plays in bass register.

Pattern length interacts with the fixed 16-step drum grid to produce polyrhythmic phasing. A 3-step bass pattern against 16-step drums reaches coincidence (LCM) at 48 steps — three bars before the cycle resets. The POLY category exploits this systematically with prime lengths 2, 3, 5, 7, 11, and 13.

Pattern Selection

Patterns were chosen to represent the range of the instrument and the history of acid house and related genres. All reconstructions are based on community consensus, published transcriptions, or direct analysis.

Da Funk
Daft Punk — Homework, 1997
108 BPM, 16 steps. Nearly all slides; continuous portamento defines the sound. Sawtooth wave, driven filter. The most covered 303 line in dance music.
16 stepssawtoothdrive
U There?
Josh Wink — Higher State of Consciousness, 1995
134 BPM, 10 steps. Irregular length creates natural polyrhythm against drums. Reconstructed from memory — the original pattern was lost when Wink dropped the 303.
10 stepssawtoothpoly
Downfall
Armando — Downfall, 1990
130 BPM, 16 steps. Transcribed from original scanned sheet music. Signature E→C sliding motion with accented A peaks. Chicago house, pre-acid-house era.
16 stepsslidesaccents
Acid Tracks
Phuture — Acid Tracks, 1987
130 BPM, 8 steps. The record that named acid house. B and C# in upper octave, screaming accents. Step 1 accent+slide into step 2 is the canonical 303 moment.
8 stepsorigin
Acperience 1 & 2
Hardfloor — Acperience 1, 1992
133 BPM. Two patterns from the same record, demonstrating sparse 8-step punctuation (Acperience 1) vs. dense 16-step G slides (Acperience 2). Foundational Teutonic acid.
8 + 16 stepstechno
Sea Foam
tnn1t1s
132 BPM, 16 steps. Original pattern. Dense D# sliding bass line with a structural rest at step 7 and 13. Built to demonstrate how continuous slides create melodic flow.
originalslides
Poly 2 — 13
Original
Prime-length bass patterns (2, 3, 5, 7, 11, 13 steps) against a fixed 16-step drum grid. Full cycles range from 16 to 208 steps before the downbeat aligns again.
polyrhythmprime steps

Visual Language

The palette is derived from the Roland TR-909 and TB-303 hardware. The main body color is RAL 9006 "White Aluminium" (#A5A8A6), a warm silver-grey used on both machines. Panel recesses step down to a darker silver-panel tone; the footer rail and knob housings use the darkest grey before reaching the near-black borders that separate the machine from the page background.

/* CSS custom property palette */ --silver-highlight: #b2b5b3; /* raised edges */ --silver-body: #a5a8a6; /* main panel — RAL 9006 */ --silver-panel: #8e9190; /* recessed sections */ --silver-footer: #727573; /* bottom rail */ --border-outer: #3a3d3b; /* machine edge */ --accent-blue: #1a5fa8; /* step indicators, active states */

Knobs are SVG with a rotation transform on the pointer line, driven by mouse drag (delta-Y mapped to angle). The tick marks are static SVG lines at 7 positions (−135° to +135° in 45° steps). No external SVG libraries or canvas are used.

The step grid uses a two-group color treatment from the 909 front panel: steps 1–4 and 9–12 use the main cell color; steps 5–8 and 13–16 (the B group) use a slightly darker background. This makes beat positions legible without bar lines.

Typography is Arial Narrow for body text and Arial Black for headings — the same compressed grotesque family Roland used for panel labeling on hardware from this era. No web fonts are loaded.