Sequencer Persistence — Serialization & State Restoration

Handles all localStorage persistence for the sequencer: auto-save, full state restore, project snapshots, and sound card snapshots. Extracted from the main sequencer.js to isolate I/O-dependent code.

Files

static/js/synth/sequencer-persistence.js (363 lines)

Storage Keys

Key Purpose Format
sequencer_state Auto-save (debounced 150ms) Full project JSON
sequencer_snapshots Project snapshots [{name, date, data}]
sequencer_sound_snapshots Sound card snapshots {soundId: [{name, date, data}]}
sequencer_song_mode Song mode slices {slices: [...], activeSlice: N}
sequencer_active_snapshot Currently loaded project snapshot name String
sequencer_active_sound_snapshot_{id} Currently loaded sound snapshot name String

Exports

Export Signature Purpose
serialize (engine) Full project state → plain JSON object
save (engine) Debounced (150ms) localStorage write
restore (engine, data) Async — full state restoration
listSnapshots () Returns [{name, date}]
saveSnapshot (engine, name) Saves current state as named snapshot
loadSnapshot (name) Writes snapshot to auto-save key, reloads page
deleteSnapshot (name) Removes one project snapshot
exportSnapshot (name) Returns snapshot data for JSON download
importSnapshot (engine, data) Saves as snapshot + reloads

Serialization (serialize(engine))

Captures the complete sequencer state:

{
  bpm, scaleRoot, scaleName, defaultOctave,
  masterVolume,
  glueThreshold, glueRatio, glueAttack, glueRelease,
  reverbMix, reverbDecay, reverbPreDelay, reverbLowCut, reverbHighCut,
  ruinaMode, ruinaDrive, ruinaFold, ruinaCrush, ruinaMix, ruinaWidth,
  strips: [{ id, name, soundIds, volume, mute, solo, pan,
             compressor, saturation, eq, filter,
             activePattern, patterns: [{ id, name, snapshots }] }],
  sounds: [{ id, name, engineId, presetName, activeSteps, maxSteps,
             params, stages, steps, modRoute, eq, sidechain, sends,
             _snapshotName }],
  soundSnapshots: (from localStorage),
  songData: (from engine.songMode.serialize())
}

Restoration (restore(engine, data))

Async function that populates the engine from saved data:

  1. Restore master settings (BPM, glue, reverb, ruina)
  2. For each sound: create engine instance, load defaults, apply saved params
  3. Restore preset manager presets if presetName is set
  4. Restore strips with patterns and active pattern's step snapshots
  5. Old-format saves (flat sounds without strips) auto-migrated to a single strip
  6. Pad all step arrays to totalSteps (128)
  7. Restore sound card snapshots to localStorage (from data.soundSnapshots)

Step Object Construction

Steps are stored as arrays of objects with defaults applied during save/restore:

function createStep(active, velocity, chance, mod, note) {
  return { active: !!active,
           velocity: velocity !== undefined ? velocity : 0.85,
           chance: chance !== undefined ? chance : 100,
           mod: mod !== undefined ? mod : 0,
           note: note !== undefined ? note : -1 };
}

ModRoute Normalization

function _normalizeModRoute(mr)

Normalizes mod route entries from saved data (handles string, array, and object formats). Backward-compatible with older save formats.

Auto-Save Flow

Mutation → setVelocity / setStep / etc.
    → this._save()
        → persist.save(this)
            → if _saveTimer active: skip
            → setTimeout 150ms:
                → serialize(engine)
                → localStorage.setItem('sequencer_state', JSON.stringify(...))

Project Snapshots

Snapshots store the full serialized state plus metadata:

{ name: 'My Song', date: 1700000000000, data: { ... full serialized state ... } }
  • Save (New button) — prompt for name, create snapshot, set as active
  • Save (overwrite) — updates the currently active snapshot in-place
  • Snapshots button — modal with Load/Export/Del per entry, plus Import

Sound Card Snapshots

Stored separately from project snapshots, keyed by soundId:

// storage format
{ 'sound-1': [{ name: 'Bass A', date: ..., data: { ... } }],
  'sound-2': [{ name: 'Kick 1', date: ..., data: { ... } }] }

Each snapshot stores the full sound state: engineId, params, stages, steps, activeSteps, maxSteps, eq, sidechain, sends, modRoute.

Included in project snapshot export via data.soundSnapshots.

Dependencies

  • registry.jsget() for engine lookup
  • preset-manager.js — dynamic import for preset reload during restore
  • localStorage — browser storage API

See Also