#!/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 : Bridge 32–47 : Final Chorus + Outro Instruments: Drums (Ch10) — four-on-the-floor dance beat Synth Lead (Ch1) — Lead 3 Calliope, arpeggios Strings (Ch2) — Choir Aahs, holding pads Bass (Ch3) — Synth Bass 2, walking chords Piano (Ch4) — Acoustic Grand, pentatonic melody MIDI generation lessons applied: - All note durations are musically realistic (not sub-millisecond) - note_off uses velocity=0 (standard MIDI spec) - All tick calculations forced to int() via tick() helper - No duplicate dead-code helper functions - set_tempo and time_signature ONLY on tempo track - Proper track names via MetaMessage('track_name') """ from mido import MidiFile, MidiTrack, Message as M, MetaMessage PPQ = 480 # ticks per quarter note BPM = 128 TEMPO = 60_000_000 // BPM # 468750 µs per beat def tick(f): """Convert fractional beat count to integer MIDI tick.""" return int(round(f * PPQ)) # --- Chord definitions: Am | F | G | E7 --- # Each chord: [bass_note, root_note, third, fifth] # Bass is one octave below root. CHORD_LIST = [ {'name': 'Am', 'notes': [24, 33, 36, 40]}, # A2, A3, C4, E4 {'name': 'F', 'notes': [21, 30, 33, 37]}, # F2, F3, A3, C4 {'name': 'G', 'notes': [23, 31, 34, 38]}, # G2, G3, B3, D4 {'name': 'E7', 'notes': [20, 29, 32, 36]}, # E2, E3, G#3, B3 ] CHORD_ORDER = ['Am', 'F', 'G', 'E7'] def get_chord(bar): """Return (chord_name, [bass, root, third, fifth]) for given bar.""" idx = bar % 4 name = CHORD_ORDER[idx] chord = CHORD_LIST[idx] return name, chord['notes'] # --- Core helpers for absolute-tick-based event generation --- # Each track builder collects (absolute_tick, Message) tuples, # then rel() converts them to relative-time Messages for Mido. def ev_on(tick_abs, note, velocity, channel=0): """(tick, message) tuple for note_on with velocity > 0.""" return (tick_abs, M('note_on', note=note, velocity=velocity, channel=channel, time=0)) def ev_off(tick_abs, note, velocity, channel=0): """(tick, message) tuple for note_off.""" return (tick_abs, M('note_off', note=note, velocity=velocity, channel=channel, time=0)) def rel(events): """Sort events by absolute tick, convert delta times to relative.""" events.sort(key=lambda item: item[0]) output, prev_tick = [], 0 for at, msg in events: msg.time = at - prev_tick output.append(msg) prev_tick = at return output # ================================================================ # TRACK 1: DRUMS # ================================================================ _CH_V = 9 # MIDI channel 10 (0-indexed) _KICK, _SNARE, _HHC, _HHO, _CLAP = 36, 38, 42, 46, 39 def drums_track(total_bars): evts = [] for bar in range(total_bars): base = tick(float(bar) * 4) # bar start tick # Intro: no drums if bar < 4: continue if bar < 32: # ---- Main body: four-on-the-floor + fill at end of every 8th bar ---- is_fill = (bar - 4) % 8 == 7 # Beat 1: kick + HH evts += [ ev_on(base, _KICK, 110, _CH_V), ev_off(base + tick(0.15), _KICK, 0, _CH_V), ev_on(base, _HHC, 65, _CH_V), ev_off(base + tick(0.25), _HHC, 0, _CH_V), ] # Beat 2: kick + snare + HH b2 = base + PPQ evts += [ ev_on(b2, _KICK, 110, _CH_V), ev_off(b2 + tick(0.15), _KICK, 0, _CH_V), ev_on(b2, _SNARE, 125, _CH_V), ev_off(b2 + tick(0.2), _SNARE, 0, _CH_V), ev_on(b2 + tick(0.5), _HHC, 60, _CH_V), ev_off(b2 + tick(0.75), _HHC, 0, _CH_V), ] # Beat 3: kick + HH b3 = base + tick(2) evts += [ ev_on(b3, _KICK, 110, _CH_V), ev_off(b3 + tick(0.15), _KICK, 0, _CH_V), ev_on(b3 + tick(0.5), _HHC, 60, _CH_V), ev_off(b3 + tick(0.75), _HHC, 0, _CH_V), ] # Beat 4: kick + snare + HH + open HH b4 = base + tick(3) evts += [ ev_on(b4, _KICK, 110, _CH_V), ev_off(b4 + tick(0.15), _KICK, 0, _CH_V), ev_on(b4, _SNARE, 125, _CH_V), ev_off(b4 + tick(0.2), _SNARE, 0, _CH_V), ev_on(b4 + tick(0.25), _HHC, 70, _CH_V), ev_off(b4 + tick(0.5), _HHC, 0, _CH_V), ev_on(b4 + tick(0.5), _HHO, 75, _CH_V), ev_off(b4 + tick(0.9), _HHO, 0, _CH_V), ] # Bar end: fill or short HH if is_fill: fb = base + tick(3.75) for k in range(4): evts += [ ev_on(fb + tick(k * 0.25), _KICK, 95, _CH_V), ev_off(fb + tick(k * 0.25) + tick(0.1), _KICK, 0, _CH_V), ] evts += [ ev_on(fb + tick(1), _CLAP, 100, _CH_V), ev_off(fb + tick(1) + tick(0.3), _CLAP, 0, _CH_V), ] else: evts += [ ev_on(base + tick(3.75), _HHC, 55, _CH_V), ev_off(base + tick(3.875), _HHC, 0, _CH_V), ] elif bar < 46: # ---- Final chorus: full beat + accent on beat 3 off ---- b1, b2, b3, b4 = base, base + PPQ, base + tick(2), base + tick(3) for tick_pos, note, vel in [ (0, _KICK, 110), (1, _SNARE, 125), (2, _KICK, 110), (3, _SNARE, 125), (2.5, _KICK, 100), # extra accent kick ]: evts += [ ev_on(base + tick(tick_pos), note, vel, _CH_V), ev_off(base + tick(tick_pos) + tick(0.15), note, 0, _CH_V), ] # High hats every 8th note for b in range(4): evts += [ ev_on(b1 + tick(b), _HHC, 65, _CH_V), ev_off(b1 + tick(b) + tick(0.25), _HHC, 0, _CH_V), ev_on(b1 + tick(b + 0.5), _HHC, 55, _CH_V), ev_off(b1 + tick(b + 0.5) + tick(0.25), _HHC, 0, _CH_V), ] return rel(evts) # ================================================================ # TRACK 2: STRINGS (Choir Aahs pads) # ================================================================ def strings_track(total_bars): evts = [] bt = PPQ * 4 # Patch + effects at bar 0 evts += [ (0, M('program_change', program=71, channel=2)), # Choir Aahs (0, M('control_change', control=91, value=40, channel=2)), # reverb (0, M('control_change', control=93, value=60, channel=2)), # chorus (0, M('control_change', control=11, value=127, channel=2)),# expression ] for bar in range(total_bars): cname, chord = get_chord(bar) root, third, fifth = chord[1], chord[2], chord[3] on = tick(float(bar) * 4) off = on + bt - tick(0.3) # crossfade 0.3 beat before bar end # Stagger chord tones by 2 ticks to avoid dur=0 ghost notes evts += [ ev_on(on, root, 55, 2), ev_off(off, root, 55, 2), ev_on(on + 2, third, 55, 2), ev_off(off + 4, third, 55, 2), ev_on(on + 4, fifth, 55, 2), ev_off(off + 6, fifth, 55, 2), ] return rel(evts) # ================================================================ # TRACK 3: BASS # ================================================================ def bass_track(total_bars): evts = [] bt = PPQ * 4 evts.append((0, M('program_change', program=36, channel=3))) # Synth Bass 2 # Bass arp interval patterns (ascending vs descending bar) arp_ascent = { 'Am': [0, 4, 7, 12], 'F': [0, 4, 8, 12], 'G': [0, 4, 7, 11], 'E7': [0, 4, 7, 12], } arp_desc = { 'Am': [12, 7, 4, 0], 'F': [12, 8, 4, 0], 'G': [12, 7, 4, 0], 'E7': [12, 7, 4, 0], } for bar in range(total_bars): cname, chord = get_chord(bar) root = chord[0] # bass root on_tick = tick(float(bar) * 4) if bar < 4: continue intervals = arp_ascent[cname] if bar % 2 == 0 else arp_desc[cname] for i, interval in enumerate(intervals): note = root + interval start = on_tick + PPQ * i # one beat apart (quarter notes) dur = 170 # 0.35 beat — short but clearly audible evts += [ev_on(start, note, 95, 3), ev_off(start + dur, note, 0, 3)] return rel(evts) # ================================================================ # TRACK 4: LEAD ARPEGGIOS # ================================================================ def lead_track(total_bars): evts = [] bt = PPQ * 4 evts += [ (0, M('program_change', program=83, channel=1)), # Lead 3 Calliope (0, M('control_change', control=11, value=120, channel=1)), (0, M('control_change', control=91, value=80, channel=1)), (0, M('control_change', control=93, value=70, channel=1)), ] for bar in range(total_bars): cname, chord = get_chord(bar) root, third, fifth = chord[1], chord[2], chord[3] # Octave-shifted chord tones ur = root + 12 # one octave up — root u3 = third + 12 # one octave up — third u5 = fifth + 12 # one octave up — fifth u2r = root + 24 # two octaves up on_tick = tick(float(bar) * 4) if bar < 4: # Intro: sparse, every other bar, 2 slow notes if bar % 2 == 1: continue for idx, n in enumerate([ur, u3]): nt = on_tick + tick(float(idx) * 2) evts += [ev_on(nt, n, 50, 1)] evts += [ev_off(nt + tick(1.5), n, 0, 1)] continue if bar >= 32: # Final chorus: denser 10-note 16th-note run arp = [ur, u3, u5, ur, u5, u3, ur, root + 7, u2r, ur] else: # Normal: 8-note 16th-note run, ascending + descending arp = [ur, u3, u5, ur, u5, u3, ur, fifth] for idx, n in enumerate(arp): nt = on_tick + tick(0.25 * idx) dur = 105 evts += [ev_on(nt, n, 78, 1), ev_off(nt + dur, n, 0, 1)] return rel(evts) # ================================================================ # TRACK 5: PIANO MELODY # ================================================================ def piano_track(total_bars): evts = [] bt = PPQ * 4 evts.append((0, M('program_change', program=0, channel=4))) # Acoustic Grand # Melody shapes: indices into pentatonic scale shapes = [ [0, 2, 4, 2, 4, 2, 0], # ascending arpeggio fragment [4, 2, 0, 2, 4, 5, 4], # descending [0, 2, 1, 4, 2, 1, 0], # stepwise [2, 4, 5, 4, 2, 0, 4], # climbing ] for bar in range(total_bars): cname, chord = get_chord(bar) root = chord[1] # root (not bass) on_tick = tick(float(bar) * 4) if bar < 12: continue # melody starts at bar 12 # Build pentatonic scale from root (2 octaves) pent = sorted(set(root + i for i in [0, 2, 3, 5, 7, 10, 12, 14, 15])) pent = [n for n in pent if n <= root + 24] # Melody density changes by section if bar % 8 in (4, 5): # Chorus: 8th-note phrases, 5 notes shape = shapes[bar % 4][:5] spacing = tick(0.5) dur = tick(0.35) elif bar % 8 == 7: # Bar 7: half notes, sustain shape = [shapes[bar % 4][-1], shapes[bar % 4][-1], shapes[bar % 4][-1]] spacing = tick(1.0) dur = tick(0.85) else: # Verse / other chorus bars: 4 notes shape = shapes[bar % 4][:4] spacing = tick(0.5) dur = tick(0.35) for idx, pidx in enumerate(shape): note = pent[min(pidx, len(pent) - 1)] nt = on_tick + spacing * idx evts += [ev_on(nt, note, 88, 4), ev_off(nt + dur, note, 0, 4)] return rel(evts) # ================================================================ # MAIN — assemble the complete MIDI file # ================================================================ def main(): total_bars = 48 outfile = 'neon_dreams.mid' mid = MidiFile(ticks_per_beat=PPQ, type=1) # Track 0: tempo + time signature t0 = MidiTrack() t0.append(MetaMessage('time_signature', numerator=4, denominator=4, time=0)) t0.append(MetaMessage('set_tempo', tempo=TEMPO, time=0)) t0.append(MetaMessage('track_name', name='Tempo', time=0)) mid.tracks.append(t0) # Track 1: Drums t1 = MidiTrack() t1.append(MetaMessage('track_name', name='Drums', time=0)) t1.extend(drums_track(total_bars)) mid.tracks.append(t1) # Track 2: Strings t2 = MidiTrack() t2.append(MetaMessage('track_name', name='Strings', time=0)) t2.extend(strings_track(total_bars)) mid.tracks.append(t2) # Track 3: Bass t3 = MidiTrack() t3.append(MetaMessage('track_name', name='Bass', time=0)) t3.extend(bass_track(total_bars)) mid.tracks.append(t3) # Track 4: Lead t4 = MidiTrack() t4.append(MetaMessage('track_name', name='Lead', time=0)) t4.extend(lead_track(total_bars)) mid.tracks.append(t4) # Track 5: Piano t5 = MidiTrack() t5.append(MetaMessage('track_name', name='Piano', time=0)) t5.extend(piano_track(total_bars)) mid.tracks.append(t5) mid.save(outfile) print(f"Saved {outfile} — {len(mid.tracks)} tracks, type={mid.type}") for i, trk in enumerate(mid.tracks): total = sum(m.time for m in trk) name = getattr(trk, 'name', f'Track {i}') print(f" Track {i} '{name}': {total} ticks = {total / PPQ / 4:.0f} bars") print(f" Playback: {mid.length:.1f}s") if __name__ == '__main__': main()