#!/usr/bin/env python3 """ NEON DREAMS — 90s-Style Dance Track Key: A minor, Tempo: 128 BPM, 48 bars (~1:50 duration) Structure: 0–3 : Intro (pads only) 4–11 : Verse (drums, bass, pads, arpeggio) 12–19 : Chorus (all instruments, piano melody added) 20–31 : 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()