subsequence.intervals

Interval and scale definitions, plus the helpers that resolve them.

Holds INTERVAL_DEFINITIONS (named scales and chords as semitone lists) and the functions that work against it — scale_notes, scale_pitch_classes, quantize_pitch, register_scale and friends.

Module Contents

subsequence.intervals.get_diatonic_intervals(scale_notes: List[int], intervals: List[int] | None = None, mode: str = 'scale') List[List[int]][source]

Construct diatonic chords from a scale.

subsequence.intervals.get_intervals(name: str) List[int][source]

Return a named interval list from the registry.

subsequence.intervals.quantize_pitch(pitch: int, scale_pcs: Sequence[int]) int[source]

Snap a MIDI pitch to the nearest note in the given scale.

Searches outward in semitone steps from the input pitch. When two notes are equidistant (e.g. C# between C and D in C major), the upward direction is preferred.

Parameters:
  • pitch – MIDI note number to quantize.

  • scale_pcs – Pitch classes accepted by the scale (0–11). Typically the output of scale_pitch_classes().

Returns:

A MIDI note number that lies within the scale.

Example

# Snap C# (61) to C (60) in C major
scale = scale_pitch_classes(0, "ionian")  # [0, 2, 4, 5, 7, 9, 11]
quantize_pitch(61, scale)  # → 60
subsequence.intervals.register_scale(name: str, intervals: List[int], qualities: List[str] | None = None) None[source]

Register a custom scale for use with p.snap_to_scale() and scale_pitch_classes().

Built-in scale names (e.g. "minor", "hirajoshi") cannot be overwritten. Custom names may be re-registered freely — live reload re-runs registration on every save, so this must not raise.

Parameters:
  • name – Scale name (used in p.snap_to_scale(key, name)). Must not be the name of a built-in scale.

  • intervals – Semitone offsets from the root (e.g. [0, 2, 3, 7, 8] for Hirajōshi). Must be whole numbers, start with 0, ascend strictly, and stay within 0–11.

  • qualities – Optional chord quality per scale degree (e.g. ["minor", "major", "minor", "major", "diminished"]). Required only if you want to use the scale with diatonic_chords() or diatonic_chord_sequence().

Raises:

ValueError – If name is a built-in scale, or intervals / qualities fail the rules above.

Example:

import subsequence

subsequence.register_scale("raga_bhairav", [0, 1, 4, 5, 7, 8, 11])

@comp.pattern(channel=0, length=4)
def melody (p):
        p.note(60, beat=0)
        p.snap_to_scale("C", "raga_bhairav")
subsequence.intervals.scale_notes(key: str, mode: str = 'ionian', low: int = 60, high: int = 72, count: int | None = None) List[int][source]

Return MIDI note numbers for a scale within a pitch range.

Parameters:
  • key – Scale root as a note name ("C", "F#", "Bb", etc.). This acts as a pitch-class filter only — it determines which semitone positions (0–11) are valid members of the scale, but does not affect which octave notes are drawn from. Notes are selected starting from low upward; key controls which notes are kept, not where the sequence starts. To guarantee the first returned note is the root, low must be a MIDI number whose pitch class matches key. When starting from an arbitrary MIDI number, derive the key name with subsequence.chords.PC_TO_NOTE_NAME[root_pitch % 12].

  • mode – Scale mode name. Supports all keys of SCALE_MODE_MAP (e.g. "ionian", "dorian", "natural_minor", "major_pentatonic"). Use register_scale() for custom scales.

  • low – Lowest MIDI note (inclusive). When count is set, this is the starting note from which the scale ascends. If ``low`` is not a member of the scale defined by ``key``, it is silently skipped and the first returned note will be the next in-scale pitch above low.

  • high – Highest MIDI note (inclusive). Ignored when count is set.

  • count – Exact number of notes to return. Notes ascend from low through successive scale degrees, cycling into higher octaves as needed. When None (default), all scale tones between low and high are returned.

Returns:

Sorted list of MIDI note numbers.

Examples

import subsequence
import subsequence.constants.midi_notes as notes

# C major: all tones from middle C to C5
subsequence.scale_notes("C", "ionian", low=notes.C4, high=notes.C5)
# → [60, 62, 64, 65, 67, 69, 71, 72]

# E natural minor (aeolian) across one octave
subsequence.scale_notes("E", "aeolian", low=notes.E2, high=notes.E3)
# → [40, 42, 43, 45, 47, 48, 50, 52]

# 15 notes of A minor pentatonic ascending from A3
subsequence.scale_notes("A", "minor_pentatonic", low=notes.A3, count=15)
# → [57, 60, 62, 64, 67, 69, 72, 74, 76, 79, 81, 84, 86, 88, 91]

# Misalignment: key="E" but low=C4 — first note is C, not E
subsequence.scale_notes("E", "minor", low=60, count=4)
# → [60, 62, 64, 66]  (C D E F# — all in E natural minor, but starts on C)

# Fix: derive key name from root_pitch so low is always in the scale
root_pitch = 64  # E4
key = subsequence.chords.PC_TO_NOTE_NAME[root_pitch % 12]  # → "E"
subsequence.scale_notes(key, "minor", low=root_pitch, count=4)
# → [64, 66, 67, 69]  (E F# G A — starts on the root)
subsequence.intervals.scale_pitch_classes(key_pc: int, mode: str = 'ionian') List[int][source]

Return the pitch classes (0–11) that belong to a key and mode.

Parameters:
  • key_pc – Root pitch class (0 = C, 1 = C#/Db, …, 11 = B).

  • mode – Scale mode name. Supports all keys of DIATONIC_MODE_MAP (e.g. "ionian", "dorian", "minor", "harmonic_minor").

Returns:

Pitch classes in scale-degree order, starting from the root (length varies by mode). Values wrap mod-12, so the list is not numerically sorted for non-C roots.

Example

# C major pitch classes
scale_pitch_classes(0, "ionian")  # → [0, 2, 4, 5, 7, 9, 11]

# A minor pitch classes
scale_pitch_classes(9, "aeolian")  # → [9, 11, 0, 2, 4, 5, 7] (mod-12)