@taotao-lib/opensheetmusicdisplay
v1.0.4
Published
An open source JavaScript engine for displaying MusicXML based on VexFlow.
Maintainers
Readme
npm(本 fork 发布页): https://www.npmjs.com/package/@taotao-lib/opensheetmusicdisplay
OpenSheetMusicDisplay (OSMD)
A MusicXML renderer for the Browser
About OSMD • Demo • Key Features • Limitations • How to Use OSMD • Sponsor OSMD • About Us • Get In Touch
:star: - Star us on Github - It really helps us a lot! :pray: - Become our Sponsor - Support our work and receive awesome perks!
Upstream Source and Local Changes
This repository is based on the upstream OpenSheetMusicDisplay project:
https://github.com/opensheetmusicdisplay/opensheetmusicdisplay.
For modifications made in this local fork, see the section Fork downstream changes at the end of this README (the previously referenced LOCAL_CHANGES_SUMMARY.md is not maintained separately).
About OSMD
OpenSheetMusicDisplay renders MusicXML sheet music in the browser. It is the missing link between MusicXML and VexFlow. Built upon many years of experience in both sheet music interactivity and engraving, it is the perfect solution for app developers seeking to build digital sheet music services.
MusicXML is the de facto standard for exchanging sheet music between music software. VexFlow is widely used for rendering sheet music. It features an extensive library of musical elements, but each measure and symbol has to be created and positioned by hand in Javascript.
OpenSheetMusicDisplay brings the two together and offers an open source turnkey solution for your digital sheet music project.
Demo
Try the Public Demo to see what OSMD can do. Learn more about the demo and OSMD in the OSMD Wiki.
Developers can also run a local development demo (see Wiki):
Key Features
- Displays MusicXML sheet music in a browser(less) environment (Javascript, Typescript, server-side: browserless NodeJS script)
- Soon: Audio Playback (work in progress, early access build available for Github sponsors)
- Uses Vexflow for rendering and (partly) layout
- Parses most MusicXML tags and integrates it into an accessible and modifiable data model (e.g. to change a note's color)
- Offers many options (OSMDOptions / EngravingRules): Page Format, Font Family, Positioning, not rendering certain elements like the title or lyrics, etc.
- Allows modification of the displayed score, like hiding parts or instruments, hiding instrument names, title or composer, a more compact layout, or coloring notes
- Outputs SVG or PNG, also via nodejs script in the command line, completely browserless (e.g. for server-side rendering)
- Written in Typescript with complete type information, 100% compatible with Javascript (minified build is .js)
- Can display tablature (guitar tabs) from MusicXML, including effects like bends and glissandi. Can be combined with treble clef.
Limitations
Not all MusicXML tags are (fully) supported:
- Pedal marks (currently in early access for sponsors)
- Glissando lines
- Wavy-line (currently in early access for sponsors)
- Etc, see OSMD 1.0 Project
Also, OSMD is a renderer, not an interactive sheet music editor. Rendering takes some time, and you can't easily/quickly move notes, place new notes, etc. (You can, however, manipulate the SVG nodes for instant changes like note re-coloring, see Exploring the Demo | Wiki)
How to Use OSMD
- Available as NPM module, can be used with plain javascript or module managers like webpack
- Getting Started: Detailed instructions in our Wiki
- If you have further technical questions, you can:
- Leave a comment in our Discussions section (especially for questions of understanding)
- Browse through our Issues
- Open a new issue (may be moved to Discussions).
Sponsor OSMD and get early access to the audio player and more
It would be great if free software were sustainable on its own. But to keep on improving and developing OpenSheetMusicDisplay we need your support. Your monthly sponsorship subscription - especially if you are already actively using OSMD - would mean everything to us - it’s a stable way of enabling us to continue our work, and improve and expand OSMD. Features already available in early access:
- OSMD Audio Player
- Native modules + example projects (React Native, Kotlin/Android, Swift/iOS) - NEW!
- Jianpu Display (Numbered Musical Notation), with playback
Features in the making, potentially available in future:
- Annotations (Add custom text, music symbols, etc. to the score and export to and import from XML)
- and more (we're always working on OSMD improvements or additional features, just look at our release history / changelog!)
Besides the early access features, you can get other perks like a personal postcard from the team in Vienna. Check them out at our GitHub sponsors page.
And there are other ways to contribute to the community - we plan on starting a blog and newsletter, and sharing our knowledge. We encourage our sponsors to bring up their desired features and pitch blog post ideas.
Though we highly recommend the sponsor route, you can also donate via Paypal:
Any support is highly appreciated.
About Us
OSMD is made by Phonicscore - a music-tech company based in Vienna. We create solutions for musicians, sheet music publishers, app developers, music stores and researchers:
- Open source software
- Sheet Music Rendering Software
- Native & web apps: PracticeBird for iOS and Android
Our mission is to provide state of the art software solutions for building MusicXML apps and to include the community in a constant thrive for improvement. We want to take away the pain of building music software from scratch and offer a shortcut when it comes to building your next MusicXML sheet music application.
We also want to thank our Github Contributors, who show that with open source many people come together to not only share but improve software.
Get In Touch
To contact us directly, you can:
- Use the Contact form on opensheetmusicdisplay.org to send a mail
- Send a mail to [email protected]
- Leave a (public) comment in our Discussions section
- Join our Discord (chat) Server
- Join the chat on Gitter.
Fork downstream changes
(本段为 二次开发说明,中文撰写。)本仓库在 OpenSheetMusicDisplay 上游基础上做了 与宿主乐谱应用(如 five-line-staff)对接 的扩展:在 分页渲染完成后 暴露「每页对应哪些全局小节」以及「每页内各小节在版面上的水平区间」,用于 视口点击 / 标注锚点 / 导出 MusicXML 时与屏上谱面一致,避免仅用「总页数均分全曲小节」导致的错位。
1. 新增类型(src/OpenSheetMusicDisplay/)
| 文件 | 说明 |
|------|------|
| PageMeasureListIndexBounds.ts | OsmdPageMeasureListIndexBounds:单页上 measureListIndex 的闭区间 [startMeasureListIndex, endMeasureListIndex](0-based,与 SourceMeasure.measureListIndex 一致)。 |
| PageMeasureHorizontalLayout.ts | OsmdMeasureHorizontalSpan(单小节 measureListIndex + left/right 绘图坐标)、OsmdPageMeasureHorizontalLayout(单页 + 该页上所有不重复小节的 span 列表,已按从左到右排序)。 |
包入口 src/OpenSheetMusicDisplay/index.ts 已 export * 上述模块,发布构建后消费方可从 @taotao-lib/opensheetmusicdisplay 引用类型。
2. OpenSheetMusicDisplay 新增公开方法
getPageMeasureListIndexBounds(): OsmdPageMeasureListIndexBounds[]
- 时机:在
load()且render()完成之后调用。 - 语义:对每个已绘制的
GraphicalMusicPage,统计该页上出现的所有图元小节的measureListIndex的最小值与最大值(含多声部同一小节)。 - 顺序:与
GraphicalMusicSheet.MusicPages一致;受EngravingRules.MaxPageToDrawNumber约束,超出页不再输出。 - 失败行为:若某一页无法得到有效 min/max,整表返回
[](与上游保守策略一致);宿主应仅在「返回数组长度与自身分页 HTML 页数一致」时使用。
getPageMeasureHorizontalLayouts(): OsmdPageMeasureHorizontalLayout[]
- 时机:同上,须在
render()之后。 - 语义:对每一分页,按
measureListIndex去重;同一小节多行谱表取所有GraphicalMeasure的 水平并集(min(left)、max(right))。 - 坐标:与排版后
BoundingBox一致,使用公开 getter:AbsolutePosition.x + BorderLeft/+ BorderRight(不得访问受保护的borderLeft/borderRight字段)。对左右边界做Math.min/Math.max规范化,避免极端情况下左右颠倒。 - 失败行为:若某一页没有任何可统计小节,整表返回
[];宿主同样应用「长度与页数一致」才采纳。 - 宿主侧对齐:典型用法是将
left/right与 该页根 SVG 的getBBox()做线性归一化,使 0–1 横坐标与「墨迹宽度」参照一致(详见 five-line-staff 中renderMusicXmlWithOsmdPages/pageMeasureInkNormSpans逻辑)。
3. 与上游的关系
- 未改动 MusicXML 解析与 VexFlow 绘制主流程;仅增加 只读查询 API 与类型导出。
- 若未来合并上游大版本,需重点解决冲突的文件主要是:
OpenSheetMusicDisplay.ts、index.ts及上述两个类型文件。
4. 版本与发布
- 携带上述 API 的 fork 版本以
package.json中version为准(例如 1.0.3 起包含getPageMeasureHorizontalLayouts)。 - 发布 npm 前请执行项目内
npm run build,确保build/(及类型声明.d.ts)包含新导出,否则 TypeScript 消费方无法识别新 API。
5. 已知限制
- 水平区间依赖当前 SVG/Canvas 后端 与排版结果;若宿主使用与 OSMD 渲染时不同的缩放或重新套版,需自行重新换算或重新
render()后再取布局。 getPageMeasureHorizontalLayouts与getPageMeasureListIndexBounds在「单页数据异常」时均可能返回 空数组;宿主 不可 在长度不匹配时退而误用「全曲按页均分」而不加校验,否则会回到旧有锚点偏差问题。
已修复问题:Slur 渲染 NaN 导致 SVG path 报错
现象
打开含有圆滑连线(slur)的 MusicXML 文件(如 春之声1-音乐学院带有标记_1778326139378.musicxml)时,浏览器控制台大量报错:
Error: <path> attribute d: Expected number, "…54.523833824256CNaN NaN,NaN NaN,…".即 OSMD 生成的 SVG <path> 元素的 d 属性中出现了 NaN,导致连线图形无法正常渲染,控制台刷屏报错。
出错元素
Slur(圆滑连线)。该文件由 Sibelius 导出,包含多段 slur 标注(<slur type="start" orientation="over"/> / <slur type="stop" orientation="over"/>)。OSMD 在计算 slur 贝塞尔曲线控制点时,某些边界条件下触发除零错误,导致控制点坐标变为 NaN,最终经 SvgVexFlowBackend.renderCurve() 写入 SVG path 的 d 属性。
根因分析
NaN 的产生链路如下:
斜率计算 (x÷0 → NaN)
→ Math.atan(NaN) → 角度 (NaN)
→ Math.cos/sin(NaN) → 控制点坐标 (NaN)
→ bezierStartPt / bezierEndPt / bezierCurveTo(…)
→ SVG path d 属性中包含 "CNaN NaN"具体触发点在 GraphicalSlur.ts 中:
| 位置 | 问题描述 |
|------|----------|
| calculateMaxLeftSlope | 坐标变换后起点为 (0, 0),若天际线某点的 x === 0,则 points[i].y / points[i].x 得到 0/0 = NaN |
| calculateMaxRightSlope | 若某点 x === end2.x,则 (y - points[i].y) / (end2.x - points[i].x) 分母为零产生 NaN |
| Math.atan (第149行 / 第322行) | 当 endX === startX 时,(endY - startY) / 0 若分子也为 0 则 0/0 = NaN |
| calculateHeightWidthRatio | 当 endX === 0 且 max === 0 时 0/0 = NaN |
| calculateAngles | 上游 NaN 斜率传入 Math.atan(NaN) 进一步传播 |
修改的文件
1. src/MusicalScore/Graphical/GraphicalSlur.ts
共 6 处修复:
| 方法 | 修复方式 |
|------|----------|
| calculateMaxLeftSlope | 对每个天际线点,若 denominator = points[i].x - x 的绝对值小于 1e-5 则跳过;终点分母同样加保护 |
| calculateMaxRightSlope | 同上:对每个点检查 x - points[i].x 是否接近零,起点分母同样加保护 |
| Math.atan (Above 分支, ≈149行) | 显式检查 endXStartXDifference !== 0,为零时直接取 ±π/2 |
| Math.atan (Below 分支, ≈322行) | 同上 |
| calculateHeightWidthRatio | 条件 endX === 0 时直接返回 0 |
| calculateAngles | 在 Math.atan() 调用前检查 isNaN() / !isFinite(),若无效则回落至 minAngle / -minAngle 安全值 |
2. src/MusicalScore/Graphical/VexFlow/SvgVexFlowBackend.ts
在 renderCurve() 方法开头增加防御层:遍历所有 8 个控制点,若任一坐标 isNaN() 则直接 return undefined,避免生成无效 SVG 元素。
验证结果
- TypeScript 编译:通过
- 全部 195 个单元测试:通过
- Webpack 生产构建:成功
