sk-engines 0.5.0
Changes since the last Release
The biggest change in this release, and reasons for the minor version bump, is the addition of the chuck engine, which runs the ChucK language and virtual machine on the spotykach. Like the csound engine, chuck is a dsp language and platform and you can program it via .ck text files which are 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.
Note that we are still exploring the limits of the such powerful engines on the daisy hardware so some complex polyphonic patch which may work on a MacBook may struggle on the daisy platform. Nonetheless, using chuck is very intuitive (and more approachable than the venerable csound). This is one of the examples provided for use with spotykach.
global float speedA; // PITCH -> tempo
global float sizeA; // SIZE -> reverb mix
global float mixA; // MIX -> level
SinOsc s => JCRev r => dac;
// a scale (semitone offsets) to pick notes from
[ 0, 2, 4, 7, 9, 11 ] @=> int hi[];
while( true )
{
// controls, read live each step
(0.05 + mixA * 0.45) => s.gain; // MIX -> level
(0.02 + sizeA * 0.60) => r.mix; // SIZE -> reverb amount (dry..wet)
// random note from the scale, across a few octaves
Std.mtof( 45 + Math.random2(0,3) * 12 + hi[Math.random2(0,hi.size()-1)] ) => s.freq;
// PITCH -> tempo: ~40 ms (fast) up to ~280 ms (slow)
40.0 + (1.0 - speedA) * 240.0 => float period;
period::ms => now;
}Added
-
ChucK engine (
ENGINE=chuck) brought up and running on hardware.make engine-chuckruns a full ChucK VM behindIEngine: the engine creates aChucKinstance, compiles a built-in.ckprogram (aSawOsc -> LPF -> dacdrone readingspeedA/mixA/sizeAglobals), runs the synthesis VM in the audio callback, and applies live panel input through ChucK's global-variable queue (setGlobalFloat). Like Csound it is aBOOT_QSPIbuild (libchuck's ~1.1 MB of code can't fit SRAM, so it executes from QSPI flash) with ChucK's STL/heap-heavy allocations routed to a free-capable SDRAM pool (the platform heap stays in SRAM). The static library is the WebChucK source subset + feature defines (no MIDI/network/threads/dlopen), cross-built from upstreamccrma/chuck(pinnedchuck-1.5.5.8) byscripts/fetch_chuck.shwith a small shim sysroot standing in for the POSIX/libsndfile userland ChucK assumes. Verified end-to-end on a bare Daisy Pod (audible drone + working knobs; confirmed over SWD running inChuck_UGen::system_tick/Chuck_VM_Shreduler::advance/Chuck_Globals_Manager::handle_global_queue_messages). The full bring-up - four independent root causes found by attaching a debugger, and the SWD flash/inspect tooling - is indocs/dev/chuck-pod-poc.md; roadmap indocs/dev/chuck-impl.md. -
ChucK SD
.ckpatch bank + Alt+PITCH live switching, hardware-verified on the spotykach. A card ofchuck/0.ck..7.ckprograms turns the engine into a runtime-loadable synth: at boot it auto-loads the lowest-numbered slot (the built-in drone if the card has none); hold Alt and turn PITCH to scroll a per-patch ring selector and release to switch live; anything missing/empty/uncompilable falls back to the built-in so the unit always sounds. The live swap runs on one persistent ChucK VM that is never torn down: a swap stops the current patch's shreds (removeAllShreds()+ a one-framerun()flush, so the old UGens detach fromdacbefore the next patch sporks), then either re-sporks the target patch's cached compiledChuck_VM_Codeor, on the patch's first visit, compiles it once and pins the bytecode. This is the fix for a cumulative patch-swap memory leak: ChucK 1.5.5.8 never frees its built-in type system on VM teardown (Chuck_Env::cleanup()frees only the base types behind a// TODO;~Chuck_Contextkeeps its namespace on success), so every tear-down-and-reload strategy leaked — recompile-per-swap,CK_MSG_CLEARVMreset, and full VM destroy+recreate (the worst: it re-leaked the entire multi-MB type system every swap until the SDRAM pool exhausted and previously-fine patches began overrunning). With one VM + a compile-once cache, total compiles are bounded by the number of distinct patches, so memory rises once per patch then stays flat across unlimited cycling — confirmed on the cased unit by ear and by a new on-panel SDRAM-pool meter (METER=1build, ring B = live pool usage; ring A = CPU load). A always-on CPU-overrun safeguard mutes a patch whoserun()can't keep up (solid-red rings) so the controls stay alive and you can Alt+PITCH away. The patch bank scan/quantizer is host-tested (make -C host test-chuck-patch). User doc:docs/engines/chuck.md; the full root-cause + fix writeup and the roadmap are indocs/dev/chuck-impl.md. (Sound-file UGens —SndBuf/WvIn/WvOut— remain unavailable; a planned WAV-over-FatFs bridge forSndBufis scoped as M6 in the dev doc.) -
More
csoundexample orchestras (examples/csound/3..6.csd). Four new ready-to-copy patches exercising a broader slice of the core opcode set than the original drone / super-saw / poly-lead trio:3.csda resonant "acid" voice (a self-oscillatingmoogladderladder filter with anlfo-swept cutoff),4.csda stereo reverb pad (drone + MIDI through a shared bus intoreverbsc, with atanh-limited dry/wet output),5.csda dub echo (a feedback delay line viadelayr/delayw+ a movabledeltapitap), and6.csda self-playing generative line (randomhsample-and-hold picking semitones on eachmetrotick). All follow the established conventions (thespeedA/sizeA/… control channels,instr 1+instr MidiNote, the global-bus + limiter pattern for the effects) and use only core, table-less opcodes; all seven (0–6) pass desktopcsound --syntax-check-only. (examples/csound/README.md)
Changed
-
The
dfilterengine has been renamed tofilter. Breaking for build invocations and artifact names so update any scripts or muscle memory:was now make ENGINE=dfiltermake ENGINE=filtermake engine-dfiltermake engine-filtermake -C host test-dfiltermake -C host test-filtersk-dfilter-<version>.binsk-filter-<version>.binThe engine is functionally identical — it is still the generated-Faust parallel (DoubleMono) dual-deck demo (one independent resonant low-pass per channel); only the short name changed. Everything that referenced it was updated: the
src/engine/filter/tree (FilterEngine/SPK_ENGINE_FILTER/ thefx_filterFaust namespace), theMakefile/CMakeLists.txt/engine_select.hbuild blocks, the host test,scripts/build_release.py, the control diagram, and the docs (docs/engines/filter.md). Previously releasedsk-dfilter-*.binbinaries (underdist/) keep their original names — they record what actually shipped.
Fixed
- ChucK no longer aborts/HardFaults at boot - four root causes (the engine was previously linking but dying on hardware with a dark LED). None were ChucK-on-Cortex-M incompatibilities; the platform, bootloader, linker script, and SDRAM were always fine. (1)
abort()incompileCode()---specs=nano.specsships libstdc++ without exceptions, so everystd::__throw_*is a barebl abort; ChucK stringifies float literals viastd::to_string->vsnprintf("%f"), which without float-printf returns a negative length ->std::string(buf, buf-1)->__throw_length_error->abort. Fixed by linking-u _printf_float(added to theENGINE=chuckbranch; was already in the csound branch). (2) HardFault right after init - the VM was neverstart()ed, soChucK::globals()returnedNULLuntilrun()lazily auto-started the VM inside the audio ISR, racing the main loop. Fixed by calling_ck->start()ininit()(single-threaded, before audio) and null-guardingglobals(). (3) Imprecise BusFault in the SDRAM allocator - the pool (CsoundPool, shared by both QSPI engines) is not reentrant, andsetGlobalFloat'snewon the main loop racedck->run()'s allocations in the audio ISR, corrupting the free list. Fixed with a PRIMASK critical section around every pool op (chuck_alloc.cpp). (4) Dead/noisy knobs - the bare Pod harness calledset_paramin an unthrottled loop, flooding ChucK's 16384-slot global queue until the VM choked draining it (~100% CPU, main loop starved); reading every block then exposed ADC jitter as zipper noise. Fixed (Pod harness only) by reading knobs once per audio block in the callback with a deadband + one-pole smooth; the spotykach UI is already event-driven (PotMoved->_apply), so it needs no equivalent. Debugging used new in-firmware crash capture (overrides ofabort/__assert_funcplusg_chuck_init_stage/g_chuck_init_errorbuffers, readable over SWD) and apod/daisy_qspi.cfgOpenOCD config that flashes (stmqspiIS25LP064A) and attaches over ST-Link without DFU. All four fixes are in the spotykachENGINE=chuckfirmware, now hardware-verified on the cased unit (it boots, runs the VM, and makes sound). (docs/dev/chuck-pod-poc.md)
Flashing an sk-engines firmware (0.5.0)
Each .bin here is a complete firmware for one engine. Flash exactly one at a time.
Prerequisite
These app binaries are not standalone: they run under the spotykach bootloader, which must
already be installed on the device. Installing the bootloader is a separate, device-level
procedure not covered here.
Step 1: enter bootloader mode
Both methods below need the device in its bootloader (DFU) mode first: hold Reset ~3s until
the bottom pad LEDs breathe white.
Step 2, option A: Daisy Web Programmer (easiest)
Needs a WebUSB browser - Chrome or Edge (Firefox and Safari will not work).
- With the device in bootloader mode, open https://flash.daisy.audio/
- On the "File Upload" tab, choose your engine binary (sk--0.5.0.bin).
- Click FLASH.
Step 2, option B: dfu-util (command line)
dfu-util -a 0 -s 0x90040000:leave -D sk-<engine>-0.5.0.bin -d ,0483:df11
Verify
- Confirm the download is intact:
shasum -a 256 -c SHA256SUMS - Confirm what a binary is:
arm-none-eabi-strings <file>.bin | grep '^spotykach '
(prints e.g.spotykach 0.5.0 engine=reverb)
Note for the csound and chuck engines
sk-csound-*.bin and sk-chuck-*.bin are QSPI apps: unlike the other engines they execute in place from QSPI flash
(their language runtimes are too big for SRAM), so they are also larger downloads and take longer to
flash. Same address and steps as above, with two caveats:
- They need a bootloader that runs QSPI apps (the spotykach bootloader does); a SRAM-only
bootloader cannot run them. - With
dfu-util, the:leavestep may print a harmlessError 74/ "get_status" message at the
end - the write has already succeeded. Ignore it (the Web Programmer does not show this).