Files
pi-midi-zone/midi_output/03_tronica.py
T
pi-agent bc8df7f33b 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.
2026-04-17 22:32:46 +00:00

116 lines
3.6 KiB
Python

#!/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")