How to Load an MPM-Toolbox Project (Score, Modelled Performance, Observed Alignment)
MpmLoader, score in ticks and quarters, MPM markup as events, modelled quarters/seconds TableMap, observed alignment MatchClaims
How to Load an MPM-Toolbox Project (Score, Modelled Performance, Observed Alignment)
An MPM-Toolbox project is a triple of sibling XML files describing one musical work: a .msm notated score, a .mpmmodelled performance overlay (tempo, dynamics, articulation, … as expressive markup), and a .mpr project file carrying an observed audio-to-score alignment. This guide loads such a project in a single call and arranges it as one multimodal spanning the logical (score), physical (performance), and graphical (spectrogram) domains.
The work is Beethoven’s Eroica Variations, Op. 35 — Var. XIV — in a 1971 Curzon recording. By the end we will have, in one bundle: the notated score in two logical units, the performance markup carried as s, a modelled tempo curve mapping score quarters to seconds, the observed onsets tied back to the score note by note, and the recording’s spectrogram as a graphical time axis.
We load the modelled markup and the observed alignment exactly as they sit on disk. Nothing here runs an aligner, and the tempo model is read as written — we do not render a beat-accurate performance from it.
The arc:
Load the whole project in one call.
Read the logical score in two units, linked by a .
Read the performance markup carried as s on the score.
Read the modelled quarters→seconds tempo map.
Read the observed alignment — the physical performance and its cross-group s.
Read the recording’s spectrogram as a graphical (pixels) time axis.
/home/laser/miniconda3/envs/timetoalign/lib/python3.11/site-packages/partitura/__init__.py:9: UserWarning: pkg_resources is deprecated as an API. See https://setuptools.pypa.io/en/latest/pkg_resources.html. The pkg_resources package is slated for removal as early as 2025-11-30. Refrain from using this package or pin to Setuptools<81.
import pkg_resources
1. Load the project in one call
is given the .mpr project file; it resolves the sibling .msm and .mpm by the bare filenames the project names, parses the score, the selected performance’s markup, and the observed alignment, and binds the recording’s audio for its sample rate. from_file() is the one-line form of the standard two-phase loader pattern.
The .mpm may hold several <performance> blocks; by default the first is selected. The pulses-per-quarter grid (720) and the chosen performance name are read straight from the files. To select a different performance, pass MpmLoader().load(MPR, performance="...").
{"pulses per quarter": loader.ppq,"performance": loader.performance_name,}
{'pulses per quarter': 720, 'performance': 'MEI export performance'}
create_bundle() assembles the : four timelines arranged in two groups — a shared logical "score" group and a physical "perf" group. Everything below reads from this single bundle.
Both hold the same 251 notes. The .msm records each note’s MIDI pitch together with the spelling it was notated with (pitchname / octave); a look at the first few notes of score:clt1 shows what the score carried:
The tick grid and the quarter grid are one conversion apart
The two logical timelines are not independent: a TicksToQuarters on score:dlt1 carries the tick grid to quarters — 720 ticks to the quarter. A on score:dlt1 exposes the conversion at any coordinate, so asking for the quarter reading at a tick is the continuous↔︎discrete link of the logical domain made visible:
A .mpm overlays the score with expressive markup — tempo, dynamics, articulation, asynchrony, and any other map type the project carries. The loader places every markup entry on score:dlt1 as an at its tick onset, sitting alongside the Note events. A single logical timeline therefore carries both the notes and the modelled performance markup, read with the same event query used everywhere else — filter by event_type.
The coordinate (start) comes back as a number; the remaining markup columns currently round-trip as strings, so we cast them as we read.
Tempo
Each Tempo event carries a beats-per-minute reading. The MPM value may be an inline number or a style name declared in the performance’s style block; either way the loader resolves it to a number, keeping the original token in a *_label column. Here the style name "Meno mosso." resolves to 100 BPM:
Dynamics events resolve the same way: the dynamic mark "p" is a style name that resolves to a MIDI volume of 48, with the mark preserved in volume_label:
Articulation events name an articulation (staccato, tenuto, …) and, where the performance declares a definition for it, resolve its numeric attributes. A staccato here resolves to an absolute played duration of 160 ms; the noteid it applies to is carried alongside (its leading # stripped):
The point of this section: tempo and dynamics style names resolve to numeric values, articulations resolve to played durations and velocities, and every one of them is an event on the score timeline, queried the same way the notes are. Any map type the loader does not model specially is still emitted, with its raw attributes carried verbatim, so nothing in a project is silently dropped.
4. The modelled tempo, as quarters → seconds
The performance’s tempo markup also defines, segment by segment, how fast the score is taken. The loader integrates it into a modelled quarters→seconds — a TableMap it exposes directly:
It is anchored at the score’s start (0 quarters → 0 seconds) and walks forward at each tempo segment’s pace. Converting a couple of quarter positions reads the modelled clock-time at which the score reaches them:
This is a constant-tempo-per-segment model: each tempo entry sets a flat pace until the next. Accelerando / ritardando ramps (an entry’s transition.to) are preserved as a Tempo-event attribute but are not rendered into the curve — the map reads what the project modelled, deliberately stopping short of synthesising a beat-accurate performance.
5. The observed alignment
The model above is one account of the performance. The .mpr carries another: an observed alignment, recording for every score note the moment it was actually played in the recording. The loader places these onsets in the physical "perf" group, in two units — perf:cpt1 in seconds and perf:dpt1 in samples, linked (as in any physical timeline) by a SamplesToSeconds carrying the recording’s sample rate.
The score group and the performance group are tied together by cross-group s — one per score note. The .mpr alignment is a perfect bijection (every score note has exactly one observed onset and vice versa), so every claim is synchronous: there are no gaps.
claims = bundle.cross_group_claims{"total claims": len(claims),"synchronous (matched)": sum(1for c in claims if c.is_synchronous),"NOMATCH": sum(1for c in claims ifnot c.is_synchronous),}
A single claim relates a score quarter to an observed performed second. This one anchors the note at score quarter 0.5 to the moment it was played, 0.8 seconds into the recording:
quarter_half_claims = [ cfor c in claimsif c.is_synchronousand c.start_anchor isnotNoneandfloat(c.start_anchor.coordinate_a) ==0.5]quarter_half_claims[0]
MatchClaimsynchronous, instant
Timeline A
score:clt1
@0.5
Timeline B
perf:cpt1
@0.828019
Metadata
agent=mpm
Try: claim.get_matchstamp()
One score position, read across the domains
Because the performance is anchored back to the score, a single score coordinate resolves across the whole bundle. get_matchstamp_at takes a coordinate on score:clt1 (quarters) and returns the corresponding coordinate on every connected timeline — here score quarter 2.0 mapped to the second at which it was observed:
Read across that : the same notated quarter resolves to a logical position (2.0 quarters) and an observed physical position (≈ 2.18 seconds), one query crossing from the score domain to the performance domain with no aligner ever run.
6. The spectrogram, a graphical axis
The project also ships a rendered spectrogram of the recording — a .png whose horizontal axis is time. The loader reads that axis as a third performance-group , perf:dgt1, measured in pixels (frame columns): one position per column of the image. This is the graphical domain — the same work, now reached visually.
Unlike the other timelines, this one carries no s. A spectrogram column is not a note; it is a tick mark on a picture’s time axis. The timeline’s job is to be that axis — its length is the image’s width in frame columns:
{"unit": perf_dgt.unit.name,"length (frame columns)": perf_dgt.length.value,"events (it is an axis, not events)": perf_dgt.n_events,}
{'unit': 'pixels',
'length (frame columns)': 26469,
'events (it is an axis, not events)': 0}
A pixel column is only meaningful once tied to clock time. Each column advances the recording by a fixed hop of audio samples, so perf:dgt1 carries a px→seconds — a ScalarMap whose scalar is hopSize / sample_rate. Pulling it off the timeline and reading a couple of columns places the picture on the same seconds clock as the performance:
Column 0 sits at the start of the recording; the rightmost column lands at the spectrogram’s full time span — the audio duration. The picture’s x-axis and the performance’s seconds are now one conversion apart, exactly as ticks and quarters were on the score.
Recap
What the bundle expresses
How
Score, two logical units
score:dlt1 (ticks) + score:clt1 (quarters), one TicksToQuarters map
Performance markup
Tempo / Dynamics / Articulation events on score:dlt1, via filter(event_type=...)
Modelled tempo
a quarters→seconds TableMap (loader.tempo_map), constant-tempo-per-segment
Observed performance, two physical units
perf:cpt1 (s) + perf:dpt1 (samples), one SamplesToSeconds
Spectrogram, a graphical axis
perf:dgt1 (pixels), no events, one px→seconds ScalarMap
Score ↔︎ performance
one synchronous per note (a perfect bijection)
A score position read across domains
bundle.get_matchstamp_at(coord, "score:clt1")
One now expresses this single work across all three domains — logical (score ticks and quarters), physical (performance seconds and samples), and graphical (spectrogram pixels). Within each group the units are tied by s; across the groups the score and performance are tied note by note by s. A single object holds what was notated, how it was modelled, how it was actually played, and how it looks — the score, the model, the recording, and its picture, all on one set of linked clocks.