Skip to main content

Module pattern

Module pattern 

Source
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§

ChainEntry
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.
PatternBlock
A whole pattern, as the audio thread holds it.
PatternEvent
One MIDI event a pattern produced, stamped with the absolute song tick it happens at.
PatternPlayer
Everything one sequencer track needs on the audio thread.
PendingOffs
Every note this track is holding, oldest first.
PlaybackWindow
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.
SwitchQuant
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§

EventSink
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 from and to.