Detect consecutive perfect intervals (parallel fifths, parallel octaves) in MusicXML scores and highlight where they occur.
Consecutive perfect intervals are a classic voice-leading concern in counterpoint. conseq finds them and marks them with color so you can review them in your notation software and decide what to do.
- Detects parallel fifths, parallel octaves, or both
- Handles rest look-through: parallels across a shared rest gap are still detected even if both voices briefly fall silent
- Works across multiple parts and staves (grand staff, SATB, etc.)
- Independent groups of parallels get distinct colors (up to 8; uses a matplotlib tab10 palette)
- Detections that share a note element are merged into one color group via union-find
- Excludes grace notes (ornamental) and same-voice octave doublings (piano reinforcement)
- Optional navigation markers (
--annotate): one numbered, color-matched rehearsal mark per detected group - Preserves the original XML prologue verbatim (declaration, DOCTYPE)
- Reads/writes stdin and stdout with
- - No runtime dependencies — stdlib only
python conseq.py [--interval {fifths,octaves,both}] [--annotate] input.xml output.xml
| Argument | Description |
|---|---|
input.xml |
Input MusicXML file. Use - to read from stdin. |
output.xml |
Output MusicXML file. Use - to write to stdout. |
--interval |
Which interval type to detect: fifths (default), octaves, or both. |
--annotate |
Insert a numbered, color-matched rehearsal mark (‖1, ‖2, …) above the first note of each detected group, to locate highlights in large scores. In MuseScore these appear in the Timeline panel as clickable jump targets. |
After processing, a summary line is printed to stderr:
4 note(s) in 2 consecutive-fifth group(s) colorized.
With --annotate it ends colorized and marked. instead.
Check for parallel fifths and write a colorized copy:
python conseq.py piece.xml piece_colored.xmlCheck for both parallel fifths and octaves:
python conseq.py --interval both piece.xml piece_colored.xmlPipe through stdin/stdout:
python conseq.py - - < piece.xml > piece_colored.xmlOpen the output in any MusicXML-aware renderer (MuseScore, Sibelius, Dorico, Finale, Flat.io, etc.) to see the colorized notes.
-
Collect notes — Traverse the MusicXML tree, converting each pitched non-grace note into a
NoteInfowith onset/offset times (as exactFractionvalues), MIDI pitch, and a voice key(part_id, staff_id, voice_id). -
Build a voice index — Group notes by attack time and release time per voice. Precompute sorted structures for efficient lookback queries.
-
Detect parallels — For each time point where a perfect interval begins, check two cases:
- Direct boundary: the same voice pair ended the same interval type immediately before.
- Rest look-through: both voices previously sounded that interval, then both fell silent without any intervening attack in either voice, and both re-enter with the same interval.
-
Merge groups — Notes that share an element across detections are unioned via union-find so they receive the same color.
-
Colorize — Set the
colorattribute on each detected<note>element (and its<stem>/<notehead>children if present) and write the modified XML.
- Python 3.8+ (uses the walrus operator)
- No third-party packages
pip install pytest pytest-cov
pytest tests/ -v --cov=conseq --cov-report=term-missing177 tests, 99% coverage.
MIT