User request live: right-hand index pinch fires breakdown instead of
relanceEvolve — SC default, generator default, web defaults, the 196
presets and the test pins all updated. relanceEvolve stays in the
assignable vocabulary.
7 style families x 8 melodic roles x 6 composition stages. Chord
progressions (i-VI-III-VII etc.) aligned across all voices per colour;
call-response via +4-step offset for melody/stab vs arp/acid. Pure
deterministic transforms: rotate, ornament, climax (+7 oct lift),
sparse, resolve-to-anchors. New MEL[role]["composed_<style>"] entries;
existing hand-authored families untouched.
Context: live sets needed variety; the single *_live.matrix per style
gave no choice during a performance.
Approach: 28 styles x 4 variants each produced by
generate_live_variant_sc() with deterministic era kits and family
overrides.
Changes:
- Add *_live-1..4.matrix for every style (112 files)
- v1 house/M1: m1_organ/m1_piano/dx7_epiano on chord/bells/melody
- v2 acid/hoover: tb303 on acid/arp, hoover on lead/reese
- v3 virus/nord: virus_saw on lead/arp, nord_lead on melody/arp
- v4 raw 909/rave: tr909_snare on snare, rave_stab/brass_stab on stab
- Each variant uses a distinct pattern family and busy-block profile
Impact: 196 total .matrix presets; SC engine discovers all via ls scan.
Add hoover/nord_lead/virus_saw to arp+lead, brass_stab/rave_stab to
stab, m1_piano/m1_organ/dx7_epiano to chord+sub+bells+melody,
reese_juno to sub+pad+reese, tr909_snare to clap+snare.
Mirror changes in web UI MATRIX_INST_CHOICES.
The deepen-bindings commit accidentally staged the preset generator's
__pycache__ directory (compiled .pyc artifacts). Untrack the files and
add a local .gitignore so preset regeneration never stages them again.
User request live: more audible depth on the hand X/Y mods. All 1848
lHand/rHand X/Y bindings across the 84 presets raised from 0.40-0.50
to 0.70 (per-voice editable in the web UI as before).
Update globalActions in all 84 .matrix preset files from old
default to: [\fill, \doubleTime, \washReverb, \muteDrums,
\relanceEvolve, \halfTime, \next, \muteMelo].
Add \doubleTime and \halfTime as drum-only subdivision feel toggles
(\matDrumFeel \normal/\double/\half, mutually exclusive). Engaging a
feel re-sources only drum voices (matLastColor reset + matApplyBar).
The \relanceEvolve composite action seeks to bar 0 then fires
matRelanceEvolve. \matDrumFeelPush sends /matrix/drumfeel on toggle
and on preset load (resets to \normal). OSCdef mat_drumfeel_get added.
Drum feel stretch applied in matVariationOverlay + matColorStepPattern
via txStretch local var; non-drum voices use matTxStretch unchanged.
Web vocab: relanceEvolve added, evolve relabelled Évolue.
Test file extended with sections R (feel toggle) and S (relanceEvolve).
SC handlers (edge detector, tick, OSCdefs, pose/hands calls) and web
UI (Poings section, sensibilite poing slider, fistprogress indicators)
removed. Python FIST_ENABLE=0 kill-switch was already in effect.
Old .matrix files with fistThresh key load without error (key silently
ignored). Test harness Q section removed from test_global_actions.scd.
Context: Task 1 wired fistThresh into the SC matrix engine (default
0.15); the generator was not yet emitting this field, so every
.matrix was missing it and the engine fell back to its hardcoded
default.
Approach: TDD — added a failing check to test_global_actions.py,
then edited to_sc so pinchHoldMs ends with a comma and fistThresh
0.15 follows as the last field before the closing paren.
Regenerated 84 presets twice for determinism.
Changes:
- generate_presets.py: pinchHoldMs now ends with comma; fistThresh
0.15 emitted as the final key in every preset's SC Event literal
- test_global_actions.py: EXPECT_FTH block checks to_sc output and
all 84 on-disk .matrix files
- *.matrix (84 files): regenerated with fistThresh field
Impact: all four gates green (GLOBALACT-PY, test_sections,
test_families, GLOBALACT SC); fistThresh is now a first-class
preset field readable at load time by ~matLoadFile.
Held closed-fist (low openness, hysteresis+presence guard) per hand
fires an action after ~matPinchHoldMs: left hand reloads the current
preset, right hand advances to the next. Progress emitted on
/matrix/fistprogress.
- ~matFistEdge edge detector (engage/release/absent + hysteresis)
- ~matFistTick fires once past hold threshold (guarded by ~matFistFired)
- ~matFistReloadCurrent and ~matNextPreset wired to hand 0/1
- /matrix/fisthresh[/get] OSCdefs + ~matFistThreshPush
- fistThresh saved and restored per .matrix file
- 30 Hz poller now calls ~matFistTick alongside ~matPinchTick
- Harness group Q: A-Q all green
Add top-level 'pinchHoldMs': 300 to every generated .matrix so the SC
engine (Task 2) reads it instead of its hardcoded default.
- generate_presets.py: globalSettings ends with ',' and
'pinchHoldMs': 300 is the last field before the closing ')'
- test_global_actions.py: EXPECT_PHMS check added (TDD red then green)
- 84 .matrix files regenerated (28 morceaux x intro/live/outro)
Expand globalActions from 4 to 8 slots:
[\fill, \drop, \breakdown, \evolve,
\muteDrums, \muteMelo, \washReverb, \next]
Drop openThresh/closeThresh from globalSettings (now
engine-side constants via Task 1). Regenerate all 84
.matrix files deterministically.
All gates: GLOBALACT-PY PASS, TEST PASS x2, GLOBALACT PASS
Replace open/close hand detection with 8 pinch slots (hand*4+finger-1).
Global actions array grows from 4 to 8 entries (L/R x index..ring).
Remove ~matHandFlip, ~matHandState, openThresh/closeThresh entirely.
Context: Phase 2 Task 1 made the SC engine load a globalSettings Event
from .matrix files (openThresh/closeThresh/transientBars/breakdownKeep)
and default it when absent. Generator was not yet writing the field.
Approach: append globalSettings after globalActions in to_sc() return
string; extend test_global_actions.py with EXPECT_SET check against
both the to_sc sample and all 84 .matrix files on disk.
Changes:
- generate_presets.py: to_sc now ends with globalActions (comma) then
globalSettings Event with default values matching SC defaults
- test_global_actions.py: add EXPECT_SET literal + two check() calls
- *.matrix (84 files): each preset now carries the globalSettings field
Impact: every preset ships with openThresh/closeThresh/transientBars/
breakdownKeep defaults; SC engine reads them on load; no more implicit
fallback needed at runtime for any preset in the library.
When the evolve toggle is OFF, ~matRelanceEvolve now snapshots
~matBaseGrid, mutates once, then restores it to nil. When ON it
keeps the persistent base and re-mutates from it. Fixes stale-
snapshot corruption across preset loads. Case I added to harness.
Context: SC engine (Task 2) loads 'globalActions' from .matrix files
and defaults to [\fill, \drop, \evolve, \breakdown] when absent.
Generated presets did not write this field, causing implicit reliance
on the engine default.
Approach: extend to_sc() return string to include the globalActions
key so every preset explicitly carries the default mapping
(LH-open=fill, LH-closed=drop, RH-open=evolve, RH-closed=breakdown).
Changes:
- generate_presets.py: add 'globalActions' line in to_sc() return
- test_global_actions.py: new Python test verifying to_sc() output
and all 84 on-disk .matrix files carry the field
- 84 .matrix files: regenerated with globalActions field present
Impact: presets are self-documenting; SC engine reads the explicit
value rather than applying a silent default.
The live loop tiled the plateau into 4 identical blocks -- full but static.
LIVE_STYLE maps each morceau to a style group; LIVE_VAR holds per-style
profiles; _live_variation recolours active cells in some 16-bar blocks
(rhythmic 2/4, melodic 3 octave) and adds phrase-end fills (colour 6) so the
full loop breathes in a genre-appropriate way -- techno double-time hats +
fills, breaks snare rolls, ambient gentle bell octaves, etc. Active cells
only -> density unchanged, no voice dropped, no intro/breakdown back.
test_sections asserts the live still has no intro/breakdown AND varies (not
4 identical blocks). Regen 84 deterministic, VALIDATE 84/84 + DEFAULTS PASS.
Each morceau's _live section reused the standalone arc, which re-introduced
(near-empty intro -> rebuild -> plateau -> breakdown). Now make_live_continuous
tiles the plateau (P, bars 32-47) across all 64 bars: full groove from bar 0,
no intro ramp, no breakdown -- a clean loop the _intro builds to and the
_outro descends from. Applied post-apply_colors for the live section only via
to_sc(post=); intro/outro unchanged.
test_sections asserts the live is full-from-start + has no breakdown dip.
Regen 84 deterministic, VALIDATE 84/84 + DEFAULTS PASS, test_families PASS.
The SP-A default bound danse->gate on the fx and sweep voices, so the
action-head classifying the dancer as "danse" toggled those voices on/off
even with no fx/sweep cells in the grid -- poses triggering FX that the
user never placed. Pose->voice triggers are now opt-in (default_poses
returns []); continuous pose modulation (default_mods) is unchanged.
Updated test_default_mods.py + the validate_presets DEFAULTS assertion
(fx voicePoses now expected empty). Regenerated 84 presets. VALIDATE
84/84 + DEFAULTS PASS; test_families + test_sections PASS.
Emit /matrix/loaded <name> at each user-facing load point (the /matrix/load
OSCdef, the queued branch of ~matWrapStep firing at the wrap, and the
not-playing fallback in /matrix/loadnext) - not from the shared ~matLoad,
to avoid spamming during the boot autoload. The web fills #matrix-name from
it, so SAUVER updates the loaded matrix in place. For CHARGER SUIVANT the
name tracks the actual swap (at the wrap), never the merely-armed preset.
VALIDATE 84/84, test_matnames PASS, node --check + 29 web tests pass.
Expand the 7 breaks morceaux from the shared _breaks_intro/_breaks_outro
helper into 14 fully-independent arc bodies (one intro + one outro each),
per the "no shared helper" requirement. Each varies by morceau character
(bass_science reese-forward, jungle_amen amen-snare, footwork kick step2,
neuro_reese stab, tribal_perc tom/perc).
test_sections PASS, VALIDATE 84/84.
Wire the hand-authored families into the 84 presets and make the launcher
boot the live section.
Changes:
- patterns_for: strip the _intro/_live/_outro suffix before the
PRESET_FAMILY lookup, so all 84 section presets resolve to their
morceau's family.
- PRESET_FAMILY: retarget mel families to the new banks where they fit -
house (detroit/rave/trance/melodic/electro), psy (hypnotic/psytrance/
minimal), dub (dub_techno/melodic_deep), ambient (ambient_intro); drum
families now style ride/shaker/clap/perc per dfam.
- launch_concert.sh: MATRIX_PRESET default -> techno_drive_live.
Impact: genre presets now use the authored families. test_families PASS,
test_sections PASS, regen deterministic, VALIDATE PASS 84/84.
Hand-write the intro and outro arc for each of the 28 morceaux (56 new
independent arc functions). intro = voices enter across the 64 bars
(sparse->full, capped at the plateau with a light O lead-in into live);
outro = voices exit (full early, dropping out toward the break/outro).
Approach:
- one arc_intro_<m> / arc_outro_<m> per morceau, registered in
INTRO_ARCS/OUTRO_ARCS; composed from the primitive fill/hit grid API
over the I/B/R/D/P/K/O section codes.
- the 7 breaks morceaux share a _breaks_intro/_breaks_outro shape
parameterized per morceau (deviation from "fully independent" - flagged
for review).
- test_sections.py gate: intro builds, outro breaks down, live densest -
all 28 PASS.
Impact: 84 presets regenerated, VALIDATE PASS 84/84. Section feel is
shape-correct; musical review by ear pending.
Add explicit 6-colour style families to the pattern bank so genre presets
feel musically distinct (merged SP-C2). All colours hand-written following
the role convention (base/busier/broken/double/sparse/fill), velocity-
humanized; existing families and every default stay untouched.
Changes:
- MEL: dub (reese/pad/chord/bells/melody), psy (sub/reese/arp/lead/stab),
house (pad/chord/stab/bells/lead/melody), ambient (pad/bells/melody/
sub/reese) - 21 families x 6 motifs.
- DRUMS: percu-voice + style-completion families for ride/shaker/crash/
rim/fx/sweep/perc (techno/house/breaks/dnb/trance/ambient).
Impact: vocabulary for the genre presets; wired per preset in a later
task. test_families.py PASS (validity + >=4 distinct + coverage).
Each morceau is now decomposed into three sections (intro/live/outro)
as three separate .matrix files, replacing the single-grid presets.
Approach:
- MORCEAUX: lazy view over PRESETS; live = the existing arc, intro/outro
resolve from INTRO_ARCS/OUTRO_ARCS registries (fallback to live) so the
per-morceau arcs can be populated anywhere at module level.
- main() emits <morceau>_<section>.matrix (28 x 3 = 84).
- test_sections.py: section-shape gate (intro builds, outro breaks down,
live densest) - currently RED on outros (placeholders) to drive the
hand-written arc tasks.
- validate_presets.scd: count 28 -> 84, DEFAULTS check on peak_drop_live.
Changes:
- generate_presets.py: MORCEAUX/_sections/_Morceaux, main() loop.
- removed the 28 old single-name .matrix; wrote 84 section files.
Impact: structural base for hand-authored intro/outro arcs (Tasks 3-9)
and the new pattern families (Task 2). VALIDATE PASS 84/84.
Busier/sparse/broken in derive_drum_colors and derive_mel_colors
now operate on the base's own rest/hit lists, not fixed i%4
positions. Stab (all hits at i%4==2) went from 4 distinct colours
(busier==broken==base, sparse EMPTY) to 6. Test strengthened with
distinctness and sparse-not-empty asserts for stab/kick/hats/acid/
shaker/pad. 28 presets regenerated; determinism confirmed (md5 zero
diff on two consecutive runs). VALIDATE PASS 28/28.
Replace weak != property checks with equality assertions against
derive_drum_colors / derive_mel_colors. The old checks passed even
without wiring; equality fails if patterns_for reverts to raw authored
defaults.
Wire derive_drum_colors/derive_mel_colors into patterns_for:
default family colours 2-6 are now derived from colour 1
(busier/broken/double/sparse/fill); authored style families
pass through unchanged. Remove dead _pick/_DRUM_VELS.
Regenerate 28 .matrix files; sclang VALIDATE PASS 28/28.
Fix 1: ~matClear now mirrors the zero grid into ~matBaseGrid when
evolving, preventing resurrection on the next ~matEvolveStep cycle.
Fix 2: ~matSetEvolve.(true) guards the base snapshot with a
not-yet-evolving check; re-enabling while already evolving no longer
overwrites the original baseline.
Fix 3: ~matLoadFile now calls ~matEvolvePush after load so
reconnecting web clients see the correct toggle and rate.
RED->GREEN on two new test assertions (test_matrix.scd).
Position sources (lHandX/Y, rHandX/Y, bodyX/Y, headX/Y, bodySym)
now default to 0.5 (centre) when absent from cache instead of 0.
Magnitude sources (speed, accel, openness, span, danse, depth,
mouthOpen) keep 0 as correct rest. Fixes pitch-flat-at-boot-without-pose
and matching meter skew in matModValuesPush.
Regenerate 28 .matrix presets with role-adapted voiceMods/voicePoses:
soloists (acid/arp/lead/stab/bells/melody) get hand bindings (rHandX/Y,
lHandX/Y); drums get body bindings (bodyVitesse/bodyX); fx/sweep get
danse->gate pose. Update validate_presets.scd to assert new scheme via
peak_drop (acid 4 hand bindings, kick 2 body, fx danse->gate).