MIDI Piano Roll Renderer
When to Use
- A MIDI file must be turned into a piano-roll picture for reading, teaching, or sharing.
- The user wants to see a song's structure: melody vs. accompaniment, chord progression, key, tempo.
- Choosing between a vertical falling-notes layout (default — easiest to play along, time flows down, inspired by Synthesia) and a horizontal DAW-style roll (option for wide screens / long phrases).
Turns any MIDI file into a polished piano-roll PNG: colored note blocks with note letters inside, a large chord track, automatic key + tempo detection, and a chromatic color legend. Two orientations are supported: vertical (time ↓ down, Synthesia-style falling notes — default) and horizontal (time → right, classic DAW style).
Requirements
- Python 3.8+ with
mido and Pillow (pip install mido pillow)
- A bold monospace TTF for labels — the scripts use
consolab.ttf (Windows Consolas Bold) and silently fall back to PIL's default font elsewhere
Usage
# vertical roll (time ↓ down, portrait) — DEFAULT, best for playing along
python scripts/render_vertical.py song.mid song_vertical.png
# horizontal roll (time → right, DAW-style) — option for wide screens / long phrases
python scripts/render_any.py song.mid song_roll.png
Common options (both scripts):
| Flag |
Meaning |
--bars N |
Render exactly N bars (skips auto-detection) |
--start N |
Start from bar N (skip an empty intro) |
--mel T |
Force melody track index |
--acc T |
Force accompaniment/bass track index |
Example:
# Skip a sparse 10-bar intro, start at the main groove, force track roles
python scripts/render_any.py mario.mid mario_roll.png --mel 1 --acc 6 --start 10
What it does automatically
- Track picking — melody = the note-rich track with the highest average pitch; accompaniment = the note-richest remaining track. Override with
--mel / --acc when a MIDI has odd instrumentation (e.g., a percussion track with a higher average pitch).
- Key detection — correlates the duration-weighted pitch-class profile against major/minor templates, then annotates the key signature accidental count, e.g.
Key: Gm (bb), Key: C# (#######).
- Chord labels — per-bar weighted pitch-class template matching (triads, 7ths, maj7, sus2/sus4). Consecutive identical chords are merged into one label. Labels are anchored at the chord's entry beat, not centered over the merged span.
- Bar count (vertical script) — measures the shortest note in the window and guarantees it at least ~30 px tall by stretching the canvas; never renders fewer than 4 bars. With an explicit
--bars, the canvas keeps a 4:3-derived height instead.
- Note letters — every melody note gets its name (C, D#, …) inside the block; very short notes get an auto-shrunk font so the letter always fits.
- Adaptive legend — the 13-swatch chromatic legend and the metadata line auto-shrink to fit narrow canvases.
Rendering conventions (dark theme)
- One bright palette color per pitch class (12 colors), accompaniment in muted steel blue.
- Black-key lanes darker than white-key lanes; thin dark lines separate every semitone row; bold white lines mark octaves (labeled C2…C7) and bar lines.
- Legend strip is always drawn below the grid, never overlapping it.
- Chord labels in bold yellow, large (110–150 px): top banner (horizontal) or left column (vertical).
Known limitations
- The chord/key heuristics are simple template matches: fast runs and chromatic passing tones can produce wrong labels (e.g.,
Emaj7 for a plain C major passage) and enharmonic oddities (A#m with 7 sharps instead of the conventional Bbm). Labels are advisory — hand-correct for publication.
- Percussion tracks can win the melody pick on sparse files; check the printed
melody=<idx> line and override if needed.
- MIDI files with very long silent intros render nearly empty — use
--start to jump into the music.
Files
scripts/render_vertical.py — vertical renderer (default; falling-notes style, best for playing)
scripts/render_any.py — horizontal renderer (DAW-style alternative)
Both are standalone (stdlib + mido + Pillow) and safe to copy into any project.
Example images
assets/tetris_vertical.png — vertical: Tetris (Korobeiniki) theme, 4 bars, A minor 80 BPM
assets/september_horizontal.png — horizontal: Wake Me Up When September Ends, 8 bars, G major 104 BPM
1---2name: midi-pianoroll-render3description: Use when a MIDI file needs rendering as a piano roll image.4license: MIT5---67# MIDI Piano Roll Renderer89## When to Use1011- A MIDI file must be turned into a **piano-roll picture** for reading, teaching, or sharing.12- The user wants to **see a song's structure**: melody vs. accompaniment, chord progression, key, tempo.13- Choosing between a **vertical** falling-notes layout (default — easiest to play along, time flows down, inspired by Synthesia) and a **horizontal** DAW-style roll (option for wide screens / long phrases).1415Turns any MIDI file into a polished piano-roll PNG: colored note blocks with note letters inside, a large chord track, automatic key + tempo detection, and a chromatic color legend. Two orientations are supported: **vertical** (time ↓ down, Synthesia-style falling notes — default) and **horizontal** (time → right, classic DAW style).1617## Requirements1819- Python 3.8+ with `mido` and `Pillow` (`pip install mido pillow`)20- A bold monospace TTF for labels — the scripts use `consolab.ttf` (Windows Consolas Bold) and silently fall back to PIL's default font elsewhere2122## Usage2324```bash25# vertical roll (time ↓ down, portrait) — DEFAULT, best for playing along26python scripts/render_vertical.py song.mid song_vertical.png2728# horizontal roll (time → right, DAW-style) — option for wide screens / long phrases29python scripts/render_any.py song.mid song_roll.png30```3132Common options (both scripts):3334| Flag | Meaning |35|------|---------|36| `--bars N` | Render exactly N bars (skips auto-detection) |37| `--start N` | Start from bar N (skip an empty intro) |38| `--mel T` | Force melody track index |39| `--acc T` | Force accompaniment/bass track index |4041Example:4243```bash44# Skip a sparse 10-bar intro, start at the main groove, force track roles45python scripts/render_any.py mario.mid mario_roll.png --mel 1 --acc 6 --start 1046```4748## What it does automatically4950- **Track picking** — melody = the note-rich track with the highest average pitch; accompaniment = the note-richest remaining track. Override with `--mel` / `--acc` when a MIDI has odd instrumentation (e.g., a percussion track with a higher average pitch).51- **Key detection** — correlates the duration-weighted pitch-class profile against major/minor templates, then annotates the key signature accidental count, e.g. `Key: Gm (bb)`, `Key: C# (#######)`.52- **Chord labels** — per-bar weighted pitch-class template matching (triads, 7ths, maj7, sus2/sus4). Consecutive identical chords are merged into one label. Labels are anchored at the chord's entry beat, not centered over the merged span.53- **Bar count (vertical script)** — measures the shortest note in the window and guarantees it at least ~30 px tall by stretching the canvas; never renders fewer than 4 bars. With an explicit `--bars`, the canvas keeps a 4:3-derived height instead.54- **Note letters** — every melody note gets its name (C, D#, …) inside the block; very short notes get an auto-shrunk font so the letter always fits.55- **Adaptive legend** — the 13-swatch chromatic legend and the metadata line auto-shrink to fit narrow canvases.5657## Rendering conventions (dark theme)5859- One bright palette color per pitch class (12 colors), accompaniment in muted steel blue.60- Black-key lanes darker than white-key lanes; thin dark lines separate every semitone row; bold white lines mark octaves (labeled C2…C7) and bar lines.61- Legend strip is always drawn below the grid, never overlapping it.62- Chord labels in bold yellow, large (110–150 px): top banner (horizontal) or left column (vertical).6364## Known limitations6566- The chord/key heuristics are simple template matches: fast runs and chromatic passing tones can produce wrong labels (e.g., `Emaj7` for a plain C major passage) and enharmonic oddities (`A#m` with 7 sharps instead of the conventional `Bbm`). Labels are advisory — hand-correct for publication.67- Percussion tracks can win the melody pick on sparse files; check the printed `melody=<idx>` line and override if needed.68- MIDI files with very long silent intros render nearly empty — use `--start` to jump into the music.6970## Files7172- `scripts/render_vertical.py` — vertical renderer (default; falling-notes style, best for playing)73- `scripts/render_any.py` — horizontal renderer (DAW-style alternative)7475Both are standalone (stdlib + mido + Pillow) and safe to copy into any project.7677## Example images7879- `assets/tetris_vertical.png` — vertical: Tetris (Korobeiniki) theme, 4 bars, A minor 80 BPM80- `assets/september_horizontal.png` — horizontal: Wake Me Up When September Ends, 8 bars, G major 104 BPM