OSC-1
The full synth, taken apart. Every stage is its own module.
Audio and MIDI
Browser session
Audio is stopped.
Input · MIDI keyboard
Your keyboard is a keyboard. Middle row plays the white notes, top row the black ones. Z and X change octave. Keys are ignored while you are typing in a field.
OSC-1
The flagship synth as a composable chain: two oscillators and a noise layer into a filter, an envelope, and the effects rack - in the canonical order. Every stage is a module you can open on its own page, and every one of them bypasses without a gap.
Signal chain · 11 stages
Every stage is its own module. Bypass one and the signal routes around it — no gap, no click, even mid-note.
Parameters · by stage
Oscillator
open moduleNoise
open moduleFilter
open moduleAmp Env
open moduleLFO
open moduleDrive
open moduleCrush
open moduleChorus
open moduleTremolo
open moduleDelay
open moduleReverb
open moduleMaster
Every knob here belongs to one stage, and its path is namespaced by that stage's slug — the Filter's cutoff is filter.frequency, the Delay's feedback is delay-stage.feedback. Those are the same paths each stage publishes on its own page. Bypassed stages keep their values and grey out.
Output · Audio signal
The signal leaving OSC-1, tapped after the output stage. It's a picture of what your speakers are getting.
About
OSC-1 is the flagship, and the flagship is a chain. Two oscillators and a noise layer feed a resonant filter, an amplitude envelope shapes the result, and then the effects rack runs in a fixed, musically-sensible order: drive, crush, chorus, tremolo, delay, reverb. That order is not a preference, it is the order the source synth has always used, and keeping it fixed is what makes a patch mean the same thing every time you load it.
What is different here is that none of those stages is welded shut. Each one is a real module with its own page, its own parameter table, and its own preview - the Filter stage in this chain is the same Filter module you can open on its own and play through a guitar take. When you learn a stage in isolation, you have learned it everywhere it appears.
Every effect stage starts bypassed, which is the same thing the source synth's Init patch does: the stage is built and sitting in the graph doing nothing, rather than absent and needing to be created. That is why toggling one mid-note does not click or drop the note - nothing is being rebuilt, the signal is simply being routed around a stage that was already there.
The preview here is the site's stand-in Web Audio engine, not the compiled kernel. The parameter names, ranges and defaults are ported from the real schemas so the vocabulary matches, but the sound is an approximation and none of the engine parity measurements apply to it.
11 stages, in signal order.
None of these is a sub-panel. Each one is a module with its own page, its own parameter table and its own preview — open any of them and it plays on its own. Inside OSC-1 they run in this order, and the ones marked bypassable can drop out of circuit without a gap.
Take it into your DAW.
Same DSP as this page. The plugin isn't a rebuild — it's the identical kernel from the identical TypeScript source, compiled native instead of to WASM.
Free forever, no account, and playing it in the browser will never ask you for one. VST3 and standalone today; AU and CLAP are on the roadmap.
Every knob, and what it actually does.
Generated from the module's parameter schema — the same source as the web UI, the worklet transport and the VST3 parameter table, so it cannot drift. For a chain, that schema is the union of its stages' own tables, in signal order — each stage's rows are the same ones its page publishes.
| Param | Range | Default | Units | What it does | Automatable |
|---|---|---|---|---|---|
| Oscillator 1 | |||||
Osc 1 Waveoscillator.osc1Type | sine · square · sawtooth · triangle | sawtooth | — | Waveform for the first oscillator. Sawtooth has every harmonic and is the usual starting point; sine has none, so a filter has nothing to remove. | Yes (stepped) |
Osc 1 Leveloscillator.osc1Level | 0 … 1 | 0.7 | — | Level of the first oscillator into the mix. | Yes |
Osc 1 Octaveoscillator.osc1Octave | -3 … 3 | 0 | oct | Transposes oscillator 1 in whole octaves. | Yes (stepped) |
Osc 1 Detuneoscillator.osc1Detune | -100 … 100 | 0 | ct | Fine pitch offset in cents, one semitone either way. | Yes |
| Oscillator 2 | |||||
Osc 2 Waveoscillator.osc2Type | sine · square · sawtooth · triangle | square | — | Waveform for the second oscillator. | Yes (stepped) |
Osc 2 Leveloscillator.osc2Level | 0 … 1 | 0.3 | — | Level of the second oscillator. Keep it under oscillator 1 and it thickens; match them and it becomes a chord of two. | Yes |
Osc 2 Octaveoscillator.osc2Octave | -3 … 3 | 0 | oct | Transposes oscillator 2 in whole octaves. Down one is the classic sub. | Yes (stepped) |
Osc 2 Detuneoscillator.osc2Detune | -100 … 100 | 7 | ct | Fine pitch offset for oscillator 2. Seven cents is the default because a small mismatch is what makes two oscillators sound like more than two. | Yes |
| Noise | |||||
Typenoise.type | white · pink · brown | white | — | White is flat and hissy, pink falls 3 dB per octave and sounds natural, brown falls twice as fast and is nearly a rumble. | Yes (stepped) |
Levelnoise.level | 0 … 1 | 0.1 | — | Noise level into the voice mix. A little goes a long way - the default is deliberately low. | Yes |
Tonenoise.tone | -100 … 100 | 0 | — | Tilts the noise dark or bright without changing its type. Negative rolls off the top, positive rolls off the bottom. | Yes |
| Filter | |||||
Typefilter.type | lowpass · highpass · bandpass · notch | lowpass | — | Which side of the cutoff survives. Low-pass is the default and the one that sounds like a synth. | Yes (stepped) |
Cutofffilter.frequency | 20 … 20000 | 2000 | Hz | Where the filter acts. The single most useful control in the whole chain, and the one worth automating. | Yes |
Resonancefilter.resonance | 0 … 20 | 1 | — | Emphasis at the cutoff. Above about 12 it whistles on its own; at 0 it is a plain tone control. | Yes |
| Filter Envelope | |||||
Env Attackfilter.envAttack | 0 … 2 | 0.01 | s | How fast the contour opens the filter at note start. | Yes |
Env Decayfilter.envDecay | 0 … 2 | 0.2 | s | Fall from the contour peak to its sustain level. | Yes |
Env Sustainfilter.envSustain | 0 … 1 | 0.5 | — | Contour level held while the key is down. | Yes |
Env Releasefilter.envRelease | 0 … 5 | 0.5 | s | How long the contour takes to close after release. | Yes |
Base Freqfilter.baseFrequency | 20 … 2000 | 200 | Hz | Where the contour starts from. The envelope sweeps up from here rather than from the cutoff knob. | Yes |
Env Octavesfilter.octaves | 0 … 8 | 4 | oct | How far above Base Freq the contour reaches at full envelope. Zero disables the contour entirely. | Yes |
| Amp Envelope | |||||
Attackenvelope.attack | 0 … 2 | 0.01 | s | Silence to full level. At zero it is a click, and a click is a perfectly good transient. | Yes |
Decayenvelope.decay | 0 … 2 | 0.1 | s | Fall from the attack peak down to the sustain level. | Yes |
Sustainenvelope.sustain | 0 … 1 | 0.5 | — | Level held while the key is down. At zero the note plucks no matter how long you hold it. | Yes |
Releaseenvelope.release | 0 … 5 | 0.5 | s | Fade after you let go. Long release plus short decay is where pads live. | Yes |
| LFO | |||||
Ratelfo.frequency | 0.1 … 20 | 4 | Hz | Cycles per second. Under 1 Hz it is a slow drift; above about 8 Hz it stops being modulation and starts being timbre. | Yes |
Shapelfo.type | sine · square · sawtooth · triangle | sine | — | The modulation waveform. Square gives a two-state jump rather than a sweep, which is a trill rather than a vibrato. | Yes (stepped) |
Targetlfo.target | pitch · filter · amplitude | pitch | — | What the LFO moves. One destination at a time here - the full modulation matrix is a later stage of the port. | Yes (stepped) |
Depthlfo.depth | 0 … 1 | 0.5 | — | How far the target moves. Scaled per destination: at full depth pitch swings 100 cents, the filter 2 kHz, amplitude the whole way to silence. | Yes |
| Drive | |||||
Amountdrive.amount | 0 … 1 | 0.4 | — | How hard the signal hits the curve. Below about 0.2 it is a clean boost; near 1 the waveform is mostly a square. | Yes |
Mixdrive.wet | 0 … 1 | 0.5 | — | Blend of driven against clean. Parallel distortion keeps the transient intact while the body melts. | Yes |
| Crush | |||||
Bitscrush.bits | 1 … 16 | 4 | — | Quantisation depth. 16 is transparent, 8 sounds like a sampler from 1987, 1 is a square wave with opinions. | Yes (stepped) |
Mixcrush.wet | 0 … 1 | 0.5 | — | Blend of crushed against clean. | Yes |
| Chorus | |||||
Ratechorus.frequency | 0.1 … 10 | 1.5 | Hz | Speed of the delay-time modulation. Slow is lush, fast is seasick. | Yes |
Depthchorus.depth | 0 … 1 | 0.7 | — | How far the delay time swings, and therefore how much pitch wobble you get. | Yes |
Delaychorus.delayTime | 0 … 20 | 3.5 | ms | Centre delay time. Under about 5 ms it flanges; above about 15 ms it stops blending and becomes a distinct double. | Yes |
Mixchorus.wet | 0 … 1 | 0.5 | — | Blend against the dry signal. Chorus is entirely an interference effect, so the dry path matters. | Yes |
| Tremolo | |||||
Ratetremolo.frequency | 0.1 … 20 | 10 | Hz | Chops per second. Above about 15 Hz the ear stops hearing rhythm and starts hearing sidebands. | Yes |
Depthtremolo.depth | 0 … 1 | 0.5 | — | How far the level drops at the bottom of each cycle. At 1 it reaches silence. | Yes |
Spreadtremolo.spread | 0 … 180 | 180 | deg | Phase offset between left and right. At 180 degrees the channels are opposite and the tremolo becomes an auto-pan. | Yes |
Mixtremolo.wet | 0 … 1 | 1 | — | Blend against the unmodulated signal. Pull it back to keep some level through the troughs. | Yes |
| Delay | |||||
Timedelay-stage.time | 0 … 1 | 0.25 | s | Spacing between repeats. Under about 0.05 s it stops being an echo and becomes a comb filter. | Yes |
Feedbackdelay-stage.feedback | 0 … 0.95 | 0.3 | — | How much of each repeat feeds back in. Capped at 0.95 so it can sing without becoming a hazard. | Yes |
Mixdelay-stage.wet | 0 … 1 | 0.3 | — | Level of the repeats against the dry signal. | Yes |
| Reverb | |||||
Room Sizereverb-stage.roomSize | 0 … 1 | 0.7 | — | Length of the tail. Small values are a booth, large ones are a hall you should not be in. | Yes |
Dampingreverb-stage.dampening | 20 … 20000 | 3000 | Hz | Where the tail starts losing its top end. Low values are a carpeted room; high values are tiled and unforgiving. | Yes |
Mixreverb-stage.wet | 0 … 1 | 0.3 | — | Level of the tail against the dry signal. | Yes |
| Master | |||||
Levelmaster.level | 0 … 1 | 0.8 | — | Output trim after every stage. The last thing in the chain. | Yes |
Polyphonymaster.polyphony | 1 … 32 | 16 | — | Voices available before note stealing. Sixteen is the source synth's default and more than most parts need. | Yes (stepped) |
Parameter paths (filter.cutoff) are what you'll see in your DAW's automation list and in the SDK. Same names everywhere.
How it works.
The chain is a fixed list of stages in signal order, and each stage declares whether it can be bypassed. Sources and the amplitude envelope are not bypassable - a synth with no oscillator and no envelope is not a quieter synth, it is a broken one. Everything after the envelope is.
Bypass is a routing decision, not a construction decision. Every stage exists in the graph from the moment the chain is built, with a dry path and a wet path into the same summing point. Disabling a stage ramps its wet path to zero and its dry path to one over a few milliseconds; enabling it does the reverse. Nothing is connected or disconnected while audio is flowing, which is why a held note survives the toggle intact.
Parameters are namespaced by stage. The filter's cutoff is filter.cutoff, the delay's feedback is delay-stage.feedback, and the chain routes each dotted path to the stage that owns it. Those are the same paths the stage's own page publishes, so a value you learn in one place is addressable from the other.
It's yours to read and to fork.
Research preview - licensing TBC, see /about
npx codex clone osc-1
npm run devThe codex CLI name is proposed, not shipped. That command gives you this module's DSP, schema, presets and UI as a local project — change a number, hear it immediately.