# 简谱 (Jianpu) 渲染技术调研报告 --- ## 执行流程 ``` 1. 信息源并发搜索 ├─ WebSearch "jianpu renderer github SVG" → 10+ 项目 ├─ WebSearch "简谱 渲染 github SVG Canvas" → 4 项目 ├─ WebSearch "jianpu MusicXML converter layout algorithm" → 学术论文 + OSMD ├─ WebSearch "jianpu typesetting spacing convention" → 排版惯例 ├─ WebSearch "简谱排版 音符间距 减时线 算法" → 中文资料 └─ WebFetch 6 个核心 Repo README 深度分析 ↓ 2. 资料整合 → 3. 事实/观点分离 → 4. 技术方案对比 → 5. 设计模式提取 ``` --- ## 执行摘要 1. **开源简谱渲染器稀缺且质量参差**:全球能找到的 Web 端简谱渲染项目约 8 个,其中只有 3 个具备实际可用性(JianpuRender、react-jianpu、OSMD-Jianpu),大部分处于 demo/prototype 阶段 2. **简谱排版的核心难题是「按拍分组」而非「按时值比例」**:与五线谱的 proportional spacing 不同,简谱遵循 beat-based grouping,即同一拍内的音符紧凑排列、共享减时线(下划线),这对布局算法提出了独特要求 3. **减时线(下划线)是最大的渲染复杂度来源**:8 分音符 1 条线、16 分音符 2 条线、32 分音符 3 条线,它们需要在同一拍组内对齐、合并、且不能和音符数字重叠,这是所有渲染器花最多精力的地方 4. **两种主流技术路线**:(A) 纯 SVG/Canvas 自行绘制,完全掌控布局;(B) 转换为 Lilypond/VexFlow 等已有排版引擎,借力专业排版能力 --- ## 一、开源项目清单 ### 1.1 Web 端 SVG/Canvas 渲染器 | 项目 | Stars | 技术栈 | 输入格式 | 输出 | 成熟度 | |------|-------|--------|---------|------|--------| | [JianpuRender](https://github.com/flufy3d/JianpuRender) | ~新 | TypeScript | JSON (自定义) | SVG/Canvas | 中 | | [react-jianpu](https://github.com/felixhao28/react-jianpu) | 78 | CoffeeScript/React | JSON (自定义) | SVG (React 组件) | 中 | | [musje](https://github.com/muzenplaats/musje) | ~20 | JavaScript | 文本 DSL | SVG | 低(学术性) | | [QinJianPu](https://github.com/Colennn/QinJianPu) | ~新 | JavaScript | 编辑器 UI | SVG (snap.svg) | 低 | | [OSMD-Jianpu](https://opensheetmusicdisplay.org/blog/jianpu-mode/) | 3.8k (OSMD总) | TypeScript/VexFlow | MusicXML | SVG | 高(但闭源 Sponsor 专属) | ### 1.2 排版引擎适配方案 | 项目 | Stars | 技术路线 | 输入格式 | 输出 | |------|-------|---------|---------|------| | [jianpu-ly](https://github.com/ssb22/jianpu-ly) | ~150 | Python → Lilypond | 文本 DSL | PDF/SVG (Lilypond) | | [musicxml_to_jianpu](https://github.com/lzh9102/musicxml_to_jianpu) | ~30 | Python | MusicXML | Lilypond | | [latex-jianpu](https://github.com/liantze/latex-jianpu) | ~20 | LaTeX3 | LaTeX 宏 | PDF | ### 1.3 字体方案(非渲染器但值得参考) | 项目 | Stars | 技术路线 | 说明 | |------|-------|---------|------| | [jianpu-ascii-font](https://github.com/RobertWinslow/jianpu-ascii-font) | ~60 | OpenType 连字 | 纯字体方案,ASCII 输入触发 ligature 显示简谱符号 | | [typst-kyphu](https://github.com/Shuenhoy/typst-kyphu) | ~新 | Typst + Tree-sitter | 利用 Typst 排版引擎,有 jianpu parser 但不完整 | --- ## 二、核心设计模式分析 ### 2.1 简谱 vs 五线谱:布局差异的本质 这是理解所有设计决策的前提: ``` 五线谱 (Western) 简谱 (Jianpu) ───────────────── ───────────────── - 音高 = 符头在五线谱上的Y位置 - 音高 = 数字 1-7 + 八度圆点 - 时值 = 符头形状 + 符杆 + 符尾 - 时值 = 下划线条数 + 增时线(横线) - Proportional spacing - Beat-based grouping (空间 ∝ 时值) (同拍音符紧凑排列) - 符尾(beam)在符杆顶部 - 减时线在数字下方 - 每个音符独立占位 - 一拍内的音符共享减时线 ``` **关键区别**:在五线谱中,一个全音符的横向空间通常是四分音符的 4 倍;但在简谱中,一个全音符写作 `1 - - -`(四个等宽位置),而四个十六分音符也可能紧凑地写在一个拍位内。这意味着简谱的间距不是音符级别的,而是**拍(beat)级别的**。 ### 2.2 三层架构模式 所有成熟渲染器都遵循类似的三层分离: ``` ┌────────────────────────────────────────┐ │ Layer 1: Music Model (数据模型) │ │ - 音符: pitch, duration, accidental │ │ - 小节: notes[], timeSignature │ │ - 乐谱: measures[], keySignature │ └───────────────┬────────────────────────┘ │ ┌───────────────▼────────────────────────┐ │ Layer 2: Layout Engine (布局引擎) │ │ - Beat grouping (按拍分组) │ │ - Measure width calculation │ │ - Line breaking (换行) │ │ - Vertical zone allocation │ └───────────────┬────────────────────────┘ │ ┌───────────────▼────────────────────────┐ │ Layer 3: Renderer (绘制层) │ │ - SVG elements / Canvas draw calls │ │ - Text rendering (数字) │ │ - Line rendering (减时线、小节线) │ │ - Dot rendering (八度点、附点) │ └────────────────────────────────────────┘ ``` ### 2.3 音符的垂直空间分区 简谱单个音符的垂直空间分为 5 个区域(从上到下): ``` Zone 1: 高八度圆点区 · (可能有 1-2 个点) · Zone 2: 升降号区 # Zone 3: 数字区 3 (核心,固定高度) Zone 4: 附点区 . (延时点在数字右侧下方) Zone 5: 减时线区 ─── (可能有 1-3 条线) ─── ─── Zone 6: 低八度圆点区 · (可能有 1-2 个点) ``` **密度问题的根源**:当有三十二分音符时,Zone 5 需要 3 条线的空间;同时如果有低八度标记,Zone 6 又需要 1-2 个点的空间。这导致行高膨胀。 ### 2.4 减时线(Underline Beam)渲染策略 这是简谱渲染中最复杂的部分。现有方案有两种策略: **策略 A:独立绘制(react-jianpu, JianpuRender)** ``` 每个音符独立计算下划线位置 - 优点:实现简单 - 缺点:同拍内音符的下划线不连续,不符合排版惯例 ``` **策略 B:连续合并绘制(jianpu-ly, OSMD)** ``` 1. 先按拍(beat)对音符分组 2. 在同组内,找到最多减时线数的音符 3. 底层线贯穿整个拍组 4. 上层线只覆盖需要的音符 示例:一个拍内有 [16th, 16th, 8th, 8th] 1 2 3 4 ─────────────── (第一层线:所有 >= 8分 的音符) ─────── (第二层线:只有 16分 音符) ``` **策略 B 是正确的排版方式**,类似五线谱的 beam grouping 规则。 ### 2.5 小节宽度计算 发现两种策略: **固定宽度模式(react-jianpu 采用)**: - 配置参数 `sectionsPerLine` 控制每行小节数 - `alignSections: true` 使所有小节等宽 - 小节内音符按拍均匀分配空间 **内容自适应模式(OSMD 采用)**: - 先用 VexFlow 计算西方记谱的小节宽度 - 简谱小节宽度与之对齐 - 支持简谱+五线谱混合乐谱(同步水平位置) **实践建议**:对于纯简谱渲染,固定宽度模式更简单且效果更好;对于混合谱表,必须用自适应模式。 --- ## 三、密度问题(Visual Density)的解决方案 ### 3.1 问题描述 当一个 4/4 拍小节内全是十六分音符时,需要容纳 16 个音符,每个带 2 条下划线。这会导致: - 水平方向:16 个数字 + 间距挤在一个小节宽度内 - 垂直方向:双层下划线占据额外行高 - 碰撞风险:附点、升降号、八度点可能与下划线重叠 ### 3.2 现有解决方案 | 策略 | 说明 | 采用者 | |------|------|--------| | **等宽字体** | 每个数字占固定宽度,利用等宽特性对齐 | jianpu-ascii-font | | **拍内紧凑排列** | 同拍音符间距缩小,拍与拍之间留更大间距 | JianpuRender, OSMD | | **动态字号** | 音符密度超过阈值时缩小字号 | 部分桌面软件 | | **自动换行** | 当小节内容过多时,减少每行小节数 | jianpu-ly (Lilypond) | | **最小宽度约束** | 每个音符有最小宽度,超出时小节自动扩展 | OSMD | ### 3.3 推荐的间距算法 基于调研总结的最佳实践: ``` // 基本常量 const NOTE_WIDTH = 16; // 数字字符宽度 (px) const NOTE_GAP = 4; // 同拍内音符间距 const BEAT_GAP = 12; // 拍与拍之间间距 const MEASURE_PADDING = 8; // 小节左右内边距 const UNDERLINE_GAP = 3; // 减时线之间垂直间距 const UNDERLINE_OFFSET = 4; // 数字底部到第一条减时线的距离 const OCTAVE_DOT_OFFSET = 3; // 八度圆点与数字的距离 const DOT_SIZE = 3; // 圆点直径 // 小节宽度计算 function measureWidth(timeSignature, beatsPerLine) { const measuresPerLine = Math.floor(beatsPerLine / timeSignature.numerator); return (containerWidth - margins) / measuresPerLine; } // 拍组宽度计算 function beatGroupWidth(notesInBeat) { return notesInBeat.length * NOTE_WIDTH + (notesInBeat.length - 1) * NOTE_GAP; } // 行高计算(动态) function lineHeight(maxUnderlines, hasUpperDots, hasLowerDots) { let height = NOTE_HEIGHT; // 基准:数字高度 height += maxUnderlines * (UNDERLINE_GAP + 1); // 减时线区域 height += UNDERLINE_OFFSET; if (hasUpperDots) height += OCTAVE_DOT_OFFSET * 2; if (hasLowerDots) height += OCTAVE_DOT_OFFSET * 2; return height; } ``` --- ## 四、SVG 渲染技术要点 ### 4.1 SVG 元素使用 | 简谱元素 | SVG 元素 | 说明 | |---------|---------|------| | 音符数字 (1-7, 0) | `<text>` | `text-anchor="middle"`, `font-family="monospace"` | | 减时线 | `<line>` 或 `<path>` | 水平线,`stroke-width: 1.5px` | | 增时线(延长破折号)| `<line>` | 水平线,在数字右侧 | | 八度圆点 | `<circle>` | `r="1.5"`, 在数字上方或下方 | | 附点 | `<circle>` | `r="1.5"`, 在数字右下方 | | 小节线 | `<line>` | 垂直线 | | 连音线/延音线 | `<path>` | 贝塞尔曲线 (`C` 或 `Q` 命令) | | 升降号 | `<text>` | `#` 或 `b`,缩小字号 | ### 4.2 分组渲染 (`<g>` 标签) 推荐的 SVG 分组结构: ```xml <svg> <g class="score"> <g class="line" transform="translate(0, lineY)"> <g class="measure" transform="translate(measureX, 0)"> <g class="beat-group" transform="translate(beatX, 0)"> <!-- 先画减时线(在底层) --> <line class="underline" x1="..." y1="..." x2="..." y2="..." /> <!-- 再画音符(在上层) --> <g class="note" transform="translate(noteX, 0)"> <circle class="octave-dot" /> <text class="pitch">3</text> <circle class="augment-dot" /> </g> </g> <line class="barline" /> </g> </g> </g> </svg> ``` ### 4.3 关键 SVG 技巧 **1. 使用 `transform` 而非绝对坐标** - 每层使用相对位移,便于整体移动和缩放 - `translate(x, y)` 比直接设 `x`/`y` 属性更灵活 **2. 减时线对齐** - 减时线的 `x1` 取拍组第一个音符的左边缘 - 减时线的 `x2` 取拍组最后一个音符的右边缘 - 确保线宽足够(1.5-2px),太细在屏幕上不清晰 **3. `shape-rendering: crispEdges`** - 对水平/垂直直线使用此属性,避免亚像素模糊 - 不要对曲线(连音线)使用,会导致锯齿 **4. 文字基线对齐** - 使用 `dominant-baseline="central"` 或手动计算 - 数字和升降号需要不同的 baseline offset --- ## 五、技术方案对比与推荐 ### 5.1 方案对比矩阵 ``` 布局控制 排版质量 开发成本 Web集成 交互性 自建SVG渲染 ●●●●● ●●●○○ ●●○○○ ●●●●● ●●●●● Lilypond转换 ●○○○○ ●●●●● ●●●●○ ●●○○○ ●○○○○ OSMD-Jianpu ●●●○○ ●●●●○ ●●●●● ●●●●○ ●●●○○ 字体方案 ●○○○○ ●●○○○ ●●●●● ●●●●● ●○○○○ ● = 高 ○ = 低 ``` ### 5.2 推荐方案 **如果需要 Web 端交互式渲染(高亮、播放同步、点击跳转)**: - 自建 SVG 渲染器,参考 JianpuRender 的三层架构 - 数据模型参考 MusicXML 的结构 - 布局引擎采用 beat-based grouping **如果需要高质量排版输出(打印、PDF)**: - jianpu-ly + Lilypond 是最成熟的方案 - 输出质量接近专业排版 **如果需要最小开发量**: - jianpu-ascii-font 字体方案,零代码 - 但无法处理复杂排版 --- ## 六、jianpu-ly 深度分析(最成熟的开源方案) jianpu-ly 值得特别关注,因为它是唯一一个功能完整、持续维护、且被广泛使用的开源简谱排版工具。 ### 6.1 输入语法 ``` 1=C 4/4 q1 q2 q3 q4 | 5 - 6 - | s1 s2 s3 s4 s5 s6 s7 s1' | ``` - `q` 前缀 = 八分音符 (quaver) - `s` 前缀 = 十六分音符 (semiquaver) - `'` 后缀 = 高八度 - `,` 后缀 = 低八度 - `-` = 延长一拍 - `.` = 附点 ### 6.2 核心设计决策 jianpu-ly 将简谱渲染为 Lilypond 的「修改外观的五线谱」,而不是从零构建渲染系统。这意味着: - Lilypond 的 spacing 引擎自动处理间距 - Lilypond 的 beam grouping 规则自动应用 - 歌词对齐、连音线等直接复用 - 分页、换行等自动排版能力直接可用 **缺点**:生成的是静态 PDF/SVG,无法在 Web 端做交互。 --- ## 七、风险与不确定性 1. **OSMD-Jianpu 是闭源方案**:最成熟的 Web 端方案需要 Sponsor 才能使用,不能直接集成 2. **简谱排版标准未成文**:没有类似 SMuFL (Standard Music Font Layout) 这样的简谱标准,各家实现的间距和排版规则不统一 3. **复杂场景覆盖不足**:调查中没有发现能完整处理「多声部简谱」「简谱+五线谱混排」「带和弦标记的简谱」的开源 Web 方案 4. **学术论文访问受限**:Springer 上有一篇关于 jianpu 数字表示和渲染框架的论文 (2025),但全文需付费,可能包含更详细的布局算法 --- ## 八、洞见与建议 ### 对于 sight-singing 项目 1. **不要用字体方案**。字体方案无法做到按拍分组合并减时线,也无法实现播放高亮等交互功能。 2. **自建轻量 SVG 渲染器是最佳路径**。参考 JianpuRender 的三层架构,但要注意: - 按拍分组是核心,先做 beat grouping 再做 rendering - 减时线合并是最难的部分,投入足够时间 - 用 `<g>` 标签做层级分组,方便后续加交互(高亮、点击) 3. **间距算法建议**: - 每行固定 4 个小节(4/4 拍)或 3 个小节(3/4 拍) - 拍内等分空间,拍间留 1.5 倍间距 - 数字字号建议 18-22px,减时线宽度 1.5px - 行高根据当前行最大减时线数动态计算 4. **数据模型建议**:直接用 MusicXML 的子集作为中间格式,未来可以从 MusicXML 文件导入 --- ## 信息来源 - [JianpuRender - TypeScript 简谱渲染库](https://github.com/flufy3d/JianpuRender) - [react-jianpu - React 简谱组件](https://github.com/felixhao28/react-jianpu) - [musje - 数字记谱法处理器](https://github.com/muzenplaats/musje) - [jianpu-ly - Lilypond 简谱排版](https://github.com/ssb22/jianpu-ly) - [OSMD Jianpu Mode - 简谱模式](https://opensheetmusicdisplay.org/blog/jianpu-mode/) - [OpenSheetMusicDisplay - MusicXML 渲染器](https://github.com/opensheetmusicdisplay/opensheetmusicdisplay) - [jianpu-ascii-font - ASCII 连字字体方案](https://github.com/RobertWinslow/jianpu-ascii-font) - [typst-kyphu - Typst 简谱方案](https://github.com/Shuenhoy/typst-kyphu) - [musicxml_to_jianpu - MusicXML 转简谱](https://github.com/lzh9102/musicxml_to_jianpu) - [QinJianPu - 简谱编辑器](https://github.com/Colennn/QinJianPu) - [latex-jianpu - LaTeX 简谱宏包](https://github.com/liantze/latex-jianpu) - [OSMD Jianpu Issue #1359](https://github.com/opensheetmusicdisplay/opensheetmusicdisplay/issues/1359) - [Springer: Digital Representation and Rendering of Chinese Jianpu Notation](https://link.springer.com/chapter/10.1007/978-981-96-4783-5_5) - [jianpu-ly 配合 Lilypond 打复杂合奏简谱](https://www.cnblogs.com/zarya/p/18984098/jianpu-ly-orchestra) - [ANU Chinese Classical Music Ensemble - Jianpu Guide](http://anuccme.com/jianpu) - [Guzheng Alive - Write Sheet Music](https://guzhengalive.com/write-sheet-music)