supercollider-synthdefs · git:20261006.2a421d0 · 2026-10-06 · sha256 b8761751699544a2

supercollider-synthdefs git:20261006.2a421d0A

Immutable. This exact content never changes and is served at /api/v1/blob/b8761751699544a2.

---
name: supercollider-synthdefs
description: Write, compile, load, debug and re-use SuperCollider SynthDefs with sc_synthdef - the unit of sound design in SuperCollider. Covers Out and ReplaceOut, argument naming, envelopes and doneAction, why a def must be loaded on a specific server before /s_new works, and how to read the compiler's error rather than guessing.
whenToUse: Use whenever a sound needs to exist as a re-usable definition rather than a one-off snippet - a named instrument, a voice for a pattern, a def to be triggered many times with different controls, or anything the user wants to save and recall after a server reboot.
---

# SynthDefs

A `SynthDef` is a compiled description of one audio process: its unit generators,
its controls, and how it ends. It is the unit of sound design in SuperCollider,
and it is what a pattern's `\instrument` names.

The lifecycle is four steps, and knowing which step failed is most of the
debugging:

```
source  ->  compiled to .scsyndef bytes  ->  sent to ONE server  ->  /s_new makes a node
   |                  |                              |                        |
 sc_check        sc_synthdef define          sc_synthdef load            sc_nodes action=new
```

A SynthDef lives **per server**. "SynthDef not found" from a `/s_new` always means
step three did not happen on *that* server — not that the def is wrong.

## The smallest useful def

Give `sc_synthdef` a name and the body — the part inside `SynthDef(\name, { ... })`:

- action: `define`
- name: `tone`
- body: `|freq = 440, amp = 0.1, gate = 1| Out.ar(0, SinOsc.ar(freq) * amp * EnvGen.kr(Env.asr(0.01, 1, 0.2), gate, doneAction: 2))`

That compiles it, caches the bytes under `$DSH_HOME/dsh-supercollider/synthdefs/`
keyed by the source's digest, and sends it to the running server. Then:

- `sc_nodes` action=`new`, name=`tone`, params `{"freq": 220}`
- `sc_nodes` action=`set`, node=`1000`, params `{"freq": 330}`
- `sc_nodes` action=`free`, node=`1000`

## Writing the body

### Arguments are the controls

```supercollider
|freq = 440, amp = 0.1, pan = 0, gate = 1|
```

- Defaults make every argument optional at `/s_new` time.
- A name in `|...|` becomes a control you can `set` while the node plays.
- `gate` is the convention for a sustained voice: an `EnvGen` with `gate` and
  `doneAction: 2` holds the sound until `gate` goes to 0, then frees its own node.

### Output

`Out.ar(bus, signal)` adds to the bus; `ReplaceOut.ar(bus, signal)` replaces what
is there. On bus 0 (the hardware output) `Out` is what you want, so several
voices mix.

```supercollider
SynthDef(\tone1, { |freq = 440, amp = 0.1| Out.ar(0, SinOsc.ar(freq) * amp) })
```

For stereo, give the signal two channels — `SinOsc.ar([freq, freq * 1.001])`, or
`Pan2.ar(sig, pan)`.

### Envelopes, and why `doneAction` matters

A synth node does not stop when its sound becomes inaudible; it stops when
something frees it. Without a `doneAction`, a percussive one-shot keeps a node
alive forever at zero amplitude.

| Intent | Envelope |
|---|---|
| A click or a hit | `EnvGen.kr(Env.perc(0.005, 0.3), doneAction: 2)` |
| A sustained voice | `EnvGen.kr(Env.asr(0.01, 1, 0.3), gate, doneAction: 2)` |
| A note of fixed length | `EnvGen.kr(Env.linen(0.01, 0.4, 0.2), doneAction: 2)` |
| A drone that never ends | No envelope — and then *you* must free it |

### A complete percussive instrument

```supercollider
SynthDef(\blip, { |freq = 440, amp = 0.2, decay = 0.25|
	var env = EnvGen.kr(Env.perc(0.004, decay), doneAction: 2);
	var sig = SinOsc.ar(freq) * env * amp;
	Out.ar(0, Pan2.ar(sig, 0));
})
```

### A complete sustained voice

```supercollider
SynthDef(\pad, { |freq = 110, amp = 0.08, gate = 1, cutoff = 900|
	var env = EnvGen.kr(Env.asr(0.4, 1, 0.8), gate, doneAction: 2);
	var sig = LFSaw.ar([freq, freq * 1.004]);
	sig = RLPF.ar(sig, cutoff, 0.4) * env * amp;
	Out.ar(0, sig);
})
```

Set `cutoff` while it plays with `sc_nodes` action=`set` — that is the point of a
sustained voice.

### A filter on a noise source

```supercollider
SynthDef(\wind, { |amp = 0.05, cutoff = 800, rq = 0.4|
	var env = EnvGen.kr(Env.asr(0.5, 1, 0.6), gate: 1, doneAction: 2);
	var sig = BPF.ar(PinkNoise.ar, cutoff, rq);
	Out.ar(0, sig * env * amp);
})
```

## Using a def from a pattern

`Pbind`'s `\instrument` is the SynthDef's name, and every other key becomes a
control:

```supercollider
Pbind(
	\instrument, \blip,
	\freq, Pseq([440, 554.4, 659.3], inf),
	\decay, 0.2,
	\dur, 0.5,
	\amp, 0.15
).play;
```

## Debugging a def that will not work

**1. Compile errors.** `sc_synthdef` returns the interpreter's own text. Read it;
it names the line and the token. The usual causes:

| Message | Cause |
|---|---|
| `Variable 'x' not defined` | A name used before it is declared, or a typo. Declare with `var` at the top of the function body. |
| `ERROR: syntax error, unexpected VAR` | A `var` not at the top of a function. Move it. |
| `ERROR: Message 'ar' not understood` | A UGen name that does not exist, or a server-side UGen used in a `kr`-only context. Check the class with `sc_help`. |
| `WARNING: ... not a valid UGen input` | A number where a signal belongs (`mul` given an array, a `nil` default). |
| `SynthDef: missing Out` | The body builds no output. It has to reach `Out.ar`. |

**2. It compiled but `/s_new` says "SynthDef not found".** The def is not loaded on
that server. `sc_synthdef` action=`load`, then try again. After
`sc_server` action=`reboot`, **every** def is gone — reload before creating nodes.

**3. It plays but makes no sound.** In order of likelihood:

- `amp` is 0 or the mix is not on bus 0 — check `Out.ar(0, ...)`.
- The envelope closed immediately (`gate` defaulted to 0, or the attack is
  shorter than the call). `sc_nodes` action=`tree` shows whether the node exists.
- The node exists but is silent: set `amp` higher with `sc_nodes` action=`set`.
- The server is not running: `sc_status`.

**4. It plays forever.** No `doneAction` on an envelope, or no envelope at all.
Add `doneAction: 2`, or free the node with `sc_nodes` action=`free`.

## Names and re-use

- A def name is an identifier: letters, digits and `_`, not starting with a digit.
  `\blip_2` is fine; `\2blip` is not.
- Re-defining a name replaces the def; nodes already running keep the old one
  until they end. That is usually what you want for editing an instrument live.
- `sc_synthdef` action=`list` shows what this session compiled and which defs a
  given server port has. `action=source` gives back the source of a compiled def,
  which is how you edit one you only have as bytes.
- Prefer a handful of well-argued defs over a new one per sound: `\blip` with
  `freq`, `decay` and `amp` covers most of what percussive material needs.