The chuck engine runs the ChucK language and virtual machine as a synth on the spotykach. A program (a .ck text file) is compiled and run at runtime, so the patch — not the firmware — defines the sound. You load programs from the SD card, switch between them live, and play them from the panel knobs.

ChucK is strongly-timed: a program sporks concurrent "shreds" that the VM schedules sample-accurately against a unit-generator graph. So a single patch can run several self-playing voices in polyrhythm, not just one signal path.

It behaves like any other engine on the panel, but is built and flashed a little differently: ChucK's code is ~1.1 MB (too big for SRAM), so this is a QSPI build that executes from flash, with ChucK's heap in SDRAM. Internals (memory model, allocator, the live-swap design, bring-up notes, roadmap) are in docs/dev/chuck-impl.md.

Build & flash

One-time: build the ChucK library (needs the arm-none-eabi GCC toolchain). The script fetches a pinned ChucK release and cross-builds it for the Daisy — there is no official ChucK Daisy port, so the script is the port:

scripts/fetch_chuck.sh           # fetch ChucK + cross-build libchuck.a (gitignored sources)

Then build + flash the engine (put the board in DFU first):

make engine-chuck                # clean + build the QSPI image + flash
make program-chuck               # re-flash the last build without rebuilding

Recover the board at any time by flashing any normal engine.

Controls

With the built-in program (used when no SD patch is loaded — a SawOsc => LPF => dac drone):

ControlEffect
PITCHpitch
SIZEbrightness (filter cutoff)
MIXlevel
Alt + PITCHpatch selector (see below)
centre mode LEDwhite = built-in program, cyan = an SD patch is loaded

What each knob actually does is up to the patch (it reads named global variables) — the table above is the convention the bundled patches follow. Patches can also be played from a MIDI keyboard: MIDI input is supported through ChucK's standard MidiIn (see Limitations → MIDI).

Loading patches from the SD card

Put .ck programs in a chuck/ folder at the card root, named 0.ck7.ck:

<card>/chuck/0.ck
<card>/chuck/1.ck

Ready-to-copy examples are in examples/chuck/ — a clean two-saw drone, a fat super-saw, a concurrent generative pad, and an STK Rhodey arpeggio, with a README.

How live switching works

The engine keeps one ChucK VM running for the whole session and swaps the patch's shreds inside it. The first time you select a given patch it is compiled (a brief silence while that happens); every later switch back to it is instant (the compiled program is cached and re-run, not recompiled). Memory use therefore rises a little the first time each distinct patch is visited and then stays flat — you can cycle patches indefinitely without the unit degrading. (The why, and the ChucK-internals story behind it, is in docs/dev/chuck-impl.md.)

Writing a patch

A patch is an ordinary .ck program — no required header. To be driven by the panel, declare and read these globals (deck A / deck B):

KnobGlobal
PITCHspeedA / speedB
MIXmixA / mixB
SIZEsizeA / sizeB
ENVenvA / envB
modifier layerfbA, modspA, modampA (+ B)

A program reads whichever globals it declares; writes to globals a patch doesn't declare are ignored. Keep to the convention the bundled patches follow — PITCH = pitch/tempo, SIZE = brightness, MIX = level — so the knobs feel consistent when you switch patches. A minimal patch:

global float speedA;   // PITCH
global float sizeA;    // SIZE
global float mixA;     // MIX

SawOsc s => LPF f => dac;
while( true )
{
    110.0 + speedA * 770.0   => s.freq;    // pitch
    1500.0 + sizeA * 6000.0  => f.freq;    // brightness
    (0.15 + mixA * 0.85) * 0.3 => s.gain;  // level
    10::ms => now;                          // re-poll the knobs
}

Format notes — a UTF-8 BOM and CRLF (Windows) line endings are tolerated (the engine strips them on load); an empty or whitespace-only file falls back to the built-in. There is no structural validation beyond that — the real check is whether the program compiles.

Two caveats from the one-VM design (fine for a curated bank, worth knowing if you author your own):

CPU and the meter

Every concurrent shred and every UGen costs time inside one audio block, and code runs from QSPI flash (slower than SRAM), so heavy or dense patches can run out of headroom. If a patch genuinely can't keep up, the engine mutes it and lights both rings solid red (a safeguard that keeps the controls alive) — switch to another patch with Alt+PITCH to recover. Lighter voices and a capped shred/voice count are the fix.

Build with METER=1 to see the headroom on the panel instead of guessing:

make engine-chuck METER=1

Limitations

Remaining incompatibilities with desktop ChucK (all are limitations of the bare-metal port, not changes to the language API):