subsequence.intervals ===================== .. py:module:: subsequence.intervals .. autoapi-nested-parse:: 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 --------------- .. py:function:: get_diatonic_intervals(scale_notes: List[int], intervals: Optional[List[int]] = None, mode: str = 'scale') -> List[List[int]] Construct diatonic chords from a scale. .. py:function:: get_intervals(name: str) -> List[int] Return a named interval list from the registry. .. py:function:: quantize_pitch(pitch: int, scale_pcs: Sequence[int]) -> int 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. :param pitch: MIDI note number to quantize. :param scale_pcs: Pitch classes accepted by the scale (0–11). Typically the output of :func:`scale_pitch_classes`. :returns: A MIDI note number that lies within the scale. .. rubric:: Example .. code-block:: python # 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 .. py:function:: register_scale(name: str, intervals: List[int], qualities: Optional[List[str]] = None) -> None 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. :param name: Scale name (used in ``p.snap_to_scale(key, name)``). Must not be the name of a built-in scale. :param 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. :param 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") .. py:function:: scale_notes(key: str, mode: str = 'ionian', low: int = 60, high: int = 72, count: Optional[int] = None) -> List[int] Return MIDI note numbers for a scale within a pitch range. :param 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]``. :param mode: Scale mode name. Supports all keys of :data:`SCALE_MODE_MAP` (e.g. ``"ionian"``, ``"dorian"``, ``"natural_minor"``, ``"major_pentatonic"``). Use :func:`register_scale` for custom scales. :param 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``. :param high: Highest MIDI note (inclusive). Ignored when ``count`` is set. :param 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. .. rubric:: Examples .. code-block:: python 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) .. py:function:: scale_pitch_classes(key_pc: int, mode: str = 'ionian') -> List[int] Return the pitch classes (0–11) that belong to a key and mode. :param key_pc: Root pitch class (0 = C, 1 = C#/Db, …, 11 = B). :param 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. .. rubric:: Example .. code-block:: python # 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)