mirror of
https://github.com/kennethreitz/interpretations.git
synced 2026-06-05 23:00:19 +00:00
860dc1f323
D minor, shruti just intonation, 75 BPM. Kalimba blips, Rhodes chords, sitar hook with microtonal bends, mellotron flute pad, tambura Sa-Pa drone, lazy boom bap with ghost snares, 808 sub, vinyl crackle. Singing bowl bookends. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
365 lines
13 KiB
Python
365 lines
13 KiB
Python
"""
|
|
SHRUTI LOFI — 22-tone microtonal lo-fi hip hop.
|
|
The pitch is slightly wrong. That's the point.
|
|
Like a tape copied so many times the intervals drifted.
|
|
D minor, shruti tuning, 75 BPM.
|
|
"""
|
|
|
|
from pytheory import Key, Duration, Score, Tone, play_score
|
|
from pytheory.rhythm import DrumSound
|
|
|
|
key = Key("D", "minor")
|
|
s = key.scale # 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=75, system="shruti", temperament="just")
|
|
|
|
K = DrumSound.KICK
|
|
S = DrumSound.SNARE
|
|
CH = DrumSound.CLOSED_HAT
|
|
OH = DrumSound.OPEN_HAT
|
|
|
|
prog = key.progression("i", "VII", "VI", "iv")
|
|
|
|
# ═══════════════════════════════════════════════════════════════════
|
|
# STRUCTURE (64 bars, ~3:25):
|
|
# Bars 1-8: Vinyl + kalimba — warm, wrong, beautiful
|
|
# Bars 9-16: Rhodes enters — microtonal chords
|
|
# Bars 17-24: Drums — lazy boom bap
|
|
# Bars 25-32: Sitar — the sample flip, shruti intervals
|
|
# Bars 33-40: Mellotron pad — tape on tape
|
|
# Bars 41-48: Everything together — the lo-fi dream
|
|
# Bars 49-56: Breakdown — just kalimba + 808
|
|
# Bars 57-64: Returns soft, fades
|
|
# ═══════════════════════════════════════════════════════════════════
|
|
|
|
# ── VINYL — the crackle, always there ─────────────────────────
|
|
vinyl = score.part("vinyl", synth="noise", envelope="pad", volume=0.035,
|
|
lowpass=1800, highpass=500,
|
|
distortion=0.35, distortion_drive=2.5,
|
|
saturation=0.5, pan=0.1)
|
|
|
|
for _ in range(64):
|
|
vinyl.add(D, Duration.WHOLE, velocity=22)
|
|
|
|
# ── KALIMBA — the seed, microtonal blips ──────────────────────
|
|
kal = score.part("kalimba", instrument="kalimba", volume=0.4,
|
|
reverb=0.3, reverb_type="taj_mahal",
|
|
delay=0.15, delay_time=0.4, delay_feedback=0.25,
|
|
lowpass=3000,
|
|
pan=-0.2, humanize=0.12)
|
|
|
|
# Bars 1-8: alone — the shruti intervals are audible, warm
|
|
kal_a = [
|
|
(D, Duration.EIGHTH, 68), (None, Duration.EIGHTH, 0),
|
|
(F, Duration.EIGHTH, 60), (A, Duration.EIGHTH, 62),
|
|
(None, Duration.EIGHTH, 0), (F, Duration.EIGHTH, 55),
|
|
(D, Duration.EIGHTH, 65), (None, Duration.EIGHTH, 0),
|
|
]
|
|
kal_b = [
|
|
(C, Duration.EIGHTH, 62), (None, Duration.EIGHTH, 0),
|
|
(D, Duration.EIGHTH, 58), (F, Duration.EIGHTH, 60),
|
|
(E, Duration.EIGHTH, 55), (None, Duration.EIGHTH, 0),
|
|
(D, Duration.EIGHTH, 62), (None, Duration.EIGHTH, 0),
|
|
]
|
|
for _ in range(4):
|
|
for note, dur, vel in kal_a:
|
|
if note is None:
|
|
kal.rest(dur)
|
|
else:
|
|
kal.add(note, dur, velocity=vel)
|
|
for note, dur, vel in kal_b:
|
|
if note is None:
|
|
kal.rest(dur)
|
|
else:
|
|
kal.add(note, dur, velocity=vel)
|
|
|
|
# Bars 9-48: continues — the heartbeat
|
|
for _ in range(20):
|
|
for note, dur, vel in kal_a:
|
|
if note is None:
|
|
kal.rest(dur)
|
|
else:
|
|
kal.add(note, dur, velocity=vel)
|
|
for note, dur, vel in kal_b:
|
|
if note is None:
|
|
kal.rest(dur)
|
|
else:
|
|
kal.add(note, dur, velocity=vel)
|
|
|
|
# Bars 49-56: breakdown — just kalimba, quieter
|
|
kal.set(volume=0.35)
|
|
for _ in range(4):
|
|
for note, dur, vel in kal_a:
|
|
if note is None:
|
|
kal.rest(dur)
|
|
else:
|
|
kal.add(note, dur, velocity=max(30, vel - 12))
|
|
for note, dur, vel in kal_b:
|
|
if note is None:
|
|
kal.rest(dur)
|
|
else:
|
|
kal.add(note, dur, velocity=max(30, vel - 12))
|
|
|
|
# Bars 57-64: fading
|
|
for rep in range(4):
|
|
off = rep * -10
|
|
for note, dur, vel in kal_a:
|
|
if note is None:
|
|
kal.rest(dur)
|
|
else:
|
|
kal.add(note, dur, velocity=max(18, vel + off - 12))
|
|
for note, dur, vel in kal_b:
|
|
if note is None:
|
|
kal.rest(dur)
|
|
else:
|
|
kal.add(note, dur, velocity=max(18, vel + off - 12))
|
|
|
|
# ── RHODES — microtonal chords, enters bar 9 ──────────────────
|
|
rhodes = score.part("rhodes", instrument="electric_piano", volume=0.3,
|
|
reverb=0.4, reverb_type="taj_mahal",
|
|
delay=0.1, delay_time=0.4, delay_feedback=0.2,
|
|
tremolo_depth=0.08, tremolo_rate=2.0,
|
|
lowpass=3000,
|
|
pan=0.2, humanize=0.1)
|
|
|
|
for _ in range(8):
|
|
rhodes.rest(Duration.WHOLE)
|
|
|
|
# Bars 9-48: sparse chords — the shruti tuning makes them shimmer
|
|
for section in range(10):
|
|
p = prog if section % 2 == 0 else key.progression("i", "v", "VI", "iv")
|
|
for chord in p:
|
|
rhodes.add(chord, Duration.EIGHTH, velocity=58)
|
|
rhodes.rest(Duration.DOTTED_QUARTER)
|
|
rhodes.rest(Duration.HALF)
|
|
|
|
# Bars 49-56: silent
|
|
for _ in range(8):
|
|
rhodes.rest(Duration.WHOLE)
|
|
|
|
# Bars 57-64: returns fading
|
|
for vel in [48, 42, 35, 28]:
|
|
for chord in prog:
|
|
rhodes.add(chord, Duration.EIGHTH, velocity=vel)
|
|
rhodes.rest(Duration.DOTTED_QUARTER)
|
|
rhodes.rest(Duration.HALF)
|
|
|
|
# ── 808 — warm, round, enters bar 9 ──────────────────────────
|
|
sub = score.part("808", synth="sine", envelope="pad", volume=0.5,
|
|
lowpass=180, distortion=0.12, distortion_drive=2.0,
|
|
sub_osc=0.4, sidechain=0.3)
|
|
|
|
for _ in range(8):
|
|
sub.rest(Duration.WHOLE)
|
|
|
|
# Bars 9-48: continuous wave, follows roots
|
|
roots = [D.add(-24), C.add(-24), Bb.add(-24), G.add(-24)]
|
|
for _ in range(10):
|
|
for root in roots:
|
|
sub.add(root, Duration.WHOLE, velocity=35)
|
|
|
|
# Bars 49-56: just the root — breakdown
|
|
for _ in range(8):
|
|
sub.add(D.add(-24), Duration.WHOLE, velocity=30)
|
|
|
|
# Bars 57-64: fading
|
|
for vel in [28, 25, 22, 18, 14, 10, 5, 0]:
|
|
if vel > 0:
|
|
sub.add(D.add(-24), Duration.WHOLE, velocity=vel)
|
|
else:
|
|
sub.rest(Duration.WHOLE)
|
|
|
|
# ── DRUMS — lazy boom bap, enters bar 17 ──────────────────────
|
|
kick = score.part("kick", volume=0.55, humanize=0.06,
|
|
reverb=0.1, lowpass=5000)
|
|
snare = score.part("snare", volume=0.35, humanize=0.06,
|
|
reverb=0.2, reverb_decay=1.0,
|
|
delay=0.06, delay_time=0.4, delay_feedback=0.1,
|
|
pan=0.05)
|
|
hats = score.part("hats", volume=0.2, pan=0.15, humanize=0.06)
|
|
|
|
for _ in range(16):
|
|
kick.rest(Duration.WHOLE)
|
|
snare.rest(Duration.WHOLE)
|
|
hats.rest(Duration.WHOLE)
|
|
|
|
# Bars 17-48: lo-fi beat — lazy, behind the grid
|
|
for _ in range(32):
|
|
# Kick on 1 and and-of-2
|
|
kick.hit(K, Duration.QUARTER, velocity=92)
|
|
kick.rest(Duration.EIGHTH)
|
|
kick.hit(K, Duration.EIGHTH, velocity=75)
|
|
kick.rest(Duration.QUARTER)
|
|
kick.rest(Duration.QUARTER)
|
|
|
|
# Snare on 2 and 4 with ghost before
|
|
snare.rest(Duration.EIGHTH)
|
|
snare.hit(S, Duration.SIXTEENTH, velocity=35)
|
|
snare.rest(Duration.SIXTEENTH)
|
|
snare.hit(S, Duration.QUARTER, velocity=85)
|
|
snare.rest(Duration.EIGHTH)
|
|
snare.hit(S, Duration.SIXTEENTH, velocity=32)
|
|
snare.rest(Duration.SIXTEENTH)
|
|
snare.hit(S, Duration.QUARTER, velocity=88)
|
|
|
|
# Hats — lazy 8ths, some louder
|
|
hats.hit(CH, Duration.EIGHTH, velocity=58)
|
|
hats.hit(CH, Duration.EIGHTH, velocity=35)
|
|
hats.hit(CH, Duration.EIGHTH, velocity=52)
|
|
hats.hit(CH, Duration.EIGHTH, velocity=38)
|
|
hats.hit(CH, Duration.EIGHTH, velocity=55)
|
|
hats.hit(CH, Duration.EIGHTH, velocity=32)
|
|
hats.hit(OH, Duration.EIGHTH, velocity=48)
|
|
hats.hit(CH, Duration.EIGHTH, velocity=35)
|
|
|
|
# Bars 49-56: breakdown — half time
|
|
for _ in range(8):
|
|
kick.hit(K, Duration.HALF, velocity=82)
|
|
kick.rest(Duration.HALF)
|
|
snare.rest(Duration.HALF)
|
|
snare.hit(S, Duration.HALF, velocity=72)
|
|
hats.rest(Duration.WHOLE)
|
|
|
|
# Bars 57-64: returns fading
|
|
for vel in [80, 72, 62, 52, 40, 28, 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 - 8))
|
|
snare.rest(Duration.HALF)
|
|
hats.hit(CH, Duration.QUARTER, velocity=max(15, vel - 25))
|
|
hats.rest(Duration.DOTTED_HALF)
|
|
else:
|
|
kick.rest(Duration.WHOLE)
|
|
snare.rest(Duration.WHOLE)
|
|
hats.rest(Duration.WHOLE)
|
|
|
|
# ── SITAR — the sample flip, enters bar 25 ────────────────────
|
|
sitar = score.part("sitar", instrument="sitar", volume=0.4,
|
|
reverb=0.2, reverb_type="taj_mahal",
|
|
delay=0.12, delay_time=0.4, delay_feedback=0.2,
|
|
lowpass=3500,
|
|
pan=-0.25, humanize=0.1)
|
|
|
|
for _ in range(24):
|
|
sitar.rest(Duration.WHOLE)
|
|
|
|
# Bars 25-32: the hook — shruti intervals make it haunting
|
|
sitar_hook = [
|
|
(D, Duration.QUARTER, 72, -0.1), (F, Duration.EIGHTH, 65, 0.0),
|
|
(E, Duration.EIGHTH, 62, 0.0), (D, Duration.HALF, 68, -0.08),
|
|
(None, Duration.QUARTER, 0, 0.0), (A.add(-12), Duration.QUARTER, 60, 0.1),
|
|
(D, Duration.QUARTER, 68, 0.0), (F, Duration.QUARTER, 65, -0.1),
|
|
(G, Duration.HALF, 70, 0.0), (F, Duration.HALF, 65, -0.08),
|
|
]
|
|
for _ in range(4):
|
|
for note, dur, vel, bend in sitar_hook:
|
|
if note is None:
|
|
sitar.rest(dur)
|
|
else:
|
|
sitar.add(note, dur, velocity=vel, bend=bend)
|
|
|
|
# Bars 33-48: continues under mellotron
|
|
for _ in range(8):
|
|
for note, dur, vel, bend in sitar_hook:
|
|
if note is None:
|
|
sitar.rest(dur)
|
|
else:
|
|
sitar.add(note, dur, velocity=max(35, vel - 8), bend=bend)
|
|
|
|
# Bars 49-56: one note — breathing
|
|
sitar.add(D, Duration.WHOLE, velocity=55, bend=-0.1)
|
|
sitar.rest(Duration.WHOLE)
|
|
sitar.add(A.add(-12), Duration.WHOLE, velocity=48, bend=0.08)
|
|
sitar.rest(Duration.WHOLE)
|
|
sitar.rest(Duration.WHOLE)
|
|
sitar.rest(Duration.WHOLE)
|
|
sitar.rest(Duration.WHOLE)
|
|
sitar.rest(Duration.WHOLE)
|
|
|
|
# Bars 57-64: hook returns fading
|
|
for vel in [58, 50, 42, 35]:
|
|
for note, dur, v, bend in sitar_hook:
|
|
if note is None:
|
|
sitar.rest(dur)
|
|
else:
|
|
sitar.add(note, dur, velocity=max(20, vel - 10), bend=bend)
|
|
|
|
# ── MELLOTRON FLUTE — tape pad, enters bar 33 ─────────────────
|
|
mello = score.part("mellotron", instrument="mellotron_flute", volume=0.2,
|
|
reverb=0.35, reverb_type="taj_mahal",
|
|
lowpass=2500,
|
|
pan=0.15, humanize=0.08)
|
|
|
|
for _ in range(32):
|
|
mello.rest(Duration.WHOLE)
|
|
|
|
# Bars 33-48: warm chords — tape warble + shruti = magic
|
|
for _ in range(4):
|
|
for chord in prog:
|
|
mello.add(chord, Duration.WHOLE, velocity=48)
|
|
|
|
# Bars 49-56: silent
|
|
for _ in range(8):
|
|
mello.rest(Duration.WHOLE)
|
|
|
|
# Bars 57-64: returns fading
|
|
for vel in [42, 35, 28, 22, 15, 10, 0, 0]:
|
|
if vel > 0:
|
|
mello.add(prog[0], Duration.WHOLE, velocity=vel)
|
|
else:
|
|
mello.rest(Duration.WHOLE)
|
|
|
|
# ── TAMBURA — the shruti drone, enters bar 9 ──────────────────
|
|
tambura = score.part("tambura", synth="sine", envelope="pad", volume=0.1,
|
|
reverb=0.35, reverb_type="taj_mahal",
|
|
chorus=0.3, chorus_rate=0.05, chorus_depth=0.01,
|
|
lowpass=800, pan=-0.1)
|
|
|
|
for _ in range(8):
|
|
tambura.rest(Duration.WHOLE)
|
|
|
|
# Sa-Pa drone — in shruti tuning the fifth is pure, not tempered
|
|
for _ in range(48):
|
|
tambura.add(D.add(-24), Duration.HALF, velocity=38)
|
|
tambura.add(A.add(-24), Duration.HALF, velocity=32)
|
|
|
|
for vel in [30, 25, 20, 15, 10, 5, 0, 0]:
|
|
if vel > 0:
|
|
tambura.add(D.add(-24), Duration.WHOLE, velocity=vel)
|
|
else:
|
|
tambura.rest(Duration.WHOLE)
|
|
|
|
# ── SINGING BOWL — just two strikes ───────────────────────────
|
|
bowl = score.part("bowl", instrument="singing_bowl", volume=0.25,
|
|
reverb=0.5, reverb_type="taj_mahal",
|
|
delay=0.12, delay_time=0.8, delay_feedback=0.15,
|
|
pan=0.2)
|
|
|
|
bowl.add(D.add(-24), Duration.WHOLE, velocity=55)
|
|
for _ in range(62):
|
|
bowl.rest(Duration.WHOLE)
|
|
bowl.add(D.add(-24), Duration.WHOLE, velocity=42)
|
|
|
|
# ═════════════════════════════════════════════════════════════════
|
|
import sys
|
|
|
|
print(f"Key: {key}")
|
|
print(f"System: shruti / just intonation")
|
|
print(f"BPM: 75")
|
|
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 SHRUTI LOFI (live engine)...")
|
|
from pytheory_live.live import LiveEngine
|
|
engine = LiveEngine(buffer_size=1024)
|
|
engine.play_score(score)
|
|
else:
|
|
print("Playing SHRUTI LOFI...")
|
|
play_score(score)
|