Files
interpretations/tracks/intrusive.py
T
kennethreitz 04d13255e1 Revisit 6 tracks with new pytheory 0.40.9 synths
Ghost Protocol: arp → drift (analog wobble)
Deep Time: shimmer → drift, added granular_pad grain layer
Voltage: added hard_sync part at bar 49
The Interruption: reese bass → drift (analog menace)
An Exception Occurred: added ring_mod during psychosis
Intrusive: thought → wavefold with organ envelope (uglier, harder)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 13:34:15 -04:00

333 lines
13 KiB
Python

"""
INTRUSIVE — the thought you can't stop thinking.
One phrase. Over and over. You try to play something else.
It comes back. It always comes back.
Until you learn to let it pass.
Bb minor, 92 BPM.
"""
from pytheory import Key, Duration, Score, Tone, play_score
from pytheory.rhythm import DrumSound
key = Key("Bb", "minor")
s = key.scale # Bb C Db Eb F Gb Ab
Bb = s[0]; C = s[1]; Db = s[2]; Eb = s[3]
F = s[4]; Gb = s[5]; Ab = s[6]
score = Score("4/4", bpm=92)
# ═══════════════════════════════════════════════════════════════════
# STRUCTURE (56 bars, ~2:26):
# Bars 1-8: THE THOUGHT — one piano phrase, repeating
# Bars 9-16: TRYING — Rhodes plays something different. The thought returns.
# Bars 17-24: FIGHTING — drums try to drown it. The thought is louder.
# Bars 25-32: SPIRALING — the thought fragments, multiplies, distorts
# Bars 33-40: ACCEPTING — stop fighting. Let it play. Something changes.
# Bars 41-48: RELEASING — the thought slows down. Spaces between.
# Bars 49-56: PASSING — it plays once more. Quiet. And doesn't come back.
# ═══════════════════════════════════════════════════════════════════
# The intrusive thought — this exact phrase, burned into your brain
THOUGHT = [
(Bb, Duration.EIGHTH, 72), (Db, Duration.EIGHTH, 68),
(F, Duration.QUARTER, 75), (Eb, Duration.EIGHTH, 65),
(Db, Duration.EIGHTH, 62), (Bb, Duration.QUARTER, 70),
]
def play_thought(part, vel_offset=0, rest_after=Duration.WHOLE):
"""The thought. Always the same. Always unwanted."""
for note, dur, vel in THOUGHT:
part.add(note, dur, velocity=min(127, max(15, vel + vel_offset)))
part.rest(rest_after)
# ── THE THOUGHT — piano, the thing you can't unthink ──────────
thought = score.part("thought", synth="wavefold", envelope="organ", volume=0.4,
lowpass=3000, distortion=0.1, distortion_drive=1.5,
saturation=0.4, legato=True, glide=0.03,
reverb=0.25, reverb_type="spring",
delay=0.12, delay_time=0.326, delay_feedback=0.15,
pan=-0.1, humanize=0.06)
# Bars 1-8: repeating. and repeating. and repeating.
for _ in range(4):
play_thought(thought)
# Bars 9-12: it keeps going while Rhodes tries
for _ in range(2):
play_thought(thought)
# Bars 13-16: louder — you noticed it and now it's worse
for _ in range(2):
play_thought(thought, vel_offset=8)
# Bars 17-20: drums enter, thought persists
for _ in range(2):
play_thought(thought, vel_offset=5)
# Bars 21-24: even louder — fighting it makes it stronger
thought.set(volume=0.6)
for _ in range(2):
play_thought(thought, vel_offset=12)
# Bars 25-28: SPIRALING — the thought starts fragmenting, distorting
thought.set(volume=0.55, reverb=0.5)
# Fragment 1: just the first three notes, repeated
for _ in range(3):
thought.add(Bb, Duration.EIGHTH, velocity=78)
thought.add(Db, Duration.EIGHTH, velocity=72)
thought.add(F, Duration.QUARTER, velocity=80)
thought.rest(Duration.QUARTER)
# Fragment 2: the ending, wrong rhythm
thought.add(Eb, Duration.SIXTEENTH, velocity=70)
thought.add(Db, Duration.SIXTEENTH, velocity=65)
thought.add(Bb, Duration.EIGHTH, velocity=72)
thought.rest(Duration.HALF)
thought.add(Eb, Duration.SIXTEENTH, velocity=72)
thought.add(Db, Duration.SIXTEENTH, velocity=68)
thought.add(Bb, Duration.EIGHTH, velocity=75)
thought.rest(Duration.QUARTER)
thought.add(F, Duration.QUARTER, velocity=78)
# Bars 29-32: the thought in wrong octaves, displaced
thought.add(Bb.add(12), Duration.EIGHTH, velocity=75)
thought.add(Db.add(12), Duration.EIGHTH, velocity=70)
thought.add(F.add(12), Duration.QUARTER, velocity=78)
thought.add(Eb, Duration.EIGHTH, velocity=62)
thought.add(Db, Duration.EIGHTH, velocity=58)
thought.add(Bb.add(-12), Duration.QUARTER, velocity=68)
thought.rest(Duration.HALF)
# Overlapping — like it's echoing inside your skull
thought.add(Bb, Duration.EIGHTH, velocity=70)
thought.add(Db, Duration.EIGHTH, velocity=65)
thought.add(F, Duration.QUARTER, velocity=72)
thought.add(Bb.add(12), Duration.EIGHTH, velocity=68)
thought.add(Db.add(12), Duration.EIGHTH, velocity=62)
thought.add(F, Duration.QUARTER, velocity=70)
thought.rest(Duration.WHOLE)
# Bars 33-40: ACCEPTING — stop fighting. The thought plays but softer.
thought.set(volume=0.4, reverb=0.3)
for _ in range(2):
play_thought(thought, vel_offset=-5)
# Slower now — gaps between repetitions
play_thought(thought, vel_offset=-10, rest_after=Duration.WHOLE)
thought.rest(Duration.WHOLE)
play_thought(thought, vel_offset=-15, rest_after=Duration.WHOLE)
thought.rest(Duration.WHOLE)
# Bars 41-48: RELEASING — more space, softer each time
thought.set(volume=0.3)
play_thought(thought, vel_offset=-20, rest_after=Duration.WHOLE)
thought.rest(Duration.WHOLE)
thought.rest(Duration.WHOLE)
play_thought(thought, vel_offset=-28, rest_after=Duration.WHOLE)
thought.rest(Duration.WHOLE)
thought.rest(Duration.WHOLE)
thought.rest(Duration.WHOLE)
# Bars 49-52: PASSING — one last time. Barely there.
thought.set(volume=0.2)
play_thought(thought, vel_offset=-40, rest_after=Duration.WHOLE)
# Bars 53-56: silence. it passed.
for _ in range(4):
thought.rest(Duration.WHOLE)
# ── RHODES — what you're trying to think instead ──────────────
rhodes = score.part("rhodes", instrument="electric_piano", volume=0.35,
reverb=0.45, reverb_type="taj_mahal",
delay=0.12, delay_time=0.326, delay_feedback=0.15,
tremolo_depth=0.1, tremolo_rate=2.5,
pan=0.2, humanize=0.1)
for _ in range(8):
rhodes.rest(Duration.WHOLE)
# Bars 9-12: tries to play its own melody — interrupted
rhodes.add(Db, Duration.QUARTER, velocity=60)
rhodes.add(Eb, Duration.QUARTER, velocity=55)
rhodes.add(F, Duration.HALF, velocity=62)
# interrupted — rest while thought plays
rhodes.rest(Duration.WHOLE)
rhodes.add(Ab, Duration.QUARTER, velocity=58)
rhodes.add(Gb, Duration.QUARTER, velocity=55)
rhodes.add(F, Duration.HALF, velocity=60)
rhodes.rest(Duration.WHOLE)
# Bars 13-16: tries again — longer phrases
rhodes.add(Db, Duration.QUARTER, velocity=62)
rhodes.add(Eb, Duration.QUARTER, velocity=58)
rhodes.add(F, Duration.QUARTER, velocity=65)
rhodes.add(Ab, Duration.QUARTER, velocity=60)
rhodes.add(Gb, Duration.HALF, velocity=62)
rhodes.add(F, Duration.HALF, velocity=58)
rhodes.rest(Duration.WHOLE)
rhodes.rest(Duration.WHOLE)
# Bars 17-24: keeps trying through the drums
for _ in range(2):
rhodes.add(Db, Duration.QUARTER, velocity=58)
rhodes.add(Eb, Duration.QUARTER, velocity=55)
rhodes.add(F, Duration.HALF, velocity=60)
rhodes.add(Ab, Duration.QUARTER, velocity=55)
rhodes.add(Gb, Duration.EIGHTH, velocity=50)
rhodes.add(F, Duration.EIGHTH, velocity=48)
rhodes.add(Eb, Duration.HALF, velocity=55)
rhodes.rest(Duration.WHOLE)
rhodes.rest(Duration.WHOLE)
# Bars 25-32: gives up — sparse, defeated
for _ in range(4):
rhodes.add(Db, Duration.QUARTER, velocity=45)
rhodes.rest(Duration.DOTTED_HALF)
rhodes.rest(Duration.WHOLE)
# Bars 33-40: acceptance — Rhodes and thought coexist
rhodes.set(volume=0.4)
prog_chords = key.progression("i", "VI", "iv", "v")
for _ in range(2):
for chord in prog_chords:
rhodes.add(chord, Duration.WHOLE, velocity=52)
# Bars 41-48: Rhodes grows — it's winning, gently
rhodes.set(volume=0.5)
rhodes.add(Db, Duration.QUARTER, velocity=65)
rhodes.add(Eb, Duration.QUARTER, velocity=60)
rhodes.add(F, Duration.HALF, velocity=68)
rhodes.add(Ab, Duration.QUARTER, velocity=62)
rhodes.add(Gb, Duration.EIGHTH, velocity=58)
rhodes.add(F, Duration.EIGHTH, velocity=55)
rhodes.add(Eb, Duration.HALF, velocity=60)
rhodes.rest(Duration.QUARTER)
rhodes.add(Bb.add(-12), Duration.QUARTER, velocity=55)
rhodes.add(Db, Duration.QUARTER, velocity=60)
rhodes.add(F, Duration.QUARTER, velocity=58)
rhodes.add(Ab, Duration.HALF, velocity=65)
rhodes.add(Gb, Duration.HALF, velocity=60)
rhodes.add(F, Duration.WHOLE, velocity=62)
rhodes.rest(Duration.WHOLE)
# Bars 49-56: Rhodes owns the space now — peaceful
rhodes.add(Db, Duration.QUARTER, velocity=62)
rhodes.add(Eb, Duration.QUARTER, velocity=58)
rhodes.add(F, Duration.HALF, velocity=65)
rhodes.add(Eb, Duration.QUARTER, velocity=58)
rhodes.add(Db, Duration.QUARTER, velocity=55)
rhodes.add(Bb.add(-12), Duration.HALF, velocity=60)
rhodes.add(Bb.add(-12), Duration.WHOLE, velocity=55)
for _ in range(5):
rhodes.rest(Duration.WHOLE)
# ── DRUMS — trying to drown the thought, bars 17-32 ──────────
K = DrumSound.KICK
S = DrumSound.SNARE
CH = DrumSound.CLOSED_HAT
drums = score.part("drums", volume=0.35, humanize=0.06,
reverb=0.2, reverb_decay=0.8,
delay=0.08, delay_time=0.326, delay_feedback=0.12,
pan=-0.05)
for _ in range(16):
drums.rest(Duration.WHOLE)
# Bars 17-24: trying to overpower it
for _ in range(8):
drums.hit(K, Duration.QUARTER, velocity=90)
drums.hit(CH, Duration.EIGHTH, velocity=55)
drums.hit(CH, Duration.EIGHTH, velocity=42)
drums.hit(S, Duration.QUARTER, velocity=88)
drums.hit(CH, Duration.EIGHTH, velocity=50)
drums.hit(K, Duration.EIGHTH, velocity=78)
# Bars 25-32: drums get frantic — can't overpower it
for bar in range(8):
if bar % 4 == 3:
# Frustrated fill
for i in range(8):
drums.hit(S, Duration.SIXTEENTH, velocity=min(100, 60 + i * 5))
drums.hit(K, Duration.HALF, velocity=95)
else:
drums.hit(K, Duration.QUARTER, velocity=95)
drums.hit(CH, Duration.SIXTEENTH, velocity=58)
drums.hit(CH, Duration.SIXTEENTH, velocity=42)
drums.hit(CH, Duration.EIGHTH, velocity=50)
drums.hit(S, Duration.QUARTER, velocity=92)
drums.hit(CH, Duration.EIGHTH, velocity=48)
drums.hit(K, Duration.EIGHTH, velocity=82)
# Bars 33-40: drums soften — stop fighting
for vel in [75, 68, 60, 52, 45, 38, 30, 22]:
drums.hit(K, Duration.QUARTER, velocity=vel)
drums.rest(Duration.QUARTER)
drums.hit(S, Duration.QUARTER, velocity=max(15, vel - 8))
drums.rest(Duration.QUARTER)
# Bars 41-56: gone — no need to fight anymore
for _ in range(16):
drums.rest(Duration.WHOLE)
# ── CELLO — enters at acceptance, warmth ───────────────────────
cello = score.part("cello", instrument="cello", volume=0.15,
reverb=0.4, reverb_type="cathedral",
delay=0.08, delay_time=0.326, delay_feedback=0.1,
pan=0.25, humanize=0.08)
for _ in range(32):
cello.rest(Duration.WHOLE)
# Bars 33-48: long tones — acceptance has weight
for note, vel in [(Bb.add(-12), 38), (Bb.add(-12), 42),
(Db, 40), (Eb, 42),
(F, 45), (Eb, 42),
(Db, 40), (Bb.add(-12), 45),
(Bb.add(-12), 48), (Db, 45),
(F, 48), (Eb, 45),
(Db, 42), (Bb.add(-12), 48),
(Bb.add(-12), 42), (Bb.add(-12), 35)]:
cello.add(note, Duration.WHOLE, velocity=vel)
# Bars 49-56: fading
for vel in [32, 25, 18, 12, 8, 0, 0, 0]:
if vel > 0:
cello.add(Bb.add(-12), Duration.WHOLE, velocity=vel)
else:
cello.rest(Duration.WHOLE)
# ── SUB — the weight of acceptance, bars 33 onward ────────────
sub = score.part("sub", synth="sine", envelope="pad", volume=0.6,
lowpass=180, distortion=0.15, distortion_drive=2.5,
sub_osc=0.5, sidechain=0.3)
for _ in range(32):
sub.rest(Duration.WHOLE)
# Bars 33-48: enters with acceptance — the ground beneath you
roots = [Bb.add(-24), Gb.add(-24), Eb.add(-24), F.add(-24)]
for _ in range(4):
for root in roots:
sub.add(root, Duration.WHOLE, velocity=35)
# Bars 49-56: one long Bb — settling
for vel in [35, 32, 28, 25, 20, 15, 10, 5]:
sub.add(Bb.add(-24), Duration.WHOLE, velocity=vel)
# ═════════════════════════════════════════════════════════════════
import sys
print(f"Key: {key}")
print(f"BPM: 92")
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 INTRUSIVE (live engine)...")
from pytheory_live.live import LiveEngine
engine = LiveEngine(buffer_size=1024)
engine.play_score(score)
else:
print("Playing INTRUSIVE...")
play_score(score)