mirror of
https://github.com/kennethreitz/pytheory.git
synced 2026-06-05 23:00:20 +00:00
6f7f9008b0
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
217 lines
6.5 KiB
Python
217 lines
6.5 KiB
Python
"""Play melodies and chord progressions with PyTheory.
|
||
|
||
Requires PortAudio: brew install portaudio (macOS)
|
||
"""
|
||
|
||
from pytheory import Tone, Chord, Key, TonedScale, play, Synth
|
||
|
||
# ── Helpers ─────────────────────────────────────────────────────────────
|
||
|
||
BPM = 180
|
||
BEAT = 60_000 // BPM # ms per beat
|
||
|
||
|
||
def play_melody(notes, synth=Synth.SINE):
|
||
"""Play a sequence of (note_string, beats) tuples."""
|
||
try:
|
||
for note, beats in notes:
|
||
if note == "REST":
|
||
import time
|
||
time.sleep(beats * BEAT / 1000)
|
||
else:
|
||
tone = Tone.from_string(note, system="western")
|
||
play(tone, synth=synth, t=int(beats * BEAT))
|
||
except KeyboardInterrupt:
|
||
print("\n Stopped.")
|
||
|
||
|
||
def play_progression(chords, beats_each=2, synth=Synth.SINE):
|
||
"""Play a list of Chord objects."""
|
||
try:
|
||
for chord in chords:
|
||
name = chord.identify() or "?"
|
||
tones = " ".join(t.full_name for t in chord.tones)
|
||
print(f" {name:20s} {tones}")
|
||
play(chord, synth=synth, t=int(beats_each * BEAT))
|
||
except KeyboardInterrupt:
|
||
print("\n Stopped.")
|
||
|
||
|
||
# ── Songs ───────────────────────────────────────────────────────────────
|
||
|
||
def twinkle_twinkle():
|
||
"""Twinkle Twinkle Little Star — C major."""
|
||
print("Twinkle Twinkle Little Star")
|
||
print("=" * 40)
|
||
|
||
melody = [
|
||
# Twinkle twinkle little star
|
||
("C4", 1), ("C4", 1), ("G4", 1), ("G4", 1),
|
||
("A4", 1), ("A4", 1), ("G4", 2),
|
||
# How I wonder what you are
|
||
("F4", 1), ("F4", 1), ("E4", 1), ("E4", 1),
|
||
("D4", 1), ("D4", 1), ("C4", 2),
|
||
# Up above the world so high
|
||
("G4", 1), ("G4", 1), ("F4", 1), ("F4", 1),
|
||
("E4", 1), ("E4", 1), ("D4", 2),
|
||
# Like a diamond in the sky
|
||
("G4", 1), ("G4", 1), ("F4", 1), ("F4", 1),
|
||
("E4", 1), ("E4", 1), ("D4", 2),
|
||
# Twinkle twinkle little star
|
||
("C4", 1), ("C4", 1), ("G4", 1), ("G4", 1),
|
||
("A4", 1), ("A4", 1), ("G4", 2),
|
||
# How I wonder what you are
|
||
("F4", 1), ("F4", 1), ("E4", 1), ("E4", 1),
|
||
("D4", 1), ("D4", 1), ("C4", 2),
|
||
]
|
||
|
||
play_melody(melody)
|
||
|
||
|
||
def ode_to_joy():
|
||
"""Ode to Joy — Beethoven's 9th Symphony, D major."""
|
||
print("Ode to Joy (Beethoven)")
|
||
print("=" * 40)
|
||
|
||
melody = [
|
||
# Main theme
|
||
("F#4", 1), ("F#4", 1), ("G4", 1), ("A4", 1),
|
||
("A4", 1), ("G4", 1), ("F#4", 1), ("E4", 1),
|
||
("D4", 1), ("D4", 1), ("E4", 1), ("F#4", 1),
|
||
("F#4", 1.5), ("E4", 0.5), ("E4", 2),
|
||
# Repeat with variation
|
||
("F#4", 1), ("F#4", 1), ("G4", 1), ("A4", 1),
|
||
("A4", 1), ("G4", 1), ("F#4", 1), ("E4", 1),
|
||
("D4", 1), ("D4", 1), ("E4", 1), ("F#4", 1),
|
||
("E4", 1.5), ("D4", 0.5), ("D4", 2),
|
||
]
|
||
|
||
play_melody(melody)
|
||
|
||
|
||
def happy_birthday():
|
||
"""Happy Birthday — G major."""
|
||
print("Happy Birthday")
|
||
print("=" * 40)
|
||
|
||
melody = [
|
||
# Happy birthday to you
|
||
("G4", 0.75), ("G4", 0.25), ("A4", 1), ("G4", 1),
|
||
("C5", 1), ("B4", 2),
|
||
# Happy birthday to you
|
||
("G4", 0.75), ("G4", 0.25), ("A4", 1), ("G4", 1),
|
||
("D5", 1), ("C5", 2),
|
||
# Happy birthday dear [name]
|
||
("G4", 0.75), ("G4", 0.25), ("G5", 1), ("E5", 1),
|
||
("C5", 1), ("B4", 1), ("A4", 2),
|
||
# Happy birthday to you
|
||
("F5", 0.75), ("F5", 0.25), ("E5", 1), ("C5", 1),
|
||
("D5", 1), ("C5", 2),
|
||
]
|
||
|
||
play_melody(melody)
|
||
|
||
|
||
def fur_elise():
|
||
"""Fur Elise — opening bars (A minor)."""
|
||
print("Fur Elise (opening)")
|
||
print("=" * 40)
|
||
|
||
melody = [
|
||
("E5", 0.5), ("D#5", 0.5), ("E5", 0.5), ("D#5", 0.5),
|
||
("E5", 0.5), ("B4", 0.5), ("D5", 0.5), ("C5", 0.5),
|
||
("A4", 1), ("REST", 0.5),
|
||
("C4", 0.5), ("E4", 0.5), ("A4", 0.5),
|
||
("B4", 1), ("REST", 0.5),
|
||
("E4", 0.5), ("G#4", 0.5), ("B4", 0.5),
|
||
("C5", 1), ("REST", 0.5),
|
||
("E4", 0.5), ("E5", 0.5), ("D#5", 0.5),
|
||
("E5", 0.5), ("D#5", 0.5), ("E5", 0.5), ("B4", 0.5),
|
||
("D5", 0.5), ("C5", 0.5),
|
||
("A4", 1),
|
||
]
|
||
|
||
play_melody(melody)
|
||
|
||
|
||
def pop_progression():
|
||
"""The I–V–vi–IV pop progression in C major."""
|
||
print("Pop Progression (I-V-vi-IV in C)")
|
||
print("=" * 40)
|
||
print()
|
||
|
||
key = Key("C", "major")
|
||
chords = key.progression("I", "V", "vi", "IV")
|
||
|
||
# Play it twice
|
||
play_progression(chords * 2)
|
||
|
||
|
||
def blues_in_a():
|
||
"""12-bar blues in A."""
|
||
print("12-Bar Blues in A")
|
||
print("=" * 40)
|
||
print()
|
||
|
||
key = Key("A", "major")
|
||
I = key.triad(0)
|
||
IV = key.triad(3)
|
||
V = key.triad(4)
|
||
|
||
bars = [I, I, I, I, IV, IV, I, I, V, IV, I, V]
|
||
|
||
play_progression(bars, beats_each=1.5)
|
||
|
||
|
||
def jazz_ii_v_i():
|
||
"""Jazz ii–V–I turnaround through several keys."""
|
||
print("Jazz ii-V-I Turnaround")
|
||
print("=" * 40)
|
||
print()
|
||
|
||
for tonic in ["C", "F", "Bb", "Eb"]:
|
||
key = Key(tonic, "major")
|
||
chords = key.progression("ii", "V", "I")
|
||
print(f" Key of {tonic}:")
|
||
play_progression(chords, beats_each=1.5)
|
||
print()
|
||
|
||
|
||
# ── Main ────────────────────────────────────────────────────────────────
|
||
|
||
SONGS = {
|
||
"1": ("Twinkle Twinkle Little Star", twinkle_twinkle),
|
||
"2": ("Ode to Joy", ode_to_joy),
|
||
"3": ("Happy Birthday", happy_birthday),
|
||
"4": ("Fur Elise (opening)", fur_elise),
|
||
"5": ("Pop Progression (I-V-vi-IV)", pop_progression),
|
||
"6": ("12-Bar Blues in A", blues_in_a),
|
||
"7": ("Jazz ii-V-I Turnaround", jazz_ii_v_i),
|
||
}
|
||
|
||
if __name__ == "__main__":
|
||
try:
|
||
print("PyTheory Song Player")
|
||
print("=" * 40)
|
||
print()
|
||
|
||
for key, (name, _) in SONGS.items():
|
||
print(f" {key}. {name}")
|
||
|
||
print()
|
||
choice = input("Pick a song (1-7, or 'all'): ").strip()
|
||
|
||
if choice == "all":
|
||
for _, (_, fn) in SONGS.items():
|
||
fn()
|
||
print()
|
||
elif choice in SONGS:
|
||
SONGS[choice][1]()
|
||
else:
|
||
print("Playing all melodies...")
|
||
for _, (_, fn) in SONGS.items():
|
||
fn()
|
||
print()
|
||
except KeyboardInterrupt:
|
||
print("\n\nBye!")
|