Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Tools, patterns, and utilities for generating professional music with realistic instrument sounds. Write custom compositions using music21 or learn from existing MIDI files.
.claude/skills/aiskillstore-music-generation/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-06 | ✗→✓ | ▲ Improved | 320% | 0% |
| case-10 | ✗→✓ | ▲ Improved | 176% | 0% |
| case-17 | ✗→✓ | ▲ Improved | 618% | 0% |
| case-18 | ✗→✓ | ▲ Improved | 183% | 0% |
| case-09 | ✓→✗ | ▼ Worse | 283% | 0% |
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.
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.
Run the automated installer for complete setup:
bashbash /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.
Alternatively, install dependencies manually:
System Dependencies:
bashapt-get update apt-get install -y fluidsynth fluid-soundfont-gm fluid-soundfont-gs ffmpeg
Python Dependencies:
bashpip install -r /mnt/skills/private/music-generation/requirements.txt
The requirements.txt includes: music21, midi2audio, pydub, mido, numpy, scipy.
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:
pythonfrom 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')
/mnt/user-data/outputs/instrument.Violin(), instrument.Violoncello(), instrument.Piano(), instrument.Trumpet(), etc.CRITICAL: This skill supports TWO rendering pipelines. You MUST choose based on the musical genre:
Use when creating:
How to render:
python# 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')
Use when creating:
How to render:
python# 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:
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
python# 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' ])
The electronic renderer includes pre-tuned synthesis presets with supersaw lead synthesis for thick, professional EDM sounds:
Supersaw Synthesis (Swedish House Mafia / Progressive House Sound):
The electronic renderer now includes unison voice synthesis for fat, buzzy leads:
How It Works:
This replicates the classic supersaw sound from Swedish House Mafia, Avicii, and modern EDM productions.
Each preset tunes:
The electronic renderer uses frequency-aware volume balancing to prevent any instrument from overpowering the mix:
How it works:
Why this matters:
To see all available presets:
bashpython /mnt/skills/private/music-generation/scripts/render_electronic.py --list-genres
For advanced control, you can create custom preset JSON files:
json{ "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:
bashpython render_electronic.py track.mid output.mp3 --preset my_preset.json
For classical pieces or complex compositions, you can:
bashpython /mnt/skills/private/music-generation/scripts/midi_inventory.py \ path/to/mozart.mid \ /mnt/user-data/outputs/mozart_structure.json
This extracts:
pythonimport 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)
bashpython /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!
All scripts are located in /mnt/skills/private/music-generation/scripts/:
Main Workflow Scripts:
render_electronic.py - Electronic music renderer with real-time synthesis (drums, bass, pads, leads)midi_inventory.py - Extract complete structure from ANY MIDI file to JSON formatmidi_render.py - Render JSON music structure to MP3 using FluidSynthmidi_transform.py - Generic MIDI transformations (transpose, tempo change, instrument swap)audio_validate.py - Validate audio file quality and formatSynthesis Engine (used by render_electronic.py):
drum_synthesizer.py - Synthesizes kicks, snares, hi-hats, claps on-the-flymelodic_synthesizer.py - Synthesizes bass, pads, and lead sounds using subtractive synthesissynthesis_presets.py - Genre presets (deep_house, techno, trance, ambient, etc.)midi_utils.py - MIDI parsing utilities for extracting events and metadata__init__.py - Python package marker (allows importing scripts as modules)Utility Scripts:
CRITICAL: music21 has limited instrument support. For most sounds (especially electronic), you MUST use mido to set program numbers after export.
python# 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"
Drums use note numbers for different sounds, NOT pitch. Must be on channel 10 (9 in 0-indexed).
python# 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"
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:
pythonfrom 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)
pythonfrom 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
pythonfrom 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)
python# 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
pythonfrom 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')
python# 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 } }
pythonfrom 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
For electronic music and drums, use mido to set MIDI programs after music21 export:
pythonfrom 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:
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.
python# 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)
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):
python# 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):
python# 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):
⚠️ CRITICAL NOTE DURATION WARNING:
At 0.25 quarterLength, guitar will be completely inaudible in the mix:
(60 / 82) × 0.25 = 0.183 seconds (183 milliseconds)Use 0.4 quarterLength minimum:
(60 / 82) × 0.4 = 0.293 seconds (293 milliseconds)python# 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:
python# 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):
Setting Instruments with mido (CRITICAL):
Since reggae Parts don't use music21 instrument classes, you MUST use mido to INSERT program_change messages:
pythonfrom 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:
Complete Solution:
pythondef 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:
Tempo: 70-90 BPM (classic roots reggae: 80-85 BPM, modern: 85-90 BPM)
Common Mistakes:
drums_part but calling drums.insert() - use correct variable nameprogram_change messages that don't exist - must INSERT themCRITICAL: House requires extreme repetition and minimal variation to create the hypnotic, groovy feel. Standard composition rules don't apply.
Bass Rules (Non-Negotiable):
python# CORRECT House Bass (audible on all playback systems) bass_pattern = [ ('A1', 32.0), # A for 8 bars - 55 Hz (bass guitar's lowest note) ('E2', 32.0), # E for 8 bars - 82 Hz (bass guitar's open E string) ('F1', 32.0), # F for 8 bars - 43.7 Hz ('C2', 32.0), # C for 8 bars - 65.4 Hz ] for pitch, duration in bass_pattern: bass_part.append(note.Note(pitch, quarterLength=duration)) # WRONG - Octave 0 is inaudible on most systems! bass_notes = ['A0', 'F0', 'C1', 'G0'] # ❌ 20-35 Hz - below hearing/speaker range! for bar in range(8): bass_part.append(note.Note(bass_notes[bar % 4], quarterLength=32.0)) # ❌ Inaudible! # ALSO WRONG - Too busy, octave jumps bass_notes = ['A1', 'A2', 'C2', 'F1'] # ❌ Octave jumps for bar in range(32): bass_part.append(note.Note(bass_notes[bar % 4], quarterLength=1.0)) # ❌ Too short!
Pad Rules:
python# CORRECT House Pads pad_progression = [ (['A2', 'C3', 'E3'], 64.0), # Am for 16 bars (['F2', 'A2', 'C3'], 64.0), # F for 16 bars ] for chord_notes, duration in pad_progression: pad_part.append(chord.Chord(chord_notes, quarterLength=duration))
Lead Rules:
python# CORRECT House Lead (enters bar 16) lead_pattern = [ ('A4', 8.0), # 2 bars ('C5', 8.0), # 2 bars ('E5', 16.0), # 4 bars ]
Core Principle: If it feels repetitive, you're doing it right. House = hypnotic loop repeated for minutes with minimal changes.
Mixing and Balance:
In House, the bass is the star. But if you don't control velocities, the pads and leads will overpower everything:
pythondef 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 with mido for i, track in enumerate(mid.tracks): if i == 1: # Drums set_track_velocity(track, 90) # Driving rhythm elif i == 2: # Bass (program 38, octaves 1-2) set_track_velocity(track, 75) # Prominent but not overpowering elif i == 3: # Pad (program 88, octaves 2-4) set_track_velocity(track, 50) # Atmospheric background elif i == 4: # Lead (program 80, octaves 4-5) set_track_velocity(track, 95) # Melodic focus (when present) mid.save(midi_path)
python# Groovy syncopated house bass bass_pattern = [ ('A1', 1.0), # Downbeat ('A1', 0.5), # Short hit ('rest', 0.25), # Space ('A1', 0.25), # Syncopation ('A2', 0.5), # Octave jump ('C2', 0.5), # Chord tone ('A1', 1.0) # Resolution ] for pitch, duration in bass_pattern: if pitch == 'rest': bass_part.append(note.Rest(quarterLength=duration)) else: bass_part.append(note.Note(pitch, quarterLength=duration))
python# Jazz voicing (7th chords) jazz_chords = [ ['C4', 'E4', 'G4', 'B4'], # Cmaj7 ['D4', 'F4', 'A4', 'C5'], # Dm7 ['G3', 'B3', 'D4', 'F4'] # G7 ] # House pad voicing (open, atmospheric) house_pads = [ ['A3', 'C4', 'E4'], # Am ['F3', 'A3', 'C4'], # F ['C3', 'E3', 'G3'] # C ] # Classical voicing (close position) classical_chords = [ ['C4', 'E4', 'G4'], # C major ['B3', 'D4', 'G4'], # G major ['C4', 'F4', 'A4'] # F major ]
python# Pentatonic scale (versatile, no "wrong" notes) pentatonic_c = ['C', 'D', 'E', 'G', 'A'] # Major scale major_c = ['C', 'D', 'E', 'F', 'G', 'A', 'B'] # Minor scale minor_a = ['A', 'B', 'C', 'D', 'E', 'F', 'G'] # Blues scale blues_c = ['C', 'Eb', 'F', 'F#', 'G', 'Bb'] # Generate melody algorithmically for i in range(16): octave = 4 + (i // 8) # Move up octave halfway through scale_degree = i % len(pentatonic_c) pitch = pentatonic_c[scale_degree] + str(octave) melody.append(note.Note(pitch, quarterLength=0.5))
pythonfrom music21 import dynamics # Set initial volume part.insert(0, dynamics.Dynamic('p')) # Piano (soft) # Add crescendo at bar 8 part.insert(32, dynamics.Crescendo()) # 32 quarter notes = 8 bars # Peak at bar 10 part.insert(40, dynamics.Dynamic('ff')) # Fortissimo (very loud) # Decrescendo part.insert(60, dynamics.Diminuendo()) # Return to soft part.insert(72, dynamics.Dynamic('p')) # Dynamic markings: ppp, pp, p, mp, mf, f, ff, fff
python# Set tempo (BPM) part.insert(0, tempo.MetronomeMark(number=120)) # 120 BPM # Tempo changes part.insert(32, tempo.MetronomeMark(number=140)) # Speed up at bar 8 # Timing variations (humanization) import random note.Note('C5', quarterLength=1.0 + random.uniform(-0.05, 0.05)) # Common time signatures (set on first part) from music21 import meter part.insert(0, meter.TimeSignature('4/4')) # Most common part.insert(0, meter.TimeSignature('3/4')) # Waltz part.insert(0, meter.TimeSignature('6/8')) # Compound meter
pythonfrom music21 import stream, note, chord, tempo from mido import MidiFile import subprocess # 1. Compose with music21 score = stream.Score() drums = stream.Part() bass_part = stream.Part() pad_part = stream.Part() lead_part = stream.Part() # Set tempo drums.insert(0, tempo.MetronomeMark(number=122)) # Add drums using .insert() for proper layering (not .append()!) bars = 32 beats_per_bar = 4 # Layer 1: Four-on-the-floor kicks for bar in range(bars): for beat in range(beats_per_bar): offset = float(bar * beats_per_bar + beat) 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): drums.insert(float(bar * beats_per_bar + 1), note.Note(38, quarterLength=1.0)) # Beat 2 drums.insert(float(bar * beats_per_bar + 3), note.Note(38, quarterLength=1.0)) # Beat 4 # Layer 3: Hi-hats on eighth notes (offbeat open hats for groove) for bar in range(bars): for eighth in range(8): offset = float(bar * beats_per_bar) + (eighth * 0.5) if eighth % 2 == 0: drums.insert(offset, note.Note(42, quarterLength=0.5)) # Closed hat else: drums.insert(offset, note.Note(46, quarterLength=0.5)) # Open hat (offbeat) # House BASS: Long sustained notes using .insert() (NOT .append()) bass_offset = 0.0 bass_pattern = [ ('A1', 32.0), # A root for 8 bars - 55 Hz (audible on all systems) ('E2', 32.0), # E fifth for 8 bars - 82 Hz ('F1', 32.0), # F for 8 bars - 43.7 Hz ('C2', 32.0), # C for 8 bars - 65.4 Hz ] for pitch, duration in bass_pattern: bass_part.insert(bass_offset, note.Note(pitch, quarterLength=duration)) bass_offset += duration # House PADS: Long sustained chords using .insert() (NOT .append()) pad_offset = 0.0 pad_progression = [ (['A2', 'C3', 'E3'], 64.0), # Am for 16 bars (['F2', 'A2', 'C3'], 64.0), # F for 16 bars ] for chord_notes, duration in pad_progression: pad_part.insert(pad_offset, chord.Chord(chord_notes, quarterLength=duration)) pad_offset += duration # House LEAD: Sparse, long notes, enters late using .insert() (NOT .append()) lead_pattern = [ ('A4', 8.0), # 2 bars ('C5', 8.0), # 2 bars ('E5', 16.0), # 4 bars (long sustain) ] # Lead enters at bar 16 (64 beats in) lead_offset = 64.0 for pitch, duration in lead_pattern: lead_part.insert(lead_offset, note.Note(pitch, quarterLength=duration)) lead_offset += duration score.append(drums) score.append(bass_part) score.append(pad_part) score.append(lead_part) midi_path = '/mnt/user-data/outputs/deep_house_track.mid' score.write('midi', fp=midi_path) # 2. Fix instruments with mido (INSERT program_change messages) from mido import Message 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 must be on channel 9 elif i == 2: # Bass track - 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)) elif i == 3: # Pad track - 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=88, time=0)) elif i == 4: # Lead track - 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=81, time=0)) mid.save(midi_path) # 3. Render with ELECTRONIC pipeline with deep_house preset! mp3_path = '/mnt/user-data/outputs/deep_house_track.mp3' result = subprocess.run([ 'python', '/mnt/skills/private/music-generation/scripts/render_electronic.py', midi_path, mp3_path, '--genre', 'deep_house' ], capture_output=True, text=True) print(result.stdout) if result.returncode == 0: print(f"✓ House track created: {mp3_path}") else: print(f"Error: {result.stderr}")
House Characteristics in This Example:
render_electronic.py with deep_house preset for warm, subby synthesispython import random n = note.Note('C5', quarterLength=1.0 + random.uniform(-0.05, 0.05)) n.volume.velocity = 80 + random.randint(-5, 5)
.insert() and .append(). See "Drum Programming" section for detailstrack.insert(pos, Message('program_change', ...)) not msg.program = X. See mido Quick ReferenceVioloncello()tempo.MetronomeMark() to first partCRITICAL: Setting MIDI program numbers alone is not enough. Without explicit velocity control, some instruments will be completely inaudible.
music21 uses default velocity 64 for all notes, which causes poor mixing. Use mido to set velocities after MIDI export:
pythonfrom mido import MidiFile 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 mid = MidiFile(midi_path) for i, track in enumerate(mid.tracks): if i == 1: # Drums set_track_velocity(track, 75) elif i == 2: # Bass set_track_velocity(track, 80) elif i == 3: # Lead instrument (sax, guitar, trumpet) set_track_velocity(track, 95) elif i == 4: # Background (organ, pads) set_track_velocity(track, 55) mid.save(midi_path)
By Role:
By Frequency Range:
FluidR3_GM instruments are recorded at different levels. Even with correct velocities, some instruments may be inaudible:
Quiet programs (avoid for lead/rhythm):
Better alternatives:
Additional fixes:
Before rendering:
✅ User requests:
❌ Not suitable for:
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-08 | fail→fail | 47,218 | 21,239 | -55% | 1 | 1 | 0% | 8,220 | 19,242 | +134% | 0 | 0 | — |
case-01 | fail→fail | 27,789 | 24,267 | -13% | 1 | 1 | 0% | 577 | 19,544 | +3287% | 0 | 0 | — |
case-02 | fail→fail | 48,408 | 22,490 | -54% | 1 | 1 | 0% | 8,241 | 19,453 | +136% | 0 | 0 | — |
case-03 | fail→fail | 18,299 | 17,585 | -4% | 1 | 1 | 0% | 726 | 19,038 | +2522% | 0 | 0 | — |
case-04 | fail→fail | 48,215 | 20,960 | -57% | 1 | 1 | 0% | 8,229 | 19,558 | +138% | 0 | 0 | — |
case-05 | fail→fail | 16,510 | 18,653 | +13% | 1 | 1 | 0% | 433 | 19,116 | +4315% | 0 | 0 | — |
case-06 | fail→pass | 31,514 | 17,739 | -44% | 1 | 1 | 0% | 4,930 | 20,717 | +320% | 0 | 0 | — |
case-07 | fail→fail | 79,040 | 21,933 | -72% | 1 | 1 | 0% | 12,963 | 19,571 | +51% | 0 | 0 | — |
case-09 | pass→fail | 33,266 | 22,492 | -32% | 1 | 1 | 0% | 5,148 | 19,722 | +283% | 0 | 0 | — |
case-10 | fail→pass | 56,174 | 25,220 | -55% | 1 | 1 | 0% | 8,217 | 22,689 | +176% | 0 | 0 | — |
case-11 | pass→pass | 26,141 | 28,326 | +8% | 1 | 1 | 0% | 4,511 | 21,615 | +379% | 0 | 0 | — |
case-12 | pass→pass | 19,994 | 13,683 | -32% | 1 | 1 | 0% | 2,862 | 20,078 | +602% | 0 | 0 | — |
case-13 | fail→fail | 17,722 | 18,775 | +6% | 1 | 1 | 0% | 515 | 19,020 | +3593% | 0 | 0 | — |
case-14 | fail→fail | 42,901 | 20,652 | -52% | 1 | 1 | 0% | 177 | 19,586 | +10966% | 0 | 0 | — |
case-15 | fail→fail | 48,002 | 19,047 | -60% | 1 | 1 | 0% | 8,238 | 19,297 | +134% | 0 | 0 | — |
case-16 | pass→pass | 13,384 | 9,058 | -32% | 1 | 1 | 0% | 1,697 | 19,230 | +1033% | 0 | 0 | — |
case-17 | fail→pass | 20,704 | 20,751 | +0% | 1 | 1 | 0% | 3,022 | 21,683 | +618% | 0 | 0 | — |
case-18 | fail→pass | 36,349 | 7,281 | -80% | 1 | 1 | 0% | 6,691 | 18,908 | +183% | 0 | 0 | — |
case-19 | fail→fail | 24,688 | 34,677 | +40% | 1 | 1 | 0% | 3,580 | 21,819 | +509% | 0 | 0 | — |
case-20 | fail→fail | 28,637 | 24,191 | -16% | 1 | 1 | 0% | 4,703 | 19,472 | +314% | 0 | 0 | — |
case-21 | fail→fail | 27,043 | 16,518 | -39% | 1 | 1 | 0% | 1,939 | 18,827 | +871% | 0 | 0 | — |
case-22 | fail→fail | 28,352 | 25,284 | -11% | 1 | 1 | 0% | 650 | 19,384 | +2882% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted, and 8 counted toward the lift figure. The other 14 produced results that are not comparable between the two arms, so they are excluded from the headline rather than averaged into it. The headline lift of +14 percentage points is the difference between those two pass rates over the 8 comparable cases. 1 case got worse with the skill loaded, and it is included in that figure.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.