musicxml-io
v0.2.11
Published
Parse and serialize MusicXML (.xml/.mxl) with high round-trip fidelity
Downloads
647
Maintainers
Readme
musicxml-io
TypeScript library for parsing and serializing MusicXML.
Architecture
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ MusicXML │ │ │ │ MusicXML │
│ .xml / .mxl │─────▶│ Score │─────▶│ .xml / .mxl │
└─────────────────┘ │ │ └─────────────────┘
parse │ ┌─────────┐ │ serialize
│ │ parts │ │ ┌─────────────────┐
│ │ └─measures │ │ MIDI │
│ │ └─entries│─────▶│ .mid │
│ └─────────┘ │ └─────────────────┘
│ │ exportMidi
└────────┬────────┘
│
┌───────────────────┼───────────────────┐
│ │ │
▼ ▼ ▼
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ QUERIES │ │ OPERATIONS │ │ ACCESSORS │
│ │ │ │ │ │
│ Score-level │ │ Score mutation │ │ Entry-level │
│ read operations │ │ operations │ │ helpers │
│ │ │ │ │ │
│ getMeasure() │ │ transpose() │ │ isRest() │
│ findNotes() │ │ addNote() │ │ isPitchedNote() │
│ getAllNotes() │ │ changeKey() │ │ getPartName() │
│ getHarmonies() │ │ insertMeasure() │ │ hasTie() │
└─────────────────┘ └─────────────────┘ └─────────────────┘
│
▼
┌─────────────────┐
│ VALIDATE │
│ │
│ validate() │
│ isValid() │
│ assertValid() │
└─────────────────┘Module Structure
| Module | File | Description |
|--------|------|-------------|
| Query | src/query/index.ts | Score-level read operations (get, find, iterate) |
| Operations | src/operations/index.ts | Score mutation operations (add, delete, modify) |
| Accessors | src/entry-accessors.ts | Entry-level helpers for notes, directions, parts |
Install
npm install musicxml-ioUsage
import { parse, serialize, transpose } from 'musicxml-io';
const score = parse(xmlString);
const transposed = transpose(score, 2); // up 2 semitones
const output = serialize(transposed);⚠️ Warning: This library's API is not yet stable and may change between versions.
File I/O (Node.js)
import { parseFile, serializeToFile } from 'musicxml-io';
const score = await parseFile('input.mxl');
await serializeToFile(score, 'output.xml');Operations
import { addNote, changeKey, changeTime } from 'musicxml-io';
const updated = addNote(score, {
partIndex: 0,
measureNumber: 1,
pitch: { step: 'C', octave: 4 },
duration: 4,
type: 'quarter',
});
const inG = changeKey(score, { fifths: 1 }, 0, 1);
const waltz = changeTime(score, { beats: 3, beatType: 4 }, 0, 1);Query
import { findNotes, getAllNotes, getMeasureCount, getHarmonies } from 'musicxml-io';
const notes = getAllNotes(score);
const quarterNotes = findNotes(score, { noteType: 'quarter' });
const count = getMeasureCount(score);
const harmonies = getHarmonies(score); // chord symbols (C7, Dm, etc.)Accessors
Entry-level helpers for working with individual notes, directions, and parts:
import {
getAllNotes,
isRest, isPitchedNote, hasTie, isChordNote,
getPartName,
getDirectionOfKind, getSoundTempo
} from 'musicxml-io';
// NoteEntry helpers
for (const item of getAllNotes(score)) {
if (isRest(item.note)) continue;
if (isPitchedNote(item.note)) {
console.log(`${item.note.pitch!.step}${item.note.pitch!.octave}`);
}
if (hasTie(item.note)) console.log('Tied note');
if (isChordNote(item.note)) console.log('Part of chord');
}
// PartInfo helpers
const partName = getPartName(score, 'P1'); // 'Piano'
// DirectionEntry helpers
for (const entry of measure.entries) {
if (entry.type === 'direction') {
const dynamics = getDirectionOfKind(entry, 'dynamics');
if (dynamics) console.log(dynamics.value); // 'ff', 'pp', etc.
const tempo = getSoundTempo(entry);
if (tempo) console.log(`Tempo: ${tempo} BPM`);
}
}MIDI Export
import { exportMidi } from 'musicxml-io';
const midi = exportMidi(score, { tempo: 120 });Validation
import { validate, isValid } from 'musicxml-io';
const { valid, errors } = validate(score);API
Parse / Serialize
| Function | Description |
|----------|-------------|
| parse(xml) | Parse MusicXML string |
| parseFile(path) | Parse from file |
| parseCompressed(buffer) | Parse .mxl |
| parseAuto(data) | Auto-detect format |
| serialize(score) | To MusicXML string |
| serializeToFile(score, path) | To file |
| serializeCompressed(score) | To .mxl |
| exportMidi(score) | To MIDI |
Operations
| Function | Description |
|----------|-------------|
| transpose(score, semitones) | Transpose pitches |
| insertNote(score, options) | Insert note at position |
| removeNote(score, options) | Remove note (replace with rest) |
| addChord(score, options) | Add note to chord |
| setNotePitch(score, options) | Change note pitch |
| changeNoteDuration(score, options) | Change note duration |
| addVoice(score, options) | Add voice to measure |
| addPart(score, options) | Add part to score |
| removePart(score, options) | Remove part from score |
| setStaves(score, options) | Set staff count |
| changeKey(score, key, part, measure) | Change key signature |
| changeTime(score, time, part, measure) | Change time signature |
| insertMeasure(score, part, after) | Insert measure |
| deleteMeasure(score, part, measure) | Delete measure |
| addTie(score, options) | Add tie between notes |
| addSlur(score, options) | Add slur between notes |
| addArticulation(score, options) | Add staccato, accent, etc. |
| addDynamics(score, options) | Add dynamics (f, p, etc.) |
| modifyDynamics(score, options) | Modify dynamics |
| addTempo(score, options) | Add tempo marking |
| modifyTempo(score, options) | Modify tempo |
| addOrnament(score, options) | Add trill, turn, etc. |
| addText(score, options) | Add text direction |
| addLyric(score, options) | Add lyric to note |
| autoBeam(score, options) | Auto-beam notes |
| createTuplet(score, options) | Create tuplet |
| addChordSymbol(score, options) | Add chord symbol |
| changeClef(score, options) | Change clef |
| setBarline(score, options) | Change barline style |
| addRepeat(score, options) | Add repeat barline |
| addEnding(score, options) | Add first/second ending |
| addFermata(score, options) | Add fermata |
| addWedge(score, options) | Add crescendo/diminuendo |
| addPedal(score, options) | Add pedal marking |
| addGraceNote(score, options) | Add grace note |
See OPERATIONS.md for the complete list.
Query
| Function | Description |
|----------|-------------|
| getAllNotes(score) | All notes with context |
| findNotes(score, filter) | Filter notes by criteria |
| getMeasure(score, { part, measure }) | Get measure by number |
| getMeasureByIndex(score, { part, measureIndex }) | Get measure by index |
| getMeasureCount(score) | Total measure count |
| getChords(measure) | Chord groups in measure |
| countNotes(score) | Total note count |
| getHarmonies(score) | All chord symbols |
| getDynamics(score) | All dynamics markings |
| getTempoMarkings(score) | All tempo markings |
Accessors
Entry-level helpers for individual notes, directions, and parts.
NoteEntry
| Function | Description |
|----------|-------------|
| isRest(note) | Check if rest |
| isPitchedNote(note) | Check if has pitch |
| isUnpitchedNote(note) | Check if percussion |
| isChordNote(note) | Check if part of chord |
| isGraceNote(note) | Check if grace note |
| isCueNote(note) | Check if cue note |
| hasTie(note) | Check if tied |
| hasTieStart(note) | Check if tie starts |
| hasTieStop(note) | Check if tie stops |
| hasBeam(note) | Check if beamed |
| hasLyrics(note) | Check if has lyrics |
| hasNotations(note) | Check if has notations |
| hasTuplet(note) | Check if in tuplet |
DirectionEntry
| Function | Description |
|----------|-------------|
| getDirectionOfKind(entry, kind) | Get first direction type |
| getDirectionsOfKind(entry, kind) | Get all direction types |
| hasDirectionOfKind(entry, kind) | Check if has type |
| getSoundTempo(entry) | Get tempo from sound |
| getSoundDynamics(entry) | Get dynamics (0-127) |
| getSoundDamperPedal(entry) | Get damper pedal state |
| getSoundSoftPedal(entry) | Get soft pedal state |
| getSoundSostenutoPedal(entry) | Get sostenuto pedal state |
PartInfo
| Function | Description |
|----------|-------------|
| getPartInfo(score, id) | Get part info by ID |
| getPartName(score, id) | Get part name |
| getPartAbbreviation(score, id) | Get part abbreviation |
| getAllPartInfos(score) | Get all part infos |
| getPartNameMap(score) | Get ID to name map |
| isPartInfo(entry) | Type guard for PartInfo |
Validate
| Function | Description |
|----------|-------------|
| validate(score) | Validation errors |
| isValid(score) | Boolean check |
| assertValid(score) | Throw if invalid |
Tree-shaking
import { transpose } from 'musicxml-io/operations';
import { findNotes } from 'musicxml-io/query';
import { isRest, getPartName } from 'musicxml-io/entry-accessors';Unique Element IDs
All elements in the Score structure have a unique _id property that is automatically generated when:
- MusicXML is parsed/imported
- New elements are created via operations
The ID format is "i" + nanoid(10) (11 characters total), where:
"i"prefix ensures XML ID compatibility (IDs must start with a letter or underscore)nanoid(10)generates a URL-safe unique identifier
import { parse, generateId } from 'musicxml-io';
const score = parse(xmlString);
console.log(score._id); // e.g., "iV1StGXR8_Z"
console.log(score.parts[0]._id); // e.g., "i2x4K9mL1Qp"
// Generate IDs manually for custom elements
const customId = generateId(); // e.g., "iAb3Cd5Ef7H"This feature enables:
- Tracking elements across transformations
- Building element references in external systems
- Implementing undo/redo functionality
- Diffing and merging scores
Round-trip Fidelity
| Metric | Score | |--------|------:| | Overall | 99.6% | | Node coverage | 99.9% | | Attribute coverage | 95.9% |
License
MIT
