OpenSheetMusicDisplay (OSMD) 2.3.0 is here, and it is faster while using much less memory. This release packs a huge set of 116 improvements: 88 bug fixes, 14 new features, and 14 performance improvements. Read on for the highlights, the changes you may notice, and what SVG-savvy developers need to update.
Highlights
Faster, with much less memory
Scores render about 13% faster (median) than with OSMD 2.2.1: 12-15% in Chrome, 12% in Firefox, 22-23% in Node (jsdom), and even the first render on a page — before anything is compiled — is about 5% faster, while loading a score is 2-5% slower. This is on top of the ~10% gained in OSMD 2.2.0 and the ~40% of OSMD 2.1.2.
At the same time, OSMD needs 8-19% less memory in Chrome and 25-43% less in Node, more for larger scores — and memory is now released. Calling clear() frees the rendering (e.g. 10 MB instead of 344 MB stay in memory for a large orchestral score in Node). The new osmd.dispose() also removes OSMD’s listeners on the window for an instance you no longer need. In Chromium-based browsers, OSMD keeps a small rendering alive so that a sheet rendered after clear() is as fast as the one before (option keepWarm, osmd.KeepWarm, default "auto"), which takes at most 11 MB of memory.
Hidden notes and voices
Notes and rests hidden in the score (print-object="no") can now be shown in light gray, for example for teaching or proofreading: use the new option drawHiddenNotes: true and color hiddenNotesColor (#402). Voices can be hidden too (Voice.Visible = false), with the other voices laid out as if they weren’t there, or keeping the full score’s layout (EngravingRules.HiddenVoicesKeepLayout).


// Show hidden notes in light gray, e.g. for teaching or proofreading
const osmd = new OpenSheetMusicDisplay("container", {
drawHiddenNotes: true,
hiddenNotesColor: "#AAAAAA", // optional, the default is a light gray
});
// Hide a voice while keeping the full score's layout
osmd.EngravingRules.HiddenVoicesKeepLayout = true; // before load()
await osmd.load(url);
osmd.Sheet.Instruments[0].Voices[1].Visible = false;
osmd.render();TypeScriptEngraving
Part groups get the symbol given in the MusicXML (brace, bracket, square bracket, line or none), and a bracket reaches the last staff of its group.

Pedals draw their Ped. sign before the line, also pedals that start with type="resume", as MuseScore writes them, and their lines continue across system breaks.

Chords split between the staves, e.g. between a pianist’s hands, are drawn with each note on its own staff, instead of all on the staff of the first note with many ledger lines.

Slurs end past accidentals instead of running through them, and go over measure numbers.

Words and dynamics at the same time stay on one line (“piu f”, “p dolce”).

Multiple rest measures end at rehearsal marks, double barlines and pause bars.

Additive time signatures like 2+2+3/8 are drawn as written.

Also:
- Crossed voices are set slightly apart instead of in one column, where a stem seemed to include both notes.
- A courtesy key signature at the end of a system is drawn in its staff’s clef, and all staves of a system end at the same position.
- A clef change in the middle of a measure is kept when its staff has no note at that time.
- In narrow containers, part names give way instead of pushing the first measure past the end of the staff.
- Metronome marks keep all their dots, and MusicXML colors with an alpha value are read correctly.
Guitar tabs
TAB notes can show their rhythm with stems, flags, beams and dots (set EngravingRules.TabRhythmRendered = true), bends are labeled by their interval (“1/2”, “Full”, “1 1/2”), and slides on chords are drawn on their own strings.


Playback data
Repeats are played once per lyric verse and as often as <repeat times> says, endings on all their passes (“1, 3”), “a tempo” and “Tempo I” return to the earlier tempo, and <sound tempo> values keep their fraction — for example for the playback of the OSMD audio player.
Loading
MXL files starting with a byte order mark and MusicXML strings without the <?xml ...?> declaration now load.
Partial rendering and API
renderNext()now respectsdrawFromMeasureNumberanddrawUpToMeasureNumber, and wedges, pedals, octave shifts and trills cut off by that range are drawn like at a system break.render()applies a changed transposition withoutupdateGraphic().getClickedObjectOfType()andGetClickableLabel()find objects and labels at a position, e.g. a lyric or the title, and the newGraphicalNote.isVisible()tells whether a note is visible.- The cursor’s
nextMeasure()andpreviousMeasure()no longer skip measures.
Changes you may notice
- The SVG output is leaner and easier to style (17-31% fewer elements, new groups and
osmd-classes, unique ids). If your code or CSS selects OSMD’s SVG elements, some ids and classes changed — see the SVG section below. - Chord symbols keep a minimum distance to the staff, by default 1.5 units above it, e.g. over a whole rest (
EngravingRules.ChordSymbolMinimumBaselineAboveStaff, was 0.6). - The WebGL skyline backend is removed: it was always slower than the default geometric calculation. With it, the optional dependency
glis gone, which removes 62 of the 80 packages npm installed with OSMD. The classWebGLSkyBottomLineBatchCalculatorBackendis removed too; its rules and option are deprecated and have no effect. - Methods that OSMD never called and that didn’t work are removed:
VoiceEntry.createVoiceEntriesForOrnament(),KeyInstruction.getAlterationForPitch(), andupdateWithBoundingBoxRecursively(),resetSkyLineInRange()andresetBottomLineInRange()ofSkyBottomLineCalculator.
SVG output: smaller, easier to style, unique ids
OSMD 2.3.0 writes a leaner and more consistent SVG. If your app styles or selects OSMD’s SVG elements with CSS or code, check the breaking changes below; everything else works as before.
Smaller
The SVG of a score has 17-31% fewer elements and a third fewer attributes, which saves memory and rendering time:
- A note’s head, stem and flag, and a beam, are drawn without a group around each when the group would hold a single element; that element gets the group’s class and id instead, e.g.
<path class="vf-notehead">instead of<g class="vf-notehead"><path></g>. Empty modifier groups are left out (PR #1864). For the previous structure, setosmd.EngravingRules.SVGMergeSingleChildGroups = false. - Style attributes that only repeat the SVG default are left out; the root
<svg>sets them once (PR #1865). - Path coordinates are rounded to 2 decimals (PR #1862).
Easier to select and style
- Groups like VexFlow 5: a measure’s staff lines are in a
g.vf-stave, whose stroke colors them (PR #1868); each barline has the classvf-stavebarlineand each tupletvf-tuplet(PR #1872), each ornament with its accidental marksvf-ornament(PR #1878), on a group or, where there is only one shape, on the shape itself; voltas, repetitions, rehearsal marks, octave brackets, pedals and wavy lines are in groups named after their VexFlow class, e.g.g.vf-volta(PR #1877). - Classes naming what an element shows, with the prefix
osmd-(PR #1874), e.g.g.vf-text.osmd-title. The prefix keeps OSMD’s elements from being styled unintentionally by general CSS rules of the page. Labels includeosmd-title,osmd-subtitle,osmd-composer,osmd-lyrics,osmd-words,osmd-tempo,osmd-dynamic,osmd-chord-symbolandosmd-fingering; lines includeosmd-slur,osmd-wedge,osmd-extenderandosmd-tremolo; groups includeosmd-stafflineandosmd-multi. - Unique ids for measures, staff lines, tremolos and buzz rolls (PRs #1875, #1876), and the new
GraphicalMeasure.getSVGGElement(), the group a measure was drawn into (PR #1870). - A note’s stem, beams, ledger lines, ties, slurs and glissandi are found in its own sheet’s drawing, also in a shadow root (web components) or outside the document (PR #1879).
// The staff lines of the second staff's first measure in red
osmd.GraphicSheet.MeasureList[0][1]
.getSVGGElement()
?.querySelector(".vf-stave")
?.setAttribute("stroke", "#FF0000");TypeScriptBreaking changes: what to update
Several OSMD instances on a page give their elements the same ids, so look ids up in the instance’s container (or use getSVGGElement()). All indices are counted from 0. The key id and class changes from 2.2.1 to 2.3.0:
- Measure group:
id="12"(the measure number, in every staff) becomesosmd-m<measure index>-<staff index>; measure 12 without a pickup isosmd-m11-0,osmd-m11-1, selectable with[id^="osmd-m11-"]. - Staffline group:
Piano (right)0-1becomesosmd-staffline<system index>-<staff index>; select the second staff in all systems with[id^="osmd-staffline"][id$="-1"]. - Tremolo strokes:
vf-tremoloBetweenNotes-<measure number>-<staff number>-<n>becomesosmd-trem<measure index>-<staff index>-<n>. - Buzz roll:
vf-buzzRoll<n>becomesosmd-buzz<measure index>-<staff index>-<n>. - Classes:
lyrics,dash,measure-number,staffline,multibecomeosmd-lyrics,osmd-dash,osmd-measure-number,osmd-staffline,osmd-multi.
The vf- classes don’t change, but some elements moved into new groups, so selectors that relied on the old structure stop matching: use .vf-stave path for staff lines (PR #1868), .vf-stavebarline for barlines (PR #1872), and .vf-notehead (not .vf-notehead path) with the default merge rule (PR #1864). For example, CSS that hid the measure numbers and colored the lyrics:
/* 2.2.1 */
.measure-number { display: none; }
.lyrics text { fill: #555; }
/* 2.3.0 */
.osmd-measure-number { display: none; }
.osmd-lyrics text { fill: #555; }CSSFor OSMD developers
npm testnow runs on Vitest instead of karma, about twice as fast (TEST_LOW_MEMORY=1for machines with little free RAM).- The visual regression tests now cover pages taller than 32767 px, and no longer run out of memory with many changed large images.
Thanks to our contributors
@sschmidTU has done most of the work as usual, working on this release day and night for weeks.
But we also had many great external contributions.
Thanks to @dotkebi, @kawato3, @Pantman, @snowyukitty, @xvlcekp, @4321ba and @isc for their contributions via pull requests, and a warm welcome to @Pantman, @snowyukitty, @xvlcekp and @4321ba on their first contributions!
You can read the full changelog and download OSMD 2.3.0 from the official GitHub release page.




