Skip to content

ChordDetect

The chord-detection function lives in its own module but is most commonly accessed via [Chord.detect][tonal_py.chord.detect].

chord_detect

Chord detection — find chord names from a list of notes.

The single public function detect returns chord names that match the input notes, weighted so root-position matches outrank inversions. Re-exported as [Chord.detect][tonal_py.chord.detect].

Example

from tonal_py.chord_detect import detect detect(["C", "E", "G"]) ['CM', 'Em#5/C'] detect(["E", "G", "C"]) ['Em#5', 'CM/E']

Source parity: @tonaljs/chord-detect

detect

detect(source: list[str], options: DetectOptions | None = None) -> list[str]

Detect chord names that match a list of notes.

The first note is treated as a likely root (root-position weight = 1.0); inversions get half-weight (0.5). Results are sorted descending by weight.

Parameters:

Name Type Description Default
source list[str]

Note names.

required
options DetectOptions | None

Optional dict. {"assumePerfectFifth": True} allows matches when the input is missing a perfect fifth (useful for shell voicings).

None

Returns:

Type Description
list[str]

Matching chord names. Empty list for empty input.

Example

from tonal_py import Chord Chord.detect(["C", "E", "G"]) ['CM', 'Em#5/C'] Chord.detect(["D", "F#", "A", "C"]) ['D7'] Chord.detect([]) []

Source code in src/tonal_py/chord_detect.py
def detect(source: list[str], options: DetectOptions | None = None) -> list[str]:
    """Detect chord names that match a list of notes.

    The first note is treated as a likely root (root-position weight = 1.0);
    inversions get half-weight (0.5). Results are sorted descending by
    weight.

    Args:
        source: Note names.
        options: Optional dict. `{"assumePerfectFifth": True}` allows
            matches when the input is missing a perfect fifth (useful for
            shell voicings).

    Returns:
        Matching chord names. Empty list for empty input.

    Example:
        >>> from tonal_py import Chord
        >>> Chord.detect(["C", "E", "G"])
        ['CM', 'Em#5/C']
        >>> Chord.detect(["D", "F#", "A", "C"])
        ['D7']
        >>> Chord.detect([])
        []
    """
    options = options or {}  # type: ignore[assignment]
    notes = [n.pc for n in (_pitch_note.note(x) for x in source) if n.pc]
    # JS source uses `note.length === 0` here — a bug (`note` is the imported
    # function so .length is its arity, never 0). The intended check is
    # `notes.length === 0`. JS still effectively returns [] for empty input
    # because all downstream loops produce no matches; in Python, an empty
    # `notes` causes `notes[0]` to raise IndexError, so we guard explicitly.
    if not notes:
        return []
    found = _find_matches(notes, 1, options)
    found = [c for c in found if c["weight"]]
    found.sort(key=lambda c: c["weight"], reverse=True)
    return [c["name"] for c in found]