mirror of
https://github.com/kennethreitz/interpretations.git
synced 2026-06-05 23:00:19 +00:00
Add Shruti Lofi (track 23) — microtonal lo-fi hip hop
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>
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 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.
|
||||
- **Track 20: Music Box Factory** — G major, 108 BPM. Eight tuned percussion instruments stacking. Kalimba, vibraphone, celesta, marimba, glockenspiel, xylophone, crotales, timpani. Tubular bells.
|
||||
|
||||
@@ -39,6 +39,7 @@ Each track is a `.py` file. Run it to hear it.
|
||||
20. **Music Box Factory** — G major, 108 BPM. Eight tuned percussion instruments only. Kalimba, vibraphone, celesta, marimba, glockenspiel, xylophone, crotales, timpani. Tubular bells mark sections. No synths, no strings — just metal and wood.
|
||||
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.
|
||||
|
||||
## Usage
|
||||
|
||||
|
||||
@@ -61,6 +61,7 @@ ALBUM_ORDER = [
|
||||
"music_box_factory.py",
|
||||
"cathedral.py",
|
||||
"beast_mode.py",
|
||||
"shruti_lofi.py",
|
||||
]
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,364 @@
|
||||
"""
|
||||
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)
|
||||
Reference in New Issue
Block a user