Scales

class pytheory.scales.Scale(*, tones: tuple[Tone, ...], degrees: tuple[str, ...] | None = None, system: str | System = 'western')[source]

Bases: object

__init__(*, tones: tuple[Tone, ...], degrees: tuple[str, ...] | None = None, system: str | System = 'western') None[source]

Initialize a Scale from a sequence of Tones.

Parameters:
  • tones – The tones that make up the scale.

  • degrees – Optional names for each scale degree (must match length of tones).

  • system – A tone system name or System instance.

Raises:

ValueError – If degrees is provided but its length differs from tones.

property system: System | None

Return the tone system for this scale.

Resolves a system name to a System object on first access.

__repr__() str[source]

Return repr(self).

__iter__()[source]

Iterate over the tones in this scale.

__len__() int[source]

Return the number of tones in this scale (including the octave).

__contains__(item: str | Tone) bool[source]

Check whether a tone or note name belongs to this scale.

property note_names: list[str]

List of note names in this scale.

fitness(*note_names: str) float[source]

Score how well a set of notes fits this scale (0.0–1.0).

Returns the fraction of the given notes that appear in the scale. Useful for melody analysis — testing whether a phrase belongs to a particular scale or mode.

Parameters:

*note_names – Note name strings (e.g. "C", "F#").

Returns:

A float from 0.0 (no notes match) to 1.0 (all notes match).

Example:

>>> c_major = TonedScale(tonic="C4")["major"]
>>> c_major.fitness("C", "D", "E", "G")
1.0
>>> c_major.fitness("C", "D", "F#", "G")
0.75
>>> c_major.fitness("C#", "D#", "F#")
0.0
degree_name(n: int, *, minor: bool = False) str[source]

Return the traditional name for the nth scale degree (0-indexed).

Parameters:
  • n – The scale degree index (0 = tonic, 1 = supertonic, etc.).

  • minor – If True, use “subtonic” instead of “leading tone” for degree 6.

Returns:

A string like “tonic”, “dominant”, etc.

Example:

>>> TonedScale(tonic="C4")["major"].degree_name(0)
'tonic'
>>> TonedScale(tonic="C4")["major"].degree_name(4)
'dominant'
chord(*degrees: int) Chord[source]

Build a Chord from scale degrees (0-indexed).

Wraps around if degrees exceed the scale length, transposing up by an octave as needed.

Example: scale.chord(0, 2, 4) builds a triad from the 1st, 3rd, 5th.

transpose(semitones: int) Scale[source]

Return a new Scale transposed by the given number of semitones.

Every tone is shifted by the same interval, preserving the scale’s interval pattern.

Example:

>>> c_major = TonedScale(tonic="C4")["major"]
>>> d_major = c_major.transpose(2)
>>> d_major.note_names
['D', 'E', 'F#', 'G', 'A', 'B', 'C#', 'D']
triad(root: int = 0) Chord[source]

Build a triad starting from the given scale degree (0-indexed).

Returns a chord with the root, 3rd, and 5th above it.

seventh(root: int = 0) Chord[source]

Build a seventh chord from the given scale degree (0-indexed).

Returns a chord with the root, 3rd, 5th, and 7th.

progression(*numerals: str) list[Chord][source]

Build a chord progression from Roman numeral strings.

The chord’s quality follows the numeral, not just the scale, so the notation says exactly what you mean:

  • case sets major vs minor — "V" is a major triad, "v" a minor one (so an uppercase "V" in a minor key gives the harmonic-minor dominant, and "IV" in minor gives the Dorian major-IV);

  • quality markers "°"/"o" (diminished), "ø" (half-diminished), "+" (augmented), "maj" (major 7th);

  • a flat/sharp prefix borrows from outside the key — "bVII" is a major triad on the lowered 7th degree (the Mixolydian/​rock ♭VII), "bII" the Neapolitan, and so on;

  • a trailing ``”7”`` makes it a seventh chord (dominant for an uppercase numeral, minor for lowercase);

  • a slash marks a secondary/applied chord — "V7/V" is the dominant of the dominant, "vii°/ii" the leading-tone chord of ii (the part before the slash is read in a major key rooted on the target).

Example:

>>> scale.progression("I", "IV", "V7", "I")
[<Chord C major>, <Chord F major>, <Chord G dominant 7th>, <Chord C major>]
>>> scale.progression("I", "bVII")           # Mixolydian vamp
[<Chord C major>, <Chord Bb major>]
>>> scale.progression("I", "V7/V", "V7", "I")
[<Chord C major>, <Chord D dominant 7th>, <Chord G dominant 7th>, <Chord C major>]
nashville(*numbers: int | str) list[Chord][source]

