acorde-io 1.1.0

MusicXML / MIDI / ABC / MSCZ parsers and serializer for acorde-core
Documentation

acorde-io

In-memory MusicXML, MIDI, ABC, and MuseScore I/O for acorde-core.

Default features are musicxml and midi. Optional features:

Feature Provides
abc parse_abc and serialize_abc
mscz parse_mscz and parse_mscx
mei parse_mei and serialize_mei for the documented subset, including scoreDef/staffDef meter, key, and clef

The default API includes parse_musicxml, parse_mxl, serialize_musicxml, parse_midi, serialize_midi, and serialize_midi_region. Parsers accept strings or byte slices and never touch the filesystem. parse_musicxml_with_report preserves simple figured-bass figure-number text as typed display-level TextStyle::FiguredBass; prefixes, suffixes, and alterations are retained in structured figure fields. MusicXML <unpitched> percussion notes retain their display-step/display-octave staff position and canonical Note.is_unpitched flag, and are re-emitted as <unpitched>; sound identity resolves only from explicit instrument declarations or retained MIDI display keys, while unmatched identities remain source-located losses. Note-level MusicXML instrument@id is retained as Note.instrument_id and re-emitted, without guessing a concrete sound from the identifier. Declared score-instrument metadata and explicit midi-unpitched keys are retained on the owning Part and re-emitted; missing sound-catalog data is not inferred. MIDI channel 10 notes are represented as unpitched notes with their key retained for display; no instrument ID is fabricated. Figured-bass prefix/suffix decoration and standard alteration values are preserved in structured figure fields while deterministic visible display text is regenerated. Standard time-modification tuplets are preserved as TupletInfo. ABC input/output preserves double accidentals and pure quarter-tone spellings; mixed semitone plus quarter-tone pitch offsets are reported as export losses.

The MEI boundary supports title, multiple numbered staves with up to four layers per staff, scoreDef/staffDef meter, key, and clef, notes, rests, dots, accidentals, ties, tuplets (num/numbase), grace notes (@grace), repeat barlines, measure tempo (mm), text chord labels (harm), rehearsal marks (reh), directions/navigation marks (dir), common dynamics/lyrics, and common articulations plus ornaments (staccato, tenuto, accent, marcato, fermata, trill, mordent, turn, and related forms), mRest/multiRest, with power-of-two durations. Tuplets are represented as per-note TupletInfo values; arbitrary MEI timing constructs outside this contract remain unsupported. MEI harm text without an attachment is preserved as a measure-level StyledText with TextStyle::ChordSymbol. A bounded set of attached text labels (C, Cm, C7, Cmaj7, slash basses, and related qualities) using @startid becomes the note's structured ChordSymbol; attached @chordref is retained as a raw URI. The bounded chordDef/chordMember tab-definition fields are retained in Score.chord_definitions; unresolved reference resolution, timing-only placement, unmodeled barre attributes, and unparseable labels remain source-located loss diagnostics. Attached MEI harm@place is retained as ChordSymbol placement, attached harm@extender is retained as its continuation flag, and attached harm@deg/harm@func are retained as harmonic-analysis metadata; timing-only placement remains diagnosed because it has no addressed note in the canonical model. Empty harm elements are also source-located as semantic loss. Attached MEI compact harm suffixes in the unambiguous add, alter, and no forms map to canonical ChordDegree values; complex structures remain diagnosed. MEI simple fb/f values are retained in order as Measure.figured_bass and also regenerate typed display-level TextStyle::FiguredBass; standard leading accidental glyphs map to the structured alteration field; common |/+ decorations and balanced parentheses are also structured. Unknown source text remains intact, and f@extender is retained in the canonical figure data. MusicXML figured-bass figure number/alter/prefix/ suffix values are retained as structured Measure.figured_bass data. Unsupported MEI <f> children/attributes and empty figure values produce source-located diagnostics. MSCX FiguredBassItem/digit values use the same ordered boundary; continuationLine is retained as the canonical figured-bass extender flag, while other engraving properties remain outside the canonical subset and are source-located by diagnostics. Known prefix/suffix modifier values are preserved semantically and rendered deterministically. Invalid digit and modifier values are reported with their original value and field path. Richer MEI and vendor-specific figure semantics remain outside the canonical model and are diagnosed. Same-measure/layer piano pedal spans using @dir="down", @startid, and @endid are supported; timestamp and other pedal attachment variants remain source-located loss diagnostics with their source attributes preserved. parse_mei_with_report reports known unsupported elements and flattened staff/layer numbers instead of silently claiming lossless interchange.

