#music-notation #verovio #musicxml #mei #music

verovioxide

Safe Rust bindings to the Verovio music notation engraving library

5 releases (3 breaking)

Uses new Rust 2024

0.3.0 Feb 7, 2026
0.2.0 Feb 2, 2026
0.1.1 Feb 1, 2026
0.1.0 Feb 1, 2026
0.0.1 Jan 31, 2026

#343 in Parser implementations

Apache-2.0

645KB
5.5K SLoC

Verovioxide

Safe Rust bindings to the Verovio music notation engraving library

Features

  • Multi-format input: Load MusicXML, MEI, ABC, Humdrum, and Plaine & Easie notation
  • Multi-format output: Render to SVG, export to MEI/Humdrum/PAE, generate MIDI
  • Bundled fonts: Leipzig, Bravura, Gootville, Leland, and Petaluma (SMuFL-compliant)
  • Type-safe API: Builder pattern for options with serde serialization
  • Zero runtime dependencies: Verovio statically linked
  • Complete API coverage: 100% of the Verovio C++ API wrapped in safe Rust
  • Production ready: Comprehensive error handling and 95%+ test coverage
  • PNG support: Verovioxide extends beyond the Verovio C++ API with PNG output (in-memory and to-file)

Installation

cargo add verovioxide

The first build compiles the Verovio C++ library from source, which takes several minutes. Subsequent builds use cached artifacts and are fast.

Faster Builds with Prebuilt Binaries

For faster initial builds, use the prebuilt feature which downloads a pre-compiled Verovio library:

cargo add verovioxide-sys --features prebuilt
cargo add verovioxide

Or in your Cargo.toml:

[dependencies]
verovioxide = "0.3"
verovioxide-sys = { version = "0.3", features = ["prebuilt"] }

Prebuilt binaries are available for:

  • macOS (x86_64, aarch64)
  • Linux (x86_64, aarch64)
  • Windows (x86_64 MSVC)

If prebuilt binaries aren't available for your platform, it automatically falls back to compiling from source.

Quick Start

use verovioxide::{Toolkit, Options, Result, Svg};
use std::path::Path;

fn main() -> Result<()> {
    // Create a Verovio toolkit with bundled resources
    let mut voxide = Toolkit::new()?;

    // Load notation (format auto-detected)
    voxide.load(Path::new("score.musicxml"))?;

    // Configure rendering
    let options = Options::builder()
        .scale(100)
        .adjust_page_height(true)
        .build();
    voxide.set_options(&options)?;
    voxide.render_to("score.svg")?;
    Ok(())
}

Rendering

Verovioxide provides a unified render API with builder pattern for type-safe, consistent output.

In-Memory Rendering

Use render() with format builders:

use verovioxide::{Svg, Midi, Mei, Timemap, Humdrum, Pae, ExpansionMap};

// SVG rendering
let svg: String = voxide.render(Svg::page(1))?;           // Single page
let svg: String = voxide.render(Svg::page(1).with_declaration())?;
let pages: Vec<String> = voxide.render(Svg::all_pages())?; // All pages
let pages: Vec<String> = voxide.render(Svg::pages(2, 5))?; // Page range

// Other formats
let midi: String = voxide.render(Midi)?;          // Base64-encoded MIDI
let mei: String = voxide.render(Mei)?;            // MEI XML
let humdrum: String = voxide.render(Humdrum)?;    // Humdrum/Kern
let pae: String = voxide.render(Pae)?;            // Plaine & Easie
let timemap: String = voxide.render(Timemap)?;    // JSON timing data
let expansion: String = voxide.render(ExpansionMap)?;

File Rendering

Use render_to() for simple cases (format inferred from extension):

voxide.render_to("output.svg")?;    // SVG page 1
voxide.render_to("output.mid")?;    // MIDI
voxide.render_to("output.mei")?;    // MEI

Use render_to_as() for explicit control:

voxide.render_to_as("output.svg", Svg::page(3))?;        // Specific page
voxide.render_to_as("output.svg", Svg::all_pages())?;    // Creates output/ directory
voxide.render_to_as("output.json", Timemap)?;            // Disambiguate .json

Format-Specific Options

Timemap and MEI support typed options:

// Timemap with options
let timemap = voxide.render(
    Timemap::with_options()
        .include_measures(true)
        .include_rests(false)
)?;

// MEI with options
let mei = voxide.render(
    Mei::with_options()
        .remove_ids(true)
        .page_based(false)
)?;

Page Information

let count = voxide.page_count();
println!("Document has {} pages", count);

Legacy Methods

The original render methods remain available for backwards compatibility:

let svg = voxide.render_to_svg(1)?;
let pages = voxide.render_all_pages()?;
let mei = voxide.get_mei()?;
let humdrum = voxide.get_humdrum()?;
let midi = voxide.render_to_midi()?;
let timemap = voxide.render_to_timemap()?;

Querying Elements

