Subsystem

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

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

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

Raises

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

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

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

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

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

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 channel to immediately silence any ringing notes or drones.

Parameters

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

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

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

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.

Patterns that should own the phrase's length call p.set_length(phrase.length) once instead.

Parameters

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

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

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

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:

Parameters

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

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

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

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

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

Returns

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

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

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:

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

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

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

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

Returns

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

Returns

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

Returns

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

PatternBuilder.legato

PatternBuilder.legato(
    ratio: subsequence.declarations.UnitInterval = 1.0,
) -> PatternBuilder

Adjust note durations to fill the gap until the next note.

Parameters

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

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

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

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

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

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

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

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

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

Returns

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

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

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

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

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 channel (e.g. a per-channel RPN config), define a separate pattern on that channel or use composition.trigger(channel=…) for a one-shot.

Parameters

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

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 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 channel, which is you addressing whatever was last selected and is left alone deliberately; and another pattern writing NRPN to the same 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

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

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

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

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

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

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

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

Raises

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

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

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

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

Returns

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

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

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

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

Raises

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

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

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

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

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

Raises

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

Raises

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

Raises

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

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

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

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.

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

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

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

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):

An optional mutation layer randomly substitutes individual notes with other input pitches on top of the deterministic branching.

Parameters

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")