Build a chord progression using Nashville number system.

The Nashville number system uses Arabic numerals instead of Roman numerals. It’s the standard chart system in Nashville recording studios.

Numbers 1-7 build diatonic triads. Suffix "7" for seventh chords, "m" to force minor.

Example:

>>> scale.nashville(1, 4, 5, 1)
[<Chord C major>, <Chord F major>, <Chord G major>, <Chord C major>]
static detect(*note_names: str) tuple[str, str, int] | None[source]

Detect the most likely scale from a set of note names.

Tries all scales in the Western system and returns the best match as a (tonic, scale_name, match_count) tuple.

Example:

>>> Scale.detect("C", "D", "E", "F", "G", "A", "B")
('C', 'major', 7)
>>> Scale.detect("C", "D", "Eb", "F", "G", "Ab", "Bb")
('C', 'minor', 7)
static recommend(*note_names: str, top: int = 5) list[tuple[str, str, float]][source]

Recommend the best-matching scales for a set of notes, ranked by fitness.

Tests the given notes against every scale in the Western system and returns the top matches. Useful for figuring out what scale a melody or chord progression belongs to, or finding alternative scales to play over a set of changes.

Parameters:
  • *note_names – Note name strings (e.g. "C", "E", "G").

  • top – Number of results to return (default 5).

Returns:

A list of (tonic, scale_name, fitness) tuples sorted by fitness descending. Fitness is 0.0–1.0.

Example:

>>> Scale.recommend("C", "D", "E", "G", "A")
[('C', 'major', 1.0), ('G', 'major', 1.0), ...]
>>> Scale.recommend("C", "Eb", "F", "Gb", "G", "Bb")
[('C', 'blues', 1.0), ...]
harmonize() list[Chord][source]

Build diatonic triads on every scale degree.

Returns a list of Chords — one triad for each degree of the scale. In a major scale this produces: I, ii, iii, IV, V, vi, vii°.

Example:

>>> [c.identify() for c in TonedScale(tonic="C4")["major"].harmonize()]
['C major', 'D minor', 'E minor', 'F major', 'G major', 'A minor', 'B diminished']
parallel_modes() dict[str, list[str]][source]

All modes that share the same notes as this scale.

For example, C major shares its notes with D dorian, E phrygian, F lydian, G mixolydian, A aeolian, and B locrian.

Returns:

A dict mapping "tonic mode" to note name lists.

Example:

>>> c_major = TonedScale(tonic="C4")["major"]
>>> c_major.parallel_modes()
{'C ionian': ['C', 'D', 'E', ...], 'D dorian': ['D', 'E', 'F', ...], ...}
degree(item: str | int | slice, major: bool | None = None, minor: bool = False) Tone | tuple[Tone, ...] | None[source]

Look up the tone(s) at a scale degree.

Parameters:
  • item – The degree to retrieve. May be an integer index (0-indexed), a slice, a Roman numeral ("IV"), or a degree/mode name ("dominant", "dorian").

  • minor (major /) – Mutually exclusive hints used when resolving an ambiguous degree reference. Pass at most one.

Returns:

A single Tone, a tuple of Tones (for a slice), or None if the degree could not be resolved.

Raises:

ValueError – If both major and minor are truthy.

__getitem__(item: str | int | slice) Tone | tuple[Tone, ...][source]

Retrieve a tone by scale degree (integer, Roman numeral, or degree name).

Raises:

KeyError – If the given degree is not found in this scale.

