mirror of
https://github.com/kennethreitz/pytheory.git
synced 2026-06-05 23:00:20 +00:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5aed586187 | |||
| 09d90b3425 |
@@ -139,7 +139,7 @@ Quality Intervals Example tones (from C)
|
||||
('C', 'E', 'G')
|
||||
|
||||
>>> chart["Cm7"].acceptable_tone_names
|
||||
('C', 'D#', 'G', 'A#')
|
||||
('C', 'Eb', 'G', 'Bb')
|
||||
|
||||
Building Chords
|
||||
---------------
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
[project]
|
||||
name = "pytheory"
|
||||
version = "0.8.1"
|
||||
version = "0.8.2"
|
||||
description = "Music Theory for Humans"
|
||||
readme = "README.md"
|
||||
license = "MIT"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"""PyTheory: Music Theory for Humans."""
|
||||
|
||||
__version__ = "0.8.1"
|
||||
__version__ = "0.8.2"
|
||||
|
||||
from .tones import Tone, Interval
|
||||
from .systems import System, SYSTEMS
|
||||
|
||||
+51
-13
@@ -193,63 +193,101 @@ class NamedChord:
|
||||
def __repr__(self):
|
||||
return f"<NamedChord name={self.name!r}>"
|
||||
|
||||
@property
|
||||
def _prefer_flats(self):
|
||||
"""Determine whether this chord's tones should use flat spellings.
|
||||
|
||||
Uses the circle-of-fifths convention:
|
||||
- Flat-root notes (Bb, Eb, Ab, Db, Gb) always prefer flats.
|
||||
- Major-type qualities prefer flats for roots: F, Bb, Eb, Ab, Db, Gb.
|
||||
- Minor-type qualities prefer flats for roots: D, G, C, F, Bb, Eb, Ab.
|
||||
"""
|
||||
# Root is itself a flat note — always prefer flats
|
||||
if "b" in self.tone_name and self.tone_name != "B":
|
||||
return True
|
||||
|
||||
_FLAT_MAJOR_ROOTS = {"F", "Bb", "Eb", "Ab", "Db", "Gb"}
|
||||
_FLAT_MINOR_ROOTS = {"D", "G", "C", "F", "Bb", "Eb", "Ab"}
|
||||
# Dominant 7th/9th chords contain a minor 7th (b7), so they
|
||||
# follow the same flat-preference roots as minor chords.
|
||||
_FLAT_DOMINANT_ROOTS = {"C", "F", "G", "Bb", "Eb", "Ab", "Db", "Gb"}
|
||||
|
||||
minor_qualities = {"m", "m6", "m7", "m9", "dim"}
|
||||
dominant_qualities = {"7", "9"}
|
||||
major_qualities = {"", "maj", "5", "maj7", "maj9"}
|
||||
|
||||
if self.quality in minor_qualities and self.tone_name in _FLAT_MINOR_ROOTS:
|
||||
return True
|
||||
if self.quality in dominant_qualities and self.tone_name in _FLAT_DOMINANT_ROOTS:
|
||||
return True
|
||||
if self.quality in major_qualities and self.tone_name in _FLAT_MAJOR_ROOTS:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
@property
|
||||
def acceptable_tones(self):
|
||||
acceptable = [self.tone]
|
||||
flats = self._prefer_flats
|
||||
|
||||
if self.quality == "maj":
|
||||
# Major triad: root, major 3rd, perfect 5th
|
||||
acceptable += [self.tone.add(4), self.tone.add(7)]
|
||||
acceptable += [self.tone.add(4, prefer_flats=flats), self.tone.add(7, prefer_flats=flats)]
|
||||
|
||||
elif self.quality == "m":
|
||||
# Minor triad: root, minor 3rd, perfect 5th
|
||||
acceptable += [self.tone.add(3), self.tone.add(7)]
|
||||
acceptable += [self.tone.add(3, prefer_flats=flats), self.tone.add(7, prefer_flats=flats)]
|
||||
|
||||
elif self.quality == "5":
|
||||
# Power chord: root, perfect 5th
|
||||
acceptable += [self.tone.add(7)]
|
||||
acceptable += [self.tone.add(7, prefer_flats=flats)]
|
||||
|
||||
elif self.quality == "7":
|
||||
# Dominant 7th: root, major 3rd, perfect 5th, minor 7th
|
||||
acceptable += [self.tone.add(4), self.tone.add(7), self.tone.add(10)]
|
||||
acceptable += [self.tone.add(4, prefer_flats=flats), self.tone.add(7, prefer_flats=flats), self.tone.add(10, prefer_flats=flats)]
|
||||
|
||||
elif self.quality == "9":
|
||||
# Dominant 9th: root, major 3rd, perfect 5th, minor 7th, major 9th
|
||||
acceptable += [self.tone.add(4), self.tone.add(7), self.tone.add(10), self.tone.add(2)]
|
||||
acceptable += [self.tone.add(4, prefer_flats=flats), self.tone.add(7, prefer_flats=flats), self.tone.add(10, prefer_flats=flats), self.tone.add(2, prefer_flats=flats)]
|
||||
|
||||
elif self.quality == "dim":
|
||||
# Diminished: root, minor 3rd, diminished 5th
|
||||
acceptable += [self.tone.add(3), self.tone.add(6)]
|
||||
acceptable += [self.tone.add(3, prefer_flats=flats), self.tone.add(6, prefer_flats=flats)]
|
||||
|
||||
elif self.quality == "m6":
|
||||
# Minor 6th: root, minor 3rd, perfect 5th, major 6th
|
||||
acceptable += [self.tone.add(3), self.tone.add(7), self.tone.add(9)]
|
||||
acceptable += [self.tone.add(3, prefer_flats=flats), self.tone.add(7, prefer_flats=flats), self.tone.add(9, prefer_flats=flats)]
|
||||
|
||||
elif self.quality == "m7":
|
||||
# Minor 7th: root, minor 3rd, perfect 5th, minor 7th
|
||||
acceptable += [self.tone.add(3), self.tone.add(7), self.tone.add(10)]
|
||||
acceptable += [self.tone.add(3, prefer_flats=flats), self.tone.add(7, prefer_flats=flats), self.tone.add(10, prefer_flats=flats)]
|
||||
|
||||
elif self.quality == "m9":
|
||||
# Minor 9th: root, minor 3rd, perfect 5th, minor 7th, major 9th
|
||||
acceptable += [self.tone.add(3), self.tone.add(7), self.tone.add(10), self.tone.add(2)]
|
||||
acceptable += [self.tone.add(3, prefer_flats=flats), self.tone.add(7, prefer_flats=flats), self.tone.add(10, prefer_flats=flats), self.tone.add(2, prefer_flats=flats)]
|
||||
|
||||
elif self.quality == "maj7":
|
||||
# Major 7th: root, major 3rd, perfect 5th, major 7th
|
||||
acceptable += [self.tone.add(4), self.tone.add(7), self.tone.add(11)]
|
||||
acceptable += [self.tone.add(4, prefer_flats=flats), self.tone.add(7, prefer_flats=flats), self.tone.add(11, prefer_flats=flats)]
|
||||
|
||||
elif self.quality == "maj9":
|
||||
# Major 9th: root, major 3rd, perfect 5th, major 7th, major 9th
|
||||
acceptable += [self.tone.add(4), self.tone.add(7), self.tone.add(11), self.tone.add(2)]
|
||||
acceptable += [self.tone.add(4, prefer_flats=flats), self.tone.add(7, prefer_flats=flats), self.tone.add(11, prefer_flats=flats), self.tone.add(2, prefer_flats=flats)]
|
||||
|
||||
else:
|
||||
# Default (no quality): major triad
|
||||
acceptable += [self.tone.add(4), self.tone.add(7)]
|
||||
acceptable += [self.tone.add(4, prefer_flats=flats), self.tone.add(7, prefer_flats=flats)]
|
||||
|
||||
return tuple(acceptable)
|
||||
|
||||
@property
|
||||
def acceptable_tone_names(self):
|
||||
return tuple([tone.name for tone in self.acceptable_tones])
|
||||
names = [tone.name for tone in self.acceptable_tones]
|
||||
# The root tone is stored internally with sharp spelling (e.g. A#
|
||||
# for Bb) via flat_to_sharp mapping; restore the original flat name.
|
||||
if names and names[0] != self.tone_name:
|
||||
names[0] = self.tone_name
|
||||
return tuple(names)
|
||||
|
||||
def _possible_fingerings(self, *, fretboard):
|
||||
# Check the _possible_cache first
|
||||
|
||||
+10
-10
@@ -417,7 +417,7 @@ def test_named_chord_c_minor_tones():
|
||||
cm = NamedChord(tone_name="C", quality="m")
|
||||
names = cm.acceptable_tone_names
|
||||
assert "C" in names
|
||||
assert "D#" in names # Eb enharmonic
|
||||
assert "Eb" in names # minor 3rd
|
||||
assert "G" in names
|
||||
|
||||
|
||||
@@ -435,24 +435,24 @@ def test_named_chord_dominant_7th():
|
||||
assert "C" in names
|
||||
assert "E" in names # major 3rd
|
||||
assert "G" in names # perfect 5th
|
||||
assert "A#" in names # minor 7th (Bb)
|
||||
assert "Bb" in names # minor 7th
|
||||
|
||||
|
||||
def test_named_chord_diminished():
|
||||
cdim = NamedChord(tone_name="C", quality="dim")
|
||||
names = cdim.acceptable_tone_names
|
||||
assert "C" in names
|
||||
assert "D#" in names # minor 3rd (Eb)
|
||||
assert "F#" in names # diminished 5th (Gb)
|
||||
assert "Eb" in names # minor 3rd
|
||||
assert "Gb" in names # diminished 5th
|
||||
|
||||
|
||||
def test_named_chord_minor_7th():
|
||||
cm7 = NamedChord(tone_name="C", quality="m7")
|
||||
names = cm7.acceptable_tone_names
|
||||
assert "C" in names
|
||||
assert "D#" in names # minor 3rd
|
||||
assert "Eb" in names # minor 3rd
|
||||
assert "G" in names # perfect 5th
|
||||
assert "A#" in names # minor 7th
|
||||
assert "Bb" in names # minor 7th
|
||||
|
||||
|
||||
def test_named_chord_major_7th():
|
||||
@@ -1258,7 +1258,7 @@ def test_named_chord_m6_tones():
|
||||
cm6 = NamedChord(tone_name="C", quality="m6")
|
||||
names = cm6.acceptable_tone_names
|
||||
assert "C" in names
|
||||
assert "D#" in names # minor 3rd
|
||||
assert "Eb" in names # minor 3rd
|
||||
assert "G" in names # perfect 5th
|
||||
assert "A" in names # major 6th
|
||||
assert len(names) == 4
|
||||
@@ -1268,9 +1268,9 @@ def test_named_chord_m9_tones():
|
||||
cm9 = NamedChord(tone_name="C", quality="m9")
|
||||
names = cm9.acceptable_tone_names
|
||||
assert "C" in names
|
||||
assert "D#" in names # minor 3rd
|
||||
assert "Eb" in names # minor 3rd
|
||||
assert "G" in names # perfect 5th
|
||||
assert "A#" in names # minor 7th
|
||||
assert "Bb" in names # minor 7th
|
||||
assert "D" in names # major 9th
|
||||
assert len(names) == 5
|
||||
|
||||
@@ -1292,7 +1292,7 @@ def test_named_chord_9_tones():
|
||||
assert "C" in names
|
||||
assert "E" in names # major 3rd
|
||||
assert "G" in names # perfect 5th
|
||||
assert "A#" in names # minor 7th
|
||||
assert "Bb" in names # minor 7th
|
||||
assert "D" in names # major 9th
|
||||
assert len(names) == 5
|
||||
|
||||
|
||||
Reference in New Issue
Block a user