Rewrite all 13 songs with per-part effects (reverb, delay, lowpass)

Every song now uses the full effects chain. Added 3 new songs:
Kingston After Dark (dub), Minimal Techno, Gospel Shuffle.
Each demonstrates different effect combinations.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-25 13:01:56 -04:00
parent 5d746ed0b1
commit cf061e3783
+257 -84
View File
@@ -1,11 +1,13 @@
"""Play songs with PyTheory — drums, chords, bass, and synth leads.
"""Play songs with PyTheory — drums, chords, bass, leads, and effects.
Requires PortAudio: brew install portaudio (macOS)
Each song uses the multi-part Score API:
- Drum pattern presets (48 genres)
- Named parts with independent synth voices and envelopes
- Chord pads, walking bass lines, and melody leads
Each song showcases the full Score API:
- 58 drum pattern presets with genre-matched fills
- 10 synth waveforms (sine, saw, triangle, square, pulse, FM, noise, supersaw, PWM slow/fast)
- 8 ADSR envelope presets
- Per-part effects: reverb, delay, lowpass filter with resonance
- Named parts with independent voices mixed together
Usage:
python examples/song.py
@@ -13,10 +15,8 @@ Usage:
import sounddevice as sd
from pytheory import (
Chord, Key, Pattern, Duration, Score,
)
from pytheory.play import play_score, render_score, SAMPLE_RATE
from pytheory import Chord, Key, Pattern, Duration, Score
from pytheory.play import render_score, SAMPLE_RATE
def play_song(score, label=""):
@@ -37,19 +37,24 @@ def play_song(score, label=""):
# ── Songs ──────────────────────────────────────────────────────────────────
def bossa_nova_girl():
"""Bossa nova in A minor — full arrangement."""
"""Bossa nova in A minor — Ipanema vibes with reverb-washed chords."""
print(" Bossa Nova in A minor")
print(" Drums: bossa nova | Lead: triangle | Bass: triangle")
print(" bossa nova drums | FM rhodes + reverb | triangle lead + delay")
print(" sine bass + LP 600Hz")
score = Score("4/4", bpm=140)
score.add_pattern(Pattern.preset("bossa nova"), repeats=4)
score.drums("bossa nova", repeats=4)
chords = score.part("chords", synth="sine", envelope="pad", volume=0.35)
lead = score.part("lead", synth="triangle", envelope="pluck", volume=0.5)
bass = score.part("bass", synth="triangle", envelope="pluck", volume=0.45)
rhodes = score.part("rhodes", synth="fm", envelope="piano",
volume=0.3, reverb=0.4, reverb_decay=1.8)
lead = score.part("lead", synth="triangle", envelope="pluck",
volume=0.45, delay=0.25, delay_time=0.32,
delay_feedback=0.35, reverb=0.2)
bass = score.part("bass", synth="sine", envelope="pluck",
volume=0.45, lowpass=600)
for sym in ["Am", "Am", "Dm", "Dm", "E7", "E7", "Am", "Am"]:
chords.add(Chord.from_symbol(sym), Duration.WHOLE)
rhodes.add(Chord.from_symbol(sym), Duration.WHOLE)
for n, d in [
("E5",.67),("D5",.33),("C5",.67),("B4",.33),("A4",1),("C5",.67),("E5",.33),
@@ -63,29 +68,32 @@ def bossa_nova_girl():
]:
lead.rest(d) if n is None else lead.add(n, d)
for n in ["A2","B2","C3","E3","D3","E3","F3","E3",
"E3","D3","C3","B2","A2","G2","A2","B2",
"A2","B2","C3","E3","D3","E3","F3","E3",
"E3","D3","C3","B2","A2","E2","A2","A2"]:
for n in ["A2","E2","A2","C3","D2","A2","D2","F2",
"E2","B2","E2","G#2","A2","E2","A2","C3",
"A2","E2","A2","C3","D2","A2","D2","F2",
"E2","B2","E2","G#2","A2","E2","A2","A2"]:
bass.add(n, Duration.QUARTER)
play_song(score)
def bebop_in_bb():
"""Bebop in Bb — rhythm changes with horn lead and walking bass."""
"""Bebop in Bb — horn lead over rhythm changes with walking bass."""
print(" Bebop in Bb major")
print(" Drums: bebop | Lead: sawtooth | Bass: triangle")
print(" bebop drums + fill | saw lead + LP 4kHz | FM rhodes + reverb")
score = Score("4/4", bpm=160)
score.add_pattern(Pattern.preset("bebop"), repeats=8)
score.drums("bebop", repeats=8, fill="jazz", fill_every=8)
chords = score.part("chords", synth="sine", envelope="piano", volume=0.3)
lead = score.part("lead", synth="saw", envelope="pluck", volume=0.45)
bass = score.part("bass", synth="triangle", envelope="pluck", volume=0.4)
rhodes = score.part("rhodes", synth="fm", envelope="piano",
volume=0.25, reverb=0.35, reverb_decay=1.2)
lead = score.part("lead", synth="saw", envelope="pluck",
volume=0.4, lowpass=4000, lowpass_q=1.1)
bass = score.part("bass", synth="triangle", envelope="pluck",
volume=0.4, lowpass=900)
for sym in ["Bb", "Gm", "Cm", "F7"] * 2:
chords.add(Chord.from_symbol(sym), Duration.WHOLE)
rhodes.add(Chord.from_symbol(sym), Duration.WHOLE)
for n, d in [
("Bb4",.67),("D5",.33),("F5",.67),("D5",.33),
@@ -117,19 +125,23 @@ def bebop_in_bb():
def salsa_descarga():
"""Salsa descarga in D minor — clave-driven jam."""
"""Salsa descarga in D minor — clave-driven with timbale lead."""
print(" Salsa Descarga in D minor")
print(" Drums: salsa | Lead: sawtooth | Bass: sine")
print(" salsa drums + fill | saw lead + delay | pulse bass + LP 500Hz")
score = Score("4/4", bpm=180)
score.add_pattern(Pattern.preset("salsa"), repeats=4)
score.drums("salsa", repeats=8, fill="salsa", fill_every=4)
chords = score.part("chords", synth="sine", envelope="pad", volume=0.3)
lead = score.part("lead", synth="saw", envelope="pluck", volume=0.4)
bass = score.part("bass", synth="sine", envelope="pluck", volume=0.45)
pads = score.part("pads", synth="pwm_slow", envelope="pad",
volume=0.2, reverb=0.3, lowpass=2000)
lead = score.part("lead", synth="saw", envelope="pluck",
volume=0.4, delay=0.2, delay_time=0.167,
delay_feedback=0.3)
bass = score.part("bass", synth="pulse", envelope="pluck",
volume=0.45, lowpass=500, lowpass_q=1.3)
for sym in ["Em7b5", "A7", "Dm7", "Bbmaj7"] * 2:
chords.add(Chord.from_symbol(sym), Duration.WHOLE)
pads.add(Chord.from_symbol(sym), Duration.WHOLE)
for n, d in [
("E5",.67),("G5",.33),("Bb5",.67),("A5",.33),
@@ -148,7 +160,6 @@ def salsa_descarga():
]:
lead.rest(d) if n is None else lead.add(n, d)
# Salsa bass: tumbao pattern
for n in ["E2","E3","A2","A3","D2","D3","Bb2","Bb3"] * 4:
bass.add(n, Duration.QUARTER)
@@ -156,21 +167,24 @@ def salsa_descarga():
def afrobeat_groove():
"""Afrobeat in E minor — Fela Kuti-inspired."""
"""Afrobeat in E minor — Fela-inspired hypnotic groove."""
print(" Afrobeat in E minor")
print(" Drums: afrobeat | Lead: sawtooth | Bass: sine")
print(" afrobeat drums + fill | saw lead + LP 3kHz | supersaw pads + reverb")
score = Score("4/4", bpm=115)
score.add_pattern(Pattern.preset("afrobeat"), repeats=8)
score.drums("afrobeat", repeats=8, fill="afrobeat", fill_every=8)
chords = score.part("chords", synth="sine", envelope="pad", volume=0.3)
lead = score.part("lead", synth="saw", envelope="pluck", volume=0.4)
bass = score.part("bass", synth="sine", envelope="pluck", volume=0.45)
pads = score.part("pads", synth="supersaw", envelope="pad",
volume=0.2, reverb=0.4, reverb_decay=2.0,
lowpass=3000)
lead = score.part("lead", synth="saw", envelope="pluck",
volume=0.4, lowpass=3000, lowpass_q=1.0)
bass = score.part("bass", synth="sine", envelope="pluck",
volume=0.5, lowpass=500)
for sym in ["Em", "Am", "D", "C"] * 2:
chords.add(Chord.from_symbol(sym), Duration.WHOLE)
pads.add(Chord.from_symbol(sym), Duration.WHOLE)
# Hypnotic pentatonic riff
riff = [("E5",.5),("G5",.5),("A5",.5),("G5",.5),
("E5",.5),("D5",.5),("E5",1),
("E5",.5),("G5",.5),("A5",.5),("B5",.5),
@@ -190,16 +204,22 @@ def afrobeat_groove():
def reggae_one_drop():
"""Reggae one-drop in G major."""
"""Reggae one-drop in G major — roots vibes with dub effects."""
print(" Reggae One-Drop in G major")
print(" Drums: reggae | Lead: triangle | Bass: sine")
print(" reggae drums + fill | triangle lead + delay + reverb")
print(" square chords + reverb | sine bass + LP 400Hz")
score = Score("4/4", bpm=80)
score.add_pattern(Pattern.preset("reggae"), repeats=8)
score.drums("reggae", repeats=8, fill="reggae", fill_every=8)
chords = score.part("chords", synth="sine", envelope="organ", volume=0.35)
lead = score.part("lead", synth="triangle", envelope="pad", volume=0.45)
bass = score.part("bass", synth="sine", envelope="pluck", volume=0.5)
chords = score.part("chords", synth="square", envelope="staccato",
volume=0.2, reverb=0.5, reverb_decay=2.0,
lowpass=2000)
lead = score.part("lead", synth="triangle", envelope="strings",
volume=0.4, delay=0.35, delay_time=0.5625,
delay_feedback=0.45, reverb=0.3)
bass = score.part("bass", synth="sine", envelope="pluck",
volume=0.55, lowpass=400, lowpass_q=1.3)
for sym in ["G", "C", "D", "C"] * 2:
chords.add(Chord.from_symbol(sym), Duration.WHOLE)
@@ -226,16 +246,20 @@ def reggae_one_drop():
def funk_workout():
"""Funk in E minor — syncopated groove."""
"""Funk in E minor — syncopated 16ths with filtered everything."""
print(" Funk Workout in E minor")
print(" Drums: funk | Lead: sawtooth | Bass: sine")
print(" funk drums + fill | saw lead + LP 3.5kHz Q=1.5 | pulse bass")
score = Score("4/4", bpm=100)
score.add_pattern(Pattern.preset("funk"), repeats=8)
score.drums("funk", repeats=8, fill="funk", fill_every=4)
chords = score.part("chords", synth="sine", envelope="staccato", volume=0.3)
lead = score.part("lead", synth="saw", envelope="pluck", volume=0.4)
bass = score.part("bass", synth="sine", envelope="pluck", volume=0.5)
chords = score.part("chords", synth="square", envelope="staccato",
volume=0.25, lowpass=2500, reverb=0.15)
lead = score.part("lead", synth="saw", envelope="pluck",
volume=0.4, lowpass=3500, lowpass_q=1.5,
delay=0.15, delay_time=0.15, delay_feedback=0.25)
bass = score.part("bass", synth="pulse", envelope="pluck",
volume=0.5, lowpass=600, lowpass_q=1.2)
for sym in ["Em", "Am", "D", "B7"] * 2:
chords.add(Chord.from_symbol(sym), Duration.WHOLE)
@@ -266,16 +290,21 @@ def funk_workout():
def blues_shuffle():
"""12/8 blues in A — slow shuffle."""
"""12/8 blues in A — slow shuffle with wailing lead."""
print(" 12/8 Blues Shuffle in A")
print(" Drums: 12/8 blues | Lead: sawtooth | Bass: sine")
print(" 12/8 blues drums | saw lead + reverb + delay | sine bass + LP 500Hz")
score = Score("12/8", bpm=70)
score.add_pattern(Pattern.preset("12/8 blues"), repeats=6)
score.drums("12/8 blues", repeats=6)
chords = score.part("chords", synth="sine", envelope="piano", volume=0.35)
lead = score.part("lead", synth="saw", envelope="pluck", volume=0.45)
bass = score.part("bass", synth="sine", envelope="pluck", volume=0.45)
chords = score.part("chords", synth="fm", envelope="piano",
volume=0.3, reverb=0.3, reverb_decay=1.5)
lead = score.part("lead", synth="saw", envelope="pluck",
volume=0.45, reverb=0.3, reverb_decay=1.2,
delay=0.2, delay_time=0.43, delay_feedback=0.3,
lowpass=3500)
bass = score.part("bass", synth="sine", envelope="pluck",
volume=0.5, lowpass=500)
for sym in ["A", "A", "D", "D", "E7", "A"]:
chords.add(Chord.from_symbol(sym), Duration.DOTTED_HALF)
@@ -306,19 +335,24 @@ def blues_shuffle():
def samba_de_janeiro():
"""Samba in G major — carnival energy."""
"""Samba in G major — carnival energy with shimmering pads."""
print(" Samba in G major")
print(" Drums: samba | Lead: triangle | Bass: sine")
print(" samba drums + fill | triangle lead + delay | supersaw pads + reverb")
score = Score("4/4", bpm=170)
score.add_pattern(Pattern.preset("samba"), repeats=8)
score.drums("samba", repeats=8, fill="samba", fill_every=8)
chords = score.part("chords", synth="sine", envelope="pad", volume=0.3)
lead = score.part("lead", synth="triangle", envelope="pluck", volume=0.45)
bass = score.part("bass", synth="sine", envelope="pluck", volume=0.45)
pads = score.part("pads", synth="supersaw", envelope="pad",
volume=0.2, reverb=0.45, reverb_decay=2.0,
lowpass=4000)
lead = score.part("lead", synth="triangle", envelope="pluck",
volume=0.45, delay=0.2, delay_time=0.176,
delay_feedback=0.3)
bass = score.part("bass", synth="sine", envelope="pluck",
volume=0.45, lowpass=700)
for sym in ["G", "Em", "Am", "D7"] * 2:
chords.add(Chord.from_symbol(sym), Duration.WHOLE)
pads.add(Chord.from_symbol(sym), Duration.WHOLE)
for n, d in [
("B5",.33),("A5",.33),("G5",.34),("F#5",.5),("E5",.5),
@@ -341,21 +375,25 @@ def samba_de_janeiro():
def jazz_waltz():
"""Jazz waltz in F major — 3/4 time."""
"""Jazz waltz in F major — 3/4 with brushes and space."""
print(" Jazz Waltz in F major")
print(" Drums: waltz | Lead: triangle | Bass: sine")
print(" waltz drums | triangle lead + reverb + delay | FM rhodes")
score = Score("3/4", bpm=150)
score.add_pattern(Pattern.preset("waltz"), repeats=16)
score.drums("waltz", repeats=16)
chords = score.part("chords", synth="sine", envelope="pad", volume=0.35)
lead = score.part("lead", synth="triangle", envelope="pluck", volume=0.45)
bass = score.part("bass", synth="sine", envelope="pluck", volume=0.4)
rhodes = score.part("rhodes", synth="fm", envelope="piano",
volume=0.3, reverb=0.4, reverb_decay=2.0)
lead = score.part("lead", synth="triangle", envelope="strings",
volume=0.4, reverb=0.3, reverb_decay=1.5,
delay=0.2, delay_time=0.4, delay_feedback=0.3)
bass = score.part("bass", synth="sine", envelope="pluck",
volume=0.4, lowpass=600)
for _ in range(2):
for sym in ["Fmaj7", "Gm", "C7", "Fmaj7"]:
for _ in range(4):
chords.add(Chord.from_symbol(sym), Duration.DOTTED_HALF)
rhodes.add(Chord.from_symbol(sym), Duration.DOTTED_HALF)
for n, d in [
("A5",1.5),("G5",.5),("F5",1),("E5",1),("C5",1),("F5",1),
@@ -378,19 +416,25 @@ def jazz_waltz():
def house_anthem():
"""House in C minor — four-on-the-floor with acid lead."""
print(" House Anthem in C minor")
print(" Drums: house | Lead: sawtooth (acid) | Bass: sine")
print(" house drums + fill | saw acid lead + LP 2kHz Q=2 + delay")
print(" supersaw pads + reverb | sine bass")
score = Score("4/4", bpm=124)
score.add_pattern(Pattern.preset("house"), repeats=8)
score.drums("house", repeats=8, fill="house", fill_every=8)
chords = score.part("chords", synth="sine", envelope="pad", volume=0.35)
lead = score.part("lead", synth="saw", envelope="staccato", volume=0.4)
bass = score.part("bass", synth="sine", envelope="pluck", volume=0.5)
pads = score.part("pads", synth="supersaw", envelope="pad",
volume=0.25, reverb=0.5, reverb_decay=2.5,
lowpass=5000)
lead = score.part("lead", synth="saw", envelope="staccato",
volume=0.35, lowpass=2000, lowpass_q=2.0,
delay=0.2, delay_time=0.242,
delay_feedback=0.35)
bass = score.part("bass", synth="sine", envelope="pluck",
volume=0.55, lowpass=300)
for sym in ["Cm", "Ab", "Bb", "Cm"] * 2:
chords.add(Chord.from_symbol(sym), Duration.WHOLE)
pads.add(Chord.from_symbol(sym), Duration.WHOLE)
# Acid arpeggios
for n, d in [
("C5",.25),("Eb5",.25),("G5",.25),("C5",.25),
("Eb5",.25),("G5",.25),("C5",.25),("Eb5",.25),
@@ -418,7 +462,6 @@ def house_anthem():
]:
lead.rest(d) if n is None else lead.add(n, d)
# Pumping bass
for n in ["C2","C2","C2","C2","Ab1","Ab1","Ab1","Ab1",
"Bb1","Bb1","Bb1","Bb1","C2","C2","C2","C2"] * 2:
bass.add(n, Duration.QUARTER)
@@ -426,6 +469,133 @@ def house_anthem():
play_song(score)
def dub_kingston():
"""Dub in A minor — deep, sparse, drenched in effects."""
print(" Kingston After Dark")
print(" dub drums | triangle melodica + delay + reverb")
print(" square skank + reverb + LP 1.5kHz | sine bass + LP 400Hz")
print(" PWM siren + reverb + LP 1.2kHz")
score = Score("4/4", bpm=72)
score.drums("dub", repeats=8)
chords = score.part("chords", synth="square", envelope="staccato",
volume=0.2, reverb=0.6, reverb_decay=2.5,
lowpass=1500, lowpass_q=0.9)
lead = score.part("lead", synth="triangle", envelope="strings",
volume=0.4, delay=0.45, delay_time=0.625,
delay_feedback=0.5, reverb=0.35, reverb_decay=2.0)
bass = score.part("bass", synth="sine", envelope="pluck",
volume=0.6, lowpass=400, lowpass_q=1.5)
siren = score.part("siren", synth="pwm_slow", envelope="pad",
volume=0.15, reverb=0.7, reverb_decay=3.0,
lowpass=1200)
for sym in ["Am", "Am", "Dm", "Dm", "Am", "Am", "Em", "Am"]:
chords.add(Chord.from_symbol(sym), Duration.WHOLE)
for n, d in [
("A4", 2), (None, 2), ("C5", 1.5), (None, 2.5),
("D5", 1), ("C5", 1), ("A4", 2), (None, 4),
("E5", 2), (None, 2), ("D5", 1.5), ("C5", 1.5), (None, 3),
("A4", 1), ("G4", 1), ("A4", 3), (None, 3),
]:
lead.rest(d) if n is None else lead.add(n, d)
for n in ["A1","A1","A1","A1","D1","D1","D1","D1",
"A1","A1","A1","A1","E1","E1","A1","A1"]:
bass.add(n, Duration.HALF)
for n, d in [
(None, 8), ("A5", 4), (None, 4),
(None, 4), ("E5", 3), (None, 5),
("D5", 4), (None, 4),
]:
siren.rest(d) if n is None else siren.add(n, d)
play_song(score)
def techno_minimal():
"""Minimal techno in F minor — hypnotic and relentless."""
print(" Minimal Techno in F minor")
print(" techno drums | PWM fast lead + LP 1.5kHz Q=3 + delay")
print(" supersaw pad + reverb | sine sub bass")
score = Score("4/4", bpm=130)
score.drums("techno", repeats=8, fill="house", fill_every=8)
pad = score.part("pad", synth="supersaw", envelope="pad",
volume=0.2, reverb=0.5, reverb_decay=3.0,
lowpass=3000)
lead = score.part("lead", synth="pwm_fast", envelope="staccato",
volume=0.35, lowpass=1500, lowpass_q=3.0,
delay=0.3, delay_time=0.231,
delay_feedback=0.4)
bass = score.part("bass", synth="sine", envelope="pluck",
volume=0.55, lowpass=250)
for sym in ["Fm", "Db", "Eb", "Fm"] * 2:
pad.add(Chord.from_symbol(sym), Duration.WHOLE)
# Minimal arpeggiated sequence — hypnotic repetition
seq = [("F4",.25),("Ab4",.25),("C5",.25),("F4",.25),
("Ab4",.25),("C5",.25),("Eb5",.25),("C5",.25)]
for n, d in seq * 8:
lead.add(n, d)
for n in ["F1","F1","F1","F1","Db1","Db1","Db1","Db1",
"Eb1","Eb1","Eb1","Eb1","F1","F1","F1","F1"] * 2:
bass.add(n, Duration.QUARTER)
play_song(score)
def gospel_shuffle():
"""Gospel in C major — joyful shuffle with FM organ."""
print(" Gospel Shuffle in C major")
print(" gospel drums + fill | FM organ + reverb | triangle lead + delay")
score = Score("4/4", bpm=108)
score.drums("gospel", repeats=8, fill="buildup", fill_every=8)
organ = score.part("organ", synth="fm", envelope="organ",
volume=0.3, reverb=0.45, reverb_decay=2.0)
lead = score.part("lead", synth="triangle", envelope="pluck",
volume=0.4, delay=0.2, delay_time=0.278,
delay_feedback=0.3, reverb=0.2)
bass = score.part("bass", synth="sine", envelope="pluck",
volume=0.45, lowpass=500)
for sym in ["C", "Am", "F", "G"] * 2:
organ.add(Chord.from_symbol(sym), Duration.WHOLE)
for n, d in [
("E5",.67),("G5",.33),("C6",1),("B5",.67),("A5",.33),
("G5",1),(None,1),
("A5",.67),("C6",.33),("E5",1),("D5",.67),("C5",.33),
("A4",1),(None,1),
("F5",.67),("A5",.33),("C6",1),("B5",.67),("A5",.33),
("G5",.67),("E5",.33),("C5",1),(None,1),
("D5",.67),("E5",.33),("G5",.67),("B5",.33),
("C6",2),(None,2),
# Second half: more intense
("C6",.67),("B5",.33),("A5",.67),("G5",.33),
("E5",1),("C5",.67),("E5",.33),
("A5",1),("G5",.67),("E5",.33),("C5",1),(None,1),
("F5",1),("A5",.67),("C6",.33),("E6",2),
("D6",.67),("C6",.33),("B5",.67),("G5",.33),
("C6",3),(None,1),
]:
lead.rest(d) if n is None else lead.add(n, d)
for n in ["C2","E2","G2","C3","A1","C2","E2","A2",
"F2","A2","C3","F2","G2","B2","D3","G2"] * 2:
bass.add(n, Duration.QUARTER)
play_song(score)
# ── Main ───────────────────────────────────────────────────────────────────
SONGS = {
@@ -439,6 +609,9 @@ SONGS = {
"8": ("Samba in G major", samba_de_janeiro),
"9": ("Jazz Waltz in F major", jazz_waltz),
"10": ("House Anthem in C minor", house_anthem),
"11": ("Kingston After Dark (Dub)", dub_kingston),
"12": ("Minimal Techno in F minor", techno_minimal),
"13": ("Gospel Shuffle in C major", gospel_shuffle),
}
if __name__ == "__main__":
@@ -452,7 +625,7 @@ if __name__ == "__main__":
print(f" {key:>2}. {name}")
print()
choice = input(" Pick a song (1-10, or 'all'): ").strip()
choice = input(" Pick a song (1-13, or 'all'): ").strip()
print()
if choice == "all":