Skip to content

sk-engines 0.5.0

Choose a tag to compare

@shakfu shakfu released this 22 Jun 07:44

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-chuck runs a full ChucK VM behind IEngine: the engine creates a ChucK instance, compiles a built-in .ck program (a SawOsc -> LPF -> dac drone reading speedA/mixA/sizeA globals), runs the synthesis VM in the audio callback, and applies live panel input through ChucK's global-variable queue (setGlobalFloat). Like Csound it is a BOOT_QSPI build (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 upstream ccrma/chuck (pinned chuck-1.5.5.8) by scripts/fetch_chuck.sh with 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 in Chuck_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 in docs/dev/chuck-pod-poc.md; roadmap in docs/dev/chuck-impl.md.

  • ChucK SD .ck patch bank + Alt+PITCH live switching, hardware-verified on the spotykach. A card of chuck/0.ck..7.ck programs 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-frame run() flush, so the old UGens detach from dac before the next patch sporks), then either re-sporks the target patch's cached compiled Chuck_VM_Code or, 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_Context keeps its namespace on success), so every tear-down-and-reload strategy leaked — recompile-per-swap, CK_MSG_CLEARVM reset, 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=1 build, ring B = live pool usage; ring A = CPU load). A always-on CPU-overrun safeguard mutes a patch whose run() 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 in docs/dev/chuck-impl.md. (Sound-file UGens — SndBuf/WvIn/WvOut — remain unavailable; a planned WAV-over-FatFs bridge for SndBuf is scoped as M6 in the dev doc.)

  • More csound example 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.csd a resonant "acid" voice (a self-oscillating moogladder ladder filter with an lfo-swept cutoff), 4.csd a stereo reverb pad (drone + MIDI through a shared bus into reverbsc, with a tanh-limited dry/wet output), 5.csd a dub echo (a feedback delay line via delayr/delayw + a movable deltapi tap), and 6.csd a self-playing generative line (randomh sample-and-hold picking semitones on each metro tick). All follow the established conventions (the speedA/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 desktop csound --syntax-check-only. (examples/csound/README.md)

Changed

  • The dfilter engine has been renamed to filter. Breaking for build invocations and artifact names so update any scripts or muscle memory:

    was now
    make ENGINE=dfilter make ENGINE=filter
    make engine-dfilter make engine-filter
    make -C host test-dfilter make -C host test-filter
    sk-dfilter-<version>.bin sk-filter-<version>.bin

    The 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 / the fx_filter Faust namespace), the Makefile / CMakeLists.txt / engine_select.h build blocks, the host test, scripts/build_release.py, the control diagram, and the docs (docs/engines/filter.md). Previously released sk-dfilter-*.bin binaries (under dist/) 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() in compileCode() - --specs=nano.specs ships libstdc++ without exceptions, so every std::__throw_* is a bare bl abort; ChucK stringifies float literals via std::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 the ENGINE=chuck branch; was already in the csound branch). (2) HardFault right after init - the VM was never start()ed, so ChucK::globals() returned NULL until run() lazily auto-started the VM inside the audio ISR, racing the main loop. Fixed by calling _ck->start() in init() (single-threaded, before audio) and null-guarding globals(). (3) Imprecise BusFault in the SDRAM allocator - the pool (CsoundPool, shared by both QSPI engines) is not reentrant, and setGlobalFloat's new on the main loop raced ck->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 called set_param in 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 of abort/__assert_func plus g_chuck_init_stage/g_chuck_init_error buffers, readable over SWD) and a pod/daisy_qspi.cfg OpenOCD config that flashes (stmqspi IS25LP064A) and attaches over ST-Link without DFU. All four fixes are in the spotykach ENGINE=chuck firmware, 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).

  1. With the device in bootloader mode, open https://flash.daisy.audio/
  2. On the "File Upload" tab, choose your engine binary (sk--0.5.0.bin).
  3. 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 :leave step may print a harmless Error 74 / "get_status" message at the
    end - the write has already succeeded. Ignore it (the Web Programmer does not show this).