Blog Creative 9 min read

SuperCollider + Gamepad — Pattern Playback with MIDIdef Macros

Bind gamepad CCs to live Pdef pattern arguments in SuperCollider. Stick-driven macros over a running Pbind, with zero-glitch hot reloads.

By Aidxn Design

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.

TL;DR
  • What you build: a SuperCollider Pdef driven by a Pbind, with every macro slot bound to a gamepad CC via MIDIdef.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 inputBridge MIDIPattern slotEffect
Left stick XCC 16\cut via linexpFilter cutoff sweep
Left stick YCC 17\octave flooredOctave climb / drop
Right stick XCC 18\detMicrotuning / chorus
Right stick YCC 19\fmFM index 0–1
L2 triggerCC 20\resResonance squelch
R2 triggerCC 21\ampVelocity 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.

Keep reading

More setup walkthroughs