Rewrite song.py with 10 full arrangements using drums, chords, and synth leads

Bossa nova, bebop, salsa, afrobeat, reggae, funk, 12/8 blues, samba,
jazz waltz, and house — each with drum patterns, chord progressions,
and hand-written melody lines rendered through the synthesizer.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-25 11:15:06 -04:00
parent adfbc3079a
commit 994c4e244a
+522 -166
View File
@@ -1,205 +1,560 @@
"""Play melodies and chord progressions with PyTheory.
"""Play songs with PyTheory — drums, chords, and synth leads.
Requires PortAudio: brew install portaudio (macOS)
Each song demonstrates a different combination of:
- Drum pattern presets (48 genres available)
- Chord progressions (Roman numeral or symbol-based)
- Monosynth melody lines (sine, saw, triangle waveforms)
- ADSR envelope shaping (piano, pluck, pad, bell, etc.)
"""
from pytheory import Tone, Chord, Key, TonedScale, play, Synth
import numpy
import sounddevice as sd
# ── Helpers ─────────────────────────────────────────────────────────────
BPM = 180
BEAT = 60_000 // BPM # ms per beat
from pytheory import (
Tone, Chord, Key, Pattern, Duration, Score,
Synth, Envelope,
)
from pytheory.play import (
_render, _render_drum_hit, _apply_envelope,
sawtooth_wave, triangle_wave, sine_wave,
SAMPLE_RATE, SAMPLE_PEAK,
)
def play_melody(notes, synth=Synth.SINE):
"""Play a sequence of (note_string, beats) tuples."""
# ── Engine ─────────────────────────────────────────────────────────────────
def render_score(score, melody=None, melody_synth=sawtooth_wave,
melody_envelope=(0.003, 0.05, 0.6, 0.08),
melody_volume=0.4, chord_volume=0.35, drum_volume=0.5):
"""Render a full arrangement to a float32 buffer.
Args:
score: Score with drum hits and chord notes.
melody: Optional list of (note_string_or_None, beats) tuples.
melody_synth: Wave function for the lead voice.
melody_envelope: (attack, decay, sustain, release) for the lead.
melody_volume: Lead mix level (0-1).
chord_volume: Chord mix level (0-1).
drum_volume: Drum mix level (0-1).
Returns:
Float32 numpy array of mixed audio.
"""
samples_per_beat = int(SAMPLE_RATE * 60.0 / score.bpm)
total_beats = score.total_beats
# If melody extends beyond score, expand
if melody:
melody_beats = sum(b for _, b in melody)
total_beats = max(total_beats, melody_beats)
total_samples = int(total_beats * samples_per_beat)
buf = numpy.zeros(total_samples, dtype=numpy.float32)
# Chords
beat_pos = 0.0
for note in score.notes:
if note.tone is not None:
start = int(beat_pos * samples_per_beat)
dur_ms = note.beats * 60_000 / score.bpm
rendered = _render(note.tone, t=dur_ms, envelope=Envelope.PIANO)
rendered_f32 = rendered.astype(numpy.float32) / SAMPLE_PEAK
end = min(start + len(rendered_f32), total_samples)
buf[start:end] += rendered_f32[:end - start] * chord_volume
beat_pos += note.beats
# Drums
for hit in score._drum_hits:
start = int(hit.position * samples_per_beat)
if start >= total_samples:
continue
remaining = total_samples - start
hit_len = min(int(SAMPLE_RATE * 0.5), remaining)
wave = _render_drum_hit(hit.sound.value, hit_len)
vel_scale = hit.velocity / 127.0
buf[start:start + hit_len] += wave * vel_scale * drum_volume
# Melody
if melody:
a, d, s, r = melody_envelope
lead_pos = 0.0
for note_name, beats in melody:
start = int(lead_pos * samples_per_beat)
dur_ms = beats * 60_000 / score.bpm
n_samples = int(SAMPLE_RATE * dur_ms / 1000)
if note_name is not None and start + n_samples <= total_samples:
tone = Tone.from_string(note_name, system="western")
hz = tone.pitch()
wave = melody_synth(hz, n_samples=n_samples)
wave_f32 = wave.astype(numpy.float32) / SAMPLE_PEAK
wave_f32 = _apply_envelope(wave_f32, a, d, s, r)
end = min(start + len(wave_f32), total_samples)
buf[start:end] += wave_f32[:end - start] * melody_volume
lead_pos += beats
# Normalize
peak = numpy.max(numpy.abs(buf))
if peak > 0:
buf = buf / peak * 0.9
return buf
def play_song(buf):
"""Play a rendered buffer. Ctrl-C to skip."""
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))
sd.play(buf, SAMPLE_RATE)
sd.wait()
except KeyboardInterrupt:
print("\n Stopped.")
sd.stop()
print(" (skipped)")
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 ──────────────────────────────────────────────────────────────────
# ── 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 IVviIV pop progression in C major."""
print("Pop Progression (I-V-vi-IV in C)")
print("=" * 40)
def bossa_nova_girl():
"""Bossa nova in A minor — The Girl from Ipanema vibe."""
print(" Bossa Nova in A minor")
print(" Drums: bossa nova | Chords: i-iv-V7-i | Lead: triangle")
print()
key = Key("C", "major")
chords = key.progression("I", "V", "vi", "IV")
score = Pattern.preset("bossa nova").to_score(repeats=4, bpm=140)
# Play it twice
play_progression(chords * 2)
# Am → Dm → E7 → Am (x2)
changes = ["Am", "Am", "Dm", "Dm", "E7", "E7", "Am", "Am"]
for sym in changes:
score.add(Chord.from_symbol(sym), Duration.WHOLE)
melody = [
# Bar 1-2: floating line over Am
("E5", 0.67), ("D5", 0.33), ("C5", 0.67), ("B4", 0.33),
("A4", 1.0), ("C5", 0.67), ("E5", 0.33),
("D5", 0.67), ("C5", 0.33), ("A4", 1.0), (None, 1.0),
# Bar 3-4: reaching up over Dm
("F5", 0.67), ("E5", 0.33), ("D5", 0.67), ("C5", 0.33),
("D5", 1.0), ("F5", 0.67), ("A5", 0.33),
("G5", 0.67), ("F5", 0.33), ("D5", 1.0), (None, 1.0),
# Bar 5-6: tension over E7
("G#5", 0.67), ("F5", 0.33), ("E5", 0.67), ("D5", 0.33),
("E5", 1.0), (None, 0.5), ("B4", 0.5),
("D5", 0.67), ("E5", 0.33), ("G#4", 1.0), (None, 1.0),
# Bar 7-8: resolve to Am
("A4", 1.0), ("C5", 0.67), ("E5", 0.33),
("A5", 1.5), (None, 0.5),
("G5", 0.67), ("E5", 0.33), ("C5", 0.67), ("A4", 0.33),
("A4", 2.0),
]
buf = render_score(score, melody=melody, melody_synth=triangle_wave,
melody_envelope=(0.01, 0.08, 0.7, 0.15),
melody_volume=0.45)
play_song(buf)
def blues_in_a():
"""12-bar blues in A."""
print("12-Bar Blues in A")
print("=" * 40)
def bebop_in_bb():
"""Bebop in Bb — rhythm changes with a horn-like lead."""
print(" Bebop in Bb major")
print(" Drums: bebop | Chords: I-vi-ii-V | Lead: sawtooth")
print()
key = Key("A", "major")
I = key.triad(0)
IV = key.triad(3)
V = key.triad(4)
score = Pattern.preset("bebop").to_score(repeats=8, bpm=160)
bars = [I, I, I, I, IV, IV, I, I, V, IV, I, V]
changes = ["Bb", "Gm", "Cm", "F7"] * 2
for sym in changes:
score.add(Chord.from_symbol(sym), Duration.WHOLE)
play_progression(bars, beats_each=1.5)
melody = [
# Bar 1: Bb — arpeggio up
("Bb4", 0.67), ("D5", 0.33), ("F5", 0.67), ("D5", 0.33),
("Bb4", 0.67), ("C5", 0.33), ("D5", 0.67), ("F5", 0.33),
# Bar 2: Gm — descending with chromatic approach
("G5", 0.67), ("F5", 0.33), ("D5", 0.67), ("Bb4", 0.33),
("A4", 0.67), ("Bb4", 0.33), ("D5", 0.67), ("G4", 0.33),
# Bar 3: Cm — climbing
("C5", 0.67), ("Eb5", 0.33), ("G5", 0.67), ("Eb5", 0.33),
("C5", 0.67), ("D5", 0.33), ("Eb5", 0.67), ("F5", 0.33),
# Bar 4: F7 — dominant tension
("A5", 0.67), ("G5", 0.33), ("F5", 0.67), ("Eb5", 0.33),
("D5", 0.67), ("C5", 0.33), ("A4", 0.5), (None, 0.5),
# Bar 5: Bb — variation
("Bb4", 1.0), ("D5", 0.67), ("F5", 0.33),
("G5", 0.67), ("F5", 0.33), ("D5", 0.67), ("Bb4", 0.33),
# Bar 6: Gm — bluesy
("Bb5", 0.67), ("A5", 0.33), ("G5", 0.67), ("F5", 0.33),
("Eb5", 0.67), ("D5", 0.33), ("Bb4", 0.67), ("G4", 0.33),
# Bar 7: Cm — syncopated
("C5", 0.5), (None, 0.5), ("Eb5", 0.67), ("G5", 0.33),
("F5", 0.67), ("Eb5", 0.33), ("D5", 0.67), ("C5", 0.33),
# Bar 8: F7 — turnaround lick
("A4", 0.67), ("C5", 0.33), ("Eb5", 0.67), ("F5", 0.33),
("G5", 0.67), ("A5", 0.33), ("Bb5", 1.0),
]
buf = render_score(score, melody=melody, melody_synth=sawtooth_wave,
melody_envelope=(0.003, 0.05, 0.6, 0.08),
melody_volume=0.4)
play_song(buf)
def jazz_ii_v_i():
"""Jazz iiVI turnaround through several keys."""
print("Jazz ii-V-I Turnaround")
print("=" * 40)
def salsa_descarga():
"""Salsa descarga in D minor — clave-driven jam."""
print(" Salsa Descarga in D minor")
print(" Drums: salsa | Chords: ii-V-i-bVI | Lead: sawtooth")
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()
score = Pattern.preset("salsa").to_score(repeats=4, bpm=180)
changes = ["Em7b5", "A7", "Dm7", "Bbmaj7"] * 2
for sym in changes:
score.add(Chord.from_symbol(sym), Duration.WHOLE)
melody = [
# Bar 1: Em7b5 — angular line
("E5", 0.67), ("G5", 0.33), ("Bb5", 0.67), ("A5", 0.33),
("G5", 0.67), ("F5", 0.33), ("E5", 0.67), ("D5", 0.33),
# Bar 2: A7 — chromatic descent
("C#5", 0.67), ("D5", 0.33), ("E5", 0.67), ("G5", 0.33),
("F5", 0.67), ("E5", 0.33), ("C#5", 0.5), (None, 0.5),
# Bar 3: Dm7 — syncopated
("D5", 0.5), (None, 0.17), ("F5", 0.67), ("A5", 0.33),
("G5", 0.67), ("F5", 0.33), ("E5", 0.67), ("D5", 0.33),
# Bar 4: Bbmaj7 — resolution
("Bb4", 1.0), ("D5", 0.67), ("F5", 0.33),
("A5", 1.0), (None, 1.0),
# Bar 5-8: second pass — variation
("E5", 0.5), ("F5", 0.5), ("G5", 0.67), ("A5", 0.33),
("Bb5", 0.67), ("A5", 0.33), ("G5", 0.67), ("E5", 0.33),
("C#5", 0.67), ("E5", 0.33), ("A5", 0.67), ("G5", 0.33),
("F5", 0.67), ("E5", 0.33), ("C#5", 0.67), ("A4", 0.33),
("D5", 1.0), ("F5", 0.67), ("A5", 0.33),
("G5", 0.67), ("F5", 0.33), ("D5", 1.0), (None, 1.0),
("Bb4", 0.67), ("D5", 0.33), ("F5", 0.67), ("Bb5", 0.33),
("A5", 1.5), (None, 0.5),
]
buf = render_score(score, melody=melody, melody_synth=sawtooth_wave,
melody_envelope=(0.005, 0.06, 0.5, 0.1),
melody_volume=0.4, drum_volume=0.55)
play_song(buf)
# ── Main ────────────────────────────────────────────────────────────────
def afrobeat_groove():
"""Afrobeat in E minor — Fela Kuti-inspired groove."""
print(" Afrobeat in E minor")
print(" Drums: afrobeat | Chords: i-iv-bVII-bVI | Lead: saw")
print()
score = Pattern.preset("afrobeat").to_score(repeats=8, bpm=115)
# 2 bars per chord, repeat once
changes = ["Em", "Am", "D", "C"] * 2
for sym in changes:
score.add(Chord.from_symbol(sym), Duration.WHOLE)
melody = [
# Repetitive, hypnotic pentatonic riff
("E5", 0.5), ("G5", 0.5), ("A5", 0.5), ("G5", 0.5),
("E5", 0.5), ("D5", 0.5), ("E5", 1.0),
("E5", 0.5), ("G5", 0.5), ("A5", 0.5), ("B5", 0.5),
("A5", 0.5), ("G5", 0.5), ("E5", 1.0),
# Variation with syncopation
(None, 0.5), ("A5", 0.5), ("G5", 0.5), ("E5", 0.5),
("D5", 1.0), ("E5", 0.5), ("G5", 0.5),
("A5", 0.5), ("B5", 0.5), ("A5", 0.5), ("G5", 0.5),
("E5", 1.5), (None, 0.5),
# Second half: octave higher accents
("E5", 0.5), ("G5", 0.5), ("A5", 0.5), ("G5", 0.5),
("E5", 0.5), ("D5", 0.5), ("B4", 1.0),
("E5", 0.5), ("G5", 0.5), ("A5", 0.5), ("B5", 0.5),
("A5", 0.5), ("G5", 0.5), ("E5", 1.0),
(None, 0.5), ("B5", 0.5), ("A5", 0.5), ("G5", 0.5),
("E5", 1.0), ("D5", 0.5), ("E5", 0.5),
("E5", 2.0), (None, 2.0),
]
buf = render_score(score, melody=melody, melody_synth=sawtooth_wave,
melody_envelope=(0.005, 0.08, 0.5, 0.1),
melody_volume=0.35, drum_volume=0.55)
play_song(buf)
def reggae_one_drop():
"""Reggae one-drop in G major — roots vibes."""
print(" Reggae One-Drop in G major")
print(" Drums: reggae | Chords: I-IV-V-IV | Lead: triangle")
print()
score = Pattern.preset("reggae").to_score(repeats=8, bpm=80)
changes = ["G", "C", "D", "C"] * 2
for sym in changes:
score.add(Chord.from_symbol(sym), Duration.WHOLE)
melody = [
# Laid-back pentatonic melody
("G5", 1.5), (None, 0.5), ("B5", 1.0), ("A5", 1.0),
("G5", 2.0), ("E5", 1.0), ("D5", 1.0),
("C5", 1.5), (None, 0.5), ("E5", 1.0), ("G5", 1.0),
("A5", 1.5), (None, 0.5), ("G5", 2.0),
# Second phrase
("D5", 1.5), (None, 0.5), ("E5", 1.0), ("G5", 1.0),
("A5", 2.0), ("B5", 1.0), ("A5", 1.0),
("G5", 1.5), (None, 0.5), ("E5", 1.0), ("D5", 1.0),
("G4", 3.0), (None, 1.0),
]
buf = render_score(score, melody=melody, melody_synth=triangle_wave,
melody_envelope=(0.02, 0.1, 0.7, 0.2),
melody_volume=0.45, drum_volume=0.45)
play_song(buf)
def funk_workout():
"""Funk in E minor — syncopated 16th note groove."""
print(" Funk Workout in E minor")
print(" Drums: funk | Chords: i-iv-bVII-V | Lead: saw")
print()
score = Pattern.preset("funk").to_score(repeats=8, bpm=100)
changes = ["Em", "Am", "D", "B7"] * 2
for sym in changes:
score.add(Chord.from_symbol(sym), Duration.WHOLE)
melody = [
# Funky 16th-note figure
("E5", 0.25), ("E5", 0.25), (None, 0.25), ("G5", 0.25),
(None, 0.25), ("A5", 0.25), ("G5", 0.25), ("E5", 0.25),
("D5", 0.5), ("E5", 0.5), (None, 0.5), ("B4", 0.5),
("E5", 0.25), ("E5", 0.25), (None, 0.25), ("G5", 0.25),
(None, 0.25), ("A5", 0.25), ("B5", 0.25), ("A5", 0.25),
("G5", 0.5), ("E5", 0.5), (None, 1.0),
# Am phrase
("A4", 0.25), ("C5", 0.25), ("E5", 0.25), ("A5", 0.25),
("G5", 0.5), ("E5", 0.5), (None, 0.5), ("C5", 0.5),
("A4", 0.5), ("C5", 0.5), ("D5", 0.5), ("E5", 0.5),
("E5", 1.0), (None, 1.0),
# D resolution
("D5", 0.25), ("F#5", 0.25), ("A5", 0.25), ("D5", 0.25),
("F#5", 0.5), ("D5", 0.5), ("A4", 0.5), ("D5", 0.5),
# B7 turnaround
("D#5", 0.5), ("F#5", 0.5), ("B4", 0.5), ("D#5", 0.5),
("F#5", 1.0), (None, 1.0),
]
buf = render_score(score, melody=melody, melody_synth=sawtooth_wave,
melody_envelope=(0.002, 0.03, 0.5, 0.05),
melody_volume=0.4, drum_volume=0.55)
play_song(buf)
def blues_shuffle():
"""12/8 blues in A — slow shuffle with a wailing lead."""
print(" 12/8 Blues Shuffle in A")
print(" Drums: 12/8 blues | Chords: I-IV-V | Lead: saw (bluesy)")
print()
score = Pattern.preset("12/8 blues").to_score(repeats=6, bpm=70)
# 12 bars: I I I I IV IV I I V IV I V (each bar = 6 beats in 12/8)
bars = ["A", "A", "A", "A", "D", "D",
"A", "A", "E7", "D", "A", "E7"]
for sym in bars:
score.add(Chord.from_symbol(sym), Duration.DOTTED_HALF)
score.add(Chord.from_symbol(sym), Duration.DOTTED_HALF)
# Wait, 12 bars * 6 beats = 72 beats, drums = 6 repeats * 6 = 36 beats
# Need to match. Let's just do 6 bars of blues (half a chorus)
# Actually, 6 repeats * 6 beats = 36 beats. 6 bars * 6 = 36. Perfect.
melody = [
# Bars 1-2 over A: classic blues opening
("A4", 1.0), ("C5", 0.67), ("A4", 0.33), ("E4", 1.0),
(None, 0.5), ("A4", 0.5), ("C5", 0.67), ("D5", 0.33),
("E5", 1.5), ("D5", 0.5), ("C5", 0.5), ("A4", 0.5),
("E4", 1.5), (None, 0.5), ("A4", 1.0),
# Bars 3-4 over D: reaching
("D5", 1.0), ("F5", 0.67), ("D5", 0.33), ("A4", 1.0),
(None, 1.0), ("D5", 0.67), ("F5", 0.33), ("A5", 1.0),
("G5", 0.67), ("F5", 0.33), ("D5", 1.0), (None, 1.0),
# Bars 5-6 over E7/D turnaround
("E5", 0.67), ("G#4", 0.33), ("B4", 0.67), ("E5", 0.33),
("D5", 1.0), ("A4", 1.0), (None, 1.0),
("A4", 0.67), ("C5", 0.33), ("E5", 0.67), ("A5", 0.33),
("A5", 2.0), (None, 1.0),
]
buf = render_score(score, melody=melody, melody_synth=sawtooth_wave,
melody_envelope=(0.01, 0.1, 0.6, 0.2),
melody_volume=0.45, drum_volume=0.45)
play_song(buf)
def samba_de_janeiro():
"""Samba in G major — carnival energy."""
print(" Samba in G major")
print(" Drums: samba | Chords: I-vi-ii-V | Lead: triangle")
print()
score = Pattern.preset("samba").to_score(repeats=8, bpm=170)
changes = ["G", "Em", "Am", "D7"] * 2
for sym in changes:
score.add(Chord.from_symbol(sym), Duration.WHOLE)
melody = [
# Fast, rhythmic melody
("B5", 0.33), ("A5", 0.33), ("G5", 0.34), ("F#5", 0.5), ("E5", 0.5),
("D5", 0.5), ("G5", 0.5), ("B5", 0.5), ("A5", 0.5),
("G5", 0.67), ("E5", 0.33), ("D5", 0.67), ("B4", 0.33),
("G4", 1.0), (None, 1.0),
# Over Em
("E5", 0.5), ("G5", 0.5), ("B5", 0.5), ("A5", 0.5),
("G5", 0.67), ("F#5", 0.33), ("E5", 0.67), ("D5", 0.33),
("E5", 1.0), (None, 1.0),
# Over Am
("A5", 0.5), ("C6", 0.5), ("B5", 0.5), ("A5", 0.5),
("G5", 0.67), ("E5", 0.33), ("C5", 0.67), ("A4", 0.33),
("A4", 1.0), (None, 1.0),
# Over D7 — turnaround
("D5", 0.5), ("F#5", 0.5), ("A5", 0.5), ("C5", 0.5),
("B4", 0.67), ("A4", 0.33), ("G4", 0.67), ("F#4", 0.33),
("G4", 2.0), (None, 2.0),
]
buf = render_score(score, melody=melody, melody_synth=triangle_wave,
melody_envelope=(0.005, 0.05, 0.6, 0.1),
melody_volume=0.4, drum_volume=0.5)
play_song(buf)
def jazz_waltz():
"""Jazz waltz in F major — 3/4 time with brushes feel."""
print(" Jazz Waltz in F major")
print(" Drums: waltz | Chords: I-ii-V-I | Lead: triangle")
print()
score = Pattern.preset("waltz").to_score(repeats=16, bpm=150)
# 4 bars per change, 3 beats each = 12 beats per chord
for _ in range(2):
for sym in ["Fmaj7", "Gm", "C7", "Fmaj7"]:
score.add(Chord.from_symbol(sym), Duration.DOTTED_HALF)
score.add(Chord.from_symbol(sym), Duration.DOTTED_HALF)
score.add(Chord.from_symbol(sym), Duration.DOTTED_HALF)
score.add(Chord.from_symbol(sym), Duration.DOTTED_HALF)
melody = [
# 3/4 phrases, lyrical
("A5", 1.5), ("G5", 0.5), ("F5", 1.0),
("E5", 1.0), ("C5", 1.0), ("F5", 1.0),
("A5", 2.0), (None, 1.0),
("G5", 2.0), (None, 1.0),
# Over Gm
("Bb5", 1.0), ("A5", 0.5), ("G5", 0.5),
("F5", 1.0), ("D5", 1.0), ("G5", 1.0),
("Bb5", 2.0), (None, 1.0),
("A5", 1.5), ("G5", 0.5), ("F5", 1.0),
# Over C7
("E5", 1.0), ("G5", 1.0), ("Bb5", 1.0),
("A5", 1.5), ("G5", 0.5), ("E5", 1.0),
("C5", 2.0), (None, 1.0),
("E5", 1.0), ("G5", 1.0), ("C5", 1.0),
# Resolve to F
("F5", 2.0), ("A5", 1.0),
("C6", 2.0), (None, 1.0),
("A5", 1.0), ("F5", 1.0), ("C5", 1.0),
("F5", 3.0),
]
buf = render_score(score, melody=melody, melody_synth=triangle_wave,
melody_envelope=(0.02, 0.1, 0.7, 0.2),
melody_volume=0.45, drum_volume=0.4)
play_song(buf)
def house_anthem():
"""House in C minor — four-on-the-floor with pad chords."""
print(" House Anthem in C minor")
print(" Drums: house | Chords: i-bVI-bVII-i | Lead: saw (acid)")
print()
score = Pattern.preset("house").to_score(repeats=8, bpm=124)
changes = ["Cm", "Ab", "Bb", "Cm"] * 2
for sym in changes:
score.add(Chord.from_symbol(sym), Duration.WHOLE)
melody = [
# Acid-style arpeggiated lead
("C5", 0.25), ("Eb5", 0.25), ("G5", 0.25), ("C5", 0.25),
("Eb5", 0.25), ("G5", 0.25), ("C5", 0.25), ("Eb5", 0.25),
("G5", 0.25), ("Eb5", 0.25), ("C5", 0.25), ("G4", 0.25),
("C5", 0.5), (None, 0.5), ("Eb5", 0.5), ("G5", 0.5),
# Over Ab
("Ab4", 0.25), ("C5", 0.25), ("Eb5", 0.25), ("Ab4", 0.25),
("C5", 0.25), ("Eb5", 0.25), ("Ab4", 0.25), ("C5", 0.25),
("Eb5", 0.25), ("C5", 0.25), ("Ab4", 0.25), ("Eb4", 0.25),
("Ab4", 0.5), (None, 0.5), ("C5", 0.5), ("Eb5", 0.5),
# Over Bb
("Bb4", 0.25), ("D5", 0.25), ("F5", 0.25), ("Bb4", 0.25),
("D5", 0.25), ("F5", 0.25), ("Bb4", 0.25), ("D5", 0.25),
("F5", 0.5), ("D5", 0.5), ("Bb4", 0.5), ("F5", 0.5),
# Back to Cm — resolution
("C5", 0.25), ("Eb5", 0.25), ("G5", 0.25), ("C6", 0.25),
("G5", 0.25), ("Eb5", 0.25), ("C5", 0.25), (None, 0.25),
("C5", 1.5), (None, 0.5),
# Second half: more intense
("G5", 0.25), ("G5", 0.25), ("Eb5", 0.25), ("C5", 0.25),
("G5", 0.25), ("G5", 0.25), ("Eb5", 0.25), ("C5", 0.25),
("Eb5", 0.5), ("C5", 0.5), ("G4", 0.5), ("C5", 0.5),
("Eb5", 1.0), (None, 1.0),
("Ab4", 0.5), ("C5", 0.5), ("Eb5", 0.5), ("Ab5", 0.5),
("G5", 0.5), ("Eb5", 0.5), ("C5", 1.0),
("Bb4", 0.5), ("D5", 0.5), ("F5", 0.5), ("Bb5", 0.5),
("F5", 0.5), ("D5", 0.5), ("Bb4", 1.0),
("C5", 0.5), ("Eb5", 0.5), ("G5", 0.5), ("C6", 0.5),
("C6", 2.0),
]
buf = render_score(score, melody=melody, melody_synth=sawtooth_wave,
melody_envelope=(0.001, 0.03, 0.4, 0.05),
melody_volume=0.35, chord_volume=0.4, drum_volume=0.5)
play_song(buf)
# ── 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),
"1": ("Bossa Nova in A minor", bossa_nova_girl),
"2": ("Bebop in Bb major", bebop_in_bb),
"3": ("Salsa Descarga in D minor", salsa_descarga),
"4": ("Afrobeat in E minor", afrobeat_groove),
"5": ("Reggae One-Drop in G major", reggae_one_drop),
"6": ("Funk Workout in E minor", funk_workout),
"7": ("12/8 Blues Shuffle in A", blues_shuffle),
"8": ("Samba in G major", samba_de_janeiro),
"9": ("Jazz Waltz in F major", jazz_waltz),
"10": ("House Anthem in C minor", house_anthem),
}
if __name__ == "__main__":
try:
print("PyTheory Song Player")
print("=" * 40)
print()
print(" PyTheory Song Player")
print(" " + "=" * 40)
print()
for key, (name, _) in SONGS.items():
print(f" {key}. {name}")
print(f" {key:>2}. {name}")
print()
choice = input("Pick a song (1-7, or 'all'): ").strip()
choice = input(" Pick a song (1-10, or 'all'): ").strip()
print()
if choice == "all":
for _, (_, fn) in SONGS.items():
@@ -208,9 +563,10 @@ if __name__ == "__main__":
elif choice in SONGS:
SONGS[choice][1]()
else:
print("Playing all melodies...")
print(" Playing all songs...")
for _, (_, fn) in SONGS.items():
fn()
print()
except KeyboardInterrupt:
print("\n\nBye!")
sd.stop()
print("\n\n Bye!")