DJFX / slip-style performance looper
A separate Daisy Pod firmware for short, direct loops. It preserves stereo, repeats one slice, and returns to the present live input on release. The original five-bank GranularTimestretchEngine remains a separate build.
Controls
- Button 1: hold to loop; release to return to live input.
- Knob 1: loop length, 12–300 ms in manual mode.
- Knob 2: −1× reverse at the left, stop at the center, +1× forward at the right. Speed changes pitch like tape; this is not independent pitch/time stretching.
- Button 2: capture fresh audio while Button 1 is held. It acts immediately.
- Encoder click: toggle USB MIDI tempo sync. Defaults off.
- Encoder clockwise: enable 1/16-note trigger quantization.
- Encoder counterclockwise: disable trigger quantization. Defaults off.
Tempo sync alone does not delay press/release. Quantization only operates when explicitly enabled, tempo sync is enabled, and clock is arriving. Releasing before a queued press executes cancels that press. Disabling sync/quantization or losing clock resolves the latest pending gesture.
Synced knob divisions, left to right: 1/64, 1/32, 1/16, 1/8, 1/4, 1/2, and one whole note (4/4 notation). Length caps at 2 seconds. With no clock, sync uses the last tempo, initially 120 BPM. Clock timeout is 500 ms. Supported tempo is 40–240 BPM. MIDI Start resets the quantization counter; MIDI Stop does not stop the external live source.
LEDs
LED 1: white while held; magenta for a pending quantized gesture; otherwise off.
LED 2: dim green in manual mode; cyan for clocked tempo sync with immediate gestures; purple for clocked sync with quantized gestures; amber for sync without clock. Red overrides these colors if measured callback load has reached 90% since startup. This is an overload indicator, not proof that lower readings are safe under all loads.
Capture and playback
Forward/zero-speed engagement anchors the recording to the processed press. At +1× its first pass is sample-identical to the input: the audible repeat occurs at the selected loop end. At slower speeds, processing is audible during that first pass. Recording continues into the protected capture buffer for up to two seconds so lengthening can reveal more of the same region. Before that region has arrived, effective length is limited to the recorded portion. Length edits apply on the next audio frame; they do not wait for the original slice to finish.
Reverse engagement uses the newest available history, ending immediately before the processed press. Lengthening a history capture exposes earlier audio while keeping the current read position where possible. Forward captures remain anchored at their beginning when resized. Changing direction during a hold keeps the same capture. If reverse is engaged before any history exists, the engine uses the growing capture as a startup fallback.
Four seconds of live history keep recording independently of the protected two-second capture. A long hold never parks the history writer, and a new reverse capture gets recent live audio. History snapshots copy at most eight stereo frames per processed frame; reads use the protected history until their frame has been copied. There is no large copy in the button handler.
A single read position determines the repeat period. There are no overlapping repeating grains, long Hann envelopes, automatic gain compensation, or wet-path saturation. Cubic interpolation preserves fractional speeds; it is not an anti-alias filter and may overshoot full-scale inputs. Keep input headroom.
Engagement and release use approximately 2 ms linear transitions. Wraps, recaptures and out-of-range size edits use a 2 ms blend from the last output sample into the new read position. This short held-tail treatment preserves loop endpoints and period, but can color transients near a boundary; listening will determine whether a more elaborate boundary crossfade is worthwhile. Speed uses a 0.5 ms one-pole time constant. Center stop fades to silence rather than emitting a stationary DC sample. Release preserves the sounding speed/direction throughout its fade.
This is a documented hybrid performance design, not a sample-exact Roland emulator. Roland documents DJFX length and signed speed but not the precise capture boundary. The reverse-history fallback is our design choice for live input. Unlike a deck playing a prerecorded track, the Pod cannot read future input.
Build and tests
From the repository root:
make -C pod/DjfxLooper/tests test
make -C pod/DjfxLooper -j4
The internal-flash build is build/DjfxLooper.bin, for the STM32 ROM bootloader only. The connected Pod uses the Daisy external-flash bootloader; build and flash its application without overwriting that bootloader:
make -C pod/DjfxLooper APP_TYPE=BOOT_QSPI BUILD_DIR=build_qspi -j4
make -C pod/DjfxLooper APP_TYPE=BOOT_QSPI BUILD_DIR=build_qspi program-dfu
Use separate build directories for the two memory layouts. The QSPI version was flashed on 2026-09-18; see RELEASE_CHECK.md for verification limits.
The audio callback uses four frames at 48 kHz. Buffers use 2,304,000 bytes of SDRAM. MIDI processing is bounded to eight messages per callback. All engine and control state belongs to the audio callback; the foreground loop idles. Button edges use libDaisy's debounce, which adds roughly 7–8 ms before the engine sees the physical gesture. The 2 ms DSP transition is additional; it is not an end-to-end latency claim.
Callback CPU measurement is built in. Inspect djfxCpuAverage and djfxCpuMaximum with a debugger under reverse recapture, knob movement and USB clock load. Host test time does not estimate hardware callback headroom.
Reuse
Compile engine/LooperEngine.cpp and include engine/DjfxLooper.h. Supply disjoint stereo float buffers; history capacity must be at least twice capture capacity, with at least four capture frames. Capacities count stereo frames; processBlock size counts interleaved floats. Buffers must remain alive. Initialization does not clear memory: valid-frame bounds prevent reads of unwritten data. Invalid initialization preserves the previous working engine. Supported sample rates are 8–192 kHz.
Call all methods from the audio thread, or synchronize externally. processBlock supports in-place buffers and ignores an unmatched final sample. No allocation occurs inside the engine. Length setters accept 12–2,000 ms, capped to supplied capacity; speed clamps to −1…+1. Non-finite control updates are ignored.
setRecaptureOnLengthChange(true) optionally implements a Slip Roll-style policy where a changed length also captures fresh input. Default is false. Use this with discrete lengths; continuously varying knob or BPM values would repeatedly restart recording. The minimal Pod build uses explicit Button 2 recapture instead.
Physical listening checks still required
Compare against the five-bank firmware using the same source and input level: engage on a drum hit at +1× and 0.5×, reverse from bypass, shorten/lengthen immediately after pressing, change direction through stop, release off the beat, hold longer than four seconds, then recapture. Check both stereo channels, transient softening, clicks, repeat timing and callback headroom. Verify USB MIDI at several tempos and with clock loss during a pending gesture.
Amen Audio release — 2026-09-18
This is the exact QSPI firmware installed and listening-approved by Gustav on the Daisy Pod. It requires the Daisy bootloader and preserves internal flash. The download is 88,080 bytes. SHA-256: db2ff85519b6e146aef4227f4b4bb6d7f04c4ad9451b2f343a155a58c3289ea3.
The native USB transfer succeeded and the application restarted. Full flash read-back did not match on the tested Daisy bootloader; its upload implementation is suspected. The browser installer requires matching read-back and therefore may stop with a verification error on this bootloader. It will not label that result verified or restart automatically. Use the native Daisy/dfu-util installation workflow when browser verification is incompatible. Do not treat an arbitrary transfer error as a successful installation. Callback headroom and browser installation remain unqualified.
Browser stops at 92%
At this stage all USB writes have completed, but read-back did not match. Some Daisy bootloaders return incorrect read-back data; this does not prove the stored image is correct. The flasher now offers Start firmware without verification for this QSPI-only case. Use that button, or press RESET on the Pod, then check the sound. If it does not run, re-enter the Daisy bootloader and reinstall. Incomplete writes and USB transfer errors do not offer this recovery action.