Add serializeShapeSeqState/restoreShapeSeqState for complete ShapeSeq
state capture (chain config, primitive states, projection knobs, voice
mode, MLP weights, freeze state, clock config). SessionPresetManager
lazily imports the session module when a ShapeSeq engine is registered,
including ShapeSeq state in save/load without a hard dependency.
When frozen, param rows dim to indicate frozen state. Tapping a row
toggles it to "live" (highlighted, receives ML deltas). Unfreezing
clears all visual indicators.
Replace the inline chain builder with a full-screen slide-up drawer
(overlay + glass backdrop + drag handle). Param sliders now subscribe
to a vanilla signal (createArraySignal) for O(changed params) updates
instead of querySelectorAll sweeps. Card list uses keyed reconciliation.
Mono mode (default) kills the previous note before firing a new one —
classic monophonic synth behavior. Poly mode retriggers same-pitch
notes without killing others. All active notes are released on mode
switch.
Unified mode: a single MLP drives both timbre and sequence — the timbre
MLP's outputs are partitioned and a configurable slice feeds the
sequencer pipeline via setSequenceOutputsFromTimbre().
Dual mode (default): separate MLPs for timbre and sequence with
independent training. The sequencer owns its own IML instance.
Two freeze modes for capturing and preserving musical state:
- Algorithm freeze: snapshot all primitive params + seeds. Individual
params can be selectively unfrozen for ML-driven exploration via
DeltaController (applies MLP deltas to frozen base values with
per-param boundary enforcement).
- Pattern freeze: snapshot the realized note pattern. The sequencer
bypasses the primitive chain entirely and loops the frozen pattern.
Extracts _runPipeline() from setSequenceInputs() for reuse.
The dirty-check that skips re-evaluation when inputs haven't changed now
also checks a generation counter. Config changes (tempo, step count,
projection, chain edits) bump the counter, ensuring the pipeline re-runs
even when joystick position is static.
The sequence MLP output count is now configurable (default 16). Hidden
layer widths scale with the output count to keep inference cheap while
providing sufficient capacity. The sequencer exposes setOutputCount()
to resize the MLP at runtime.
Replace the composable transform chain (VelocityCurve, GateThreshold,
RangeMap, OctaveFolder, StutterMap, presets) with 3 straightforward
post-chain knobs: velocityCurve, gateThreshold, and pitchRange. The
old system was over-engineered for a research prototype — the new API
is easier to wire to UI controls and reason about.
PitchWalker was classified as a generator but always consumed the
incoming pattern's triggers. Reclassify as a processor so it modifies
the pattern produced by an upstream generator (clonePattern instead of
createPattern). Add reEvalOnLoop flag on the Primitive base class; when
set, the sequencer re-evaluates the chain at each loop start so
stateful primitives produce evolving patterns across loops.
Generators can now have independent step counts. When merging patterns
of different lengths, both are tiled to their LCM. Chain exposes
setGeneratorStepCount/getGeneratorStepCount for polyrhythmic control.
Replace the pitch*127 encoding hack with a dedicated midiNote field on
pattern steps. IntervalLock now writes integer MIDI notes to step.midiNote
while preserving the original [0,1] pitch for other consumers. The clock
and sequencer bridge pass midiNote through the event pipeline.
Also reworks mergePatterns to be trigger-aware: when only one generator
fires at a step, use its values directly instead of averaging with the
other generator's defaults.