pytheory.scales.PROGRESSIONS = {'12-bar blues': ('I', 'I', 'I', 'I', 'IV', 'IV', 'I', 'I', 'V', 'IV', 'I', 'V'), '12-bar quick change': ('I', 'IV', 'I', 'I', 'IV', 'IV', 'I', 'I', 'V', 'IV', 'I', 'V'), '8-bar blues': ('I', 'I', 'IV', 'IV', 'I', 'V', 'I', 'V'), 'Aeolian': ('i', 'VI', 'VII'), 'Andalusian': ('i', 'VII', 'VI', 'V'), 'Dorian vamp': ('i', 'IV'), 'I-IV-I-V': ('I', 'IV', 'I', 'V'), 'I-IV-V-I': ('I', 'IV', 'V', 'I'), 'I-IV-V-vi': ('I', 'IV', 'V', 'vi'), 'I-IV-vi-V': ('I', 'IV', 'vi', 'V'), 'I-V-vi-IV': ('I', 'V', 'vi', 'IV'), 'I-V-vi-iii-IV': ('I', 'V', 'vi', 'iii', 'IV'), 'I-iii-IV-V': ('I', 'iii', 'IV', 'V'), 'I-iii-vi-IV': ('I', 'iii', 'vi', 'IV'), 'I-vi-IV-V': ('I', 'vi', 'IV', 'V'), 'I-vi-ii-V': ('I', 'vi', 'ii', 'V'), 'Mixolydian vamp': ('I', 'bVII'), 'Pachelbel': ('I', 'V', 'vi', 'iii', 'IV', 'I', 'IV', 'V'), 'circle of fifths': ('I', 'IV', 'vii°', 'iii', 'vi', 'ii', 'V', 'I'), 'i-VI-VII-i': ('i', 'VI', 'VII', 'i'), 'i-VII-i': ('i', 'VII', 'i'), 'i-bVI-bIII-bVII': ('i', 'VI', 'III', 'VII'), 'i-iv-VII-III': ('i', 'iv', 'VII', 'III'), 'i-iv-v-i': ('i', 'iv', 'v', 'i'), 'i-v-VI-IV': ('i', 'v', 'VI', 'IV'), 'ii-V-I': ('ii', 'V7', 'I'), 'iii-vi-ii-V': ('iii', 'vi', 'ii', 'V'), 'minor 12-bar blues': ('i', 'i', 'i', 'i', 'iv', 'iv', 'i', 'i', 'V7', 'iv', 'i', 'V7'), 'minor ii-V-i': ('ii°', 'V7', 'i'), 'ragtime': ('I', 'V7/ii', 'V7/V', 'V7'), 'rhythm changes bridge': ('V7/vi', 'V7/ii', 'V7/V', 'V7'), 'vi-IV-I-V': ('vi', 'IV', 'I', 'V'), 'vi-V-IV-V': ('vi', 'V', 'IV', 'V'), 'vi-ii-V-I': ('vi', 'ii', 'V7', 'I')}

Common chord progressions as Roman numeral tuples.

Use with Scale.progression() or Key.progression():

Key("C", "major").progression(*PROGRESSIONS["I-V-vi-IV"])
class pytheory.scales.Key(tonic: str, mode: str = 'major', system: str | System | None = None)[source]

Bases: object

A musical key — a convenient entry point for scales and harmony.

A Key represents a tonic note and a mode. It provides quick access to the scale, diatonic chords, and common progressions.

Example:

>>> key = Key("C", "major")
>>> key.scale.note_names
['C', 'D', 'E', 'F', 'G', 'A', 'B', 'C']
>>> key.chords
['C major', 'D minor', 'E minor', 'F major', ...]
>>> key.progression("I", "V", "vi", "IV")
[<Chord (C,E,G)>, <Chord (G,B,D)>, ...]
__init__(tonic: str, mode: str = 'major', system: str | System | None = None) None[source]
classmethod detect(*note_names: str) Key | None[source]

Detect the most likely key from a set of note names.

Tries every possible major and minor key and returns the one whose scale contains the most of the given notes.

Notes are compared as pitch classes, so enharmonic spellings don’t matter — "A#" and "Bb" count the same. Ties go to the key whose tonic (then fifth) is actually among the notes, then to major over minor.

Example:

>>> Key.detect("C", "D", "E", "F", "G", "A", "B")
<Key C major>
>>> Key.detect("A", "B", "C", "D", "E", "F", "G")
<Key C major>
>>> Key.detect("A", "C", "E")
<Key A minor>
Returns:

The best-matching Key, or None if no notes given.

__repr__() str[source]

Return repr(self).

property scale: Scale

The scale for this key.

property note_names: list[str]

Note names in this key’s scale.

property chords: list[str]

Names of all diatonic triads in this key.

property seventh_chords: list[str]

Names of all diatonic seventh chords in this key.

triad(degree: int) Chord[source]

Build a diatonic triad on the given degree (0-indexed).

seventh(degree: int) Chord[source]

Build a diatonic seventh chord on the given degree (0-indexed).

progression(*numerals: str) list[Chord][source]

Build a chord progression from Roman numerals.

Example:

>>> Key("G", "major").progression("I", "IV", "V7", "I")
nashville(*numbers: int | str) list[Chord][source]

Build a chord progression using Nashville numbers.

Example:

>>> Key("G", "major").nashville(1, 4, 5, 1)
secondary_dominant(degree: int) Chord[source]

Build a secondary dominant (V/x) for the given scale degree.