Verovioxide provides a unified query API with type-safe return values.

Element Queries

Use get() with query types for type-safe element information:

use verovioxide::{Toolkit, Page, Attrs, Time, Times};

let mut voxide = Toolkit::new()?;
voxide.load("score.mei")?;

// Get page containing an element
let page: u32 = voxide.get(Page::of("note-001"))?;

// Get element attributes as JSON
let attrs: String = voxide.get(Attrs::of("note-001"))?;

// Get timing information
let time: f64 = voxide.get(Time::of("note-001"))?;
let times: String = voxide.get(Times::of("note-001"))?;

Time-Based Queries

use verovioxide::{Toolkit, Elements};

// Get elements sounding at 5000ms
let elements: String = voxide.get(Elements::at(5000))?;

Descriptive Features

use verovioxide::{Toolkit, Features};

let features: String = voxide.get(Features)?;

Legacy Methods

The original query methods remain available for backwards compatibility:

let page = voxide.get_page_with_element("note-001")?;
let attrs = voxide.get_element_attr("note-001")?;
let time = voxide.get_time_for_element("note-001")?;
let elements = voxide.get_elements_at_time(5000)?;

Configuration

Options Builder

The Options builder provides type-safe configuration:

use verovioxide::{Options, BreakMode, HeaderMode, FooterMode};

let options = Options::builder()
    // Page dimensions (MEI units, 10 units = 1mm)
    .page_width(2100)      // A4 width
    .page_height(2970)     // A4 height
    .adjust_page_height(true)

    // Margins
    .page_margin(50)
    .page_margin_top(100)

    // Scale and spacing
    .scale(100)            // Percentage
    .spacing_staff(12)
    .spacing_system(6)

    // Font
    .font("Leipzig")       // or "Bravura", "Gootville", "Leland", "Petaluma"
    .lyric_size(0.8)

    // Layout
    .breaks(BreakMode::Auto)
    .header(HeaderMode::None)
    .footer(FooterMode::None)

    // SVG output
    .svg_view_box(true)
    .svg_remove_xlink(true)
    .svg_css("svg { background: white; }")

    // MIDI generation
    .midi_tempo(120.0)
    .midi_velocity(80)

    // Transposition
    .transpose("M2")       // Up a major second

    .build();

voxide.set_options(&options)?;

Available Options

Category Options
Page page_width, page_height, adjust_page_height, page_margin, page_margin_top, page_margin_bottom, page_margin_left, page_margin_right
Scale/Spacing scale, spacing_staff, spacing_system, spacing_linear, spacing_non_linear, even_note_spacing, min_measure_width
Font font, lyric_size
Layout breaks, condense, condense_first_page, condense_tempo_pages, header, footer
SVG svg_xml_declaration, svg_bounding_boxes, svg_view_box, svg_remove_xlink, svg_css, svg_format_raw, svg_font_face_include
MIDI midi_tempo, midi_velocity
Input input_from, mdiv_x_path_query, expansion
Transposition transpose, transpose_selected_only, transpose_to_sounding_pitch

Option Modes

// Break modes
BreakMode::Auto     // Automatic page/system breaks
BreakMode::None     // No automatic breaks
BreakMode::Encoded  // Use breaks from input file
BreakMode::Line     // Break at each line
BreakMode::Smart    // Smart break placement

// Condense modes
CondenseMode::None
CondenseMode::Auto
CondenseMode::Encoded

// Header/Footer modes
HeaderMode::None | HeaderMode::Auto | HeaderMode::Encoded
FooterMode::None | FooterMode::Auto | FooterMode::Encoded | FooterMode::Always

JSON Serialization

Options can be serialized/deserialized:

let json = options.to_json()?;
let options = Options::from_json(&json)?;

Supported Input Formats

Format Extensions Description
MusicXML .musicxml, .xml, .mxl Standard music interchange format
MEI .mei Music Encoding Initiative XML
ABC .abc Text-based notation format
Humdrum .krn, .hmd Kern and other Humdrum formats
PAE Plaine & Easie Code (RISM)

Format detection is automatic based on file content.

Supported Output Formats

Format Unified API Description
SVG render(Svg::page(1)) Scalable vector graphics for display
MEI render(Mei) Music Encoding Initiative XML
Humdrum render(Humdrum) Humdrum/Kern format
PAE render(Pae) Plaine & Easie Code
MIDI render(Midi) Base64-encoded MIDI for playback
Timemap render(Timemap) JSON timing data for synchronization
Expansion Map render(ExpansionMap) JSON expansion/repeat data
PNG render(Png::page(1)) Raster image for display/printing

PNG Rendering

PNG support is enabled by default via the png feature. It uses resvg for high-quality SVG-to-PNG conversion.

In-Memory PNG

use verovioxide::Png;

// Basic rendering (returns Vec<u8>)
let png_bytes: Vec<u8> = voxide.render(Png::page(1))?;

