mirror of
https://github.com/kennethreitz/interpretations.git
synced 2026-06-05 14:50:20 +00:00
e40156787b
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
377 lines
12 KiB
Python
377 lines
12 KiB
Python
"""
|
|
GHOST PROTOCOL — dark, patient, hypnotic.
|
|
Trip-hop intro dissolves into a slow-building trance arp.
|
|
One pluck line that IS the track.
|
|
The kick doesn't arrive until you've forgotten you're waiting for it.
|
|
"""
|
|
|
|
from pytheory import Key, Duration, Score, Tone, play_score
|
|
from pytheory.rhythm import DrumSound
|
|
|
|
key = Key("F", "minor")
|
|
s = key.scale
|
|
|
|
F = s[0]; G = s[1]; Ab = s[2]; Bb = s[3]
|
|
C = s[4]; Db = s[5]; Eb = s[6]
|
|
|
|
score = Score("4/4", bpm=128)
|
|
|
|
K = DrumSound.KICK
|
|
CL = DrumSound.CLAP
|
|
CH = DrumSound.CLOSED_HAT
|
|
OH = DrumSound.OPEN_HAT
|
|
|
|
prog = key.progression("i", "VI", "VII", "i")
|
|
|
|
# ═══════════════════════════════════════════════════════════════════
|
|
# STRUCTURE (128 bars, ~6 min at 128 BPM):
|
|
# Bars 1-16: Portishead — dark, downtempo feel, scratchy
|
|
# Bars 17-32: The arp emerges from the darkness
|
|
# Bars 33-48: Pad swells, arp grows, still no kick
|
|
# Bars 49-64: Kick arrives — release, the Strobe moment
|
|
# Bars 65-80: Full energy, filter wide open
|
|
# Bars 81-96: Peak — everything singing
|
|
# Bars 97-112: Filtering down, layers drop
|
|
# Bars 113-128: Just the arp and pad, dissolving
|
|
# ═══════════════════════════════════════════════════════════════════
|
|
|
|
# ── RHODES — Portishead dark chords, sparse, tremolo ────────────
|
|
rhodes = score.part("rhodes", instrument="electric_piano", volume=0.25,
|
|
reverb=0.6, reverb_type="taj_mahal",
|
|
delay=0.2, delay_time=0.468, delay_feedback=0.3,
|
|
tremolo_depth=0.2, tremolo_rate=2.5,
|
|
pan=-0.2, humanize=0.1)
|
|
|
|
# Bars 1-16: dark sparse chords — Portishead vibe
|
|
for _ in range(4):
|
|
for chord in prog:
|
|
rhodes.add(chord, Duration.HALF, velocity=70)
|
|
rhodes.rest(Duration.HALF)
|
|
|
|
# Bars 17-32: thinner, making room for the arp
|
|
for _ in range(4):
|
|
for chord in prog:
|
|
rhodes.add(chord, Duration.QUARTER, velocity=55)
|
|
rhodes.rest(Duration.DOTTED_HALF)
|
|
|
|
# Bars 33-48: fading out
|
|
for _ in range(2):
|
|
for chord in prog:
|
|
rhodes.add(chord, Duration.QUARTER, velocity=40)
|
|
rhodes.rest(Duration.DOTTED_HALF)
|
|
for _ in range(8):
|
|
rhodes.rest(Duration.WHOLE)
|
|
|
|
# Bars 49-128: gone
|
|
for _ in range(80):
|
|
rhodes.rest(Duration.WHOLE)
|
|
|
|
# ── TRIP-HOP BEAT — Portishead style, bars 5-32 ────────────────
|
|
trip = score.part("trip_hop", volume=0.3, humanize=0.08,
|
|
reverb=0.25, reverb_decay=1.2,
|
|
delay=0.3, delay_time=0.468, delay_feedback=0.35)
|
|
|
|
# Bars 1-4: silence — let rhodes breathe
|
|
for _ in range(4):
|
|
trip.rest(Duration.WHOLE)
|
|
|
|
# Bars 5-16: slow, lazy breakbeat — Portishead pocket
|
|
S = DrumSound.SNARE
|
|
for _ in range(12):
|
|
trip.hit(K, Duration.QUARTER, velocity=90)
|
|
trip.rest(Duration.EIGHTH)
|
|
trip.hit(CH, Duration.EIGHTH, velocity=50)
|
|
trip.hit(S, Duration.QUARTER, velocity=85)
|
|
trip.hit(CH, Duration.EIGHTH, velocity=48)
|
|
trip.hit(K, Duration.EIGHTH, velocity=75)
|
|
|
|
# Bars 17-24: beat thins out
|
|
for _ in range(8):
|
|
trip.hit(K, Duration.QUARTER, velocity=70)
|
|
trip.rest(Duration.QUARTER)
|
|
trip.hit(S, Duration.QUARTER, velocity=60)
|
|
trip.rest(Duration.QUARTER)
|
|
|
|
# Bars 25-32: just ghost hits, disappearing
|
|
for bar in range(8):
|
|
vel = max(20, 60 - bar * 5)
|
|
trip.hit(K, Duration.QUARTER, velocity=vel)
|
|
trip.rest(Duration.DOTTED_HALF)
|
|
|
|
# Bars 33-128: gone
|
|
for _ in range(96):
|
|
trip.rest(Duration.WHOLE)
|
|
|
|
# ── THE ARP — the soul of the track, enters quietly bar 17 ─────
|
|
arp = score.part("arp", synth="drift", envelope="pluck", volume=0.35,
|
|
reverb=0.35, delay=0.4, delay_time=0.234,
|
|
delay_feedback=0.45, lowpass=1200, detune=6,
|
|
pan=0.15, humanize=0.04)
|
|
# Slow filter open over the entire track
|
|
arp.lfo("lowpass", rate=0.008, min=800, max=6000, bars=128, shape="saw")
|
|
|
|
# Bars 1-16: silence
|
|
for _ in range(16):
|
|
arp.rest(Duration.WHOLE)
|
|
|
|
# The pattern — hypnotic, never changes, just grows
|
|
arp_pattern = [
|
|
F, None, C.add(12), None,
|
|
Eb, None, C.add(12), Ab,
|
|
F, None, C.add(12), None,
|
|
Ab, None, Eb, C.add(12),
|
|
]
|
|
|
|
# Bars 17-32: arp emerges, barely audible
|
|
for _ in range(4):
|
|
for note in arp_pattern:
|
|
if note is None:
|
|
arp.rest(Duration.SIXTEENTH)
|
|
else:
|
|
arp.add(note, Duration.SIXTEENTH, velocity=50)
|
|
|
|
# Bars 33-48: arp grows
|
|
for _ in range(4):
|
|
for note in arp_pattern:
|
|
if note is None:
|
|
arp.rest(Duration.SIXTEENTH)
|
|
else:
|
|
arp.add(note, Duration.SIXTEENTH, velocity=65)
|
|
|
|
# Bars 49-64: kick arrives, arp confident
|
|
for _ in range(4):
|
|
for note in arp_pattern:
|
|
if note is None:
|
|
arp.rest(Duration.SIXTEENTH)
|
|
else:
|
|
arp.add(note, Duration.SIXTEENTH, velocity=80)
|
|
|
|
# Bars 65-96: full energy, arp singing
|
|
for _ in range(8):
|
|
for note in arp_pattern:
|
|
if note is None:
|
|
arp.rest(Duration.SIXTEENTH)
|
|
else:
|
|
arp.add(note, Duration.SIXTEENTH, velocity=90)
|
|
|
|
# Bars 97-112: filtering down
|
|
for _ in range(4):
|
|
for note in arp_pattern:
|
|
if note is None:
|
|
arp.rest(Duration.SIXTEENTH)
|
|
else:
|
|
arp.add(note, Duration.SIXTEENTH, velocity=70)
|
|
|
|
# Bars 113-128: dissolving
|
|
for rep in range(4):
|
|
vel = max(25, 60 - rep * 10)
|
|
for note in arp_pattern:
|
|
if note is None:
|
|
arp.rest(Duration.SIXTEENTH)
|
|
else:
|
|
arp.add(note, Duration.SIXTEENTH, velocity=vel)
|
|
|
|
# ── PAD — supersaw atmosphere, builds imperceptibly ─────────────
|
|
pad = score.part("pad", synth="supersaw", envelope="pad", volume=0.12,
|
|
reverb=0.75, reverb_type="taj_mahal",
|
|
chorus=0.4, chorus_rate=0.2,
|
|
chorus_depth=0.01, lowpass=600)
|
|
pad.lfo("lowpass", rate=0.008, min=400, max=5000, bars=128, shape="triangle")
|
|
|
|
# Bars 1-16: dark, barely there
|
|
for _ in range(4):
|
|
for chord in prog:
|
|
pad.add(chord, Duration.WHOLE, velocity=40)
|
|
|
|
# Bars 17-48: slowly swelling
|
|
for _ in range(8):
|
|
for chord in prog:
|
|
pad.add(chord, Duration.WHOLE, velocity=55)
|
|
|
|
# Bars 49-96: full, warm
|
|
for _ in range(12):
|
|
for chord in prog:
|
|
pad.add(chord, Duration.WHOLE, velocity=70)
|
|
|
|
# Bars 97-128: fading
|
|
for rep in range(8):
|
|
vel = max(20, 65 - rep * 6)
|
|
for chord in prog:
|
|
pad.add(chord, Duration.WHOLE, velocity=vel)
|
|
|
|
# ── BASS — enters with the kick, bar 49 ────────────────────────
|
|
bass = score.part("bass", synth="saw", envelope="pluck", volume=0.35,
|
|
lowpass=250, distortion=0.15, distortion_drive=2.5,
|
|
sub_osc=0.5, sidechain=0.3)
|
|
|
|
# Bars 1-48: silence
|
|
for _ in range(48):
|
|
bass.rest(Duration.WHOLE)
|
|
|
|
# Bars 49-96: the groove
|
|
bass_line = [
|
|
(F.add(-24), Duration.EIGHTH), (None, Duration.EIGHTH),
|
|
(F.add(-24), Duration.EIGHTH), (None, Duration.EIGHTH),
|
|
(F.add(-24), Duration.EIGHTH), (None, Duration.QUARTER),
|
|
(Ab.add(-24), Duration.EIGHTH),
|
|
]
|
|
for _ in range(12):
|
|
for note, dur in bass_line:
|
|
if note is None:
|
|
bass.rest(dur)
|
|
else:
|
|
bass.add(note, dur, velocity=100)
|
|
|
|
# Bars 97-128: fading
|
|
for rep in range(8):
|
|
vel = max(20, 90 - rep * 10)
|
|
for note, dur in bass_line:
|
|
if note is None:
|
|
bass.rest(dur)
|
|
else:
|
|
bass.add(note, dur, velocity=vel)
|
|
|
|
# ── KICK — the Strobe moment, enters bar 49 ────────────────────
|
|
kick = score.part("kick", volume=0.6, humanize=0.03)
|
|
|
|
# Bars 1-48: no kick — this IS the point
|
|
for _ in range(48):
|
|
kick.rest(Duration.WHOLE)
|
|
|
|
# Bars 49-96: four on the floor — the release
|
|
for _ in range(48):
|
|
for beat in range(4):
|
|
kick.hit(K, Duration.QUARTER, velocity=115)
|
|
|
|
# Bars 97-112: kick continues, stable
|
|
for _ in range(16):
|
|
for beat in range(4):
|
|
kick.hit(K, Duration.QUARTER, velocity=108)
|
|
|
|
# Bars 113-128: kick fades last — the final heartbeat
|
|
for bar in range(16):
|
|
vel = max(25, 105 - bar * 5)
|
|
for beat in range(4):
|
|
kick.hit(K, Duration.QUARTER, velocity=vel)
|
|
|
|
# ── CLAP — with the kick ───────────────────────────────────────
|
|
clap = score.part("clap", volume=0.3, reverb=0.15, humanize=0.04)
|
|
|
|
# Bars 1-48: silence
|
|
for _ in range(48):
|
|
clap.rest(Duration.WHOLE)
|
|
|
|
# Bars 49-96: 2 and 4
|
|
for _ in range(48):
|
|
clap.rest(Duration.QUARTER)
|
|
clap.hit(CL, Duration.QUARTER, velocity=95)
|
|
clap.rest(Duration.QUARTER)
|
|
clap.hit(CL, Duration.QUARTER, velocity=98)
|
|
|
|
# Bars 97-128: fading
|
|
for bar in range(32):
|
|
vel = max(20, 95 - bar * 2)
|
|
clap.rest(Duration.QUARTER)
|
|
clap.hit(CL, Duration.QUARTER, velocity=vel)
|
|
clap.rest(Duration.QUARTER)
|
|
clap.hit(CL, Duration.QUARTER, velocity=vel)
|
|
|
|
# ── HATS — offbeat, with the kick ──────────────────────────────
|
|
hats = score.part("hats", volume=0.25, humanize=0.04)
|
|
|
|
# Bars 1-48: silence
|
|
for _ in range(48):
|
|
hats.rest(Duration.WHOLE)
|
|
|
|
# Bars 49-96: offbeat 8ths
|
|
for _ in range(48):
|
|
for beat in range(4):
|
|
hats.rest(Duration.EIGHTH)
|
|
hats.hit(CH, Duration.EIGHTH, velocity=70)
|
|
|
|
# Bars 97-128: fading
|
|
for bar in range(32):
|
|
vel = max(20, 70 - bar * 2)
|
|
for beat in range(4):
|
|
hats.rest(Duration.EIGHTH)
|
|
hats.hit(CH, Duration.EIGHTH, velocity=vel)
|
|
|
|
# ── NES MELODY — emotional square wave, the heart of the peak ───
|
|
nes = score.part("nes", synth="square", envelope="organ", volume=0.18,
|
|
reverb=0.5, reverb_type="taj_mahal",
|
|
delay=0.35, delay_time=0.234, delay_feedback=0.45,
|
|
lowpass=4000, pan=-0.1, humanize=0.05)
|
|
|
|
# Bars 1-64: silence
|
|
for _ in range(64):
|
|
nes.rest(Duration.WHOLE)
|
|
|
|
# Bars 65-80: the emotional peak — simple, singing melody
|
|
nes_melody = [
|
|
(F.add(12), Duration.HALF, 85),
|
|
(Eb.add(12), Duration.QUARTER, 80),
|
|
(C.add(12), Duration.QUARTER, 78),
|
|
(Db.add(12), Duration.HALF, 82),
|
|
(C.add(12), Duration.HALF, 80),
|
|
(Ab, Duration.HALF, 78),
|
|
(Bb, Duration.QUARTER, 75),
|
|
(C.add(12), Duration.QUARTER, 80),
|
|
(F.add(12), Duration.WHOLE, 88),
|
|
]
|
|
for _ in range(4):
|
|
for note, dur, vel in nes_melody:
|
|
nes.add(note, dur, velocity=vel)
|
|
|
|
# Bars 81-96: melody fades
|
|
for rep in range(2):
|
|
vel_off = rep * 15
|
|
for note, dur, vel in nes_melody:
|
|
nes.add(note, dur, velocity=max(25, vel - vel_off))
|
|
for _ in range(8):
|
|
nes.rest(Duration.WHOLE)
|
|
|
|
# Bars 97-128: silence
|
|
for _ in range(32):
|
|
nes.rest(Duration.WHOLE)
|
|
|
|
# ── PLUCK STABS — offbeat chords, bars 65-96 ───────────────────
|
|
pluck = score.part("pluck", synth="saw", envelope="pluck", volume=0.18,
|
|
reverb=0.25, delay=0.25, delay_time=0.234,
|
|
delay_feedback=0.35, lowpass=2500, detune=8,
|
|
pan=0.25)
|
|
|
|
# Bars 1-64: silence
|
|
for _ in range(64):
|
|
pluck.rest(Duration.WHOLE)
|
|
|
|
# Bars 65-96: the peak — offbeat stabs
|
|
for _ in range(8):
|
|
for chord in prog:
|
|
pluck.rest(Duration.EIGHTH)
|
|
pluck.add(chord, Duration.EIGHTH, velocity=85)
|
|
pluck.rest(Duration.QUARTER)
|
|
pluck.rest(Duration.EIGHTH)
|
|
pluck.add(chord, Duration.EIGHTH, velocity=80)
|
|
pluck.rest(Duration.QUARTER)
|
|
|
|
# Bars 97-128: silence
|
|
for _ in range(32):
|
|
pluck.rest(Duration.WHOLE)
|
|
|
|
# ═════════════════════════════════════════════════════════════════
|
|
import sys
|
|
|
|
print(f"Key: {key}")
|
|
print(f"BPM: 128")
|
|
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 GHOST PROTOCOL (live engine)...")
|
|
from pytheory_live.live import LiveEngine
|
|
engine = LiveEngine(buffer_size=1024)
|
|
engine.play_score(score)
|
|
else:
|
|
print("Playing GHOST PROTOCOL...")
|
|
play_score(score)
|