A secondary dominant is the dominant chord of a non-tonic degree. For example, in C major, V/V is D major (the V chord of G). Secondary dominants create momentary tonicizations that add color and forward motion.

Common secondary dominants:

  • V/V (e.g. D7 in C major) — approaches the dominant

  • V/ii (e.g. A7 in C major) — approaches the supertonic

  • V/vi (e.g. E7 in C major) — approaches the relative minor

Parameters:

degree – Scale degree to target (1-indexed). 5 means “build the V of the 5th degree.” Degrees past the octave wrap (9 targets the 2nd degree); a non-positive degree raises ValueError.

Returns:

A dominant 7th Chord that resolves to the given degree.

Example:

>>> Key("C", "major").secondary_dominant(5)  # V/V = D7
<Chord D dominant 7th>
common_progressions() dict[str, list][source]

Named chord progressions realized in this key.

Returns a dict mapping progression names (from PROGRESSIONS) to lists of Chord objects built in this key.

Example:

>>> key = Key("C", "major")
>>> for name, chords in key.common_progressions().items():
...     symbols = [c.symbol or str(c) for c in chords]
...     print(f"{name}: {' → '.join(symbols)}")
I-IV-V-I: C → F → G → C
I-V-vi-IV: C → G → Am → F
...
classmethod all_keys() list[Key][source]

Return all 24 major and minor keys.

Returns:

A list of Key objects for all 12 major and 12 minor keys.

Example:

>>> for k in Key.all_keys():
...     print(k)
property signature: dict

The key signature — number and names of sharps or flats.

In Western music, each key has a unique key signature that tells you which notes are sharped or flatted throughout a piece.

Returns:

  • sharps (int): number of sharps (0 if flat key)

  • flats (int): number of flats (0 if sharp key)

  • accidentals (list[str]): the sharped/flatted note names

Return type:

A dict with

Example:

>>> Key("G", "major").signature
{'sharps': 1, 'flats': 0, 'accidentals': ['F#']}
>>> Key("F", "major").signature
{'sharps': 0, 'flats': 1, 'accidentals': ['Bb']}
>>> Key("C", "major").signature
{'sharps': 0, 'flats': 0, 'accidentals': []}
property borrowed_chords: list[str]

Chords borrowed from the parallel key.

Modal interchange (or modal mixture) borrows chords from the parallel major or minor key. In C major, the parallel minor is C minor, which provides chords like Ab major, Bb major, and Eb major — commonly heard in rock, film, and pop music.

Returns:

A list of chord names from the parallel key that are NOT in the current key’s diatonic chords.

Example:

>>> Key("C", "major").borrowed_chords
['C minor', 'D diminished', 'D# major', ...]
random_progression(length: int = 4) list[source]

Generate a random diatonic chord progression.

Uses weighted probabilities based on common chord function: I and vi are most common, IV and V are very common, ii is common, iii and viidim are rare. Always starts on I and ends on I or V.

Parameters:

length – Number of chords (default 4).

Returns:

A list of Chord objects.

Example:

>>> Key("C", "major").random_progression(4)
[<Chord C major>, <Chord F major>, <Chord G major>, <Chord C major>]
suggest_next(chord) list[source]

Suggest likely next chords based on voice-leading tendencies.

Given a chord in this key, returns a ranked list of chords that commonly follow it, based on standard functional harmony rules (e.g. V → I, ii → V, IV → V).

Parameters:

chord – A Chord object currently being played.

Returns:

A list of Chord objects, most likely first.

Example:

>>> key = Key("C", "major")
>>> g = key.triad(4)  # G major (V)
>>> [c.symbol for c in key.suggest_next(g)]
['C', 'Am', 'F']
property relative: Key | None

The relative major or minor key.

If this is a major key, returns the relative minor (vi). If this is a minor key, returns the relative major (bIII).

property parallel: Key | None

The parallel major or minor key (same tonic, different mode).

modulation_path(target: Key) list[source]

Suggest a chord-by-chord path from this key to a target key.

Strategy:

  • Find pivot chords (common to both keys)

  • Build: [I of current key, pivot chord, V of target key, I of target key]

  • If no pivot chord exists, use chromatic approach: [current I, target V, target I]

Parameters:

target – The target Key to modulate to.

Returns:

A list of Chord objects forming a modulation path.

Example:

>>> path = Key("C", "major").modulation_path(Key("G", "major"))
>>> len(path)
4
pivot_chords(target: Key) list[str][source]

Find chords common to this key and a target key.

