docs(spec): merge the two SLP-Workshop specs into one
Consolidate docs/slp-workshop/SPEC.md into docs/specs/slp-workshop- firmware.md so the project has a single SLP-Workshop spec. The unified spec is now two parts: - Part I (§1-8, shipped/stable): the mode + Jolt & OU-explore gestures. - Part II (§9-13, planned): the output-mode evolution — Continuous/Rhythm stream model, gate sequences, CV/MIDI output config, Manifold UX, and the locked operator decisions (2026-06-28). Frontmatter bumped stable -> evolving (the doc now spans shipped + planned). Donor docs/slp-workshop/SPEC.md removed; README.md row and MAP.md ## Specs note updated. No code change.
This commit is contained in:
parent
68f7d681fe
commit
2e75194fbb
4 changed files with 123 additions and 247 deletions
2
MAP.md
2
MAP.md
|
|
@ -175,4 +175,4 @@ See `ALIGNMENT.md`.
|
||||||
- **Index**: none
|
- **Index**: none
|
||||||
- **Skill**: invoke `/specs` to review/maintain/add/navigate.
|
- **Skill**: invoke `/specs` to review/maintain/add/navigate.
|
||||||
|
|
||||||
The corpus is a tiered T0–T4 prescriptive plan ("what we are going to build"); only `README.md` plus a handful of files exist so far. Alongside the tiers it also holds **stable reference specs** for already-built features — e.g. `slp-workshop-firmware.md` (SLP-Workshop firmware mode + the Jolt / OU-explore adaptive-learning gestures). Before changing the behaviour a spec covers, find it via `/specs`; the spec wins by intent — if it's wrong, update it in the same commit as the code.
|
The corpus is a tiered T0–T4 prescriptive plan ("what we are going to build"); only `README.md` plus a handful of files exist so far. Alongside the tiers it also holds **feature specs** — e.g. `slp-workshop-firmware.md`, the single SLP-Workshop spec: Part I (shipped) covers the mode + Jolt / OU-explore learning gestures; Part II (planned) covers the output-mode evolution, gate sequences, and Manifold config UX. Before changing the behaviour a spec covers, find it via `/specs`; the spec wins by intent — if it's wrong, update it in the same commit as the code.
|
||||||
|
|
|
||||||
|
|
@ -1,231 +0,0 @@
|
||||||
# SLP-Workshop — output modes, gate sequences & config UX
|
|
||||||
|
|
||||||
**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
|
|
||||||
|
|
||||||
`memlcelium` is the **hybrid**, and it is **one MLP**, not two:
|
|
||||||
|
|
||||||
- Net: `MLP<4, [10,14,18], 56>` (`nisps/engines/memlcelium.hpp`).
|
|
||||||
- The 56 outputs are split:
|
|
||||||
- `[0..13]` — **sequencer**: 2 sequences × **7 ratio-seq params** each.
|
|
||||||
- `[14..55]` — **synthesis**: 42 continuous params (Voice 0 + Voice 1).
|
|
||||||
- The single net therefore produces **both** the continuous values **and** the
|
|
||||||
RatioSeq parameters. An internal RatioSeq tick turns the seq params into note
|
|
||||||
triggers; the engine already exposes `pop_events()` so those triggers can be
|
|
||||||
consumed externally (MIDI / CV / gate) — that is the hook for gate outputs.
|
|
||||||
|
|
||||||
So the **two-separate-networks** hybrid is a genuinely different topology (two MLP
|
|
||||||
heads), which is why the hardware does the shared-net version and only the browser
|
|
||||||
(more compute, dynamic) does the split one.
|
|
||||||
|
|
||||||
### 7 ratio-seq params per track
|
|
||||||
|
|
||||||
`ratios[0..2]` (3), `phasor_mul` (1), `phase_off` (1), `amp_ratios[0..1]` (2) = 7.
|
|
||||||
(The April browser uSEQ-Celium used 8 — it added a pulse-width param. Use **7**
|
|
||||||
for firmware parity; pulse width can be an optional 8th later.)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 2. The unifying model: two output STREAMS
|
|
||||||
|
|
||||||
Everything below collapses to two output streams, each driven by an MLP head:
|
|
||||||
|
|
||||||
| Stream | MLP outputs | Generates | Routes to |
|
|
||||||
|----------------|----------------------------------|----------------------|----------------------|
|
|
||||||
| **Continuous** | 1 value per channel | smooth 0..1 values | MIDI CC / CV jack |
|
|
||||||
| **Rhythm** | 7 params per gate-sequence track | clock-driven gates | MIDI note / gate jack|
|
|
||||||
|
|
||||||
A **mode** is just *which streams are active* and *whether they share a network*.
|
|
||||||
|
|
||||||
### How the Rhythm stream generates gates (RatioSeq)
|
|
||||||
|
|
||||||
Per gate-sequence track, each control tick (`nisps/engines/*` `ratio_seq_*`):
|
|
||||||
|
|
||||||
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
|
|
||||||
decide high/low).
|
|
||||||
4. `ratio_seq_2(amp_ratios)` → **2-level velocity** (127 / 64).
|
|
||||||
5. Rising edge → note-on / gate-high (with velocity); falling edge → note-off /
|
|
||||||
gate-low.
|
|
||||||
|
|
||||||
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
|
|
||||||
> drives the gates. (Implementation note — not built yet.)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 3. Modes
|
|
||||||
|
|
||||||
### Firmware (SLP-Workshop) — compile-time, one chosen at build
|
|
||||||
|
|
||||||
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 | 2 × 7 = 14 seq params |
|
|
||||||
|
|
||||||
### Browser (Manifold) — dynamic, switchable live
|
|
||||||
|
|
||||||
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). `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;
|
|
||||||
gates > 0 with Shared net ⇒ hybrid-shared; gates > 0 with Separate net ⇒
|
|
||||||
hybrid-split; continuous-count = 0 ⇒ Rhythm-only.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 4. Configuring gate sequences (Manifold, CV **and** MIDI modes)
|
|
||||||
|
|
||||||
The user can add **gate-sequence outputs** in both CV and MIDI modes. Two new
|
|
||||||
controls only; everything else is automatic.
|
|
||||||
|
|
||||||
### 4.1 The two output kinds
|
|
||||||
|
|
||||||
- **Continuous** → CC (MIDI) / CV jack (CV).
|
|
||||||
- **Gate sequence** → note (MIDI) / gate jack (CV). Each = one RatioSeq track.
|
|
||||||
|
|
||||||
**MIDI mode** — two independent steppers (capped by the model output budget):
|
|
||||||
|
|
||||||
```
|
|
||||||
Continuous (CC): [ 8 ]
|
|
||||||
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*. Gates are **optional** — pure CV (0 gates) is valid. One
|
|
||||||
control:
|
|
||||||
|
|
||||||
```
|
|
||||||
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 ∈ [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
|
|
||||||
> digital pins (`docs/useq-celium/protocol.md`). The CV backend's `CvSpec` already
|
|
||||||
> lets any output target a CV *or* a gate channel — this extends it so PWM jacks
|
|
||||||
> can be gate targets too.
|
|
||||||
|
|
||||||
### 4.2 The Rhythm network (shown only when gate sequences > 0)
|
|
||||||
|
|
||||||
```
|
|
||||||
Rhythm network: ( Shared ) ( Separate ◀ default )
|
|
||||||
```
|
|
||||||
|
|
||||||
- **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 / 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)
|
|
||||||
|
|
||||||
In `BackendAdvanced` (full-depth modal), each output row can override the
|
|
||||||
defaults: which model output drives each CC/CV; which rhythm track drives each
|
|
||||||
note/gate; gate threshold (for continuous-derived gates); MIDI note#/channel; CV
|
|
||||||
polarity. Defaults (identity assignment) make this unnecessary for the common case.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 5. Proposed UI summary (the "simple" target)
|
|
||||||
|
|
||||||
Outputs panel, top to bottom:
|
|
||||||
|
|
||||||
1. **Output kinds** — `Continuous [n]` + `Gate sequences [n]` (MIDI), or the
|
|
||||||
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.
|
|
||||||
|
|
||||||
No mode dropdown for the rhythm/continuous split — the counts + toggle *are* the
|
|
||||||
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 + 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. 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 (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.
|
|
||||||
|
|
@ -33,7 +33,7 @@ Files:
|
||||||
| T3 | `T3-component-design/{core,playground,firmware,backends}.md` | not started |
|
| T3 | `T3-component-design/{core,playground,firmware,backends}.md` | not started |
|
||||||
| T4 | `T4-implementation/{core,playground,firmware,backends}.md` → beads | not started |
|
| T4 | `T4-implementation/{core,playground,firmware,backends}.md` → beads | not started |
|
||||||
| — | `00-decisions-log.md` | the interview outcomes that seed these tiers |
|
| — | `00-decisions-log.md` | the interview outcomes that seed these tiers |
|
||||||
| ref | `slp-workshop-firmware.md` | **stable reference** — SLP-Workshop firmware mode + Jolt / OU-explore gestures (built + merged) |
|
| ref | `slp-workshop-firmware.md` | **evolving** — SLP-Workshop: Part I shipped (mode + Jolt / OU-explore gestures), Part II planned (output modes, gate sequences, Manifold config UX) |
|
||||||
|
|
||||||
## Audit protocol (why tiers exist)
|
## Audit protocol (why tiers exist)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,26 +1,34 @@
|
||||||
---
|
---
|
||||||
stability: stable
|
stability: evolving
|
||||||
layer: binding
|
layer: binding
|
||||||
---
|
---
|
||||||
|
|
||||||
# SLP-Workshop Firmware & Adaptive-Learning Gestures
|
# SLP-Workshop Firmware, Learning Gestures & Output Modes
|
||||||
|
|
||||||
> Reference spec for the **SLP-Workshop** firmware mode (built for a workshop at
|
> The single spec for **SLP-Workshop** (built for a workshop at Synth Library
|
||||||
> Synth Library Portland) and the two adaptive-learning gestures it introduced —
|
> Portland). It has two parts:
|
||||||
> **Jolt** (held continuous weight morph) and **OU explore** (Ornstein-Uhlenbeck
|
|
||||||
> output walk). Both gestures live in the shared mode base, so they are available
|
|
||||||
> to every mode; SLP-Workshop is the mode that surfaces them.
|
|
||||||
>
|
>
|
||||||
> This describes a feature that is already built and merged. It is a stable
|
> - **Part I (§1–8) — shipped & stable.** The `slp_workshop` mode and the two
|
||||||
> reference, not a forward plan: it records what is, and why the load-bearing
|
> adaptive-learning gestures it introduced — **Jolt** (held continuous weight
|
||||||
> decisions were made the way they were.
|
> morph) and **OU explore** (Ornstein-Uhlenbeck output walk). These live in the
|
||||||
|
> shared mode base, so every mode gains them; SLP-Workshop is the mode that
|
||||||
|
> surfaces them. Built and merged.
|
||||||
|
> - **Part II (§9–13) — planned / evolving.** The output-mode evolution: the
|
||||||
|
> Continuous/Rhythm stream model, gate sequences, and the Manifold-side
|
||||||
|
> configuration UX so firmware and browser stay aligned. **Not implemented
|
||||||
|
> yet** — this part fixes the model and shapes, not the final counts.
|
||||||
>
|
>
|
||||||
> Provenance: landed on branch `workshop/synth-fw-audit` (commits `4e60d01`
|
> The file is marked `evolving` because Part II is still being figured out; treat
|
||||||
> core + firmware, `57c9ede` browser controls); merged to `main` at `527b8fc`.
|
> Part I as the stable baseline and Part II as the agreed direction.
|
||||||
|
>
|
||||||
|
> Migrated from `docs/slp-workshop/SPEC.md` (folded in 2026-06-28; donor removed).
|
||||||
|
> Provenance of Part I: branch `workshop/synth-fw-audit` (commits `4e60d01` core +
|
||||||
|
> firmware, `57c9ede` browser controls); merged to `main` at `527b8fc`.
|
||||||
|
|
||||||
## Source files
|
## Source files
|
||||||
|
|
||||||
- `nisps/modes/slp_workshop.hpp` — `nisps::modes::SLPWorkshopMode` (mode_id `slp_workshop`); reuses the MEMLCelium engine + MLP shape verbatim.
|
- `nisps/modes/slp_workshop.hpp` — `nisps::modes::SLPWorkshopMode` (mode_id `slp_workshop`); reuses the MEMLCelium engine + MLP shape verbatim.
|
||||||
|
- `nisps/engines/memlcelium.hpp` — the shared engine: one `MLP<4,[10,14,18],56>`, 56 outputs split into sequencer + synthesis; exposes `pop_events()`.
|
||||||
- `nisps/ml/jolt.hpp` — `ml::Jolt`: held-gesture continuous weight morph + post-release LR ramp.
|
- `nisps/ml/jolt.hpp` — `ml::Jolt`: held-gesture continuous weight morph + post-release LR ramp.
|
||||||
- `nisps/ml/ou_noise.hpp` — `ml::OUNoise<N>`: Ornstein-Uhlenbeck random walk on the output vector.
|
- `nisps/ml/ou_noise.hpp` — `ml::OUNoise<N>`: Ornstein-Uhlenbeck random walk on the output vector.
|
||||||
- `nisps/modes/base.hpp` — `nisps::ModeBase` CRTP base; owns `jolt_`/`ou_` and the guarded integration points.
|
- `nisps/modes/base.hpp` — `nisps::ModeBase` CRTP base; owns `jolt_`/`ou_` and the guarded integration points.
|
||||||
|
|
@ -33,16 +41,19 @@ layer: binding
|
||||||
- `playground/src/modes/SLPWorkshopMode.tsx`, `playground/src/modes/mode-runtime.ts` — browser controls.
|
- `playground/src/modes/SLPWorkshopMode.tsx`, `playground/src/modes/mode-runtime.ts` — browser controls.
|
||||||
- `playground/src/ml/jolt.ts`, `playground/src/output/ou-explore.ts` — browser-side gesture math.
|
- `playground/src/ml/jolt.ts`, `playground/src/output/ou-explore.ts` — browser-side gesture math.
|
||||||
- `tests/cpp/test_mlp_jolt.cpp`, `tests/cpp/test_mlp_ou_noise.cpp`, `tests/cpp/test_mode_learning.cpp`, `tests/cpp/test_mode_concepts.cpp` — anchoring tests.
|
- `tests/cpp/test_mlp_jolt.cpp`, `tests/cpp/test_mlp_ou_noise.cpp`, `tests/cpp/test_mode_learning.cpp`, `tests/cpp/test_mode_concepts.cpp` — anchoring tests.
|
||||||
|
- **Part II companions (planned work):** `docs/useq-celium/protocol.md` (CV wire protocol), `manifold/src/backends/` (CV/MIDI output backends), `manifold/src/inputs/` (input layer).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
# Part I — Shipped: mode + adaptive-learning gestures
|
||||||
|
|
||||||
## 1. Frame
|
## 1. Frame
|
||||||
|
|
||||||
1.1 SLP-Workshop is a distinct nisps mode (`nisps::modes::SLPWorkshopMode`, mode_id `slp_workshop`) that **MUST** reuse the MEMLCelium engine and MLP shape verbatim: engine_id stays `memlcelium`, and the MLP is `MLP<4, 10, 14, 18, 56>`.
|
1.1 SLP-Workshop is a distinct nisps mode (`nisps::modes::SLPWorkshopMode`, mode_id `slp_workshop`) that **MUST** reuse the MEMLCelium engine and MLP shape verbatim: engine_id stays `memlcelium`, and the MLP is `MLP<4, 10, 14, 18, 56>`.
|
||||||
**Why:** the workshop wanted its own identity (preset directory, display name) and a UI that foregrounds the two adaptive-learning gestures, but no new synthesis behaviour — so the synthesis mapping is byte-for-byte MEMLCelium's, and only the mode wrapper, identity, and surfaced controls differ.
|
**Why:** the workshop wanted its own identity (preset directory, display name) and a UI that foregrounds the two adaptive-learning gestures, but no new synthesis behaviour — so the synthesis mapping is byte-for-byte MEMLCelium's, and only the mode wrapper, identity, and surfaced controls differ.
|
||||||
|
|
||||||
1.2 The synthesis mapping in SLP-Workshop **MUST** stay identical to `MEMLCeliumMode` for the same seed and inputs.
|
1.2 The synthesis mapping in SLP-Workshop **MUST** stay identical to `MEMLCeliumMode` for the same seed and inputs.
|
||||||
**Why:** the two adaptive-learning gestures live in the shared base (§2), not in this mode; SLP-Workshop adds only identity and control surfacing. A test asserts SLP-Workshop ≡ MEMLCelium (same seed, identical audio) with the new features off.
|
**Why:** the two adaptive-learning gestures live in the shared base (§5), not in this mode; SLP-Workshop adds only identity and control surfacing. A test asserts SLP-Workshop ≡ MEMLCelium (same seed, identical audio) with the new features off.
|
||||||
|
|
||||||
1.3 Firmware compiles exactly one mode at a time (`MEMLNAUT_MODE_TYPE`), so the per-mode code cost of an extra mode is negligible.
|
1.3 Firmware compiles exactly one mode at a time (`MEMLNAUT_MODE_TYPE`), so the per-mode code cost of an extra mode is negligible.
|
||||||
**Why:** modes are selected at compile time, not linked together; an unused mode contributes nothing to the firmware image.
|
**Why:** modes are selected at compile time, not linked together; an unused mode contributes nothing to the firmware image.
|
||||||
|
|
@ -149,9 +160,105 @@ layer: binding
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
# Part II — Planned: output modes, gate sequences & config UX
|
||||||
|
|
||||||
|
> **Status: planned, not implemented.** This part fixes the *model and shapes*, not
|
||||||
|
> the final counts. **Locked operator decisions (2026-06-28):** (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.
|
||||||
|
|
||||||
|
## 9. Ground truth: what `memlcelium` is
|
||||||
|
|
||||||
|
9.1 `memlcelium` is **one** MLP (`MLP<4, [10,14,18], 56>`), not two. Its 56 outputs split: `[0..13]` **sequencer** (2 sequences × 7 ratio-seq params), `[14..55]` **synthesis** (42 continuous params, Voice 0 + Voice 1). The single net produces both the continuous values and the RatioSeq params; an internal RatioSeq tick turns the seq params into note triggers, exposed via `pop_events()`.
|
||||||
|
**Why:** the planned output modes are *slices/reshapes of this one net*, and the gate stream already exists inside the engine — so gate outputs are a routing/consumption feature, not new DSP. (A true two-MLP-head "split" topology is genuinely different and therefore browser-only; §11.2.)
|
||||||
|
|
||||||
|
9.2 The 7 ratio-seq params per track are: `ratios[0..2]` (3), `phasor_mul` (1), `phase_off` (1), `amp_ratios[0..1]` (2).
|
||||||
|
**Why:** firmware parity. (The April browser uSEQ-Celium used 8, adding a pulse-width param; pulse width is an optional 8th later — §13.)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. The unifying model: two output streams
|
||||||
|
|
||||||
|
10.1 Everything collapses to two output **streams**, each driven by an MLP head:
|
||||||
|
|
||||||
|
| Stream | MLP outputs | Generates | Routes to |
|
||||||
|
|----------------|----------------------------------|--------------------|-----------------------|
|
||||||
|
| **Continuous** | 1 value per channel | smooth 0..1 values | MIDI CC / CV jack |
|
||||||
|
| **Rhythm** | 7 params per gate-sequence track | clock-driven gates | MIDI note / gate jack |
|
||||||
|
|
||||||
|
A **mode** is just *which streams are active* and *whether they share a network*.
|
||||||
|
|
||||||
|
10.2 The Rhythm stream **SHOULD** generate gates via RatioSeq, per track per control tick: (1) a shared internal-BPM clock advances a bar phasor; (2) `seq_phasor = (bar_phasor × phasor_mul + phase_off) mod 1`; (3) `ratio_seq_3(seq_phasor, ratios, pw=0.5)` → boolean gate; (4) `ratio_seq_2(amp_ratios)` → 2-level velocity (127/64); (5) rising edge → note-on / gate-high, falling edge → note-off / gate-low.
|
||||||
|
**Why:** 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 (pulse-width 0.5, 2-level velocity) match firmware.
|
||||||
|
|
||||||
|
10.3 Where RatioSeq runs (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 drives the gates.
|
||||||
|
**Why:** the gate generator must work whether or not the WASM audio engine is the active backend.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 11. Modes
|
||||||
|
|
||||||
|
11.1 **Firmware (compile-time, one chosen at build)** **MUST** always use exactly 2 ratio sequences (memlcelium verbatim). Three slices of the `slp_workshop` mode:
|
||||||
|
|
||||||
|
| Mode | Streams | Nets | 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 | 2 × 7 = 14 seq params |
|
||||||
|
|
||||||
|
**Why:** the firmware does not vary sequence count (locked decision); exact output_size counts for the sliced modes are the firmware agent's call — this spec fixes the shapes.
|
||||||
|
|
||||||
|
11.2 **Browser (Manifold, dynamic, switchable live)** **MAY** offer the same three plus a **split-nets** variant (a separate Continuous MLP and Rhythm MLP), with **as many ratio sequences as the user wants** (each gate sequence = its own 7-param track; the net reshapes to suit). The browser mode is **implied by the Outputs config** (§12), not a separate picker: continuous>0 & gates=0 ⇒ Continuous-only; gates>0 & Shared net ⇒ hybrid-shared; gates>0 & Separate net ⇒ hybrid-split; continuous=0 ⇒ Rhythm-only.
|
||||||
|
**Why:** the browser has the compute for two MLP heads and dynamic reshape; making the mode a consequence of the config avoids a redundant mode dropdown. Shared-net `output_size = continuous + 7 × n_gates` must respect the 126-output WASM cap (~16 gates max shared); a separate Rhythm net is sized independently and scales further.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 12. Configuring gate sequences (Manifold, CV **and** MIDI)
|
||||||
|
|
||||||
|
12.1 Two output kinds: **Continuous** → CC (MIDI) / CV jack (CV); **Gate sequence** → note (MIDI) / gate jack (CV), each = one RatioSeq track.
|
||||||
|
|
||||||
|
12.2 **MIDI mode** **SHOULD** expose two independent counts — Continuous (CC) and Gate-sequences — capped by the model output budget.
|
||||||
|
|
||||||
|
12.3 **CV mode** hardware is fixed (11 PWM/CV-capable jacks + 3 digital gate-only jacks), so the two counts are **linked** and gates are optional. The rule **MUST** be `gates ∈ [0, 14]`; `CV = gates ≤ 3 ? 11 : 14 − gates`.
|
||||||
|
**Why:** the first 3 gate sequences land on the dedicated gate-only jacks and cost no CV (0→11 CV, 3→11 CV, 6→8 CV, 14→0 CV); converting CV jacks only begins past 3. Pure CV (0 gates) is valid.
|
||||||
|
|
||||||
|
12.4 Wire-protocol impact **MUST** be none: a CV jack acting as a gate carries 0/full (or the 2-level velocity) in its `u16` slot; the 3 dedicated gate bits stay digital pins (`docs/useq-celium/protocol.md`). `CvSpec` extends so PWM jacks can also be gate targets.
|
||||||
|
**Why:** reusing the existing protocol slots avoids a protocol revision for a routing feature.
|
||||||
|
|
||||||
|
12.5 The **Rhythm network** toggle (shown only when gate sequences > 0) selects **Separate** (default) or **Shared**. Separate gives the Rhythm stream its own MLP and routes each input channel to one engine, automatically by source kind: XY pad → a second on-screen pad (pad 1 → Continuous, pad 2 → Rhythm); gamepad → double-stick (left → Continuous, right → Rhythm); MIDI controller → per-CC `Continuous | Rhythm` toggle. Shared uses one MLP for both streams (all inputs feed it; the hardware-parity hybrid).
|
||||||
|
**Why:** the decision surface stays **two numbers + one toggle** — the second pad / double-stick / per-CC tag is a *consequence* of Separate, surfaced inline, reusing the existing input layer (`InputSource.axisCount()` / `axisLabels()`).
|
||||||
|
|
||||||
|
12.6 `BackendAdvanced` (full-depth modal) **MAY** override per-output defaults: which model output drives each CC/CV; which rhythm track drives each note/gate; gate threshold; MIDI note#/channel; CV polarity.
|
||||||
|
**Why:** identity defaults cover the common case, so advanced overrides stay optional.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 13. Proposed UI (the "simple" target)
|
||||||
|
|
||||||
|
13.1 The Outputs panel **SHOULD** read, top to bottom: (1) **Output kinds** — `Continuous [n]` + `Gate sequences [n]` (MIDI) or the linked `Gate sequences 0–14` slider with a live `CV n · Gate n` readout (CV); (2) the **Rhythm network** toggle `( Shared | Separate )` plus its inline input surface (2nd pad / double-stick / per-CC tags), shown only when gate sequences > 0; (3) the existing per-output rows (off/fixed/live, mute, arm, min/max/curve) and per-backend specifics (CC#, CV jack, note#).
|
||||||
|
**Why:** the counts + toggle *are* the mode, so no mode dropdown is needed for the rhythm/continuous split.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Open / Deferred
|
## Open / Deferred
|
||||||
|
|
||||||
|
Part I (shipped) caveats:
|
||||||
|
|
||||||
- **Browser↔firmware noise parity is intentionally NOT guaranteed.** `playground/src/ml/jolt.ts` and `playground/src/output/ou-explore.ts` reimplement the gesture math in TypeScript (driving weights via the existing `nisps_ml_get/set_weights` bindings) rather than calling C++ `ml::Jolt` / `ml::OUNoise` through WASM, and they use `Math.random()` not the deterministic `Rng`. Acceptable for stochastic exploration aids, but the noise itself will not be bit-identical across firmware and browser.
|
- **Browser↔firmware noise parity is intentionally NOT guaranteed.** `playground/src/ml/jolt.ts` and `playground/src/output/ou-explore.ts` reimplement the gesture math in TypeScript (driving weights via the existing `nisps_ml_get/set_weights` bindings) rather than calling C++ `ml::Jolt` / `ml::OUNoise` through WASM, and they use `Math.random()` not the deterministic `Rng`. Acceptable for stochastic exploration aids, but the noise itself will not be bit-identical across firmware and browser.
|
||||||
- **The post-release LR ramp (§3.4) is not ported to the browser.** The browser SLP mode trains only on explicit gestures, so there is no resumed continuous trainer to ease back in.
|
- **The post-release LR ramp (§3.4) is not ported to the browser.** The browser SLP mode trains only on explicit gestures, so there is no resumed continuous trainer to ease back in.
|
||||||
- **Verified in the dev environment:** C++ host tests (`tests/cpp/test_mlp_jolt.cpp`, `test_mlp_ou_noise.cpp`, `test_mode_learning.cpp`, `test_mode_concepts.cpp`), lint, WASM build, native↔WASM parity, codegen golden, playground typecheck.
|
- **Verified in the dev environment:** C++ host tests (`tests/cpp/test_mlp_jolt.cpp`, `test_mlp_ou_noise.cpp`, `test_mode_learning.cpp`, `test_mode_concepts.cpp`), lint, WASM build, native↔WASM parity, codegen golden, playground typecheck.
|
||||||
- **NOT verifiable in the dev environment (hardware/toolchain-bound; corresponds to verification chokepoints A/B):** firmware compile (no arduino-cli / submodules), Playwright e2e, on-hardware audio. To close: run `scripts/build-firmware.sh SLPWorkshop`, then flash on the RP2350.
|
- **NOT verifiable in the dev environment (hardware/toolchain-bound; verification chokepoints A/B):** firmware compile (no arduino-cli / submodules), Playwright e2e, on-hardware audio. To close: run `scripts/build-firmware.sh SLPWorkshop`, then flash on the RP2350.
|
||||||
|
|
||||||
|
Part II (planned) build deltas, when we proceed:
|
||||||
|
|
||||||
|
- **Manifold engine:** today one MLP head on the spine. "Separate" needs a second Rhythm MLP head + per-input-axis engine routing (engine + input-layer scope).
|
||||||
|
- **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 (2 sequences on firmware; arbitrary count in the browser).
|
||||||
|
- **MIDI backend:** gate sequences → note on/off (it currently sends CC only).
|
||||||
|
- **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 the "Continuous only" / "Rhythm only" slices of `slp_workshop` — the firmware agent's call; this spec fixes the shapes, not exact counts.
|
||||||
|
- **Deferred niceties (explicitly not blocking):** external-MIDI-clock sync; per-track pulse-width (gate length, the optional 8th ratio-seq param); continuous velocity/accent beyond the 127/64 two-level.
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue