Skip to content

Chord

Chord

Chord(
    root: Union[str, int, Pitch, None] = None,
    quality: Optional[str] = None,
    pitches: Optional[Sequence] = None,
    bass: Union[str, int, Pitch, None] = None,
)

Represents a chord as a set of pitch classes, with optional root, quality, and spelling. Properties that cannot be determined are set to None.

Parameters:

  • root (str | int | Pitch | None, default: None ) –

    The root of the chord, as a pitch name ("C"), pitch-class int (0), or Pitch. If None, a rootless chord is created from pitches.

  • quality (str | None, default: None ) –

    Quality string, e.g. "major", "min7", "°" typically as imported from some user provided string, or corpus source. If both root and quality are given, pitches is derived automatically. If only root is given, quality remains None unless it can be inferred from pitches.

  • pitches (sequence of (str | int | Pitch) | None, default: None ) –

    Explicit pitch members. Each element may be a pitch name ("Bb"), a pitch-class int (10), or a Pitch. If given alongside root and quality, these are stored as the chord's spelling.

  • bass (str | int | Pitch | None, default: None ) –

    The bass note if it differs from the root (slash chords, inversions).

Attributes:

  • root (Pitch | None) –
  • quality (str | None) –

    Canonical quality name, e.g. "major", "minor7".

  • pitches (tuple[Pitch, ...] | None) –

    Spelling as pitch-class Pitches (octave == -1), in the order given.

  • bass (Pitch | None) –
  • key (str | None) –

    Set when constructed via :meth:from_roman.

Examples:

These examples demonstrate different ways to build Chord objects, and some of the attributes.

  1. From a root + quality string:
>>> c = Chord("C", "major")
>>> c
Chord(C major)
>>> c == Chord("C", "maj")
True

Here are some derived properties:

>>> c.pitch_class_set
[0, 4, 7]
>>> c.intervals
(0, 4, 7)
>>> c.pitch_class_vector
(1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0)

Here is a different quality example (see QUALTIES for all options):

>>> Chord("G", "dominant7").label
'G7'
  1. From a Roman numeral, given a key.
>>> Chord.from_roman("IV", "C").root.name
'F'
>>> Chord.from_roman("IV", "C").root.pitch_class
5
>>> Chord.from_roman("V7", "G").label
'D7'

