Add Music Box Factory (20) + Cathedral (21)

Music Box Factory: G major, 108 BPM — 8 tuned percussion instruments.
Kalimba, vibraphone, celesta, marimba, glockenspiel, xylophone,
crotales, timpani. No synths, no strings.

Cathedral: D minor, 60 BPM — tubular bells in taj_mahal, bagpipe
drone, mellotron choir, timpani thunder, pipe organ, kick in
cathedral reverb. Mellotron choir solo at the heart.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-03 00:43:18 -04:00
parent 1abd7f7d1a
commit a9dbff25d3
5 changed files with 766 additions and 1 deletions
+2
View File
@@ -3,6 +3,8 @@
## 2026-04-03
- **Track 18: Tape Memory** — Db minor, 90 BPM. Mellotron flute, FM bells, drift oscillator, crotales, granular texture, hard_sync bass, PWM lead, wavefold, ring_mod. Theremin solo at peak. Singing bowls + tingsha. Everything pytheory 0.40.9 can do.
- **Track 21: Cathedral** — D minor, 60 BPM. Tubular bells (taj_mahal 0.85), bagpipe, mellotron choir, timpani, pipe organ, mellotron strings, kick in cathedral reverb.
- **Track 20: Music Box Factory** — G major, 108 BPM. Eight tuned percussion instruments stacking. Kalimba, vibraphone, celesta, marimba, glockenspiel, xylophone, crotales, timpani. Tubular bells.
- **Track 19: Emergence** — E minor, 100 BPM. Singing bowls + tingsha opening, didgeridoo, mellotron flute, sitar 16th arps building to 32nd shreds, synths emerge at bar 33, both worlds collide, mellotron solo, bowls alone ending.
- Waveforms: extended to 3:16 with FM solo, saw/square duet, sine/triangle/PWM canon, finale. Added FM, PWM, wavefold synths.
- play.py: track picker loops back after playback, extracted _play_track()
+3 -1
View File
@@ -35,7 +35,9 @@ Each track is a `.py` file. Run it to hear it.
16. **Intrusive** — Bb minor, 92 BPM. One saw synth phrase repeating. Rhodes tries to play something else. Drums try to drown it. Stop fighting — acceptance, sub bass, cello. It passes.
17. **Waveforms** — F minor, 118 BPM. Pure synthesis showcase — percussive blips stacking, FM solo, saw/square duet in thirds, sine/triangle/PWM canon.
18. **Tape Memory** — Db minor, 90 BPM. Mellotron flute dreams surrounded by new synthesis. FM bells, drift oscillator, crotales, granular texture, hard_sync bass, PWM lead, wavefold, ring_mod. Theremin solo at the peak.
19. **Emergence** — E minor, 100 BPM. The acoustic world births the electronic one. Singing bowls, tingsha, didgeridoo, mellotron flute, sitar 16th arps with 32nd shreds then synths emerge. Saw, FM, square, supersaw. Both worlds collide at the peak. Bowls alone at the end.
19. **Emergence** — E minor, 100 BPM. Acoustic births electronic. Singing bowls, tingsha, didgeridoo, mellotron flute, sitar 16th arps with 32nd shreds, then synths emerge. Both worlds collide at the peak.
20. **Music Box Factory** — G major, 108 BPM. Eight tuned percussion instruments only. Kalimba, vibraphone, celesta, marimba, glockenspiel, xylophone, crotales, timpani. Tubular bells mark sections. No synths, no strings — just metal and wood.
21. **Cathedral** — D minor, 60 BPM. Ancient stone. Tubular bells in taj_mahal reverb, bagpipe drone, mellotron choir, timpani thunder with 16th note rolls, pipe organ hymn, kick in cathedral reverb. Mellotron choir solo. Bells alone at the end.
## Usage
+2
View File
@@ -58,6 +58,8 @@ ALBUM_ORDER = [
"waveforms.py",
"tape_memory.py",
"emergence.py",
"music_box_factory.py",
"cathedral.py",
]
+335
View File
@@ -0,0 +1,335 @@
"""
CATHEDRAL — ancient stone, heavy air.
Bagpipe drone, timpani thunder, mellotron choir, tubular bells.
The sound of a building that has stood for a thousand years.
D minor, 60 BPM.
"""
from pytheory import Key, Duration, Score, Tone, play_score
from pytheory.rhythm import DrumSound
key = Key("D", "minor")
s = key.scale # D E F G A Bb C
D = s[0]; E = s[1]; F = s[2]; G = s[3]
A = s[4]; Bb = s[5]; C = s[6]
score = Score("4/4", bpm=60)
prog = key.progression("i", "iv", "VII", "i")
prog2 = key.progression("i", "VI", "iv", "V")
# ═══════════════════════════════════════════════════════════════════
# STRUCTURE (64 bars, ~4:16):
# Bars 1-8: Tubular bells alone — echoing in empty stone
# Bars 9-16: Bagpipe drone — the ancient breath
# Bars 17-24: Mellotron choir enters — voices from the walls
# Bars 25-32: Timpani — the heartbeat of the building
# Bars 33-40: Pipe organ — the full weight of God
# Bars 41-48: All together — the cathedral sings
# Bars 49-56: Mellotron choir solo — the most human moment
# Bars 57-64: Bells alone again — the echo outlasts us all
# ═══════════════════════════════════════════════════════════════════
# ── TUBULAR BELLS — the space itself ──────────────────────────
bells = score.part("bells", instrument="tubular_bells", volume=0.4,
reverb=0.85, reverb_type="taj_mahal",
delay=0.3, delay_time=1.0, delay_feedback=0.35,
pan=0.15)
# Bars 1-8: alone — one strike, let it ring, another
bells.add(D.add(-12), Duration.WHOLE, velocity=65)
bells.rest(Duration.WHOLE)
bells.rest(Duration.WHOLE)
bells.add(A.add(-12), Duration.WHOLE, velocity=58)
bells.rest(Duration.WHOLE)
bells.rest(Duration.WHOLE)
bells.add(D.add(-12), Duration.WHOLE, velocity=62)
bells.rest(Duration.WHOLE)
# Bars 9-56: every 4 bars
for section in range(12):
vel = max(30, 60 - section * 2)
bells.add(D.add(-12), Duration.WHOLE, velocity=vel)
bells.rest(Duration.WHOLE)
bells.rest(Duration.WHOLE)
bells.rest(Duration.WHOLE)
# Bars 57-64: alone again — the ending mirrors the beginning
bells.add(D.add(-12), Duration.WHOLE, velocity=58)
bells.rest(Duration.WHOLE)
bells.rest(Duration.WHOLE)
bells.add(A.add(-12), Duration.WHOLE, velocity=50)
bells.rest(Duration.WHOLE)
bells.rest(Duration.WHOLE)
bells.add(D.add(-12), Duration.WHOLE, velocity=42)
bells.rest(Duration.WHOLE)
# ── BAGPIPE — the ancient drone, enters bar 9 ────────────────
bagpipe = score.part("bagpipe", instrument="bagpipe", volume=0.2,
reverb=0.45, reverb_type="cathedral",
chorus=0.15, chorus_rate=0.08, chorus_depth=0.008,
pan=-0.2, humanize=0.06)
for _ in range(8):
bagpipe.rest(Duration.WHOLE)
# Bars 9-16: drone enters — D and A, the ancient fifth
for vel in [25, 32, 38, 42, 45, 45, 42, 40]:
bagpipe.add(D.add(-12), Duration.HALF, velocity=vel)
bagpipe.add(A.add(-12), Duration.HALF, velocity=max(15, vel - 8))
# Bars 17-40: melody emerges from the drone
bagpipe_melody = [
(D, Duration.HALF, 52), (E, Duration.QUARTER, 48),
(F, Duration.QUARTER, 50),
(G, Duration.HALF, 55), (F, Duration.QUARTER, 48),
(E, Duration.QUARTER, 45),
(D, Duration.WHOLE, 52),
(None, Duration.HALF, 0), (A.add(-12), Duration.HALF, 48),
]
for _ in range(6):
for note, dur, vel in bagpipe_melody:
if note is None:
bagpipe.rest(dur)
else:
bagpipe.add(note, dur, velocity=vel)
# Bars 41-48: full power — drone + melody together
bagpipe.set(volume=0.25)
for _ in range(2):
for note, dur, vel in bagpipe_melody:
if note is None:
bagpipe.rest(dur)
else:
bagpipe.add(note, dur, velocity=min(70, vel + 8))
# Bars 49-56: drops to drone — choir takes over
bagpipe.set(volume=0.15)
for _ in range(8):
bagpipe.add(D.add(-12), Duration.HALF, velocity=38)
bagpipe.add(A.add(-12), Duration.HALF, velocity=30)
# Bars 57-64: fading
for vel in [32, 28, 22, 18, 14, 10, 5, 0]:
if vel > 0:
bagpipe.add(D.add(-12), Duration.WHOLE, velocity=vel)
else:
bagpipe.rest(Duration.WHOLE)
# ── MELLOTRON CHOIR — voices from the stone, enters bar 17 ────
choir = score.part("choir", instrument="mellotron_choir", volume=0.2,
reverb=0.55, reverb_type="cathedral",
chorus=0.2, chorus_rate=0.06, chorus_depth=0.01,
pan=0.1, humanize=0.08)
for _ in range(16):
choir.rest(Duration.WHOLE)
# Bars 17-24: slow chords — the walls start singing
for _ in range(2):
for chord in prog:
choir.add(chord, Duration.WHOLE, velocity=40)
# Bars 25-40: fuller
for _ in range(4):
for chord in prog:
choir.add(chord, Duration.WHOLE, velocity=48)
# Bars 41-48: peak — full voice
choir.set(volume=0.28)
for _ in range(2):
for chord in prog2:
choir.add(chord, Duration.WHOLE, velocity=58)
# Bars 49-56: SOLO — the most human moment
choir.set(volume=0.32)
choir_melody = [
(A, Duration.HALF, 62), (Bb, Duration.QUARTER, 58),
(A, Duration.QUARTER, 55),
(G, Duration.HALF, 60), (F, Duration.QUARTER, 55),
(E, Duration.QUARTER, 52),
(F, Duration.DOTTED_HALF, 62), (E, Duration.QUARTER, 55),
(D, Duration.WHOLE, 58),
(F, Duration.QUARTER, 60), (G, Duration.QUARTER, 58),
(A, Duration.HALF, 65),
(Bb, Duration.QUARTER, 60), (A, Duration.QUARTER, 58),
(G, Duration.HALF, 55),
(F, Duration.HALF, 52), (E, Duration.HALF, 55),
(D, Duration.WHOLE, 58),
]
for note, dur, vel in choir_melody:
choir.add(note, dur, velocity=vel)
# Bars 57-64: fading — the voices retreat into stone
for vel in [48, 42, 35, 28, 22, 15, 10, 0]:
if vel > 0:
choir.add(prog[0], Duration.WHOLE, velocity=vel)
else:
choir.rest(Duration.WHOLE)
# ── TIMPANI — thunder, enters bar 25 ──────────────────────────
timp = score.part("timpani", instrument="timpani", volume=0.5,
reverb=0.4, reverb_type="cathedral",
delay=0.08, delay_time=0.5, delay_feedback=0.1,
pan=-0.1, humanize=0.06)
for _ in range(24):
timp.rest(Duration.WHOLE)
# Bars 25-32: sparse, heavy — like thunder in stone
timp.add(D.add(-12), Duration.WHOLE, velocity=75)
timp.rest(Duration.WHOLE)
timp.rest(Duration.WHOLE)
timp.add(A.add(-24), Duration.WHOLE, velocity=68)
timp.rest(Duration.WHOLE)
timp.add(D.add(-12), Duration.HALF, velocity=72)
timp.add(D.add(-12), Duration.HALF, velocity=65)
timp.rest(Duration.WHOLE)
timp.add(D.add(-12), Duration.WHOLE, velocity=78)
# Bars 33-40: more active — rolls
for bar in range(8):
if bar % 4 == 3:
# Timpani roll — 16th notes crescendo
for i in range(16):
timp.add(D.add(-12), Duration.SIXTEENTH, velocity=min(95, 50 + i * 3))
else:
timp.add(D.add(-12), Duration.QUARTER, velocity=72)
timp.rest(Duration.DOTTED_HALF)
# Bars 41-48: peak — full rolls + accents
for bar in range(8):
if bar % 2 == 1:
for i in range(16):
timp.add(D.add(-12), Duration.SIXTEENTH, velocity=min(100, 55 + i * 3))
else:
timp.add(D.add(-12), Duration.HALF, velocity=80)
timp.rest(Duration.QUARTER)
timp.add(A.add(-24), Duration.QUARTER, velocity=72)
# Bars 49-56: sparse again — under choir solo
for _ in range(4):
timp.add(D.add(-12), Duration.WHOLE, velocity=55)
timp.rest(Duration.WHOLE)
# Bars 57-64: fading
for vel in [48, 40, 32, 25, 0, 0, 0, 0]:
if vel > 0:
timp.add(D.add(-12), Duration.WHOLE, velocity=vel)
else:
timp.rest(Duration.WHOLE)
# ── KICK — the deepest thunder, enters bar 25 ─────────────────
K = DrumSound.KICK
kick = score.part("kick", volume=0.7, humanize=0.03,
reverb=0.3, reverb_type="cathedral",
distortion=0.1, distortion_drive=2.0)
for _ in range(24):
kick.rest(Duration.WHOLE)
# Bars 25-32: one hit per bar — synced with timpani
for _ in range(8):
kick.hit(K, Duration.QUARTER, velocity=95)
kick.rest(Duration.DOTTED_HALF)
# Bars 33-48: two hits per bar — heartbeat under the organ
for _ in range(16):
kick.hit(K, Duration.QUARTER, velocity=100)
kick.rest(Duration.QUARTER)
kick.hit(K, Duration.QUARTER, velocity=85)
kick.rest(Duration.QUARTER)
# Bars 49-56: sparse — under choir solo
for _ in range(8):
kick.hit(K, Duration.HALF, velocity=80)
kick.rest(Duration.HALF)
# Bars 57-64: fading
for vel in [72, 60, 48, 35, 22, 0, 0, 0]:
if vel > 0:
kick.hit(K, Duration.QUARTER, velocity=vel)
kick.rest(Duration.DOTTED_HALF)
else:
kick.rest(Duration.WHOLE)
# ── PIPE ORGAN — the full weight, enters bar 33 ──────────────
organ = score.part("organ", instrument="pipe_organ", volume=0.2,
reverb=0.5, reverb_type="cathedral",
chorus=0.1, chorus_rate=0.1, chorus_depth=0.005,
pan=0.05)
for _ in range(32):
organ.rest(Duration.WHOLE)
# Bars 33-48: hymn chords — the weight of the building
for _ in range(4):
for chord in prog:
organ.add(chord, Duration.WHOLE, velocity=48)
# Bars 49-56: sustains under choir solo
organ.set(volume=0.15)
for _ in range(2):
for chord in prog:
organ.add(chord, Duration.WHOLE, velocity=38)
# Bars 57-64: fading
for vel in [35, 30, 25, 20, 15, 10, 5, 0]:
if vel > 0:
organ.add(prog[0], Duration.WHOLE, velocity=vel)
else:
organ.rest(Duration.WHOLE)
# ── MELLOTRON STRINGS — bed, enters bar 33 ────────────────────
strings = score.part("strings", instrument="mellotron_strings", volume=0.12,
reverb=0.45, reverb_type="cathedral",
pan=-0.15)
for _ in range(32):
strings.rest(Duration.WHOLE)
# Bars 33-56: tape strings — the warmth
for _ in range(6):
for chord in prog:
strings.add(chord, Duration.WHOLE, velocity=42)
# Bars 57-64: fading
for vel in [35, 28, 22, 18, 12, 8, 0, 0]:
if vel > 0:
strings.add(prog[0], Duration.WHOLE, velocity=vel)
else:
strings.rest(Duration.WHOLE)
# ── SUB — the stone floor vibrating ──────────────────────────
sub = score.part("sub", synth="sine", envelope="pad", volume=0.3,
lowpass=100, sub_osc=0.3)
for _ in range(24):
sub.rest(Duration.WHOLE)
for _ in range(32):
sub.add(D.add(-36), Duration.WHOLE, velocity=40)
for vel in [35, 28, 22, 15, 10, 5, 0, 0]:
if vel > 0:
sub.add(D.add(-36), Duration.WHOLE, velocity=vel)
else:
sub.rest(Duration.WHOLE)
# ═════════════════════════════════════════════════════════════════
import sys
print(f"Key: {key}")
print(f"BPM: 60")
print(f"Parts: {list(score.parts.keys())}")
print(f"Duration: {score.duration_ms / 1000:.1f}s | {score.measures} measures")
if "--live" in sys.argv:
print("Playing CATHEDRAL (live engine)...")
from pytheory_live.live import LiveEngine
engine = LiveEngine(buffer_size=1024)
engine.play_score(score)
else:
print("Playing CATHEDRAL...")
play_score(score)
+424
View File
@@ -0,0 +1,424 @@
"""
MUSIC BOX FACTORY — tuned percussion only.
Kalimba, vibraphone, celesta, marimba, glockenspiel, xylophone, crotales.
No synths. No strings. Just metal and wood and keys.
G major, 108 BPM.
"""
from pytheory import Key, Duration, Score, Tone, play_score
from pytheory.rhythm import DrumSound
key = Key("G", "major")
s = key.scale # G A B C D E F#
G = s[0]; A = s[1]; B = s[2]; C = s[3]
D = s[4]; E = s[5]; Fs = s[6]
score = Score("4/4", bpm=108)
prog = key.progression("I", "vi", "IV", "V")
# ═══════════════════════════════════════════════════════════════════
# STRUCTURE (72 bars, ~4:00):
# Bars 1-8: Kalimba alone — thumb piano, intimate
# Bars 9-16: Vibraphone joins — jazz shimmer, motor wobble
# Bars 17-24: Celesta — ethereal, high, Tchaikovsky's ghost
# Bars 25-32: Marimba — warm wood, the low end
# Bars 33-40: Glockenspiel — bright, cutting through
# Bars 41-48: All together — the factory floor
# Bars 49-56: Xylophone + crotales — the brightest moment
# Bars 57-64: Kalimba melody reprise — over everything
# Bars 65-72: Winding down — one by one they stop
# ═══════════════════════════════════════════════════════════════════
# ── KALIMBA — the seed, thumb piano ───────────────────────────
kalimba = score.part("kalimba", instrument="kalimba", volume=0.45,
reverb=0.25, reverb_type="taj_mahal",
delay=0.15, delay_time=0.278, delay_feedback=0.2,
pan=-0.15, humanize=0.1)
# Bars 1-8: alone — simple, percussive, melodic
kalimba_phrase_a = [
(G, Duration.EIGHTH, 72), (None, Duration.EIGHTH, 0),
(B, Duration.EIGHTH, 65), (D, Duration.EIGHTH, 68),
(None, Duration.EIGHTH, 0), (B, Duration.EIGHTH, 62),
(G, Duration.EIGHTH, 70), (None, Duration.EIGHTH, 0),
]
kalimba_phrase_b = [
(E, Duration.EIGHTH, 68), (None, Duration.EIGHTH, 0),
(G, Duration.EIGHTH, 65), (B, Duration.EIGHTH, 70),
(A, Duration.EIGHTH, 62), (None, Duration.EIGHTH, 0),
(G, Duration.EIGHTH, 68), (None, Duration.EIGHTH, 0),
]
for _ in range(4):
for note, dur, vel in kalimba_phrase_a:
if note is None:
kalimba.rest(dur)
else:
kalimba.add(note, dur, velocity=vel)
for note, dur, vel in kalimba_phrase_b:
if note is None:
kalimba.rest(dur)
else:
kalimba.add(note, dur, velocity=vel)
# Bars 9-56: continues — the constant heartbeat
for _ in range(24):
for note, dur, vel in kalimba_phrase_a:
if note is None:
kalimba.rest(dur)
else:
kalimba.add(note, dur, velocity=vel)
for note, dur, vel in kalimba_phrase_b:
if note is None:
kalimba.rest(dur)
else:
kalimba.add(note, dur, velocity=vel)
# Bars 57-64: REPRISE — melody sings above everything
kalimba.set(volume=0.55)
kalimba_melody = [
(D, Duration.QUARTER, 78), (E, Duration.EIGHTH, 72),
(D, Duration.EIGHTH, 68), (B, Duration.HALF, 75),
(A, Duration.QUARTER, 70), (G, Duration.EIGHTH, 65),
(Fs, Duration.EIGHTH, 62), (G, Duration.HALF, 72),
(B, Duration.QUARTER, 75), (D, Duration.QUARTER, 72),
(E, Duration.HALF, 78),
(D, Duration.QUARTER, 72), (B, Duration.QUARTER, 68),
(A, Duration.QUARTER, 65), (G, Duration.QUARTER, 70),
(G, Duration.WHOLE, 72),
(None, Duration.WHOLE, 0),
]
for note, dur, vel in kalimba_melody:
if note is None:
kalimba.rest(dur)
else:
kalimba.add(note, dur, velocity=vel)
# Bars 65-72: last one playing — fading
kalimba.set(volume=0.4)
for rep in range(4):
off = rep * -12
for note, dur, vel in kalimba_phrase_a:
if note is None:
kalimba.rest(dur)
else:
kalimba.add(note, dur, velocity=max(20, vel + off))
for note, dur, vel in kalimba_phrase_b:
if note is None:
kalimba.rest(dur)
else:
kalimba.add(note, dur, velocity=max(20, vel + off))
# ── VIBRAPHONE — jazz shimmer, enters bar 9 ──────────────────
vib = score.part("vibraphone", instrument="vibraphone", volume=0.35,
reverb=0.3, reverb_type="cathedral",
delay=0.12, delay_time=0.556, delay_feedback=0.2,
pan=0.25, humanize=0.08)
for _ in range(8):
vib.rest(Duration.WHOLE)
# Bars 9-16: sustained chords — the motor wobble gives life
for _ in range(2):
for chord in prog:
vib.add(chord, Duration.WHOLE, velocity=55)
# Bars 17-40: continues — warm bed under everything
for _ in range(6):
for chord in prog:
vib.add(chord, Duration.WHOLE, velocity=52)
# Bars 41-48: arpeggiated — the factory speeds up
vib_arp = [G, B, D, B, G, D.add(-12), B.add(-12), D.add(-12)]
for _ in range(8):
for note in vib_arp:
vib.add(note, Duration.EIGHTH, velocity=60)
# Bars 49-56: peak chords
for _ in range(2):
for chord in prog:
vib.add(chord, Duration.WHOLE, velocity=62)
# Bars 57-64: under kalimba melody
for _ in range(2):
for chord in prog:
vib.add(chord, Duration.WHOLE, velocity=50)
# Bars 65-72: fading
for vel in [45, 40, 35, 30, 25, 18, 10, 0]:
if vel > 0:
vib.add(prog[0], Duration.WHOLE, velocity=vel)
else:
vib.rest(Duration.WHOLE)
# ── CELESTA — ethereal, enters bar 17 ────────────────────────
celesta = score.part("celesta", instrument="celesta", volume=0.3,
reverb=0.35, reverb_type="taj_mahal",
delay=0.15, delay_time=0.278, delay_feedback=0.25,
pan=-0.3, humanize=0.08)
for _ in range(16):
celesta.rest(Duration.WHOLE)
# Bars 17-24: high, sparkling countermelody
celesta_phrase = [
(None, Duration.QUARTER, 0),
(D, Duration.EIGHTH, 62), (E, Duration.EIGHTH, 58),
(None, Duration.QUARTER, 0),
(B, Duration.EIGHTH, 60), (None, Duration.EIGHTH, 0),
(None, Duration.QUARTER, 0),
(E, Duration.EIGHTH, 62), (D, Duration.EIGHTH, 58),
(B, Duration.EIGHTH, 55), (None, Duration.EIGHTH, 0),
(None, Duration.QUARTER, 0),
]
for _ in range(8):
for note, dur, vel in celesta_phrase:
if note is None:
celesta.rest(dur)
else:
celesta.add(note, dur, velocity=vel)
# Bars 25-56: continues
for _ in range(32):
for note, dur, vel in celesta_phrase:
if note is None:
celesta.rest(dur)
else:
celesta.add(note, dur, velocity=vel)
# Bars 57-72: fading
for rep in range(8):
off = rep * -6
for note, dur, vel in celesta_phrase:
if note is None:
celesta.rest(dur)
else:
celesta.add(note, dur, velocity=max(15, vel + off))
for _ in range(8):
celesta.rest(Duration.WHOLE)
# ── MARIMBA — warm wood, the bass, enters bar 25 ─────────────
marimba = score.part("marimba", instrument="marimba", volume=0.4,
reverb=0.2, reverb_decay=1.0,
delay=0.08, delay_time=0.278, delay_feedback=0.1,
pan=0.1, humanize=0.08)
for _ in range(24):
marimba.rest(Duration.WHOLE)
# Bars 25-32: low register — the warm bass of the ensemble
marimba_bass = [
(G.add(-12), Duration.QUARTER, 72), (None, Duration.EIGHTH, 0),
(G.add(-12), Duration.EIGHTH, 62), (B.add(-12), Duration.QUARTER, 68),
(None, Duration.QUARTER, 0),
(D, Duration.QUARTER, 65), (None, Duration.EIGHTH, 0),
(B.add(-12), Duration.EIGHTH, 60), (G.add(-12), Duration.HALF, 70),
]
for _ in range(8):
for note, dur, vel in marimba_bass:
if note is None:
marimba.rest(dur)
else:
marimba.add(note, dur, velocity=vel)
# Bars 33-56: continues — the floor
for _ in range(24):
for note, dur, vel in marimba_bass:
if note is None:
marimba.rest(dur)
else:
marimba.add(note, dur, velocity=vel)
# Bars 57-72: fading
for rep in range(8):
off = rep * -8
for note, dur, vel in marimba_bass:
if note is None:
marimba.rest(dur)
else:
marimba.add(note, dur, velocity=max(20, vel + off))
for _ in range(8):
marimba.rest(Duration.WHOLE)
# ── GLOCKENSPIEL — bright, cutting, enters bar 33 ────────────
glock = score.part("glockenspiel", instrument="glockenspiel", volume=0.2,
reverb=0.3, reverb_type="cathedral",
delay=0.12, delay_time=0.139, delay_feedback=0.15,
pan=-0.4, humanize=0.06)
for _ in range(32):
glock.rest(Duration.WHOLE)
# Bars 33-40: high bright hits — sparse, like light catching metal
glock_hits = [
(D, Duration.QUARTER, 58), (None, Duration.DOTTED_HALF, 0),
(None, Duration.HALF, 0), (E, Duration.QUARTER, 55),
(None, Duration.QUARTER, 0),
(B, Duration.QUARTER, 60), (None, Duration.DOTTED_HALF, 0),
(None, Duration.WHOLE, 0),
(D, Duration.QUARTER, 55), (None, Duration.QUARTER, 0),
(G, Duration.QUARTER, 58), (None, Duration.QUARTER, 0),
(None, Duration.WHOLE, 0),
(None, Duration.HALF, 0), (Fs, Duration.QUARTER, 52),
(None, Duration.QUARTER, 0),
(None, Duration.WHOLE, 0),
]
for note, dur, vel in glock_hits:
if note is None:
glock.rest(dur)
else:
glock.add(note, dur, velocity=vel)
# Bars 41-56: more active — 16th note runs
glock_run = [G, A, B, D, B, A, G, Fs, G, B, D, E, D, B, A, G]
for _ in range(4):
for note in glock_run:
glock.add(note, Duration.SIXTEENTH, velocity=55)
for _ in range(4):
for note, dur, vel in glock_hits:
if note is None:
glock.rest(dur)
else:
glock.add(note, dur, velocity=vel)
for _ in range(4):
for note in glock_run:
glock.add(note, Duration.SIXTEENTH, velocity=58)
# Bars 57-72: fading runs
for _ in range(4):
for note in glock_run:
glock.add(note, Duration.SIXTEENTH, velocity=max(20, 48))
for _ in range(12):
glock.rest(Duration.WHOLE)
# ── XYLOPHONE — bright wood, enters bar 49 ───────────────────
xylo = score.part("xylophone", instrument="xylophone", volume=0.25,
reverb=0.15, reverb_decay=0.6,
delay=0.1, delay_time=0.139, delay_feedback=0.12,
pan=0.35, humanize=0.06)
for _ in range(48):
xylo.rest(Duration.WHOLE)
# Bars 49-56: rapid arps — woody brightness
xylo_arp_a = [G, B, D, G, D, B, G, D]
xylo_arp_b = [A, C, E, A, E, C, A, E]
for _ in range(4):
for note in xylo_arp_a:
xylo.add(note, Duration.SIXTEENTH, velocity=65)
for note in xylo_arp_b:
xylo.add(note, Duration.SIXTEENTH, velocity=62)
# Bars 57-64: continues under kalimba melody
for _ in range(4):
for note in xylo_arp_a:
xylo.add(note, Duration.SIXTEENTH, velocity=58)
for note in xylo_arp_b:
xylo.add(note, Duration.SIXTEENTH, velocity=55)
# Bars 65-72: fading
for vel in [50, 42, 35, 28, 22, 15, 0, 0]:
if vel > 0:
for note in xylo_arp_a:
xylo.add(note, Duration.SIXTEENTH, velocity=vel)
for note in xylo_arp_b:
xylo.add(note, Duration.SIXTEENTH, velocity=max(15, vel - 5))
else:
xylo.rest(Duration.WHOLE)
# ── CROTALES — crystalline, enters bar 49 ─────────────────────
crot = score.part("crotales", instrument="crotales", volume=0.18,
reverb=0.35, reverb_type="taj_mahal",
delay=0.15, delay_time=0.556, delay_feedback=0.2,
pan=-0.35)
for _ in range(48):
crot.rest(Duration.WHOLE)
# Bars 49-56: sparse strikes — like tiny church bells
crot_map = {49: (D, 52), 51: (G, 48), 53: (B, 50), 55: (E, 45)}
for bar in range(49, 73):
if bar in crot_map:
note, vel = crot_map[bar]
crot.add(note, Duration.WHOLE, velocity=vel)
elif bar > 56 and bar % 3 == 0:
crot.add(D, Duration.WHOLE, velocity=max(20, 45 - (bar - 57) * 2))
else:
crot.rest(Duration.WHOLE)
# ── TUBULAR BELLS — section markers ───────────────────────────
bells = score.part("tubular_bells", instrument="tubular_bells", volume=0.2,
reverb=0.4, reverb_type="cathedral",
delay=0.15, delay_time=0.556, delay_feedback=0.15,
pan=0.15)
bell_bars = {1: 60, 9: 55, 17: 52, 25: 58, 33: 55, 41: 62, 49: 58, 57: 65}
for bar in range(1, 73):
if bar in bell_bars:
bells.add(G.add(-12), Duration.WHOLE, velocity=bell_bars[bar])
else:
bells.rest(Duration.WHOLE)
# ── TIMPANI — the low end, enters bar 25 ──────────────────────
timp = score.part("timpani", instrument="timpani", volume=0.3,
reverb=0.2, reverb_decay=1.0,
delay=0.06, delay_time=0.278, delay_feedback=0.08,
pan=0.05, humanize=0.06)
for _ in range(24):
timp.rest(Duration.WHOLE)
# Bars 25-32: sparse — one hit per bar, like a grandfather clock
for note, vel in [(G.add(-12), 68), (D.add(-12), 62),
(G.add(-12), 65), (None, 0),
(G.add(-12), 70), (B.add(-12), 60),
(G.add(-12), 68), (D.add(-12), 62)]:
if note is None:
timp.rest(Duration.WHOLE)
else:
timp.add(note, Duration.QUARTER, velocity=vel)
timp.rest(Duration.DOTTED_HALF)
# Bars 33-48: more active — rhythmic pulse
for _ in range(16):
timp.add(G.add(-12), Duration.QUARTER, velocity=65)
timp.rest(Duration.QUARTER)
timp.add(D.add(-12), Duration.QUARTER, velocity=58)
timp.rest(Duration.QUARTER)
# Bars 49-56: peak — 8th note rolls
for bar in range(8):
if bar % 4 == 3:
for i in range(16):
timp.add(G.add(-12), Duration.SIXTEENTH, velocity=min(82, 48 + i * 2))
else:
timp.add(G.add(-12), Duration.QUARTER, velocity=68)
timp.rest(Duration.QUARTER)
timp.add(D.add(-12), Duration.QUARTER, velocity=60)
timp.rest(Duration.QUARTER)
# Bars 57-72: fading
for vel in [58, 52, 45, 38, 32, 25, 18, 10, 0, 0, 0, 0, 0, 0, 0, 0]:
if vel > 0:
timp.add(G.add(-12), Duration.QUARTER, velocity=vel)
timp.rest(Duration.DOTTED_HALF)
else:
timp.rest(Duration.WHOLE)
# ═════════════════════════════════════════════════════════════════
import sys
print(f"Key: {key}")
print(f"BPM: 108")
print(f"Parts: {list(score.parts.keys())}")
print(f"Duration: {score.duration_ms / 1000:.1f}s | {score.measures} measures")
if "--live" in sys.argv:
print("Playing MUSIC BOX FACTORY (live engine)...")
from pytheory_live.live import LiveEngine
engine = LiveEngine(buffer_size=1024)
engine.play_score(score)
else:
print("Playing MUSIC BOX FACTORY...")
play_score(score)