# Music Generation

> Tools, patterns, and utilities for generating professional music with realistic instrument sounds. Write custom compositions using music21 or learn from existing MIDI files.

- Skill: `aiskillstore/music-generation` (Agent Skill, multi-file: 14 files)
- Install (CLI): `npx skillmds@latest add aiskillstore/music-generation`
- Raw SKILL.md: https://api.skillmd.com/api/skills/aiskillstore/music-generation/raw
- Safety review: pending (external: skill-scanner WARNING, skillspector PASS)
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: aiskillstore (https://skillmd.com/u/aiskillstore)
- Updated: 2026-09-08
- Page: https://skillmd.com/skills/aiskillstore/music-generation

---


## 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
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:**
```bash
apt-get update
apt-get install -y fluidsynth fluid-soundfont-gm fluid-soundfont-gs ffmpeg
```

**Python Dependencies:**
```bash
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

```python
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:**
```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')
```

### 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:**
```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:**
- 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**
```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'
])
```

### 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:
```bash
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:

```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`:
```bash
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

```bash
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

```python
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

```bash
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.**

```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"
```

### 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).**

```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"
```

### 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**:

```python
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)**
```python
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**
```python
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)**
```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**
```python
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

```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
    }
}
```

### music21 Instrument Classes

```python
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:

```python
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.**

```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)
```

### 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

```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)**:
- **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

```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)**:
- **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

```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**:
- **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

```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)**:
- **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:

```python
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:

1. **Soundfont level**: Program 27 (Electric Guitar - clean) recorded 12-15dB quieter than program 16 (Drawbar Organ) in FluidR3_GM
2. **Note duration**: 0.25 quarterLength = 183ms @ 82 BPM (below perceptual threshold)
3. **Velocity difference**: Need 40+ point separation

**Complete Solution**:
```python
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)
