Skip to content
stringsyncPublic

About

MusicXML to Vexflow

Resources

Stars

43 stars

Watchers

7 watching

Forks

Repository files navigation

vexml

Render sheet music in the browser. Try it at https://vexml.dev.

npm install @stringsync/vexml
import { render } from '@stringsync/vexml';

const score = await render(musicXML, document.getElementById('score'));

Building with a coding agent? Add the vexml skill, then ask your agent for what you want:

npx skills add stringsync/vexml@vexml

The skill is the full guide, also readable at skills/vexml/SKILL.md.

Features

Notation and tablature

Two measures of Amazing Grace with lyrics, chord diagrams, guitar notation and tab

Standard notation, guitar tab, lyrics, chord symbols and diagrams, repeats and voltas. The score scales to fit its container.

vexml engraves an mdom document, so it reads whatever mdom does: MusicXML text (.musicxml, .xml), a compressed .mxl Blob, or a document mdom parsed from another format, such as Guitar Pro (.gp).

import { GuitarProParser } from '@stringsync/mdom';

const document = await new GuitarProParser().parseFromBlob(gpFile);
await render(document, element);

Playback cursor

A playback bar sweeping two measures while sounding notes turn red

A cursor walks the timeline with repeats and voltas unrolled, reporting which notes start, sustain and stop, so you can drive audio or light up notes.

const cursor = score.createCursor();
cursor.sync(score.createPlayhead());
cursor.events.on('change', (e) => e.highlighted.forEach((note) => note.color.on('red')));
cursor.seekMs(audio.currentTime * 1000);

Pointer events

A halo following the pointer from note to note

Every note, measure and fret is hit-testable. Events report what's under the pointer, and notes can be colored or haloed.

import { Note } from '@stringsync/vexml';

score.events.on('click', (e) => {
  if (e.target instanceof Note) e.target.halo.on('rgba(41, 98, 255, 0.35)');
});

Editing

A selection stepping through notes, extending, then the selected notes moving up to G

EditingSession adds keyboard and pointer selection with undo and redo over an editable document.

const editor = new EditingSession(document);
score.createEditingController(editor);
editor.setPitch({ step: 'G', octave: 5 });

Sticky clef and key

A single-line score scrolled sideways with its clefs and key pinned at the left edge

A single scrolling line keeps its clefs and key signatures pinned at the left edge, switching when a change scrolls under them.

await render(musicXML, element, {
  layout: { type: 'panoramic', stickySignatures: true },
  width: 420,
});

Fonts and colors

A score engraved in Petaluma with light ink on a dark background

Swap the music font (Bravura by default) and the text font, and recolor notation, text and background for any theme.

await render(musicXML, element, {
  backgroundColor: '#0f172a',
  fonts: {
    notation: { family: 'Petaluma', url: '/fonts/petaluma.woff2', color: '#e2e8f0' },
    text: { family: 'Inter', url: '/fonts/inter.woff2', color: '#fbbf24' },
  },
});

Gap measures

A labeled Intro gap before the first measure

Insert empty, labeled measures that last a fixed time, to line the score up with a video or recording that pauses between passages.

await render(musicXML, element, {
  gaps: [{ beforeMeasureIndex: 0, durationMs: 8000, label: 'Intro' }],
});

Layouts and printing

Fit the container, scroll one long line, or lay the score out on fixed-size pages and export each page as a PNG for print.

const score = await render(musicXML, element, {
  layout: { type: 'paged', pageWidth: 816, pageHeight: 1056, margin: 48 },
});
const pngs = await Promise.all(score.getPages().map((page) => page.toBlob()));

Canvas layers

Draw your own annotations in front of or behind the engraving on a 2D canvas that stays aligned with the score.

const layer = score.addLayer('content', -1);
layer.ctx.fillRect(50, 50, 100, 80);

Snapshots

Cache a rendered score as JSON and render it again roughly 10x faster, make snapshots on a server, or paint one with an 8 KB script before your app loads.

const snapshot = score.snapshot();
await render(snapshot, element);

Development

Needs bun and docker. The library is packages/vexml; the rest of the repo builds, tests and demos it.

./bin/vex dev                              # run the playground
./bin/vex render --input song.musicxml     # render a MusicXML file to a PNG

./bin/vex readme re-records this README's images and GIFs (it needs ImageMagick).

About

MusicXML to Vexflow

Resources

Stars

43 stars

Watchers

7 watching

Forks

Releases

Packages

Used by

Contributors

Languages