Expand description
Step-sequencer patterns: what the audio thread plays, and the arithmetic that turns it into notes.
A pattern is a grid — eight lanes of up to thirty-two steps — that generates MIDI for the instrument on its own track. It is not an instrument: it makes no sound, it makes note events, and the child instrument in the track’s plugin slot turns those into audio. TR and Elektron lineage, with the DAW transport as master.
§Why the shapes here are what they are
Fixed size and Copy. A pattern crosses to the audio thread whole, as
a value inside a crate::mixer::MixerCommand. No Vec, no Box, no
Arc: receiving one is a move into memory that already exists, and the
audio thread never reaches the allocator to accept an edit. That costs
PatternBlock::SIZE bytes per queued command, which is the price of
never taking a lock or an allocation on the deadline side.
Position-derived, never free-running. The step under the playhead is
(position / ticks_per_step) mod steps — a function of the transport’s
tick position and nothing else. There is no cursor that advances one step
per callback, because a cursor drifts: starting playback in bar 5 would
sound different from starting in bar 1 and waiting, and that is exactly
the invariant clips already hold. Everything else follows from it —
starting mid-pattern fires only the onsets that remain, and a loop wrap
neither drops nor doubles the first step.
One window, shared with clips. PlaybackWindow is the span of song
time one callback renders, and both clip playback and pattern playback in
mixer.rs take their events from the same value. That is what makes “a
pattern step and a clip note on the same beat land on the same sample”
structural rather than a coincidence two code paths have to keep agreeing
on. It lives in this module because the sync guarantee is the reason this
module exists at all.
§Notes have to end
Every note this module starts is written into a PendingOffs table with
the tick its note-off is due at, and the table is drained in tick order as
the windows go by. A tied step (gate = Step::TIE) has no due tick at
all — it is ended by the lane’s next onset, which is the 303 slide feel —
and the table is flushed whole at every discontinuity: stop, pause, a
position jump, a loop wrap, a pattern switch, a panic. The table holds
thirty-two notes and an overflow forces off the oldest rather than
dropping the new one, because a note that is never turned off is a stuck
voice and this project has already shipped one fix for that class of bug.
§What is pure and what is not
Everything except PatternPlayer is a pure function of its arguments,
and the player’s state is four scalars and the pending table. There is no
mixer, no sample rate and no plugin anywhere in this file: events come out
stamped with the absolute tick they happen at, and turning a tick into a
sample offset is PlaybackWindow::sample_offset’s single job. That is
what lets the bounce in phosphor-app compile a pattern to a clip through
the same generator the audio thread runs, which is the only way “the
bounce sounds identical to the live pattern” can be a fact rather than a
hope.
Structs§
- Chain
Entry - One entry of a pattern chain: a slot, and how many times through.
- Lane
- One row of the grid: a voice, and the steps that fire it.
- Pattern
Block - A whole pattern, as the audio thread holds it.
- Pattern
Event - One MIDI event a pattern produced, stamped with the absolute song tick it happens at.
- Pattern
Player - Everything one sequencer track needs on the audio thread.
- Pending
Offs - Every note this track is holding, oldest first.
- Playback
Window - The span of song time one callback renders.
- Step
- One cell of the grid.
Enums§
- Chord
- What a melodic step plays: one note, or several.
- Mode
- The scale a pattern’s pitch controls walk in.
- Rate
- How long one step lasts, as a musical division.
- Switch
Quant - When a queued pattern change takes effect.
- Voicing
- How the notes of a chord are spread out.
Constants§
- LANES
- Lanes in a pattern.
- MAX_
CHAIN - Entries in a pattern chain. Each carries a repeat count, so “A×4 B×4 A×3 C” is four entries rather than twelve.
- MAX_
CHORD_ NOTES - The most notes one step can produce: a four-note chord plus the bass double.
- MAX_
PENDING_ OFFS - How many sounding notes one track can be holding at once.
- MAX_
STEPS - The longest a pattern can be. Shorter patterns mask the tail rather than
clearing it — see
PatternBlock::step_count. - SLOTS
- Pattern slots per sequencer track.
- STEP_
COUNTS - The step counts a pattern may be set to.
Traits§
- Event
Sink - Somewhere for generated events to go.
Functions§
- chord_
notes - The notes one step plays, ascending, written into
out. - compile_
cycle - Compile one time through a pattern, as note events from tick zero.
- generate
- Write every event one pattern produces between
fromandto.