TypeScript library for parsing and serializing MusicXML and ABC notation.
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ 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 | 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 |
npm install musicxml-ioimport { parse, serialize, transpose } from 'musicxml-io';
const score = parse(xmlString);
const transposed = transpose(score, 2); // up 2 semitones
const output = serialize(transposed);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);import { parseFile, serializeToFile } from 'musicxml-io';
const score = await parseFile('input.mxl');
await serializeToFile(score, 'output.xml');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);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.)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`);
}
}import { exportMidi } from 'musicxml-io';
const midi = exportMidi(score, { tempo: 120 });import { validate, isValid } from 'musicxml-io';
const { valid, errors } = validate(score);| 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 |
| 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.
| 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 |
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 |
| Function | Description |
|---|---|
validate(score) |
Validation errors |
isValid(score) |
Boolean check |
assertValid(score) |
Throw if invalid |
import { transpose } from 'musicxml-io/operations';
import { findNotes } from 'musicxml-io/query';
import { isRest, getPartName } from 'musicxml-io/entry-accessors';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
| Metric | Score |
|---|---|
| Overall | 99.6% |
| Node coverage | 99.9% |
| Attribute coverage | 95.9% |
| Path | Fidelity |
|---|---|
| ABC → Score → ABC | High (42 fixtures passing) |
| ABC → MusicXML → ABC | Musical content preserved |
Contributions are welcome! Whether it's bug reports, feature requests, documentation improvements, or code contributions, we appreciate your help in making this library better.
# 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- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Make your changes
- Run tests to ensure everything works (
npm test) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
- Write tests for new features
- Follow the existing code style
- Update documentation as needed
- Keep PRs focused on a single change
MIT