Same -/+ stepper controls duplicated on the MATRICE tab; the wiring is
factorized into bindStepGroup so both tabs' sliders and readouts stay
in sync while driving the same OSC addresses.
The web surface had no master control at all (SC-only via
/control/doMaster 0..2). Adds the slider next to Tempo with 0.05
steppers and a live readout, same pattern as the tempo control.
One-BPM steps beside the tempo slider (fine adjustment is hard on a
touch slider mid-performance) plus a live numeric readout; both paths
share the same /launch/tempo send.
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.
SC's mod cache keeps the last received value when a hand vanishes, but
the gauge markers and wrist cross disappeared — the screen no longer
showed what the matrix was hearing. Markers now hold the last known
position (dim conf 0.45) and go full-bright when the hand is live.
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.
Metal lines are 1px and the skeleton was barely visible in
performance. Each ARKit bone is now drawn in concentric offset passes
(SKEL_LINE_WIDTH, default 4 approx px; 1 restores the thin look).
VIZ_DEFAULT_MODE=<name> boots the viz on that mode and disables the
auto-openpos switch entirely, so the mode is truly always active (user
request: the t-key voronoi effect permanent). Concert launcher defaults
to voronoi; "auto" keeps the historical behavior.
The module doc still mandated landscape (2026-06-30 ARKit constraint);
portrait was validated live 2026-07-02 and is now the concert default.
Document the rotation split (video ccw, 2D points already upright).
The X/Y voice mods were anchored to the 21-landmark centroid, so
opening or pinching the fingers shifted the mod values (gesture/mod
cross-talk). cx/cy now track the WRIST (kp 0). The renderer draws a
cross marker at each wrist on the video whenever features exist, so
the control point stays visible even for far hands the overlay
filters out (user request live 2026-07-02).
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).
qsdfghjkl fired auto-synth scenes on stray keypresses during matrix
performance — same gate as wxcvbn (VIZ_AUDIO_KEYS=1 re-enables). The
m matrix-mode shortcut (scene stop + openpos) stays active.
m now stops the auto data-synth scene AND forces the openpos viz
(matrix unaffected — it is not a scene). Repairs the two test pins the
frozen pinch defaults broke (ext_min 1) and makes the skeleton path
safe for __new__-built renderers (hand gate getattr).
Live session outcomes: (1) pinch thresholds validated for inner
fingers frozen as defaults (ratio_on 0.50, margin 0.05, ext_min 1);
(2) panel frames + X/Y gauges now permanent (no early-return when
nothing is detected); (3) wxcvbn source-bundle shortcuts gated off by
default (VIZ_SOURCE_KEYS=1 re-enables) — a stray key mid-performance
switched the audio scene.
Add gesture_quality() pure helper (hand_display.py) that maps a slot's
plausibility, facing, and proximity to a score in [0,1]. Renderer uses
it to modulate panel frame brightness (0.25+0.75*q) and stroke count
(1..3 passes at q≥0, ≥0.5, ≥0.85 / status==3). Status still drives
hue (pid 7/8/9). New gesture_slot_quality field in State written
alongside gesture_slot_status each tick.
Add gauge_segments() for X/Y position gauges around each panel: a
horizontal rail below and a vertical rail on the outer side, each with
a bold two-tick notch at the hand's normalised cx/cy (mirror-aware for
X). Dim rails (conf 0.25) when slot absent. Renderer reads hand_feats
L/R directly from state.
11 new tests (6 gesture_quality + 5 gauge_segments); suite 430 passed.
Add gesture_slot_status [0..3] to State (absent/detected/armed/engaged).
PinchDetector.engaged_slots() exposes per-slot pinch state. Publisher
writes status after each tick via _update_gesture_slot_status().
Renderer draws panel frame always: dim (pid7 conf=0.3) when absent,
normal (pid7) detected, pid8 armed, pid9 double-stroke engaged.
Add hand_facing() palm-spread metric (dist(kp5,kp17)/hand_size).
Gate GestureSlotStabilizer slot activation on facing >= HAND_FACE_MIN
(default 0.5) with 0.8x hysteresis; side-on palms no longer arm
pinch/strike. Add VizConfig.hand_face_min + HAND_FACE_MIN env var.
Update test fixtures: _near_hand() now spreads kp[5]/kp[17] so
existing tests keep passing through both near+face gates.
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).
Live: skeleton still misrotated after the conjugation fix. Both app
2D streams (ARKit skeleton2D + Vision hands) arrive already uprighted
in portrait — the interface orientation drives the projection. Points
are no longer rotated by default; VIDEO_ROTATE only turns the video.
IPHONE_SKEL_ROTATE / IPHONE_HANDS_ROTATE remain as scan knobs.
Live report: skeleton straight but hands off the wrists. The app runs
Vision with a hardcoded .right orientation (ARBodySession.swift:261),
so hand coords live in an uprighted frame while ARKit skeleton2D lives
in the raw sensor frame. Hands get their own IPHONE_HANDS_ROTATE knob
(default none); VIDEO_ROTATE now only rotates the skeleton2D points.
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.
Live report: video straight but skeleton rotated wrong. Flip and
90-degree rotation do not commute — the video is flipped THEN rotated
while points are rotated THEN display-flipped. Points now use the
mirror-conjugated mode (cw/ccw swapped under mirror), proven by a
composition test.
Portrait mode validated live: VIDEO_ROTATE now also rotates the
iPhone 2D landmarks (skeleton2D + Vision hands) at the decode layer so
overlays/panels/gestures stay aligned with the rotated video. ARKit
per-finger joints are masked from the wireframe (unreliable 2D, seen
as false hands); the display shows the filtered Vision hands instead.
Apply hand_plausible + HandPersistenceGate in _step_stab before
route_hands so gestures and /pose/hands features only see quality-
gated hands. Cache per-tick validated set (_tick_hands/_tick_chir/
_tick_mirror) so all three emitters share one acquisition. Gate
stepped at 30 Hz publisher cadence (audit R6). Non-protocol hands
(raw numpy arrays) pass through via try/except for backward compat.
The body-mesh triangles (solid cyan blob reported live) were the one
surface no filter covered: under iphone-usb persons_body is the ARKit
conversion with c=1.0, so garbage/stale joints painted full triangles.
Skip BODY_TRIANGLES whenever the ARKit topology is present (webcam
MediaPipe path unchanged); the body stays the freshness-gated ARKit
wireframe.
config.py: PINCH_EXT_MIN docs said "min frames for debounce"
— it is min extended fingers required to fire; fix wording.
HAND_PERSIST_FRAMES said "persist after disappearance" — it
is frames required before a hand is drawn; fix wording.
CLAUDE.md: MEDIAPIPE_DELEGATE default was "cpu" — real
default is "gpu" (config.py); fix the RC0.1+ table row.
Add missing PINCH_ENABLE / VIZ_HUD / CONCERT_MIRROR rows
to the main env-vars table.
iphone_usb_source.py: after stream ends, clear
persons_hands_iphone, persons_hands_chirality, and
persons_hands (when write_hands=True) under state.lock()
to prevent renderer display freeze.
test_iphone_usb_reconnect.py: add
test_hands_cleared_on_stream_drop to verify lists are
empty after simulated USB disconnect.
renderer.py: read ARKIT_FULL_SKELETON via VizConfig at
initWithState_ instead of module-level os.environ; store as
self._arkit_full, use getattr fallback for __new__ test bypass.
main.py: pass cfg.iphone_osc_port to IphoneOSCListener
instead of hardcoded 57128; log actual port.
hand_slots.py: import _finite/_coord/_clamp from hand_features
at module level (acyclic: hand_features imports hand_slots only
lazily inside step()); delete the duplicate copies.
User request live 2026-07-02: disable the fist gesture completely
(false held-fist actions during play). fist is forced to 0.0 at the
extractor unless FIST_ENABLE=1; continuous features unchanged. SC
held-fist actions (reload/next) go silent without touching SC.
iphone-usb path: camera decode + ARKit conversion is cheap (no
MediaPipe inference on Mac). Frame-based gesture calibrations
(debounce/hold/grace/persist) assume 30 Hz. Keep 18 Hz for the
webcam MediaPipe path (M5 CPU protection).
Tests: period formula + MultiWorker.__init__ signature check.
Read state.mirror_2d in _pick_hands and thread it through
_step_stab (route_hands mirror=) and _emit_hands
(HandFeatureExtractor.step mirror=) so the cx fallback slot
assignment matches the renderer panel layout on chirality dropout.
Add mirror kwarg to HandFeatureExtractor.step plumbed to route_hands.
Tests: extractor-level mirror flip + publisher _emit_hands mirror_2d.
Detection v3 review follow-ups: pin hand_persist_grace/near_off/
hold_frames defaults; use the module-level route_hands (restores the
monkeypatch seam, drops the dead local alias); document the grace
count-inheritance window in the gate docstring.
hand_slots module docstring still claimed inverted chirality (default
1) after the live A/B validated chirality correct (default 0); align
it. Pin the MESH_RIG ne0 bool convention (chantier 5 review minor).