SuperCollider gamepad performance is the prettiest little workflow in livecoded music. You write a Pbind, wrap it in a Pdef, and the pattern keeps looping while you redefine its insides from another part of the buffer. Pair that with a MIDIdef.cc for every gamepad axis and you get a continuous macro layer — sticks fold and unfold a running loop, no Stop, no Resume, no clicks. The DualSense becomes a hardware live-coding pedal that controls the breath of the music while your fingers are still on the keyboard.
- What you build: a SuperCollider
Pdefdriven by aPbind, with every macro slot bound to a gamepad CC viaMIDIdef.cc. - What you need: SuperCollider 3.13+, Universal Controller MIDI, any controller the bridge supports.
- Time: ~20 minutes to type, less to perform.
- Why it slaps: hot-reloadable pattern + continuous gamepad macros + zero plugin chain.
Why Pdef + MIDIdef.cc is the right shape
Two SuperCollider primitives deserve all the credit here. Pdef(\name, pattern) stores a pattern under a symbol so it can be replaced live without restarting the stream — the new pattern takes over at the next event boundary. MIDIdef.cc registers a callback for incoming control changes, scoped to a name so it can be redefined safely too. Combine the two with a handful of global variables, and the gamepad becomes a macro source that any pattern can subscribe to. The MIDIdef docs are worth a one-page read.
Boot the server and wire MIDI
In a fresh sclang document, evaluate the lines below one block at a time (Cmd+Return / Ctrl+Return). The bridge's virtual port should appear in the SuperCollider device list — if it doesn't, recycle the bridge once and rerun MIDIClient.init.
s.boot;
// Inspect MIDI devices
MIDIClient.init;
MIDIClient.sources; // look for "Gamepad Out"
MIDIIn.connectAll; The macro SynthDef
A single SynthDef does duty as the voice. Every gamepad-controlled parameter is a named argument, each smoothed with .lag so a stick sweep doesn't zipper.
SynthDef(\stickvoice, { |out=0, freq=220, cut=800, res=0.4, det=0, fm=0, amp=0.5|
var car = SinOsc.ar(freq * [1, 1 + det.lag(0.05)]);
var mod = SinOsc.ar(freq * 2) * fm.lag(0.05) * freq;
var sig = SinOsc.ar(freq + mod) * 0.5 + (car * 0.5);
sig = MoogFF.ar(sig, cut.lag(0.05), res.lag(0.05));
sig = sig * amp.lag(0.03) * EnvGen.kr(Env.perc(0.005, 0.6), doneAction: 2);
Out.ar(out, sig ! 2);
}).add; Gamepad CCs become global macros
Eight global variables hold the most recent CC value, each on a 0–1 range. MIDIdef.cc updates them whenever the bridge sends a message. Name your defs so they're easy to redefine.
// Globals — start centred
~lx = 0.5; ~ly = 0.5; ~rx = 0.5; ~ry = 0.5;
~l2 = 0.0; ~r2 = 0.0; ~face = 0.0; ~dpad = 0.0;
// Each CC re-uses the same MIDIdef name so a re-evaluation is safe.
MIDIdef.cc(\gpLx, { |val| ~lx = val / 127 }, 16);
MIDIdef.cc(\gpLy, { |val| ~ly = val / 127 }, 17);
MIDIdef.cc(\gpRx, { |val| ~rx = val / 127 }, 18);
MIDIdef.cc(\gpRy, { |val| ~ry = val / 127 }, 19);
MIDIdef.cc(\gpL2, { |val| ~l2 = val / 127 }, 20);
MIDIdef.cc(\gpR2, { |val| ~r2 = val / 127 }, 21);
Reload-safety matters here. SuperCollider is a livecoding language — you will re-evaluate this block fifty times in a session. MIDIdef.cc's name (\gpLx) makes that a no-op.
The Pdef — patterns that read the macros
Now the part you actually perform. A Pdef wraps a Pbind; each macro key reads its current value from a global. Pfunc evaluates a closure per event so the pattern sees fresh CC values on every note.
Pdef(\stickloop,
Pbind(
\instrument, \stickvoice,
\dur, Pwrand(#[0.125, 0.25, 0.5], #[0.5, 0.35, 0.15], inf),
\degree, Pseq([0, 2, 4, 7, 9, 7, 4, 2], inf),
\octave, Pfunc({ 3 + (~ly * 3).floor }),
\cut, Pfunc({ ~lx.linexp(0, 1, 200, 8000) }),
\res, Pfunc({ ~l2.linlin(0, 1, 0.05, 0.95) }),
\det, Pfunc({ ~rx.linlin(0, 1, -0.02, 0.02) }),
\fm, Pfunc({ ~ry }),
\amp, Pfunc({ 0.15 + ~r2 * 0.5 })
)
).play;
Hit evaluate. The loop starts. Move the left stick — cutoff sweeps and octave climbs in real time. Re-evaluate the same block with a different \degree sequence and the new notes take over at the next bar, no glitch, gamepad still steering. That is livecoding the way it was always meant to be.
Pattern slot mapping reference
| Gamepad input | Bridge MIDI | Pattern slot | Effect |
|---|---|---|---|
| Left stick X | CC 16 | \cut via linexp | Filter cutoff sweep |
| Left stick Y | CC 17 | \octave floored | Octave climb / drop |
| Right stick X | CC 18 | \det | Microtuning / chorus |
| Right stick Y | CC 19 | \fm | FM index 0–1 |
| L2 trigger | CC 20 | \res | Resonance squelch |
| R2 trigger | CC 21 | \amp | Velocity ride |
Buttons as Pdef swappers
Treat face buttons as preset selectors. A MIDIdef.noteOn per button can replace the \degree sequence on the fly — the same Pdef keeps running, but the melody under your thumb changes shape.
MIDIdef.noteOn(\faceA, { |val, num, chan|
switch(num,
60, { Pdef(\stickloop).set(\degree, Pseq([0, 3, 5, 7], inf)) },
61, { Pdef(\stickloop).set(\degree, Pxrand([0, 2, 4, 7, 9], inf)) },
62, { Pdef(\stickloop).set(\degree, Pseq([0, 0, 7, 0, 5, 0, 7, 12], inf)) },
63, { Pdef(\stickloop).set(\degree, Pwhite(-7, 12, inf)) }
)
}); Stopping cleanly and recording
Pdef(\stickloop).stop halts the loop without unloading the SynthDef. To capture a take, call s.record before you start and s.stopRecording when finished — a WAV lands in the SC recordings folder with every gamepad sweep frozen in place. Compose live, render perfectly. Pair this with our notes on Csound + gamepad and Orca livecoding for the trifecta of creative-coding setups that take a controller as a first-class macro source. Then go grab Universal Controller MIDI, drop into a fresh sclang doc, and play the pattern instead of just defining it.