-
Notifications
You must be signed in to change notification settings - Fork 0
Score Reading
Everything you can pull out of a loaded score short of editing it.
let md = tk.metadata()?;
println!("{:?}", md.title);
println!("{:?}", md.composer);
println!("{:?}", md.lyricist);
println!("{:?}", md.arranger);
println!("{:?}", md.copyright);
for instr in &md.instruments {
println!("{instr}");
}Source format detection:
-
MEI:
<title>,<persName role="composer|lyricist|arranger">,<availability>/<useRestrict>,<staffDef>/<label> -
MusicXML:
<work-title>,<creator type="composer|lyricist">,<rights>,<part-name>(DTDs allowed) -
PAE / ABC / plaintext: best-effort
T:/C:/Z:scrape
Verovio's C++ Toolkit doesn't surface these — they're parsed from the
verbatim input cached on load_data. One extra String per loaded
score.
let timemap = tk.timemap()?;
for ev in &timemap {
println!("{}ms — on: {:?}, off: {:?}, tempo: {:?}",
ev.tstamp, ev.on, ev.off, ev.tempo);
}Exact (rational) timestamps for tuplet-heavy or long scores where f64 drift matters:
let exact = tk.timemap_exact()?;
for ev in &exact {
let (num, den) = ev.quarter_beats();
let ms_at_60 = ev.tstamp_ms_at_tempo(60.0);
// …
}These walk a cached Timemap without re-entering Verovio. Benchmarked
at ~100× faster than Toolkit::elements_at_time per call.
| Function | What it does |
|---|---|
sounding_at(tm, ms) |
IDs currently sounding (sorted) |
sounding_at_into(tm, ms, &mut Vec) |
Same, buffer reuse |
sounding_count_at(tm, ms) |
Count only (UI badges) |
chord_at(tm, ms) |
IDs sharing the latest onset |
note_duration(tm, id) |
(on_ms, off_ms) for one ID |
events_in_range(tm, start, end) |
Slice of events by ms window |
next_event_after(tm, ms) / prev_event_before
|
Step navigation |
duration_ms(tm) |
Total wall-clock duration |
distinct_element_count(tm) |
Unique-ID count over the score |
measure_at_in(events, ms) |
Enclosing measure ID |
measures_from_events(events) |
Build measure timeline |
measure_by_id(measures, id) |
MeasureInfo by MEI ID |
For monotonic playback (audio / animation loops):
use verovio::lookup::PlaybackCursor;
let mut cursor = PlaybackCursor::new(&timemap);
for tick_ms in clock_ticks() {
let active = cursor.advance_to(tick_ms); // amortized O(1)
// …
}For loop-region practice ([start..end) with auto-wrap):
use verovio::lookup::LoopCursor;
let mut cursor = LoopCursor::new(&timemap, 4000.0, 8000.0);
for tick_ms in monotonic_clock() {
let active = cursor.advance_to(tick_ms); // wraps inside loop region
// …
}let tm = tk.tempo_map()?.unwrap();
tm.qstamp_to_ms(4.0); // beats → ms (handles changes)
tm.ms_to_qstamp(2000.0); // ms → beats
tm.bpm_at_qstamp(4.0); // tempo at quarter position
tm.bpm_at_ms(2000.0); // tempo at wall position
tm.scaled(0.5); // practice mode (50% speed)for m in tk.measures()? {
println!("measure {} from {}ms to {}ms (q {:?}–{:?})",
m.id, m.start_ms, m.end_ms, m.start_qfrac, m.end_qfrac);
}
let kinds = tk.classified_elements()?;
for (id, kind) in &kinds {
use verovio::ElementKind::*;
match kind {
Note => println!("{id} is a note"),
Chord => println!("{id} is a chord"),
Rest => println!("{id} is a rest"),
Measure => println!("{id} is a measure"),
}
}Two side tables, both keyed by MEI ID.
Staff map (id → 1-indexed staff number):
let staff = tk.staff_map()?;
for (id, n) in staff.iter().take(5) {
println!("{id} on staff {n}");
}BBox map (id → SVG-unit pixel rect) — for click-to-seek hit testing and "highlight box around playing note" overlays:
use verovio::BBox;
let bboxes = tk.bbox_map()?;
if let Some(bbox) = bboxes.get("n1") {
println!("note n1 on page {} at ({}, {}) size {}×{}",
bbox.page, bbox.x, bbox.y, bbox.width, bbox.height);
}
// Hit-test a click point
let click_x = 1500.0;
let click_y = 800.0;
let hit = bboxes.iter().find(|(_, bb)| bb.contains(click_x, click_y));Accuracy contract: bbox covers <use> anchor points and <path>
M/L coordinates, with a 200×300 notehead approximation. Good enough
for hit testing and visible highlights; not pixel-perfect for layout
debugging (use Verovio's svgBoundingBoxes option for that).
Given an MEI element ID, these answer "what page is it on, when does it
play, what pitch/duration is it" — the inverse of the timemap walk.
Pairs naturally with bbox_map to
complete the click-to-seek story: hit-test a click against the
bbox map to get an ID, then resolve the ID through these to get
page / wall-clock-ms / pitch.
// Click handler: pixel -> id -> page + onset ms
if let Some((id, _)) = bboxes.iter().find(|(_, bb)| bb.contains(cx, cy)) {
let page = tk.page_with_element(id); // Option<u32>
let onset_ms = tk.time_for_element(id); // Option<u32>
let midi = tk.midi_values_for_element(id)?; // Option<MidiValues { time, pitch, duration }>
// … seek the player to onset_ms, preview the pitch, scroll to page
}Full surface:
| Method | Returns |
|---|---|
page_with_element(id) |
Option<u32> — 1-based page |
time_for_element(id) |
Option<u32> — wall-clock onset (ms) |
times_for_element(id) |
Option<ElementTimes> — qfrac + ms onset / offset / dur |
midi_values_for_element(id) |
Option<MidiValues> — MIDI pitch, duration, onset |
element_attr(id) |
HashMap<String, serde_json::Value> — every MEI attribute |
notated_id_for_element(id) |
String — expansion clone → notated id (pass-through if no expansion) |
expansion_ids_for_element(id) |
Vec<String> — every clone sharing a notated id |
The three time-aware methods (time_for_element,
midi_values_for_element, times_for_element) auto-bootstrap
Verovio's MIDI doc on first call — no need to call render_to_midi
first.
let exp = tk.expansion_map()?;
for (original, expanded) in &exp {
println!("{original} → {expanded:?}"); // expanded order in playback
}Round-trip notated ↔ expansion-clone ids for repeat-heavy scores:
// "What's the original id behind this rendered (expanded) note?"
let notated = tk.notated_id_for_element("note-clone-3");
// "Every place this notated id appears in playback order"
let clones = tk.expansion_ids_for_element("note-1")?;