mirror of
https://github.com/kennethreitz/pytheory.git
synced 2026-06-05 14:50:18 +00:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1ed90c72d6 | |||
| 41e8404624 |
@@ -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`
|
||||
|
||||
@@ -111,19 +111,19 @@ Generate fingerings for guitar and ukulele with
|
||||
|
||||
>>> fb = Fretboard.guitar()
|
||||
>>> fb.chord("C")
|
||||
Fingering(e=0, B=1, G=0, D=2, A=3, E=x)
|
||||
Fingering(E=x, A=3, D=2, G=0, B=1, e=0)
|
||||
>>> fb.chord("G")
|
||||
Fingering(e=3, B=0, G=0, D=0, A=2, E=3)
|
||||
Fingering(E=3, A=2, D=0, G=0, B=0, e=3)
|
||||
>>> fb.chord("Am")
|
||||
Fingering(e=0, B=1, G=2, D=2, A=0, E=x)
|
||||
Fingering(E=x, A=0, D=2, G=2, B=1, e=0)
|
||||
>>> fb.chord("D")
|
||||
Fingering(e=2, B=3, G=2, D=0, A=x, E=x)
|
||||
Fingering(E=x, A=x, D=0, G=2, B=3, e=2)
|
||||
|
||||
>>> uke = Fretboard.ukulele()
|
||||
>>> uke.chord("C")
|
||||
Fingering(A=3, E=0, C=0, G=0)
|
||||
Fingering(G=0, C=0, E=0, A=3)
|
||||
>>> uke.chord("G")
|
||||
Fingering(A=2, E=3, C=2, G=0)
|
||||
Fingering(G=0, C=2, E=3, A=2)
|
||||
|
||||
Explore an Interval
|
||||
-------------------
|
||||
|
||||
@@ -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 |
|
||||
B| - | - | - | D | Eb| E | - | - | - | - | A | - | - |
|
||||
G| - | - | A | - | - | C | - | D | Eb| E | - | - | - |
|
||||
D| - | - | - | - | A | - | - | C | - | D | Eb| E | - |
|
||||
A| A | - | - | C | - | 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
|
||||
@@ -243,30 +243,30 @@ Get the tab for any chord on any instrument:
|
||||
>>> fb = Fretboard.guitar()
|
||||
>>> print(fb.tab("C"))
|
||||
C major
|
||||
e|--0--
|
||||
B|--1--
|
||||
G|--0--
|
||||
D|--2--
|
||||
A|--3--
|
||||
E|--x--
|
||||
A|--3--
|
||||
D|--2--
|
||||
G|--0--
|
||||
B|--1--
|
||||
e|--0--
|
||||
|
||||
>>> print(fb.tab("Am"))
|
||||
A minor
|
||||
e|--0--
|
||||
B|--1--
|
||||
G|--2--
|
||||
D|--2--
|
||||
A|--0--
|
||||
E|--x--
|
||||
A|--0--
|
||||
D|--2--
|
||||
G|--2--
|
||||
B|--1--
|
||||
e|--0--
|
||||
|
||||
>>> print(fb.tab("E7"))
|
||||
E dominant 7th
|
||||
e|--0--
|
||||
B|--0--
|
||||
G|--1--
|
||||
D|--0--
|
||||
A|--2--
|
||||
E|--0--
|
||||
A|--2--
|
||||
D|--0--
|
||||
G|--1--
|
||||
B|--0--
|
||||
e|--0--
|
||||
|
||||
Works with any instrument:
|
||||
|
||||
@@ -275,24 +275,27 @@ Works with any instrument:
|
||||
>>> uke = Fretboard.ukulele()
|
||||
>>> print(uke.tab("C"))
|
||||
C major
|
||||
A|--3--
|
||||
E|--0--
|
||||
C|--0--
|
||||
G|--0--
|
||||
C|--0--
|
||||
E|--0--
|
||||
A|--3--
|
||||
|
||||
Reading Tab Notation
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
::
|
||||
|
||||
e|--0-- ← open string (don't fret, just pluck)
|
||||
B|--1-- ← press fret 1
|
||||
G|--0-- ← open string
|
||||
D|--2-- ← press fret 2
|
||||
A|--3-- ← press fret 3
|
||||
E|--x-- ← muted (don't play this string)
|
||||
A|--3-- ← press fret 3
|
||||
D|--2-- ← press fret 2
|
||||
G|--0-- ← open string
|
||||
B|--1-- ← press fret 1
|
||||
e|--0-- ← open string (don't fret, just pluck)
|
||||
|
||||
- Each line is a string (highest pitch at top, lowest at bottom)
|
||||
- Each line is a string. Chord tab from ``fb.tab()`` lists strings
|
||||
low-to-high (lowest pitch at top) by default since v0.43.0; pass
|
||||
``high_to_low=True`` to the fretboard for the traditional
|
||||
highest-pitch-on-top layout.
|
||||
- Numbers are fret positions (0 = open, 1-24 = fretted)
|
||||
- ``x`` means the string is muted / not played
|
||||
- ``|`` marks measure boundaries in sequence tabs
|
||||
|
||||
@@ -130,7 +130,7 @@ Guitar fingerings:
|
||||
|
||||
>>> fb = Fretboard.guitar()
|
||||
>>> fb.chord("Am")
|
||||
Fingering(e=0, B=1, G=2, D=2, A=0, E=x)
|
||||
Fingering(E=x, A=0, D=2, G=2, B=1, e=0)
|
||||
|
||||
All of the above works without PortAudio, without sounddevice,
|
||||
without any audio setup at all. It's pure Python music theory.
|
||||
|
||||
+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