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);
}
}
}
_songPlaybackflag — 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,_globalStepsequencer-ui.js—renderSongView, Song button togglesequencer-persistence.js— project snapshotsongDatafield
See Also
docs/synth/sequencer.md— SequencerEngine overviewdocs/synth/sequencer-audio.md— audio graphdocs/synth/sequencer-persistence.md— save/restore