f19e3f0633
- 01-basic-chord.py: chord progressions (C major) - 02-90s-dance-track.py: 5-track 90s dance (arp, bass, pads, drums, piano) - 03-arpeggiator.py: reusable arpeggio generator with dir/type options - 04-single-note.py: minimal single-note example - Rewrite compose_neon_dreams.py: cleaner track functions, proper velocity=0 note_off, tick helper - Add README.md with setup and API reference Uses MIDIUtil library (beats-based API) instead of mido (clocks-based API)
175 lines
5.9 KiB
Python
175 lines
5.9 KiB
Python
#!/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")
|