diff --git a/docs/slp-workshop/SPEC.md b/docs/slp-workshop/SPEC.md index a7f6a59..8260917 100644 --- a/docs/slp-workshop/SPEC.md +++ b/docs/slp-workshop/SPEC.md @@ -1,14 +1,25 @@ # SLP-Workshop — output modes, gate sequences & config UX -**Status:** DRAFT / spec-only (2026-06-28). The SLP-Workshop firmware (a fork of -`memlcelium`) is being built by another agent and is WIP. This document specs the -mode model, the Manifold-side gate-sequence configuration, and a proposed UI so -the firmware and the browser app stay aligned. Nothing here is implemented yet. +**Status:** DRAFT / spec-only (2026-06-28). The SLP-Workshop firmware exists as +the `slp_workshop` mode (Synth Library Portland workshop; reuses the MEMLCelium +engine + MLP shape verbatim, foregrounds the Jolt / OU-noise learning gestures — +branch `workshop/synth-fw-audit`). The **three output modes** below are a planned +evolution: compile-time *slices* of that mode. This document specs the mode model, +the Manifold-side gate-sequence configuration, and a proposed UI so the firmware +and the browser app stay aligned. The output-mode slicing + Manifold config are +NOT implemented yet. Companion docs: `docs/useq-celium/protocol.md` (the CV wire protocol), the manifold backends (`manifold/src/backends/`), the input layer (`manifold/src/inputs/`). +**Decisions locked (2026-06-28, operator):** (1) hardware keeps exactly 2 ratio +sequences (memlcelium verbatim); the browser may instantiate as many as the user +wants. (2) The 3 uSEQ gate-only jacks make gates *optional* — pure CV is valid; +if doing gates at all, use those 3 first, then convert CV jacks. (3) Split-net +input is per-input-channel engine routing (each pad/stick/CC tagged Continuous or +Rhythm). (4) Internal BPM clock now; external-MIDI-clock sync is a later nicety. + --- ## 1. Ground truth: what `memlcelium` actually is @@ -51,7 +62,8 @@ A **mode** is just *which streams are active* and *whether they share a network* Per gate-sequence track, each control tick (`nisps/engines/*` `ratio_seq_*`): -1. A shared **clock** advances a bar phasor (BPM-driven). +1. A shared **internal-BPM clock** advances a bar phasor. (External-MIDI-clock + sync is a desirable later addition, not a launch requirement — operator.) 2. `seq_phasor = (bar_phasor × phasor_mul + phase_off) mod 1`. 3. `ratio_seq_3(seq_phasor, ratios, pw=0.5)` → **boolean gate** (the 3 ratios carve the cycle into proportional segments; the phasor's segment + pulse-width @@ -64,6 +76,12 @@ So a gate is **clock + learned pattern**, not a threshold on a continuous value this is what makes "Rhythm" a distinct stream, and why it needs its own 7-params per track rather than one output per gate. +**Defaults = firmware parity** (pulse width fixed at 0.5; 2-level velocity). Two +deferred extensions, not blocking: a per-track **pulse-width** (the 8th param → +controllable gate length, matters for envelope vs trigger) and **continuous +velocity/accent** instead of the 127/64 two-level. Add later if the workshop +wants them. + > Where RatioSeq runs in the browser: when the active engine *is* the memlcelium / > SLP WASM engine, consume `pop_events()`. For CV/MIDI modes that don't run that > audio engine, a small **TS RatioSeq** fed by the Rhythm MLP's 7-params/track @@ -75,18 +93,24 @@ per track rather than one output per gate. ### Firmware (SLP-Workshop) — compile-time, one chosen at build -| Mode | Streams | Net | MLP output_size (≈) | +Always **exactly 2 ratio sequences** (memlcelium verbatim) — the firmware does not +vary sequence count. + +| Mode | Streams | Net | MLP output_size | |-----------------------|----------------------|-----|----------------------------| | **Continuous only** | Continuous | 1 | continuous params only | | **Continuous & Rhythm** (= memlcelium) | both, **shared** | 1 | 14 seq + 42 synth = 56 | -| **Rhythm only** | Rhythm | 1 | 7 × n_tracks (e.g. 14) | +| **Rhythm only** | Rhythm | 1 | 2 × 7 = 14 seq params | ### Browser (Manifold) — dynamic, switchable live -Same three **plus** a fourth: +Same three **plus** a fourth, and with **as many ratio sequences as the user wants** +(each gate sequence = its own independent 7-param track; the net reshapes to suit): - **Continuous & Rhythm (split nets)** — Continuous MLP + a separate Rhythm MLP. - Browser-only (two MLP heads; hardware uses one). + Browser-only (two MLP heads; hardware uses one). `output_size`: shared net = + `continuous + 7 × n_gates` (mind the 126-output WASM cap → ~16 gates max shared); + split net = the Rhythm net is sized independently, so it scales further. The browser mode is implied by the Outputs config (§4), not a separate picker: choosing continuous-count > 0 and gate-sequences = 0 ⇒ Continuous-only; @@ -113,16 +137,20 @@ Gate sequences: [ 2 ] each → a rhythm track → note on/off ``` **CV mode** — hardware is fixed (11 PWM/CV-capable + 3 digital/gate-only), so the -two counts are *linked*. One control: +two counts are *linked*. Gates are **optional** — pure CV (0 gates) is valid. One +control: ``` -uSEQ jacks CV 8 · Gate 6 -Gate sequences 3 ●───────────── 14 - └ 3 are free (digital pins); each extra converts a CV jack +uSEQ jacks CV 11 · Gate 0 +Gate sequences 0 ●───────────── 14 + └ first 3 are FREE (the gate-only digital jacks); the 4th+ convert a CV jack ``` -Rule: `gates ∈ [3, 14]`, `CV = 14 − gates` (so CV ∈ [0, 11]; gates=3 ⇒ CV=11). -"Add more gates by swapping CV outs to gate outs" = dragging this up past 3. +Rule: `gates ∈ [0, 14]`; `CV = gates ≤ 3 ? 11 : 14 − gates`. So the first 3 gate +sequences land on the dedicated gate-only jacks and cost no CV (gates 0→11 CV; +3→11 CV; 6→8 CV; 14→0 CV). "Add more gates by swapping CV outs to gate outs" only +kicks in past 3. (If you want gates at all, those 3 gate-only jacks are there to +use; if you don't, they sit idle and you keep all 11 CV.) > Wire-protocol impact: **none**. A CV jack acting as a gate just carries 0/full > (or the 2-level velocity) in its `u16` slot; the 3 dedicated gate bits stay the @@ -136,20 +164,25 @@ Rule: `gates ∈ [3, 14]`, `CV = 14 − gates` (so CV ∈ [0, 11]; gates=3 ⇒ C Rhythm network: ( Shared ) ( Separate ◀ default ) ``` -- **Separate** (default): the Rhythm stream gets its **own MLP**. The input is - then automatic, following the active input source: - - **XY pad (internal)** → a **second on-screen XY pad** appears (the Rhythm - pad); the first drives Continuous. - - **Gamepad** → the controller switches to **double-stick**: **left stick → - Continuous**, **right stick → Rhythm**. -- **Shared**: one MLP drives both streams; a **single** input source feeds it - (one pad / one stick). This is the hardware-parity hybrid. +- **Separate** (default): the Rhythm stream gets its **own MLP**, so each input + channel is **routed to one of the two engines** (Continuous or Rhythm). The + routing is automatic per source kind: + - **XY pad (internal)** → a **second on-screen XY pad** appears; pad 1 → + Continuous, pad 2 → Rhythm. + - **Gamepad** → **double-stick**: left stick → Continuous, right stick → Rhythm. + - **MIDI controller** → **per-CC routing**: each learned CC has a small + `Continuous | Rhythm` toggle, so the user assigns which knobs drive which + engine. (The pad/gamepad cases are just the pre-grouped 2-axis versions of + this same per-channel routing.) +- **Shared**: one MLP drives both streams; **all** input channels feed the single + net (one pad / one stick / all CCs). This is the hardware-parity hybrid. That is the whole decision surface: **two numbers + one toggle.** The second -pad / double-stick is a consequence shown as an explanatory line, not a separate -control. It reuses the existing input layer (`InputSource.axisCount()` already -supports gamepad single↔double-stick; "Separate" composes a second `xy-pad` -source or flips the gamepad to 4-axis). +pad / double-stick / per-CC tag is a *consequence* of Separate, surfaced inline, +not a separate mode control. It reuses the existing input layer +(`InputSource.axisCount()` already supports gamepad single↔double-stick; per-axis +labels exist via `axisLabels()`) — "Separate" adds an engine tag per input axis +and composes a second `xy-pad` source / flips the gamepad to 4-axis as needed. ### 4.3 Per-output detail (advanced, optional) @@ -165,9 +198,9 @@ polarity. Defaults (identity assignment) make this unnecessary for the common ca Outputs panel, top to bottom: 1. **Output kinds** — `Continuous [n]` + `Gate sequences [n]` (MIDI), or the - linked `Gate sequences 3–14` slider with the live `CV n · Gate n` readout (CV). -2. **Rhythm network** toggle `( Shared | Separate )` + the one-line input - explainer — only when gate sequences > 0. + linked `Gate sequences 0–14` slider with the live `CV n · Gate n` readout (CV). +2. **Rhythm network** toggle `( Shared | Separate )` + the inline input surface + (2nd pad / double-stick / per-CC engine tags) — only when gate sequences > 0. 3. The existing per-output rows (off/fixed/live, mute, arm, min/max/curve) + the per-backend specifics (CC#, CV jack, note#) as today. @@ -178,14 +211,21 @@ mode. ## 6. Open questions / deltas to build later +Build deltas (when we proceed): + - **Manifold engine**: today one MLP head on the spine. "Separate" needs a second - Rhythm MLP head + a second input slice. Scope: engine + input-layer change. + Rhythm MLP head + per-input-axis engine routing. Scope: engine + input-layer. - **TS RatioSeq**: a browser-side RatioSeq (or a `pop_events()` bridge) to turn - Rhythm-MLP params into gate/note events for the CV & MIDI backends. + Rhythm-MLP params into gate/note events for the CV & MIDI backends. 2 sequences + on firmware; arbitrary count in the browser. - **MIDI backend**: gate sequences → note on/off (it currently only sends CC). -- **CvSpec**: allow PWM jacks (cv1..cv11) to be gate targets (velocity gate), - with the digital pins still the always-on 3. -- **Reshape**: the dynamic browser net's `output_size` = continuous_count + - 7 × gate_sequences (shared) — confirm against the reshape/reset-on-reshape flow. -- **Firmware output_size** for "Continuous only" / "Rhythm only" — the other - agent's call; this doc only fixes the *shapes*, not exact counts. +- **CvSpec**: allow PWM jacks (cv1..cv11) to be gate targets (incl. velocity-CV), + with the 3 digital pins as the first-used gate jacks. +- **Reshape**: shared-net `output_size` = continuous + 7 × gate_sequences (≤126 + WASM cap) — confirm against the reshape/reset-on-reshape flow. +- **Firmware output_size** for "Continuous only" / "Rhythm only" — slices of the + `slp_workshop` mode (`workshop/synth-fw-audit`); the firmware agent's call. This + doc fixes the *shapes*, not exact counts. + +Deferred niceties (explicitly not blocking): external-MIDI-clock sync; per-track +pulse-width (gate length); continuous velocity/accent beyond 127/64.