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.
|
||||
|
||||
## 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
|
||||
|
||||
- **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"]
|
||||
>>> print(fb.scale_diagram(blues, frets=12))
|
||||
0 1 2 3 4 5 6 7 8 9 10 11 12
|
||||
E| - | - | - | - | - | A | - | - | C | - | D | Eb| E |
|
||||
A| A | - | - | C | - | D | Eb| E | - | - | - | - | A |
|
||||
D| - | - | - | - | A | - | - | C | - | D | Eb| E | - |
|
||||
G| - | - | A | - | - | C | - | D | Eb| E | - | - | - |
|
||||
B| - | - | - | D | Eb| E | - | - | - | - | A | - | - |
|
||||
E| - | - | - | - | - | A | - | - | C | - | D | Eb| E |
|
||||
E| E | - | - | G | - | A | - | - | C | - | D | Eb| E |
|
||||
A| A | - | - | C | - | D | Eb| E | - | - | G | - | A |
|
||||
D| D | Eb| E | - | - | G | - | A | - | - | C | - | D |
|
||||
G| G | - | A | - | - | C | - | D | Eb| E | - | - | G |
|
||||
B| - | C | - | D | Eb| E | - | - | G | - | A | - | - |
|
||||
E| E | - | - | G | - | A | - | - | C | - | D | Eb| E |
|
||||
|
||||
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
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
[project]
|
||||
name = "pytheory"
|
||||
version = "0.43.0"
|
||||
version = "0.43.1"
|
||||
description = "Music Theory for Humans"
|
||||
readme = "README.md"
|
||||
license = "MIT"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"""PyTheory: Music Theory for Humans."""
|
||||
|
||||
__version__ = "0.43.0"
|
||||
__version__ = "0.43.1"
|
||||
|
||||
from .tones import Tone, Interval
|
||||
from .systems import System, SYSTEMS, TET
|
||||
|
||||
+23
-7
@@ -1867,10 +1867,24 @@ class Fretboard:
|
||||
>>> am = Chord.from_symbol("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()
|
||||
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)
|
||||
lines = []
|
||||
@@ -1885,13 +1899,15 @@ class Fretboard:
|
||||
fret_marks = []
|
||||
for f in range(frets + 1):
|
||||
note = tone.add(f)
|
||||
if note.name in scale_notes:
|
||||
if chord_notes and note.name in chord_notes:
|
||||
fret_marks.append(f" {note.name.upper():<2s}")
|
||||
key = _resolve(note.name)
|
||||
if key in scale_notes:
|
||||
label = scale_display[key]
|
||||
if chord_notes and key in chord_notes:
|
||||
fret_marks.append(f" {label.upper():<2s}")
|
||||
elif chord_notes:
|
||||
fret_marks.append(f" {note.name.lower():<2s}")
|
||||
fret_marks.append(f" {label.lower():<2s}")
|
||||
else:
|
||||
fret_marks.append(f" {note.name:<2s}")
|
||||
fret_marks.append(f" {label:<2s}")
|
||||
else:
|
||||
fret_marks.append(" - ")
|
||||
line = f"{tone.name:>{max_name}}|{'|'.join(fret_marks)}|"
|
||||
|
||||
@@ -3522,6 +3522,19 @@ def test_scale_diagram():
|
||||
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 ─────────────────────────────────────────────────────
|
||||
|
||||
def test_tone_init_octave_parsed_from_name():
|
||||
|
||||
Reference in New Issue
Block a user