Skip to content

v0.8.0: storage and PRST presets (#49, #50) - #53

Merged
Worldwave merged 12 commits into
mainfrom
issue50-prst
Oct 9, 2026
Merged

Worldwave merged 12 commits into
mainfrom
issue50-prst

Conversation

@Worldwave

Copy link
Copy Markdown
Owner

v0.8.0: storage and PRST presets

Storage (#49, done) and PRST (#50) in one PR, as agreed when #51 was closed. For #50 rather than Closes: some of its hardware checks are still open (below), and the manual note about unsaved sessions is still to come.

Storage (#49)

  • The eMMC becomes a FAT32 volume (one partition, label SP-1). In STANDBY with a computer attached it is a USB drive; turning ON takes it back after the transfer in flight, and the power-on hold waits while the computer is reading or writing.
  • Only wakes-sp1-fresh.bin formats. A normal image contains no formatter (check_image.py enforces it). CI builds wakes-sp1, wakes-sp1-fresh and the fallback, and checks each image's config against its name.

PRST (#50)

  • Four slots: WAKES/PRST/1 … 4, one .prst (ini text) per folder, with any file name. If a folder holds more than one, the first in alphabetical order is used. WAKES/PRST/SLOT holds the last-loaded slot.
  • Load at ON: all four slots are read into RAM before audio starts, waiting at most 10 s with the 30 s backstop live. A failed card turns PRST off for that session and keeps the patch in memory. Wakes always comes up in PLAITS with the clock stopped.
  • Save at a normal power-off only: the shutdown animation completes, then UI locked, LEDs off, audio stopped, save (limit 4 s), then OFF or STANDBY. The 30 s backstop never saves. The save writes a temporary file and renames it over the slot's file. It verifies the slot's folder first and creates it if missing.
  • What a slot holds: everything except VOL, the clock's run/stop, the module and page on show, and Marbles' seed and DEJA VU loop.
  • Field check: a corrupt value (NaN, infinity, out of range, an unknown word, a destination the output can't take, a quantizer together with V/Oct) loads that parameter's ROTC default, and the rest of the file still loads. An unknown format version loads the whole slot from defaults. The build fails if the field list changes without a new format version (tools/gen_prst.py).
  • SHFT + PLAY:
    • A tap shows the current slot: its face LED ramps at 10 Hz for 1.6 s, and the other face LEDs are black.
    • A tap while it shows moves to the next slot (4 → 1), loaded at once from RAM.
    • Letting go of SHFT leaves the browser.
    • Held 3 s from a press that didn't change slot is ROTC, with a new animation: glyph, black, rise, then the Unpatch animation.
    • With PRST off, the glyph's 1.6 s becomes a slow fade.
  • ROTC on Marbles now leaves t2 → TRIG and X2 → V/Oct, and turns the PLAITS quantizer off as routing V/Oct does. The default slots use this patch.
  • Engines not on engines.csv load from a preset and play. Their flash is all four face LEDs ramping 70 % → 0 at 15.6 Hz. T3 goes to the first listed engine and T2 to the last; MIDI MODEL counts from list position 1.
  • wakes-sp1-fresh writes the four default slots and SLOT = 1 after it formats.
  • File names: Windows' rules, but only the US DOS character set (code page 437). A name outside it reads as an empty slot.

Cost

  • No audio-thread work. Loading and saving run in the storage thread (below audio) while audio is stopped; a slot change copies from RAM in the main thread.
  • Image: 459 728 B (v0.7.2: 427 772; storage alone was 449 308). RAM 204 724 B. 0 warnings, links at 0x20000, check_image.py passes. Fresh 464 612 B, fallback 86 640 B.

Tests

  • prsttest (new, in CI): round trips; every kind of corrupt field one at a time; hand-edited text; writer bounds; ~23 000 cut-off, damaged or random files, all giving a safe patch; engines off the list.
  • uitest and miditest were updated for the new ROTC routing and for MODEL from an off-list engine.
  • All ten suites pass. Negative controls fail where they should.
  • The SHFT + PLAY state machine was checked on the host by running the real main.c block tick by tick.

On hardware (Adara)

  • Done: storage and drive mode (FEATURE: eMMC storage and drive mode #49); PRST load and save including the backstop; the browser and ROTC (smoke test).
  • Still open: a slot with a broken format version; engines off the list; the save recreating a deleted folder; letting go of SHFT in the browser; ROTC with PRST off.

🤖 Generated with Claude Code

Worldwave and others added 11 commits October 7, 2026 17:54
Storage for PRST (presets), off by default: the release image is
unchanged at 427 772 B.

- CONFIG_SP1_STORAGE: the eMMC driver (sp1_emmc.c, 1-bit, bit-banged;
  protocol from sp1-tape-looper and SP-1-dev), FatFs through Zephyr's
  fs layer, and a storage thread at K_PRIO_PREEMPT(5) -- below audio,
  main and USB, so it only uses idle CPU. It never feeds the watchdog.
  Commands sent: 0 1 2 3 7 8 13 16 17 24 only; nothing writes EXT_CSD,
  erases or partitions. Reads are CRC16-checked; writes need the card's
  "accepted" token and are retried.
- CONFIG_SP1_FRESH (wakes-sp1-fresh): formats the card on its first
  ON -- one MBR partition, FAT32 at block 8192 (the 4 MB erase group),
  32 KB clusters, 2 FATs, block 0 cleared first and the MBR written
  last -- and stamps WAKES/FRESH.ID with its build ID; it formats again
  only when that stamp is missing or names another build (Adara: option
  A). The format holds ON entry with a progress bar on T1-T4, then two
  0-100 % flickers and a quick fade into the page. Only the 30 s "••"
  backstop powers off meanwhile; it runs every tick, first and
  unconditionally, and stops the format via sp1_quiesce_peripherals.
- Only the fresh image can format, by construction: a normal image has
  no f_mkfs, every mount is NO_FORMAT, our Kconfig defaults
  FS_FATFS_MOUNT_MKFS to n (Zephyr's y would format any unmountable
  card), and check_image.py fails an image that breaks either rule.
- CONFIG_SP1_STORAGE_TEST: the card report ("EMMC" lines) and a file
  test ("STORE" lines) on every ON.
- west.yml: the fatfs module (Adara). FatFs R0.16 in NOTICE and
  LICENSES/FatFs.txt.

On hardware (logs/sp1-20261007-173828/-174058/-174227): format 11.0 s,
FAT32 as above; second ON did not format; a normal image mounted the
card with no format and the ON counter went 1, 2, 3; 64 KB written,
read back identical; 0 retries, CRC errors, rejects or timeouts; write
busy <= 7.1 ms; storage stack <= 1168 of 4096 B; SYSTEM_OFF reached.
The format's write estimate now counts the WAKES folder's cluster
(2035 writes measured against 1966 estimated).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The tape-looper's method, with a slow path behind every fast step:
- 512-byte payloads and their CRC16 ride SPIM3 + EasyDMA at 16 MHz
  (inside the card's 26 MHz default-speed limit); the start-bit hunt,
  the CRC-status token, the busy and every command stay bit-banged.
  SPIM3 is otherwise unused in Wakes.
- Commands at register speed once the card is identified; the first
  four tries are fast, the rest at the 1 us identification clock.
- CMD18 / CMD25 bursts with CMD12 (R1b waited after writes). A burst
  that fails part-way finishes block by block; a block is retried
  bit-banged, then at 1 us. The device's last block never ends a CMD18.
- Table-driven CRC16, checked against "123456789" = 0x31C3 at init.
- Anomaly 195 workaround on power-down; anomaly 198 (SPIM3 TX) is caught
  by the card's own CRC check and retried.
- spim_xfer re-reads EVENTS_END after its 5 ms bound: the storage
  thread is the lowest priority there is, and a preemption between the
  two reads made finished transfers look timed out (38 in 512 KB on
  hardware, each dropping a burst to the slow path).
- Timeouts are counted by cause (data / busy / spim); the report
  counts burst blocks and fallbacks.
- The storage test moves 512 KB in 16 KB chunks (32-block bursts).

On hardware (logs/sp1-20261007-194750.log), audio running at ~62 %:
512 KB written at 294-297 KB/s and read at 315-321 KB/s, identical;
2048 blocks in bursts, 0 fallbacks; retries, CRC errors, rejects and
timeouts all 0; write busy <= 6.3 ms. Bit-banged was 25/28 KB/s.
Audio fail 0 / rst 0. Release image unchanged (427 772 B).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Drive mode (CONFIG_SP1_DRIVE, off by default):
- In STANDBY with a host attached (not a charger) the eMMC is a USB
  drive, automatically; turning ON takes it back after the transfer
  in flight. The mass-storage interface is always in the descriptor and
  reads "no medium" while ON, so STANDBY <-> ON never re-enumerates.
  It is registered last, so CDC/MIDI/audio interface numbers do not
  move; bcdDevice 0x0500 with the drive (SP1_USB_BCD_DEVICE), 0x0403
  without, as every release so far.
- Zephyr's usbd_msc thread starts cooperative, above audio: moved to
  PREEMPT(main + 1).
- Two disks over one card ("EMMC" for Wakes, "DRIVE" for the host) and
  an owner under a lock every transfer holds. A card with no
  filesystem is not offered (a host would offer to format it).
- STANDBY: the T2/T3 plugged/charging lights are gone in every build
  (the play-row bar still breathes while charging); host reads and
  writes chase T1, T3, T2, T4 from 50 %.
- Measured: with a host attached and ON, usbd_msc 0.0 % CPU.

Burst READS are closed-ended (CMD23 + CMD18):
- Open-ended CMD18 stopped by CMD12 made this card return, as the
  SECOND block of a later burst, its internal buffer with a valid CRC.
  Reproduced on the device: 4 ON entries x 3 rounds of FAT scan + 512 KB
  read, open-ended 5 bad blocks (all at position 1), closed-ended 0.
  Writes were never affected (~10 000 verified, 0 wrong) and stay
  CMD25 ... CMD12.

Formatted check (Adara): every ON looks for a FAT volume. Without one
Wakes boots with PRST off ("STORE PRST off"); sp1_store_volume() is the
check every PRST feature and key combo must pass, and UNKNOWN counts as
no. Only wakes-sp1-fresh formats.

Long file names on (presets will be "<name>.prst", ini inside); the
fresh format labels the volume "SP-1", which is what a host shows.

Diagnostics, test builds only: CONFIG_SP1_EMMC_VERIFY (read every write
back), host-activity counters per STANDBY visit, the mismatch report,
and the open- vs closed-ended reproduction in the storage test.

Release image 427 844 B (+72: bcdDevice set explicitly, STANDBY).
Test image m6-8 (drive + diagnostics) 459 312 B; 0 warnings,
check_image PASS. The fix image is not yet confirmed on hardware.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Drive mode: in plugged-in STANDBY, every block the computer reads or
  writes restarts the power-on fill, so ON needs 1 s without a transfer
  with "••" held. No time limit (Adara). Power-off and the 30 s backstop
  are untouched.
- Closed-ended burst reads are the only read path: the open-ended switch,
  the stress comparison and the host-activity counters are removed.
  SP1_STORAGE_TEST and SP1_EMMC_VERIFY stay, off by default.
- SP1_STORAGE and SP1_DRIVE default on in Plaits builds: the plain build
  is the release wakes-sp1.bin, which never formats.
- CI builds wakes-sp1, wakes-sp1-fresh (renamed, never a plain-named
  copy) and the fallback recovery image (no storage), and checks each
  image's .config against its name. Releases attach the fresh image and
  explain what it erases.
- Manual updated (Adara).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- sp1_prst.{c,h}: a PRST slot as ini text -- write, read, and a field
  check. A field holding an impossible value (NaN, infinity, out of
  range, an exponent, an unknown token, a destination off its output's
  ring, a scale not included) loads THAT parameter's ROTC default and the
  rest of the file still loads; a bad BASE fader takes the neutral value
  of the engine that was loaded. Unknown engine -> slot 1. A missing or
  unknown format version loads the whole slot from defaults.
- tools/gen_prst.py: the one field list (46 fields, choice tokens mapped
  to enum names) and the format versions. The build fails if the list
  changes without a new version, and prints the line to add.
- The rips are now "rip patch, then put" in both UIs, so the ROTC values
  have one source; PRST's defaults come from it. sp1_pui/mui_get/put
  sanitise. main.c's output / burst / drive defaults are SP1_PRST_DEF_*.
- prsttest (host + CI): round trips, every corrupt field, hand-edited
  text, writer bounds, ~23 000 truncated / damaged / random files.

Not called yet: nothing user-visible changes. Image 450 028 B.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…50)

- Load: the ON job reads WAKES/PRST/SLOT and the four slot folders (the
  first .prst of each, case-insensitive) into RAM after the volume check.
  A folder with no .prst is that slot at the ROTC defaults; no card, no
  filesystem or a read error turns PRST off for the session. main waits
  for it before audio starts, at most 10 s, with the 30 s backstop live,
  then applies the current slot (Wakes comes up in PLAITS). With PRST off
  the patch in memory is kept.
- Save: only when the ordinary shutdown animation completes
  (sp1_power_set_save_hook; never from the 30 s backstop): LEDs off,
  audio stopped, the slot written to SAVE.TMP then renamed over the
  loaded file (or N.prst), then SLOT. Limit 4 s, watchdog fed.
- Field check: a quantizer together with a V/Oct route -> quantizer off
  (the M4b interlock).
- ROTC on MARBLES now routes t2 -> TRIG and X2 -> V/Oct (Adara); the rip
  calls voct_took_over(). Docs and both test suites updated.
- wakes-sp1-fresh writes WAKES/PRST/1..4/N.prst (the ROTC patch) and
  SLOT = 1 after its format, before the stamp.

Image 458 060 B (fresh 462 920 B), 0 warnings. Not yet tested on
hardware.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- PLAY pressed with "••" held shows the current slot: its face LED
  (T1..T4) ramps 100 % -> 0 at 10 Hz for 1.6 s, the other three face
  LEDs black, the play row as normal; then the page fades back.
- PLAY pressed again while it shows, in the same "••" hold: the next slot
  (4 -> 1), loaded at once from RAM -- the old slot's unsaved changes
  dropped, faders picked up, module and page kept. Saved at shutdown.
- PLAY held from a press that did not change slot: ROTC, still 3 s --
  glyph 1.6 s, black 0.25 s, rise to full 0.4 s, the Unpatch animation
  0.75 s, then the wipe (BUILD_ASSERT on the sum). Let go past the glyph:
  cancelled, back to the SHIFT screen. No ROTC after a slot change until
  "••" is released.
- Every PRST part checks prst_available(): the volume is OK and this ON
  session loaded a slot.
- docs/UI-PAGES.md, docs/UI-SPEC.md updated.

Smoke-tested on hardware (Adara). Image 458 664 B, fresh 463 524 B.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With no PRST (no filesystem, or no slot loaded this session) there is no
slot glyph, so the first 1.6 s of a "••" + PLAY hold fade the face LEDs
slowly from the SHIFT screen to black (Adara); then the same black, rise
and Unpatch animation, and the wipe at 3 s. Letting go during the fade
cancels back to the SHIFT screen.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…he browser (#50)

- Engines not in config/engines.csv: every build contains all of Plaits'
  engines, so a PRST slot may name one that is not listed and it plays.
  A selection is a list position or SP1_PUI_OFFLIST + its Plaits index;
  engine facts come from a new all-engine table (gen_engines.py). No
  glyph of its own: its flash is all four face LEDs ramping 70 % -> 0 every
  64 ms for 0.7 s. T3 from it -> the first listed engine, T2 -> the last;
  MIDI's MODEL CC counts from list position 1. A name no engine of the
  build has -> list position 1. Fixes sp1_pui_slot_leds() reading past
  the table for a selection that is not a list position.
- The save at OFF verifies WAKES, PRST and the slot's folder: a folder is
  used, a missing one is created (logged), a FILE in its place fails the
  save and is left alone.
- Letting go of SHFT leaves the PRST browser at once: the glyph ends, PLAY
  no longer changes slot, the page fades back in.
- Tests: prsttest section 9 and miditest's MODEL-from-off-list; all ten
  host suites pass.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Storage (#49) and PRST presets (#50): the eMMC as a FAT32 volume and a USB
drive in STANDBY; four preset slots in WAKES/PRST/1..4 loaded at ON and
saved at a normal power-off; the slot browser and the new ROTC animation
on SHFT + PLAY. A minor version: ROTC on Marbles now leaves t2 -> TRIG and
X2 -> V/Oct, and first-time users need wakes-sp1-fresh.bin, which formats
the eMMC. Manual updated (Adara).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Worldwave Worldwave linked an issue Oct 9, 2026 that may be closed by this pull request
10 tasks
PRST contents (no DJVU), the WAKES/PRST folders and file names, 30 s
emergency shutdowns skip saving, backups, fresh formats the card, ENG and
SCL buttons the right way round, ROTC's routing on Marbles.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Worldwave Worldwave added this to the M6 milestone Oct 9, 2026
@Worldwave
Worldwave merged commit da0a787 into main Oct 9, 2026
4 checks passed
@Worldwave
Worldwave deleted the issue50-prst branch October 9, 2026 03:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

FEATURE: PRST presets

1 participant