mirror of
https://github.com/kennethreitz/interpretations.git
synced 2026-06-05 23:00:19 +00:00
Add Apex (track 24), play all/render all, first-run prompt, 30fps picker
Apex: Eb minor, 140 BPM — koto hook, wavefold bass, mellotron strings, timpani, 32nd koto shreds. Beast Mode's meaner sibling. play.py: 'a' play all, 'R' render all, first-run render prompt, 30fps picker refresh, WAV cache checkmarks. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -3,6 +3,7 @@
|
||||
## 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 24: Apex** — Eb minor, 140 BPM. Koto hook, wavefold bass, mellotron strings, timpani, two hooks, 32nd koto shreds.
|
||||
- **Track 23: Shruti Lofi** — D minor, shruti just intonation, 75 BPM. Microtonal lo-fi hip hop. Kalimba, Rhodes, sitar, mellotron flute, tambura drone, lazy boom bap, vinyl crackle.
|
||||
- **Track 22: Beast Mode** — G minor, 135 BPM. Trap + sitar hook + mellotron flute drop + timpani war drums + 808 slides. Sidechained saw bass.
|
||||
- **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.
|
||||
|
||||
@@ -40,6 +40,7 @@ Each track is a `.py` file. Run it to hear it.
|
||||
21. **Cathedral** — D minor, 60 BPM. Ancient stone. Tubular bells in taj_mahal, bagpipe drone, mellotron choir, timpani thunder, pipe organ, kick in cathedral reverb.
|
||||
22. **Beast Mode** — G minor, 135 BPM. Trap drums, 808 slides, distorted saw bass, sitar hook + shred solo, mellotron flute drop, timpani war drums. The hardest track on the album.
|
||||
23. **Shruti Lofi** — D minor, shruti just intonation, 75 BPM. Microtonal lo-fi hip hop. Kalimba, Rhodes, sitar hook, mellotron flute, tambura drone, lazy boom bap. Sounds like a tape found in a temple thrift store.
|
||||
24. **Apex** — Eb minor, 140 BPM. Beast Mode's meaner sibling. Koto hook + shreds, wavefold bass, mellotron strings, timpani throughout, two hooks, 32nd note koto runs. The fastest track on the album.
|
||||
|
||||
## Usage
|
||||
|
||||
|
||||
@@ -62,6 +62,7 @@ ALBUM_ORDER = [
|
||||
"cathedral.py",
|
||||
"beast_mode.py",
|
||||
"shruti_lofi.py",
|
||||
"apex.py",
|
||||
]
|
||||
|
||||
|
||||
@@ -428,7 +429,7 @@ def pick_track():
|
||||
pass
|
||||
stdscr.addstr(3, max(0, (w - 15) // 2), "─" * 15,
|
||||
curses.color_pair(2))
|
||||
stdscr.addstr(4, max(0, (w - 42) // 2), "↑/↓ navigate ↵ play r render q quit",
|
||||
stdscr.addstr(4, max(0, (w - 56) // 2), "↑/↓ navigate ↵ play r render a play all R render all q quit",
|
||||
curses.A_DIM)
|
||||
|
||||
# Track list
|
||||
@@ -488,7 +489,7 @@ def pick_track():
|
||||
pass
|
||||
|
||||
stdscr.refresh()
|
||||
curses.napms(50)
|
||||
curses.napms(33)
|
||||
|
||||
key = stdscr.getch()
|
||||
if key == curses.KEY_UP or key == ord("k"):
|
||||
@@ -503,6 +504,14 @@ def pick_track():
|
||||
result[0] = entries[selected[0]][0]
|
||||
action[0] = "render"
|
||||
return
|
||||
elif key == ord("a"):
|
||||
result[0] = "ALL"
|
||||
action[0] = "play_all"
|
||||
return
|
||||
elif key == ord("R"):
|
||||
result[0] = "ALL"
|
||||
action[0] = "render_all"
|
||||
return
|
||||
elif key == ord("q") or key == 27:
|
||||
return
|
||||
|
||||
@@ -626,7 +635,7 @@ def _render_and_cache(path, args):
|
||||
return buf, sr, offset_sec, score, mod
|
||||
|
||||
|
||||
def _play_track(path, args, force_render=False):
|
||||
def _play_track(path, args, force_render=False, render_only=False):
|
||||
"""Load, render, and play a single track. Uses cached WAV if available."""
|
||||
path = Path(path)
|
||||
if not path.exists():
|
||||
@@ -656,6 +665,9 @@ def _play_track(path, args, force_render=False):
|
||||
else:
|
||||
buf, sr, offset_sec, score, mod = _render_and_cache(path, args)
|
||||
|
||||
if render_only:
|
||||
return
|
||||
|
||||
if args.output:
|
||||
save_wav(buf, sr, args.output)
|
||||
duration_sec = len(buf) / sr
|
||||
@@ -690,12 +702,45 @@ def main():
|
||||
|
||||
# ── Track picker when no score given ─────────────────────────
|
||||
if not args.score:
|
||||
# First run — offer to render all if no cached WAVs
|
||||
if not WAVS_DIR.exists() or not list(WAVS_DIR.glob("*.wav")):
|
||||
print()
|
||||
print(" Welcome to Interpretations!")
|
||||
print()
|
||||
print(" No cached WAVs found. First play of each track requires")
|
||||
print(" rendering (~30-80s per track). You can render all tracks")
|
||||
print(" now for instant playback later, or render on demand.")
|
||||
print()
|
||||
try:
|
||||
choice = input(" Render all tracks now? [y/N] ").strip().lower()
|
||||
except (KeyboardInterrupt, EOFError):
|
||||
print()
|
||||
return
|
||||
if choice == "y":
|
||||
files = sorted_tracks(list(TRACKS_DIR.glob("*.py")))
|
||||
WAVS_DIR.mkdir(exist_ok=True)
|
||||
for i, f in enumerate(files, 1):
|
||||
print(f"\n [{i}/{len(files)}] {f.name}")
|
||||
_play_track(f, args, force_render=True, render_only=True)
|
||||
print(f"\n Done! {len(files)} tracks cached.\n")
|
||||
|
||||
while True:
|
||||
result = pick_track()
|
||||
if result is None or result[0] is None:
|
||||
return
|
||||
path, act = result
|
||||
if act == "render":
|
||||
if act == "play_all":
|
||||
files = sorted_tracks(list(TRACKS_DIR.glob("*.py")))
|
||||
for f in files:
|
||||
print(f"\n{'═' * 40}")
|
||||
_play_track(f, args)
|
||||
elif act == "render_all":
|
||||
files = sorted_tracks(list(TRACKS_DIR.glob("*.py")))
|
||||
WAVS_DIR.mkdir(exist_ok=True)
|
||||
for f in files:
|
||||
print(f"\n{'═' * 40}")
|
||||
_play_track(f, args, force_render=True, render_only=True)
|
||||
elif act == "render":
|
||||
_play_track(path, args, force_render=True)
|
||||
else:
|
||||
_play_track(path, args)
|
||||
|
||||
+538
@@ -0,0 +1,538 @@
|
||||
"""
|
||||
APEX — beast mode's meaner sibling.
|
||||
Koto hook instead of sitar. Wavefold bass. Faster. Harder.
|
||||
Eb minor, 140 BPM.
|
||||
"""
|
||||
|
||||
from pytheory import Key, Duration, Score, Tone, play_score
|
||||
from pytheory.rhythm import DrumSound
|
||||
|
||||
key = Key("Eb", "minor")
|
||||
s = key.scale # Eb F Gb Ab Bb Cb Db
|
||||
|
||||
Eb = s[0]; F = s[1]; Gb = s[2]; Ab = s[3]
|
||||
Bb = s[4]; Cb = s[5]; Db = s[6]
|
||||
|
||||
score = Score("4/4", bpm=140)
|
||||
|
||||
K = DrumSound.KICK
|
||||
S = DrumSound.SNARE
|
||||
CH = DrumSound.CLOSED_HAT
|
||||
OH = DrumSound.OPEN_HAT
|
||||
CL = DrumSound.CLAP
|
||||
|
||||
prog = key.progression("i", "VII", "VI", "iv")
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════
|
||||
# STRUCTURE (80 bars, ~3:26):
|
||||
# Bars 1-4: 808 + timpani hit — the warning
|
||||
# Bars 5-8: Koto hook drops — you weren't ready
|
||||
# Bars 9-16: Drums slam in — full trap
|
||||
# Bars 17-24: Wavefold bass — the grind underneath
|
||||
# Bars 25-32: Mellotron strings — the dark beauty
|
||||
# Bars 33-40: Breakdown — 808 slides, half time, tension
|
||||
# Bars 41-48: BUILD — timpani rolls, hats accelerate
|
||||
# Bars 49-56: APEX — everything maxed, koto shreds
|
||||
# Bars 57-64: Second hook — different melody, harder
|
||||
# Bars 65-72: Last drop — mellotron + timpani + all
|
||||
# Bars 73-80: Outro — 808 alone, descending
|
||||
# ═══════════════════════════════════════════════════════════════════
|
||||
|
||||
# ── 808 — massive, pitch slides ───────────────────────────────
|
||||
sub = score.part("808", synth="sine", envelope="pad", volume=0.6,
|
||||
lowpass=220, distortion=0.25, distortion_drive=3.5,
|
||||
sub_osc=0.5, saturation=0.4, sidechain=0.45)
|
||||
|
||||
# Bars 1-4: alone — announces itself
|
||||
sub.add(Eb.add(-24), Duration.WHOLE, velocity=38)
|
||||
sub.add(Eb.add(-24), Duration.HALF, velocity=40)
|
||||
sub.add(Db.add(-24), Duration.HALF, velocity=38)
|
||||
sub.add(Eb.add(-24), Duration.WHOLE, velocity=42)
|
||||
sub.add(Cb.add(-24), Duration.HALF, velocity=40)
|
||||
sub.add(Eb.add(-24), Duration.HALF, velocity=42)
|
||||
|
||||
# Bars 5-72: pattern
|
||||
bass_pattern = [
|
||||
(Eb.add(-24), 40), (Db.add(-24), 38),
|
||||
(Cb.add(-24), 40), (Eb.add(-24), 42),
|
||||
]
|
||||
for _ in range(17):
|
||||
for root, vel in bass_pattern:
|
||||
sub.add(root, Duration.WHOLE, velocity=vel)
|
||||
|
||||
# Bars 73-80: descending slide out
|
||||
for root, vel in [(Eb.add(-24), 38), (Db.add(-24), 35),
|
||||
(Cb.add(-24), 32), (Bb.add(-24), 28),
|
||||
(Ab.add(-24), 25), (Gb.add(-24), 22),
|
||||
(F.add(-24), 18), (Eb.add(-36), 12)]:
|
||||
sub.add(root, Duration.WHOLE, velocity=vel)
|
||||
|
||||
# ── TIMPANI — the war drum ───────────────────────────────────
|
||||
timp = score.part("timpani", instrument="timpani", volume=0.4,
|
||||
reverb=0.25, reverb_type="cathedral",
|
||||
delay=0.06, delay_time=0.214, delay_feedback=0.08,
|
||||
pan=-0.05)
|
||||
|
||||
# Bar 1: massive hit with 808
|
||||
timp.add(Eb.add(-12), Duration.WHOLE, velocity=85)
|
||||
for _ in range(7):
|
||||
timp.rest(Duration.WHOLE)
|
||||
|
||||
# Bars 9-32: sparse accents
|
||||
for bar in range(24):
|
||||
if bar % 4 == 0:
|
||||
timp.add(Eb.add(-12), Duration.QUARTER, velocity=75)
|
||||
timp.rest(Duration.DOTTED_HALF)
|
||||
else:
|
||||
timp.rest(Duration.WHOLE)
|
||||
|
||||
# Bars 33-40: breakdown — half time hits
|
||||
for _ in range(8):
|
||||
timp.add(Eb.add(-12), Duration.HALF, velocity=78)
|
||||
timp.rest(Duration.HALF)
|
||||
|
||||
# Bars 41-48: BUILD — 16th note rolls crescendo
|
||||
for bar in range(8):
|
||||
if bar < 4:
|
||||
for i in range(16):
|
||||
timp.add(Eb.add(-12), Duration.SIXTEENTH, velocity=min(92, 50 + bar * 5 + i * 2))
|
||||
else:
|
||||
for i in range(16):
|
||||
timp.add(Eb.add(-12), Duration.SIXTEENTH, velocity=min(100, 65 + i * 2))
|
||||
|
||||
# Bars 49-72: accents on 1 + rolls every 4
|
||||
for bar in range(24):
|
||||
if bar % 4 == 3:
|
||||
for i in range(16):
|
||||
timp.add(Eb.add(-12), Duration.SIXTEENTH, velocity=min(95, 55 + i * 3))
|
||||
else:
|
||||
timp.add(Eb.add(-12), Duration.QUARTER, velocity=72)
|
||||
timp.rest(Duration.DOTTED_HALF)
|
||||
|
||||
# Bars 73-80: fading
|
||||
for vel in [62, 50, 38, 28, 18, 0, 0, 0]:
|
||||
if vel > 0:
|
||||
timp.add(Eb.add(-12), Duration.QUARTER, velocity=vel)
|
||||
timp.rest(Duration.DOTTED_HALF)
|
||||
else:
|
||||
timp.rest(Duration.WHOLE)
|
||||
|
||||
# ── KOTO — the hook, enters bar 5 ────────────────────────────
|
||||
koto = score.part("koto", instrument="koto", volume=0.5,
|
||||
reverb=0.2, reverb_type="taj_mahal",
|
||||
delay=0.12, delay_time=0.214, delay_feedback=0.2,
|
||||
pan=-0.25, humanize=0.08)
|
||||
|
||||
for _ in range(4):
|
||||
koto.rest(Duration.WHOLE)
|
||||
|
||||
# The hook — dark, pentatonic, catchy
|
||||
hook = [
|
||||
(Eb, Duration.EIGHTH, 82), (Gb, Duration.EIGHTH, 75),
|
||||
(Ab, Duration.QUARTER, 85), (Bb, Duration.EIGHTH, 72),
|
||||
(Ab, Duration.EIGHTH, 70), (Gb, Duration.QUARTER, 78),
|
||||
(Eb, Duration.EIGHTH, 80), (None, Duration.EIGHTH, 0),
|
||||
(Db, Duration.EIGHTH, 70), (Eb, Duration.EIGHTH, 78),
|
||||
(None, Duration.QUARTER, 0),
|
||||
(Gb, Duration.EIGHTH, 72), (F, Duration.EIGHTH, 68),
|
||||
(Eb, Duration.HALF, 80),
|
||||
]
|
||||
|
||||
# Bars 5-8: hook alone over 808
|
||||
for _ in range(2):
|
||||
for note, dur, vel in hook:
|
||||
if note is None:
|
||||
koto.rest(dur)
|
||||
else:
|
||||
koto.add(note, dur, velocity=vel)
|
||||
|
||||
# Bars 9-24: hook continues under drums
|
||||
for _ in range(8):
|
||||
for note, dur, vel in hook:
|
||||
if note is None:
|
||||
koto.rest(dur)
|
||||
else:
|
||||
koto.add(note, dur, velocity=max(40, vel - 5))
|
||||
|
||||
# Bars 25-32: quieter under mellotron
|
||||
koto.set(volume=0.35)
|
||||
for _ in range(4):
|
||||
for note, dur, vel in hook:
|
||||
if note is None:
|
||||
koto.rest(dur)
|
||||
else:
|
||||
koto.add(note, dur, velocity=max(35, vel - 15))
|
||||
|
||||
# Bars 33-40: silent — breakdown
|
||||
for _ in range(8):
|
||||
koto.rest(Duration.WHOLE)
|
||||
|
||||
# Bars 41-48: builds back
|
||||
koto.set(volume=0.4)
|
||||
for _ in range(4):
|
||||
for note, dur, vel in hook:
|
||||
if note is None:
|
||||
koto.rest(dur)
|
||||
else:
|
||||
koto.add(note, dur, velocity=vel)
|
||||
|
||||
# Bars 49-56: APEX — koto shreds, 16th arps + 32nd fills
|
||||
koto.set(volume=0.6)
|
||||
arp_i = [Eb, Gb, Bb, Gb, Eb, Bb.add(-12), Gb.add(-12), Bb.add(-12)]
|
||||
arp_vi = [Cb.add(-12), Eb, Gb, Eb, Cb.add(-12), Gb.add(-12), Eb.add(-12), Gb.add(-12)]
|
||||
|
||||
def koto_arp(notes, base_vel):
|
||||
vels = [base_vel, base_vel-12, base_vel-8, base_vel+5,
|
||||
base_vel-5, base_vel-15, base_vel-18, base_vel-10]
|
||||
for note, vel in zip(notes, vels):
|
||||
koto.add(note, Duration.SIXTEENTH, velocity=max(35, vel))
|
||||
|
||||
for bar in range(8):
|
||||
if bar % 4 == 3:
|
||||
# 32nd shred
|
||||
for note in [Eb, F, Gb, Ab, Bb, Cb, Db, Eb.add(12),
|
||||
Db, Cb, Bb, Ab, Gb, F, Eb, F]:
|
||||
koto.add(note, 0.125, velocity=105)
|
||||
else:
|
||||
koto_arp(arp_i if bar % 2 == 0 else arp_vi, 90)
|
||||
koto_arp(arp_i if bar % 2 == 0 else arp_vi, 85)
|
||||
|
||||
# Bars 57-64: second hook — different melody, higher
|
||||
hook2 = [
|
||||
(Bb, Duration.EIGHTH, 85), (Ab, Duration.EIGHTH, 78),
|
||||
(Gb, Duration.QUARTER, 82), (Ab, Duration.EIGHTH, 75),
|
||||
(Bb, Duration.EIGHTH, 80), (Cb, Duration.QUARTER, 85),
|
||||
(Bb, Duration.EIGHTH, 78), (None, Duration.EIGHTH, 0),
|
||||
(Ab, Duration.EIGHTH, 72), (Gb, Duration.EIGHTH, 75),
|
||||
(None, Duration.QUARTER, 0),
|
||||
(Eb, Duration.QUARTER, 80),
|
||||
(Eb, Duration.HALF, 78),
|
||||
]
|
||||
koto.set(volume=0.55)
|
||||
for _ in range(4):
|
||||
for note, dur, vel in hook2:
|
||||
if note is None:
|
||||
koto.rest(dur)
|
||||
else:
|
||||
koto.add(note, dur, velocity=vel)
|
||||
|
||||
# Bars 65-72: hook 1 returns
|
||||
koto.set(volume=0.5)
|
||||
for _ in range(4):
|
||||
for note, dur, vel in hook:
|
||||
if note is None:
|
||||
koto.rest(dur)
|
||||
else:
|
||||
koto.add(note, dur, velocity=vel)
|
||||
|
||||
# Bars 73-80: fading
|
||||
for _ in range(4):
|
||||
for note, dur, vel in hook:
|
||||
if note is None:
|
||||
koto.rest(dur)
|
||||
else:
|
||||
koto.add(note, dur, velocity=max(20, vel - 30))
|
||||
for _ in range(4):
|
||||
koto.rest(Duration.WHOLE)
|
||||
|
||||
# ── KICK — hard ──────────────────────────────────────────────
|
||||
kick = score.part("kick", volume=0.9, humanize=0.02,
|
||||
distortion=0.12, distortion_drive=2.0)
|
||||
|
||||
for _ in range(8):
|
||||
kick.rest(Duration.WHOLE)
|
||||
|
||||
# Bars 9-32: trap kick
|
||||
for _ in range(24):
|
||||
kick.hit(K, Duration.QUARTER, velocity=120)
|
||||
kick.rest(Duration.EIGHTH)
|
||||
kick.hit(K, Duration.EIGHTH, velocity=100)
|
||||
kick.hit(K, Duration.QUARTER, velocity=115)
|
||||
kick.rest(Duration.EIGHTH)
|
||||
kick.hit(K, Duration.EIGHTH, velocity=98)
|
||||
|
||||
# Bars 33-40: half time
|
||||
for _ in range(8):
|
||||
kick.hit(K, Duration.HALF, velocity=118)
|
||||
kick.rest(Duration.HALF)
|
||||
|
||||
# Bars 41-48: builds back
|
||||
for _ in range(8):
|
||||
kick.hit(K, Duration.QUARTER, velocity=118)
|
||||
kick.hit(K, Duration.EIGHTH, velocity=102)
|
||||
kick.hit(K, Duration.EIGHTH, velocity=95)
|
||||
kick.hit(K, Duration.QUARTER, velocity=115)
|
||||
kick.hit(K, Duration.EIGHTH, velocity=105)
|
||||
kick.hit(K, Duration.EIGHTH, velocity=110)
|
||||
|
||||
# Bars 49-72: full power
|
||||
for _ in range(24):
|
||||
kick.hit(K, Duration.QUARTER, velocity=122)
|
||||
kick.rest(Duration.EIGHTH)
|
||||
kick.hit(K, Duration.EIGHTH, velocity=105)
|
||||
kick.hit(K, Duration.QUARTER, velocity=118)
|
||||
kick.rest(Duration.EIGHTH)
|
||||
kick.hit(K, Duration.EIGHTH, velocity=102)
|
||||
|
||||
# Bars 73-80: fading
|
||||
for vel in [112, 98, 82, 65, 48, 32, 18, 0]:
|
||||
if vel > 0:
|
||||
kick.hit(K, Duration.QUARTER, velocity=vel)
|
||||
kick.rest(Duration.DOTTED_HALF)
|
||||
else:
|
||||
kick.rest(Duration.WHOLE)
|
||||
|
||||
# ── SNARE — crack + clap layer ────────────────────────────────
|
||||
snare = score.part("snare", volume=0.5, humanize=0.03,
|
||||
reverb=0.1, distortion=0.08,
|
||||
delay=0.04, delay_time=0.214, delay_feedback=0.06,
|
||||
pan=0.05)
|
||||
clap = score.part("clap", volume=0.2, reverb=0.12,
|
||||
delay=0.05, delay_time=0.214, delay_feedback=0.08,
|
||||
pan=-0.08)
|
||||
|
||||
for _ in range(8):
|
||||
snare.rest(Duration.WHOLE)
|
||||
clap.rest(Duration.WHOLE)
|
||||
|
||||
for _ in range(24):
|
||||
snare.rest(Duration.QUARTER)
|
||||
snare.hit(S, Duration.QUARTER, velocity=110)
|
||||
snare.rest(Duration.QUARTER)
|
||||
snare.hit(S, Duration.QUARTER, velocity=112)
|
||||
clap.rest(Duration.QUARTER)
|
||||
clap.hit(CL, Duration.QUARTER, velocity=82)
|
||||
clap.rest(Duration.QUARTER)
|
||||
clap.hit(CL, Duration.QUARTER, velocity=85)
|
||||
|
||||
# Breakdown
|
||||
for _ in range(8):
|
||||
snare.rest(Duration.HALF)
|
||||
snare.hit(S, Duration.HALF, velocity=108)
|
||||
clap.rest(Duration.WHOLE)
|
||||
|
||||
# Build — snare rolls
|
||||
for bar in range(8):
|
||||
if bar >= 6:
|
||||
for i in range(16):
|
||||
snare.hit(S, Duration.SIXTEENTH, velocity=min(118, 72 + i * 3))
|
||||
clap.rest(Duration.WHOLE)
|
||||
else:
|
||||
snare.rest(Duration.QUARTER)
|
||||
snare.hit(S, Duration.QUARTER, velocity=110)
|
||||
snare.rest(Duration.QUARTER)
|
||||
snare.hit(S, Duration.QUARTER, velocity=112)
|
||||
clap.rest(Duration.QUARTER)
|
||||
clap.hit(CL, Duration.QUARTER, velocity=82)
|
||||
clap.rest(Duration.QUARTER)
|
||||
clap.hit(CL, Duration.QUARTER, velocity=85)
|
||||
|
||||
# Full power
|
||||
for _ in range(24):
|
||||
snare.rest(Duration.QUARTER)
|
||||
snare.hit(S, Duration.QUARTER, velocity=115)
|
||||
snare.rest(Duration.QUARTER)
|
||||
snare.hit(S, Duration.QUARTER, velocity=118)
|
||||
clap.rest(Duration.QUARTER)
|
||||
clap.hit(CL, Duration.QUARTER, velocity=85)
|
||||
clap.rest(Duration.QUARTER)
|
||||
clap.hit(CL, Duration.QUARTER, velocity=88)
|
||||
|
||||
# Fading
|
||||
for vel in [105, 90, 72, 55, 38, 22, 0, 0]:
|
||||
if vel > 0:
|
||||
snare.rest(Duration.QUARTER)
|
||||
snare.hit(S, Duration.QUARTER, velocity=vel)
|
||||
snare.rest(Duration.HALF)
|
||||
clap.rest(Duration.WHOLE)
|
||||
else:
|
||||
snare.rest(Duration.WHOLE)
|
||||
clap.rest(Duration.WHOLE)
|
||||
|
||||
# ── HATS — trap ──────────────────────────────────────────────
|
||||
hats = score.part("hats", volume=0.28, pan=0.25, humanize=0.04)
|
||||
|
||||
for _ in range(8):
|
||||
hats.rest(Duration.WHOLE)
|
||||
|
||||
# 16ths with evolving patterns
|
||||
for _ in range(24):
|
||||
hats.hit(CH, Duration.SIXTEENTH, velocity=75)
|
||||
hats.hit(CH, Duration.SIXTEENTH, velocity=40)
|
||||
hats.hit(OH, Duration.SIXTEENTH, velocity=62)
|
||||
hats.hit(CH, Duration.SIXTEENTH, velocity=35)
|
||||
hats.hit(CH, Duration.SIXTEENTH, velocity=72)
|
||||
hats.hit(CH, Duration.SIXTEENTH, velocity=42)
|
||||
hats.hit(CH, Duration.SIXTEENTH, velocity=58)
|
||||
hats.hit(CH, Duration.SIXTEENTH, velocity=38)
|
||||
hats.hit(CH, Duration.SIXTEENTH, velocity=75)
|
||||
hats.hit(CH, Duration.SIXTEENTH, velocity=40)
|
||||
hats.hit(CH, Duration.SIXTEENTH, velocity=55)
|
||||
hats.hit(CH, Duration.SIXTEENTH, velocity=35)
|
||||
hats.hit(OH, Duration.SIXTEENTH, velocity=60)
|
||||
hats.hit(CH, Duration.SIXTEENTH, velocity=38)
|
||||
hats.hit(CH, Duration.SIXTEENTH, velocity=52)
|
||||
hats.hit(CH, Duration.SIXTEENTH, velocity=35)
|
||||
|
||||
# Breakdown — sparse
|
||||
for _ in range(8):
|
||||
hats.hit(CH, Duration.QUARTER, velocity=48)
|
||||
hats.rest(Duration.DOTTED_HALF)
|
||||
|
||||
# Build — 32nd rolls
|
||||
for bar in range(8):
|
||||
if bar % 2 == 1:
|
||||
for i in range(32):
|
||||
hats.hit(CH, 0.125, velocity=min(82, 38 + i * 2))
|
||||
else:
|
||||
for beat in range(4):
|
||||
hats.hit(CH, Duration.SIXTEENTH, velocity=72)
|
||||
hats.hit(CH, Duration.SIXTEENTH, velocity=40)
|
||||
hats.hit(CH, Duration.SIXTEENTH, velocity=58)
|
||||
hats.hit(CH, Duration.SIXTEENTH, velocity=35)
|
||||
|
||||
# Peak
|
||||
for bar in range(24):
|
||||
if bar % 4 == 3:
|
||||
for i in range(32):
|
||||
hats.hit(CH, 0.125, velocity=min(85, 40 + i * 2))
|
||||
else:
|
||||
hats.hit(CH, Duration.SIXTEENTH, velocity=78)
|
||||
hats.hit(CH, Duration.SIXTEENTH, velocity=42)
|
||||
hats.hit(OH, Duration.SIXTEENTH, velocity=65)
|
||||
hats.hit(CH, Duration.SIXTEENTH, velocity=38)
|
||||
hats.hit(CH, Duration.SIXTEENTH, velocity=75)
|
||||
hats.hit(CH, Duration.SIXTEENTH, velocity=40)
|
||||
hats.hit(CH, Duration.SIXTEENTH, velocity=58)
|
||||
hats.hit(CH, Duration.SIXTEENTH, velocity=42)
|
||||
hats.hit(CH, Duration.SIXTEENTH, velocity=78)
|
||||
hats.hit(CH, Duration.SIXTEENTH, velocity=40)
|
||||
hats.hit(CH, Duration.SIXTEENTH, velocity=55)
|
||||
hats.hit(CH, Duration.SIXTEENTH, velocity=35)
|
||||
hats.hit(OH, Duration.SIXTEENTH, velocity=62)
|
||||
hats.hit(CH, Duration.SIXTEENTH, velocity=38)
|
||||
hats.hit(CH, Duration.SIXTEENTH, velocity=55)
|
||||
hats.hit(CH, Duration.SIXTEENTH, velocity=40)
|
||||
|
||||
# Fading
|
||||
for vel in [62, 52, 42, 32, 22, 12, 0, 0]:
|
||||
if vel > 0:
|
||||
for beat in range(4):
|
||||
hats.hit(CH, Duration.EIGHTH, velocity=vel)
|
||||
hats.hit(CH, Duration.EIGHTH, velocity=max(12, vel - 25))
|
||||
else:
|
||||
hats.rest(Duration.WHOLE)
|
||||
|
||||
# ── WAVEFOLD BASS — the grind, enters bar 17 ──────────────────
|
||||
wf = score.part("wavefold_bass", synth="wavefold", volume=0.35,
|
||||
lowpass=1500,
|
||||
distortion=0.2, distortion_drive=3.0,
|
||||
saturation=0.5, legato=True, glide=0.01,
|
||||
reverb=0.1, reverb_type="spring",
|
||||
delay=0.08, delay_time=0.214, delay_feedback=0.1,
|
||||
sidechain=0.35, pan=0.2)
|
||||
|
||||
for _ in range(16):
|
||||
wf.rest(Duration.WHOLE)
|
||||
|
||||
wf_line = [
|
||||
(Eb.add(-12), Duration.SIXTEENTH, 95), (None, Duration.SIXTEENTH, 0),
|
||||
(Eb.add(-12), Duration.SIXTEENTH, 65), (Gb.add(-12), Duration.SIXTEENTH, 60),
|
||||
(Eb.add(-12), Duration.SIXTEENTH, 92), (None, Duration.SIXTEENTH, 0),
|
||||
(Db.add(-12), Duration.EIGHTH, 58),
|
||||
(Eb.add(-12), Duration.SIXTEENTH, 98), (Bb.add(-24), Duration.SIXTEENTH, 62),
|
||||
(Eb.add(-12), Duration.SIXTEENTH, 68), (None, Duration.SIXTEENTH, 0),
|
||||
(Cb.add(-12), Duration.EIGHTH, 55),
|
||||
(Eb.add(-12), Duration.EIGHTH, 90),
|
||||
]
|
||||
|
||||
# Bars 17-32
|
||||
for _ in range(16):
|
||||
for note, dur, vel in wf_line:
|
||||
if note is None:
|
||||
wf.rest(dur)
|
||||
else:
|
||||
wf.add(note, dur, velocity=vel)
|
||||
|
||||
# Breakdown — silent
|
||||
for _ in range(8):
|
||||
wf.rest(Duration.WHOLE)
|
||||
|
||||
# Build + peak
|
||||
for _ in range(32):
|
||||
for note, dur, vel in wf_line:
|
||||
if note is None:
|
||||
wf.rest(dur)
|
||||
else:
|
||||
wf.add(note, dur, velocity=min(110, vel + 5))
|
||||
|
||||
# Fading
|
||||
for _ in range(4):
|
||||
for note, dur, vel in wf_line:
|
||||
if note is None:
|
||||
wf.rest(dur)
|
||||
else:
|
||||
wf.add(note, dur, velocity=max(25, vel - 25))
|
||||
for _ in range(4):
|
||||
wf.rest(Duration.WHOLE)
|
||||
|
||||
# ── MELLOTRON STRINGS — dark beauty, enters bar 25 ────────────
|
||||
mello = score.part("mellotron", instrument="mellotron_strings", volume=0.22,
|
||||
reverb=0.35, reverb_type="taj_mahal",
|
||||
pan=-0.15)
|
||||
|
||||
for _ in range(24):
|
||||
mello.rest(Duration.WHOLE)
|
||||
|
||||
# Bars 25-32: dark pad
|
||||
for _ in range(2):
|
||||
for chord in prog:
|
||||
mello.add(chord, Duration.WHOLE, velocity=52)
|
||||
|
||||
# Bars 33-48: continues through breakdown and build
|
||||
for _ in range(4):
|
||||
for chord in prog:
|
||||
mello.add(chord, Duration.WHOLE, velocity=48)
|
||||
|
||||
# Bars 49-72: peak + drops
|
||||
mello.set(volume=0.28)
|
||||
for _ in range(6):
|
||||
for chord in prog:
|
||||
mello.add(chord, Duration.WHOLE, velocity=55)
|
||||
|
||||
# Fading
|
||||
for vel in [48, 40, 32, 25, 18, 12, 0, 0]:
|
||||
if vel > 0:
|
||||
mello.add(prog[0], Duration.WHOLE, velocity=vel)
|
||||
else:
|
||||
mello.rest(Duration.WHOLE)
|
||||
|
||||
# ── SINGING BOWL — marks sections ─────────────────────────────
|
||||
bowl = score.part("bowl", instrument="singing_bowl", volume=0.22,
|
||||
reverb=0.45, reverb_type="taj_mahal",
|
||||
delay=0.12, delay_time=0.428, delay_feedback=0.15,
|
||||
pan=0.15)
|
||||
|
||||
markers = {1: 62, 5: 55, 9: 58, 25: 60, 33: 52, 41: 58, 49: 65, 57: 58, 65: 62}
|
||||
for bar in range(1, 81):
|
||||
if bar in markers:
|
||||
bowl.add(Eb.add(-24), Duration.WHOLE, velocity=markers[bar])
|
||||
else:
|
||||
bowl.rest(Duration.WHOLE)
|
||||
|
||||
# ═════════════════════════════════════════════════════════════════
|
||||
import sys
|
||||
|
||||
print(f"Key: {key}")
|
||||
print(f"BPM: 140")
|
||||
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 APEX (live engine)...")
|
||||
from pytheory_live.live import LiveEngine
|
||||
engine = LiveEngine(buffer_size=1024)
|
||||
engine.play_score(score)
|
||||
else:
|
||||
print("Playing APEX...")
|
||||
play_score(score)
|
||||
Reference in New Issue
Block a user