// With options
let png_bytes = voxide.render(
    Png::page(1)
        .width(800)           // Scale to 800px width
        .height(600)          // Or scale to fit height
        .scale(2.0)           // Or use zoom factor
        .white_background()   // White instead of transparent
)?;

// Custom background color
let png_bytes = voxide.render(
    Png::page(1).background(240, 240, 240, 255)  // Light gray
)?;

// Render all pages
let all_pngs: Vec<Vec<u8>> = voxide.render(Png::all_pages())?;

// Render page range
let pngs: Vec<Vec<u8>> = voxide.render(Png::pages(2, 5))?;

File Rendering

// Format inferred from extension
voxide.render_to("output.png")?;

// Explicit control
voxide.render_to_as("output.png", Png::page(3))?;
voxide.render_to_as("output.png", Png::all_pages())?;  // Creates output/ directory

Terminal Display with viuer

The PNG bytes are compatible with the image crate for terminal display:

use verovioxide::Png;
use image::load_from_memory;
use viuer::Config;

let png_bytes = voxide.render(Png::page(1).width(800))?;
let img = load_from_memory(&png_bytes)?;
viuer::print(&img, &Config::default())?;

Disabling PNG

To disable PNG support (reduces compile time and binary size):

[dependencies]
verovioxide = { version = "0.3", default-features = false, features = ["bundled-data"] }

Feature Flags

verovioxide

Feature Default Description
bundled-data Yes Include bundled SMuFL fonts and resources
png Yes PNG rendering support via resvg
font-leipzig Yes Leipzig SMuFL font (default font)
font-bravura No Bravura SMuFL font
font-gootville No Gootville SMuFL font
font-leland No Leland SMuFL font
font-petaluma No Petaluma SMuFL font
all-fonts No Enable all fonts

Note: Bravura baseline data is always included as it is required for Verovio's glyph name table.

verovioxide-sys

Feature Default Description
bundled Yes Compile Verovio C++ library from source
prebuilt No Download pre-built library from GitHub releases (faster)
force-rebuild No Force fresh compilation, bypassing cache

The prebuilt and bundled features can be used together - if prebuilt download fails, it falls back to compilation.

Enable Additional Fonts

[dependencies]
verovioxide = { version = "0.3", features = ["font-bravura", "font-leland"] }

Custom Resource Path

To use your own Verovio resources instead of bundled data:

[dependencies]
verovioxide = { version = "0.3", default-features = false }
let voxide = Toolkit::with_resource_path(Path::new("/path/to/verovio/data"))?;

Building from Source

Clone with submodules:

git clone --recursive https://github.com/oxur/verovioxide.git
cd verovioxide

Build and test:

cargo build
cargo test

Build Caching

The Verovio C++ library is compiled once and cached at target/verovio-cache/. Subsequent builds link to the cached library and complete in seconds.

To force a fresh recompilation:

cargo build --features force-rebuild

Corporate/Restricted Networks

If your network blocks GitHub downloads, you can provide a local Verovio source:

VEROVIO_SOURCE_DIR=/path/to/verovio cargo build

Examples

Render MusicXML to SVG

cargo run --example render_musicxml -- \
  test-fixtures/musicxml/simple.musicxml \
  simple.svg

Render ABC Notation

cargo run --example render_abc

Render All Pages

mkdir output-dir
cargo run --example render_all_pages -- \
  "examples/Goldberg-Variationen-1-and-2.musicxml" \
  output-dir/
Creating Verovio toolkit with bundled resources...
Verovio version: 5.7.0
Setting page dimensions: width=2100, height=2970 (A4-like)
Loading file: examples/Goldberg-Variationen-1-and-2.musicxml (format auto-detected)
[Warning] MusicXML import: Dangling ending tag skipped
Document loaded successfully. Total pages: 3
Rendering 3 pages...
  Page 1/3: output-dir/-001.svg (380226 bytes)
  Page 2/3: output-dir/-002.svg (424047 bytes)
  Page 3/3: output-dir/-003.svg (121350 bytes)
Done! Rendered 3 pages.

Crate Structure

Crate Description
verovioxide High-level safe Rust API
verovioxide-sys Low-level FFI bindings to Verovio C API
verovioxide-data Bundled SMuFL fonts and resources

Technical Notes

Thread Safety

Toolkit implements Send but not Sync. You can move a toolkit between threads, but cannot share references across threads. For concurrent rendering, create separate toolkit instances.

Verovio Version

This release uses Verovio 5.7.0.

Logging

// Enable logging to stderr
Toolkit::enable_log(true);

// Or capture to buffer
Toolkit::enable_log_to_buffer(true);
// ... operations ...
let log = voxide.get_log();

License

This project is licensed under the Apache License 2.0.

Dependencies:

  • Verovio is licensed under the LGPL-3.0
  • SMuFL fonts have their own licenses (see the respective font directories)

Dependencies

~1–10MB
~205K SLoC