# 简谱 (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)