Pivot chords are the bridge for modulation — they belong to both keys, so a listener accepts them in either context. The more pivot chords two keys share, the smoother the modulation.

Closely related keys (e.g. C major → G major) share many pivot chords. Distant keys (e.g. C major → F# major) share few or none.

Parameters:

target – The key to modulate to.

Returns:

A list of chord name strings common to both keys.

Example:

>>> Key("C", "major").pivot_chords(Key("G", "major"))
['G major', 'A minor', 'B minor', 'C major', 'D major', 'E minor']
chords_by_function() dict[str, list[Chord]][source]

Group the diatonic triads by harmonic function.

Functional harmony sorts the seven diatonic chords into three families by how they behave: tonic chords feel like home (I, iii, vi), subdominant chords move away from it (ii, IV), and dominant chords pull back toward it (V, vii°). Chords in the same family are largely interchangeable — swapping vi for I, or ii for IV, keeps a progression’s function intact while changing its color.

The grouping is by scale degree, so it holds for minor keys too (i/III/VI are tonic, ii°/iv subdominant, V/VII dominant).

Returns:

A dict with "tonic", "subdominant" and "dominant" keys, each mapping to a list of Chord objects.

Example:

>>> fams = Key("C", "major").chords_by_function()
>>> [c.symbol for c in fams["tonic"]]
['C', 'Em', 'Am']
>>> [c.symbol for c in fams["dominant"]]
['G', 'Bdim']
tonic_chords() list[Chord][source]

Diatonic chords with tonic function (I, iii, vi).

subdominant_chords() list[Chord][source]

Diatonic chords with subdominant function (ii, IV).

dominant_chords() list[Chord][source]

Diatonic chords with dominant function (V, vii°).

circle_of_fifths() dict[source]

Map this key’s neighborhood on the circle of fifths.

The circle of fifths arranges keys so that immediate neighbors differ by a single accidental and share most of their diatonic chords — which is exactly what makes them feel close and easy to modulate between.

Returns relational data centered on this key:

  • key — this Key

  • position — signed place on the circle: number of sharps, or negative for flats (C major = 0, G major = 1, F major = -1)

  • relative — the relative minor/major (shares all notes)

  • parallel — the parallel minor/major (shares the tonic)

  • dominant — neighbor a fifth up (sharp side), with the diatonic chords shared with it

  • subdominant — neighbor a fifth down (flat side), with the diatonic chords shared with it

  • circle — the twelve keys of this mode, clockwise from here

Example:

>>> cof = Key("C", "major").circle_of_fifths()
>>> str(cof["dominant"]["key"]), str(cof["subdominant"]["key"])
('G major', 'F major')
>>> str(cof["relative"]), str(cof["parallel"])
('A minor', 'C minor')
>>> len(cof["dominant"]["shared_chords"])
4
negative_harmony() dict[source]

Negative-harmony map of this key (Ernst Levy reflection).

Negative harmony mirrors every pitch across the axis that runs between the tonic and the dominant. The reflection turns a major key’s harmony into its “shadow” — major becomes minor, the dominant becomes a minor subdominant — while preserving the gravitational pull toward the tonic.

Returns:

  • axis — the tonic↔dominant pair the mirror runs between

  • axis_notes — the two pitches the mirror actually bisects (the “hinge”: where major and minor third meet)

  • negative_dominant — the reflection of V, the chord that does the dominant’s job in the negative world and so bridges the two harmonic families (e.g. C major → Fm)

  • scale — the negative scale’s note names, from the tonic

  • chords — each diatonic triad’s negative reflection

Return type:

A dict with

Example:

>>> neg = Key("C", "major").negative_harmony()
>>> neg["axis"]
('C', 'G')
>>> neg["negative_dominant"].symbol
'Fm'
>>> neg["scale"]
['C', 'D', 'Eb', 'F', 'G', 'Ab', 'Bb']
class pytheory.scales.TonedScale(*, system: str | System = <System semitones=12>, tonic: str | Tone)[source]

Bases: object

__init__(*, system: str | System = <System semitones=12>, tonic: str | Tone) None[source]

Initialize a TonedScale with a tonic note and tone system.

Parameters:
  • system – A tone system name or System instance.

  • tonic – The tonic note as a string (e.g. "C4") or Tone.

__repr__() str[source]

Return repr(self).

__getitem__(scale: str) Scale[source]

Retrieve a scale by name.

Raises:

KeyError – If the named scale is not found in this system.

get(scale: str) Scale | None[source]

Look up a scale by name, returning None if not found.

property scales: tuple[str, ...]

Tuple of all available scale names in this system.