Add MIDIUtil examples with working 90s dance compositions
- 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)
This commit is contained in:
@@ -0,0 +1,119 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
03-arpeggiator.py - MIDIUtil arpeggiator
|
||||
Generates automatic ascending/descending arpeggios from chord sets.
|
||||
Shows clean patterns using MIDIUtil's beat-based time API.
|
||||
"""
|
||||
from midiutil import MIDIFile
|
||||
|
||||
# ==============================
|
||||
# Configuration
|
||||
# ==============================
|
||||
BPM = 132
|
||||
PPQ = 960
|
||||
|
||||
# Chord definitions: (root MIDI note, number of beats)
|
||||
# Using A minor progression: Am | F | G | E
|
||||
CHORDS = [
|
||||
{"root": 57, "beats": 4}, # Am (A3)
|
||||
{"root": 53, "beats": 4}, # F (F3)
|
||||
{"root": 55, "beats": 4}, # G (G3)
|
||||
{"root": 52, "beats": 4}, # E (E3)
|
||||
]
|
||||
|
||||
# Arpeggio patterns: semitone intervals from chord root
|
||||
CHORD_TYPES = {
|
||||
"major": [0, 4, 7, 12], # Major triad + octave
|
||||
"minor": [0, 3, 7, 12], # Minor triad + octave
|
||||
"dim": [0, 3, 6, 9], # Diminished
|
||||
"aug": [0, 4, 8, 12], # Augmented
|
||||
}
|
||||
|
||||
# ==============================
|
||||
# Helper function
|
||||
# ==============================
|
||||
def make_arpeggio(midi, track, channel, chord_list,
|
||||
arp_type="minor", arp_direction="up",
|
||||
subdivisions=4, volume=90):
|
||||
"""
|
||||
Generate an arpeggio track from a chord list.
|
||||
|
||||
Args:
|
||||
midi: MIDIFile object
|
||||
track: track index (int)
|
||||
channel: MIDI channel (int, 0-15)
|
||||
chord_list: list of {"root": M, "beats": N} with MIDI root notes
|
||||
arp_type: chord type key for CHORD_TYPES dict ("minor", "major", etc.)
|
||||
arp_direction: "up" or "down" for arpeggio direction
|
||||
subdivisions: 16th-note speed (4 = standard 16ths, 8 = 32nds)
|
||||
volume: note velocity (0-127)
|
||||
"""
|
||||
notes = CHORD_TYPES[arp_type].copy()
|
||||
if arp_direction == "down":
|
||||
notes = notes[::-1]
|
||||
|
||||
note_beats = 1.0 / subdivisions # duration per arpeggio hit
|
||||
|
||||
beat = 0.0
|
||||
for chord in chord_list:
|
||||
root = chord["root"]
|
||||
chord_duration = chord["beats"]
|
||||
|
||||
total_hits = int(chord_duration / note_beats)
|
||||
for i in range(total_hits):
|
||||
note_idx = i % len(notes)
|
||||
octave = i // len(notes)
|
||||
pitch = root + notes[note_idx] + (octave * 12)
|
||||
|
||||
# Cap MIDI pitch range
|
||||
pitch = max(0, min(127, pitch))
|
||||
|
||||
midi.addNote(track, channel, pitch, beat, note_beats, volume)
|
||||
beat += note_beats
|
||||
|
||||
|
||||
# ==============================
|
||||
# Create multi-track file
|
||||
# ==============================
|
||||
midi = MIDIFile(3, file_format=1, ticks_per_quarternote=PPQ)
|
||||
midi.addTempo(0, 0, BPM)
|
||||
midi.addTrackName(0, 0, "Arpeggio Lead 1 (Minor)")
|
||||
midi.addTrackName(1, 0, "Arpeggio Lead 2 (Major)")
|
||||
midi.addTrackName(2, 0, "Bass")
|
||||
|
||||
# Track 0: Upward minor arpeggio (GM 80 - Lead 1 Synth)
|
||||
midi.addProgramChange(0, 0, 0, 80)
|
||||
make_arpeggio(midi, 0, 0, CHORDS,
|
||||
arp_type="minor", arp_direction="up",
|
||||
subdivisions=4, volume=85)
|
||||
|
||||
# Track 1: Descending major arpeggio (GM 84 - Lead 3 Synth)
|
||||
midi.addProgramChange(1, 0, 0, 84)
|
||||
make_arpeggio(midi, 1, 0, CHORDS,
|
||||
arp_type="major", arp_direction="down",
|
||||
subdivisions=8, volume=80)
|
||||
|
||||
# Track 2: Bass follows chord roots (quarter notes)
|
||||
midi.addProgramChange(2, 0, 0, 37) # Synth Bass 1
|
||||
for chord in CHORDS:
|
||||
root = chord["root"]
|
||||
chord_duration = chord["beats"]
|
||||
|
||||
# Two quarter-note pulses per beat
|
||||
for beat_offset in range(0, int(chord_duration * 2)):
|
||||
time = beat_offset * 0.5
|
||||
midi.addNote(2, 0, root, time, 0.4, 100)
|
||||
# Octave pulse for depth
|
||||
octave = root + 12
|
||||
if octave <= 127:
|
||||
midi.addNote(2, 0, octave, time, 0.2, 80)
|
||||
|
||||
|
||||
# ==============================
|
||||
# Write to Disk
|
||||
# ==============================
|
||||
with open("arpeggiator.mid", "wb") as output_file:
|
||||
midi.writeFile(output_file)
|
||||
|
||||
print("Written: arpeggiator.mid")
|
||||
print(" 3-track arpeggio piece with minor/major contrast")
|
||||
Reference in New Issue
Block a user