#!/usr/bin/env python3 """ "Kestrel Over The Fens" — N64-era ambient exploration theme Style: Ocarina-of-Time-adjacent. G major with Lydian sparkle, 72 BPM. Structure: 4-bar intro, phrase 1 (8), phrase 2 higher (8), coda (4). The N64 fingerprints: - pan flute lead, long breathy phrases, gentle vibrato - warm pad chords breathing underneath (whole notes, low velocity) - harp arpeggios instead of the DOS 16ths — everything floats - one C#6 in bar 13 (Lydian #11) that does the emotional heavy lifting """ 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 from songkit import bar, melody, arp, vibrato, TPQ BPM = 72 OUT = os.path.join(os.path.dirname(__file__), "..", "midi", "02_kestrel_over_the_fens.mid") CHORDS = ( [("G", "add9"), ("D", "7"), ("E", "m7"), ("C", "maj7")] + # intro [("G", "add9"), ("D", "7"), ("E", "m7"), ("C", "maj7"), ("A", "m7"), ("D", "sus"), ("G", "add9"), ("C", "add9")] + # P1 [("C", "add9"), ("G", "add9"), ("E", "m7"), ("D", "sus"), ("C", "add9"), ("G", "add9"), ("A", "m7"), ("D", "sus")] + # P2 [("G", "add9"), ("C", "add9"), ("G", "add9"), ("G", "")] # coda ) # D7/F# feel via bass line; Dsus resolves the phrase. PHRASE_1 = """ D5-4 G5-4 A5-2 | F#5-4 E5-4 D5-2 | E5-4 G5-4 F#5-2 | E5-4 D5-4 C5-2 | A4-8 C5-8 E5-4 A5-2 | D5-4 E5-4 D5-2 | G5-1 | r4 B5-4 A5-4 G5-4 """ PHRASE_2 = """ E5-4 G5-4 D5-4 E5-4 | B5-4 C#6-4 B5-2 | D6-4 B5-4 G5-4 E5-4 | F#5-4 A5-4 G5-2 | E5-8 G5-8 A5-4 G5-4 E5-4 | D5-4 G5-4 B5-2 | C6-4 B5-4 A5-4 E5-4 | D6-2 A5-4 F#5-4 """ CODA = """ G5-1 | E5-2 D5-2 | D5-2 B4-2 | G5-1 """ def build(): mf = MidiFile(tpq=TPQ) mf.time_sig = (4, 2) mf.key_sig = (1, False) # G major mf.tempos = [MPQN.from_bpm(0, BPM)] lead = Track("Pan flute", channel=0) lead.program(0, "pan flute") lead.cc(0, 7, 92); lead.cc(0, 10, 58) pad = Track("Warm pad", channel=1) pad.program(0, "warm pad") pad.cc(0, 7, 58); pad.cc(0, 10, 40) harp = Track("Harp", channel=2) harp.program(0, "orchestral harp") harp.cc(0, 7, 72); harp.cc(0, 10, 84) bass = Track("Cello", channel=3) bass.program(0, "cello") bass.cc(0, 7, 80); bass.cc(0, 10, 34) def pad_chords(start, count): for i in range(count): root, qual = CHORDS[start + i] for iv in (0, 4 if qual not in ("m", "m7", "m7b5") else 3, 7): pad.note(bar(start + i), N(root + "3") + iv, TPQ * 4, 55) def bass_roots(start, count): for i in range(count): bass.note(bar(start + i), N(CHORDS[start + i][0] + "2"), TPQ * 4, 70) # intro: harp + pad only arp(harp, 0, CHORDS[0:4], pattern="0121", subdivision=2, velocity=52, octave=4, duration=TPQ) pad_chords(0, 4); bass_roots(0, 4) # phrase 1 melody(lead, 4, PHRASE_1) vibrato(lead, bar(4), bar(12), tempo_bpm=BPM, depth=0.4, rate=4.5) arp(harp, 4, CHORDS[4:12], pattern="0121", subdivision=2, velocity=56, octave=4, duration=TPQ) pad_chords(4, 8); bass_roots(4, 8) # phrase 2: harp moves up, pad swells melody(lead, 12, PHRASE_2) vibrato(lead, bar(12), bar(20), tempo_bpm=BPM, depth=0.45, rate=5.0) arp(harp, 12, CHORDS[12:20], pattern="012321", subdivision=2, velocity=62, octave=4, duration=TPQ) pad_chords(12, 8); bass_roots(12, 8) # coda: everything lets go melody(lead, 20, CODA) vibrato(lead, bar(20), bar(24), tempo_bpm=BPM, depth=0.35, rate=4.0) arp(harp, 20, CHORDS[20:24], pattern="0121", subdivision=2, velocity=48, octave=4, duration=TPQ) pad_chords(20, 4); bass_roots(20, 4) # final G(add9), long, soft for p in ("G2", "D3", "G3", "B3", "D4", "A4"): pad.note(bar(23), N(p), TPQ * 6, 45) harp.note(bar(23), N(p), TPQ * 6, 40) mf.add_track(lead); mf.add_track(pad); mf.add_track(harp); mf.add_track(bass) lead.marker(bar(4), "LOOP_START") lead.marker(bar(20), "LOOP_END") return mf if __name__ == "__main__": mf = build() mf.write(OUT) d = verify(OUT, min_sec=50, max_sec=150) print("OK:", OUT) print(" duration:", d["duration_sec"], "s | notes:", sum(len(t["notes"]) for t in d["tracks"]), "| tracks:", len(d["tracks"]))