117 lines
3.3 KiB
JavaScript
117 lines
3.3 KiB
JavaScript
|
|
/**
|
||
|
|
* ShapeSeq session state serialization.
|
||
|
|
*
|
||
|
|
* Captures and restores the complete ShapeSeq state:
|
||
|
|
* - Chain configuration (which primitives, in what order)
|
||
|
|
* - Primitive states (e.g., PitchWalker position)
|
||
|
|
* - Generator combine mode
|
||
|
|
* - Projection config (3 knobs)
|
||
|
|
* - Voice mode (mono/poly)
|
||
|
|
* - Sequence MLP weights (if in dual mode)
|
||
|
|
* - Freeze state (mode, frozen params, live flags, seeds)
|
||
|
|
* - Clock config (BPM, step count)
|
||
|
|
*
|
||
|
|
* @module shapeseq/session
|
||
|
|
*/
|
||
|
|
|
||
|
|
import { PRIMITIVE_REGISTRY } from './primitives.js';
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Serialize ShapeSeq engine state to a plain object (JSON-safe).
|
||
|
|
* @param {import('./sequencer.js').ShapeSeqEngine} engine
|
||
|
|
* @returns {Object}
|
||
|
|
*/
|
||
|
|
export function serializeShapeSeqState(engine) {
|
||
|
|
const chain = engine.getChain();
|
||
|
|
const prims = chain.getPrimitives();
|
||
|
|
|
||
|
|
return {
|
||
|
|
version: 1,
|
||
|
|
chain: {
|
||
|
|
primitives: prims.map(p => ({
|
||
|
|
name: p.name,
|
||
|
|
})),
|
||
|
|
states: chain.getState(),
|
||
|
|
generatorCombineMode: chain.generatorCombineMode,
|
||
|
|
},
|
||
|
|
projection: engine.getProjection(),
|
||
|
|
voiceMode: engine.voiceMode,
|
||
|
|
clock: {
|
||
|
|
bpm: engine.getClock().bpm,
|
||
|
|
},
|
||
|
|
// Sequence MLP weights (for dual mode restoration)
|
||
|
|
sequenceWeights: engine.getSequenceIML()?.getWeights?.() ?? null,
|
||
|
|
// Freeze state
|
||
|
|
freeze: serializeFreezeState(engine.freezeManager),
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Serialize freeze manager state to a JSON-safe object.
|
||
|
|
* Returns null when not frozen.
|
||
|
|
*
|
||
|
|
* @param {import('./freeze.js').FreezeManager} fm
|
||
|
|
* @returns {Object|null}
|
||
|
|
*/
|
||
|
|
function serializeFreezeState(fm) {
|
||
|
|
if (!fm.isFrozen) return null;
|
||
|
|
return {
|
||
|
|
mode: fm.freezeMode,
|
||
|
|
frozenParams: fm.getFrozenParams() ? Array.from(fm.getFrozenParams()) : null,
|
||
|
|
liveFlags: fm.getLiveFlags() ? Array.from(fm.getLiveFlags()) : null,
|
||
|
|
seeds: fm.getFrozenSeeds(),
|
||
|
|
states: fm.getFrozenStates(),
|
||
|
|
masterSeed: fm.getMasterSeed(),
|
||
|
|
// For pattern mode:
|
||
|
|
frozenPattern: fm.getFrozenPattern() ?? null,
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Restore ShapeSeq engine state from a serialized object.
|
||
|
|
* @param {import('./sequencer.js').ShapeSeqEngine} engine
|
||
|
|
* @param {Object} state - from serializeShapeSeqState
|
||
|
|
*/
|
||
|
|
export function restoreShapeSeqState(engine, state) {
|
||
|
|
if (!state || state.version !== 1) return;
|
||
|
|
|
||
|
|
const chain = engine.getChain();
|
||
|
|
|
||
|
|
// Rebuild chain from primitive names
|
||
|
|
if (state.chain?.primitives) {
|
||
|
|
// Clear existing
|
||
|
|
const existing = chain.getPrimitives();
|
||
|
|
for (let i = existing.length - 1; i >= 0; i--) {
|
||
|
|
chain.removePrimitive(i);
|
||
|
|
}
|
||
|
|
// Add from state
|
||
|
|
for (const p of state.chain.primitives) {
|
||
|
|
const Ctor = PRIMITIVE_REGISTRY[p.name];
|
||
|
|
if (Ctor) chain.addPrimitive(new Ctor());
|
||
|
|
}
|
||
|
|
// Restore states
|
||
|
|
if (state.chain.states) chain.setState(state.chain.states);
|
||
|
|
if (state.chain.generatorCombineMode) {
|
||
|
|
chain.generatorCombineMode = state.chain.generatorCombineMode;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Projection
|
||
|
|
if (state.projection) engine.setProjection(state.projection);
|
||
|
|
|
||
|
|
// Voice mode
|
||
|
|
if (state.voiceMode) engine.setVoiceMode(state.voiceMode);
|
||
|
|
|
||
|
|
// Clock
|
||
|
|
if (state.clock?.bpm) engine.setTempo(state.clock.bpm);
|
||
|
|
|
||
|
|
// Sequence MLP weights
|
||
|
|
if (state.sequenceWeights) {
|
||
|
|
const iml = engine.getSequenceIML();
|
||
|
|
if (iml?.setWeights) iml.setWeights(state.sequenceWeights);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Freeze state restoration is complex — skip for now, just ensure unfrozen on load
|
||
|
|
// TODO: restore freeze state properly
|
||
|
|
}
|