#!/usr/bin/env python3 """ 02-90s-dance-track.py - Full 90s dance track style composition Creates a multi-track 90s dance track with: - Track 1: Synthesizer arpeggio lead (GM patch 80) - Track 2: Synth bass (GM patch 37) - Track 3: Pad/choir (GM patch 88) - Track 4: Drums on channel 10 - Track 5: Piano melody (GM patch 1) Uses MIDIUtil with time in beats (quarter notes). """ from midiutil import MIDIFile # ============================== # Constants / Configuration # ============================== BPM = 128 TEMPO_US = int(60000000 / BPM) PPQ = 960 # Ticks per quarter note (MIDIUtil default) CHANNEL_DRUMS = 9 # MIDI channel 10 = channel index 9 VOLUME = 100 # === Chord data: (root, duration_beats, notes) === # vi-IV-V-I in A minor: Am | F | G | E CHORD_PROGRESSION = [ (57, 4), # Am: A3, C4, E4 - 4 beats (53, 4), # F: F3, A3, C4 - 4 beats (55, 4), # G: G3, B3, D4 - 4 beats (52, 4), # E: E3, G#3, B3 - 4 beats ] # Arpeggio patterns: list of note offsets from chord root ARP_PATTERNS = [ [0, 3, 7, 12], # Ascending: root-3rd-5th-octave [12, 7, 3, 0], # Descending: octave-5th-3rd-root ] # ============================== # Create MIDI File # ============================== # Format 1: 5 user tracks + 1 tempo track = 6 total tracks midi = MIDIFile(5, file_format=1, ticks_per_quarternote=PPQ) midi.addTempo(0, 0, BPM) # Tempo goes to the implicit tempo track midi.addTrackName(0, 0, "Arpeggio Lead") midi.addTrackName(1, 0, "Synth Bass") midi.addTrackName(2, 0, "Warm Pad") midi.addTrackName(3, 0, "Drums (Ch10)") midi.addTrackName(4, 0, "Piano Melody") # ============================== # Track 0: Arpeggio Lead (GM Patch 80 - Lead 1 Synth) # ============================== track_arp = 0 midi.addProgramChange(track_arp, 0, 0, 80) current_beat = 0 for chord_root, chord_dur in CHORD_PROGRESSION: arp_idx = 0 arp_pattern = ARP_PATTERNS[arp_idx % len(ARP_PATTERNS)] arp_idx += 1 # Each chord tone gets a 1/4 note (1 beat / 4 arp notes = 1/4 beat per note) beat_duration = chord_dur / len(arp_pattern) # 1 beat / 4 = 0.25 for i, (note_offset, next_offset) in enumerate(zip(arp_pattern, arp_pattern[1:] + arp_pattern[:0])): note = chord_root + note_offset if arp_idx % 2 == 0: # Descending on even patterns note = chord_root + arp_pattern[-1 - i] midi.addNote(track_arp, 0, note, current_beat + i * beat_duration, beat_duration, 90) current_beat += chord_dur arp_idx += 1 # ============================== # Track 1: Synth Bass (GM Patch 37) # ============================== track_bass = 1 midi.addProgramChange(track_bass, 0, 0, 37) current_beat = 0 for chord_root, chord_dur in CHORD_PROGRESSION: # Root notes on beats 1 and 3 (quarter notes) midi.addNote(track_bass, 0, chord_root, current_beat, 0.5, 100) midi.addNote(track_bass, 0, chord_root + 7, current_beat + 2, 0.5, 95) current_beat += chord_dur # ============================== # Track 2: Warm Pad (GM Patch 88) # ============================== track_pad = 2 midi.addProgramChange(track_pad, 0, 0, 88) # Add reverb and expression to pad midi.addControllerEvent(track_pad, 0, 0, 91, 60) # Reverb CC91 = 60 midi.addControllerEvent(track_pad, 0, 0, 93, 70) # Chorus CC93 = 70 current_beat = 0 for chord_root, chord_dur in CHORD_PROGRESSION: # Pad plays the full chord for 3.5 of 4 beats with fade root = chord_root third = chord_root + 4 fifth = chord_root + 7 # Note On (pad instruments need note_on to sound) for pitch in [root, third, fifth]: midi.addNote(track_pad, 0, pitch, current_beat, chord_dur - 0.5, 70) current_beat += chord_dur # ============================== # Track 3: Drums (Channel 10 = MIDI channel 9) # Standard 90s dance pattern: # Kick on 1, 2, 3, 4 (kick = note 36) # Snare on 2, 4 (snare = note 38) # Closed HH on every eighth (hh closed = note 42) # Open HH on off-beats (hh open = note 46) # ============================== track_drums = 3 current_beat = 0 total_bars = len(CHORD_PROGRESSION) # One bar = 4 chords for bar in range(total_bars): for beat in range(4): # Kick on every quarter note midi.addNote(track_drums, CHANNEL_DRUMS, 36, current_beat + beat, 0.2, 110) # Snare on beats 2 and 4 if beat in [1, 3]: midi.addNote(track_drums, CHANNEL_DRUMS, 38, current_beat + beat, 0.2, 100) # Closed hi-hat on every eighth note midi.addNote(track_drums, CHANNEL_DRUMS, 42, current_beat + beat + 0.5, 0.15, 80) # Open hi-hat on off-beats (between quarters) midi.addNote(track_drums, CHANNEL_DRUMS, 46, current_beat + beat + 0.25, 0.1, 70) midi.addNote(track_drums, CHANNEL_DRUMS, 46, current_beat + beat + 0.75, 0.1, 70) current_beat += 4 # One bar = 4 beats # ============================== # Track 4: Piano Melody (GM Patch 1) # Simple pentatonic melody in A minor # ============================== track_piano = 4 midi.addProgramChange(track_piano, 0, 0, 1) # Melody notes: (beat, pitch, duration, velocity) melody_notes = [ # Verse melody - A minor pentatonic (0, 69, 0.5, 95), # E4, half beat (0.5, 72, 0.5, 90), # F4 (1.0, 69, 0.5, 85), # E4 (1.5, 67, 0.5, 90), # D4 (2.0, 65, 0.5, 95), # C4 (2.5, 67, 0.5, 90), # D4 (3.0, 69, 1.0, 85), # E4, whole beat # Repeat with variation (4.0, 69, 0.5, 95), # E4 (4.5, 72, 0.5, 90), # F4 (5.0, 74, 0.5, 95), # G4 (5.5, 72, 0.5, 90), # F4 (6.0, 69, 1.0, 85), # E4 (7.0, 67, 0.5, 80), # D4 (7.5, 65, 1.0, 75), # C4, whole beat ] for beat, pitch, duration, vol in melody_notes: midi.addNote(track_piano, 0, pitch, beat, duration, vol) # ============================== # Write to Disk # ============================== with open("90s-dance-track.mid", "wb") as output_file: midi.writeFile(output_file) print("Written: 90s-dance-track.mid") print(f" Tracks: {len(CHORD_PROGRESSION)} chord bars at {BPM} BPM")