Quick Start (Read This First!)
IMPORTANT: This file is located at /mnt/skills/private/music-generation/SKILL.md
If you need to reference this skill again during your session, read that exact path directly. Do not explore directories or use find commands - just read the file path above.
Philosophy
This skill provides tools and patterns for music composition, not pre-baked solutions. You should use your intelligence and the music21 library to compose dynamically based on user requests.
Core Principle: Write custom code that composes music algorithmically rather than calling functions with hardcoded melodies.
Installation & Setup
Quick Installation
Run the automated installer for complete setup:
bash /mnt/skills/private/music-generation/install.sh
This installs all system dependencies, Python packages, and verifies the installation.
Note: The install script may display "error: externally-managed-environment" messages at the end. These are expected and can be safely ignored - the dependencies are already installed. If you see these messages, the installation was successful.
Manual Installation
Alternatively, install dependencies manually:
System Dependencies:
apt-get update
apt-get install -y fluidsynth fluid-soundfont-gm fluid-soundfont-gs ffmpeg
Python Dependencies:
pip install -r /mnt/skills/private/music-generation/requirements.txt
The requirements.txt includes: music21, midi2audio, pydub, mido, numpy, scipy.
Available SoundFonts
Traditional Pipeline (Orchestral/Acoustic):
/usr/share/sounds/sf2/FluidR3_GM.sf2(141MB, General MIDI soundfont for orchestral/classical)/usr/share/sounds/sf2/default.sf2(symlink to best available)
Electronic Pipeline:
- No soundfonts required - uses real-time synthesis for all electronic sounds
Quick Start: Write Custom Compositions
Basic Music Generation Pattern
from music21 import stream, note, chord, instrument, tempo, dynamics
from midi2audio import FluidSynth
from pydub import AudioSegment
# 1. Create score and parts
score = stream.Score()
violin_part = stream.Part()
violin_part.insert(0, instrument.Violin())
violin_part.insert(0, tempo.MetronomeMark(number=120))
# 2. Generate notes algorithmically
for measure in range(16):
violin_part.append(note.Note('E5', quarterLength=1.0))
violin_part.append(note.Note('G5', quarterLength=1.0))
violin_part.append(note.Note('A5', quarterLength=2.0))
# 3. Export to MIDI
score.append(violin_part)
midi_path = '/mnt/user-data/outputs/composition.mid'
score.write('midi', fp=midi_path)
# 4. Render with FluidSynth
fs = FluidSynth('/usr/share/sounds/sf2/FluidR3_GM.sf2')
wav_path = '/mnt/user-data/outputs/composition.wav'
fs.midi_to_audio(midi_path, wav_path)
# 5. Convert to MP3
audio = AudioSegment.from_wav(wav_path)
mp3_path = '/mnt/user-data/outputs/composition.mp3'
audio.export(mp3_path, format='mp3', bitrate='192k')
Key Concepts
- Always create downloadable MP3 files (not HTML players)
- All output goes to
/mnt/user-data/outputs/ - Use music21.instrument classes:
instrument.Violin(),instrument.Violoncello(),instrument.Piano(),instrument.Trumpet(), etc. - Generate notes programmatically - avoid hardcoded sequences
Choosing the Right Rendering Pipeline
CRITICAL: This skill supports TWO rendering pipelines. You MUST choose based on the musical genre:
Traditional Pipeline (Orchestral, Classical, Acoustic)
Use when creating:
- Orchestral music (violin, cello, trumpet, etc.)
- Classical compositions (Mozart, Beethoven style)
- Piano music, chamber music, symphonies
- Acoustic guitar, brass ensembles
- Any music with traditional/acoustic instruments
How to render:
# After composing with music21 and exporting MIDI...
from midi2audio import FluidSynth
from pydub import AudioSegment
fs = FluidSynth('/usr/share/sounds/sf2/FluidR3_GM.sf2')
fs.midi_to_audio(midi_path, wav_path)
audio = AudioSegment.from_wav(wav_path)
audio.export(mp3_path, format='mp3', bitrate='192k')
Electronic Pipeline (House, Techno, EDM, Electronic)
Use when creating:
- House, techno, trance, EDM
- Electronic dance music with synth bass/pads/leads
- DJ beats, club music
- Any music described as "electronic" or "synth-heavy"
- Music referencing DJs like Keinemusik, Black Coffee, etc.
How to render:
# After composing with music21, using mido for instruments, and exporting MIDI...
import subprocess
# Use the electronic rendering script
result = subprocess.run([
'python',
'/mnt/skills/private/music-generation/scripts/render_electronic.py',
midi_path,
mp3_path
], capture_output=True, text=True)
print(result.stdout)
if result.returncode != 0:
print(f"Error: {result.stderr}")
Why this matters:
- The orchestral soundfont (FluidR3_GM.sf2) sounds terrible for electronic music
- Its "synth" instruments are basic 1990s approximations
- The electronic pipeline uses real-time synthesis for authentic electronic sound
- Synthesizes 808-style kicks, electronic snares, and hi-hats on-the-fly (NO external samples required)
- Bass/pads/leads use subtractive synthesis with filters and ADSR envelopes
- Genre presets (deep_house, techno, trance, ambient) tune synthesis parameters automatically
Drum Synthesis:
The electronic renderer uses real-time drum synthesis (no external samples needed). All drum sounds (kicks, snares, hi-hats, claps) are synthesized on-the-fly with genre-specific parameters.
Example: House Track
# 1. Compose with music21 (same as always)
score = stream.Score()
drums = stream.Part()
bass = stream.Part()
pads = stream.Part()
# ... compose your music
# 2. Export MIDI
midi_path = '/mnt/user-data/outputs/deep_house.mid'
score.write('midi', fp=midi_path)
# 3. Fix instruments with mido (INSERT program_change messages)
from mido import MidiFile, Message
mid = MidiFile(midi_path)
for i, track in enumerate(mid.tracks):
if i == 1: # Drums
for msg in track:
if hasattr(msg, 'channel'):
msg.channel = 9
elif i == 2: # Bass - INSERT program_change
insert_pos = 0
for j, msg in enumerate(track):
if msg.type == 'track_name':
insert_pos = j + 1
break
track.insert(insert_pos, Message('program_change', program=38, time=0))
mid.save(midi_path)
# 4. Render with ELECTRONIC pipeline with deep_house preset!
import subprocess
subprocess.run([
'python',
'/mnt/skills/private/music-generation/scripts/render_electronic.py',
midi_path,
'/mnt/user-data/outputs/deep_house.mp3',
'--genre', 'deep_house'
])
Available Genre Presets
The electronic renderer includes pre-tuned synthesis presets with supersaw lead synthesis for thick, professional EDM sounds:
- deep_house: Warm bass with 3-voice leads (120-125 BPM)
- techno: Hard-hitting with 7-voice supersaw leads (125-135 BPM)
- trance: Uplifting with massive 9-voice supersaw leads (130-140 BPM)
- ambient: Soft, atmospheric with 5-voice pads (60-90 BPM)
- acid_house: Squelchy TB-303 bass with 5-voice leads (120-130 BPM)
- default: Balanced 5-voice leads (120-130 BPM)
Supersaw Synthesis (Swedish House Mafia / Progressive House Sound):
The electronic renderer now includes unison voice synthesis for fat, buzzy leads:
- Multiple detuned oscillators: 3-9 sawtooth waves per note (genre-dependent)
- Aggressive detuning: 6-15 cents spread creates buzzy chorus effect
- Enhanced saturation: 2.5x distortion for punch and aggression
- Phase spreading: Creates wide stereo image
How It Works:
- House: 3 voices, ±8 cents (subtle, warm)
- Techno: 7 voices, ±12 cents (aggressive, punchy)
- Trance: 9 voices, ±15 cents (massive, soaring)
- Acid house: 5 voices, ±12 cents (squelchy, aggressive)
This replicates the classic supersaw sound from Swedish House Mafia, Avicii, and modern EDM productions.
Each preset tunes:
- Drum synthesis: kick pitch/decay/punch, snare tone/snap, hat brightness/metallic
- Bass synthesis: waveform, filter cutoff/resonance, ADSR envelope
- Pad synthesis: attack/release times, detune amount, brightness
- Lead synthesis: brightness, envelope, portamento
- Volume balance: intelligent mix levels per instrument with frequency-aware compensation
- Velocity curves: exponential, linear, or logarithmic response to MIDI velocity
Intelligent Volume Management
The electronic renderer uses frequency-aware volume balancing to prevent any instrument from overpowering the mix:
How it works:
- Bass frequencies (<100Hz): Automatically reduced by -4 to -6dB (sub-bass has high perceived energy)
- Mid frequencies (200-800Hz): Balanced naturally
- High frequencies (>800Hz): Slightly boosted for clarity (+1 to +1.5dB)
- Genre-specific balance: Each preset has optimized levels (e.g., House bass gets -3dB)
- Velocity curves: MIDI velocity maps intelligently (not just linear)
- Auto-limiting: Final mix is limited to -1dB to prevent clipping
Why this matters:
- House bass (A1, E2) at 55-82Hz naturally has more power - now automatically compensated
- Prevents "bass overpowering everything" issues
- Maintains balanced mix across all genres
- No manual volume tweaking needed
To see all available presets:
python /mnt/skills/private/music-generation/scripts/render_electronic.py --list-genres
Customizing Synthesis Parameters
For advanced control, you can create custom preset JSON files:
{
"drums": {
"kick": {"pitch": 52.0, "decay": 0.6, "punch": 0.9},
"snare": {"tone_mix": 0.25, "snap": 0.8}
},
"bass": {
"waveform": "sawtooth",
"cutoff": 180,
"resonance": 0.7
},
"pad": {
"attack": 1.0,
"brightness": 0.35
}
}
Then use with --preset:
python render_electronic.py track.mid output.mp3 --preset my_preset.json
Advanced Workflow: Learn from Existing MIDI
For classical pieces or complex compositions, you can:
1. Extract Structure from ANY MIDI File
python /mnt/skills/private/music-generation/scripts/midi_inventory.py \
path/to/mozart.mid \
/mnt/user-data/outputs/mozart_structure.json
This extracts:
- Tempo, key signature, time signature
- Track information and instruments
- Complete note sequences with timing
- Musical structure
2. Modify the JSON Structure
import json
# Load extracted structure
with open('/mnt/user-data/outputs/mozart_structure.json', 'r') as f:
structure = json.load(f)
# Modify instruments, notes, timing, etc.
structure['tracks']['track-0']['instrument'] = 'violin' # Change piano to violin!
# Save modified structure
with open('/mnt/user-data/outputs/mozart_violin.json', 'w') as f:
json.dump(structure, f)
3. Render Modified Structure to MP3
python /mnt/skills/private/music-generation/scripts/midi_render.py \
/mnt/user-data/outputs/mozart_violin.json \
/mnt/user-data/outputs/mozart_violin.mp3
This workflow lets you "recreate" any classical piece with different instruments!
Available Scripts
All scripts are located in /mnt/skills/private/music-generation/scripts/:
Main Workflow Scripts:
render_electronic.py- Electronic music renderer with real-time synthesis (drums, bass, pads, leads)midi_inventory.py- Extract complete structure from ANY MIDI file to JSON formatmidi_render.py- Render JSON music structure to MP3 using FluidSynthmidi_transform.py- Generic MIDI transformations (transpose, tempo change, instrument swap)audio_validate.py- Validate audio file quality and format
Synthesis Engine (used by render_electronic.py):
drum_synthesizer.py- Synthesizes kicks, snares, hi-hats, claps on-the-flymelodic_synthesizer.py- Synthesizes bass, pads, and lead sounds using subtractive synthesissynthesis_presets.py- Genre presets (deep_house, techno, trance, ambient, etc.)midi_utils.py- MIDI parsing utilities for extracting events and metadata__init__.py- Python package marker (allows importing scripts as modules)
Utility Scripts:
Music Theory Reference
Complete General MIDI Instrument Map (Programs 0-127)
CRITICAL: music21 has limited instrument support. For most sounds (especially electronic), you MUST use mido to set program numbers after export.
# Piano (0-7)
0: "Acoustic Grand Piano"
1: "Bright Acoustic Piano"
2: "Electric Grand Piano"
3: "Honky-tonk Piano"
4: "Electric Piano 1"
5: "Electric Piano 2"
6: "Harpsichord"
7: "Clavinet"
# Chromatic Percussion (8-15)
8: "Celesta"
9: "Glockenspiel"
10: "Music Box"
11: "Vibraphone"
12: "Marimba"
13: "Xylophone"
14: "Tubular Bells"
15: "Dulcimer"
# Organ (16-23)
16: "Drawbar Organ"
17: "Percussive Organ"
18: "Rock Organ"
19: "Church Organ"
20: "Reed Organ"
21: "Accordion"
22: "Harmonica"
23: "Tango Accordion"
# Guitar (24-31)
24: "Acoustic Guitar (nylon)"
25: "Acoustic Guitar (steel)"
26: "Electric Guitar (jazz)"
27: "Electric Guitar (clean)"
28: "Electric Guitar (muted)"
29: "Overdriven Guitar"
30: "Distortion Guitar"
31: "Guitar Harmonics"
# Bass (32-39)
32: "Acoustic Bass"
33: "Electric Bass (finger)"
34: "Electric Bass (pick)"
35: "Fretless Bass"
36: "Slap Bass 1"
37: "Slap Bass 2"
38: "Synth Bass 1"
39: "Synth Bass 2"
# Strings (40-47)
40: "Violin"
41: "Viola"
42: "Cello"
43: "Contrabass"
44: "Tremolo Strings"
45: "Pizzicato Strings"
46: "Orchestral Harp"
47: "Timpani"
# Ensemble (48-55)
48: "String Ensemble 1"
49: "String Ensemble 2"
50: "Synth Strings 1"
51: "Synth Strings 2"
52: "Choir Aahs"
53: "Voice Oohs"
54: "Synth Voice"
55: "Orchestra Hit"
# Brass (56-63)
56: "Trumpet"
57: "Trombone"
58: "Tuba"
59: "Muted Trumpet"
60: "French Horn"
61: "Brass Section"
62: "Synth Brass 1"
63: "Synth Brass 2"
# Reed (64-71)
64: "Soprano Sax"
65: "Alto Sax"
66: "Tenor Sax"
67: "Baritone Sax"
68: "Oboe"
69: "English Horn"
70: "Bassoon"
71: "Clarinet"
# Pipe (72-79)
72: "Piccolo"
73: "Flute"
74: "Recorder"
75: "Pan Flute"
76: "Blown Bottle"
77: "Shakuhachi"
78: "Whistle"
79: "Ocarina"
# Synth Lead (80-87)
80: "Lead 1 (square)"
81: "Lead 2 (sawtooth)"
82: "Lead 3 (calliope)"
83: "Lead 4 (chiff)"
84: "Lead 5 (charang)"
85: "Lead 6 (voice)"
86: "Lead 7 (fifths)"
87: "Lead 8 (bass + lead)"
# Synth Pad (88-95)
88: "Pad 1 (new age)"
89: "Pad 2 (warm)"
90: "Pad 3 (polysynth)"
91: "Pad 4 (choir)"
92: "Pad 5 (bowed)"
93: "Pad 6 (metallic)"
94: "Pad 7 (halo)"
95: "Pad 8 (sweep)"
# Synth Effects (96-103)
96: "FX 1 (rain)"
97: "FX 2 (soundtrack)"
98: "FX 3 (crystal)"
99: "FX 4 (atmosphere)"
100: "FX 5 (brightness)"
101: "FX 6 (goblins)"
102: "FX 7 (echoes)"
103: "FX 8 (sci-fi)"
# Ethnic (104-111)
104: "Sitar"
105: "Banjo"
106: "Shamisen"
107: "Koto"
108: "Kalimba"
109: "Bag pipe"
110: "Fiddle"
111: "Shanai"
# Percussive (112-119)
112: "Tinkle Bell"
113: "Agogo"
114: "Steel Drums"
115: "Woodblock"
116: "Taiko Drum"
117: "Melodic Tom"
118: "Synth Drum"
119: "Reverse Cymbal"
# Sound Effects (120-127)
120: "Guitar Fret Noise"
121: "Breath Noise"
122: "Seashore"
123: "Bird Tweet"
124: "Telephone Ring"
125: "Helicopter"
126: "Applause"
127: "Gunshot"
Complete Drum Map (MIDI Channel 10, Notes 35-81)
Drums use note numbers for different sounds, NOT pitch. Must be on channel 10 (9 in 0-indexed).
# Bass Drums
35: "Acoustic Bass Drum"
36: "Bass Drum 1" # Most common kick
# Snares
38: "Acoustic Snare" # Standard snare
40: "Electric Snare"
# Toms
41: "Low Floor Tom"
43: "High Floor Tom"
45: "Low Tom"
47: "Low-Mid Tom"
48: "Hi-Mid Tom"
50: "High Tom"
# Hi-Hats
42: "Closed Hi-Hat" # Most used
44: "Pedal Hi-Hat"
46: "Open Hi-Hat"
# Cymbals
49: "Crash Cymbal 1"
51: "Ride Cymbal 1"
52: "Chinese Cymbal"
53: "Ride Bell"
55: "Splash Cymbal"
57: "Crash Cymbal 2"
59: "Ride Cymbal 2"
# Percussion
37: "Side Stick"
39: "Hand Clap"
54: "Tambourine"
56: "Cowbell"
58: "Vibraslap"
60: "Hi Bongo"
61: "Low Bongo"
62: "Mute Hi Conga"
63: "Open Hi Conga"
64: "Low Conga"
65: "High Timbale"
66: "Low Timbale"
67: "High Agogo"
68: "Low Agogo"
69: "Cabasa"
70: "Maracas"
71: "Short Whistle"
72: "Long Whistle"
73: "Short Guiro"
74: "Long Guiro"
75: "Claves"
76: "Hi Wood Block"
77: "Low Wood Block"
78: "Mute Cuica"
79: "Open Cuica"
80: "Mute Triangle"
81: "Open Triangle"
How to Use Any Instrument (mido workflow)
music21 has built-in classes for orchestral instruments (Violin, Piano, Trumpet, etc.) but NO support for synths, electronic instruments, or many others. To use any GM instrument:
CRITICAL RULE: When you create a stream.Part() WITHOUT assigning a music21 instrument class, music21 WILL NOT create program_change messages in the MIDI file. You MUST use mido to INSERT these messages manually. Simply trying to modify them with if msg.type == 'program_change': msg.program = X will fail silently because no such messages exist!
Helper Function for Setting Instruments:
from mido import Message
def set_track_instrument(track, program):
"""Insert a program_change message at the beginning of a MIDI track."""
insert_pos = 0
for j, msg in enumerate(track):
if msg.type == 'track_name':
insert_pos = j + 1
break
track.insert(insert_pos, Message('program_change', program=program, time=0))
# Usage after loading MIDI with mido:
# set_track_instrument(mid.tracks[2], 33) # Set track 2 to Electric Bass
Step 1: Compose with music21 (use placeholder or skip instrument)
from music21 import stream, note, chord, tempo
score = stream.Score()
# Create parts - don't worry about instrument assignment yet
synth_lead = stream.Part()
synth_pad = stream.Part()
bass = stream.Part()
# Add your notes/chords
synth_lead.append(note.Note('E5', quarterLength=1.0))
# ... compose your music
score.append(synth_lead)
score.append(synth_pad)
score.append(bass)
# Export to MIDI
midi_path = '/mnt/user-data/outputs/track.mid'
score.write('midi', fp=midi_path)
Step 2: Assign correct instruments with mido
from mido import MidiFile, Message
mid = MidiFile(midi_path)
# Track 0 is tempo/metadata, actual parts start at track 1
# CRITICAL: You must INSERT program_change messages, not just modify existing ones!
# music21 doesn't create program_change messages if you don't assign instruments
for i, track in enumerate(mid.tracks):
if i == 1: # First part (synth_lead)
# Insert program_change at beginning of track (after track name if present)
insert_pos = 0
for j, msg in enumerate(track):
if msg.type == 'track_name':
insert_pos = j + 1
break
track.insert(insert_pos, Message('program_change', program=80, time=0))
elif i == 2: # Second part (synth_pad)
insert_pos = 0
for j, msg in enumerate(track):
if msg.type == 'track_name':
insert_pos = j + 1
break
track.insert(insert_pos, Message('program_change', program=88, time=0))
elif i == 3: # Third part (bass)
insert_pos = 0
for j, msg in enumerate(track):
if msg.type == 'track_name':
insert_pos = j + 1
break
track.insert(insert_pos, Message('program_change', program=38, time=0))
mid.save(midi_path)
Step 3: For drums, ALSO set channel to 9 (channel 10)
# If track is drums, set ALL messages to channel 9
for i, track in enumerate(mid.tracks):
if i == 1: # This is the drum track
for msg in track:
if hasattr(msg, 'channel'):
msg.channel = 9 # Channel 10 in 1-indexed
Step 4: Render to audio
from midi2audio import FluidSynth
from pydub import AudioSegment
fs = FluidSynth('/usr/share/sounds/sf2/FluidR3_GM.sf2')
wav_path = '/mnt/user-data/outputs/track.wav'
fs.midi_to_audio(midi_path, wav_path)
audio = AudioSegment.from_wav(wav_path)
mp3_path = '/mnt/user-data/outputs/track.mp3'
audio.export(mp3_path, format='mp3', bitrate='192k')
Common Chord Progressions & Styles
# Standard Progressions (Roman numerals)
"pop": ["I", "V", "vi", "IV"] # C-G-Am-F (Journey, Adele)
"epic": ["i", "VI", "III", "VII"] # Am-F-C-G (Epic trailer music)
"sad": ["i", "VI", "iv", "V"] # Am-F-Dm-E (Melancholic)
"jazz": ["ii", "V", "I", "vi"] # Dm-G-C-Am (Jazz standard)
"classical": ["I", "IV", "V", "I"] # C-F-G-C (Classical cadence)
"blues": ["I", "I", "I", "I", "IV", "IV", "I", "I", "V", "IV", "I", "I"] # 12-bar blues
"house": ["i", "VI", "III", "VII"] # Minor house progression
"reggae": ["I", "V", "vi", "IV"] # Offbeat rhythm style
"country": ["I", "IV", "V", "I"] # Simple and direct
"rock": ["I", "bVII", "IV", "I"] # Power chord style
"r&b": ["I", "V", "vi", "iii", "IV", "I", "IV", "V"] # Complex R&B
# Genre-Specific Characteristics
STYLES = {
"house": {
"bpm": 120-128,
"time_signature": "4/4",
"drum_pattern": "4-on-floor kick, offbeat hats",
"bass": "Synth bass with groove",
"common_instruments": [38, 80, 88, 4] # Synth bass, lead, pad, e-piano
},
"jazz": {
"bpm": 100-180,
"time_signature": "4/4 or 3/4",
"chords": "Extended (7th, 9th, 11th, 13th)",
"common_instruments": [0, 32, 64, 56, 73] # Piano, bass, sax, trumpet, drums
},
"orchestral": {
"bpm": 60-140,
"sections": ["strings", "woodwinds", "brass", "percussion"],
"common_instruments": [40, 41, 42, 56, 73, 47] # Violin, viola, cello, trumpet, flute, timpani
},
"rock": {
"bpm": 100-140,
"time_signature": "4/4",
"guitars": "Distorted (30) or clean (27)",
"common_instruments": [30, 33, 0, 128] # Distortion guitar, bass, piano, drums
},
"ambient": {
"bpm": 60-90,
"characteristics": "Long sustained notes, atmospheric pads",
"common_instruments": [88, 89, 90, 91, 52] # Various pads, choir
},
"trap": {
"bpm": 130-170,
"drums": "Tight snare rolls, 808 bass kicks",
"hi_hats": "Fast hi-hat patterns (1/16 or 1/32 notes)",
"common_instruments": [38, 128] # Synth bass, drums
}
}
music21 Instrument Classes
from music21 import instrument
# Strings
instrument.Violin()
instrument.Viola()
instrument.Violoncello() # Note: NOT Cello()
instrument.Contrabass()
instrument.Harp()
# Piano
instrument.Piano()
instrument.Harpsichord()
# Brass
instrument.Trumpet()
instrument.Trombone()
instrument.Tuba()
instrument.Horn() # French horn
# Woodwinds
instrument.Flute()
instrument.Clarinet()
instrument.Oboe()
instrument.Bassoon()
instrument.SopranoSaxophone()
instrument.AltoSaxophone()
instrument.TenorSaxophone() # Most common for jazz
instrument.BaritoneSaxophone()
# Other
instrument.AcousticGuitar()
instrument.ElectricGuitar()
instrument.Bass()
instrument.Timpani()
# CRITICAL: music21 has LIMITED support for electronic instruments and drums
# For synths, drums, and electronic sounds, you MUST:
# 1. Create a Part without an instrument (or use a placeholder like Piano())
# 2. Use mido library to INSERT program_change messages after export
# 3. Set drums to MIDI channel 10 (channel 9 in 0-indexed) or they won't sound like drums
#
# Common mistakes:
# - instrument.Cello() doesn't exist - use Violoncello()
# - instrument.FrenchHorn() doesn't exist - use Horn()
# - Setting part.partName doesn't change the sound - you must set MIDI program with mido
# - Drums on channel 0 will play as pitched notes, not drum sounds
Note Durations (Quarter Note = 1.0)
- Whole note: 4.0
- Half note: 2.0
- Quarter note: 1.0
- Eighth note: 0.5
- Sixteenth note: 0.25
- Dotted quarter: 1.5
- Triplet quarter: 0.667
mido Quick Reference
For electronic music and drums, use mido to set MIDI programs after music21 export:
from mido import MidiFile, Message
mid = MidiFile(midi_path)
# Insert program_change message
for i, track in enumerate(mid.tracks):
if i == 1: # Your track (tracks start at 1, not 0)
insert_pos = 0
for j, msg in enumerate(track):
if msg.type == 'track_name':
insert_pos = j + 1
break
track.insert(insert_pos, Message('program_change', program=38, time=0))
# For drums: Set channel to 9 (channel 10 in 1-indexed)
for i, track in enumerate(mid.tracks):
if i == 1: # Drum track
for msg in track:
if hasattr(msg, 'channel'):
msg.channel = 9
mid.save(midi_path)
Common MIDI Programs:
- 38: Synth Bass 1
- 80: Square Lead
- 81: Sawtooth Lead
- 88: Pad 1 (New Age)
- 25: Acoustic Guitar (Steel) - loud, cuts through
- 33: Acoustic Bass
Common Techniques
Drum Programming (4-on-floor house beat)
CRITICAL: music21's .append() adds notes sequentially (one after another), not simultaneously. For layered drums where kicks, snares, and hats play at the same time, you MUST use .insert(offset, note) with explicit timing.
⚠️ ALWAYS USE .insert() FOR ALL TRACKS:
Since layering is needed for nearly all good music composition, you should ALWAYS use .insert(offset, note) for ALL tracks - drums, bass, guitar, pads, everything. This prevents timing bugs and ensures proper synchronization.
NEVER mix .insert() and .append() - If you use .insert() for drums and .append() for other instruments, music21 will miscalculate track lengths and create tracks that are 5-10× longer than intended (8 minutes instead of 1.5 minutes), with only the first 20-25% containing actual sound.
The .append() method should only be used in rare cases where you have a single melodic line with no other instruments.
# WRONG: This plays kick, then 32 hats, then snare pattern (not layered!)
# for beat in range(16):
# drums.append(note.Note(36, quarterLength=1.0)) # Kicks play first
# for eighth in range(32):
# drums.append(note.Note(42, quarterLength=0.5)) # Hats play AFTER all kicks
# # Result: Timing is completely wrong!
# CORRECT: Use .insert() with explicit offsets for simultaneous layering
bars = 32
beats_per_bar = 4
total_beats = bars * beats_per_bar
# Layer 1: Four-on-the-floor kicks (every beat)
for beat in range(total_beats):
offset = float(beat) # Beat 0, 1, 2, 3, 4, 5, ...
drums.insert(offset, note.Note(36, quarterLength=1.0))
# Layer 2: Snare on beats 2 and 4 of each bar
for bar in range(bars):
# Snare on beat 2 (second beat of bar)
offset = float(bar * beats_per_bar + 1)
drums.insert(offset, note.Note(38, quarterLength=1.0))
# Snare on beat 4 (fourth beat of bar)
offset = float(bar * beats_per_bar + 3)
drums.insert(offset, note.Note(38, quarterLength=1.0))
# Layer 3: Hi-hats on eighth notes (every 0.5 beats) - creates groove
for bar in range(bars):
for eighth in range(8): # 8 eighth notes per bar
offset = float(bar * beats_per_bar) + (eighth * 0.5)
if eighth % 2 == 0:
# Closed hat on even eighths (on the beat)
drums.insert(offset, note.Note(42, quarterLength=0.5))
else:
# Open hat on odd eighths (offbeat) - signature house groove
drums.insert(offset, note.Note(46, quarterLength=0.5))
# Result: Properly layered four-on-the-floor with offbeat open hats
# Bar 0: Kicks at 0.0, 1.0, 2.0, 3.0
# Snares at 1.0, 3.0 (on top of kicks)
# Hats at 0.0, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0, 3.5 (layered throughout)
Reggae Specific Guidelines
CRITICAL: Reggae has a unique rhythmic identity that requires precise drum patterns, offbeat accents, and heavy bass. If you don't follow these rules, it won't sound like reggae.
Drum Rules (Non-Negotiable):
- "One Drop" pattern: Kick drum on beat 3 ONLY (not beat 1), creating the signature reggae "drop"
- Snare/Rimshot: Beats 2 and 4 (or just beat 3 with the kick)
- Hi-hats: OFFBEAT eighth notes only (never on the beat), creating the "skank" rhythm
- Cross-stick (note 37): Optional on beats 2 and 4 for classic sound
- NO four-on-floor kicks - this is house/electronic, not reggae
# CORRECT Reggae "One Drop" Drum Pattern
bars = 32
beats_per_bar = 4
for bar in range(bars):
for beat in range(beats_per_bar):
offset = float(bar * beats_per_bar + beat)
# Kick ONLY on beat 3 (the "drop")
if beat == 2: # Beat 3 in 0-indexed (0, 1, 2, 3)
drums_part.insert(offset, note.Note(36, quarterLength=1.0))
# Snare on beats 2 and 4
if beat == 1 or beat == 3:
drums_part.insert(offset, note.Note(38, quarterLength=1.0))
# OFFBEAT hi-hats (the "skank") - CRITICAL for reggae feel
for eighth in range(8):
offset = float(bar * beats_per_bar) + (eighth * 0.5)
# ONLY odd eighths (offbeat) - never on the beat
if eighth % 2 == 1: # 0.5, 1.5, 2.5, 3.5 (offbeat)
drums_part.insert(offset, note.Note(42, quarterLength=0.5))
# WRONG - Four-on-floor (this is house, not reggae!)
# for beat in range(total_beats):
# drums_part.insert(float(beat), note.Note(36, quarterLength=1.0)) # ❌ Kick on every beat
Bass Rules (Non-Negotiable):
- Heavy and prominent - Bass is the lead instrument in reggae
- Octave 1-2 (A1, C2, E2, F1, G1) - not too low, not too high
- Syncopated rhythm - plays between beats, not just on downbeats
- Walking patterns - moves between root, third, fifth of chords
- Quarter to half notes (1.0-2.0 quarterLength) - NOT whole notes like House
# CORRECT Reggae Bass (Am-D-F-G progression, 8-bar pattern)
# This pattern has movement and syncopation - it "walks"
bass_pattern = [
# Bar 1-2: Am (root A)
('A1', 1.0), ('A1', 0.5), ('C2', 0.5), ('A1', 2.0), # Bar 1
('A1', 1.0), ('E2', 1.0), ('A1', 2.0), # Bar 2
# Bar 3-4: D (root D)
('D2', 1.0), ('D2', 0.5), ('F2', 0.5), ('D2', 2.0), # Bar 3
('D2', 1.0), ('A1', 1.0), ('D2', 2.0), # Bar 4
# Bar 5-6: F (root F)
('F1', 1.0), ('F1', 0.5), ('A1', 0.5), ('F1', 2.0), # Bar 5
('F1', 1.0), ('C2', 1.0), ('F1', 2.0), # Bar 6
# Bar 7-8: G (root G, with E for resolution)
('G1', 1.0), ('G1', 0.5), ('B1', 0.5), ('E2', 2.0), # Bar 7
('G1', 1.0), ('D2', 1.0), ('E2', 2.0), # Bar 8
]
# Use .insert() to place bass notes at explicit offsets (synchronizes with drums)
offset = 0.0
for repetition in range(bars // 8):
for pitch, duration in bass_pattern:
bass_part.insert(offset, note.Note(pitch, quarterLength=duration))
offset += duration
# WRONG - Using .append() will cause 8-minute tracks when mixed with .insert() drums
# for pitch, duration in bass_pattern * (bars // 8):
# bass_part.append(note.Note(pitch, quarterLength=duration)) # ❌ Causes timing bug!
Guitar "Skank" Rules (Non-Negotiable):
- OFFBEAT chords only - plays on upbeats (the "and" of beats), never downbeats
- CRITICAL: Sufficient duration - Minimum 0.35-0.4 quarterLength (NOT 0.25) to be audible in mix
- Mid-register voicings - Octaves 3-4 (A3, C4, E4)
- Muted/percussive - In real reggae, these are muted strums creating rhythm
⚠️ CRITICAL NOTE DURATION WARNING:
At 0.25 quarterLength, guitar will be completely inaudible in the mix:
- At 82 BPM:
(60 / 82) × 0.25 = 0.183 seconds(183 milliseconds) - Human perceptual threshold: ~200-300ms needed to register in dense mix
- Organ plays at 0.5 (366ms) - TWICE as long
Use 0.4 quarterLength minimum:
- At 82 BPM:
(60 / 82) × 0.4 = 0.293 seconds(293 milliseconds) - Crosses perceptual threshold while maintaining staccato feel
- Adjust rest to 0.1 to maintain 1.0 beat total per skank cycle
# CORRECT Reggae Guitar "Skank" (offbeat chords with AUDIBLE duration)
guitar_chords = [
['A3', 'C4', 'E4'], # Am
['A3', 'C4', 'E4'], # Am (repeat for 2 bars)
['D3', 'F#3', 'A3'], # D
['D3', 'F#3', 'A3'], # D (repeat for 2 bars)
['F3', 'A3', 'C4'], # F
['F3', 'A3', 'C4'], # F (repeat for 2 bars)
['G3', 'B3', 'D4'], # G
['G3', 'B3', 'D4'], # G (repeat for 2 bars)
]
# Use .insert() to place guitar at explicit offsets (synchronizes with drums/bass)
offset = 0.0
for repetition in range(bars // 8):
for chord_notes in guitar_chords:
# Each bar: 4 offbeat skanks
for beat in range(4):
# REST on the beat (downbeat)
guitar_part.insert(offset, note.Rest(quarterLength=0.5))
offset += 0.5
# CHORD on the offbeat (upbeat) - 0.4 duration for audibility
guitar_part.insert(offset, chord.Chord(chord_notes, quarterLength=0.4))
offset += 0.4
# SHORT REST after chord (creates staccato effect)
guitar_part.insert(offset, note.Rest(quarterLength=0.1))
offset += 0.1
# WRONG - Using .append() causes 8-minute tracks when mixed with .insert() drums
# guitar_part.append(chord.Chord(chord_notes, quarterLength=0.4)) # ❌ Causes timing bug!
# WRONG - Duration too short (will be inaudible!)
# guitar_part.insert(offset, chord.Chord(chord_notes, quarterLength=0.25)) # ❌ Only 183ms @ 82 BPM
Organ "Bubble" Rules:
- Alternating on-and-off pattern - Creates rhythmic "bubbling" effect
- Higher register (octaves 4-5) - Sits above guitar
- Plays same chords as guitar but different rhythm
- Shorter duration (0.5 quarterLength) with rests between
# CORRECT Reggae Organ "Bubble"
organ_chords = [
['A4', 'C5', 'E5'], # Am (high register)
['A4', 'C5', 'E5'],
['D4', 'F#4', 'A4'], # D
['D4', 'F#4', 'A4'],
['F4', 'A4', 'C5'], # F
['F4', 'A4', 'C5'],
['G4', 'B4', 'D5'], # G
['G4', 'B4', 'D5'],
]
# Use .insert() to place organ at explicit offsets (synchronizes with drums/bass/guitar)
offset = 0.0
for repetition in range(bars // 8):
for organ_chord in organ_chords:
# Each bar: bubble pattern (chord, rest, chord, rest)
for _ in range(2): # Twice per bar
organ_part.insert(offset, chord.Chord(organ_chord, quarterLength=0.5))
offset += 0.5
organ_part.insert(offset, note.Rest(quarterLength=0.5))
offset += 0.5
organ_part.insert(offset, chord.Chord(organ_chord, quarterLength=0.5))
offset += 0.5
organ_part.insert(offset, note.Rest(quarterLength=0.5))
offset += 0.5
# WRONG - Using .append() causes 8-minute tracks when mixed with .insert() drums
# organ_part.append(chord.Chord(organ_chord, quarterLength=0.5)) # ❌ Causes timing bug!
Reggae Instruments (MIDI Programs):
- Drums: Channel 9 (MIDI channel 10) - ALWAYS required
- Bass: Program 33 (Electric Bass - finger) or 34 (Electric Bass - pick)
- Guitar:
- PRIMARY: Program 25 (Acoustic Guitar - steel) - Bright, percussive, cuts through mix
- Alternative: Program 28 (Electric Guitar - muted) - Percussive skank sound
- ⚠️ AVOID: Program 27 (Electric Guitar - clean) - Recorded 12-15dB quieter in FluidR3_GM, will be inaudible even at velocity 95
- Organ: Program 16 (Drawbar Organ) or 17 (Percussive Organ)
Setting Instruments with mido (CRITICAL):
Since reggae Parts don't use music21 instrument classes, you MUST use mido to INSERT program_change messages:
from mido import MidiFile, Message
# After score.write('midi', fp=midi_path)
mid = MidiFile(midi_path)
for i, track in enumerate(mid.tracks):
if i == 1: # Drums track
for msg in track:
if hasattr(msg, 'channel'):
msg.channel = 9 # Drums on channel 9
elif i == 2: # Bass track
# INSERT program_change message (don't try to modify - it doesn't exist!)
insert_pos = 0
for j, msg in enumerate(track):
if msg.type == 'track_name':
insert_pos = j + 1
break
track.insert(insert_pos, Message('program_change', program=33, time=0))
elif i == 3: # Guitar track
insert_pos = 0
for j, msg in enumerate(track):
if msg.type == 'track_name':
insert_pos = j + 1
break
track.insert(insert_pos, Message('program_change', program=25, time=0)) # Acoustic steel - bright and audible
elif i == 4: # Organ track
insert_pos = 0
for j, msg in enumerate(track):
if msg.type == 'track_name':
insert_pos = j + 1
break
track.insert(insert_pos, Message('program_change', program=16, time=0))
mid.save(midi_path)
Mixing and Balance (CRITICAL - Guitar Will Be Inaudible Without This!):
Setting the correct instrument programs and velocities is NOT enough. In reggae, the guitar will still be completely inaudible if you don't address THREE issues:
- Soundfont level: Program 27 (Electric Guitar - clean) recorded 12-15dB quieter than program 16 (Drawbar Organ) in FluidR3_GM
- Note duration: 0.25 quarterLength = 183ms @ 82 BPM (below perceptual threshold)
- Velocity difference: Need 40+ point separation
Complete Solution:
def set_track_velocity(track, velocity):
"""Set velocity for all note_on messages in a track."""
for msg in track:
if msg.type == 'note_on' and msg.velocity > 0:
msg.velocity = velocity
# After setting instruments, BEFORE saving
for i, track in enumerate(mid.tracks):
if i == 1: # Drums
set_track_velocity(track, 70)
elif i == 2: # Bass - prominent in reggae
set_track_velocity(track, 80)
elif i == 3: # Guitar - RHYTHM INSTRUMENT, needs to cut through
# Use program 25 (steel acoustic) instead of 27 (too quiet)
# Use 0.4 quarterLength instead of 0.25 (too short)
set_track_velocity(track, 95) # LOUD - this is critical!
elif i == 4: # Organ - BACKGROUND atmosphere
set_track_velocity(track, 55) # QUIET - don't overpower guitar
mid.save(midi_path)
Why this THREE-PART solution works:
- Program 25 (Acoustic Guitar - steel): Recorded 8-10dB louder than program 27, bright harmonics, percussive attack
- Duration 0.4 (293ms): Crosses perceptual threshold vs 0.25 (183ms) which is too short
- Velocity 95 vs 55: 40-point difference creates clear separation
- Combined effect: Guitar now has 3× more presence (program × duration × velocity)
Tempo: 70-90 BPM (classic roots reggae: 80-85 BPM, modern: 85-90 BPM)
Common Mistakes:
- Creating
drums_partbut callingdrums.insert()- use correct variable name - Trying to MODIFY
program_changemessages that don't exist - must INSERT them - Not setting drums to channel 9 - drums will sound like melody notes
- CRITICAL: Using program 27 (too quiet) instead of program 25
- CRITICAL: Using 0.25 quarterLength (too short) instead of 0.4
- CRITICAL: Not setting velocities - guitar will be completely inaudible, organ will dominate
House Specific Guidelines
CRITICAL: House requires extreme repetition and minimal variation to create the hypnotic, groovy feel. Standard composition rules don't apply.
Bass Rules (Non-Negotiable):
- ONE NOTE held for 8-16 bars minimum (32.0-64.0 quarterLength)
- Octave 1-2 RANGE (A1, C2, E2, F1, G1) - the actual bass guitar range (55-110 Hz)
- NEVER use octave 0 (A0, F0, G0, etc.) - these are 20-30 Hz sub-sonic frequencies inaudible on 99% of playback systems (laptop speakers, headphones, even many studio monitors can't reproduce them)
- Whole notes or longer (4.0+ quart
…(truncated)