subsequence.pattern_midi ======================== .. py:module:: subsequence.pattern_midi .. autoapi-nested-parse:: 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 --------------- .. py:class:: PatternMidiMixin 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__``. .. py:method:: bend(note: int, amount: float, start: float = 0.0, end: float = 1.0, shape: Union[str, 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. Call this *after* ``legato()`` / ``detached()`` / ``duration()`` so that note durations are final. :param note: Note index (0 = first, -1 = last, etc.). :param amount: Target bend normalised to -1.0..1.0 (positive = up). With a standard ±2-semitone pitch wheel range, 0.5 = 1 semitone. :param start: Fraction of the note's duration at which the ramp begins (0.0 = note onset, default). :param end: Fraction of the note's duration at which the ramp ends (1.0 = note end, default). :param shape: Easing curve — a name string (e.g. ``"ease_in"``) or any callable mapping [0, 1] → [0, 1]. Defaults to ``"linear"``. :param resolution: Pulses between pitch bend messages. :raises IndexError: If *note* is out of range for the current pattern. .. rubric:: Example .. code-block:: python 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) .. py:method:: cc(control: Union[int, str], value: int, beat: float = 0.0) -> subsequence.pattern_builder.PatternBuilder Send a single CC message at a beat position. :param control: MIDI CC number (0–127), or a string name resolved via the pattern's ``cc_name_map``. :param value: CC value (0–127); out-of-range values are clamped. :param beat: Beat position within the pattern. .. py:method:: cc_ramp(control: Union[int, str], start: int, end: int, beat_start: float = 0.0, beat_end: Optional[float] = None, resolution: int = 1, shape: Union[str, subsequence.easing.EasingFn] = 'linear') -> subsequence.pattern_builder.PatternBuilder Interpolate a CC value over a beat range. :param control: MIDI CC number (0–127), or a string name resolved via the pattern's ``cc_name_map``. :param start: Starting CC value (0–127). :param end: Ending CC value (0–127). :param beat_start: Beat position to begin the ramp. :param beat_end: Beat position to end the ramp. Defaults to pattern length. :param 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. :param shape: Easing curve — a name string (e.g. ``"exponential"``) or any callable that maps [0, 1] → [0, 1]. Defaults to ``"linear"``. See :mod:`subsequence.easing` for available shapes. .. py:method:: nrpn(parameter: Union[int, str], value: int, beat: float = 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. :param parameter: 14-bit NRPN parameter number (0–16383), or a string resolved via the pattern's ``nrpn_name_map``. :param value: Parameter value. 0–127 if ``fine=False``; 0–16383 if ``fine=True``. :param beat: Beat position within the pattern. :param 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. :param 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. .. rubric:: Example .. code-block:: python # 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) .. py:method:: nrpn_ramp(parameter: Union[int, str], start: int, end: int, beat_start: float = 0.0, beat_end: Optional[float] = None, resolution: int = 4, shape: Union[str, 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``. **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. :param parameter: 14-bit NRPN parameter number, or a string resolved via the pattern's ``nrpn_name_map``. :param start: Starting value (0–16383 when ``fine=True``, 0–127 when False). :param end: Ending value. :param beat_start: Beat position to begin the ramp. :param beat_end: Beat position to end the ramp. Defaults to pattern length. :param resolution: Pulses between Data Entry messages (default 4). :param shape: Easing curve — string name or callable [0, 1] → [0, 1]. :param fine: If True (default), use full 14-bit Data Entry MSB+LSB. :param null_reset: If True (default), append the null sentinel at the end of the ramp (not per step). .. py:method:: osc(address: str, *args: Any, beat: float = 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. :param address: OSC address path (e.g. ``"/mixer/fader/1"``). :param ``*args``: OSC arguments — float, int, str, or bytes. :param beat: Beat position within the pattern (default 0.0). .. rubric:: Example .. code-block:: python # 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) .. py:method:: osc_ramp(address: str, start: float, end: float, beat_start: float = 0.0, beat_end: Optional[float] = None, resolution: int = 4, shape: Union[str, 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. :param address: OSC address path (e.g. ``"/mixer/fader/1"``). :param start: Starting float value. :param end: Ending float value. :param beat_start: Beat position to begin the ramp (default 0.0). :param beat_end: Beat position to end the ramp. Defaults to pattern length. :param 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. :param shape: Easing curve — a name string (e.g. ``"ease_in"``) or any callable that maps [0, 1] → [0, 1]. Defaults to ``"linear"``. See :mod:`subsequence.easing` for available shapes. .. rubric:: Example .. code-block:: python # 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") .. py:method:: pitch_bend(value: float, beat: float = 0.0) -> subsequence.pattern_builder.PatternBuilder Send a single pitch bend message at a beat position. :param value: Pitch bend amount, normalised from -1.0 to 1.0. :param beat: Beat position within the pattern. .. py:method:: pitch_bend_ramp(start: float, end: float, beat_start: float = 0.0, beat_end: Optional[float] = None, resolution: int = 1, shape: Union[str, subsequence.easing.EasingFn] = 'linear') -> subsequence.pattern_builder.PatternBuilder Interpolate pitch bend over a beat range. :param start: Starting pitch bend (-1.0 to 1.0). :param end: Ending pitch bend (-1.0 to 1.0). :param beat_start: Beat position to begin the ramp. :param beat_end: Beat position to end the ramp. Defaults to pattern length. :param 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. :param shape: Easing curve — a name string (e.g. ``"ease_out"``) or any callable that maps [0, 1] → [0, 1]. Defaults to ``"linear"``. See :mod:`subsequence.easing` for available shapes. .. py:method:: portamento(time: float = 0.15, shape: Union[str, subsequence.easing.EasingFn] = 'linear', resolution: int = 1, bend_range: 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. Call this *after* ``legato()`` / ``detached()`` / ``duration()`` so that note durations are final. Most effective on mono instruments where pitch bend is per-channel. :param time: Fraction of each note's duration used for the glide (default 0.15 — last 15% of the note). :param shape: Easing curve. Defaults to ``"linear"``. :param resolution: Pulses between pitch bend messages. :param 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). :param wrap: If ``True`` (default), glide from the last note toward the first note of the next cycle. .. rubric:: Example .. code-block:: python 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) .. py:method:: program_change(program: int, beat: float = 0.0, bank_msb: Optional[int] = None, bank_lsb: 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 synthesizer expects. :param program: Program (patch) number (0–127). :param beat: Beat position within the pattern (default 0.0). :param bank_msb: Bank select coarse (CC 0), 0–127. ``None`` = omit. :param bank_lsb: Bank select fine (CC 32), 0–127. ``None`` = omit. .. rubric:: Example .. code-block:: python @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) .. py:method:: rpn(parameter: Union[int, str], value: int, beat: float = 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. :param parameter: 14-bit RPN parameter number (0–16383), or one of the standard string names above. :param 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. :param beat: Beat position within the pattern. :param fine: If True, send 14-bit value via Data Entry MSB+LSB. :param null_reset: If True (default), follow with the RPN null sentinel. .. rubric:: Example .. code-block:: python # 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) .. py:method:: rpn_ramp(parameter: Union[int, str], start: int, end: int, beat_start: float = 0.0, beat_end: Optional[float] = None, resolution: int = 4, shape: Union[str, 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 :meth:`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. .. py:method:: slide(notes: Optional[List[int]] = None, steps: Optional[List[int]] = None, time: float = 0.15, shape: Union[str, subsequence.easing.EasingFn] = 'linear', resolution: int = 1, bend_range: 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. Call this *after* ``legato()`` / ``detached()`` / ``duration()`` so that note durations are final. :param notes: List of note indices to slide *into* (0 = first). Supports negative indexing. Mutually exclusive with *steps*. :param steps: List of step grid indices to slide *into*. Converted to pulse positions using ``self._default_grid``. Mutually exclusive with *notes*. :param time: Fraction of the preceding note's duration used for the glide. :param shape: Easing curve. Defaults to ``"linear"``. :param resolution: Pulses between pitch bend messages. :param bend_range: Instrument's pitch wheel range in semitones (default 2.0). Pairs with larger intervals are skipped. Pass ``None`` to disable range checking. :param wrap: If ``True`` (default), include a wrap-around slide from the last note back toward the first. :param 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. .. rubric:: Example .. code-block:: python 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) .. py:method:: sysex(data: Union[bytes, List[int]], beat: float = 0.0) -> subsequence.pattern_builder.PatternBuilder 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. :param data: SysEx payload as ``bytes`` or a list of integers (0–127). :param beat: Beat position within the pattern (default 0.0). .. rubric:: Example .. code-block:: python # GM System On — reset a GM-compatible device to defaults p.sysex([0x7E, 0x7F, 0x09, 0x01])