MusicXML notes are mapped from voice numbers 1–4 to the corresponding Measure.voices entries; serialization emits the same voice numbers and <backup> boundaries for round-trip fidelity. MIDI pitch-bend events are preserved on Part::midi_pitch_bends with their absolute canonical 480-PPQ tick, source channel, and signed 14-bit value; Controller Change, Program Change, and key/channel Aftertouch events are likewise preserved with their tick/channel data and MIDI export emits them on the preserved channels. MIDI delta values exceeding the SMF 28-bit VLQ limit are rejected instead of being shortened. Tempo and time-signature changes at canonical measure boundaries are attached to measure metadata; off-boundary changes are reported rather than silently shifted. The checked-in public-domain fixture verifies note pitch/duration/rest semantics and bend event meaning across parse → serialize → parse. parse_midi_with_report preserves Controller Change, Program Change, and Aftertouch events with absolute tick/channel data; it reports unsupported SysEx and Escape events with their track/event index, tick, and channel where applicable instead of silently claiming full interchange. It also reports unmatched note-on/note-off events; overlapping same-pitch notes are paired FIFO per channel. SMPTE timing is normalized to the current PPQ boundary and reported as a loss diagnostic. serialize_midi_with_report reports fractional-pitch rounding when a host has not supplied explicit pitch-bend events. Malformed MusicXML divisions, duration, and voice values are source-located with their original values instead of being indistinguishably replaced by parser defaults.

MSCX tab staffs preserve StaffType line/tuning metadata and note-level string/fret positions when supplied by the source. MSCX Tuplet/endTuplet ranges preserve their actualNotes and normalNotes ratio on each covered chord/rest. MSCX Tremolo subtypes map to canonical speed-level articulations; simple Harmony/name values and the bounded harmonyInfo/root subset are preserved as typed display text or canonical ChordSymbol data; harmonyInfo/base maps to slash bass where present, while richer structured harmony fields remain source-located diagnostics. Harmony placement is retained in the canonical ChordSymbol when present; harmonyInfo/function maps to harmony_function. Harmony root/base TPC values outside the bounded 6..=26 spelling range are rejected with a source-located diagnostic. Unambiguous compact Harmony/name suffixes (add, alter, no) map to canonical ChordDegree values; unknown suffixes are retained verbatim. Multiple MSCX and MusicXML Fingering elements on one note are retained as ordered Note.fingerings candidates; the first value remains mirrored in the canonical Note.fingering field for compatibility. Part-local bracket and barLineSpan metadata is retained as Part.staff_groups connector ranges when the referenced span is valid. One-note versus two-note tremolo pairing is not represented by the current model. ABC ^/ and _/ use the same 50-cent quarter accidental contract as MEI qs/qf and MSCX quarter accidental subtypes. MusicXML export reports tablature capo as an explicit loss because it has no standard staff-details representation. parse_abc_with_report reports unsupported ABC headers and !decoration!/+decoration+ constructs with line-based source locations. serialize_abc_with_report reports omitted staves/voices and microtones outside the supported quarter-tone spelling subset. It also reports ABC's lack of a canonical tablature staff, string/fret positions, and guitar-specific techniques as source-located losses. parse_mscx_with_report reports known unsupported MuseScore elements such as tremolos, ottavas, glissandos, and harmony with source paths.

Each supported format also exposes *_with_report wrappers returning ImportReport or ExportReport<T>. The MEI export report identifies score fields outside its canonical subset (for example tablature and host-specific playback techniques) instead of silently dropping them. Reports carry structured diagnostics with severity, source location, preserved value, and loss reason fields; an empty diagnostic list means no loss was detected by that API. Serialized reports include schema_version (currently 1); report readers should check this field before relying on future diagnostic fields. Reports written by older versions default to the current schema version when deserialized.

Parser and archive trust-boundary rules are documented in the security contract. Non-archive parser inputs use a 64 MiB baseline rejection limit; callers should enforce tighter budgets when their host resource envelope requires it. MIDI decoding also caps events at 500,000, and ABC token scanning caps each logical line at 1 MiB. MXL and MSCZ archives also reject a total declared uncompressed size above the archive budget before selecting or reading an entry, and reject duplicate or traversal-style entry names.

use acorde_io::{parse_musicxml, serialize_musicxml};

# fn main() -> Result<(), acorde_io::Error> {
# let xml = "<score-partwise version=\"3.1\"></score-partwise>";
let score = parse_musicxml(xml)?;
let output = serialize_musicxml(&score)?;
# let _ = output;
# Ok(())
# }

API documentation · Repository