diff --git a/tracks/monsoon_bass.py b/tracks/monsoon_bass.py new file mode 100644 index 0000000..d9b4933 --- /dev/null +++ b/tracks/monsoon_bass.py @@ -0,0 +1,240 @@ +""" +MONSOON BASS — Indian classical meets trip-hop. +Talvin Singh meets Massive Attack. Tabla over breakbeat, +sitar loops through delay fog, 808 sub holds it all down. +""" + +from pytheory import Key, Duration, Score, play_score +from pytheory.rhythm import DrumSound + +# ── Scale ──────────────────────────────────────────────────────── +key = Key("A", "phrygian") +s = key.scale # A Bb C D E F G + +Sa = s[0] # A (Sa) +Re = s[1] # Bb (komal Re) +Ga = s[2] # C (komal Ga) +Ma = s[3] # D (Ma) +Pa = s[4] # E (Pa) +Dha = s[5] # F (komal Dha) +Ni = s[6] # G (komal Ni) + +score = Score("4/4", bpm=98, system="shruti", temperament="just") + +# ── Tabla Bols ────────────────────────────────────────────────── +NA = DrumSound.TABLA_NA +TIT = DrumSound.TABLA_TIT +GE = DrumSound.TABLA_GE +tDHA = DrumSound.TABLA_DHA +KE = DrumSound.TABLA_KE +GEB = DrumSound.TABLA_GE_BEND + +# ── BREAKBEAT — the spine ─────────────────────────────────────── +score.drums("breakbeat", repeats=16, fill="bayan", fill_every=8) +score.set_drum_effects(reverb=0.2, reverb_decay=1.0, volume=0.35, humanize=0.12) + +# ── TABLA LAYER — rides on top of the breakbeat ───────────────── +tabla = score.part("tabla", volume=0.0, reverb=0.3, reverb_decay=1.2, + humanize=0.08) + +# Silent for 4 bars — let the beat establish +for _ in range(4): + tabla.rest(Duration.WHOLE) + +# Bars 5-16: looping tabla pattern (keherwa-influenced) +tabla.set(volume=0.35) +for _ in range(12): + 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=50) + tabla.hit(NA, Duration.EIGHTH, velocity=70) + tabla.hit(TIT, Duration.EIGHTH, velocity=45) + tabla.hit(tDHA, Duration.EIGHTH, velocity=88, articulation="accent") + tabla.hit(NA, Duration.EIGHTH, velocity=68) + +# ── 808 SUB BASS — deep, sustained, minimal ──────────────────── +sub = score.part("sub", synth="sine", envelope="pad", volume=0.55, + distortion=0.3, distortion_drive=4.0, + lowpass=160, lowpass_q=2.0) + +# Bars 1-4: just the root, establishing gravity +for _ in range(4): + sub.add(Sa.add(-24), Duration.HALF) # Sa low + sub.rest(Duration.QUARTER) + sub.add(Sa.add(-24), Duration.QUARTER) + +# Bars 5-16: minimal bass line, mostly Sa and Pa +for _ in range(3): + sub.add(Sa.add(-24), Duration.HALF) + sub.rest(Duration.QUARTER) + sub.add(Pa.add(-24), Duration.QUARTER) + sub.add(Sa.add(-24), Duration.HALF) + sub.rest(Duration.HALF) + sub.add(Ma.add(-24), Duration.HALF) + sub.rest(Duration.QUARTER) + sub.add(Sa.add(-24), Duration.QUARTER) + sub.add(Sa.add(-24), Duration.DOTTED_HALF) + sub.rest(Duration.QUARTER) + +# ── SITAR LOOP — hypnotic, delay-drenched ────────────────────── +sitar = score.part("sitar", instrument="sitar", volume=0.0, + reverb=0.5, reverb_type="taj_mahal", + delay=0.45, delay_time=0.306, delay_feedback=0.5, + pan=-0.2, saturation=0.2, humanize=0.08) + +# Silent for 2 bars +sitar.rest(Duration.WHOLE) +sitar.rest(Duration.WHOLE) + +# Bars 3-8: sparse, mysterious — notes float in delay +sitar.set(volume=0.4) +sitar_phrase_a = [ + (Sa, Duration.QUARTER, 85), + (None, Duration.EIGHTH, 0), + (Re, Duration.EIGHTH, 70), + (Ga, Duration.QUARTER, 90), + (None, Duration.QUARTER, 0), + # bar 2 + (Ma, Duration.EIGHTH, 80), + (Ga, Duration.EIGHTH, 70), + (Re, Duration.QUARTER, 75), + (Sa, Duration.QUARTER, 65), + (None, Duration.QUARTER, 0), +] +for _ in range(3): + for note, dur, vel in sitar_phrase_a: + if note is None: + sitar.rest(dur) + else: + sitar.add(note, dur, velocity=vel) + +# Bars 9-12: more active, higher register +sitar.set(volume=0.45) +sitar_phrase_b = [ + (Pa, Duration.EIGHTH, 100), + (Ma, Duration.EIGHTH, 80), + (Pa, Duration.QUARTER, 95), + (None, Duration.EIGHTH, 0), + (Dha, Duration.EIGHTH, 85), + (Pa, Duration.QUARTER, 90), + # bar 2 + (Ma, Duration.QUARTER, 80), + (Ga, Duration.EIGHTH, 70), + (Re, Duration.EIGHTH, 75), + (Sa, Duration.HALF, 85), +] +for _ in range(2): + for note, dur, vel in sitar_phrase_b: + if note is None: + sitar.rest(dur) + else: + sitar.add(note, dur, velocity=vel) + +# Bars 13-16: climax — faster, descending runs +sitar.set(volume=0.5) +sitar_phrase_c = [ + (Sa.add(12), Duration.EIGHTH, 115), + (Ni, Duration.EIGHTH, 100), + (Dha, Duration.EIGHTH, 90), + (Pa, Duration.EIGHTH, 85), + (Ma, Duration.QUARTER, 95), + (Ga, Duration.QUARTER, 80), + # bar 2 + (Re, Duration.EIGHTH, 75), + (Sa, Duration.EIGHTH, 70), + (Re, Duration.QUARTER, 80), + (Ma, Duration.QUARTER, 90), + (Sa, Duration.QUARTER, 75), + (None, Duration.QUARTER, 0), +] +for _ in range(2): + for note, dur, vel in sitar_phrase_c: + if note is None: + sitar.rest(dur) + else: + sitar.add(note, dur, velocity=vel) + +# ── PAD — dark, atmospheric wash ──────────────────────────────── +pad = score.part("pad", synth="supersaw", envelope="pad", volume=0.0, + reverb=0.7, reverb_type="taj_mahal", + chorus=0.4, chorus_rate=0.15, chorus_depth=0.01, + lowpass=1400, pan=0.1) +pad.lfo("lowpass", rate=0.0625, min=600, max=2000, bars=16, shape="triangle") + +# Silent for 4 bars +for _ in range(4): + pad.rest(Duration.WHOLE) + +# Bars 5-8: creep in +pad.set(volume=0.12) +pad.hold(Sa.add(-12), Duration.WHOLE * 4, velocity=50) +pad.add(Pa.add(-12), Duration.WHOLE, velocity=45) +pad.add(Ma.add(-12), Duration.WHOLE, velocity=50) +pad.add(Pa.add(-12), Duration.WHOLE, velocity=45) +pad.add(Sa.add(-12), Duration.WHOLE, velocity=50) + +# Bars 9-12: fuller +pad.set(volume=0.18) +pad.hold(Sa.add(-12), Duration.WHOLE * 4, velocity=60) +pad.hold(Pa.add(-12), Duration.WHOLE * 4, velocity=50) +pad.add(Ga, Duration.WHOLE, velocity=55) +pad.add(Re, Duration.WHOLE, velocity=50) +pad.add(Ga, Duration.WHOLE, velocity=55) +pad.add(Sa, Duration.WHOLE, velocity=60) + +# Bars 13-16: biggest +pad.set(volume=0.22) +pad.hold(Sa.add(-12), Duration.WHOLE * 4, velocity=65) +pad.hold(Pa.add(-12), Duration.WHOLE * 4, velocity=55) +pad.hold(Ga, Duration.WHOLE * 2, velocity=50) +pad.add(Ma, Duration.WHOLE, velocity=55) +pad.add(Ga, Duration.WHOLE, velocity=50) +pad.add(Re, Duration.WHOLE, velocity=55) +pad.add(Sa, Duration.WHOLE, velocity=60) + +# ── HI-HAT TEXTURE — 16th note shimmer ───────────────────────── +hats = score.part("hats", synth="noise", envelope="staccato", volume=0.0, + lowpass=8000, reverb=0.15) + +# Silent for 4 bars +for _ in range(4): + hats.rest(Duration.WHOLE) + +# Bars 5-16: subtle 16th note texture with accents +hats.set(volume=0.04) +for _ in range(12): + for beat in range(4): + hats.add("C5", Duration.SIXTEENTH, velocity=90) + hats.set(volume=0.02) + hats.add("C5", Duration.SIXTEENTH, velocity=60) + hats.add("C5", Duration.SIXTEENTH, velocity=55) + hats.set(volume=0.04) + hats.add("C5", Duration.SIXTEENTH, velocity=75) + +# ── TAMBURA — subtle drone, not as prominent as raga midnight ─── +tambura = score.part("tambura", synth="sine", envelope="pad", volume=0.2, + reverb=0.8, reverb_type="taj_mahal", + chorus=0.3, chorus_rate=0.08, chorus_depth=0.008, + lowpass=1000, pan=-0.3) + +for _ in range(16): + tambura.add(Sa.add(-24), Duration.HALF) + tambura.add(Pa.add(-24), Duration.HALF) + +# ═════════════════════════════════════════════════════════════════ +import sys + +print(f"Key: {key}") +print(f"Temperament: shruti / just intonation") +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 MONSOON BASS (live engine)...") + from pytheory_live.live import LiveEngine + engine = LiveEngine(buffer_size=1024) + engine.play_score(score) +else: + print("Playing MONSOON BASS...") + play_score(score)