Files
pi-midi-zone/examples/compose_neon_dreams.py
T

311 lines
11 KiB
Python
Raw Normal View History

#!/usr/bin/env python3
"""
NEON DREAMS — 90s-Style Dance Track
Key: A minor, Tempo: 128 BPM, 48 bars (~1:50 duration)
Structure:
03 : Intro (pads only)
411 : Verse (drums, bass, pads, arpeggio)
1219 : Chorus (all instruments, piano melody added)
2031 : Outro (drums simplify, piano + arpeggio only)
Instruments:
Drums (Ch10) — four-on-the-floor dance beat
Synth Lead (Ch1) — sawtooth arpeggios
Pads (Ch2) — warm New Age pad
Bass (Ch3) — synth bass walking roots
Piano (Ch4) — pentatonic melody
"""
from mido import MidiFile, MidiTrack, Message as M, MetaMessage
PPQ = 480 # ticks per quarter note
BPM = 128
TEMPO = 60_000_000 // BPM
def t(x):
"""Beat-time (fractional) to ticks."""
return int(x * PPQ)
# Am | F | G | E7
CHORD = [
[45, 60, 64], # Am
[41, 45, 60], # F
[43, 46, 50], # G
[40, 47, 52], # E7
]
def rel(items):
"""Absolute timestamps → relative Mido messages."""
items.sort(key=lambda x: x[0])
out, prev = [], 0
for at, msg in items:
msg.time = at - prev
out.append(msg)
prev = at
return out
# === DRUM PATTERNS ===
_K, _S, _HH, _HHO = 36, 38, 42, 46 # GM drum codes
def drum_events(total_bars=48):
events = []
def kick(tick):
events.append((tick, M('note_on', note=_K, velocity=110, channel=9)))
events.append((tick + t(0.5), M('note_off', note=_K, velocity=50, channel=9)))
def kick_soft(tick):
events.append((tick, M('note_on', note=_K, velocity=90, channel=9)))
events.append((tick + t(0.35), M('note_off', note=_K, velocity=40, channel=9)))
def snare(tick):
events.append((tick, M('note_on', note=_S, velocity=125, channel=9)))
events.append((tick + t(0.25), M('note_off', note=_S, velocity=60, channel=9)))
def hh(v=70):
events.append((v // 10 * 10, M('note_on', note=_HH, velocity=v, channel=9))) # hack, let me redo
events.append((v // 10 * 10, M('note_off', note=_HH, velocity=30, channel=9)))
# Actually, let me do this differently:
# I'll add events directly with tick offsets
for bar in range(total_bars):
base = bar * t(4.0)
# Determine bar type based on section
if bar < 4:
# Intro: no drums
continue
elif bar < 40:
# Normal dance beat (standard)
# Beat 1: Kick
kick(base)
events.append((base + t(0.5), M('note_on', note=_HH, velocity=65, channel=9)))
events.append((base + t(0.5), M('note_off', note=_HH, velocity=35, channel=9)))
# Beat 2: Kick + Snare + HH
kick(base + t(1.0))
snare(base + t(1.0))
events.append((base + t(1.5), M('note_on', note=_HH, velocity=60, channel=9)))
events.append((base + t(1.5), M('note_off', note=_HH, velocity=35, channel=9)))
# Beat 3: Kick + HH
kick(base + t(2.0))
events.append((base + t(2.5), M('note_on', note=_HH, velocity=60, channel=9)))
events.append((base + t(2.5), M('note_off', note=_HH, velocity=35, channel=9)))
# Beat 4: Kick + Snare + HH + Open HH
kick(base + t(3.0))
snare(base + t(3.0))
events.append((base + t(3.3), M('note_on', note=_HH, velocity=80, channel=9)))
events.append((base + t(3.3), M('note_off', note=_HH, velocity=35, channel=9)))
events.append((base + t(3.3), M('note_on', note=_HHO, velocity=75, channel=9)))
events.append((base + t(3.9), M('note_off', note=_HHO, velocity=35, channel=9)))
# HH fill at end of bar
events.append((base + t(3.8), M('note_on', note=_HH, velocity=55, channel=9)))
events.append((base + t(3.8), M('note_off', note=_HH, velocity=35, channel=9)))
# Extra kick at bar 6 (dance build-up)
if bar % 8 == 6 and bar < 40:
kick(base + t(2.5))
else:
# Outro: simplified, softer drums
events.append((base + t(0), M('note_on', note=_K, velocity=85, channel=9)))
events.append((base + t(0.5), M('note_off', note=_K, velocity=40, channel=9)))
events.append((base + t(1.0), M('note_on', note=_K, velocity=85, channel=9)))
events.append((base + t(1.5), M('note_off', note=_K, velocity=40, channel=9)))
events.append((base + t(2.0), M('note_on', note=_K, velocity=85, channel=9)))
events.append((base + t(2.5), M('note_off', note=_K, velocity=40, channel=9)))
events.append((base + t(3.0), M('note_on', note=_K, velocity=85, channel=9)))
events.append((base + t(3.5), M('note_off', note=_K, velocity=40, channel=9)))
events.append((base + t(3.8), M('note_on', note=_HH, velocity=50, channel=9)))
events.append((base + t(3.8), M('note_off', note=_HH, velocity=30, channel=9)))
return rel(events)
# === PADS ===
def pad_events(total_bars=48):
events = [
(0, M('program_change', program=88, channel=2)),
(0, M('control_change', control=11, value=127, channel=2)),
(0, M('control_change', control=91, value=45, channel=2)),
(0, M('control_change', control=93, value=55, channel=2)),
]
for bar in range(total_bars):
chord = CHORD[bar % 4]
on_tick = bar * t(4.0)
off_tick = on_tick + t(3.7)
# Crossfade: next chord starts 0.3 beats before this one ends
for note in chord:
events.append((on_tick, M('note_on', note=note, velocity=55, channel=2)))
events.append((off_tick, M('note_off', note=note, velocity=25, channel=2)))
return rel(events)
# === BASS ===
def bass_events(total_bars=48):
events = [
(0, M('program_change', program=37, channel=3)),
]
for bar in range(total_bars):
root, third, fifth = CHORD[bar % 4]
base = bar * t(4.0)
# Section-aware patterns
if bar < 4:
# Intro: no bass
continue
elif bar < 16 or bar % 4 == 0:
notes = [root, root + 9, root + 16, (root + 9)] # pentatonic walk
elif bar % 4 == 2:
notes = [root, root + 2, root + 5, root + 7] # chromatic climb
else:
notes = [root, root + 4, root + 7, root + 12] # triad arpeggio
for i, note in enumerate(notes):
on = base + t(1.0 * i)
off = base + t(1.0 + 0.01 * i)
events.append((on, M('note_on', note=note, velocity=92, channel=3)))
events.append((off, M('note_off', note=note, velocity=30, channel=3)))
return rel(events)
# === SYNTH LEAD ARPEGGIOS ===
def lead_events(total_bars=48):
events = [
(0, M('program_change', program=80, channel=1)),
(0, M('control_change', control=11, value=100, channel=1)),
(0, M('control_change', control=91, value=80, channel=1)),
(0, M('control_change', control=93, value=70, channel=1)),
(0, M('control_change', control=5, value=60, channel=1)), # filter cutoff
]
for bar in range(total_bars):
root, third, fifth = CHORD[bar % 4]
base = bar * t(4.0)
if bar < 4:
# Intro: sparse arpeggio (every 2nd bar)
if bar % 2 == 1:
continue
arp = [root + 12, root + 19, root + 24]
for i, note in enumerate(arp):
on = base + t(1.0 * i)
off = base + t(3.0 * i) + t(2.9)
events.append((on, M('note_on', note=note, velocity=55, channel=1)))
events.append((off, M('note_off', note=note, velocity=30, channel=1)))
continue
# Octave-shifted arpeggio
u3 = third + 12
u5 = fifth + 12
ur = root + 12
if bar < 40:
# Normal: 8 notes per bar (ascending-descending octaves)
arp = [ur, u3, u5, ur, u5, u3, ur, fifth]
else:
# Outro: sparse
arp = [ur, fifth, ur, u3]
for i, note in enumerate(arp):
on = base + t(0.25 * i)
off = base + t(0.25 * i) + t(0.2)
events.append((on, M('note_on', note=note, velocity=75, channel=1)))
events.append((off, M('note_off', note=note, velocity=40, channel=1)))
return rel(events)
# === PIANO MELODY ===
def piano_events(total_bars=48):
events = [
(0, M('program_change', program=1, channel=4)),
]
shapes = [
lambda p: [p[2], p[4], p[0], p[2]],
lambda p: [p[3], p[0], p[1], p[3]],
lambda p: [p[4], p[2], p[1], p[4]],
lambda p: [p[0], p[2], p[3], p[0]],
]
for bar in range(total_bars):
root = CHORD[bar % 4][0]
base = bar * t(4.0)
if bar < 12:
# Chorus starts at bar 12
continue
elif bar >= 47:
break
pent = [root + i for i in [0, 3, 5, 7, 10]]
mel = shapes[bar % 4](pent)
for i, note in enumerate(mel):
on = base + t(1.0 * i)
off = base + t(1.0 * i) + t(0.8)
events.append((on, M('note_on', note=note, velocity=85, channel=4)))
events.append((off, M('note_off', note=note, velocity=30, channel=4)))
return rel(events)
# === MAIN ===
def main():
total_bars = 48
mid = MidiFile(ticks_per_beat=PPQ)
# Track 0: Tempo
tempo_trk = MidiTrack()
tempo_trk.append(MetaMessage('set_tempo', tempo=TEMPO, time=0))
tempo_trk.append(MetaMessage('track_name', 'Tempo', time=0))
mid.tracks.append(tempo_trk)
# Track 1: Drums
drums_trk = MidiTrack()
drums_trk.append(MetaMessage('set_tempo', tempo=TEMPO, time=0))
drums_trk.extend(drum_events(total_bars))
mid.tracks.append(drums_trk)
# Track 2: Pads
pads_trk = MidiTrack()
pads_trk.extend(pad_events(total_bars))
mid.tracks.append(pads_trk)
# Track 3: Bass
bass_trk = MidiTrack()
bass_trk.extend(bass_events(total_bars))
mid.tracks.append(bass_trk)
# Track 4: Synth Lead
lead_trk = MidiTrack()
lead_trk.extend(lead_events(total_bars))
mid.tracks.append(lead_trk)
# Track 5: Piano
piano_trk = MidiTrack()
piano_trk.extend(piano_events(total_bars))
mid.tracks.append(piano_trk)
outfile = 'neon_dreams.mid'
mid.save(outfile)
total_ticks = sum(m.time for trk in mid.tracks for m in trk)
print(f"Saved {outfile}{len(mid.tracks)} tracks")
for i, trk in enumerate(mid.tracks):
tt = sum(m.time for m in trk)
name = trk.name if hasattr(trk, 'name') and trk.name else f'Track {i}'
print(f" Track {i} ({name}): {tt} ticks = {tt/480/4:.0f} bars = {tt/480/128*60/60:.1f}s")
if __name__ == '__main__':
main()