Roman numerals are complex and not consistent across standards. This illustrates a case in point: The triad in a minor key on the second degree is diminshed diatonically, and the seventh is a half-diminshed. Parsing routines vary. Here we take "ii7" to imply the diatonic half-diminshed, "iiø7" is equivalent (not needed to specify the half-diminshed) and "iio7" modifies to diminished. See also the from_scale_degree method (also demo'd here).

>>> Chord.from_roman("ii", "d").label
'Eo'
>>> Chord.from_roman("ii7", "d").label
'Eø7'
>>> Chord.from_roman("iiø7", "d").label
'Eø7'
>>> Chord.from_scale_degree(2, "d").label
'Eo'

Explicit suffix takes priority over scale-stacking

>>> Chord.from_roman("iio7", "d").label
'Eo7'
  1. From pitches directly, either as pitch name or pitch class:
>>> Chord(pitches=["C", "Eb", "G"]).quality
'minor'
>>> Chord(pitches=[0, 3, 7]).quality
'minor'
>>> Chord(pitches=["C", "E", "G", "Bb"]).label
'C7'
Source code in amads/core/chord.py
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
def __init__(
    self,
    root:    Union[str, int, Pitch, None] = None,
    quality: Optional[str]                = None,
    pitches: Optional[Sequence]           = None,
    bass:    Union[str, int, Pitch, None] = None,
) -> None:

    self.key = None
    self.root = _parse_root(root) if root is not None else None
    self.quality = _canonical_quality(quality) if quality is not None else None
    if pitches is not None: # Pitches explicitly supplied:
        self.pitches = tuple(_parse_root(p) for p in pitches)
    elif self.root is not None and self.quality is not None: # Derive pitches from root + quality:
        intervals = QUALITIES[self.quality]
        root_pc   = self.root.key_num % 12
        self.pitches = tuple(
            Pitch((root_pc + i) % 12)
            for i in intervals
        )
    else:
        self.pitches = None

    # Derive quality from pitches:
    if self.quality is None and self.pitches is not None:
        self.quality = self._infer_quality()

    # Derive root from pitches + quality:
    if self.root is None and self.quality is not None and self.pitches is not None:
        self.root = self._infer_root()

    self.bass = _parse_root(bass) if bass is not None else None

Attributes

pitch_class_set property

pitch_class_set: Optional[list[int]]

Sorted list of pitch classes, or None if pitches unknown.

pitch_class_vector property

pitch_class_vector: Optional[tuple[int, ...]]

12-dimensional indicator vector, or None if pitches unknown.

pitch_names property

pitch_names: Optional[list[str]]

Spelled pitch names (e.g. ["C", "Eb", "G"]), or None.

intervals property

intervals: Optional[tuple[int, ...]]

Semitone intervals above the root, or None if root unknown.

inversion property

inversion: Optional[int]

0 = root position, 1 = first inversion, etc.; None if undetermined.

label property

label: Optional[str]

Short label like "Cmaj7", "Dmin", "G7". Broadly matches leadsheet notation.

Returns None if either or both of root or quality unknown.

Functions

from_scale_degree classmethod

from_scale_degree(
    degree: int, key: Union[str, int, Pitch], *, seventh: bool = False
) -> Chord

Alternate constructor for making a Chord from a scale degree and key.

Parameters:

  • degree (int) –

    Scale degree. Note that this is 1-indexed (1 = tonic, ..., 7 = leading tone).

  • key (str | int | Pitch) –

    Key, e.g. "C", "d" (lower-case = minor) ...

  • seventh (bool, default: False ) –

    If True, stack the diatonic seventh above the triad.

Returns:

Raises:

  • ValueError

    If degree is outside 1–7.

Examples:

>>> Chord.from_scale_degree(2, "C").label
'Dm'
>>> ii7 = Chord.from_scale_degree(2, "C", seventh=True)
>>> ii7.label
'Dm7'
>>> ii7.intervals
(0, 3, 7, 10)
>>> Chord.from_scale_degree(7, "C").label
'Bo'
>>> Chord.from_scale_degree(2, "d").label
'Eo'
>>> Chord.from_scale_degree(2, "d", seventh=True).label
'Eø7'
Source code in amads/core/chord.py
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
@classmethod
def from_scale_degree(
        cls,
        degree: int,
        key: Union[str, int, Pitch],
        *,
        seventh: bool = False,
) -> "Chord":
    """
    Alternate constructor for making a `Chord` from a scale degree and key.

    Parameters
    ----------
    degree : int
        Scale degree.
        Note that this is 1-indexed (1 = tonic, ..., 7 = leading tone).
    key : str | int | Pitch
        Key, e.g. `"C"`, `"d"` (lower-case = minor) ...
    seventh : bool
        If True, stack the diatonic seventh above the triad.

    Returns
    -------
    Chord

    Raises
    ------
    ValueError
        If *degree* is outside 1–7.

    Examples
    --------
    >>> Chord.from_scale_degree(2, "C").label
    'Dm'

    >>> ii7 = Chord.from_scale_degree(2, "C", seventh=True)
    >>> ii7.label
    'Dm7'

    >>> ii7.intervals
    (0, 3, 7, 10)

    >>> Chord.from_scale_degree(7, "C").label
    'Bo'

    >>> Chord.from_scale_degree(2, "d").label
    'Eo'

    >>> Chord.from_scale_degree(2, "d", seventh=True).label
    'Eø7'
    """
    if not 1 <= degree <= 7:
        raise ValueError(f"Scale degree must be between 1 and 7, got {degree!r}.")
    numeral = ["I", "II", "III", "IV", "V", "VI", "VII"][degree - 1]
    return cls.from_roman(numeral, key, seventh=seventh)

from_pitch_collection classmethod

from_pitch_collection(collection: PitchCollection) -> Chord

Alternate constructor for making a Chord from a PitchCollection.

Quality is identified by matching the pitch-class set against the quality registry under all rotations (potential roots). If exactly one root yields a known quality, both are set; if multiple match (e.g. augmented triads), root is left None. Spelling is preserved from the collection.

Parameters:

Returns:

Source code in amads/core/chord.py
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
@classmethod
def from_pitch_collection(cls, collection: PitchCollection) -> "Chord":
    """
    Alternate constructor for making a `Chord` from a PitchCollection.

    Quality is identified by matching the pitch-class set against the
    quality registry under all rotations (potential roots).  If exactly
    one root yields a known quality, both are set; if multiple match
    (e.g. augmented triads), root is left None.  Spelling is preserved
    from the collection.

    Parameters
    ----------
    collection : PitchCollection

    Returns
    -------
    Chord
    """
    pitches = collection.pitches
    pc_pitches: list[Pitch] = [Pitch(p.key_num % 12, p.alt) for p in pitches]
    pc_set = frozenset(p.key_num % 12 for p in pc_pitches)

    # try each member as potential root
    matches: list[tuple[Pitch, str]] = []
    for candidate_root in pc_set:
        intervals = tuple(sorted((pc - candidate_root) % 12 for pc in pc_set))
        key = frozenset(intervals)
        if key in _INTERVAL_SET_TO_QUALITY:
            matches.append((Pitch(candidate_root), _INTERVAL_SET_TO_QUALITY[key]))

    chord = cls.__new__(cls)
    chord.key  = None
    chord.bass = pc_pitches[0] if pc_pitches else None
    chord.pitches = tuple(pc_pitches)

    if len(matches) == 1:
        chord.root, chord.quality = matches[0]
    elif len(matches) > 1:
        # ambiguous (e.g. augmented): no single root
        chord.root    = None
        chord.quality = matches[0][1]  # at least quality is unambiguous
    else:
        chord.root    = None
        chord.quality = None

    return chord

roman_numeral

roman_numeral(key: Union[str, int, Pitch]) -> Optional[str]

Return the Roman numeral of this chord in the given key, or None.

Source code in amads/core/chord.py
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
def roman_numeral(self, key: Union[str, int, Pitch]) -> Optional[str]:
    """Return the Roman numeral of this chord in the given key, or None."""
    if self.root is None or self.quality is None:
        return None

    scale, tonic_pc = _parse_key(key)
    root_pc  = int(self.root.key_num) % 12
    interval = (root_pc - tonic_pc) % 12

    if interval not in scale:
        return None  # chromatic root — don't attempt diatonic numeral

    degree = scale.index(interval)
    numerals = ["I", "II", "III", "IV", "V", "VI", "VII"]
    numeral  = numerals[degree]

    # lowercase for minor/diminished
    if self.quality in ("minor", "minor7", "diminished", "diminished7", "half-dim7"):
        numeral = numeral.lower()
    if self.quality in ("diminished", "diminished7"):
        numeral += "°"
    if self.quality in ("dominant7", "major7", "minor7",
                        "diminished7", "half-dim7"):
        numeral += "7"

    return numeral