subsequence.chords ================== .. py:module:: subsequence.chords .. autoapi-nested-parse:: Chord definitions and pitch class utilities. This module provides chord quality definitions, pitch class mappings, and the ``Chord`` class for representing and manipulating chords. Module-level constants: - ``NOTE_NAME_TO_PC``: Maps note names (e.g., ``"C"``, ``"F#"``, ``"Bb"``) to pitch classes (0-11) - ``PC_TO_NOTE_NAME``: Maps pitch classes to note names - ``CHORD_INTERVALS``: Maps chord quality names to interval lists (semitones from root) - ``CHORD_SUFFIX``: Maps chord quality names to human-readable suffixes (e.g., ``"m"``, ``"7"``) Module-level helpers: - ``key_name_to_pc(key_name)``: Validate a key name and return its pitch class (0–11). Raises ``ValueError`` for unknown names. This is the canonical key validation function used by ``harmony.py``, ``pattern_builder.snap_to_scale()``, and ``chord_graphs.validate_key_name()``. Chord qualities: ``"major"``, ``"minor"``, ``"diminished"``, ``"augmented"``, ``"dominant_7th"``, ``"major_7th"``, ``"minor_7th"``, ``"half_diminished_7th"``, ``"sus2"``, ``"sus4"`` Module Contents --------------- .. py:class:: Chord Represents a chord as a root pitch class and quality. .. py:method:: bass_note(root_midi: int, octave_offset: int = -1) -> int Return the chord root shifted by a number of octaves. Commonly used to produce a bass register note one or two octaves below the chord voicing. :param root_midi: Reference MIDI note number (passed to :meth:`root_note`). :param octave_offset: Octaves to shift; negative moves down (default ``-1``). :returns: MIDI note number of the chord root in the target register. .. rubric:: Example .. code-block:: python chord = Chord(root_pc=4, quality="major") # E major chord.bass_note(64) # → 52 (E3, one octave down from E4) chord.bass_note(64, -2) # → 40 (E2, two octaves down) .. py:method:: intervals() -> List[int] Return the chord intervals for this chord quality. .. py:method:: name() -> str Return a human-friendly chord name. A registered quality without a suffix prints as ``root(quality)`` (e.g. ``"C(quartal)"``) rather than masquerading as a plain major. .. py:method:: root_note(root_midi: int) -> int Return the MIDI note number for the chord root nearest to *root_midi*. This is equivalent to ``self.tones(root_midi)[0]`` but makes intent explicit when you only need the single root pitch. :param root_midi: Reference MIDI note number used to find the closest octave of this chord's root pitch class. :returns: MIDI note number of the chord root. .. rubric:: Example .. code-block:: python chord = Chord(root_pc=4, quality="major") # E major chord.root_note(60) # → 64 (E4, nearest to C4) chord.root_note(69) # → 64 (E4, nearest to A4) .. py:method:: tones(root: int, inversion: int = 0, count: Optional[int] = None) -> List[int] Return MIDI note numbers for chord tones starting from a root. Finds the MIDI note corresponding to the chord's root pitch class that is closest to the provided ``root`` argument. :param root: MIDI note number (e.g., 60 = middle C) to center the chord around. :param inversion: Chord inversion (0 = root position, 1 = first, 2 = second, ...). Wraps around for values >= number of notes. :param count: Number of notes to return. When set, the chord intervals cycle into higher octaves until ``count`` notes are produced. When ``None`` (default), returns the natural chord tones. :returns: List of MIDI note numbers for chord tones .. rubric:: Example .. code-block:: python chord = Chord(root_pc=0, quality="major") # C major chord.tones(root=60) # [60, 64, 67] - root position around C4 chord.tones(root=62) # [60, 64, 67] - still finds C4 as closest root chord.tones(root=70) # [72, 76, 79] - finds C5 as closest root .. py:function:: key_name_to_pc(key_name: str) -> int Validate a key name and return its pitch class (0–11). :param key_name: Note name (e.g. ``"C"``, ``"F#"``, ``"Bb"``). :returns: Pitch class integer (0–11). :raises ValueError: If the key name is not recognised. .. rubric:: Example .. code-block:: python key_name_to_pc("C") # → 0 key_name_to_pc("F#") # → 6 key_name_to_pc("Bb") # → 10 .. py:function:: parse_chord(name: str) -> Chord Parse a chord name like ``"Cm7"`` or ``"Dbmaj7"`` into a :class:`Chord`. The name is a root note (``A``–``G`` with an optional ``#`` or ``b``) followed by a quality suffix: ``""`` major, ``m`` minor, ``dim`` diminished, ``+``/``aug`` augmented, ``7`` dominant 7th, ``maj7`` major 7th, ``m7`` minor 7th, ``m7b5``/``ø`` half-diminished 7th, ``sus2``, ``sus4``. A few common alternates (``min``, ``-``, ``M7``, …) are accepted too. Raises ``ValueError`` for anything it can't read, so a typo surfaces at the call site rather than as a silently wrong chord. .. rubric:: Example .. code-block:: python parse_chord("Cm7") # → Chord(root_pc=0, quality="minor_7th") parse_chord("Dbmaj7") # → Chord(root_pc=1, quality="major_7th") parse_chord("F#") # → Chord(root_pc=6, quality="major") .. py:function:: register_chord_quality(name: str, intervals: List[int], suffix: Optional[str] = None) -> None Register a custom chord quality for use everywhere chords are used. The counterpart to :func:`subsequence.intervals.register_scale` — it opens the quality table so quartal stacks, clusters, and extended chords become first-class symbolic chords: they work in progressions, graphs, voice leading, and ``describe()`` output. Built-in qualities (e.g. ``"minor"``) 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: Quality name (used as ``Chord(root_pc, quality=name)``). :param intervals: Semitone offsets from the root (e.g. ``[0, 5, 10]`` for a quartal stack, ``[0, 3, 7, 10, 14]`` for a minor 9th). Must start with 0, ascend strictly, and stay within 0–24 (extensions reach past the octave). :param suffix: Optional chord-name suffix. When given, ``parse_chord()`` accepts ``"A" + suffix`` and ``Chord.name()`` prints it — so ``register_chord_quality("minor_9th", [0, 3, 7, 10, 14], suffix="m9")`` makes ``"Am9"`` parse from then on. Must not collide with a built-in suffix. .. rubric:: Example .. code-block:: python import subsequence subsequence.register_chord_quality("quartal", [0, 5, 10], suffix="q4") subsequence.parse_chord("Dq4") # → Chord(root_pc=2, quality="quartal")