docs: per-color pattern restructure design

This commit is contained in:
clement
2026-06-29 00:01:24 +02:00
parent 74e43d6915
commit be02a71bde
@@ -0,0 +1,131 @@
# Matrix per-color pattern restructure — design
Date: 2026-06-28
Status: approved (brainstorming), pending implementation plan
Scope: `sound_algo/data_only/matrix.scd` + `web_realart/public/control/`
Builds on / restructures: the complete per-instrument editor
(`2026-06-28-matrix-instrument-editor-design.md`), already on main.
## Goal
Make each matrix color (1-6) a fully autonomous pattern bundling its own
instrument, 16-step note+velocity sequence, capture effect, and pose binding. The
matrix grid cell selects which color/pattern plays for a voice at each bar. The
per-voice effect (mod) and pose bindings — just built — move to per-color. The
editor modal is reorganized around a color selector + a single per-color pattern
editor.
## Decisions (from brainstorming)
- 1 color = 1 pattern = { instrument, sequence, effect, pose }, independent per
color. The grid cell's color is the live selection.
- `effect` (capture mod) and `pose` bindings move from per-voice
(`~matMod[vi]`/`~matPoseBindings[vi]`) to per-color (fields of
`~matColorDefs[vi][color]`).
- Modal is **color-centric**: a color selector (1-6) at the top, then one pattern
editor for the selected color (instrument · variation · this color's 16-step
sequence · effect · pose).
- Backward-compat: legacy `.matrix` files with top-level per-voice `mods`/
`poseBindings` migrate on load (a voice's mod copied to all 6 colors; each pose
binding routed into its color's pose list). New saves nest mod+pose in colorDefs.
## Data model
`~matColorDefs[vi][color]` (an Event) gains two fields beyond the existing
`inst, stretch, octave, amp, cutoff, pan, steps`:
- `mod`: `(source: Sym, target: Sym, depth: Float)` or `nil` (no effect).
- `pose`: an Array of `(poseId: Sym, action: Sym)` (the color is implicit).
`~matMod` and `~matPoseBindings` (per-voice) are REMOVED. The instrument remains
`spec[\inst] ? ~matInstruments[vi]` (per-color override, else the voice default).
## Engine (matrix.scd)
- `~matVariationOverlay` and `~matColorStepPattern` read the mod from `spec[\mod]`
(the color def) instead of `~matMod[vi]`. The mod→key logic (cutoff/pan/amp via
`~matModSourceVal`, neutral cutoff, depth) is unchanged except its source.
Factor the mod-key builder into a helper `~matModPairs.(name, spec, volOf)` so
both the overlay and the stepped pattern share it (DRY — today it is duplicated).
- `~matPoseFire.(poseId)` iterates voices × colors × that color's `pose` list:
on a match, `\trigger` re-sources the voice to that color now; `\gate` toggles
the voice's Pdef. (The binding's color is the color it lives under.)
- `~matSetColorMod.(vi, color, source, target, depth)` sets/clears
`~matColorDefs[vi][color][\mod]` (validate target ∈ `~matModTargets[name]`,
source ∈ `~matModSources`; `\none` clears), re-source, echo.
- `~matSetColorPose.(vi, color, poseId, action)` adds/replaces a pose binding in
that color (dedup by poseId; empty poseId clears), echo.
- Pushes: extend the per-voice editor push so the modal gets each color's mod +
pose. Add `~matColorModPush.(vi, color)``/matrix/colormod vi color source
target depth`; `~matColorPosePush.(vi, color)``/matrix/colorpose vi color n
<poseId,action>*`. `/matrix/colordefs/get vi` triggers colordefs + steps + mod +
pose for all 6 colors.
## OSC contract
| address | dir | payload | meaning |
|---|---|---|---|
| `/matrix/colormod` | in/out | vi color source target depth | set/echo a color's effect |
| `/matrix/colorpose` | in | vi color poseId action | set a color's pose binding |
| `/matrix/colorpose` | out | vi color n (poseId,action)* | echo a color's pose list |
| `/matrix/pose` | in | poseId | fire (unchanged) |
The old `/matrix/mod`, `/matrix/posebind`, `/matrix/mods`, `/matrix/poses` (per-voice)
are removed. `/matrix/instrument`, `/matrix/colordef`, `/matrix/step`,
`/matrix/audition`, `/matrix/colordefs|steps/get` stay.
## Web (color-centric modal)
Replace the tabbed modal with a color-selector + per-color pattern editor:
- A row of 6 color buttons (tinted m1-m6); selecting one sets `cdColor`.
- The pattern editor for `cdColor` shows, stacked:
- Instrument `<select>` (curated; sets that color's `inst`).
- Variation: stretch / octave / amp / cutoff / pan (filter/pan voices only).
- Sequence: a single 16-step row (this color's steps; click toggle, wheel degree,
shift+wheel velocity — same interactions as the current grid, one row).
- Effect: source / target / depth selects → `/matrix/colormod vi cdColor ...`.
- Pose: poseId input + action + add; list of this color's bindings →
`/matrix/colorpose vi cdColor ...`.
- State: `matColorDefs[vi][color]` (JS mirror) gains `mod` + `pose`. WS handlers for
`/matrix/colormod`, `/matrix/colorpose` update state + re-render if open on that
voice/color. AUDITION toggle (cycle 6 colors) stays.
- The `matSteps`/`matMod`/`matPoses` JS arrays are folded into `matColorDefs`'
per-color `mod`/`pose`/`steps` (single source), persisted under `cdef`.
## Persistence
`.matrix` colorDefs Events now carry `mod` + `pose` (round-trip via
`asCompileString`). Extend `~matLoadFile`'s colorDefs allow-list to copy `mod`
(Event) and `pose` (Array). Migration: when a legacy Event has top-level `mods`
(16 per-voice) → copy each voice's mod into all 6 of its colors' `mod`; top-level
`poseBindings` (16 lists of (poseId,action,color)) → route each into
`colorDefs[vi][color][\pose]`. localStorage `cdef` carries mod+pose per color;
drop the separate `steps`/`pose`/`mod` localStorage keys (folded into `cdef`).
## Error handling / robustness
- Setters validate ranges and membership (target ∈ voice's `~matModTargets`,
source ∈ `~matModSources`); invalid → ignored.
- Empty `mod`/`pose` (nil / empty list) = no effect / no binding — default state.
- `/matrix/pose` unknown id → no match → no-op.
- nil-guarded, idempotent reload; defaults reproduce current behavior (empty
mod/pose ⇒ same as today's no-mod path).
## Testing
- **SC** (`test_matrix.scd`, headless, P:0 B:0):
- colorDefs default `mod` nil + `pose` empty; `~matSetColorMod`/`~matSetColorPose`
set + validate + echo; a stepped/overlay pattern reads the color's mod (cutoff
target → `\cutoff` key present when set, absent when nil).
- `~matPoseFire` fires a per-color binding (trigger re-sources the right color);
unknown id no-op.
- persistence round-trip of per-color mod+pose; legacy per-voice `mods`/
`poseBindings` migrate into colorDefs on load.
- **Web**: `node --check`; manual — color selector switches the pattern editor;
editing a color's instrument/variation/sequence/effect/pose round-trips and only
affects that color; audition cycles colors.
## Out of scope
- Pose recognition (still consumes `/matrix/pose <poseId>`).
- Per-step effect/pose (effect+pose are per-color, not per-step); variable step
counts; cross-color linking.