Add 3 composed MIDI tracks (midi_output)
- midnight_drive.mid: 90s House/Dance at 128 BPM (75s, 5-track) Arpeggiated synth lead, driving bass, pads, brass stabs, dance beat - starlit_conversation.mid: 90s Slow Jam/Love Song at 88 BPM (99s, 4-track) Rhodes electric piano, strings, choir pads, soft drum groove - tronica.mid: 90s Trance/EBM at 138 BPM (69s, 4-track) Multi-section arpeggiated lead, wide pads, trance beat, accent lead Each .mid has a corresponding .py source showing how to compose it with MIDIUtil.
This commit is contained in:
@@ -13,34 +13,27 @@ sudo apt-get install -y fluidsynth fluid-soundfont-gm
|
|||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
| # | File | Description |
|
| # | File | Description |
|
||||||
|---|------|------|
|
|---|---|---|
|
||||||
| 01 | basic-chord.py | Chord progressions in C major |
|
| 01 | basic-chord.py | Chord progressions in C major |
|
||||||
| 02 | 90s-dance-track.py | Full multi-track 90s dance composition (arpeggio, bass, pads, drums, piano) |
|
| 02 | 90s-dance-track.py | Full multi-track 90s dance composition |
|
||||||
| 03 | arpeggiator.py | Automated arpeggiator with ascending/descending patterns |
|
| 03 | arpeggiator.py | Automated arpeggiator with patterns |
|
||||||
| 04 | single-note.py | Minimal single-note example |
|
| 04 | single-note.py | Minimal single-note example |
|
||||||
|
|
||||||
|
## Compositions
|
||||||
|
|
||||||
|
| # | File | Genre | Duration |
|
||||||
|
|---|---|---|---|
|
||||||
|
| 1 | `midi_output/midnight_drive.mid` | 90s House/Dance (128 BPM, 75s) |
|
||||||
|
| 2 | `midi_output/starlit_conversation.mid` | 90s Slow Jam/Love Song (88 BPM, 99s) |
|
||||||
|
| 3 | `midi_output/tronica.mid` | 90s Trance/EBM (138 BPM, 69s) |
|
||||||
|
|
||||||
## Quick Start
|
## Quick Start
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
# Run an example to generate a MIDI file
|
||||||
cd examples/
|
cd examples/
|
||||||
python3 04-single-note.py
|
python3 04-single-note.py
|
||||||
python3 01-basic-chord.py
|
|
||||||
python3 03-arpeggiator.py
|
|
||||||
python3 02-90s-dance-track.py
|
|
||||||
|
|
||||||
# Play back with fluidsynth
|
# Play back
|
||||||
fluidsynth -a alsa /usr/share/sounds/sf2/FluidR3_GM.sf2 90s-dance-track.mid
|
fluidsynth -a alsa /usr/share/sounds/sf2/FluidR3_GM.sf2 example.mid
|
||||||
```
|
```
|
||||||
|
|
||||||
## API Reference
|
|
||||||
|
|
||||||
All time values are **in beats** (quarter notes):
|
|
||||||
|
|
||||||
| Method | Signature |
|
|
||||||
|--------|-----------|
|
|
||||||
| `addNote` | `(track, channel, pitch, time, duration, volume)` |
|
|
||||||
| `addTempo` | `(track, time, bpm)` |
|
|
||||||
| `addTrackName` | `(track, time, name)` |
|
|
||||||
| `addProgramChange` | `(track, channel, time, program)` |
|
|
||||||
| `addControllerEvent` | `(track, channel, time, controller, value)` |
|
|
||||||
| `writeFile` | `(fileHandle)` |
|
|
||||||
|
|||||||
@@ -0,0 +1,244 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Midnight Drive — Classic 90s Dance/House
|
||||||
|
=========================================
|
||||||
|
Driving four-on-the-floor dance track with arpeggiated synth lead,
|
||||||
|
rolling bass, and atmospheric pads. 128 BPM, A minor progression.
|
||||||
|
|
||||||
|
Structure: 32 bars (2 min at 128 BPM)
|
||||||
|
Intro(4) → Verse(8) → Chorus(8) → Verse(8) → Chorus(4) → Outro(8)
|
||||||
|
|
||||||
|
Instruments:
|
||||||
|
T0: Synth Lead 1 (GM 80) — ascending arpeggios
|
||||||
|
T1: Synth Bass 1 (GM 37) — driving root pulses
|
||||||
|
T2: Drum set (Ch 10) — four-on-the-floor
|
||||||
|
T3: Pad 4 Choir (GM 88) — warm sustaining chords
|
||||||
|
T4: Brass Section (GM 59) — accent hits and stabs
|
||||||
|
"""
|
||||||
|
|
||||||
|
from midiutil import MIDIFile
|
||||||
|
|
||||||
|
# ========== Configuration ==========
|
||||||
|
BPM = 128
|
||||||
|
PPQ = 960
|
||||||
|
PITCHBEND_SCALE = 15 # semitones for full pitchwheel
|
||||||
|
|
||||||
|
# Chord progression: Am | F | G | E (vi-IV-V-I)
|
||||||
|
CHORDS = [
|
||||||
|
{"root": 57, "name": "Am"}, # A3
|
||||||
|
{"root": 53, "name": "F"}, # F3
|
||||||
|
{"root": 55, "name": "G"}, # G3
|
||||||
|
{"root": 52, "name": "E"}, # E3
|
||||||
|
]
|
||||||
|
|
||||||
|
STRUCTURE = [
|
||||||
|
("intro", 4),
|
||||||
|
("verse", 8),
|
||||||
|
("chorus", 8),
|
||||||
|
("verse2", 8),
|
||||||
|
("chorus2", 4),
|
||||||
|
("outro", 8),
|
||||||
|
]
|
||||||
|
|
||||||
|
# ========== Helper functions ==========
|
||||||
|
def make_arpeggios(midi, track, channel, chords, arp_type, direction,
|
||||||
|
subdivisions, vol):
|
||||||
|
"""Generate ascending/descending arpeggios over chord progression."""
|
||||||
|
if arp_type == "major":
|
||||||
|
intervals = [0, 4, 7, 12]
|
||||||
|
else:
|
||||||
|
intervals = [0, 3, 7, 12] # minor
|
||||||
|
|
||||||
|
if direction == "down":
|
||||||
|
intervals = intervals[::-1]
|
||||||
|
|
||||||
|
duration = 1.0 / subdivisions
|
||||||
|
beat = 0.0
|
||||||
|
for chord in chords:
|
||||||
|
root = chord["root"]
|
||||||
|
chord_beats = 4
|
||||||
|
total_notes = int(chord_beats / duration)
|
||||||
|
for i in range(total_notes):
|
||||||
|
note_idx = i % len(intervals)
|
||||||
|
octave = i // len(intervals)
|
||||||
|
pitch = max(0, min(127, root + intervals[note_idx] + octave * 12))
|
||||||
|
midi.addNote(track, channel, pitch, beat, duration * 0.9, vol)
|
||||||
|
beat += duration
|
||||||
|
|
||||||
|
|
||||||
|
def make_drum_track(midi, track, num_bars, bars_with_fill=None):
|
||||||
|
"""Standard four-on-the-floor 90s house beat."""
|
||||||
|
if bars_with_fill is None:
|
||||||
|
bars_with_fill = set()
|
||||||
|
|
||||||
|
for bar in range(num_bars):
|
||||||
|
base = bar * 4
|
||||||
|
is_fill = bar in bars_with_fill
|
||||||
|
|
||||||
|
for beat in range(4):
|
||||||
|
t = base + beat
|
||||||
|
# Kick on every quarter
|
||||||
|
midi.addNote(track, 9, 36, t, 0.2, 110)
|
||||||
|
# Snare on 2 & 4
|
||||||
|
if beat in (1, 3):
|
||||||
|
midi.addNote(track, 9, 38, t, 0.2, 105)
|
||||||
|
# Closed HH every 8th note
|
||||||
|
midi.addNote(track, 9, 42, t + 0.5, 0.12, 75)
|
||||||
|
# verse starts after 4-bar intro
|
||||||
|
if bar >= 4:
|
||||||
|
midi.addNote(track, 9, 46, t + 0.25, 0.1, 55)
|
||||||
|
midi.addNote(track, 9, 42, t + 0.75, 0.12, 70)
|
||||||
|
else:
|
||||||
|
midi.addNote(track, 9, 42, t + 0.25, 0.12, 65)
|
||||||
|
midi.addNote(track, 9, 42, t + 0.75, 0.12, 65)
|
||||||
|
|
||||||
|
# Bar-end fill (clap roll)
|
||||||
|
if is_fill:
|
||||||
|
cl = base + 3.75
|
||||||
|
for k in range(3):
|
||||||
|
midi.addNote(track, 9, 39, cl + k * 0.15, 0.1, 95)
|
||||||
|
midi.addNote(track, 9, 49, cl + 0.6, 0.5, 90)
|
||||||
|
midi.addNote(track, 9, 38, cl + 0.6, 0.3, 120)
|
||||||
|
|
||||||
|
|
||||||
|
def make_pad_track(midi, track, channel, num_bars, chord_root_fn, vol=65):
|
||||||
|
"""Chord pad holding notes for each bar."""
|
||||||
|
midi.addControllerEvent(track, channel, 0, 91, 65) # reverb
|
||||||
|
midi.addControllerEvent(track, channel, 0, 93, 75) # chorus
|
||||||
|
midi.addControllerEvent(track, channel, 0, 11, 120) # expression
|
||||||
|
|
||||||
|
for bar in range(num_bars):
|
||||||
|
root = chord_root_fn(bar)
|
||||||
|
third = root + (4 if bar % 8 in (4, 16) else 3) # shift for variation
|
||||||
|
fifth = root + 7
|
||||||
|
off_time = (bar + 1) * 4 - 0.3
|
||||||
|
|
||||||
|
for note in [root, third, fifth]:
|
||||||
|
midi.addNote(track, channel, note, bar * 4, 3.7, vol)
|
||||||
|
|
||||||
|
|
||||||
|
def make_brass_track(midi, track, channel, num_bars):
|
||||||
|
"""4-bar brass stabs on chorus beats."""
|
||||||
|
# Stab pattern: [beat_in_bar, note_offset_in_octave_from_root]
|
||||||
|
stab_beats = [(0, 7), (0, 11), (2, 7), (2, 11)]
|
||||||
|
for bar in range(num_bars):
|
||||||
|
if bar % 4 not in (0, 1, 2): # not intro
|
||||||
|
chord_idx = bar % 4
|
||||||
|
root = CHORDS[chord_idx]["root"]
|
||||||
|
base = bar * 4
|
||||||
|
for beat_off, note_off in stab_beats:
|
||||||
|
pitch = root + note_off + (24 if note_off == 7 else 19)
|
||||||
|
midi.addNote(track, channel, pitch,
|
||||||
|
base + beat_off, 0.8, 95)
|
||||||
|
|
||||||
|
|
||||||
|
# ========== Build the composition ==========
|
||||||
|
midi = MIDIFile(5, file_format=1, ticks_per_quarternote=PPQ)
|
||||||
|
midi.addTempo(0, 0, BPM)
|
||||||
|
midi.addTrackName(0, 0, "Synth Lead")
|
||||||
|
midi.addTrackName(1, 0, "Synth Bass")
|
||||||
|
midi.addTrackName(2, 0, "Drums")
|
||||||
|
midi.addTrackName(3, 0, "Warm Pads")
|
||||||
|
midi.addTrackName(4, 0, "Brass Stabs")
|
||||||
|
|
||||||
|
# Setup instruments
|
||||||
|
midi.addProgramChange(0, 0, 0, 80) # Lead 1 Synth — arpeggio
|
||||||
|
midi.addProgramChange(1, 0, 0, 37) # Synth Bass 1
|
||||||
|
midi.addProgramChange(3, 9, 0, 88) # Pad 4 Choir
|
||||||
|
midi.addProgramChange(4, 9, 0, 59) # Brass Section
|
||||||
|
|
||||||
|
total_bars = sum(count for _, count in STRUCTURE)
|
||||||
|
bars_with_fill = {11, 19, 27} # end of each verse/chorus
|
||||||
|
|
||||||
|
# Track 2: Drums
|
||||||
|
make_drum_track(midi, 2, total_bars, bars_with_fill)
|
||||||
|
|
||||||
|
# Track 3: Pads
|
||||||
|
make_pad_track(midi, 3, 9, total_bars,
|
||||||
|
lambda bar: CHORDS[bar % 4]["root"])
|
||||||
|
|
||||||
|
# Track 4: Brass
|
||||||
|
make_brass_track(midi, 4, 9, total_bars)
|
||||||
|
|
||||||
|
# Track 0: Arpeggio lead (sparse intro, then full)
|
||||||
|
intro_bars = STRUCTURE[0][1]
|
||||||
|
bar_offset = intro_bars
|
||||||
|
|
||||||
|
# Intro: every other bar, sparse notes (8th notes, only 2 per bar)
|
||||||
|
for bar in range(intro_bars):
|
||||||
|
root = CHORDS[bar % 4]["root"]
|
||||||
|
beat = bar * 4
|
||||||
|
# Only play on even bars in intro
|
||||||
|
if bar % 2 == 0:
|
||||||
|
for note_off in [0, 3, 7]:
|
||||||
|
note = root + note_off + 12
|
||||||
|
midi.addNote(0, 0, note, beat + note_off * 0.25, 0.5, 70)
|
||||||
|
bar_offset = beat + 4
|
||||||
|
|
||||||
|
# Verse: full arpeggio
|
||||||
|
make_arpeggios(midi, 0, 0, CHORDS * 2,
|
||||||
|
arp_type="minor", direction="up",
|
||||||
|
subdivisions=4, vol=85)
|
||||||
|
bar_offset += 4 * 8 # verse
|
||||||
|
|
||||||
|
# Chorus: faster arpeggio (32nd notes alternating direction)
|
||||||
|
for bar in range(8):
|
||||||
|
root = CHORDS[(bar_offset // 4) % 4]["root"]
|
||||||
|
bar_beat = bar_offset + bar * 4
|
||||||
|
for i in range(16): # 16 notes over 4 beats = 32nd notes
|
||||||
|
direction = 1 if (bar_offset // 4 + bar) % 2 == 0 else -1
|
||||||
|
intervals = [0, 3, 7, 12] if direction == 1 else [12, 7, 3, 0]
|
||||||
|
idx = i % len(intervals)
|
||||||
|
octave = i // len(intervals)
|
||||||
|
pitch = root + intervals[idx] + octave * 12
|
||||||
|
midi.addNote(0, 0, pitch, bar_beat + i * 0.25, 0.2, 90)
|
||||||
|
bar_offset += 32
|
||||||
|
|
||||||
|
# Verse 2: same as verse
|
||||||
|
make_arpeggios(midi, 0, 0, CHORDS * 2,
|
||||||
|
arp_type="minor", direction="up",
|
||||||
|
subdivisions=4, vol=85)
|
||||||
|
bar_offset += 32
|
||||||
|
|
||||||
|
# Chorus 2: faster, slightly louder
|
||||||
|
for bar in range(4):
|
||||||
|
root = CHORDS[(bar_offset // 4) % 4]["root"]
|
||||||
|
bar_beat = bar_offset + bar * 4
|
||||||
|
for i in range(16):
|
||||||
|
direction = 1 if (bar_offset // 4 + bar) % 2 == 0 else -1
|
||||||
|
intervals = [0, 3, 7, 12] if direction == 1 else [12, 7, 3, 0]
|
||||||
|
idx = i % len(intervals)
|
||||||
|
octave = i // len(intervals)
|
||||||
|
pitch = root + intervals[idx] + octave * 12
|
||||||
|
midi.addNote(0, 0, pitch, bar_beat + i * 0.25, 0.2, 95)
|
||||||
|
bar_offset += 16
|
||||||
|
|
||||||
|
# Outro: fade-out (return to sparse intro-style, quieter)
|
||||||
|
for bar in range(8):
|
||||||
|
root = CHORDS[bar % 4]["root"]
|
||||||
|
base = bar_offset + bar * 4
|
||||||
|
if bar % 2 == 0:
|
||||||
|
fade_vol = max(30, 85 - bar * 8) # gradually get quieter
|
||||||
|
for note_off in [0, 3, 7]:
|
||||||
|
note = root + note_off + 12
|
||||||
|
midi.addNote(0, 0, note, base + note_off * 0.25, 0.5, fade_vol)
|
||||||
|
|
||||||
|
# Track 1: Bass (simplified — just roots on quarter notes)
|
||||||
|
bar_offset = 0 # reset for bass which plays the whole song
|
||||||
|
for bar in range(total_bars):
|
||||||
|
root = CHORDS[bar % 4]["root"]
|
||||||
|
base = bar * 4
|
||||||
|
# Skip bass in intro
|
||||||
|
if bar >= 4:
|
||||||
|
midi.addNote(1, 0, root, base, 0.35, 100)
|
||||||
|
midi.addNote(1, 0, root, base + 2, 0.35, 100)
|
||||||
|
|
||||||
|
|
||||||
|
# ========== Write to disk ==========
|
||||||
|
output_file = "/home/paperclip/projects/gitea/pi-midi-zone/midi_output/midnight_drive.mid"
|
||||||
|
with open(output_file, "wb") as f:
|
||||||
|
midi.writeFile(f)
|
||||||
|
|
||||||
|
print(f"Written: {output_file}")
|
||||||
|
print(f" 5-track, {total_bars} bars at {BPM} BPM")
|
||||||
|
print(f" Style: 90s House / Dance, A minor")
|
||||||
@@ -0,0 +1,200 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Starlit Conversation — 90s Slow Jam / Love Song
|
||||||
|
============================================
|
||||||
|
Romantic, emotional ballad with Rhodes electric piano,
|
||||||
|
warm pads, gentle strings, and soft drum groove. 88 BPM.
|
||||||
|
|
||||||
|
Structure: 26 bars (~1.8 min at 88 BPM)
|
||||||
|
Intro (2 bars) — Rhodes soft chords only
|
||||||
|
Verse (8 bars) — Rhodes melody + pads
|
||||||
|
Chorus (8 bars) — Rhodes + strings + soft drums
|
||||||
|
Verse 2 (8 bars) — Rhodes + pads + drums
|
||||||
|
Chorus 2 (4 bars) — Rhodes + strings + drums
|
||||||
|
Outro (6 bars) — Rhodes fade only
|
||||||
|
|
||||||
|
Instruments:
|
||||||
|
T0: Rhodes (GM 16) — electric piano chords + melody
|
||||||
|
T1: Pads (GM 88) — warm choral backdrop
|
||||||
|
T2: Strings (GM 49) — sustained chord accents
|
||||||
|
T3: Drums (Ch 10) — soft 90s slow-jam groove
|
||||||
|
"""
|
||||||
|
|
||||||
|
from midiutil import MIDIFile
|
||||||
|
|
||||||
|
# ========== Configuration =============
|
||||||
|
BPM = 88
|
||||||
|
PPQ = 960
|
||||||
|
|
||||||
|
# Chord progression: Bb | G | Am | F
|
||||||
|
CHORDS = [
|
||||||
|
{"root": 58, "notes": [58, 62, 66]}, # Bb3: Bb3, D4, F4
|
||||||
|
{"root": 55, "notes": [55, 59, 63]}, # G3: G3, Bb3, D4
|
||||||
|
{"root": 57, "notes": [57, 61, 65]}, # Am3: A3, C4, Em4
|
||||||
|
{"root": 53, "notes": [53, 57, 61]}, # F3: F3, Am3, C4
|
||||||
|
]
|
||||||
|
|
||||||
|
STRUCTURE = [
|
||||||
|
("intro", 2),
|
||||||
|
("verse", 8),
|
||||||
|
("chorus", 8),
|
||||||
|
("verse2", 8),
|
||||||
|
("chorus2", 4),
|
||||||
|
("outro", 6),
|
||||||
|
]
|
||||||
|
total_bars = sum(c for _, c in STRUCTURE)
|
||||||
|
|
||||||
|
# ========== Helper functions =============
|
||||||
|
def pad_sustain(midi, track, channel, num_bars, chord_root_fn, vol=55):
|
||||||
|
"""Add sustained warm pad chords. Each note gets ONE note_on/note_off."""
|
||||||
|
midi.addControllerEvent(track, channel, 0, 91, 70) # reverb
|
||||||
|
midi.addControllerEvent(track, channel, 0, 93, 80) # chorus
|
||||||
|
midi.addControllerEvent(track, channel, 0, 1, 40) # modulation
|
||||||
|
midi.addControllerEvent(track, channel, 0, 11, 125) # expression
|
||||||
|
|
||||||
|
for bar in range(num_bars):
|
||||||
|
root = chord_root_fn(bar)
|
||||||
|
beat = bar * 4.0
|
||||||
|
# Three-note voicing, one note_on each
|
||||||
|
for note in [root + 7, root + 12, root + 19]:
|
||||||
|
midi.addNote(track, channel, note, beat, 3.6, vol)
|
||||||
|
|
||||||
|
|
||||||
|
def slow_jam_drums(midi, track, num_bars):
|
||||||
|
"""
|
||||||
|
Soft drum pattern — no four-on-the-floor:
|
||||||
|
Kick on 1. Ghost kick on 3.
|
||||||
|
Snare/CLAP on 2, 3, 4.
|
||||||
|
HH on every 8th note.
|
||||||
|
"""
|
||||||
|
for bar in range(num_bars):
|
||||||
|
b = bar * 4.0
|
||||||
|
|
||||||
|
# Kick
|
||||||
|
midi.addNote(track, 9, 36, b + 0.0, 0.45, 85) # beat 1
|
||||||
|
midi.addNote(track, 9, 36, b + 2.5, 0.25, 60) # ghost on 3
|
||||||
|
|
||||||
|
# Snare on 2, 3, 4
|
||||||
|
for bp in [2.0, 3.0, 3.5]:
|
||||||
|
midi.addNote(track, 9, 38, b + bp, 0.35, 65)
|
||||||
|
|
||||||
|
# Closed HH on every half-beat
|
||||||
|
for bp in range(8):
|
||||||
|
midi.addNote(track, 9, 42, b + bp * 0.5, 0.25, 45)
|
||||||
|
|
||||||
|
# Open HH shimmer at end of chorus bars
|
||||||
|
if bar % 8 in (8, 20):
|
||||||
|
midi.addNote(track, 9, 46, b + 3.8, 0.3, 40)
|
||||||
|
|
||||||
|
|
||||||
|
def rhodes_melody(midi, track, channel, bars_offset, num_bars,
|
||||||
|
chords, pattern):
|
||||||
|
"""
|
||||||
|
Rhodes electric piano melody.
|
||||||
|
pattern: "intro", "verse", "chorus", "outro"
|
||||||
|
bars_offset is the starting bar index.
|
||||||
|
"""
|
||||||
|
for i in range(num_bars):
|
||||||
|
bar = bars_offset + i
|
||||||
|
chord = chords[bar % len(chords)]
|
||||||
|
root = chord["notes"][0]
|
||||||
|
base = bar * 4.0
|
||||||
|
|
||||||
|
if pattern == "intro":
|
||||||
|
# Soft sustained chords — one note on per bar
|
||||||
|
notes = [chord["notes"][(j * 3) % len(chord["notes"])] for j in range(2)]
|
||||||
|
for j, n in enumerate(notes):
|
||||||
|
midi.addNote(track, channel, n + 12, base + j * 1.5, 1.3, 70)
|
||||||
|
|
||||||
|
elif pattern == "verse":
|
||||||
|
# 3-note phrases leaving space
|
||||||
|
phrases = [
|
||||||
|
(0.0, root + 7, 1.5), # 5th
|
||||||
|
(2.0, root + 12, 1.0), # root + octave
|
||||||
|
(3.5, root + 16 + (i % 2), 1.0), # variation
|
||||||
|
]
|
||||||
|
for bp, pitch, dur in phrases:
|
||||||
|
midi.addNote(track, channel, pitch, base + bp, dur, 78)
|
||||||
|
|
||||||
|
elif pattern == "chorus":
|
||||||
|
# Flowing 1/8-note arpeggio phrases
|
||||||
|
offsets = [0, 3, 7, 12, 7, 3, 0, 4] if i % 2 == 0 else [0, 4, 7, 11, 7, 4, 0, -3]
|
||||||
|
for j, off in enumerate(offsets):
|
||||||
|
pitch = root + off + 12
|
||||||
|
midi.addNote(track, channel, pitch, base + j * 0.5, 0.35, 82)
|
||||||
|
|
||||||
|
elif pattern == "outro":
|
||||||
|
# Descending resolution, get quieter
|
||||||
|
notes_seq = [root + 24, root + 19, root + 16, root + 12, root + 7, root + 4]
|
||||||
|
vol = max(40, 80 - i * 8)
|
||||||
|
for j, note in enumerate(notes_seq):
|
||||||
|
midi.addNote(track, channel, note, base + j * 0.85, 0.6, vol)
|
||||||
|
|
||||||
|
|
||||||
|
def strings_accent(midi, track, channel, bars_offset, num_bars,
|
||||||
|
chords, pattern="soft"):
|
||||||
|
"""
|
||||||
|
Add string chord accents during chorus sections.
|
||||||
|
Only played on first half of each chorus bar.
|
||||||
|
"""
|
||||||
|
for i in range(num_bars):
|
||||||
|
bar = bars_offset + i
|
||||||
|
chord = chords[bar % len(chords)]
|
||||||
|
root = chord["notes"][0]
|
||||||
|
base = bar * 4.0
|
||||||
|
vol = 55 if pattern == "soft" else 40
|
||||||
|
|
||||||
|
# Add single string chord (one note) to avoid overlap
|
||||||
|
midi.addNote(track, channel, root + 20, base, 2.0, vol)
|
||||||
|
|
||||||
|
|
||||||
|
# ========== Build the composition =============
|
||||||
|
midi = MIDIFile(4, file_format=1, ticks_per_quarternote=PPQ)
|
||||||
|
midi.addTempo(0, 0, BPM)
|
||||||
|
midi.addTrackName(0, 0, "Rhodes Electric")
|
||||||
|
midi.addTrackName(1, 0, "Choir Pads")
|
||||||
|
midi.addTrackName(2, 0, "Strings")
|
||||||
|
midi.addTrackName(3, 0, "Soft Drums")
|
||||||
|
|
||||||
|
midi.addProgramChange(0, 0, 0, 16) # Rhodes
|
||||||
|
midi.addProgramChange(1, 0, 0, 88) # Choir Pad
|
||||||
|
midi.addProgramChange(2, 0, 0, 49) # Strings
|
||||||
|
|
||||||
|
# Track 3: Drums (enters at bar 2, verse 1) — but plays all to keep it simple
|
||||||
|
slow_jam_drums(midi, 3, total_bars)
|
||||||
|
|
||||||
|
# Track 1: Pads (full song)
|
||||||
|
pad_sustain(midi, 1, 0, total_bars,
|
||||||
|
lambda bar: CHORDS[bar % 4]["root"])
|
||||||
|
|
||||||
|
# --- Track 0: Rhodes piano sections ---
|
||||||
|
# Intro (bar 0-1): soft sustained
|
||||||
|
rhodes_melody(midi, 0, 0, 0, 2, CHORDS, "intro")
|
||||||
|
|
||||||
|
# Verse (bar 2-9): verse pattern
|
||||||
|
rhodes_melody(midi, 0, 0, 2, 8, CHORDS, "verse")
|
||||||
|
|
||||||
|
# Chorus (bar 10-17): flowing pattern
|
||||||
|
rhodes_melody(midi, 0, 0, 10, 8, CHORDS, "chorus")
|
||||||
|
|
||||||
|
# Verse 2 (bar 18-25): verse pattern
|
||||||
|
rhodes_melody(midi, 0, 0, 18, 8, CHORDS, "verse")
|
||||||
|
|
||||||
|
# Chorus 2 (bar 26-29): flowing pattern
|
||||||
|
rhodes_melody(midi, 0, 0, 26, 4, CHORDS, "chorus")
|
||||||
|
|
||||||
|
# Outro (bar 30-35): fading descent
|
||||||
|
rhodes_melody(midi, 0, 0, 30, 6, CHORDS, "outro")
|
||||||
|
|
||||||
|
# --- Track 2: Strings (only during chorus bars) ---
|
||||||
|
strings_accent(midi, 2, 0, 10, 8, CHORDS, "soft")
|
||||||
|
strings_accent(midi, 2, 0, 26, 4, CHORDS, "soft")
|
||||||
|
|
||||||
|
# ========== Write to disk =============
|
||||||
|
output_file = "/home/paperclip/projects/gitea/pi-midi-zone/midi_output/starlit_conversation.mid"
|
||||||
|
with open(output_file, "wb") as f:
|
||||||
|
midi.writeFile(f)
|
||||||
|
|
||||||
|
print(f"Written: {output_file}")
|
||||||
|
print(f" 4-track, {total_bars} bars at {BPM} BPM")
|
||||||
|
print(f" Style: 90s Slow Jam / Love Song")
|
||||||
@@ -0,0 +1,115 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Tronica - 90s Trance / EBM
|
||||||
|
Driving hypnotic 90s trance with arpeggiated leads, wide choir pads,
|
||||||
|
and propulsive rhythm. 138 BPM.
|
||||||
|
|
||||||
|
Structure: 40 bars
|
||||||
|
Intro (4 bars) - sparse 16th-notes
|
||||||
|
Build (8 bars) - speed to 32nd-notes
|
||||||
|
Drop (8 bars) - full 32nd-notes
|
||||||
|
Break (8 bars) - back to 16th-notes
|
||||||
|
Drop2 (8 bars) - full 32nd-notes
|
||||||
|
Outro (4 bars) - fade out
|
||||||
|
|
||||||
|
Instruments:
|
||||||
|
T0: Lead 1 Synth GM 80 - driving arpeggio
|
||||||
|
T1: Pad 4 Choir GM 88 - sustained pads
|
||||||
|
T2: Drum set Ch 10 - trance dance beat
|
||||||
|
T3: Lead 3 Synth GM 84 - accent in drops
|
||||||
|
"""
|
||||||
|
from midiutil import MIDIFile
|
||||||
|
|
||||||
|
BPM = 138
|
||||||
|
PPQ = 960
|
||||||
|
|
||||||
|
CHORDS = [
|
||||||
|
{"root": 48, "arp": (0, 3, 7, 12)},
|
||||||
|
{"root": 45, "arp": (0, 4, 7, 12)},
|
||||||
|
{"root": 50, "arp": (0, 4, 7, 12)},
|
||||||
|
{"root": 46, "arp": (0, 2, 7, 12)},
|
||||||
|
]
|
||||||
|
|
||||||
|
total_bars = 40
|
||||||
|
fill_bars = {3, 11, 19, 27}
|
||||||
|
|
||||||
|
def sp(v):
|
||||||
|
"""Safe pitch: return only if 0-127."""
|
||||||
|
return v if 0 <= v < 128 else None
|
||||||
|
|
||||||
|
# ========= Create MIDI File =========
|
||||||
|
midi = MIDIFile(4, file_format=1, ticks_per_quarternote=PPQ)
|
||||||
|
midi.addTempo(0, 0, BPM)
|
||||||
|
midi.addProgramChange(0, 0, 0, 80)
|
||||||
|
midi.addProgramChange(1, 0, 0, 88)
|
||||||
|
midi.addProgramChange(3, 9, 0, 84)
|
||||||
|
|
||||||
|
# ========= Track 0: Arpeggio =========
|
||||||
|
for start, num, spd in [(0,4,4), (4,8,8), (12,8,8), (20,8,4), (28,8,8)]:
|
||||||
|
dt = 1.0 / spd
|
||||||
|
for bar in range(start, start + num):
|
||||||
|
chord = CHORDS[bar % 4]
|
||||||
|
r = chord["root"]
|
||||||
|
ints = chord["arp"]
|
||||||
|
for i in range(4 * spd):
|
||||||
|
p = sp(r + ints[i % 4] + (i // 4) * 12)
|
||||||
|
if p is not None:
|
||||||
|
v = 88 if i % 4 else 93
|
||||||
|
midi.addNote(0, 0, p, bar * 4 + i * dt, dt * 0.85, v)
|
||||||
|
|
||||||
|
# Outro fade (4 bars, decrescendo)
|
||||||
|
for bar in range(36, 40):
|
||||||
|
chord = CHORDS[bar % 4]
|
||||||
|
for i in range(4):
|
||||||
|
p = sp(chord["root"] + chord["arp"][i % 4])
|
||||||
|
if p is not None:
|
||||||
|
midi.addNote(0, 0, p, bar * 4 + i * 0.25, 0.2, max(35, 75 - (bar-36)*15))
|
||||||
|
|
||||||
|
# ========= Track 1: Pads =========
|
||||||
|
midi.addControllerEvent(1, 0, 0, 91, 75) # Reverb
|
||||||
|
midi.addControllerEvent(1, 0, 0, 93, 85) # Chorus
|
||||||
|
midi.addControllerEvent(1, 0, 0, 11, 127) # Expression
|
||||||
|
for bar in range(total_bars):
|
||||||
|
r = CHORDS[bar % 4]["root"]
|
||||||
|
for n in [r+7, r+12, r+19]:
|
||||||
|
midi.addNote(1, 0, n, bar*4, 3.9 if bar%2==0 else 3.7, 50)
|
||||||
|
|
||||||
|
# ========= Track 2: Drums =========
|
||||||
|
for bar in range(total_bars):
|
||||||
|
base = bar * 4
|
||||||
|
for beat in range(4):
|
||||||
|
bt = base + beat
|
||||||
|
midi.addNote(2, 9, 36, bt, 0.2, 115 if beat==0 else 105)
|
||||||
|
if beat in (1, 3):
|
||||||
|
midi.addNote(2, 9, 38, bt, 0.2, 110 if bar in fill_bars else 90)
|
||||||
|
midi.addNote(2, 9, 42, bt + 0.5, 0.1, 70)
|
||||||
|
if bar in fill_bars:
|
||||||
|
midi.addNote(2, 9, 46, base + 3.5, 0.3, 80)
|
||||||
|
midi.addNote(2, 9, 46, base + 3.75, 0.2, 75)
|
||||||
|
|
||||||
|
# ========= Track 3: Lead Cues =========
|
||||||
|
for bar in range(8):
|
||||||
|
bt = (12 + bar) * 4
|
||||||
|
r = CHORDS[(12 + bar) % 4]["root"]
|
||||||
|
for bp, off in [(0,12),(0.5,16),(1.0,19),(2.0,16),(2.5,19),(3.0,24)]:
|
||||||
|
midi.addNote(3, 9, r+off, bt+bp, 0.5, 90)
|
||||||
|
for bar in range(8):
|
||||||
|
bt = (28 + bar) * 4
|
||||||
|
r = CHORDS[(28 + bar) % 4]["root"]
|
||||||
|
for bp, off in [(0,12),(0.5,16),(1.0,19),(2.0,16),(2.5,19),(3.0,24)]:
|
||||||
|
midi.addNote(3, 9, r+off, bt+bp, 0.5, 90)
|
||||||
|
|
||||||
|
# Pitch bend
|
||||||
|
for bar in range(8):
|
||||||
|
bt = (4 + bar) * 4
|
||||||
|
midi.addPitchWheelEvent(0, 0, bt, 3000 + bar*500)
|
||||||
|
midi.addPitchWheelEvent(0, 0, bt + 2, -(3000 + bar*500))
|
||||||
|
|
||||||
|
# ========= Write =========
|
||||||
|
output_file = "/home/paperclip/projects/gitea/pi-midi-zone/midi_output/tronica.mid"
|
||||||
|
with open(output_file, "wb") as f:
|
||||||
|
midi.writeFile(f)
|
||||||
|
|
||||||
|
print(f"Written: {output_file}")
|
||||||
|
print(f" 4-track, {total_bars} bars at {BPM} BPM")
|
||||||
|
print(f" Style: 90s Trance / EBM")
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user