Files
kennethreitz 00fc35de19 Refactor all tracks to be more pythonic — audio verified bit-identical
Every track: tuple-unpacked scale degrees, small local helpers
(rest_bars, chord_bars, play_phrase), data-driven drum patterns and
phrase tuples, sparse-event dicts, explicit velocity lists for fades,
dead code removed. Net -1,415 lines across 25 files.

Adds .fingerprint.py, a verification harness that hashes every audible
parameter of a score (notes, voicings, velocities, bends, drum hits,
LFO automation, part settings). All 25 tracks fingerprint identical to
their pre-refactor baselines, stored in .fingerprints/.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 00:32:38 -04:00

574 lines
21 KiB
Python

"""
EMERGENCE — the acoustic world births the electronic one.
Singing bowls, tingsha, didgeridoo, sitar — then the synths arrive.
E minor, 100 BPM.
"""
from pytheory import Key, Duration, Score, play_score
from pytheory.rhythm import DrumSound
key = Key("E", "minor")
s = key.scale # E F# G A B C D
E, Fs, G, A, B, C, D = s[:7]
score = Score("4/4", bpm=100)
K = DrumSound.KICK
S = DrumSound.SNARE
CH = DrumSound.CLOSED_HAT
prog = key.progression("i", "VII", "VI", "iv")
# ═══════════════════════════════════════════════════════════════════
# STRUCTURE (88 bars, ~5:17):
# Bars 1-8: Bowls + tingsha — a room full of ringing metal
# Bars 9-16: Didgeridoo + mellotron flute — the drone bed
# Bars 17-24: Sitar enters — 8ths, then 16th arps building
# Bars 25-32: Sitar 16th arps full + 32nd fills — the swarm
# Bars 33-40: THE SHIFT — synths emerge from the acoustic bed
# Bars 41-48: Synths take over — saw, square, FM, the machine
# Bars 49-56: Both worlds — sitar + synths together
# Bars 57-64: PEAK — everything, 32nd shreds, mellotron chords
# Bars 65-72: Mellotron solo — the bridge between worlds
# Bars 73-80: Unwinding — synths fade, acoustic returns
# Bars 81-88: Bowls alone — where we started, changed
# ═══════════════════════════════════════════════════════════════════
def rest_bars(part, n):
for _ in range(n):
part.rest(Duration.WHOLE)
def play_phrase(part, phrase, shape=None):
for note, dur, vel in phrase:
if note is None:
part.rest(dur)
else:
part.add(note, dur, velocity=shape(vel) if shape else vel)
def chord_bars(part, repeats, vel):
for _ in range(repeats):
for chord in prog:
part.add(chord, Duration.WHOLE, velocity=vel)
# ── SINGING BOWLS — a chorus, mostly dry ───────────────────────
bowl_1 = score.part("bowl_low", instrument="singing_bowl", volume=0.4,
reverb=0.4, reverb_type="taj_mahal",
delay=0.1, delay_time=0.6, delay_feedback=0.15,
pan=-0.35)
bowl_2 = score.part("bowl_mid", instrument="singing_bowl_ring", volume=0.35,
reverb=0.35, reverb_type="taj_mahal",
delay=0.08, delay_time=0.45, delay_feedback=0.12,
pan=0.25)
bowl_3 = score.part("bowl_hi", instrument="singing_bowl_ring", volume=0.3,
reverb=0.3, reverb_type="taj_mahal",
delay=0.06, delay_time=0.3, delay_feedback=0.1,
pan=-0.15)
# Bars 1-8: the room — bowls at different intervals, cascading
# Low bowl: every 2 bars
for _ in range(4):
bowl_1.add(E.add(-24), Duration.WHOLE, velocity=68)
bowl_1.rest(Duration.WHOLE)
# Mid bowl: offset, every 3 bars roughly
bowl2_hits = {2: (B.add(-12), 60), 5: (E.add(-12), 58), 7: (B.add(-12), 55)}
# High bowl: sparse, bright
bowl3_hits = {3: (E, 52), 6: (B, 48)}
for part, hits in [(bowl_2, bowl2_hits), (bowl_3, bowl3_hits)]:
for bar in range(1, 9):
if bar in hits:
note, vel = hits[bar]
part.add(note, Duration.WHOLE, velocity=vel)
else:
part.rest(Duration.WHOLE)
# Bars 9-72: bowls continue sparser
for bar in range(64):
if bar % 8 == 0:
bowl_1.add(E.add(-24), Duration.WHOLE, velocity=max(35, 60 - bar // 4))
else:
bowl_1.rest(Duration.WHOLE)
if bar % 10 == 3:
bowl_2.add(B.add(-12), Duration.WHOLE, velocity=max(30, 52 - bar // 4))
else:
bowl_2.rest(Duration.WHOLE)
if bar % 12 == 5:
bowl_3.add(E, Duration.WHOLE, velocity=max(25, 45 - bar // 4))
else:
bowl_3.rest(Duration.WHOLE)
# Bars 73-80: bowls come back stronger — returning
for vel in [45, 52, 58, 62, 65, 60, 55, 50]:
bowl_1.add(E.add(-24), Duration.WHOLE, velocity=vel)
for _ in range(4):
bowl_2.add(B.add(-12), Duration.WHOLE, velocity=55)
bowl_2.rest(Duration.WHOLE)
for _ in range(4):
bowl_3.rest(Duration.WHOLE)
bowl_3.add(E, Duration.WHOLE, velocity=48)
# Bars 81-88: bowls alone — ending
for vel in [62, 58, 52, 48, 42, 35, 28, 20]:
bowl_1.add(E.add(-24), Duration.WHOLE, velocity=vel)
for vel in [50, 45, 40, 35, 28, 22, 0, 0]:
if vel > 0:
bowl_2.add(B.add(-12), Duration.WHOLE, velocity=vel)
else:
bowl_2.rest(Duration.WHOLE)
for vel in [42, 38, 32, 25, 0, 0, 0, 0]:
if vel > 0:
bowl_3.add(E, Duration.WHOLE, velocity=vel)
else:
bowl_3.rest(Duration.WHOLE)
# ── TINGSHA — crystalline hits, dry and present ────────────────
ting_1 = score.part("tingsha_l", instrument="tingsha", volume=0.22,
reverb=0.35, reverb_type="taj_mahal",
delay=0.08, delay_time=0.6, delay_feedback=0.1,
pan=-0.4)
ting_2 = score.part("tingsha_r", instrument="tingsha", volume=0.2,
reverb=0.3, reverb_type="taj_mahal",
delay=0.06, delay_time=0.45, delay_feedback=0.08,
pan=0.4)
# Bars 1-8: scattered between the bowls — left and right
ting_hits_l = {2: (E, 55), 5: (B, 50), 7: (Fs, 48)}
ting_hits_r = {1: (B, 52), 4: (E, 48), 6: (A, 45), 8: (Fs, 50)}
for bar in range(1, 89):
if bar in ting_hits_l:
note, vel = ting_hits_l[bar]
ting_1.add(note.add(12), Duration.WHOLE, velocity=vel)
elif bar > 8 and bar % 6 == 2 and bar < 73:
ting_1.add(E.add(12), Duration.WHOLE, velocity=max(25, 45 - bar // 5))
elif bar > 72 and bar % 3 == 0:
ting_1.add(E.add(12), Duration.WHOLE, velocity=max(20, 48 - (bar - 73) * 3))
else:
ting_1.rest(Duration.WHOLE)
if bar in ting_hits_r:
note, vel = ting_hits_r[bar]
ting_2.add(note.add(12), Duration.WHOLE, velocity=vel)
elif bar > 8 and bar % 7 == 4 and bar < 73:
ting_2.add(B.add(12), Duration.WHOLE, velocity=max(25, 42 - bar // 5))
elif bar > 72 and bar % 3 == 1:
ting_2.add(B.add(12), Duration.WHOLE, velocity=max(20, 45 - (bar - 73) * 3))
else:
ting_2.rest(Duration.WHOLE)
# ── DIDGERIDOO — primal drone, enters bar 9 ───────────────────
didge = score.part("didge", instrument="didgeridoo", volume=0.1,
reverb=0.2, reverb_type="cathedral",
chorus=0.15, chorus_rate=0.05, chorus_depth=0.008,
lowpass=350, pan=0.05)
rest_bars(didge, 8)
for _ in range(24):
didge.add(E.add(-24), Duration.WHOLE, velocity=60)
# Bars 33-72: fades as synths enter
for vel in [52, 45, 38, 30, 22, 15, 10, 5]:
didge.add(E.add(-24), Duration.WHOLE, velocity=vel)
rest_bars(didge, 32)
# Bars 73-80: returns
for vel in [15, 22, 30, 38, 45, 40, 32, 22]:
didge.add(E.add(-24), Duration.WHOLE, velocity=vel)
rest_bars(didge, 8)
# ── MELLOTRON FLUTE — lively, enters bar 9 ────────────────────
mello = score.part("mellotron", instrument="mellotron_flute", volume=0.3,
reverb=0.25, reverb_type="taj_mahal",
delay=0.1, delay_time=0.3, delay_feedback=0.15,
pan=-0.2, humanize=0.1)
rest_bars(mello, 8)
# Bars 9-16: lively melody — not just held chords, actual phrases
mello_phrase_a = [
(E, Duration.QUARTER, 65), (G, Duration.EIGHTH, 58),
(A, Duration.EIGHTH, 60), (B, Duration.QUARTER, 68),
(None, Duration.QUARTER, 0),
(A, Duration.QUARTER, 62), (G, Duration.EIGHTH, 55),
(Fs, Duration.EIGHTH, 52), (E, Duration.HALF, 60),
(None, Duration.QUARTER, 0), (D, Duration.QUARTER, 55),
(E, Duration.QUARTER, 62), (G, Duration.QUARTER, 58),
(B, Duration.HALF, 65), (A, Duration.HALF, 60),
(G, Duration.WHOLE, 62),
(None, Duration.WHOLE, 0),
]
play_phrase(mello, mello_phrase_a)
# Bars 17-32: continues underneath sitar
chord_bars(mello, 4, 50)
# Bars 33-40: transition — mellotron chords while synths emerge
chord_bars(mello, 2, 55)
# Bars 41-48: drops out — synths own this
rest_bars(mello, 8)
# Bars 49-56: returns — both worlds together
chord_bars(mello, 2, 52)
# Bars 57-64: PEAK — mellotron chords full
mello.set(volume=0.35)
chord_bars(mello, 2, 62)
# Bars 65-72: MELLOTRON SOLO — the bridge between worlds
mello.set(volume=0.4)
mello_solo = [
(B, Duration.HALF, 72), (A, Duration.QUARTER, 65),
(G, Duration.QUARTER, 62),
(Fs, Duration.HALF, 68), (E, Duration.QUARTER, 60),
(D, Duration.QUARTER, 58),
(E, Duration.DOTTED_HALF, 70), (Fs, Duration.QUARTER, 62),
(G, Duration.WHOLE, 65),
(A, Duration.QUARTER, 68), (B, Duration.QUARTER, 72),
(A, Duration.QUARTER, 65), (G, Duration.QUARTER, 62),
(Fs, Duration.HALF, 68), (E, Duration.HALF, 65),
(D, Duration.HALF, 60), (E, Duration.HALF, 65),
(E, Duration.WHOLE, 68),
(None, Duration.WHOLE, 0),
]
play_phrase(mello, mello_solo)
# Bars 73-80: fading chords
for vel in [55, 48, 42, 35, 28, 22, 15, 8]:
mello.add(prog[0], Duration.WHOLE, velocity=vel)
# Bars 81-88: gone
rest_bars(mello, 8)
# ── SITAR — enters bar 17, builds from 8ths to 16ths to shreds ─
sitar = score.part("sitar", instrument="sitar", volume=0.55,
reverb=0.2, reverb_type="taj_mahal",
delay=0.12, delay_time=0.3, delay_feedback=0.2,
pan=-0.25, saturation=0.2, humanize=0.1)
rest_bars(sitar, 16)
# Bars 17-20: 8th note melody — introducing itself
sitar.add(E, Duration.EIGHTH, velocity=75)
sitar.add(G, Duration.EIGHTH, velocity=70)
sitar.add(A, Duration.QUARTER, velocity=78)
sitar.add(B, Duration.EIGHTH, velocity=72)
sitar.add(A, Duration.EIGHTH, velocity=70)
sitar.add(G, Duration.HALF, velocity=75)
sitar.add(Fs, Duration.EIGHTH, velocity=68)
sitar.add(E, Duration.EIGHTH, velocity=65)
sitar.add(D, Duration.QUARTER, velocity=70)
sitar.add(E, Duration.HALF, velocity=72)
sitar.rest(Duration.WHOLE)
sitar.add(B, Duration.EIGHTH, velocity=78)
sitar.add(A, Duration.EIGHTH, velocity=72)
sitar.add(G, Duration.QUARTER, velocity=75)
sitar.add(Fs, Duration.EIGHTH, velocity=68)
sitar.add(E, Duration.EIGHTH, velocity=70)
sitar.add(D, Duration.HALF, velocity=72)
# Bars 21-24: 16th arps begin — the swarm starts, dynamics build
arp_i = [E, G, B, G, E, B.add(-12), G.add(-12), B.add(-12)]
arp_vii = [D, Fs, A, Fs, D, A.add(-12), Fs.add(-12), A.add(-12)]
arp_vi = [C, E, G, E, C, G.add(-12), E.add(-12), G.add(-12)]
arp_iv = [A.add(-12), C, E, C, A.add(-12), E.add(-12), C.add(-12), E.add(-12)]
arps = [arp_i, arp_vii, arp_vi, arp_iv]
# Each arp crescendos: first note accented, middle soft, peak at top
def sitar_arp(part, notes, base_vel, dur=Duration.SIXTEENTH):
vels = [base_vel, base_vel-12, base_vel-8, base_vel+5, # accent, dip, rise, peak
base_vel-5, base_vel-15, base_vel-18, base_vel-10]
for note, vel in zip(notes, vels):
part.add(note, dur, velocity=max(30, vel))
# Building: 65 → 72 → 78 → 82
for arp, base in [(arp_i, 65), (arp_vii, 70), (arp_vi, 75), (arp_iv, 80)]:
sitar_arp(sitar, arp, base)
sitar_arp(sitar, arp, max(40, base - 8))
# Bars 25-32: full 16th arps with 32nd fills every 4 bars
sitar.set(volume=0.6)
for bar in range(8):
if bar % 4 == 3:
# 32nd note fill — crescendo up, decrescendo down
up = [E, Fs, G, A, B, C, D, E.add(12)]
down = [D, C, B, A, G, Fs, E, D]
for i, note in enumerate(up):
sitar.add(note, 0.125, velocity=min(110, 75 + i * 5))
for i, note in enumerate(down):
sitar.add(note, 0.125, velocity=max(60, 105 - i * 5))
else:
base = [85, 82, 88, 85][bar % 4] # i louder, vi loudest
sitar_arp(sitar, arps[bar % 4], base)
sitar_arp(sitar, arps[bar % 4], max(40, base - 8))
# Bars 33-40: sitar pulls back — making room for synths
sitar.set(volume=0.45)
for _ in range(4):
sitar_arp(sitar, arp_i, 68)
sitar_arp(sitar, arp_i, 58)
sitar_arp(sitar, arp_vii, 65)
sitar_arp(sitar, arp_vii, 55)
# Bars 41-48: sitar drops out — synths own this
rest_bars(sitar, 8)
# Bars 49-56: BOTH WORLDS — sitar arps over synths, more intensity
sitar.set(volume=0.55)
for bar in range(8):
if bar % 4 == 3:
# 32nd fill — massive crescendo
up = [E, Fs, G, A, B, C, D, E.add(12)]
down = [E.add(12), D, C, B, A, G, Fs, E]
for i, note in enumerate(up):
sitar.add(note, 0.125, velocity=min(115, 80 + i * 5))
for i, note in enumerate(down):
sitar.add(note, 0.125, velocity=max(65, 110 - i * 6))
else:
base = [88, 85, 92, 88][bar % 4]
sitar_arp(sitar, arps[bar % 4], base)
sitar_arp(sitar, arps[bar % 4], max(45, base - 10))
# Bars 57-64: PEAK — sitar shredding with maximum dynamics
sitar.set(volume=0.65)
for bar in range(8):
if bar % 2 == 1:
# 32nd note shred — crescendo up, accent the peak
shred_a = [E, G, B, E.add(12), B, G, E, B.add(-12)]
shred_b = [E, A, C, E.add(12), C, A, E, C.add(-12)]
for i, note in enumerate(shred_a):
sitar.add(note, 0.125, velocity=min(118, 82 + i * 5))
for i, note in enumerate(shred_b):
sitar.add(note, 0.125, velocity=max(70, 115 - i * 5))
else:
sitar_arp(sitar, arp_i, 95)
sitar_arp(sitar, arp_vi, 92)
# Bars 65-72: drops back — mellotron solo
sitar.set(volume=0.35)
for _ in range(4):
for arp in [arp_i, arp_vii]:
for note in arp:
sitar.add(note, Duration.SIXTEENTH, velocity=60)
for note in arp:
sitar.add(note, Duration.SIXTEENTH, velocity=55)
# Bars 73-80: returns for the ending
sitar.set(volume=0.5)
for bar in range(4):
arp = arps[bar]
for note in arp:
sitar.add(note, Duration.SIXTEENTH, velocity=max(40, 75 - bar * 8))
for note in arp:
sitar.add(note, Duration.SIXTEENTH, velocity=max(35, 70 - bar * 8))
# Fade
for vel in [55, 42, 30, 18]:
for note in arp_i:
sitar.add(note, Duration.SIXTEENTH, velocity=vel)
for note in arp_i:
sitar.add(note, Duration.SIXTEENTH, velocity=max(12, vel - 8))
# Bars 81-88: gone
rest_bars(sitar, 8)
# ═══════════════════════════════════════════════════════════════════
# THE SYNTHS — emerge from the acoustic bed at bar 33
# ═══════════════════════════════════════════════════════════════════
# ── SAW — the main synth voice ────────────────────────────────
saw = score.part("saw", synth="saw", volume=0.4,
lowpass=4000,
distortion=0.15, distortion_drive=2.0,
saturation=0.5, legato=True, glide=0.03,
reverb=0.2, reverb_type="spring",
delay=0.2, delay_time=0.3, delay_feedback=0.25,
pan=0.25)
saw.lfo("lowpass", rate=0.012, min=1500, max=7000, bars=88, shape="triangle")
rest_bars(saw, 32)
# Bars 33-40: emerges — mono line, rhythmic
saw_riff = [
(E, Duration.SIXTEENTH, 82), (None, Duration.SIXTEENTH, 0),
(E, Duration.SIXTEENTH, 78), (G, Duration.SIXTEENTH, 72),
(E, Duration.SIXTEENTH, 85), (None, Duration.SIXTEENTH, 0),
(D, Duration.EIGHTH, 70),
(E, Duration.SIXTEENTH, 88), (B.add(-12), Duration.SIXTEENTH, 72),
(E, Duration.SIXTEENTH, 80), (None, Duration.SIXTEENTH, 0),
(C, Duration.EIGHTH, 75),
(E, Duration.EIGHTH, 85),
]
for _ in range(8):
play_phrase(saw, saw_riff)
# Bars 41-48: full power — synths own this
saw.set(volume=0.5)
for _ in range(8):
play_phrase(saw, saw_riff, lambda vel: min(100, vel + 8))
# Bars 49-64: continues through both worlds + peak
for _ in range(16):
play_phrase(saw, saw_riff, lambda vel: min(105, vel + 10))
# Bars 65-80: fading
saw.set(volume=0.3)
for _ in range(8):
play_phrase(saw, saw_riff, lambda vel: max(30, vel - 15))
rest_bars(saw, 8)
# ── FM — metallic texture ────────────────────────────────────
fm = score.part("fm", synth="fm", envelope="pluck", volume=0.2,
reverb=0.2, reverb_type="cathedral",
delay=0.12, delay_time=0.3, delay_feedback=0.15,
pan=-0.3)
rest_bars(fm, 40)
# Bars 41-64: bell blips
fm_blip = [
(B, Duration.QUARTER, 60), (None, Duration.QUARTER, 0),
(A, Duration.QUARTER, 55), (None, Duration.QUARTER, 0),
]
for _ in range(24):
play_phrase(fm, fm_blip)
# Bars 65-88: fading
for vel in [50, 42, 35, 28, 22, 15, 0, 0]:
if vel > 0:
fm.add(B, Duration.QUARTER, velocity=vel)
fm.rest(Duration.DOTTED_HALF)
else:
fm.rest(Duration.WHOLE)
rest_bars(fm, 16)
# ── SQUARE — counter rhythm ───────────────────────────────────
square = score.part("square", synth="square", volume=0.25,
lowpass=2500,
distortion=0.1, distortion_drive=1.5,
reverb=0.12, reverb_type="taj_mahal",
delay=0.1, delay_time=0.45, delay_feedback=0.15,
detune=6, pan=0.35)
rest_bars(square, 40)
sq_line = [
(None, Duration.SIXTEENTH, 0), (E, Duration.SIXTEENTH, 70),
(None, Duration.EIGHTH, 0),
(G, Duration.SIXTEENTH, 65), (None, Duration.SIXTEENTH, 0),
(E, Duration.EIGHTH, 72),
(None, Duration.SIXTEENTH, 0), (D, Duration.SIXTEENTH, 62),
(E, Duration.SIXTEENTH, 68), (None, Duration.SIXTEENTH, 0),
(B.add(-12), Duration.EIGHTH, 65),
(E, Duration.EIGHTH, 72),
]
for _ in range(24):
play_phrase(square, sq_line)
rest_bars(square, 24)
# ── SUPERSAW PAD — the synth wall ─────────────────────────────
pad = score.part("pad", synth="supersaw", envelope="pad", volume=0.12,
reverb=0.4, reverb_type="taj_mahal",
chorus=0.3, chorus_rate=0.15, chorus_depth=0.008,
lowpass=2500)
rest_bars(pad, 40)
chord_bars(pad, 6, 48)
for vel in [42, 35, 28, 22, 15, 10, 5, 0]:
if vel > 0:
pad.add(prog[0], Duration.WHOLE, velocity=vel)
else:
pad.rest(Duration.WHOLE)
rest_bars(pad, 16)
# ── DRUMS — enters bar 33 ────────────────────────────────────
kick = score.part("kick", volume=0.6, humanize=0.03,
distortion=0.08, distortion_drive=1.5)
snare = score.part("snare", volume=0.4, humanize=0.04,
reverb=0.15, delay=0.05, delay_time=0.3,
delay_feedback=0.08, pan=0.05)
hats = score.part("hats", volume=0.22, pan=0.15, humanize=0.04)
for part in (kick, snare, hats):
rest_bars(part, 32)
for _ in range(40):
kick.hit(K, Duration.QUARTER, velocity=105)
kick.rest(Duration.EIGHTH)
kick.hit(K, Duration.EIGHTH, velocity=88)
kick.hit(K, Duration.QUARTER, velocity=100)
kick.rest(Duration.QUARTER)
snare.rest(Duration.QUARTER)
snare.hit(S, Duration.QUARTER, velocity=92)
snare.rest(Duration.QUARTER)
snare.hit(S, Duration.QUARTER, velocity=95)
for _ in range(4):
hats.hit(CH, Duration.SIXTEENTH, velocity=65)
hats.hit(CH, Duration.SIXTEENTH, velocity=38)
hats.hit(CH, Duration.SIXTEENTH, velocity=52)
hats.hit(CH, Duration.SIXTEENTH, velocity=35)
# Bars 73-80: fading
for vel in [92, 78, 65, 52, 38, 25, 0, 0]:
if vel > 0:
kick.hit(K, Duration.QUARTER, velocity=vel)
kick.rest(Duration.DOTTED_HALF)
snare.rest(Duration.QUARTER)
snare.hit(S, Duration.QUARTER, velocity=max(15, vel - 10))
snare.rest(Duration.HALF)
hats.hit(CH, Duration.QUARTER, velocity=max(15, vel - 30))
hats.rest(Duration.DOTTED_HALF)
else:
kick.rest(Duration.WHOLE)
snare.rest(Duration.WHOLE)
hats.rest(Duration.WHOLE)
for part in (kick, snare, hats):
rest_bars(part, 8)
# ── SUB — enters bar 33 ──────────────────────────────────────
sub = score.part("sub", synth="sine", envelope="pad", volume=0.5,
lowpass=150, distortion=0.15, distortion_drive=2.5,
sub_osc=0.4, sidechain=0.3)
rest_bars(sub, 32)
roots = [E.add(-24), D.add(-24), C.add(-24), A.add(-24)]
for _ in range(10):
for root in roots:
sub.add(root, Duration.WHOLE, velocity=35)
for vel in [30, 25, 20, 15, 10, 5, 0, 0]:
if vel > 0:
sub.add(E.add(-24), Duration.WHOLE, velocity=vel)
else:
sub.rest(Duration.WHOLE)
rest_bars(sub, 8)
# ═════════════════════════════════════════════════════════════════
import sys
print(f"Key: {key}")
print(f"BPM: 100")
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 EMERGENCE (live engine)...")
from pytheory_live.live import LiveEngine
engine = LiveEngine(buffer_size=1024)
engine.play_score(score)
else:
print("Playing EMERGENCE...")
play_score(score)