subsequence.pattern_midi

Mixin class providing MIDI and OSC control-message methods for PatternBuilder.

This module is not intended to be used directly. PatternMidiMixin is inherited by PatternBuilder in pattern_builder.py.

Module Contents

class subsequence.pattern_midi.PatternMidiMixin[source]

MIDI control, OSC, and note-correlated pitch bend methods for PatternBuilder.

All methods here operate on self._pattern (a Pattern instance), which is set by PatternBuilder.__init__.

bend(note: int, amount: float, start: float = 0.0, end: float = 1.0, shape: str | subsequence.easing.EasingFn = 'linear', resolution: int = 1) subsequence.pattern_builder.PatternBuilder[source]

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. Call this after legato() / detached() / duration() so that note durations are final.

Parameters:
  • note – Note index (0 = first, -1 = last, etc.).

  • amount – Target bend normalised to -1.0..1.0 (positive = up). With a standard ±2-semitone pitch wheel range, 0.5 = 1 semitone.

  • start – Fraction of the note’s duration at which the ramp begins (0.0 = note onset, default).

  • end – Fraction of the note’s duration at which the ramp ends (1.0 = note end, default).

  • shape – Easing curve — a name string (e.g. "ease_in") or any callable mapping [0, 1] → [0, 1]. Defaults to "linear".

  • resolution – Pulses between pitch bend messages.

Raises:

IndexError – If note is out of range for the current pattern.

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)
cc(control: int | str, value: int, beat: float = 0.0) subsequence.pattern_builder.PatternBuilder[source]

Send a single CC message at a beat position.

Parameters:
  • control – MIDI CC number (0–127), or a string name resolved via the pattern’s cc_name_map.

  • value – CC value (0–127); out-of-range values are clamped.

  • beat – Beat position within the pattern.

cc_ramp(control: int | str, start: int, end: int, beat_start: float = 0.0, beat_end: float | None = None, resolution: int = 1, shape: str | subsequence.easing.EasingFn = 'linear') subsequence.pattern_builder.PatternBuilder[source]

Interpolate a CC value over a beat range.

Parameters:
  • control – MIDI CC number (0–127), or a string name resolved via the pattern’s cc_name_map.

  • start – Starting CC value (0–127).

  • end – Ending CC value (0–127).

  • beat_start – Beat position to begin the ramp.

  • beat_end – Beat position to end the ramp. Defaults to pattern length.

  • resolution – Pulses between CC messages (1 = every pulse, ~20ms at 120 BPM). Higher values (e.g. 2 or 4) reduce MIDI traffic density but may sound stepped at slow tempos.

  • shape – Easing curve — a name string (e.g. "exponential") or any callable that maps [0, 1] → [0, 1]. Defaults to "linear". See subsequence.easing for available shapes.

nrpn(parameter: int | str, value: int, beat: float = 0.0, fine: bool = False, null_reset: bool = True) subsequence.pattern_builder.PatternBuilder[source]

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:
  • parameter – 14-bit NRPN parameter number (0–16383), or a string resolved via the pattern’s nrpn_name_map.

  • value – Parameter value. 0–127 if fine=False; 0–16383 if fine=True.

  • beat – Beat position within the pattern.

  • fine – If True, send 14-bit value via Data Entry MSB+LSB (CC 6 + CC 38). If False (default), send only Data Entry MSB — sufficient for the common 0–127 range.

  • null_reset – If True (default), follow with the RPN null sentinel to deselect the active parameter and prevent stray later CC 6 / 38 messages from hitting it.

Example

# Sequential Take 5 fine-tune (14-bit, range 0–1400)
p.nrpn(9, 700, fine=True)

# Roland JV-1080 reverb level (7-bit)
p.nrpn(0x0140, 80)
nrpn_ramp(parameter: int | str, start: int, end: int, beat_start: float = 0.0, beat_end: float | None = None, resolution: int = 4, shape: str | subsequence.easing.EasingFn = 'linear', fine: bool = True, null_reset: bool = True) subsequence.pattern_builder.PatternBuilder[source]

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.

Mid-ramp parameter persistence: between beat_start and beat_end the synth still has this NRPN selected. Avoid issuing p.cc(6, …) or p.cc(38, …) on the same channel during the ramp window — they would land on the ramped parameter rather than acting as plain data-entry CCs.

Bandwidth note: with fine=True (default) every step emits two CCs. Default resolution=4 is one update every four pulses (~83 ms at 120 BPM, where one pulse is ~21 ms), which keeps the bus lightly loaded. Increase resolution (e.g. 8) on slow DIN-MIDI links if you hear other messages getting delayed.

Emitted on the pattern’s MIDI channel.

