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 format
midi_render.py - Render JSON music structure to MP3 using FluidSynth
midi_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-fly
melodic_synthesizer.py - Synthesizes bass, pads, and lead sounds using subtractive synthesis
synthesis_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_part but calling drums.insert() - use correct variable name
- Trying to MODIFY
program_change messages 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)
1---2name: music-generation3description: Tools, patterns, and utilities for generating professional music with realistic instrument sounds. Write custom compositions using music21 or learn from existing MIDI files.4when_to_use: When users request downloadable .mp3 or .wav music files, original compositions, classical pieces, or timed music for videos. This skill emphasizes intelligence and composition over pre-made functions.5version: 2.0.06dependencies: music21, midi2audio, pydub, mido, numpy, scipy7---89## Quick Start (Read This First!)1011**IMPORTANT: This file is located at `/mnt/skills/private/music-generation/SKILL.md`**1213If 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.1415## Philosophy1617This 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.1819**Core Principle**: Write custom code that composes music algorithmically rather than calling functions with hardcoded melodies.2021## Installation & Setup2223### Quick Installation2425Run the automated installer for complete setup:2627```bash28bash /mnt/skills/private/music-generation/install.sh29```3031This installs all system dependencies, Python packages, and verifies the installation.3233**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.3435### Manual Installation3637Alternatively, install dependencies manually:3839**System Dependencies:**40```bash41apt-get update42apt-get install -y fluidsynth fluid-soundfont-gm fluid-soundfont-gs ffmpeg43```4445**Python Dependencies:**46```bash47pip install -r /mnt/skills/private/music-generation/requirements.txt48```4950The `requirements.txt` includes: music21, midi2audio, pydub, mido, numpy, scipy.5152### Available SoundFonts5354**Traditional Pipeline (Orchestral/Acoustic):**55- `/usr/share/sounds/sf2/FluidR3_GM.sf2` (141MB, General MIDI soundfont for orchestral/classical)56- `/usr/share/sounds/sf2/default.sf2` (symlink to best available)5758**Electronic Pipeline:**59- No soundfonts required - uses real-time synthesis for all electronic sounds6061## Quick Start: Write Custom Compositions6263### Basic Music Generation Pattern6465```python66from music21 import stream, note, chord, instrument, tempo, dynamics67from midi2audio import FluidSynth68from pydub import AudioSegment6970# 1. Create score and parts71score = stream.Score()72violin_part = stream.Part()73violin_part.insert(0, instrument.Violin())74violin_part.insert(0, tempo.MetronomeMark(number=120))7576# 2. Generate notes algorithmically77for measure in range(16):78 violin_part.append(note.Note('E5', quarterLength=1.0))79 violin_part.append(note.Note('G5', quarterLength=1.0))80 violin_part.append(note.Note('A5', quarterLength=2.0))8182# 3. Export to MIDI83score.append(violin_part)84midi_path = '/mnt/user-data/outputs/composition.mid'85score.write('midi', fp=midi_path)8687# 4. Render with FluidSynth88fs = FluidSynth('/usr/share/sounds/sf2/FluidR3_GM.sf2')89wav_path = '/mnt/user-data/outputs/composition.wav'90fs.midi_to_audio(midi_path, wav_path)9192# 5. Convert to MP393audio = AudioSegment.from_wav(wav_path)94mp3_path = '/mnt/user-data/outputs/composition.mp3'95audio.export(mp3_path, format='mp3', bitrate='192k')96```9798### Key Concepts99100- **Always create downloadable MP3 files** (not HTML players)101- **All output goes to** `/mnt/user-data/outputs/`102- **Use music21.instrument classes**: `instrument.Violin()`, `instrument.Violoncello()`, `instrument.Piano()`, `instrument.Trumpet()`, etc.103- **Generate notes programmatically** - avoid hardcoded sequences104105## Choosing the Right Rendering Pipeline106107**CRITICAL**: This skill supports TWO rendering pipelines. You MUST choose based on the musical genre:108109### Traditional Pipeline (Orchestral, Classical, Acoustic)110111**Use when creating:**112- Orchestral music (violin, cello, trumpet, etc.)113- Classical compositions (Mozart, Beethoven style)114- Piano music, chamber music, symphonies115- Acoustic guitar, brass ensembles116- Any music with traditional/acoustic instruments117118**How to render:**119```python120# After composing with music21 and exporting MIDI...121from midi2audio import FluidSynth122from pydub import AudioSegment123124fs = FluidSynth('/usr/share/sounds/sf2/FluidR3_GM.sf2')125fs.midi_to_audio(midi_path, wav_path)126127audio = AudioSegment.from_wav(wav_path)128audio.export(mp3_path, format='mp3', bitrate='192k')129```130131### Electronic Pipeline (House, Techno, EDM, Electronic)132133**Use when creating:**134- House, techno, trance, EDM135- Electronic dance music with synth bass/pads/leads136- DJ beats, club music137- Any music described as "electronic" or "synth-heavy"138- Music referencing DJs like Keinemusik, Black Coffee, etc.139140**How to render:**141```python142# After composing with music21, using mido for instruments, and exporting MIDI...143import subprocess144145# Use the electronic rendering script146result = subprocess.run([147 'python',148 '/mnt/skills/private/music-generation/scripts/render_electronic.py',149 midi_path,150 mp3_path151], capture_output=True, text=True)152153print(result.stdout)154if result.returncode != 0:155 print(f"Error: {result.stderr}")156```157158**Why this matters:**159- The orchestral soundfont (FluidR3_GM.sf2) sounds **terrible** for electronic music160- Its "synth" instruments are basic 1990s approximations161- The electronic pipeline uses **real-time synthesis** for authentic electronic sound162- Synthesizes 808-style kicks, electronic snares, and hi-hats on-the-fly (NO external samples required)163- Bass/pads/leads use subtractive synthesis with filters and ADSR envelopes164- Genre presets (deep_house, techno, trance, ambient) tune synthesis parameters automatically165166**Drum Synthesis:**167168The 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.169170**Example: House Track**171```python172# 1. Compose with music21 (same as always)173score = stream.Score()174drums = stream.Part()175bass = stream.Part()176pads = stream.Part()177# ... compose your music178179# 2. Export MIDI180midi_path = '/mnt/user-data/outputs/deep_house.mid'181score.write('midi', fp=midi_path)182183# 3. Fix instruments with mido (INSERT program_change messages)184from mido import MidiFile, Message185mid = MidiFile(midi_path)186for i, track in enumerate(mid.tracks):187 if i == 1: # Drums188 for msg in track:189 if hasattr(msg, 'channel'):190 msg.channel = 9191 elif i == 2: # Bass - INSERT program_change192 insert_pos = 0193 for j, msg in enumerate(track):194 if msg.type == 'track_name':195 insert_pos = j + 1196 break197 track.insert(insert_pos, Message('program_change', program=38, time=0))198mid.save(midi_path)199200# 4. Render with ELECTRONIC pipeline with deep_house preset!201import subprocess202subprocess.run([203 'python',204 '/mnt/skills/private/music-generation/scripts/render_electronic.py',205 midi_path,206 '/mnt/user-data/outputs/deep_house.mp3',207 '--genre', 'deep_house'208])209```210211### Available Genre Presets212213The electronic renderer includes pre-tuned synthesis presets with **supersaw lead synthesis** for thick, professional EDM sounds:214215- **deep_house**: Warm bass with 3-voice leads (120-125 BPM)216- **techno**: Hard-hitting with 7-voice supersaw leads (125-135 BPM)217- **trance**: Uplifting with massive 9-voice supersaw leads (130-140 BPM)218- **ambient**: Soft, atmospheric with 5-voice pads (60-90 BPM)219- **acid_house**: Squelchy TB-303 bass with 5-voice leads (120-130 BPM)220- **default**: Balanced 5-voice leads (120-130 BPM)221222**Supersaw Synthesis (Swedish House Mafia / Progressive House Sound):**223224The electronic renderer now includes **unison voice synthesis** for fat, buzzy leads:225- **Multiple detuned oscillators**: 3-9 sawtooth waves per note (genre-dependent)226- **Aggressive detuning**: 6-15 cents spread creates buzzy chorus effect227- **Enhanced saturation**: 2.5x distortion for punch and aggression228- **Phase spreading**: Creates wide stereo image229230**How It Works:**231- House: 3 voices, ±8 cents (subtle, warm)232- Techno: 7 voices, ±12 cents (aggressive, punchy)233- Trance: 9 voices, ±15 cents (massive, soaring)234- Acid house: 5 voices, ±12 cents (squelchy, aggressive)235236This replicates the classic supersaw sound from Swedish House Mafia, Avicii, and modern EDM productions.237238Each preset tunes:239- **Drum synthesis**: kick pitch/decay/punch, snare tone/snap, hat brightness/metallic240- **Bass synthesis**: waveform, filter cutoff/resonance, ADSR envelope241- **Pad synthesis**: attack/release times, detune amount, brightness242- **Lead synthesis**: brightness, envelope, portamento243- **Volume balance**: intelligent mix levels per instrument with frequency-aware compensation244- **Velocity curves**: exponential, linear, or logarithmic response to MIDI velocity245246### Intelligent Volume Management247248The electronic renderer uses **frequency-aware volume balancing** to prevent any instrument from overpowering the mix:249250**How it works:**251- **Bass frequencies (<100Hz)**: Automatically reduced by -4 to -6dB (sub-bass has high perceived energy)252- **Mid frequencies (200-800Hz)**: Balanced naturally253- **High frequencies (>800Hz)**: Slightly boosted for clarity (+1 to +1.5dB)254- **Genre-specific balance**: Each preset has optimized levels (e.g., House bass gets -3dB)255- **Velocity curves**: MIDI velocity maps intelligently (not just linear)256- **Auto-limiting**: Final mix is limited to -1dB to prevent clipping257258**Why this matters:**259- House bass (A1, E2) at 55-82Hz naturally has more power - now automatically compensated260- Prevents "bass overpowering everything" issues261- Maintains balanced mix across all genres262- No manual volume tweaking needed263264To see all available presets:265```bash266python /mnt/skills/private/music-generation/scripts/render_electronic.py --list-genres267```268269### Customizing Synthesis Parameters270271For advanced control, you can create custom preset JSON files:272273```json274{275 "drums": {276 "kick": {"pitch": 52.0, "decay": 0.6, "punch": 0.9},277 "snare": {"tone_mix": 0.25, "snap": 0.8}278 },279 "bass": {280 "waveform": "sawtooth",281 "cutoff": 180,282 "resonance": 0.7283 },284 "pad": {285 "attack": 1.0,286 "brightness": 0.35287 }288}289```290291Then use with `--preset`:292```bash293python render_electronic.py track.mid output.mp3 --preset my_preset.json294```295296## Advanced Workflow: Learn from Existing MIDI297298For classical pieces or complex compositions, you can:299300### 1. Extract Structure from ANY MIDI File301302```bash303python /mnt/skills/private/music-generation/scripts/midi_inventory.py \304 path/to/mozart.mid \305 /mnt/user-data/outputs/mozart_structure.json306```307308This extracts:309- Tempo, key signature, time signature310- Track information and instruments311- Complete note sequences with timing312- Musical structure313314### 2. Modify the JSON Structure315316```python317import json318319# Load extracted structure320with open('/mnt/user-data/outputs/mozart_structure.json', 'r') as f:321 structure = json.load(f)322323# Modify instruments, notes, timing, etc.324structure['tracks']['track-0']['instrument'] = 'violin' # Change piano to violin!325326# Save modified structure327with open('/mnt/user-data/outputs/mozart_violin.json', 'w') as f:328 json.dump(structure, f)329```330331### 3. Render Modified Structure to MP3332333```bash334python /mnt/skills/private/music-generation/scripts/midi_render.py \335 /mnt/user-data/outputs/mozart_violin.json \336 /mnt/user-data/outputs/mozart_violin.mp3337```338339**This workflow lets you "recreate" any classical piece with different instruments!**340341## Available Scripts342343All scripts are located in `/mnt/skills/private/music-generation/scripts/`:344345**Main Workflow Scripts:**346- **`render_electronic.py`** - Electronic music renderer with real-time synthesis (drums, bass, pads, leads)347- **`midi_inventory.py`** - Extract complete structure from ANY MIDI file to JSON format348- **`midi_render.py`** - Render JSON music structure to MP3 using FluidSynth349- **`midi_transform.py`** - Generic MIDI transformations (transpose, tempo change, instrument swap)350- **`audio_validate.py`** - Validate audio file quality and format351352**Synthesis Engine (used by render_electronic.py):**353- **`drum_synthesizer.py`** - Synthesizes kicks, snares, hi-hats, claps on-the-fly354- **`melodic_synthesizer.py`** - Synthesizes bass, pads, and lead sounds using subtractive synthesis355- **`synthesis_presets.py`** - Genre presets (deep_house, techno, trance, ambient, etc.)356- **`midi_utils.py`** - MIDI parsing utilities for extracting events and metadata357- **`__init__.py`** - Python package marker (allows importing scripts as modules)358359**Utility Scripts:**360361## Music Theory Reference362363### Complete General MIDI Instrument Map (Programs 0-127)364365**CRITICAL: music21 has limited instrument support. For most sounds (especially electronic), you MUST use mido to set program numbers after export.**366367```python368# Piano (0-7)3690: "Acoustic Grand Piano"3701: "Bright Acoustic Piano"3712: "Electric Grand Piano"3723: "Honky-tonk Piano"3734: "Electric Piano 1"3745: "Electric Piano 2"3756: "Harpsichord"3767: "Clavinet"377378# Chromatic Percussion (8-15)3798: "Celesta"3809: "Glockenspiel"38110: "Music Box"38211: "Vibraphone"38312: "Marimba"38413: "Xylophone"38514: "Tubular Bells"38615: "Dulcimer"387388# Organ (16-23)38916: "Drawbar Organ"39017: "Percussive Organ"39118: "Rock Organ"39219: "Church Organ"39320: "Reed Organ"39421: "Accordion"39522: "Harmonica"39623: "Tango Accordion"397398# Guitar (24-31)39924: "Acoustic Guitar (nylon)"40025: "Acoustic Guitar (steel)"40126: "Electric Guitar (jazz)"40227: "Electric Guitar (clean)"40328: "Electric Guitar (muted)"40429: "Overdriven Guitar"40530: "Distortion Guitar"40631: "Guitar Harmonics"407408# Bass (32-39)40932: "Acoustic Bass"41033: "Electric Bass (finger)"41134: "Electric Bass (pick)"41235: "Fretless Bass"41336: "Slap Bass 1"41437: "Slap Bass 2"41538: "Synth Bass 1"41639: "Synth Bass 2"417418# Strings (40-47)41940: "Violin"42041: "Viola"42142: "Cello"42243: "Contrabass"42344: "Tremolo Strings"42445: "Pizzicato Strings"42546: "Orchestral Harp"42647: "Timpani"427428# Ensemble (48-55)42948: "String Ensemble 1"43049: "String Ensemble 2"43150: "Synth Strings 1"43251: "Synth Strings 2"43352: "Choir Aahs"43453: "Voice Oohs"43554: "Synth Voice"43655: "Orchestra Hit"437438# Brass (56-63)43956: "Trumpet"44057: "Trombone"44158: "Tuba"44259: "Muted Trumpet"44360: "French Horn"44461: "Brass Section"44562: "Synth Brass 1"44663: "Synth Brass 2"447448# Reed (64-71)44964: "Soprano Sax"45065: "Alto Sax"45166: "Tenor Sax"45267: "Baritone Sax"45368: "Oboe"45469: "English Horn"45570: "Bassoon"45671: "Clarinet"457458# Pipe (72-79)45972: "Piccolo"46073: "Flute"46174: "Recorder"46275: "Pan Flute"46376: "Blown Bottle"46477: "Shakuhachi"46578: "Whistle"46679: "Ocarina"467468# Synth Lead (80-87)46980: "Lead 1 (square)"47081: "Lead 2 (sawtooth)"47182: "Lead 3 (calliope)"47283: "Lead 4 (chiff)"47384: "Lead 5 (charang)"47485: "Lead 6 (voice)"47586: "Lead 7 (fifths)"47687: "Lead 8 (bass + lead)"477478# Synth Pad (88-95)47988: "Pad 1 (new age)"48089: "Pad 2 (warm)"48190: "Pad 3 (polysynth)"48291: "Pad 4 (choir)"48392: "Pad 5 (bowed)"48493: "Pad 6 (metallic)"48594: "Pad 7 (halo)"48695: "Pad 8 (sweep)"487488# Synth Effects (96-103)48996: "FX 1 (rain)"49097: "FX 2 (soundtrack)"49198: "FX 3 (crystal)"49299: "FX 4 (atmosphere)"493100: "FX 5 (brightness)"494101: "FX 6 (goblins)"495102: "FX 7 (echoes)"496103: "FX 8 (sci-fi)"497498# Ethnic (104-111)499104: "Sitar"500105: "Banjo"501106: "Shamisen"502107: "Koto"503108: "Kalimba"504109: "Bag pipe"505110: "Fiddle"506111: "Shanai"507508# Percussive (112-119)509112: "Tinkle Bell"510113: "Agogo"511114: "Steel Drums"512115: "Woodblock"513116: "Taiko Drum"514117: "Melodic Tom"515118: "Synth Drum"516119: "Reverse Cymbal"517518# Sound Effects (120-127)519120: "Guitar Fret Noise"520121: "Breath Noise"521122: "Seashore"522123: "Bird Tweet"523124: "Telephone Ring"524125: "Helicopter"525126: "Applause"526127: "Gunshot"527```528529### Complete Drum Map (MIDI Channel 10, Notes 35-81)530531**Drums use note numbers for different sounds, NOT pitch. Must be on channel 10 (9 in 0-indexed).**532533```python534# Bass Drums53535: "Acoustic Bass Drum"53636: "Bass Drum 1" # Most common kick537538# Snares53938: "Acoustic Snare" # Standard snare54040: "Electric Snare"541542# Toms54341: "Low Floor Tom"54443: "High Floor Tom"54545: "Low Tom"54647: "Low-Mid Tom"54748: "Hi-Mid Tom"54850: "High Tom"549550# Hi-Hats55142: "Closed Hi-Hat" # Most used55244: "Pedal Hi-Hat"55346: "Open Hi-Hat"554555# Cymbals55649: "Crash Cymbal 1"55751: "Ride Cymbal 1"55852: "Chinese Cymbal"55953: "Ride Bell"56055: "Splash Cymbal"56157: "Crash Cymbal 2"56259: "Ride Cymbal 2"563564# Percussion56537: "Side Stick"56639: "Hand Clap"56754: "Tambourine"56856: "Cowbell"56958: "Vibraslap"57060: "Hi Bongo"57161: "Low Bongo"57262: "Mute Hi Conga"57363: "Open Hi Conga"57464: "Low Conga"57565: "High Timbale"57666: "Low Timbale"57767: "High Agogo"57868: "Low Agogo"57969: "Cabasa"58070: "Maracas"58171: "Short Whistle"58272: "Long Whistle"58373: "Short Guiro"58474: "Long Guiro"58575: "Claves"58676: "Hi Wood Block"58777: "Low Wood Block"58878: "Mute Cuica"58979: "Open Cuica"59080: "Mute Triangle"59181: "Open Triangle"592```593594### How to Use Any Instrument (mido workflow)595596music21 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:597598**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!599600**Helper Function for Setting Instruments**:601602```python603from mido import Message604605def set_track_instrument(track, program):606 """Insert a program_change message at the beginning of a MIDI track."""607 insert_pos = 0608 for j, msg in enumerate(track):609 if msg.type == 'track_name':610 insert_pos = j + 1611 break612 track.insert(insert_pos, Message('program_change', program=program, time=0))613614# Usage after loading MIDI with mido:615# set_track_instrument(mid.tracks[2], 33) # Set track 2 to Electric Bass616```617618**Step 1: Compose with music21 (use placeholder or skip instrument)**619```python620from music21 import stream, note, chord, tempo621622score = stream.Score()623624# Create parts - don't worry about instrument assignment yet625synth_lead = stream.Part()626synth_pad = stream.Part()627bass = stream.Part()628629# Add your notes/chords630synth_lead.append(note.Note('E5', quarterLength=1.0))631# ... compose your music632633score.append(synth_lead)634score.append(synth_pad)635score.append(bass)636637# Export to MIDI638midi_path = '/mnt/user-data/outputs/track.mid'639score.write('midi', fp=midi_path)640```641642**Step 2: Assign correct instruments with mido**643```python644from mido import MidiFile, Message645646mid = MidiFile(midi_path)647648# Track 0 is tempo/metadata, actual parts start at track 1649# CRITICAL: You must INSERT program_change messages, not just modify existing ones!650# music21 doesn't create program_change messages if you don't assign instruments651652for i, track in enumerate(mid.tracks):653 if i == 1: # First part (synth_lead)654 # Insert program_change at beginning of track (after track name if present)655 insert_pos = 0656 for j, msg in enumerate(track):657 if msg.type == 'track_name':658 insert_pos = j + 1659 break660 track.insert(insert_pos, Message('program_change', program=80, time=0))661662 elif i == 2: # Second part (synth_pad)663 insert_pos = 0664 for j, msg in enumerate(track):665 if msg.type == 'track_name':666 insert_pos = j + 1667 break668 track.insert(insert_pos, Message('program_change', program=88, time=0))669670 elif i == 3: # Third part (bass)671 insert_pos = 0672 for j, msg in enumerate(track):673 if msg.type == 'track_name':674 insert_pos = j + 1675 break676 track.insert(insert_pos, Message('program_change', program=38, time=0))677678mid.save(midi_path)679```680681**Step 3: For drums, ALSO set channel to 9 (channel 10)**682```python683# If track is drums, set ALL messages to channel 9684for i, track in enumerate(mid.tracks):685 if i == 1: # This is the drum track686 for msg in track:687 if hasattr(msg, 'channel'):688 msg.channel = 9 # Channel 10 in 1-indexed689```690691**Step 4: Render to audio**692```python693from midi2audio import FluidSynth694from pydub import AudioSegment695696fs = FluidSynth('/usr/share/sounds/sf2/FluidR3_GM.sf2')697wav_path = '/mnt/user-data/outputs/track.wav'698fs.midi_to_audio(midi_path, wav_path)699700audio = AudioSegment.from_wav(wav_path)701mp3_path = '/mnt/user-data/outputs/track.mp3'702audio.export(mp3_path, format='mp3', bitrate='192k')703```704705### Common Chord Progressions & Styles706707```python708# Standard Progressions (Roman numerals)709"pop": ["I", "V", "vi", "IV"] # C-G-Am-F (Journey, Adele)710"epic": ["i", "VI", "III", "VII"] # Am-F-C-G (Epic trailer music)711"sad": ["i", "VI", "iv", "V"] # Am-F-Dm-E (Melancholic)712"jazz": ["ii", "V", "I", "vi"] # Dm-G-C-Am (Jazz standard)713"classical": ["I", "IV", "V", "I"] # C-F-G-C (Classical cadence)714"blues": ["I", "I", "I", "I", "IV", "IV", "I", "I", "V", "IV", "I", "I"] # 12-bar blues715"house": ["i", "VI", "III", "VII"] # Minor house progression716"reggae": ["I", "V", "vi", "IV"] # Offbeat rhythm style717"country": ["I", "IV", "V", "I"] # Simple and direct718"rock": ["I", "bVII", "IV", "I"] # Power chord style719"r&b": ["I", "V", "vi", "iii", "IV", "I", "IV", "V"] # Complex R&B720721# Genre-Specific Characteristics722STYLES = {723 "house": {724 "bpm": 120-128,725 "time_signature": "4/4",726 "drum_pattern": "4-on-floor kick, offbeat hats",727 "bass": "Synth bass with groove",728 "common_instruments": [38, 80, 88, 4] # Synth bass, lead, pad, e-piano729 },730 "jazz": {731 "bpm": 100-180,732 "time_signature": "4/4 or 3/4",733 "chords": "Extended (7th, 9th, 11th, 13th)",734 "common_instruments": [0, 32, 64, 56, 73] # Piano, bass, sax, trumpet, drums735 },736 "orchestral": {737 "bpm": 60-140,738 "sections": ["strings", "woodwinds", "brass", "percussion"],739 "common_instruments": [40, 41, 42, 56, 73, 47] # Violin, viola, cello, trumpet, flute, timpani740 },741 "rock": {742 "bpm": 100-140,743 "time_signature": "4/4",744 "guitars": "Distorted (30) or clean (27)",745 "common_instruments": [30, 33, 0, 128] # Distortion guitar, bass, piano, drums746 },747 "ambient": {748 "bpm": 60-90,749 "characteristics": "Long sustained notes, atmospheric pads",750 "common_instruments": [88, 89, 90, 91, 52] # Various pads, choir751 },752 "trap": {753 "bpm": 130-170,754 "drums": "Tight snare rolls, 808 bass kicks",755 "hi_hats": "Fast hi-hat patterns (1/16 or 1/32 notes)",756 "common_instruments": [38, 128] # Synth bass, drums757 }758}759```760761### music21 Instrument Classes762763```python764from music21 import instrument765766# Strings767instrument.Violin()768instrument.Viola()769instrument.Violoncello() # Note: NOT Cello()770instrument.Contrabass()771instrument.Harp()772773# Piano774instrument.Piano()775instrument.Harpsichord()776777# Brass778instrument.Trumpet()779instrument.Trombone()780instrument.Tuba()781instrument.Horn() # French horn782783# Woodwinds784instrument.Flute()785instrument.Clarinet()786instrument.Oboe()787instrument.Bassoon()788instrument.SopranoSaxophone()789instrument.AltoSaxophone()790instrument.TenorSaxophone() # Most common for jazz791instrument.BaritoneSaxophone()792793# Other794instrument.AcousticGuitar()795instrument.ElectricGuitar()796instrument.Bass()797instrument.Timpani()798799# CRITICAL: music21 has LIMITED support for electronic instruments and drums800# For synths, drums, and electronic sounds, you MUST:801# 1. Create a Part without an instrument (or use a placeholder like Piano())802# 2. Use mido library to INSERT program_change messages after export803# 3. Set drums to MIDI channel 10 (channel 9 in 0-indexed) or they won't sound like drums804#805# Common mistakes:806# - instrument.Cello() doesn't exist - use Violoncello()807# - instrument.FrenchHorn() doesn't exist - use Horn()808# - Setting part.partName doesn't change the sound - you must set MIDI program with mido809# - Drums on channel 0 will play as pitched notes, not drum sounds810```811812### Note Durations (Quarter Note = 1.0)813814- Whole note: 4.0815- Half note: 2.0816- Quarter note: 1.0817- Eighth note: 0.5818- Sixteenth note: 0.25819- Dotted quarter: 1.5820- Triplet quarter: 0.667821822### mido Quick Reference823824For electronic music and drums, use `mido` to set MIDI programs after music21 export:825826```python827from mido import MidiFile, Message828829mid = MidiFile(midi_path)830831# Insert program_change message832for i, track in enumerate(mid.tracks):833 if i == 1: # Your track (tracks start at 1, not 0)834 insert_pos = 0835 for j, msg in enumerate(track):836 if msg.type == 'track_name':837 insert_pos = j + 1838 break839 track.insert(insert_pos, Message('program_change', program=38, time=0))840841# For drums: Set channel to 9 (channel 10 in 1-indexed)842for i, track in enumerate(mid.tracks):843 if i == 1: # Drum track844 for msg in track:845 if hasattr(msg, 'channel'):846 msg.channel = 9847848mid.save(midi_path)849```850851**Common MIDI Programs:**852- 38: Synth Bass 1853- 80: Square Lead854- 81: Sawtooth Lead855- 88: Pad 1 (New Age)856- 25: Acoustic Guitar (Steel) - loud, cuts through857- 33: Acoustic Bass858859## Common Techniques860861### Drum Programming (4-on-floor house beat)862863**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.864865**⚠️ ALWAYS USE .insert() FOR ALL TRACKS:**866867Since 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.868869**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.870871**The .append() method should only be used in rare cases where you have a single melodic line with no other instruments.**872873```python874# WRONG: This plays kick, then 32 hats, then snare pattern (not layered!)875# for beat in range(16):876# drums.append(note.Note(36, quarterLength=1.0)) # Kicks play first877# for eighth in range(32):878# drums.append(note.Note(42, quarterLength=0.5)) # Hats play AFTER all kicks879# # Result: Timing is completely wrong!880881# CORRECT: Use .insert() with explicit offsets for simultaneous layering882bars = 32883beats_per_bar = 4884total_beats = bars * beats_per_bar885886# Layer 1: Four-on-the-floor kicks (every beat)887for beat in range(total_beats):888 offset = float(beat) # Beat 0, 1, 2, 3, 4, 5, ...889 drums.insert(offset, note.Note(36, quarterLength=1.0))890891# Layer 2: Snare on beats 2 and 4 of each bar892for bar in range(bars):893 # Snare on beat 2 (second beat of bar)894 offset = float(bar * beats_per_bar + 1)895 drums.insert(offset, note.Note(38, quarterLength=1.0))896897 # Snare on beat 4 (fourth beat of bar)898 offset = float(bar * beats_per_bar + 3)899 drums.insert(offset, note.Note(38, quarterLength=1.0))900901# Layer 3: Hi-hats on eighth notes (every 0.5 beats) - creates groove902for bar in range(bars):903 for eighth in range(8): # 8 eighth notes per bar904 offset = float(bar * beats_per_bar) + (eighth * 0.5)905906 if eighth % 2 == 0:907 # Closed hat on even eighths (on the beat)908 drums.insert(offset, note.Note(42, quarterLength=0.5))909 else:910 # Open hat on odd eighths (offbeat) - signature house groove911 drums.insert(offset, note.Note(46, quarterLength=0.5))912913# Result: Properly layered four-on-the-floor with offbeat open hats914# Bar 0: Kicks at 0.0, 1.0, 2.0, 3.0915# Snares at 1.0, 3.0 (on top of kicks)916# Hats at 0.0, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0, 3.5 (layered throughout)917```918919### Reggae Specific Guidelines920921**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.922923**Drum Rules (Non-Negotiable)**:924- **"One Drop" pattern**: Kick drum on beat 3 ONLY (not beat 1), creating the signature reggae "drop"925- **Snare/Rimshot**: Beats 2 and 4 (or just beat 3 with the kick)926- **Hi-hats**: OFFBEAT eighth notes only (never on the beat), creating the "skank" rhythm927- **Cross-stick (note 37)**: Optional on beats 2 and 4 for classic sound928- **NO four-on-floor kicks** - this is house/electronic, not reggae929930```python931# CORRECT Reggae "One Drop" Drum Pattern932bars = 32933beats_per_bar = 4934935for bar in range(bars):936 for beat in range(beats_per_bar):937 offset = float(bar * beats_per_bar + beat)938939 # Kick ONLY on beat 3 (the "drop")940 if beat == 2: # Beat 3 in 0-indexed (0, 1, 2, 3)941 drums_part.insert(offset, note.Note(36, quarterLength=1.0))942943 # Snare on beats 2 and 4944 if beat == 1 or beat == 3:945 drums_part.insert(offset, note.Note(38, quarterLength=1.0))946947 # OFFBEAT hi-hats (the "skank") - CRITICAL for reggae feel948 for eighth in range(8):949 offset = float(bar * beats_per_bar) + (eighth * 0.5)950 # ONLY odd eighths (offbeat) - never on the beat951 if eighth % 2 == 1: # 0.5, 1.5, 2.5, 3.5 (offbeat)952 drums_part.insert(offset, note.Note(42, quarterLength=0.5))953954# WRONG - Four-on-floor (this is house, not reggae!)955# for beat in range(total_beats):956# drums_part.insert(float(beat), note.Note(36, quarterLength=1.0)) # ❌ Kick on every beat957```958959**Bass Rules (Non-Negotiable)**:960- **Heavy and prominent** - Bass is the lead instrument in reggae961- **Octave 1-2** (A1, C2, E2, F1, G1) - not too low, not too high962- **Syncopated rhythm** - plays between beats, not just on downbeats963- **Walking patterns** - moves between root, third, fifth of chords964- **Quarter to half notes** (1.0-2.0 quarterLength) - NOT whole notes like House965966```python967# CORRECT Reggae Bass (Am-D-F-G progression, 8-bar pattern)968# This pattern has movement and syncopation - it "walks"969bass_pattern = [970 # Bar 1-2: Am (root A)971 ('A1', 1.0), ('A1', 0.5), ('C2', 0.5), ('A1', 2.0), # Bar 1972 ('A1', 1.0), ('E2', 1.0), ('A1', 2.0), # Bar 2973974 # Bar 3-4: D (root D)975 ('D2', 1.0), ('D2', 0.5), ('F2', 0.5), ('D2', 2.0), # Bar 3976 ('D2', 1.0), ('A1', 1.0), ('D2', 2.0), # Bar 4977978 # Bar 5-6: F (root F)979 ('F1', 1.0), ('F1', 0.5), ('A1', 0.5), ('F1', 2.0), # Bar 5980 ('F1', 1.0), ('C2', 1.0), ('F1', 2.0), # Bar 6981982 # Bar 7-8: G (root G, with E for resolution)983 ('G1', 1.0), ('G1', 0.5), ('B1', 0.5), ('E2', 2.0), # Bar 7984 ('G1', 1.0), ('D2', 1.0), ('E2', 2.0), # Bar 8985]986987# Use .insert() to place bass notes at explicit offsets (synchronizes with drums)988offset = 0.0989for repetition in range(bars // 8):990 for pitch, duration in bass_pattern:991 bass_part.insert(offset, note.Note(pitch, quarterLength=duration))992 offset += duration993994# WRONG - Using .append() will cause 8-minute tracks when mixed with .insert() drums995# for pitch, duration in bass_pattern * (bars // 8):996# bass_part.append(note.Note(pitch, quarterLength=duration)) # ❌ Causes timing bug!997```998999**Guitar "Skank" Rules (Non-Negotiable)**:1000- **OFFBEAT chords only** - plays on upbeats (the "and" of beats), never downbeats1001- **CRITICAL: Sufficient duration** - Minimum 0.35-0.4 quarterLength (NOT 0.25) to be audible in mix1002- **Mid-register voicings** - Octaves 3-4 (A3, C4, E4)1003- **Muted/percussive** - In real reggae, these are muted strums creating rhythm10041005**⚠️ CRITICAL NOTE DURATION WARNING:**10061007At 0.25 quarterLength, guitar will be **completely inaudible** in the mix:1008- At 82 BPM: `(60 / 82) × 0.25 = 0.183 seconds` (183 milliseconds)1009- Human perceptual threshold: ~200-300ms needed to register in dense mix1010- Organ plays at 0.5 (366ms) - TWICE as long10111012**Use 0.4 quarterLength minimum:**1013- At 82 BPM: `(60 / 82) × 0.4 = 0.293 seconds` (293 milliseconds)1014- Crosses perceptual threshold while maintaining staccato feel1015- Adjust rest to 0.1 to maintain 1.0 beat total per skank cycle10161017```python1018# CORRECT Reggae Guitar "Skank" (offbeat chords with AUDIBLE duration)1019guitar_chords = [1020 ['A3', 'C4', 'E4'], # Am1021 ['A3', 'C4', 'E4'], # Am (repeat for 2 bars)1022 ['D3', 'F#3', 'A3'], # D1023 ['D3', 'F#3', 'A3'], # D (repeat for 2 bars)1024 ['F3', 'A3', 'C4'], # F1025 ['F3', 'A3', 'C4'], # F (repeat for 2 bars)1026 ['G3', 'B3', 'D4'], # G1027 ['G3', 'B3', 'D4'], # G (repeat for 2 bars)1028]10291030# Use .insert() to place guitar at explicit offsets (synchronizes with drums/bass)1031offset = 0.01032for repetition in range(bars // 8):1033 for chord_notes in guitar_chords:1034 # Each bar: 4 offbeat skanks1035 for beat in range(4):1036 # REST on the beat (downbeat)1037 guitar_part.insert(offset, note.Rest(quarterLength=0.5))1038 offset += 0.51039 # CHORD on the offbeat (upbeat) - 0.4 duration for audibility1040 guitar_part.insert(offset, chord.Chord(chord_notes, quarterLength=0.4))1041 offset += 0.41042 # SHORT REST after chord (creates staccato effect)1043 guitar_part.insert(offset, note.Rest(quarterLength=0.1))1044 offset += 0.110451046# WRONG - Using .append() causes 8-minute tracks when mixed with .insert() drums1047# guitar_part.append(chord.Chord(chord_notes, quarterLength=0.4)) # ❌ Causes timing bug!10481049# WRONG - Duration too short (will be inaudible!)1050# guitar_part.insert(offset, chord.Chord(chord_notes, quarterLength=0.25)) # ❌ Only 183ms @ 82 BPM1051```10521053**Organ "Bubble" Rules**:1054- **Alternating on-and-off pattern** - Creates rhythmic "bubbling" effect1055- **Higher register** (octaves 4-5) - Sits above guitar1056- **Plays same chords as guitar** but different rhythm1057- **Shorter duration** (0.5 quarterLength) with rests between10581059```python1060# CORRECT Reggae Organ "Bubble"1061organ_chords = [1062 ['A4', 'C5', 'E5'], # Am (high register)1063 ['A4', 'C5', 'E5'],1064 ['D4', 'F#4', 'A4'], # D1065 ['D4', 'F#4', 'A4'],1066 ['F4', 'A4', 'C5'], # F1067 ['F4', 'A4', 'C5'],1068 ['G4', 'B4', 'D5'], # G1069 ['G4', 'B4', 'D5'],1070]10711072# Use .insert() to place organ at explicit offsets (synchronizes with drums/bass/guitar)1073offset = 0.01074for repetition in range(bars // 8):1075 for organ_chord in organ_chords:1076 # Each bar: bubble pattern (chord, rest, chord, rest)1077 for _ in range(2): # Twice per bar1078 organ_part.insert(offset, chord.Chord(organ_chord, quarterLength=0.5))1079 offset += 0.51080 organ_part.insert(offset, note.Rest(quarterLength=0.5))1081 offset += 0.51082 organ_part.insert(offset, chord.Chord(organ_chord, quarterLength=0.5))1083 offset += 0.51084 organ_part.insert(offset, note.Rest(quarterLength=0.5))1085 offset += 0.510861087# WRONG - Using .append() causes 8-minute tracks when mixed with .insert() drums1088# organ_part.append(chord.Chord(organ_chord, quarterLength=0.5)) # ❌ Causes timing bug!1089```10901091**Reggae Instruments (MIDI Programs)**:1092- **Drums**: Channel 9 (MIDI channel 10) - ALWAYS required1093- **Bass**: Program 33 (Electric Bass - finger) or 34 (Electric Bass - pick)1094- **Guitar**:1095 - **PRIMARY: Program 25 (Acoustic Guitar - steel)** - Bright, percussive, cuts through mix1096 - Alternative: Program 28 (Electric Guitar - muted) - Percussive skank sound1097 - **⚠️ AVOID: Program 27 (Electric Guitar - clean)** - Recorded 12-15dB quieter in FluidR3_GM, will be inaudible even at velocity 951098- **Organ**: Program 16 (Drawbar Organ) or 17 (Percussive Organ)10991100**Setting Instruments with mido (CRITICAL)**:11011102Since reggae Parts don't use music21 instrument classes, you MUST use mido to INSERT program_change messages:11031104```python1105from mido import MidiFile, Message11061107# After score.write('midi', fp=midi_path)1108mid = MidiFile(midi_path)11091110for i, track in enumerate(mid.tracks):1111 if i == 1: # Drums track1112 for msg in track:1113 if hasattr(msg, 'channel'):1114 msg.channel = 9 # Drums on channel 91115 elif i == 2: # Bass track1116 # INSERT program_change message (don't try to modify - it doesn't exist!)1117 insert_pos = 01118 for j, msg in enumerate(track):1119 if msg.type == 'track_name':1120 insert_pos = j + 11121 break1122 track.insert(insert_pos, Message('program_change', program=33, time=0))1123 elif i == 3: # Guitar track1124 insert_pos = 01125 for j, msg in enumerate(track):1126 if msg.type == 'track_name':1127 insert_pos = j + 11128 break1129 track.insert(insert_pos, Message('program_change', program=25, time=0)) # Acoustic steel - bright and audible1130 elif i == 4: # Organ track1131 insert_pos = 01132 for j, msg in enumerate(track):1133 if msg.type == 'track_name':1134 insert_pos = j + 11135 break1136 track.insert(insert_pos, Message('program_change', program=16, time=0))11371138mid.save(midi_path)1139```11401141**Mixing and Balance (CRITICAL - Guitar Will Be Inaudible Without This!)**:11421143Setting 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:114411451. **Soundfont level**: Program 27 (Electric Guitar - clean) recorded 12-15dB quieter than program 16 (Drawbar Organ) in FluidR3_GM11462. **Note duration**: 0.25 quarterLength = 183ms @ 82 BPM (below perceptual threshold)11473. **Velocity difference**: Need 40+ point separation11481149**Complete Solution**:1150```python1151def set_track_velocity(track, velocity):1152 """Set velocity for all note_on messages in a track."""1153 for msg in track:1154 if msg.type == 'note_on' and msg.velocity > 0:1155 msg.velocity = velocity11561157# After setting instruments, BEFORE saving1158for i, track in enumerate(mid.tracks):1159 if i == 1: # Drums1160 set_track_velocity(track, 70)1161 elif i == 2: # Bass - prominent in reggae1162 set_track_velocity(track, 80)1163 elif i == 3: # Guitar - RHYTHM INSTRUMENT, needs to cut through1164 # Use program 25 (steel acoustic) instead of 27 (too quiet)1165 # Use 0.4 quarterLength instead of 0.25 (too short)1166 set_track_velocity(track, 95) # LOUD - this is critical!1167 elif i == 4: # Organ - BACKGROUND atmosphere1168 set_track_velocity(track, 55) # QUIET - don't overpower guitar11691170mid.save(midi_path)1171```11721173**Why this THREE-PART solution works**:1174- **Program 25 (Acoustic Guitar - steel)**: Recorded 8-10dB louder than program 27, bright harmonics, percussive attack1175- **Duration 0.4 (293ms)**: Crosses perceptual threshold vs 0.25 (183ms) which is too short1176- **Velocity 95 vs 55**: 40-point difference creates clear separation1177- **Combined effect**: Guitar now has 3× more presence (program × duration × velocity)11781179**Tempo**: 70-90 BPM (classic roots reggae: 80-85 BPM, modern: 85-90 BPM)11801181**Common Mistakes**:1182- Creating `drums_part` but calling `drums.insert()` - use correct variable name1183- Trying to MODIFY `program_change` messages that don't exist - must INSERT them1184- Not setting drums to channel 9 - drums will sound like melody notes1185- **CRITICAL: Using program 27 (too quiet) instead of program 25**1186- **CRITICAL: Using 0.25 quarterLength (too short) instead of 0.4**1187- **CRITICAL: Not setting velocities - guitar will be completely inaudible, organ will dominate**11881189### House Specific Guidelines11901191**CRITICAL**: House requires **extreme repetition** and **minimal variation** to create the hypnotic, groovy feel. Standard composition rules don't apply.11921193**Bass Rules (Non-Negotiable)**:1194- **ONE NOTE** held for 8-16 bars minimum (32.0-64.0 quarterLength)1195- **Octave 1-2 RANGE** (A1, C2, E2, F1, G1) - the actual bass guitar range (55-110 Hz)1196- **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)1197- **Whole notes or longer** (4.0+ quarterLength minimum, prefer 32.0+)1198- **Simple patterns**: Root for 8 bars → Fifth for 8 bars → repeat1199- **NO octave jumps**, **NO busy basslines**, **NO quarter notes**12001201```python1202# CORRECT House Bass (audible on all playback systems)1203bass_pattern = [1204 ('A1', 32.0), # A for 8 bars - 55 Hz (bass guitar's lowest note)1205 ('E2', 32.0), # E for 8 bars - 82 Hz (bass guitar's open E string)1206 ('F1', 32.0), # F for 8 bars - 43.7 Hz1207 ('C2', 32.0), # C for 8 bars - 65.4 Hz1208]1209for pitch, duration in bass_pattern:1210 bass_part.append(note.Note(pitch, quarterLength=duration))12111212# WRONG - Octave 0 is inaudible on most systems!1213bass_notes = ['A0', 'F0', 'C1', 'G0'] # ❌ 20-35 Hz - below hearing/speaker range!1214for bar in range(8):1215 bass_part.append(note.Note(bass_notes[bar % 4], quarterLength=32.0)) # ❌ Inaudible!12161217# ALSO WRONG - Too busy, octave jumps1218bass_notes = ['A1', 'A2', 'C2', 'F1'] # ❌ Octave jumps1219for bar in range(32):1220 bass_part.append(note.Note(bass_notes[bar % 4], quarterLength=1.0)) # ❌ Too short!1221```12221223**Pad Rules**:1224- **ONE CHORD** held for 8-16 bars (32.0-64.0 quarterLength)1225- **Mid-range octaves** (2-4): A2, C3, E3 voicings1226- **Long attack/release** for smooth transitions1227- Change chords rarely (every 8-16 bars, not every 4 bars)12281229```python1230# CORRECT House Pads1231pad_progression = [1232 (['A2', 'C3', 'E3'], 64.0), # Am for 16 bars1233 (['F2', 'A2', 'C3'], 64.0), # F for 16 bars1234]1235for chord_notes, duration in pad_progression:1236 pad_part.append(chord.Chord(chord_notes, quarterLength=duration))1237```12381239**Lead Rules**:1240- **Sparse** - only play every 4-8 bars, lots of silence1241- **Long notes** (4.0-16.0 quarterLength)1242- **Enter late** (bar 16+, not immediately)1243- **Mid octaves** (A4, C5, E5 max)12441245```python1246# CORRECT House Lead (enters bar 16)1247lead_pattern = [1248 ('A4', 8.0), # 2 bars1249 ('C5', 8.0), # 2 bars1250 ('E5', 16.0), # 4 bars1251]1252```12531254**Core Principle**: If it feels repetitive, you're doing it right. House = **hypnotic loop** repeated for minutes with **minimal changes**.12551256**Mixing and Balance**:12571258In House, the bass is the star. But if you don't control velocities, the pads and leads will overpower everything:12591260```python1261def set_track_velocity(track, velocity):1262 """Set velocity for all note_on messages in a track."""1263 for msg in track:1264 if msg.type == 'note_on' and msg.velocity > 0:1265 msg.velocity = velocity12661267# After setting instruments with mido1268for i, track in enumerate(mid.tracks):1269 if i == 1: # Drums1270 set_track_velocity(track, 90) # Driving rhythm1271 elif i == 2: # Bass (program 38, octaves 1-2)1272 set_track_velocity(track, 75) # Prominent but not overpowering1273 elif i == 3: # Pad (program 88, octaves 2-4)1274 set_track_velocity(track, 50) # Atmospheric background1275 elif i == 4: # Lead (program 80, octaves 4-5)1276 set_track_velocity(track, 95) # Melodic focus (when present)12771278mid.save(midi_path)1279```12801281### Bassline Patterns12821283```python1284# Groovy syncopated house bass1285bass_pattern = [1286 ('A1', 1.0), # Downbeat1287 ('A1', 0.5), # Short hit1288 ('rest', 0.25), # Space1289 ('A1', 0.25), # Syncopation1290 ('A2', 0.5), # Octave jump1291 ('C2', 0.5), # Chord tone1292 ('A1', 1.0) # Resolution1293]12941295for pitch, duration in bass_pattern:1296 if pitch == 'rest':1297 bass_part.append(note.Rest(quarterLength=duration))1298 else:1299 bass_part.append(note.Note(pitch, quarterLength=duration))1300```13011302### Chord Voicings13031304```python1305# Jazz voicing (7th chords)1306jazz_chords = [1307 ['C4', 'E4', 'G4', 'B4'], # Cmaj71308 ['D4', 'F4', 'A4', 'C5'], # Dm71309 ['G3', 'B3', 'D4', 'F4'] # G71310]13111312# House pad voicing (open, atmospheric)1313house_pads = [1314 ['A3', 'C4', 'E4'], # Am1315 ['F3', 'A3', 'C4'], # F1316 ['C3', 'E3', 'G3'] # C1317]13181319# Classical voicing (close position)1320classical_chords = [1321 ['C4', 'E4', 'G4'], # C major1322 ['B3', 'D4', 'G4'], # G major1323 ['C4', 'F4', 'A4'] # F major1324]1325```13261327### Melody Construction13281329```python1330# Pentatonic scale (versatile, no "wrong" notes)1331pentatonic_c = ['C', 'D', 'E', 'G', 'A']13321333# Major scale1334major_c = ['C', 'D', 'E', 'F', 'G', 'A', 'B']13351336# Minor scale1337minor_a = ['A', 'B', 'C', 'D', 'E', 'F', 'G']13381339# Blues scale1340blues_c = ['C', 'Eb', 'F', 'F#', 'G', 'Bb']13411342# Generate melody algorithmically1343for i in range(16):1344 octave = 4 + (i // 8) # Move up octave halfway through1345 scale_degree = i % len(pentatonic_c)1346 pitch = pentatonic_c[scale_degree] + str(octave)1347 melody.append(note.Note(pitch, quarterLength=0.5))1348```13491350### Dynamic Control (Crescendos, Volume Changes)13511352```python1353from music21 import dynamics13541355# Set initial volume1356part.insert(0, dynamics.Dynamic('p')) # Piano (soft)13571358# Add crescendo at bar 81359part.insert(32, dynamics.Crescendo()) # 32 quarter notes = 8 bars13601361# Peak at bar 101362part.insert(40, dynamics.Dynamic('ff')) # Fortissimo (very loud)13631364# Decrescendo1365part.insert(60, dynamics.Diminuendo())13661367# Return to soft1368part.insert(72, dynamics.Dynamic('p'))13691370# Dynamic markings: ppp, pp, p, mp, mf, f, ff, fff1371```13721373### Timing & Tempo13741375```python1376# Set tempo (BPM)1377part.insert(0, tempo.MetronomeMark(number=120)) # 120 BPM13781379# Tempo changes1380part.insert(32, tempo.MetronomeMark(number=140)) # Speed up at bar 813811382# Timing variations (humanization)1383import random1384note.Note('C5', quarterLength=1.0 + random.uniform(-0.05, 0.05))13851386# Common time signatures (set on first part)1387from music21 import meter1388part.insert(0, meter.TimeSignature('4/4')) # Most common1389part.insert(0, meter.TimeSignature('3/4')) # Waltz1390part.insert(0, meter.TimeSignature('6/8')) # Compound meter1391```13921393## Complete Example: Deep House Track13941395```python1396from music21 import stream, note, chord, tempo1397from mido import MidiFile1398import subprocess13991400# 1. Compose with music211401score = stream.Score()1402drums = stream.Part()1403bass_part = stream.Part()1404pad_part = stream.Part()1405lead_part = stream.Part()14061407# Set tempo1408drums.insert(0, tempo.MetronomeMark(number=122))14091410# Add drums using .insert() for proper layering (not .append()!)1411bars = 321412beats_per_bar = 414131414# Layer 1: Four-on-the-floor kicks1415for bar in range(bars):1416 for beat in range(beats_per_bar):1417 offset = float(bar * beats_per_bar + beat)1418 drums.insert(offset, note.Note(36, quarterLength=1.0))14191420# Layer 2: Snare on beats 2 and 4 of each bar1421for bar in range(bars):1422 drums.insert(float(bar * beats_per_bar + 1), note.Note(38, quarterLength=1.0)) # Beat 21423 drums.insert(float(bar * beats_per_bar + 3), note.Note(38, quarterLength=1.0)) # Beat 414241425# Layer 3: Hi-hats on eighth notes (offbeat open hats for groove)1426for bar in range(bars):1427 for eighth in range(8):1428 offset = float(bar * beats_per_bar) + (eighth * 0.5)1429 if eighth % 2 == 0:1430 drums.insert(offset, note.Note(42, quarterLength=0.5)) # Closed hat1431 else:1432 drums.insert(offset, note.Note(46, quarterLength=0.5)) # Open hat (offbeat)14331434# House BASS: Long sustained notes using .insert() (NOT .append())1435bass_offset = 0.01436bass_pattern = [1437 ('A1', 32.0), # A root for 8 bars - 55 Hz (audible on all systems)1438 ('E2', 32.0), # E fifth for 8 bars - 82 Hz1439 ('F1', 32.0), # F for 8 bars - 43.7 Hz1440 ('C2', 32.0), # C for 8 bars - 65.4 Hz1441]14421443for pitch, duration in bass_pattern:1444 bass_part.insert(bass_offset, note.Note(pitch, quarterLength=duration))1445 bass_offset += duration14461447# House PADS: Long sustained chords using .insert() (NOT .append())1448pad_offset = 0.01449pad_progression = [1450 (['A2', 'C3', 'E3'], 64.0), # Am for 16 bars1451 (['F2', 'A2', 'C3'], 64.0), # F for 16 bars1452]14531454for chord_notes, duration in pad_progression:1455 pad_part.insert(pad_offset, chord.Chord(chord_notes, quarterLength=duration))1456 pad_offset += duration14571458# House LEAD: Sparse, long notes, enters late using .insert() (NOT .append())1459lead_pattern = [1460 ('A4', 8.0), # 2 bars1461 ('C5', 8.0), # 2 bars1462 ('E5', 16.0), # 4 bars (long sustain)1463]14641465# Lead enters at bar 16 (64 beats in)1466lead_offset = 64.01467for pitch, duration in lead_pattern:1468 lead_part.insert(lead_offset, note.Note(pitch, quarterLength=duration))1469 lead_offset += duration14701471score.append(drums)1472score.append(bass_part)1473score.append(pad_part)1474score.append(lead_part)14751476midi_path = '/mnt/user-data/outputs/deep_house_track.mid'1477score.write('midi', fp=midi_path)14781479# 2. Fix instruments with mido (INSERT program_change messages)1480from mido import Message1481mid = MidiFile(midi_path)14821483for i, track in enumerate(mid.tracks):1484 if i == 1: # Drums track1485 for msg in track:1486 if hasattr(msg, 'channel'):1487 msg.channel = 9 # Drums must be on channel 91488 elif i == 2: # Bass track - INSERT program_change1489 insert_pos = 01490 for j, msg in enumerate(track):1491 if msg.type == 'track_name':1492 insert_pos = j + 11493 break1494 track.insert(insert_pos, Message('program_change', program=38, time=0))1495 elif i == 3: # Pad track - INSERT program_change1496 insert_pos = 01497 for j, msg in enumerate(track):1498 if msg.type == 'track_name':1499 insert_pos = j + 11500 break1501 track.insert(insert_pos, Message('program_change', program=88, time=0))1502 elif i == 4: # Lead track - INSERT program_change1503 insert_pos = 01504 for j, msg in enumerate(track):1505 if msg.type == 'track_name':1506 insert_pos = j + 11507 break1508 track.insert(insert_pos, Message('program_change', program=81, time=0))15091510mid.save(midi_path)15111512# 3. Render with ELECTRONIC pipeline with deep_house preset!1513mp3_path = '/mnt/user-data/outputs/deep_house_track.mp3'15141515result = subprocess.run([1516 'python',1517 '/mnt/skills/private/music-generation/scripts/render_electronic.py',1518 midi_path,1519 mp3_path,1520 '--genre', 'deep_house'1521], capture_output=True, text=True)15221523print(result.stdout)1524if result.returncode == 0:1525 print(f"✓ House track created: {mp3_path}")1526else:1527 print(f"Error: {result.stderr}")1528```15291530**House Characteristics in This Example:**1531- **Bass**: ONE note (A1, E2, F1, C2) held for 8 bars each - deep bass in octave 1-2 (55-82 Hz range, audible on all playback systems)1532- **Pads**: ONE chord held for 16 bars - extreme sustain creates hypnotic atmosphere1533- **Lead**: Sparse (enters bar 16), long notes (2-4 bars each) - not busy1534- **Repetition**: Minimal variation = hypnotic, groovy House feel1535- Uses `render_electronic.py` with `deep_house` preset for warm, subby synthesis1536- Bass synthesized with proper low-end frequencies that consumer audio equipment can reproduce1537- Pads use slow attack (0.9s) and long release (1.2s) for smooth transitions1538- Genre preset automatically tunes all synthesis parameters1539- No external samples or soundfonts needed - fully self-contained15401541## Best Practices15421543### Composition Quality1544- **Generate variety**: Don't repeat the same 4 bars for entire piece1545- **Use music theory**: Real chord progressions, proper voice leading1546- **Respect instrument ranges**: Violin (G3-E7), Cello (C2-C6), Trumpet (E3-C6)1547- **Add dynamics**: Use p, mp, mf, f, ff markings and crescendos1548- **Structure**: Intro → Development → Climax → Resolution1549- **Add humanization**: Vary timing and velocity to avoid robotic sound1550 ```python1551 import random1552 n = note.Note('C5', quarterLength=1.0 + random.uniform(-0.05, 0.05))1553 n.volume.velocity = 80 + random.randint(-5, 5)1554 ```15551556### Technical Quality1557- **SoundFont**: Use FluidR3_GM.sf2 for best quality1558- **Bitrate**: 192kbps minimum, 320kbps for high quality1559- **Timing precision**: Use quarterLength values carefully1560- **Cleanup**: Remove temporary MIDI/WAV files after MP3 conversion15611562### Common Pitfalls1563- **CRITICAL: Always use .insert() for ALL tracks** - Never mix `.insert()` and `.append()`. See "Drum Programming" section for details1564- **CRITICAL: INSERT program_change messages** - Use `track.insert(pos, Message('program_change', ...))` not `msg.program = X`. See mido Quick Reference1565- **CRITICAL: Set velocities** - Lead 90-105, background 50-65. See "Mixing and Balance" section1566- **CRITICAL: Bass octaves** - Use A1-A2 (55-110 Hz), never A0-G0 (inaudible on most systems)1567- **instrument.Cello()** doesn't exist - use `Violoncello()`1568- **Forgetting tempo** - Add `tempo.MetronomeMark()` to first part1569- **Drums not sounding like drums** - Set channel to 9 with mido (see mido Quick Reference)15701571## Mixing and Balance15721573**CRITICAL**: Setting MIDI program numbers alone is not enough. Without explicit velocity control, some instruments will be **completely inaudible**.15741575### Setting Velocities15761577music21 uses default velocity 64 for all notes, which causes poor mixing. Use mido to set velocities after MIDI export:15781579```python1580from mido import MidiFile15811582def set_track_velocity(track, velocity):1583 """Set velocity for all note_on messages in a track."""1584 for msg in track:1585 if msg.type == 'note_on' and msg.velocity > 0:1586 msg.velocity = velocity15871588mid = MidiFile(midi_path)1589for i, track in enumerate(mid.tracks):1590 if i == 1: # Drums1591 set_track_velocity(track, 75)1592 elif i == 2: # Bass1593 set_track_velocity(track, 80)1594 elif i == 3: # Lead instrument (sax, guitar, trumpet)1595 set_track_velocity(track, 95)1596 elif i == 4: # Background (organ, pads)1597 set_track_velocity(track, 55)1598mid.save(midi_path)1599```16001601### Velocity Guidelines16021603**By Role:**1604- Lead instruments (melody, solos): **90-105**1605- Rhythm instruments (guitar skanks, comping): **85-100**1606- Bass: **75-85**1607- Drums: **70-90**1608- Background (pads, organs): **50-65**16091610**By Frequency Range:**1611- **Low (20-250 Hz)**: Bass, kick - only ONE dominant at 75-851612- **Mid (250-2000 Hz)**: Most crowded - use velocity to separate (lead 90+, background 50-65)1613- **High (2000+ Hz)**: Hi-hats, cymbals - 70-85 for clarity without harshness16141615### Soundfont Level Issues16161617FluidR3_GM instruments are recorded at different levels. Even with correct velocities, some instruments may be inaudible:16181619**Quiet programs** (avoid for lead/rhythm):1620- Program 27 (Electric Guitar - clean) - Very quiet1621- Program 24 (Acoustic Guitar - nylon)1622- Program 73 (Flute)16231624**Better alternatives**:1625- Program 25 (Acoustic Guitar - steel) - 8-10dB louder, cuts through1626- Program 28 (Electric Guitar - muted) - Percussive1627- Program 30 (Distortion Guitar) - Aggressive16281629**Additional fixes:**1630- Increase note duration (0.4 quarterLength minimum vs 0.25)1631- Use octave separation (move competing instruments to different octaves)1632- Extreme velocity contrast (quiet instrument at 110, loud at 40)16331634### Mixing Checklist16351636Before rendering:1637- ✅ Lead at velocity 90-1051638- ✅ Background at velocity 50-651639- ✅ Bass at velocity 75-851640- ✅ Check for quiet instruments (programs 24, 27, 73) and use alternatives1641- ✅ Minimum 0.4 quarterLength for rhythm instruments16421643## Resources16441645- **music21 Documentation**: https://web.mit.edu/music21/doc/1646- **General MIDI Spec**: https://www.midi.org/specifications-old/item/gm-level-1-sound-set1647- **Music Theory**: https://www.musictheory.net/1648- **IMSLP (Free Scores)**: https://imslp.org/ - Download classical MIDIs here!16491650## Limitations16511652- **Instrumental only** - No lyrics/vocals1653- **MIDI-based synthesis** - Not studio-quality recordings1654- **No real-time playback** - Files must be rendered before playback1655- **SoundFont quality** - Good but not as realistic as sample libraries16561657## When to Use This Skill16581659✅ User requests:1660- Original compositions with specific moods/styles1661- Classical music in MP3 format1662- Timed music for videos/presentations1663- Specific instrumentation (orchestral, piano, strings, etc.)1664- Dynamic music with crescendos, tempo changes16651666❌ Not suitable for:1667- Vocal/lyrical music1668- Audio mixing/mastering (reverb, EQ, compression)1669- Real-time MIDI playback1670- Professional studio recording quality