mirror of
https://github.com/kennethreitz/interpretations.git
synced 2026-06-05 06:46:15 +00:00
a9b356f5cc
Silk Road: mellotron_flute in finale. The Dialogue: drift pad, mellotron ending. Acid Reign: wavefold in breakdown. Chakra: drift at crown. Raga Midnight: mellotron_strings over 808 drop. The Temple: granular_pad texture. Sleight of Hand: mellotron_choir. Gravity: drift tambura. Ghost Protocol: NES pulled back. An Exception: psycho bass 0.3. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
400 lines
17 KiB
Python
400 lines
17 KiB
Python
"""
|
|
SILK ROAD — a caravan picking up musicians along the ancient trade route.
|
|
Each civilization layers on top, nobody leaves. D minor throughout.
|
|
|
|
China → India → Persia → Mediterranean → All Together
|
|
"""
|
|
|
|
from pytheory import Key, Duration, Score, Tone, play_score
|
|
from pytheory.rhythm import DrumSound
|
|
|
|
key = Key("D", "minor")
|
|
s = key.scale
|
|
|
|
# D minor: 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=95)
|
|
|
|
|
|
def play_phrase(part, phrase, vel_offset=0):
|
|
"""Play a phrase — list of (note, duration, velocity) tuples."""
|
|
for note, dur, vel in phrase:
|
|
if note is None:
|
|
part.rest(dur)
|
|
else:
|
|
part.add(note, dur, velocity=min(127, max(20, vel + vel_offset)))
|
|
|
|
|
|
def play_phrases(part, phrases, reps=1, vel_offset=0):
|
|
"""Play a list of phrases, repeated."""
|
|
for _ in range(reps):
|
|
for phrase in phrases:
|
|
play_phrase(part, phrase, vel_offset)
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════════
|
|
# STRUCTURE (80 bars, ~5 min):
|
|
# Bars 1-16: China — koto solo, pentatonic
|
|
# Bars 17-32: India — sitar + tabla join the koto
|
|
# Bars 33-48: Persia — mandolin (oud) + doumbek layer in
|
|
# Bars 49-64: Mediterranean — guitar + cajon, flamenco tinge
|
|
# Bars 65-80: All Together — full caravan, everyone plays
|
|
# ═══════════════════════════════════════════════════════════════════
|
|
|
|
# ── KOTO — China, pentatonic, sparse ───────────────────────────
|
|
# D minor pentatonic: D F G A C (removes E and Bb)
|
|
koto = score.part("koto", instrument="koto", volume=0.4,
|
|
reverb=0.15, reverb_type="taj_mahal",
|
|
delay=0.08, delay_time=0.316, delay_feedback=0.1,
|
|
pan=-0.45, humanize=0.1)
|
|
|
|
# Bars 1-16: solo koto — starts intimate, reverb grows
|
|
koto_phrases = [
|
|
# Phrase 1: simple, announcing
|
|
[(D.add(12), Duration.HALF, 80), (None, Duration.QUARTER, 0),
|
|
(A, Duration.QUARTER, 70)],
|
|
[(G, Duration.QUARTER, 75), (F, Duration.QUARTER, 68),
|
|
(D, Duration.HALF, 72)],
|
|
# Phrase 2: climbing
|
|
[(None, Duration.QUARTER, 0), (A, Duration.QUARTER, 75),
|
|
(C.add(12), Duration.QUARTER, 82), (D.add(12), Duration.QUARTER, 78)],
|
|
[(F.add(12), Duration.HALF, 85), (D.add(12), Duration.QUARTER, 75),
|
|
(None, Duration.QUARTER, 0)],
|
|
# Phrase 3: descending
|
|
[(D.add(12), Duration.QUARTER, 80), (C.add(12), Duration.EIGHTH, 72),
|
|
(A, Duration.EIGHTH, 68), (G, Duration.HALF, 75)],
|
|
[(F, Duration.QUARTER, 70), (D, Duration.QUARTER, 65),
|
|
(None, Duration.HALF, 0)],
|
|
# Phrase 4: resolving
|
|
[(A, Duration.QUARTER, 78), (G, Duration.EIGHTH, 70),
|
|
(F, Duration.EIGHTH, 65), (G, Duration.QUARTER, 72),
|
|
(A, Duration.QUARTER, 75)],
|
|
[(D, Duration.WHOLE, 80)],
|
|
]
|
|
# First pass: dry, intimate, close
|
|
play_phrases(koto, koto_phrases, reps=1)
|
|
# Second pass: reverb creeping in
|
|
koto.set(reverb=0.15, reverb_type="taj_mahal", delay=0.1, delay_time=0.316, delay_feedback=0.15)
|
|
play_phrases(koto, koto_phrases, reps=1)
|
|
|
|
# Bars 17-80: koto continues, reverb grows as the caravan fills
|
|
koto.set(reverb=0.3, delay=0.2, delay_feedback=0.25)
|
|
play_phrases(koto, koto_phrases, reps=2, vel_offset=-15)
|
|
koto.set(reverb=0.45, delay=0.3, delay_feedback=0.35)
|
|
play_phrases(koto, koto_phrases, reps=2, vel_offset=-20)
|
|
koto.set(reverb=0.55, delay=0.3, delay_feedback=0.4)
|
|
play_phrases(koto, koto_phrases, reps=4, vel_offset=-25)
|
|
|
|
# ── SITAR — India, enters bar 17 ───────────────────────────────
|
|
sitar = score.part("sitar", instrument="sitar", volume=0.5,
|
|
reverb=0.35, reverb_type="taj_mahal",
|
|
delay=0.2, delay_time=0.333, delay_feedback=0.3,
|
|
pan=-0.3, humanize=0.1)
|
|
|
|
# Bars 1-16: silent
|
|
for _ in range(16):
|
|
sitar.rest(Duration.WHOLE)
|
|
|
|
# Bars 17-32: sitar melody — raga-influenced, ornamental
|
|
sitar_phrases = [
|
|
[(D, Duration.HALF, 85), (E, Duration.QUARTER, 75),
|
|
(F, Duration.QUARTER, 80)],
|
|
[(G, Duration.QUARTER, 85), (A, Duration.EIGHTH, 78),
|
|
(G, Duration.EIGHTH, 72), (F, Duration.HALF, 80)],
|
|
[(A, Duration.QUARTER, 90), (Bb, Duration.EIGHTH, 82),
|
|
(A, Duration.EIGHTH, 78), (G, Duration.QUARTER, 75),
|
|
(F, Duration.QUARTER, 70)],
|
|
[(E, Duration.QUARTER, 72), (D, Duration.DOTTED_HALF, 85)],
|
|
]
|
|
play_phrases(sitar, sitar_phrases, reps=4)
|
|
|
|
# Bars 33-80: sitar continues through all sections
|
|
play_phrases(sitar, sitar_phrases, reps=12)
|
|
|
|
# ── TABLA — India, enters bar 17 ───────────────────────────────
|
|
NA = DrumSound.TABLA_NA
|
|
TIT = DrumSound.TABLA_TIT
|
|
GE = DrumSound.TABLA_GE
|
|
tDHA = DrumSound.TABLA_DHA
|
|
GEB = DrumSound.TABLA_GE_BEND
|
|
|
|
tabla = score.part("tabla", volume=0.3, reverb=0.3, reverb_type="cathedral",
|
|
reverb_decay=1.5, pan=-0.15, humanize=0.08)
|
|
|
|
for _ in range(16):
|
|
tabla.rest(Duration.WHOLE)
|
|
|
|
# Bars 17-80: keherwa groove — gets louder as the caravan grows
|
|
# Bars 17-32: India section — gentle
|
|
tabla.set(volume=0.2)
|
|
for bar in range(16):
|
|
if bar % 8 == 7:
|
|
tabla.hit(tDHA, Duration.EIGHTH, velocity=95, articulation="accent")
|
|
tabla.hit(GEB, Duration.EIGHTH, velocity=108)
|
|
tabla.hit(NA, Duration.EIGHTH, velocity=72)
|
|
tabla.hit(GEB, Duration.EIGHTH, velocity=105)
|
|
tabla.hit(tDHA, Duration.EIGHTH, velocity=90, articulation="accent")
|
|
tabla.hit(NA, Duration.SIXTEENTH, velocity=62)
|
|
tabla.hit(TIT, Duration.SIXTEENTH, velocity=52)
|
|
tabla.hit(GEB, Duration.QUARTER, velocity=112)
|
|
else:
|
|
tabla.hit(tDHA, Duration.EIGHTH, velocity=85, articulation="accent")
|
|
tabla.hit(GE, Duration.EIGHTH, velocity=55)
|
|
tabla.hit(NA, Duration.EIGHTH, velocity=65)
|
|
tabla.hit(TIT, Duration.EIGHTH, velocity=42)
|
|
tabla.hit(NA, Duration.EIGHTH, velocity=60)
|
|
tabla.hit(TIT, Duration.EIGHTH, velocity=38)
|
|
tabla.hit(tDHA, Duration.EIGHTH, velocity=80, articulation="accent")
|
|
tabla.hit(NA, Duration.EIGHTH, velocity=58)
|
|
|
|
# Bars 33-48: Persia joins — fuller
|
|
tabla.set(volume=0.3)
|
|
for bar in range(16):
|
|
if bar % 8 == 7:
|
|
tabla.hit(tDHA, Duration.EIGHTH, velocity=100, articulation="accent")
|
|
tabla.hit(GEB, Duration.EIGHTH, velocity=112)
|
|
tabla.hit(NA, Duration.EIGHTH, velocity=78)
|
|
tabla.hit(GEB, Duration.EIGHTH, velocity=110)
|
|
tabla.hit(tDHA, Duration.EIGHTH, velocity=95, articulation="accent")
|
|
tabla.hit(NA, Duration.SIXTEENTH, velocity=68)
|
|
tabla.hit(TIT, Duration.SIXTEENTH, velocity=55)
|
|
tabla.hit(GEB, Duration.QUARTER, velocity=118)
|
|
else:
|
|
tabla.hit(tDHA, Duration.EIGHTH, velocity=90, articulation="accent")
|
|
tabla.hit(GE, Duration.EIGHTH, velocity=60)
|
|
tabla.hit(NA, Duration.EIGHTH, velocity=70)
|
|
tabla.hit(TIT, Duration.EIGHTH, velocity=48)
|
|
tabla.hit(NA, Duration.EIGHTH, velocity=65)
|
|
tabla.hit(TIT, Duration.EIGHTH, velocity=42)
|
|
tabla.hit(tDHA, Duration.EIGHTH, velocity=85, articulation="accent")
|
|
tabla.hit(NA, Duration.EIGHTH, velocity=62)
|
|
|
|
# Bars 49-64: Mediterranean joins — confident
|
|
tabla.set(volume=0.38)
|
|
for bar in range(16):
|
|
if bar % 8 == 7:
|
|
tabla.hit(tDHA, Duration.EIGHTH, velocity=105, articulation="accent")
|
|
tabla.hit(GEB, Duration.EIGHTH, velocity=115)
|
|
tabla.hit(NA, Duration.EIGHTH, velocity=82)
|
|
tabla.hit(GEB, Duration.EIGHTH, velocity=112)
|
|
tabla.hit(tDHA, Duration.EIGHTH, velocity=100, articulation="accent")
|
|
tabla.hit(NA, Duration.SIXTEENTH, velocity=72)
|
|
tabla.hit(TIT, Duration.SIXTEENTH, velocity=58)
|
|
tabla.hit(GEB, Duration.QUARTER, velocity=120)
|
|
else:
|
|
tabla.hit(tDHA, Duration.EIGHTH, velocity=95, articulation="accent")
|
|
tabla.hit(GE, Duration.EIGHTH, velocity=65)
|
|
tabla.hit(NA, Duration.EIGHTH, velocity=75)
|
|
tabla.hit(TIT, Duration.EIGHTH, velocity=52)
|
|
tabla.hit(NA, Duration.EIGHTH, velocity=70)
|
|
tabla.hit(TIT, Duration.EIGHTH, velocity=45)
|
|
tabla.hit(tDHA, Duration.EIGHTH, velocity=90, articulation="accent")
|
|
tabla.hit(NA, Duration.EIGHTH, velocity=68)
|
|
|
|
# Bars 65-80: all together — full power
|
|
tabla.set(volume=0.45)
|
|
for bar in range(16):
|
|
if bar % 8 == 7:
|
|
tabla.hit(tDHA, Duration.EIGHTH, velocity=110, articulation="accent")
|
|
tabla.hit(GEB, Duration.EIGHTH, velocity=118)
|
|
tabla.hit(NA, Duration.EIGHTH, velocity=85)
|
|
tabla.hit(GEB, Duration.EIGHTH, velocity=115)
|
|
tabla.hit(tDHA, Duration.EIGHTH, velocity=105, articulation="accent")
|
|
tabla.hit(NA, Duration.SIXTEENTH, velocity=75)
|
|
tabla.hit(TIT, Duration.SIXTEENTH, velocity=62)
|
|
tabla.hit(GEB, Duration.QUARTER, velocity=122)
|
|
else:
|
|
tabla.hit(tDHA, Duration.EIGHTH, velocity=100, articulation="accent")
|
|
tabla.hit(GE, Duration.EIGHTH, velocity=70)
|
|
tabla.hit(NA, Duration.EIGHTH, velocity=80)
|
|
tabla.hit(TIT, Duration.EIGHTH, velocity=55)
|
|
tabla.hit(NA, Duration.EIGHTH, velocity=75)
|
|
tabla.hit(TIT, Duration.EIGHTH, velocity=48)
|
|
tabla.hit(tDHA, Duration.EIGHTH, velocity=95, articulation="accent")
|
|
tabla.hit(NA, Duration.EIGHTH, velocity=72)
|
|
|
|
# ── MANDOLIN (oud) — Persia, enters bar 33 ─────────────────────
|
|
oud = score.part("oud", instrument="mandolin", volume=0.4,
|
|
reverb=0.45, reverb_type="cathedral",
|
|
delay=0.3, delay_time=0.316, delay_feedback=0.35,
|
|
pan=0.15, humanize=0.08)
|
|
|
|
for _ in range(32):
|
|
oud.rest(Duration.WHOLE)
|
|
|
|
# Bars 33-80: maqam-influenced melody — chromatic ornaments
|
|
oud_phrases = [
|
|
# Descending maqam feel
|
|
[(A, Duration.EIGHTH, 88), (Bb, Duration.EIGHTH, 78),
|
|
(A, Duration.QUARTER, 85), (G, Duration.QUARTER, 80),
|
|
(F, Duration.QUARTER, 75)],
|
|
[(E, Duration.EIGHTH, 72), (F, Duration.EIGHTH, 68),
|
|
(E, Duration.QUARTER, 75), (D, Duration.HALF, 82)],
|
|
# Climbing with ornaments
|
|
[(D, Duration.EIGHTH, 80), (E, Duration.SIXTEENTH, 70),
|
|
(F, Duration.SIXTEENTH, 72), (G, Duration.QUARTER, 82),
|
|
(A, Duration.QUARTER, 88), (None, Duration.QUARTER, 0)],
|
|
[(Bb, Duration.QUARTER, 85), (A, Duration.EIGHTH, 78),
|
|
(G, Duration.EIGHTH, 72), (A, Duration.HALF, 80)],
|
|
]
|
|
play_phrases(oud, oud_phrases, reps=12)
|
|
|
|
# ── DOUMBEK — Persia, enters bar 33 ────────────────────────────
|
|
DKD = DrumSound.DOUMBEK_DUM
|
|
DKT = DrumSound.DOUMBEK_TEK
|
|
DKK = DrumSound.DOUMBEK_KA
|
|
|
|
doumbek = score.part("doumbek", volume=0.3, reverb=0.2, reverb_decay=1.0,
|
|
pan=0.2, humanize=0.06)
|
|
|
|
for _ in range(32):
|
|
doumbek.rest(Duration.WHOLE)
|
|
|
|
# Bars 33-80: maqsoum-inspired rhythm
|
|
for bar in range(48):
|
|
if bar % 8 == 7:
|
|
# Fill
|
|
doumbek.hit(DKD, Duration.SIXTEENTH, velocity=100, articulation="accent")
|
|
doumbek.hit(DKT, Duration.SIXTEENTH, velocity=65)
|
|
doumbek.hit(DKT, Duration.SIXTEENTH, velocity=62)
|
|
doumbek.hit(DKD, Duration.SIXTEENTH, velocity=95)
|
|
doumbek.hit(DKT, Duration.SIXTEENTH, velocity=68)
|
|
doumbek.hit(DKK, Duration.SIXTEENTH, velocity=58)
|
|
doumbek.hit(DKD, Duration.SIXTEENTH, velocity=100, articulation="accent")
|
|
doumbek.hit(DKT, Duration.SIXTEENTH, velocity=65)
|
|
doumbek.hit(DKT, Duration.SIXTEENTH, velocity=60)
|
|
doumbek.hit(DKK, Duration.SIXTEENTH, velocity=55)
|
|
doumbek.hit(DKD, Duration.SIXTEENTH, velocity=98)
|
|
doumbek.hit(DKT, Duration.SIXTEENTH, velocity=62)
|
|
doumbek.hit(DKD, Duration.QUARTER, velocity=110, articulation="marcato")
|
|
else:
|
|
# Maqsoum: DUM-tek-tek-DUM-tek
|
|
doumbek.hit(DKD, Duration.EIGHTH, velocity=90)
|
|
doumbek.hit(DKT, Duration.EIGHTH, velocity=60)
|
|
doumbek.hit(DKT, Duration.EIGHTH, velocity=58)
|
|
doumbek.hit(DKD, Duration.EIGHTH, velocity=85)
|
|
doumbek.hit(DKT, Duration.EIGHTH, velocity=62)
|
|
doumbek.hit(DKT, Duration.EIGHTH, velocity=55)
|
|
doumbek.hit(DKT, Duration.EIGHTH, velocity=58)
|
|
doumbek.hit(DKT, Duration.EIGHTH, velocity=52)
|
|
|
|
# ── GUITAR — Mediterranean, enters bar 49 ──────────────────────
|
|
guitar = score.part("guitar", instrument="acoustic_guitar", volume=0.35,
|
|
reverb=0.4, reverb_type="cathedral",
|
|
delay=0.2, delay_time=0.316, delay_feedback=0.25,
|
|
pan=0.35, humanize=0.1)
|
|
|
|
for _ in range(48):
|
|
guitar.rest(Duration.WHOLE)
|
|
|
|
# Bars 49-80: fingerpicked arpeggios — flamenco-tinged
|
|
prog = key.progression("i", "VII", "VI", "v")
|
|
guitar_arps = [
|
|
# i — Dm
|
|
[D.add(-12), A.add(-12), D, F, A, F, D, A.add(-12)],
|
|
# VII — C
|
|
[C.add(-12), G.add(-12), C, E, G, E, C, G.add(-12)],
|
|
# VI — Bb
|
|
[Bb.add(-24), F.add(-12), Bb.add(-12), D, F, D, Bb.add(-12), F.add(-12)],
|
|
# v — Am
|
|
[A.add(-24), E.add(-12), A.add(-12), C, E, C, A.add(-12), E.add(-12)],
|
|
]
|
|
for _ in range(8):
|
|
for arp in guitar_arps:
|
|
for note in arp:
|
|
guitar.add(note, Duration.EIGHTH, velocity=75)
|
|
|
|
# ── CAJON — Mediterranean, enters bar 49 ────────────────────────
|
|
CS = DrumSound.CAJON_SLAP
|
|
CT = DrumSound.CAJON_TAP
|
|
CSS = DrumSound.CAJON_SLAP_SNARE
|
|
|
|
cajon = score.part("cajon", volume=0.25, reverb=0.15, reverb_decay=0.6,
|
|
pan=0.4, humanize=0.08)
|
|
|
|
for _ in range(48):
|
|
cajon.rest(Duration.WHOLE)
|
|
|
|
# Bars 49-80: flamenco-ish groove
|
|
for bar in range(32):
|
|
if bar % 4 == 3:
|
|
# Fill
|
|
cajon.hit(CSS, Duration.SIXTEENTH, velocity=100, articulation="accent")
|
|
cajon.hit(CT, Duration.SIXTEENTH, velocity=55)
|
|
cajon.hit(CSS, Duration.SIXTEENTH, velocity=98)
|
|
cajon.hit(CT, Duration.SIXTEENTH, velocity=52)
|
|
cajon.hit(CSS, Duration.SIXTEENTH, velocity=102, articulation="accent")
|
|
cajon.hit(CT, Duration.SIXTEENTH, velocity=55)
|
|
cajon.hit(CS, Duration.SIXTEENTH, velocity=95)
|
|
cajon.hit(CT, Duration.SIXTEENTH, velocity=50)
|
|
cajon.hit(CSS, Duration.SIXTEENTH, velocity=105, articulation="accent")
|
|
cajon.hit(CT, Duration.SIXTEENTH, velocity=55)
|
|
cajon.hit(CS, Duration.SIXTEENTH, velocity=92)
|
|
cajon.hit(CT, Duration.SIXTEENTH, velocity=52)
|
|
cajon.hit(CSS, Duration.QUARTER, velocity=110, articulation="marcato")
|
|
else:
|
|
cajon.hit(CS, Duration.EIGHTH, velocity=88, articulation="accent")
|
|
cajon.hit(CT, Duration.EIGHTH, velocity=48)
|
|
cajon.hit(CSS, Duration.EIGHTH, velocity=82)
|
|
cajon.hit(CT, Duration.EIGHTH, velocity=45)
|
|
cajon.hit(CS, Duration.EIGHTH, velocity=85, articulation="accent")
|
|
cajon.hit(CT, Duration.EIGHTH, velocity=50)
|
|
cajon.hit(CSS, Duration.EIGHTH, velocity=78)
|
|
cajon.hit(CT, Duration.EIGHTH, velocity=45)
|
|
|
|
# ── TAMBURA — the thread connecting everything ──────────────────
|
|
tambura = score.part("tambura", synth="sine", envelope="pad", volume=0.18,
|
|
reverb=0.8, reverb_type="taj_mahal",
|
|
chorus=0.4, chorus_rate=0.06, chorus_depth=0.012,
|
|
lowpass=1000, sub_osc=0.3)
|
|
|
|
for _ in range(80):
|
|
tambura.add(D.add(-24), Duration.HALF)
|
|
tambura.add(A.add(-24), Duration.HALF)
|
|
|
|
# ── PAD — swells in the last section ───────────────────────────
|
|
pad = score.part("pad", synth="supersaw", envelope="pad", volume=0.1,
|
|
reverb=0.6, reverb_type="taj_mahal",
|
|
chorus=0.4, chorus_rate=0.2, chorus_depth=0.008,
|
|
lowpass=1800)
|
|
|
|
for _ in range(64):
|
|
pad.rest(Duration.WHOLE)
|
|
|
|
# Bars 65-80: all together, pad glues it
|
|
for _ in range(4):
|
|
for chord in prog:
|
|
pad.add(chord, Duration.WHOLE, velocity=60)
|
|
|
|
# ── MELLOTRON — ghostly flute layer in the finale ──────────────
|
|
mello = score.part("mellotron", instrument="mellotron_flute", volume=0.15,
|
|
reverb=0.3, reverb_type="taj_mahal",
|
|
pan=0.1, humanize=0.08)
|
|
|
|
for _ in range(64):
|
|
mello.rest(Duration.WHOLE)
|
|
|
|
# Bars 65-80: plays the progression as whole notes
|
|
for _ in range(4):
|
|
for chord in prog:
|
|
mello.add(chord, Duration.WHOLE, velocity=48)
|
|
|
|
# ═════════════════════════════════════════════════════════════════
|
|
import sys
|
|
|
|
print(f"Key: {key}")
|
|
print(f"BPM: 95")
|
|
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 SILK ROAD (live engine)...")
|
|
from pytheory_live.live import LiveEngine
|
|
engine = LiveEngine(buffer_size=1024)
|
|
engine.play_score(score)
|
|
else:
|
|
print("Playing SILK ROAD...")
|
|
play_score(score)
|