Add mandolin, mandola, violin, viola, cello, banjo, 12-string presets

New Fretboard presets:
- Fretboard.mandolin()  — E5 A4 D4 G3 (tuned in 5ths)
- Fretboard.mandola()   — A4 D4 G3 C3 (octave below mandolin)
- Fretboard.violin()    — E5 A4 D4 G3 (same as mandolin)
- Fretboard.viola()     — A4 D4 G3 C3 (5th below violin)
- Fretboard.cello()     — A3 D3 G2 C2 (octave below viola)
- Fretboard.banjo(tuning) — open G, open D, double C
- Fretboard.twelve_string() — 12-string guitar (6 doubled courses)

Updated fretboard docs with string family section and custom
instrument examples (mandola, baritone ukulele, upright bass).

347 tests passing.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-22 06:48:25 -04:00
parent 858f0c1c06
commit fcfba20ec5
3 changed files with 233 additions and 14 deletions
+57 -13
View File
@@ -31,10 +31,21 @@ Preset Tunings
from pytheory import Fretboard
# Guitars
guitar = Fretboard.guitar() # Standard EADGBE
twelve = Fretboard.twelve_string() # 12-string (6 doubled courses)
bass = Fretboard.bass() # Standard EADG
bass5 = Fretboard.bass(five_string=True) # 5-string BEADG
ukulele = Fretboard.ukulele() # GCEA (re-entrant)
# Plucked strings
ukulele = Fretboard.ukulele() # GCEA (re-entrant)
mandolin = Fretboard.mandolin() # GDAE (tuned in 5ths)
banjo = Fretboard.banjo() # Open G (5-string with drone)
# Bowed strings
violin = Fretboard.violin() # GDAE
viola = Fretboard.viola() # CGDA (5th below violin)
cello = Fretboard.cello() # CGDA (octave below viola)
Alternate Guitar Tunings
~~~~~~~~~~~~~~~~~~~~~~~~
@@ -58,31 +69,64 @@ PyTheory supports several common alternate tunings, including
# Custom tuning with any notes
custom = Fretboard.guitar(("D4", "A3", "F#3", "D3", "A2", "D2"))
The String Family
-----------------
All four members of the orchestral string family are tuned in
`perfect fifths <https://en.wikipedia.org/wiki/Perfect_fifth>`_
(7 semitones between adjacent strings):
.. code-block:: python
violin = Fretboard.violin() # E5 A4 D4 G3 — soprano
viola = Fretboard.viola() # A4 D4 G3 C3 — alto (5th below violin)
cello = Fretboard.cello() # A3 D3 G2 C2 — tenor/bass (octave below viola)
Unlike fretted instruments, bowed strings have no frets — the player
can produce any pitch along the fingerboard, enabling vibrato and
microtonal inflections.
Other String Instruments
------------------------
.. code-block:: python
mandolin = Fretboard.mandolin() # E5 A4 D4 G3 (violin tuning)
banjo = Fretboard.banjo() # Open G (with high drone string)
banjo_d = Fretboard.banjo("open d") # Open D (clawhammer)
twelve = Fretboard.twelve_string() # 12 strings (6 doubled courses)
Custom Instruments
------------------
Any fretted instrument can be modeled, including `banjo <https://en.wikipedia.org/wiki/Banjo>`_,
`mandolin <https://en.wikipedia.org/wiki/Mandolin>`_, and more:
Any stringed instrument can be modeled:
.. code-block:: python
from pytheory import Tone, Fretboard
# Banjo (open G tuning)
banjo = Fretboard(tones=[
Tone.from_string("D4"),
# Mandola (octave below mandolin, like viola to violin)
mandola = Fretboard(tones=[
Tone.from_string("E4"),
Tone.from_string("A3"),
Tone.from_string("D3"),
Tone.from_string("G2"),
])
# Baritone ukulele (DGBE — like the top 4 guitar strings)
bari_uke = Fretboard(tones=[
Tone.from_string("E4"),
Tone.from_string("B3"),
Tone.from_string("G3"),
Tone.from_string("D3"),
Tone.from_string("G4"), # 5th string (high drone)
])
# Mandolin
mandolin = Fretboard(tones=[
Tone.from_string("E5"),
Tone.from_string("A4"),
Tone.from_string("D4"),
Tone.from_string("G3"),
# Double bass / upright bass
upright = Fretboard(tones=[
Tone.from_string("G2"),
Tone.from_string("D2"),
Tone.from_string("A1"),
Tone.from_string("E1"),
])
Getting Fingerings
+123 -1
View File
@@ -589,7 +589,10 @@ class Fretboard:
@classmethod
def ukulele(cls):
"""Standard ukulele tuning (A4 E4 C4 G4)."""
"""Standard ukulele tuning (A4 E4 C4 G4).
Re-entrant tuning: the G4 string is higher than C4.
"""
from .tones import Tone
return cls(tones=[
Tone.from_string("A4", system="western"),
@@ -598,6 +601,125 @@ class Fretboard:
Tone.from_string("G4", system="western"),
])
@classmethod
def mandolin(cls):
"""Standard mandolin tuning (E5 A4 D4 G3).
Tuned in fifths, same as a violin but one octave relationship.
Strings are typically doubled (paired courses).
"""
from .tones import Tone
return cls(tones=[
Tone.from_string("E5", system="western"),
Tone.from_string("A4", system="western"),
Tone.from_string("D4", system="western"),
Tone.from_string("G3", system="western"),
])
@classmethod
def mandola(cls):
"""Standard mandola tuning (A4 D4 G3 C3).
An octave below the mandolin, same relationship as viola to
violin. Tuned in fifths: C3-G3-D4-A4.
"""
from .tones import Tone
return cls(tones=[
Tone.from_string("A4", system="western"),
Tone.from_string("D4", system="western"),
Tone.from_string("G3", system="western"),
Tone.from_string("C3", system="western"),
])
@classmethod
def violin(cls):
"""Standard violin tuning (E5 A4 D4 G3).
Tuned in perfect fifths. The violin has no frets — intonation
is continuous, allowing vibrato and microtonal inflections
not possible on fretted instruments.
"""
from .tones import Tone
return cls(tones=[
Tone.from_string("E5", system="western"),
Tone.from_string("A4", system="western"),
Tone.from_string("D4", system="western"),
Tone.from_string("G3", system="western"),
])
@classmethod
def viola(cls):
"""Standard viola tuning (A4 D4 G3 C3).
A perfect fifth below the violin. The viola's darker, warmer
tone comes from its larger body and lower register.
"""
from .tones import Tone
return cls(tones=[
Tone.from_string("A4", system="western"),
Tone.from_string("D4", system="western"),
Tone.from_string("G3", system="western"),
Tone.from_string("C3", system="western"),
])
@classmethod
def cello(cls):
"""Standard cello tuning (A3 D3 G2 C2).
An octave below the viola. Tuned in fifths. The cello spans
the range of the human voice — tenor through bass.
"""
from .tones import Tone
return cls(tones=[
Tone.from_string("A3", system="western"),
Tone.from_string("D3", system="western"),
Tone.from_string("G2", system="western"),
Tone.from_string("C2", system="western"),
])
@classmethod
def banjo(cls, tuning="open g"):
"""Banjo with the given tuning.
Args:
tuning: ``"open g"`` (default, bluegrass) or ``"open d"``
(old-time, clawhammer). The 5th string is a high
drone — a defining feature of the banjo sound.
Standard open G: G4 D3 G3 B3 D4 (5th string is the short
high G4 drone).
"""
from .tones import Tone
tunings = {
"open g": ("D4", "B3", "G3", "D3", "G4"),
"open d": ("D4", "A3", "F#3", "D3", "A4"),
"double c": ("D4", "C4", "G3", "C3", "G4"),
}
if isinstance(tuning, str):
tuning = tunings[tuning]
return cls(tones=[Tone.from_string(t, system="western") for t in tuning])
@classmethod
def twelve_string(cls):
"""12-string guitar in standard tuning.
The lower 4 courses are doubled at the octave; the upper 2
are doubled in unison. This creates the characteristic
shimmering, chorus-like sound.
Represented as 12 strings (high to low, pairs together).
"""
from .tones import Tone
strings = [
"E4", "E4", # 1st course (unison)
"B3", "B3", # 2nd course (unison)
"G4", "G3", # 3rd course (octave)
"D4", "D3", # 4th course (octave)
"A3", "A2", # 5th course (octave)
"E3", "E2", # 6th course (octave)
]
return cls(tones=[Tone.from_string(t, system="western") for t in strings])
def fingering(self, *positions):
if not len(positions) == len(self.tones):
raise ValueError(
+53
View File
@@ -1829,6 +1829,59 @@ def test_fretboard_tunings_dict():
assert len(fb) == 6, f"Tuning {name} should have 6 strings"
def test_fretboard_mandolin():
fb = Fretboard.mandolin()
assert len(fb) == 4
assert fb.tones[0].name == "E"
assert fb.tones[-1].name == "G"
def test_fretboard_violin():
fb = Fretboard.violin()
assert len(fb) == 4
names = [t.name for t in fb]
assert names == ["E", "A", "D", "G"]
def test_fretboard_viola():
fb = Fretboard.viola()
assert len(fb) == 4
names = [t.name for t in fb]
assert names == ["A", "D", "G", "C"]
def test_fretboard_cello():
fb = Fretboard.cello()
assert len(fb) == 4
names = [t.name for t in fb]
assert names == ["A", "D", "G", "C"]
assert fb.tones[0].octave == 3
def test_fretboard_banjo():
fb = Fretboard.banjo()
assert len(fb) == 5
assert fb.tones[-1].name == "G" # high drone string
def test_fretboard_banjo_open_d():
fb = Fretboard.banjo("open d")
assert len(fb) == 5
def test_fretboard_twelve_string():
fb = Fretboard.twelve_string()
assert len(fb) == 12
def test_fretboard_violin_tuned_in_fifths():
"""Violin strings should be a perfect 5th apart."""
fb = Fretboard.violin()
for i in range(len(fb.tones) - 1):
interval = fb.tones[i] - fb.tones[i + 1]
assert interval == 7, f"Strings {i} and {i+1} not a 5th apart"
# ── Ergonomic integration tests ─────────────────────────────────────────────
def test_ergonomic_workflow():