From bc8df7f33bce804a8df92997914dfbba4b69e44e Mon Sep 17 00:00:00 2001 From: Pi Agent Date: Fri, 17 Apr 2026 22:32:46 +0000 Subject: [PATCH] Add 3 composed MIDI tracks (midi_output) - midnight_drive.mid: 90s House/Dance at 128 BPM (75s, 5-track) Arpeggiated synth lead, driving bass, pads, brass stabs, dance beat - starlit_conversation.mid: 90s Slow Jam/Love Song at 88 BPM (99s, 4-track) Rhodes electric piano, strings, choir pads, soft drum groove - tronica.mid: 90s Trance/EBM at 138 BPM (69s, 4-track) Multi-section arpeggiated lead, wide pads, trance beat, accent lead Each .mid has a corresponding .py source showing how to compose it with MIDIUtil. --- README.md | 35 ++-- midi_output/01_midnight_drive.py | 244 +++++++++++++++++++++++++ midi_output/02_starlit_conversation.py | 200 ++++++++++++++++++++ midi_output/03_tronica.py | 115 ++++++++++++ midi_output/midnight_drive.mid | Bin 0 -> 11532 bytes midi_output/starlit_conversation.mid | Bin 0 -> 7367 bytes midi_output/tronica.mid | Bin 0 -> 12865 bytes 7 files changed, 573 insertions(+), 21 deletions(-) create mode 100644 midi_output/01_midnight_drive.py create mode 100644 midi_output/02_starlit_conversation.py create mode 100644 midi_output/03_tronica.py create mode 100644 midi_output/midnight_drive.mid create mode 100644 midi_output/starlit_conversation.mid create mode 100644 midi_output/tronica.mid diff --git a/README.md b/README.md index 045818a..2ee117f 100644 --- a/README.md +++ b/README.md @@ -13,34 +13,27 @@ sudo apt-get install -y fluidsynth fluid-soundfont-gm ## Examples | # | File | Description | -|---|------|------| +|---|---|---| | 01 | basic-chord.py | Chord progressions in C major | -| 02 | 90s-dance-track.py | Full multi-track 90s dance composition (arpeggio, bass, pads, drums, piano) | -| 03 | arpeggiator.py | Automated arpeggiator with ascending/descending patterns | +| 02 | 90s-dance-track.py | Full multi-track 90s dance composition | +| 03 | arpeggiator.py | Automated arpeggiator with patterns | | 04 | single-note.py | Minimal single-note example | +## Compositions + +| # | File | Genre | Duration | +|---|---|---|---| +| 1 | `midi_output/midnight_drive.mid` | 90s House/Dance (128 BPM, 75s) | +| 2 | `midi_output/starlit_conversation.mid` | 90s Slow Jam/Love Song (88 BPM, 99s) | +| 3 | `midi_output/tronica.mid` | 90s Trance/EBM (138 BPM, 69s) | + ## Quick Start ```bash +# Run an example to generate a MIDI file cd examples/ python3 04-single-note.py -python3 01-basic-chord.py -python3 03-arpeggiator.py -python3 02-90s-dance-track.py -# Play back with fluidsynth -fluidsynth -a alsa /usr/share/sounds/sf2/FluidR3_GM.sf2 90s-dance-track.mid +# Play back +fluidsynth -a alsa /usr/share/sounds/sf2/FluidR3_GM.sf2 example.mid ``` - -## API Reference - -All time values are **in beats** (quarter notes): - -| Method | Signature | -|--------|-----------| -| `addNote` | `(track, channel, pitch, time, duration, volume)` | -| `addTempo` | `(track, time, bpm)` | -| `addTrackName` | `(track, time, name)` | -| `addProgramChange` | `(track, channel, time, program)` | -| `addControllerEvent` | `(track, channel, time, controller, value)` | -| `writeFile` | `(fileHandle)` | diff --git a/midi_output/01_midnight_drive.py b/midi_output/01_midnight_drive.py new file mode 100644 index 0000000..2c85f4b --- /dev/null +++ b/midi_output/01_midnight_drive.py @@ -0,0 +1,244 @@ +#!/usr/bin/env python3 +""" +Midnight Drive — Classic 90s Dance/House +========================================= +Driving four-on-the-floor dance track with arpeggiated synth lead, +rolling bass, and atmospheric pads. 128 BPM, A minor progression. + +Structure: 32 bars (2 min at 128 BPM) +Intro(4) → Verse(8) → Chorus(8) → Verse(8) → Chorus(4) → Outro(8) + +Instruments: + T0: Synth Lead 1 (GM 80) — ascending arpeggios + T1: Synth Bass 1 (GM 37) — driving root pulses + T2: Drum set (Ch 10) — four-on-the-floor + T3: Pad 4 Choir (GM 88) — warm sustaining chords + T4: Brass Section (GM 59) — accent hits and stabs +""" + +from midiutil import MIDIFile + +# ========== Configuration ========== +BPM = 128 +PPQ = 960 +PITCHBEND_SCALE = 15 # semitones for full pitchwheel + +# Chord progression: Am | F | G | E (vi-IV-V-I) +CHORDS = [ + {"root": 57, "name": "Am"}, # A3 + {"root": 53, "name": "F"}, # F3 + {"root": 55, "name": "G"}, # G3 + {"root": 52, "name": "E"}, # E3 +] + +STRUCTURE = [ + ("intro", 4), + ("verse", 8), + ("chorus", 8), + ("verse2", 8), + ("chorus2", 4), + ("outro", 8), +] + +# ========== Helper functions ========== +def make_arpeggios(midi, track, channel, chords, arp_type, direction, + subdivisions, vol): + """Generate ascending/descending arpeggios over chord progression.""" + if arp_type == "major": + intervals = [0, 4, 7, 12] + else: + intervals = [0, 3, 7, 12] # minor + + if direction == "down": + intervals = intervals[::-1] + + duration = 1.0 / subdivisions + beat = 0.0 + for chord in chords: + root = chord["root"] + chord_beats = 4 + total_notes = int(chord_beats / duration) + for i in range(total_notes): + note_idx = i % len(intervals) + octave = i // len(intervals) + pitch = max(0, min(127, root + intervals[note_idx] + octave * 12)) + midi.addNote(track, channel, pitch, beat, duration * 0.9, vol) + beat += duration + + +def make_drum_track(midi, track, num_bars, bars_with_fill=None): + """Standard four-on-the-floor 90s house beat.""" + if bars_with_fill is None: + bars_with_fill = set() + + for bar in range(num_bars): + base = bar * 4 + is_fill = bar in bars_with_fill + + for beat in range(4): + t = base + beat + # Kick on every quarter + midi.addNote(track, 9, 36, t, 0.2, 110) + # Snare on 2 & 4 + if beat in (1, 3): + midi.addNote(track, 9, 38, t, 0.2, 105) + # Closed HH every 8th note + midi.addNote(track, 9, 42, t + 0.5, 0.12, 75) + # verse starts after 4-bar intro + if bar >= 4: + midi.addNote(track, 9, 46, t + 0.25, 0.1, 55) + midi.addNote(track, 9, 42, t + 0.75, 0.12, 70) + else: + midi.addNote(track, 9, 42, t + 0.25, 0.12, 65) + midi.addNote(track, 9, 42, t + 0.75, 0.12, 65) + + # Bar-end fill (clap roll) + if is_fill: + cl = base + 3.75 + for k in range(3): + midi.addNote(track, 9, 39, cl + k * 0.15, 0.1, 95) + midi.addNote(track, 9, 49, cl + 0.6, 0.5, 90) + midi.addNote(track, 9, 38, cl + 0.6, 0.3, 120) + + +def make_pad_track(midi, track, channel, num_bars, chord_root_fn, vol=65): + """Chord pad holding notes for each bar.""" + midi.addControllerEvent(track, channel, 0, 91, 65) # reverb + midi.addControllerEvent(track, channel, 0, 93, 75) # chorus + midi.addControllerEvent(track, channel, 0, 11, 120) # expression + + for bar in range(num_bars): + root = chord_root_fn(bar) + third = root + (4 if bar % 8 in (4, 16) else 3) # shift for variation + fifth = root + 7 + off_time = (bar + 1) * 4 - 0.3 + + for note in [root, third, fifth]: + midi.addNote(track, channel, note, bar * 4, 3.7, vol) + + +def make_brass_track(midi, track, channel, num_bars): + """4-bar brass stabs on chorus beats.""" + # Stab pattern: [beat_in_bar, note_offset_in_octave_from_root] + stab_beats = [(0, 7), (0, 11), (2, 7), (2, 11)] + for bar in range(num_bars): + if bar % 4 not in (0, 1, 2): # not intro + chord_idx = bar % 4 + root = CHORDS[chord_idx]["root"] + base = bar * 4 + for beat_off, note_off in stab_beats: + pitch = root + note_off + (24 if note_off == 7 else 19) + midi.addNote(track, channel, pitch, + base + beat_off, 0.8, 95) + + +# ========== Build the composition ========== +midi = MIDIFile(5, file_format=1, ticks_per_quarternote=PPQ) +midi.addTempo(0, 0, BPM) +midi.addTrackName(0, 0, "Synth Lead") +midi.addTrackName(1, 0, "Synth Bass") +midi.addTrackName(2, 0, "Drums") +midi.addTrackName(3, 0, "Warm Pads") +midi.addTrackName(4, 0, "Brass Stabs") + +# Setup instruments +midi.addProgramChange(0, 0, 0, 80) # Lead 1 Synth — arpeggio +midi.addProgramChange(1, 0, 0, 37) # Synth Bass 1 +midi.addProgramChange(3, 9, 0, 88) # Pad 4 Choir +midi.addProgramChange(4, 9, 0, 59) # Brass Section + +total_bars = sum(count for _, count in STRUCTURE) +bars_with_fill = {11, 19, 27} # end of each verse/chorus + +# Track 2: Drums +make_drum_track(midi, 2, total_bars, bars_with_fill) + +# Track 3: Pads +make_pad_track(midi, 3, 9, total_bars, + lambda bar: CHORDS[bar % 4]["root"]) + +# Track 4: Brass +make_brass_track(midi, 4, 9, total_bars) + +# Track 0: Arpeggio lead (sparse intro, then full) +intro_bars = STRUCTURE[0][1] +bar_offset = intro_bars + +# Intro: every other bar, sparse notes (8th notes, only 2 per bar) +for bar in range(intro_bars): + root = CHORDS[bar % 4]["root"] + beat = bar * 4 + # Only play on even bars in intro + if bar % 2 == 0: + for note_off in [0, 3, 7]: + note = root + note_off + 12 + midi.addNote(0, 0, note, beat + note_off * 0.25, 0.5, 70) + bar_offset = beat + 4 + +# Verse: full arpeggio +make_arpeggios(midi, 0, 0, CHORDS * 2, + arp_type="minor", direction="up", + subdivisions=4, vol=85) +bar_offset += 4 * 8 # verse + +# Chorus: faster arpeggio (32nd notes alternating direction) +for bar in range(8): + root = CHORDS[(bar_offset // 4) % 4]["root"] + bar_beat = bar_offset + bar * 4 + for i in range(16): # 16 notes over 4 beats = 32nd notes + direction = 1 if (bar_offset // 4 + bar) % 2 == 0 else -1 + intervals = [0, 3, 7, 12] if direction == 1 else [12, 7, 3, 0] + idx = i % len(intervals) + octave = i // len(intervals) + pitch = root + intervals[idx] + octave * 12 + midi.addNote(0, 0, pitch, bar_beat + i * 0.25, 0.2, 90) +bar_offset += 32 + +# Verse 2: same as verse +make_arpeggios(midi, 0, 0, CHORDS * 2, + arp_type="minor", direction="up", + subdivisions=4, vol=85) +bar_offset += 32 + +# Chorus 2: faster, slightly louder +for bar in range(4): + root = CHORDS[(bar_offset // 4) % 4]["root"] + bar_beat = bar_offset + bar * 4 + for i in range(16): + direction = 1 if (bar_offset // 4 + bar) % 2 == 0 else -1 + intervals = [0, 3, 7, 12] if direction == 1 else [12, 7, 3, 0] + idx = i % len(intervals) + octave = i // len(intervals) + pitch = root + intervals[idx] + octave * 12 + midi.addNote(0, 0, pitch, bar_beat + i * 0.25, 0.2, 95) +bar_offset += 16 + +# Outro: fade-out (return to sparse intro-style, quieter) +for bar in range(8): + root = CHORDS[bar % 4]["root"] + base = bar_offset + bar * 4 + if bar % 2 == 0: + fade_vol = max(30, 85 - bar * 8) # gradually get quieter + for note_off in [0, 3, 7]: + note = root + note_off + 12 + midi.addNote(0, 0, note, base + note_off * 0.25, 0.5, fade_vol) + +# Track 1: Bass (simplified — just roots on quarter notes) +bar_offset = 0 # reset for bass which plays the whole song +for bar in range(total_bars): + root = CHORDS[bar % 4]["root"] + base = bar * 4 + # Skip bass in intro + if bar >= 4: + midi.addNote(1, 0, root, base, 0.35, 100) + midi.addNote(1, 0, root, base + 2, 0.35, 100) + + +# ========== Write to disk ========== +output_file = "/home/paperclip/projects/gitea/pi-midi-zone/midi_output/midnight_drive.mid" +with open(output_file, "wb") as f: + midi.writeFile(f) + +print(f"Written: {output_file}") +print(f" 5-track, {total_bars} bars at {BPM} BPM") +print(f" Style: 90s House / Dance, A minor") diff --git a/midi_output/02_starlit_conversation.py b/midi_output/02_starlit_conversation.py new file mode 100644 index 0000000..cbf4aa0 --- /dev/null +++ b/midi_output/02_starlit_conversation.py @@ -0,0 +1,200 @@ +#!/usr/bin/env python3 +""" +Starlit Conversation — 90s Slow Jam / Love Song +============================================ +Romantic, emotional ballad with Rhodes electric piano, +warm pads, gentle strings, and soft drum groove. 88 BPM. + +Structure: 26 bars (~1.8 min at 88 BPM) +Intro (2 bars) — Rhodes soft chords only +Verse (8 bars) — Rhodes melody + pads +Chorus (8 bars) — Rhodes + strings + soft drums +Verse 2 (8 bars) — Rhodes + pads + drums +Chorus 2 (4 bars) — Rhodes + strings + drums +Outro (6 bars) — Rhodes fade only + +Instruments: + T0: Rhodes (GM 16) — electric piano chords + melody + T1: Pads (GM 88) — warm choral backdrop + T2: Strings (GM 49) — sustained chord accents + T3: Drums (Ch 10) — soft 90s slow-jam groove +""" + +from midiutil import MIDIFile + +# ========== Configuration ============= +BPM = 88 +PPQ = 960 + +# Chord progression: Bb | G | Am | F +CHORDS = [ + {"root": 58, "notes": [58, 62, 66]}, # Bb3: Bb3, D4, F4 + {"root": 55, "notes": [55, 59, 63]}, # G3: G3, Bb3, D4 + {"root": 57, "notes": [57, 61, 65]}, # Am3: A3, C4, Em4 + {"root": 53, "notes": [53, 57, 61]}, # F3: F3, Am3, C4 +] + +STRUCTURE = [ + ("intro", 2), + ("verse", 8), + ("chorus", 8), + ("verse2", 8), + ("chorus2", 4), + ("outro", 6), +] +total_bars = sum(c for _, c in STRUCTURE) + +# ========== Helper functions ============= +def pad_sustain(midi, track, channel, num_bars, chord_root_fn, vol=55): + """Add sustained warm pad chords. Each note gets ONE note_on/note_off.""" + midi.addControllerEvent(track, channel, 0, 91, 70) # reverb + midi.addControllerEvent(track, channel, 0, 93, 80) # chorus + midi.addControllerEvent(track, channel, 0, 1, 40) # modulation + midi.addControllerEvent(track, channel, 0, 11, 125) # expression + + for bar in range(num_bars): + root = chord_root_fn(bar) + beat = bar * 4.0 + # Three-note voicing, one note_on each + for note in [root + 7, root + 12, root + 19]: + midi.addNote(track, channel, note, beat, 3.6, vol) + + +def slow_jam_drums(midi, track, num_bars): + """ + Soft drum pattern — no four-on-the-floor: + Kick on 1. Ghost kick on 3. + Snare/CLAP on 2, 3, 4. + HH on every 8th note. + """ + for bar in range(num_bars): + b = bar * 4.0 + + # Kick + midi.addNote(track, 9, 36, b + 0.0, 0.45, 85) # beat 1 + midi.addNote(track, 9, 36, b + 2.5, 0.25, 60) # ghost on 3 + + # Snare on 2, 3, 4 + for bp in [2.0, 3.0, 3.5]: + midi.addNote(track, 9, 38, b + bp, 0.35, 65) + + # Closed HH on every half-beat + for bp in range(8): + midi.addNote(track, 9, 42, b + bp * 0.5, 0.25, 45) + + # Open HH shimmer at end of chorus bars + if bar % 8 in (8, 20): + midi.addNote(track, 9, 46, b + 3.8, 0.3, 40) + + +def rhodes_melody(midi, track, channel, bars_offset, num_bars, + chords, pattern): + """ + Rhodes electric piano melody. + pattern: "intro", "verse", "chorus", "outro" + bars_offset is the starting bar index. + """ + for i in range(num_bars): + bar = bars_offset + i + chord = chords[bar % len(chords)] + root = chord["notes"][0] + base = bar * 4.0 + + if pattern == "intro": + # Soft sustained chords — one note on per bar + notes = [chord["notes"][(j * 3) % len(chord["notes"])] for j in range(2)] + for j, n in enumerate(notes): + midi.addNote(track, channel, n + 12, base + j * 1.5, 1.3, 70) + + elif pattern == "verse": + # 3-note phrases leaving space + phrases = [ + (0.0, root + 7, 1.5), # 5th + (2.0, root + 12, 1.0), # root + octave + (3.5, root + 16 + (i % 2), 1.0), # variation + ] + for bp, pitch, dur in phrases: + midi.addNote(track, channel, pitch, base + bp, dur, 78) + + elif pattern == "chorus": + # Flowing 1/8-note arpeggio phrases + offsets = [0, 3, 7, 12, 7, 3, 0, 4] if i % 2 == 0 else [0, 4, 7, 11, 7, 4, 0, -3] + for j, off in enumerate(offsets): + pitch = root + off + 12 + midi.addNote(track, channel, pitch, base + j * 0.5, 0.35, 82) + + elif pattern == "outro": + # Descending resolution, get quieter + notes_seq = [root + 24, root + 19, root + 16, root + 12, root + 7, root + 4] + vol = max(40, 80 - i * 8) + for j, note in enumerate(notes_seq): + midi.addNote(track, channel, note, base + j * 0.85, 0.6, vol) + + +def strings_accent(midi, track, channel, bars_offset, num_bars, + chords, pattern="soft"): + """ + Add string chord accents during chorus sections. + Only played on first half of each chorus bar. + """ + for i in range(num_bars): + bar = bars_offset + i + chord = chords[bar % len(chords)] + root = chord["notes"][0] + base = bar * 4.0 + vol = 55 if pattern == "soft" else 40 + + # Add single string chord (one note) to avoid overlap + midi.addNote(track, channel, root + 20, base, 2.0, vol) + + +# ========== Build the composition ============= +midi = MIDIFile(4, file_format=1, ticks_per_quarternote=PPQ) +midi.addTempo(0, 0, BPM) +midi.addTrackName(0, 0, "Rhodes Electric") +midi.addTrackName(1, 0, "Choir Pads") +midi.addTrackName(2, 0, "Strings") +midi.addTrackName(3, 0, "Soft Drums") + +midi.addProgramChange(0, 0, 0, 16) # Rhodes +midi.addProgramChange(1, 0, 0, 88) # Choir Pad +midi.addProgramChange(2, 0, 0, 49) # Strings + +# Track 3: Drums (enters at bar 2, verse 1) — but plays all to keep it simple +slow_jam_drums(midi, 3, total_bars) + +# Track 1: Pads (full song) +pad_sustain(midi, 1, 0, total_bars, + lambda bar: CHORDS[bar % 4]["root"]) + +# --- Track 0: Rhodes piano sections --- +# Intro (bar 0-1): soft sustained +rhodes_melody(midi, 0, 0, 0, 2, CHORDS, "intro") + +# Verse (bar 2-9): verse pattern +rhodes_melody(midi, 0, 0, 2, 8, CHORDS, "verse") + +# Chorus (bar 10-17): flowing pattern +rhodes_melody(midi, 0, 0, 10, 8, CHORDS, "chorus") + +# Verse 2 (bar 18-25): verse pattern +rhodes_melody(midi, 0, 0, 18, 8, CHORDS, "verse") + +# Chorus 2 (bar 26-29): flowing pattern +rhodes_melody(midi, 0, 0, 26, 4, CHORDS, "chorus") + +# Outro (bar 30-35): fading descent +rhodes_melody(midi, 0, 0, 30, 6, CHORDS, "outro") + +# --- Track 2: Strings (only during chorus bars) --- +strings_accent(midi, 2, 0, 10, 8, CHORDS, "soft") +strings_accent(midi, 2, 0, 26, 4, CHORDS, "soft") + +# ========== Write to disk ============= +output_file = "/home/paperclip/projects/gitea/pi-midi-zone/midi_output/starlit_conversation.mid" +with open(output_file, "wb") as f: + midi.writeFile(f) + +print(f"Written: {output_file}") +print(f" 4-track, {total_bars} bars at {BPM} BPM") +print(f" Style: 90s Slow Jam / Love Song") diff --git a/midi_output/03_tronica.py b/midi_output/03_tronica.py new file mode 100644 index 0000000..bca5196 --- /dev/null +++ b/midi_output/03_tronica.py @@ -0,0 +1,115 @@ +#!/usr/bin/env python3 +""" +Tronica - 90s Trance / EBM +Driving hypnotic 90s trance with arpeggiated leads, wide choir pads, +and propulsive rhythm. 138 BPM. + +Structure: 40 bars +Intro (4 bars) - sparse 16th-notes +Build (8 bars) - speed to 32nd-notes +Drop (8 bars) - full 32nd-notes +Break (8 bars) - back to 16th-notes +Drop2 (8 bars) - full 32nd-notes +Outro (4 bars) - fade out + +Instruments: + T0: Lead 1 Synth GM 80 - driving arpeggio + T1: Pad 4 Choir GM 88 - sustained pads + T2: Drum set Ch 10 - trance dance beat + T3: Lead 3 Synth GM 84 - accent in drops +""" +from midiutil import MIDIFile + +BPM = 138 +PPQ = 960 + +CHORDS = [ + {"root": 48, "arp": (0, 3, 7, 12)}, + {"root": 45, "arp": (0, 4, 7, 12)}, + {"root": 50, "arp": (0, 4, 7, 12)}, + {"root": 46, "arp": (0, 2, 7, 12)}, +] + +total_bars = 40 +fill_bars = {3, 11, 19, 27} + +def sp(v): + """Safe pitch: return only if 0-127.""" + return v if 0 <= v < 128 else None + +# ========= Create MIDI File ========= +midi = MIDIFile(4, file_format=1, ticks_per_quarternote=PPQ) +midi.addTempo(0, 0, BPM) +midi.addProgramChange(0, 0, 0, 80) +midi.addProgramChange(1, 0, 0, 88) +midi.addProgramChange(3, 9, 0, 84) + +# ========= Track 0: Arpeggio ========= +for start, num, spd in [(0,4,4), (4,8,8), (12,8,8), (20,8,4), (28,8,8)]: + dt = 1.0 / spd + for bar in range(start, start + num): + chord = CHORDS[bar % 4] + r = chord["root"] + ints = chord["arp"] + for i in range(4 * spd): + p = sp(r + ints[i % 4] + (i // 4) * 12) + if p is not None: + v = 88 if i % 4 else 93 + midi.addNote(0, 0, p, bar * 4 + i * dt, dt * 0.85, v) + +# Outro fade (4 bars, decrescendo) +for bar in range(36, 40): + chord = CHORDS[bar % 4] + for i in range(4): + p = sp(chord["root"] + chord["arp"][i % 4]) + if p is not None: + midi.addNote(0, 0, p, bar * 4 + i * 0.25, 0.2, max(35, 75 - (bar-36)*15)) + +# ========= Track 1: Pads ========= +midi.addControllerEvent(1, 0, 0, 91, 75) # Reverb +midi.addControllerEvent(1, 0, 0, 93, 85) # Chorus +midi.addControllerEvent(1, 0, 0, 11, 127) # Expression +for bar in range(total_bars): + r = CHORDS[bar % 4]["root"] + for n in [r+7, r+12, r+19]: + midi.addNote(1, 0, n, bar*4, 3.9 if bar%2==0 else 3.7, 50) + +# ========= Track 2: Drums ========= +for bar in range(total_bars): + base = bar * 4 + for beat in range(4): + bt = base + beat + midi.addNote(2, 9, 36, bt, 0.2, 115 if beat==0 else 105) + if beat in (1, 3): + midi.addNote(2, 9, 38, bt, 0.2, 110 if bar in fill_bars else 90) + midi.addNote(2, 9, 42, bt + 0.5, 0.1, 70) + if bar in fill_bars: + midi.addNote(2, 9, 46, base + 3.5, 0.3, 80) + midi.addNote(2, 9, 46, base + 3.75, 0.2, 75) + +# ========= Track 3: Lead Cues ========= +for bar in range(8): + bt = (12 + bar) * 4 + r = CHORDS[(12 + bar) % 4]["root"] + for bp, off in [(0,12),(0.5,16),(1.0,19),(2.0,16),(2.5,19),(3.0,24)]: + midi.addNote(3, 9, r+off, bt+bp, 0.5, 90) +for bar in range(8): + bt = (28 + bar) * 4 + r = CHORDS[(28 + bar) % 4]["root"] + for bp, off in [(0,12),(0.5,16),(1.0,19),(2.0,16),(2.5,19),(3.0,24)]: + midi.addNote(3, 9, r+off, bt+bp, 0.5, 90) + +# Pitch bend +for bar in range(8): + bt = (4 + bar) * 4 + midi.addPitchWheelEvent(0, 0, bt, 3000 + bar*500) + midi.addPitchWheelEvent(0, 0, bt + 2, -(3000 + bar*500)) + +# ========= Write ========= +output_file = "/home/paperclip/projects/gitea/pi-midi-zone/midi_output/tronica.mid" +with open(output_file, "wb") as f: + midi.writeFile(f) + +print(f"Written: {output_file}") +print(f" 4-track, {total_bars} bars at {BPM} BPM") +print(f" Style: 90s Trance / EBM") diff --git a/midi_output/midnight_drive.mid b/midi_output/midnight_drive.mid new file mode 100644 index 0000000000000000000000000000000000000000..343922345334b0cbd69b76d0edb268e967c69677 GIT binary patch literal 11532 zcmeI1O-~zF7=>?2n<_=BB6Y(i5(_>g773}ajKM{A%s7@Y3I>xp5XS==lvq_z1gS(9 zsq9p$G*TC{9rY*l7qDa7ztC;feV5tMa~%7;R#EAPMATMyfqCwkx$pZhbGc)~dgEE- zoV)I>x$9$R>y4u?FdBE~^|23UKX&JL+}mjU+?|howDr~D%V(3dgT2U|ZMY;TyCk>W zYj$(n6G@(s-=63)EpSjkTEQUTAi!VB)Xvj8Mb*&8|UbTURZ0UFV*0zG?+AyGzm>Wi_jW{ zkV%LnB1C`=p#xA;X-G_}YAOwhX}X$9Lt>h)rqYm@rmLwmB&J>hX-G`H1k#Y0dI_W< zF{!GTG$bZf^^%6fG+n)qdMG$f{80%=H0y#&&bm{iqE8WNML zdPzfKnyy~bkeH^csWc>}>1rwsiK&-B8WK}4fixthUIJ-IOseW74T(ury`&*AO;;~z zNKDh!R2mW+Oz-_VpXA#3)z7uD0}=9o0-*p12*D^+m{gF|2sJ>RP#=W`lLnF|p$TXa zTB8s$36VsE2+$#P0BR}?iAhyWr6Dm*S5s+7Ow-j=8WPiVHI;_M)Jq@@iK&-B8WK}4 zfixs0RrQjF#H6ZT(vX;@tCut+rs--b4T))bu>DPl_tL!=cz*-p3*KjeYTG~DOiBT$ z6yRu?#FwN>ss=b3WI#cNb4h$jYNT3*bEiS2Y0f3_C8?6C)AljoyjDw7+Dn~t72qTjI4^0 zRWY(EMpk7&w%8{=#bW=ojv!})ZscpT3aU!`+v_`ZhkgYnd%OJ_R$ zlUzL0x$Hz=$zLa}r`y>uzMbvev;|eh!YaSQiu>Jyy5!8kin`H4Le$)Wb+ql=ctP3&d7uCTR#O4A zf4i>i9j8k6vNA7DI)D|hrDISG>VmpAH4}H8uS3Lzh;uM9#3`q9`TaAD-L8`%0BG{LRtgjzvsJ zwd{Rdd}~#p^^3=bQM4Hd)FWEC?8@UU?{V*rm`$JWj6 zgTL$_)6?{gdwOw$KZoD%b3;Em{(;s#Ma!O&4nY`n2ZTXC0b#gZTsFI9pt+~=FT7_q z`mc{FkB1-MjcBs6MU(pvvcZ@KF<#lKU0Z`fT6M4Ex5uk)R-UXhFBJyNGMMCJ@`_#e zs_M4R#e2r?t9;kw728L)kLKSX1v|vPpL1tcL6z(|c=;7Dzne^N6;#abE4#096|m=# n>6s$3ePqAu@_Qh!*gh)i9-|dh%$`TNw{bz0{-^I8{P_3{$W(|c literal 0 HcmV?d00001 diff --git a/midi_output/tronica.mid b/midi_output/tronica.mid new file mode 100644 index 0000000000000000000000000000000000000000..d8bd22e5e339557c92c00ae5d00668154d9bb50c GIT binary patch literal 12865 zcmeI1U2j@-9LN7=b1_+5Ceg&iT{KyxI6@O^}G&iVa0oYT4DS8K+Y zQ|2A>?un;!#qF9ma+YycMOGnokvb%(PLR%s zoDrNckugYABr2j#l+L7_Nt}d80+JRQ;PGb3jPXI5kuQV=PKs8gV`BxebyEK-J4 zMXHdnI$=8Fa>jAGAH$Yo|MX zmOPKnqZhyhum~1~cvr}-f!DxIa8pRWNqz%; z1HA+8fV<$XkbIZ?F8VI|A^5QT&BnoJtA?R;n8^S$=`fN3M$%y-1B`;hK!W5^@=5e0 zIsqoYG?*5Wr^#o~Gw4}x7A$}TA$fs(3B80agJrM^R@uPfuyLz&s&p>nT_#%t*T8jf zT}Zx8ejR-sy#;Q8x4>IM@>}G0(09=Hz!z4+i08ICZ9%6 zqdOaxL+8*r@*Md*dLG@`uoAk2E|HhWE9eUP<%YQpbb}474;i;Xr$NWTbLez7tch-- zo8(RMZS*#}vtfJaJ@g*=9{B_GgYvhHgAcD5mW#t`39woiQA!ao0>;3YkUU1-8Pz0y z5-RaYXXm?qpX+*dp6mBH?q5H@_50k`v-6t2wtDnC(qCI|`rC1k z*A^FU{`EdP!N1;TC;Z*(egA%uf4_*a(H*~kbic?yxBKUIZeQr8*Bjm&@_#qK>HQ#o zZTV}gK=0eZn+~OBaktXF-TM-8mANv z!ooqz9TypgL`9+yTf`1h3Y~|A&Reb}VnHGz5lBoVcK+}`c_+`9r)BeGHE*6&m&}vX z4~;t!Hg23pD*VGx3(-QfQsKHgh8g9N2!CI1AzFx5BK&CRxoeZ8^%A*=R+^VYFk?KD zdI_S1XeIR$|F5|I9`k4H|Fm8XzZK5>WW3>>{X~0s=h2XNF;j0}%sj?gH{OMc>~+(= zP&pbsw~tzX-X5^`Zg%!b^XLwE{XRL?HeTqGcJTh$cIXmeuSjNjL(~}n!i&CEynLK<0vg4NV(kki6 zv&C%c(<0N5!|b?ayqrpU@?v5!>fJX;%F)v+mhm#m^fcJpxBuIKZ{VOGKrP7M0l2?- A6aWAK literal 0 HcmV?d00001