Parameters:
  • parameter – 14-bit NRPN parameter number, or a string resolved via the pattern’s nrpn_name_map.

  • start – Starting value (0–16383 when fine=True, 0–127 when False).

  • end – Ending value.

  • beat_start – Beat position to begin the ramp.

  • beat_end – Beat position to end the ramp. Defaults to pattern length.

  • resolution – Pulses between Data Entry messages (default 4).

  • shape – Easing curve — string name or callable [0, 1] → [0, 1].

  • fine – If True (default), use full 14-bit Data Entry MSB+LSB.

  • null_reset – If True (default), append the null sentinel at the end of the ramp (not per step).

osc(address: str, *args: Any, beat: float = 0.0) subsequence.pattern_builder.PatternBuilder[source]

Send an OSC message at a beat position.

Requires composition.osc() to be called before composition.play(). If no OSC server is configured the event is silently dropped.

Parameters:
  • address – OSC address path (e.g. "/mixer/fader/1").

  • *args – OSC arguments — float, int, str, or bytes.

  • beat – Beat position within the pattern (default 0.0).

Example

# Enable a chorus effect at beat 2
p.osc("/fx/chorus/enable", 1, beat=2.0)

# Set a mixer pan value immediately
p.osc("/mixer/pan/1", -0.5)
osc_ramp(address: str, start: float, end: float, beat_start: float = 0.0, beat_end: float | None = None, resolution: int = 4, shape: str | subsequence.easing.EasingFn = 'linear') subsequence.pattern_builder.PatternBuilder[source]

Interpolate an OSC float value over a beat range.

Generates one OSC message per resolution pulses, sending the interpolated value to address at each step. Useful for smoothly automating mixer faders, effect parameters, and other continuous controls on a remote machine.

Requires composition.osc() to be called before composition.play(). If no OSC server is configured the events are silently dropped.

Parameters:
  • address – OSC address path (e.g. "/mixer/fader/1").

  • start – Starting float value.

  • end – Ending float value.

  • beat_start – Beat position to begin the ramp (default 0.0).

  • beat_end – Beat position to end the ramp. Defaults to pattern length.

  • resolution – Pulses between OSC messages (default 4 — approximately 6 messages per beat at 120 BPM, which is smooth for fader automation while keeping UDP traffic light). Use resolution=1 for pulse-level precision.

  • shape – Easing curve — a name string (e.g. "ease_in") or any callable that maps [0, 1] → [0, 1]. Defaults to "linear". See subsequence.easing for available shapes.

Example

# Fade a mixer fader up over 4 beats
p.osc_ramp("/mixer/fader/1", start=0.0, end=1.0)

# Ease in a reverb send over the last 2 beats
p.osc_ramp("/fx/reverb/wet", 0.0, 0.8, beat_start=2, beat_end=4, shape="ease_in")
pitch_bend(value: float, beat: float = 0.0) subsequence.pattern_builder.PatternBuilder[source]

Send a single pitch bend message at a beat position.

Parameters:
  • value – Pitch bend amount, normalised from -1.0 to 1.0.

  • beat – Beat position within the pattern.

pitch_bend_ramp(start: float, end: float, beat_start: float = 0.0, beat_end: float | None = None, resolution: int = 1, shape: str | subsequence.easing.EasingFn = 'linear') subsequence.pattern_builder.PatternBuilder[source]

Interpolate pitch bend over a beat range.

Parameters:
  • start – Starting pitch bend (-1.0 to 1.0).

  • end – Ending pitch bend (-1.0 to 1.0).

  • beat_start – Beat position to begin the ramp.

  • beat_end – Beat position to end the ramp. Defaults to pattern length.

  • resolution – Pulses between pitch bend messages (1 = every pulse). Higher values (e.g. 2 or 4) reduce MIDI traffic density but may sound stepped at slow tempos.

  • shape – Easing curve — a name string (e.g. "ease_out") or any callable that maps [0, 1] → [0, 1]. Defaults to "linear". See subsequence.easing for available shapes.

portamento(time: float = 0.15, shape: str | subsequence.easing.EasingFn = 'linear', resolution: int = 1, bend_range: float | None = 2.0, wrap: bool = True) subsequence.pattern_builder.PatternBuilder[source]

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. Call this after legato() / detached() / duration() so that note durations are final.

Most effective on mono instruments where pitch bend is per-channel.

Parameters:
  • time – Fraction of each note’s duration used for the glide (default 0.15 — last 15% of the note).

  • shape – Easing curve. Defaults to "linear".

  • resolution – Pulses between pitch bend messages.

  • bend_range – Instrument’s pitch wheel range in semitones (default 2.0 — standard ±2 st). Pairs with intervals larger than this value are skipped. Pass None to disable range checking and always generate the bend (large intervals are clamped to ±1.0).

  • wrap – If True (default), glide from the last note toward the first note of the next cycle.

Example

p.sequence(steps=[0, 4, 8, 12], pitches=[40, 42, 40, 43])
p.legato(0.95)

# Gentle glide across all note transitions
p.portamento(time=0.15, shape="ease_in_out")

# Wide bend range (synth set to ±12 semitones)
p.portamento(time=0.2, bend_range=12)

