Programmatic music with music21
music21 models notation superbly and models performance not at all, and it does not
render audio. That gap is where generated music goes wrong: theoretically correct, audibly
dead, or silently broken in ways you cannot hear because you cannot listen.
This skill covers both halves — the library in depth, and the performance and rendering
layer that turns a Stream into something a person would play twice.
Setup
pip install music21 # 10.5.0 stable; everything here is verified on it
brew install fluidsynth ffmpeg # or: apt install fluidsynth ffmpeg
python -c "from m21kit import render; print(render.ensure_soundfont())" # ~141MB, once
m21kit/ is this skill's helper library — put it on PYTHONPATH or copy it into the
project. It is plain functions over music21; nothing is subclassed or monkeypatched.
The five rules that matter most
- Give every Part its own MIDI channel. music21 folds Parts that share an instrument
onto one channel, and then one voice's note-off cuts another's identical pitch short.
Always
midiio.write_midi(score, path, channels=[1,2,3,10]). Verify with
midiio.describe_midi.
- Percussion goes on channel 10 or it plays as a piano.
Dynamic scales velocity -- including velocity you set yourself. A note with
velocity = 100 under a Dynamic('pp') renders at 50, because the Dynamic multiplies
rather than defers. Shape velocity by hand and keep Dynamic objects out of the Part.
Hairpins (Crescendo/Diminuendo) do nothing at all. Default velocity is 90.
- Never write below an instrument's real range. A guitar stops at E2 (40). Sampled
patches will render lower notes and they will sound synthetic.
verify.range_check.
- You cannot hear it, so measure it. Before you call anything finished, run the
checks in
references/15-verifying-without-listening.md.
Workflow
Composing from scratch
- Decide form, key/mode, meter, tempo and instrumentation in words first. Name the
climax. If you cannot say why a section exists, the listener won't find a reason.
- Build flat Parts and
insert() at absolute quarter-length offsets — not Measures +
append() — for anything with performance timing. (references/01-object-model.md)
- Melody and harmony from
scales/roman/harmony; voicings as real instrument shapes.
- Add performance: strums, rolls, tremolo, micro-timing, velocity shaping
(
m21kit.perform), and a groove from m21kit.drums.
render.score_to_mp3(score, out, stems=[...]) — one stem per voice, balanced in dB.
- Verify. Fix. Re-render.
Arranging or restyling existing material — get the score first (m21kit
scripts/fetch_score.py, or converter.parse on MIDI/MusicXML/kern/ABC), extract the
line you want, then treat it as step 3 above. Quote the source exactly and prove it with
verify.melody_match; the arrangement lives in the accompaniment, compás and articulation.
From audio — scripts/audio_to_score.py transcribes to a music21 Score, then as above.
Reference chapters
Read the one you need; do not read them all.
|
|
00-quickstart.md |
shortest path to a working piece |
01-object-model.md |
Streams, offsets, flatten vs recurse, makeNotation, deepcopy |
02-pitch-notes-chords.md |
pitch, notes, chords, velocity, microtones, grace notes |
03-meter-tempo-expression.md |
meter incl. additive, tempo maps, ornaments, articulations |
04-harmony-scales-theory.md |
intervals, keys, custom/non-Western scales, roman numerals, chord symbols, figured bass |
05-io-formats.md |
every parse/write format, tinyNotation, MusicXML, PDF/PNG |
06-midi-deep.md |
channels, percussion, ticks, tempo events, round-trip |
07-audio-to-score.md |
transcription: music21's own, and the modern external pipeline |
08-analysis-and-verification.md |
key finding, chordify, features, self-checking |
09-instruments-and-notation.md |
full instrument/GM/range table, transposing instruments |
10-scores-and-datasets.md |
where to get scores, verified sources |
11-audio-rendering.md |
soundfonts, FluidSynth, stems, mixing, mastering |
12-performance-realism.md |
strums, tremolo, swing, humanisation, voicing |
13-idioms-and-grooves.md |
concrete style recipes across traditions |
14-workflows.md |
end-to-end playbooks |
15-verifying-without-listening.md |
read this before shipping anything |
16-pitfalls.md |
202 recorded traps, consolidated from every chapter |
Scripts
python scripts/analyze_score.py <file|corpus-path> # key, chords, form, features
python scripts/audio_to_score.py in.mp3 -o out.mid # audio -> music21
python scripts/fetch_score.py --corpus bach/bwv66.6 # get source material
python scripts/check_ranges.py score.mid --instrument Violin
python scripts/render_audio.py score.mid -o out.mp3 # MIDI -> mastered mp3
python scripts/verify_music.py out.mp3 --midi out.mid
python scripts/build_pitfalls.py # regenerate ch.16 after editing a chapter
python scripts/new_project.py my-piece # scaffold a composition
Honesty
Say that you verified numerically and could not listen. Do not imply you heard it. When
you quote existing music, say what is the source's and what is yours. When a tradition's
rhythm is non-metric (an adhan, an alap, a taranta), do not force it into a compás and
call it faithful — verify.pulse will show you whether you did.
1---2name: programmatic-music3description: Compose, arrange, analyse and render real music in code with music21 — from scratch or from an existing score or audio file. Use when asked to write/compose/generate a piece, song, melody, chord progression, groove or arrangement; to transcribe or "make an mp3 of" something; to arrange or restyle existing music into another genre (jazz, flamenco, orchestral, lo-fi, metal, maqam, raga…); to harmonise, reharmonise, transpose or change the mode/meter/groove of a score; to analyse a MIDI/MusicXML/audio file for key, chords, form or features; or for anything involving music21, MIDI generation, soundfonts, FluidSynth rendering, or turning notation into audio. Don't use for audio DSP with no notes involved (mixing existing stems, mastering a podcast, sound design), or for lyric writing alone.4---56# Programmatic music with music2178music21 models **notation** superbly and models **performance** not at all, and it does not9render audio. That gap is where generated music goes wrong: theoretically correct, audibly10dead, or silently broken in ways you cannot hear because you cannot listen.1112This skill covers both halves — the library in depth, and the performance and rendering13layer that turns a Stream into something a person would play twice.1415## Setup1617```bash18pip install music21 # 10.5.0 stable; everything here is verified on it19brew install fluidsynth ffmpeg # or: apt install fluidsynth ffmpeg20python -c "from m21kit import render; print(render.ensure_soundfont())" # ~141MB, once21```2223`m21kit/` is this skill's helper library — put it on `PYTHONPATH` or copy it into the24project. It is plain functions over music21; nothing is subclassed or monkeypatched.2526## The five rules that matter most27281. **Give every Part its own MIDI channel.** music21 folds Parts that share an instrument29 onto one channel, and then one voice's note-off cuts another's identical pitch short.30 Always `midiio.write_midi(score, path, channels=[1,2,3,10])`. Verify with31 `midiio.describe_midi`.322. **Percussion goes on channel 10** or it plays as a piano.333. **`Dynamic` scales velocity -- including velocity you set yourself.** A note with34 `velocity = 100` under a `Dynamic('pp')` renders at 50, because the Dynamic multiplies35 rather than defers. Shape velocity by hand and keep `Dynamic` objects out of the Part.36 Hairpins (`Crescendo`/`Diminuendo`) do nothing at all. Default velocity is **90**.374. **Never write below an instrument's real range.** A guitar stops at E2 (40). Sampled38 patches will render lower notes and they will sound synthetic. `verify.range_check`.395. **You cannot hear it, so measure it.** Before you call anything finished, run the40 checks in `references/15-verifying-without-listening.md`.4142## Workflow4344**Composing from scratch**45461. Decide form, key/mode, meter, tempo and instrumentation *in words* first. Name the47 climax. If you cannot say why a section exists, the listener won't find a reason.482. Build flat Parts and `insert()` at absolute quarter-length offsets — not Measures +49 `append()` — for anything with performance timing. (`references/01-object-model.md`)503. Melody and harmony from `scales`/`roman`/`harmony`; voicings as real instrument shapes.514. Add performance: strums, rolls, tremolo, micro-timing, velocity shaping52 (`m21kit.perform`), and a groove from `m21kit.drums`.535. `render.score_to_mp3(score, out, stems=[...])` — one stem per voice, balanced in dB.546. Verify. Fix. Re-render.5556**Arranging or restyling existing material** — get the score first (`m21kit`57`scripts/fetch_score.py`, or `converter.parse` on MIDI/MusicXML/kern/ABC), extract the58line you want, then treat it as step 3 above. Quote the source exactly and prove it with59`verify.melody_match`; the arrangement lives in the accompaniment, compás and articulation.6061**From audio** — `scripts/audio_to_score.py` transcribes to a music21 Score, then as above.6263## Reference chapters6465Read the one you need; do not read them all.6667| | |68|---|---|69| `00-quickstart.md` | shortest path to a working piece |70| `01-object-model.md` | Streams, offsets, `flatten` vs `recurse`, `makeNotation`, deepcopy |71| `02-pitch-notes-chords.md` | pitch, notes, chords, **velocity**, microtones, grace notes |72| `03-meter-tempo-expression.md` | meter incl. additive, tempo maps, ornaments, articulations |73| `04-harmony-scales-theory.md` | intervals, keys, **custom/non-Western scales**, roman numerals, chord symbols, figured bass |74| `05-io-formats.md` | every parse/write format, tinyNotation, MusicXML, PDF/PNG |75| `06-midi-deep.md` | **channels, percussion, ticks, tempo events, round-trip** |76| `07-audio-to-score.md` | transcription: music21's own, and the modern external pipeline |77| `08-analysis-and-verification.md` | key finding, `chordify`, features, self-checking |78| `09-instruments-and-notation.md` | full instrument/GM/range table, transposing instruments |79| `10-scores-and-datasets.md` | where to get scores, verified sources |80| `11-audio-rendering.md` | soundfonts, FluidSynth, stems, mixing, mastering |81| `12-performance-realism.md` | strums, tremolo, swing, humanisation, voicing |82| `13-idioms-and-grooves.md` | concrete style recipes across traditions |83| `14-workflows.md` | end-to-end playbooks |84| `15-verifying-without-listening.md` | **read this before shipping anything** |85| `16-pitfalls.md` | 202 recorded traps, consolidated from every chapter |8687## Scripts8889```bash90python scripts/analyze_score.py <file|corpus-path> # key, chords, form, features91python scripts/audio_to_score.py in.mp3 -o out.mid # audio -> music2192python scripts/fetch_score.py --corpus bach/bwv66.6 # get source material93python scripts/check_ranges.py score.mid --instrument Violin94python scripts/render_audio.py score.mid -o out.mp3 # MIDI -> mastered mp395python scripts/verify_music.py out.mp3 --midi out.mid96python scripts/build_pitfalls.py # regenerate ch.16 after editing a chapter97python scripts/new_project.py my-piece # scaffold a composition98```99100## Honesty101102Say that you verified numerically and could not listen. Do not imply you heard it. When103you quote existing music, say what is the source's and what is yours. When a tradition's104rhythm is non-metric (an adhan, an alap, a taranta), do not force it into a compás and105call it faithful — `verify.pulse` will show you whether you did.