Rewrite song.py with 7 playable songs and chord progressions

Melodies: Twinkle Twinkle, Ode to Joy, Happy Birthday, Fur Elise
Progressions: Pop I-V-vi-IV, 12-bar blues in A, Jazz ii-V-I turnaround
Interactive menu for picking songs. Clean helper functions for
melody and chord progression playback.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-22 20:28:31 -04:00
parent 9da3ac8b28
commit c006f5b3da
+192 -63
View File
@@ -1,78 +1,207 @@
from time import sleep
"""Play melodies and chord progressions with PyTheory.
from pytheory import TonedScale, Tone, CHARTS, play
Requires PortAudio: brew install portaudio (macOS)
"""
from pytheory import Tone, Chord, Key, TonedScale, play, Synth
# ── Helpers ─────────────────────────────────────────────────────────────
BPM = 120
BEAT = 60_000 // BPM # ms per beat
# Add this constant at the top of the file, after the imports
EIGHTH_NOTE = 0.25
QUARTER_NOTE = 0.5
# Add scale definition after the constants
C_MAJOR = TonedScale(tonic="C4")
def play_melody(notes, synth=Synth.SINE):
"""Play a sequence of (note_string, beats) tuples."""
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))
def play_note(note, t=0.1):
# Convert scale degree (1-7) to note name (0-based index)
scale_notes = ["C4", "D4", "E4", "F4", "G4", "A4", "B4"]
note_name = scale_notes[note - 1] # Subtract 1 because scale degrees are 1-based
tone = Tone(note_name)
play(tone, t=t * 1_000)
sleep(t)
def play_progression(chords, beats_each=2, synth=Synth.TRIANGLE):
"""Play a list of Chord objects."""
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))
# Twinkle Twinkle Little Star in C major
# C C G G A A G (first line)
# F F E E D D C (second line)
# G G F F E E D (third line)
# G G F F E E D (fourth line)
# C C G G A A G (fifth line)
# F F E E D D C (sixth line)
# ── Songs ───────────────────────────────────────────────────────────────
def twinkle_twinkle():
"""Twinkle Twinkle Little Star — C major."""
print("Twinkle Twinkle Little Star")
print("=" * 40)
def play_twinkle():
# Define the patterns using scale degrees instead of note names
line1 = [
(1, EIGHTH_NOTE), # C4
(1, EIGHTH_NOTE), # C4
(5, EIGHTH_NOTE), # G4
(5, EIGHTH_NOTE), # G4
(6, EIGHTH_NOTE), # A4
(6, EIGHTH_NOTE), # A4
(5, QUARTER_NOTE), # G4
]
line2 = [
(4, EIGHTH_NOTE), # F4
(4, EIGHTH_NOTE), # F4
(3, EIGHTH_NOTE), # E4
(3, EIGHTH_NOTE), # E4
(2, EIGHTH_NOTE), # D4
(2, EIGHTH_NOTE), # D4
(1, QUARTER_NOTE), # C4
]
line3 = [
(5, EIGHTH_NOTE), # G4
(5, EIGHTH_NOTE), # G4
(4, EIGHTH_NOTE), # F4
(4, EIGHTH_NOTE), # F4
(3, EIGHTH_NOTE), # E4
(3, EIGHTH_NOTE), # E4
(2, QUARTER_NOTE), # D4
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),
]
# Construct the full melody using the patterns
melody = (
line1 # Twinkle twinkle little star
+ line2 # How I wonder what you are
+ line3 # Up above the world so high
+ line3 # Like a diamond in the sky
+ line1 # Twinkle twinkle little star
+ line2 # How I wonder what you are
)
play_melody(melody)
print("Playing Twinkle Twinkle Little Star...")
for note, duration in melody:
play_note(note, duration)
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 IVviIV 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 iiVI 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__":
play_twinkle()
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()