#!/usr/bin/env python3 """ "Moonlit Mangrove Calypso" — minor-key Caribbean adventure music in the spirit of a certain scrappy young pirate's theme. D minor, 104 BPM. Structure: 4-bar intro, A (16), B (8), A' (8), outro (4). Loop: bars 4-36. The calypso fingerprints: - steel-pan lead (GM 114, labelled "calypso drum" in the MT-32 table — the patch name alone is a time machine) - guitar skank: short chord stabs on the OFFBEATS (the & of every beat) - walking upright bass: root, fifth, octave, chromatic approach - straight-eighth shaker under kick-on-1-and-3, rimshot on 2 and 4 - syncopated melody: sixteenth turns, rests on the strong beats, and the harmonic-minor C# leading tone doing its 1990 thing No relation to any existing pirate, sword, or grog empire is claimed. """ import os, sys sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "lib")) from midikit import MidiFile, Track, MPQN, verify, note as N, DRUMS from songkit import (bar, melody, vibrato, crash, humanize, parse_score, QUALITIES, TPQ) BPM = 104 OUT = os.path.join(os.path.dirname(__file__), "..", "midi", "05_moonlit_mangrove_calypso.mid") # Chord chart, one (root, quality) per bar, 40 bars CHORDS = ( [("D", "m"), ("G", "m"), ("Bb", ""), ("A", "")] + # intro [("D", "m"), ("D", "m"), ("G", "m"), ("G", "m"), # A (16) ("Bb", ""), ("Bb", ""), ("A", ""), ("A", ""), ("D", "m"), ("D", "m"), ("G", "m"), ("A", ""), ("Bb", ""), ("A", ""), ("D", "m"), ("D", "m")] + [("G", "m"), ("G", "m"), ("A", ""), ("A", ""), # B (8) ("Bb", ""), ("Bb", ""), ("G", "m"), ("A", "")] + [("D", "m"), ("G", "m"), ("Bb", ""), ("A", ""), # A' (8) ("D", "m"), ("G", "m"), ("A", ""), ("D", "m")] + [("Bb", ""), ("A", ""), ("D", "m"), ("D", "m")] # outro ) # --- melodies (bar grids, every bar lints to 4/4) -------------------------- MELODY_A = """ r8 D4-16 F4-16 A4-8 C5-8 D5-4 r4 | C5-8 A4-16 C5-16 A4-8 F4-8 A4-4 r8 D5-16 C#5-16 | D5-4 r4 A4-8 C5-8 D5-8 F5-8 | E5-8 D5-16 E5-16 D5-8 C#5-8 A4-4 A4-4 | F5-4 E5-8 F5-16 E5-16 D5-8 C5-8 A4-4 | Bb4-8 C5-8 D5-4 r8 D5-8 C5-8 A4-8 | Bb4-4 A4-8 Bb4-16 A4-16 G4-8 A4-8 C#5-4 | D5-2 C#5-4 A4-4 | r8 D4-16 F4-16 A4-8 C5-8 D5-4 r4 | C5-8 A4-16 C5-16 A4-8 F4-8 A4-4 r8 D5-16 C#5-16 | F5-8 E5-8 D5-4 C5-8 Bb4-8 A4-4 | G4-8 A4-8 C#5-4 C#5-8 D5-8 E5-4 | F5-4 E5-4 D5-8 E5-8 F5-8 A5-8 | G5-8 F5-16 G5-16 F5-8 E5-8 C5-4 C5-4 | D5-4 C5-8 D5-8 A4-4 F4-8 A4-8 | D5-2 r4 r4 """ MELODY_B = """ r4 A5-8 G5-8 F5-4 E5-8 F5-8 | G5-4 F5-8 G5-8 F5-4 D5-4 | r8 Bb5-16 A5-16 G5-8 F5-8 E5-4 D5-4 | C#5-8 D5-8 E5-4 r8 F5-8 E5-8 D5-8 | F5-8 G5-8 A5-4 r8 A5-8 G5-8 F5-8 | E5-4 F5-8 E5-8 D5-4 C5-8 Bb4-8 | A4-8 Bb4-8 C5-4 C5-8 D5-8 E5-4 | D5-2 C#5-4 D5-4 """ MELODY_A2 = """ r8 D4-16 F4-16 A4-8 C5-8 D5-4 r4 | C5-8 A4-16 C5-16 A4-8 F4-8 A4-4 r8 D5-16 C#5-16 | D5-4 r4 A4-8 C5-8 D5-8 F5-8 | E5-8 D5-16 E5-16 D5-8 C#5-8 A4-4 A4-4 | F5-4 E5-8 F5-16 E5-16 D5-8 C5-8 A4-4 | Bb4-8 C5-8 D5-4 r8 D5-8 C5-8 A4-8 | Bb4-4 A4-8 Bb4-16 A4-16 G4-8 A4-8 C#5-4 | D5-2 C#5-4 A4-4 """ PREVIEW = """ r8 D4-16-72 F4-16-72 A4-8-72 C5-8-72 D5-4-72 r4 | C5-8-72 A4-16-72 C5-16-72 A4-8-72 F4-8-72 A4-4-72 r8 D5-16-72 C#5-16-72 """ TAG = """ r4 A4-8 C5-8 D5-2 | r2 E5-4 D5-4 | r4 A4-4 D5-2 | r1 """ def skank(track, start_bar, chords, channel=2): """Calypso offbeat chord stabs: short, tight, on the & of every beat.""" for i, (root, qual) in enumerate(chords): for k, beat in enumerate((0.5, 1.5, 2.5, 3.5)): vel = humanize(76 if k in (0, 2) else 68, seed=start_bar * 4 + k + i * 17) for iv in QUALITIES[qual][:3]: track.note(bar(start_bar + i, beat), N(root + "4") + iv, TPQ // 2 - 60, vel, channel) return track def walking_bass(track, start_bar, chords, channel=3): """Root, fifth, octave, chromatic approach to the next root.""" for i, (root, qual) in enumerate(chords): nxt = chords[(i + 1) % len(chords)][0] if i + 1 < len(chords) else root r = N(root + "2") approach = N(nxt + "2") - 1 if i + 1 < len(chords) else r + 7 for k, p in enumerate((r, r + 7, r + 12, approach)): track.note(bar(start_bar + i, k), p, TPQ - 30, humanize(88, seed=start_bar * 4 + k), channel) return track def calypso_groove(track, start_bar, bars): """Kick 1 and 3 (+ the & of 3), rimshot 2 and 4, eighths on hat and shaker, open hat now and then. This is the merengue-ish calypso bed.""" for b in range(bars): bb = start_bar + b track.note(bar(bb, 0), DRUMS["bass1"], TPQ // 4, 104) track.note(bar(bb, 2), DRUMS["bass1"], TPQ // 4, 96) track.note(bar(bb, 2.5), DRUMS["bass1"], TPQ // 4, 72) # the push track.note(bar(bb, 1), DRUMS["stick"], TPQ // 4, 98) track.note(bar(bb, 3), DRUMS["stick"], TPQ // 4, 98) for k in range(8): track.note(bar(bb, k * 0.5), DRUMS["hat"], TPQ // 4, 60 if k % 2 == 0 else 46) track.note(bar(bb, k * 0.5), DRUMS["shaker"], TPQ // 4, 44 if k % 2 == 0 else 34) if b % 4 == 3: track.note(bar(bb, 3.5), DRUMS["openhat"], TPQ // 4, 70) return track def calypso_fill(track, bar_no): """Sixteenth roll with a tom tumble down into the next section.""" for k in range(8): track.note(bar(bar_no, 2 + k * 0.25), DRUMS["snare"], TPQ // 8, 60 + k * 7) for k, d in enumerate(("hightom", "midtom", "lotom")): track.note(bar(bar_no, 3 + k * 0.333), DRUMS[d], TPQ // 4, 92 - k * 8) return track def build(): mf = MidiFile(tpq=TPQ) mf.time_sig = (4, 2) mf.key_sig = (-1, True) # D minor mf.tempos = [MPQN.from_bpm(0, BPM)] lead = Track("Steel pan", channel=0) lead.program(0, "calypso drum") # GM 114 = steel drums lead.cc(0, 7, 100); lead.cc(0, 10, 54) sk = Track("Skank guitar", channel=2) sk.program(0, "clean electric") sk.cc(0, 7, 70); sk.cc(0, 10, 84) bass = Track("Walking bass", channel=3) bass.program(0, "acoustic bass") bass.cc(0, 7, 92); bass.cc(0, 10, 30) d = Track("Calypso kit", channel=9) d.cc(0, 7, 96) # intro (0-3): groove + skank only, quiet pan preview of the theme in 2-3 skank(sk, 0, CHORDS[0:4]) walking_bass(bass, 0, CHORDS[0:4]) calypso_groove(d, 1, 3) melody(lead, 2, PREVIEW) crash(d, bar(4), vel=96) # A (4-19) skank(sk, 4, CHORDS[4:20]) walking_bass(bass, 4, CHORDS[4:20]) calypso_groove(d, 4, 16) melody(lead, 4, MELODY_A) vibrato(lead, bar(4), bar(20), tempo_bpm=BPM, depth=0.35, rate=6.0) calypso_fill(d, 19) crash(d, bar(20)) # B (20-27): higher, more wistful — the moon over the mangrove skank(sk, 20, CHORDS[20:28]) walking_bass(bass, 20, CHORDS[20:28]) calypso_groove(d, 20, 8) melody(lead, 20, MELODY_B) vibrato(lead, bar(20), bar(28), tempo_bpm=BPM, depth=0.4, rate=6.0) calypso_fill(d, 27) crash(d, bar(28)) # A' (28-35): theme returns, busier hats implied by the open hats skank(sk, 28, CHORDS[28:36]) walking_bass(bass, 28, CHORDS[28:36]) calypso_groove(d, 28, 8) melody(lead, 28, MELODY_A2) vibrato(lead, bar(28), bar(36), tempo_bpm=BPM, depth=0.45, rate=6.5) calypso_fill(d, 35) crash(d, bar(36)) # outro (36-39): tag, then the whole band holds one last Dm skank(sk, 36, CHORDS[36:40]) walking_bass(bass, 36, CHORDS[36:40]) calypso_groove(d, 36, 2) melody(lead, 36, TAG) crash(d, bar(36)) for p in ("D3", "A3", "D4", "F4", "A4"): sk.note(bar(38), N(p), TPQ * 8, 72) bass.note(bar(38), N("D2"), TPQ * 8, 84) lead.note(bar(38), N("D6"), TPQ * 8, 78) crash(d, bar(38), vel=100) mf.add_track(lead); mf.add_track(sk); mf.add_track(bass); mf.add_track(d) # game loop: intro plays once, then A-B-A' (bars 4-35) repeats mf.set_loop(bar(4), bar(36)) return mf if __name__ == "__main__": os.makedirs(os.path.dirname(OUT), exist_ok=True) mf = build() mf.write(OUT) d = verify(OUT, min_sec=60, max_sec=180) print("OK:", OUT) print(" duration:", d["duration_sec"], "s | notes:", sum(len(t["notes"]) for t in d["tracks"]), "| tracks:", len(d["tracks"]))