Skip to content

tan-z-tan/musicxml-io

Repository files navigation

musicxml-io

npm version License: MIT

TypeScript library for parsing and serializing MusicXML and ABC notation.

Architecture

┌─────────────────┐      ┌─────────────────┐      ┌─────────────────┐
│   MusicXML      │      │                 │      │   MusicXML      │
│   .xml / .mxl   │─────▶│      Score      │─────▶│   .xml / .mxl   │
└─────────────────┘      │                 │      └─────────────────┘
                   parse │   ┌─────────┐   │ serialize
┌─────────────────┐      │   │ parts   │   │      ┌─────────────────┐
│   ABC notation  │      │   │  └─measures │      │   ABC notation  │
│   .abc          │─────▶│   │    └─entries│─────▶│   .abc          │
└─────────────────┘      │   └─────────┘   │      └─────────────────┘
              parseAbc   │                 │ serializeAbc
                         │                 │      ┌─────────────────┐
                         │                 │      │     MIDI        │
                         │                 │─────▶│   .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-io

Usage

import { parse, serialize, transpose } from 'musicxml-io';

const score = parse(xmlString);
const transposed = transpose(score, 2);  // up 2 semitones
const output = serialize(transposed);

ABC Notation

import { parseAbc, serializeAbc } from 'musicxml-io';

// ABC → Score
const score = parseAbc(abcString);

// Score → ABC
const abc = serializeAbc(score, {
  referenceNumber: 1,
  includeChordSymbols: true,
  includeDynamics: true,
  includeLyrics: true,
});

// Auto-detect format (MusicXML, .mxl, or ABC)
import { parseAuto } from 'musicxml-io';
const score2 = parseAuto(input);

⚠️ 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
parseAbc(abc) Parse ABC notation string
parseAuto(data) Auto-detect format (MusicXML / .mxl / ABC)
serialize(score) To MusicXML string
serializeToFile(score, path) To file
serializeCompressed(score) To .mxl
serializeAbc(score, options?) To ABC notation string
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

MusicXML

Metric Score
Overall 99.6%
Node coverage 99.9%
Attribute coverage 95.9%

ABC Notation

Path Fidelity
ABC → Score → ABC High (42 fixtures passing)
ABC → MusicXML → ABC Musical content preserved

Contributing

Contributions are welcome! Whether it's bug reports, feature requests, documentation improvements, or code contributions, we appreciate your help in making this library better.

Development Setup

# Clone the repository
git clone https://github.com/tan-z-tan/musicxml-io.git
cd musicxml-io

# Install dependencies
npm install

# Run tests
npm test

# Build
npm run build

# Type check
npm run typecheck

# Lint
npm run lint

How to Contribute

  1. Fork the repository
  2. Create a feature branch (git checkout -b feature/amazing-feature)
  3. Make your changes
  4. Run tests to ensure everything works (npm test)
  5. Commit your changes (git commit -m 'Add amazing feature')
  6. Push to the branch (git push origin feature/amazing-feature)
  7. Open a Pull Request

Guidelines

  • Write tests for new features
  • Follow the existing code style
  • Update documentation as needed
  • Keep PRs focused on a single change

License

MIT

About

A TypeScript library for parsing, manipulating, and serializing MusicXML with a cleaner data model.

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages