subsequence.pattern_builder
The API reference for PatternBuilder.
PatternBuilder
class PatternBuilder(
pattern: subsequence.pattern.Pattern,
cycle: int,
conductor: typing.Optional[subsequence.conductor.Conductor] = None,
drum_note_map: typing.Optional[typing.Dict[str, int]] = None,
cc_name_map: typing.Optional[typing.Dict[str, int]] = None,
nrpn_name_map: typing.Optional[typing.Dict[str, int]] = None,
section: typing.Any = None,
bar: int = 0,
rng: typing.Optional[random.Random] = None,
tweaks: typing.Optional[typing.Dict[str, typing.Any]] = None,
default_grid: int = 16,
data: typing.Optional[typing.Dict[str, typing.Any]] = None,
key: typing.Optional[str] = None,
scale: typing.Optional[str] = None,
time_signature: typing.Tuple[int, int] = (4, 4),
held_notes: typing.Optional[subsequence.held_notes.HeldNotes] = None,
harmony: typing.Optional[typing.Any] = None,
section_motifs: typing.Optional[typing.Dict[typing.Tuple[str, typing.Optional[str]], typing.Any]] = None,
energy: float = 0.5,
stream_seed: typing.Optional[int] = None,
repeating: bool = False,
zero_indexed_channels: bool = False,
)
The musician's 'palette' for creating musical content.
A PatternBuilder instance (commonly named p) is passed to every
pattern function. It provides methods for placing notes, generating rhythms,
and transforming the resulting sequence (e.g., swinging, reversing, or transposing).
Rhythm in Subsequence is typically expressed in beats (where 1.0 is a quarter note) or steps (subdivisions of a pattern).
Initialise the builder with pattern context, cycle count, and optional section info.
Parameters
pattern: ThePatterninstance this builder populates.cycle: Zero-based rebuild counter.conductor: OptionalConductorfor time-varying signals.drum_note_map: Optional mapping of drum names to MIDI notes.cc_name_map: Optional mapping of CC names to MIDI CC numbers.nrpn_name_map: Optional mapping of NRPN parameter names to 14-bit parameter numbers (0–16383). Used byp.nrpn()andp.nrpn_ramp()for symbolic access - typically a device-specific dictionary (e.g. Sequential Take 5'sOsc1FreqFine→ 9).section: CurrentSectionInfo(orNone).bar: Global bar count.rng: Optional seededRandomfor reproducibility.tweaks: Per-pattern overrides set viacomposition.tweak().default_grid: Number of grid slots used byhit_steps(),sequence(), androtate()when no explicitgridis passed. Normally set automatically from the decorator'sbeats/bars/stepsandstep_durationparameters.data: Shared state dict from the parentComposition(same object ascomposition.data). Read and write viap.datafor cross-pattern communication and external data access. Patterns rebuild in definition order; when two patterns share the samelength, a writer defined earlier in source is guaranteed to run before a reader defined later in the same cycle.key: The composition's key (e.g."C"), used byp.progression()to generate chords from a graph style and byp.motif()to resolve scale degrees.Nonewhen the composition has no key set.scale: The composition's scale/mode name (e.g."minor"), read viap.scaleand used to resolve scale degrees inp.motif().Nonemeans ionian/major.time_signature: The composition's time signature, read viap.time_signature; setsp.bar_beatsand powers the metric-weight table.section_motifs: Optional reference to the composition's section-motif registry, read byp.section_motif().harmony: Optional read-only harmony window view for this cycle (p.harmony) -p.harmony.chord,chord_at(beat),next_chord,until_change.Noneuntil the harmonic clock has published a window.held_notes: Optional live held-note tracker fromcomposition.note_input(). Read viap.held_notes().Nonewhen no note input was declared (and when rendering headlessly), so the accessor returns an empty list.energy: The current section's energy level (0.0–1.0), read viap.energy- the arranging dial. 0.5 when no energy source is configured.stream_seed: This pattern's derived stream seed, whichp.scratch()takes a child stream of.Nonewhen the composition is unseeded.repeating: True when the pattern is rebuilt and rescheduled every cycle, soset_length()refuses a length itsreschedule_lookaheadwould run past. One-shots -trigger()and transition fills - leave it False: they never reschedule, so their lookahead means nothing.zero_indexed_channels: Whether the composition numbers MIDI channels from 0, so a MIDI channel pool given toapply_tuning()is read the way every other MIDI channel is.
Members: apply_tuning, arpeggio, bar, bar_beats, bar_cycle, bend, branch, bresenham, bresenham_poly, broken_chord, build_ghost_bias, build_velocity_ramp, c, capture, cc, cc_ramp, cellular_1d, cellular_2d, chord, conductor, cycle, data, de_bruijn, detached, drone, drone_off, dropout, duck_map, duration, energy, euclidean, every, evolve, fibonacci, ghost_fill, golden, grid, groove, harmony, held_notes, hit, hit_steps, invert, key, legato, lorenz, lsystem, markov, melody, motif, note, note_off, note_on, nrpn, nrpn_ramp, osc, osc_ramp, param, phrase, pitch_bend, pitch_bend_ramp, placed, portamento, program_change, progression, randomize, ratchet, reaction_diffusion, recaman, repeat, reverse, rng, rotate, rpn, rpn_ramp, scale, scale_velocities, scratch, section, section_motif, self_avoiding_walk, seq, sequence, set_length, signal, silence, slide, snap_to_scale, stretch, strum, swing, sysex, thin, thue_morse, time_signature, transpose, velocity_shape
PatternBuilder.cycle
PatternBuilder.cycle
PatternBuilder.conductor
PatternBuilder.conductor
PatternBuilder.section
PatternBuilder.section
PatternBuilder.bar
PatternBuilder.bar
PatternBuilder.rng
PatternBuilder.rng: random.Random
PatternBuilder.data
PatternBuilder.data: typing.Dict[str, typing.Any]
PatternBuilder.key
PatternBuilder.key: typing.Optional[str]
PatternBuilder.scale
PatternBuilder.scale: typing.Optional[str]
PatternBuilder.time_signature
PatternBuilder.time_signature: typing.Tuple[int, int]
PatternBuilder.harmony
PatternBuilder.harmony: typing.Optional[typing.Any]
PatternBuilder.energy
PatternBuilder.energy: float
PatternBuilder.grid
property PatternBuilder.grid: int
Number of grid slots in this pattern (e.g. 16 for a 4-beat sixteenth-note pattern).
Follows set_length(steps=…), which changes how many steps there are.
PatternBuilder.bar_beats
property PatternBuilder.bar_beats: float
How many beats (quarter notes) one bar lasts: beats × 4 / unit, so 3.5 in 7/8.
Pass it wherever a bar size is asked for without a composition to
read it from, such as sentence(beats_per_bar=p.bar_beats).
PatternBuilder.c
property PatternBuilder.c: typing.Optional[subsequence.conductor.Conductor]
Alias for self.conductor.
PatternBuilder.signal
PatternBuilder.signal(name: str) -> float
Read a conductor signal at the current bar.
Shorthand for p.c.get(name, p.bar * p.bar_beats), so the signal
is read at the beat this bar actually starts on, in any metre.
Returns 0.0 if no conductor is attached or the signal is not
defined.
PatternBuilder.held_notes
PatternBuilder.held_notes() -> typing.List[int]
Return the MIDI notes currently held on the note_input keyboard.
The notes are sorted ascending. Pass the result straight to
p.arpeggio() to arpeggiate whatever the player is holding -
p.arpeggio(p.held_notes()) rests when no keys are down. Returns
an empty list when no note_input() source was declared and when
rendering headlessly (so seeded output stays deterministic).
The set is sampled once per rebuild; note_input(release_ms=…)
smooths the gap during hand-position changes so the arp does not drop
out, and note_input(latch=True) holds the chord until you play a
new one.
PatternBuilder.param
PatternBuilder.param(
name: str,
default: typing.Any = None,
) -> typing.Any
Read a tweakable parameter for this pattern.
Returns the value set via composition.tweak() if one
exists, otherwise returns default.
Parameters
name: The parameter name.default: The value to return if no tweak is active.
Example:
@composition.pattern(channel=1, beats=4)
def bass (p):
pitches = p.param("pitches", [60, 64, 67, 72])
p.sequence(steps=[0, 4, 8, 12], pitches=pitches)
PatternBuilder.set_length
PatternBuilder.set_length(
length: typing.Optional[subsequence.declarations.Beats] = None,
*,
steps: typing.Optional[subsequence.declarations.StepCount] = None,
) -> PatternBuilder
Change how long the pattern is, in beats or in its own steps.
In beats, the pattern keeps its number of steps and they stretch or
squeeze to fit: set_length(3) on a sixteen-step bar is still sixteen
steps, each now three sixteenths of a beat.
In steps, every step keeps its size and the pattern gains or loses
steps: set_length(steps=12) on a sixteen-step bar is twelve
sixteenths - three beats - and p.grid becomes 12, so euclidean()
and every other method that counts steps spreads over those twelve. A
step is the size the pattern was declared with, whatever lengths it has
been given since.
Notes already placed in this build keep their positions; anything placed after the call sees the new length. The sequencer plays it from the next cycle, which starts where the current one ends, and it stays in force for later cycles until it is set again - so a pattern left shorter than its neighbours drifts against them, which is the polyrhythm.
p.set_length(steps=12) # twelve sixteenths against a sixteen-step kick
p.euclidean(42, pulses=5)
Parameters
length: The new length in beats (e.g.4.0for a bar of 4/4).steps: The new length as a count of the pattern's steps.
Raises
ValueError: If both or neither are given, ifstepsis not a whole number of at least 1, or if the length would be shorter than thereschedule_lookaheadof a pattern that repeats - which would leave it silent.
Returns self for fluent chaining.
PatternBuilder.note
PatternBuilder.note(
pitch: subsequence.declarations.Pitch,
beat: subsequence.declarations.GridBeats,
velocity: subsequence.declarations.VelocityValue = subsequence.constants.velocity.DEFAULT_VELOCITY,
duration: subsequence.declarations.GateBeats = 0.25,
) -> PatternBuilder
Place a single MIDI note at a specific beat position.
A drum name is carried through to the mirror fan-out so each device can
re-resolve it through its own drum_note_map. A name no destination
maps (not in the pattern's own map nor any mirror's) is dropped and
warned once - it does not raise - so device maps can legitimately lack
voices others have. (A string pitch with no drum_note_map at all
is still a configuration error and raises.)
Parameters
pitch: MIDI note number (0-127) or a drum name string from the pattern'sdrum_note_map.beat: The beat position (0.0 is the start). Negative values wrap from the end (e.g., -1.0 is one beat before the end).velocity: MIDI velocity (0-127, default 100), or a(low, high)tuple for a single random draw.duration: Note duration in beats (default 0.25).
Example
p.note(60, beat=0, velocity=110) # Middle C on beat 1
p.note("kick", beat=1.0) # Kick on beat 2
p.note(67, beat=-0.5, duration=0.5) # G on the 'and' of the last beat
PatternBuilder.note_on
PatternBuilder.note_on(
pitch: subsequence.declarations.Pitch,
beat: subsequence.declarations.GridBeats,
velocity: subsequence.declarations.VelocityValue = subsequence.constants.velocity.DEFAULT_VELOCITY,
) -> PatternBuilder
Place an explicit Note On event without a duration.
Useful for drones or infinite sustains. Must be paired with
a note_off() later to silence the note.
Parameters
pitch: MIDI note number (0-127) or a drum name string.beat: The beat position (0.0 is the start).velocity: MIDI velocity (0-127, default 100), or a(low, high)tuple for a single random draw.
A drum name this device's drum_note_map lacks is dropped (warned
once) rather than raising - consistent with the step-note methods. A
string pitch with no drum_note_map at all is still a configuration
error and raises.
PatternBuilder.note_off
PatternBuilder.note_off(
pitch: subsequence.declarations.Pitch,
beat: subsequence.declarations.GridBeats,
) -> PatternBuilder
Place an explicit Note Off event to silence a drone.
Parameters
pitch: MIDI note number (0-127) or a drum name string.beat: The beat position (0.0 is the start).
A drum name this device's drum_note_map lacks is dropped (warned
once) rather than raising; with no drum_note_map at all it raises.
PatternBuilder.drone
PatternBuilder.drone(
pitch: subsequence.declarations.Pitch,
beat: subsequence.declarations.GridBeats = 0.0,
velocity: subsequence.declarations.VelocityValue = subsequence.constants.velocity.DEFAULT_VELOCITY,
) -> PatternBuilder
A musical alias for note_on. Places a raw Note On event without a duration,
typically used for sustained notes that span multiple cycles.
Must be silenced later using drone_off().
Parameters
pitch: MIDI note number (0-127) or a drum name string.beat: The beat position (0.0 is the start).velocity: MIDI velocity (0-127, default 100), or a(low, high)tuple for a single random draw.
PatternBuilder.drone_off
PatternBuilder.drone_off(
pitch: subsequence.declarations.Pitch,
) -> PatternBuilder
A musical alias for note_off. Places a raw Note Off event at beat 0.0.
Used to stop a sequence started by drone().
Parameters
pitch: MIDI note number (0-127) or a drum name string.
PatternBuilder.silence
PatternBuilder.silence(
beat: subsequence.declarations.GridBeats = 0.0,
) -> PatternBuilder
Sends an 'All Notes Off' (CC 123) and 'All Sound Off' (CC 120) message on the pattern's MIDI channel to immediately silence any ringing notes or drones.
Parameters
beat: The beat position (0.0 is the start).
PatternBuilder.hit
PatternBuilder.hit(
pitch: subsequence.declarations.Pitch,
beats: typing.List[subsequence.declarations.BeatPosition],
velocity: subsequence.declarations.VelocityValue = subsequence.constants.velocity.DEFAULT_VELOCITY,
duration: subsequence.declarations.GateBeats = 0.1,
) -> PatternBuilder
Place multiple short 'hits' at a list of beat positions.
Parameters
pitch: MIDI note number or drum name.beats: List of beat positions.velocity: MIDI velocity (0-127), or a(low, high)tuple for a fresh random draw per hit.duration: Note duration in beats.
Example
p.hit("snare", [1, 3]) # Standard backbeat
p.hit("snare", [1, 3], velocity=(80, 110)) # Human velocity range
PatternBuilder.hit_steps
PatternBuilder.hit_steps(
pitch: subsequence.declarations.Pitch,
steps: typing.Sequence[subsequence.declarations.StepPosition],
velocity: subsequence.declarations.VelocityValue = subsequence.constants.velocity.DEFAULT_VELOCITY,
duration: subsequence.declarations.GateBeats = 0.1,
grid: typing.Optional[subsequence.declarations.StepCount] = None,
probability: subsequence.declarations.UnitInterval = 1.0,
seed: typing.Optional[int] = None,
rng: typing.Optional[random.Random] = None,
) -> PatternBuilder
Place short hits at specific step (grid) positions.
Parameters
pitch: MIDI note number or drum name.steps: A list of grid indices (0 togrid - 1), or a range such asrange(0, 16, 4).velocity: MIDI velocity (0-127), or a(low, high)tuple for a fresh random draw per step.duration: Note duration in beats.grid: How many grid slots the pattern is divided into. Defaults to the pattern'sdefault_grid(set from the decorator'ssteps/step_duration, or sixteenth-note resolution whenunitis omitted).probability: Chance (0.0 to 1.0) that each hit will play.seed: Fix the probability gating for this call (an int); omit to use the pattern's RNG.rng: Advanced determinism form - arandom.Random(wins overseed=).
Example
# Typical sixteenth-note hi-hats with some probability variation
p.hit_steps("hh", range(16), velocity=70, probability=0.8)
# Humanised hi-hats - each step gets a fresh random velocity.
p.hit_steps("hh", range(16), velocity=(40, 90))
PatternBuilder.motif
PatternBuilder.motif(
m: subsequence.motifs.Motif,
beat: subsequence.declarations.GridBeats = 0.0,
span: typing.Optional[subsequence.declarations.GridBeats] = None,
root: int = 60,
velocity: typing.Optional[subsequence.declarations.VelocityValue] = None,
fit: typing.Optional[subsequence.declarations.UnitInterval] = None,
fit_weights: typing.Optional[typing.List[float]] = None,
resolution: typing.Optional[int] = None,
) -> PatternBuilder
Place an immutable Motif onto the pattern.
Note events route through the universal note() funnel (drum names,
mirrors, velocity tuples all work); control gestures emit through the
same machinery as cc() / cc_ramp() / pitch_bend() /
nrpn() / osc(). Pitch specs resolve here, late: ints are MIDI,
strings are drum names, scale degrees resolve against the composition
key + scale anchored near root=. Per-event probabilities roll
fresh each cycle against the pattern's seeded stream.
Parameters
m: The motif value (anything exposing.events/.lengthplaces;.controlsis read when present).beat: Where the motif starts within the pattern.span: Clamp - events whose onset falls at or beyond span beats into the motif are dropped (thearpeggio()convention).root: Register anchor for scale-degree resolution: the tonic lands at its nearest instance to this MIDI note (ties resolve upward) and the melody keeps its written contour from there.velocity: Optional override applied to every note (otherwise each event's own velocity is used).fit: The chord-tones-on-strong-beats dial, 0.0–1.0: resolved Degree/int pitches landing on strong beats (metric weight= 0.5) snap to the nearest chord tone with this probability. Defaults to each note's own
fit(0.7 on the notesMotif.generate()makes, none on hand-written ones - typed degrees are sacred), so a written line beside a generated one still plays as written; inactive without a chord context. ChordTone and Approach events never snap - their harmony reading is inherent (an Approach's chromaticism is the point).fit_weights: Custom per-step metric weight list (thebuild_ghost_biasprecedent) for additive or non-isochronous metres; defaults to the time signature's table.resolution: Pulses between control-ramp messages (defaults to each control verb's own default). Kept out of the value by design: beats and shapes are music, traffic density is wire.
PatternBuilder.phrase
PatternBuilder.phrase(
value: typing.Any,
root: int = 60,
velocity: typing.Optional[subsequence.declarations.VelocityValue] = None,
fit: typing.Optional[subsequence.declarations.UnitInterval] = None,
resolution: typing.Optional[int] = None,
align: subsequence.declarations.PhraseAlign = 'pattern',
offset: subsequence.declarations.GridBeats = 0.0,
) -> PatternBuilder
Place this cycle's window of a Phrase - position computed, never stored.
The playback position is stateless arithmetic over the engine's own
counters: pos = (p.cycle * pattern_length + offset) % phrase.length -
deterministic under live reload, form_jump, and render, with
zero new state. A pattern shorter than the phrase walks through it
cycle by cycle; deliberately mismatched lengths are phase drift
(polymeter against the phrase). When the cycle window crosses the
phrase's end, the phrase loops, and a control write on its end, which
closes a gesture, is sent where it loops, as motif() sends it.
Patterns that should own the phrase's length call
p.set_length(phrase.length) once instead.
Parameters
value: A Phrase (or any value with.length/.slice; a Motif places its window directly).root: Register anchor for degree resolution (seemotif()).velocity: Optional override applied to every note.fit: Passed through tomotif()(active with the melody engine stage).resolution: Control-ramp pulse density (seemotif()).align:"pattern"(default) counts pattern cycles;"section"uses the bar within the current form section, so the phrase restarts when the section does.offset: Beats added to the computed position (a phase shift).
Example
@comp.pattern(channel=4, bars=2)
def lead (p):
p.phrase(lead_line, root=72)
PatternBuilder.section_motif
PatternBuilder.section_motif(
part: typing.Optional[str] = None,
) -> typing.Optional[typing.Any]
The Motif/Phrase bound to the current section (and part), or None.
Reads the composition.section_motifs() registry for the section
currently playing. A section with no binding returns None -
bind material or rest; no fallback guessing:
@comp.pattern(channel=4, bars=2)
def lead (p):
line = p.section_motif("lead")
if line is not None:
p.phrase(line, root=72)
PatternBuilder.scratch
PatternBuilder.scratch(name: str = 'scratch') -> PatternBuilder
An empty builder sharing this pattern's musical context.
Everything a generator reads is carried over - key, scale, harmony, section, bar, cycle, conductor, tweaks, shared data, drum and control name maps, held notes, time signature and energy - so a generator behaves the same on a scratch as it does here. A composition can build one by hand, and then it holds a dozen copied fields that go stale the day a thirteenth is added.
The scratch has its own empty pattern of the same length, so nothing it
places sounds. Read the result back with capture or
placed, and place it here with motif.
The random stream is a child, not the same one. Sharing this
builder's would advance it, so how the parent's later draws come out
would depend on how many scratches were made - and lock() promises
a pattern realises identically each cycle, which would then be true
only for a fixed number of them. A fresh unseeded stream would be
worse: it would break reproducibility outright. So the child is
derived by name, the same crc32 way Composition derives a
pattern's stream from the composition seed, and from where this
pattern's stream stood when the build began. Set a seed once at the
top and every scratch under it is reproducible, and it changes from
cycle to cycle exactly when this pattern does: under lock() it
repeats, as the pattern does. Two scratches with different names never
draw the same numbers.
Parameters
name: Names this scratch's stream. Give each one its own name if you make several, or they draw identically.
Example
@composition.pattern(channel=10, beats=4)
def drums (p):
p.hit("kick", [0, 2])
layer = p.scratch("hats").euclidean("hihat_closed", pulses=7)
p.motif(layer.capture(0.0, 4.0))
PatternBuilder.placed
PatternBuilder.placed() -> typing.List[subsequence.pattern.PlacedNote]
Read back every note placed on this pattern so far.
Answers "which notes did that generator put there" without reaching into the pattern: call it either side of a verb and take the difference. A control surface uses it to draw a generated layer in a different style from the steps somebody tapped by hand.
Returns a list of PlacedNote - a frozen,
hashable copy of each note, carrying origin so a named drum voice
can be matched back to the panel row that asked for it. Positions and
durations are in pulses; a drone's duration is None.
Only this cycle's placements are reported: the pattern is emptied at
the start of every rebuild, so a drone still sounding from an earlier
cycle is not here. Note Offs are not reported either - note_off()
and drone_off() end a note rather than placing one, and drawing a
release as a hit would show a step that never sounds.
Ordered by position, hand-placed notes before drones at the same pulse. The order is fixed only so two reads agree; nothing should depend on it.
Example:
@composition.pattern(channel=10, beats=4)
def drums (p):
p.hit("kick", [0, 2])
before = set(p.placed())
p.euclidean("hihat_closed", pulses=7)
generated = set(p.placed()) - before
PatternBuilder.capture
PatternBuilder.capture(
beat: subsequence.declarations.GridBeats = 0.0,
span: float = 4.0,
) -> subsequence.motifs.Motif
Read the notes placed so far back out as a Motif.
The captured motif is absolute MIDI and lossy by design: relative specs (degrees, chord tones) do not survive resolution, timing is pulse-truncated, probabilities have already rolled, and control gestures are not captured. The round trip is generate → place → capture → hand-edit → rebind.
A named drum is the exception: its name rides along beside the number
as the event's origin, so vary,
transpose and
invert go on refusing it - a varied
kick is a different instrument, not a variation.
A captured drum stays a named drum when it is placed (#2372).
motif puts it back by name, so the kit it is placed on
resolves its own number for it, a mirror carrying its own map sounds
its own voice, and a kit with no such voice drops it with the usual
one-time warning. Placed back where it came from, nothing changes.
Parameters
beat: Where the window starts in the pattern. A negative beat counts back from the end, as it does in every verb that places something, socapture(beat=-1, span=1)reads the last beat (#3544). Nothing is read past the pattern's end, since the builder knows only this cycle; the motif is stillspanbeats long.span: Window length in beats (also the captured motif's length).
PatternBuilder.sequence
PatternBuilder.sequence(
steps: typing.Sequence[subsequence.declarations.StepPosition],
pitches: typing.Union[subsequence.declarations.Pitch, typing.Sequence[subsequence.declarations.Pitch]],
velocities: typing.Union[int, typing.Tuple[int, int], typing.List[int]] = subsequence.constants.velocity.DEFAULT_VELOCITY,
velocity: typing.Optional[subsequence.declarations.VelocityValue] = None,
durations: typing.Union[float, typing.List[float]] = 0.1,
grid: typing.Optional[subsequence.declarations.StepCount] = None,
probability: subsequence.declarations.UnitInterval = 1.0,
seed: typing.Optional[int] = None,
rng: typing.Optional[random.Random] = None,
) -> PatternBuilder
A multi-parameter step sequencer.
Define which grid steps fire, and then provide a list of pitches, velocities, and durations. If you provide a list for any parameter, Subsequence will step through it as it places each note. A list shorter than the steps starts again from its beginning, so three pitches over eight steps make a figure that drifts against the rhythm; a longer one is cut short, with a warning.
Parameters
steps: List of grid indices to trigger. An empty list is a no-op - no notes are placed and the builder is returned unchanged (handy when probabilistic gating rejects every step).pitches: Pitch or list of pitches.velocities: Velocity (default 100),(low, high)tuple for a fresh random draw per step, or a list of velocities, one per step.velocity: The same asvelocitiesfor a single value or a(low, high)range, and the name every other verb uses - which is what a control surface drives, since a two-element list here means one value per step. Pass one or the other.durations: Duration or list of durations (default 0.1).grid: Grid resolution. Defaults to the pattern'sdefault_grid(derived from the decorator'sbeats/stepsandunit).probability: Chance (0.0 to 1.0) that each step will play.seed: Fix the probability gating for this call (an int); omit to use the pattern's RNG.rng: Advanced determinism form - arandom.Random(wins overseed=).
PatternBuilder.seq
PatternBuilder.seq(
notation: str,
pitch: typing.Union[str, int, None] = None,
velocity: subsequence.declarations.VelocityValue = subsequence.constants.velocity.DEFAULT_VELOCITY,
seed: typing.Optional[int] = None,
rng: typing.Optional[random.Random] = None,
) -> PatternBuilder
Build a pattern using an expressive string-based 'mini-notation'.
The notation distributes events evenly across the current pattern length.
Syntax:
x y z: Items separated by spaces are distributed across the bar.[a b]: Groups items into a single subdivided step.~or.: A rest._: Extends the previous note (sustain).x?0.6: Probability suffix - fires with the given probability (0.0–1.0).
Parameters
notation: The mini-notation string.pitch: If provided, all symbols in the string are triggers for this specific pitch. IfNone, symbols are interpreted as pitches (e.g., "60" or "kick").velocity: MIDI velocity (default 100), or a(low, high)tuple for a fresh random draw per event.seed: Fix the?probability gating for this call (an int); omit to use the pattern's RNG.rng: Advanced determinism form - arandom.Random(wins overseed=).
Example
# Simple kick rhythm
p.seq("kick . [kick kick] .")
# Subdivided melody
p.seq("60 [62 64] 67 60")
# Ghost snare: snare on 2 and 4, ghost note 50% of the time
p.seq(". snare?0.5 . snare")
PatternBuilder.repeat
PatternBuilder.repeat(
pitch: subsequence.declarations.Pitch,
spacing: typing.Annotated[float, subsequence.declarations.Span(low=0.01), subsequence.declarations.Unit(beats), subsequence.declarations.Step(0.25)],
velocity: subsequence.declarations.VelocityValue = subsequence.constants.velocity.DEFAULT_VELOCITY,
duration: subsequence.declarations.GateBeats = 0.25,
) -> PatternBuilder
Repeat a note at a fixed beat interval for the whole pattern.
The classic 'Note Repeat' of MPC, Push, and Maschine fame: one pitch firing at a steady rate - running hi-hats, a pulsing bass note, a metronome click.
Parameters
pitch: MIDI note number or drum name.spacing: Time between each note in beats (0.25 = sixteenth notes).velocity: MIDI velocity (default 100), or a(low, high)tuple for a fresh random draw per note.duration: Note duration in beats.
Example
p.repeat("hh", spacing=0.25) # sixteenth notes
p.repeat("hh", spacing=0.25, velocity=(40, 80)) # humanised
PatternBuilder.arpeggio
PatternBuilder.arpeggio(
notes: typing.Union[subsequence.chords.Chord, str, typing.Sequence[subsequence.declarations.Pitch]],
root: typing.Optional[int] = None,
velocity: subsequence.declarations.VelocityValue = subsequence.constants.velocity.DEFAULT_VELOCITY,
count: typing.Optional[int] = None,
inversion: int = 0,
beat: subsequence.declarations.GridBeats = 0.0,
span: typing.Optional[subsequence.declarations.GridBeats] = None,
spacing: subsequence.declarations.GridBeats = 0.25,
duration: typing.Optional[subsequence.declarations.GateBeats] = None,
direction: subsequence.declarations.ArpeggioDirection = 'forward',
seed: typing.Optional[int] = None,
rng: typing.Optional[random.Random] = None,
) -> PatternBuilder
Arpeggiate a chord (or a list of pitches) - cycle the notes one at a time at regular beat intervals.
Like chord() and strum(), the first argument can be a chord - the
chord passed to your pattern function, or any chord from
p.progression() - and root / count / inversion voice it
exactly as they do. So "play this as a chord, a strum, or an arpeggio" is a
one-word verb swap:
for chord, start, length in p.progression("phrygian_minor", harmonic_rhythm=...):
p.arpeggio(chord, root=48, beat=start, span=length, spacing=0.25, count=4)
Pass a list of pitches instead to arpeggiate something that isn't a chord (a
scale fragment, a custom voicing). Unlike a held chord(), an arpeggio is
a stream of single notes, so it has no sustain / legato / detached -
use duration for how long each note rings and span for how much of
the bar the figure fills.
An empty pitch list rests (places nothing), so a live arpeggiator over
p.held_notes() is simply silent when no keys are held:
p.arpeggio(p.held_notes(), direction="forward")
Parameters
-
notes: A chord to arpeggiate - anything with a.tones()method (the pattern'schord, or a chord fromp.progression()), or a chord name like"Cmaj7", which is the form a control surface can send - or a list of MIDI note numbers (e.g.60) / drum-name strings when the pattern has adrum_note_map. For pitched note names use the integer constants insubsequence.constants.midi_notes(e.g.notes.C4). In the list form, a drum name the map lacks is dropped (warned once); a string with no map at all still raises. -
root: MIDI root note for the chord form (e.g. 48), exactly aschord(). Required for a chord; not used for a plain pitch list. -
velocity: MIDI velocity for all notes (default 100 - arpeggios sit in the melodic-line velocity bucket, not the softened-chord bucket; passvelocity=90to matchchord()), or a(low, high)tuple for a fresh random draw per note. -
count: Number of voices for the chord form (cycles tones into higher octaves if larger than the chord's natural size). Chord form only. -
inversion: Chord inversion for the chord form (ignored when voice leading is on). Chord form only. -
beat: Beat to start the figure at (default 0.0 = the start of the pattern; a negative beat counts from the end). Use it to place an arpeggio over one progression chord. -
span: How many beats the figure fills, starting atbeat(default: to the end of the pattern). Pass the chord'slengthfrom a progression loop to confine the arpeggio to its slot. -
spacing: Time between each note in beats (default 0.25 = 16th note). -
duration: Note duration in beats. Defaults tospacing(each note fills its slot exactly). -
direction: Order in which the notes are cycled.forwardandreversewalk the pitches in the order they were given. For a chord that is ascending, because a chord's tones arrive sorted; for a list somebody chose it is the order they chose, which is musically real -G, C, Eis a different figure fromC, E, G. Thelow_to_highpair sorts by pitch first, whatever order they arrived in."forward"- as given, then wrap (default)."reverse"- as given, backwards."forward_and_back"- as given, there and back (ping-pong)."low_to_high"- sorted, ascending."high_to_low"- sorted, descending."low_to_high_and_back"- sorted, there and back: the figure a hardware arpeggiator calls up-down."random"- shuffled once per call using rng.
-
seed: Fix thedirection="random"shuffle for this call (an int); omit to use the pattern's RNG. -
rng: Advanced determinism form - arandom.Random(wins overseed=).
Example
# Arpeggiate the pattern's current chord, four voices ascending
p.arpeggio(chord, root=60, count=4, spacing=0.25)
# A list you chose, there and back: C E G E C E G E ...
p.arpeggio([60, 64, 67], spacing=0.25, direction="forward_and_back")
# One chord of a progression, confined to its slot, humanised
p.arpeggio(chord, root=48, beat=start, span=length, velocity=(60, 95))
PatternBuilder.chord
PatternBuilder.chord(
chord_obj: typing.Union[subsequence.chords.Chord, str, typing.Sequence[subsequence.declarations.Pitch]],
root: typing.Optional[int] = None,
velocity: subsequence.declarations.VelocityValue = subsequence.constants.velocity.DEFAULT_CHORD_VELOCITY,
sustain: bool = False,
duration: subsequence.declarations.GateBeats = 1.0,
inversion: int = 0,
count: typing.Optional[int] = None,
legato: typing.Optional[subsequence.declarations.UnitInterval] = None,
detached: typing.Optional[subsequence.declarations.GateBeats] = None,
beat: subsequence.declarations.GridBeats = 0.0,
) -> PatternBuilder
Place a chord at beat (the start of the pattern by default).
Note: If the pattern was registered with voice_leading=True,
this method automatically chooses the best inversion.
Parameters
chord_obj: The chord to play (usually thechordparameter passed to your pattern function, or a name like"Cmaj7") - or, exactly asarpeggio()takes it, a plain list of pitches to voice as written: MIDI note numbers, or drum names when the pattern has adrum_note_map. A name is carried to the mirror fan-out so each device re-resolves it through its own map; one no destination maps at all is dropped (warned once), and an empty list rests.root: MIDI root note (e.g., 60 for Middle C). Required for a chord, and not used for a plain pitch list - passing it with one raises, rather than looking as though it applied.velocity: MIDI velocity (default 90), or a(low, high)tuple for a fresh random draw per chord tone (each voice gets a slightly different velocity - useful for humanising the "fingers" feel).sustain: If True, the notes last for the entire pattern duration. Mutually exclusive withlegatoanddetached.duration: Note duration in beats (default 1.0). Ignored whenlegatoordetachedis set, since those recalculate durations.inversion: Specific chord inversion (ignored if voice leading is on).count: Number of notes to play (cycles tones if higher than the chord's natural size).legato: If given, the chord rings forratioof the gap to the next attack after it (round the cycle, where it plays again, if nothing comes sooner), measured once the build is done - so a chord placed later still cuts it, and nothing else in the pattern is resized. Mutually exclusive withsustainanddetached.detached: If given, the chord rings untildetachedbeats before the next cycle - equivalent to settingduration = pattern.length - detached. Use this for a declarative polyphony-safety margin so the chord always releases before the next chord begins. Mutually exclusive withsustainandlegato.beat: Beat offset to place the chord at (default 0.0 = the start of the pattern; a negative beat counts from the end).sustainanddetachedstill measure their ring from the pattern length, not frombeat- when placing several positioned chords (e.g. over a progression) setdurationexplicitly instead.
Example:
# Ring each chord for 90% of the way to the next one
p.chord(chord, root=root, velocity=85, count=4, legato=0.9)
# Hold the chord almost the full cycle, releasing 0.25 beats
# before the next chord begins.
p.chord(chord, root=root, velocity=85, count=5, detached=0.25)
PatternBuilder.strum
PatternBuilder.strum(
chord_obj: typing.Union[subsequence.chords.Chord, str, typing.Sequence[subsequence.declarations.Pitch]],
root: typing.Optional[int] = None,
velocity: subsequence.declarations.VelocityValue = subsequence.constants.velocity.DEFAULT_CHORD_VELOCITY,
sustain: bool = False,
duration: subsequence.declarations.GateBeats = 1.0,
inversion: int = 0,
count: typing.Optional[int] = None,
spacing: subsequence.declarations.GateBeats = 0.05,
direction: subsequence.declarations.StrumDirection = 'forward',
legato: typing.Optional[subsequence.declarations.UnitInterval] = None,
detached: typing.Optional[subsequence.declarations.GateBeats] = None,
beat: subsequence.declarations.GridBeats = 0.0,
) -> PatternBuilder
Play a chord with a small time offset between each note (strum effect).
Works exactly like chord() but staggers the notes instead of
playing them simultaneously. The first note lands on beat (0 by default);
subsequent notes are delayed by spacing beats each.
Parameters
chord_obj: The chord to play (usually thechordparameter passed to your pattern function, or a name like"Cmaj7") - or, exactly asarpeggio()takes it, a plain list of pitches to voice as written: MIDI note numbers, or drum names when the pattern has adrum_note_map. A name is carried to the mirror fan-out so each device re-resolves it through its own map; one no destination maps at all is dropped (warned once), and an empty list rests.root: MIDI root note (e.g., 60 for Middle C). Required for a chord, and not used for a plain pitch list - passing it with one raises, rather than looking as though it applied.velocity: MIDI velocity (default 90), or a(low, high)tuple for a fresh random draw per strum note.sustain: If True, the notes last for the entire pattern duration. Mutually exclusive withlegatoanddetached.duration: Note duration in beats (default 1.0). Ignored whenlegatoordetachedis set, since those recalculate durations.inversion: Specific chord inversion (ignored if voice leading is on).count: Number of notes to play (cycles tones if higher than the chord's natural size).spacing: Time in beats between each note onset (default 0.05). Onsets fall on whole pulses, 24 to a beat, so a spacing below about 0.04 puts some strings together.direction:"forward"staggers the pitches in the order they were given (ascending for a chord, whose tones arrive sorted) and"reverse"staggers them backwards;"low_to_high"and"high_to_low"sort by pitch first. A guitarist's downstroke is"low_to_high"whatever order the notes were handed over in.beat: Beat offset for the first note (default 0.0); the stagger is added on top.sustain/detachedring from the pattern length, not frombeat- setdurationexplicitly when placing positioned strums.legato: If given, the strum rings as one attack: every string lasts the same, so the last lets go atratioof the gap from the first string to the next attack after the last, and the earlier strings sooner - the shapedetachedgives. Measured once the build is done, and nothing else in the pattern is resized. Mutually exclusive withsustainanddetached.detached: If given, every strum note rings with a uniform duration ofpattern.length - detached - (count - 1) * spacing. The last note ends exactlydetachedbeats before the next cycle; earlier notes end proportionally sooner, so releases are staggered in the same shape as the placements (the hand lifts the way it landed). Polyphony-safe: guarantees nothing from this strum is still sounding when the next chord begins. Mutually exclusive withsustainandlegato.
Example:
# Gentle upward strum with legato
p.strum(chord, root=52, velocity=85, spacing=0.06, legato=0.95)
# Fast downward strum
p.strum(chord, root=52, direction="reverse", spacing=0.03)
# Five-voice strum with a 0.25-beat safety gap before the
# next chord - won't exhaust polyphony on a 5-voice synth.
p.strum(chord, root=48, count=5, spacing=0.1, detached=0.25)
PatternBuilder.progression
PatternBuilder.progression(
source: subsequence.progressions.ProgressionSource,
harmonic_rhythm: subsequence.progressions.HarmonicRhythmSpec,
key: typing.Optional[str] = None,
seed: typing.Optional[int] = None,
rng: typing.Optional[random.Random] = None,
) -> subsequence.progressions.Progression
Realise a chord progression across the pattern, returning it to place yourself.
Returns a freshly realised Progression -
an iterable of (chord, start, length) events laying a progression
end-to-end across the pattern's length, each chord given a length drawn
from harmonic_rhythm (the musical term for how often the chords
change). You loop over it and play each chord however you like -
block, strummed, or arpeggiated:
for chord, start, length in p.progression("phrygian_minor",
harmonic_rhythm=between(WHOLE, 3 * WHOLE, step=WHOLE), seed=7):
p.strum(chord, root=48, beat=start, duration=length - 0.25, spacing=0.04, count=4)
This is the part-level progression seam: it re-realises a fresh value each rebuild (the breathing behaviour), runs entirely outside the global harmonic clock - so a part can inhabit its own harmonic world (polytonality) or move faster than the clock's span floor - and never advances engine state.
For a one-call block-chord part with no loop, use composition.chords().
Parameters
source: A built-in chord-graph style name (e.g."phrygian_minor") to generate a progression; an explicit element list - ints where diatonic, name or roman strings (["Cm7", 6, "bVII"]),Chordobjects - cycled to fill the pattern; or aProgressionvalue (its spans cycled, decoration preserved).harmonic_rhythm: How long each chord lasts, in beats. One of: a single number (static); a list of lengths (a shaped rhythm such as[WHOLE, HALF, HALF], cycled per chord); orbetween(low, high, step=...)for a bounded, optionally-quantised random length.key: Key for styles and key-relative elements (degrees/romans); defaults to the composition's key.seed: If given, the progression is realised from a freshRandom(seed)so it is identical on every cycle (a fixed phrase). When omitted, the pattern's own RNG is used, so it can vary per cycle (still reproducible under a composition seed).rng: Advanced determinism form - arandom.Random(wins overseed=).
Returns
subsequence.progressions.Progression: AProgressionyou can iterate as(chord, start, length)tuples (or read via.events()/print()).
PatternBuilder.broken_chord
PatternBuilder.broken_chord(
chord_obj: typing.Union[subsequence.chords.Chord, str],
root: int,
order: typing.List[int],
spacing: subsequence.declarations.GridBeats = 0.25,
velocity: subsequence.declarations.VelocityValue = subsequence.constants.velocity.DEFAULT_CHORD_VELOCITY,
duration: typing.Optional[subsequence.declarations.GateBeats] = None,
inversion: int = 0,
beat: subsequence.declarations.GridBeats = 0.0,
span: typing.Optional[subsequence.declarations.GridBeats] = None,
) -> PatternBuilder
Play a chord as an arpeggio in a specific or random order.
This generates the chord tones and maps them according to the provided
order list of indices, then delegates to arpeggio(). It is ideal
for broken chords or random chord-tone melodies.
Because the order is a list of node indices, the number of generated tones
is automatically set to max(order) + 1 to ensure all indices are valid.
Higher indices will cycle into the next octave.
Parameters
chord_obj: The chord to play (usually fromp.section.chord), or a chord name like"Cmaj7".root: MIDI root note (e.g., 60 for Middle C).order: List of indices into the chord tones array, dictating playback order.spacing: Time between each note in beats (default 0.25 = 16th note).velocity: MIDI velocity for all notes (default 90 - broken_chord is a chord voice, so it sits in the softer chord velocity bucket likechord()andstrum()), or a(low, high)tuple for a fresh random draw per note.duration: Note duration in beats. Defaults tospacing.inversion: Specific chord inversion (ignored if voice leading is on).beat: Beat to start the broken chord at (default 0.0; a negative beat counts from the end).span: How many beats to fill frombeat(default: to the end of the pattern). Likearpeggio(), use it to place a broken chord over one chord of a progression.
Example:
# A 5-note broken chord using a predefined pattern
p.broken_chord(chord, root=60, order=[4, 0, 2, 1, 3], spacing=0.25)
# A fully random broken chord using the pattern's deterministic RNG
order = list(range(5))
p.rng.shuffle(order)
p.broken_chord(chord, root=60, order=order)
PatternBuilder.swing
PatternBuilder.swing(
percent: typing.Annotated[float, subsequence.declarations.Unit(percent), subsequence.declarations.Step(1.0)] = 57.0,
grid: subsequence.declarations.GridBeats = 0.25,
strength: subsequence.declarations.UnitInterval = 1.0,
) -> PatternBuilder
Apply swing feel to all notes in the pattern, in steps of a whole pulse (a 24th of a beat).
A shortcut for p.groove(Groove.swing(percent, grid), strength). Swing is a
groove where every other grid note is delayed - the simplest way to
give a mechanical pattern a pushed, human feel.
50% is perfectly straight (no swing). 57% is the Ableton default (a gentle shuffle). 67% is classic triplet swing.
A swung note moves by whole pulses, 24 to a beat, so neighbouring
percentages often sound the same. On sixteenths (grid=0.25) a
swung pair lasts 12 pulses: 50–54 play straight, 55–62 all sound as
about 58%, 63–70 as 67% and 71–79 as 75%. On eighths (grid=0.5)
each pulse is about 4%: 53–56 sound as 54%, 57–60 as 58%, 61–64 as
62.5%, 65–68 as 67% and 69–72 as 71%. strength scales the delay
before it is rounded, so it moves in the same whole pulses.
Swing is counted from the start of the piece rather than the start of this pattern, so a three-sixteenth hat line and a one-bar kick given the same percentage swing together.
Parameters
percent: Swing amount as a percentage (50-75 is the useful range). 50 = straight, 57 = moderate shuffle, 67 ≈ triplet swing.grid: Grid size in beats (0.25 = 16th notes, 0.5 = 8th notes).strength: How much swing to apply (0.0-1.0). 0.0 = no effect, 1.0 = full swing at the given percent. Useful for dialling back the feel without changing the swing percentage.
Example:
p.hit_steps("hh", range(16), velocity=80)
p.swing(57) # gentle 16th-note shuffle
p.swing(57, strength=0.5) # half-strength - subtler feel
PatternBuilder.groove
PatternBuilder.groove(
template: subsequence.groove.Groove,
strength: float = 1.0,
) -> PatternBuilder
Apply a groove template to all notes in the pattern.
A groove is a repeating pattern of per-step timing offsets and optional velocity adjustments. It gives a pattern its characteristic rhythmic feel - swing, shuffle, MPC pocket, or any custom shape.
Construct a groove with one of the factory methods:
Groove.swing(percent)- simple swing by percentage (or use thep.swing()shortcut for common cases)Groove.from_agr(path)- import timing from an Ableton .agr fileGroove(offsets=[...], grid=0.25, velocities=[...])- fully custom
p.groove() is a post-build transform - call it after all notes
have been placed. It pairs well with p.randomize() for
structured feel plus organic micro-variation.
A groove's slots are counted from the start of the piece, not from the
start of each pattern, so one Groove given to every part swings
them all together whatever their lengths. A pattern shorter than the
groove's cycle (grid × len(offsets)) therefore plays a different
stretch of the groove each time round, which is how a long custom
groove shapes a short pattern.
Nothing plays before its own cycle begins, so a groove cannot pull a note earlier than the pattern's first pulse: in a groove that pulls notes early, a note on that first pulse stays where it is.
The verbs that read the grid - thin(), scale_velocities() and
ratchet(steps=) - still count each note as the step it was placed
on, so they can come before the groove or after it.
Parameters
template: AGrooveinstance defining the timing/velocity template.strength: How much of the groove to apply (0.0-1.0). 0.0 = no effect, 1.0 = full groove. Blends timing offsets and velocity deviation proportionally - equivalent to Ableton's TimingAmount and VelocityAmount dials.
Example:
groove = subsequence.Groove.swing(percent=57)
@composition.pattern(channel=10, beats=4)
def drums (p):
p.hit_steps("kick", [0, 8], velocity=100)
p.hit_steps("hh", range(16), velocity=80)
p.groove(groove) # full strength
p.groove(groove, strength=0.5) # half-strength blend
PatternBuilder.dropout
PatternBuilder.dropout(
probability: subsequence.declarations.UnitInterval,
seed: typing.Optional[int] = None,
rng: typing.Optional[random.Random] = None,
) -> PatternBuilder
Randomly remove notes from the pattern.
This operates on all notes currently placed in the builder.
Parameters
probability: The chance (0.0 to 1.0) of each pulse POSITION being removed - all notes sharing that position (a chord's voices, layered drums) live or die together.seed: Fix the dropout for this call (an int); omit to use the pattern's RNG.rng: Advanced determinism form - arandom.Random(wins overseed=).
PatternBuilder.velocity_shape
PatternBuilder.velocity_shape(
low: int = subsequence.constants.velocity.VELOCITY_SHAPE_LOW,
high: int = subsequence.constants.velocity.VELOCITY_SHAPE_HIGH,
) -> PatternBuilder
Apply organic velocity variation to all notes in the pattern.
Uses a van der Corput sequence to distribute velocities evenly across the specified range, which often sounds more 'human' than purely random velocity variation.
Parameters
low: Minimum velocity (default 64).high: Maximum velocity (default 127).
PatternBuilder.duck_map
PatternBuilder.duck_map(
steps: typing.Iterable[int],
floor: float = 0.0,
grid: typing.Optional[subsequence.declarations.StepCount] = None,
) -> typing.List[float]
Build a per-step velocity multiplier list for sidechain-style ducking.
Returns a list of floats, one per grid step: floor at each trigger
step in steps, 1.0 everywhere else. Pass the result to
p.data for another pattern to read, then apply with
p.scale_velocities().
Parameters
steps: Grid indices that trigger ducking (e.g. kick hit positions).floor: Multiplier written at trigger steps.0.0= full silence,1.0= no effect. Values in between give partial ducking.grid: Grid resolution (defaults top.grid).
Returns
typing.List[float]:List[float]of lengthgrid.
Example:
# Full duck on kick hits
p.data["kick_sc"] = p.duck_map(kick_steps)
# Softer duck
p.data["kick_sc"] = p.duck_map(kick_steps, floor=0.3)
# Velocity-proportional: deeper duck for harder kicks
p.data["kick_sc"] = p.duck_map(kick_steps, floor=1.0 - (velocity / 127))
PatternBuilder.build_velocity_ramp
PatternBuilder.build_velocity_ramp(
low: int,
high: int,
shape: subsequence.declarations.EasingCurve = 'linear',
grid: typing.Optional[subsequence.declarations.StepCount] = None,
) -> typing.List[int]
Build a per-step velocity list that ramps from low to high.
A musician-friendly shortcut for the common pattern of generating
a fixed-length velocity sweep using an easing curve. Returns
List[int] ready to pass directly to velocities= parameters.
Parameters
low: Velocity at the first step (0–127).high: Velocity at the last step (0–127).shape: Easing curve name (seesubsequence.easing). Common values:"linear","ease_in","ease_out","ease_in_out". Defaults to"linear".grid: Number of steps (defaults top.grid).
Returns
typing.List[int]:List[int]of lengthgrid, values clamped to 0–127.
Example:
# Snare roll that swells into a downbeat
p.sequence(
steps=range(16),
pitches="snare_1",
durations=0.1,
velocities=p.build_velocity_ramp(25, 100, "ease_in"),
)
# Fade-out ghost fill
p.ghost_fill("snare_1", 1,
velocity=p.build_velocity_ramp(80, 20, "ease_out"),
bias="sixteenths", no_overlap=True)
PatternBuilder.scale_velocities
PatternBuilder.scale_velocities(
factors: typing.Sequence[float],
grid: typing.Optional[subsequence.declarations.StepCount] = None,
) -> PatternBuilder
Scale note velocities by a per-step multiplier list.
Each note's velocity is multiplied by the factor at the corresponding
grid step index. A factor of 1.0 leaves the velocity unchanged;
0.0 silences the note; 0.5 halves it.
A note takes the factor of the step it was placed on, however far
swing(), groove() or randomize() has since moved it, so a
duck map lands on the same notes whether it comes before the feel or
after it.
Parameters
factors: Per-step multipliers, one float per grid step. Values outside[0.0, 1.0]are valid - result is clamped to[0, 127]after scaling.grid: Grid resolution (defaults top.grid). Must match the length offactors.
Returns
PatternBuilder:selffor fluent chaining.
Example:
# Sidechain ducking: silence bass on kick steps, full volume elsewhere.
kick_steps = {0, 4, 8, 12}
p.data["kick_sc"] = [0.0 if s in kick_steps else 1.0 for s in range(p.grid)]
# In the bass pattern:
p.scale_velocities(p.data.get("kick_sc", [1.0] * p.grid))
PatternBuilder.randomize
PatternBuilder.randomize(
timing: typing.Annotated[subsequence.declarations.Beats, subsequence.declarations.Step(0.01)] = 0.03,
velocity: subsequence.declarations.UnitInterval = 0.0,
seed: typing.Optional[int] = None,
rng: typing.Optional[random.Random] = None,
) -> PatternBuilder
Add random variations to note timing and velocity.
Introduces small imperfections - the micro-variations that distinguish a played performance from a perfectly quantised sequence.
Called with no arguments, only timing variation is applied (velocity defaults to 0.0 - no change). Pass a velocity value to also randomise dynamics:
# Timing only (default)
p.randomize()
# Both axes
p.randomize(timing=0.04, velocity=0.08)
# Stronger feel
p.randomize(timing=0.08, velocity=0.15)
Resolution note: the sequencer runs at 24 PPQN. At 120 BPM, one pulse ≈ 20ms. Timing shifts smaller than roughly 0.04 beats may have no audible effect because they round to zero pulses. Recommended range: timing=0.02–0.08, velocity=0.05–0.15.
When the composition has a seed set, p.rng is deterministic,
so p.randomize() produces the same result on every run.
Parameters
timing: Maximum timing offset in beats (e.g. 0.05 = ±1.2 pulses at 24 PPQN). Notes shift by a random amount within[-timing, +timing]beats. Clamped to pulse 0 at the lower bound.velocity: Maximum velocity scale factor (0.0 to 1.0). Each note's velocity is multiplied by a random value in[1 - velocity, 1 + velocity], clamped to 1–127.seed: Fix the variations for this call (an int); omit to use the pattern's RNG (seeded when the composition has a seed).rng: Advanced determinism form - arandom.Random(wins overseed=).
PatternBuilder.legato
PatternBuilder.legato(
ratio: subsequence.declarations.UnitInterval = 1.0,
) -> PatternBuilder
Adjust note durations to fill the gap until the next note.
Parameters
ratio: How much of the gap to fill (0.0 to 1.0). 1.0 is full legato, < 1.0 is staccato.
PatternBuilder.duration
PatternBuilder.duration(
beats: typing.Annotated[float, subsequence.declarations.Span(low=0.01), subsequence.declarations.Unit(beats), subsequence.declarations.Step(0.05)],
) -> PatternBuilder
Set every note's duration to a fixed length in beats.
This overrides any existing note durations, acting as a global
'gate time' relative to the beat (1.0 = a quarter note). Short
values clip notes tight; long values let them ring. For a
guaranteed gap before each next onset regardless of note spacing,
use detached; for a classic staccato articulation, either
a short fixed value (p.duration(0.1)) or p.detached() works.
Parameters
beats: Fixed note duration in beats (relative to a quarter note). 0.5 = eighth-note length, 0.25 = sixteenth-note length. Must be positive.
PatternBuilder.detached
PatternBuilder.detached(
beats: subsequence.declarations.GateBeats = 0.05,
) -> PatternBuilder
Shorten note durations so a guaranteed silence precedes the next onset.
The complement of legato. For every placed note, the duration
is shrunk so that at least beats beats of silence remain before
the next note begins (wrapping around to the first note for the last
one). Use this when you want a clean detached articulation, or as a
polyphony-safety margin between chord transitions on a monophonic or
voice-limited synth.
Parameters
beats: Minimum gap in beats before the next onset (default 0.05 - roughly 25 ms at 120 BPM). Must be positive.
Example:
# Bassline on a mono synth: each 16th note ends 0.05 beats
# before the next, so the synth never retriggers mid-note.
p.arpeggio(chord.tones(36, count=4), spacing=0.25).detached()
# Explicit larger gap for a longer release tail.
p.melody(state, spacing=0.25).detached(0.1)
PatternBuilder.snap_to_scale
PatternBuilder.snap_to_scale(
key: subsequence.declarations.KeyName,
mode: str = 'ionian',
strength: subsequence.declarations.UnitInterval = 1.0,
seed: typing.Optional[int] = None,
rng: typing.Optional[random.Random] = None,
) -> PatternBuilder
Snap all notes in the pattern to the nearest pitch in a scale.
Useful after generative or sensor-driven pitch work (random walks, mapping data values to note numbers, etc.) to ensure every note lands on a musically valid scale degree. The snap is applied in place; notes already on a scale degree are left unchanged.
When a note falls equidistant between two scale tones, the upward direction is preferred.
Parameters
key: Root note name (e.g."C","F#","Bb").mode: Scale mode. Any modescale_notesaccepts, including one added withregister_scale:"ionian"(default),"dorian","minor","harmonic_minor", etc.strength: Probability that each note is snapped (0.0–1.0). At 1.0 (default), every note snaps to the scale. At 0.0, no notes are affected. Values in between create melodies that are mostly in key with occasional chromatic passing tones. Uses the pattern's seeded RNG for reproducibility.seed: Fix the partial-strength snapping for this call (an int); omit to use the pattern's RNG.rng: Advanced determinism form - arandom.Random(wins overseed=).
Example
@composition.pattern(channel=1, beats=4)
def melody (p):
for beat in range(16):
pitch = 60 + random.randint(-5, 5)
p.note(pitch, beat=beat * 0.25)
p.snap_to_scale("G", "dorian", strength=0.8)
PatternBuilder.apply_tuning
PatternBuilder.apply_tuning(
tuning: subsequence.tuning.Tuning,
bend_range: float = 2.0,
channels: typing.Optional[typing.List[int]] = None,
reference_note: int = 60,
) -> PatternBuilder
Apply a microtonal tuning to this pattern via pitch bend injection.
For each note in the pattern, the nearest 12-TET MIDI pitch is
computed and a pitchwheel CcEvent is injected at the note's onset
to shift the synthesiser to the exact tuned frequency. Other pitch
bends (from p.portamento(), p.slide(), etc.) are shifted
additively so they still work correctly within the tuned pitch space.
The tuning is applied when the build finishes, after the notes have
reached their final places and any glides have been laid, so it can be
called anywhere in the builder: before or after p.groove(),
p.slide() or anything else.
For polyphonic patterns, supply a channels pool. Notes will be
spread across those MIDI channels so each can carry an independent pitch
bend. For monophonic patterns, leave channels=None.
The synthesiser's pitch-bend range must match bend_range. Most
synths default to ±2 semitones. For tunings that deviate more than
one semitone from 12-TET, increase bend_range (e.g., 12 or 24)
and configure the synth to match.
Parameters
tuning: TheTuningto apply.bend_range: Synth pitch-bend range in semitones (default ±2).channels: MIDI channel pool for polyphonic rotation, numbered like every other MIDI channel: 1-16, or 0-15 when the composition was made withzero_indexed_channels=True. The part plays through the pool, its notes rotating when they overlap and otherwise sitting on the pool's first MIDI channel.Nonekeeps all notes on the pattern's own MIDI channel.reference_note: MIDI note number that maps to scale degree 0. Default 60 (middle C).
Example
from subsequence import Tuning
meantone = Tuning.from_scl("meanquar.scl")
@composition.pattern(channel=1, beats=4)
def melody (p):
p.seq("x x x x", pitch=60)
p.apply_tuning(meantone, bend_range=2.0)
PatternBuilder.reverse
PatternBuilder.reverse() -> PatternBuilder
Flip the pattern backwards in time (retrograde).
PatternBuilder.stretch
PatternBuilder.stretch(
factor: typing.Annotated[float, subsequence.declarations.Span(low=0.01)],
) -> PatternBuilder
Stretch the pattern in time, scaling note positions and durations.
stretch(2.0) makes everything twice as long (half speed) - what
theorists call augmentation; stretch(0.5) squeezes the pattern
into half the time (double speed) - diminution. Any positive
factor works: stretch(2/3) compresses a dotted feel into
straight time, for example.
Notes whose start lands past the end of the pattern are dropped,
and compression leaves the freed space empty - the pattern is not
tiled to fill it. Durations scale without clipping, so a stretched
note may ring past the pattern's end exactly like a legato note,
and stretch(1.0) is a true no-op. Positions and durations
truncate to the pulse grid (matching note()'s beat-to-pulse
truncation).
Parameters
factor: Time multiplier. Greater than 1.0 slows the pattern down, less than 1.0 speeds it up. Must be positive.
PatternBuilder.rotate
PatternBuilder.rotate(
steps: subsequence.declarations.StepCount,
grid: typing.Optional[subsequence.declarations.StepCount] = None,
) -> PatternBuilder
Rotate the pattern by a number of grid steps, wrapping around.
Notes pushed past the end of the pattern re-enter at the start (and vice versa for negative values) - the step-sequencer rotation familiar from Euclidean rhythm tools.
Parameters
steps: Positive values rotate later in time, negative values earlier.grid: The grid resolution. Defaults to the pattern'sdefault_grid(derived from the decorator'sbeats/stepsandstep_duration).
PatternBuilder.transpose
PatternBuilder.transpose(
semitones: typing.Annotated[int, subsequence.declarations.Unit(semitones)],
within: typing.Optional[subsequence.declarations.PitchRange] = None,
) -> PatternBuilder
Shift all note pitches up or down.
Parameters
semitones: Positive for up, negative for down.within:(low, high)- notes moved outside this range are removed rather than pinned to its edge. Omit it and pitches clamp to 0-127 as they always have.
An instrument's reach is usually narrower than MIDI's. A Minitaur sounds notes 0-72, and a note transposed past that is silent on the instrument - so clamping it to 72 sounds a note nobody asked for, piling voices onto the top note (#2464). Dropping is the honest answer, and a position left with no notes goes with them.
Example
# Move the part up an octave, losing whatever the synth cannot reach
p.transpose(12, within=(0, 72))
PatternBuilder.invert
PatternBuilder.invert(pivot: int = 60) -> PatternBuilder
Invert all pitches around a pivot note.
PatternBuilder.every
PatternBuilder.every(
n: int,
fn: typing.Callable[[PatternBuilder], None],
) -> PatternBuilder
Apply a transformation every Nth cycle.
A cycle is one pass of this pattern, which is the same thing as a bar
only when the pattern is one bar long: a two-bar pattern calling
every(4, ...) fires every eight bars. To count bars, use
bar_cycle.
Parameters
n: How many cycles between applications (e.g. 4 = every 4th cycle).fn: A function (often a lambda) that receives the builder and calls further methods.
Example
# Reverse every 4th cycle
p.every(4, lambda p: p.reverse())
PatternBuilder.bar_cycle
PatternBuilder.bar_cycle(length: int) -> BarCycle
Return the current bar's position within a repeating cycle of bars.
A thin wrapper around p.bar % length that replaces opaque modulo
arithmetic with readable, musician-friendly properties.
Parameters
length: The cycle length in bars (e.g., 4, 8, 16).
Returns
- A
BarCyclewith.bar,.first,.last, and.progressproperties.
Example
# Every 4 bars (replaces: if p.bar % 4 == 0)
if p.bar_cycle(4).first:
p.hit_steps("snare_1", [0, 8], velocity=110)
# Last bar of every 16-bar cycle (replaces: if p.bar % 16 == 15)
if p.bar_cycle(16).last:
p.euclidean("hi_hat_open", 3)
# Build intensity over an 8-bar arc
intensity = p.bar_cycle(8).progress # 0.0 → 0.875
p.velocity_shape(low=int(40 + 40 * intensity), high=100)
PatternBuilder.cc
PatternBuilder.cc(
control: typing.Union[int, str],
value: int,
beat: subsequence.declarations.GridBeats = 0.0,
) -> subsequence.pattern_builder.PatternBuilder
Send a single CC message at a beat position.
Parameters
control: MIDI CC number (0–127), or a string name resolved via the pattern'scc_name_map. A number outside 0–127 raises, naming it: MIDI cannot carry it, so it would have been dropped at every send (#3004).value: CC value (0–127); out-of-range values are clamped, as on every sibling verb - a computed value running past an end is a controller reaching its limit, not a mistake.beat: Beat position within the pattern.
PatternBuilder.cc_ramp
PatternBuilder.cc_ramp(
control: typing.Union[int, str],
start: int,
end: int,
beat_start: float = 0.0,
beat_end: typing.Optional[float] = None,
resolution: int = 1,
shape: typing.Union[subsequence.declarations.EasingCurve, subsequence.easing.EasingFn] = 'linear',
) -> subsequence.pattern_builder.PatternBuilder
Interpolate a CC value over a beat range.
Parameters
control: MIDI CC number (0–127), or a string name resolved via the pattern'scc_name_map.start: Starting CC value (0–127).end: Ending CC value (0–127).beat_start: Beat position to begin the ramp.beat_end: Beat position to end the ramp. Defaults to pattern length.resolution: Pulses between CC messages (1 = every pulse, ~20ms at 120 BPM). Higher values (e.g. 2 or 4) reduce MIDI traffic density but may sound stepped at slow tempos.shape: Easing curve - a name string (e.g."exponential") or any callable that maps [0, 1] → [0, 1]. Defaults to"linear". Seesubsequence.easingfor available shapes.
PatternBuilder.pitch_bend
PatternBuilder.pitch_bend(
value: float,
beat: subsequence.declarations.GridBeats = 0.0,
) -> subsequence.pattern_builder.PatternBuilder
Send a single pitch bend message at a beat position.
Parameters
value: Pitch bend amount, normalised from -1.0 to 1.0.beat: Beat position within the pattern.
PatternBuilder.pitch_bend_ramp
PatternBuilder.pitch_bend_ramp(
start: float,
end: float,
beat_start: float = 0.0,
beat_end: typing.Optional[float] = None,
resolution: int = 1,
shape: typing.Union[subsequence.declarations.EasingCurve, subsequence.easing.EasingFn] = 'linear',
) -> subsequence.pattern_builder.PatternBuilder
Interpolate pitch bend over a beat range.
Parameters
start: Starting pitch bend (-1.0 to 1.0).end: Ending pitch bend (-1.0 to 1.0).beat_start: Beat position to begin the ramp.beat_end: Beat position to end the ramp. Defaults to pattern length.resolution: Pulses between pitch bend messages (1 = every pulse). Higher values (e.g. 2 or 4) reduce MIDI traffic density but may sound stepped at slow tempos.shape: Easing curve - a name string (e.g."ease_out") or any callable that maps [0, 1] → [0, 1]. Defaults to"linear". Seesubsequence.easingfor available shapes.
PatternBuilder.nrpn
PatternBuilder.nrpn(
parameter: typing.Union[int, str],
value: int,
beat: subsequence.declarations.GridBeats = 0.0,
fine: bool = False,
null_reset: bool = True,
) -> subsequence.pattern_builder.PatternBuilder
Send a single NRPN parameter write at a beat position.
NRPN (Non-Registered Parameter Number) addresses synth-specific
parameters that don't fit into the 128 standard CC slots - Sequential,
Korg, Roland, Elektron and others use it heavily for filter cutoff,
envelope amounts, oscillator detune, and similar deep parameters.
Many such parameters need values beyond 0–127 (e.g. 0–1023, 0–254);
set fine=True for full 14-bit precision.
Emitted on the pattern's MIDI channel. To target a different MIDI channel
(e.g. a per-channel RPN config), define a separate pattern on that
MIDI channel or use composition.trigger(channel=…) for a one-shot.
Parameters
parameter: 14-bit NRPN parameter number (0–16383), or a string resolved via the pattern'snrpn_name_map.value: Parameter value. 0–127 iffine=False; 0–16383 iffine=True.beat: Beat position within the pattern.fine: If True, send 14-bit value via Data Entry MSB+LSB (CC 6 + CC 38). If False (default), send only Data Entry MSB - sufficient for the common 0–127 range.null_reset: If True (default), follow with the RPN null sentinel to deselect the active parameter and prevent stray later CC 6 / 38 messages from hitting it.
Example
# Sequential Take 5 fine-tune (14-bit, range 0–1400)
p.nrpn(9, 700, fine=True)
# Roland JV-1080 reverb level (7-bit)
p.nrpn(0x0140, 80)
PatternBuilder.rpn
PatternBuilder.rpn(
parameter: typing.Union[int, subsequence.declarations.RpnParameter],
value: int,
beat: subsequence.declarations.GridBeats = 0.0,
fine: bool = False,
null_reset: bool = True,
) -> subsequence.pattern_builder.PatternBuilder
Send a single RPN parameter write at a beat position.
RPN (Registered Parameter Number) addresses the small standardised
set of parameters defined by the MIDI specification - pitch bend
range, master tuning, modulation depth - supported by virtually any
MIDI synth. String names resolve via pymididefs.rpn.RPN_MAP
out of the box, no map needed.
Standard RPN names: pitch_bend_sensitivity,
channel_fine_tuning, channel_coarse_tuning,
tuning_program_select, tuning_bank_select,
modulation_depth_range.
Emitted on the pattern's MIDI channel.
Parameters
parameter: 14-bit RPN parameter number (0–16383), or one of the standard string names above.value: Parameter value. 0–127 iffine=False; 0–16383 iffine=True. Pitch bend sensitivity uses MSB = semitones and LSB = cents, so setfine=Truefor sub-semitone control.beat: Beat position within the pattern.fine: If True, send 14-bit value via Data Entry MSB+LSB.null_reset: If True (default), follow with the RPN null sentinel.
Example
# Set pitch bend range to ±12 semitones
p.rpn("pitch_bend_sensitivity", 12)
# 4 semitones plus 50 cents
p.rpn("pitch_bend_sensitivity", 4 * 128 + 50, fine=True)
PatternBuilder.nrpn_ramp
PatternBuilder.nrpn_ramp(
parameter: typing.Union[int, str],
start: int,
end: int,
beat_start: float = 0.0,
beat_end: typing.Optional[float] = None,
resolution: int = 4,
shape: typing.Union[subsequence.declarations.EasingCurve, subsequence.easing.EasingFn] = 'linear',
fine: bool = True,
null_reset: bool = True,
) -> subsequence.pattern_builder.PatternBuilder
Interpolate an NRPN value over a beat range.
The parameter is selected once at beat_start; subsequent steps
emit only Data Entry messages. Synths track the most recently
selected NRPN per the spec, so re-selecting per step would just
waste bandwidth. If null_reset=True the RPN null sentinel is
appended once at beat_end.
Another ramp or one-shot in the window is safe (#3070). A second
nrpn_ramp, an rpn_ramp, or a one-shot nrpn()/rpn()
takes the MIDI channel's selection, which used to redirect every later step
of this ramp - a one-shot's default null_reset sent them to the NULL
parameter, where they did nothing at all. The end of the build now
re-selects wherever the selection has drifted, and only there, so a ramp
on its own still emits exactly the messages described above.
What it cannot see: a plain p.cc(6, …) or p.cc(38, …) on
this MIDI channel, which is you addressing whatever was last selected and is
left alone deliberately; and another pattern writing NRPN to the same
MIDI channel, which is outside this builder entirely.
Bandwidth note: with fine=True (default) every step emits two
CCs. Default resolution=4 is one update every four pulses
(~83 ms at 120 BPM, where one pulse is ~21 ms), which keeps the bus
lightly loaded. Increase
resolution (e.g. 8) on slow DIN-MIDI links if you hear
other messages getting delayed.
Emitted on the pattern's MIDI channel.
Parameters
parameter: 14-bit NRPN parameter number, or a string resolved via the pattern'snrpn_name_map.start: Starting value (0–16383 whenfine=True, 0–127 when False).end: Ending value.beat_start: Beat position to begin the ramp.beat_end: Beat position to end the ramp. Defaults to pattern length.resolution: Pulses between Data Entry messages (default 4).shape: Easing curve - string name or callable [0, 1] → [0, 1].fine: If True (default), use full 14-bit Data Entry MSB+LSB.null_reset: If True (default), append the null sentinel at the end of the ramp (not per step).
PatternBuilder.rpn_ramp
PatternBuilder.rpn_ramp(
parameter: typing.Union[int, subsequence.declarations.RpnParameter],
start: int,
end: int,
beat_start: float = 0.0,
beat_end: typing.Optional[float] = None,
resolution: int = 4,
shape: typing.Union[subsequence.declarations.EasingCurve, subsequence.easing.EasingFn] = 'linear',
fine: bool = True,
null_reset: bool = True,
) -> subsequence.pattern_builder.PatternBuilder
Interpolate an RPN value over a beat range.
Identical to nrpn_ramp but uses CC 101 / 100 for parameter
selection. String names resolve via pymididefs.rpn.RPN_MAP.
Another ramp or one-shot in the window is safe for the same reason
(#3070); a plain p.cc(6, …) on this MIDI channel is still yours to keep
track of.
PatternBuilder.program_change
PatternBuilder.program_change(
program: int,
beat: subsequence.declarations.GridBeats = 0.0,
bank_msb: typing.Optional[int] = None,
bank_lsb: typing.Optional[int] = None,
) -> subsequence.pattern_builder.PatternBuilder
Send a Program Change message, optionally preceded by bank select.
Switches the instrument patch on this pattern's MIDI channel. Program numbers follow the General MIDI numbering (0–127, where e.g. 0 = Acoustic Grand Piano, 40 = Violin, 33 = Electric Bass).
To select a patch in a specific bank, provide bank_msb and/or
bank_lsb. The bank select CC messages (CC 0 for MSB, CC 32 for
LSB) are sent at the same beat position immediately before the
program change, in the order the synthesiser expects. All of them
reach the synthesiser before any note starting on the same beat, so
that note already plays with the new patch.
Parameters
program: Program (patch) number (0–127).beat: Beat position within the pattern (default 0.0).bank_msb: Bank select coarse (CC 0), 0–127.None= omit.bank_lsb: Bank select fine (CC 32), 0–127.None= omit.
Example
@composition.pattern(channel=1, beats=4)
def strings (p):
# GM - no bank needed
p.program_change(48)
# Roland JV-1080 bank 1, patch 48
p.program_change(48, bank_msb=81, bank_lsb=0)
# Change patch only at the first bar of each section
if p.section.bar == 0:
p.program_change(48, bank_msb=1)
PatternBuilder.sysex
PatternBuilder.sysex(
data: typing.Union[bytes, typing.List[int]],
beat: subsequence.declarations.GridBeats = 0.0,
) -> subsequence.pattern_builder.PatternBuilder
Send a System Exclusive (SysEx) message at a beat position.
SysEx messages allow deep integration with synthesisers and other
hardware: patch dumps, parameter control, and vendor-specific commands.
The data argument should contain only the inner payload bytes,
without the surrounding 0xF0 / 0xF7 framing - mido adds those
automatically.
Parameters
data: SysEx payload asbytesor a list of integers (0–127).beat: Beat position within the pattern (default 0.0).
Example
# GM System On - reset a GM-compatible device to defaults
p.sysex([0x7E, 0x7F, 0x09, 0x01])
PatternBuilder.osc
PatternBuilder.osc(
address: str,
*args: typing.Any,
beat: subsequence.declarations.GridBeats = 0.0,
) -> subsequence.pattern_builder.PatternBuilder
Send an OSC message at a beat position.
Requires composition.osc() to be called before composition.play().
If no OSC server is configured the event is silently dropped.
Parameters
address: OSC address path (e.g."/mixer/fader/1").*args: OSC arguments - float, int, str, or bytes.beat: Beat position within the pattern (default 0.0).
Example
# Enable a chorus effect at beat 2
p.osc("/fx/chorus/enable", 1, beat=2.0)
# Set a mixer pan value immediately
p.osc("/mixer/pan/1", -0.5)
PatternBuilder.osc_ramp
PatternBuilder.osc_ramp(
address: str,
start: float,
end: float,
beat_start: float = 0.0,
beat_end: typing.Optional[float] = None,
resolution: int = 4,
shape: typing.Union[subsequence.declarations.EasingCurve, subsequence.easing.EasingFn] = 'linear',
) -> subsequence.pattern_builder.PatternBuilder
Interpolate an OSC float value over a beat range.
Generates one OSC message per resolution pulses, sending the
interpolated value to address at each step. Useful for smoothly
automating mixer faders, effect parameters, and other continuous controls
on a remote machine.
Requires composition.osc() to be called before composition.play().
If no OSC server is configured the events are silently dropped.
Parameters
address: OSC address path (e.g."/mixer/fader/1").start: Starting float value.end: Ending float value.beat_start: Beat position to begin the ramp (default 0.0).beat_end: Beat position to end the ramp. Defaults to pattern length.resolution: Pulses between OSC messages (default 4 - approximately 6 messages per beat at 120 BPM, which is smooth for fader automation while keeping UDP traffic light). Useresolution=1for pulse-level precision.shape: Easing curve - a name string (e.g."ease_in") or any callable that maps [0, 1] → [0, 1]. Defaults to"linear". Seesubsequence.easingfor available shapes.
Example
# Fade a mixer fader up over 4 beats
p.osc_ramp("/mixer/fader/1", start=0.0, end=1.0)
# Ease in a reverb send over the last 2 beats
p.osc_ramp("/fx/reverb/wet", 0.0, 0.8, beat_start=2, beat_end=4, shape="ease_in")
PatternBuilder.bend
PatternBuilder.bend(
note: int,
amount: float,
start: float = 0.0,
end: float = 1.0,
shape: typing.Union[subsequence.declarations.EasingCurve, subsequence.easing.EasingFn] = 'linear',
resolution: int = 1,
) -> subsequence.pattern_builder.PatternBuilder
Bend a specific note by index.
Generates a pitch bend ramp that covers a fraction of the target note's duration, then resets to 0.0 at the next note's onset.
The bend is laid when the build finishes, against the notes where they
finally sit, so it can be called anywhere in the builder: before or
after legato(), groove() or any other transform. The index
counts the notes as they finally play.
Parameters
note: Note index (0 = first, -1 = last, etc.). If this cycle has no such note, nothing is bent, and a warning says so once.amount: Target bend normalised to -1.0..1.0 (positive = up). With a standard ±2-semitone pitch wheel range, 0.5 = 1 semitone.start: Fraction of the note's duration at which the ramp begins (0.0 = note onset, default).end: Fraction of the note's duration at which the ramp ends (1.0 = note end, default). A note that rings on past the next one's onset counts only its time before it, since the next note resets the pitch wheel they share.shape: Easing curve - a name string (e.g."ease_in") or any callable mapping [0, 1] → [0, 1]. Defaults to"linear".resolution: Pulses between pitch bend messages.
Example
p.sequence(steps=[0, 4, 8, 12], pitches=midi_notes.E1)
p.legato(0.95)
# Bend the last note up one semitone (with ±2 st range), easing in
p.bend(note=-1, amount=0.5, shape="ease_in")
# Bend the second note down, starting halfway through
p.bend(note=1, amount=-0.3, start=0.5)
PatternBuilder.portamento
PatternBuilder.portamento(
time: float = 0.15,
shape: typing.Union[subsequence.declarations.EasingCurve, subsequence.easing.EasingFn] = 'linear',
resolution: int = 1,
bend_range: typing.Optional[float] = 2.0,
wrap: bool = True,
) -> subsequence.pattern_builder.PatternBuilder
Glide between all consecutive notes using pitch bend.
Generates a pitch bend ramp in the tail of each note, bending toward the next note's pitch, then resets at the next note's onset.
The glides are laid when the build finishes, against the notes where
they finally sit, so this can be called anywhere in the builder: before
or after legato(), groove() or any other transform. Each glide
ends on the next note's actual onset, swung or not.
Most effective on mono instruments where pitch bend is per-channel.
Parameters
time: Fraction of each note's duration used for the glide (default 0.15 - last 15% of the note).shape: Easing curve. Defaults to"linear".resolution: Pulses between pitch bend messages.bend_range: Instrument's pitch wheel range in semitones (default 2.0 - standard ±2 st). Pairs with intervals larger than this value are skipped. PassNoneto disable range checking and always generate the bend (large intervals are clamped to ±1.0).wrap: IfTrue(default), glide from the last note toward the first note of the next cycle.
Example
p.sequence(steps=[0, 4, 8, 12], pitches=[40, 42, 40, 43])
p.legato(0.95)
# Gentle glide across all note transitions
p.portamento(time=0.15, shape="ease_in_out")
# Wide bend range (synth set to ±12 semitones)
p.portamento(time=0.2, bend_range=12)
# No range limit - bend as far as MIDI allows
p.portamento(time=0.1, bend_range=None)
PatternBuilder.slide
PatternBuilder.slide(
notes: typing.Optional[typing.List[int]] = None,
steps: typing.Optional[typing.List[int]] = None,
time: float = 0.15,
shape: typing.Union[subsequence.declarations.EasingCurve, subsequence.easing.EasingFn] = 'linear',
resolution: int = 1,
bend_range: typing.Optional[float] = 2.0,
wrap: bool = True,
extend: bool = True,
) -> subsequence.pattern_builder.PatternBuilder
TB-303-style selective slide into specific notes.
Like portamento() but only applies to flagged destination notes.
Specify target notes by index (notes=[1, 3]) or by step grid
position (steps=[4, 12]). If extend=True (default) the
preceding note's duration is extended to meet the slide target, matching
the 303's behaviour where slide notes do not retrigger.
The slides are laid when the build finishes, against the notes where
they finally sit, so this can be called anywhere in the builder: before
or after legato(), groove() or any other transform. Each glide
ends on its target's actual onset, swung or not.
A target with no note (an index past the last note, or a step no note falls on) is skipped, so a bar that comes out sparse still plays. If none of the named targets has a note, a warning says so once.
Parameters
notes: List of note indices to slide into (0 = first), counting the notes as they finally play. Supports negative indexing. Mutually exclusive with steps.steps: List of step grid indices to slide into. A step's note is found where swing or a groove moved it, from a quarter of a step early to half a step late. Mutually exclusive with notes.time: Fraction of the preceding note's duration used for the glide.shape: Easing curve. Defaults to"linear".resolution: Pulses between pitch bend messages.bend_range: Instrument's pitch wheel range in semitones (default 2.0). Pairs with larger intervals are skipped. PassNoneto disable range checking.wrap: IfTrue(default), include a wrap-around slide from the last note back toward the first.extend: IfTrue(default), extend the preceding note's duration to reach the slide target's onset - 303-style legato through the glide.
Raises
ValueError: If neither or both of notes and steps are provided.
Example
p.sequence(steps=[0, 4, 8, 12], pitches=[40, 42, 40, 43])
p.legato(0.95)
# Slide into the 2nd and 4th notes
p.slide(notes=[1, 3], time=0.2, shape="ease_in")
# Same using step grid indices
p.slide(steps=[4, 12], time=0.2, shape="ease_in")
# Slide without extending the preceding note
p.slide(notes=[1, 3], extend=False)
PatternBuilder.euclidean
PatternBuilder.euclidean(
pitch: subsequence.declarations.Pitch,
pulses: int,
velocity: subsequence.declarations.VelocityValue = subsequence.constants.velocity.DEFAULT_VELOCITY,
duration: subsequence.declarations.GateBeats = 0.1,
probability: subsequence.declarations.UnitInterval = 1.0,
no_overlap: bool = False,
seed: typing.Optional[int] = None,
rng: typing.Optional[random.Random] = None,
) -> subsequence.pattern_builder.PatternBuilder
Generate a Euclidean rhythm.
This distributes a fixed number of 'pulses' as evenly as possible across the pattern. This produces many of the world's most common musical rhythms.
Parameters
pitch: MIDI note or drum name.pulses: Total number of notes to place.velocity: MIDI velocity, or a(low, high)tuple for a fresh random draw per hit.duration: Note duration.probability: Chance (0.0–1.0) that each pulse plays - 1.0 places them all, lower thins the rhythm.seed: Fix the thinning for this call (an int); omit to use the pattern's RNG.rng: Advanced determinism form - arandom.Random(wins overseed=).no_overlap: If True, skip steps where a note of the same pitch already exists. Useful for layering ghost notes around hand-placed anchors.
Example
# A classic 3-against-16 rhythm
p.euclidean("kick", pulses=3)
PatternBuilder.bresenham
PatternBuilder.bresenham(
pitch: subsequence.declarations.Pitch,
pulses: int,
velocity: subsequence.declarations.VelocityValue = subsequence.constants.velocity.DEFAULT_VELOCITY,
duration: subsequence.declarations.GateBeats = 0.1,
probability: subsequence.declarations.UnitInterval = 1.0,
no_overlap: bool = False,
seed: typing.Optional[int] = None,
rng: typing.Optional[random.Random] = None,
) -> subsequence.pattern_builder.PatternBuilder
Generate a rhythm using the Bresenham line algorithm.
This is an alternative to Euclidean rhythms that often results in slightly different (but still mathematically even) distributions.
Parameters
pitch: MIDI note or drum name.pulses: Total number of notes to place.velocity: MIDI velocity, or a(low, high)tuple for a fresh random draw per hit.duration: Note duration.probability: Chance (0.0–1.0) that each pulse plays - 1.0 places them all, lower thins the rhythm.seed: Fix the thinning for this call (an int); omit to use the pattern's RNG.rng: Advanced determinism form - arandom.Random(wins overseed=).no_overlap: If True, skip steps where a note of the same pitch already exists. Useful for layering ghost notes around hand-placed anchors.
PatternBuilder.bresenham_poly
PatternBuilder.bresenham_poly(
parts: typing.Dict[typing.Union[int, str], float],
velocity: typing.Union[int, typing.Dict[typing.Union[int, str], int]] = subsequence.constants.velocity.DEFAULT_VELOCITY,
duration: subsequence.declarations.GateBeats = 0.1,
grid: typing.Optional[subsequence.declarations.StepCount] = None,
probability: subsequence.declarations.UnitInterval = 1.0,
no_overlap: bool = False,
seed: typing.Optional[int] = None,
rng: typing.Optional[random.Random] = None,
) -> subsequence.pattern_builder.PatternBuilder
Distribute multiple drum voices across the pattern using weighted Bresenham.
Each step is assigned to exactly one voice - voices never overlap, producing
interlocking rhythmic patterns. Density weights control how frequently each
voice fires. If the weights sum to less than 1.0, the remainder becomes
evenly-distributed rests (silent steps). Weights adding up to more than
1.0 are scaled down in proportion, so every voice keeps its share of the
steps: {"kick_1": 1.0, "hi_hat_closed": 1.0, "snare_1": 0.1} still
plays its snare.
Because notes are placed via self.note(), all post-placement transforms
(groove, randomize, velocity_shape, rotate, etc.) work normally.
Parameters
parts: Mapping of pitch (MIDI note or drum name) to density weight. Higher weight means more hits per bar. Weights in the range (0, 1] are typical; a weight of 0.5 targets roughly one hit every two steps while the weights add up to 1 or less.velocity: Either a single MIDI velocity applied to all voices, or a dict mapping each pitch to its own velocity. Pitches absent from the dict fall back to the default velocity (100).duration: Note duration in beats (default 0.1).grid: Number of steps to divide the pattern into. Defaults to the pattern'sdefault_grid.probability: Chance (0.0–1.0) that each hit plays - 1.0 places them all, lower thins.seed: Fix the thinning for this call (an int); omit to use the pattern's RNG.no_overlap: If True, skip steps where a note of the same pitch already exists. Useful for layering ghost notes around hand-placed anchors.rng: Advanced determinism form - arandom.Random(wins overseed=).
Example
p.bresenham_poly(
parts={"kick_1": 0.25, "snare_1": 0.125, "hi_hat_closed": 0.5},
velocity={"kick_1": 100, "snare_1": 90, "hi_hat_closed": 70},
)
Layering with hand-placed hits
# Algorithmic base - interlocking texture, no overlaps within this layer
p.bresenham_poly(
parts={"hi_hat_closed": 0.5, "snare_2": 0.1},
velocity={"hi_hat_closed": 65, "snare_2": 40},
)
# Hand-placed anchors on top - these CAN overlap the algorithmic layer
p.hit_steps("kick_1", [0, 8], velocity=110)
p.hit_steps("snare_1", [4, 12], velocity=100)
Stable vs shifting patterns
Because the algorithm redistributes all positions when weights change, a single voice with a continuously ramping density will shift positions every bar. This is great for background texture (hats, shakers) but can sound jarring for prominent, distinctive sounds (claps, cowbells).
For stable patterns - use bresenham() with integer pulses.
Positions stay fixed until the pulse count steps up:
pulses = max(1, round(density * 16))
p.bresenham("hand_clap", pulses=pulses, velocity=95)
For shifting texture - use bresenham_poly() with continuous
density. Positions evolve every bar:
p.bresenham_poly(parts={"hi_hat_closed": density}, velocity=70)
To stabilise a solo voice - pair it with a second voice. More voices in a single call means less positional shift per voice:
p.bresenham_poly(
parts={"hand_clap": 0.12, "snare_2": 0.08},
velocity={"hand_clap": 95, "snare_2": 40},
)
PatternBuilder.build_ghost_bias
staticmethod PatternBuilder.build_ghost_bias(
grid: int,
bias: subsequence.declarations.BiasCurve,
beats: float = 4,
) -> typing.List[float]
Build probability weights for ghost notes or other generative functions.
Generates a list of probability weights (values between 0.0 and 1.0) spanning a given grid size. These curves shape probability over a beat, assigning higher or lower chances of an event occurring based on the rhythmic position within the beat (downbeat, offbeat, syncopated 16th note, etc).
This is a public escape hatch: call it yourself, manipulate the returned list,
then pass the result as bias= to ghost_fill(). This lets you pin
specific steps, boost a weak position, or combine two named curves.
Parameters
-
grid: The total number of steps in the sequence (usually 16 or 32). -
bias: The probability distribution shape to generate:"uniform"- 1.0 everywhere."offbeat"- 1.0 on 8th note off-beats (&), 0.3 on 16ths (e/a), 0.05 on downbeats."sixteenths"- 1.0 on 16th notes (e/a), 0.3 on 8th off-beats (&), 0.05 on downbeats."before"- 1.0 preceding a beat, 0.25 on other 16ths, 0.05 on beats."after"- 1.0 following a beat, 0.25 on other 16ths, 0.05 on beats."downbeat"- 1.0 on downbeats, 0.15 on 8th off-beats, 0.05 on other 16ths."upbeat"- 1.0 on 8th note off-beats only, 0.05 everywhere else."e_and_a"- 1.0 on all non-downbeat 16th positions, 0.05 on downbeats.
-
beats: How many beats the grid spans, which sets where each beat falls (default 4).ghost_fill()andthin()pass their pattern's own length, so this matters only when building a curve yourself for a pattern that is not four beats long.
Returns
typing.List[float]: AList[float]of lengthgridwhere each value is a probability multiplier from 0.0 to 1.0. The list is a plain Python list - modify it freely before passing toghost_fill(bias=...).
Example
# Start from a named curve, then zero out beat 3 (step 8) entirely
# and give the step before the snare (step 11) maximum weight.
weights = p.build_ghost_bias(16, "sixteenths")
weights[8] = 0.0 # silence around beat 3
weights[11] = 1.0 # boost the "and" before beat 4
p.ghost_fill("snare_1", density=0.25, velocity=(25, 45),
bias=weights, no_overlap=True)
PatternBuilder.ghost_fill
PatternBuilder.ghost_fill(
pitch: subsequence.declarations.Pitch,
density: subsequence.declarations.UnitInterval = 0.3,
velocity: subsequence.declarations.VelocityValue = subsequence.constants.velocity.GHOST_FILL_VELOCITY,
velocities: typing.Optional[typing.Union[int, typing.Tuple[int, int], typing.Sequence[typing.Union[int, float]], typing.Callable[[int], typing.Union[int, float]]]] = None,
bias: typing.Union[subsequence.declarations.BiasCurve, typing.List[float]] = 'uniform',
no_overlap: bool = True,
grid: typing.Optional[subsequence.declarations.StepCount] = None,
duration: subsequence.declarations.GateBeats = 0.1,
seed: typing.Optional[int] = None,
rng: typing.Optional[random.Random] = None,
) -> subsequence.pattern_builder.PatternBuilder
Fill the pattern with probability-biased ghost notes.
A single method for generating musically-aware ghost note layers. Combines density control, velocity randomisation, and rhythmic bias to produce the micro-detail layering heard in dense electronic music production.
Parameters
-
pitch: MIDI note number or drum name. -
density: Overall density (0.0–1.0). How many available steps receive ghost notes. 0.3 = roughly 30% of steps at peak bias. -
velocity: Single velocity, or a(low, high)range drawn per note. A two-element list means the same range, since JSON has no tuple. -
velocities: One value per step, as a list read by step index or a callable taking the step indexi. Allows dynamic values like Perlin noise curves. It also takes whatvelocitytakes: one value, or a(low, high)tuple drawn per note (a two-element list here is one value per step). Wins overvelocity. -
bias: Probability distribution shape:"uniform"- equal probability everywhere"offbeat"- prefer 8th-note off-beats (&)"sixteenths"- prefer 16th-note subdivisions (e/a)"before"- cluster just before beat positions"after"- cluster just after beat positions"downbeat"- reinforce the beat (inverse of offbeat)"upbeat"- strictly 8th-note off-beats only"e_and_a"- all non-downbeat 16th positions- Or: a list of floats (one per grid step) for a custom field.
Use
build_ghost_biasto generate a named curve and then modify specific steps before passing it here.
-
no_overlap: If True (default), skip where same pitch already exists. Essential for layering ghosts around hand-placed anchors. -
grid: Grid resolution. Defaults to the pattern's default grid. -
duration: Note duration in beats (default 0.1). -
seed: Fix the ghost layer for this call (an int); omit to use the pattern's RNG.Tip - freeze the layer each cycle:
seed=starts a fresh random stream on every rebuild, so the same steps - and the same(low, high)velocity draws - are chosen on every cycle: the ghost layer is locked in place. The defaultself.rngadvances state across rebuilds, so placement differs every cycle. -
rng: Advanced determinism form - arandom.Random(wins overseed=).
Example
p.hit_steps("kick_1", [0, 4, 8, 12], velocity=100)
p.hit_steps("snare_1", [4, 12], velocity=95)
# Different ghost placement each cycle (default)
p.ghost_fill("kick_1", density=0.2, velocity=(30, 45),
bias="sixteenths", no_overlap=True)
# The same ghost layer every cycle - placement frozen
p.ghost_fill("snare_1", density=0.15, velocity=(25, 40),
bias="before", seed=42)
PatternBuilder.cellular_1d
PatternBuilder.cellular_1d(
pitch: subsequence.declarations.Pitch,
rule: int = 30,
generation: typing.Optional[int] = None,
velocity: subsequence.declarations.VelocityValue = subsequence.constants.velocity.DEFAULT_CA_VELOCITY,
duration: subsequence.declarations.GateBeats = 0.1,
no_overlap: bool = False,
probability: subsequence.declarations.UnitInterval = 1.0,
seed: typing.Optional[int] = None,
rng: typing.Optional[random.Random] = None,
) -> subsequence.pattern_builder.PatternBuilder
Generate an evolving rhythm using a 1D cellular automaton.
Uses an elementary CA (1D binary cellular automaton) to produce rhythmic patterns that change organically each bar. The CA state evolves by one generation per cycle, creating patterns that are deterministic yet surprising - structured chaos.
Rule 30 is the default: it produces quasi-random patterns with hidden self-similarity. Rule 90 produces fractal patterns. Rule 110 is Turing-complete.
Parameters
pitch: MIDI note number or drum name.rule: Wolfram rule number (0–255). Default 30.generation: CA generation to render. Defaults toself.cycleso the pattern evolves each bar automatically.velocity: MIDI velocity, or a(low, high)tuple for a fresh random draw per hit.duration: Note duration in beats.no_overlap: If True, skip where same pitch already exists.probability: Chance (0.0–1.0) that each hit plays - 1.0 places them all, lower thins.seed: Fix the thinning for this call (an int); omit to use the pattern's RNG.rng: Advanced determinism form - arandom.Random(wins overseed=).
Example
p.hit_steps("kick_1", [0, 8], velocity=100)
p.cellular_1d("kick_1", rule=30, velocity=40, no_overlap=True)
PatternBuilder.cellular_2d
PatternBuilder.cellular_2d(
pitches: typing.Sequence[subsequence.declarations.Pitch],
rule: str = 'B368/S245',
generation: typing.Optional[int] = None,
velocity: subsequence.declarations.VelocityValue = subsequence.constants.velocity.DEFAULT_CA_VELOCITY,
velocities: typing.Optional[typing.Union[int, typing.Tuple[int, int], typing.List[int]]] = None,
duration: subsequence.declarations.GateBeats = 0.1,
no_overlap: bool = False,
probability: subsequence.declarations.UnitInterval = 1.0,
initial_state: typing.Union[subsequence.declarations.CellularSeed, typing.List[typing.List[int]]] = 'random',
density: subsequence.declarations.UnitInterval = 0.5,
seed: typing.Optional[int] = None,
rng: typing.Optional[random.Random] = None,
) -> subsequence.pattern_builder.PatternBuilder
Generate polyphonic patterns using a 2D Life-like cellular automaton.
Evolves a 2D grid where rows map to pitches or instruments and columns map to time steps. Live cells in the final generation become note onsets, producing patterns with spatial structure that evolves each bar.
The default rule B368/S245 (Morley/"Move") produces chaotic, active patterns. B3/S23 is Conway's Life; B36/S23 is HighLife.
Left alone, most grids this small die out or settle into a loop within tens of bars. So a random start, the default, is redrawn whenever it dies out or falls into a loop of one or two bars, and the part carries on with a fresh grid instead of falling silent for good.
Parameters
pitches: MIDI note numbers or drum names, one per row. Row 0 maps to the first pitch.rule: Birth/Survival notation, e.g."B3/S23"for Conway's Life,"B368/S245"for Morley.generation: CA generation to render. Defaults toself.cycleso the grid evolves each bar automatically.velocity: Single MIDI velocity for every row, or a(low, high)range drawn per note.velocities: One value per row, as a list read by row index. It also takes whatvelocitytakes: one value, or a(low, high)tuple drawn per note (a two-element list here is one value per row). Wins overvelocity.duration: Note duration in beats.no_overlap: If True, skip notes where same pitch already exists.probability: Chance (0.0–1.0) that each live cell plays - 1.0 places them all, lower thins.initial_state: The generation-0 grid."random"(default) fills cells with probability density. The fill is drawn once for the pattern, from the composition's seed when it has one, so a seeded piece plays the same on every run, and it then evolves a generation per bar, redrawn as described above."center"lights a single cell at the centre, which lives only under a rule that can grow a lone cell (one with B1, B2 or S0); under the rules above it plays once and falls silent. An explicitlist[list[int]](rows × cols) starts from that grid. Neither is ever redrawn.density: Fill probability forinitial_state="random"(0.0–1.0).seed: An int that fixes the"random"fill, and every grid drawn after it, whatever the composition's seed. Ignored for"center"or an explicit grid (a warning is emitted if passed there).rng: Random generator for the probability thinning, and for the one draw of a random start with no seed. Defaults toself.rng.
Example
pitches = [36, 38, 42, 46] # kick, snare, hihat, open hihat
p.cellular_2d(pitches, rule="B3/S23", initial_state="random", seed=7, density=0.3)
PatternBuilder.markov
PatternBuilder.markov(
transitions: typing.Dict[str, typing.List[typing.Tuple[str, int]]],
pitch_map: typing.Dict[str, int],
velocity: subsequence.declarations.VelocityValue = subsequence.constants.velocity.DEFAULT_VELOCITY,
duration: subsequence.declarations.GateBeats = 0.1,
spacing: subsequence.declarations.GridBeats = 0.25,
start: typing.Optional[str] = None,
seed: typing.Optional[int] = None,
rng: typing.Optional[random.Random] = None,
) -> subsequence.pattern_builder.PatternBuilder
Generate a sequence by walking a first-order Markov chain.
Builds a WeightedGraph from
transitions and walks it, placing one note per spacing beats.
The probability of each next state depends only on the current one -
use this to generate basslines, melodies, or rhythm motifs that have
stylistic coherence without being perfectly repetitive.
The transition dict uses the same (target, weight) pair format
as Composition.form, so the idiom is already familiar.
Parameters
transitions: Mapping of state name to a list of(next_state, weight)tuples. Higher weight means higher probability of that transition.pitch_map: Mapping of state name to absolute MIDI note number. States absent from this dict are walked but produce no note.velocity: MIDI velocity for all placed notes (default 100), or a(low, high)tuple for a fresh random draw per step.duration: Note duration in beats (default 0.1).spacing: Time between note onsets in beats (default 0.25 = 16th note).start: Name of the starting state. Defaults to the first key intransitionswhen not provided.seed: Fix the walk for this call (an int); omit to use the pattern's RNG.rng: Advanced determinism form - arandom.Random(wins overseed=).
Raises
ValueError: Iftransitionsorpitch_mapis empty.
Example
# Walking bassline: root anchors, 3rd and 5th passing tones
p.markov(
transitions={
"root": [("3rd", 3), ("5th", 2), ("root", 1)],
"3rd": [("5th", 3), ("root", 2)],
"5th": [("root", 3), ("3rd", 1)],
},
pitch_map={"root": 52, "3rd": 56, "5th": 59},
velocity=80,
spacing=0.5,
)
PatternBuilder.melody
PatternBuilder.melody(
state: subsequence.melodic_state.MelodicState,
spacing: subsequence.declarations.GridBeats = 0.25,
velocity: subsequence.declarations.VelocityValue = subsequence.constants.velocity.DEFAULT_CHORD_VELOCITY,
duration: subsequence.declarations.GateBeats = 0.2,
chord_tones: typing.Optional[typing.List[int]] = None,
seed: typing.Optional[int] = None,
rng: typing.Optional[random.Random] = None,
) -> subsequence.pattern_builder.PatternBuilder
Generate a melodic line by querying a persistent MelodicState.
Places one note (or rest) per spacing beats for the full pattern
length. Pitch selection is guided by the NIR cognitive model inside
state: after a large leap the model expects a direction reversal;
after a small step it expects continuation. Chord tones, range
gravity, and a pitch-diversity penalty further shape the output.
Because state lives outside the pattern builder and persists
across bar rebuilds, melodic continuity is maintained automatically -
no manual history management is required.
Parameters
state: PersistentMelodicStateinstance created once at module level.spacing: Time between note onsets in beats (default 0.25 = 16th note).velocity: MIDI velocity. Anintapplies a fixed level; a(low, high)tuple draws uniformly from that range each spacing.duration: Note duration in beats (default 0.2 - slightly shorter than a 16th note, giving a crisp attack).chord_tones: Optional list of MIDI note numbers that are chord tones this bar (e.g. fromchord.tones(root)). Chord-tone pitch classes receive achord_weightbonus insidestate.seed: Fix the walk for this call (an int); omit to use the pattern's RNG.rng: Advanced determinism form - arandom.Random(wins overseed=).
Example
melody_state = subsequence.MelodicState(
key="A", mode="aeolian",
low=60, high=84,
nir_strength=0.6,
chord_weight=0.4,
)
@composition.pattern(channel=4, beats=4)
def lead (p, chord):
tones = chord.tones(72) if chord else None
p.melody(melody_state, spacing=0.5, velocity=(70, 100), chord_tones=tones)
PatternBuilder.lsystem
PatternBuilder.lsystem(
pitch_map: typing.Dict[str, typing.Union[int, str]],
axiom: str,
rules: typing.Dict[str, typing.Union[str, typing.List[typing.Tuple[str, float]]]],
generations: int = 3,
spacing: typing.Optional[subsequence.declarations.GridBeats] = None,
velocity: subsequence.declarations.VelocityValue = subsequence.constants.velocity.DEFAULT_GENERATIVE_VELOCITY,
duration: subsequence.declarations.GateBeats = 0.2,
offset: typing.Annotated[int, subsequence.declarations.Span(0)] = 0,
seed: typing.Optional[int] = None,
rng: typing.Optional[random.Random] = None,
) -> subsequence.pattern_builder.PatternBuilder
Generate a note sequence using L-system string rewriting.
Expands axiom by applying rules for generations
iterations, then walks the resulting string placing a note for
each character found in pitch_map. Unmapped characters are
silent rests - they advance time but produce no note.
The defining musical property is self-similarity: patterns repeat
at different time scales. The Fibonacci-word rule (A → AB,
B → A) spaces hits evenly but never quite repeats within the
string. Koch and dragon curve rules produce fractal melodic
contours. (Hits land on the grid here - for events placed off the
grid by the golden ratio, see golden.)
With spacing=None (default) the entire expanded string is fitted
into the bar: each generation makes the notes denser by as much as
the rules lengthen the string - about 1.6 times for the Fibonacci
word below - while preserving the overall shape. With a fixed
spacing the string is truncated to fit and the density stays
constant.
On its own every bar plays the same string from its start.
offset= treats the string as a loop and starts the bar that many
symbols in, wrapping round to its start. With spacing=None the
bar still holds the whole string, turned: offset=p.cycle turns it
a symbol further each bar - 21 different bars for the six-generation
Fibonacci word before it comes round again. With a fixed
spacing, offset=p.cycle * 16 (sixteen quarter-beat symbols to
a four-beat bar) walks on through the string a bar at a time; more
generations make a longer walk before it wraps.
Parameters
pitch_map: Maps single characters to MIDI notes or drum names. Characters absent from the map produce rests.axiom: Starting string (e.g."A").rules: Production rules. Deterministic:{"A": "AB"}. Stochastic:{"A": [("AB", 3), ("BA", 1)]}.generations: Rewriting iterations. String length grows exponentially - keep this to 3–8 for practical use.spacing: Time between symbols in beats.None(default) auto-fits the full expanded string into the bar. A float uses fixed spacing and truncates excess symbols.velocity: MIDI velocity. An(low, high)tuple randomises per note.duration: Note duration in beats.offset: How many symbols into the string the bar starts, wrapping round to its start. 0 (the default) starts every bar at the beginning.seed: Fix the stochastic-rule choices and velocity draws for this call (an int); omit to use the pattern's RNG.rng: Advanced determinism form - arandom.Random(wins overseed=).
Example
# Fibonacci-word kick rhythm - self-similar hit spacing
p.lsystem(
pitch_map={"A": "kick_1"},
axiom="A",
rules={"A": "AB", "B": "A"},
generations=6,
velocity=80,
)
# The same word, turned a symbol further each bar
p.lsystem(
pitch_map={"A": "kick_1"},
axiom="A",
rules={"A": "AB", "B": "A"},
generations=6,
velocity=80,
offset=p.cycle,
)
# Fractal melody over scale notes
p.lsystem(
pitch_map={"F": 60, "G": 62, "+": 64, "-": 67},
axiom="F",
rules={"F": "F+G", "G": "-F"},
generations=4,
spacing=0.25,
velocity=(70, 100),
)
PatternBuilder.thue_morse
PatternBuilder.thue_morse(
pitch: subsequence.declarations.Pitch,
velocity: subsequence.declarations.VelocityValue = subsequence.constants.velocity.DEFAULT_VELOCITY,
duration: subsequence.declarations.GateBeats = 0.1,
pitch_b: typing.Optional[subsequence.declarations.Pitch] = None,
velocity_b: typing.Optional[subsequence.declarations.VelocityValue] = None,
no_overlap: bool = False,
probability: subsequence.declarations.UnitInterval = 1.0,
offset: typing.Annotated[int, subsequence.declarations.Span(0)] = 0,
seed: typing.Optional[int] = None,
rng: typing.Optional[random.Random] = None,
) -> subsequence.pattern_builder.PatternBuilder
Place notes using the Thue-Morse aperiodic binary sequence.
The Thue-Morse sequence (0 1 1 0 1 0 0 1 1 0 0 1 0 1 1 0 …) is perfectly balanced, overlap-free, and self-similar but never periodic. Within a bar it never settles into a simple repeating figure - a quality distinct from Euclidean rhythms (evenly spaced) and cellular automata (rule-driven evolution).
On its own it plays the start of the sequence, so every bar is the
same. offset= starts further in, and offset=p.cycle * p.grid
carries the sequence on from bar to bar. On 8, 16 or 32 steps that
alternates the first bar with its mirror image - hits and rests
swapped, or the two pitches - and the bars themselves fall in
Thue-Morse order; on 12 or 24 steps it gives six different bars.
offset=p.cycle slides it on a step a bar instead, for far more
variety: 46 different bars in 64 on 16 steps.
In single-pitch mode (default), notes are placed at positions where
the sequence is 1. In two-pitch mode (pitch_b given), pitch
flips to the 0-positions and pitch_b takes the 1-positions - useful
for alternating two drums or two chord tones.
Parameters
pitch: Pitch (MIDI note number or drum name). Placed at the sequence's 1-positions in single-pitch mode; at the 0-positions whenpitch_btakes over the 1s.velocity: MIDI velocity forpitch, or a(low, high)tuple for a fresh random draw per hit.duration: Note duration in beats.pitch_b: Optional second pitch placed at sequence-1 positions. When set, all steps produce a note (no rests).velocity_b: Velocity forpitch_b(int or(low, high)tuple). Defaults tovelocity.no_overlap: Skip steps wherepitchis already sounding.probability: Chance (0.0–1.0) that each active step plays - 1.0 places them all, lower thins.offset: How many steps into the sequence the bar starts. 0 (the default) plays its start every bar.seed: Fix the thinning for this call (an int); omit to use the pattern's RNG.rng: Advanced determinism form - arandom.Random(wins overseed=).
Example
# Single-pitch Thue-Morse kick
p.thue_morse("kick_1", velocity=100)
# Two-pitch mode: alternate kick and snare
p.thue_morse("kick_1", pitch_b="snare_1", velocity=100)
# Carried on from bar to bar: the first bar, then its mirror, in Thue-Morse order
p.thue_morse("kick_1", pitch_b="snare_1", offset=p.cycle * p.grid)
PatternBuilder.de_bruijn
PatternBuilder.de_bruijn(
pitches: typing.Sequence[subsequence.declarations.Pitch],
window: int = 2,
spacing: typing.Optional[subsequence.declarations.GridBeats] = None,
velocity: subsequence.declarations.VelocityValue = subsequence.constants.velocity.DEFAULT_GENERATIVE_VELOCITY,
duration: subsequence.declarations.GateBeats = 0.2,
seed: typing.Optional[int] = None,
rng: typing.Optional[random.Random] = None,
) -> subsequence.pattern_builder.PatternBuilder
Generate a melody that exhaustively traverses all pitch subsequences.
A de Bruijn sequence B(k, n) over an alphabet of size k with window
n contains every possible subsequence of length n exactly once
(cyclically). Mapping each symbol to a pitch produces a melody that
systematically explores all possible n-gram transitions - every
permutation of window consecutive pitches appears exactly once.
With spacing=None (default) the full sequence is auto-fitted into the
bar, matching the behaviour of lsystem. With a fixed spacing
the sequence is truncated to fill the available beats.
Parameters
pitches: List of MIDI note numbers or note strings. The alphabet sizekislen(pitches).window: Subsequence lengthn. The output haslen(pitches) ** windownotes. Keep small (2–4) for practical bar lengths - the cost is combinatorial in both arguments, so a window that is modest over two pitches is enormous over eight. A window whose output would exceed the generated-note budget is reduced (warned once) to the largest that fits, which is still a complete de Bruijn sequence.spacing: Time between notes in beats.Noneauto-fits the sequence into the bar; a float uses fixed spacing and truncates.velocity: MIDI velocity. An(low, high)tuple randomises per note.duration: Note duration in beats.seed: Fix the velocity draws for this call (an int) - the note order itself is deterministic; omit to use the pattern's RNG.rng: Advanced determinism form - arandom.Random(wins overseed=).
Example
# All 2-note combinations of a pentatonic scale
p.de_bruijn([60, 62, 64, 67, 69], window=2, velocity=(60, 100))
PatternBuilder.golden
PatternBuilder.golden(
pitches: typing.Union[subsequence.declarations.Pitch, typing.Sequence[subsequence.declarations.Pitch]],
count: int,
velocity: subsequence.declarations.VelocityValue = subsequence.constants.velocity.DEFAULT_GENERATIVE_VELOCITY,
duration: subsequence.declarations.GateBeats = 0.2,
seed: typing.Optional[int] = None,
rng: typing.Optional[random.Random] = None,
) -> subsequence.pattern_builder.PatternBuilder
Place notes at golden-ratio-spaced beat positions.
Uses the golden angle method - position_i = frac(i × φ) × bar_length,
where frac keeps only the fractional part - to distribute count
events across the bar as a low-discrepancy (sunflower-seed) spread.
The result is sorted into ascending time order. Unlike a Euclidean rhythm (maximally even
spacing on a fixed grid), golden-ratio timing is irrational and places
events off-grid in a way that sounds organic and avoids metronomic
repetition.
Give a single pitch (or drum name) for one voice, or a list to cycle a pool through the placed notes in time order - the first note takes the first pitch, and the pool repeats once exhausted. The positions themselves never change, so swapping one pitch for a pool re-voices a rhythm without moving it.
This shapes time only - the pool is walked in order, not chosen by the
golden ratio. For the Fibonacci integer sequence as pitch material, see
fibonacci.
Parameters
pitches: A MIDI note number or drum name, or a list of them to cycle.count: Number of notes to place.velocity: MIDI velocity. An(low, high)tuple randomises per note.duration: Note duration in beats.seed: Fix the velocity draws for this call (an int) - the timing itself is deterministic; omit to use the pattern's RNG.rng: Advanced determinism form - arandom.Random(wins overseed=).
Raises
ValueError: Ifpitchesis an empty list.
Example
# 11 hi-hat hits with golden-ratio spacing
p.golden("hi_hat_closed", count=11, velocity=(60, 90))
# The same spread, cycling a pentatonic pool
p.golden([60, 62, 65, 67, 70], count=11)
PatternBuilder.recaman
PatternBuilder.recaman(
pitches: typing.Sequence[subsequence.declarations.Pitch],
count: typing.Optional[int] = None,
spacing: typing.Optional[subsequence.declarations.GridBeats] = None,
velocity: subsequence.declarations.VelocityValue = subsequence.constants.velocity.DEFAULT_GENERATIVE_VELOCITY,
duration: subsequence.declarations.GateBeats = 0.2,
start: int = 0,
skip: int = 0,
octave_span: typing.Annotated[int, subsequence.declarations.Unit(octaves)] = 2,
mapping: typing.Optional[typing.Callable[[int, int], typing.Optional[typing.Tuple[typing.Union[int, str], int, float]]]] = None,
seed: typing.Optional[int] = None,
rng: typing.Optional[random.Random] = None,
) -> subsequence.pattern_builder.PatternBuilder
Play Recamán's sequence - a melody that wanders off and never repeats.
The rule is step back if you can, otherwise step forward, by one at the first note, two at the second, and so on. Because the steps keep growing, the line lurches ever wider, and the back-and-forth splits into two voices: alternate notes sink while the ones between them climb, so a single line is heard as two moving apart. That wedge is the reason to reach for this - nothing else here produces it, and it is deterministic, so the same call always gives it back.
It is at its best over one or two bars. Left running much longer the sequence spreads out and starts to sound like a random walk.
Because the numbers grow without limit, they are read as scale degrees plus register: each value picks a note from the pool and how many octaves up to put it, which is what turns the widening into an audible opening-out. Give a pool of one octave - the degrees of your scale. A multi-octave pool works, but it stacks octaves on octaves and the wedge gets very wide very fast.
The sister generator fibonacci always returns home; this one never does.
Parameters
pitches: One octave of pitches - the degrees values are drawn from.count: How many notes to place. Defaults to the pattern's grid.spacing: Beats between notes. Omit to spread them across the bar.velocity: MIDI velocity. An(low, high)tuple randomises per note.duration: Note duration in beats.start: The first value. From 2 upward this is genuinely new material, and the higher it is the longer the melody's opening descent.0and1give the same shape, so step by more than one to hear a change -start=2 + p.cycle * 3evolves the line every cycle.skip: Start further along the sequence, discarding this many values.octave_span: How many octaves the line may climb before it turns back. Set to 0 to keep everything in one octave.mapping:f(value, index)returning(pitch, velocity, duration)to place, or None for a rest - full control over how numbers become notes.seed: Fix the velocity draws for this call (an int) - the pitches and timing are deterministic; omit to use the pattern's RNG.rng: Advanced determinism form - arandom.Random(wins overseed=).
Raises
ValueError: Ifpitchesis empty,spacingis not positive, orskipis negative.
Example
# The wedge, over one octave of C minor.
p.recaman(subsequence.intervals.scale_notes("C", "minor", low=60, high=71))
# A line that reinvents itself every cycle.
p.recaman([60, 62, 63, 65, 67, 68, 71], start=2 + p.cycle * 3)
PatternBuilder.fibonacci
PatternBuilder.fibonacci(
pitches: typing.Sequence[subsequence.declarations.Pitch],
modulus: typing.Optional[int] = None,
count: typing.Optional[int] = None,
spacing: typing.Optional[subsequence.declarations.GridBeats] = None,
velocity: subsequence.declarations.VelocityValue = subsequence.constants.velocity.DEFAULT_GENERATIVE_VELOCITY,
duration: subsequence.declarations.GateBeats = 0.2,
a: int = 1,
b: int = 1,
mapping: typing.Optional[typing.Callable[[int, int], typing.Optional[typing.Tuple[typing.Union[int, str], int, float]]]] = None,
seed: typing.Optional[int] = None,
rng: typing.Optional[random.Random] = None,
) -> subsequence.pattern_builder.PatternBuilder
Play the Fibonacci sequence as a repeating melodic cycle.
Each number is the sum of the previous two, folded into your pitch pool. The musical trick is that folding makes the sequence repeat, and the length it repeats after is decided by the size of the pool - so the number of notes you hand it chooses the phrase length: a triad gives 8 steps, a pentatonic 20, a seven-note scale 16, an octatonic 12, the full chromatic 24. Called bare, it plays exactly one complete cycle, so the phrase closes on itself.
Unlike recaman, which wanders off and never comes back, this always
returns home - pair them when you want one voice looping against one that
doesn't. For golden-ratio timing (which has no Fibonacci numbers in it at
all), see golden.
Parameters
pitches: Pitch pool. Values index into it, so its size sets the cycle length. Ordered low-to-high it reads as a scale.modulus: Fold values into[0, modulus). Defaults to the pool size, which is almost always what you want; set it larger than the pool to make the line wrap through the pool more than once per cycle.count: How many notes to place. Omit for one complete cycle.spacing: Beats between notes. Omit to spread the whole cycle across the bar, however long it is. Setting a spacing fixes the note length instead, so a cycle longer than the bar is cut off where the bar ends - a 20-step cycle atspacing=0.25gets its first 16 notes.velocity: MIDI velocity. An(low, high)tuple randomises per note.duration: Note duration in beats.a: The first number. Defaults to 1.b: The second number. Defaults to 1.(2, 1)gives the Lucas numbers, a different cycle through the same pool. Note many pairs are that same cycle started elsewhere -(1, 3)is Lucas one step along.mapping:f(value, index)returning(pitch, velocity, duration)to place, or None for a rest - full control over how numbers become notes.seed: Fix the velocity draws for this call (an int) - the pitches and timing are deterministic; omit to use the pattern's RNG.rng: Advanced determinism form - arandom.Random(wins overseed=).
Raises
ValueError: Ifpitchesis empty, orspacingis not positive.
Example
# One full 16-step cycle over a minor scale, spread across the bar.
p.fibonacci(subsequence.intervals.scale_notes("C", "minor", low=60, high=71))
# The Lucas variant, as steady eighth notes.
p.fibonacci([60, 62, 63, 65, 67], a=2, b=1, spacing=0.5)
PatternBuilder.lorenz
PatternBuilder.lorenz(
pitches: typing.Sequence[subsequence.declarations.Pitch],
spacing: subsequence.declarations.GridBeats = 0.25,
velocity: subsequence.declarations.VelocityValue = subsequence.constants.velocity.DEFAULT_GENERATIVE_VELOCITY,
duration: subsequence.declarations.GateBeats = 0.2,
dt: typing.Annotated[float, subsequence.declarations.Span(0.001, 0.5)] = 0.1,
sigma: float = 10.0,
rho: float = 28.0,
beta: float = 8.0 / 3.0,
x0: float = 0.1,
y0: float = 0.0,
z0: float = 0.0,
mapping: typing.Optional[typing.Callable[[float, float, float], typing.Optional[typing.Tuple[typing.Union[int, str], int, float]]]] = None,
) -> subsequence.pattern_builder.PatternBuilder
Generate a note sequence driven by the Lorenz strange attractor.
Integrates the Lorenz system and walks one trajectory of (x, y, z) points, carried on from bar to bar: each bar plays the stretch after the last one's, so the line keeps moving instead of replaying one bar. The three axes provide correlated but independent modulation sources: by default x drives pitch selection, y drives velocity, and z drives duration.
Each axis is measured against the range the trajectory covers over its
first sixty time units, the same for every bar, so a stretch that
circles one wing of the butterfly stays on a few pitches and a swing to
the other wing sweeps across the pool. At the default dt about half the
notes repeat the one before and most of the rest move to a neighbouring
pitch; a bar spans a median of five steps of an eight-note pool. A
smaller dt lingers - at 0.05, 70% of notes repeat - and a larger one
moves faster and leaps more.
The system is extremely sensitive to where it starts: two lines whose
x0 differ by a millionth part company by the second bar, so x0
picks a different line. Turn rho down to calm it: at about 15 or
below, the line spirals in and comes to rest on one pitch within 30
bars at the default dt.
A custom mapping callable can override the default x/y/z → pitch/vel/dur
assignment, or return None for a rest.
Parameters
pitches: Pitch pool. The x-axis selects an index, low to high:min(int(x * len(pitches)), len(pitches) - 1).spacing: Time between notes in beats. Default 0.25 (16th note).velocity: Fixed velocity or(low, high)tuple. Overridden bymapping.duration: Maximum note duration. z is scaled to[0.05, duration]. Overridden bymapping.dt: Time along the trajectory between one note and the next, 0.001 to 0.5. Longer steps move further round the attractor per note; any length is integrated in steps of at most 0.01, so it never runs off. Default 0.1.sigma, rho, beta: Lorenz parameters. Defaults produce the classic butterfly attractor (chaotic regime).x0, y0, z0: Where the trajectory starts. A different start plays a different line.mapping: Optional callable(x, y, z) -> (pitch, velocity, duration)orNonefor rest.
Example
scale = [60, 62, 64, 65, 67, 69, 71, 72]
p.lorenz(scale, spacing=0.25, velocity=(50, 110))
PatternBuilder.reaction_diffusion
PatternBuilder.reaction_diffusion(
pitch: subsequence.declarations.Pitch,
threshold: subsequence.declarations.UnitInterval = 0.5,
velocity: subsequence.declarations.VelocityValue = subsequence.constants.velocity.DEFAULT_GENERATIVE_VELOCITY,
duration: subsequence.declarations.GateBeats = 0.1,
feed_rate: typing.Annotated[float, subsequence.declarations.Span(0.0, 0.2)] = 0.08,
kill_rate: typing.Annotated[float, subsequence.declarations.Span(0.0, 0.07)] = 0.061,
steps: typing.Annotated[int, subsequence.declarations.Span(1, 20000)] = 1000,
no_overlap: bool = False,
probability: subsequence.declarations.UnitInterval = 1.0,
seed: typing.Optional[int] = None,
rng: typing.Optional[random.Random] = None,
) -> subsequence.pattern_builder.PatternBuilder
Generate a rhythm from a 1D Gray-Scott reaction-diffusion simulation.
Simulates the Gray-Scott model on a ring of _default_grid cells,
then thresholds the final V-concentration to produce a binary hit
pattern. Cells where concentration exceeds threshold become note
events.
Unlike cellular automata - where rules are discrete and the state is
binary - reaction-diffusion evolves a continuous concentration field
governed by diffusion rates and chemical reactions. On a ring the size
of a bar it settles into spots - runs of hits - and a (low, high)
velocity follows the chemical along each one: a short run swells to its
middle, and a long one is loudest just inside each end.
Only a narrow band of feed_rate and kill_rate forms a pattern.
Too much kill for the feed and the chemical dies out; too little and it
evens out around the whole bar. Either way there is no pattern, so the
call plays nothing, and the log says which happened, once per setting.
Raising steps does not help: a setting with no pattern at 1000
steps has none at 20000.
The default rates form a pattern on any grid of 8 steps or more: a run
of 4 on 8 steps, a run of 6 on 12 or 16, and two runs of 5 on 24 or 32.
On 16 steps at the default kill, feed_rate sets how long the run
is: 0.058 plays 12 steps, 0.066 plays 10, 0.072 plays 8, 0.08 plays 6
and 0.088 plays 4, and below about 0.048 or above about 0.094 it dies
out. More kill shortens the runs too, over a narrower range; from
about 0.065 every setting dies out.
Parameters
pitch: MIDI note number or drum name.threshold: V-concentration threshold for note placement (0.0–1.0). Lower values produce denser patterns.velocity: MIDI velocity. An(low, high)tuple is NOT random: each step's local V-concentration is mapped into the range deterministically, so notes are louder where the chemical is stronger and softest at the edges of each run.duration: Note duration in beats.feed_rate: How fast the chemical the pattern lives on is replenished, 0 to 0.2. Default 0.08.kill_rate: How fast the pattern's own chemical is removed, 0 to 0.07. Default 0.061.steps: Number of simulation iterations. More = more developed pattern. Default 1000, and bounded at 20000 - the cost is linear (about 3 ms per thousand) and a rebuild that overruns delays the whole pattern. The pattern settles by about 2000 in any case; beyond that it drifts rather than develops.no_overlap: Skip steps wherepitchis already sounding.probability: Chance (0.0–1.0) that each active step plays - 1.0 places them all, lower thins.seed: Fix the thinning for this call (an int); omit to use the pattern's RNG.rng: Advanced determinism form - arandom.Random(wins overseed=).
Example
# Six closed-hat sixteenths across the middle of the bar,
# swelling to their centre
p.reaction_diffusion("hi_hat_closed", velocity=(50, 110))
# Ten, loudest just inside each end
p.reaction_diffusion("hi_hat_closed", feed_rate=0.066, velocity=(50, 110))
PatternBuilder.self_avoiding_walk
PatternBuilder.self_avoiding_walk(
pitches: typing.Sequence[subsequence.declarations.Pitch],
spacing: subsequence.declarations.GridBeats = 0.25,
velocity: subsequence.declarations.VelocityValue = subsequence.constants.velocity.DEFAULT_GENERATIVE_VELOCITY,
duration: subsequence.declarations.GateBeats = 0.2,
seed: typing.Optional[int] = None,
rng: typing.Optional[random.Random] = None,
) -> subsequence.pattern_builder.PatternBuilder
Generate a melody using a self-avoiding random walk.
The walk moves through pitches in order, mostly a step at a time and
now and then skipping one, and it remembers about the last half of the
list: it goes only where it has not been lately, so a pitch never comes
back sooner than three notes later, and the line keeps moving instead of
trilling between two notes. Where every neighbour was heard lately, as
at the ends of a short list, it goes to the one heard longest ago.
So the line stays step-wise and keeps finding new notes. Over 500 seeds, a bar of sixteenths on the eight notes of C major from 60 to 72 gave 188 different melodies, with about a quarter of the moves skipping a note. Two pitches can only alternate.
It is one line, not a bar at a time. The first bar starts on the middle of the list (65 in that scale), and each bar after it goes on from where the last one ended, still keeping clear of what it heard there. If the list changes from one bar to the next, as it does when it follows the chord, the walk goes on from the note in the new list nearest the one it ended on.
Parameters
pitches: Ordered list of MIDI note numbers or note strings. The walk moves through indices[0, len(pitches) - 1], mapping each to the corresponding pitch.spacing: Time between notes in beats. Default 0.25 (16th note).velocity: MIDI velocity. An(low, high)tuple randomises per note.duration: Note duration in beats.seed: Fix the walk's choices (an int), so it plays the same on every run; omit to use the pattern's RNG.rng: Advanced determinism form - arandom.Random(wins overseed=).
Example
scale = subsequence.scale_notes("C", "ionian", low=60, high=72)
p.self_avoiding_walk(scale, spacing=0.25, velocity=(60, 100))
PatternBuilder.thin
PatternBuilder.thin(
pitch: typing.Optional[subsequence.declarations.Pitch] = None,
strategy: typing.Union[subsequence.declarations.ThinStrategy, typing.List[float]] = 'strength',
amount: subsequence.declarations.UnitInterval = 0.5,
grid: typing.Optional[subsequence.declarations.StepCount] = None,
seed: typing.Optional[int] = None,
rng: typing.Optional[random.Random] = None,
) -> subsequence.pattern_builder.PatternBuilder
Remove notes from the pattern based on their rhythmic position.
This is the musical inverse of ghost_fill(). Where ghost_fill
uses bias weights to decide where to add ghost notes, thin uses the
same position vocabulary to decide where to remove notes.
The strategy names match those in build_ghost_bias() and carry the
same rhythmic meaning, taken at its word: a position a strategy keeps is
never touched, whatever the amount, and a position it removes goes
with probability amount.
"sixteenths"- removes 16th-note subdivisions (e/a), keeps beats and &."offbeat"- removes the & position, straightens the groove, and thins the e/a lightly, which sets it apart from"upbeat"."e_and_a"- removes all non-downbeat positions, keeps only beats."downbeat"- removes beat positions (floating/displaced effect)."upbeat"- removes only the & position."before"- removes only the step just before each beat."after"- removes only the step just after each beat."uniform"- removes from all positions equally (per-instrument dropout)."strength"- progressive thinning: weakest positions (e/a) drop first, strongest (downbeat) last. Useful for Perlin-driven density control.
When pitch is given, only notes of that instrument are affected -
useful for drum layers. When pitch is None (the default), all
notes regardless of pitch are candidates. This makes thin a
rhythm-aware generalisation of dropout(), and is ideal for
tonal patterns such as arpeggios where each step carries a different pitch.
Position classification is zone-based: each grid step owns the pulse range
[N * step_pulses, (N + 1) * step_pulses), and a note counts in the step
it was placed on, however far swing(), groove() or randomize()
has since moved it, early or late. So thin can come before the feel
or after it.
Parameters
pitch: Drum name or MIDI note number to target, orNoneto thin all notes regardless of pitch. Defaults toNone.strategy: Named strategy string or a list of per-step drop-priority floats (0.0 = never drop, 1.0 = highest drop priority). Must have length equal togridwhen a list is provided.amount: Overall thinning depth (0.0 = remove nothing, 1.0 = remove all qualifying). Effective drop probability =priority * amount. Drive this with a Perlin field or section progress for smooth, organic thinning over time.grid: Step grid size. Defaults to the pattern'sdefault_grid.seed: Fix the thinning for this call (an int); omit to use the pattern's RNG.rng: Advanced determinism form - arandom.Random(wins overseed=).
Example:
# Thin 16th ghost notes from the kick, keep anchors and off-beats
p.hit_steps("kick_1", [0, 4, 8, 12], velocity=100)
p.ghost_fill("kick_1", density=0.3, velocity=(25, 40), bias="sixteenths")
p.thin("kick_1", "sixteenths", amount=0.8)
# Perlin-driven progressive thinning of hi-hats
sparseness = perlin_1d(p.cycle * 0.07, seed=42)
p.thin("hi_hat_closed", "strength", amount=sparseness)
# Thin an arpeggio (all pitches) - no pitch loop needed
p.thin(strategy="strength", amount=sparseness)
PatternBuilder.ratchet
PatternBuilder.ratchet(
subdivisions: int = 2,
pitch: typing.Optional[subsequence.declarations.Pitch] = None,
probability: subsequence.declarations.UnitInterval = 1.0,
velocity_start: subsequence.declarations.VelocityScale = 1.0,
velocity_end: subsequence.declarations.VelocityScale = 1.0,
shape: typing.Union[subsequence.declarations.EasingCurve, typing.Callable[[float], float]] = 'linear',
gate: subsequence.declarations.UnitInterval = 0.5,
steps: typing.Optional[typing.List[subsequence.declarations.StepPosition]] = None,
grid: typing.Optional[subsequence.declarations.StepCount] = None,
seed: typing.Optional[int] = None,
rng: typing.Optional[random.Random] = None,
) -> subsequence.pattern_builder.PatternBuilder
Subdivide existing notes into rapid repeated hits (rolls/ratchets).
A post-placement transform: takes notes already in the pattern and
replaces each one with subdivisions evenly-spaced sub-hits within
the original note's duration window. The velocity of each sub-hit is
interpolated from velocity_start to velocity_end (as multipliers
on the original velocity) using the shape easing curve, so crescendo
rolls, decrescendo buzzes, and flat repeats are all one parameter apart.
Call ratchet() after note-placement methods (euclidean,
hit_steps, arpeggio, etc.) and after swing or groove,
so that each roll moves with its note. A groove moves only the notes
that sit near a grid line, so a roll made before it has only its first
sub-hit moved: the roll is squeezed, and with enough swing its sub-hits
land on one pulse or play in the wrong order.
Parameters
subdivisions: Number of sub-hits replacing each note (default 2). If the note's duration is shorter thansubdivisionspulses, subdivisions are clamped tonote.durationso they never stack on the same pulse.pitch: Only ratchet notes matching this pitch (MIDI number or drum name).None(default) ratchets all notes regardless of pitch - useful for melodic patterns such as arpeggios.probability: Chance (0.0–1.0) that each note gets ratcheted. Notes that fail the check are left completely unchanged. Default 1.0 (every note is ratcheted).velocity_start: Velocity multiplier for the first sub-hit (0.0–2.0). Default 1.0 (same as the original).velocity_end: Velocity multiplier for the last sub-hit (0.0–2.0). Default 1.0. Setvelocity_start=0.3, velocity_end=1.0for a crescendo roll;1.0, 0.2for a decrescendo buzz.shape: Easing curve applied to the velocity interpolation across sub-hits. Accepts any name fromsubsequence.easing(e.g."ease_in","ease_out","s_curve") or a custom callablef(t) → tfor t ∈ [0, 1]. Default"linear".gate: Sub-note duration as a fraction of each subdivision slot (0.0–1.0).1.0= legato (sub-hits touch),0.5= staccato (half the slot). Default 0.5.steps: Grid positions to ratchet (e.g.[0, 4, 12]). Each note counts as the step it was placed on, the same waythin()counts it, however far swing, a groove orrandomize()has moved it.None(default) applies ratchet to all eligible notes.grid: Grid resolution used forstepszone classification. Defaults to the pattern'sdefault_grid.seed: Fix the probability gating for this call (an int); omit to use the pattern's RNG.rng: Advanced determinism form - arandom.Random(wins overseed=).
Examples
# Subdivide every hi-hat into a triplet roll
p.euclidean("hi_hat_closed", 8).ratchet(3, pitch="hi_hat_closed")
# Crescendo roll into a snare
p.hit_steps("snare", [12]).ratchet(4, velocity_start=0.3,
velocity_end=1.0,
shape="ease_in")
# Probabilistic 2× ratchet on hi-hats only
p.euclidean("hi_hat_closed", 12).ratchet(2, pitch="hi_hat_closed",
probability=0.4, gate=0.3)
# Ratchet only steps 0 and 8 (downbeats)
p.euclidean("kick_1", 6).ratchet(2, pitch="kick_1", steps=[0, 8])
PatternBuilder.evolve
PatternBuilder.evolve(
pitches: typing.Sequence[subsequence.declarations.Pitch],
length: typing.Optional[int] = None,
drift: subsequence.declarations.UnitInterval = 0.0,
velocity: subsequence.declarations.VelocityValue = subsequence.constants.velocity.DEFAULT_GENERATIVE_VELOCITY,
duration: subsequence.declarations.GateBeats = 0.2,
spacing: subsequence.declarations.GridBeats = 0.25,
seed: typing.Optional[int] = None,
rng: typing.Optional[random.Random] = None,
) -> subsequence.pattern_builder.PatternBuilder
Loop a pitch sequence that gradually mutates each cycle.
On cycle 0, the sequence is locked to the initial pitches (truncated to
length if provided). Each subsequent cycle, every step has a drift
probability of being replaced by a randomly-chosen value from the pool.
When drift=0.0 the loop never changes; when drift=1.0 every step
is redrawn every cycle.
State is stored in p.data under a key derived from the pitch content, so the
buffer persists across pattern rebuilds. The buffer is reset whenever
cycle == 0 so restarts produce deterministic output.
Combine with p.snap_to_scale() to keep drifted pitches in key:
p.evolve([60, 64, 67, 72], length=8, drift=0.12)
p.snap_to_scale("C", "minor")
Parameters
pitches: Initial pitch pool. The initial buffer is built from the firstlengthvalues (cycling if shorter thanlength). Mutation also draws replacements from this pool.length: Number of steps in the loop. Defaults tolen(pitches).drift: Per-step mutation probability each cycle (0.0–1.0).0.0= locked loop,1.0= fully random each cycle.velocity: MIDI velocity. An(low, high)tuple randomises per step.duration: Note duration in beats.spacing: Beat interval between steps.seed: Fix the drift mutations and velocity draws for this call (an int); omit to use the pattern's RNG.rng: Advanced determinism form - arandom.Random(wins overseed=).
Example
# 8-step loop that slowly diverges from its seed
p.evolve([60, 62, 64, 65, 67, 69], length=8, drift=0.1,
velocity=(70, 100), spacing=0.5)
p.snap_to_scale("C", "dorian")
PatternBuilder.branch
PatternBuilder.branch(
pitches: typing.Sequence[subsequence.declarations.Pitch],
depth: int = 2,
path: int = 0,
mutation: subsequence.declarations.UnitInterval = 0.0,
velocity: subsequence.declarations.VelocityValue = subsequence.constants.velocity.DEFAULT_GENERATIVE_VELOCITY,
duration: subsequence.declarations.GateBeats = 0.2,
spacing: subsequence.declarations.GridBeats = 0.25,
seed: typing.Optional[int] = None,
rng: typing.Optional[random.Random] = None,
) -> subsequence.pattern_builder.PatternBuilder
Generate a melodic variation by navigating a fractal tree of transforms.
The pitches sequence is the "trunk". At each branch level, two musical
transforms are assigned deterministically (derived from the original
sequence), and the path index selects left or right at each level.
After depth levels the result is a variation that is always
structurally related to the input pitches.
Use path=p.cycle to step through all 2 ** depth variations in
order; the index wraps automatically.
Transforms (assigned deterministically per level):
- Retrograde - reverse the sequence.
- Invert - mirror each pitch around the first note.
- Transpose - shift all pitches by the interval between the first two notes.
- Rotate - shift the starting position by one step.
- Scale intervals - multiply intervals from the first note by 0.5 (compress) or 2.0 (expand), rounded to the nearest semitone.
An optional mutation layer randomly substitutes individual notes
with other input pitches on top of the deterministic branching.
Parameters
pitches: Original pitch sequence. All variations are derived from this.depth: Branching levels.2 ** depthunique variations are available before the path wraps.path: Which variation to play (0-based).path=p.cycleadvances automatically. Values wrap modulo2 ** depth.mutation: Probability that any step is replaced by a random input pitch after branching (0.0 = none, 1.0 = fully random).velocity: MIDI velocity. An(low, high)tuple randomises per step.duration: Note duration in beats.spacing: Beat interval between steps.seed: Fix the mutation substitutions and velocity draws for this call (an int) - the variation tree itself is deterministic; omit to use the pattern's RNG.rng: Advanced determinism form - arandom.Random(wins overseed=).
Example
# Cycle through 8 variations (depth=3) of a 4-note motif
p.branch([60, 64, 67, 72], depth=3, path=p.cycle,
velocity=85, spacing=0.5)
p.snap_to_scale("C", "minor")