diff --git a/pytheory/chords.py b/pytheory/chords.py index e2183c9..5889301 100644 --- a/pytheory/chords.py +++ b/pytheory/chords.py @@ -927,6 +927,32 @@ class Fretboard: Tone.from_string("E4", system="western"), ]) + @classmethod + def keyboard(cls, keys=88, start="A0"): + """Piano or keyboard with the given number of keys. + + Args: + keys: Number of keys (default 88 for a full piano). + Common sizes: 25, 37, 49, 61, 76, 88. + start: The lowest note (default ``"A0"`` for standard piano). + + A full 88-key piano spans A0 (27.5 Hz) to C8 (4186 Hz) — + the widest range of any standard acoustic instrument. + Smaller MIDI controllers typically start at C. + + Examples:: + + Fretboard.keyboard() # 88-key piano + Fretboard.keyboard(61, "C2") # 61-key controller + Fretboard.keyboard(25, "C3") # 25-key mini controller + """ + from .tones import Tone + start_tone = Tone.from_string(start, system="western") + tones = [] + for i in range(keys - 1, -1, -1): + tones.append(start_tone.add(i)) + return cls(tones=tones) + @classmethod def lute(cls): """Renaissance lute in G tuning (6 courses). diff --git a/test_pytheory.py b/test_pytheory.py index ee1d411..bd22734 100644 --- a/test_pytheory.py +++ b/test_pytheory.py @@ -1942,7 +1942,7 @@ def test_all_instruments_create(): "violin", "viola", "cello", "double_bass", "banjo", "harp", "pedal_steel", "bouzouki", "oud", "sitar", "shamisen", "erhu", - "charango", "pipa", "balalaika", "lute", + "charango", "pipa", "balalaika", "lute", "keyboard", ] for name in instruments: fb = getattr(Fretboard, name)() @@ -2002,6 +2002,23 @@ def test_fretboard_pipa(): assert len(fb) == 4 +def test_keyboard_88(): + kb = Fretboard.keyboard() + assert len(kb) == 88 + + +def test_keyboard_25(): + kb = Fretboard.keyboard(25, "C3") + assert len(kb) == 25 + assert kb.tones[-1].name == "C" + assert kb.tones[-1].octave == 3 + + +def test_keyboard_custom(): + kb = Fretboard.keyboard(61, "C2") + assert len(kb) == 61 + + # ── Ergonomic integration tests ───────────────────────────────────────────── def test_ergonomic_workflow():