mirror of
https://github.com/kennethreitz/pytheory.git
synced 2026-06-05 06:46:14 +00:00
Fix scale_diagram enharmonic matching — v0.43.1
Scale notes spelled with flats (e.g. the Eb blue note in the blues scale) were silently dropped from scale_diagram() because the fretboard spells that pitch as D#, so the string comparison never matched. Match notes enharmonically via the system's canonical name and display them using the scale's own spelling. Pre-existing bug (also in 0.42.x), surfaced while flipping the docs for the low-to-high release. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -2,6 +2,14 @@
|
|||||||
|
|
||||||
All notable changes to PyTheory are documented here.
|
All notable changes to PyTheory are documented here.
|
||||||
|
|
||||||
|
## 0.43.1
|
||||||
|
|
||||||
|
- **Fix `Fretboard.scale_diagram()` enharmonic matching.** Scale notes
|
||||||
|
spelled with flats (e.g. the `Eb` blue note in the blues scale) were
|
||||||
|
silently omitted from the diagram, because the fretboard spells that
|
||||||
|
pitch as `D#`. Notes are now matched enharmonically (by pitch) and
|
||||||
|
displayed using the scale's own spelling.
|
||||||
|
|
||||||
## 0.43.0
|
## 0.43.0
|
||||||
|
|
||||||
- **BREAKING — fingerings now read low-to-high by default.** `Fretboard`
|
- **BREAKING — fingerings now read low-to-high by default.** `Fretboard`
|
||||||
|
|||||||
@@ -208,12 +208,12 @@ Visualize the blues scale on guitar to see the patterns:
|
|||||||
>>> blues = TonedScale(tonic="A4", system="blues")["blues"]
|
>>> blues = TonedScale(tonic="A4", system="blues")["blues"]
|
||||||
>>> print(fb.scale_diagram(blues, frets=12))
|
>>> print(fb.scale_diagram(blues, frets=12))
|
||||||
0 1 2 3 4 5 6 7 8 9 10 11 12
|
0 1 2 3 4 5 6 7 8 9 10 11 12
|
||||||
E| - | - | - | - | - | A | - | - | C | - | D | Eb| E |
|
E| E | - | - | G | - | A | - | - | C | - | D | Eb| E |
|
||||||
A| A | - | - | C | - | D | Eb| E | - | - | - | - | A |
|
A| A | - | - | C | - | D | Eb| E | - | - | G | - | A |
|
||||||
D| - | - | - | - | A | - | - | C | - | D | Eb| E | - |
|
D| D | Eb| E | - | - | G | - | A | - | - | C | - | D |
|
||||||
G| - | - | A | - | - | C | - | D | Eb| E | - | - | - |
|
G| G | - | A | - | - | C | - | D | Eb| E | - | - | G |
|
||||||
B| - | - | - | D | Eb| E | - | - | - | - | A | - | - |
|
B| - | C | - | D | Eb| E | - | - | G | - | A | - | - |
|
||||||
E| - | - | - | - | - | A | - | - | C | - | D | Eb| E |
|
E| E | - | - | G | - | A | - | - | C | - | D | Eb| E |
|
||||||
|
|
||||||
The minor pentatonic (same scale without the Eb) is the most-played
|
The minor pentatonic (same scale without the Eb) is the most-played
|
||||||
scale in rock guitar. Add the blue note and you have the full blues
|
scale in rock guitar. Add the blue note and you have the full blues
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "pytheory"
|
name = "pytheory"
|
||||||
version = "0.43.0"
|
version = "0.43.1"
|
||||||
description = "Music Theory for Humans"
|
description = "Music Theory for Humans"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"""PyTheory: Music Theory for Humans."""
|
"""PyTheory: Music Theory for Humans."""
|
||||||
|
|
||||||
__version__ = "0.43.0"
|
__version__ = "0.43.1"
|
||||||
|
|
||||||
from .tones import Tone, Interval
|
from .tones import Tone, Interval
|
||||||
from .systems import System, SYSTEMS, TET
|
from .systems import System, SYSTEMS, TET
|
||||||
|
|||||||
+23
-7
@@ -1867,10 +1867,24 @@ class Fretboard:
|
|||||||
>>> am = Chord.from_symbol("Am")
|
>>> am = Chord.from_symbol("Am")
|
||||||
>>> print(fb.scale_diagram(pentatonic, frets=5, chord=am))
|
>>> print(fb.scale_diagram(pentatonic, frets=5, chord=am))
|
||||||
"""
|
"""
|
||||||
scale_notes = set(scale.note_names)
|
# Match notes enharmonically: the fretboard spells tones with
|
||||||
|
# sharps (e.g. D#), but a scale may use flats (e.g. Eb). Compare
|
||||||
|
# via the system's canonical name so Eb and D# count as the same
|
||||||
|
# pitch — and display using the scale's own spelling.
|
||||||
|
_system = self._tones[0].system
|
||||||
|
def _resolve(name):
|
||||||
|
resolved = _system.resolve_name(name)
|
||||||
|
return resolved if resolved is not None else name
|
||||||
|
|
||||||
|
# Map canonical pitch -> the scale's preferred spelling for display.
|
||||||
|
scale_display = {}
|
||||||
|
for n in scale.note_names:
|
||||||
|
scale_display.setdefault(_resolve(n), n)
|
||||||
|
scale_notes = set(scale_display)
|
||||||
|
|
||||||
chord_notes = set()
|
chord_notes = set()
|
||||||
if chord is not None:
|
if chord is not None:
|
||||||
chord_notes = {t.name for t in chord.tones}
|
chord_notes = {_resolve(t.name) for t in chord.tones}
|
||||||
|
|
||||||
max_name = max(len(t.name) for t in self.tones)
|
max_name = max(len(t.name) for t in self.tones)
|
||||||
lines = []
|
lines = []
|
||||||
@@ -1885,13 +1899,15 @@ class Fretboard:
|
|||||||
fret_marks = []
|
fret_marks = []
|
||||||
for f in range(frets + 1):
|
for f in range(frets + 1):
|
||||||
note = tone.add(f)
|
note = tone.add(f)
|
||||||
if note.name in scale_notes:
|
key = _resolve(note.name)
|
||||||
if chord_notes and note.name in chord_notes:
|
if key in scale_notes:
|
||||||
fret_marks.append(f" {note.name.upper():<2s}")
|
label = scale_display[key]
|
||||||
|
if chord_notes and key in chord_notes:
|
||||||
|
fret_marks.append(f" {label.upper():<2s}")
|
||||||
elif chord_notes:
|
elif chord_notes:
|
||||||
fret_marks.append(f" {note.name.lower():<2s}")
|
fret_marks.append(f" {label.lower():<2s}")
|
||||||
else:
|
else:
|
||||||
fret_marks.append(f" {note.name:<2s}")
|
fret_marks.append(f" {label:<2s}")
|
||||||
else:
|
else:
|
||||||
fret_marks.append(" - ")
|
fret_marks.append(" - ")
|
||||||
line = f"{tone.name:>{max_name}}|{'|'.join(fret_marks)}|"
|
line = f"{tone.name:>{max_name}}|{'|'.join(fret_marks)}|"
|
||||||
|
|||||||
@@ -3522,6 +3522,19 @@ def test_scale_diagram():
|
|||||||
assert len(lines) == 7
|
assert len(lines) == 7
|
||||||
|
|
||||||
|
|
||||||
|
def test_scale_diagram_enharmonic_flat_note():
|
||||||
|
"""A flat-spelled scale note (e.g. the blues Eb) must render even
|
||||||
|
though the fretboard spells that pitch as D#."""
|
||||||
|
fb = Fretboard.guitar()
|
||||||
|
blues = TonedScale(tonic="A4", system="blues")["blues"]
|
||||||
|
assert "Eb" in blues.note_names
|
||||||
|
diagram = fb.scale_diagram(blues, frets=12)
|
||||||
|
# The blue note shows up using the scale's own (flat) spelling,
|
||||||
|
# never the fretboard's sharp spelling.
|
||||||
|
assert "Eb" in diagram
|
||||||
|
assert "D#" not in diagram
|
||||||
|
|
||||||
|
|
||||||
# ── Coverage gap tests ─────────────────────────────────────────────────────
|
# ── Coverage gap tests ─────────────────────────────────────────────────────
|
||||||
|
|
||||||
def test_tone_init_octave_parsed_from_name():
|
def test_tone_init_octave_parsed_from_name():
|
||||||
|
|||||||
Reference in New Issue
Block a user