Song Mode — Slice-Based Arranger

A macro-level arranger that lets you sequence sound card snapshots into a timeline. Songs play through slices sequentially, switching at bar boundaries. Accessible via the Song button in the sequencer transport bar.

Files

static/js/synth/sequencer-song.js (161 lines)

Data Model

{
  slices: [
    { id: 'slice-1', name: 'Intro', bars: 4,
      snapshots: { 'sound-1': 'Kick A', 'sound-2': null } },
    { id: 'slice-2', name: 'Drop',  bars: 8,
      snapshots: { 'sound-1': 'Kick B', 'sound-2': 'Snare Rim' } }
  ],
  activeSlice: 0
}

Each slice has: - id — unique identifier (slice-N) - name — display name (clickable to rename) - bars — number of bars (1–64, clickable to change) - snapshots — map of soundId → snapshotName (or null to leave unchanged)

SongMode Class

Constructor

constructor(engine)

Takes a reference to SequencerEngine. Initializes empty slices with activeSlice = -1.

Slice CRUD

Method Description
addSlice(name, bars) Appends new slice, sets active if first
removeSlice(id) Removes slice (refuses if only 1 remains)
renameSlice(id, name) Updates slice name
setSliceBars(id, bars) Updates bar count (clamped 1–64)
cloneSlice(id) Duplicates slice (appends (copy) to name, inserts after original)
moveSlice(id, direction) Swaps with neighbour (−1 = up, +1 = down)

Cell Management

Method Description
setCellSnapshot(sliceId, soundId, snapshotName) Assigns a snapshot to a cell. null clears it.

Playback

Method Description
activateSlice(idx) Loads all mapped snapshots via loadSoundSnapshot, sets activeSlice
getSliceAtStep(globalStep) Returns {index, slice, localStep} for a global step position
totalSteps() Sum of all slices' bars × 16

Persistence

Method Description
serialize() Returns {slices, activeSlice} for storage
restore(data) Restores from serialized data
save() Writes to localStorage('sequencer_song_mode')
loadFromProject(data) Calls restore then save for project snapshot integration

UI (sequencer-ui.js)

Toggle via Song button in the transport bar. Replaces the strip rack with an inline table view. Header row: sound card names. Body rows: one per slice with:

  • Slice name (click to rename)
  • Bar count (click to change)
  • ▶ NOW / → NEXT indicators with bar counter
  • Go button — queue slice switch (next bar if playing, immediate if stopped)
  • Up / Dn — reorder slices (hidden for first/last slice)
  • Cp — clone slice
  • Del — delete slice (confirmation dialog, hidden if only 1 slice)
  • Per-sound cell — clickable label showing snapshot name, opens picker

Playback Integration

Auto-advance

In _executePendingPatterns() in sequencer.js:

if (_songPlayback && activeSlice >= 0 && slices.length > 0) {
  if (allAtZero && _globalStep >= currentSlice.bars * 16) {
    if (_pendingSlice !== null) {
      nextSlice = _pendingSlice;  // user-queued jump
      _pendingSlice = null;
    } else {
      nextSlice = activeSlice + 1;  // auto-advance
    }
    if (nextSlice < slices.length) {
      _globalStep = 0;
      activateSlice(nextSlice);
    } else {
      // Last slice finished — stop
      _songPlayback = false;
      activateSlice(0);
    }
  }
}
  • _songPlayback flag — only active when Song view is visible
  • _globalStep — resets to 0 at each slice start
  • _pendingSlice — set by Go button, overrides auto-advance

Bar Counter

A requestAnimationFrame loop updates the bar counter on the active slice row: Bar N / M. Reads _globalStep % (slice.bars × 16).

State Protection

When entering Song mode, the current sound state is deep-cloned into _savedSongState. When exiting, it's restored (params, stages, steps, eq, sidechain, sends, modRoute, _snapshotName), and strip cards are rebuilt.

Edge Cases

  • No snapshots assigned: Sound keeps its current state across slices
  • All cells empty in a slice: Slice changes nothing, just advances time
  • Last slice finishes: Playback stops, first slice activated
  • Jump during playback: Queued via _pendingSlice, consumed at bar boundary
  • Multiple sounds with different activeSteps: All sounds must hit step 0 simultaneously for bar boundary detection

Persistence

Song data is stored in localStorage('sequencer_song_mode') and included in project snapshot exports via data.songData.

Dependencies

  • sequencer.js_playNote, _executePendingPatterns, _globalStep
  • sequencer-ui.jsrenderSongView, Song button toggle
  • sequencer-persistence.js — project snapshot songData field

See Also