# No range limit — bend as far as MIDI allows
p.portamento(time=0.1, bend_range=None)
program_change(program: int, beat: float = 0.0, bank_msb: int | None = None, bank_lsb: int | None = None) subsequence.pattern_builder.PatternBuilder[source]

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 synthesizer expects.

Parameters:
  • program – Program (patch) number (0–127).

  • beat – Beat position within the pattern (default 0.0).

  • bank_msb – Bank select coarse (CC 0), 0–127. None = omit.

  • bank_lsb – Bank select fine (CC 32), 0–127. None = omit.

Example

@composition.pattern(channel=1, beats=4)
def strings (p):
    # GM — no bank needed
    p.program_change(48)

    # Roland JV-1080 bank 1, patch 48
    p.program_change(48, bank_msb=81, bank_lsb=0)

    # Change patch only at the first bar of each section
    if p.section.bar == 0:
        p.program_change(48, bank_msb=1)
rpn(parameter: int | str, value: int, beat: float = 0.0, fine: bool = False, null_reset: bool = True) subsequence.pattern_builder.PatternBuilder[source]

Send a single RPN parameter write at a beat position.

RPN (Registered Parameter Number) addresses the small standardised set of parameters defined by the MIDI specification — pitch bend range, master tuning, modulation depth — supported by virtually any MIDI synth. String names resolve via pymididefs.rpn.RPN_MAP out of the box, no map needed.

Standard RPN names: pitch_bend_sensitivity, channel_fine_tuning, channel_coarse_tuning, tuning_program_select, tuning_bank_select, modulation_depth_range.

Emitted on the pattern’s MIDI channel.

Parameters:
  • parameter – 14-bit RPN parameter number (0–16383), or one of the standard string names above.

  • value – Parameter value. 0–127 if fine=False; 0–16383 if fine=True. Pitch bend sensitivity uses MSB = semitones and LSB = cents, so set fine=True for sub-semitone control.

  • beat – Beat position within the pattern.

  • fine – If True, send 14-bit value via Data Entry MSB+LSB.

  • null_reset – If True (default), follow with the RPN null sentinel.

Example

# Set pitch bend range to ±12 semitones
p.rpn("pitch_bend_sensitivity", 12)

# 4 semitones plus 50 cents
p.rpn("pitch_bend_sensitivity", 4 * 128 + 50, fine=True)
rpn_ramp(parameter: int | str, start: int, end: int, beat_start: float = 0.0, beat_end: float | None = None, resolution: int = 4, shape: str | subsequence.easing.EasingFn = 'linear', fine: bool = True, null_reset: bool = True) subsequence.pattern_builder.PatternBuilder[source]

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. The same mid-ramp persistence note applies: avoid plain p.cc(6, …) on this channel during the ramp window.

slide(notes: List[int] | None = None, steps: List[int] | None = None, time: float = 0.15, shape: str | subsequence.easing.EasingFn = 'linear', resolution: int = 1, bend_range: float | None = 2.0, wrap: bool = True, extend: bool = True) subsequence.pattern_builder.PatternBuilder[source]

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.

Call this after legato() / detached() / duration() so that note durations are final.

Parameters:
  • notes – List of note indices to slide into (0 = first). Supports negative indexing. Mutually exclusive with steps.

  • steps – List of step grid indices to slide into. Converted to pulse positions using self._default_grid. Mutually exclusive with notes.

  • time – Fraction of the preceding note’s duration used for the glide.

  • shape – Easing curve. Defaults to "linear".

  • resolution – Pulses between pitch bend messages.

  • bend_range – Instrument’s pitch wheel range in semitones (default 2.0). Pairs with larger intervals are skipped. Pass None to disable range checking.

  • wrap – If True (default), include a wrap-around slide from the last note back toward the first.

  • extend – If True (default), extend the preceding note’s duration to reach the slide target’s onset — 303-style legato through the glide.

Raises:

ValueError – If neither notes nor steps is provided.

Example

p.sequence(steps=[0, 4, 8, 12], pitches=[40, 42, 40, 43])
p.legato(0.95)

# Slide into the 2nd and 4th notes
p.slide(notes=[1, 3], time=0.2, shape="ease_in")

# Same using step grid indices
p.slide(steps=[4, 12], time=0.2, shape="ease_in")

# Slide without extending the preceding note
p.slide(notes=[1, 3], extend=False)
sysex(data: bytes | List[int], beat: float = 0.0) subsequence.pattern_builder.PatternBuilder[source]

Send a System Exclusive (SysEx) message at a beat position.

SysEx messages allow deep integration with synthesizers and other hardware: patch dumps, parameter control, and vendor-specific commands. The data argument should contain only the inner payload bytes, without the surrounding 0xF0 / 0xF7 framing — mido adds those automatically.

Parameters:
  • data – SysEx payload as bytes or a list of integers (0–127).

  • beat – Beat position within the pattern (default 0.0).

Example

# GM System On — reset a GM-compatible device to defaults
p.sysex([0x7E, 0x7F, 0x09, 0x01])