瀏覽代碼

feat: 简谱渲染引擎开发1.0

tianyong 2 月之前
父節點
當前提交
7df5d63210
共有 54 個文件被更改,包括 31400 次插入866 次删除
  1. 7 0
      .claude/settings.local.json
  2. 311 199
      docs/jianpu-renderer/01-TASKS_CHECKLIST.md
  3. 368 47
      docs/jianpu-renderer/02-PROGRESS.md
  4. 765 22
      docs/jianpu-renderer/04-MUSICXML_MAPPING.md
  5. 524 20
      docs/jianpu-renderer/05-VEXFLOW_COMPAT.md
  6. 999 23
      docs/jianpu-renderer/06-RENDER_SPEC.md
  7. 2982 0
      docs/musicxml/二声部.xml
  8. 516 0
      docs/musicxml/单声部.xml
  9. 10594 0
      docs/musicxml/总谱对齐.xml
  10. 520 0
      docs/musicxml/自己打的-简单-单声部.xml
  11. 1 1
      osmd-extended
  12. 818 161
      package-lock.json
  13. 7 1
      package.json
  14. 324 0
      src/jianpu-renderer/__tests__/DivisionsHandler.test.ts
  15. 392 0
      src/jianpu-renderer/__tests__/MeasureLayoutEngine.test.ts
  16. 506 0
      src/jianpu-renderer/__tests__/MultiVoiceAligner.test.ts
  17. 495 0
      src/jianpu-renderer/__tests__/NotePositionCalculator.test.ts
  18. 497 0
      src/jianpu-renderer/__tests__/OSMDDataParser.test.ts
  19. 554 0
      src/jianpu-renderer/__tests__/SystemLayoutEngine.test.ts
  20. 450 0
      src/jianpu-renderer/__tests__/TimeCalculator.test.ts
  21. 109 0
      src/jianpu-renderer/__tests__/baseline/README.md
  22. 21 0
      src/jianpu-renderer/__tests__/baseline/basic/times.json
  23. 21 0
      src/jianpu-renderer/__tests__/baseline/complex/times.json
  24. 21 0
      src/jianpu-renderer/__tests__/baseline/mixed-durations/times.json
  25. 21 0
      src/jianpu-renderer/__tests__/baseline/multi-voice/times.json
  26. 21 0
      src/jianpu-renderer/__tests__/baseline/with-lyrics/times.json
  27. 589 0
      src/jianpu-renderer/__tests__/collect-baseline.html
  28. 683 0
      src/jianpu-renderer/__tests__/compare.html
  29. 151 0
      src/jianpu-renderer/__tests__/fixtures/README.md
  30. 254 0
      src/jianpu-renderer/__tests__/fixtures/basic.xml
  31. 475 0
      src/jianpu-renderer/__tests__/fixtures/complex.xml
  32. 357 0
      src/jianpu-renderer/__tests__/fixtures/mixed-durations.xml
  33. 465 0
      src/jianpu-renderer/__tests__/fixtures/multi-voice.xml
  34. 342 0
      src/jianpu-renderer/__tests__/fixtures/with-lyrics.xml
  35. 587 0
      src/jianpu-renderer/__tests__/integration.test.ts
  36. 233 0
      src/jianpu-renderer/__tests__/models.test.ts
  37. 406 0
      src/jianpu-renderer/__tests__/parser.test.ts
  38. 0 18
      src/jianpu-renderer/__tests__/setup.test.ts
  39. 90 0
      src/jianpu-renderer/__tests__/setup.ts
  40. 385 12
      src/jianpu-renderer/core/layout/MeasureLayoutEngine.ts
  41. 364 6
      src/jianpu-renderer/core/layout/MultiVoiceAligner.ts
  42. 508 5
      src/jianpu-renderer/core/layout/NotePositionCalculator.ts
  43. 619 5
      src/jianpu-renderer/core/layout/SystemLayoutEngine.ts
  44. 344 0
      src/jianpu-renderer/core/parser/DivisionsHandler.ts
  45. 794 16
      src/jianpu-renderer/core/parser/OSMDDataParser.ts
  46. 485 7
      src/jianpu-renderer/core/parser/TimeCalculator.ts
  47. 1 0
      src/jianpu-renderer/core/parser/index.ts
  48. 569 0
      src/jianpu-renderer/docs/API.md
  49. 363 0
      src/jianpu-renderer/docs/DEVELOPMENT.md
  50. 49 3
      src/jianpu-renderer/models/JianpuMeasure.ts
  51. 57 0
      src/jianpu-renderer/models/JianpuNote.ts
  52. 9 1
      src/jianpu-renderer/models/JianpuScore.ts
  53. 45 0
      vitest.config.ts
  54. 332 319
      yarn.lock

+ 7 - 0
.claude/settings.local.json

@@ -0,0 +1,7 @@
+{
+  "permissions": {
+    "allow": [
+      "Bash(grep:*)"
+    ]
+  }
+}

+ 311 - 199
docs/jianpu-renderer/01-TASKS_CHECKLIST.md

@@ -10,8 +10,8 @@
 ## 📋 总览
 
 ### 开发阶段(已调整,总计10周)
-- [ ] **阶段0:准备工作**(第1周)- 50% 完成
-- [ ] **阶段0.5:规范文档编写**(第2周)⭐ 新增
+- [x] **阶段0:准备工作**(第1周)- ✅ 已完成
+- [ ] **阶段0.5:规范文档编写**(第2周)⭐ 进行中
 - [ ] **阶段1:核心解析器**(第3周)- 已调整细化
 - [ ] **阶段2:布局引擎**(第4-5周)
 - [ ] **阶段3:绘制引擎**(第6-7周)- 已调整细化
@@ -74,28 +74,28 @@
 
 ---
 
-### 任务0.3:创建测试数据和环境 ⏸️ 待开始
-- [ ] 准备测试用的MusicXML文件(至少5个)
-  - [ ] `basic.xml` - 基础简谱(只有四分音符
-  - [ ] `mixed-durations.xml` - 混合时值(八分、四分、二分音符
-  - [ ] `multi-voice.xml` - 多声部简谱
-  - [ ] `with-lyrics.xml` - 带歌词简谱
-  - [ ] `complex.xml` - 复杂记号(装饰音、连音符等
-- [ ] 创建测试环境
-  - [ ] 创建对比测试页面 `__tests__/compare.html`
-  - [ ] 配置测试框架(Jest或其他
-  - [ ] 编写基础测试用例
-- [ ] 创建开发文档
-  - [ ] 创建 `DEVELOPMENT.md` 开发指南
-  - [ ] 创建 `API.md` API文档框架
+### 任务0.3:创建测试数据和环境 ✅ 已完成
+- [x] 准备测试用的MusicXML文件(5个)
+  - [x] `basic.xml` - 基础简谱(四分音符、休止符、高低音
+  - [x] `mixed-durations.xml` - 混合时值(八分/十六分/四分/二分/全音符、附点
+  - [x] `multi-voice.xml` - 多声部简谱(双声部、和弦)
+  - [x] `with-lyrics.xml` - 带歌词简谱(中文歌词、多遍歌词)
+  - [x] `complex.xml` - 复杂记号(升降号、三连音、装饰音、延音线、变拍
+- [x] 创建测试环境
+  - [x] 创建对比测试页面 `__tests__/compare.html`
+  - [x] 配置测试框架(Vitest
+  - [x] 编写基础测试用例(models.test.ts, parser.test.ts)
+- [x] 创建开发文档
+  - [x] 创建 `docs/DEVELOPMENT.md` 开发指南
+  - [x] 创建 `docs/API.md` API文档框架
 
 **验收标准:**
-- [ ] 至少5个测试XML文件准备完成
-- [ ] 测试页面可以正常打开
-- [ ] 测试框架配置完成
-- [ ] 开发文档创建完成
+- [x] 5个测试XML文件准备完成
+- [x] 测试页面可以正常打开
+- [x] 测试框架配置完成
+- [x] 开发文档创建完成
 
-**预计时间:** 0.5天
+**实际用时:** 1小时
 
 ---
 
@@ -132,87 +132,87 @@
 
 > **为什么需要这个阶段:** 磨刀不误砍柴工!先定义清楚规范,开发时就不会反复讨论"应该怎么处理"
 
-### 任务0.5.1:创建MusicXML元素映射规范 ⏸️ 待开始
+### 任务0.5.1:创建MusicXML元素映射规范 ✅ 已完成
 **目标:** 创建MusicXML标签到简谱的完整映射表
 
 **文件:** `docs/jianpu-renderer/04-MUSICXML_MAPPING.md`
 
 **内容:**
-- [ ] **Divisions转换规则**
+- [x] **Divisions转换规则**
   - divisions的定义和作用
   - duration到实际时值的转换公式
   - 处理不同divisions值(1, 256, 480, 960等)
   - 异常情况处理(divisions=0, null等)
   
-- [ ] **音高元素解析规则**
+- [x] **音高元素解析规则**
   - `<step>` → 简谱数字(C=1, D=2, ... B=7)
   - `<octave>` → 高低音点数量
   - `<alter>` → 升降号(1=#, -1=♭, 0=♮)
   - 首调vs固定调的处理差异
   
-- [ ] **时值元素解析规则**
+- [x] **时值元素解析规则**
   - `<type>` → 音符基准类型
   - `<dot>` → 附点时值计算(+50%, +75%)
   - `<duration>` → 实际时值
   - 时值验证(确保不超过小节限制)
   
-- [ ] **特殊元素处理规则**
+- [x] **特殊元素处理规则**
   - `<grace>` → 装饰音(duration=0)
   - `<rest>` → 休止符
   - `<chord>` → 和弦(多个音符同时发声)
   - `<tuplet>` → 连音符(3连音、5连音等)
   - `<tie>` → 延音线
   
-- [ ] **修饰符处理规则**
+- [x] **修饰符处理规则**
   - `<accidental>` → 临时升降号
   - `<articulations>` → 演奏技法(staccato顿音等)
   - `<ornaments>` → 装饰音记号
   - `<dynamics>` → 力度记号
 
 **验收标准:**
-- [ ] 文档包含完整的映射表(至少20个元素)
-- [ ] 每个元素都有示例XML
-- [ ] 每个元素都有转换公式
-- [ ] 包含边界情况处理方案
+- [x] 文档包含完整的映射表(至少20个元素)
+- [x] 每个元素都有示例XML
+- [x] 每个元素都有转换公式
+- [x] 包含边界情况处理方案
 
-**预计时间:** 2天
+**实际用时:** 1.5小时
 
 ---
 
-### 任务0.5.2:创建简谱渲染规范 ⏸️ 待开始
+### 任务0.5.2:创建简谱渲染规范 ✅ 已完成
 **目标:** 定义简谱各元素的精确渲染规则
 
 **文件:** `docs/jianpu-renderer/06-RENDER_SPEC.md`
 
 **内容:**
-- [ ] **增时线渲染规范** ⭐⭐⭐
+- [x] **增时线渲染规范** ⭐⭐⭐
   - 增时线数量计算公式:`Math.floor(realValue) - 1`
   - 增时线位置计算:每条占1个四分音符空间
   - 增时线长度:占空间的70%,居中显示
   - 附点+增时线的组合处理
   - 多声部场景下的垂直对齐要求
   
-- [ ] **减时线渲染规范**
+- [x] **减时线渲染规范**
   - 减时线数量计算公式:`Math.log2(4 / realValue)`
   - 减时线位置:紧贴音符下方
   - 减时线间距:2-3像素
   - 连续音符的减时线连接规则
   
-- [ ] **高低音点规范**
+- [x] **高低音点规范**
   - 高音点:在数字上方,每个点代表高八度
   - 低音点:在数字下方,每个点代表低八度
   - 点的大小和间距标准
   
-- [ ] **附点规范**
+- [x] **附点规范**
   - 附点位置:音符右侧
   - 多附点的排列方式
   
-- [ ] **升降号规范**
+- [x] **升降号规范**
   - 位置:音符左上方
   - 大小和字体
   - 固定调vs首调的显示差异
   
-- [ ] **视觉尺寸标准**
+- [x] **视觉尺寸标准**
   - 音符字体大小:20px(默认)
   - 歌词字体大小:14px
   - 高低音点半径:2px
@@ -220,83 +220,100 @@
   - 小节线粗细:1px
 
 **验收标准:**
-- [ ] 所有渲染元素都有精确的位置和尺寸定义
-- [ ] 包含视觉示意图(ASCII或图片)
-- [ ] 包含特殊情况的处理规则
+- [x] 所有渲染元素都有精确的位置和尺寸定义
+- [x] 包含视觉示意图(ASCII或图片)
+- [x] 包含特殊情况的处理规则
 
-**预计时间:** 1天
+**实际用时:** 1小时
 
 ---
 
-### 任务0.5.3:创建VexFlow兼容性规范 ⏸️ 待开始
+### 任务0.5.3:创建VexFlow兼容性规范 ✅ 已完成
 **目标:** 定义与VexFlow/OSMD的完整兼容规范
 
 **文件:** `docs/jianpu-renderer/05-VEXFLOW_COMPAT.md`
 
 **内容:**
-- [ ] **DOM结构规范**
+- [x] **DOM结构规范**
   - 元素ID命名规则:`vf-{noteId}`
   - CSS类名清单:`vf-note`, `vf-numbered-note-head`等
   - 元素层级结构
   - 属性标记规则
   
-- [ ] **state.times字段完整清单** ⭐⭐⭐
-  - 从业务代码中提取所有使用的字段(30-40个)
+- [x] **state.times字段完整清单** ⭐⭐⭐
+  - 从业务代码中提取所有使用的字段(40+个)
   - 每个字段的类型定义
   - 每个字段的来源(如何计算/从哪里获取)
   - 每个字段的用途(哪个业务功能使用)
   
-- [ ] **cursor接口规范**
+- [x] **cursor接口规范**
   - Iterator对象的必需方法
   - reset(), next()等方法的行为定义
   - CurrentVoiceEntries的数据结构
   
-- [ ] **GraphicSheet接口规范**
+- [x] **GraphicSheet接口规范**
   - MeasureList结构
   - parentSourceMeasure必需字段
   
-- [ ] **其他兼容接口**
-  - Sheet.userStartTempoInBPM
-  - EngravingRules.DYMusicScoreType
-  - 其他业务代码使用的接口
+- [x] **其他兼容接口**
+  - 颜色和样式规范
+  - 事件和交互规范
+  - 迁移策略
 
 **验收标准:**
-- [ ] state.times字段清单完整(通过业务代码验证)
-- [ ] DOM规范可以生成兼容的HTML
-- [ ] 所有接口都有TypeScript类型定义
+- [x] state.times字段清单完整(通过业务代码验证)
+- [x] DOM规范可以生成兼容的HTML
+- [x] 所有接口都有TypeScript类型定义
 
-**预计时间:** 1.5天
+**实际用时:** 1.5小时
 
 ---
 
-### 任务0.5.4:创建测试基准数据 ⏸️ 待开始
+### 任务0.5.4:创建测试基准数据 ✅ 已完成(工具和结构)
 **目标:** 使用现有引擎生成测试基准,作为新引擎的对比标准
 
 **内容:**
-- [ ] 选择5个测试曲谱
+- [x] 选择5个测试曲谱
   - basic.xml: 基础简谱
   - mixed-durations.xml: 混合时值
   - multi-voice.xml: 多声部
   - with-lyrics.xml: 带歌词
   - complex.xml: 复杂记号
   
-- [ ] 使用旧引擎渲染,保存基准
-  - 截图保存视觉效果
-  - console.log保存state.times数据
-  - 保存DOM结构(innerHTML)
-  - 记录关键测量值(小节宽度、音符位置等)
+- [x] 创建基准数据收集工具
+  - 创建 `collect-baseline.html` 数据收集工具页面
+  - 创建 `baseline/` 目录结构
+  - 提供控制台命令用于导出state.times
+  - 提供JSON导出功能
   
-- [ ] 创建对比测试页面
+- [x] 创建对比测试页面
   - 左侧显示旧引擎渲染结果
   - 右侧显示新引擎渲染结果
-  - 自动对比差异
+  - 添加加载基准数据功能
+  - 自动对比差异(基础实现)
+
+**新增文件:**
+```
+src/jianpu-renderer/__tests__/
+├── collect-baseline.html           # 数据收集工具
+└── baseline/
+    ├── README.md                   # 使用说明
+    ├── basic/times.json            # 模板
+    ├── mixed-durations/times.json  # 模板
+    ├── multi-voice/times.json      # 模板
+    ├── with-lyrics/times.json      # 模板
+    └── complex/times.json          # 模板
+```
 
 **验收标准:**
-- [ ] 5个测试曲谱准备完成
-- [ ] 基准数据完整保存
-- [ ] 对比页面可以正常工作
+- [x] 5个测试曲谱准备完成
+- [x] 基准数据收集工具创建完成
+- [x] 对比页面可以正常工作
+- [ ] 实际基准数据收集(需要手动在运行环境中执行)
+
+**实际用时:** 1小时
 
-**预计时间:** 1.5天
+**备注:** 基准数据的实际收集需要在项目运行环境中手动执行,已提供详细的收集说明和工具。
 
 ---
 
@@ -309,147 +326,197 @@
 
 ## 🔧 阶段1:核心解析器(第3周)⭐ 已调整细化
 
-### 任务1.0:创建Divisions处理器 ⏸️ 待开始 ⭐ 新增
+### 任务1.0:创建Divisions处理器 ✅ 已完成
 **目标:** 实现MusicXML divisions的正确处理(这是时值计算的基础!)
 
 **文件:** `src/jianpu-renderer/core/parser/DivisionsHandler.ts`
 
 **内容:**
-- [ ] 实现divisions缓存机制
+- [x] 实现divisions缓存机制
   - 每个小节可能有不同的divisions
   - 缓存当前divisions值
   
-- [ ] 实现duration转换
+- [x] 实现duration转换
   ```typescript
   realValue = duration / divisions;  // 转换为四分音符为单位
   ```
   
-- [ ] 处理异常情况
+- [x] 处理异常情况
   - divisions为0
   - divisions为null/undefined
   - duration为负数
   
-- [ ] 编写单元测试
+- [x] 编写单元测试
   - 测试不同divisions值(1, 256, 480, 960)
   - 测试转换准确性
   - 测试异常处理
 
 **验收标准:**
-- [ ] 能正确处理任意divisions值
-- [ ] 转换准确(误差<0.001)
-- [ ] 异常情况有明确错误提示
-- [ ] 单元测试通过(至少8个测试用例)
+- [x] 能正确处理任意divisions值
+- [x] 转换准确(误差<0.001)
+- [x] 异常情况有明确错误提示
+- [x] 单元测试通过(39个测试用例)✅
 
-**预计时间:** 0.5天
+**实际时间:** 0.5小时
+
+**新增功能(超出预期):**
+- 附点音符计算(applyDots)
+- 秒数计算(toSeconds)
+- 音符类型映射(getNoteTypeRealValue)
+- 带详细信息的转换(toRealValueWithInfo)
+- 严格模式支持
+- 工厂函数和快捷函数
 
 ---
 
-### 任务1.1:实现OSMD数据解析器 ⏸️ 待开始 ⭐ 已调整细化
-- [ ] 实现 `OSMDDataParser.parse()` 主方法
-  - [ ] 解析OSMD对象结构
-  - [ ] 提取曲谱元数据(标题、作曲家等)
-  - [ ] 提取速度信息
-- [ ] 实现 `parseMeasures()` 方法
-  - [ ] 遍历 `osmd.GraphicSheet.MeasureList`
-  - [ ] 提取每个小节的拍号
-  - [ ] 提取每个小节的调号
-  - [ ] 提取小节号
-- [ ] 实现 `parseNotes()` 方法
-  - [ ] 使用 `osmd.cursor.Iterator` 遍历音符
-  - [ ] 提取音符音高(转换为1-7)
-  - [ ] 提取音符时值
-  - [ ] 提取八度信息
-  - [ ] 提取修饰符(升降号、附点)
-  - [ ] 识别休止符
-  - [ ] 保存OSMD原始数据引用
-- [ ] 实现辅助方法
-  - [ ] `getPitchNumber()` - 音名转简谱数字
-  - [ ] `getOctaveOffset()` - 计算八度偏移
-  - [ ] `getKeyString()` - 获取调号字符串
+### 任务1.1:实现OSMD数据解析器 ✅ 已完成
+- [x] 实现 `OSMDDataParser.parse()` 主方法
+  - [x] 解析OSMD对象结构
+  - [x] 提取曲谱元数据(标题、作曲家等)
+  - [x] 提取速度信息
+- [x] 实现 `parseMeasures()` 方法
+  - [x] 遍历 `osmd.GraphicSheet.MeasureList`
+  - [x] 提取每个小节的拍号
+  - [x] 提取每个小节的调号
+  - [x] 提取小节号
+- [x] 实现 `parseNotes()` 方法
+  - [x] 使用 `osmd.cursor.Iterator` 遍历音符
+  - [x] 提取音符音高(转换为1-7)
+  - [x] 提取音符时值
+  - [x] 提取八度信息
+  - [x] 提取修饰符(升降号、附点)
+  - [x] 识别休止符
+  - [x] 保存OSMD原始数据引用
+- [x] 实现辅助方法
+  - [x] `getPitchNumber()` - 音名转简谱数字
+  - [x] `getOctaveOffset()` - 计算八度偏移
+  - [x] `alterToAccidental()` - 升降号转换
+  - [x] `noteTypeToRealValue()` - 音符类型转时值
 
 **验收标准:**
-- [ ] 能正确解析小节数量和拍号
-- [ ] 能正确解析音符音高(1-7)
-- [ ] 能正确解析音符时值
-- [ ] 能正确识别休止符
-- [ ] 能正确识别升降号和附点
-- [ ] 保留了OSMD原始数据引用
-- [ ] 单元测试通过(至少10个测试用例)
+- [x] 能正确解析小节数量和拍号
+- [x] 能正确解析音符音高(1-7)
+- [x] 能正确解析音符时值
+- [x] 能正确识别休止符
+- [x] 能正确识别升降号和附点
+- [x] 保留了OSMD原始数据引用
+- [x] 单元测试通过(30个测试用例)✅
 
 **测试用例:**
 ```
 ✓ 应该正确解析4/4拍小节
 ✓ 应该正确解析3/4拍小节
-✓ 应该正确解析音符音高 do re mi fa sol la si
+✓ 应该正确解析6/8拍小节
+✓ 应该正确解析多个小节
+✓ 应该正确解析音符音高 C D E F G A B
 ✓ 应该正确识别休止符
 ✓ 应该正确解析附点音符
 ✓ 应该正确解析升降号
-✓ 应该正确解析多声部
 ✓ 应该正确解析不同时值的音符
 ✓ 应该保留OSMD数据引用
 ✓ 应该正确解析调号变化
+✓ 应该正确解析高音/低音/中音八度
 ```
 
-**预计时间:** 3天
+**实际时间:** 1小时
+
+**新增功能:**
+- 支持两种解析路径(cursor遍历 和 MeasureList遍历)
+- 解析统计信息(noteCount, restCount, graceNoteCount等)
+- 导出工具函数供其他模块使用
 
 ---
 
-### 任务1.2:实现时间计算器 ⏸️ 待开始
-- [ ] 实现 `calculateTimes()` 主方法
-  - [ ] 根据BPM计算四分音符时长
-  - [ ] 按小节和时间戳排序音符
-  - [ ] 计算每个音符的绝对开始时间
-  - [ ] 计算每个音符的绝对结束时间
-- [ ] 支持变速
-  - [ ] 识别小节内的速度变化
-  - [ ] 正确计算变速后的时间
-- [ ] 处理反复记号
-  - [ ] 识别反复记号(暂时可以简化处理)
+### 任务1.2:实现时间计算器 ✅ 已完成
+- [x] 实现 `calculateTimes()` 主方法
+  - [x] 根据BPM计算四分音符时长
+  - [x] 按小节和时间戳排序音符
+  - [x] 计算每个音符的绝对开始时间
+  - [x] 计算每个音符的绝对结束时间
+- [x] 支持变速
+  - [x] 识别小节内的速度变化
+  - [x] 正确计算变速后的时间
+- [x] 处理弱起小节
+  - [x] 检测弱起小节并补充时间
+- [x] 支持节拍器前奏时间
+  - [x] 可配置节拍器拍数
+  - [x] 计算fixtime
 
 **验收标准:**
-- [ ] 时间计算准确(误差<1ms)
-- [ ] 支持不同速度(60-240 BPM)
-- [ ] 支持变速
-- [ ] 单元测试通过
+- [x] 时间计算准确(误差<1ms)
+- [x] 支持不同速度(60-240 BPM)
+- [x] 支持变速
+- [x] 单元测试通过(23个测试用例)✅
 
 **测试用例:**
 ```
 ✓ 120 BPM时四分音符应该是0.5秒
 ✓ 60 BPM时四分音符应该是1秒
+✓ 240 BPM时四分音符应该是0.25秒
 ✓ 应该正确计算连续音符的时间
-✓ 应该正确处理变速
+✓ 应该正确计算不同时值的音符
+✓ 应该正确计算多小节的累计时间
+✓ 启用节拍器时应该添加前奏时间
+✓ 可以配置节拍器拍数
+✓ 应该检测弱起小节并补充时间
+✓ 完整小节不应该被检测为弱起
+✓ 应该正确处理小节速度变化
 ✓ 时间计算误差应该小于1ms
+✓ 应该填充OSMD兼容字段
 ```
 
-**预计时间:** 1天
+**实际时间:** 1小时
+
+**新增功能:**
+- 弱起小节检测和时间补充
+- 节拍器前奏时间计算(可配置拍数)
+- OSMD兼容字段填充(time, endtime, relativeTime等)
+- 工具函数导出(getFixTime, realValueToSeconds, formatTime等)
 
 ---
 
-### 任务1.3:集成测试解析器 ⏸️ 待开始
-- [ ] 使用真实的MusicXML文件测试
-- [ ] 验证解析结果的完整性
-- [ ] 修复发现的bug
-- [ ] 优化性能
+### 任务1.3:集成测试解析器 ✅ 已完成
+- [x] 使用真实的MusicXML文件测试
+  - [x] basic.xml - 基础简谱测试
+  - [x] mixed-durations.xml - 混合时值测试
+- [x] 验证解析结果的完整性
+  - [x] 小节数量、拍号、速度
+  - [x] 音符音高、时值、八度
+  - [x] 休止符、附点音符
+  - [x] 时间计算准确性
+- [x] 性能测试
+  - [x] 解析速度 < 100ms
+- [x] 边界情况测试
+  - [x] 空XML、无效XML处理
 
-**预计时间:** 0.5天
+**验收标准:**
+- [x] 29个集成测试通过 ✅
+- [x] 性能测试通过(< 100ms)
+- [x] 边界情况正确处理
+
+**实际时间:** 0.5小时
+
+**新增文件:**
+- `src/jianpu-renderer/__tests__/integration.test.ts` - 集成测试(~450行)
+- `SimpleMusicXMLParser` - 简单的MusicXML解析器(用于测试)
 
 ---
 
 ## 📐 阶段2:布局引擎(第3-4周)
 
-### 任务2.1:实现小节布局计算器 ⏸️ 待开始
-- [ ] 实现固定时间比例算法
-  - [ ] 根据拍号计算小节总时值
-  - [ ] 计算小节内容宽度
-  - [ ] 计算小节总宽度(包含padding)
-- [ ] 实现 `calculateNotePositions()` 方法
-  - [ ] 计算每个音符的X坐标
-  - [ ] 应用固定时间比例公式
-  - [ ] 计算音符宽度
-  - [ ] 应用最小间距限制
-- [ ] 实现 `layoutMeasures()` 批量布局方法
-  - [ ] 遍历所有小节
-  - [ ] 累计X坐标
+### 任务2.1:实现小节布局计算器 ✅ 已完成
+- [x] 实现固定时间比例算法
+  - [x] 根据拍号计算小节总时值
+  - [x] 计算小节内容宽度
+  - [x] 计算小节总宽度(包含padding)
+- [x] 实现 `calculateNotePositions()` 方法
+  - [x] 计算每个音符的X坐标
+  - [x] 应用固定时间比例公式
+  - [x] 计算音符宽度
+  - [x] 应用最小间距限制
+- [x] 实现 `layoutMeasures()` 批量布局方法
+  - [x] 遍历所有小节
+  - [x] 累计X坐标
 
 **核心算法:**
 ```typescript
@@ -461,39 +528,46 @@ noteX = measureX + measurePadding + timestamp × (beatType / 4) × quarterNoteSp
 ```
 
 **验收标准:**
-- [ ] 同一拍号的小节宽度完全一致
-- [ ] 音符间距严格按时值比例分配
-- [ ] 左右padding正确应用
-- [ ] 单元测试通过
+- [x] 同一拍号的小节宽度完全一致
+- [x] 音符间距严格按时值比例分配
+- [x] 左右padding正确应用
+- [x] 单元测试通过(31个测试用例)✅
 
 **测试用例:**
 ```
 ✓ 4/4拍所有小节宽度应该相同
-✓ 3/4拍所有小节宽度应该相同
+✓ 3/4拍小节宽度应该正确
+✓ 6/8拍小节宽度应该正确
 ✓ 4/4拍宽度应该是3/4拍的4/3倍
 ✓ 二分音符间距应该是四分音符的2倍
 ✓ 八分音符间距应该是四分音符的0.5倍
 ✓ padding应该正确应用
+✓ 最小间距限制应该生效
 ```
 
-**预计时间:** 3天
+**实际时间:** 1小时
+
+**新增功能:**
+- 布局统计(measureCount, noteCount, totalWidth等)
+- 音符宽度计算(支持升降号、附点)
+- 工具函数导出(calculateMeasureRealValue, validateMeasureWidths等)
 
 ---
 
-### 任务2.2:实现多声部对齐 ⏸️ 待开始
-- [ ] 实现 `MultiVoiceAligner.alignVoices()` 方法
-  - [ ] 收集所有时间戳
-  - [ ] 为每个时间戳分配统一的X坐标
-  - [ ] 更新所有声部音符的X坐标
-- [ ] 处理边界情况
-  - [ ] 单声部不需要对齐
-  - [ ] 休止符的对齐
-  - [ ] 不同时值音符的对齐
+### 任务2.2:实现多声部对齐 ✅ 已完成
+- [x] 实现 `MultiVoiceAligner.alignVoices()` 方法
+  - [x] 收集所有时间戳
+  - [x] 为每个时间戳分配统一的X坐标
+  - [x] 更新所有声部音符的X坐标
+- [x] 处理边界情况
+  - [x] 单声部不需要对齐
+  - [x] 休止符的对齐
+  - [x] 不同时值音符的对齐
 
 **验收标准:**
-- [ ] 相同时间戳的音符X坐标完全相同
-- [ ] 不影响单声部的布局
-- [ ] 多声部总谱测试通过
+- [x] 相同时间戳的音符X坐标完全相同
+- [x] 不影响单声部的布局
+- [x] 多声部总谱测试通过(31个测试用例)✅
 
 **测试用例:**
 ```
@@ -501,48 +575,86 @@ noteX = measureX + measurePadding + timestamp × (beatType / 4) × quarterNoteSp
 ✓ 单声部不应该被影响
 ✓ 休止符应该正确对齐
 ✓ 不同时值的音符应该正确对齐
+✓ 三个声部正确对齐
+✓ 对齐策略(max/min/avg)
+✓ 浮点数时间戳精度处理
 ```
 
-**预计时间:** 2天
+**实际时间:** 1小时
+
+**新增功能:**
+- 对齐策略配置(max/min/avg)
+- 时间戳精度控制(处理浮点数误差)
+- 休止符参与对齐可配置
+- 验证对齐结果方法
+- 工具函数(alignMeasureVoices, getVoiceStats等)
 
 ---
 
-### 任务2.3:实现行布局和自动换行 ⏸️ 待开始
-- [ ] 实现 `SystemLayoutEngine.layoutSystems()` 方法
-  - [ ] 设置行宽度
-  - [ ] 遍历小节,计算是否需要换行
-  - [ ] 创建新行
-  - [ ] 分配小节到行
-- [ ] 实现Y坐标计算
-  - [ ] 计算行间距
-  - [ ] 计算每行的Y坐标
-- [ ] 优化行分配
-  - [ ] 避免小节被拆分
-  - [ ] 优化最后一行
+### 任务2.3:实现行布局和自动换行 ✅ 已完成
+- [x] 实现 `SystemLayoutEngine.layoutSystems()` 方法
+  - [x] 设置行宽度
+  - [x] 遍历小节,计算是否需要换行
+  - [x] 创建新行
+  - [x] 分配小节到行
+- [x] 实现Y坐标计算
+  - [x] 计算行间距
+  - [x] 计算每行的Y坐标
+- [x] 优化行分配
+  - [x] 避免小节被拆分
+  - [x] 优化最后一行
+  - [x] 支持行拉伸填满宽度
+  - [x] 支持多声部行高计算
 
 **验收标准:**
-- [ ] 自动换行功能正常
-- [ ] 行间距正确
-- [ ] 不会出现空行
-- [ ] 小节不会被拆分
-- [ ] 测试通过
-
-**预计时间:** 2天
+- [x] 自动换行功能正常
+- [x] 行间距正确
+- [x] 不会出现空行
+- [x] 小节不会被拆分
+- [x] 测试通过(40个测试用例)✅
+
+**实际时间:** 0.5小时
+
+**新增功能(超出预期):**
+- 行拉伸算法(非最后一行自动填满行宽)
+- 多声部行高计算
+- 布局统计信息
+- 验证工具函数(validateSystemLayout)
+- 辅助查询函数(findSystemForMeasure, getMeasureRange, getSystemInfoForMeasure)
+- 最优分配算法(calculateOptimalSystemAllocation)
 
 ---
 
-### 任务2.4:实现音符Y坐标计算 ⏸️ 待开始
-- [ ] 实现 `NotePositionCalculator.calculateNoteY()` 方法
-  - [ ] 根据声部数量计算间距
-  - [ ] 为每个声部分配Y坐标
-  - [ ] 确保声部之间有足够间距
+### 任务2.4:实现音符Y坐标计算 ✅ 已完成
+- [x] 实现 `NotePositionCalculator.calculateNoteY()` 方法
+  - [x] 根据声部数量计算间距
+  - [x] 为每个声部分配Y坐标
+  - [x] 确保声部之间有足够间距
+- [x] 实现垂直区域计算
+  - [x] 升降号区域
+  - [x] 高音点区域
+  - [x] 低音点区域
+  - [x] 减时线区域
+  - [x] 歌词区域
+- [x] 实现小节和行布局方法
+  - [x] layoutMeasureNotes() - 小节音符布局
+  - [x] layoutSystemNotes() - 行音符布局
+  - [x] layoutAllSystems() - 批量布局
 
 **验收标准:**
-- [ ] 音符Y坐标正确
-- [ ] 声部间距合理
-- [ ] 多声部不重叠
+- [x] 音符Y坐标正确
+- [x] 声部间距合理
+- [x] 多声部不重叠
+- [x] 测试通过(35个测试用例)✅
 
-**预计时间:** 1天
+**实际时间:** 0.5小时
+
+**新增功能(超出预期):**
+- 垂直区域计算(calculateVerticalRegions)
+- 单声部/多声部高度计算
+- 音符各部分位置计算(getNotePartPositions)
+- 布局验证工具(validateNoteYPositions)
+- 工厂函数和工具函数导出
 
 ---
 
@@ -873,11 +985,11 @@ noteX = measureX + measurePadding + timestamp × (beatType / 4) × quarterNoteSp
 ## 📊 进度跟踪
 
 ### 当前状态
-- **当前阶段:** 阶段0 - 准备工作
-- **当前任务:** 任务0.3 - 创建测试数据和环境
-- **完成度:** 25%
+- **当前阶段:** 阶段0.5 - 规范文档编写
+- **当前任务:** 任务0.5.1 - 创建MusicXML元素映射规范
+- **完成度:** 15%
 - **开始日期:** 2026-01-29
-- **预计完成:** 2026-03-26(8周后)
+- **预计完成:** 2026-04-09(10周后)
 
 ### 里程碑
 - [ ] **Checkpoint 1** - 阶段0完成(第1周结束)

+ 368 - 47
docs/jianpu-renderer/02-PROGRESS.md

@@ -1,30 +1,30 @@
 # 简谱渲染引擎重写 - 开发进度追踪
 
 > **最后更新:** 2026-01-29  
-> **当前阶段:** 阶段0 - 准备工作  
-> **整体进度:** 10% ██░░░░░░░░░░░░░░░░░░
+> **当前阶段:** 阶段0.5 - 规范文档编写  
+> **整体进度:** 15% ███░░░░░░░░░░░░░░░░░
 
 ---
 
 ## 📊 当前状态
 
 ### 正在进行
-- **阶段:** 阶段0 - 准备工作(第1周)
-- **任务:** 任务0.3 - 创建测试数据和环境
-- **进度:** 已完成任务0.1、0.2、0.4,正在进行任务0.3
+- **阶段:** 阶段0.5 - 规范文档编写(第2周)
+- **任务:** 任务0.5.4 - 创建测试基准数据
+- **进度:** 阶段0.5已完成3/4任务
 
 ### 下一步行动
-1. 完成任务0.3:准备测试XML文件和对比页面
-2. 进入阶段0.5:编写规范文档(新增阶段)
-3. 创建MusicXML映射规范
-4. 创建简谱渲染规范
-5. 创建VexFlow兼容性规范
+1. ~~创建MusicXML元素映射规范文档~~ ✅ 已完成
+2. ~~创建简谱渲染规范文档~~ ✅ 已完成
+3. ~~创建VexFlow兼容性规范文档~~ ✅ 已完成
+4. 创建测试基准数据 ← **当前任务**
+5. 进入阶段1:核心解析器
 
 ---
 
 ## ✅ 已完成任务
 
-### 阶段0:准备工作(进行中 - 75%完成)
+### 阶段0:准备工作(✅ 已完成)
 
 #### ✅ 任务0.1:创建项目结构
 **完成日期:** 2026-01-29  
@@ -76,27 +76,304 @@ docs/jianpu-renderer/
 ├── 01-TASKS_CHECKLIST.md     ✅ 任务清单(已更新为10周计划)
 ├── 02-PROGRESS.md            ✅ 进度追踪
 ├── 03-MUSICXML_KNOWLEDGE.md  ✅ MusicXML知识
-├── 04-MUSICXML_MAPPING.md    ⏸️ 待完成(阶段0.5
-├── 05-VEXFLOW_COMPAT.md      ⏸️ 待完成(阶段0.5
-├── 06-RENDER_SPEC.md         ⏸️ 待完成(阶段0.5
+├── 04-MUSICXML_MAPPING.md    ✅ 已完成(800+行
+├── 05-VEXFLOW_COMPAT.md      ✅ 已完成(500+行
+├── 06-RENDER_SPEC.md         ✅ 已完成(600+行
 └── README.md                 ✅ 文档中心
 ```
 
 ---
 
-## 📋 进行中任务
+#### ✅ 任务0.3:创建测试数据和环境
+**完成日期:** 2026-01-29  
+**用时:** 1小时
+
+**完成内容:**
+- [x] 创建5个测试用MusicXML文件
+  - `basic.xml` - 基础简谱(四分音符、休止符、高低音)
+  - `mixed-durations.xml` - 混合时值(八分/十六分/四分/二分/全音符、附点)
+  - `multi-voice.xml` - 多声部(双声部、和弦、对齐)
+  - `with-lyrics.xml` - 带歌词(中文歌词、多遍歌词)
+  - `complex.xml` - 复杂记号(升降号、三连音、装饰音、延音线)
+- [x] 创建对比测试HTML页面 `compare.html`
+- [x] 配置Vitest测试框架
+- [x] 编写基础测试用例(模型测试、解析器测试)
+- [x] 创建开发文档 `DEVELOPMENT.md` 和 `API.md`
+
+**新增文件:**
+```
+src/jianpu-renderer/
+├── __tests__/
+│   ├── fixtures/
+│   │   ├── basic.xml
+│   │   ├── mixed-durations.xml
+│   │   ├── multi-voice.xml
+│   │   ├── with-lyrics.xml
+│   │   ├── complex.xml
+│   │   └── README.md
+│   ├── setup.ts
+│   ├── models.test.ts
+│   ├── parser.test.ts
+│   └── compare.html
+└── docs/
+    ├── DEVELOPMENT.md
+    └── API.md
+```
+
+---
+
+## ✅ 阶段0.5已完成任务
+
+### ✅ 任务0.5.1:创建MusicXML元素映射规范
+**完成日期:** 2026-01-29  
+**用时:** 1.5小时
+
+**完成内容:**
+- [x] Divisions转换规则(详细的转换公式和边界处理)
+- [x] 音高元素解析规则(step/octave/alter映射)
+- [x] 时值元素解析规则(type/duration/dot计算)
+- [x] 特殊元素处理规则(休止符/装饰音/和弦/连音符/延音线)
+- [x] 修饰符处理规则(升降号/演奏技法/装饰音记号)
 
-### 任务0.3:创建测试数据和环境
-**开始日期:** 2026-01-29  
-**预计用时:** 0.5天
+---
 
-**进度:** 0%
+### ✅ 任务0.5.2:创建简谱渲染规范
+**完成日期:** 2026-01-29  
+**用时:** 1小时
 
-**待完成:**
-- [ ] 从 `osmd-extended/test/data/` 中选择5个测试XML文件
-- [ ] 复制到项目测试目录
-- [ ] 创建对比测试HTML页面
-- [ ] 配置测试框架
+**完成内容:**
+- [x] 增时线渲染规范(计算公式/位置/长度)
+- [x] 减时线渲染规范(连音规则/连接规则)
+- [x] 高低音点规范
+- [x] 附点规范
+- [x] 升降号规范
+- [x] 视觉尺寸标准
+
+---
+
+### ✅ 任务0.5.3:创建VexFlow兼容性规范
+**完成日期:** 2026-01-29  
+**用时:** 1.5小时
+
+**完成内容:**
+- [x] DOM结构规范(ID命名/CSS类名/层级结构)
+- [x] state.times字段完整清单(40+字段,含类型和用途)
+- [x] cursor接口规范(Iterator/必需方法)
+- [x] GraphicSheet接口规范(MeasureList/SourceMeasure)
+- [x] 颜色样式规范
+- [x] 迁移策略
+
+---
+
+### ✅ 任务0.5.4:创建测试基准数据
+**完成日期:** 2026-01-29  
+**用时:** 1小时
+
+**完成内容:**
+- [x] 创建基准数据收集工具 `collect-baseline.html`
+- [x] 创建基准数据目录结构 `baseline/`
+- [x] 为5个测试文件创建模板
+- [x] 更新对比测试页面,添加基准数据加载功能
+- [x] 提供控制台命令用于手动收集数据
+
+**新增文件:**
+```
+src/jianpu-renderer/__tests__/
+├── collect-baseline.html           # 数据收集工具
+└── baseline/
+    ├── README.md                   # 使用说明
+    ├── basic/times.json            # 模板
+    ├── mixed-durations/times.json  # 模板
+    ├── multi-voice/times.json      # 模板
+    ├── with-lyrics/times.json      # 模板
+    └── complex/times.json          # 模板
+```
+
+---
+
+### ✅ 任务1.0:创建Divisions处理器
+**完成日期:** 2026-01-29  
+**用时:** 0.5小时
+
+**完成内容:**
+- [x] 实现divisions缓存机制(支持每小节不同divisions)
+- [x] 实现duration转换(toRealValue、toDuration)
+- [x] 处理异常情况(0、null、undefined、负数)
+- [x] 编写单元测试(39个测试用例全部通过)
+
+**新增功能:**
+- 附点音符计算(applyDots)
+- 秒数计算(toSeconds)
+- 音符类型映射(getNoteTypeRealValue)
+- 严格模式支持
+
+**新增文件:**
+```
+src/jianpu-renderer/core/parser/DivisionsHandler.ts    # 主实现(~300行)
+src/jianpu-renderer/__tests__/DivisionsHandler.test.ts # 测试(~300行)
+```
+
+---
+
+### ✅ 任务1.1:实现OSMD数据解析器
+**完成日期:** 2026-01-30  
+**用时:** 1小时
+
+**完成内容:**
+- [x] 实现 `parse()` 主方法(解析OSMD对象,返回JianpuScore)
+- [x] 实现 `parseMeasures()` 方法(支持SourceMeasures和MeasureList两种路径)
+- [x] 实现 `parseNotes()` 方法(支持cursor遍历和MeasureList回退)
+- [x] 实现辅助方法(getPitchNumber, getOctaveOffset, alterToAccidental等)
+- [x] 编写单元测试(30个测试用例全部通过)
+
+**新增文件:**
+```
+src/jianpu-renderer/core/parser/OSMDDataParser.ts   # 完整实现(~600行)
+src/jianpu-renderer/__tests__/OSMDDataParser.test.ts # 测试(~400行)
+```
+
+**导出的工具函数:**
+- `stepToJianpu(step)` - 音名转简谱数字
+- `octaveToOffset(octave)` - 八度转偏移
+- `alterToAccidental(alter)` - 升降号转换
+- `noteTypeToRealValue(type)` - 音符类型转时值
+
+---
+
+### ✅ 任务1.2:实现时间计算器
+**完成日期:** 2026-01-30  
+**用时:** 1小时
+
+**完成内容:**
+- [x] 实现 `calculateTimes()` 主方法
+- [x] 计算每个音符的绝对开始/结束时间
+- [x] 处理速度变化(小节级别)
+- [x] 计算fixtime和节拍器时间
+- [x] 弱起小节检测和时间补充
+- [x] OSMD兼容字段填充
+- [x] 编写单元测试(23个测试用例全部通过)
+
+**新增文件:**
+```
+src/jianpu-renderer/core/parser/TimeCalculator.ts   # 完整实现(~450行)
+src/jianpu-renderer/__tests__/TimeCalculator.test.ts # 测试(~350行)
+```
+
+**导出的工具函数:**
+- `getFixTime(bpm, beatCount)` - 计算节拍器时间
+- `realValueToSeconds(realValue, bpm)` - 时值转秒
+- `secondsToRealValue(seconds, bpm)` - 秒转时值
+- `formatTime(seconds)` - 格式化时间 MM:SS.mmm
+- `convertBeatUnit(bpm, fromUnit, toUnit)` - 节拍单位转换
+- `retain(value, precision)` - 保留精度
+
+---
+
+### ✅ 任务1.3:集成测试解析器
+**完成日期:** 2026-01-30  
+**用时:** 0.5小时
+
+**完成内容:**
+- [x] 创建SimpleMusicXMLParser用于解析测试XML
+- [x] basic.xml集成测试(12个测试用例)
+- [x] mixed-durations.xml集成测试(11个测试用例)
+- [x] 性能测试(解析时间<100ms)
+- [x] 边界情况测试
+
+**新增文件:**
+```
+src/jianpu-renderer/__tests__/integration.test.ts  # 集成测试(~450行)
+```
+
+**测试覆盖:**
+- 标题、作曲家解析
+- 小节数量、拍号、速度
+- 音符音高(1-7)、八度偏移
+- 休止符识别
+- 不同时值(十六分~全音符)
+- 附点音符
+- 时间计算准确性
+
+---
+
+## 🎉 阶段1全部完成!
+
+### 阶段1完成总结
+
+| 任务 | 文件 | 测试数 | 用时 |
+|------|------|--------|------|
+| 1.0 Divisions处理器 | DivisionsHandler.ts | 39 | 0.5h |
+| 1.1 OSMD数据解析器 | OSMDDataParser.ts | 30 | 1h |
+| 1.2 时间计算器 | TimeCalculator.ts | 23 | 1h |
+| 1.3 集成测试 | integration.test.ts | 29 | 0.5h |
+
+**总计:** 4个任务,~2000行代码,121个测试用例,用时约3小时
+
+---
+
+### ✅ 任务2.1:实现小节布局计算器
+**完成日期:** 2026-01-30  
+**用时:** 1小时
+
+**完成内容:**
+- [x] 实现固定时间比例算法
+- [x] 实现 `calculateMeasureWidth()` 方法
+- [x] 实现 `calculateNotePositions()` 方法
+- [x] 实现 `layoutMeasures()` 批量布局方法
+- [x] 实现音符宽度计算
+- [x] 编写单元测试(31个测试用例全部通过)
+
+**新增文件:**
+```
+src/jianpu-renderer/core/layout/MeasureLayoutEngine.ts   # 完整实现(~350行)
+src/jianpu-renderer/__tests__/MeasureLayoutEngine.test.ts # 测试(~350行)
+```
+
+**核心算法验证:**
+- ✅ 4/4拍小节宽度 = 4 × 50 + 40 = 240px
+- ✅ 3/4拍小节宽度 = 3 × 50 + 40 = 190px
+- ✅ 四分音符间距 = 50px
+- ✅ 八分音符间距 = 25px
+- ✅ 最小间距限制生效
+
+---
+
+### ✅ 任务2.2:实现多声部对齐
+**完成日期:** 2026-01-30  
+**用时:** 1小时
+
+**完成内容:**
+- [x] 实现 `MultiVoiceAligner.alignVoices()` 方法
+- [x] 收集所有声部的唯一时间戳
+- [x] 为相同时间戳的音符分配统一X坐标
+- [x] 支持三种对齐策略(max/min/avg)
+- [x] 处理边界情况(单声部、休止符、浮点数精度)
+- [x] 编写单元测试(31个测试用例全部通过)
+
+**新增文件:**
+```
+src/jianpu-renderer/core/layout/MultiVoiceAligner.ts   # 完整实现(~300行)
+src/jianpu-renderer/__tests__/MultiVoiceAligner.test.ts # 测试(~480行)
+```
+
+**核心功能:**
+- ✅ 相同时间戳音符垂直对齐
+- ✅ 单声部不受影响
+- ✅ 休止符参与/不参与对齐可配置
+- ✅ 浮点数时间戳精度处理
+
+---
+
+## 📋 下一步任务
+
+### 任务2.3:实现行布局和自动换行
+**状态:** ⏸️ 待开始  
+**预计用时:** 3天
+
+**内容:**
+- [ ] 实现 `SystemLayoutEngine.layoutSystems()` 方法
+- [ ] 设置行宽度,遍历小节计算是否需要换行
+- [ ] 实现Y坐标计算(行间距)
 
 ---
 
@@ -104,26 +381,26 @@ docs/jianpu-renderer/
 
 ### Checkpoint 0:阶段0完成(第1周结束)
 **预期日期:** 2026-02-05  
-**状态:** 🚧 进行中(75%完成)
+**状态:** ✅ 已完成
 
 **检查项:**
 - [x] 项目结构创建完成
 - [x] 数据模型定义完成
 - [x] 文档体系建立完成
-- [ ] 测试环境准备完成 ← 当前任务
+- [x] 测试环境准备完成
 
 ---
 
 ### Checkpoint 0.5:阶段0.5完成(第2周结束)⭐ 新增
 **预期日期:** 2026-02-12  
-**状态:** ⏸️ 未开始
+**状态:** ✅ 已完成
 
 **检查项:**
-- [ ] MusicXML映射规范文档完成
-- [ ] 简谱渲染规范文档完成
-- [ ] VexFlow兼容性规范文档完成
-- [ ] 测试基准数据准备完成
-- [ ] 可以开始编码
+- [x] MusicXML映射规范文档完成
+- [x] 简谱渲染规范文档完成
+- [x] VexFlow兼容性规范文档完成
+- [x] 测试基准数据准备完成(工具和结构)
+- [x] 可以开始编码
 
 ---
 
@@ -161,27 +438,30 @@ docs/jianpu-renderer/
 
 ### 整体进度
 - **总任务数:** 60+个任务(已调整)
-- **已完成:** 4个任务
-- **进行中:** 1个任务
-- **待开始:** 55+个任务
-- **完成度:** 10%
+- **已完成:** 15个任务
+- **进行中:** 0个任务
+- **待开始:** 45+个任务
+- **完成度:** 47%
 
 ### 阶段进度
 | 阶段 | 进度 | 状态 | 预计时间 |
 |------|------|------|---------|
-| 阶段0:准备工作 | 75% | 🚧 进行中 | 第1周 |
-| 阶段0.5:规范文档 | 0% | ⏸️ 未开始 | 第2周 ⭐新增 |
-| 阶段1:核心解析器 | 0% | ⏸️ 未开始 | 第3周 |
-| 阶段2:布局引擎 | 0% | ⏸️ 未开始 | 第4-5周 |
+| 阶段0:准备工作 | 100% | ✅ 已完成 | 第1周 |
+| 阶段0.5:规范文档 | 100% | ✅ 已完成 | 第2周 ⭐新增 |
+| 阶段1:核心解析器 | 100% | ✅ 已完成 | 第3周 |
+| 阶段2:布局引擎 | 66% | 🚧 进行中 | 第4-5周 |
 | 阶段3:绘制引擎 | 0% | ⏸️ 未开始 | 第6-7周 |
 | 阶段4:兼容层 | 0% | ⏸️ 未开始 | 第8周 |
 | 阶段5:测试优化 | 0% | ⏸️ 未开始 | 第9-10周 |
 
 ### 代码统计
-- **文件总数:** 29个代码文件 + 8个文档文件
-- **代码行数:** ~1500行(框架代码)
-- **文档行数:** ~1500行
-- **测试文件:** 1个
+- **文件总数:** 36个代码文件 + 31个文档/测试文件
+- **代码行数:** ~6300行(框架代码+测试)
+- **文档行数:** ~5000行(3个规范文档+工具页面)
+- **测试文件:** 9个
+- **测试用例:** 238个(全部通过)✅
+- **测试XML文件:** 5个
+- **基准数据模板:** 5个
 
 ---
 
@@ -222,7 +502,48 @@ docs/jianpu-renderer/
 - ✅ 深入学习MusicXML和VexFlow知识
 - ✅ 调整开发计划为10周
 - ✅ 新增阶段0.5:规范文档编写
-- 📝 下一步:完成任务0.3,准备测试环境
+- ✅ 创建5个测试MusicXML文件
+- ✅ 创建对比测试HTML页面
+- ✅ 配置Vitest测试框架
+- ✅ 编写基础测试用例
+- ✅ 创建开发文档和API文档
+- ✅ **阶段0全部完成!**
+- ✅ 创建MusicXML元素映射规范(04-MUSICXML_MAPPING.md,800+行)
+- ✅ 创建简谱渲染规范(06-RENDER_SPEC.md,600+行)
+- ✅ 创建VexFlow兼容性规范(05-VEXFLOW_COMPAT.md,500+行)
+  - 完整的state.times字段清单(40+字段)
+  - DOM结构规范
+  - cursor/GraphicSheet接口规范
+- ✅ 创建测试基准数据收集工具
+  - collect-baseline.html 数据收集页面
+  - baseline/ 目录结构
+  - 5个测试文件的times.json模板
+  - 更新compare.html添加基准加载功能
+- ✅ **阶段0.5全部完成!**
+- ✅ 创建Divisions处理器(任务1.0)
+  - 实现divisions缓存机制
+  - 实现duration转换函数
+  - 39个单元测试全部通过
+- ✅ **阶段1启动!核心解析器开发中**
+- ✅ 实现OSMD数据解析器(任务1.1)
+  - 完整的parse()方法
+  - 支持两种解析路径
+  - 30个单元测试通过
+- ✅ 实现时间计算器(任务1.2)
+  - 计算音符绝对时间
+  - 支持变速、弱起、节拍器
+  - 23个单元测试通过
+- ✅ 集成测试解析器(任务1.3)
+  - 使用真实MusicXML测试
+  - 29个集成测试通过
+- ✅ **阶段1全部完成!核心解析器开发完成**
+- ✅ 实现小节布局计算器(任务2.1)
+  - 固定时间比例算法
+  - 31个单元测试通过
+- ✅ 实现多声部对齐(任务2.2)
+  - 支持max/min/avg三种对齐策略
+  - 31个单元测试通过
+- 📝 下一步:任务2.3 - 实现行布局和自动换行
 
 ---
 
@@ -258,6 +579,6 @@ docs/jianpu-renderer/
 
 ---
 
-**最后更新:** 2026-01-29 20:00  
+**最后更新:** 2026-01-30 02:30  
 **更新人:** 开发团队  
-**版本:** v1.1(已调整为10周计划
+**版本:** v2.0(阶段2进行中,任务2.2完成,238个测试通过

+ 765 - 22
docs/jianpu-renderer/04-MUSICXML_MAPPING.md

@@ -1,33 +1,776 @@
 # MusicXML元素映射规范
 
-> **文档状态:** ⏸️ 待完成(阶段0.5任务0.5.1的产出)  
+> **文档状态:** ✅ 已完成  
 > **创建日期:** 2026-01-29  
-> **用途:** 定义MusicXML标签到简谱的完整映射规则
+> **用途:** 定义MusicXML标签到简谱的完整映射规则  
+> **适用于:** 简谱渲染引擎 - 解析器模块
 
 ---
 
-## 📋 待填充内容
+## 目录
 
-这个文档将在**阶段0.5 - 任务0.5.1**中完成,包含:
+1. [Divisions转换规则](#1-divisions转换规则)
+2. [音高元素解析规则](#2-音高元素解析规则)
+3. [时值元素解析规则](#3-时值元素解析规则)
+4. [特殊元素处理规则](#4-特殊元素处理规则)
+5. [修饰符处理规则](#5-修饰符处理规则)
+6. [映射表总览](#6-映射表总览)
 
-1. **Divisions转换规则**
-   - divisions的定义和作用
-   - duration到实际时值的转换公式
-   - 不同divisions值的处理
-   
-2. **音高元素解析规则**
-   - step → 简谱数字映射
-   - octave → 高低音点计算
-   - alter → 升降号处理
-   
-3. **时值元素解析规则**
-   - type → 音符基准类型
-   - dot → 附点时值计算
-   - duration → 实际时值
-   
-4. **特殊元素处理规则**
-   - grace, rest, chord, tuplet, tie等
+---
+
+## 1. Divisions转换规则
+
+### 1.1 什么是Divisions
+
+`<divisions>` 是MusicXML中最重要的元素之一,它定义了**每个四分音符包含多少个基本时值单位**。
+
+```xml
+<attributes>
+  <divisions>256</divisions>  <!-- 1个四分音符 = 256个divisions -->
+</attributes>
+```
+
+### 1.2 核心转换公式
+
+```typescript
+/**
+ * 将MusicXML的duration转换为实际时值(以四分音符为单位)
+ * @param duration MusicXML中的duration值
+ * @param divisions 当前小节的divisions值
+ * @returns 实际时值(1.0 = 四分音符)
+ */
+function toRealValue(duration: number, divisions: number): number {
+  return duration / divisions;
+}
+```
+
+### 1.3 常见Divisions值对照表
+
+| divisions值 | 四分音符 | 八分音符 | 十六分音符 | 二分音符 | 全音符 | 附点四分 |
+|------------|---------|---------|-----------|---------|-------|---------|
+| 1 | 1 | 0.5 | 0.25 | 2 | 4 | 1.5 |
+| 256 | 256 | 128 | 64 | 512 | 1024 | 384 |
+| 480 | 480 | 240 | 120 | 960 | 1920 | 720 |
+| 960 | 960 | 480 | 240 | 1920 | 3840 | 1440 |
+
+### 1.4 异常情况处理
+
+| 情况 | 处理方式 | 代码示例 |
+|------|---------|---------|
+| divisions = 0 | 抛出错误 | `throw new Error('divisions不能为0')` |
+| divisions = null/undefined | 使用默认值256 | `divisions = divisions ?? 256` |
+| divisions为负数 | 取绝对值并警告 | `divisions = Math.abs(divisions)` |
+| duration为负数 | 取绝对值并警告 | `duration = Math.abs(duration)` |
+
+### 1.5 实现代码模板
+
+```typescript
+class DivisionsHandler {
+  private currentDivisions: number = 256;
+  
+  /**
+   * 更新当前divisions值
+   */
+  setDivisions(divisions: number): void {
+    if (divisions === 0) {
+      throw new Error('divisions值不能为0');
+    }
+    if (divisions < 0) {
+      console.warn(`divisions为负数(${divisions}),已取绝对值`);
+      divisions = Math.abs(divisions);
+    }
+    this.currentDivisions = divisions;
+  }
+  
+  /**
+   * 转换duration到实际时值
+   */
+  toRealValue(duration: number): number {
+    if (duration < 0) {
+      console.warn(`duration为负数(${duration}),已取绝对值`);
+      duration = Math.abs(duration);
+    }
+    return duration / this.currentDivisions;
+  }
+  
+  /**
+   * 获取当前divisions值
+   */
+  getDivisions(): number {
+    return this.currentDivisions;
+  }
+}
+```
+
+---
+
+## 2. 音高元素解析规则
+
+### 2.1 Pitch元素结构
+
+```xml
+<pitch>
+  <step>C</step>      <!-- 音名:C D E F G A B -->
+  <alter>1</alter>    <!-- 升降:1=升,-1=降,0或不存在=自然 -->
+  <octave>4</octave>  <!-- 八度:通常0-9,4是中央C所在的八度 -->
+</pitch>
+```
+
+### 2.2 Step到简谱数字的映射
+
+| MusicXML Step | 简谱数字 | 唱名 | 说明 |
+|--------------|---------|------|------|
+| C | 1 | do | 基准音 |
+| D | 2 | re | |
+| E | 3 | mi | |
+| F | 4 | fa | |
+| G | 5 | sol | |
+| A | 6 | la | |
+| B | 7 | si | |
+
+**实现代码:**
+```typescript
+const STEP_TO_JIANPU: Record<string, number> = {
+  'C': 1, 'D': 2, 'E': 3, 'F': 4, 'G': 5, 'A': 6, 'B': 7
+};
+
+function stepToJianpu(step: string): number {
+  const pitch = STEP_TO_JIANPU[step.toUpperCase()];
+  if (pitch === undefined) {
+    throw new Error(`无效的音名: ${step}`);
+  }
+  return pitch;
+}
+```
+
+### 2.3 Octave到高低音点的映射
+
+| MusicXML Octave | 八度偏移 | 简谱表示 | 说明 |
+|----------------|---------|---------|------|
+| 2 | -2 | 两个下点 | 低两个八度 |
+| 3 | -1 | 一个下点 | 低一个八度 |
+| 4 | 0 | 无点 | 中音区(基准) |
+| 5 | +1 | 一个上点 | 高一个八度 |
+| 6 | +2 | 两个上点 | 高两个八度 |
+
+**实现代码:**
+```typescript
+const BASE_OCTAVE = 4;  // 中央C所在的八度
+
+function octaveToOffset(octave: number): number {
+  return octave - BASE_OCTAVE;
+}
+
+// 示例
+octaveToOffset(3);  // -1(低音)
+octaveToOffset(4);  // 0(中音)
+octaveToOffset(5);  // +1(高音)
+```
+
+### 2.4 Alter到升降号的映射
+
+| MusicXML Alter | 简谱符号 | 含义 | 类型 |
+|---------------|---------|------|------|
+| 2 | ×(或##) | 重升 | double-sharp |
+| 1 | # | 升半音 | sharp |
+| 0 | ♮ | 还原 | natural |
+| -1 | ♭ | 降半音 | flat |
+| -2 | ♭♭ | 重降 | double-flat |
+| 不存在 | 无 | 自然音 | none |
+
+**实现代码:**
+```typescript
+type Accidental = 'sharp' | 'flat' | 'natural' | 'double-sharp' | 'double-flat' | null;
+
+function alterToAccidental(alter: number | null): Accidental {
+  if (alter === null || alter === undefined) return null;
+  
+  switch (alter) {
+    case 2: return 'double-sharp';
+    case 1: return 'sharp';
+    case 0: return 'natural';
+    case -1: return 'flat';
+    case -2: return 'double-flat';
+    default:
+      console.warn(`未知的alter值: ${alter}`);
+      return null;
+  }
+}
+```
+
+### 2.5 完整音高解析示例
+
+```xml
+<!-- 示例1:中央C -->
+<pitch>
+  <step>C</step>
+  <octave>4</octave>
+</pitch>
+<!-- 结果:pitch=1, octave=0, accidental=null -->
+
+<!-- 示例2:高音升F -->
+<pitch>
+  <step>F</step>
+  <alter>1</alter>
+  <octave>5</octave>
+</pitch>
+<!-- 结果:pitch=4, octave=1, accidental='sharp' -->
+
+<!-- 示例3:低音降B -->
+<pitch>
+  <step>B</step>
+  <alter>-1</alter>
+  <octave>3</octave>
+</pitch>
+<!-- 结果:pitch=7, octave=-1, accidental='flat' -->
+```
+
+---
+
+## 3. 时值元素解析规则
+
+### 3.1 Type元素(音符类型)
+
+| MusicXML Type | 简谱时值 | 增/减时线 | 说明 |
+|--------------|---------|----------|------|
+| 128th | 0.03125 | 5条减时线 | 一百二十八分音符 |
+| 64th | 0.0625 | 4条减时线 | 六十四分音符 |
+| 32nd | 0.125 | 3条减时线 | 三十二分音符 |
+| 16th | 0.25 | 2条减时线 | 十六分音符 |
+| eighth | 0.5 | 1条减时线 | 八分音符 |
+| quarter | 1.0 | 无 | 四分音符(基准) |
+| half | 2.0 | 1条增时线 | 二分音符 |
+| whole | 4.0 | 3条增时线 | 全音符 |
+| breve | 8.0 | 7条增时线 | 二全音符 |
+
+**实现代码:**
+```typescript
+const TYPE_TO_DURATION: Record<string, number> = {
+  '128th': 0.03125,
+  '64th': 0.0625,
+  '32nd': 0.125,
+  '16th': 0.25,
+  'eighth': 0.5,
+  'quarter': 1.0,
+  'half': 2.0,
+  'whole': 4.0,
+  'breve': 8.0,
+};
+
+function typeToBaseDuration(type: string): number {
+  const duration = TYPE_TO_DURATION[type.toLowerCase()];
+  if (duration === undefined) {
+    console.warn(`未知的音符类型: ${type},使用四分音符`);
+    return 1.0;
+  }
+  return duration;
+}
+```
+
+### 3.2 Dot元素(附点)
+
+附点会增加原时值的特定比例:
+
+| 附点数量 | 时值倍数 | 计算公式 | 示例(四分音符) |
+|---------|---------|---------|----------------|
+| 0 | 1.0 | base | 1.0 |
+| 1 | 1.5 | base × 1.5 | 1.5 |
+| 2 | 1.75 | base × 1.75 | 1.75 |
+| 3 | 1.875 | base × 1.875 | 1.875 |
+
+**计算公式:**
+```typescript
+/**
+ * 计算附点后的时值
+ * @param baseDuration 基准时值
+ * @param dots 附点数量
+ * @returns 附点后的时值
+ */
+function applyDots(baseDuration: number, dots: number): number {
+  let multiplier = 1.0;
+  let addition = 0.5;
+  
+  for (let i = 0; i < dots; i++) {
+    multiplier += addition;
+    addition /= 2;
+  }
+  
+  return baseDuration * multiplier;
+}
+
+// 示例
+applyDots(1.0, 0);  // 1.0(四分音符)
+applyDots(1.0, 1);  // 1.5(附点四分音符)
+applyDots(1.0, 2);  // 1.75(双附点四分音符)
+applyDots(2.0, 1);  // 3.0(附点二分音符)
+```
+
+### 3.3 Duration元素的验证
+
+**重要:** 我们应该优先使用 `duration / divisions` 计算实际时值,而不是依赖 `type`。`type` 可以用于验证。
+
+```typescript
+/**
+ * 解析并验证时值
+ */
+function parseDuration(
+  duration: number,
+  divisions: number,
+  type: string | null,
+  dots: number
+): { realValue: number; isValid: boolean } {
+  // 从duration计算实际时值
+  const realValue = duration / divisions;
+  
+  // 如果有type,用于验证
+  if (type) {
+    const expectedFromType = applyDots(typeToBaseDuration(type), dots);
+    const tolerance = 0.001;
+    
+    if (Math.abs(realValue - expectedFromType) > tolerance) {
+      console.warn(
+        `时值不一致: duration计算=${realValue}, type计算=${expectedFromType}`
+      );
+    }
+  }
+  
+  return { realValue, isValid: true };
+}
+```
+
+### 3.4 时值到增/减时线的转换
+
+```typescript
+/**
+ * 计算增时线数量
+ * 规则:只有时值 >= 1.0(四分音符及以上)才有增时线
+ * 公式:Math.floor(realValue) - 1
+ */
+function calcExtensionLines(realValue: number): number {
+  if (realValue < 1.0) return 0;
+  return Math.floor(realValue) - 1;
+}
+
+/**
+ * 计算减时线数量
+ * 规则:只有时值 < 1.0(短于四分音符)才有减时线
+ * 公式:Math.round(Math.log2(1 / realValue))
+ */
+function calcUnderlines(realValue: number): number {
+  if (realValue >= 1.0) return 0;
+  return Math.round(Math.log2(1 / realValue));
+}
+
+// 验证表
+// realValue=4.0(全音符)→ 3条增时线
+// realValue=2.0(二分音符)→ 1条增时线
+// realValue=1.5(附点四分音符)→ 0条增时线(有附点)
+// realValue=1.0(四分音符)→ 0条增时线
+// realValue=0.5(八分音符)→ 1条减时线
+// realValue=0.25(十六分音符)→ 2条减时线
+// realValue=0.125(三十二分音符)→ 3条减时线
+```
+
+---
+
+## 4. 特殊元素处理规则
+
+### 4.1 Rest(休止符)
+
+```xml
+<note>
+  <rest/>
+  <duration>256</duration>
+  <type>quarter</type>
+</note>
+```
+
+**映射规则:**
+| 属性 | 值 | 说明 |
+|------|-----|------|
+| pitch | 0 | 休止符用0表示 |
+| isRest | true | 标记为休止符 |
+| duration | 正常计算 | 休止符也有时值 |
+| 增时线 | 不绘制 | 休止符不绘制增时线 |
+| 减时线 | 可以绘制 | 八分休止符等有减时线 |
+
+**实现代码:**
+```typescript
+function parseNote(noteElement: Element): JianpuNote {
+  const isRest = noteElement.querySelector('rest') !== null;
+  
+  if (isRest) {
+    return {
+      pitch: 0,
+      octave: 0,
+      isRest: true,
+      // ... 其他属性正常解析
+    };
+  }
+  // ... 正常音符解析
+}
+```
+
+### 4.2 Grace(装饰音)
+
+```xml
+<note>
+  <grace/>  <!-- 或 <grace slash="yes"/> -->
+  <pitch>
+    <step>D</step>
+    <octave>4</octave>
+  </pitch>
+  <type>eighth</type>
+</note>
+```
+
+**映射规则:**
+| 属性 | 值 | 说明 |
+|------|-----|------|
+| duration | 0 | 装饰音不占时值 |
+| isGraceNote | true | 标记为装饰音 |
+| 渲染 | 小字体 | 字体约为正常的60% |
+| 位置 | 主音符前 | 紧贴主音符左侧 |
+
+**实现代码:**
+```typescript
+function parseNote(noteElement: Element): JianpuNote {
+  const isGrace = noteElement.querySelector('grace') !== null;
+  
+  if (isGrace) {
+    return {
+      // 装饰音没有duration元素,时值为0
+      duration: 0,
+      isGraceNote: true,
+      // 音高正常解析
+    };
+  }
+}
+```
+
+### 4.3 Chord(和弦)
+
+```xml
+<!-- 第一个音符(和弦根音) -->
+<note>
+  <pitch><step>C</step><octave>4</octave></pitch>
+  <duration>256</duration>
+  <type>quarter</type>
+</note>
+<!-- 第二个音符(和弦成员) -->
+<note>
+  <chord/>  <!-- 标记为和弦成员 -->
+  <pitch><step>E</step><octave>4</octave></pitch>
+  <duration>256</duration>
+  <type>quarter</type>
+</note>
+<!-- 第三个音符(和弦成员) -->
+<note>
+  <chord/>
+  <pitch><step>G</step><octave>4</octave></pitch>
+  <duration>256</duration>
+  <type>quarter</type>
+</note>
+```
+
+**映射规则:**
+| 属性 | 值 | 说明 |
+|------|-----|------|
+| timestamp | 与前一音符相同 | 和弦成员不增加时间 |
+| X坐标 | 与前一音符相同 | 垂直排列 |
+| chordIndex | 0, 1, 2, ... | 在和弦中的位置 |
+
+**实现代码:**
+```typescript
+let lastTimestamp = 0;
+
+function parseNote(noteElement: Element): JianpuNote {
+  const isChord = noteElement.querySelector('chord') !== null;
+  
+  if (isChord) {
+    // 和弦成员使用与前一音符相同的时间戳
+    return {
+      timestamp: lastTimestamp,
+      isChordMember: true,
+    };
+  } else {
+    // 更新时间戳
+    const duration = parseDuration(noteElement);
+    lastTimestamp += duration;
+    return {
+      timestamp: lastTimestamp - duration,
+    };
+  }
+}
+```
+
+### 4.4 Tuplet(连音符)
+
+```xml
+<note>
+  <pitch><step>C</step><octave>4</octave></pitch>
+  <duration>170</duration>  <!-- 注意:不是标准时值! -->
+  <type>eighth</type>
+  <time-modification>
+    <actual-notes>3</actual-notes>  <!-- 实际3个音 -->
+    <normal-notes>2</normal-notes>  <!-- 占用2个八分音符的时间 -->
+  </time-modification>
+</note>
+```
+
+**映射规则:**
+| 属性 | 值 | 说明 |
+|------|-----|------|
+| duration | 直接使用XML的duration | 已经是调整后的值 |
+| isPartOfTuplet | true | 标记为连音符 |
+| tupletNumber | 3, 5, 6, ... | 连音符类型(三连音、五连音等) |
+| 减时线 | 正常计算 | 基于实际时值 |
+| 标记 | 在音符上方显示数字 | 如"3"表示三连音 |
+
+**计算说明:**
+```typescript
+// 三连音八分音符(divisions=256)
+// normal: 2个八分音符 = 256 divisions
+// actual: 3个音
+// 每个音的duration = 256 / 3 ≈ 85.33,但XML通常会四舍五入
+
+// 关键:直接使用XML中的duration值,不要自己计算
+const realValue = duration / divisions;  // 这就是正确的时值
+```
+
+### 4.5 Tie(延音线)
+
+```xml
+<!-- 延音线开始 -->
+<note>
+  <pitch><step>C</step><octave>4</octave></pitch>
+  <duration>512</duration>
+  <tie type="start"/>
+</note>
+
+<!-- 延音线结束 -->
+<note>
+  <pitch><step>C</step><octave>4</octave></pitch>
+  <duration>512</duration>
+  <tie type="stop"/>
+</note>
+```
+
+**映射规则:**
+| 属性 | 值 | 说明 |
+|------|-----|------|
+| tieStart | true/false | 是否是延音线开始 |
+| tieStop | true/false | 是否是延音线结束 |
+| 渲染 | 弧线连接 | 在两个音符之间画弧线 |
+| 播放 | 合并时值 | 作为一个音符播放 |
+
+**实现代码:**
+```typescript
+function parseNote(noteElement: Element): JianpuNote {
+  const ties = noteElement.querySelectorAll('tie');
+  let tieStart = false;
+  let tieStop = false;
+  
+  ties.forEach(tie => {
+    const type = tie.getAttribute('type');
+    if (type === 'start') tieStart = true;
+    if (type === 'stop') tieStop = true;
+  });
+  
+  return {
+    tieStart,
+    tieStop,
+  };
+}
+```
+
+### 4.6 Backup和Forward
+
+```xml
+<!-- backup:时间倒退,用于多声部 -->
+<backup>
+  <duration>1024</duration>  <!-- 倒退1024个divisions -->
+</backup>
+
+<!-- forward:时间前进,用于跳过空白 -->
+<forward>
+  <duration>256</duration>  <!-- 前进256个divisions -->
+</forward>
+```
+
+**映射规则:**
+| 元素 | 作用 | 处理方式 |
+|------|------|---------|
+| backup | 回退时间指针 | `currentTime -= duration/divisions` |
+| forward | 前进时间指针 | `currentTime += duration/divisions` |
+
+---
+
+## 5. 修饰符处理规则
+
+### 5.1 Accidental(临时升降号)
+
+```xml
+<note>
+  <pitch>
+    <step>F</step>
+    <alter>1</alter>
+    <octave>4</octave>
+  </pitch>
+  <accidental>sharp</accidental>  <!-- 显式显示升号 -->
+</note>
+```
+
+**accidental类型映射:**
+| MusicXML accidental | 显示符号 | 说明 |
+|---------------------|---------|------|
+| sharp | # | 升号 |
+| flat | ♭ | 降号 |
+| natural | ♮ | 还原号 |
+| double-sharp | × | 重升 |
+| double-flat | 𝄫 | 重降 |
+| sharp-sharp | ## | 重升(另一种写法) |
+| flat-flat | ♭♭ | 重降(另一种写法) |
+
+**处理规则:**
+- 如果有 `<accidental>` 元素,则**必须显示**升降号
+- 如果只有 `<alter>` 没有 `<accidental>`,根据调号决定是否显示
+
+### 5.2 Articulations(演奏技法)
+
+```xml
+<notations>
+  <articulations>
+    <staccato/>      <!-- 顿音 -->
+    <accent/>        <!-- 重音 -->
+    <tenuto/>        <!-- 保持音 -->
+    <staccatissimo/> <!-- 极短音 -->
+  </articulations>
+</notations>
+```
+
+**映射规则:**
+| MusicXML | 简谱符号 | 位置 | 说明 |
+|----------|---------|------|------|
+| staccato | · | 音符上方 | 顿音点 |
+| accent | > | 音符上方 | 重音记号 |
+| tenuto | - | 音符上方 | 保持音线 |
+| staccatissimo | ▼ | 音符上方 | 极短音 |
+
+### 5.3 Ornaments(装饰音记号)
+
+```xml
+<notations>
+  <ornaments>
+    <trill-mark/>           <!-- 颤音 -->
+    <mordent/>              <!-- 波音 -->
+    <inverted-mordent/>     <!-- 逆波音 -->
+    <turn/>                 <!-- 回音 -->
+  </ornaments>
+</notations>
+```
+
+**映射规则:**
+| MusicXML | 简谱符号 | 位置 |
+|----------|---------|------|
+| trill-mark | tr~ | 音符上方 |
+| mordent | ∽ | 音符上方 |
+| inverted-mordent | ∿ | 音符上方 |
+| turn | ∞ | 音符上方 |
+
+### 5.4 Dynamics(力度记号)
+
+```xml
+<direction>
+  <direction-type>
+    <dynamics>
+      <pp/>   <!-- 很弱 -->
+      <p/>    <!-- 弱 -->
+      <mp/>   <!-- 中弱 -->
+      <mf/>   <!-- 中强 -->
+      <f/>    <!-- 强 -->
+      <ff/>   <!-- 很强 -->
+    </dynamics>
+  </direction-type>
+</direction>
+```
+
+**映射规则:**
+| MusicXML | 显示文本 | 力度值(0-127) |
+|----------|---------|--------------|
+| ppp | ppp | 20 |
+| pp | pp | 40 |
+| p | p | 60 |
+| mp | mp | 70 |
+| mf | mf | 80 |
+| f | f | 100 |
+| ff | ff | 110 |
+| fff | fff | 120 |
+
+---
+
+## 6. 映射表总览
+
+### 6.1 完整元素映射表
+
+| MusicXML元素 | 解析目标 | 必需 | 默认值 | 备注 |
+|-------------|---------|------|-------|------|
+| `<divisions>` | divisions | ✅ | 256 | 时值计算基础 |
+| `<pitch>/<step>` | pitch | ✅ | - | 1-7 |
+| `<pitch>/<octave>` | octave | ✅ | 4 | 基准是4 |
+| `<pitch>/<alter>` | accidental | ❌ | null | 升降号 |
+| `<duration>` | duration | ✅ | - | 需除以divisions |
+| `<type>` | type | ❌ | quarter | 可用于验证 |
+| `<dot>` | dots | ❌ | 0 | 附点数量 |
+| `<rest>` | isRest | ❌ | false | 休止符标记 |
+| `<grace>` | isGraceNote | ❌ | false | 装饰音标记 |
+| `<chord>` | isChordMember | ❌ | false | 和弦成员 |
+| `<tie>` | tieStart/tieStop | ❌ | false | 延音线 |
+| `<time-modification>` | tuplet信息 | ❌ | null | 连音符 |
+| `<accidental>` | showAccidental | ❌ | false | 是否显示升降号 |
+| `<lyric>` | lyrics | ❌ | [] | 歌词数组 |
+
+### 6.2 时值计算快速参考
+
+```typescript
+// 核心公式
+const realValue = duration / divisions;
+
+// 增时线
+const extensionLines = realValue >= 1 ? Math.floor(realValue) - 1 : 0;
+
+// 减时线
+const underlines = realValue < 1 ? Math.round(Math.log2(1 / realValue)) : 0;
+
+// 附点
+const dottedValue = baseDuration * (dots === 0 ? 1 : dots === 1 ? 1.5 : 1.75);
+```
+
+### 6.3 音高转换快速参考
+
+```typescript
+// 音名转简谱
+const pitch = { C:1, D:2, E:3, F:4, G:5, A:6, B:7 }[step];
+
+// 八度偏移
+const octaveOffset = octave - 4;
+
+// 升降号
+const accidental = alter === 1 ? 'sharp' : alter === -1 ? 'flat' : null;
+```
+
+---
+
+## 验收标准检查
+
+- [x] 文档包含完整的映射表(超过20个元素)
+- [x] 每个元素都有示例XML
+- [x] 每个元素都有转换公式/代码
+- [x] 包含边界情况处理方案
+- [x] 包含实现代码模板
 
 ---
 
-**下一步:** 完成任务0.3后,开始编写此文档
+**文档版本:** v1.0  
+**最后更新:** 2026-01-29  
+**维护者:** 简谱渲染引擎开发团队

+ 524 - 20
docs/jianpu-renderer/05-VEXFLOW_COMPAT.md

@@ -1,32 +1,536 @@
 # VexFlow兼容性规范
 
-> **文档状态:** ⏸️ 待完成(阶段0.5任务0.5.3的产出)  
+> **文档状态:** ✅ 已完成  
 > **创建日期:** 2026-01-29  
 > **用途:** 定义与VexFlow/OSMD的完整兼容规范
 
 ---
 
-## 📋 待填充内容
+## 📋 概述
 
-这个文档将在**阶段0.5 - 任务0.5.3**中完成,包含:
+本文档详细定义新简谱引擎与现有OSMD/VexFlow系统的兼容性规范。为确保业务功能(播放、评测、选段等)正常工作,新引擎必须生成兼容的数据结构。
 
-1. **DOM结构规范**
-   - 元素ID命名规则
-   - CSS类名完整清单
-   - 元素层级结构
-   
-2. **state.times字段完整清单** (核心!)
-   - 从业务代码提取的所有字段(30-40个)
-   - 每个字段的类型、来源、用途
-   
-3. **cursor接口规范**
-   - Iterator对象必需方法
-   - 行为定义
-   
-4. **GraphicSheet接口规范**
-   - MeasureList结构
-   - 必需字段
+---
+
+## 1. DOM结构规范
+
+### 1.1 元素ID命名规则
+
+```
+音符元素ID格式: vf-{唯一标识符}
+示例: vf-auto12345, vf-n0-0-0
+```
+
+| 元素类型 | ID格式 | 示例 |
+|---------|--------|------|
+| 音符 | `vf-{id}` | `vf-auto12345` |
+| 音符符干 | `vf-{id}-stem` | `vf-auto12345-stem` |
+| 音符连线 | `vf-{id}-lines` | `vf-auto12345-lines` |
+| 小节 | 带 `data-num` 属性 | `<g class="vf-measure" data-num="1">` |
+
+### 1.2 CSS类名完整清单
+
+#### 音符相关
+| 类名 | 用途 | 说明 |
+|------|------|------|
+| `.vf-stavenote` | 普通音符 | VexFlow音符基类 |
+| `.vf-numbered-note-head` | 简谱音符头 | 数字音符的头部 |
+| `.noteActive` | 激活的音符 | 播放时高亮显示 |
+| `.voiceActive` | 激活的声部 | 整个声部高亮 |
+| `.rectActive` | 激活的矩形 | 延长线"-"高亮 |
+
+#### 歌词相关
+| 类名 | 用途 | 说明 |
+|------|------|------|
+| `.vf-lyric` | 歌词元素 | 所有歌词的基类 |
+| `.lyric{noteId}` | 特定音符歌词 | 关联到特定音符 |
+| `.lyricActive` | 激活的歌词 | 播放时高亮显示 |
+
+#### 小节相关
+| 类名 | 用途 | 说明 |
+|------|------|------|
+| `.vf-measure` | 小节容器 | 每个小节的容器 |
+| `.vf-custom-bg` | 小节背景 | 可通过fill属性设置颜色 |
+| `.measureIndex_{n}` | 小节索引 | n为小节编号 |
+
+### 1.3 元素层级结构
+
+```
+<svg id="osmdSvgPage">
+  └── <g class="vf-system">           // 谱表系统
+       └── <g class="vf-stave">        // 单个谱表
+            └── <g class="vf-measure" data-num="1">  // 小节
+                 ├── <rect class="vf-custom-bg"/>     // 小节背景
+                 ├── <g class="vf-voices">            // 声部容器
+                 │    └── <g id="vf-auto123" class="vf-stavenote">  // 音符
+                 │         ├── <g class="vf-numbered-note-head"/>   // 音符头
+                 │         ├── <g id="vf-auto123-stem"/>            // 符干
+                 │         └── <g id="vf-auto123-lines"/>           // 连线
+                 └── <g class="vf-lyric lyric0">      // 歌词
+```
+
+---
+
+## 2. state.times 字段完整清单 ⭐核心
+
+`state.times` 是业务功能的核心数据结构,包含所有音符的时间和位置信息。
+
+### 2.1 基础标识字段
+
+| 字段名 | 类型 | 说明 | 示例 |
+|--------|------|------|------|
+| `i` | number | 音符在times数组中的索引 | `0, 1, 2...` |
+| `id` | string | SVG元素ID(不含vf-前缀) | `"auto12345"` |
+| `noteId` | number | 音符唯一标识(NoteToGraphicalNoteObjectId) | `0, 1, 2...` |
+
+### 2.2 时间信息字段
+
+| 字段名 | 类型 | 说明 | 单位 |
+|--------|------|------|------|
+| `time` | number | 音符开始播放时间(含前奏) | 秒 |
+| `endtime` | number | 音符结束播放时间 | 秒 |
+| `relativeTime` | number | 相对时间(不含前奏) | 秒 |
+| `relaEndtime` | number | 相对结束时间 | 秒 |
+| `duration` | number | 持续时长(endtime - time) | 秒 |
+| `fixtime` | number | 弱起/节拍器补充时间 | 秒 |
+| `difftime` | number | 弱起时间差 | 秒 |
+| `noteLengthTime` | number | 音符时值时长 | 秒 |
+
+### 2.3 小节信息字段
+
+| 字段名 | 类型 | 说明 | 示例 |
+|--------|------|------|------|
+| `MeasureNumberXML` | number | XML小节编号(从1开始) | `1, 2, 3...` |
+| `measureListIndex` | number | 小节在列表中的索引(从0开始) | `0, 1, 2...` |
+| `measureOpenIndex` | number | 打开的小节索引 | `0, 1, 2...` |
+| `measureLength` | number | 小节时值长度 | 秒 |
+| `relaMeasureLength` | number | 相对小节时值长度 | 秒 |
+| `measures` | any[] | 同小节的所有音符引用 | `[note1, note2]` |
+
+### 2.4 速度信息字段
+
+| 字段名 | 类型 | 说明 | 示例 |
+|--------|------|------|------|
+| `speed` | number | 当前速度 | `120` |
+| `beatSpeed` | number | 节拍速度 | `120` |
+| `measureSpeed` | number | 小节速度(tempoInBPM) | `100` |
+| `tempoInBPM` | number | BPM速度 | `120` |
+| `speedBeatUnit` | string | 速度单位 | `"1/4"` |
+| `stepSpeeds` | number[] | 渐变速度数组 | `[100, 105, 110]` |
+
+### 2.5 音高和频率字段
+
+| 字段名 | 类型 | 说明 | 示例 |
+|--------|------|------|------|
+| `note` | number | MIDI音高(halfTone + 12) | `60` |
+| `halfTone` | number | 半音值 | `48` |
+| `frequency` | number | 音频频率(Hz) | `440` |
+| `nextFrequency` | number | 下一音符频率 | `493.88` |
+| `prevFrequency` | number | 上一音符频率 | `392` |
+| `frequencyList` | number[] | 和弦音符频率列表 | `[440, 554, 659]` |
+| `realKey` | number | 实际音高(用于指法) | `48` |
+| `fixedKey` | number | 固定调偏移 | `0` |
+
+### 2.6 元素引用字段
+
+| 字段名 | 类型 | 说明 |
+|--------|------|------|
+| `noteElement` | any | OSMD Note对象引用 |
+| `svgElement` | any | VexFlow SVG元素引用(含attrs.id) |
+| `stave` | any | VexFlow Stave对象引用 |
+| `firstVerticalMeasure` | any | 第一个垂直小节引用 |
+| `osdmContext` | any | OSMD上下文对象 |
+
+### 2.7 渲染和状态字段
+
+| 字段名 | 类型 | 说明 | 示例 |
+|--------|------|------|------|
+| `si` | number | 音符在小节内的索引 | `0, 1, 2...` |
+| `noteLength` | number | 小节内音符数量 | `4` |
+| `maxNoteNum` | number | 最大音符数量 | `8` |
+| `realValue` | number | 时间戳实际值 | `0.5` |
+| `_noteLength` | number | 原始音符时值 | `0.25` |
+| `octaveOffset` | number | 八度偏移 | `0, 1, -1` |
+| `trackIndex` | number | 分轨索引 | `0` |
+
+### 2.8 特殊标记字段
+
+| 字段名 | 类型 | 说明 | 示例 |
+|--------|------|------|------|
+| `isRestFlag` | boolean | 是否休止符 | `true/false` |
+| `isStaccato` | boolean | 是否顿音 | `true/false` |
+| `hasGraceNote` | boolean | 是否有装饰音 | `true/false` |
+| `multipleRestMeasures` | number | 合并休止小节索引 | `1, 2, 3` |
+| `totalMultipleRestMeasures` | number | 总合并休止小节数 | `4` |
+| `repeatIdx` | number | 循环播放次数索引 | `0, 1, 2` |
+
+### 2.9 歌词字段
+
+| 字段名 | 类型 | 说明 | 示例 |
+|--------|------|------|------|
+| `formatLyricsEntries` | string[] | 格式化后的歌词数组 | `["你", "好"]` |
+
+### 2.10 位置信息字段
+
+| 字段名 | 类型 | 说明 |
+|--------|------|------|
+| `bbox` | object | 边界框信息 |
+| `bbox.x` | number | X坐标 |
+| `bbox.y` | number | Y坐标 |
+| `bbox.width` | number | 宽度 |
+| `bbox.height` | number | 高度 |
+| `bbox.left` | number | 左边距(用于简谱光标定位) |
+
+### 2.11 唱名相关字段(evxml)
+
+| 字段名 | 类型 | 说明 |
+|--------|------|------|
+| `xmlNoteTime` | number | XML音符开始时间 |
+| `xmlNoteEndTime` | number | XML音符结束时间 |
+| `xmlMp3BeatFixTime` | number | XML节拍器时间 |
+| `notBeatFixtime` | number | 不含节拍器的fixtime |
+| `notBeatTime` | number | 不含节拍器的开始时间 |
+| `notBeatEndTime` | number | 不含节拍器的结束时间 |
+
+---
+
+## 3. cursor 接口规范
+
+OSMD的cursor用于遍历和定位音符。
+
+### 3.1 必需属性
+
+```typescript
+interface OSMDCursor {
+  /** 迭代器对象 */
+  Iterator: CursorIterator;
+  
+  /** 当前光标元素 */
+  cursorElement: HTMLElement;
+  
+  /** 图形音符ID */
+  noteGraphicalId: string;
+}
+```
+
+### 3.2 必需方法
+
+```typescript
+interface OSMDCursor {
+  /** 重置光标到起始位置 */
+  reset(): void;
+  
+  /** 移动到下一个音符 */
+  next(): void;
+  
+  /** 显示光标 */
+  show(): void;
+  
+  /** 隐藏光标 */
+  hide(): void;
+}
+```
+
+### 3.3 Iterator对象规范
+
+```typescript
+interface CursorIterator {
+  /** 是否到达末尾 */
+  EndReached: boolean;
+  
+  /** 当前时间戳 */
+  currentTimeStamp: {
+    RealValue: number;
+    realValue: number;
+  };
+  
+  /** 当前声部条目 */
+  currentVoiceEntries: VoiceEntry[];
+  CurrentVoiceEntries: VoiceEntry[];
+  
+  /** 当前小节 */
+  currentMeasure: Measure;
+  currentMeasureIndex: number;
+  
+  /** 移动到下一个可见声部条目 */
+  moveToNextVisibleVoiceEntry(skipGrace: boolean): void;
+  
+  /** 跳转标记 */
+  backJumpOccurred: boolean;
+  forwardJumpOccurred: boolean;
+  repeatIdx: number;
+}
+```
+
+---
+
+## 4. GraphicSheet 接口规范
+
+GraphicSheet包含渲染后的图形化乐谱数据。
+
+### 4.1 MeasureList结构
+
+```typescript
+interface GraphicSheet {
+  /** 小节列表(二维数组:[小节索引][声部索引]) */
+  MeasureList: GraphicalMeasure[][];
+}
+
+interface GraphicalMeasure {
+  /** 小节编号(XML中的number) */
+  MeasureNumber: number;
+  
+  /** 父源小节 */
+  parentSourceMeasure: SourceMeasure;
+  
+  /** VexFlow声部 */
+  vfVoices: {
+    [voiceId: string]: {
+      tickables: VexFlowNote[];
+    };
+  };
+  
+  /** VexFlow谱表 */
+  stave: VexFlowStave;
+  
+  /** 谱表条目 */
+  staffEntries: StaffEntry[];
+}
+```
+
+### 4.2 SourceMeasure结构
+
+```typescript
+interface SourceMeasure {
+  /** 小节编号(XML) */
+  MeasureNumberXML: number;
+  
+  /** 小节索引 */
+  measureListIndex: number;
+  
+  /** 速度(BPM) */
+  tempoInBPM: number;
+  
+  /** 拍号 */
+  ActiveTimeSignature: {
+    numerator: number;
+    denominator: number;
+  };
+  
+  /** 时值 */
+  Duration: Fraction;
+  
+  /** 垂直小节列表 */
+  verticalMeasureList: GraphicalMeasure[];
+  
+  /** 速度表达式 */
+  TempoExpressions: TempoExpression[];
+  
+  /** 合并休止小节数 */
+  multipleRestMeasures?: number;
+  
+  /** 反复指令 */
+  lastRepetitionInstructions: RepetitionInstruction[];
+}
+```
+
+---
+
+## 5. 音符元素属性规范
+
+### 5.1 VexFlow Note属性
+
+```typescript
+interface VexFlowNote {
+  /** 属性对象 */
+  attrs: {
+    id: string;      // 唯一ID
+    type: string;    // 类型:"StaveNote" | "GhostNote"
+  };
+  
+  /** 修饰符(装饰音组等) */
+  modifiers: NoteModifier[];
+}
+
+interface NoteModifier {
+  attrs: {
+    type: string;    // "GraceNoteGroup" 等
+  };
+}
+```
+
+### 5.2 OSMD Note属性
+
+```typescript
+interface OSMDNote {
+  /** 音高信息 */
+  pitch: {
+    frequency: number;
+    nextFrequency: number;
+    prevFrequency: number;
+  };
+  
+  /** 半音值 */
+  halfTone: number;
+  
+  /** 时值 */
+  length: {
+    realValue: number;
+    numerator: number;
+    denominator: number;
+    wholeValue: number;
+  };
+  
+  /** 源小节 */
+  sourceMeasure: SourceMeasure;
+  
+  /** 是否休止符 */
+  isRestFlag: boolean;
+  
+  /** 是否装饰音 */
+  IsGraceNote: boolean;
+  
+  /** 是否和弦音 */
+  IsChordNote: boolean;
+  
+  /** 连音信息 */
+  tie: {
+    StartNote: OSMDNote;
+  };
+  
+  /** 声部条目 */
+  voiceEntry: VoiceEntry;
+  
+  /** 父谱表条目 */
+  parentStaffEntry: StaffEntry;
+  
+  /** 图形音符ID */
+  NoteToGraphicalNoteObjectId: number;
+}
+```
 
 ---
 
-**下一步:** 完成任务0.5.2后,开始编写此文档
+## 6. 事件和交互规范
+
+### 6.1 音符点击事件
+
+业务代码通过以下方式处理音符点击:
+
+```typescript
+// 通过state.times查找被点击的音符
+const clickedNote = state.times.find(note => 
+  note.svgElement?.attrs?.id === clickedElementId.replace('vf-', '')
+);
+
+if (clickedNote) {
+  skipNotePlay(clickedNote.i, false, 'manual');
+}
+```
+
+### 6.2 小节选择
+
+```typescript
+// 选段功能使用MeasureNumberXML
+const startNote = state.times.find(n => 
+  n.noteElement.sourceMeasure.MeasureNumberXML === startMeasure
+);
+
+const endNote = state.times.filter(n => 
+  n.noteElement.sourceMeasure.MeasureNumberXML === endMeasure
+).last();
+```
+
+---
+
+## 7. 颜色和样式规范
+
+### 7.1 选中小节背景色
+
+```javascript
+// 播放中的小节
+item.querySelector('.vf-custom-bg')?.setAttribute("fill", "rgba(9,159,255,0.15)");
+
+// 预备小节
+item.querySelector('.vf-custom-bg')?.setAttribute("fill", "rgba(255,193,48,0.15)");
+
+// 未选中小节
+item.querySelector('.vf-custom-bg')?.setAttribute("fill", "transparent");
+```
+
+### 7.2 音符高亮类名
+
+```javascript
+// 添加高亮
+svgEl?.classList.add('noteActive');
+stemEl?.classList.add('noteActive');
+
+// 移除高亮
+svgEl?.classList.remove('noteActive');
+```
+
+---
+
+## 8. 兼容性检查清单
+
+新引擎实现时,需要确保以下兼容性:
+
+### 8.1 必须实现的功能
+
+- [ ] 生成符合规范的state.times数组
+- [ ] 实现cursor.reset()和cursor.next()方法
+- [ ] SVG元素ID遵循 `vf-{id}` 格式
+- [ ] 小节元素包含 `data-num` 属性
+- [ ] 支持 `.noteActive` 等CSS类名切换
+
+### 8.2 必须保留的字段
+
+**关键字段(播放功能):**
+- `time`, `endtime` - 时间定位
+- `i` - 索引定位
+- `MeasureNumberXML` - 小节定位
+
+**关键字段(评测功能):**
+- `frequency`, `frequencyList` - 音高检测
+- `halfTone`, `realKey` - 指法显示
+- `duration` - 时值验证
+
+**关键字段(渲染功能):**
+- `svgElement` - DOM操作
+- `bbox` - 位置计算
+- `noteElement` - 原始数据
+
+---
+
+## 9. 迁移策略
+
+### 9.1 渐进式迁移
+
+1. **第一阶段**:新引擎生成兼容的times数组
+2. **第二阶段**:替换渲染逻辑,保持数据结构不变
+3. **第三阶段**:优化数据结构,提供适配层
+
+### 9.2 适配器模式
+
+```typescript
+class OSMDCompatibilityAdapter {
+  // 将新引擎数据转换为兼容格式
+  generateTimesArray(): CompatibleTimesItem[] {
+    return this.renderer.getAllNotes().map(note => ({
+      i: note.index,
+      id: note.id,
+      time: note.startTime,
+      endtime: note.endTime,
+      // ... 其他兼容字段
+    }));
+  }
+}
+```
+
+---
+
+## 📝 更新日志
+
+| 日期 | 版本 | 说明 |
+|------|------|------|
+| 2026-01-29 | v1.0 | 初始版本,完成全部规范定义 |

+ 999 - 23
docs/jianpu-renderer/06-RENDER_SPEC.md

@@ -1,35 +1,1011 @@
 # 简谱渲染细节规范
 
-> **文档状态:** ⏸️ 待完成(阶段0.5任务0.5.2的产出)  
+> **文档状态:** ✅ 已完成  
 > **创建日期:** 2026-01-29  
-> **用途:** 定义简谱各元素的精确渲染规则
+> **用途:** 定义简谱各元素的精确渲染规则  
+> **适用于:** 简谱渲染引擎 - 绘制模块
 
 ---
 
-## 📋 待填充内容
+## 目录
 
-这个文档将在**阶段0.5 - 任务0.5.2**中完成,包含:
+1. [视觉尺寸标准](#1-视觉尺寸标准)
+2. [音符数字渲染](#2-音符数字渲染)
+3. [增时线渲染规范](#3-增时线渲染规范)
+4. [减时线渲染规范](#4-减时线渲染规范)
+5. [高低音点规范](#5-高低音点规范)
+6. [附点规范](#6-附点规范)
+7. [升降号规范](#7-升降号规范)
+8. [休止符规范](#8-休止符规范)
+9. [小节线规范](#9-小节线规范)
+10. [歌词渲染规范](#10-歌词渲染规范)
+11. [布局规范](#11-布局规范)
 
-1. **增时线渲染规范**(核心难点!)
-   - 数量计算公式
-   - 位置计算(按时值分配空间)
-   - 长度和样式
-   - 多声部对齐要求
-   
-2. **减时线渲染规范**
-   - 数量计算公式
-   - 位置和间距
-   - 连续音符处理
-   
-3. **高低音点规范**
-   - 位置和大小
-   - 多个点的排列
-   
-4. **附点、升降号等其他元素规范**
+---
+
+## 1. 视觉尺寸标准
+
+### 1.1 默认配置常量
+
+```typescript
+const RENDER_CONSTANTS = {
+  // ===== 间距配置 =====
+  /** 四分音符水平间距(像素) */
+  QUARTER_NOTE_SPACING: 50,
+  
+  /** 小节左右内边距(像素) */
+  MEASURE_PADDING: 20,
+  
+  /** 行间距(像素) */
+  SYSTEM_SPACING: 100,
+  
+  /** 声部间距(像素) */
+  VOICE_SPACING: 60,
+  
+  // ===== 字体配置 =====
+  /** 音符数字字体大小(像素) */
+  NOTE_FONT_SIZE: 24,
+  
+  /** 歌词字体大小(像素) */
+  LYRIC_FONT_SIZE: 14,
+  
+  /** 升降号字体大小(像素) */
+  ACCIDENTAL_FONT_SIZE: 12,
+  
+  /** 音符字体 */
+  NOTE_FONT: 'Arial, "Noto Sans SC", sans-serif',
+  
+  /** 歌词字体 */
+  LYRIC_FONT: '"Microsoft YaHei", "Noto Sans SC", sans-serif',
+  
+  // ===== 尺寸配置 =====
+  /** 高低音点半径(像素) */
+  OCTAVE_DOT_RADIUS: 2.5,
+  
+  /** 附点半径(像素) */
+  DURATION_DOT_RADIUS: 2,
+  
+  /** 减时线高度/粗细(像素) */
+  UNDERLINE_HEIGHT: 1.5,
+  
+  /** 减时线间距(像素) */
+  UNDERLINE_GAP: 3,
+  
+  /** 增时线高度/粗细(像素) */
+  EXTENSION_LINE_HEIGHT: 1.5,
+  
+  /** 小节线粗细(像素) */
+  BARLINE_WIDTH: 1,
+  
+  // ===== 颜色配置 =====
+  /** 音符颜色 */
+  NOTE_COLOR: '#000000',
+  
+  /** 线条颜色 */
+  LINE_COLOR: '#000000',
+  
+  /** 歌词颜色 */
+  LYRIC_COLOR: '#333333',
+};
+```
+
+### 1.2 尺寸比例关系
+
+```
+音符区域高度分配:
+┌─────────────────────────────┐
+│    升降号区域 (12px)         │  ← accidental
+├─────────────────────────────┤
+│    高音点区域 (8px)          │  ← octave dots (high)
+├─────────────────────────────┤
+│                             │
+│    音符数字 (24px)           │  ← note number
+│                             │
+├─────────────────────────────┤
+│    低音点区域 (8px)          │  ← octave dots (low)
+├─────────────────────────────┤
+│    减时线区域 (12px)         │  ← underlines
+├─────────────────────────────┤
+│    歌词区域 (20px)           │  ← lyrics
+└─────────────────────────────┘
+
+总高度约:84px(单声部无歌词约64px)
+```
+
+---
+
+## 2. 音符数字渲染
+
+### 2.1 基本渲染规则
+
+```typescript
+interface NoteRenderSpec {
+  /** 数字内容:1-7 或 0(休止符) */
+  text: string;
+  
+  /** 字体大小 */
+  fontSize: number;
+  
+  /** 字体粗细 */
+  fontWeight: 'normal' | 'bold';
+  
+  /** 水平对齐 */
+  textAnchor: 'middle';
+  
+  /** 垂直对齐基线 */
+  dominantBaseline: 'central';
+}
+```
+
+### 2.2 SVG实现
+
+```typescript
+function drawNoteNumber(
+  x: number,
+  y: number,
+  pitch: number,
+  config: RenderConfig
+): SVGTextElement {
+  const text = document.createElementNS(SVG_NS, 'text');
+  
+  text.setAttribute('x', String(x));
+  text.setAttribute('y', String(y));
+  text.setAttribute('font-size', String(config.noteFontSize));
+  text.setAttribute('font-family', config.noteFont);
+  text.setAttribute('text-anchor', 'middle');
+  text.setAttribute('dominant-baseline', 'central');
+  text.setAttribute('fill', config.noteColor);
+  text.setAttribute('class', 'vf-numbered-note-head');
+  
+  text.textContent = String(pitch);
+  
+  return text;
+}
+```
+
+### 2.3 音符宽度计算
+
+```typescript
+/**
+ * 计算音符显示宽度
+ * 基于时值计算,保证固定时间比例
+ */
+function calculateNoteWidth(realValue: number, quarterSpacing: number): number {
+  return realValue * quarterSpacing;
+}
+
+// 示例:
+// 四分音符(1.0) → 50px
+// 八分音符(0.5) → 25px
+// 二分音符(2.0) → 100px
+// 全音符(4.0) → 200px
+```
+
+---
+
+## 3. 增时线渲染规范
+
+### 3.1 核心规则 ⭐⭐⭐
+
+**增时线是简谱渲染的核心难点!**
+
+```
+增时线规则:
+1. 只有时值 >= 1.0(四分音符及以上)才可能有增时线
+2. 数量 = Math.floor(realValue) - 1
+3. 每条增时线代表1个四分音符的时值
+4. 增时线应该按时值均匀分布在占用的空间中
+```
+
+### 3.2 数量计算
+
+| 音符类型 | 时值(realValue) | 增时线数量 | 视觉表示 |
+|---------|----------------|-----------|---------|
+| 四分音符 | 1.0 | 0 | `5` |
+| 附点四分 | 1.5 | 0 | `5·` |
+| 二分音符 | 2.0 | 1 | `5 —` |
+| 附点二分 | 3.0 | 2 | `5· — —` |
+| 全音符 | 4.0 | 3 | `5 — — —` |
+
+```typescript
+function calcExtensionLineCount(realValue: number): number {
+  if (realValue < 1.0) return 0;
+  return Math.floor(realValue) - 1;
+}
+```
+
+### 3.3 位置计算 ⭐⭐⭐
+
+**关键:增时线要按四分音符间距均匀分布!**
+
+```typescript
+interface ExtensionLinePosition {
+  /** 增时线起始X坐标 */
+  x: number;
+  /** 增时线Y坐标(与音符数字基线平齐) */
+  y: number;
+  /** 增时线长度 */
+  width: number;
+}
+
+/**
+ * 计算增时线位置
+ * @param noteX 音符中心X坐标
+ * @param noteY 音符中心Y坐标
+ * @param lineIndex 增时线索引(0开始)
+ * @param quarterSpacing 四分音符间距
+ */
+function calcExtensionLinePosition(
+  noteX: number,
+  noteY: number,
+  lineIndex: number,
+  quarterSpacing: number
+): ExtensionLinePosition {
+  // 每条增时线占据1个四分音符的空间
+  // 第1条增时线在音符后的第1个四分音符位置
+  // 第2条增时线在音符后的第2个四分音符位置...
+  
+  const lineStartX = noteX + (lineIndex + 1) * quarterSpacing;
+  const lineWidth = quarterSpacing * 0.7;  // 占空间的70%,居中
+  const lineX = lineStartX - lineWidth / 2;
+  
+  return {
+    x: lineX,
+    y: noteY,
+    width: lineWidth,
+  };
+}
+```
+
+### 3.4 视觉示意图
+
+```
+二分音符(2拍):
+┌─────────┬─────────┐
+│    5    │    —    │
+│  第1拍   │  第2拍   │
+└─────────┴─────────┘
+  音符       增时线
+
+全音符(4拍):
+┌─────────┬─────────┬─────────┬─────────┐
+│    5    │    —    │    —    │    —    │
+│  第1拍   │  第2拍   │  第3拍   │  第4拍   │
+└─────────┴─────────┴─────────┴─────────┘
+  音符       增时线1    增时线2    增时线3
+```
+
+### 3.5 增时线样式
+
+```typescript
+function drawExtensionLine(
+  x: number,
+  y: number,
+  width: number,
+  config: RenderConfig
+): SVGRectElement {
+  const line = document.createElementNS(SVG_NS, 'rect');
+  
+  line.setAttribute('x', String(x));
+  line.setAttribute('y', String(y - config.extensionLineHeight / 2));
+  line.setAttribute('width', String(width));
+  line.setAttribute('height', String(config.extensionLineHeight));
+  line.setAttribute('fill', config.lineColor);
+  line.setAttribute('class', 'vf-extension-line');
+  
+  return line;
+}
+```
+
+### 3.6 附点+增时线组合
+
+```
+附点二分音符(3拍):
+┌───────────────┬─────────┬─────────┐
+│     5·        │    —    │    —    │
+│   1.5拍        │  第2拍   │  第3拍   │
+└───────────────┴─────────┴─────────┘
+  附点四分音符占1.5拍,后面2条增时线各占1拍
+
+计算方式:
+- realValue = 3.0
+- 增时线数量 = Math.floor(3.0) - 1 = 2
+- 第1条增时线位置:noteX + quarterSpacing * 1.5(从附点结束位置开始)
+- 第2条增时线位置:noteX + quarterSpacing * 2.5
+```
+
+### 3.7 多声部场景
+
+```
+多声部时,增时线要与其他声部的音符垂直对齐:
+
+声部1:  5 — — —    (全音符)
+声部2:  1 2 3 4    (四个四分音符)
+        ↑ ↑ ↑ ↑
+        X坐标必须对齐
+
+规则:先进行多声部对齐计算,再绘制增时线
+```
+
+---
+
+## 4. 减时线渲染规范
+
+### 4.1 数量计算
+
+| 音符类型 | 时值(realValue) | 减时线数量 | 视觉表示 |
+|---------|----------------|-----------|---------|
+| 四分音符 | 1.0 | 0 | `5` |
+| 八分音符 | 0.5 | 1 | `5` + 1条线 |
+| 十六分音符 | 0.25 | 2 | `5` + 2条线 |
+| 三十二分音符 | 0.125 | 3 | `5` + 3条线 |
+
+```typescript
+function calcUnderlineCount(realValue: number): number {
+  if (realValue >= 1.0) return 0;
+  return Math.round(Math.log2(1 / realValue));
+}
+```
+
+### 4.2 位置和间距
+
+```typescript
+interface UnderlineSpec {
+  /** 减时线宽度 */
+  width: number;
+  /** 减时线高度(粗细) */
+  height: number;
+  /** 第一条线距离音符底部的距离 */
+  topOffset: number;
+  /** 多条线之间的间距 */
+  gap: number;
+}
+
+const UNDERLINE_SPEC: UnderlineSpec = {
+  width: 16,      // 略宽于数字宽度
+  height: 1.5,    // 线条粗细
+  topOffset: 4,   // 距音符底部4px
+  gap: 3,         // 多条线间距3px
+};
+```
+
+### 4.3 视觉示意图
+
+```
+八分音符(1条减时线):
+    5
+   ───
+
+十六分音符(2条减时线):
+    5
+   ───
+   ───
+
+三十二分音符(3条减时线):
+    5
+   ───
+   ───
+   ───
+```
+
+### 4.4 SVG实现
+
+```typescript
+function drawUnderlines(
+  noteX: number,
+  noteBottomY: number,
+  count: number,
+  config: RenderConfig
+): SVGGElement {
+  const group = document.createElementNS(SVG_NS, 'g');
+  group.setAttribute('class', 'vf-underlines');
+  
+  const width = UNDERLINE_SPEC.width;
+  const startX = noteX - width / 2;
+  
+  for (let i = 0; i < count; i++) {
+    const y = noteBottomY + UNDERLINE_SPEC.topOffset + 
+              i * (UNDERLINE_SPEC.height + UNDERLINE_SPEC.gap);
+    
+    const line = document.createElementNS(SVG_NS, 'rect');
+    line.setAttribute('x', String(startX));
+    line.setAttribute('y', String(y));
+    line.setAttribute('width', String(width));
+    line.setAttribute('height', String(UNDERLINE_SPEC.height));
+    line.setAttribute('fill', config.lineColor);
+    line.setAttribute('class', 'vf-underline');
+    
+    group.appendChild(line);
+  }
+  
+  return group;
+}
+```
+
+### 4.5 连续音符的减时线连接
+
+```
+连续的相同时值音符,减时线应该连接:
+
+分离绘制:          连接绘制(推荐):
+  1   2   3   4       1   2   3   4
+  _   _   _   _       ─────────────
+
+规则:
+- 同一拍内的连续短音符,减时线连接
+- 跨拍的音符,减时线可以分开
+- 当前实现先使用分离绘制,后续优化为连接绘制
+```
+
+---
+
+## 5. 高低音点规范
+
+### 5.1 位置规则
+
+```
+高音点:在音符数字上方
+低音点:在音符数字下方
+
+高两个八度:  ··
+              5
+
+高一个八度:   ·
+              5
+
+中音:        5
+
+低一个八度:   5
+              ·
+
+低两个八度:   5
+              ··
+```
+
+### 5.2 尺寸和间距
+
+```typescript
+interface OctaveDotSpec {
+  /** 点的半径 */
+  radius: number;
+  /** 第一个点距离数字的距离 */
+  offset: number;
+  /** 多个点之间的间距 */
+  gap: number;
+}
+
+const OCTAVE_DOT_SPEC: OctaveDotSpec = {
+  radius: 2.5,
+  offset: 6,   // 距离数字6px
+  gap: 5,      // 点间距5px
+};
+```
+
+### 5.3 SVG实现
+
+```typescript
+function drawOctaveDots(
+  noteX: number,
+  noteY: number,
+  octaveOffset: number,
+  config: RenderConfig
+): SVGGElement {
+  const group = document.createElementNS(SVG_NS, 'g');
+  group.setAttribute('class', 'vf-octave-dots');
+  
+  const count = Math.abs(octaveOffset);
+  const isHigh = octaveOffset > 0;
+  
+  // 高音点在上方,低音点在下方
+  const baseY = isHigh 
+    ? noteY - config.noteFontSize / 2 - OCTAVE_DOT_SPEC.offset
+    : noteY + config.noteFontSize / 2 + OCTAVE_DOT_SPEC.offset;
+  
+  for (let i = 0; i < count; i++) {
+    const dotY = isHigh
+      ? baseY - i * OCTAVE_DOT_SPEC.gap
+      : baseY + i * OCTAVE_DOT_SPEC.gap;
+    
+    const dot = document.createElementNS(SVG_NS, 'circle');
+    dot.setAttribute('cx', String(noteX));
+    dot.setAttribute('cy', String(dotY));
+    dot.setAttribute('r', String(OCTAVE_DOT_SPEC.radius));
+    dot.setAttribute('fill', config.noteColor);
+    dot.setAttribute('class', isHigh ? 'vf-high-dot' : 'vf-low-dot');
+    
+    group.appendChild(dot);
+  }
+  
+  return group;
+}
+```
+
+---
+
+## 6. 附点规范
+
+### 6.1 位置规则
+
+```
+附点位置:在音符数字的右侧,垂直居中
+
+单附点:   5·
+
+双附点:   5··
+
+三附点:   5···(极少见)
+```
+
+### 6.2 尺寸和间距
+
+```typescript
+interface DurationDotSpec {
+  /** 点的半径 */
+  radius: number;
+  /** 第一个点距离数字右边的距离 */
+  offset: number;
+  /** 多个点之间的间距 */
+  gap: number;
+}
+
+const DURATION_DOT_SPEC: DurationDotSpec = {
+  radius: 2,
+  offset: 4,   // 距离数字右边4px
+  gap: 4,      // 点间距4px
+};
+```
+
+### 6.3 SVG实现
+
+```typescript
+function drawDurationDots(
+  noteX: number,
+  noteY: number,
+  dotCount: number,
+  config: RenderConfig
+): SVGGElement {
+  const group = document.createElementNS(SVG_NS, 'g');
+  group.setAttribute('class', 'vf-duration-dots');
+  
+  // 数字宽度约为字体大小的0.6
+  const digitWidth = config.noteFontSize * 0.6;
+  const startX = noteX + digitWidth / 2 + DURATION_DOT_SPEC.offset;
+  
+  for (let i = 0; i < dotCount; i++) {
+    const dotX = startX + i * (DURATION_DOT_SPEC.radius * 2 + DURATION_DOT_SPEC.gap);
+    
+    const dot = document.createElementNS(SVG_NS, 'circle');
+    dot.setAttribute('cx', String(dotX));
+    dot.setAttribute('cy', String(noteY));
+    dot.setAttribute('r', String(DURATION_DOT_SPEC.radius));
+    dot.setAttribute('fill', config.noteColor);
+    dot.setAttribute('class', 'vf-duration-dot');
+    
+    group.appendChild(dot);
+  }
+  
+  return group;
+}
+```
+
+---
+
+## 7. 升降号规范
+
+### 7.1 位置规则
+
+```
+升降号位置:在音符数字的左上方
+
+升号:    #
+          5
+
+降号:    ♭
+          5
+
+还原号:   ♮
+          5
+```
+
+### 7.2 符号字符
+
+| 类型 | Unicode | 显示 |
+|------|---------|------|
+| 升号 | `#` 或 `\u266F` | # 或 ♯ |
+| 降号 | `\u266D` | ♭ |
+| 还原号 | `\u266E` | ♮ |
+| 重升 | `\u00D7` 或 `##` | × 或 ## |
+| 重降 | `\u266D\u266D` | ♭♭ |
+
+### 7.3 尺寸和间距
+
+```typescript
+interface AccidentalSpec {
+  /** 字体大小(相对于音符字体) */
+  fontSizeRatio: number;
+  /** 水平偏移(向左) */
+  offsetX: number;
+  /** 垂直偏移(向上) */
+  offsetY: number;
+}
+
+const ACCIDENTAL_SPEC: AccidentalSpec = {
+  fontSizeRatio: 0.5,  // 音符字体的50%
+  offsetX: 8,          // 向左偏移8px
+  offsetY: 8,          // 向上偏移8px
+};
+```
+
+### 7.4 SVG实现
+
+```typescript
+const ACCIDENTAL_SYMBOLS: Record<string, string> = {
+  'sharp': '#',
+  'flat': '♭',
+  'natural': '♮',
+  'double-sharp': '×',
+  'double-flat': '♭♭',
+};
+
+function drawAccidental(
+  noteX: number,
+  noteY: number,
+  accidental: string,
+  config: RenderConfig
+): SVGTextElement {
+  const text = document.createElementNS(SVG_NS, 'text');
+  
+  const fontSize = config.noteFontSize * ACCIDENTAL_SPEC.fontSizeRatio;
+  const x = noteX - config.noteFontSize * 0.3 - ACCIDENTAL_SPEC.offsetX;
+  const y = noteY - config.noteFontSize * 0.3 - ACCIDENTAL_SPEC.offsetY;
+  
+  text.setAttribute('x', String(x));
+  text.setAttribute('y', String(y));
+  text.setAttribute('font-size', String(fontSize));
+  text.setAttribute('font-family', config.noteFont);
+  text.setAttribute('text-anchor', 'middle');
+  text.setAttribute('fill', config.noteColor);
+  text.setAttribute('class', 'vf-accidental');
+  
+  text.textContent = ACCIDENTAL_SYMBOLS[accidental] || accidental;
+  
+  return text;
+}
+```
+
+---
+
+## 8. 休止符规范
+
+### 8.1 渲染规则
+
+```
+休止符用数字"0"表示,位置与普通音符相同
+
+四分休止符:    0
+八分休止符:    0
+               ─
+二分休止符:    0 —(不画增时线,用空白表示)
+全休止符:      0 — — —(不画增时线,用空白表示)
+```
+
+### 8.2 特殊处理
+
+```typescript
+function renderRest(
+  x: number,
+  y: number,
+  realValue: number,
+  config: RenderConfig
+): SVGGElement {
+  const group = document.createElementNS(SVG_NS, 'g');
+  group.setAttribute('class', 'vf-rest');
+  
+  // 绘制数字"0"
+  const text = drawNoteNumber(x, y, 0, config);
+  group.appendChild(text);
+  
+  // 休止符的减时线正常绘制
+  if (realValue < 1.0) {
+    const underlineCount = calcUnderlineCount(realValue);
+    const underlines = drawUnderlines(x, y + config.noteFontSize / 2, underlineCount, config);
+    group.appendChild(underlines);
+  }
+  
+  // 休止符不绘制增时线!
+  // 长休止符通过占据空间来表示
+  
+  return group;
+}
+```
+
+---
+
+## 9. 小节线规范
+
+### 9.1 类型
+
+| 类型 | 样式 | 说明 |
+|------|------|------|
+| single | `│` | 普通单线 |
+| double | `║` | 双线(段落结束) |
+| repeat-start | `║:` | 反复开始 |
+| repeat-end | `:║` | 反复结束 |
+| final | `║█` | 终止线 |
+
+### 9.2 尺寸
+
+```typescript
+interface BarlineSpec {
+  /** 单线粗细 */
+  thinWidth: number;
+  /** 粗线粗细 */
+  thickWidth: number;
+  /** 双线间距 */
+  doubleGap: number;
+  /** 反复点半径 */
+  repeatDotRadius: number;
+  /** 反复点间距 */
+  repeatDotGap: number;
+}
+
+const BARLINE_SPEC: BarlineSpec = {
+  thinWidth: 1,
+  thickWidth: 3,
+  doubleGap: 3,
+  repeatDotRadius: 2,
+  repeatDotGap: 8,
+};
+```
+
+### 9.3 SVG实现
+
+```typescript
+function drawBarline(
+  x: number,
+  topY: number,
+  bottomY: number,
+  type: string,
+  config: RenderConfig
+): SVGGElement {
+  const group = document.createElementNS(SVG_NS, 'g');
+  group.setAttribute('class', `vf-barline vf-barline-${type}`);
+  
+  const height = bottomY - topY;
+  
+  switch (type) {
+    case 'single':
+      group.appendChild(createLine(x, topY, x, bottomY, BARLINE_SPEC.thinWidth));
+      break;
+      
+    case 'double':
+      group.appendChild(createLine(x - BARLINE_SPEC.doubleGap, topY, 
+                                   x - BARLINE_SPEC.doubleGap, bottomY, 
+                                   BARLINE_SPEC.thinWidth));
+      group.appendChild(createLine(x, topY, x, bottomY, BARLINE_SPEC.thinWidth));
+      break;
+      
+    case 'final':
+      group.appendChild(createLine(x - BARLINE_SPEC.doubleGap - BARLINE_SPEC.thickWidth, 
+                                   topY, 
+                                   x - BARLINE_SPEC.doubleGap - BARLINE_SPEC.thickWidth, 
+                                   bottomY, 
+                                   BARLINE_SPEC.thinWidth));
+      group.appendChild(createRect(x - BARLINE_SPEC.thickWidth, topY, 
+                                   BARLINE_SPEC.thickWidth, height));
+      break;
+      
+    // ... 其他类型
+  }
+  
+  return group;
+}
+```
+
+---
+
+## 10. 歌词渲染规范
+
+### 10.1 位置规则
+
+```
+歌词位置:在音符下方,与音符水平对齐
+
+单行歌词:
+    5     3     2     1
+   小    星    星    亮
+
+多遍歌词(垂直排列):
+    5     3     2     1
+   小    星    星    亮    ← 第1遍
+   一    闪    一    闪    ← 第2遍
+```
+
+### 10.2 尺寸和间距
+
+```typescript
+interface LyricSpec {
+  /** 字体大小 */
+  fontSize: number;
+  /** 距离音符底部的距离 */
+  topOffset: number;
+  /** 多遍歌词行间距 */
+  lineHeight: number;
+}
+
+const LYRIC_SPEC: LyricSpec = {
+  fontSize: 14,
+  topOffset: 25,    // 距离音符底部25px
+  lineHeight: 18,   // 行间距18px
+};
+```
+
+### 10.3 DOM结构要求
+
+```html
+<!-- 歌词元素必须包含以下属性,用于业务层高亮匹配 -->
+<text 
+  class="vf-lyric lyric{noteId}"
+  lyricIndex="1"
+  data-note-id="{noteId}"
+>
+  歌词文字
+</text>
+```
+
+### 10.4 SVG实现
+
+```typescript
+function drawLyric(
+  noteX: number,
+  noteBottomY: number,
+  text: string,
+  lyricIndex: number,
+  noteId: string,
+  config: RenderConfig
+): SVGTextElement {
+  const textEl = document.createElementNS(SVG_NS, 'text');
+  
+  const y = noteBottomY + LYRIC_SPEC.topOffset + 
+            (lyricIndex - 1) * LYRIC_SPEC.lineHeight;
+  
+  textEl.setAttribute('x', String(noteX));
+  textEl.setAttribute('y', String(y));
+  textEl.setAttribute('font-size', String(LYRIC_SPEC.fontSize));
+  textEl.setAttribute('font-family', config.lyricFont);
+  textEl.setAttribute('text-anchor', 'middle');
+  textEl.setAttribute('fill', config.lyricColor);
+  textEl.setAttribute('class', `vf-lyric lyric${noteId}`);
+  textEl.setAttribute('lyricIndex', String(lyricIndex));
+  textEl.setAttribute('data-note-id', noteId);
+  
+  textEl.textContent = text;
+  
+  return textEl;
+}
+```
+
+---
+
+## 11. 布局规范
+
+### 11.1 小节宽度计算
+
+```typescript
+/**
+ * 计算小节宽度(固定时间比例)
+ * 公式:(拍数 / 单位拍 × 4) × 四分音符间距 + 左右padding
+ */
+function calculateMeasureWidth(
+  beats: number,
+  beatType: number,
+  quarterSpacing: number,
+  padding: number
+): number {
+  // 小节总时值(以四分音符为单位)
+  const totalBeats = beats / (beatType / 4);
+  
+  // 内容宽度
+  const contentWidth = totalBeats * quarterSpacing;
+  
+  // 总宽度
+  return contentWidth + padding * 2;
+}
+
+// 示例:
+// 4/4拍: (4 / 1) × 50 + 20 × 2 = 240px
+// 3/4拍: (3 / 1) × 50 + 20 × 2 = 190px
+// 6/8拍: (6 / 2) × 50 + 20 × 2 = 190px
+```
+
+### 11.2 音符X坐标计算
+
+```typescript
+/**
+ * 计算音符X坐标(固定时间比例)
+ * 公式:小节X + padding + 时间戳 × 四分音符间距
+ */
+function calculateNoteX(
+  measureX: number,
+  timestamp: number,
+  padding: number,
+  quarterSpacing: number
+): number {
+  return measureX + padding + timestamp * quarterSpacing;
+}
+```
+
+### 11.3 自动换行规则
+
+```typescript
+interface LineBreakSpec {
+  /** 最大行宽 */
+  maxWidth: number;
+  /** 最小小节数/行 */
+  minMeasuresPerLine: number;
+  /** 最大小节数/行 */
+  maxMeasuresPerLine: number;
+}
+
+function calculateLineBreaks(
+  measures: JianpuMeasure[],
+  maxWidth: number
+): number[][] {
+  const lines: number[][] = [];
+  let currentLine: number[] = [];
+  let currentWidth = 0;
+  
+  measures.forEach((measure, index) => {
+    if (currentWidth + measure.width > maxWidth && currentLine.length > 0) {
+      lines.push(currentLine);
+      currentLine = [];
+      currentWidth = 0;
+    }
+    
+    currentLine.push(index);
+    currentWidth += measure.width;
+  });
+  
+  if (currentLine.length > 0) {
+    lines.push(currentLine);
+  }
+  
+  return lines;
+}
+```
+
+### 11.4 多声部Y坐标计算
+
+```typescript
+/**
+ * 计算声部Y坐标
+ */
+function calculateVoiceY(
+  baseY: number,
+  voiceIndex: number,
+  voiceCount: number,
+  voiceSpacing: number
+): number {
+  // 多声部垂直居中分布
+  const totalHeight = (voiceCount - 1) * voiceSpacing;
+  const startY = baseY - totalHeight / 2;
+  
+  return startY + voiceIndex * voiceSpacing;
+}
+```
+
+---
+
+## 验收标准检查
 
-5. **视觉尺寸标准**
-   - 所有元素的标准尺寸
+- [x] 所有渲染元素都有精确的位置和尺寸定义
+- [x] 包含视觉示意图(ASCII图)
+- [x] 包含特殊情况的处理规则
+- [x] 包含完整的SVG实现代码模板
+- [x] 包含增时线的详细布局算法
 
 ---
 
-**下一步:** 完成任务0.5.1后,开始编写此文档
+**文档版本:** v1.0  
+**最后更新:** 2026-01-29  
+**维护者:** 简谱渲染引擎开发团队

+ 2982 - 0
docs/musicxml/二声部.xml

@@ -0,0 +1,2982 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<score-partwise fill-line="1" page-width="1516" version="3.1">
+    <custom-rule>
+        <measure-show>
+            <part id="P1">
+                <show-order end-measure-num="17" start-measure-num="1"/>
+            </part>
+            <part id="P2">
+                <show-order end-measure-num="8" measure-show="no" start-measure-num="1"/>
+            <show-order end-measure-num="17" lyric-show="no" start-measure-num="9"/>
+            </part>
+        </measure-show>
+    </custom-rule>
+	<work>
+		<work-title>友谊地久天长</work-title>
+	</work>
+	<identification>
+		<creator type="composer">苏格兰民歌</creator>
+		<creator type="lyricist">[英] 罗伯特·朋斯  词</creator>
+		<creator type="arranger">邓映易 译配</creator>
+		<encoding>
+			<protocol-version>1</protocol-version>
+			<software>evmusic_software-0.10.22</software>
+		</encoding>
+	</identification>
+	<defaults>
+		<lyric-font font-size="40.000000" font-style="normal" font-weight="normal"/>
+	</defaults>
+	<credit>
+		<credit-type>composer</credit-type>
+		<credit-words font-size="12" justify="right" valign="bottom">苏格兰民歌</credit-words>
+	</credit>
+	<credit>
+		<credit-type>lyricist</credit-type>
+		<credit-words font-size="12" justify="left" valign="bottom">[英] 罗伯特·朋斯  词</credit-words>
+	</credit>
+	<credit>
+		<credit-type/>
+		<credit-words font-size="24" justify="center" valign="top">友谊地久天长</credit-words>
+	</credit>
+	<credit>
+		<credit-type/>
+		<credit-words font-size="14" justify="center" valign="top">(Auld Lang Syne)</credit-words>
+	</credit>
+	<systems>
+		<system number="1">
+			<part begin-measure="1" end-measure="3" id="P1"/>
+		</system>
+		<system number="2">
+			<part begin-measure="4" end-measure="6" id="P1"/>
+		</system>
+		<system number="3">
+			<part begin-measure="7" end-measure="8" id="P1"/>
+		</system>
+		<system number="4">
+			<part begin-measure="9" end-measure="12" id="P1"/>
+			<part begin-measure="1" end-measure="4" id="P2"/>
+		</system>
+		<system number="5">
+			<part begin-measure="13" end-measure="15" id="P1"/>
+			<part begin-measure="5" end-measure="7" id="P2"/>
+		</system>
+		<system number="6">
+			<part begin-measure="16" end-measure="17" id="P1"/>
+			<part begin-measure="8" end-measure="9" id="P2"/>
+		</system>
+	</systems>
+	<part-list>
+		<part-group type="start">
+			<group-symbol>bracket</group-symbol>
+		</part-group>
+		<score-part id="P1">
+			<score-instrument id="P1-I1"/>
+			<midi-device id="P1-I1" port="1"/>
+			<midi-instrument id="P1-I1">
+				<midi-channel>1</midi-channel>
+				<midi-program>1</midi-program>
+				<volume>78.740196</volume>
+				<pan>0</pan>
+			</midi-instrument>
+		</score-part>
+		<score-part id="P2">
+			<score-instrument id="P2-I1"/>
+			<midi-device id="P2-I1" port="1"/>
+			<midi-instrument id="P2-I1">
+				<midi-channel>2</midi-channel>
+				<midi-program>1</midi-program>
+				<volume>78.740196</volume>
+				<pan>0</pan>
+			</midi-instrument>
+		</score-part>
+		<part-group type="stop"/>
+	</part-list>
+	<part id="P1">
+		<measure number="1">
+			<attributes>
+				<divisions>2</divisions>
+				<key>
+					<fifths>-3</fifths>
+				</key>
+				<time>
+					<beats>4</beats>
+					<beat-type>4</beat-type>
+				</time>
+				<clef/>
+			</attributes>
+			<direction>
+				<direction-type>
+					<metronome>
+						<beat-unit>quarter</beat-unit>
+						<per-minute>78</per-minute>
+					</metronome>
+				</direction-type>
+			</direction>
+			<direction>
+				<direction-type>
+					<words>中速 深情地</words>
+				</direction-type>
+			</direction>
+			<note>
+				<pitch>
+					<step>B</step>
+					<alter>-1</alter>
+					<octave>3</octave>
+				</pitch>
+				<voice>1</voice>
+				<duration>2</duration>
+				<type>quarter</type>
+				<lyric number="1">
+					<text>1.怎</text>
+				</lyric>
+				<lyric number="2">
+					<text>Should</text>
+				</lyric>
+				<lyric number="3">
+					<text>2.我</text>
+				</lyric>
+				<lyric number="4">
+					<text>And</text>
+				</lyric>
+			</note>
+			<barline location="right">
+				<bar-style>light</bar-style>
+			</barline>
+		</measure>
+		<measure number="2">
+			<note>
+				<pitch>
+					<step>E</step>
+					<alter>-1</alter>
+					<octave>4</octave>
+				</pitch>
+				<voice>1</voice>
+				<duration>3</duration>
+				<type>quarter</type>
+				<dot/>
+				<lyric number="1">
+					<text>能</text>
+				</lyric>
+				<lyric number="2">
+					<text>auld</text>
+				</lyric>
+				<lyric number="3">
+					<text>们</text>
+				</lyric>
+				<lyric number="4">
+					<text>here's</text>
+				</lyric>
+			</note>
+			<note>
+				<space/>
+			</note>
+			<note>
+				<pitch>
+					<step>E</step>
+					<alter>-1</alter>
+					<octave>4</octave>
+				</pitch>
+				<voice>1</voice>
+				<duration>1</duration>
+				<type>eighth</type>
+				<lyric number="1">
+					<text>忘</text>
+				</lyric>
+				<lyric number="2">
+					<text>ac\-</text>
+				</lyric>
+				<lyric number="3">
+					<text>往</text>
+				</lyric>
+				<lyric number="4">
+					<text>a</text>
+				</lyric>
+			</note>
+			<note>
+				<space/>
+			</note>
+			<note>
+				<pitch>
+					<step>E</step>
+					<alter>-1</alter>
+					<octave>4</octave>
+				</pitch>
+				<voice>1</voice>
+				<duration>2</duration>
+				<type>quarter</type>
+				<lyric number="1">
+					<text>记</text>
+				</lyric>
+				<lyric number="2">
+					<text>quaint\-</text>
+				</lyric>
+				<lyric number="3">
+					<text>日</text>
+				</lyric>
+				<lyric number="4">
+					<text>hand,</text>
+				</lyric>
+			</note>
+			<note>
+				<space/>
+			</note>
+			<note>
+				<space/>
+			</note>
+			<note>
+				<pitch>
+					<step>G</step>
+					<octave>4</octave>
+				</pitch>
+				<voice>1</voice>
+				<duration>2</duration>
+				<type>quarter</type>
+				<lyric number="1">
+					<text>旧</text>
+				</lyric>
+				<lyric number="2">
+					<text>ance</text>
+				</lyric>
+				<lyric number="3">
+					<text>情</text>
+				</lyric>
+				<lyric number="4">
+					<text>my</text>
+				</lyric>
+			</note>
+			<barline location="right">
+				<bar-style>light</bar-style>
+			</barline>
+		</measure>
+		<measure number="3">
+			<note>
+				<pitch>
+					<step>F</step>
+					<octave>4</octave>
+				</pitch>
+				<voice>1</voice>
+				<duration>3</duration>
+				<type>quarter</type>
+				<dot/>
+				<lyric number="1">
+					<text>日</text>
+				</lyric>
+				<lyric number="2">
+					<text>be</text>
+				</lyric>
+				<lyric number="3">
+					<text>意</text>
+				</lyric>
+				<lyric number="4">
+					<text>trusty</text>
+				</lyric>
+			</note>
+			<note>
+				<space/>
+			</note>
+			<note>
+				<pitch>
+					<step>E</step>
+					<alter>-1</alter>
+					<octave>4</octave>
+				</pitch>
+				<voice>1</voice>
+				<duration>1</duration>
+				<type>eighth</type>
+				<lyric number="1">
+					<text>朋</text>
+				</lyric>
+				<lyric number="2">
+					<text>for\-</text>
+				</lyric>
+				<lyric number="3">
+					<text>相</text>
+				</lyric>
+				<lyric number="4">
+					<text>frien',</text>
+				</lyric>
+			</note>
+			<note>
+				<space/>
+			</note>
+			<note>
+				<pitch>
+					<step>F</step>
+					<octave>4</octave>
+				</pitch>
+				<voice>1</voice>
+				<duration>2</duration>
+				<type>quarter</type>
+				<lyric number="1">
+					<text>友,</text>
+				</lyric>
+				<lyric number="2">
+					<text>got,</text>
+				</lyric>
+				<lyric number="3">
+					<text>投,</text>
+				</lyric>
+			</note>
+			<note>
+				<space/>
+			</note>
+			<note>
+				<pitch>
+					<step>G</step>
+					<octave>4</octave>
+				</pitch>
+				<voice>1</voice>
+				<duration>2</duration>
+				<type>quarter</type>
+				<lyric number="1">
+					<text>心</text>
+				</lyric>
+				<lyric number="2">
+					<text>and</text>
+				</lyric>
+				<lyric number="3">
+					<text>让</text>
+				</lyric>
+				<lyric number="4">
+					<text>And</text>
+				</lyric>
+			</note>
+			<barline location="right">
+				<bar-style>light</bar-style>
+			</barline>
+		</measure>
+		<measure number="4">
+			<print new-system="yes"/>
+			<note>
+				<pitch>
+					<step>E</step>
+					<alter>-1</alter>
+					<octave>4</octave>
+				</pitch>
+				<voice>1</voice>
+				<duration>3</duration>
+				<type>quarter</type>
+				<dot/>
+				<lyric number="1">
+					<text>中</text>
+				</lyric>
+				<lyric number="2">
+					<text>ne\-</text>
+				</lyric>
+				<lyric number="3">
+					<text>我</text>
+				</lyric>
+				<lyric number="4">
+					<text>gie's</text>
+				</lyric>
+			</note>
+			<note>
+				<space/>
+			</note>
+			<note>
+				<pitch>
+					<step>E</step>
+					<alter>-1</alter>
+					<octave>4</octave>
+				</pitch>
+				<voice>1</voice>
+				<duration>1</duration>
+				<type>eighth</type>
+				<lyric number="1">
+					<text>能</text>
+				</lyric>
+				<lyric number="2">
+					<text>ver</text>
+				</lyric>
+				<lyric number="3">
+					<text>们</text>
+				</lyric>
+				<lyric number="4">
+					<text>a</text>
+				</lyric>
+			</note>
+			<note>
+				<space/>
+			</note>
+			<note>
+				<pitch>
+					<step>G</step>
+					<octave>4</octave>
+				</pitch>
+				<voice>1</voice>
+				<duration>2</duration>
+				<type>quarter</type>
+				<lyric number="1">
+					<text>不</text>
+				</lyric>
+				<lyric number="2">
+					<text>brought</text>
+				</lyric>
+				<lyric number="3">
+					<text>紧</text>
+				</lyric>
+				<lyric number="4">
+					<text>hand</text>
+				</lyric>
+			</note>
+			<note>
+				<space/>
+			</note>
+			<note>
+				<space/>
+			</note>
+			<note>
+				<space/>
+			</note>
+			<note>
+				<pitch>
+					<step>B</step>
+					<alter>-1</alter>
+					<octave>4</octave>
+				</pitch>
+				<voice>1</voice>
+				<duration>2</duration>
+				<type>quarter</type>
+				<lyric number="1">
+					<text>怀</text>
+				</lyric>
+				<lyric number="2">
+					<text>to</text>
+				</lyric>
+				<lyric number="3">
+					<text>握</text>
+				</lyric>
+				<lyric number="4">
+					<text>of</text>
+				</lyric>
+			</note>
+			<barline location="right">
+				<bar-style>light</bar-style>
+			</barline>
+		</measure>
+		<measure number="5">
+			<note>
+				<pitch>
+					<step>C</step>
+					<octave>5</octave>
+				</pitch>
+				<voice>1</voice>
+				<duration>6</duration>
+				<type>half</type>
+				<dot/>
+				<lyric number="1">
+					<text>想?</text>
+				</lyric>
+				<lyric number="2">
+					<text>mine?</text>
+				</lyric>
+				<lyric number="3">
+					<text>手,</text>
+				</lyric>
+				<lyric number="4">
+					<text>thine.</text>
+				</lyric>
+			<stem>down</stem>
+            </note>
+			<note>
+				<pitch>
+					<step>C</step>
+					<octave>5</octave>
+				</pitch>
+				<voice>1</voice>
+				<duration>2</duration>
+				<type>quarter</type>
+				<lyric number="1">
+					<text>旧</text>
+				</lyric>
+				<lyric number="2">
+					<text>Should</text>
+				</lyric>
+				<lyric number="3">
+					<text>让</text>
+				</lyric>
+				<lyric number="4">
+					<text>We'll</text>
+				</lyric>
+			<stem>down</stem>
+            </note>
+			<note>
+				<space/>
+			</note>
+			<barline location="right">
+				<bar-style>light</bar-style>
+			</barline>
+		</measure>
+		<measure number="6">
+			<note>
+				<pitch>
+					<step>B</step>
+					<alter>-1</alter>
+					<octave>4</octave>
+				</pitch>
+				<voice>1</voice>
+				<duration>3</duration>
+				<type>quarter</type>
+				<dot/>
+				<lyric number="1">
+					<text>日</text>
+				</lyric>
+				<lyric number="2">
+					<text>auld</text>
+				</lyric>
+				<lyric number="3">
+					<text>我</text>
+				</lyric>
+				<lyric number="4">
+					<text>take</text>
+				</lyric>
+			</note>
+			<note>
+				<space/>
+			</note>
+			<note>
+				<pitch>
+					<step>G</step>
+					<octave>4</octave>
+				</pitch>
+				<voice>1</voice>
+				<duration>1</duration>
+				<type>eighth</type>
+				<lyric number="1">
+					<text>朋</text>
+				</lyric>
+				<lyric number="2">
+					<text>ac\-</text>
+				</lyric>
+				<lyric number="3">
+					<text>们</text>
+				</lyric>
+				<lyric number="4">
+					<text>a</text>
+				</lyric>
+			</note>
+			<note>
+				<space/>
+			</note>
+			<note>
+				<pitch>
+					<step>G</step>
+					<octave>4</octave>
+				</pitch>
+				<voice>1</voice>
+				<duration>2</duration>
+				<type>quarter</type>
+				<lyric number="1">
+					<text>友</text>
+				</lyric>
+				<lyric number="2">
+					<text>quaint-</text>
+				</lyric>
+				<lyric number="3">
+					<text>来</text>
+				</lyric>
+				<lyric number="4">
+					<text>cup</text>
+				</lyric>
+			</note>
+			<note>
+				<space/>
+			</note>
+			<note>
+				<space/>
+			</note>
+			<note>
+				<space/>
+			</note>
+			<note>
+				<pitch>
+					<step>E</step>
+					<alter>-1</alter>
+					<octave>4</octave>
+				</pitch>
+				<voice>1</voice>
+				<duration>2</duration>
+				<type>quarter</type>
+				<lyric number="1">
+					<text>岂</text>
+				</lyric>
+				<lyric number="2">
+					<text>ance</text>
+				</lyric>
+				<lyric number="3">
+					<text>举</text>
+				</lyric>
+				<lyric number="4">
+					<text>of</text>
+				</lyric>
+			</note>
+			<barline location="right">
+				<bar-style>light</bar-style>
+			</barline>
+		</measure>
+		<measure number="7">
+			<print new-system="yes"/>
+			<note>
+				<pitch>
+					<step>F</step>
+					<octave>4</octave>
+				</pitch>
+				<voice>1</voice>
+				<duration>3</duration>
+				<type>quarter</type>
+				<dot/>
+				<lyric number="1">
+					<text>能</text>
+				</lyric>
+				<lyric number="2">
+					<text>be</text>
+				</lyric>
+				<lyric number="3">
+					<text>杯</text>
+				</lyric>
+				<lyric number="4">
+					<text>kind\-</text>
+				</lyric>
+			</note>
+			<note>
+				<pitch>
+					<step>E</step>
+					<alter>-1</alter>
+					<octave>4</octave>
+				</pitch>
+				<voice>1</voice>
+				<duration>1</duration>
+				<type>eighth</type>
+				<lyric number="1">
+					<text>相</text>
+				</lyric>
+				<lyric number="2">
+					<text>for\-</text>
+				</lyric>
+				<lyric number="3">
+					<text>畅</text>
+				</lyric>
+				<lyric number="4">
+					<text>ness</text>
+				</lyric>
+			</note>
+			<note>
+				<space/>
+			</note>
+			<note>
+				<pitch>
+					<step>F</step>
+					<octave>4</octave>
+				</pitch>
+				<voice>1</voice>
+				<duration>2</duration>
+				<type>quarter</type>
+				<lyric number="1">
+					<text>忘,</text>
+				</lyric>
+				<lyric number="2">
+					<text>got,</text>
+				</lyric>
+				<lyric number="3">
+					<text>饮,</text>
+				</lyric>
+				<lyric number="4">
+					<text>yet,</text>
+				</lyric>
+			</note>
+			<note>
+				<space/>
+			</note>
+			<note>
+				<pitch>
+					<step>G</step>
+					<octave>4</octave>
+				</pitch>
+				<voice>1</voice>
+				<duration>2</duration>
+				<type>quarter</type>
+				<lyric number="1">
+					<text>友</text>
+				</lyric>
+				<lyric number="2">
+					<text>and</text>
+				</lyric>
+				<lyric number="3">
+					<text>友</text>
+				</lyric>
+				<lyric number="4">
+					<text>For</text>
+				</lyric>
+			</note>
+			<barline location="right">
+				<bar-style>light</bar-style>
+			</barline>
+		</measure>
+		<measure number="8">
+			<note>
+				<pitch>
+					<step>E</step>
+					<alter>-1</alter>
+					<octave>4</octave>
+				</pitch>
+				<voice>1</voice>
+				<duration>3</duration>
+				<type>quarter</type>
+				<dot/>
+				<lyric number="1">
+					<text>谊</text>
+				</lyric>
+				<lyric number="2">
+					<text>days</text>
+				</lyric>
+				<lyric number="3">
+					<text>谊</text>
+				</lyric>
+				<lyric number="4">
+					<text>auld</text>
+				</lyric>
+			</note>
+			<note>
+				<pitch>
+					<step>C</step>
+					<octave>4</octave>
+				</pitch>
+				<voice>1</voice>
+				<duration>1</duration>
+				<type>eighth</type>
+				<lyric number="1">
+					<text>地</text>
+				</lyric>
+				<lyric number="2">
+					<text>of</text>
+				</lyric>
+				<lyric number="3">
+					<text>地</text>
+				</lyric>
+				<lyric number="4">
+					<text>lang</text>
+				</lyric>
+			</note>
+			<note>
+				<space/>
+			</note>
+			<note>
+				<pitch>
+					<step>C</step>
+					<octave>4</octave>
+				</pitch>
+				<voice>1</voice>
+				<duration>2</duration>
+				<type>quarter</type>
+				<lyric number="1">
+					<text>久</text>
+				</lyric>
+				<lyric number="2">
+					<text>auld</text>
+				</lyric>
+				<lyric number="3">
+					<text>久</text>
+				</lyric>
+			</note>
+			<note>
+				<space/>
+			</note>
+			<note>
+				<pitch>
+					<step>B</step>
+					<alter>-1</alter>
+					<octave>3</octave>
+				</pitch>
+				<voice>1</voice>
+				<duration>2</duration>
+				<type>quarter</type>
+				<lyric number="1">
+					<text>天</text>
+				</lyric>
+				<lyric number="2">
+					<text>lang</text>
+				</lyric>
+				<lyric number="3">
+					<text>天</text>
+				</lyric>
+			</note>
+			<barline location="right">
+				<bar-style>light</bar-style>
+			</barline>
+		</measure>
+		<measure number="9">
+			<print new-system="yes"/>
+			<note>
+				<pitch>
+					<step>E</step>
+					<alter>-1</alter>
+					<octave>4</octave>
+				</pitch>
+				<voice>1</voice>
+				<duration>6</duration>
+				<type>half</type>
+				<dot/>
+				<lyric number="1">
+					<text>长;</text>
+				</lyric>
+				<lyric number="2">
+					<text>syne?</text>
+				</lyric>
+				<lyric number="3">
+					<text>长;</text>
+				</lyric>
+				<lyric number="4">
+					<text>syne?</text>
+				</lyric>
+			</note>
+			<note>
+				<pitch>
+					<step>C</step>
+					<octave>5</octave>
+				</pitch>
+				<voice>1</voice>
+				<duration>2</duration>
+				<type>quarter</type>
+				<lyric number="1">
+					<text>友</text>
+				</lyric>
+				<lyric number="2">
+					<text>For</text>
+				</lyric>
+				<lyric number="3">
+					<text>友</text>
+				</lyric>
+				<lyric number="4">
+					<text>For</text>
+				</lyric>
+			<stem>down</stem>
+            </note>
+			<barline location="right">
+				<bar-style>light</bar-style>
+			</barline>
+		</measure>
+		<measure number="10">
+			<note>
+				<pitch>
+					<step>B</step>
+					<alter>-1</alter>
+					<octave>4</octave>
+				</pitch>
+				<voice>1</voice>
+				<duration>3</duration>
+				<type>quarter</type>
+				<dot/>
+				<lyric number="1">
+					<text>谊</text>
+				</lyric>
+				<lyric number="2">
+					<text>auld</text>
+				</lyric>
+				<lyric number="3">
+					<text>谊</text>
+				</lyric>
+				<lyric number="4">
+					<text>auld</text>
+				</lyric>
+				<notations>
+					<slur type="start"/>
+				</notations>
+			</note>
+			<note>
+				<space/>
+			</note>
+			<note>
+				<pitch>
+					<step>G</step>
+					<octave>4</octave>
+				</pitch>
+				<voice>1</voice>
+				<duration>1</duration>
+				<type>eighth</type>
+				<notations>
+					<slur type="stop"/>
+				</notations>
+			</note>
+			<note>
+				<space/>
+			</note>
+			<note>
+				<pitch>
+					<step>G</step>
+					<octave>4</octave>
+				</pitch>
+				<voice>1</voice>
+				<duration>2</duration>
+				<type>quarter</type>
+				<lyric number="1">
+					<text>万</text>
+				</lyric>
+				<lyric number="2">
+					<text>lang</text>
+				</lyric>
+				<lyric number="3">
+					<text>万</text>
+				</lyric>
+				<lyric number="4">
+					<text>lang</text>
+				</lyric>
+				<notations>
+					<slur type="start"/>
+				</notations>
+			</note>
+			<note>
+				<space/>
+			</note>
+			<note>
+				<space/>
+			</note>
+			<note>
+				<pitch>
+					<step>E</step>
+					<alter>-1</alter>
+					<octave>4</octave>
+				</pitch>
+				<voice>1</voice>
+				<duration>2</duration>
+				<type>quarter</type>
+				<notations>
+					<slur type="stop"/>
+				</notations>
+			</note>
+			<barline location="right">
+				<bar-style>light</bar-style>
+			</barline>
+		</measure>
+		<measure number="11">
+			<note>
+				<pitch>
+					<step>F</step>
+					<octave>4</octave>
+				</pitch>
+				<voice>1</voice>
+				<duration>3</duration>
+				<type>quarter</type>
+				<dot/>
+				<lyric number="1">
+					<text>岁,</text>
+				</lyric>
+				<lyric number="2">
+					<text>syne,</text>
+				</lyric>
+				<lyric number="3">
+					<text>岁,</text>
+				</lyric>
+				<lyric number="4">
+					<text>syne,</text>
+				</lyric>
+				<notations>
+					<slur type="start"/>
+				</notations>
+			</note>
+			<note>
+				<space/>
+			</note>
+			<note>
+				<space/>
+			</note>
+			<note>
+				<space/>
+			</note>
+			<note>
+				<pitch>
+					<step>E</step>
+					<alter>-1</alter>
+					<octave>4</octave>
+				</pitch>
+				<voice>1</voice>
+				<duration>1</duration>
+				<type>eighth</type>
+				<lyric number="1"/>
+				<lyric number="2">
+					<text>my</text>
+				</lyric>
+				<lyric number="3"/>
+				<lyric number="4">
+					<text>my</text>
+				</lyric>
+			</note>
+			<note>
+				<space/>
+			</note>
+			<note>
+				<pitch>
+					<step>F</step>
+					<octave>4</octave>
+				</pitch>
+				<voice>1</voice>
+				<duration>2</duration>
+				<type>quarter</type>
+				<lyric number="1"/>
+				<lyric number="2">
+					<text>dear,</text>
+				</lyric>
+				<lyric number="3"/>
+				<lyric number="4">
+					<text>dear,</text>
+				</lyric>
+				<notations>
+					<slur type="stop"/>
+				</notations>
+			</note>
+			<note>
+				<space/>
+			</note>
+			<note>
+				<space/>
+			</note>
+			<note>
+				<space/>
+			</note>
+			<note>
+				<pitch>
+					<step>C</step>
+					<octave>5</octave>
+				</pitch>
+				<voice>1</voice>
+				<duration>2</duration>
+				<type>quarter</type>
+				<lyric number="1">
+					<text>友</text>
+				</lyric>
+				<lyric number="2">
+					<text>for</text>
+				</lyric>
+				<lyric number="3">
+					<text>友</text>
+				</lyric>
+				<lyric number="4">
+					<text>for</text>
+				</lyric>
+			<stem>down</stem>
+            </note>
+			<barline location="right">
+				<bar-style>light</bar-style>
+			</barline>
+		</measure>
+		<measure number="12">
+			<note>
+				<pitch>
+					<step>B</step>
+					<alter>-1</alter>
+					<octave>4</octave>
+				</pitch>
+				<voice>1</voice>
+				<duration>3</duration>
+				<type>quarter</type>
+				<dot/>
+				<lyric number="1">
+					<text>谊</text>
+				</lyric>
+				<lyric number="2">
+					<text>auld</text>
+				</lyric>
+				<lyric number="3">
+					<text>谊</text>
+				</lyric>
+				<lyric number="4">
+					<text>auld</text>
+				</lyric>
+				<notations>
+					<slur type="start"/>
+				</notations>
+			</note>
+			<note>
+				<space/>
+			</note>
+			<note>
+				<pitch>
+					<step>G</step>
+					<octave>4</octave>
+				</pitch>
+				<voice>1</voice>
+				<duration>1</duration>
+				<type>eighth</type>
+				<notations>
+					<slur type="stop"/>
+				</notations>
+			</note>
+			<note>
+				<space/>
+			</note>
+			<note>
+				<pitch>
+					<step>G</step>
+					<octave>4</octave>
+				</pitch>
+				<voice>1</voice>
+				<duration>2</duration>
+				<type>quarter</type>
+				<lyric number="1">
+					<text>万</text>
+				</lyric>
+				<lyric number="2">
+					<text>lang</text>
+				</lyric>
+				<lyric number="3">
+					<text>万</text>
+				</lyric>
+				<lyric number="4">
+					<text>lang</text>
+				</lyric>
+				<notations>
+					<slur type="start"/>
+				</notations>
+			</note>
+			<note>
+				<space/>
+			</note>
+			<note>
+				<pitch>
+					<step>B</step>
+					<alter>-1</alter>
+					<octave>4</octave>
+				</pitch>
+				<voice>1</voice>
+				<duration>2</duration>
+				<type>quarter</type>
+				<notations>
+					<slur type="stop"/>
+				</notations>
+			</note>
+			<barline location="right">
+				<bar-style>light</bar-style>
+			</barline>
+		</measure>
+		<measure number="13">
+			<print new-system="yes"/>
+			<note>
+				<pitch>
+					<step>C</step>
+					<octave>5</octave>
+				</pitch>
+				<voice>1</voice>
+				<duration>6</duration>
+				<type>half</type>
+				<dot/>
+				<lyric number="1">
+					<text>岁!</text>
+				</lyric>
+				<lyric number="2">
+					<text>syne;</text>
+				</lyric>
+				<lyric number="3">
+					<text>岁!</text>
+				</lyric>
+				<lyric number="4">
+					<text>syne;</text>
+				</lyric>
+			<stem>down</stem>
+            </note>
+			<note>
+				<pitch>
+					<step>E</step>
+					<alter>-1</alter>
+					<octave>5</octave>
+				</pitch>
+				<voice>1</voice>
+				<duration>2</duration>
+				<type>quarter</type>
+				<lyric number="1">
+					<text>举</text>
+				</lyric>
+				<lyric number="2">
+					<text>We'll</text>
+				</lyric>
+				<lyric number="3">
+					<text>举</text>
+				</lyric>
+				<lyric number="4">
+					<text>We'll</text>
+				</lyric>
+			<stem>down</stem>
+            </note>
+			<note>
+				<space/>
+			</note>
+			<note>
+				<space/>
+			</note>
+			<barline location="right">
+				<bar-style>light</bar-style>
+			</barline>
+		</measure>
+		<measure number="14">
+			<note>
+				<pitch>
+					<step>B</step>
+					<alter>-1</alter>
+					<octave>4</octave>
+				</pitch>
+				<voice>1</voice>
+				<duration>3</duration>
+				<type>quarter</type>
+				<dot/>
+				<lyric number="1">
+					<text>杯</text>
+				</lyric>
+				<lyric number="2">
+					<text>take</text>
+				</lyric>
+				<lyric number="3">
+					<text>杯</text>
+				</lyric>
+				<lyric number="4">
+					<text>take</text>
+				</lyric>
+			</note>
+			<note>
+				<space/>
+			</note>
+			<note>
+				<pitch>
+					<step>G</step>
+					<octave>4</octave>
+				</pitch>
+				<voice>1</voice>
+				<duration>1</duration>
+				<type>eighth</type>
+				<lyric number="1">
+					<text>畅</text>
+				</lyric>
+				<lyric number="2">
+					<text>a</text>
+				</lyric>
+				<lyric number="3">
+					<text>畅</text>
+				</lyric>
+				<lyric number="4">
+					<text>a</text>
+				</lyric>
+			</note>
+			<note>
+				<space/>
+			</note>
+			<note>
+				<pitch>
+					<step>G</step>
+					<octave>4</octave>
+				</pitch>
+				<voice>1</voice>
+				<duration>2</duration>
+				<type>quarter</type>
+				<lyric number="1">
+					<text>饮,</text>
+				</lyric>
+				<lyric number="2">
+					<text>cup</text>
+				</lyric>
+				<lyric number="3">
+					<text>饮,</text>
+				</lyric>
+				<lyric number="4">
+					<text>cup</text>
+				</lyric>
+			</note>
+			<note>
+				<space/>
+			</note>
+			<note>
+				<pitch>
+					<step>E</step>
+					<alter>-1</alter>
+					<octave>4</octave>
+				</pitch>
+				<voice>1</voice>
+				<duration>2</duration>
+				<type>quarter</type>
+				<lyric number="1">
+					<text>同</text>
+				</lyric>
+				<lyric number="2">
+					<text>of</text>
+				</lyric>
+				<lyric number="3">
+					<text>同</text>
+				</lyric>
+				<lyric number="4">
+					<text>of</text>
+				</lyric>
+			</note>
+			<barline location="right">
+				<bar-style>light</bar-style>
+			</barline>
+		</measure>
+		<measure number="15">
+			<note>
+				<pitch>
+					<step>F</step>
+					<octave>4</octave>
+				</pitch>
+				<voice>1</voice>
+				<duration>3</duration>
+				<type>quarter</type>
+				<dot/>
+				<lyric number="1">
+					<text>声</text>
+				</lyric>
+				<lyric number="2">
+					<text>kind\-</text>
+				</lyric>
+				<lyric number="3">
+					<text>声</text>
+				</lyric>
+				<lyric number="4">
+					<text>kind\-</text>
+				</lyric>
+			</note>
+			<note>
+				<space/>
+			</note>
+			<note>
+				<pitch>
+					<step>E</step>
+					<alter>-1</alter>
+					<octave>4</octave>
+				</pitch>
+				<voice>1</voice>
+				<duration>1</duration>
+				<type>eighth</type>
+				<lyric number="1">
+					<text>歌</text>
+				</lyric>
+				<lyric number="2">
+					<text>ness</text>
+				</lyric>
+				<lyric number="3">
+					<text>歌</text>
+				</lyric>
+				<lyric number="4">
+					<text>ness</text>
+				</lyric>
+			</note>
+			<note>
+				<space/>
+			</note>
+			<note>
+				<space/>
+			</note>
+			<note>
+				<pitch>
+					<step>F</step>
+					<octave>4</octave>
+				</pitch>
+				<voice>1</voice>
+				<duration>2</duration>
+				<type>quarter</type>
+				<lyric number="1">
+					<text>颂</text>
+				</lyric>
+				<lyric number="2">
+					<text>yet,</text>
+				</lyric>
+				<lyric number="3">
+					<text>颂</text>
+				</lyric>
+				<lyric number="4">
+					<text>yet,</text>
+				</lyric>
+			</note>
+			<note>
+				<space/>
+			</note>
+			<note>
+				<pitch>
+					<step>G</step>
+					<octave>4</octave>
+				</pitch>
+				<voice>1</voice>
+				<duration>2</duration>
+				<type>quarter</type>
+				<lyric number="1">
+					<text>友</text>
+				</lyric>
+				<lyric number="2">
+					<text>for</text>
+				</lyric>
+				<lyric number="3">
+					<text>友</text>
+				</lyric>
+				<lyric number="4">
+					<text>for</text>
+				</lyric>
+			</note>
+			<barline location="right">
+				<bar-style>light</bar-style>
+			</barline>
+		</measure>
+		<measure number="16">
+			<print new-system="yes"/>
+			<note>
+				<pitch>
+					<step>E</step>
+					<alter>-1</alter>
+					<octave>4</octave>
+				</pitch>
+				<voice>1</voice>
+				<duration>3</duration>
+				<type>quarter</type>
+				<dot/>
+				<lyric number="1">
+					<text>谊</text>
+				</lyric>
+				<lyric number="2">
+					<text>auld</text>
+				</lyric>
+				<lyric number="3">
+					<text>谊</text>
+				</lyric>
+				<lyric number="4">
+					<text>auld</text>
+				</lyric>
+			</note>
+			<note>
+				<space/>
+			</note>
+			<note>
+				<pitch>
+					<step>C</step>
+					<octave>4</octave>
+				</pitch>
+				<voice>1</voice>
+				<duration>1</duration>
+				<type>eighth</type>
+				<lyric number="1">
+					<text>地</text>
+				</lyric>
+				<lyric number="2"/>
+				<lyric number="3">
+					<text>地</text>
+				</lyric>
+			</note>
+			<note>
+				<space/>
+			</note>
+			<note>
+				<space/>
+			</note>
+			<note>
+				<pitch>
+					<step>C</step>
+					<octave>4</octave>
+				</pitch>
+				<voice>1</voice>
+				<duration>2</duration>
+				<type>quarter</type>
+				<lyric number="1">
+					<text>久</text>
+				</lyric>
+				<lyric number="2">
+					<text>lang</text>
+				</lyric>
+				<lyric number="3">
+					<text>久</text>
+				</lyric>
+				<lyric number="4">
+					<text>lang</text>
+				</lyric>
+			</note>
+			<note>
+				<space/>
+			</note>
+			<note>
+				<pitch>
+					<step>B</step>
+					<alter>-1</alter>
+					<octave>3</octave>
+				</pitch>
+				<voice>1</voice>
+				<duration>2</duration>
+				<type>quarter</type>
+				<lyric number="1">
+					<text>天</text>
+				</lyric>
+				<lyric number="2"/>
+				<lyric number="3">
+					<text>天</text>
+				</lyric>
+			</note>
+			<barline location="right">
+				<bar-style>light</bar-style>
+			</barline>
+		</measure>
+		<measure number="17">
+			<note>
+				<pitch>
+					<step>E</step>
+					<alter>-1</alter>
+					<octave>4</octave>
+				</pitch>
+				<voice>1</voice>
+				<duration>6</duration>
+				<type>half</type>
+				<dot/>
+				<lyric number="1">
+					<text>长。</text>
+				</lyric>
+				<lyric number="2">
+					<text>syne.</text>
+				</lyric>
+				<lyric number="3">
+					<text>长。</text>
+				</lyric>
+				<lyric number="4">
+					<text>syne.</text>
+				</lyric>
+			</note>
+			<barline location="right">
+				<bar-style>light-heavy</bar-style>
+			</barline>
+		</measure>
+	</part>
+	<part id="P2">
+        <measure number="1">
+			<attributes>
+				<divisions>2</divisions>
+				<key>
+					<fifths>-3</fifths>
+				</key>
+				<time>
+					<beats>4</beats>
+					<beat-type>4</beat-type>
+				</time>
+				<clef/>
+			</attributes>
+			
+			
+			<direction>
+				<direction-type>
+					<metronome>
+						<beat-unit>quarter</beat-unit>
+						<per-minute>78</per-minute>
+					</metronome>
+				</direction-type>
+			</direction>
+            <direction>
+				<direction-type>
+					<words>中速 深情地</words>
+				</direction-type>
+			</direction>
+            <note>
+				<pitch>
+					<step>B</step>
+					<alter>-1</alter>
+					<octave>3</octave>
+				</pitch>
+				<voice>1</voice>
+				<duration>2</duration>
+				<type>quarter</type>
+				<lyric number="1">
+					<text>1.怎</text>
+				</lyric>
+				<lyric number="2">
+					<text>Should</text>
+				</lyric>
+				<lyric number="3">
+					<text>2.我</text>
+				</lyric>
+				<lyric number="4">
+					<text>And</text>
+				</lyric>
+			</note>
+			<barline location="right">
+				<bar-style>light</bar-style>
+			</barline>
+		</measure>
+        <measure number="2">
+			<note>
+				<pitch>
+					<step>E</step>
+					<alter>-1</alter>
+					<octave>4</octave>
+				</pitch>
+				<voice>1</voice>
+				<duration>3</duration>
+				<type>quarter</type>
+				<dot/>
+				<lyric number="1">
+					<text>能</text>
+				</lyric>
+				<lyric number="2">
+					<text>auld</text>
+				</lyric>
+				<lyric number="3">
+					<text>们</text>
+				</lyric>
+				<lyric number="4">
+					<text>here's</text>
+				</lyric>
+			</note>
+			<note>
+				<space/>
+			</note>
+			<note>
+				<pitch>
+					<step>E</step>
+					<alter>-1</alter>
+					<octave>4</octave>
+				</pitch>
+				<voice>1</voice>
+				<duration>1</duration>
+				<type>eighth</type>
+				<lyric number="1">
+					<text>忘</text>
+				</lyric>
+				<lyric number="2">
+					<text>ac\-</text>
+				</lyric>
+				<lyric number="3">
+					<text>往</text>
+				</lyric>
+				<lyric number="4">
+					<text>a</text>
+				</lyric>
+			</note>
+			<note>
+				<space/>
+			</note>
+			<note>
+				<pitch>
+					<step>E</step>
+					<alter>-1</alter>
+					<octave>4</octave>
+				</pitch>
+				<voice>1</voice>
+				<duration>2</duration>
+				<type>quarter</type>
+				<lyric number="1">
+					<text>记</text>
+				</lyric>
+				<lyric number="2">
+					<text>quaint\-</text>
+				</lyric>
+				<lyric number="3">
+					<text>日</text>
+				</lyric>
+				<lyric number="4">
+					<text>hand,</text>
+				</lyric>
+			</note>
+			<note>
+				<space/>
+			</note>
+			<note>
+				<space/>
+			</note>
+			<note>
+				<pitch>
+					<step>G</step>
+					<octave>4</octave>
+				</pitch>
+				<voice>1</voice>
+				<duration>2</duration>
+				<type>quarter</type>
+				<lyric number="1">
+					<text>旧</text>
+				</lyric>
+				<lyric number="2">
+					<text>ance</text>
+				</lyric>
+				<lyric number="3">
+					<text>情</text>
+				</lyric>
+				<lyric number="4">
+					<text>my</text>
+				</lyric>
+			</note>
+			<barline location="right">
+				<bar-style>light</bar-style>
+			</barline>
+		</measure>
+        <measure number="3">
+			<note>
+				<pitch>
+					<step>F</step>
+					<octave>4</octave>
+				</pitch>
+				<voice>1</voice>
+				<duration>3</duration>
+				<type>quarter</type>
+				<dot/>
+				<lyric number="1">
+					<text>日</text>
+				</lyric>
+				<lyric number="2">
+					<text>be</text>
+				</lyric>
+				<lyric number="3">
+					<text>意</text>
+				</lyric>
+				<lyric number="4">
+					<text>trusty</text>
+				</lyric>
+			</note>
+			<note>
+				<space/>
+			</note>
+			<note>
+				<pitch>
+					<step>E</step>
+					<alter>-1</alter>
+					<octave>4</octave>
+				</pitch>
+				<voice>1</voice>
+				<duration>1</duration>
+				<type>eighth</type>
+				<lyric number="1">
+					<text>朋</text>
+				</lyric>
+				<lyric number="2">
+					<text>for\-</text>
+				</lyric>
+				<lyric number="3">
+					<text>相</text>
+				</lyric>
+				<lyric number="4">
+					<text>frien',</text>
+				</lyric>
+			</note>
+			<note>
+				<space/>
+			</note>
+			<note>
+				<pitch>
+					<step>F</step>
+					<octave>4</octave>
+				</pitch>
+				<voice>1</voice>
+				<duration>2</duration>
+				<type>quarter</type>
+				<lyric number="1">
+					<text>友,</text>
+				</lyric>
+				<lyric number="2">
+					<text>got,</text>
+				</lyric>
+				<lyric number="3">
+					<text>投,</text>
+				</lyric>
+			</note>
+			<note>
+				<space/>
+			</note>
+			<note>
+				<pitch>
+					<step>G</step>
+					<octave>4</octave>
+				</pitch>
+				<voice>1</voice>
+				<duration>2</duration>
+				<type>quarter</type>
+				<lyric number="1">
+					<text>心</text>
+				</lyric>
+				<lyric number="2">
+					<text>and</text>
+				</lyric>
+				<lyric number="3">
+					<text>让</text>
+				</lyric>
+				<lyric number="4">
+					<text>And</text>
+				</lyric>
+			</note>
+			<barline location="right">
+				<bar-style>light</bar-style>
+			</barline>
+		</measure>
+        <measure number="4">
+			<print new-system="yes"/>
+			<note>
+				<pitch>
+					<step>E</step>
+					<alter>-1</alter>
+					<octave>4</octave>
+				</pitch>
+				<voice>1</voice>
+				<duration>3</duration>
+				<type>quarter</type>
+				<dot/>
+				<lyric number="1">
+					<text>中</text>
+				</lyric>
+				<lyric number="2">
+					<text>ne\-</text>
+				</lyric>
+				<lyric number="3">
+					<text>我</text>
+				</lyric>
+				<lyric number="4">
+					<text>gie's</text>
+				</lyric>
+			</note>
+			<note>
+				<space/>
+			</note>
+			<note>
+				<pitch>
+					<step>E</step>
+					<alter>-1</alter>
+					<octave>4</octave>
+				</pitch>
+				<voice>1</voice>
+				<duration>1</duration>
+				<type>eighth</type>
+				<lyric number="1">
+					<text>能</text>
+				</lyric>
+				<lyric number="2">
+					<text>ver</text>
+				</lyric>
+				<lyric number="3">
+					<text>们</text>
+				</lyric>
+				<lyric number="4">
+					<text>a</text>
+				</lyric>
+			</note>
+			<note>
+				<space/>
+			</note>
+			<note>
+				<pitch>
+					<step>G</step>
+					<octave>4</octave>
+				</pitch>
+				<voice>1</voice>
+				<duration>2</duration>
+				<type>quarter</type>
+				<lyric number="1">
+					<text>不</text>
+				</lyric>
+				<lyric number="2">
+					<text>brought</text>
+				</lyric>
+				<lyric number="3">
+					<text>紧</text>
+				</lyric>
+				<lyric number="4">
+					<text>hand</text>
+				</lyric>
+			</note>
+			<note>
+				<space/>
+			</note>
+			<note>
+				<space/>
+			</note>
+			<note>
+				<space/>
+			</note>
+			<note>
+				<pitch>
+					<step>B</step>
+					<alter>-1</alter>
+					<octave>4</octave>
+				</pitch>
+				<voice>1</voice>
+				<duration>2</duration>
+				<type>quarter</type>
+				<lyric number="1">
+					<text>怀</text>
+				</lyric>
+				<lyric number="2">
+					<text>to</text>
+				</lyric>
+				<lyric number="3">
+					<text>握</text>
+				</lyric>
+				<lyric number="4">
+					<text>of</text>
+				</lyric>
+			</note>
+			<barline location="right">
+				<bar-style>light</bar-style>
+			</barline>
+		</measure>
+        <measure number="5">
+			<note>
+				<pitch>
+					<step>C</step>
+					<octave>5</octave>
+				</pitch>
+				<voice>1</voice>
+				<duration>6</duration>
+				<type>half</type>
+				<dot/>
+				<lyric number="1">
+					<text>想?</text>
+				</lyric>
+				<lyric number="2">
+					<text>mine?</text>
+				</lyric>
+				<lyric number="3">
+					<text>手,</text>
+				</lyric>
+				<lyric number="4">
+					<text>thine.</text>
+				</lyric>
+			<stem>down</stem>
+            </note>
+			<note>
+				<pitch>
+					<step>C</step>
+					<octave>5</octave>
+				</pitch>
+				<voice>1</voice>
+				<duration>2</duration>
+				<type>quarter</type>
+				<lyric number="1">
+					<text>旧</text>
+				</lyric>
+				<lyric number="2">
+					<text>Should</text>
+				</lyric>
+				<lyric number="3">
+					<text>让</text>
+				</lyric>
+				<lyric number="4">
+					<text>We'll</text>
+				</lyric>
+			<stem>down</stem>
+            </note>
+			<note>
+				<space/>
+			</note>
+			<barline location="right">
+				<bar-style>light</bar-style>
+			</barline>
+		</measure>
+        <measure number="6">
+			<note>
+				<pitch>
+					<step>B</step>
+					<alter>-1</alter>
+					<octave>4</octave>
+				</pitch>
+				<voice>1</voice>
+				<duration>3</duration>
+				<type>quarter</type>
+				<dot/>
+				<lyric number="1">
+					<text>日</text>
+				</lyric>
+				<lyric number="2">
+					<text>auld</text>
+				</lyric>
+				<lyric number="3">
+					<text>我</text>
+				</lyric>
+				<lyric number="4">
+					<text>take</text>
+				</lyric>
+			</note>
+			<note>
+				<space/>
+			</note>
+			<note>
+				<pitch>
+					<step>G</step>
+					<octave>4</octave>
+				</pitch>
+				<voice>1</voice>
+				<duration>1</duration>
+				<type>eighth</type>
+				<lyric number="1">
+					<text>朋</text>
+				</lyric>
+				<lyric number="2">
+					<text>ac\-</text>
+				</lyric>
+				<lyric number="3">
+					<text>们</text>
+				</lyric>
+				<lyric number="4">
+					<text>a</text>
+				</lyric>
+			</note>
+			<note>
+				<space/>
+			</note>
+			<note>
+				<pitch>
+					<step>G</step>
+					<octave>4</octave>
+				</pitch>
+				<voice>1</voice>
+				<duration>2</duration>
+				<type>quarter</type>
+				<lyric number="1">
+					<text>友</text>
+				</lyric>
+				<lyric number="2">
+					<text>quaint-</text>
+				</lyric>
+				<lyric number="3">
+					<text>来</text>
+				</lyric>
+				<lyric number="4">
+					<text>cup</text>
+				</lyric>
+			</note>
+			<note>
+				<space/>
+			</note>
+			<note>
+				<space/>
+			</note>
+			<note>
+				<space/>
+			</note>
+			<note>
+				<pitch>
+					<step>E</step>
+					<alter>-1</alter>
+					<octave>4</octave>
+				</pitch>
+				<voice>1</voice>
+				<duration>2</duration>
+				<type>quarter</type>
+				<lyric number="1">
+					<text>岂</text>
+				</lyric>
+				<lyric number="2">
+					<text>ance</text>
+				</lyric>
+				<lyric number="3">
+					<text>举</text>
+				</lyric>
+				<lyric number="4">
+					<text>of</text>
+				</lyric>
+			</note>
+			<barline location="right">
+				<bar-style>light</bar-style>
+			</barline>
+		</measure>
+        <measure number="7">
+			<print new-system="yes"/>
+			<note>
+				<pitch>
+					<step>F</step>
+					<octave>4</octave>
+				</pitch>
+				<voice>1</voice>
+				<duration>3</duration>
+				<type>quarter</type>
+				<dot/>
+				<lyric number="1">
+					<text>能</text>
+				</lyric>
+				<lyric number="2">
+					<text>be</text>
+				</lyric>
+				<lyric number="3">
+					<text>杯</text>
+				</lyric>
+				<lyric number="4">
+					<text>kind\-</text>
+				</lyric>
+			</note>
+			<note>
+				<pitch>
+					<step>E</step>
+					<alter>-1</alter>
+					<octave>4</octave>
+				</pitch>
+				<voice>1</voice>
+				<duration>1</duration>
+				<type>eighth</type>
+				<lyric number="1">
+					<text>相</text>
+				</lyric>
+				<lyric number="2">
+					<text>for\-</text>
+				</lyric>
+				<lyric number="3">
+					<text>畅</text>
+				</lyric>
+				<lyric number="4">
+					<text>ness</text>
+				</lyric>
+			</note>
+			<note>
+				<space/>
+			</note>
+			<note>
+				<pitch>
+					<step>F</step>
+					<octave>4</octave>
+				</pitch>
+				<voice>1</voice>
+				<duration>2</duration>
+				<type>quarter</type>
+				<lyric number="1">
+					<text>忘,</text>
+				</lyric>
+				<lyric number="2">
+					<text>got,</text>
+				</lyric>
+				<lyric number="3">
+					<text>饮,</text>
+				</lyric>
+				<lyric number="4">
+					<text>yet,</text>
+				</lyric>
+			</note>
+			<note>
+				<space/>
+			</note>
+			<note>
+				<pitch>
+					<step>G</step>
+					<octave>4</octave>
+				</pitch>
+				<voice>1</voice>
+				<duration>2</duration>
+				<type>quarter</type>
+				<lyric number="1">
+					<text>友</text>
+				</lyric>
+				<lyric number="2">
+					<text>and</text>
+				</lyric>
+				<lyric number="3">
+					<text>友</text>
+				</lyric>
+				<lyric number="4">
+					<text>For</text>
+				</lyric>
+			</note>
+			<barline location="right">
+				<bar-style>light</bar-style>
+			</barline>
+		</measure>
+        <measure number="8">
+			<note>
+				<pitch>
+					<step>E</step>
+					<alter>-1</alter>
+					<octave>4</octave>
+				</pitch>
+				<voice>1</voice>
+				<duration>3</duration>
+				<type>quarter</type>
+				<dot/>
+				<lyric number="1">
+					<text>谊</text>
+				</lyric>
+				<lyric number="2">
+					<text>days</text>
+				</lyric>
+				<lyric number="3">
+					<text>谊</text>
+				</lyric>
+				<lyric number="4">
+					<text>auld</text>
+				</lyric>
+			</note>
+			<note>
+				<pitch>
+					<step>C</step>
+					<octave>4</octave>
+				</pitch>
+				<voice>1</voice>
+				<duration>1</duration>
+				<type>eighth</type>
+				<lyric number="1">
+					<text>地</text>
+				</lyric>
+				<lyric number="2">
+					<text>of</text>
+				</lyric>
+				<lyric number="3">
+					<text>地</text>
+				</lyric>
+				<lyric number="4">
+					<text>lang</text>
+				</lyric>
+			</note>
+			<note>
+				<space/>
+			</note>
+			<note>
+				<pitch>
+					<step>C</step>
+					<octave>4</octave>
+				</pitch>
+				<voice>1</voice>
+				<duration>2</duration>
+				<type>quarter</type>
+				<lyric number="1">
+					<text>久</text>
+				</lyric>
+				<lyric number="2">
+					<text>auld</text>
+				</lyric>
+				<lyric number="3">
+					<text>久</text>
+				</lyric>
+			</note>
+			<note>
+				<space/>
+			</note>
+			<note>
+				<pitch>
+					<step>B</step>
+					<alter>-1</alter>
+					<octave>3</octave>
+				</pitch>
+				<voice>1</voice>
+				<duration>2</duration>
+				<type>quarter</type>
+				<lyric number="1">
+					<text>天</text>
+				</lyric>
+				<lyric number="2">
+					<text>lang</text>
+				</lyric>
+				<lyric number="3">
+					<text>天</text>
+				</lyric>
+			</note>
+			<barline location="right">
+				<bar-style>light</bar-style>
+			</barline>
+		</measure>
+		<measure number="9">
+			
+			
+			
+			<note>
+				<pitch>
+					<step>E</step>
+					<alter>-1</alter>
+					<octave>4</octave>
+				</pitch>
+				<voice>1</voice>
+				<duration>6</duration>
+				<type>half</type>
+				<dot/>
+			<lyric number="1">
+					<text>长;</text>
+				</lyric>
+                <lyric number="2">
+					<text>syne?</text>
+				</lyric>
+                <lyric number="3">
+					<text>长;</text>
+				</lyric>
+                <lyric number="4">
+					<text>syne?</text>
+				</lyric>
+            </note>
+			<note>
+				<pitch>
+					<step>A</step>
+					<alter>-1</alter>
+					<octave>4</octave>
+				</pitch>
+				<voice>1</voice>
+				<duration>2</duration>
+				<type>quarter</type>
+			<lyric number="1">
+					<text>友</text>
+				</lyric>
+                <lyric number="2">
+					<text>For</text>
+				</lyric>
+                <lyric number="3">
+					<text>友</text>
+				</lyric>
+                <lyric number="4">
+					<text>For</text>
+				</lyric>
+            </note>
+			<barline location="right">
+				<bar-style>light</bar-style>
+			</barline>
+		</measure>
+		<measure number="10">
+			<note>
+				<pitch>
+					<step>G</step>
+					<octave>4</octave>
+				</pitch>
+				<voice>1</voice>
+				<duration>3</duration>
+				<type>quarter</type>
+				<dot/>
+				<notations>
+					<slur type="start"/>
+				</notations>
+			<lyric number="1">
+					<text>谊</text>
+				</lyric>
+                <lyric number="2">
+					<text>auld</text>
+				</lyric>
+                <lyric number="3">
+					<text>谊</text>
+				</lyric>
+                <lyric number="4">
+					<text>auld</text>
+				</lyric>
+            </note>
+			<note>
+				<space/>
+			</note>
+			<note>
+				<pitch>
+					<step>E</step>
+					<alter>-1</alter>
+					<octave>4</octave>
+				</pitch>
+				<voice>1</voice>
+				<duration>1</duration>
+				<type>eighth</type>
+				<notations>
+					<slur type="stop"/>
+				</notations>
+			</note>
+			<note>
+				<space/>
+			</note>
+			<note>
+				<pitch>
+					<step>E</step>
+					<alter>-1</alter>
+					<octave>4</octave>
+				</pitch>
+				<voice>1</voice>
+				<duration>4</duration>
+				<type>half</type>
+			<lyric number="1">
+					<text>万</text>
+				</lyric>
+                <lyric number="2">
+					<text>lang</text>
+				</lyric>
+                <lyric number="3">
+					<text>万</text>
+				</lyric>
+                <lyric number="4">
+					<text>lang</text>
+				</lyric>
+            </note>
+			<barline location="right">
+				<bar-style>light</bar-style>
+			</barline>
+		</measure>
+		<measure number="11">
+			<note>
+				<pitch>
+					<step>D</step>
+					<octave>4</octave>
+				</pitch>
+				<voice>1</voice>
+				<duration>3</duration>
+				<type>quarter</type>
+				<dot/>
+				<notations>
+					<slur type="start"/>
+				</notations>
+			<lyric number="1">
+					<text>岁,</text>
+				</lyric>
+                <lyric number="2">
+					<text>syne,</text>
+				</lyric>
+                <lyric number="3">
+					<text>岁,</text>
+				</lyric>
+                <lyric number="4">
+					<text>syne,</text>
+				</lyric>
+            </note>
+			<note>
+				<space/>
+			</note>
+			<note>
+				<space/>
+			</note>
+			<note>
+				<space/>
+			</note>
+			<note>
+				<pitch>
+					<step>C</step>
+					<octave>4</octave>
+				</pitch>
+				<voice>1</voice>
+				<duration>1</duration>
+				<type>eighth</type>
+			<lyric number="1"/>
+                <lyric number="2">
+					<text>my</text>
+				</lyric>
+                <lyric number="3"/>
+                <lyric number="4">
+					<text>my</text>
+				</lyric>
+            </note>
+			<note>
+				<space/>
+			</note>
+			<note>
+				<pitch>
+					<step>D</step>
+					<octave>4</octave>
+				</pitch>
+				<voice>1</voice>
+				<duration>2</duration>
+				<type>quarter</type>
+				<notations>
+					<slur type="stop"/>
+				</notations>
+			<lyric number="1"/>
+                <lyric number="2">
+					<text>dear,</text>
+				</lyric>
+                <lyric number="3"/>
+                <lyric number="4">
+					<text>dear,</text>
+				</lyric>
+            </note>
+			<note>
+				<space/>
+			</note>
+			<note>
+				<space/>
+			</note>
+			<note>
+				<space/>
+			</note>
+			<note>
+				<pitch>
+					<step>A</step>
+					<alter>-1</alter>
+					<octave>4</octave>
+				</pitch>
+				<voice>1</voice>
+				<duration>2</duration>
+				<type>quarter</type>
+			<lyric number="1">
+					<text>友</text>
+				</lyric>
+                <lyric number="2">
+					<text>for</text>
+				</lyric>
+                <lyric number="3">
+					<text>友</text>
+				</lyric>
+                <lyric number="4">
+					<text>for</text>
+				</lyric>
+            </note>
+			<barline location="right">
+				<bar-style>light</bar-style>
+			</barline>
+		</measure>
+		<measure number="12">
+			<note>
+				<pitch>
+					<step>G</step>
+					<octave>4</octave>
+				</pitch>
+				<voice>1</voice>
+				<duration>3</duration>
+				<type>quarter</type>
+				<dot/>
+				<notations>
+					<slur type="start"/>
+				</notations>
+			<lyric number="1">
+					<text>谊</text>
+				</lyric>
+                <lyric number="2">
+					<text>auld</text>
+				</lyric>
+                <lyric number="3">
+					<text>谊</text>
+				</lyric>
+                <lyric number="4">
+					<text>auld</text>
+				</lyric>
+            </note>
+			<note>
+				<space/>
+			</note>
+			<note>
+				<pitch>
+					<step>E</step>
+					<alter>-1</alter>
+					<octave>4</octave>
+				</pitch>
+				<voice>1</voice>
+				<duration>1</duration>
+				<type>eighth</type>
+				<notations>
+					<slur type="stop"/>
+				</notations>
+			</note>
+			<note>
+				<space/>
+			</note>
+			<note>
+				<pitch>
+					<step>E</step>
+					<alter>-1</alter>
+					<octave>4</octave>
+				</pitch>
+				<voice>1</voice>
+				<duration>2</duration>
+				<type>quarter</type>
+				<notations>
+					<slur type="start"/>
+				</notations>
+			<lyric number="1">
+					<text>万</text>
+				</lyric>
+                <lyric number="2">
+					<text>lang</text>
+				</lyric>
+                <lyric number="3">
+					<text>万</text>
+				</lyric>
+                <lyric number="4">
+					<text>lang</text>
+				</lyric>
+            </note>
+			<note>
+				<space/>
+			</note>
+			<note>
+				<pitch>
+					<step>G</step>
+					<octave>4</octave>
+				</pitch>
+				<voice>1</voice>
+				<duration>2</duration>
+				<type>quarter</type>
+				<notations>
+					<slur type="stop"/>
+				</notations>
+			</note>
+			<barline location="right">
+				<bar-style>light</bar-style>
+			</barline>
+		</measure>
+		<measure number="13">
+			<print new-system="yes"/>
+			<note>
+				<pitch>
+					<step>A</step>
+					<alter>-1</alter>
+					<octave>4</octave>
+				</pitch>
+				<voice>1</voice>
+				<duration>6</duration>
+				<type>half</type>
+				<dot/>
+			<lyric number="1">
+					<text>岁!</text>
+				</lyric>
+                <lyric number="2">
+					<text>syne;</text>
+				</lyric>
+                <lyric number="3">
+					<text>岁!</text>
+				</lyric>
+                <lyric number="4">
+					<text>syne;</text>
+				</lyric>
+            </note>
+			<note>
+				<pitch>
+					<step>C</step>
+					<octave>5</octave>
+				</pitch>
+				<voice>1</voice>
+				<duration>2</duration>
+				<type>quarter</type>
+			<lyric number="1">
+					<text>举</text>
+				</lyric>
+                <lyric number="2">
+					<text>We'll</text>
+				</lyric>
+                <lyric number="3">
+					<text>举</text>
+				</lyric>
+                <lyric number="4">
+					<text>We'll</text>
+				</lyric>
+            <stem>down</stem>
+            </note>
+			<note>
+				<space/>
+			</note>
+			<note>
+				<space/>
+			</note>
+			<barline location="right">
+				<bar-style>light</bar-style>
+			</barline>
+		</measure>
+		<measure number="14">
+			<note>
+				<pitch>
+					<step>G</step>
+					<octave>4</octave>
+				</pitch>
+				<voice>1</voice>
+				<duration>3</duration>
+				<type>quarter</type>
+				<dot/>
+			<lyric number="1">
+					<text>杯</text>
+				</lyric>
+                <lyric number="2">
+					<text>take</text>
+				</lyric>
+                <lyric number="3">
+					<text>杯</text>
+				</lyric>
+                <lyric number="4">
+					<text>take</text>
+				</lyric>
+            </note>
+			<note>
+				<space/>
+			</note>
+			<note>
+				<pitch>
+					<step>E</step>
+					<alter>-1</alter>
+					<octave>4</octave>
+				</pitch>
+				<voice>1</voice>
+				<duration>1</duration>
+				<type>eighth</type>
+			<lyric number="1">
+					<text>畅</text>
+				</lyric>
+                <lyric number="2">
+					<text>a</text>
+				</lyric>
+                <lyric number="3">
+					<text>畅</text>
+				</lyric>
+                <lyric number="4">
+					<text>a</text>
+				</lyric>
+            </note>
+			<note>
+				<space/>
+			</note>
+			<note>
+				<pitch>
+					<step>E</step>
+					<alter>-1</alter>
+					<octave>4</octave>
+				</pitch>
+				<voice>1</voice>
+				<duration>2</duration>
+				<type>quarter</type>
+			<lyric number="1">
+					<text>饮,</text>
+				</lyric>
+                <lyric number="2">
+					<text>cup</text>
+				</lyric>
+                <lyric number="3">
+					<text>饮,</text>
+				</lyric>
+                <lyric number="4">
+					<text>cup</text>
+				</lyric>
+            </note>
+			<note>
+				<space/>
+			</note>
+			<note>
+				<pitch>
+					<step>E</step>
+					<alter>-1</alter>
+					<octave>4</octave>
+				</pitch>
+				<voice>1</voice>
+				<duration>2</duration>
+				<type>quarter</type>
+			<lyric number="1">
+					<text>同</text>
+				</lyric>
+                <lyric number="2">
+					<text>of</text>
+				</lyric>
+                <lyric number="3">
+					<text>同</text>
+				</lyric>
+                <lyric number="4">
+					<text>of</text>
+				</lyric>
+            </note>
+			<barline location="right">
+				<bar-style>light</bar-style>
+			</barline>
+		</measure>
+		<measure number="15">
+			<note>
+				<pitch>
+					<step>D</step>
+					<octave>4</octave>
+				</pitch>
+				<voice>1</voice>
+				<duration>3</duration>
+				<type>quarter</type>
+				<dot/>
+			<lyric number="1">
+					<text>声</text>
+				</lyric>
+                <lyric number="2">
+					<text>kind\-</text>
+				</lyric>
+                <lyric number="3">
+					<text>声</text>
+				</lyric>
+                <lyric number="4">
+					<text>kind\-</text>
+				</lyric>
+            </note>
+			<note>
+				<space/>
+			</note>
+			<note>
+				<pitch>
+					<step>C</step>
+					<octave>4</octave>
+				</pitch>
+				<voice>1</voice>
+				<duration>1</duration>
+				<type>eighth</type>
+			<lyric number="1">
+					<text>歌</text>
+				</lyric>
+                <lyric number="2">
+					<text>ness</text>
+				</lyric>
+                <lyric number="3">
+					<text>歌</text>
+				</lyric>
+                <lyric number="4">
+					<text>ness</text>
+				</lyric>
+            </note>
+			<note>
+				<space/>
+			</note>
+			<note>
+				<space/>
+			</note>
+			<note>
+				<pitch>
+					<step>D</step>
+					<octave>4</octave>
+				</pitch>
+				<voice>1</voice>
+				<duration>2</duration>
+				<type>quarter</type>
+			<lyric number="1">
+					<text>颂</text>
+				</lyric>
+                <lyric number="2">
+					<text>yet,</text>
+				</lyric>
+                <lyric number="3">
+					<text>颂</text>
+				</lyric>
+                <lyric number="4">
+					<text>yet,</text>
+				</lyric>
+            </note>
+			<note>
+				<space/>
+			</note>
+			<note>
+				<pitch>
+					<step>B</step>
+					<alter>-1</alter>
+					<octave>3</octave>
+				</pitch>
+				<voice>1</voice>
+				<duration>2</duration>
+				<type>quarter</type>
+			<lyric number="1">
+					<text>友</text>
+				</lyric>
+                <lyric number="2">
+					<text>for</text>
+				</lyric>
+                <lyric number="3">
+					<text>友</text>
+				</lyric>
+                <lyric number="4">
+					<text>for</text>
+				</lyric>
+            </note>
+			<barline location="right">
+				<bar-style>light</bar-style>
+			</barline>
+		</measure>
+		<measure number="16">
+			<print new-system="yes"/>
+			<note>
+				<pitch>
+					<step>C</step>
+					<octave>4</octave>
+				</pitch>
+				<voice>1</voice>
+				<duration>3</duration>
+				<type>quarter</type>
+				<dot/>
+			<lyric number="1">
+					<text>谊</text>
+				</lyric>
+                <lyric number="2">
+					<text>auld</text>
+				</lyric>
+                <lyric number="3">
+					<text>谊</text>
+				</lyric>
+                <lyric number="4">
+					<text>auld</text>
+				</lyric>
+            </note>
+			<note>
+				<space/>
+			</note>
+			<note>
+				<pitch>
+					<step>C</step>
+					<octave>4</octave>
+				</pitch>
+				<voice>1</voice>
+				<duration>1</duration>
+				<type>eighth</type>
+			<lyric number="1">
+					<text>地</text>
+				</lyric>
+                <lyric number="2"/>
+                <lyric number="3">
+					<text>地</text>
+				</lyric>
+            </note>
+			<note>
+				<space/>
+			</note>
+			<note>
+				<space/>
+			</note>
+			<note>
+				<pitch>
+					<step>C</step>
+					<octave>4</octave>
+				</pitch>
+				<voice>1</voice>
+				<duration>2</duration>
+				<type>quarter</type>
+			<lyric number="1">
+					<text>久</text>
+				</lyric>
+                <lyric number="2">
+					<text>lang</text>
+				</lyric>
+                <lyric number="3">
+					<text>久</text>
+				</lyric>
+                <lyric number="4">
+					<text>lang</text>
+				</lyric>
+            </note>
+			<note>
+				<space/>
+			</note>
+			<note>
+				<pitch>
+					<step>B</step>
+					<alter>-1</alter>
+					<octave>3</octave>
+				</pitch>
+				<voice>1</voice>
+				<duration>2</duration>
+				<type>quarter</type>
+			<lyric number="1">
+					<text>天</text>
+				</lyric>
+                <lyric number="2"/>
+                <lyric number="3">
+					<text>天</text>
+				</lyric>
+            </note>
+			<barline location="right">
+				<bar-style>light</bar-style>
+			</barline>
+		</measure>
+		<measure number="17">
+			<note>
+				<pitch>
+					<step>E</step>
+					<alter>-1</alter>
+					<octave>4</octave>
+				</pitch>
+				<voice>1</voice>
+				<duration>6</duration>
+				<type>half</type>
+				<dot/>
+			<lyric number="1">
+					<text>长。</text>
+				</lyric>
+                <lyric number="2">
+					<text>syne.</text>
+				</lyric>
+                <lyric number="3">
+					<text>长。</text>
+				</lyric>
+                <lyric number="4">
+					<text>syne.</text>
+				</lyric>
+            </note>
+			<barline location="right">
+				<bar-style>light-heavy</bar-style>
+			</barline>
+		</measure>
+	</part>
+	<tags>
+		<timegap>
+			<anchor measure="0" note="0" part="0" staff="0" system="0"/>
+			<place>
+				<hplacement value="0"/>
+				<vplacement value="1"/>
+				<xoffset value="0"/>
+				<yoffset value="0"/>
+			</place>
+			<values>
+				<item den="4" num="23" whole="0"/>
+				<item den="4" num="16" whole="0"/>
+			</values>
+		</timegap>
+		<cen-same-lyric></cen-same-lyric>
+	</tags>
+	<comments>
+		<comment>版本:0.7.2。时间:2021/4/16下午3:07:07。</comment>
+		<comment>版本:0.7.15。时间:2021/4/29上午10:57:50。</comment>
+		<comment>版本:0.8.4。时间:2021/7/15上午9:14:08。</comment>
+		<comment>版本:0.10.22。时间:2022/5/19 10:32:48。</comment>
+		<comment>版本:0.10.22。时间:2022/5/19 17:49:56。</comment>
+	</comments>
+</score-partwise>

+ 516 - 0
docs/musicxml/单声部.xml

@@ -0,0 +1,516 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<score-partwise fill-line="1" page-width="1440" version="3.1">
+	<work>
+		<work-title>早上好</work-title>
+	</work>
+	<identification>
+		<creator type="composer">乌干达民歌</creator>
+		<encoding>
+			<protocol-version>1</protocol-version>
+			<software>evmusic_software-0.8.2</software>
+		</encoding>
+	</identification>
+	<defaults>
+		<lyric-font font-size="40.000000" font-style="normal" font-weight="normal"/>
+	</defaults>
+	<credit>
+		<credit-words font-size="12" justify="right" valign="bottom">乌干达民歌</credit-words>
+	</credit>
+	<credit>
+		<credit-words font-size="12" justify="left" valign="bottom"/>
+	</credit>
+	<credit>
+		<credit-words font-size="24" justify="center" valign="top">早上好</credit-words>
+	</credit>
+	<credit>
+		<credit-words font-size="14" justify="center" valign="top"/>
+	</credit>
+	<credit>
+		<credit-words font-size="12" justify="center" valign="bottom"/>
+	</credit>
+	<systems>
+		<system number="1">
+			<part begin-measure="1" end-measure="4" id="P1"/>
+		</system>
+		<system number="2">
+			<part begin-measure="5" end-measure="8" id="P1"/>
+		</system>
+	</systems>
+	<part-list>
+		<score-part id="P1">
+			<score-instrument id="P1-I1"/>
+			<midi-device id="P1-I1" port="1"/>
+			<midi-instrument id="P1-I1">
+				<midi-channel>1</midi-channel>
+				<midi-program>1</midi-program>
+				<volume>78.740196</volume>
+				<pan>0</pan>
+			</midi-instrument>
+		</score-part>
+	</part-list>
+	<part id="P1">
+		<measure number="1">
+			<attributes>
+				<divisions>4</divisions>
+				<key>
+					<fifths>-1</fifths>
+				</key>
+				<time>
+					<beats>3</beats>
+					<beat-type>4</beat-type>
+				</time>
+
+				<clef/>
+			</attributes>
+			<direction>
+				<direction-type>
+					<metronome>
+						<beat-unit>quarter</beat-unit>
+						<per-minute>116</per-minute>
+					</metronome>
+				</direction-type>
+			</direction>
+			<note>
+				<space/>
+			</note>
+			<note>
+				<pitch>
+					<step>C</step>
+					<octave>5</octave>
+				</pitch>
+				<duration>4</duration>
+				<type>quarter</type>
+				<lyric>
+					<text>Mor-</text>
+				</lyric>
+			<stem>down</stem>
+            </note>
+			<note>
+				<space/>
+			</note>
+			<note>
+				<pitch>
+					<step>A</step>
+					<octave>4</octave>
+				</pitch>
+				<duration>4</duration>
+				<type>quarter</type>
+				<lyric>
+					<text>ing</text>
+				</lyric>
+			</note>
+			<note>
+				<rest/>
+				<duration>2</duration>
+				<type>eighth</type>
+			</note>
+			<note>
+				<pitch>
+					<step>F</step>
+					<octave>4</octave>
+				</pitch>
+				<duration>2</duration>
+				<type>eighth</type>
+				<lyric>
+					<text>请</text>
+				</lyric>
+			</note>
+			<barline location="right">
+				<bar-style>light</bar-style>
+			</barline>
+		</measure>
+		<measure number="2">
+			<note>
+				<pitch>
+					<step>B</step>
+					<alter>-1</alter>
+					<octave>4</octave>
+				</pitch>
+				<duration>2</duration>
+				<type>eighth</type>
+				<lyric>
+					<text>说</text>
+				</lyric>
+			<beam number="1">begin</beam>
+            </note>
+			<note>
+				<pitch>
+					<step>B</step>
+					<alter>-1</alter>
+					<octave>4</octave>
+				</pitch>
+				<duration>2</duration>
+				<type>eighth</type>
+				<lyric>
+					<text>声</text>
+				</lyric>
+			<beam number="1">end</beam>
+            </note>
+			<note>
+				<pitch>
+					<step>G</step>
+					<octave>4</octave>
+				</pitch>
+				<duration>8</duration>
+				<type>half</type>
+				<lyric>
+					<text>早,</text>
+				</lyric>
+			</note>
+			<barline location="right">
+				<bar-style>light</bar-style>
+			</barline>
+		</measure>
+		<measure number="3">
+			<note>
+				<pitch>
+					<step>C</step>
+					<octave>5</octave>
+				</pitch>
+				<duration>4</duration>
+				<type>quarter</type>
+				<lyric>
+					<text>Mor-</text>
+				</lyric>
+			<stem>down</stem>
+            </note>
+			<note>
+				<space/>
+			</note>
+			<note>
+				<pitch>
+					<step>A</step>
+					<octave>4</octave>
+				</pitch>
+				<duration>4</duration>
+				<type>quarter</type>
+				<lyric>
+					<text>ing</text>
+				</lyric>
+			</note>
+			<note>
+				<space/>
+			</note>
+			<note>
+				<rest/>
+				<duration>2</duration>
+				<type>eighth</type>
+			</note>
+			<note>
+				<pitch>
+					<step>F</step>
+					<octave>4</octave>
+				</pitch>
+				<duration>2</duration>
+				<type>eighth</type>
+				<lyric>
+					<text>请</text>
+				</lyric>
+			</note>
+			<barline location="right">
+				<bar-style>light</bar-style>
+			</barline>
+		</measure>
+		<measure number="4">
+			<note>
+				<pitch>
+					<step>G</step>
+					<octave>4</octave>
+				</pitch>
+				<duration>2</duration>
+				<type>eighth</type>
+				<lyric>
+					<text>说</text>
+				</lyric>
+			<beam number="1">begin</beam>
+            </note>
+			<note>
+				<pitch>
+					<step>G</step>
+					<octave>4</octave>
+				</pitch>
+				<duration>2</duration>
+				<type>eighth</type>
+				<lyric>
+					<text>声</text>
+				</lyric>
+			<beam number="1">end</beam>
+            </note>
+			<note>
+				<pitch>
+					<step>F</step>
+					<octave>4</octave>
+				</pitch>
+				<duration>8</duration>
+				<type>half</type>
+				<lyric>
+					<text>早。</text>
+				</lyric>
+			</note>
+			<barline location="right">
+				<bar-style>light-heavy</bar-style>
+				<repeat direction="backward"/>
+			</barline>
+		</measure>
+		<measure number="5">
+			<attributes>
+				<time>
+					<beats>2</beats>
+					<beat-type>4</beat-type>
+				</time>
+			</attributes>
+			<barline location="left">
+				<bar-style>heavy-light</bar-style>
+				<repeat direction="forward"/>
+			</barline>
+			<print new-system="yes"/>
+			<note>
+				<pitch>
+					<step>A</step>
+					<octave>4</octave>
+				</pitch>
+				<duration>4</duration>
+				<type>quarter</type>
+				<lyric>
+					<text>唻</text>
+				</lyric>
+			</note>
+			<note>
+				<space/>
+			</note>
+			<note>
+				<pitch>
+					<step>A</step>
+					<octave>4</octave>
+				</pitch>
+				<duration>4</duration>
+				<type>quarter</type>
+				<lyric>
+					<text>唻</text>
+				</lyric>
+			</note>
+			<note>
+				<space/>
+			</note>
+			<barline location="right">
+				<bar-style>light</bar-style>
+			</barline>
+		</measure>
+		<measure number="6">
+			<note>
+				<pitch>
+					<step>B</step>
+					<alter>-1</alter>
+					<octave>4</octave>
+				</pitch>
+				<duration>1</duration>
+				<type>16th</type>
+				<lyric>
+					<text>唻</text>
+				</lyric>
+			<beam number="1">begin</beam>
+                <beam number="2">forward hook</beam>
+            </note>
+			<note>
+				<pitch>
+					<step>B</step>
+					<alter>-1</alter>
+					<octave>4</octave>
+				</pitch>
+				<duration>3</duration>
+				<type>eighth</type>
+				<dot/>
+				<lyric>
+					<text>唻</text>
+				</lyric>
+			<beam number="1">end</beam>
+            </note>
+			<note>
+				<space/>
+			</note>
+			<note>
+				<space/>
+			</note>
+			<note>
+				<pitch>
+					<step>G</step>
+					<octave>4</octave>
+				</pitch>
+				<duration>4</duration>
+				<type>quarter</type>
+				<lyric>
+					<text>唻</text>
+				</lyric>
+			</note>
+			<note>
+				<space/>
+			</note>
+			<barline location="right">
+				<bar-style>light</bar-style>
+			</barline>
+		</measure>
+		<measure number="7">
+			<note>
+				<pitch>
+					<step>B</step>
+					<alter>-1</alter>
+					<octave>4</octave>
+				</pitch>
+				<duration>1</duration>
+				<type>16th</type>
+				<lyric>
+					<text>唻</text>
+				</lyric>
+			<beam number="1">begin</beam>
+                <beam number="2">forward hook</beam>
+            </note>
+			<note>
+				<pitch>
+					<step>B</step>
+					<alter>-1</alter>
+					<octave>4</octave>
+				</pitch>
+				<duration>3</duration>
+				<type>eighth</type>
+				<dot/>
+				<lyric>
+					<text>唻</text>
+				</lyric>
+			<beam number="1">end</beam>
+            </note>
+			<note>
+				<pitch>
+					<step>A</step>
+					<octave>4</octave>
+				</pitch>
+				<duration>1</duration>
+				<type>16th</type>
+				<lyric>
+					<text>唻</text>
+				</lyric>
+			<beam number="1">begin</beam>
+                <beam number="2">forward hook</beam>
+            </note>
+			<note>
+				<pitch>
+					<step>A</step>
+					<octave>4</octave>
+				</pitch>
+				<duration>3</duration>
+				<type>eighth</type>
+				<dot/>
+				<lyric>
+					<text>唻</text>
+				</lyric>
+			<beam number="1">end</beam>
+            </note>
+			<barline location="right">
+				<bar-style>light</bar-style>
+			</barline>
+		</measure>
+		<measure number="8">
+			<note>
+				<pitch>
+					<step>G</step>
+					<octave>4</octave>
+				</pitch>
+				<duration>1</duration>
+				<type>16th</type>
+				<lyric>
+					<text>唻</text>
+				</lyric>
+			<beam number="1">begin</beam>
+                <beam number="2">forward hook</beam>
+            </note>
+			<note>
+				<pitch>
+					<step>G</step>
+					<octave>4</octave>
+				</pitch>
+				<duration>3</duration>
+				<type>eighth</type>
+				<dot/>
+				<lyric>
+					<text>唻</text>
+				</lyric>
+			<beam number="1">end</beam>
+            </note>
+			<note>
+				<space/>
+			</note>
+			<note>
+				<space/>
+			</note>
+			<note>
+				<pitch>
+					<step>F</step>
+					<octave>4</octave>
+				</pitch>
+				<times>
+					<time begin="22241.398" end="22758.64" />
+					<time begin="26379.338" end="37241.4" />
+				</times>					
+				<duration>4</duration>
+				<type>quarter</type>
+				<lyric>
+					<text>唻</text>
+				</lyric>
+			</note>
+			<note>
+				<space/>
+			</note>
+			<direction>
+				<direction-type>
+					<words>D.C.</words>
+				</direction-type>
+				<sound dacapo="yes"/>
+			</direction>
+			<barline location="right">
+				<bar-style>light-heavy</bar-style>
+				<repeat direction="backward"/>
+			</barline>
+		</measure>
+	</part>
+	<tags>
+		<timegap>
+			<anchor measure="0" note="0" part="0" staff="0" system="0"/>
+			<place>
+				<hplacement value="0"/>
+				<vplacement value="1"/>
+				<xoffset value="0"/>
+				<yoffset value="0"/>
+			</place>
+			<values>
+				<item den="4" num="12" whole="0"/>
+			</values>
+		</timegap>
+		<custom-note-time></custom-note-time>
+	</tags>
+	<comments>
+		<comment>2021/3/20下午1:44:24</comment>
+		<comment>2021/3/20下午1:44:54</comment>
+		<comment>2021/3/20下午1:45:24</comment>
+		<comment>2021/3/20下午1:45:54</comment>
+		<comment>2021/3/20下午1:46:24</comment>
+		<comment>2021/3/20下午1:46:54</comment>
+		<comment>2021/3/20下午1:47:24</comment>
+		<comment>2021/3/20下午1:47:54</comment>
+		<comment>2021/3/20下午1:48:24</comment>
+		<comment>2021/3/20下午1:48:54</comment>
+		<comment>2021/3/20下午1:49:24</comment>
+		<comment>2021/3/20下午1:49:54</comment>
+		<comment>2021/3/20下午1:50:24</comment>
+		<comment>2021/3/20下午1:50:54</comment>
+		<comment>2021/3/20下午1:51:24</comment>
+		<comment>2021/3/20下午1:51:54</comment>
+		<comment>2021/3/20下午1:52:24</comment>
+		<comment>2021/3/20下午1:52:54</comment>
+		<comment>2021/3/20下午1:53:24</comment>
+		<comment>2021/3/20下午1:53:54</comment>
+		<comment>2021/3/20下午1:54:05</comment>
+		<comment>版本:0.7.2。时间:2021/4/9下午4:49:38。</comment>
+		<comment>版本:0.7.2。时间:2021/4/9下午4:49:57。</comment>
+		<comment>版本:0.8.1。时间:2021/5/10下午2:53:24。</comment>
+		<comment>版本:0.8.2。时间:2021/5/21下午4:37:57。</comment>
+	</comments>
+</score-partwise>

+ 10594 - 0
docs/musicxml/总谱对齐.xml

@@ -0,0 +1,10594 @@
+<?xml version="1.0" encoding='UTF-8' standalone='no' ?>
+<!DOCTYPE score-partwise PUBLIC "-//Recordare//DTD MusicXML 3.0 Partwise//EN" "http://www.musicxml.org/dtds/partwise.dtd">
+<score-partwise version="3.0">
+ <work>
+  <work-title />
+ </work>
+ <identification>
+  <rights>版权 © </rights>
+  <encoding>
+   <encoding-date>2025-07-15</encoding-date>
+   <encoder>Administrator</encoder>
+   <software>Sibelius 19.5.0</software>
+   <software>Direct export, not from Dolet</software>
+   <encoding-description>Sibelius / MusicXML 3.0</encoding-description>
+   <supports element="print" type="yes" value="yes" attribute="new-system" />
+   <supports element="print" type="yes" value="yes" attribute="new-page" />
+   <supports element="accidental" type="yes" />
+   <supports element="beam" type="yes" />
+   <supports element="stem" type="yes" />
+  </encoding>
+ </identification>
+ <defaults>
+  <scaling>
+   <millimeters>210</millimeters>
+   <tenths>1200</tenths>
+  </scaling>
+  <page-layout>
+   <page-height>1697</page-height>
+   <page-width>1200</page-width>
+   <page-margins type="both">
+    <left-margin>72</left-margin>
+    <right-margin>72</right-margin>
+    <top-margin>72</top-margin>
+    <bottom-margin>72</bottom-margin>
+   </page-margins>
+  </page-layout>
+  <system-layout>
+   <system-margins>
+    <left-margin>22</left-margin>
+    <right-margin>0</right-margin>
+   </system-margins>
+   <system-distance>92</system-distance>
+  </system-layout>
+  <appearance>
+   <line-width type="stem">0.9375</line-width>
+   <line-width type="beam">5</line-width>
+   <line-width type="staff">0.9375</line-width>
+   <line-width type="light barline">1.5625</line-width>
+   <line-width type="heavy barline">5</line-width>
+   <line-width type="leger">1.5625</line-width>
+   <line-width type="ending">1.5625</line-width>
+   <line-width type="wedge">1.25</line-width>
+   <line-width type="enclosure">0.9375</line-width>
+   <line-width type="tuplet bracket">1.25</line-width>
+   <line-width type="bracket">5</line-width>
+   <line-width type="dashes">1.5625</line-width>
+   <line-width type="extend">0.9375</line-width>
+   <line-width type="octave shift">1.5625</line-width>
+   <line-width type="pedal">1.5625</line-width>
+   <line-width type="slur middle">1.5625</line-width>
+   <line-width type="slur tip">0.625</line-width>
+   <line-width type="tie middle">1.5625</line-width>
+   <line-width type="tie tip">0.625</line-width>
+   <note-size type="cue">75</note-size>
+   <note-size type="grace">60</note-size>
+  </appearance>
+  <music-font font-family="Opus Std" font-size="19.8425" />
+  <lyric-font font-family="Times New Roman" font-size="11.4715" />
+  <lyric-language xml:lang="zh" />
+ </defaults>
+ <credit page="2">
+  <credit-words default-x="72" default-y="1624" font-style="normal" font-weight="normal" justify="left" valign="middle">2</credit-words>
+ </credit>
+ <credit page="3">
+  <credit-words default-x="1127" default-y="1624" font-style="normal" font-weight="normal" justify="left" valign="middle">3</credit-words>
+ </credit>
+ <credit page="4">
+  <credit-words default-x="72" default-y="1624" font-style="normal" font-weight="normal" justify="left" valign="middle">4</credit-words>
+ </credit>
+ <part-list>
+  <score-part id="P1">
+   <part-name> </part-name>
+   <part-name-display>
+    <display-text> </display-text>
+   </part-name-display>
+   <part-abbreviation> </part-abbreviation>
+   <part-abbreviation-display>
+    <display-text> </display-text>
+   </part-abbreviation-display>
+   <score-instrument id="P1-I1">
+    <instrument-name> </instrument-name>
+    <virtual-instrument>
+     <virtual-library>Sibelius 7 Sounds</virtual-library>
+    </virtual-instrument>
+   </score-instrument>
+  </score-part>
+  <score-part id="P2">
+   <part-name> </part-name>
+   <part-name-display>
+    <display-text> </display-text>
+   </part-name-display>
+   <part-abbreviation> </part-abbreviation>
+   <part-abbreviation-display>
+    <display-text> </display-text>
+   </part-abbreviation-display>
+   <score-instrument id="P2-I1">
+    <instrument-name> </instrument-name>
+    <virtual-instrument>
+     <virtual-library>Sibelius 7 Sounds</virtual-library>
+    </virtual-instrument>
+   </score-instrument>
+  </score-part>
+  <score-part id="P3">
+   <part-name> </part-name>
+   <part-name-display>
+    <display-text> </display-text>
+   </part-name-display>
+   <part-abbreviation> </part-abbreviation>
+   <part-abbreviation-display>
+    <display-text> </display-text>
+   </part-abbreviation-display>
+   <score-instrument id="P3-I1">
+    <instrument-name> </instrument-name>
+    <virtual-instrument>
+     <virtual-library>Sibelius 7 Sounds</virtual-library>
+    </virtual-instrument>
+   </score-instrument>
+  </score-part>
+  <score-part id="P4">
+   <part-name> </part-name>
+   <part-name-display>
+    <display-text> </display-text>
+   </part-name-display>
+   <part-abbreviation> </part-abbreviation>
+   <part-abbreviation-display>
+    <display-text> </display-text>
+   </part-abbreviation-display>
+   <score-instrument id="P4-I1">
+    <instrument-name> </instrument-name>
+    <virtual-instrument>
+     <virtual-library>Sibelius 7 Sounds</virtual-library>
+    </virtual-instrument>
+   </score-instrument>
+  </score-part>
+  <score-part id="P5">
+   <part-name> </part-name>
+   <part-name-display>
+    <display-text> </display-text>
+   </part-name-display>
+   <part-abbreviation> </part-abbreviation>
+   <part-abbreviation-display>
+    <display-text> </display-text>
+   </part-abbreviation-display>
+   <score-instrument id="P5-I1">
+    <instrument-name> </instrument-name>
+    <virtual-instrument>
+     <virtual-library>Sibelius 7 Sounds</virtual-library>
+    </virtual-instrument>
+   </score-instrument>
+  </score-part>
+ </part-list>
+ <part id="P1">
+  <!--============== Part: P1, Measure: 1 ==============-->
+  <measure number="1" width="362">
+   <print new-page="yes">
+    <system-layout>
+     <system-margins>
+      <left-margin>22</left-margin>
+      <right-margin>0</right-margin>
+     </system-margins>
+     <top-system-distance>218</top-system-distance>
+    </system-layout>
+   </print>
+   <attributes>
+    <divisions>256</divisions>
+    <key color="#000000">
+     <fifths>0</fifths>
+     <mode>major</mode>
+    </key>
+    <time color="#000000">
+     <beats>4</beats>
+     <beat-type>4</beat-type>
+    </time>
+    <staves>1</staves>
+    <clef number="1" color="#000000">
+     <sign>G</sign>
+     <line>2</line>
+    </clef>
+    <staff-details number="1" print-object="yes" />
+   </attributes>
+   <note default-x="76">
+    <rest />
+    <duration>1024</duration>
+    <instrument id="P1-I1" />
+    <voice>1</voice>
+    <type>whole</type>
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P1, Measure: 2 ==============-->
+  <measure number="2" width="319">
+   <note default-x="15">
+    <rest />
+    <duration>768</duration>
+    <instrument id="P1-I1" />
+    <voice>1</voice>
+    <type>half</type>
+    <dot />
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="237" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>192</duration>
+    <instrument id="P1-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <dot />
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">begin</beam>
+   </note>
+   <note color="#000000" default-x="299" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>64</duration>
+    <instrument id="P1-I1" />
+    <voice>1</voice>
+    <type>16th</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">end</beam>
+    <beam number="2">backward hook</beam>
+   </note>
+  </measure>
+  <!--============== Part: P1, Measure: 3 ==============-->
+  <measure number="3" width="101">
+   <note color="#000000" default-x="15">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>1024</duration>
+    <instrument id="P1-I1" />
+    <voice>1</voice>
+    <type>whole</type>
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P1, Measure: 4 ==============-->
+  <measure number="4" width="249">
+   <note default-x="15">
+    <rest />
+    <duration>384</duration>
+    <instrument id="P1-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <dot />
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="95" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P1-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="124" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>512</duration>
+    <instrument id="P1-I1" />
+    <voice>1</voice>
+    <type>half</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P1, Measure: 5 ==============-->
+  <measure number="5" width="297">
+   <print new-system="yes">
+    <system-layout>
+     <system-margins>
+      <left-margin>22</left-margin>
+      <right-margin>0</right-margin>
+     </system-margins>
+     <system-distance>340</system-distance>
+    </system-layout>
+   </print>
+   <attributes>
+    <staff-details number="1" print-object="yes" />
+   </attributes>
+   <note color="#000000" default-x="57">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>1024</duration>
+    <instrument id="P1-I1" />
+    <voice>1</voice>
+    <type>whole</type>
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P1, Measure: 6 ==============-->
+  <measure number="6" width="180">
+   <note color="#000000" default-x="15" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>256</duration>
+    <instrument id="P1-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note default-x="56">
+    <rest />
+    <duration>256</duration>
+    <instrument id="P1-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="97" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>512</duration>
+    <instrument id="P1-I1" />
+    <voice>1</voice>
+    <type>half</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P1, Measure: 7 ==============-->
+  <measure number="7" width="332">
+   <note color="#000000" default-x="8" default-y="2">
+    <grace />
+    <pitch>
+     <step>B</step>
+     <octave>4</octave>
+    </pitch>
+    <instrument id="P1-I1" />
+    <voice>1</voice>
+    <type>16th</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">begin</beam>
+    <beam number="2">begin</beam>
+   </note>
+   <note color="#000000" default-x="23" default-y="2">
+    <grace />
+    <pitch>
+     <step>C</step>
+     <octave>5</octave>
+    </pitch>
+    <instrument id="P1-I1" />
+    <voice>1</voice>
+    <type>16th</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">continue</beam>
+    <beam number="2">continue</beam>
+   </note>
+   <note color="#000000" default-x="38" default-y="2">
+    <grace />
+    <pitch>
+     <step>B</step>
+     <octave>4</octave>
+    </pitch>
+    <instrument id="P1-I1" />
+    <voice>1</voice>
+    <type>16th</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">end</beam>
+    <beam number="2">end</beam>
+   </note>
+   <note color="#000000" default-x="55" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>512</duration>
+    <instrument id="P1-I1" />
+    <voice>1</voice>
+    <type>half</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="168">
+    <grace />
+    <pitch>
+     <step>B</step>
+     <octave>4</octave>
+    </pitch>
+    <instrument id="P1-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="186" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>512</duration>
+    <instrument id="P1-I1" />
+    <voice>1</voice>
+    <type>half</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P1, Measure: 8 ==============-->
+  <measure number="8" width="221">
+   <attributes>
+    <time color="#000000">
+     <beats>3</beats>
+     <beat-type>4</beat-type>
+    </time>
+   </attributes>
+   <note default-x="33">
+    <rest />
+    <duration>768</duration>
+    <instrument id="P1-I1" />
+    <voice>1</voice>
+    <type>whole</type>
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P1, Measure: 9 ==============-->
+  <measure number="9" width="266">
+   <print new-page="yes">
+    <system-layout>
+     <system-margins>
+      <left-margin>22</left-margin>
+      <right-margin>0</right-margin>
+     </system-margins>
+     <top-system-distance>73</top-system-distance>
+    </system-layout>
+   </print>
+   <attributes>
+    <staff-details number="1" print-object="yes" />
+   </attributes>
+   <note default-x="57">
+    <rest />
+    <duration>512</duration>
+    <instrument id="P1-I1" />
+    <voice>1</voice>
+    <type>half</type>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="197" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>256</duration>
+    <instrument id="P1-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P1, Measure: 10 ==============-->
+  <measure number="10" width="91">
+   <note color="#000000" default-x="15" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>768</duration>
+    <instrument id="P1-I1" />
+    <voice>1</voice>
+    <type>half</type>
+    <dot />
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P1, Measure: 11 ==============-->
+  <measure number="11" width="149">
+   <note color="#000000" default-x="15" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>384</duration>
+    <instrument id="P1-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <dot />
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note default-x="66">
+    <rest />
+    <duration>128</duration>
+    <instrument id="P1-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <staff>1</staff>
+   </note>
+   <note default-x="95">
+    <rest />
+    <duration>256</duration>
+    <instrument id="P1-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P1, Measure: 12 ==============-->
+  <measure number="12" width="192">
+   <note default-x="15">
+    <rest />
+    <duration>384</duration>
+    <instrument id="P1-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <dot />
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="97" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P1-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="125" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>256</duration>
+    <instrument id="P1-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P1, Measure: 13 ==============-->
+  <measure number="13" width="153">
+   <note default-x="15">
+    <rest />
+    <duration>512</duration>
+    <instrument id="P1-I1" />
+    <voice>1</voice>
+    <type>half</type>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="115" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>256</duration>
+    <instrument id="P1-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P1, Measure: 14 ==============-->
+  <measure number="14" width="157">
+   <note color="#000000" default-x="8">
+    <grace />
+    <pitch>
+     <step>B</step>
+     <octave>4</octave>
+    </pitch>
+    <instrument id="P1-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="25" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>512</duration>
+    <instrument id="P1-I1" />
+    <voice>1</voice>
+    <type>half</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="102">
+    <grace />
+    <pitch>
+     <step>B</step>
+     <octave>4</octave>
+    </pitch>
+    <instrument id="P1-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="120" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>256</duration>
+    <instrument id="P1-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P1, Measure: 15 ==============-->
+  <measure number="15" width="226">
+   <print new-system="yes">
+    <system-layout>
+     <system-margins>
+      <left-margin>22</left-margin>
+      <right-margin>0</right-margin>
+     </system-margins>
+     <system-distance>84</system-distance>
+    </system-layout>
+   </print>
+   <attributes>
+    <time color="#000000">
+     <beats>2</beats>
+     <beat-type>4</beat-type>
+    </time>
+    <staff-details number="1" print-object="yes" />
+   </attributes>
+   <note default-x="76">
+    <rest />
+    <duration>512</duration>
+    <instrument id="P1-I1" />
+    <voice>1</voice>
+    <type>whole</type>
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P1, Measure: 16 ==============-->
+  <measure number="16" width="215">
+   <note default-x="15">
+    <rest />
+    <duration>512</duration>
+    <instrument id="P1-I1" />
+    <voice>1</voice>
+    <type>half</type>
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P1, Measure: 17 ==============-->
+  <measure number="17" width="206">
+   <note color="#000000" default-x="8" default-y="2">
+    <grace />
+    <pitch>
+     <step>B</step>
+     <octave>4</octave>
+    </pitch>
+    <instrument id="P1-I1" />
+    <voice>1</voice>
+    <type>16th</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">begin</beam>
+    <beam number="2">begin</beam>
+   </note>
+   <note color="#000000" default-x="23" default-y="2">
+    <grace />
+    <pitch>
+     <step>C</step>
+     <octave>5</octave>
+    </pitch>
+    <instrument id="P1-I1" />
+    <voice>1</voice>
+    <type>16th</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">continue</beam>
+    <beam number="2">continue</beam>
+   </note>
+   <note color="#000000" default-x="38" default-y="2">
+    <grace />
+    <pitch>
+     <step>B</step>
+     <octave>4</octave>
+    </pitch>
+    <instrument id="P1-I1" />
+    <voice>1</voice>
+    <type>16th</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">end</beam>
+    <beam number="2">end</beam>
+   </note>
+   <note color="#000000" default-x="55" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>512</duration>
+    <instrument id="P1-I1" />
+    <voice>1</voice>
+    <type>half</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P1, Measure: 18 ==============-->
+  <measure number="18" width="383">
+   <attributes>
+    <time color="#000000">
+     <beats>2</beats>
+     <beat-type>2</beat-type>
+    </time>
+   </attributes>
+   <note default-x="32">
+    <rest />
+    <duration>1024</duration>
+    <instrument id="P1-I1" />
+    <voice>1</voice>
+    <type>whole</type>
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P1, Measure: 19 ==============-->
+  <measure number="19" width="369">
+   <print new-system="yes">
+    <system-layout>
+     <system-margins>
+      <left-margin>22</left-margin>
+      <right-margin>0</right-margin>
+     </system-margins>
+     <system-distance>99</system-distance>
+    </system-layout>
+   </print>
+   <attributes>
+    <staff-details number="1" print-object="yes" />
+   </attributes>
+   <note default-x="57">
+    <rest />
+    <duration>768</duration>
+    <instrument id="P1-I1" />
+    <voice>1</voice>
+    <type>half</type>
+    <dot />
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="280" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>192</duration>
+    <instrument id="P1-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <dot />
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">begin</beam>
+   </note>
+   <note color="#000000" default-x="347" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>64</duration>
+    <instrument id="P1-I1" />
+    <voice>1</voice>
+    <type>16th</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">end</beam>
+    <beam number="2">backward hook</beam>
+   </note>
+  </measure>
+  <!--============== Part: P1, Measure: 20 ==============-->
+  <measure number="20" width="289">
+   <note color="#000000" default-x="8" default-y="2">
+    <grace />
+    <pitch>
+     <step>B</step>
+     <octave>4</octave>
+    </pitch>
+    <instrument id="P1-I1" />
+    <voice>1</voice>
+    <type>16th</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">begin</beam>
+    <beam number="2">begin</beam>
+   </note>
+   <note color="#000000" default-x="23" default-y="2">
+    <grace />
+    <pitch>
+     <step>C</step>
+     <octave>5</octave>
+    </pitch>
+    <instrument id="P1-I1" />
+    <voice>1</voice>
+    <type>16th</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">continue</beam>
+    <beam number="2">continue</beam>
+   </note>
+   <note color="#000000" default-x="38" default-y="2">
+    <grace />
+    <pitch>
+     <step>B</step>
+     <octave>4</octave>
+    </pitch>
+    <instrument id="P1-I1" />
+    <voice>1</voice>
+    <type>16th</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">end</beam>
+    <beam number="2">end</beam>
+   </note>
+   <note color="#000000" default-x="55" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>512</duration>
+    <instrument id="P1-I1" />
+    <voice>1</voice>
+    <type>half</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="155">
+    <grace />
+    <pitch>
+     <step>B</step>
+     <octave>4</octave>
+    </pitch>
+    <instrument id="P1-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="172" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>512</duration>
+    <instrument id="P1-I1" />
+    <voice>1</voice>
+    <type>half</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P1, Measure: 21 ==============-->
+  <measure number="21" width="109">
+   <note color="#000000" default-x="15">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>1024</duration>
+    <instrument id="P1-I1" />
+    <voice>1</voice>
+    <type>whole</type>
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P1, Measure: 22 ==============-->
+  <measure number="22" width="264">
+   <note default-x="15">
+    <rest />
+    <duration>384</duration>
+    <instrument id="P1-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <dot />
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="102" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P1-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="131" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>512</duration>
+    <instrument id="P1-I1" />
+    <voice>1</voice>
+    <type>half</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P1, Measure: 23 ==============-->
+  <measure number="23" width="260">
+   <print new-page="yes">
+    <system-layout>
+     <system-margins>
+      <left-margin>22</left-margin>
+      <right-margin>0</right-margin>
+     </system-margins>
+     <top-system-distance>88</top-system-distance>
+    </system-layout>
+   </print>
+   <attributes>
+    <staff-details number="1" print-object="yes" />
+   </attributes>
+   <note color="#000000" default-x="57">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>1024</duration>
+    <instrument id="P1-I1" />
+    <voice>1</voice>
+    <type>whole</type>
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P1, Measure: 24 ==============-->
+  <measure number="24" width="155">
+   <note color="#000000" default-x="15" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>256</duration>
+    <instrument id="P1-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note default-x="50">
+    <rest />
+    <duration>256</duration>
+    <instrument id="P1-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="85" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>512</duration>
+    <instrument id="P1-I1" />
+    <voice>1</voice>
+    <type>half</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P1, Measure: 25 ==============-->
+  <measure number="25" width="218">
+   <note color="#000000" default-x="15">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>1024</duration>
+    <instrument id="P1-I1" />
+    <voice>1</voice>
+    <type>whole</type>
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P1, Measure: 26 ==============-->
+  <measure number="26" width="155">
+   <note color="#000000" default-x="15" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>256</duration>
+    <instrument id="P1-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note default-x="50">
+    <rest />
+    <duration>256</duration>
+    <instrument id="P1-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="85" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>512</duration>
+    <instrument id="P1-I1" />
+    <voice>1</voice>
+    <type>half</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P1, Measure: 27 ==============-->
+  <measure number="27" width="243">
+   <attributes>
+    <time color="#000000">
+     <beats>3</beats>
+     <beat-type>2</beat-type>
+    </time>
+   </attributes>
+   <note color="#000000" default-x="32">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>1536</duration>
+    <instrument id="P1-I1" />
+    <voice>1</voice>
+    <type>whole</type>
+    <dot />
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P1, Measure: 28 ==============-->
+  <measure number="28" width="162">
+   <print new-system="yes">
+    <system-layout>
+     <system-margins>
+      <left-margin>22</left-margin>
+      <right-margin>0</right-margin>
+     </system-margins>
+     <system-distance>464</system-distance>
+    </system-layout>
+   </print>
+   <attributes>
+    <staff-details number="1" print-object="yes" />
+   </attributes>
+   <note color="#000000" default-x="57">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>1536</duration>
+    <instrument id="P1-I1" />
+    <voice>1</voice>
+    <type>whole</type>
+    <dot />
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P1, Measure: 29 ==============-->
+  <measure number="29" width="232">
+   <note color="#000000" default-x="15" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>256</duration>
+    <instrument id="P1-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="54" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>768</duration>
+    <instrument id="P1-I1" />
+    <voice>1</voice>
+    <type>half</type>
+    <dot />
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="160" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>512</duration>
+    <instrument id="P1-I1" />
+    <voice>1</voice>
+    <type>half</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P1, Measure: 30 ==============-->
+  <measure number="30" width="265">
+   <note color="#000000" default-x="15" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>768</duration>
+    <instrument id="P1-I1" />
+    <voice>1</voice>
+    <type>half</type>
+    <dot />
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="153" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>512</duration>
+    <instrument id="P1-I1" />
+    <voice>1</voice>
+    <type>half</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note default-x="225">
+    <rest />
+    <duration>256</duration>
+    <instrument id="P1-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P1, Measure: 31 ==============-->
+  <measure number="31" width="372">
+   <note color="#000000" default-x="15" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>512</duration>
+    <instrument id="P1-I1" />
+    <voice>1</voice>
+    <type>half</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="128" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P1-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="163" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>384</duration>
+    <instrument id="P1-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <dot />
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="255" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>384</duration>
+    <instrument id="P1-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <dot />
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="340" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P1-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P1, Measure: 32 ==============-->
+  <measure number="32" width="360">
+   <print new-page="yes">
+    <system-layout>
+     <system-margins>
+      <left-margin>22</left-margin>
+      <right-margin>0</right-margin>
+     </system-margins>
+     <top-system-distance>88</top-system-distance>
+    </system-layout>
+   </print>
+   <attributes>
+    <staff-details number="1" print-object="yes" />
+   </attributes>
+   <note default-x="57">
+    <rest />
+    <duration>384</duration>
+    <instrument id="P1-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <dot />
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="138" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P1-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="167" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>256</duration>
+    <instrument id="P1-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="221">
+    <grace />
+    <pitch>
+     <step>B</step>
+     <octave>4</octave>
+    </pitch>
+    <instrument id="P1-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="239" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>512</duration>
+    <instrument id="P1-I1" />
+    <voice>1</voice>
+    <type>half</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="305">
+    <grace />
+    <pitch>
+     <step>B</step>
+     <octave>4</octave>
+    </pitch>
+    <instrument id="P1-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="323" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>256</duration>
+    <instrument id="P1-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P1, Measure: 33 ==============-->
+  <measure number="33" width="81">
+   <attributes>
+    <time color="#000000">
+     <beats>3</beats>
+     <beat-type>8</beat-type>
+    </time>
+   </attributes>
+   <note color="#000000" default-x="31" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>384</duration>
+    <instrument id="P1-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <dot />
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P1, Measure: 34 ==============-->
+  <measure number="34" width="145">
+   <note color="#000000" default-x="15" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>256</duration>
+    <instrument id="P1-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note default-x="104">
+    <rest />
+    <duration>128</duration>
+    <instrument id="P1-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P1, Measure: 35 ==============-->
+  <measure number="35" width="158">
+   <note default-x="55">
+    <rest />
+    <duration>384</duration>
+    <instrument id="P1-I1" />
+    <voice>1</voice>
+    <type>whole</type>
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P1, Measure: 36 ==============-->
+  <measure number="36" width="107">
+   <attributes>
+    <time color="#000000">
+     <beats>6</beats>
+     <beat-type>8</beat-type>
+    </time>
+   </attributes>
+   <note color="#000000" default-x="32" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>768</duration>
+    <instrument id="P1-I1" />
+    <voice>1</voice>
+    <type>half</type>
+    <dot />
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P1, Measure: 37 ==============-->
+  <measure number="37" width="179">
+   <note color="#000000" default-x="15" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>512</duration>
+    <instrument id="P1-I1" />
+    <voice>1</voice>
+    <type>half</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="125" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P1-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">begin</beam>
+   </note>
+   <note color="#000000" default-x="152" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P1-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">end</beam>
+   </note>
+  </measure>
+  <!--============== Part: P1, Measure: 38 ==============-->
+  <measure number="38" width="492">
+   <print new-system="yes">
+    <system-layout>
+     <system-margins>
+      <left-margin>22</left-margin>
+      <right-margin>0</right-margin>
+     </system-margins>
+     <system-distance>84</system-distance>
+    </system-layout>
+   </print>
+   <attributes>
+    <staff-details number="1" print-object="yes" />
+   </attributes>
+   <note default-x="57">
+    <rest />
+    <duration>768</duration>
+    <instrument id="P1-I1" />
+    <voice>1</voice>
+    <type>whole</type>
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P1, Measure: 39 ==============-->
+  <measure number="39" width="540">
+   <attributes>
+    <time color="#000000">
+     <beats>9</beats>
+     <beat-type>8</beat-type>
+    </time>
+   </attributes>
+   <note color="#000000" default-x="32" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>384</duration>
+    <instrument id="P1-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <dot />
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="131" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>256</duration>
+    <instrument id="P1-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note default-x="294">
+    <rest />
+    <duration>128</duration>
+    <instrument id="P1-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <staff>1</staff>
+   </note>
+   <note default-x="380">
+    <rest />
+    <duration>384</duration>
+    <instrument id="P1-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <dot />
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P1, Measure: 40 ==============-->
+  <measure number="40" width="414">
+   <print new-system="yes">
+    <system-layout>
+     <system-margins>
+      <left-margin>22</left-margin>
+      <right-margin>0</right-margin>
+     </system-margins>
+     <system-distance>84</system-distance>
+    </system-layout>
+   </print>
+   <attributes>
+    <staff-details number="1" print-object="yes" />
+   </attributes>
+   <note color="#000000" default-x="57" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>384</duration>
+    <instrument id="P1-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <dot />
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="120" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>256</duration>
+    <instrument id="P1-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="224" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>512</duration>
+    <instrument id="P1-I1" />
+    <voice>1</voice>
+    <type>half</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P1, Measure: 41 ==============-->
+  <measure number="41" width="303">
+   <attributes>
+    <time color="#000000">
+     <beats>5</beats>
+     <beat-type>4</beat-type>
+    </time>
+   </attributes>
+   <note color="#000000" default-x="33">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>1024</duration>
+    <instrument id="P1-I1" />
+    <voice>1</voice>
+    <type>whole</type>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="252" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>256</duration>
+    <instrument id="P1-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P1, Measure: 42 ==============-->
+  <measure number="42" width="315">
+   <note default-x="15">
+    <rest />
+    <duration>1024</duration>
+    <instrument id="P1-I1" />
+    <voice>1</voice>
+    <type>whole</type>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="241" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>256</duration>
+    <instrument id="P1-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <barline>
+    <bar-style>light-heavy</bar-style>
+   </barline>
+  </measure>
+ </part>
+ <part id="P2">
+  <!--============== Part: P2, Measure: 1 ==============-->
+  <measure number="1" width="362">
+   <print new-page="yes">
+    <system-layout>
+     <system-margins>
+      <left-margin>22</left-margin>
+      <right-margin>0</right-margin>
+     </system-margins>
+    </system-layout>
+    <staff-layout number="1">
+     <staff-distance>65</staff-distance>
+    </staff-layout>
+   </print>
+   <attributes>
+    <divisions>256</divisions>
+    <key color="#000000">
+     <fifths>0</fifths>
+     <mode>major</mode>
+    </key>
+    <time color="#000000">
+     <beats>4</beats>
+     <beat-type>4</beat-type>
+    </time>
+    <staves>1</staves>
+    <clef number="1" color="#000000">
+     <sign>G</sign>
+     <line>2</line>
+    </clef>
+    <staff-details number="1" print-object="yes" />
+   </attributes>
+   <note color="#000000" default-x="76" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">begin</beam>
+   </note>
+   <note color="#000000" default-x="103" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">continue</beam>
+   </note>
+   <note color="#000000" default-x="129" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">continue</beam>
+   </note>
+   <note color="#000000" default-x="178" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">end</beam>
+   </note>
+   <note color="#000000" default-x="206" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">begin</beam>
+   </note>
+   <note color="#000000" default-x="235" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">continue</beam>
+   </note>
+   <note color="#000000" default-x="272" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">continue</beam>
+   </note>
+   <note color="#000000" default-x="313" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">end</beam>
+   </note>
+  </measure>
+  <!--============== Part: P2, Measure: 2 ==============-->
+  <measure number="2" width="319">
+   <note color="#000000" default-x="15" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">begin</beam>
+   </note>
+   <note color="#000000" default-x="46" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">continue</beam>
+   </note>
+   <note color="#000000" default-x="88" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">continue</beam>
+   </note>
+   <note color="#000000" default-x="115" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">end</beam>
+   </note>
+   <note color="#000000" default-x="155" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">begin</beam>
+   </note>
+   <note color="#000000" default-x="196" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">continue</beam>
+   </note>
+   <note color="#000000" default-x="237" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">continue</beam>
+   </note>
+   <note color="#000000" default-x="278" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">end</beam>
+   </note>
+  </measure>
+  <!--============== Part: P2, Measure: 3 ==============-->
+  <measure number="3" width="101">
+   <note color="#000000" default-x="15">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>1024</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>whole</type>
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P2, Measure: 4 ==============-->
+  <measure number="4" width="249">
+   <note color="#000000" default-x="15" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note default-x="42">
+    <rest />
+    <duration>384</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <dot />
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="124" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>85</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <time-modification>
+     <actual-notes>3</actual-notes>
+     <normal-notes>2</normal-notes>
+     <normal-type>eighth</normal-type>
+    </time-modification>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">begin</beam>
+    <notations>
+     <tuplet type="start" bracket="no" number="1" default-y="-20" placement="above" />
+    </notations>
+   </note>
+   <note color="#000000" default-x="146" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>85</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <time-modification>
+     <actual-notes>3</actual-notes>
+     <normal-notes>2</normal-notes>
+     <normal-type>eighth</normal-type>
+    </time-modification>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">continue</beam>
+   </note>
+   <note color="#000000" default-x="168" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>86</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <time-modification>
+     <actual-notes>3</actual-notes>
+     <normal-notes>2</normal-notes>
+     <normal-type>eighth</normal-type>
+    </time-modification>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">end</beam>
+    <notations>
+     <tuplet type="stop" bracket="no" number="1" default-y="-20" placement="above" />
+    </notations>
+   </note>
+   <note default-x="190">
+    <rest />
+    <duration>128</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="216" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P2, Measure: 5 ==============-->
+  <measure number="5" width="297">
+   <print new-system="yes">
+    <system-layout>
+     <system-margins>
+      <left-margin>22</left-margin>
+      <right-margin>0</right-margin>
+     </system-margins>
+    </system-layout>
+    <staff-layout number="1">
+     <staff-distance>65</staff-distance>
+    </staff-layout>
+   </print>
+   <attributes>
+    <staff-details number="1" print-object="yes" />
+   </attributes>
+   <note color="#000000" default-x="57" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">begin</beam>
+   </note>
+   <note color="#000000" default-x="87" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">continue</beam>
+   </note>
+   <note color="#000000" default-x="117" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">continue</beam>
+   </note>
+   <note color="#000000" default-x="147" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">end</beam>
+   </note>
+   <note color="#000000" default-x="177" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">begin</beam>
+   </note>
+   <note color="#000000" default-x="207" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">continue</beam>
+   </note>
+   <note color="#000000" default-x="237" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">continue</beam>
+   </note>
+   <note color="#000000" default-x="267" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">end</beam>
+   </note>
+  </measure>
+  <!--============== Part: P2, Measure: 6 ==============-->
+  <measure number="6" width="180">
+   <note color="#000000" default-x="15" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>512</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>half</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note default-x="97">
+    <rest />
+    <duration>256</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="139" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>256</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P2, Measure: 7 ==============-->
+  <measure number="7" width="332">
+   <note color="#000000" default-x="55" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">begin</beam>
+   </note>
+   <note color="#000000" default-x="88" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">continue</beam>
+   </note>
+   <note color="#000000" default-x="126" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">continue</beam>
+   </note>
+   <note color="#000000" default-x="156" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">end</beam>
+   </note>
+   <note color="#000000" default-x="186" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">begin</beam>
+   </note>
+   <note color="#000000" default-x="221" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">continue</beam>
+   </note>
+   <note color="#000000" default-x="258" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">continue</beam>
+   </note>
+   <note color="#000000" default-x="302" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">end</beam>
+   </note>
+  </measure>
+  <!--============== Part: P2, Measure: 8 ==============-->
+  <measure number="8" width="221">
+   <attributes>
+    <time color="#000000">
+     <beats>3</beats>
+     <beat-type>4</beat-type>
+    </time>
+   </attributes>
+   <note color="#000000" default-x="33" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">begin</beam>
+   </note>
+   <note color="#000000" default-x="63" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">end</beam>
+   </note>
+   <note color="#000000" default-x="93" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">begin</beam>
+   </note>
+   <note color="#000000" default-x="123" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">end</beam>
+   </note>
+   <note color="#000000" default-x="153" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">begin</beam>
+   </note>
+   <note color="#000000" default-x="183" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">end</beam>
+   </note>
+  </measure>
+  <!--============== Part: P2, Measure: 9 ==============-->
+  <measure number="9" width="266">
+   <print new-page="yes">
+    <system-layout>
+     <system-margins>
+      <left-margin>22</left-margin>
+      <right-margin>0</right-margin>
+     </system-margins>
+    </system-layout>
+    <staff-layout number="1">
+     <staff-distance>54</staff-distance>
+    </staff-layout>
+   </print>
+   <attributes>
+    <staff-details number="1" print-object="yes" />
+   </attributes>
+   <note color="#000000" default-x="57" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">begin</beam>
+   </note>
+   <note color="#000000" default-x="91" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">end</beam>
+   </note>
+   <note color="#000000" default-x="127" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">begin</beam>
+   </note>
+   <note color="#000000" default-x="155" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">end</beam>
+   </note>
+   <note color="#000000" default-x="197" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">begin</beam>
+   </note>
+   <note color="#000000" default-x="232" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">end</beam>
+   </note>
+  </measure>
+  <!--============== Part: P2, Measure: 10 ==============-->
+  <measure number="10" width="91">
+   <note color="#000000" default-x="15" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>768</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>half</type>
+    <dot />
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P2, Measure: 11 ==============-->
+  <measure number="11" width="149">
+   <note default-x="15">
+    <rest />
+    <duration>384</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <dot />
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="66" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="95" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">begin</beam>
+   </note>
+   <note color="#000000" default-x="122" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">end</beam>
+   </note>
+  </measure>
+  <!--============== Part: P2, Measure: 12 ==============-->
+  <measure number="12" width="192">
+   <note color="#000000" default-x="15" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note default-x="42">
+    <rest />
+    <duration>384</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <dot />
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="125" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>85</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <time-modification>
+     <actual-notes>3</actual-notes>
+     <normal-notes>2</normal-notes>
+     <normal-type>eighth</normal-type>
+    </time-modification>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">begin</beam>
+    <notations>
+     <tuplet type="start" bracket="no" number="1" default-y="-20" placement="above" />
+    </notations>
+   </note>
+   <note color="#000000" default-x="148" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>85</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <time-modification>
+     <actual-notes>3</actual-notes>
+     <normal-notes>2</normal-notes>
+     <normal-type>eighth</normal-type>
+    </time-modification>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">continue</beam>
+   </note>
+   <note color="#000000" default-x="170" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>86</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <time-modification>
+     <actual-notes>3</actual-notes>
+     <normal-notes>2</normal-notes>
+     <normal-type>eighth</normal-type>
+    </time-modification>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">end</beam>
+    <notations>
+     <tuplet type="stop" bracket="no" number="1" default-y="-20" placement="above" />
+    </notations>
+   </note>
+  </measure>
+  <!--============== Part: P2, Measure: 13 ==============-->
+  <measure number="13" width="153">
+   <note color="#000000" default-x="15" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>384</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <dot />
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="87" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note default-x="115">
+    <rest />
+    <duration>256</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P2, Measure: 14 ==============-->
+  <measure number="14" width="157">
+   <note color="#000000" default-x="25" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>256</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note default-x="82">
+    <rest />
+    <duration>256</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="120" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>256</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P2, Measure: 15 ==============-->
+  <measure number="15" width="226">
+   <print new-system="yes">
+    <system-layout>
+     <system-margins>
+      <left-margin>22</left-margin>
+      <right-margin>0</right-margin>
+     </system-margins>
+    </system-layout>
+    <staff-layout number="1">
+     <staff-distance>54</staff-distance>
+    </staff-layout>
+   </print>
+   <attributes>
+    <time color="#000000">
+     <beats>2</beats>
+     <beat-type>4</beat-type>
+    </time>
+    <staff-details number="1" print-object="yes" />
+   </attributes>
+   <note color="#000000" default-x="76" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">begin</beam>
+   </note>
+   <note color="#000000" default-x="114" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">end</beam>
+   </note>
+   <note color="#000000" default-x="151" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">begin</beam>
+   </note>
+   <note color="#000000" default-x="189" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">end</beam>
+   </note>
+  </measure>
+  <!--============== Part: P2, Measure: 16 ==============-->
+  <measure number="16" width="215">
+   <note color="#000000" default-x="15" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">begin</beam>
+   </note>
+   <note color="#000000" default-x="52" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">end</beam>
+   </note>
+   <note color="#000000" default-x="100" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">begin</beam>
+   </note>
+   <note color="#000000" default-x="157" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">end</beam>
+   </note>
+  </measure>
+  <!--============== Part: P2, Measure: 17 ==============-->
+  <measure number="17" width="206">
+   <note color="#000000" default-x="55" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">begin</beam>
+   </note>
+   <note color="#000000" default-x="93" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">end</beam>
+   </note>
+   <note color="#000000" default-x="131" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">begin</beam>
+   </note>
+   <note color="#000000" default-x="169" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">end</beam>
+   </note>
+  </measure>
+  <!--============== Part: P2, Measure: 18 ==============-->
+  <measure number="18" width="383">
+   <attributes>
+    <time color="#000000">
+     <beats>2</beats>
+     <beat-type>2</beat-type>
+    </time>
+   </attributes>
+   <note color="#000000" default-x="32" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">begin</beam>
+   </note>
+   <note color="#000000" default-x="70" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">continue</beam>
+   </note>
+   <note color="#000000" default-x="107" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">continue</beam>
+   </note>
+   <note color="#000000" default-x="145" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">end</beam>
+   </note>
+   <note color="#000000" default-x="183" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">begin</beam>
+   </note>
+   <note color="#000000" default-x="220" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">continue</beam>
+   </note>
+   <note color="#000000" default-x="268" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">continue</beam>
+   </note>
+   <note color="#000000" default-x="325" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">end</beam>
+   </note>
+  </measure>
+  <!--============== Part: P2, Measure: 19 ==============-->
+  <measure number="19" width="369">
+   <print new-system="yes">
+    <system-layout>
+     <system-margins>
+      <left-margin>22</left-margin>
+      <right-margin>0</right-margin>
+     </system-margins>
+    </system-layout>
+    <staff-layout number="1">
+     <staff-distance>54</staff-distance>
+    </staff-layout>
+   </print>
+   <attributes>
+    <staff-details number="1" print-object="yes" />
+   </attributes>
+   <note color="#000000" default-x="57" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">begin</beam>
+   </note>
+   <note color="#000000" default-x="93" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">continue</beam>
+   </note>
+   <note color="#000000" default-x="132" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">continue</beam>
+   </note>
+   <note color="#000000" default-x="161" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">end</beam>
+   </note>
+   <note color="#000000" default-x="206" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">begin</beam>
+   </note>
+   <note color="#000000" default-x="243" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">continue</beam>
+   </note>
+   <note color="#000000" default-x="280" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">continue</beam>
+   </note>
+   <note color="#000000" default-x="324" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">end</beam>
+   </note>
+  </measure>
+  <!--============== Part: P2, Measure: 20 ==============-->
+  <measure number="20" width="289">
+   <note color="#000000" default-x="55" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">begin</beam>
+   </note>
+   <note color="#000000" default-x="85" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">continue</beam>
+   </note>
+   <note color="#000000" default-x="114" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">continue</beam>
+   </note>
+   <note color="#000000" default-x="143" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">end</beam>
+   </note>
+   <note color="#000000" default-x="172" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">begin</beam>
+   </note>
+   <note color="#000000" default-x="201" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">continue</beam>
+   </note>
+   <note color="#000000" default-x="230" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">continue</beam>
+   </note>
+   <note color="#000000" default-x="260" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">end</beam>
+   </note>
+  </measure>
+  <!--============== Part: P2, Measure: 21 ==============-->
+  <measure number="21" width="109">
+   <note color="#000000" default-x="15">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>1024</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>whole</type>
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P2, Measure: 22 ==============-->
+  <measure number="22" width="264">
+   <note color="#000000" default-x="15" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note default-x="44">
+    <rest />
+    <duration>384</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <dot />
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="131" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>85</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <time-modification>
+     <actual-notes>3</actual-notes>
+     <normal-notes>2</normal-notes>
+     <normal-type>eighth</normal-type>
+    </time-modification>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">begin</beam>
+    <notations>
+     <tuplet type="start" bracket="no" number="1" default-y="-20" placement="above" />
+    </notations>
+   </note>
+   <note color="#000000" default-x="155" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>85</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <time-modification>
+     <actual-notes>3</actual-notes>
+     <normal-notes>2</normal-notes>
+     <normal-type>eighth</normal-type>
+    </time-modification>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">continue</beam>
+   </note>
+   <note color="#000000" default-x="179" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>86</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <time-modification>
+     <actual-notes>3</actual-notes>
+     <normal-notes>2</normal-notes>
+     <normal-type>eighth</normal-type>
+    </time-modification>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">end</beam>
+    <notations>
+     <tuplet type="stop" bracket="no" number="1" default-y="-20" placement="above" />
+    </notations>
+   </note>
+   <note default-x="203">
+    <rest />
+    <duration>128</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="232" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P2, Measure: 23 ==============-->
+  <measure number="23" width="260">
+   <print new-page="yes">
+    <system-layout>
+     <system-margins>
+      <left-margin>22</left-margin>
+      <right-margin>0</right-margin>
+     </system-margins>
+    </system-layout>
+    <staff-layout number="1">
+     <staff-distance>70</staff-distance>
+    </staff-layout>
+   </print>
+   <attributes>
+    <staff-details number="1" print-object="yes" />
+   </attributes>
+   <note color="#000000" default-x="57" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">begin</beam>
+   </note>
+   <note color="#000000" default-x="82" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">continue</beam>
+   </note>
+   <note color="#000000" default-x="108" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">continue</beam>
+   </note>
+   <note color="#000000" default-x="133" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">end</beam>
+   </note>
+   <note color="#000000" default-x="159" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">begin</beam>
+   </note>
+   <note color="#000000" default-x="184" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">continue</beam>
+   </note>
+   <note color="#000000" default-x="210" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">continue</beam>
+   </note>
+   <note color="#000000" default-x="235" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">end</beam>
+   </note>
+  </measure>
+  <!--============== Part: P2, Measure: 24 ==============-->
+  <measure number="24" width="155">
+   <note color="#000000" default-x="15" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>512</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>half</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note default-x="85">
+    <rest />
+    <duration>256</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="120" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>256</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P2, Measure: 25 ==============-->
+  <measure number="25" width="218">
+   <note color="#000000" default-x="15" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">begin</beam>
+   </note>
+   <note color="#000000" default-x="40" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">continue</beam>
+   </note>
+   <note color="#000000" default-x="65" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">continue</beam>
+   </note>
+   <note color="#000000" default-x="91" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">end</beam>
+   </note>
+   <note color="#000000" default-x="116" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">begin</beam>
+   </note>
+   <note color="#000000" default-x="142" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">continue</beam>
+   </note>
+   <note color="#000000" default-x="167" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">continue</beam>
+   </note>
+   <note color="#000000" default-x="192" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">end</beam>
+   </note>
+  </measure>
+  <!--============== Part: P2, Measure: 26 ==============-->
+  <measure number="26" width="155">
+   <note color="#000000" default-x="15" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>512</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>half</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note default-x="85">
+    <rest />
+    <duration>256</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="120" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>256</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P2, Measure: 27 ==============-->
+  <measure number="27" width="243">
+   <attributes>
+    <time color="#000000">
+     <beats>3</beats>
+     <beat-type>2</beat-type>
+    </time>
+   </attributes>
+   <note default-x="32">
+    <rest />
+    <duration>1536</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>whole</type>
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P2, Measure: 28 ==============-->
+  <measure number="28" width="162">
+   <print new-system="yes">
+    <system-layout>
+     <system-margins>
+      <left-margin>22</left-margin>
+      <right-margin>0</right-margin>
+     </system-margins>
+    </system-layout>
+    <staff-layout number="1">
+     <staff-distance>70</staff-distance>
+    </staff-layout>
+   </print>
+   <attributes>
+    <staff-details number="1" print-object="yes" />
+   </attributes>
+   <note color="#000000" default-x="57">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>1536</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>whole</type>
+    <dot />
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P2, Measure: 29 ==============-->
+  <measure number="29" width="232">
+   <note color="#000000" default-x="15" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>768</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>half</type>
+    <dot />
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="120" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>256</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="160" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>512</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>half</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P2, Measure: 30 ==============-->
+  <measure number="30" width="265">
+   <note color="#000000" default-x="15" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>512</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>half</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note default-x="96">
+    <rest />
+    <duration>512</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>half</type>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="192" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>512</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>half</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P2, Measure: 31 ==============-->
+  <measure number="31" width="372">
+   <note color="#000000" default-x="15" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">begin</beam>
+   </note>
+   <note color="#000000" default-x="43" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">continue</beam>
+   </note>
+   <note color="#000000" default-x="71" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">continue</beam>
+   </note>
+   <note color="#000000" default-x="100" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">end</beam>
+   </note>
+   <note color="#000000" default-x="128" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">begin</beam>
+   </note>
+   <note color="#000000" default-x="163" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">continue</beam>
+   </note>
+   <note color="#000000" default-x="198" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">continue</beam>
+   </note>
+   <note color="#000000" default-x="226" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">end</beam>
+   </note>
+   <note color="#000000" default-x="255" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">begin</beam>
+   </note>
+   <note color="#000000" default-x="283" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">continue</beam>
+   </note>
+   <note color="#000000" default-x="311" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">continue</beam>
+   </note>
+   <note color="#000000" default-x="340" default-y="9">
+    <pitch>
+     <step>B</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">end</beam>
+   </note>
+  </measure>
+  <!--============== Part: P2, Measure: 32 ==============-->
+  <measure number="32" width="360">
+   <print new-page="yes">
+    <system-layout>
+     <system-margins>
+      <left-margin>22</left-margin>
+      <right-margin>0</right-margin>
+     </system-margins>
+    </system-layout>
+    <staff-layout number="1">
+     <staff-distance>54</staff-distance>
+    </staff-layout>
+   </print>
+   <attributes>
+    <staff-details number="1" print-object="yes" />
+   </attributes>
+   <note color="#000000" default-x="57" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note default-x="85">
+    <rest />
+    <duration>384</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <dot />
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="167" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>85</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <time-modification>
+     <actual-notes>3</actual-notes>
+     <normal-notes>2</normal-notes>
+     <normal-type>eighth</normal-type>
+    </time-modification>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">begin</beam>
+    <notations>
+     <tuplet type="start" bracket="no" number="1" default-y="-20" placement="above" />
+    </notations>
+   </note>
+   <note color="#000000" default-x="189" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>85</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <time-modification>
+     <actual-notes>3</actual-notes>
+     <normal-notes>2</normal-notes>
+     <normal-type>eighth</normal-type>
+    </time-modification>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">continue</beam>
+   </note>
+   <note color="#000000" default-x="216" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>86</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <time-modification>
+     <actual-notes>3</actual-notes>
+     <normal-notes>2</normal-notes>
+     <normal-type>eighth</normal-type>
+    </time-modification>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">end</beam>
+    <notations>
+     <tuplet type="stop" bracket="no" number="1" default-y="-20" placement="above" />
+    </notations>
+   </note>
+   <note color="#000000" default-x="239" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>256</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note default-x="286">
+    <rest />
+    <duration>256</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="323" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>256</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P2, Measure: 33 ==============-->
+  <measure number="33" width="81">
+   <attributes>
+    <time color="#000000">
+     <beats>3</beats>
+     <beat-type>8</beat-type>
+    </time>
+   </attributes>
+   <note color="#000000" default-x="31" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>384</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <dot />
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P2, Measure: 34 ==============-->
+  <measure number="34" width="145">
+   <note default-x="15">
+    <rest />
+    <duration>128</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="63" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>256</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P2, Measure: 35 ==============-->
+  <measure number="35" width="158">
+   <note default-x="55">
+    <rest />
+    <duration>384</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <dot />
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P2, Measure: 36 ==============-->
+  <measure number="36" width="107">
+   <attributes>
+    <time color="#000000">
+     <beats>6</beats>
+     <beat-type>8</beat-type>
+    </time>
+   </attributes>
+   <note color="#000000" default-x="32" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>768</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>half</type>
+    <dot />
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P2, Measure: 37 ==============-->
+  <measure number="37" width="179">
+   <note color="#000000" default-x="15" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">begin</beam>
+   </note>
+   <note color="#000000" default-x="41" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">continue</beam>
+   </note>
+   <note color="#000000" default-x="68" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">end</beam>
+   </note>
+   <note color="#000000" default-x="99" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">begin</beam>
+   </note>
+   <note color="#000000" default-x="125" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">continue</beam>
+   </note>
+   <note color="#000000" default-x="152" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">end</beam>
+   </note>
+  </measure>
+  <!--============== Part: P2, Measure: 38 ==============-->
+  <measure number="38" width="492">
+   <print new-system="yes">
+    <system-layout>
+     <system-margins>
+      <left-margin>22</left-margin>
+      <right-margin>0</right-margin>
+     </system-margins>
+    </system-layout>
+    <staff-layout number="1">
+     <staff-distance>54</staff-distance>
+    </staff-layout>
+   </print>
+   <attributes>
+    <staff-details number="1" print-object="yes" />
+   </attributes>
+   <note color="#000000" default-x="57" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>384</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <dot />
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="304" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>192</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <dot />
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">begin</beam>
+   </note>
+   <note color="#000000" default-x="398" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>64</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>16th</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">continue</beam>
+    <beam number="2">backward hook</beam>
+   </note>
+   <note color="#000000" default-x="438" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">end</beam>
+   </note>
+  </measure>
+  <!--============== Part: P2, Measure: 39 ==============-->
+  <measure number="39" width="540">
+   <attributes>
+    <time color="#000000">
+     <beats>9</beats>
+     <beat-type>8</beat-type>
+    </time>
+   </attributes>
+   <note color="#000000" default-x="32" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>384</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <dot />
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note default-x="131">
+    <rest />
+    <duration>128</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="212" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>256</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note default-x="380">
+    <rest />
+    <duration>384</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <dot />
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P2, Measure: 40 ==============-->
+  <measure number="40" width="414">
+   <print new-system="yes">
+    <system-layout>
+     <system-margins>
+      <left-margin>22</left-margin>
+      <right-margin>0</right-margin>
+     </system-margins>
+    </system-layout>
+    <staff-layout number="1">
+     <staff-distance>54</staff-distance>
+    </staff-layout>
+   </print>
+   <attributes>
+    <staff-details number="1" print-object="yes" />
+   </attributes>
+   <note color="#000000" default-x="57" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>384</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <dot />
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note default-x="120">
+    <rest />
+    <duration>128</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="172" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>256</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="276" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="328" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>256</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P2, Measure: 41 ==============-->
+  <measure number="41" width="303">
+   <attributes>
+    <time color="#000000">
+     <beats>5</beats>
+     <beat-type>4</beat-type>
+    </time>
+   </attributes>
+   <note color="#000000" default-x="33" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>768</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>half</type>
+    <dot />
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="185" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>512</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>half</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P2, Measure: 42 ==============-->
+  <measure number="42" width="315">
+   <note default-x="15">
+    <rest />
+    <duration>768</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>half</type>
+    <dot />
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="194" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>256</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="241" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>256</duration>
+    <instrument id="P2-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <barline>
+    <bar-style>light-heavy</bar-style>
+   </barline>
+  </measure>
+ </part>
+ <part id="P3">
+  <!--============== Part: P3, Measure: 1 ==============-->
+  <measure number="1" width="362">
+   <print new-page="yes">
+    <system-layout>
+     <system-margins>
+      <left-margin>22</left-margin>
+      <right-margin>0</right-margin>
+     </system-margins>
+    </system-layout>
+    <staff-layout number="1">
+     <staff-distance>65</staff-distance>
+    </staff-layout>
+   </print>
+   <attributes>
+    <divisions>256</divisions>
+    <key color="#000000">
+     <fifths>0</fifths>
+     <mode>major</mode>
+    </key>
+    <time color="#000000">
+     <beats>4</beats>
+     <beat-type>4</beat-type>
+    </time>
+    <staves>1</staves>
+    <clef number="1" color="#000000">
+     <sign>G</sign>
+     <line>2</line>
+    </clef>
+    <staff-details number="1" print-object="yes" />
+   </attributes>
+   <note color="#000000" default-x="76" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>384</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <dot />
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="178" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="206" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>192</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <dot />
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">begin</beam>
+   </note>
+   <note color="#000000" default-x="251" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>64</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>16th</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">end</beam>
+    <beam number="2">backward hook</beam>
+   </note>
+   <note color="#000000" default-x="272" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>64</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>16th</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">begin</beam>
+    <beam number="2">begin</beam>
+   </note>
+   <note color="#000000" default-x="292" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>64</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>16th</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">continue</beam>
+    <beam number="2">continue</beam>
+   </note>
+   <note color="#000000" default-x="313" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>64</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>16th</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">continue</beam>
+    <beam number="2">continue</beam>
+   </note>
+   <note color="#000000" default-x="341" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>64</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>16th</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">end</beam>
+    <beam number="2">end</beam>
+   </note>
+  </measure>
+  <!--============== Part: P3, Measure: 2 ==============-->
+  <measure number="2" width="319">
+   <note color="#000000" default-x="15" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>192</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <dot />
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">begin</beam>
+   </note>
+   <note color="#000000" default-x="67" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>64</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>16th</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">end</beam>
+    <beam number="2">backward hook</beam>
+   </note>
+   <note color="#000000" default-x="88" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">begin</beam>
+   </note>
+   <note color="#000000" default-x="115" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>64</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>16th</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">continue</beam>
+    <beam number="2">begin</beam>
+   </note>
+   <note color="#000000" default-x="135" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>64</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>16th</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">end</beam>
+    <beam number="2">end</beam>
+   </note>
+   <note color="#000000" default-x="155" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>64</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>16th</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">begin</beam>
+    <beam number="2">forward hook</beam>
+   </note>
+   <note color="#000000" default-x="176" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">continue</beam>
+   </note>
+   <note color="#000000" default-x="217" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>64</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>16th</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">end</beam>
+    <beam number="2">backward hook</beam>
+   </note>
+   <note color="#000000" default-x="237" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>64</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>16th</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">begin</beam>
+    <beam number="2">begin</beam>
+   </note>
+   <note color="#000000" default-x="258" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>64</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>16th</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">continue</beam>
+    <beam number="2">continue</beam>
+   </note>
+   <note color="#000000" default-x="278" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>64</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>16th</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">continue</beam>
+    <beam number="2">continue</beam>
+   </note>
+   <note color="#000000" default-x="299" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>64</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>16th</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">end</beam>
+    <beam number="2">end</beam>
+   </note>
+  </measure>
+  <!--============== Part: P3, Measure: 3 ==============-->
+  <measure number="3" width="101">
+   <note color="#000000" default-x="15">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>1024</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>whole</type>
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P3, Measure: 4 ==============-->
+  <measure number="4" width="249">
+   <note default-x="15">
+    <rest />
+    <duration>768</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>half</type>
+    <dot />
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="190" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>256</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P3, Measure: 5 ==============-->
+  <measure number="5" width="297">
+   <print new-system="yes">
+    <system-layout>
+     <system-margins>
+      <left-margin>22</left-margin>
+      <right-margin>0</right-margin>
+     </system-margins>
+    </system-layout>
+    <staff-layout number="1">
+     <staff-distance>65</staff-distance>
+    </staff-layout>
+   </print>
+   <attributes>
+    <staff-details number="1" print-object="yes" />
+   </attributes>
+   <note default-x="57">
+    <rest />
+    <duration>1024</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>whole</type>
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P3, Measure: 6 ==============-->
+  <measure number="6" width="180">
+   <note default-x="15">
+    <rest />
+    <duration>256</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="56" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>512</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>half</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="139" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>256</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P3, Measure: 7 ==============-->
+  <measure number="7" width="332">
+   <note color="#000000" default-x="55" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>384</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <dot />
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="156" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="186" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="221" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>384</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <dot />
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P3, Measure: 8 ==============-->
+  <measure number="8" width="221">
+   <attributes>
+    <time color="#000000">
+     <beats>3</beats>
+     <beat-type>4</beat-type>
+    </time>
+   </attributes>
+   <note color="#000000" default-x="33" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>384</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <dot />
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="123" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="153" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>192</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <dot />
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">begin</beam>
+   </note>
+   <note color="#000000" default-x="198" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>64</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>16th</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">end</beam>
+    <beam number="2">backward hook</beam>
+   </note>
+  </measure>
+  <!--============== Part: P3, Measure: 9 ==============-->
+  <measure number="9" width="266">
+   <print new-page="yes">
+    <system-layout>
+     <system-margins>
+      <left-margin>22</left-margin>
+      <right-margin>0</right-margin>
+     </system-margins>
+    </system-layout>
+    <staff-layout number="1">
+     <staff-distance>54</staff-distance>
+    </staff-layout>
+   </print>
+   <attributes>
+    <staff-details number="1" print-object="yes" />
+   </attributes>
+   <note color="#000000" default-x="57" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>192</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <dot />
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">begin</beam>
+   </note>
+   <note color="#000000" default-x="106" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>64</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>16th</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">end</beam>
+    <beam number="2">backward hook</beam>
+   </note>
+   <note color="#000000" default-x="127" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">begin</beam>
+   </note>
+   <note color="#000000" default-x="155" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>64</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>16th</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">continue</beam>
+    <beam number="2">begin</beam>
+   </note>
+   <note color="#000000" default-x="176" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>64</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>16th</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">end</beam>
+    <beam number="2">end</beam>
+   </note>
+   <note color="#000000" default-x="197" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>64</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>16th</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">begin</beam>
+    <beam number="2">forward hook</beam>
+   </note>
+   <note color="#000000" default-x="218" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">continue</beam>
+   </note>
+   <note color="#000000" default-x="245" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>64</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>16th</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">end</beam>
+    <beam number="2">backward hook</beam>
+   </note>
+  </measure>
+  <!--============== Part: P3, Measure: 10 ==============-->
+  <measure number="10" width="91">
+   <note color="#000000" default-x="15" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>768</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>half</type>
+    <dot />
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P3, Measure: 11 ==============-->
+  <measure number="11" width="149">
+   <note color="#000000" default-x="15" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>768</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>half</type>
+    <dot />
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P3, Measure: 12 ==============-->
+  <measure number="12" width="192">
+   <note default-x="15">
+    <rest />
+    <duration>768</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>half</type>
+    <dot />
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P3, Measure: 13 ==============-->
+  <measure number="13" width="153">
+   <note color="#000000" default-x="15" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>384</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <dot />
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note default-x="87">
+    <rest />
+    <duration>128</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="115" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>256</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P3, Measure: 14 ==============-->
+  <measure number="14" width="157">
+   <note color="#000000" default-x="25" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="54" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>384</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <dot />
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="120" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>256</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P3, Measure: 15 ==============-->
+  <measure number="15" width="226">
+   <print new-system="yes">
+    <system-layout>
+     <system-margins>
+      <left-margin>22</left-margin>
+      <right-margin>0</right-margin>
+     </system-margins>
+    </system-layout>
+    <staff-layout number="1">
+     <staff-distance>54</staff-distance>
+    </staff-layout>
+   </print>
+   <attributes>
+    <time color="#000000">
+     <beats>2</beats>
+     <beat-type>4</beat-type>
+    </time>
+    <staff-details number="1" print-object="yes" />
+   </attributes>
+   <note color="#000000" default-x="76" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>384</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <dot />
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="189" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P3, Measure: 16 ==============-->
+  <measure number="16" width="215">
+   <note color="#000000" default-x="15" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>192</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <dot />
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">begin</beam>
+   </note>
+   <note color="#000000" default-x="71" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>64</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>16th</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">end</beam>
+    <beam number="2">backward hook</beam>
+   </note>
+   <note color="#000000" default-x="100" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>64</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>16th</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">begin</beam>
+    <beam number="2">begin</beam>
+   </note>
+   <note color="#000000" default-x="129" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>64</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>16th</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">continue</beam>
+    <beam number="2">continue</beam>
+   </note>
+   <note color="#000000" default-x="157" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>64</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>16th</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">continue</beam>
+    <beam number="2">continue</beam>
+   </note>
+   <note color="#000000" default-x="186" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>64</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>16th</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">end</beam>
+    <beam number="2">end</beam>
+   </note>
+  </measure>
+  <!--============== Part: P3, Measure: 17 ==============-->
+  <measure number="17" width="206">
+   <note color="#000000" default-x="55" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>384</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <dot />
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="169" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P3, Measure: 18 ==============-->
+  <measure number="18" width="383">
+   <attributes>
+    <time color="#000000">
+     <beats>2</beats>
+     <beat-type>2</beat-type>
+    </time>
+   </attributes>
+   <note color="#000000" default-x="32" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>384</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <dot />
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="145" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="183" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>192</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <dot />
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">begin</beam>
+   </note>
+   <note color="#000000" default-x="239" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>64</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>16th</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">end</beam>
+    <beam number="2">backward hook</beam>
+   </note>
+   <note color="#000000" default-x="268" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>64</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>16th</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">begin</beam>
+    <beam number="2">begin</beam>
+   </note>
+   <note color="#000000" default-x="297" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>64</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>16th</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">continue</beam>
+    <beam number="2">continue</beam>
+   </note>
+   <note color="#000000" default-x="325" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>64</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>16th</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">continue</beam>
+    <beam number="2">continue</beam>
+   </note>
+   <note color="#000000" default-x="355" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>64</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>16th</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">end</beam>
+    <beam number="2">end</beam>
+   </note>
+  </measure>
+  <!--============== Part: P3, Measure: 19 ==============-->
+  <measure number="19" width="369">
+   <print new-system="yes">
+    <system-layout>
+     <system-margins>
+      <left-margin>22</left-margin>
+      <right-margin>0</right-margin>
+     </system-margins>
+    </system-layout>
+    <staff-layout number="1">
+     <staff-distance>54</staff-distance>
+    </staff-layout>
+   </print>
+   <attributes>
+    <staff-details number="1" print-object="yes" />
+   </attributes>
+   <note color="#000000" default-x="57" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>192</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <dot />
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">begin</beam>
+   </note>
+   <note color="#000000" default-x="110" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>64</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>16th</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">end</beam>
+    <beam number="2">backward hook</beam>
+   </note>
+   <note color="#000000" default-x="132" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">begin</beam>
+   </note>
+   <note color="#000000" default-x="161" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>64</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>16th</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">continue</beam>
+    <beam number="2">begin</beam>
+   </note>
+   <note color="#000000" default-x="184" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>64</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>16th</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">end</beam>
+    <beam number="2">end</beam>
+   </note>
+   <note color="#000000" default-x="206" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>64</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>16th</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">begin</beam>
+    <beam number="2">forward hook</beam>
+   </note>
+   <note color="#000000" default-x="228" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">continue</beam>
+   </note>
+   <note color="#000000" default-x="257" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>64</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>16th</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">end</beam>
+    <beam number="2">backward hook</beam>
+   </note>
+   <note color="#000000" default-x="280" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>64</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>16th</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">begin</beam>
+    <beam number="2">begin</beam>
+   </note>
+   <note color="#000000" default-x="302" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>64</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>16th</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">continue</beam>
+    <beam number="2">continue</beam>
+   </note>
+   <note color="#000000" default-x="324" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>64</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>16th</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">continue</beam>
+    <beam number="2">continue</beam>
+   </note>
+   <note color="#000000" default-x="347" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>64</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>16th</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">end</beam>
+    <beam number="2">end</beam>
+   </note>
+  </measure>
+  <!--============== Part: P3, Measure: 20 ==============-->
+  <measure number="20" width="289">
+   <note color="#000000" default-x="55" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>384</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <dot />
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="143" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="172" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="201" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>384</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <dot />
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P3, Measure: 21 ==============-->
+  <measure number="21" width="109">
+   <note color="#000000" default-x="15">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>1024</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>whole</type>
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P3, Measure: 22 ==============-->
+  <measure number="22" width="264">
+   <note default-x="15">
+    <rest />
+    <duration>768</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>half</type>
+    <dot />
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="203" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>256</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P3, Measure: 23 ==============-->
+  <measure number="23" width="260">
+   <print new-page="yes">
+    <system-layout>
+     <system-margins>
+      <left-margin>22</left-margin>
+      <right-margin>0</right-margin>
+     </system-margins>
+    </system-layout>
+    <staff-layout number="1">
+     <staff-distance>69</staff-distance>
+    </staff-layout>
+   </print>
+   <attributes>
+    <staff-details number="1" print-object="yes" />
+   </attributes>
+   <note default-x="57">
+    <rest />
+    <duration>1024</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>whole</type>
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P3, Measure: 24 ==============-->
+  <measure number="24" width="155">
+   <note default-x="15">
+    <rest />
+    <duration>256</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="50" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>512</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>half</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="120" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>256</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P3, Measure: 25 ==============-->
+  <measure number="25" width="218">
+   <note color="#000000" default-x="15" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>256</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="65" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>256</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="116" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>256</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="167" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>256</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P3, Measure: 26 ==============-->
+  <measure number="26" width="155">
+   <note default-x="15">
+    <rest />
+    <duration>256</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="50" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>512</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>half</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="120" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>256</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P3, Measure: 27 ==============-->
+  <measure number="27" width="243">
+   <attributes>
+    <time color="#000000">
+     <beats>3</beats>
+     <beat-type>2</beat-type>
+    </time>
+   </attributes>
+   <note color="#000000" default-x="32" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>256</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="67" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>256</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="102" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>256</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="137" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>256</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="172" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>256</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="208" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>256</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P3, Measure: 28 ==============-->
+  <measure number="28" width="162">
+   <print new-system="yes">
+    <system-layout>
+     <system-margins>
+      <left-margin>22</left-margin>
+      <right-margin>0</right-margin>
+     </system-margins>
+    </system-layout>
+    <staff-layout number="1">
+     <staff-distance>70</staff-distance>
+    </staff-layout>
+   </print>
+   <attributes>
+    <staff-details number="1" print-object="yes" />
+   </attributes>
+   <note color="#000000" default-x="57">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>1536</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>whole</type>
+    <dot />
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P3, Measure: 29 ==============-->
+  <measure number="29" width="232">
+   <note color="#000000" default-x="15" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>768</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>half</type>
+    <dot />
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="120" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>512</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>half</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="193" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>256</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P3, Measure: 30 ==============-->
+  <measure number="30" width="265">
+   <note default-x="15">
+    <rest />
+    <duration>512</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>half</type>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="96" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>512</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>half</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="192" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>512</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>half</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P3, Measure: 31 ==============-->
+  <measure number="31" width="372">
+   <note default-x="15">
+    <rest />
+    <duration>384</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <dot />
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="100" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="128">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>1024</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>whole</type>
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P3, Measure: 32 ==============-->
+  <measure number="32" width="360">
+   <print new-page="yes">
+    <system-layout>
+     <system-margins>
+      <left-margin>22</left-margin>
+      <right-margin>0</right-margin>
+     </system-margins>
+    </system-layout>
+    <staff-layout number="1">
+     <staff-distance>54</staff-distance>
+    </staff-layout>
+   </print>
+   <attributes>
+    <staff-details number="1" print-object="yes" />
+   </attributes>
+   <note default-x="57">
+    <rest />
+    <duration>768</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>half</type>
+    <dot />
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="239" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="267" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>384</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <dot />
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="323" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>256</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P3, Measure: 33 ==============-->
+  <measure number="33" width="81">
+   <attributes>
+    <time color="#000000">
+     <beats>3</beats>
+     <beat-type>8</beat-type>
+    </time>
+   </attributes>
+   <note color="#000000" default-x="31" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>384</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <dot />
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P3, Measure: 34 ==============-->
+  <measure number="34" width="145">
+   <note color="#000000" default-x="15" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">begin</beam>
+   </note>
+   <note color="#000000" default-x="63" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">continue</beam>
+   </note>
+   <note color="#000000" default-x="104" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">end</beam>
+   </note>
+  </measure>
+  <!--============== Part: P3, Measure: 35 ==============-->
+  <measure number="35" width="158">
+   <note color="#000000" default-x="55" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">begin</beam>
+   </note>
+   <note color="#000000" default-x="104" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">continue</beam>
+   </note>
+   <note color="#000000" default-x="131" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">end</beam>
+   </note>
+  </measure>
+  <!--============== Part: P3, Measure: 36 ==============-->
+  <measure number="36" width="107">
+   <attributes>
+    <time color="#000000">
+     <beats>6</beats>
+     <beat-type>8</beat-type>
+    </time>
+   </attributes>
+   <note color="#000000" default-x="32" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>768</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>half</type>
+    <dot />
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P3, Measure: 37 ==============-->
+  <measure number="37" width="179">
+   <note color="#000000" default-x="15" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>384</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <dot />
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note default-x="99">
+    <rest />
+    <duration>384</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <dot />
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P3, Measure: 38 ==============-->
+  <measure number="38" width="492">
+   <print new-system="yes">
+    <system-layout>
+     <system-margins>
+      <left-margin>22</left-margin>
+      <right-margin>0</right-margin>
+     </system-margins>
+    </system-layout>
+    <staff-layout number="1">
+     <staff-distance>54</staff-distance>
+    </staff-layout>
+   </print>
+   <attributes>
+    <staff-details number="1" print-object="yes" />
+   </attributes>
+   <note color="#000000" default-x="57" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>64</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>16th</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">begin</beam>
+    <beam number="2">forward hook</beam>
+   </note>
+   <note color="#000000" default-x="98" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">continue</beam>
+   </note>
+   <note color="#000000" default-x="179" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>64</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>16th</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">continue</beam>
+    <beam number="2">begin</beam>
+   </note>
+   <note color="#000000" default-x="220" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>64</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>16th</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">continue</beam>
+    <beam number="2">continue</beam>
+   </note>
+   <note color="#000000" default-x="260" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>64</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>16th</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">end</beam>
+    <beam number="2">end</beam>
+   </note>
+   <note color="#000000" default-x="304" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">begin</beam>
+   </note>
+   <note color="#000000" default-x="357" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">continue</beam>
+   </note>
+   <note color="#000000" default-x="438" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">end</beam>
+   </note>
+  </measure>
+  <!--============== Part: P3, Measure: 39 ==============-->
+  <measure number="39" width="540">
+   <attributes>
+    <time color="#000000">
+     <beats>9</beats>
+     <beat-type>8</beat-type>
+    </time>
+   </attributes>
+   <note color="#000000" default-x="32" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>384</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <dot />
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="131" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">begin</beam>
+   </note>
+   <note color="#000000" default-x="212" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">continue</beam>
+   </note>
+   <note color="#000000" default-x="294" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">end</beam>
+   </note>
+   <note color="#000000" default-x="380" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">begin</beam>
+   </note>
+   <note color="#000000" default-x="434" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">continue</beam>
+   </note>
+   <note color="#000000" default-x="487" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">end</beam>
+   </note>
+  </measure>
+  <!--============== Part: P3, Measure: 40 ==============-->
+  <measure number="40" width="414">
+   <print new-system="yes">
+    <system-layout>
+     <system-margins>
+      <left-margin>22</left-margin>
+      <right-margin>0</right-margin>
+     </system-margins>
+    </system-layout>
+    <staff-layout number="1">
+     <staff-distance>54</staff-distance>
+    </staff-layout>
+   </print>
+   <attributes>
+    <staff-details number="1" print-object="yes" />
+   </attributes>
+   <note color="#000000" default-x="57" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>384</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <dot />
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="120" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">begin</beam>
+   </note>
+   <note color="#000000" default-x="172" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">continue</beam>
+   </note>
+   <note color="#000000" default-x="224" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">end</beam>
+   </note>
+   <note color="#000000" default-x="276" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>384</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <dot />
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P3, Measure: 41 ==============-->
+  <measure number="41" width="303">
+   <attributes>
+    <time color="#000000">
+     <beats>5</beats>
+     <beat-type>4</beat-type>
+    </time>
+   </attributes>
+   <note color="#000000" default-x="33" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>256</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="80" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>256</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="138" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>256</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="185" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>256</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="252" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>256</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P3, Measure: 42 ==============-->
+  <measure number="42" width="315">
+   <note default-x="15">
+    <rest />
+    <duration>512</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>half</type>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="129" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>512</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>half</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note default-x="241">
+    <rest />
+    <duration>256</duration>
+    <instrument id="P3-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <staff>1</staff>
+   </note>
+   <barline>
+    <bar-style>light-heavy</bar-style>
+   </barline>
+  </measure>
+ </part>
+ <part id="P4">
+  <!--============== Part: P4, Measure: 1 ==============-->
+  <measure number="1" width="362">
+   <print new-page="yes">
+    <system-layout>
+     <system-margins>
+      <left-margin>22</left-margin>
+      <right-margin>0</right-margin>
+     </system-margins>
+    </system-layout>
+    <staff-layout number="1">
+     <staff-distance>65</staff-distance>
+    </staff-layout>
+   </print>
+   <attributes>
+    <divisions>256</divisions>
+    <key color="#000000">
+     <fifths>0</fifths>
+     <mode>major</mode>
+    </key>
+    <time color="#000000">
+     <beats>4</beats>
+     <beat-type>4</beat-type>
+    </time>
+    <staves>1</staves>
+    <clef number="1" color="#000000">
+     <sign>G</sign>
+     <line>2</line>
+    </clef>
+    <staff-details number="1" print-object="yes" />
+   </attributes>
+   <note color="#000000" default-x="76" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>768</duration>
+    <instrument id="P4-I1" />
+    <voice>1</voice>
+    <type>half</type>
+    <dot />
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="272" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>256</duration>
+    <instrument id="P4-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P4, Measure: 2 ==============-->
+  <measure number="2" width="319">
+   <note color="#000000" default-x="15">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>1024</duration>
+    <instrument id="P4-I1" />
+    <voice>1</voice>
+    <type>whole</type>
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P4, Measure: 3 ==============-->
+  <measure number="3" width="101">
+   <note color="#000000" default-x="15">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>1024</duration>
+    <instrument id="P4-I1" />
+    <voice>1</voice>
+    <type>whole</type>
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P4, Measure: 4 ==============-->
+  <measure number="4" width="249">
+   <note color="#000000" default-x="15" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>512</duration>
+    <instrument id="P4-I1" />
+    <voice>1</voice>
+    <type>half</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note default-x="124">
+    <rest />
+    <duration>384</duration>
+    <instrument id="P4-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <dot />
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="216" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P4-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P4, Measure: 5 ==============-->
+  <measure number="5" width="297">
+   <print new-system="yes">
+    <system-layout>
+     <system-margins>
+      <left-margin>22</left-margin>
+      <right-margin>0</right-margin>
+     </system-margins>
+    </system-layout>
+    <staff-layout number="1">
+     <staff-distance>65</staff-distance>
+    </staff-layout>
+   </print>
+   <attributes>
+    <staff-details number="1" print-object="yes" />
+   </attributes>
+   <note color="#000000" default-x="57" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>512</duration>
+    <instrument id="P4-I1" />
+    <voice>1</voice>
+    <type>half</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note default-x="177">
+    <rest />
+    <duration>256</duration>
+    <instrument id="P4-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="237" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>256</duration>
+    <instrument id="P4-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P4, Measure: 6 ==============-->
+  <measure number="6" width="180">
+   <note color="#000000" default-x="15" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>768</duration>
+    <instrument id="P4-I1" />
+    <voice>1</voice>
+    <type>half</type>
+    <dot />
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note default-x="139">
+    <rest />
+    <duration>256</duration>
+    <instrument id="P4-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P4, Measure: 7 ==============-->
+  <measure number="7" width="332">
+   <note color="#000000" default-x="55" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>256</duration>
+    <instrument id="P4-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="126" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>768</duration>
+    <instrument id="P4-I1" />
+    <voice>1</voice>
+    <type>half</type>
+    <dot />
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P4, Measure: 8 ==============-->
+  <measure number="8" width="221">
+   <attributes>
+    <time color="#000000">
+     <beats>3</beats>
+     <beat-type>4</beat-type>
+    </time>
+   </attributes>
+   <note color="#000000" default-x="33" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>768</duration>
+    <instrument id="P4-I1" />
+    <voice>1</voice>
+    <type>half</type>
+    <dot />
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P4, Measure: 9 ==============-->
+  <measure number="9" width="266">
+   <print new-page="yes">
+    <system-layout>
+     <system-margins>
+      <left-margin>22</left-margin>
+      <right-margin>0</right-margin>
+     </system-margins>
+    </system-layout>
+    <staff-layout number="1">
+     <staff-distance>54</staff-distance>
+    </staff-layout>
+   </print>
+   <attributes>
+    <staff-details number="1" print-object="yes" />
+   </attributes>
+   <note color="#000000" default-x="57" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>768</duration>
+    <instrument id="P4-I1" />
+    <voice>1</voice>
+    <type>half</type>
+    <dot />
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P4, Measure: 10 ==============-->
+  <measure number="10" width="91">
+   <note color="#000000" default-x="15" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>768</duration>
+    <instrument id="P4-I1" />
+    <voice>1</voice>
+    <type>half</type>
+    <dot />
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P4, Measure: 11 ==============-->
+  <measure number="11" width="149">
+   <note default-x="15">
+    <rest />
+    <duration>768</duration>
+    <instrument id="P4-I1" />
+    <voice>1</voice>
+    <type>whole</type>
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P4, Measure: 12 ==============-->
+  <measure number="12" width="192">
+   <note color="#000000" default-x="15" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>512</duration>
+    <instrument id="P4-I1" />
+    <voice>1</voice>
+    <type>half</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note default-x="125">
+    <rest />
+    <duration>256</duration>
+    <instrument id="P4-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P4, Measure: 13 ==============-->
+  <measure number="13" width="153">
+   <note default-x="15">
+    <rest />
+    <duration>128</duration>
+    <instrument id="P4-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="42" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>384</duration>
+    <instrument id="P4-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <dot />
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note default-x="115">
+    <rest />
+    <duration>256</duration>
+    <instrument id="P4-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P4, Measure: 14 ==============-->
+  <measure number="14" width="157">
+   <note color="#000000" default-x="25" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>256</duration>
+    <instrument id="P4-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="82" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>512</duration>
+    <instrument id="P4-I1" />
+    <voice>1</voice>
+    <type>half</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P4, Measure: 15 ==============-->
+  <measure number="15" width="226">
+   <print new-system="yes">
+    <system-layout>
+     <system-margins>
+      <left-margin>22</left-margin>
+      <right-margin>0</right-margin>
+     </system-margins>
+    </system-layout>
+    <staff-layout number="1">
+     <staff-distance>54</staff-distance>
+    </staff-layout>
+   </print>
+   <attributes>
+    <time color="#000000">
+     <beats>2</beats>
+     <beat-type>4</beat-type>
+    </time>
+    <staff-details number="1" print-object="yes" />
+   </attributes>
+   <note color="#000000" default-x="76" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>512</duration>
+    <instrument id="P4-I1" />
+    <voice>1</voice>
+    <type>half</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P4, Measure: 16 ==============-->
+  <measure number="16" width="215">
+   <note color="#000000" default-x="15" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P4-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="52" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>384</duration>
+    <instrument id="P4-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <dot />
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P4, Measure: 17 ==============-->
+  <measure number="17" width="206">
+   <note color="#000000" default-x="38" default-y="-4">
+    <grace />
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <instrument id="P4-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="55" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>256</duration>
+    <instrument id="P4-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="113" default-y="-4">
+    <grace />
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <instrument id="P4-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="131" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>256</duration>
+    <instrument id="P4-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P4, Measure: 18 ==============-->
+  <measure number="18" width="383">
+   <attributes>
+    <time color="#000000">
+     <beats>2</beats>
+     <beat-type>2</beat-type>
+    </time>
+   </attributes>
+   <note color="#000000" default-x="32" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>768</duration>
+    <instrument id="P4-I1" />
+    <voice>1</voice>
+    <type>half</type>
+    <dot />
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="268" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>256</duration>
+    <instrument id="P4-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P4, Measure: 19 ==============-->
+  <measure number="19" width="369">
+   <print new-system="yes">
+    <system-layout>
+     <system-margins>
+      <left-margin>22</left-margin>
+      <right-margin>0</right-margin>
+     </system-margins>
+    </system-layout>
+    <staff-layout number="1">
+     <staff-distance>54</staff-distance>
+    </staff-layout>
+   </print>
+   <attributes>
+    <staff-details number="1" print-object="yes" />
+   </attributes>
+   <note color="#000000" default-x="57">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>1024</duration>
+    <instrument id="P4-I1" />
+    <voice>1</voice>
+    <type>whole</type>
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P4, Measure: 20 ==============-->
+  <measure number="20" width="289">
+   <note color="#000000" default-x="55" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>256</duration>
+    <instrument id="P4-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="114" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>768</duration>
+    <instrument id="P4-I1" />
+    <voice>1</voice>
+    <type>half</type>
+    <dot />
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P4, Measure: 21 ==============-->
+  <measure number="21" width="109">
+   <note color="#000000" default-x="15">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>1024</duration>
+    <instrument id="P4-I1" />
+    <voice>1</voice>
+    <type>whole</type>
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P4, Measure: 22 ==============-->
+  <measure number="22" width="264">
+   <note color="#000000" default-x="15" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>512</duration>
+    <instrument id="P4-I1" />
+    <voice>1</voice>
+    <type>half</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note default-x="131">
+    <rest />
+    <duration>384</duration>
+    <instrument id="P4-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <dot />
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="232" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P4-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P4, Measure: 23 ==============-->
+  <measure number="23" width="260">
+   <print new-page="yes">
+    <system-layout>
+     <system-margins>
+      <left-margin>22</left-margin>
+      <right-margin>0</right-margin>
+     </system-margins>
+    </system-layout>
+    <staff-layout number="1">
+     <staff-distance>70</staff-distance>
+    </staff-layout>
+   </print>
+   <attributes>
+    <staff-details number="1" print-object="yes" />
+   </attributes>
+   <note color="#000000" default-x="57" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>512</duration>
+    <instrument id="P4-I1" />
+    <voice>1</voice>
+    <type>half</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note default-x="159">
+    <rest />
+    <duration>256</duration>
+    <instrument id="P4-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="210" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>256</duration>
+    <instrument id="P4-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P4, Measure: 24 ==============-->
+  <measure number="24" width="155">
+   <note color="#000000" default-x="15" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>768</duration>
+    <instrument id="P4-I1" />
+    <voice>1</voice>
+    <type>half</type>
+    <dot />
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note default-x="120">
+    <rest />
+    <duration>256</duration>
+    <instrument id="P4-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P4, Measure: 25 ==============-->
+  <measure number="25" width="218">
+   <note color="#000000" default-x="15" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>512</duration>
+    <instrument id="P4-I1" />
+    <voice>1</voice>
+    <type>half</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note default-x="116">
+    <rest />
+    <duration>256</duration>
+    <instrument id="P4-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="167" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>256</duration>
+    <instrument id="P4-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P4, Measure: 26 ==============-->
+  <measure number="26" width="155">
+   <note color="#000000" default-x="15" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>768</duration>
+    <instrument id="P4-I1" />
+    <voice>1</voice>
+    <type>half</type>
+    <dot />
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note default-x="120">
+    <rest />
+    <duration>256</duration>
+    <instrument id="P4-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P4, Measure: 27 ==============-->
+  <measure number="27" width="243">
+   <attributes>
+    <time color="#000000">
+     <beats>3</beats>
+     <beat-type>2</beat-type>
+    </time>
+   </attributes>
+   <note color="#000000" default-x="32" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>512</duration>
+    <instrument id="P4-I1" />
+    <voice>1</voice>
+    <type>half</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note default-x="102">
+    <rest />
+    <duration>256</duration>
+    <instrument id="P4-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="137" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>512</duration>
+    <instrument id="P4-I1" />
+    <voice>1</voice>
+    <type>half</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note default-x="208">
+    <rest />
+    <duration>256</duration>
+    <instrument id="P4-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P4, Measure: 28 ==============-->
+  <measure number="28" width="162">
+   <print new-system="yes">
+    <system-layout>
+     <system-margins>
+      <left-margin>22</left-margin>
+      <right-margin>0</right-margin>
+     </system-margins>
+    </system-layout>
+    <staff-layout number="1">
+     <staff-distance>69</staff-distance>
+    </staff-layout>
+   </print>
+   <attributes>
+    <staff-details number="1" print-object="yes" />
+   </attributes>
+   <note color="#000000" default-x="57">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>1536</duration>
+    <instrument id="P4-I1" />
+    <voice>1</voice>
+    <type>whole</type>
+    <dot />
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P4, Measure: 29 ==============-->
+  <measure number="29" width="232">
+   <note default-x="15">
+    <rest />
+    <duration>256</duration>
+    <instrument id="P4-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="54" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>768</duration>
+    <instrument id="P4-I1" />
+    <voice>1</voice>
+    <type>half</type>
+    <dot />
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="160" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>512</duration>
+    <instrument id="P4-I1" />
+    <voice>1</voice>
+    <type>half</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P4, Measure: 30 ==============-->
+  <measure number="30" width="265">
+   <note color="#000000" default-x="15" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>512</duration>
+    <instrument id="P4-I1" />
+    <voice>1</voice>
+    <type>half</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note default-x="96">
+    <rest />
+    <duration>512</duration>
+    <instrument id="P4-I1" />
+    <voice>1</voice>
+    <type>half</type>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="192" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>512</duration>
+    <instrument id="P4-I1" />
+    <voice>1</voice>
+    <type>half</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P4, Measure: 31 ==============-->
+  <measure number="31" width="372">
+   <note color="#000000" default-x="15" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P4-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note default-x="43">
+    <rest />
+    <duration>384</duration>
+    <instrument id="P4-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <dot />
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="128" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>85</duration>
+    <instrument id="P4-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <time-modification>
+     <actual-notes>3</actual-notes>
+     <normal-notes>2</normal-notes>
+     <normal-type>eighth</normal-type>
+    </time-modification>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">begin</beam>
+    <notations>
+     <tuplet type="start" bracket="no" number="1" default-y="-20" placement="above" />
+    </notations>
+   </note>
+   <note color="#000000" default-x="151" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>85</duration>
+    <instrument id="P4-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <time-modification>
+     <actual-notes>3</actual-notes>
+     <normal-notes>2</normal-notes>
+     <normal-type>eighth</normal-type>
+    </time-modification>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">continue</beam>
+   </note>
+   <note color="#000000" default-x="175" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>86</duration>
+    <instrument id="P4-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <time-modification>
+     <actual-notes>3</actual-notes>
+     <normal-notes>2</normal-notes>
+     <normal-type>eighth</normal-type>
+    </time-modification>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">end</beam>
+    <notations>
+     <tuplet type="stop" bracket="no" number="1" default-y="-20" placement="above" />
+    </notations>
+   </note>
+   <note default-x="198">
+    <rest />
+    <duration>128</duration>
+    <instrument id="P4-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="226" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P4-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="255" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>512</duration>
+    <instrument id="P4-I1" />
+    <voice>1</voice>
+    <type>half</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P4, Measure: 32 ==============-->
+  <measure number="32" width="360">
+   <print new-page="yes">
+    <system-layout>
+     <system-margins>
+      <left-margin>22</left-margin>
+      <right-margin>0</right-margin>
+     </system-margins>
+    </system-layout>
+    <staff-layout number="1">
+     <staff-distance>54</staff-distance>
+    </staff-layout>
+   </print>
+   <attributes>
+    <staff-details number="1" print-object="yes" />
+   </attributes>
+   <note color="#000000" default-x="57" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>512</duration>
+    <instrument id="P4-I1" />
+    <voice>1</voice>
+    <type>half</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note default-x="167">
+    <rest />
+    <duration>256</duration>
+    <instrument id="P4-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="239" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>256</duration>
+    <instrument id="P4-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="286" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>512</duration>
+    <instrument id="P4-I1" />
+    <voice>1</voice>
+    <type>half</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P4, Measure: 33 ==============-->
+  <measure number="33" width="81">
+   <attributes>
+    <time color="#000000">
+     <beats>3</beats>
+     <beat-type>8</beat-type>
+    </time>
+   </attributes>
+   <note color="#000000" default-x="31" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>384</duration>
+    <instrument id="P4-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <dot />
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P4, Measure: 34 ==============-->
+  <measure number="34" width="145">
+   <note color="#000000" default-x="15" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>64</duration>
+    <instrument id="P4-I1" />
+    <voice>1</voice>
+    <type>16th</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">begin</beam>
+    <beam number="2">begin</beam>
+   </note>
+   <note color="#000000" default-x="42" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>64</duration>
+    <instrument id="P4-I1" />
+    <voice>1</voice>
+    <type>16th</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">continue</beam>
+    <beam number="2">continue</beam>
+   </note>
+   <note color="#000000" default-x="63" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>64</duration>
+    <instrument id="P4-I1" />
+    <voice>1</voice>
+    <type>16th</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">continue</beam>
+    <beam number="2">continue</beam>
+   </note>
+   <note color="#000000" default-x="83" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>64</duration>
+    <instrument id="P4-I1" />
+    <voice>1</voice>
+    <type>16th</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">continue</beam>
+    <beam number="2">continue</beam>
+   </note>
+   <note color="#000000" default-x="104" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>64</duration>
+    <instrument id="P4-I1" />
+    <voice>1</voice>
+    <type>16th</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">continue</beam>
+    <beam number="2">continue</beam>
+   </note>
+   <note color="#000000" default-x="124" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>64</duration>
+    <instrument id="P4-I1" />
+    <voice>1</voice>
+    <type>16th</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">end</beam>
+    <beam number="2">end</beam>
+   </note>
+  </measure>
+  <!--============== Part: P4, Measure: 35 ==============-->
+  <measure number="35" width="158">
+   <note color="#000000" default-x="38">
+    <grace />
+    <pitch>
+     <step>B</step>
+     <octave>4</octave>
+    </pitch>
+    <instrument id="P4-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="55" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>384</duration>
+    <instrument id="P4-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <dot />
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P4, Measure: 36 ==============-->
+  <measure number="36" width="107">
+   <attributes>
+    <time color="#000000">
+     <beats>6</beats>
+     <beat-type>8</beat-type>
+    </time>
+   </attributes>
+   <note color="#000000" default-x="32" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>768</duration>
+    <instrument id="P4-I1" />
+    <voice>1</voice>
+    <type>half</type>
+    <dot />
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P4, Measure: 37 ==============-->
+  <measure number="37" width="179">
+   <note color="#000000" default-x="15" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>256</duration>
+    <instrument id="P4-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note default-x="68">
+    <rest />
+    <duration>512</duration>
+    <instrument id="P4-I1" />
+    <voice>1</voice>
+    <type>half</type>
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P4, Measure: 38 ==============-->
+  <measure number="38" width="492">
+   <print new-system="yes">
+    <system-layout>
+     <system-margins>
+      <left-margin>22</left-margin>
+      <right-margin>0</right-margin>
+     </system-margins>
+    </system-layout>
+    <staff-layout number="1">
+     <staff-distance>54</staff-distance>
+    </staff-layout>
+   </print>
+   <attributes>
+    <staff-details number="1" print-object="yes" />
+   </attributes>
+   <note color="#000000" default-x="57" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>64</duration>
+    <instrument id="P4-I1" />
+    <voice>1</voice>
+    <type>16th</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">begin</beam>
+    <beam number="2">begin</beam>
+   </note>
+   <note color="#000000" default-x="98" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>64</duration>
+    <instrument id="P4-I1" />
+    <voice>1</voice>
+    <type>16th</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">continue</beam>
+    <beam number="2">continue</beam>
+   </note>
+   <note color="#000000" default-x="139" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>64</duration>
+    <instrument id="P4-I1" />
+    <voice>1</voice>
+    <type>16th</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">continue</beam>
+    <beam number="2">continue</beam>
+   </note>
+   <note color="#000000" default-x="179" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>64</duration>
+    <instrument id="P4-I1" />
+    <voice>1</voice>
+    <type>16th</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">continue</beam>
+    <beam number="2">continue</beam>
+   </note>
+   <note color="#000000" default-x="220" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>64</duration>
+    <instrument id="P4-I1" />
+    <voice>1</voice>
+    <type>16th</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">continue</beam>
+    <beam number="2">continue</beam>
+   </note>
+   <note color="#000000" default-x="260" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>64</duration>
+    <instrument id="P4-I1" />
+    <voice>1</voice>
+    <type>16th</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">end</beam>
+    <beam number="2">end</beam>
+   </note>
+   <note color="#000000" default-x="286">
+    <grace />
+    <pitch>
+     <step>B</step>
+     <octave>4</octave>
+    </pitch>
+    <instrument id="P4-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="304" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>384</duration>
+    <instrument id="P4-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <dot />
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P4, Measure: 39 ==============-->
+  <measure number="39" width="540">
+   <attributes>
+    <time color="#000000">
+     <beats>9</beats>
+     <beat-type>8</beat-type>
+    </time>
+   </attributes>
+   <note color="#000000" default-x="32" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>384</duration>
+    <instrument id="P4-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <dot />
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="131" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>64</duration>
+    <instrument id="P4-I1" />
+    <voice>1</voice>
+    <type>16th</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">begin</beam>
+    <beam number="2">begin</beam>
+   </note>
+   <note color="#000000" default-x="172" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>64</duration>
+    <instrument id="P4-I1" />
+    <voice>1</voice>
+    <type>16th</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">continue</beam>
+    <beam number="2">continue</beam>
+   </note>
+   <note color="#000000" default-x="212" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>64</duration>
+    <instrument id="P4-I1" />
+    <voice>1</voice>
+    <type>16th</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">continue</beam>
+    <beam number="2">continue</beam>
+   </note>
+   <note color="#000000" default-x="253" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>64</duration>
+    <instrument id="P4-I1" />
+    <voice>1</voice>
+    <type>16th</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">continue</beam>
+    <beam number="2">continue</beam>
+   </note>
+   <note color="#000000" default-x="294" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>64</duration>
+    <instrument id="P4-I1" />
+    <voice>1</voice>
+    <type>16th</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">continue</beam>
+    <beam number="2">continue</beam>
+   </note>
+   <note color="#000000" default-x="335" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>64</duration>
+    <instrument id="P4-I1" />
+    <voice>1</voice>
+    <type>16th</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">end</beam>
+    <beam number="2">end</beam>
+   </note>
+   <note color="#000000" default-x="363">
+    <grace />
+    <pitch>
+     <step>B</step>
+     <octave>4</octave>
+    </pitch>
+    <instrument id="P4-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="380" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>384</duration>
+    <instrument id="P4-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <dot />
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P4, Measure: 40 ==============-->
+  <measure number="40" width="414">
+   <print new-system="yes">
+    <system-layout>
+     <system-margins>
+      <left-margin>22</left-margin>
+      <right-margin>0</right-margin>
+     </system-margins>
+    </system-layout>
+    <staff-layout number="1">
+     <staff-distance>54</staff-distance>
+    </staff-layout>
+   </print>
+   <attributes>
+    <staff-details number="1" print-object="yes" />
+   </attributes>
+   <note color="#000000" default-x="57" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>384</duration>
+    <instrument id="P4-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <dot />
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="120" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>64</duration>
+    <instrument id="P4-I1" />
+    <voice>1</voice>
+    <type>16th</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">begin</beam>
+    <beam number="2">begin</beam>
+   </note>
+   <note color="#000000" default-x="146" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>64</duration>
+    <instrument id="P4-I1" />
+    <voice>1</voice>
+    <type>16th</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">continue</beam>
+    <beam number="2">continue</beam>
+   </note>
+   <note color="#000000" default-x="172" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>64</duration>
+    <instrument id="P4-I1" />
+    <voice>1</voice>
+    <type>16th</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">continue</beam>
+    <beam number="2">continue</beam>
+   </note>
+   <note color="#000000" default-x="198" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>64</duration>
+    <instrument id="P4-I1" />
+    <voice>1</voice>
+    <type>16th</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">continue</beam>
+    <beam number="2">continue</beam>
+   </note>
+   <note color="#000000" default-x="224" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>64</duration>
+    <instrument id="P4-I1" />
+    <voice>1</voice>
+    <type>16th</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">continue</beam>
+    <beam number="2">continue</beam>
+   </note>
+   <note color="#000000" default-x="250" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>64</duration>
+    <instrument id="P4-I1" />
+    <voice>1</voice>
+    <type>16th</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">end</beam>
+    <beam number="2">end</beam>
+   </note>
+   <note color="#000000" default-x="276" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>64</duration>
+    <instrument id="P4-I1" />
+    <voice>1</voice>
+    <type>16th</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">begin</beam>
+    <beam number="2">forward hook</beam>
+   </note>
+   <note color="#000000" default-x="302" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P4-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">continue</beam>
+   </note>
+   <note color="#000000" default-x="345" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>64</duration>
+    <instrument id="P4-I1" />
+    <voice>1</voice>
+    <type>16th</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">continue</beam>
+    <beam number="2">backward hook</beam>
+   </note>
+   <note color="#000000" default-x="370" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P4-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">end</beam>
+   </note>
+  </measure>
+  <!--============== Part: P4, Measure: 41 ==============-->
+  <measure number="41" width="303">
+   <attributes>
+    <time color="#000000">
+     <beats>5</beats>
+     <beat-type>4</beat-type>
+    </time>
+   </attributes>
+   <note color="#000000" default-x="33" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>384</duration>
+    <instrument id="P4-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <dot />
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="104" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P4-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="138" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>256</duration>
+    <instrument id="P4-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="185" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P4-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="219" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>384</duration>
+    <instrument id="P4-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <dot />
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P4, Measure: 42 ==============-->
+  <measure number="42" width="315">
+   <note default-x="15">
+    <rest />
+    <duration>384</duration>
+    <instrument id="P4-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <dot />
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="95" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P4-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="129" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>256</duration>
+    <instrument id="P4-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="194" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>384</duration>
+    <instrument id="P4-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <dot />
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="266" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P4-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <barline>
+    <bar-style>light-heavy</bar-style>
+   </barline>
+  </measure>
+ </part>
+ <part id="P5">
+  <!--============== Part: P5, Measure: 1 ==============-->
+  <measure number="1" width="362">
+   <print new-page="yes">
+    <system-layout>
+     <system-margins>
+      <left-margin>22</left-margin>
+      <right-margin>0</right-margin>
+     </system-margins>
+    </system-layout>
+    <staff-layout number="1">
+     <staff-distance>65</staff-distance>
+    </staff-layout>
+   </print>
+   <attributes>
+    <divisions>256</divisions>
+    <key color="#000000">
+     <fifths>0</fifths>
+     <mode>major</mode>
+    </key>
+    <time color="#000000">
+     <beats>4</beats>
+     <beat-type>4</beat-type>
+    </time>
+    <staves>1</staves>
+    <clef number="1" color="#000000">
+     <sign>G</sign>
+     <line>2</line>
+    </clef>
+    <staff-details number="1" print-object="yes" />
+   </attributes>
+   <note default-x="76">
+    <rest />
+    <duration>512</duration>
+    <instrument id="P5-I1" />
+    <voice>1</voice>
+    <type>half</type>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="206" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P5-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="235" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>384</duration>
+    <instrument id="P5-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <dot />
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P5, Measure: 2 ==============-->
+  <measure number="2" width="319">
+   <note color="#000000" default-x="15" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>85</duration>
+    <instrument id="P5-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <time-modification>
+     <actual-notes>3</actual-notes>
+     <normal-notes>2</normal-notes>
+     <normal-type>eighth</normal-type>
+    </time-modification>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">begin</beam>
+    <notations>
+     <tuplet type="start" bracket="no" number="1" default-y="-20" placement="above" />
+    </notations>
+   </note>
+   <note color="#000000" default-x="36" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>85</duration>
+    <instrument id="P5-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <time-modification>
+     <actual-notes>3</actual-notes>
+     <normal-notes>2</normal-notes>
+     <normal-type>eighth</normal-type>
+    </time-modification>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">continue</beam>
+   </note>
+   <note color="#000000" default-x="60" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>86</duration>
+    <instrument id="P5-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <time-modification>
+     <actual-notes>3</actual-notes>
+     <normal-notes>2</normal-notes>
+     <normal-type>eighth</normal-type>
+    </time-modification>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">end</beam>
+    <notations>
+     <tuplet type="stop" bracket="no" number="1" default-y="-20" placement="above" />
+    </notations>
+   </note>
+   <note color="#000000" default-x="88" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>768</duration>
+    <instrument id="P5-I1" />
+    <voice>1</voice>
+    <type>half</type>
+    <dot />
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P5, Measure: 3 ==============-->
+  <measure number="3" width="101">
+   <note color="#000000" default-x="15">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>1024</duration>
+    <instrument id="P5-I1" />
+    <voice>1</voice>
+    <type>whole</type>
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P5, Measure: 4 ==============-->
+  <measure number="4" width="249">
+   <note color="#000000" default-x="15" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P5-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">begin</beam>
+   </note>
+   <note color="#000000" default-x="42" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P5-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">continue</beam>
+   </note>
+   <note color="#000000" default-x="69" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P5-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">continue</beam>
+   </note>
+   <note color="#000000" default-x="95" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P5-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">end</beam>
+   </note>
+   <note color="#000000" default-x="124" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P5-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">begin</beam>
+   </note>
+   <note color="#000000" default-x="157" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P5-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">continue</beam>
+   </note>
+   <note color="#000000" default-x="190" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P5-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">continue</beam>
+   </note>
+   <note color="#000000" default-x="216" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P5-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">end</beam>
+   </note>
+  </measure>
+  <!--============== Part: P5, Measure: 5 ==============-->
+  <measure number="5" width="297">
+   <print new-system="yes">
+    <system-layout>
+     <system-margins>
+      <left-margin>22</left-margin>
+      <right-margin>0</right-margin>
+     </system-margins>
+    </system-layout>
+    <staff-layout number="1">
+     <staff-distance>65</staff-distance>
+    </staff-layout>
+   </print>
+   <attributes>
+    <staff-details number="1" print-object="yes" />
+   </attributes>
+   <note color="#000000" default-x="57" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>768</duration>
+    <instrument id="P5-I1" />
+    <voice>1</voice>
+    <type>half</type>
+    <dot />
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note default-x="237">
+    <rest />
+    <duration>256</duration>
+    <instrument id="P5-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P5, Measure: 6 ==============-->
+  <measure number="6" width="180">
+   <note default-x="15">
+    <rest />
+    <duration>1024</duration>
+    <instrument id="P5-I1" />
+    <voice>1</voice>
+    <type>whole</type>
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P5, Measure: 7 ==============-->
+  <measure number="7" width="332">
+   <note color="#000000" default-x="38">
+    <grace />
+    <pitch>
+     <step>B</step>
+     <octave>4</octave>
+    </pitch>
+    <instrument id="P5-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="55">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>1024</duration>
+    <instrument id="P5-I1" />
+    <voice>1</voice>
+    <type>whole</type>
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P5, Measure: 8 ==============-->
+  <measure number="8" width="221">
+   <attributes>
+    <time color="#000000">
+     <beats>3</beats>
+     <beat-type>4</beat-type>
+    </time>
+   </attributes>
+   <note default-x="33">
+    <rest />
+    <duration>512</duration>
+    <instrument id="P5-I1" />
+    <voice>1</voice>
+    <type>half</type>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="153" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>256</duration>
+    <instrument id="P5-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P5, Measure: 9 ==============-->
+  <measure number="9" width="266">
+   <print new-page="yes">
+    <system-layout>
+     <system-margins>
+      <left-margin>22</left-margin>
+      <right-margin>0</right-margin>
+     </system-margins>
+    </system-layout>
+    <staff-layout number="1">
+     <staff-distance>54</staff-distance>
+    </staff-layout>
+   </print>
+   <attributes>
+    <staff-details number="1" print-object="yes" />
+   </attributes>
+   <note color="#000000" default-x="57" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>85</duration>
+    <instrument id="P5-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <time-modification>
+     <actual-notes>3</actual-notes>
+     <normal-notes>2</normal-notes>
+     <normal-type>eighth</normal-type>
+    </time-modification>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">begin</beam>
+    <notations>
+     <tuplet type="start" bracket="no" number="1" default-y="-20" placement="above" />
+    </notations>
+   </note>
+   <note color="#000000" default-x="79" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>85</duration>
+    <instrument id="P5-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <time-modification>
+     <actual-notes>3</actual-notes>
+     <normal-notes>2</normal-notes>
+     <normal-type>eighth</normal-type>
+    </time-modification>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">continue</beam>
+   </note>
+   <note color="#000000" default-x="102" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>86</duration>
+    <instrument id="P5-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <time-modification>
+     <actual-notes>3</actual-notes>
+     <normal-notes>2</normal-notes>
+     <normal-type>eighth</normal-type>
+    </time-modification>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">end</beam>
+    <notations>
+     <tuplet type="stop" bracket="no" number="1" default-y="-20" placement="above" />
+    </notations>
+   </note>
+   <note color="#000000" default-x="127" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>512</duration>
+    <instrument id="P5-I1" />
+    <voice>1</voice>
+    <type>half</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P5, Measure: 10 ==============-->
+  <measure number="10" width="91">
+   <note color="#000000" default-x="15" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>768</duration>
+    <instrument id="P5-I1" />
+    <voice>1</voice>
+    <type>half</type>
+    <dot />
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P5, Measure: 11 ==============-->
+  <measure number="11" width="149">
+   <note color="#000000" default-x="15" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>512</duration>
+    <instrument id="P5-I1" />
+    <voice>1</voice>
+    <type>half</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="95" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>256</duration>
+    <instrument id="P5-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P5, Measure: 12 ==============-->
+  <measure number="12" width="192">
+   <note color="#000000" default-x="15" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P5-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">begin</beam>
+   </note>
+   <note color="#000000" default-x="42" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P5-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">end</beam>
+   </note>
+   <note color="#000000" default-x="69" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P5-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">begin</beam>
+   </note>
+   <note color="#000000" default-x="97" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P5-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">end</beam>
+   </note>
+   <note color="#000000" default-x="125" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P5-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">begin</beam>
+   </note>
+   <note color="#000000" default-x="159" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P5-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">end</beam>
+   </note>
+  </measure>
+  <!--============== Part: P5, Measure: 13 ==============-->
+  <measure number="13" width="153">
+   <note color="#000000" default-x="15" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>256</duration>
+    <instrument id="P5-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note default-x="65">
+    <rest />
+    <duration>256</duration>
+    <instrument id="P5-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <staff>1</staff>
+   </note>
+   <note default-x="115">
+    <rest />
+    <duration>256</duration>
+    <instrument id="P5-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P5, Measure: 14 ==============-->
+  <measure number="14" width="157">
+   <note color="#000000" default-x="8">
+    <grace />
+    <pitch>
+     <step>B</step>
+     <octave>4</octave>
+    </pitch>
+    <instrument id="P5-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="25" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>768</duration>
+    <instrument id="P5-I1" />
+    <voice>1</voice>
+    <type>half</type>
+    <dot />
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P5, Measure: 15 ==============-->
+  <measure number="15" width="226">
+   <print new-system="yes">
+    <system-layout>
+     <system-margins>
+      <left-margin>22</left-margin>
+      <right-margin>0</right-margin>
+     </system-margins>
+    </system-layout>
+    <staff-layout number="1">
+     <staff-distance>54</staff-distance>
+    </staff-layout>
+   </print>
+   <attributes>
+    <time color="#000000">
+     <beats>2</beats>
+     <beat-type>4</beat-type>
+    </time>
+    <staff-details number="1" print-object="yes" />
+   </attributes>
+   <note default-x="76">
+    <rest />
+    <duration>512</duration>
+    <instrument id="P5-I1" />
+    <voice>1</voice>
+    <type>half</type>
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P5, Measure: 16 ==============-->
+  <measure number="16" width="215">
+   <note color="#000000" default-x="15" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>512</duration>
+    <instrument id="P5-I1" />
+    <voice>1</voice>
+    <type>half</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P5, Measure: 17 ==============-->
+  <measure number="17" width="206">
+   <note color="#000000" default-x="38">
+    <grace />
+    <pitch>
+     <step>B</step>
+     <octave>4</octave>
+    </pitch>
+    <instrument id="P5-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="55" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>512</duration>
+    <instrument id="P5-I1" />
+    <voice>1</voice>
+    <type>half</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P5, Measure: 18 ==============-->
+  <measure number="18" width="383">
+   <attributes>
+    <time color="#000000">
+     <beats>2</beats>
+     <beat-type>2</beat-type>
+    </time>
+   </attributes>
+   <note default-x="32">
+    <rest />
+    <duration>512</duration>
+    <instrument id="P5-I1" />
+    <voice>1</voice>
+    <type>half</type>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="183" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P5-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="220" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>384</duration>
+    <instrument id="P5-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <dot />
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P5, Measure: 19 ==============-->
+  <measure number="19" width="369">
+   <print new-system="yes">
+    <system-layout>
+     <system-margins>
+      <left-margin>22</left-margin>
+      <right-margin>0</right-margin>
+     </system-margins>
+    </system-layout>
+    <staff-layout number="1">
+     <staff-distance>54</staff-distance>
+    </staff-layout>
+   </print>
+   <attributes>
+    <staff-details number="1" print-object="yes" />
+   </attributes>
+   <note color="#000000" default-x="57" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>85</duration>
+    <instrument id="P5-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <time-modification>
+     <actual-notes>3</actual-notes>
+     <normal-notes>2</normal-notes>
+     <normal-type>eighth</normal-type>
+    </time-modification>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">begin</beam>
+    <notations>
+     <tuplet type="start" bracket="no" number="1" default-y="-20" placement="above" />
+    </notations>
+   </note>
+   <note color="#000000" default-x="81" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>85</duration>
+    <instrument id="P5-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <time-modification>
+     <actual-notes>3</actual-notes>
+     <normal-notes>2</normal-notes>
+     <normal-type>eighth</normal-type>
+    </time-modification>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">continue</beam>
+   </note>
+   <note color="#000000" default-x="105" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>86</duration>
+    <instrument id="P5-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <time-modification>
+     <actual-notes>3</actual-notes>
+     <normal-notes>2</normal-notes>
+     <normal-type>eighth</normal-type>
+    </time-modification>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">end</beam>
+    <notations>
+     <tuplet type="stop" bracket="no" number="1" default-y="-20" placement="above" />
+    </notations>
+   </note>
+   <note color="#000000" default-x="132" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>768</duration>
+    <instrument id="P5-I1" />
+    <voice>1</voice>
+    <type>half</type>
+    <dot />
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P5, Measure: 20 ==============-->
+  <measure number="20" width="289">
+   <note color="#000000" default-x="38">
+    <grace />
+    <pitch>
+     <step>B</step>
+     <octave>4</octave>
+    </pitch>
+    <instrument id="P5-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="55">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>1024</duration>
+    <instrument id="P5-I1" />
+    <voice>1</voice>
+    <type>whole</type>
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P5, Measure: 21 ==============-->
+  <measure number="21" width="109">
+   <note color="#000000" default-x="15">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>1024</duration>
+    <instrument id="P5-I1" />
+    <voice>1</voice>
+    <type>whole</type>
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P5, Measure: 22 ==============-->
+  <measure number="22" width="264">
+   <note color="#000000" default-x="15" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P5-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">begin</beam>
+   </note>
+   <note color="#000000" default-x="44" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P5-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">continue</beam>
+   </note>
+   <note color="#000000" default-x="73" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P5-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">continue</beam>
+   </note>
+   <note color="#000000" default-x="102" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P5-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">end</beam>
+   </note>
+   <note color="#000000" default-x="131" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P5-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">begin</beam>
+   </note>
+   <note color="#000000" default-x="167" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P5-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">continue</beam>
+   </note>
+   <note color="#000000" default-x="203" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P5-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">continue</beam>
+   </note>
+   <note color="#000000" default-x="232" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P5-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">end</beam>
+   </note>
+  </measure>
+  <!--============== Part: P5, Measure: 23 ==============-->
+  <measure number="23" width="260">
+   <print new-page="yes">
+    <system-layout>
+     <system-margins>
+      <left-margin>22</left-margin>
+      <right-margin>0</right-margin>
+     </system-margins>
+    </system-layout>
+    <staff-layout number="1">
+     <staff-distance>69</staff-distance>
+    </staff-layout>
+   </print>
+   <attributes>
+    <staff-details number="1" print-object="yes" />
+   </attributes>
+   <note color="#000000" default-x="57" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>768</duration>
+    <instrument id="P5-I1" />
+    <voice>1</voice>
+    <type>half</type>
+    <dot />
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note default-x="210">
+    <rest />
+    <duration>256</duration>
+    <instrument id="P5-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P5, Measure: 24 ==============-->
+  <measure number="24" width="155">
+   <note default-x="15">
+    <rest />
+    <duration>1024</duration>
+    <instrument id="P5-I1" />
+    <voice>1</voice>
+    <type>whole</type>
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P5, Measure: 25 ==============-->
+  <measure number="25" width="218">
+   <note color="#000000" default-x="15" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>768</duration>
+    <instrument id="P5-I1" />
+    <voice>1</voice>
+    <type>half</type>
+    <dot />
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note default-x="167">
+    <rest />
+    <duration>256</duration>
+    <instrument id="P5-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P5, Measure: 26 ==============-->
+  <measure number="26" width="155">
+   <note default-x="15">
+    <rest />
+    <duration>1024</duration>
+    <instrument id="P5-I1" />
+    <voice>1</voice>
+    <type>whole</type>
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P5, Measure: 27 ==============-->
+  <measure number="27" width="243">
+   <attributes>
+    <time color="#000000">
+     <beats>3</beats>
+     <beat-type>2</beat-type>
+    </time>
+   </attributes>
+   <note color="#000000" default-x="32" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>768</duration>
+    <instrument id="P5-I1" />
+    <voice>1</voice>
+    <type>half</type>
+    <dot />
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="137" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>768</duration>
+    <instrument id="P5-I1" />
+    <voice>1</voice>
+    <type>half</type>
+    <dot />
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P5, Measure: 28 ==============-->
+  <measure number="28" width="162">
+   <print new-system="yes">
+    <system-layout>
+     <system-margins>
+      <left-margin>22</left-margin>
+      <right-margin>0</right-margin>
+     </system-margins>
+    </system-layout>
+    <staff-layout number="1">
+     <staff-distance>70</staff-distance>
+    </staff-layout>
+   </print>
+   <attributes>
+    <staff-details number="1" print-object="yes" />
+   </attributes>
+   <note color="#000000" default-x="57">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>1536</duration>
+    <instrument id="P5-I1" />
+    <voice>1</voice>
+    <type>whole</type>
+    <dot />
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P5, Measure: 29 ==============-->
+  <measure number="29" width="232">
+   <note color="#000000" default-x="15" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>768</duration>
+    <instrument id="P5-I1" />
+    <voice>1</voice>
+    <type>half</type>
+    <dot />
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note default-x="120">
+    <rest />
+    <duration>256</duration>
+    <instrument id="P5-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="160" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>512</duration>
+    <instrument id="P5-I1" />
+    <voice>1</voice>
+    <type>half</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P5, Measure: 30 ==============-->
+  <measure number="30" width="265">
+   <note color="#000000" default-x="15" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>384</duration>
+    <instrument id="P5-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <dot />
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="67" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P5-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="96" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P5-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="125" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>384</duration>
+    <instrument id="P5-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <dot />
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="192" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>512</duration>
+    <instrument id="P5-I1" />
+    <voice>1</voice>
+    <type>half</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P5, Measure: 31 ==============-->
+  <measure number="31" width="372">
+   <note default-x="15">
+    <rest />
+    <duration>768</duration>
+    <instrument id="P5-I1" />
+    <voice>1</voice>
+    <type>half</type>
+    <dot />
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="198" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>256</duration>
+    <instrument id="P5-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note default-x="255">
+    <rest />
+    <duration>384</duration>
+    <instrument id="P5-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <dot />
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="340" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P5-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P5, Measure: 32 ==============-->
+  <measure number="32" width="360">
+   <print new-page="yes">
+    <system-layout>
+     <system-margins>
+      <left-margin>22</left-margin>
+      <right-margin>0</right-margin>
+     </system-margins>
+    </system-layout>
+    <staff-layout number="1">
+     <staff-distance>54</staff-distance>
+    </staff-layout>
+   </print>
+   <attributes>
+    <staff-details number="1" print-object="yes" />
+   </attributes>
+   <note color="#000000" default-x="57" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P5-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">begin</beam>
+   </note>
+   <note color="#000000" default-x="85" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P5-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">continue</beam>
+   </note>
+   <note color="#000000" default-x="111" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P5-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">continue</beam>
+   </note>
+   <note color="#000000" default-x="138" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P5-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">end</beam>
+   </note>
+   <note color="#000000" default-x="167" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P5-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">begin</beam>
+   </note>
+   <note color="#000000" default-x="200" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P5-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">end</beam>
+   </note>
+   <note color="#000000" default-x="221">
+    <grace />
+    <pitch>
+     <step>B</step>
+     <octave>4</octave>
+    </pitch>
+    <instrument id="P5-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="239" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>768</duration>
+    <instrument id="P5-I1" />
+    <voice>1</voice>
+    <type>half</type>
+    <dot />
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P5, Measure: 33 ==============-->
+  <measure number="33" width="81">
+   <attributes>
+    <time color="#000000">
+     <beats>3</beats>
+     <beat-type>8</beat-type>
+    </time>
+   </attributes>
+   <note color="#000000" default-x="31" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>384</duration>
+    <instrument id="P5-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <dot />
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P5, Measure: 34 ==============-->
+  <measure number="34" width="145">
+   <note color="#000000" default-x="15" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>192</duration>
+    <instrument id="P5-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <dot />
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">begin</beam>
+   </note>
+   <note color="#000000" default-x="83" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>64</duration>
+    <instrument id="P5-I1" />
+    <voice>1</voice>
+    <type>16th</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">continue</beam>
+    <beam number="2">backward hook</beam>
+   </note>
+   <note color="#000000" default-x="104" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P5-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">end</beam>
+   </note>
+  </measure>
+  <!--============== Part: P5, Measure: 35 ==============-->
+  <measure number="35" width="158">
+   <note color="#000000" default-x="8">
+    <grace />
+    <pitch>
+     <step>B</step>
+     <octave>4</octave>
+    </pitch>
+    <instrument id="P5-I1" />
+    <voice>1</voice>
+    <type>16th</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">begin</beam>
+    <beam number="2">begin</beam>
+   </note>
+   <note color="#000000" default-x="23">
+    <grace />
+    <pitch>
+     <step>B</step>
+     <octave>4</octave>
+    </pitch>
+    <instrument id="P5-I1" />
+    <voice>1</voice>
+    <type>16th</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">continue</beam>
+    <beam number="2">continue</beam>
+   </note>
+   <note color="#000000" default-x="38">
+    <grace />
+    <pitch>
+     <step>B</step>
+     <octave>4</octave>
+    </pitch>
+    <instrument id="P5-I1" />
+    <voice>1</voice>
+    <type>16th</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">end</beam>
+    <beam number="2">end</beam>
+   </note>
+   <note color="#000000" default-x="55" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>384</duration>
+    <instrument id="P5-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <dot />
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P5, Measure: 36 ==============-->
+  <measure number="36" width="107">
+   <attributes>
+    <time color="#000000">
+     <beats>6</beats>
+     <beat-type>8</beat-type>
+    </time>
+   </attributes>
+   <note color="#000000" default-x="32" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>768</duration>
+    <instrument id="P5-I1" />
+    <voice>1</voice>
+    <type>half</type>
+    <dot />
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P5, Measure: 37 ==============-->
+  <measure number="37" width="179">
+   <note color="#000000" default-x="15" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>384</duration>
+    <instrument id="P5-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <dot />
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="99" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>384</duration>
+    <instrument id="P5-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <dot />
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P5, Measure: 38 ==============-->
+  <measure number="38" width="492">
+   <print new-system="yes">
+    <system-layout>
+     <system-margins>
+      <left-margin>22</left-margin>
+      <right-margin>0</right-margin>
+     </system-margins>
+    </system-layout>
+    <staff-layout number="1">
+     <staff-distance>54</staff-distance>
+    </staff-layout>
+   </print>
+   <attributes>
+    <staff-details number="1" print-object="yes" />
+   </attributes>
+   <note color="#000000" default-x="57" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P5-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">begin</beam>
+   </note>
+   <note color="#000000" default-x="139" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P5-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">continue</beam>
+   </note>
+   <note color="#000000" default-x="220" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P5-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">end</beam>
+   </note>
+   <note color="#000000" default-x="256">
+    <grace />
+    <pitch>
+     <step>B</step>
+     <octave>4</octave>
+    </pitch>
+    <instrument id="P5-I1" />
+    <voice>1</voice>
+    <type>16th</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">begin</beam>
+    <beam number="2">begin</beam>
+   </note>
+   <note color="#000000" default-x="271">
+    <grace />
+    <pitch>
+     <step>B</step>
+     <octave>4</octave>
+    </pitch>
+    <instrument id="P5-I1" />
+    <voice>1</voice>
+    <type>16th</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">continue</beam>
+    <beam number="2">continue</beam>
+   </note>
+   <note color="#000000" default-x="286">
+    <grace />
+    <pitch>
+     <step>B</step>
+     <octave>4</octave>
+    </pitch>
+    <instrument id="P5-I1" />
+    <voice>1</voice>
+    <type>16th</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">end</beam>
+    <beam number="2">end</beam>
+   </note>
+   <note color="#000000" default-x="304" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>384</duration>
+    <instrument id="P5-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <dot />
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P5, Measure: 39 ==============-->
+  <measure number="39" width="540">
+   <attributes>
+    <time color="#000000">
+     <beats>9</beats>
+     <beat-type>8</beat-type>
+    </time>
+   </attributes>
+   <note color="#000000" default-x="32" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>384</duration>
+    <instrument id="P5-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <dot />
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="131" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>192</duration>
+    <instrument id="P5-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <dot />
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">begin</beam>
+   </note>
+   <note color="#000000" default-x="253" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>64</duration>
+    <instrument id="P5-I1" />
+    <voice>1</voice>
+    <type>16th</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">continue</beam>
+    <beam number="2">backward hook</beam>
+   </note>
+   <note color="#000000" default-x="294" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P5-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">end</beam>
+   </note>
+   <note color="#000000" default-x="333">
+    <grace />
+    <pitch>
+     <step>B</step>
+     <octave>4</octave>
+    </pitch>
+    <instrument id="P5-I1" />
+    <voice>1</voice>
+    <type>16th</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">begin</beam>
+    <beam number="2">begin</beam>
+   </note>
+   <note color="#000000" default-x="348">
+    <grace />
+    <pitch>
+     <step>B</step>
+     <octave>4</octave>
+    </pitch>
+    <instrument id="P5-I1" />
+    <voice>1</voice>
+    <type>16th</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">continue</beam>
+    <beam number="2">continue</beam>
+   </note>
+   <note color="#000000" default-x="363">
+    <grace />
+    <pitch>
+     <step>B</step>
+     <octave>4</octave>
+    </pitch>
+    <instrument id="P5-I1" />
+    <voice>1</voice>
+    <type>16th</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">end</beam>
+    <beam number="2">end</beam>
+   </note>
+   <note color="#000000" default-x="380" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>384</duration>
+    <instrument id="P5-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <dot />
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P5, Measure: 40 ==============-->
+  <measure number="40" width="414">
+   <print new-system="yes">
+    <system-layout>
+     <system-margins>
+      <left-margin>22</left-margin>
+      <right-margin>0</right-margin>
+     </system-margins>
+    </system-layout>
+    <staff-layout number="1">
+     <staff-distance>54</staff-distance>
+    </staff-layout>
+   </print>
+   <attributes>
+    <staff-details number="1" print-object="yes" />
+   </attributes>
+   <note color="#000000" default-x="57" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>384</duration>
+    <instrument id="P5-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <dot />
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="120" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>192</duration>
+    <instrument id="P5-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <dot />
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">begin</beam>
+   </note>
+   <note color="#000000" default-x="198" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>64</duration>
+    <instrument id="P5-I1" />
+    <voice>1</voice>
+    <type>16th</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">continue</beam>
+    <beam number="2">backward hook</beam>
+   </note>
+   <note color="#000000" default-x="224" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P5-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">end</beam>
+   </note>
+   <note color="#000000" default-x="276" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P5-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">begin</beam>
+   </note>
+   <note color="#000000" default-x="328" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>192</duration>
+    <instrument id="P5-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <dot />
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">continue</beam>
+   </note>
+   <note color="#000000" default-x="388" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>64</duration>
+    <instrument id="P5-I1" />
+    <voice>1</voice>
+    <type>16th</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">end</beam>
+    <beam number="2">backward hook</beam>
+   </note>
+  </measure>
+  <!--============== Part: P5, Measure: 41 ==============-->
+  <measure number="41" width="303">
+   <attributes>
+    <time color="#000000">
+     <beats>5</beats>
+     <beat-type>4</beat-type>
+    </time>
+   </attributes>
+   <note default-x="33">
+    <rest />
+    <duration>1280</duration>
+    <instrument id="P5-I1" />
+    <voice>1</voice>
+    <type>whole</type>
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P5, Measure: 42 ==============-->
+  <measure number="42" width="315">
+   <note color="#000000" default-x="15" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P5-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note default-x="48">
+    <rest />
+    <duration>384</duration>
+    <instrument id="P5-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <dot />
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="129" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>256</duration>
+    <instrument id="P5-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="194" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>256</duration>
+    <instrument id="P5-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="241" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>256</duration>
+    <instrument id="P5-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <barline>
+    <bar-style>light-heavy</bar-style>
+   </barline>
+  </measure>
+ </part>
+</score-partwise>

+ 520 - 0
docs/musicxml/自己打的-简单-单声部.xml

@@ -0,0 +1,520 @@
+<?xml version="1.0" encoding='UTF-8' standalone='no' ?>
+<!DOCTYPE score-partwise PUBLIC "-//Recordare//DTD MusicXML 3.0 Partwise//EN" "http://www.musicxml.org/dtds/partwise.dtd">
+<score-partwise version="3.0">
+ <work>
+  <work-title />
+ </work>
+ <identification>
+  <rights>版权 © </rights>
+  <encoding>
+   <encoding-date>2026-01-29</encoding-date>
+   <encoder>Administrator</encoder>
+   <software>Sibelius 19.5.0</software>
+   <software>Direct export, not from Dolet</software>
+   <encoding-description>Sibelius / MusicXML 3.0</encoding-description>
+   <supports element="print" type="yes" value="yes" attribute="new-system" />
+   <supports element="print" type="yes" value="yes" attribute="new-page" />
+   <supports element="accidental" type="yes" />
+   <supports element="beam" type="yes" />
+   <supports element="stem" type="yes" />
+  </encoding>
+ </identification>
+ <defaults>
+  <scaling>
+   <millimeters>210</millimeters>
+   <tenths>1200</tenths>
+  </scaling>
+  <page-layout>
+   <page-height>1697</page-height>
+   <page-width>1200</page-width>
+   <page-margins type="both">
+    <left-margin>72</left-margin>
+    <right-margin>72</right-margin>
+    <top-margin>72</top-margin>
+    <bottom-margin>72</bottom-margin>
+   </page-margins>
+  </page-layout>
+  <system-layout>
+   <system-margins>
+    <left-margin>22</left-margin>
+    <right-margin>0</right-margin>
+   </system-margins>
+   <system-distance>92</system-distance>
+  </system-layout>
+  <appearance>
+   <line-width type="stem">0.9375</line-width>
+   <line-width type="beam">5</line-width>
+   <line-width type="staff">0.9375</line-width>
+   <line-width type="light barline">1.5625</line-width>
+   <line-width type="heavy barline">5</line-width>
+   <line-width type="leger">1.5625</line-width>
+   <line-width type="ending">1.5625</line-width>
+   <line-width type="wedge">1.25</line-width>
+   <line-width type="enclosure">0.9375</line-width>
+   <line-width type="tuplet bracket">1.25</line-width>
+   <line-width type="bracket">5</line-width>
+   <line-width type="dashes">1.5625</line-width>
+   <line-width type="extend">0.9375</line-width>
+   <line-width type="octave shift">1.5625</line-width>
+   <line-width type="pedal">1.5625</line-width>
+   <line-width type="slur middle">1.5625</line-width>
+   <line-width type="slur tip">0.625</line-width>
+   <line-width type="tie middle">1.5625</line-width>
+   <line-width type="tie tip">0.625</line-width>
+   <note-size type="cue">75</note-size>
+   <note-size type="grace">60</note-size>
+  </appearance>
+  <music-font font-family="Opus Std" font-size="19.8425" />
+  <lyric-font font-family="Times New Roman" font-size="11.4715" />
+  <lyric-language xml:lang="zh" />
+ </defaults>
+ <part-list>
+  <score-part id="P1">
+   <part-name> </part-name>
+   <part-name-display>
+    <display-text> </display-text>
+   </part-name-display>
+   <part-abbreviation> </part-abbreviation>
+   <part-abbreviation-display>
+    <display-text> </display-text>
+   </part-abbreviation-display>
+   <score-instrument id="P1-I1">
+    <instrument-name> </instrument-name>
+    <virtual-instrument>
+     <virtual-library>Sibelius 7 Sounds</virtual-library>
+    </virtual-instrument>
+   </score-instrument>
+  </score-part>
+ </part-list>
+ <part id="P1">
+  <!--============== Part: P1, Measure: 1 ==============-->
+  <measure number="1" width="267">
+   <print new-page="yes">
+    <system-layout>
+     <system-margins>
+      <left-margin>22</left-margin>
+      <right-margin>0</right-margin>
+     </system-margins>
+     <top-system-distance>218</top-system-distance>
+    </system-layout>
+   </print>
+   <attributes>
+    <divisions>256</divisions>
+    <key color="#000000">
+     <fifths>0</fifths>
+     <mode>major</mode>
+    </key>
+    <time color="#000000">
+     <beats>4</beats>
+     <beat-type>4</beat-type>
+    </time>
+    <staves>1</staves>
+    <clef number="1" color="#000000">
+     <sign>G</sign>
+     <line>2</line>
+    </clef>
+    <staff-details number="1" print-object="yes" />
+   </attributes>
+   <note color="#000000" default-x="76" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>256</duration>
+    <instrument id="P1-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="124" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>256</duration>
+    <instrument id="P1-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="172" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>256</duration>
+    <instrument id="P1-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="220" default-y="10">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>256</duration>
+    <instrument id="P1-I1" />
+    <voice>1</voice>
+    <type>quarter</type>
+    <stem>up</stem>
+    <staff>1</staff>
+   </note>
+  </measure>
+  <!--============== Part: P1, Measure: 2 ==============-->
+  <measure number="2" width="310">
+   <note color="#000000" default-x="15" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P1-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">begin</beam>
+   </note>
+   <note color="#000000" default-x="49" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P1-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">continue</beam>
+   </note>
+   <note color="#000000" default-x="84" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P1-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">continue</beam>
+   </note>
+   <note color="#000000" default-x="119" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P1-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">end</beam>
+   </note>
+   <note color="#000000" default-x="153" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P1-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">begin</beam>
+   </note>
+   <note color="#000000" default-x="188" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P1-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">end</beam>
+   </note>
+   <note color="#000000" default-x="222" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>128</duration>
+    <instrument id="P1-I1" />
+    <voice>1</voice>
+    <type>eighth</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">begin</beam>
+   </note>
+   <note color="#000000" default-x="257" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>64</duration>
+    <instrument id="P1-I1" />
+    <voice>1</voice>
+    <type>16th</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">continue</beam>
+    <beam number="2">begin</beam>
+   </note>
+   <note color="#000000" default-x="284" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>64</duration>
+    <instrument id="P1-I1" />
+    <voice>1</voice>
+    <type>16th</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">end</beam>
+    <beam number="2">end</beam>
+   </note>
+  </measure>
+  <!--============== Part: P1, Measure: 3 ==============-->
+  <measure number="3" width="454">
+   <note color="#000000" default-x="15" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>64</duration>
+    <instrument id="P1-I1" />
+    <voice>1</voice>
+    <type>16th</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">begin</beam>
+    <beam number="2">begin</beam>
+   </note>
+   <note color="#000000" default-x="41" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>64</duration>
+    <instrument id="P1-I1" />
+    <voice>1</voice>
+    <type>16th</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">continue</beam>
+    <beam number="2">continue</beam>
+   </note>
+   <note color="#000000" default-x="67" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>64</duration>
+    <instrument id="P1-I1" />
+    <voice>1</voice>
+    <type>16th</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">continue</beam>
+    <beam number="2">continue</beam>
+   </note>
+   <note color="#000000" default-x="94" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>64</duration>
+    <instrument id="P1-I1" />
+    <voice>1</voice>
+    <type>16th</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">end</beam>
+    <beam number="2">end</beam>
+   </note>
+   <note color="#000000" default-x="120" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>64</duration>
+    <instrument id="P1-I1" />
+    <voice>1</voice>
+    <type>16th</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">begin</beam>
+    <beam number="2">begin</beam>
+   </note>
+   <note color="#000000" default-x="147" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>64</duration>
+    <instrument id="P1-I1" />
+    <voice>1</voice>
+    <type>16th</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">continue</beam>
+    <beam number="2">continue</beam>
+   </note>
+   <note color="#000000" default-x="174" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>64</duration>
+    <instrument id="P1-I1" />
+    <voice>1</voice>
+    <type>16th</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">continue</beam>
+    <beam number="2">continue</beam>
+   </note>
+   <note color="#000000" default-x="200" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>64</duration>
+    <instrument id="P1-I1" />
+    <voice>1</voice>
+    <type>16th</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">end</beam>
+    <beam number="2">end</beam>
+   </note>
+   <note color="#000000" default-x="226" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>64</duration>
+    <instrument id="P1-I1" />
+    <voice>1</voice>
+    <type>16th</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">begin</beam>
+    <beam number="2">begin</beam>
+   </note>
+   <note color="#000000" default-x="253" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>64</duration>
+    <instrument id="P1-I1" />
+    <voice>1</voice>
+    <type>16th</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">continue</beam>
+    <beam number="2">continue</beam>
+   </note>
+   <note color="#000000" default-x="280" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>64</duration>
+    <instrument id="P1-I1" />
+    <voice>1</voice>
+    <type>16th</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">continue</beam>
+    <beam number="2">continue</beam>
+   </note>
+   <note color="#000000" default-x="306" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>64</duration>
+    <instrument id="P1-I1" />
+    <voice>1</voice>
+    <type>16th</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">end</beam>
+    <beam number="2">end</beam>
+   </note>
+   <note color="#000000" default-x="333" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>64</duration>
+    <instrument id="P1-I1" />
+    <voice>1</voice>
+    <type>16th</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">begin</beam>
+    <beam number="2">begin</beam>
+   </note>
+   <note color="#000000" default-x="359" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>64</duration>
+    <instrument id="P1-I1" />
+    <voice>1</voice>
+    <type>16th</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">continue</beam>
+    <beam number="2">continue</beam>
+   </note>
+   <note color="#000000" default-x="385" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>64</duration>
+    <instrument id="P1-I1" />
+    <voice>1</voice>
+    <type>16th</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">continue</beam>
+    <beam number="2">continue</beam>
+   </note>
+   <note color="#000000" default-x="412" default-y="9">
+    <pitch>
+     <step>A</step>
+     <octave>4</octave>
+    </pitch>
+    <duration>64</duration>
+    <instrument id="P1-I1" />
+    <voice>1</voice>
+    <type>16th</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">end</beam>
+    <beam number="2">end</beam>
+   </note>
+   <barline>
+    <bar-style>light-heavy</bar-style>
+   </barline>
+  </measure>
+ </part>
+</score-partwise>

+ 1 - 1
osmd-extended

@@ -1 +1 @@
-Subproject commit bae380f80800211a0585d82b88faaf34d145836e
+Subproject commit d15b4f5130044558b9cb4cf0d77f89d507d89652

File diff suppressed because it is too large
+ 818 - 161
package-lock.json


+ 7 - 1
package.json

@@ -5,7 +5,10 @@
   "scripts": {
     "dev": "vite --host --force",
     "build": "vite build",
-    "preview": "vite preview --open"
+    "preview": "vite preview --open",
+    "test": "vitest",
+    "test:run": "vitest run",
+    "test:coverage": "vitest run --coverage"
   },
   "dependencies": {
     "@vant/touch-emulator": "^1.4.0",
@@ -40,7 +43,9 @@
     "@vitejs/plugin-legacy": "^4.0.2",
     "@vitejs/plugin-vue": "^4.3.4",
     "@vitejs/plugin-vue-jsx": "^3.0.1",
+    "@vitest/coverage-v8": "^1.6.1",
     "canvg": "^4.0.1",
+    "jsdom": "^24.1.3",
     "less": "^4.1.3",
     "postcss-pxtorem": "^6.0.0",
     "rollup-plugin-visualizer": "^5.12.0",
@@ -52,6 +57,7 @@
     "vite-plugin-mkcert": "^1.15.0",
     "vite-plugin-style-import": "^2.0.0",
     "vite-svg-loader": "^4.0.0",
+    "vitest": "^1.6.1",
     "vue-tsc": "^1.2.0"
   }
 }

+ 324 - 0
src/jianpu-renderer/__tests__/DivisionsHandler.test.ts

@@ -0,0 +1,324 @@
+/**
+ * DivisionsHandler 单元测试
+ * 
+ * 测试覆盖:
+ * - 基本时值转换
+ * - 不同divisions值处理
+ * - 异常情况处理
+ * - 附点计算
+ * - 小节divisions缓存
+ */
+
+import { describe, it, expect, beforeEach, vi } from 'vitest';
+import { 
+  DivisionsHandler, 
+  createDivisionsHandler,
+  toRealValue,
+  applyDots 
+} from '../core/parser/DivisionsHandler';
+
+describe('DivisionsHandler', () => {
+  let handler: DivisionsHandler;
+
+  beforeEach(() => {
+    handler = new DivisionsHandler();
+    // 清除console警告以保持测试输出整洁
+    vi.spyOn(console, 'warn').mockImplementation(() => {});
+    vi.spyOn(console, 'error').mockImplementation(() => {});
+  });
+
+  // ==================== 基本功能测试 ====================
+  
+  describe('基本功能', () => {
+    it('应该使用默认divisions值256', () => {
+      expect(handler.getDivisions()).toBe(256);
+    });
+
+    it('应该能设置divisions值', () => {
+      handler.setDivisions(480);
+      expect(handler.getDivisions()).toBe(480);
+    });
+
+    it('应该正确转换duration到realValue', () => {
+      handler.setDivisions(256);
+      expect(handler.toRealValue(256)).toBe(1);    // 四分音符
+      expect(handler.toRealValue(128)).toBe(0.5);  // 八分音符
+      expect(handler.toRealValue(512)).toBe(2);    // 二分音符
+      expect(handler.toRealValue(1024)).toBe(4);   // 全音符
+    });
+  });
+
+  // ==================== 不同divisions值测试 ====================
+  
+  describe('不同divisions值处理', () => {
+    it('divisions=1时应该正确转换', () => {
+      handler.setDivisions(1);
+      expect(handler.toRealValue(1)).toBe(1);     // 四分音符
+      expect(handler.toRealValue(0.5)).toBe(0.5); // 八分音符
+      expect(handler.toRealValue(2)).toBe(2);     // 二分音符
+      expect(handler.toRealValue(4)).toBe(4);     // 全音符
+    });
+
+    it('divisions=256时应该正确转换', () => {
+      handler.setDivisions(256);
+      expect(handler.toRealValue(256)).toBe(1);   // 四分音符
+      expect(handler.toRealValue(128)).toBe(0.5); // 八分音符
+      expect(handler.toRealValue(64)).toBe(0.25); // 十六分音符
+      expect(handler.toRealValue(384)).toBe(1.5); // 附点四分音符
+    });
+
+    it('divisions=480时应该正确转换', () => {
+      handler.setDivisions(480);
+      expect(handler.toRealValue(480)).toBe(1);   // 四分音符
+      expect(handler.toRealValue(240)).toBe(0.5); // 八分音符
+      expect(handler.toRealValue(120)).toBe(0.25); // 十六分音符
+      expect(handler.toRealValue(960)).toBe(2);   // 二分音符
+    });
+
+    it('divisions=960时应该正确转换', () => {
+      handler.setDivisions(960);
+      expect(handler.toRealValue(960)).toBe(1);    // 四分音符
+      expect(handler.toRealValue(480)).toBe(0.5);  // 八分音符
+      expect(handler.toRealValue(240)).toBe(0.25); // 十六分音符
+      expect(handler.toRealValue(1920)).toBe(2);   // 二分音符
+      expect(handler.toRealValue(3840)).toBe(4);   // 全音符
+    });
+  });
+
+  // ==================== 异常情况处理测试 ====================
+  
+  describe('异常情况处理', () => {
+    it('divisions为0时应该使用默认值', () => {
+      handler.setDivisions(0);
+      expect(handler.getDivisions()).toBe(256);
+    });
+
+    it('divisions为null时应该使用默认值', () => {
+      handler.setDivisions(null);
+      expect(handler.getDivisions()).toBe(256);
+    });
+
+    it('divisions为undefined时应该使用默认值', () => {
+      handler.setDivisions(undefined);
+      expect(handler.getDivisions()).toBe(256);
+    });
+
+    it('divisions为负数时应该取绝对值', () => {
+      handler.setDivisions(-480);
+      expect(handler.getDivisions()).toBe(480);
+    });
+
+    it('duration为负数时应该取绝对值', () => {
+      handler.setDivisions(256);
+      expect(handler.toRealValue(-256)).toBe(1);
+    });
+
+    it('duration为null时应该返回0', () => {
+      handler.setDivisions(256);
+      expect(handler.toRealValue(null as any)).toBe(0);
+    });
+
+    it('严格模式下divisions为0时应该抛出错误', () => {
+      const strictHandler = new DivisionsHandler(true);
+      expect(() => strictHandler.setDivisions(0)).toThrow('divisions值不能为0');
+    });
+  });
+
+  // ==================== 转换精度测试 ====================
+  
+  describe('转换精度', () => {
+    it('转换误差应该小于0.001', () => {
+      handler.setDivisions(256);
+      
+      // 测试各种时值的转换精度
+      const testCases = [
+        { duration: 256, expected: 1 },
+        { duration: 128, expected: 0.5 },
+        { duration: 64, expected: 0.25 },
+        { duration: 384, expected: 1.5 },
+        { duration: 192, expected: 0.75 },
+      ];
+
+      testCases.forEach(({ duration, expected }) => {
+        const result = handler.toRealValue(duration);
+        expect(Math.abs(result - expected)).toBeLessThan(0.001);
+      });
+    });
+
+    it('realValue到duration的逆转换应该准确', () => {
+      handler.setDivisions(256);
+      
+      // 测试逆转换
+      expect(handler.toDuration(1)).toBe(256);
+      expect(handler.toDuration(0.5)).toBe(128);
+      expect(handler.toDuration(2)).toBe(512);
+      expect(handler.toDuration(1.5)).toBe(384);
+    });
+  });
+
+  // ==================== 附点音符测试 ====================
+  
+  describe('附点音符处理', () => {
+    it('单附点应该增加50%时值', () => {
+      expect(handler.applyDots(1, 1)).toBe(1.5);    // 四分 -> 附点四分
+      expect(handler.applyDots(0.5, 1)).toBe(0.75); // 八分 -> 附点八分
+      expect(handler.applyDots(2, 1)).toBe(3);      // 二分 -> 附点二分
+    });
+
+    it('双附点应该增加75%时值', () => {
+      expect(handler.applyDots(1, 2)).toBe(1.75);   // 四分 -> 双附点四分
+      expect(handler.applyDots(2, 2)).toBe(3.5);    // 二分 -> 双附点二分
+    });
+
+    it('无附点应该返回原值', () => {
+      expect(handler.applyDots(1, 0)).toBe(1);
+      expect(handler.applyDots(0.5, 0)).toBe(0.5);
+    });
+
+    it('负数附点应该返回原值', () => {
+      expect(handler.applyDots(1, -1)).toBe(1);
+    });
+  });
+
+  // ==================== 时长计算测试 ====================
+  
+  describe('时长计算(秒)', () => {
+    it('BPM=120时四分音符应该是0.5秒', () => {
+      handler.setDivisions(256);
+      expect(handler.toSeconds(256, 120)).toBe(0.5);
+    });
+
+    it('BPM=60时四分音符应该是1秒', () => {
+      handler.setDivisions(256);
+      expect(handler.toSeconds(256, 60)).toBe(1);
+    });
+
+    it('BPM=120时八分音符应该是0.25秒', () => {
+      handler.setDivisions(256);
+      expect(handler.toSeconds(128, 120)).toBe(0.25);
+    });
+
+    it('BPM为0时应该使用默认值120', () => {
+      handler.setDivisions(256);
+      expect(handler.toSeconds(256, 0)).toBe(0.5);
+    });
+
+    it('BPM为负数时应该使用默认值120', () => {
+      handler.setDivisions(256);
+      expect(handler.toSeconds(256, -60)).toBe(0.5);
+    });
+  });
+
+  // ==================== 小节divisions缓存测试 ====================
+  
+  describe('小节divisions缓存', () => {
+    it('应该能为不同小节设置不同divisions', () => {
+      handler.setMeasureDivisions(0, 256);
+      handler.setMeasureDivisions(1, 480);
+      handler.setMeasureDivisions(2, 960);
+
+      expect(handler.getMeasureDivisions(0)).toBe(256);
+      expect(handler.getMeasureDivisions(1)).toBe(480);
+      expect(handler.getMeasureDivisions(2)).toBe(960);
+    });
+
+    it('未设置的小节应该返回当前divisions值', () => {
+      handler.setDivisions(480);
+      expect(handler.getMeasureDivisions(99)).toBe(480);
+    });
+
+    it('reset应该清除所有缓存', () => {
+      handler.setMeasureDivisions(0, 480);
+      handler.setMeasureDivisions(1, 960);
+      
+      handler.reset();
+      
+      expect(handler.getDivisions()).toBe(256);
+      expect(handler.getAllMeasureDivisions().size).toBe(0);
+    });
+  });
+
+  // ==================== 音符类型测试 ====================
+  
+  describe('音符类型转换', () => {
+    it('应该返回正确的音符类型时值', () => {
+      expect(handler.getNoteTypeRealValue('whole')).toBe(4);
+      expect(handler.getNoteTypeRealValue('half')).toBe(2);
+      expect(handler.getNoteTypeRealValue('quarter')).toBe(1);
+      expect(handler.getNoteTypeRealValue('eighth')).toBe(0.5);
+      expect(handler.getNoteTypeRealValue('16th')).toBe(0.25);
+      expect(handler.getNoteTypeRealValue('32nd')).toBe(0.125);
+    });
+
+    it('未知音符类型应该返回1(四分音符)', () => {
+      expect(handler.getNoteTypeRealValue('unknown')).toBe(1);
+    });
+
+    it('大小写不敏感', () => {
+      expect(handler.getNoteTypeRealValue('QUARTER')).toBe(1);
+      expect(handler.getNoteTypeRealValue('Quarter')).toBe(1);
+    });
+  });
+
+  // ==================== toRealValueWithInfo测试 ====================
+  
+  describe('toRealValueWithInfo', () => {
+    it('正常值应该无警告', () => {
+      handler.setDivisions(256);
+      const result = handler.toRealValueWithInfo(256);
+      expect(result.value).toBe(1);
+      expect(result.hasWarning).toBe(false);
+    });
+
+    it('负数duration应该有警告', () => {
+      handler.setDivisions(256);
+      const result = handler.toRealValueWithInfo(-256);
+      expect(result.value).toBe(1);
+      expect(result.hasWarning).toBe(true);
+      expect(result.warningMessage).toContain('负数');
+    });
+
+    it('null duration应该有警告', () => {
+      const result = handler.toRealValueWithInfo(null as any);
+      expect(result.value).toBe(0);
+      expect(result.hasWarning).toBe(true);
+    });
+  });
+});
+
+// ==================== 工具函数测试 ====================
+
+describe('工具函数', () => {
+  describe('createDivisionsHandler', () => {
+    it('应该创建非严格模式的实例', () => {
+      const handler = createDivisionsHandler();
+      handler.setDivisions(0); // 不应该抛出错误
+      expect(handler.getDivisions()).toBe(256);
+    });
+
+    it('应该创建严格模式的实例', () => {
+      const handler = createDivisionsHandler(true);
+      expect(() => handler.setDivisions(0)).toThrow();
+    });
+  });
+
+  describe('toRealValue快捷函数', () => {
+    it('应该正确转换', () => {
+      expect(toRealValue(256, 256)).toBe(1);
+      expect(toRealValue(128, 256)).toBe(0.5);
+      expect(toRealValue(480, 480)).toBe(1);
+    });
+
+    it('divisions为0时应该抛出错误', () => {
+      expect(() => toRealValue(256, 0)).toThrow('divisions不能为0');
+    });
+  });
+
+  describe('applyDots快捷函数', () => {
+    it('应该正确计算附点时值', () => {
+      expect(applyDots(1, 1)).toBe(1.5);
+      expect(applyDots(1, 2)).toBe(1.75);
+      expect(applyDots(2, 1)).toBe(3);
+    });
+  });
+});

+ 392 - 0
src/jianpu-renderer/__tests__/MeasureLayoutEngine.test.ts

@@ -0,0 +1,392 @@
+/**
+ * MeasureLayoutEngine 单元测试
+ * 
+ * 测试覆盖:
+ * - 小节宽度计算
+ * - 音符位置计算
+ * - 固定时间比例验证
+ * - 不同拍号处理
+ * - padding应用
+ * - 最小间距限制
+ */
+
+import { describe, it, expect, beforeEach, vi } from 'vitest';
+import { 
+  MeasureLayoutEngine, 
+  createMeasureLayoutEngine,
+  calculateMeasureRealValue,
+  calculateTimestampRatio,
+  validateMeasureWidths,
+} from '../core/layout/MeasureLayoutEngine';
+import { JianpuMeasure, createDefaultMeasure } from '../models/JianpuMeasure';
+import { JianpuNote, createDefaultNote } from '../models/JianpuNote';
+
+// ==================== 辅助函数 ====================
+
+/**
+ * 创建带音符的测试小节
+ */
+function createMeasureWithNotes(
+  measureNumber: number,
+  notes: Array<{ pitch: number; duration: number; timestamp: number }>,
+  timeSignature: { beats: number; beatType: number } = { beats: 4, beatType: 4 }
+): JianpuMeasure {
+  const measure = createDefaultMeasure(measureNumber, timeSignature);
+  measure.voices[0] = notes.map((n, i) => 
+    createDefaultNote({
+      pitch: n.pitch,
+      duration: n.duration,
+      timestamp: n.timestamp,
+      measureIndex: measureNumber - 1,
+    })
+  );
+  return measure;
+}
+
+// ==================== 测试用例 ====================
+
+describe('MeasureLayoutEngine', () => {
+  let engine: MeasureLayoutEngine;
+  const defaultConfig = {
+    quarterNoteSpacing: 50,
+    measurePadding: 20,
+    minNoteSpacing: 10,
+    noteFontSize: 20,
+  };
+
+  beforeEach(() => {
+    engine = new MeasureLayoutEngine(defaultConfig);
+    vi.spyOn(console, 'log').mockImplementation(() => {});
+    vi.spyOn(console, 'warn').mockImplementation(() => {});
+  });
+
+  // ==================== 基本功能测试 ====================
+
+  describe('基本功能', () => {
+    it('应该能创建布局引擎实例', () => {
+      expect(engine).toBeDefined();
+      expect(engine).toBeInstanceOf(MeasureLayoutEngine);
+    });
+
+    it('应该能使用工厂函数创建实例', () => {
+      const e = createMeasureLayoutEngine();
+      expect(e).toBeInstanceOf(MeasureLayoutEngine);
+    });
+
+    it('应该能获取配置', () => {
+      const config = engine.getConfig();
+      expect(config.quarterNoteSpacing).toBe(50);
+      expect(config.measurePadding).toBe(20);
+    });
+
+    it('应该能更新配置', () => {
+      engine.updateConfig({ quarterNoteSpacing: 60 });
+      expect(engine.getConfig().quarterNoteSpacing).toBe(60);
+    });
+  });
+
+  // ==================== 小节宽度计算测试 ====================
+
+  describe('小节宽度计算', () => {
+    it('4/4拍小节宽度应该正确', () => {
+      const measure = createDefaultMeasure(1, { beats: 4, beatType: 4 });
+      const { width, contentWidth } = engine.calculateMeasureWidth(measure);
+      
+      // 4/4拍 = 4个四分音符 = 4 * 50 = 200
+      // 总宽度 = 200 + 20 * 2 = 240
+      expect(contentWidth).toBe(200);
+      expect(width).toBe(240);
+    });
+
+    it('3/4拍小节宽度应该正确', () => {
+      const measure = createDefaultMeasure(1, { beats: 3, beatType: 4 });
+      const { width, contentWidth } = engine.calculateMeasureWidth(measure);
+      
+      // 3/4拍 = 3个四分音符 = 3 * 50 = 150
+      // 总宽度 = 150 + 20 * 2 = 190
+      expect(contentWidth).toBe(150);
+      expect(width).toBe(190);
+    });
+
+    it('6/8拍小节宽度应该正确', () => {
+      const measure = createDefaultMeasure(1, { beats: 6, beatType: 8 });
+      const { width, contentWidth } = engine.calculateMeasureWidth(measure);
+      
+      // 6/8拍 = 6/8 * 4 = 3个四分音符 = 3 * 50 = 150
+      // 总宽度 = 150 + 20 * 2 = 190
+      expect(contentWidth).toBe(150);
+      expect(width).toBe(190);
+    });
+
+    it('2/4拍小节宽度应该正确', () => {
+      const measure = createDefaultMeasure(1, { beats: 2, beatType: 4 });
+      const { width, contentWidth } = engine.calculateMeasureWidth(measure);
+      
+      // 2/4拍 = 2个四分音符 = 2 * 50 = 100
+      // 总宽度 = 100 + 20 * 2 = 140
+      expect(contentWidth).toBe(100);
+      expect(width).toBe(140);
+    });
+
+    it('4/4拍宽度应该是3/4拍的4/3倍', () => {
+      const measure44 = createDefaultMeasure(1, { beats: 4, beatType: 4 });
+      const measure34 = createDefaultMeasure(2, { beats: 3, beatType: 4 });
+      
+      const { contentWidth: width44 } = engine.calculateMeasureWidth(measure44);
+      const { contentWidth: width34 } = engine.calculateMeasureWidth(measure34);
+      
+      expect(width44 / width34).toBeCloseTo(4 / 3, 5);
+    });
+
+    it('同一拍号的所有小节宽度应该相同', () => {
+      const measures = [
+        createDefaultMeasure(1, { beats: 4, beatType: 4 }),
+        createDefaultMeasure(2, { beats: 4, beatType: 4 }),
+        createDefaultMeasure(3, { beats: 4, beatType: 4 }),
+      ];
+      
+      engine.layoutMeasures(measures);
+      
+      expect(measures[0].width).toBe(measures[1].width);
+      expect(measures[1].width).toBe(measures[2].width);
+    });
+  });
+
+  // ==================== 音符位置计算测试 ====================
+
+  describe('音符位置计算', () => {
+    it('第一个音符应该在左padding位置', () => {
+      const measure = createMeasureWithNotes(1, [
+        { pitch: 1, duration: 1, timestamp: 0 },
+      ]);
+      measure.x = 0;
+      
+      const positions = engine.calculateNotePositions(measure);
+      
+      // 第一个音符X = 小节X + 左padding = 0 + 20 = 20
+      expect(positions[0].x).toBe(20);
+    });
+
+    it('二分音符间距应该是四分音符的2倍', () => {
+      const measure = createMeasureWithNotes(1, [
+        { pitch: 1, duration: 2, timestamp: 0 },  // 二分音符
+        { pitch: 2, duration: 2, timestamp: 2 },  // 二分音符(时间戳+2)
+      ]);
+      measure.x = 0;
+      
+      const positions = engine.calculateNotePositions(measure);
+      
+      // 间距 = 2 * 50 = 100(因为时间戳差是2个四分音符)
+      expect(positions[1].x - positions[0].x).toBe(100);
+    });
+
+    it('八分音符间距应该是四分音符的0.5倍', () => {
+      const measure = createMeasureWithNotes(1, [
+        { pitch: 1, duration: 0.5, timestamp: 0 },    // 八分音符
+        { pitch: 2, duration: 0.5, timestamp: 0.5 },  // 八分音符
+      ]);
+      measure.x = 0;
+      
+      const positions = engine.calculateNotePositions(measure);
+      
+      // 间距 = 0.5 * 50 = 25
+      expect(positions[1].x - positions[0].x).toBe(25);
+    });
+
+    it('四分音符间距应该是50像素', () => {
+      const measure = createMeasureWithNotes(1, [
+        { pitch: 1, duration: 1, timestamp: 0 },
+        { pitch: 2, duration: 1, timestamp: 1 },
+        { pitch: 3, duration: 1, timestamp: 2 },
+        { pitch: 4, duration: 1, timestamp: 3 },
+      ]);
+      measure.x = 0;
+      
+      const positions = engine.calculateNotePositions(measure);
+      
+      expect(positions[1].x - positions[0].x).toBe(50);
+      expect(positions[2].x - positions[1].x).toBe(50);
+      expect(positions[3].x - positions[2].x).toBe(50);
+    });
+
+    it('应该正确处理6/8拍的音符位置', () => {
+      // 6/8拍,beatType=8,间距系数=4/8=0.5
+      const measure = createMeasureWithNotes(1, [
+        { pitch: 1, duration: 0.5, timestamp: 0 },    // 八分音符
+        { pitch: 2, duration: 0.5, timestamp: 0.5 },  // 八分音符
+      ], { beats: 6, beatType: 8 });
+      measure.x = 0;
+      
+      const positions = engine.calculateNotePositions(measure);
+      
+      // 在6/8拍中,间距 = 0.5 * (4/8) * 50 = 12.5
+      expect(positions[1].x - positions[0].x).toBe(12.5);
+    });
+  });
+
+  // ==================== 批量布局测试 ====================
+
+  describe('批量布局', () => {
+    it('应该正确计算累计X坐标', () => {
+      const measures = [
+        createDefaultMeasure(1, { beats: 4, beatType: 4 }),
+        createDefaultMeasure(2, { beats: 4, beatType: 4 }),
+        createDefaultMeasure(3, { beats: 4, beatType: 4 }),
+      ];
+      
+      engine.layoutMeasures(measures);
+      
+      // 每个小节宽度 = 240
+      expect(measures[0].x).toBe(0);
+      expect(measures[1].x).toBe(240);
+      expect(measures[2].x).toBe(480);
+    });
+
+    it('应该返回正确的统计信息', () => {
+      const measures = [
+        createMeasureWithNotes(1, [
+          { pitch: 1, duration: 1, timestamp: 0 },
+          { pitch: 2, duration: 1, timestamp: 1 },
+        ]),
+        createMeasureWithNotes(2, [
+          { pitch: 3, duration: 1, timestamp: 0 },
+        ]),
+      ];
+      
+      engine.layoutMeasures(measures);
+      const stats = engine.getStats();
+      
+      expect(stats.measureCount).toBe(2);
+      expect(stats.noteCount).toBe(3);
+      expect(stats.totalWidth).toBe(480); // 2 * 240
+    });
+
+    it('应该支持自定义起始X坐标', () => {
+      const measures = [
+        createDefaultMeasure(1, { beats: 4, beatType: 4 }),
+      ];
+      
+      engine.layoutMeasures(measures, 100);
+      
+      expect(measures[0].x).toBe(100);
+    });
+  });
+
+  // ==================== Padding测试 ====================
+
+  describe('Padding应用', () => {
+    it('应该正确应用左右padding', () => {
+      const measure = createDefaultMeasure(1, { beats: 4, beatType: 4 });
+      const result = engine.layoutMeasure(measure);
+      
+      // 内容宽度 = 200,总宽度 = 200 + 40 = 240
+      expect(result.width - result.contentWidth).toBe(40); // 2 * 20
+    });
+
+    it('修改padding应该影响小节宽度', () => {
+      engine.updateConfig({ measurePadding: 30 });
+      
+      const measure = createDefaultMeasure(1, { beats: 4, beatType: 4 });
+      const result = engine.layoutMeasure(measure);
+      
+      // 内容宽度 = 200,总宽度 = 200 + 60 = 260
+      expect(result.width).toBe(260);
+    });
+  });
+
+  // ==================== 最小间距测试 ====================
+
+  describe('最小间距限制', () => {
+    it('音符间距不应该小于最小间距', () => {
+      // 创建间距很小的音符
+      const measure = createMeasureWithNotes(1, [
+        { pitch: 1, duration: 0.125, timestamp: 0 },      // 32分音符
+        { pitch: 2, duration: 0.125, timestamp: 0.125 },  // 32分音符
+      ]);
+      measure.x = 0;
+      
+      const positions = engine.calculateNotePositions(measure);
+      
+      // 理论间距 = 0.125 * 50 = 6.25,但最小间距是10
+      expect(positions[1].x - positions[0].x).toBeGreaterThanOrEqual(10);
+    });
+  });
+
+  // ==================== 音符宽度计算测试 ====================
+
+  describe('音符宽度计算', () => {
+    it('普通音符宽度应该基于字体大小', () => {
+      const note = createDefaultNote({ pitch: 1 });
+      const width = engine.calculateNoteWidth(note);
+      
+      // 基础宽度 = 20 * 0.6 = 12
+      expect(width).toBe(12);
+    });
+
+    it('带升降号的音符应该更宽', () => {
+      const note = createDefaultNote({ pitch: 1, accidental: 'sharp' });
+      const normalNote = createDefaultNote({ pitch: 1 });
+      
+      const sharpWidth = engine.calculateNoteWidth(note);
+      const normalWidth = engine.calculateNoteWidth(normalNote);
+      
+      expect(sharpWidth).toBeGreaterThan(normalWidth);
+    });
+
+    it('带附点的音符应该更宽', () => {
+      const note = createDefaultNote({ pitch: 1, dots: 1 });
+      const normalNote = createDefaultNote({ pitch: 1 });
+      
+      const dottedWidth = engine.calculateNoteWidth(note);
+      const normalWidth = engine.calculateNoteWidth(normalNote);
+      
+      expect(dottedWidth).toBeGreaterThan(normalWidth);
+    });
+  });
+});
+
+// ==================== 工具函数测试 ====================
+
+describe('工具函数', () => {
+  describe('calculateMeasureRealValue', () => {
+    it('4/4拍应该是4', () => {
+      expect(calculateMeasureRealValue(4, 4)).toBe(4);
+    });
+
+    it('3/4拍应该是3', () => {
+      expect(calculateMeasureRealValue(3, 4)).toBe(3);
+    });
+
+    it('6/8拍应该是3', () => {
+      expect(calculateMeasureRealValue(6, 8)).toBe(3);
+    });
+
+    it('2/2拍应该是4', () => {
+      expect(calculateMeasureRealValue(2, 2)).toBe(4);
+    });
+  });
+
+  describe('calculateTimestampRatio', () => {
+    it('4/4拍中时间戳2应该在50%位置', () => {
+      expect(calculateTimestampRatio(2, 4, 4)).toBe(0.5);
+    });
+
+    it('4/4拍中时间戳1应该在25%位置', () => {
+      expect(calculateTimestampRatio(1, 4, 4)).toBe(0.25);
+    });
+  });
+
+  describe('validateMeasureWidths', () => {
+    it('同一拍号的小节宽度相同时应该返回true', () => {
+      const engine = new MeasureLayoutEngine();
+      const measures = [
+        createDefaultMeasure(1, { beats: 4, beatType: 4 }),
+        createDefaultMeasure(2, { beats: 4, beatType: 4 }),
+      ];
+      
+      engine.layoutMeasures(measures);
+      
+      expect(validateMeasureWidths(measures)).toBe(true);
+    });
+  });
+});

+ 506 - 0
src/jianpu-renderer/__tests__/MultiVoiceAligner.test.ts

@@ -0,0 +1,506 @@
+/**
+ * MultiVoiceAligner 单元测试
+ * 
+ * 测试覆盖:
+ * - 多声部对齐功能
+ * - 单声部不受影响
+ * - 休止符对齐
+ * - 不同时值音符对齐
+ * - 边界情况处理
+ */
+
+import { describe, it, expect, beforeEach, vi } from 'vitest';
+import { 
+  MultiVoiceAligner, 
+  createMultiVoiceAligner,
+  alignMeasureVoices,
+  alignAllMeasureVoices,
+  areNotesAtSameTime,
+  getVoiceStats,
+} from '../core/layout/MultiVoiceAligner';
+import { JianpuMeasure, createDefaultMeasure } from '../models/JianpuMeasure';
+import { JianpuNote, createDefaultNote } from '../models/JianpuNote';
+
+// ==================== 辅助函数 ====================
+
+/**
+ * 创建多声部测试小节
+ */
+function createMultiVoiceMeasure(
+  measureNumber: number,
+  voicesData: Array<Array<{ pitch: number; duration: number; timestamp: number; x?: number; isRest?: boolean }>>
+): JianpuMeasure {
+  const measure = createDefaultMeasure(measureNumber);
+  
+  measure.voices = voicesData.map((voiceNotes, voiceIndex) =>
+    voiceNotes.map((n, noteIndex) => 
+      createDefaultNote({
+        pitch: n.pitch,
+        duration: n.duration,
+        timestamp: n.timestamp,
+        x: n.x ?? 0,
+        isRest: n.isRest ?? false,
+        measureIndex: measureNumber - 1,
+        voiceIndex,
+      })
+    )
+  );
+  
+  // 同步 notes 属性
+  measure.notes = measure.voices;
+  
+  return measure;
+}
+
+// ==================== 测试用例 ====================
+
+describe('MultiVoiceAligner', () => {
+  let aligner: MultiVoiceAligner;
+
+  beforeEach(() => {
+    aligner = new MultiVoiceAligner();
+    vi.spyOn(console, 'log').mockImplementation(() => {});
+  });
+
+  // ==================== 基本功能测试 ====================
+
+  describe('基本功能', () => {
+    it('应该能创建对齐器实例', () => {
+      expect(aligner).toBeDefined();
+      expect(aligner).toBeInstanceOf(MultiVoiceAligner);
+    });
+
+    it('应该能使用工厂函数创建实例', () => {
+      const a = createMultiVoiceAligner();
+      expect(a).toBeInstanceOf(MultiVoiceAligner);
+    });
+
+    it('应该能获取配置', () => {
+      const config = aligner.getConfig();
+      expect(config.timestampPrecision).toBe(0.001);
+      expect(config.includeRests).toBe(true);
+      expect(config.alignmentStrategy).toBe('max');
+    });
+
+    it('应该能更新配置', () => {
+      aligner.updateConfig({ alignmentStrategy: 'min' });
+      expect(aligner.getConfig().alignmentStrategy).toBe('min');
+    });
+  });
+
+  // ==================== 单声部测试 ====================
+
+  describe('单声部处理', () => {
+    it('单声部小节不应该被对齐处理', () => {
+      const measure = createMultiVoiceMeasure(1, [
+        [
+          { pitch: 1, duration: 1, timestamp: 0, x: 20 },
+          { pitch: 2, duration: 1, timestamp: 1, x: 70 },
+        ],
+      ]);
+
+      const result = aligner.alignVoices(measure);
+
+      expect(result.voiceCount).toBe(1);
+      expect(result.alignedNoteCount).toBe(0);
+      expect(measure.voices[0][0].x).toBe(20); // X坐标不变
+      expect(measure.voices[0][1].x).toBe(70);
+    });
+
+    it('needsAlignment应该对单声部返回false', () => {
+      const measure = createMultiVoiceMeasure(1, [
+        [{ pitch: 1, duration: 1, timestamp: 0 }],
+      ]);
+
+      expect(aligner.needsAlignment(measure)).toBe(false);
+    });
+  });
+
+  // ==================== 多声部对齐测试 ====================
+
+  describe('多声部对齐', () => {
+    it('相同时间点的音符应该X坐标相同', () => {
+      // 两个声部,在时间戳0都有音符
+      const measure = createMultiVoiceMeasure(1, [
+        [{ pitch: 1, duration: 1, timestamp: 0, x: 20 }],  // 声部1
+        [{ pitch: 5, duration: 1, timestamp: 0, x: 25 }],  // 声部2
+      ]);
+
+      aligner.alignVoices(measure);
+
+      // 默认策略是max,所以应该取25
+      expect(measure.voices[0][0].x).toBe(25);
+      expect(measure.voices[1][0].x).toBe(25);
+    });
+
+    it('不同时间点的音符不应该互相影响', () => {
+      const measure = createMultiVoiceMeasure(1, [
+        [
+          { pitch: 1, duration: 1, timestamp: 0, x: 20 },
+          { pitch: 2, duration: 1, timestamp: 1, x: 70 },
+        ],
+        [
+          { pitch: 5, duration: 1, timestamp: 0, x: 25 },
+          { pitch: 6, duration: 1, timestamp: 1, x: 75 },
+        ],
+      ]);
+
+      aligner.alignVoices(measure);
+
+      // 时间戳0的音符对齐到25
+      expect(measure.voices[0][0].x).toBe(25);
+      expect(measure.voices[1][0].x).toBe(25);
+
+      // 时间戳1的音符对齐到75
+      expect(measure.voices[0][1].x).toBe(75);
+      expect(measure.voices[1][1].x).toBe(75);
+    });
+
+    it('三个声部应该正确对齐', () => {
+      const measure = createMultiVoiceMeasure(1, [
+        [{ pitch: 1, duration: 1, timestamp: 0, x: 20 }],
+        [{ pitch: 5, duration: 1, timestamp: 0, x: 25 }],
+        [{ pitch: 3, duration: 1, timestamp: 0, x: 30 }],
+      ]);
+
+      aligner.alignVoices(measure);
+
+      // 所有声部对齐到最大值30
+      expect(measure.voices[0][0].x).toBe(30);
+      expect(measure.voices[1][0].x).toBe(30);
+      expect(measure.voices[2][0].x).toBe(30);
+    });
+  });
+
+  // ==================== 对齐策略测试 ====================
+
+  describe('对齐策略', () => {
+    it('max策略应该取最大X坐标', () => {
+      const alignerMax = new MultiVoiceAligner({ alignmentStrategy: 'max' });
+      const measure = createMultiVoiceMeasure(1, [
+        [{ pitch: 1, duration: 1, timestamp: 0, x: 20 }],
+        [{ pitch: 5, duration: 1, timestamp: 0, x: 30 }],
+      ]);
+
+      alignerMax.alignVoices(measure);
+
+      expect(measure.voices[0][0].x).toBe(30);
+      expect(measure.voices[1][0].x).toBe(30);
+    });
+
+    it('min策略应该取最小X坐标', () => {
+      const alignerMin = new MultiVoiceAligner({ alignmentStrategy: 'min' });
+      const measure = createMultiVoiceMeasure(1, [
+        [{ pitch: 1, duration: 1, timestamp: 0, x: 20 }],
+        [{ pitch: 5, duration: 1, timestamp: 0, x: 30 }],
+      ]);
+
+      alignerMin.alignVoices(measure);
+
+      expect(measure.voices[0][0].x).toBe(20);
+      expect(measure.voices[1][0].x).toBe(20);
+    });
+
+    it('avg策略应该取平均X坐标', () => {
+      const alignerAvg = new MultiVoiceAligner({ alignmentStrategy: 'avg' });
+      const measure = createMultiVoiceMeasure(1, [
+        [{ pitch: 1, duration: 1, timestamp: 0, x: 20 }],
+        [{ pitch: 5, duration: 1, timestamp: 0, x: 30 }],
+      ]);
+
+      alignerAvg.alignVoices(measure);
+
+      expect(measure.voices[0][0].x).toBe(25); // (20 + 30) / 2
+      expect(measure.voices[1][0].x).toBe(25);
+    });
+  });
+
+  // ==================== 休止符测试 ====================
+
+  describe('休止符处理', () => {
+    it('默认情况下休止符应该参与对齐', () => {
+      const measure = createMultiVoiceMeasure(1, [
+        [{ pitch: 1, duration: 1, timestamp: 0, x: 20 }],
+        [{ pitch: 0, duration: 1, timestamp: 0, x: 30, isRest: true }],
+      ]);
+
+      aligner.alignVoices(measure);
+
+      expect(measure.voices[0][0].x).toBe(30);
+      expect(measure.voices[1][0].x).toBe(30);
+    });
+
+    it('配置includeRests=false时休止符不参与对齐', () => {
+      const alignerNoRest = new MultiVoiceAligner({ includeRests: false });
+      const measure = createMultiVoiceMeasure(1, [
+        [{ pitch: 1, duration: 1, timestamp: 0, x: 20 }],
+        [{ pitch: 0, duration: 1, timestamp: 0, x: 30, isRest: true }],
+      ]);
+
+      alignerNoRest.alignVoices(measure);
+
+      // 只有一个非休止符,不满足对齐条件
+      expect(measure.voices[0][0].x).toBe(20); // 不变
+      expect(measure.voices[1][0].x).toBe(30); // 不变
+    });
+  });
+
+  // ==================== 不同时值测试 ====================
+
+  describe('不同时值音符对齐', () => {
+    it('不同时值的音符在相同开始时间应该对齐', () => {
+      const measure = createMultiVoiceMeasure(1, [
+        [
+          { pitch: 1, duration: 2, timestamp: 0, x: 20 },  // 二分音符
+        ],
+        [
+          { pitch: 5, duration: 1, timestamp: 0, x: 25 },  // 四分音符
+          { pitch: 6, duration: 1, timestamp: 1, x: 75 },  // 四分音符
+        ],
+      ]);
+
+      aligner.alignVoices(measure);
+
+      // 时间戳0对齐
+      expect(measure.voices[0][0].x).toBe(25);
+      expect(measure.voices[1][0].x).toBe(25);
+
+      // 时间戳1只有声部2有音符,不需要对齐
+      expect(measure.voices[1][1].x).toBe(75);
+    });
+
+    it('八分音符和四分音符混合应该正确对齐', () => {
+      const measure = createMultiVoiceMeasure(1, [
+        [
+          { pitch: 1, duration: 1, timestamp: 0, x: 20 },    // 四分音符
+          { pitch: 2, duration: 1, timestamp: 1, x: 70 },    // 四分音符
+        ],
+        [
+          { pitch: 5, duration: 0.5, timestamp: 0, x: 25 },    // 八分音符
+          { pitch: 6, duration: 0.5, timestamp: 0.5, x: 50 },  // 八分音符
+          { pitch: 7, duration: 0.5, timestamp: 1, x: 75 },    // 八分音符
+          { pitch: 1, duration: 0.5, timestamp: 1.5, x: 100 }, // 八分音符
+        ],
+      ]);
+
+      aligner.alignVoices(measure);
+
+      // 时间戳0对齐
+      expect(measure.voices[0][0].x).toBe(25);
+      expect(measure.voices[1][0].x).toBe(25);
+
+      // 时间戳0.5只有声部2
+      expect(measure.voices[1][1].x).toBe(50);
+
+      // 时间戳1对齐
+      expect(measure.voices[0][1].x).toBe(75);
+      expect(measure.voices[1][2].x).toBe(75);
+
+      // 时间戳1.5只有声部2
+      expect(measure.voices[1][3].x).toBe(100);
+    });
+  });
+
+  // ==================== 批量对齐测试 ====================
+
+  describe('批量对齐', () => {
+    it('应该能对齐多个小节', () => {
+      const measures = [
+        createMultiVoiceMeasure(1, [
+          [{ pitch: 1, duration: 1, timestamp: 0, x: 20 }],
+          [{ pitch: 5, duration: 1, timestamp: 0, x: 30 }],
+        ]),
+        createMultiVoiceMeasure(2, [
+          [{ pitch: 2, duration: 1, timestamp: 0, x: 260 }],
+          [{ pitch: 6, duration: 1, timestamp: 0, x: 270 }],
+        ]),
+      ];
+
+      const results = aligner.alignMeasures(measures);
+
+      expect(results.length).toBe(2);
+      expect(measures[0].voices[0][0].x).toBe(30);
+      expect(measures[1].voices[0][0].x).toBe(270);
+    });
+  });
+
+  // ==================== 辅助方法测试 ====================
+
+  describe('辅助方法', () => {
+    it('getUniqueTimestamps应该返回排序后的时间戳', () => {
+      const measure = createMultiVoiceMeasure(1, [
+        [
+          { pitch: 1, duration: 1, timestamp: 1 },
+          { pitch: 2, duration: 1, timestamp: 0 },
+        ],
+        [
+          { pitch: 5, duration: 0.5, timestamp: 0.5 },
+        ],
+      ]);
+
+      const timestamps = aligner.getUniqueTimestamps(measure);
+
+      expect(timestamps).toEqual([0, 0.5, 1]);
+    });
+
+    it('getNotesAtTimestamp应该返回指定时间戳的音符', () => {
+      const measure = createMultiVoiceMeasure(1, [
+        [{ pitch: 1, duration: 1, timestamp: 0 }],
+        [{ pitch: 5, duration: 1, timestamp: 0 }],
+      ]);
+
+      const notes = aligner.getNotesAtTimestamp(measure, 0);
+
+      expect(notes.length).toBe(2);
+      expect(notes[0].voiceIndex).toBe(0);
+      expect(notes[1].voiceIndex).toBe(1);
+    });
+
+    it('validateAlignment应该验证对齐结果', () => {
+      const measure = createMultiVoiceMeasure(1, [
+        [{ pitch: 1, duration: 1, timestamp: 0, x: 20 }],
+        [{ pitch: 5, duration: 1, timestamp: 0, x: 25 }],
+      ]);
+
+      // 对齐前应该不通过
+      expect(aligner.validateAlignment(measure)).toBe(false);
+
+      // 对齐后应该通过
+      aligner.alignVoices(measure);
+      expect(aligner.validateAlignment(measure)).toBe(true);
+    });
+  });
+
+  // ==================== 边界情况测试 ====================
+
+  describe('边界情况', () => {
+    it('空小节应该正常处理', () => {
+      const measure = createDefaultMeasure(1);
+      const result = aligner.alignVoices(measure);
+      expect(result.alignedNoteCount).toBe(0);
+    });
+
+    it('只有空声部的小节应该正常处理', () => {
+      const measure = createDefaultMeasure(1);
+      measure.voices = [[], []];
+      measure.notes = measure.voices;
+
+      const result = aligner.alignVoices(measure);
+      expect(result.alignedNoteCount).toBe(0);
+    });
+
+    it('浮点数时间戳精度问题应该正确处理', () => {
+      const measure = createMultiVoiceMeasure(1, [
+        [{ pitch: 1, duration: 1, timestamp: 0.333333, x: 20 }],
+        [{ pitch: 5, duration: 1, timestamp: 0.333334, x: 30 }], // 接近相同
+      ]);
+
+      aligner.alignVoices(measure);
+
+      // 由于时间戳非常接近(差异<0.001),应该被视为相同并对齐
+      expect(measure.voices[0][0].x).toBe(30);
+      expect(measure.voices[1][0].x).toBe(30);
+    });
+  });
+});
+
+// ==================== 工具函数测试 ====================
+
+describe('工具函数', () => {
+  describe('alignMeasureVoices', () => {
+    it('应该快速对齐单个小节', () => {
+      const measure = createMultiVoiceMeasure(1, [
+        [{ pitch: 1, duration: 1, timestamp: 0, x: 20 }],
+        [{ pitch: 5, duration: 1, timestamp: 0, x: 30 }],
+      ]);
+
+      const aligned = alignMeasureVoices(measure);
+
+      expect(aligned).toBe(true);
+      expect(measure.voices[0][0].x).toBe(30);
+    });
+
+    it('单声部应该返回false', () => {
+      const measure = createMultiVoiceMeasure(1, [
+        [{ pitch: 1, duration: 1, timestamp: 0, x: 20 }],
+      ]);
+
+      const aligned = alignMeasureVoices(measure);
+
+      expect(aligned).toBe(false);
+    });
+  });
+
+  describe('alignAllMeasureVoices', () => {
+    it('应该快速对齐多个小节', () => {
+      const measures = [
+        createMultiVoiceMeasure(1, [
+          [{ pitch: 1, duration: 1, timestamp: 0, x: 20 }],
+          [{ pitch: 5, duration: 1, timestamp: 0, x: 30 }],
+        ]),
+        createMultiVoiceMeasure(2, [
+          [{ pitch: 2, duration: 1, timestamp: 0, x: 260 }],
+        ]), // 单声部,不需要对齐
+      ];
+
+      const count = alignAllMeasureVoices(measures);
+
+      expect(count).toBe(1); // 只有第一个小节被对齐
+    });
+  });
+
+  describe('areNotesAtSameTime', () => {
+    it('相同时间戳应该返回true', () => {
+      const note1 = createDefaultNote({ timestamp: 0 });
+      const note2 = createDefaultNote({ timestamp: 0 });
+
+      expect(areNotesAtSameTime(note1, note2)).toBe(true);
+    });
+
+    it('不同时间戳应该返回false', () => {
+      const note1 = createDefaultNote({ timestamp: 0 });
+      const note2 = createDefaultNote({ timestamp: 1 });
+
+      expect(areNotesAtSameTime(note1, note2)).toBe(false);
+    });
+
+    it('接近的时间戳应该根据精度判断', () => {
+      const note1 = createDefaultNote({ timestamp: 0.0001 });
+      const note2 = createDefaultNote({ timestamp: 0.0002 });
+
+      expect(areNotesAtSameTime(note1, note2, 0.001)).toBe(true);
+      expect(areNotesAtSameTime(note1, note2, 0.00001)).toBe(false);
+    });
+  });
+
+  describe('getVoiceStats', () => {
+    it('应该返回正确的声部统计', () => {
+      const measure = createMultiVoiceMeasure(1, [
+        [
+          { pitch: 1, duration: 1, timestamp: 0 },
+          { pitch: 2, duration: 1, timestamp: 1 },
+        ],
+        [
+          { pitch: 5, duration: 1, timestamp: 0 },
+        ],
+      ]);
+
+      const stats = getVoiceStats(measure);
+
+      expect(stats.voiceCount).toBe(2);
+      expect(stats.noteCountPerVoice).toEqual([2, 1]);
+      expect(stats.hasMultiVoice).toBe(true);
+      expect(stats.maxNotesInVoice).toBe(2);
+    });
+
+    it('单声部应该hasMultiVoice为false', () => {
+      const measure = createMultiVoiceMeasure(1, [
+        [{ pitch: 1, duration: 1, timestamp: 0 }],
+      ]);
+
+      const stats = getVoiceStats(measure);
+
+      expect(stats.hasMultiVoice).toBe(false);
+    });
+  });
+});

+ 495 - 0
src/jianpu-renderer/__tests__/NotePositionCalculator.test.ts

@@ -0,0 +1,495 @@
+/**
+ * 音符位置计算器测试
+ * 
+ * @description 测试NotePositionCalculator的Y坐标计算功能
+ */
+
+import { describe, it, expect, beforeEach } from 'vitest';
+import {
+  NotePositionCalculator,
+  createNotePositionCalculator,
+  calculateVoiceY,
+  validateNoteYPositions,
+  getDefaultNotePositionConfig,
+  NotePositionConfig,
+} from '../core/layout/NotePositionCalculator';
+import { JianpuNote, createDefaultNote } from '../models/JianpuNote';
+import { JianpuMeasure, createDefaultMeasure } from '../models/JianpuMeasure';
+import { JianpuSystem } from '../models/JianpuSystem';
+
+// ==================== 测试辅助函数 ====================
+
+/**
+ * 创建测试用音符
+ */
+function createTestNote(options: {
+  voiceIndex?: number;
+  octave?: number;
+  pitch?: number;
+  timestamp?: number;
+} = {}): JianpuNote {
+  return createDefaultNote({
+    voiceIndex: options.voiceIndex ?? 0,
+    octave: options.octave ?? 0,
+    pitch: options.pitch ?? 1,
+    timestamp: options.timestamp ?? 0,
+    measureIndex: 0,
+  });
+}
+
+/**
+ * 创建带有多声部音符的测试小节
+ */
+function createTestMeasure(voiceCount: number, notesPerVoice: number = 4): JianpuMeasure {
+  const measure = createDefaultMeasure(1);
+  measure.index = 0;
+  measure.voices = [];
+  
+  for (let v = 0; v < voiceCount; v++) {
+    const voice: JianpuNote[] = [];
+    for (let n = 0; n < notesPerVoice; n++) {
+      voice.push(createTestNote({
+        voiceIndex: v,
+        pitch: ((n % 7) + 1) as 1 | 2 | 3 | 4 | 5 | 6 | 7,
+        timestamp: n,
+      }));
+    }
+    measure.voices.push(voice);
+  }
+  
+  measure.notes = measure.voices;
+  return measure;
+}
+
+/**
+ * 创建测试用行
+ */
+function createTestSystem(measureCount: number, voiceCount: number = 1): JianpuSystem {
+  const measures: JianpuMeasure[] = [];
+  for (let i = 0; i < measureCount; i++) {
+    const measure = createTestMeasure(voiceCount, 4);
+    measure.index = i;
+    measure.measureNumber = i + 1;
+    measure.x = i * 200;
+    measure.width = 200;
+    measures.push(measure);
+  }
+
+  return {
+    index: 0,
+    measures,
+    x: 0,
+    y: 100,
+    width: measureCount * 200,
+    height: 100,
+  };
+}
+
+// ==================== 测试用例 ====================
+
+describe('NotePositionCalculator', () => {
+  let calculator: NotePositionCalculator;
+
+  beforeEach(() => {
+    calculator = new NotePositionCalculator();
+  });
+
+  // ==================== 基础功能测试 ====================
+
+  describe('基础功能', () => {
+    it('应该能创建实例', () => {
+      expect(calculator).toBeInstanceOf(NotePositionCalculator);
+    });
+
+    it('应该能获取配置', () => {
+      const config = calculator.getConfig();
+      expect(config.voiceSpacing).toBe(60);
+      expect(config.noteFontSize).toBeDefined();
+    });
+
+    it('应该能更新配置', () => {
+      calculator.updateConfig({ voiceSpacing: 80 });
+      expect(calculator.getConfig().voiceSpacing).toBe(80);
+    });
+
+    it('应该使用自定义配置', () => {
+      const customCalc = new NotePositionCalculator({
+        voiceSpacing: 100,
+        noteFontSize: 30,
+      });
+      const config = customCalc.getConfig();
+      expect(config.voiceSpacing).toBe(100);
+      expect(config.noteFontSize).toBe(30);
+    });
+  });
+
+  // ==================== 单声部Y坐标测试 ====================
+
+  describe('单声部Y坐标计算', () => {
+    it('单声部音符Y坐标应该等于baseY', () => {
+      const note = createTestNote({ voiceIndex: 0 });
+      const baseY = 100;
+      
+      const y = calculator.calculateNoteY(note, 1, baseY);
+      
+      expect(y).toBe(baseY);
+    });
+
+    it('单声部所有音符Y坐标应该相同', () => {
+      const notes = [
+        createTestNote({ voiceIndex: 0, timestamp: 0 }),
+        createTestNote({ voiceIndex: 0, timestamp: 1 }),
+        createTestNote({ voiceIndex: 0, timestamp: 2 }),
+      ];
+      const baseY = 100;
+      
+      const ys = notes.map(n => calculator.calculateNoteY(n, 1, baseY));
+      
+      expect(ys.every(y => y === baseY)).toBe(true);
+    });
+  });
+
+  // ==================== 多声部Y坐标测试 ====================
+
+  describe('多声部Y坐标计算', () => {
+    it('双声部应该垂直居中分布', () => {
+      const baseY = 100;
+      const voiceSpacing = calculator.getConfig().voiceSpacing; // 60
+      
+      const voice0Y = calculator.calculateVoiceY(baseY, 0, 2);
+      const voice1Y = calculator.calculateVoiceY(baseY, 1, 2);
+      
+      // 双声部:中心点在baseY
+      // voice0 = baseY - spacing/2 = 100 - 30 = 70
+      // voice1 = baseY + spacing/2 = 100 + 30 = 130
+      expect(voice0Y).toBe(baseY - voiceSpacing / 2);
+      expect(voice1Y).toBe(baseY + voiceSpacing / 2);
+    });
+
+    it('三声部应该垂直居中分布', () => {
+      const baseY = 100;
+      const voiceSpacing = calculator.getConfig().voiceSpacing; // 60
+      
+      const voice0Y = calculator.calculateVoiceY(baseY, 0, 3);
+      const voice1Y = calculator.calculateVoiceY(baseY, 1, 3);
+      const voice2Y = calculator.calculateVoiceY(baseY, 2, 3);
+      
+      // 三声部:中间声部在baseY
+      // totalHeight = 2 * 60 = 120
+      // startY = 100 - 60 = 40
+      // voice0 = 40, voice1 = 100, voice2 = 160
+      expect(voice0Y).toBe(baseY - voiceSpacing);
+      expect(voice1Y).toBe(baseY);
+      expect(voice2Y).toBe(baseY + voiceSpacing);
+    });
+
+    it('多声部Y坐标应该递增', () => {
+      const baseY = 100;
+      
+      for (let voiceCount = 2; voiceCount <= 5; voiceCount++) {
+        const ys = [];
+        for (let v = 0; v < voiceCount; v++) {
+          ys.push(calculator.calculateVoiceY(baseY, v, voiceCount));
+        }
+        
+        // 验证Y坐标递增
+        for (let i = 1; i < ys.length; i++) {
+          expect(ys[i]).toBeGreaterThan(ys[i - 1]);
+        }
+      }
+    });
+
+    it('多声部中心点应该在baseY附近', () => {
+      const baseY = 100;
+      
+      for (let voiceCount = 2; voiceCount <= 5; voiceCount++) {
+        const ys = [];
+        for (let v = 0; v < voiceCount; v++) {
+          ys.push(calculator.calculateVoiceY(baseY, v, voiceCount));
+        }
+        
+        const center = (ys[0] + ys[ys.length - 1]) / 2;
+        expect(center).toBeCloseTo(baseY, 5);
+      }
+    });
+
+    it('声部间距应该等于配置值', () => {
+      const baseY = 100;
+      const voiceSpacing = calculator.getConfig().voiceSpacing;
+      
+      for (let voiceCount = 2; voiceCount <= 4; voiceCount++) {
+        for (let v = 1; v < voiceCount; v++) {
+          const prevY = calculator.calculateVoiceY(baseY, v - 1, voiceCount);
+          const currY = calculator.calculateVoiceY(baseY, v, voiceCount);
+          expect(currY - prevY).toBeCloseTo(voiceSpacing, 5);
+        }
+      }
+    });
+  });
+
+  // ==================== 垂直区域测试 ====================
+
+  describe('垂直区域计算', () => {
+    it('应该正确计算垂直区域', () => {
+      const noteY = 100;
+      const regions = calculator.calculateVerticalRegions(noteY);
+      
+      expect(regions.noteY).toBe(noteY);
+      expect(regions.accidentalY).toBeLessThan(noteY);
+      expect(regions.highOctaveY).toBeLessThan(noteY);
+      expect(regions.lowOctaveY).toBeGreaterThan(noteY);
+      expect(regions.underlineY).toBeGreaterThan(regions.lowOctaveY);
+      expect(regions.lyricY).toBeGreaterThan(regions.underlineY);
+    });
+
+    it('totalHeight应该包含所有区域', () => {
+      const noteY = 100;
+      const regions = calculator.calculateVerticalRegions(noteY);
+      
+      expect(regions.totalHeight).toBeGreaterThan(0);
+    });
+  });
+
+  // ==================== 高度计算测试 ====================
+
+  describe('高度计算', () => {
+    it('单声部高度应该正确', () => {
+      const height = calculator.calculateSingleVoiceHeight();
+      expect(height).toBeGreaterThan(0);
+    });
+
+    it('不显示歌词时高度应该更小', () => {
+      const withLyrics = calculator.calculateSingleVoiceHeight(true);
+      const withoutLyrics = calculator.calculateSingleVoiceHeight(false);
+      
+      expect(withoutLyrics).toBeLessThan(withLyrics);
+    });
+
+    it('多声部高度应该大于单声部', () => {
+      const singleHeight = calculator.calculateMultiVoiceHeight(1);
+      const doubleHeight = calculator.calculateMultiVoiceHeight(2);
+      const tripleHeight = calculator.calculateMultiVoiceHeight(3);
+      
+      expect(doubleHeight).toBeGreaterThan(singleHeight);
+      expect(tripleHeight).toBeGreaterThan(doubleHeight);
+    });
+
+    it('多声部高度增量应该等于voiceSpacing', () => {
+      const voiceSpacing = calculator.getConfig().voiceSpacing;
+      
+      for (let v = 2; v <= 4; v++) {
+        const prevHeight = calculator.calculateMultiVoiceHeight(v - 1);
+        const currHeight = calculator.calculateMultiVoiceHeight(v);
+        expect(currHeight - prevHeight).toBeCloseTo(voiceSpacing, 5);
+      }
+    });
+  });
+
+  // ==================== 小节布局测试 ====================
+
+  describe('小节布局', () => {
+    it('应该为单声部小节布局音符', () => {
+      const measure = createTestMeasure(1, 4);
+      const baseY = 100;
+      
+      const result = calculator.layoutMeasureNotes(measure, baseY);
+      
+      expect(result.notes).toHaveLength(4);
+      expect(result.voiceLayouts).toHaveLength(1);
+      expect(result.stats.noteCount).toBe(4);
+      expect(result.stats.voiceCount).toBe(1);
+    });
+
+    it('应该为多声部小节布局音符', () => {
+      const measure = createTestMeasure(3, 4);
+      const baseY = 100;
+      
+      const result = calculator.layoutMeasureNotes(measure, baseY);
+      
+      expect(result.notes).toHaveLength(12); // 3 voices * 4 notes
+      expect(result.voiceLayouts).toHaveLength(3);
+      expect(result.stats.voiceCount).toBe(3);
+    });
+
+    it('布局后音符Y坐标应该被更新', () => {
+      const measure = createTestMeasure(2, 4);
+      const baseY = 100;
+      
+      // 布局前Y坐标为0
+      expect(measure.voices[0][0].y).toBe(0);
+      
+      calculator.layoutMeasureNotes(measure, baseY);
+      
+      // 布局后Y坐标应该被更新
+      expect(measure.voices[0][0].y).not.toBe(0);
+      expect(measure.voices[1][0].y).not.toBe(0);
+      expect(measure.voices[0][0].y).not.toBe(measure.voices[1][0].y);
+    });
+
+    it('同一声部的音符Y坐标应该相同', () => {
+      const measure = createTestMeasure(2, 4);
+      const baseY = 100;
+      
+      calculator.layoutMeasureNotes(measure, baseY);
+      
+      const voice0Y = measure.voices[0][0].y;
+      for (const note of measure.voices[0]) {
+        expect(note.y).toBe(voice0Y);
+      }
+    });
+  });
+
+  // ==================== 行布局测试 ====================
+
+  describe('行布局', () => {
+    it('应该为行内所有小节布局音符', () => {
+      const system = createTestSystem(3, 2);
+      
+      const result = calculator.layoutSystemNotes(system);
+      
+      expect(result.notes.length).toBe(3 * 2 * 4); // 3 measures * 2 voices * 4 notes
+      expect(result.stats.noteCount).toBe(24);
+      expect(result.stats.voiceCount).toBe(2);
+    });
+
+    it('行内所有小节的对应声部Y坐标应该相同', () => {
+      const system = createTestSystem(3, 2);
+      
+      calculator.layoutSystemNotes(system);
+      
+      // 获取第一个小节的声部Y坐标
+      const voice0Y = system.measures[0].voices[0][0].y;
+      const voice1Y = system.measures[0].voices[1][0].y;
+      
+      // 验证所有小节的对应声部Y坐标相同
+      for (const measure of system.measures) {
+        expect(measure.voices[0][0].y).toBe(voice0Y);
+        expect(measure.voices[1][0].y).toBe(voice1Y);
+      }
+    });
+  });
+
+  // ==================== 批量布局测试 ====================
+
+  describe('批量布局', () => {
+    it('应该为所有行布局音符', () => {
+      const systems = [
+        createTestSystem(2, 1),
+        createTestSystem(3, 2),
+      ];
+      systems[1].y = 200; // 设置不同的Y坐标
+      
+      const results = calculator.layoutAllSystems(systems);
+      
+      expect(results).toHaveLength(2);
+      expect(results[0].stats.voiceCount).toBe(1);
+      expect(results[1].stats.voiceCount).toBe(2);
+    });
+  });
+
+  // ==================== 音符部分位置测试 ====================
+
+  describe('音符部分位置', () => {
+    it('应该计算音符各部分的Y坐标', () => {
+      const note = createTestNote({ octave: 1 });
+      note.y = 100;
+      
+      const positions = calculator.getNotePartPositions(note);
+      
+      expect(positions.accidentalY).toBeLessThan(positions.noteY);
+      expect(positions.noteY).toBe(100);
+      expect(positions.underlineY).toBeGreaterThan(positions.noteY);
+      expect(positions.lyricY).toBeGreaterThan(positions.underlineY);
+    });
+
+    it('高音符应该有高音点位置', () => {
+      const note = createTestNote({ octave: 2 });
+      note.y = 100;
+      
+      const positions = calculator.getNotePartPositions(note);
+      
+      expect(positions.highDotsY.length).toBe(2);
+      expect(positions.lowDotsY.length).toBe(0);
+    });
+
+    it('低音符应该有低音点位置', () => {
+      const note = createTestNote({ octave: -1 });
+      note.y = 100;
+      
+      const positions = calculator.getNotePartPositions(note);
+      
+      expect(positions.highDotsY.length).toBe(0);
+      expect(positions.lowDotsY.length).toBe(1);
+    });
+  });
+});
+
+// ==================== 工厂函数测试 ====================
+
+describe('createNotePositionCalculator', () => {
+  it('应该创建默认配置的计算器', () => {
+    const calc = createNotePositionCalculator();
+    expect(calc).toBeInstanceOf(NotePositionCalculator);
+  });
+
+  it('应该创建自定义配置的计算器', () => {
+    const calc = createNotePositionCalculator({ voiceSpacing: 80 });
+    expect(calc.getConfig().voiceSpacing).toBe(80);
+  });
+});
+
+// ==================== 工具函数测试 ====================
+
+describe('calculateVoiceY', () => {
+  it('单声部应该返回baseY', () => {
+    expect(calculateVoiceY(100, 0, 1)).toBe(100);
+  });
+
+  it('双声部应该正确分布', () => {
+    const y0 = calculateVoiceY(100, 0, 2, 60);
+    const y1 = calculateVoiceY(100, 1, 2, 60);
+    
+    expect(y0).toBe(70);
+    expect(y1).toBe(130);
+  });
+
+  it('应该支持自定义间距', () => {
+    const y0 = calculateVoiceY(100, 0, 2, 100);
+    const y1 = calculateVoiceY(100, 1, 2, 100);
+    
+    expect(y1 - y0).toBe(100);
+  });
+});
+
+describe('validateNoteYPositions', () => {
+  it('正确布局应该验证通过', () => {
+    const calculator = new NotePositionCalculator();
+    const measure = createTestMeasure(2, 4);
+    calculator.layoutMeasureNotes(measure, 100);
+    
+    const result = validateNoteYPositions([measure]);
+    
+    expect(result.valid).toBe(true);
+    expect(result.errors).toHaveLength(0);
+  });
+
+  it('未布局的小节应该失败', () => {
+    const measure = createTestMeasure(2, 4);
+    // 不进行布局,所有Y坐标为0
+    
+    const result = validateNoteYPositions([measure]);
+    
+    // 由于所有Y都是0,不递增所以应该失败
+    expect(result.valid).toBe(false);
+  });
+});
+
+describe('getDefaultNotePositionConfig', () => {
+  it('应该返回默认配置', () => {
+    const config = getDefaultNotePositionConfig();
+    
+    expect(config.voiceSpacing).toBe(60);
+    expect(config.noteFontSize).toBeDefined();
+    expect(config.showLyrics).toBeDefined();
+  });
+});

+ 497 - 0
src/jianpu-renderer/__tests__/OSMDDataParser.test.ts

@@ -0,0 +1,497 @@
+/**
+ * OSMDDataParser 单元测试
+ * 
+ * 测试覆盖:
+ * - 元数据解析
+ * - 小节解析(拍号、调号)
+ * - 音符解析(音高、时值、八度)
+ * - 休止符识别
+ * - 升降号和附点
+ * - 多声部解析
+ * - OSMD数据引用保留
+ */
+
+import { describe, it, expect, beforeEach, vi } from 'vitest';
+import { 
+  OSMDDataParser, 
+  createOSMDDataParser,
+  stepToJianpu,
+  octaveToOffset,
+  alterToAccidental,
+  noteTypeToRealValue,
+} from '../core/parser/OSMDDataParser';
+
+// ==================== Mock OSMD 数据 ====================
+
+/**
+ * 创建模拟的OSMD实例
+ */
+function createMockOSMD(config: {
+  title?: string;
+  composer?: string;
+  measures?: any[];
+  tempo?: number;
+} = {}) {
+  const defaultMeasure = {
+    ActiveTimeSignature: { numerator: 4, denominator: 4 },
+    ActiveKeySignature: { keyTypeOriginal: 0, Mode: 0 },
+    tempoInBPM: config.tempo ?? 120,
+    lastRepetitionInstructions: [],
+  };
+
+  return {
+    Sheet: {
+      Title: { text: config.title ?? 'Test Song' },
+      Composer: { text: config.composer ?? 'Test Composer' },
+      SourceMeasures: config.measures ?? [defaultMeasure],
+    },
+    GraphicSheet: {
+      MeasureList: [[{ parentSourceMeasure: config.measures?.[0] ?? defaultMeasure }]],
+    },
+    cursor: {
+      Iterator: {
+        EndReached: true,
+        currentVoiceEntries: [],
+        currentMeasureIndex: 0,
+        currentTimeStamp: { RealValue: 0 },
+      },
+      reset: vi.fn(),
+      next: vi.fn(),
+    },
+  };
+}
+
+/**
+ * 创建模拟的音符
+ */
+function createMockNote(config: {
+  step?: string;
+  octave?: number;
+  alter?: number;
+  duration?: number;
+  realValue?: number;
+  isRest?: boolean;
+  isGraceNote?: boolean;
+  dots?: number;
+  halfTone?: number;
+} = {}) {
+  return {
+    pitch: config.isRest ? null : {
+      step: config.step ?? 'C',
+      octave: config.octave ?? 4,
+      alter: config.alter ?? 0,
+      frequency: 261.63,
+    },
+    length: {
+      realValue: config.realValue ?? config.duration ?? 1.0,
+      RealValue: config.realValue ?? config.duration ?? 1.0,
+    },
+    isRestFlag: config.isRest ?? false,
+    IsGraceNote: config.isGraceNote ?? false,
+    dots: config.dots ?? 0,
+    halfTone: config.halfTone,
+  };
+}
+
+/**
+ * 创建带音符的OSMD实例(使用cursor遍历)
+ */
+function createMockOSMDWithNotes(notes: any[], measureCount: number = 1) {
+  let noteIndex = 0;
+  
+  const measures = Array(measureCount).fill(null).map((_, i) => ({
+    ActiveTimeSignature: { numerator: 4, denominator: 4 },
+    ActiveKeySignature: { keyTypeOriginal: 0, Mode: 0 },
+    tempoInBPM: 120,
+    lastRepetitionInstructions: [],
+  }));
+
+  const iterator = {
+    EndReached: false,
+    currentVoiceEntries: [],
+    CurrentVoiceEntries: [],
+    currentMeasureIndex: 0,
+    currentTimeStamp: { RealValue: 0, realValue: 0 },
+    moveToNextVisibleVoiceEntry: vi.fn(() => {
+      noteIndex++;
+      if (noteIndex >= notes.length) {
+        iterator.EndReached = true;
+      } else {
+        iterator.currentVoiceEntries = [{
+          Notes: [notes[noteIndex]],
+          ParentVoice: { VoiceId: 0 },
+        }];
+        iterator.CurrentVoiceEntries = iterator.currentVoiceEntries;
+        iterator.currentTimeStamp = { RealValue: noteIndex * 0.25, realValue: noteIndex * 0.25 };
+      }
+    }),
+  };
+
+  // 初始化第一个音符
+  if (notes.length > 0) {
+    iterator.currentVoiceEntries = [{
+      Notes: [notes[0]],
+      ParentVoice: { VoiceId: 0 },
+    }];
+    iterator.CurrentVoiceEntries = iterator.currentVoiceEntries;
+  } else {
+    iterator.EndReached = true;
+  }
+
+  return {
+    Sheet: {
+      Title: { text: 'Test Song' },
+      Composer: { text: 'Test Composer' },
+      SourceMeasures: measures,
+    },
+    GraphicSheet: {
+      MeasureList: measures.map(m => [{ parentSourceMeasure: m }]),
+    },
+    cursor: {
+      Iterator: iterator,
+      reset: vi.fn(),
+      next: vi.fn(),
+    },
+  };
+}
+
+// ==================== 测试用例 ====================
+
+describe('OSMDDataParser', () => {
+  let parser: OSMDDataParser;
+
+  beforeEach(() => {
+    parser = new OSMDDataParser();
+    vi.spyOn(console, 'log').mockImplementation(() => {});
+    vi.spyOn(console, 'warn').mockImplementation(() => {});
+  });
+
+  // ==================== 基本功能测试 ====================
+
+  describe('基本功能', () => {
+    it('应该能创建解析器实例', () => {
+      expect(parser).toBeDefined();
+      expect(parser).toBeInstanceOf(OSMDDataParser);
+    });
+
+    it('应该能使用工厂函数创建实例', () => {
+      const p = createOSMDDataParser();
+      expect(p).toBeInstanceOf(OSMDDataParser);
+    });
+
+    it('OSMD实例为空时应该抛出错误', () => {
+      expect(() => parser.parse(null as any)).toThrow('OSMD实例不能为空');
+    });
+  });
+
+  // ==================== 元数据解析测试 ====================
+
+  describe('元数据解析', () => {
+    it('应该正确解析标题', () => {
+      const osmd = createMockOSMD({ title: '月亮代表我的心' });
+      const score = parser.parse(osmd);
+      expect(score.title).toBe('月亮代表我的心');
+    });
+
+    it('应该正确解析作曲家', () => {
+      const osmd = createMockOSMD({ composer: '翁清溪' });
+      const score = parser.parse(osmd);
+      expect(score.composer).toBe('翁清溪');
+    });
+
+    it('应该正确解析速度', () => {
+      const osmd = createMockOSMD({ tempo: 80 });
+      const score = parser.parse(osmd);
+      expect(score.tempo).toBe(80);
+    });
+
+    it('标题为空时应该使用默认值', () => {
+      const osmd = createMockOSMD({});
+      osmd.Sheet!.Title = undefined;
+      const score = parser.parse(osmd);
+      expect(score.title).toBe('Untitled');
+    });
+  });
+
+  // ==================== 小节解析测试 ====================
+
+  describe('小节解析', () => {
+    it('应该正确解析4/4拍小节', () => {
+      const osmd = createMockOSMD({
+        measures: [{
+          ActiveTimeSignature: { numerator: 4, denominator: 4 },
+          ActiveKeySignature: { keyTypeOriginal: 0 },
+          tempoInBPM: 120,
+          lastRepetitionInstructions: [],
+        }],
+      });
+      const score = parser.parse(osmd);
+      
+      expect(score.measures.length).toBe(1);
+      expect(score.measures[0].timeSignature).toEqual({ beats: 4, beatType: 4 });
+    });
+
+    it('应该正确解析3/4拍小节', () => {
+      const osmd = createMockOSMD({
+        measures: [{
+          ActiveTimeSignature: { numerator: 3, denominator: 4 },
+          ActiveKeySignature: { keyTypeOriginal: 0 },
+          tempoInBPM: 120,
+          lastRepetitionInstructions: [],
+        }],
+      });
+      const score = parser.parse(osmd);
+      
+      expect(score.measures[0].timeSignature).toEqual({ beats: 3, beatType: 4 });
+    });
+
+    it('应该正确解析6/8拍小节', () => {
+      const osmd = createMockOSMD({
+        measures: [{
+          ActiveTimeSignature: { numerator: 6, denominator: 8 },
+          ActiveKeySignature: { keyTypeOriginal: 0 },
+          tempoInBPM: 120,
+          lastRepetitionInstructions: [],
+        }],
+      });
+      const score = parser.parse(osmd);
+      
+      expect(score.measures[0].timeSignature).toEqual({ beats: 6, beatType: 8 });
+    });
+
+    it('应该正确解析多个小节', () => {
+      const osmd = createMockOSMD({
+        measures: [
+          { ActiveTimeSignature: { numerator: 4, denominator: 4 }, ActiveKeySignature: { keyTypeOriginal: 0 }, tempoInBPM: 120, lastRepetitionInstructions: [] },
+          { ActiveTimeSignature: { numerator: 4, denominator: 4 }, ActiveKeySignature: { keyTypeOriginal: 0 }, tempoInBPM: 120, lastRepetitionInstructions: [] },
+          { ActiveTimeSignature: { numerator: 4, denominator: 4 }, ActiveKeySignature: { keyTypeOriginal: 0 }, tempoInBPM: 120, lastRepetitionInstructions: [] },
+        ],
+      });
+      const score = parser.parse(osmd);
+      
+      expect(score.measures.length).toBe(3);
+      expect(score.totalMeasures).toBe(3);
+    });
+
+    it('应该正确解析调号变化', () => {
+      const osmd = createMockOSMD({
+        measures: [{
+          ActiveTimeSignature: { numerator: 4, denominator: 4 },
+          ActiveKeySignature: { keyTypeOriginal: 2, Mode: 0 }, // D大调
+          tempoInBPM: 120,
+          lastRepetitionInstructions: [],
+        }],
+      });
+      const score = parser.parse(osmd);
+      
+      expect(score.measures[0].keySignature.key).toBe('D');
+      expect(score.measures[0].keySignature.mode).toBe('major');
+    });
+  });
+
+  // ==================== 音符解析测试 ====================
+
+  describe('音符解析', () => {
+    it('应该正确解析音符音高 C D E F G A B', () => {
+      const notes = [
+        createMockNote({ step: 'C', octave: 4 }),
+        createMockNote({ step: 'D', octave: 4 }),
+        createMockNote({ step: 'E', octave: 4 }),
+        createMockNote({ step: 'F', octave: 4 }),
+        createMockNote({ step: 'G', octave: 4 }),
+        createMockNote({ step: 'A', octave: 4 }),
+        createMockNote({ step: 'B', octave: 4 }),
+      ];
+      
+      const osmd = createMockOSMDWithNotes(notes);
+      const score = parser.parse(osmd);
+      
+      const parsedNotes = score.measures[0].voices[0];
+      expect(parsedNotes.length).toBe(7);
+      expect(parsedNotes.map(n => n.pitch)).toEqual([1, 2, 3, 4, 5, 6, 7]);
+    });
+
+    it('应该正确识别休止符', () => {
+      const notes = [
+        createMockNote({ isRest: true, duration: 1.0 }),
+      ];
+      
+      const osmd = createMockOSMDWithNotes(notes);
+      const score = parser.parse(osmd);
+      
+      const note = score.measures[0].voices[0][0];
+      expect(note.isRest).toBe(true);
+      expect(note.pitch).toBe(0);
+    });
+
+    it('应该正确解析附点音符', () => {
+      const notes = [
+        createMockNote({ step: 'C', octave: 4, realValue: 1.5, dots: 1 }),
+      ];
+      
+      const osmd = createMockOSMDWithNotes(notes);
+      const score = parser.parse(osmd);
+      
+      const note = score.measures[0].voices[0][0];
+      expect(note.dots).toBe(1);
+      expect(note.duration).toBe(1.5);
+    });
+
+    it('应该正确解析升降号', () => {
+      const notes = [
+        createMockNote({ step: 'F', octave: 4, alter: 1 }), // F#
+        createMockNote({ step: 'B', octave: 4, alter: -1 }), // Bb
+      ];
+      
+      const osmd = createMockOSMDWithNotes(notes);
+      const score = parser.parse(osmd);
+      
+      expect(score.measures[0].voices[0][0].accidental).toBe('sharp');
+      expect(score.measures[0].voices[0][1].accidental).toBe('flat');
+    });
+
+    it('应该正确解析不同时值的音符', () => {
+      const notes = [
+        createMockNote({ step: 'C', octave: 4, realValue: 4.0 }),  // 全音符
+        createMockNote({ step: 'C', octave: 4, realValue: 2.0 }),  // 二分音符
+        createMockNote({ step: 'C', octave: 4, realValue: 1.0 }),  // 四分音符
+        createMockNote({ step: 'C', octave: 4, realValue: 0.5 }),  // 八分音符
+        createMockNote({ step: 'C', octave: 4, realValue: 0.25 }), // 十六分音符
+      ];
+      
+      const osmd = createMockOSMDWithNotes(notes);
+      const score = parser.parse(osmd);
+      
+      const durations = score.measures[0].voices[0].map(n => n.duration);
+      expect(durations).toEqual([4.0, 2.0, 1.0, 0.5, 0.25]);
+    });
+
+    it('应该保留OSMD数据引用', () => {
+      const originalNote = createMockNote({ step: 'C', octave: 4 });
+      const osmd = createMockOSMDWithNotes([originalNote]);
+      const score = parser.parse(osmd);
+      
+      const note = score.measures[0].voices[0][0];
+      expect(note.osmdCompatible).toBeDefined();
+      expect(note.osmdCompatible.noteElement).toBe(originalNote);
+    });
+  });
+
+  // ==================== 八度解析测试 ====================
+
+  describe('八度解析', () => {
+    it('应该正确解析高音(八度5)', () => {
+      const notes = [createMockNote({ step: 'C', octave: 5 })];
+      const osmd = createMockOSMDWithNotes(notes);
+      const score = parser.parse(osmd);
+      
+      expect(score.measures[0].voices[0][0].octave).toBe(1);
+    });
+
+    it('应该正确解析低音(八度3)', () => {
+      const notes = [createMockNote({ step: 'C', octave: 3 })];
+      const osmd = createMockOSMDWithNotes(notes);
+      const score = parser.parse(osmd);
+      
+      expect(score.measures[0].voices[0][0].octave).toBe(-1);
+    });
+
+    it('应该正确解析中音(八度4)', () => {
+      const notes = [createMockNote({ step: 'C', octave: 4 })];
+      const osmd = createMockOSMDWithNotes(notes);
+      const score = parser.parse(osmd);
+      
+      expect(score.measures[0].voices[0][0].octave).toBe(0);
+    });
+
+    it('应该正确解析超高音(八度6)', () => {
+      const notes = [createMockNote({ step: 'C', octave: 6 })];
+      const osmd = createMockOSMDWithNotes(notes);
+      const score = parser.parse(osmd);
+      
+      expect(score.measures[0].voices[0][0].octave).toBe(2);
+    });
+  });
+
+  // ==================== 统计信息测试 ====================
+
+  describe('统计信息', () => {
+    it('应该返回正确的解析统计', () => {
+      const notes = [
+        createMockNote({ step: 'C', octave: 4 }),
+        createMockNote({ isRest: true }),
+        createMockNote({ step: 'E', octave: 4 }),
+      ];
+      
+      const osmd = createMockOSMDWithNotes(notes);
+      parser.parse(osmd);
+      
+      const stats = parser.getStats();
+      expect(stats.noteCount).toBe(3);
+      expect(stats.restCount).toBe(1);
+      expect(stats.measureCount).toBe(1);
+    });
+  });
+});
+
+// ==================== 工具函数测试 ====================
+
+describe('工具函数', () => {
+  describe('stepToJianpu', () => {
+    it('应该正确转换音名', () => {
+      expect(stepToJianpu('C')).toBe(1);
+      expect(stepToJianpu('D')).toBe(2);
+      expect(stepToJianpu('E')).toBe(3);
+      expect(stepToJianpu('F')).toBe(4);
+      expect(stepToJianpu('G')).toBe(5);
+      expect(stepToJianpu('A')).toBe(6);
+      expect(stepToJianpu('B')).toBe(7);
+    });
+
+    it('应该支持小写输入', () => {
+      expect(stepToJianpu('c')).toBe(1);
+      expect(stepToJianpu('g')).toBe(5);
+    });
+
+    it('无效音名应该抛出错误', () => {
+      expect(() => stepToJianpu('H')).toThrow('无效的音名');
+      expect(() => stepToJianpu('X')).toThrow('无效的音名');
+    });
+  });
+
+  describe('octaveToOffset', () => {
+    it('应该正确计算八度偏移', () => {
+      expect(octaveToOffset(4)).toBe(0);
+      expect(octaveToOffset(5)).toBe(1);
+      expect(octaveToOffset(3)).toBe(-1);
+      expect(octaveToOffset(6)).toBe(2);
+      expect(octaveToOffset(2)).toBe(-2);
+    });
+  });
+
+  describe('alterToAccidental', () => {
+    it('应该正确转换升降号', () => {
+      expect(alterToAccidental(1)).toBe('sharp');
+      expect(alterToAccidental(2)).toBe('sharp');
+      expect(alterToAccidental(-1)).toBe('flat');
+      expect(alterToAccidental(-2)).toBe('flat');
+      expect(alterToAccidental(0)).toBe('natural');
+      expect(alterToAccidental(null)).toBe(null);
+    });
+  });
+
+  describe('noteTypeToRealValue', () => {
+    it('应该正确转换音符类型', () => {
+      expect(noteTypeToRealValue('whole')).toBe(4.0);
+      expect(noteTypeToRealValue('half')).toBe(2.0);
+      expect(noteTypeToRealValue('quarter')).toBe(1.0);
+      expect(noteTypeToRealValue('eighth')).toBe(0.5);
+      expect(noteTypeToRealValue('16th')).toBe(0.25);
+    });
+
+    it('应该支持大小写', () => {
+      expect(noteTypeToRealValue('QUARTER')).toBe(1.0);
+      expect(noteTypeToRealValue('Quarter')).toBe(1.0);
+    });
+  });
+});

+ 554 - 0
src/jianpu-renderer/__tests__/SystemLayoutEngine.test.ts

@@ -0,0 +1,554 @@
+/**
+ * 行布局引擎测试
+ * 
+ * @description 测试SystemLayoutEngine的自动换行和Y坐标计算功能
+ */
+
+import { describe, it, expect, beforeEach } from 'vitest';
+import { 
+  SystemLayoutEngine, 
+  createSystemLayoutEngine,
+  calculateOptimalSystemAllocation,
+  validateSystemLayout,
+  getSystemInfoForMeasure,
+  SystemLayoutConfig,
+} from '../core/layout/SystemLayoutEngine';
+import { JianpuMeasure, createDefaultMeasure } from '../models/JianpuMeasure';
+import { JianpuNote } from '../models/JianpuNote';
+
+// ==================== 测试辅助函数 ====================
+
+/**
+ * 创建测试用小节
+ */
+function createTestMeasure(
+  index: number,
+  width: number,
+  beats: number = 4,
+  beatType: number = 4,
+  voiceCount: number = 1
+): JianpuMeasure {
+  const measure = createDefaultMeasure(index + 1, { beats, beatType });
+  measure.index = index;
+  measure.width = width;
+  measure.x = 0;
+  measure.y = 0;
+  
+  // 创建声部
+  measure.voices = [];
+  for (let v = 0; v < voiceCount; v++) {
+    measure.voices.push([]);
+  }
+  measure.notes = measure.voices;
+  
+  return measure;
+}
+
+/**
+ * 创建带有音符的测试小节
+ */
+function createMeasureWithNotes(
+  index: number,
+  width: number,
+  noteCount: number = 4
+): JianpuMeasure {
+  const measure = createTestMeasure(index, width);
+  
+  // 添加音符
+  const noteSpacing = width / (noteCount + 1);
+  for (let i = 0; i < noteCount; i++) {
+    const note: JianpuNote = {
+      id: `note-${index}-${i}`,
+      pitch: ((i % 7) + 1) as 1 | 2 | 3 | 4 | 5 | 6 | 7,
+      octave: 0,
+      duration: 1,
+      realValue: 1,
+      dots: 0,
+      timestamp: i,
+      x: noteSpacing * (i + 1),
+      y: 0,
+      width: 12,
+      height: 24,
+      isRest: false,
+      measureIndex: index,
+      voiceIndex: 0,
+    };
+    measure.voices[0].push(note);
+  }
+  
+  return measure;
+}
+
+/**
+ * 创建多个等宽小节
+ */
+function createEqualWidthMeasures(count: number, width: number): JianpuMeasure[] {
+  return Array.from({ length: count }, (_, i) => createTestMeasure(i, width));
+}
+
+/**
+ * 创建不同宽度的小节
+ */
+function createVariableWidthMeasures(widths: number[]): JianpuMeasure[] {
+  return widths.map((width, i) => createTestMeasure(i, width));
+}
+
+// ==================== 测试用例 ====================
+
+describe('SystemLayoutEngine', () => {
+  let engine: SystemLayoutEngine;
+
+  beforeEach(() => {
+    engine = new SystemLayoutEngine({
+      systemWidth: 800,
+      systemHeight: 100,
+      systemSpacing: 50,
+      marginLeft: 20,
+      marginTop: 30,
+    });
+  });
+
+  // ==================== 基础功能测试 ====================
+
+  describe('基础功能', () => {
+    it('应该能创建引擎实例', () => {
+      expect(engine).toBeInstanceOf(SystemLayoutEngine);
+    });
+
+    it('应该能获取配置', () => {
+      const config = engine.getConfig();
+      expect(config.systemWidth).toBe(800);
+      expect(config.systemHeight).toBe(100);
+      expect(config.systemSpacing).toBe(50);
+    });
+
+    it('应该能更新配置', () => {
+      engine.updateConfig({ systemWidth: 1000 });
+      expect(engine.getConfig().systemWidth).toBe(1000);
+    });
+
+    it('空小节数组应该返回空行数组', () => {
+      const result = engine.layoutSystems([]);
+      expect(result.systems).toHaveLength(0);
+      expect(result.stats.systemCount).toBe(0);
+    });
+  });
+
+  // ==================== 单行布局测试 ====================
+
+  describe('单行布局', () => {
+    it('少量小节应该放在一行', () => {
+      // 3个100px宽的小节,总宽度300px,小于可用宽度760px
+      const measures = createEqualWidthMeasures(3, 100);
+      const result = engine.layoutSystems(measures);
+      
+      expect(result.systems).toHaveLength(1);
+      expect(result.systems[0].measures).toHaveLength(3);
+    });
+
+    it('单个小节应该放在一行', () => {
+      const measures = createEqualWidthMeasures(1, 100);
+      const result = engine.layoutSystems(measures);
+      
+      expect(result.systems).toHaveLength(1);
+      expect(result.systems[0].measures).toHaveLength(1);
+    });
+
+    it('刚好填满行宽的小节应该在一行', () => {
+      // 可用宽度 = 800 - 20*2 = 760px
+      // 4个190px的小节刚好填满
+      const measures = createEqualWidthMeasures(4, 190);
+      const result = engine.layoutSystems(measures);
+      
+      expect(result.systems).toHaveLength(1);
+      expect(result.systems[0].measures).toHaveLength(4);
+    });
+  });
+
+  // ==================== 自动换行测试 ====================
+
+  describe('自动换行', () => {
+    it('超过行宽的小节应该自动换行', () => {
+      // 可用宽度 = 760px
+      // 10个100px的小节,总宽度1000px,需要2行
+      const measures = createEqualWidthMeasures(10, 100);
+      const result = engine.layoutSystems(measures);
+      
+      expect(result.systems.length).toBeGreaterThan(1);
+      // 第一行应该有7个小节(700px < 760px)
+      expect(result.systems[0].measures.length).toBe(7);
+      // 第二行应该有3个小节
+      expect(result.systems[1].measures.length).toBe(3);
+    });
+
+    it('大小节应该正确换行', () => {
+      // 每个小节400px,可用宽度760px,每行只能放1个
+      const measures = createEqualWidthMeasures(3, 400);
+      const result = engine.layoutSystems(measures);
+      
+      expect(result.systems).toHaveLength(3);
+      result.systems.forEach(system => {
+        expect(system.measures).toHaveLength(1);
+      });
+    });
+
+    it('不等宽小节应该正确换行', () => {
+      // 可用宽度760px
+      // 宽度: [200, 200, 200, 200, 200] = 第一行3个(600) + 第二行2个(400)
+      const measures = createVariableWidthMeasures([200, 200, 200, 200, 200]);
+      const result = engine.layoutSystems(measures);
+      
+      expect(result.systems).toHaveLength(2);
+      expect(result.systems[0].measures).toHaveLength(3); // 600px
+      expect(result.systems[1].measures).toHaveLength(2); // 400px
+    });
+
+    it('极端情况:单个小节超过行宽', () => {
+      // 单个小节1000px,超过可用宽度760px,但仍需放在一行
+      const measures = createEqualWidthMeasures(2, 1000);
+      const result = engine.layoutSystems(measures);
+      
+      // 每行一个,即使超宽
+      expect(result.systems).toHaveLength(2);
+    });
+  });
+
+  // ==================== Y坐标计算测试 ====================
+
+  describe('Y坐标计算', () => {
+    it('第一行Y坐标应该等于marginTop', () => {
+      const measures = createEqualWidthMeasures(3, 100);
+      const result = engine.layoutSystems(measures);
+      
+      expect(result.systems[0].y).toBe(30); // marginTop
+    });
+
+    it('多行的Y坐标应该递增', () => {
+      const measures = createEqualWidthMeasures(20, 100);
+      const result = engine.layoutSystems(measures);
+      
+      for (let i = 1; i < result.systems.length; i++) {
+        expect(result.systems[i].y).toBeGreaterThan(result.systems[i - 1].y);
+      }
+    });
+
+    it('行间距应该正确', () => {
+      const measures = createEqualWidthMeasures(20, 100);
+      const result = engine.layoutSystems(measures);
+      
+      // 检查行间距 = systemHeight + systemSpacing = 100 + 50 = 150
+      for (let i = 1; i < result.systems.length; i++) {
+        const gap = result.systems[i].y - result.systems[i - 1].y;
+        expect(gap).toBe(150); // 100(height) + 50(spacing)
+      }
+    });
+
+    it('小节Y坐标应该等于所在行的Y坐标', () => {
+      const measures = createEqualWidthMeasures(10, 100);
+      const result = engine.layoutSystems(measures);
+      
+      for (const system of result.systems) {
+        for (const measure of system.measures) {
+          expect(measure.y).toBe(system.y);
+        }
+      }
+    });
+  });
+
+  // ==================== X坐标更新测试 ====================
+
+  describe('X坐标更新', () => {
+    it('每行第一个小节X坐标应该等于marginLeft', () => {
+      const measures = createEqualWidthMeasures(10, 100);
+      const result = engine.layoutSystems(measures);
+      
+      for (const system of result.systems) {
+        expect(system.measures[0].x).toBe(20); // marginLeft
+      }
+    });
+
+    it('小节X坐标应该连续', () => {
+      const measures = createEqualWidthMeasures(10, 100);
+      const result = engine.layoutSystems(measures);
+      
+      for (const system of result.systems) {
+        let expectedX = system.x;
+        for (const measure of system.measures) {
+          expect(measure.x).toBeCloseTo(expectedX, 1);
+          expectedX += measure.width;
+        }
+      }
+    });
+
+    it('音符X坐标应该随小节更新', () => {
+      const measures = [
+        createMeasureWithNotes(0, 100, 4),
+        createMeasureWithNotes(1, 100, 4),
+      ];
+      
+      // 记录原始音符X坐标
+      const originalNoteX = measures[0].voices[0][0].x;
+      
+      const result = engine.layoutSystems(measures);
+      
+      // 音符X坐标应该更新到正确位置
+      const newNoteX = result.systems[0].measures[0].voices[0][0].x;
+      expect(newNoteX).not.toBe(originalNoteX);
+      expect(newNoteX).toBeGreaterThan(0);
+    });
+  });
+
+  // ==================== 多声部测试 ====================
+
+  describe('多声部处理', () => {
+    it('多声部小节应该增加行高', () => {
+      // 单声部
+      const singleVoiceMeasures = createEqualWidthMeasures(3, 100);
+      const singleResult = engine.layoutSystems(singleVoiceMeasures);
+      
+      // 双声部
+      const dualVoiceMeasures = [
+        createTestMeasure(0, 100, 4, 4, 2),
+        createTestMeasure(1, 100, 4, 4, 2),
+        createTestMeasure(2, 100, 4, 4, 2),
+      ];
+      const dualResult = engine.layoutSystems(dualVoiceMeasures);
+      
+      // 双声部行高应该更大
+      expect(dualResult.systems[0].height).toBeGreaterThan(singleResult.systems[0].height);
+    });
+
+    it('混合声部数量应该使用最大声部数计算行高', () => {
+      const measures = [
+        createTestMeasure(0, 100, 4, 4, 1), // 1声部
+        createTestMeasure(1, 100, 4, 4, 3), // 3声部
+        createTestMeasure(2, 100, 4, 4, 2), // 2声部
+      ];
+      
+      const result = engine.layoutSystems(measures);
+      
+      // 行高应该基于3声部计算
+      const config = engine.getConfig();
+      const expectedHeight = config.systemHeight + (3 - 1) * config.voiceSpacing;
+      expect(result.systems[0].height).toBe(expectedHeight);
+    });
+  });
+
+  // ==================== 拉伸测试 ====================
+
+  describe('行拉伸', () => {
+    it('非最后一行应该拉伸填满行宽', () => {
+      // 15个100px小节,分成3行
+      const measures = createEqualWidthMeasures(15, 100);
+      const result = engine.layoutSystems(measures);
+      
+      const config = engine.getConfig();
+      const expectedWidth = config.systemWidth - config.marginLeft * 2;
+      
+      // 前几行应该被拉伸到行宽
+      for (let i = 0; i < result.systems.length - 1; i++) {
+        const totalMeasureWidth = result.systems[i].measures.reduce(
+          (sum, m) => sum + m.width, 0
+        );
+        expect(totalMeasureWidth).toBeCloseTo(expectedWidth, 0);
+      }
+    });
+
+    it('最后一行不应该拉伸(默认配置)', () => {
+      const measures = createEqualWidthMeasures(15, 100);
+      const result = engine.layoutSystems(measures);
+      
+      const lastSystem = result.systems[result.systems.length - 1];
+      const originalWidth = 100 * lastSystem.measures.length;
+      const actualWidth = lastSystem.measures.reduce((sum, m) => sum + m.width, 0);
+      
+      // 最后一行保持原始宽度
+      expect(actualWidth).toBeCloseTo(originalWidth, 0);
+    });
+
+    it('配置stretchLastSystem时最后一行也应该拉伸', () => {
+      const stretchEngine = new SystemLayoutEngine({
+        systemWidth: 800,
+        marginLeft: 20,
+        stretchLastSystem: true,
+      });
+      
+      const measures = createEqualWidthMeasures(10, 100);
+      const result = stretchEngine.layoutSystems(measures);
+      
+      const config = stretchEngine.getConfig();
+      const expectedWidth = config.systemWidth - config.marginLeft * 2;
+      
+      // 所有行都应该被拉伸
+      for (const system of result.systems) {
+        const totalMeasureWidth = system.measures.reduce((sum, m) => sum + m.width, 0);
+        expect(totalMeasureWidth).toBeCloseTo(expectedWidth, 0);
+      }
+    });
+  });
+
+  // ==================== 统计信息测试 ====================
+
+  describe('统计信息', () => {
+    it('应该正确统计行数', () => {
+      const measures = createEqualWidthMeasures(15, 100);
+      const result = engine.layoutSystems(measures);
+      
+      expect(result.stats.systemCount).toBe(result.systems.length);
+    });
+
+    it('应该正确统计小节数', () => {
+      const measures = createEqualWidthMeasures(15, 100);
+      const result = engine.layoutSystems(measures);
+      
+      expect(result.stats.measureCount).toBe(15);
+    });
+
+    it('应该正确计算平均每行小节数', () => {
+      const measures = createEqualWidthMeasures(15, 100);
+      const result = engine.layoutSystems(measures);
+      
+      expect(result.stats.avgMeasuresPerSystem).toBe(15 / result.stats.systemCount);
+    });
+
+    it('应该正确计算总高度', () => {
+      const measures = createEqualWidthMeasures(20, 100);
+      const result = engine.layoutSystems(measures);
+      
+      const lastSystem = result.systems[result.systems.length - 1];
+      expect(result.stats.totalHeight).toBe(lastSystem.y + lastSystem.height);
+    });
+
+    it('应该记录布局耗时', () => {
+      const measures = createEqualWidthMeasures(100, 100);
+      const result = engine.layoutSystems(measures);
+      
+      expect(result.stats.layoutTime).toBeGreaterThanOrEqual(0);
+    });
+  });
+
+  // ==================== 辅助方法测试 ====================
+
+  describe('辅助方法', () => {
+    it('estimateSystemCount应该估算行数', () => {
+      const widths = Array(10).fill(100);
+      const count = engine.estimateSystemCount(widths);
+      
+      expect(count).toBeGreaterThan(0);
+    });
+
+    it('findSystemForMeasure应该找到小节所在行', () => {
+      const measures = createEqualWidthMeasures(15, 100);
+      const result = engine.layoutSystems(measures);
+      
+      // 查找第10个小节(索引9)
+      const systemIndex = engine.findSystemForMeasure(result.systems, 9);
+      
+      expect(systemIndex).toBeGreaterThanOrEqual(0);
+      expect(result.systems[systemIndex].measures.some(m => m.index === 9)).toBe(true);
+    });
+
+    it('findSystemForMeasure对不存在的小节应该返回-1', () => {
+      const measures = createEqualWidthMeasures(5, 100);
+      const result = engine.layoutSystems(measures);
+      
+      const systemIndex = engine.findSystemForMeasure(result.systems, 100);
+      expect(systemIndex).toBe(-1);
+    });
+
+    it('getMeasureRange应该返回正确的小节范围', () => {
+      const measures = createEqualWidthMeasures(15, 100);
+      const result = engine.layoutSystems(measures);
+      
+      const range = engine.getMeasureRange(result.systems, 0);
+      
+      expect(range).not.toBeNull();
+      expect(range![0]).toBe(0); // 第一个小节索引
+      expect(range![1]).toBe(result.systems[0].measures.length - 1); // 最后一个小节索引
+    });
+  });
+});
+
+// ==================== 工厂函数测试 ====================
+
+describe('createSystemLayoutEngine', () => {
+  it('应该创建默认配置的引擎', () => {
+    const engine = createSystemLayoutEngine();
+    expect(engine).toBeInstanceOf(SystemLayoutEngine);
+  });
+
+  it('应该创建自定义配置的引擎', () => {
+    const engine = createSystemLayoutEngine({
+      systemWidth: 1200,
+      systemSpacing: 80,
+    });
+    
+    const config = engine.getConfig();
+    expect(config.systemWidth).toBe(1200);
+    expect(config.systemSpacing).toBe(80);
+  });
+});
+
+// ==================== 工具函数测试 ====================
+
+describe('calculateOptimalSystemAllocation', () => {
+  it('应该计算最优分配', () => {
+    const widths = [100, 100, 100, 100, 100, 100, 100, 100, 100, 100];
+    const allocation = calculateOptimalSystemAllocation(widths, 800, 20);
+    
+    expect(allocation.length).toBeGreaterThan(0);
+    expect(allocation.reduce((a, b) => a + b, 0)).toBe(10);
+  });
+
+  it('空数组应该返回空分配', () => {
+    const allocation = calculateOptimalSystemAllocation([], 800, 20);
+    expect(allocation).toHaveLength(0);
+  });
+
+  it('单个小节应该返回[1]', () => {
+    const allocation = calculateOptimalSystemAllocation([100], 800, 20);
+    expect(allocation).toEqual([1]);
+  });
+});
+
+describe('validateSystemLayout', () => {
+  it('有效布局应该返回valid=true', () => {
+    const engine = new SystemLayoutEngine({
+      systemWidth: 800,
+      marginLeft: 20,
+    });
+    
+    const measures = createEqualWidthMeasures(10, 100);
+    const result = engine.layoutSystems(measures);
+    
+    const validation = validateSystemLayout(result.systems, 800);
+    expect(validation.valid).toBe(true);
+    expect(validation.errors).toHaveLength(0);
+  });
+});
+
+describe('getSystemInfoForMeasure', () => {
+  it('应该返回正确的行信息', () => {
+    const engine = new SystemLayoutEngine({
+      systemWidth: 800,
+      marginLeft: 20,
+    });
+    
+    const measures = createEqualWidthMeasures(15, 100);
+    const result = engine.layoutSystems(measures);
+    
+    const info = getSystemInfoForMeasure(result.systems, 5);
+    
+    expect(info).not.toBeNull();
+    expect(info!.system.measures.some(m => m.index === 5)).toBe(true);
+  });
+
+  it('不存在的小节应该返回null', () => {
+    const engine = new SystemLayoutEngine();
+    const measures = createEqualWidthMeasures(5, 100);
+    const result = engine.layoutSystems(measures);
+    
+    const info = getSystemInfoForMeasure(result.systems, 100);
+    expect(info).toBeNull();
+  });
+});

+ 450 - 0
src/jianpu-renderer/__tests__/TimeCalculator.test.ts

@@ -0,0 +1,450 @@
+/**
+ * TimeCalculator 单元测试
+ * 
+ * 测试覆盖:
+ * - 基本时间计算
+ * - 不同BPM速度
+ * - 变速处理
+ * - 弱起小节
+ * - 节拍器时间
+ * - 精度验证
+ */
+
+import { describe, it, expect, beforeEach, vi } from 'vitest';
+import { 
+  TimeCalculator, 
+  createTimeCalculator,
+  getFixTime,
+  realValueToSeconds,
+  secondsToRealValue,
+  formatTime,
+  convertBeatUnit,
+  retain,
+} from '../core/parser/TimeCalculator';
+import { JianpuScore } from '../models/JianpuScore';
+import { JianpuMeasure, createDefaultMeasure } from '../models/JianpuMeasure';
+import { JianpuNote, createDefaultNote } from '../models/JianpuNote';
+
+// ==================== 辅助函数 ====================
+
+/**
+ * 创建测试用的JianpuScore
+ */
+function createTestScore(config: {
+  tempo?: number;
+  measures?: JianpuMeasure[];
+} = {}): JianpuScore {
+  return {
+    title: 'Test Score',
+    systems: [],
+    measures: config.measures ?? [],
+    tempo: config.tempo ?? 120,
+    initialTimeSignature: { beats: 4, beatType: 4 },
+    initialKeySignature: { key: 'C', mode: 'major' },
+  };
+}
+
+/**
+ * 创建带音符的小节
+ */
+function createMeasureWithNotes(
+  measureNumber: number,
+  notes: Array<{ pitch: number; duration: number; timestamp?: number }>
+): JianpuMeasure {
+  const measure = createDefaultMeasure(measureNumber);
+  measure.voices[0] = notes.map((n, i) => {
+    const note = createDefaultNote({
+      pitch: n.pitch,
+      duration: n.duration,
+      timestamp: n.timestamp ?? 0,
+      measureIndex: measureNumber - 1,
+    });
+    return note;
+  });
+  return measure;
+}
+
+// ==================== 测试用例 ====================
+
+describe('TimeCalculator', () => {
+  let calculator: TimeCalculator;
+
+  beforeEach(() => {
+    calculator = new TimeCalculator();
+    vi.spyOn(console, 'log').mockImplementation(() => {});
+    vi.spyOn(console, 'warn').mockImplementation(() => {});
+  });
+
+  // ==================== 基本功能测试 ====================
+
+  describe('基本功能', () => {
+    it('应该能创建计算器实例', () => {
+      expect(calculator).toBeDefined();
+      expect(calculator).toBeInstanceOf(TimeCalculator);
+    });
+
+    it('应该能使用工厂函数创建实例', () => {
+      const calc = createTimeCalculator();
+      expect(calc).toBeInstanceOf(TimeCalculator);
+    });
+
+    it('空小节数组应该返回空结果', () => {
+      const score = createTestScore({ measures: [] });
+      const result = calculator.calculateTimes(score);
+      expect(result.totalDuration).toBe(0);
+    });
+  });
+
+  // ==================== BPM速度测试 ====================
+
+  describe('BPM速度计算', () => {
+    it('120 BPM时四分音符应该是0.5秒', () => {
+      const measure = createMeasureWithNotes(1, [
+        { pitch: 1, duration: 1.0, timestamp: 0 },
+      ]);
+      const score = createTestScore({ tempo: 120, measures: [measure] });
+      
+      calculator.calculateTimes(score);
+      
+      const note = score.measures[0].voices[0][0];
+      const noteDuration = note.endTime - note.startTime;
+      expect(noteDuration).toBeCloseTo(0.5, 3);
+    });
+
+    it('60 BPM时四分音符应该是1秒', () => {
+      const measure = createMeasureWithNotes(1, [
+        { pitch: 1, duration: 1.0, timestamp: 0 },
+      ]);
+      const score = createTestScore({ tempo: 60, measures: [measure] });
+      
+      calculator.calculateTimes(score);
+      
+      const note = score.measures[0].voices[0][0];
+      const noteDuration = note.endTime - note.startTime;
+      expect(noteDuration).toBeCloseTo(1.0, 3);
+    });
+
+    it('240 BPM时四分音符应该是0.25秒', () => {
+      const measure = createMeasureWithNotes(1, [
+        { pitch: 1, duration: 1.0, timestamp: 0 },
+      ]);
+      const score = createTestScore({ tempo: 240, measures: [measure] });
+      
+      calculator.calculateTimes(score);
+      
+      const note = score.measures[0].voices[0][0];
+      const noteDuration = note.endTime - note.startTime;
+      expect(noteDuration).toBeCloseTo(0.25, 3);
+    });
+  });
+
+  // ==================== 连续音符测试 ====================
+
+  describe('连续音符时间计算', () => {
+    it('应该正确计算连续音符的时间', () => {
+      // 4个四分音符,BPM=120
+      const measure = createMeasureWithNotes(1, [
+        { pitch: 1, duration: 1.0, timestamp: 0 },
+        { pitch: 2, duration: 1.0, timestamp: 1 },
+        { pitch: 3, duration: 1.0, timestamp: 2 },
+        { pitch: 4, duration: 1.0, timestamp: 3 },
+      ]);
+      const score = createTestScore({ tempo: 120, measures: [measure] });
+      
+      calculator.calculateTimes(score);
+      
+      const notes = score.measures[0].voices[0];
+      
+      // 每个音符0.5秒
+      expect(notes[0].startTime).toBeCloseTo(0, 3);
+      expect(notes[0].endTime).toBeCloseTo(0.5, 3);
+      
+      expect(notes[1].startTime).toBeCloseTo(0.5, 3);
+      expect(notes[1].endTime).toBeCloseTo(1.0, 3);
+      
+      expect(notes[2].startTime).toBeCloseTo(1.0, 3);
+      expect(notes[2].endTime).toBeCloseTo(1.5, 3);
+      
+      expect(notes[3].startTime).toBeCloseTo(1.5, 3);
+      expect(notes[3].endTime).toBeCloseTo(2.0, 3);
+    });
+
+    it('应该正确计算不同时值的音符', () => {
+      // 全音符(4) + 二分音符(2) + 四分音符(1) + 八分音符(0.5)
+      const measure = createMeasureWithNotes(1, [
+        { pitch: 1, duration: 4.0, timestamp: 0 },
+      ]);
+      const measure2 = createMeasureWithNotes(2, [
+        { pitch: 2, duration: 2.0, timestamp: 0 },
+      ]);
+      const score = createTestScore({ 
+        tempo: 120, 
+        measures: [measure, measure2] 
+      });
+      
+      calculator.calculateTimes(score);
+      
+      // BPM=120, 四分音符=0.5秒
+      // 全音符 = 2秒
+      const note1 = score.measures[0].voices[0][0];
+      expect(note1.endTime - note1.startTime).toBeCloseTo(2.0, 3);
+      
+      // 二分音符 = 1秒
+      const note2 = score.measures[1].voices[0][0];
+      expect(note2.endTime - note2.startTime).toBeCloseTo(1.0, 3);
+    });
+  });
+
+  // ==================== 多小节测试 ====================
+
+  describe('多小节计算', () => {
+    it('应该正确计算多小节的累计时间', () => {
+      const measure1 = createMeasureWithNotes(1, [
+        { pitch: 1, duration: 4.0, timestamp: 0 }, // 全音符填满4/4小节
+      ]);
+      const measure2 = createMeasureWithNotes(2, [
+        { pitch: 2, duration: 4.0, timestamp: 0 },
+      ]);
+      const score = createTestScore({ 
+        tempo: 120, 
+        measures: [measure1, measure2] 
+      });
+      
+      const result = calculator.calculateTimes(score);
+      
+      // 每个4/4小节 = 4 * 0.5 = 2秒
+      // 总时长 = 4秒
+      expect(result.totalDuration).toBeCloseTo(4.0, 3);
+      
+      // 第一小节音符从0开始
+      expect(score.measures[0].voices[0][0].startTime).toBeCloseTo(0, 3);
+      
+      // 第二小节音符从2秒开始
+      expect(score.measures[1].voices[0][0].startTime).toBeCloseTo(2.0, 3);
+    });
+  });
+
+  // ==================== 节拍器测试 ====================
+
+  describe('节拍器时间计算', () => {
+    it('启用节拍器时应该添加前奏时间', () => {
+      const calc = new TimeCalculator({ enableMetronome: true, handlePickupMeasure: false });
+      // 创建完整的4/4小节(4个四分音符)
+      const measure = createMeasureWithNotes(1, [
+        { pitch: 1, duration: 1.0, timestamp: 0 },
+        { pitch: 2, duration: 1.0, timestamp: 1 },
+        { pitch: 3, duration: 1.0, timestamp: 2 },
+        { pitch: 4, duration: 1.0, timestamp: 3 },
+      ]);
+      const score = createTestScore({ tempo: 120, measures: [measure] });
+      
+      const result = calc.calculateTimes(score);
+      
+      // 默认2拍节拍器,BPM=120,每拍0.5秒,共1秒
+      expect(result.metronomeTime).toBeCloseTo(1.0, 3);
+      expect(result.fixtime).toBeCloseTo(1.0, 3);
+      
+      // 音符开始时间应该加上fixtime
+      const note = score.measures[0].voices[0][0];
+      expect(note.startTime).toBeCloseTo(1.0, 3);
+    });
+
+    it('可以配置节拍器拍数', () => {
+      const calc = new TimeCalculator({ 
+        enableMetronome: true, 
+        metronomeBeatCount: 4 
+      });
+      const measure = createMeasureWithNotes(1, [
+        { pitch: 1, duration: 1.0, timestamp: 0 },
+      ]);
+      const score = createTestScore({ tempo: 120, measures: [measure] });
+      
+      const result = calc.calculateTimes(score);
+      
+      // 4拍节拍器,BPM=120,每拍0.5秒,共2秒
+      expect(result.metronomeTime).toBeCloseTo(2.0, 3);
+    });
+  });
+
+  // ==================== 弱起小节测试 ====================
+
+  describe('弱起小节检测', () => {
+    it('应该检测弱起小节并补充时间', () => {
+      // 创建只有1拍的4/4小节(弱起)
+      const measure = createDefaultMeasure(1);
+      measure.voices[0] = [
+        createDefaultNote({ pitch: 1, duration: 1.0, timestamp: 0 }),
+      ];
+      
+      const score = createTestScore({ tempo: 120, measures: [measure] });
+      
+      const result = calculator.calculateTimes(score);
+      
+      // 4/4小节缺少3拍,补充时间 = 3 * 0.5 = 1.5秒
+      expect(result.hasPickup).toBe(true);
+      expect(result.pickupTime).toBeCloseTo(1.5, 3);
+    });
+
+    it('完整小节不应该被检测为弱起', () => {
+      // 创建完整的4/4小节
+      const measure = createMeasureWithNotes(1, [
+        { pitch: 1, duration: 4.0, timestamp: 0 },
+      ]);
+      
+      const score = createTestScore({ tempo: 120, measures: [measure] });
+      
+      const result = calculator.calculateTimes(score);
+      
+      expect(result.hasPickup).toBe(false);
+      expect(result.pickupTime).toBe(0);
+    });
+  });
+
+  // ==================== 变速测试 ====================
+
+  describe('变速处理', () => {
+    it('应该正确处理小节速度变化', () => {
+      const measure1 = createDefaultMeasure(1);
+      measure1.tempo = 120;
+      measure1.voices[0] = [
+        createDefaultNote({ pitch: 1, duration: 4.0, timestamp: 0 }),
+      ];
+      
+      const measure2 = createDefaultMeasure(2);
+      measure2.tempo = 60; // 速度减半
+      measure2.voices[0] = [
+        createDefaultNote({ pitch: 2, duration: 4.0, timestamp: 0 }),
+      ];
+      
+      const score = createTestScore({ 
+        tempo: 120, 
+        measures: [measure1, measure2] 
+      });
+      
+      calculator.calculateTimes(score);
+      
+      // 第一小节:BPM=120,4拍 = 2秒
+      const note1 = score.measures[0].voices[0][0];
+      expect(note1.endTime - note1.startTime).toBeCloseTo(2.0, 3);
+      
+      // 第二小节:BPM=60,4拍 = 4秒
+      const note2 = score.measures[1].voices[0][0];
+      expect(note2.endTime - note2.startTime).toBeCloseTo(4.0, 3);
+    });
+  });
+
+  // ==================== 精度测试 ====================
+
+  describe('时间计算精度', () => {
+    it('时间计算误差应该小于1ms', () => {
+      const measure = createMeasureWithNotes(1, [
+        { pitch: 1, duration: 1.0, timestamp: 0 },
+        { pitch: 2, duration: 1.0, timestamp: 1 },
+        { pitch: 3, duration: 1.0, timestamp: 2 },
+        { pitch: 4, duration: 1.0, timestamp: 3 },
+      ]);
+      const score = createTestScore({ tempo: 120, measures: [measure] });
+      
+      calculator.calculateTimes(score);
+      
+      const notes = score.measures[0].voices[0];
+      
+      // 检查每个音符的时间精度
+      notes.forEach((note, i) => {
+        const expectedStart = i * 0.5;
+        const expectedEnd = (i + 1) * 0.5;
+        
+        // 误差小于1ms (0.001秒)
+        expect(Math.abs(note.startTime - expectedStart)).toBeLessThan(0.001);
+        expect(Math.abs(note.endTime - expectedEnd)).toBeLessThan(0.001);
+      });
+    });
+  });
+
+  // ==================== OSMD兼容数据测试 ====================
+
+  describe('OSMD兼容数据', () => {
+    it('应该填充OSMD兼容字段', () => {
+      const measure = createMeasureWithNotes(1, [
+        { pitch: 1, duration: 1.0, timestamp: 0 },
+      ]);
+      const score = createTestScore({ tempo: 120, measures: [measure] });
+      
+      calculator.calculateTimes(score);
+      
+      const note = score.measures[0].voices[0][0];
+      const compat = note.osmdCompatible as any;
+      
+      expect(compat.time).toBeDefined();
+      expect(compat.endtime).toBeDefined();
+      expect(compat.relativeTime).toBeDefined();
+      expect(compat.duration).toBeDefined();
+    });
+  });
+});
+
+// ==================== 工具函数测试 ====================
+
+describe('工具函数', () => {
+  describe('getFixTime', () => {
+    it('应该正确计算节拍器时间', () => {
+      expect(getFixTime(120, 2)).toBeCloseTo(1.0, 3);
+      expect(getFixTime(60, 2)).toBeCloseTo(2.0, 3);
+      expect(getFixTime(120, 4)).toBeCloseTo(2.0, 3);
+    });
+
+    it('BPM为0时应该返回0', () => {
+      expect(getFixTime(0)).toBe(0);
+    });
+  });
+
+  describe('realValueToSeconds', () => {
+    it('应该正确转换时值到秒', () => {
+      // 四分音符,BPM=120 -> 0.5秒
+      expect(realValueToSeconds(1.0, 120)).toBeCloseTo(0.5, 3);
+      
+      // 二分音符,BPM=120 -> 1.0秒
+      expect(realValueToSeconds(2.0, 120)).toBeCloseTo(1.0, 3);
+      
+      // 八分音符,BPM=120 -> 0.25秒
+      expect(realValueToSeconds(0.5, 120)).toBeCloseTo(0.25, 3);
+    });
+  });
+
+  describe('secondsToRealValue', () => {
+    it('应该正确转换秒到时值', () => {
+      // 0.5秒,BPM=120 -> 四分音符
+      expect(secondsToRealValue(0.5, 120)).toBeCloseTo(1.0, 3);
+      
+      // 1.0秒,BPM=120 -> 二分音符
+      expect(secondsToRealValue(1.0, 120)).toBeCloseTo(2.0, 3);
+    });
+  });
+
+  describe('formatTime', () => {
+    it('应该正确格式化时间', () => {
+      expect(formatTime(0)).toBe('00:00.000');
+      expect(formatTime(1.5)).toBe('00:01.500');
+      expect(formatTime(65.123)).toBe('01:05.123');
+      expect(formatTime(125.5)).toBe('02:05.500');
+    });
+  });
+
+  describe('convertBeatUnit', () => {
+    it('应该正确转换节拍单位', () => {
+      // 120 BPM (1/4) -> 60 BPM (1/2)
+      expect(convertBeatUnit(120, '1/4', '1/2')).toBeCloseTo(60, 3);
+      
+      // 120 BPM (1/4) -> 240 BPM (1/8)
+      expect(convertBeatUnit(120, '1/4', '1/8')).toBeCloseTo(240, 3);
+    });
+  });
+
+  describe('retain', () => {
+    it('应该正确保留精度', () => {
+      expect(retain(1.23456789, 2)).toBe(1.23);
+      expect(retain(1.23456789, 4)).toBe(1.2346);
+      expect(retain(1.23456789, 6)).toBe(1.234568);
+    });
+  });
+});

+ 109 - 0
src/jianpu-renderer/__tests__/baseline/README.md

@@ -0,0 +1,109 @@
+# 测试基准数据
+
+> **用途:** 存储旧引擎渲染的基准数据,用于新引擎对比验证
+
+---
+
+## 📁 目录结构
+
+```
+baseline/
+├── README.md           # 本文件
+├── basic/              # basic.xml 基准数据
+│   ├── screenshot.png  # 渲染截图
+│   ├── times.json      # state.times 数据
+│   └── dom.html        # DOM结构
+├── mixed-durations/    # mixed-durations.xml 基准数据
+├── multi-voice/        # multi-voice.xml 基准数据
+├── with-lyrics/        # with-lyrics.xml 基准数据
+└── complex/            # complex.xml 基准数据
+```
+
+---
+
+## 🛠️ 数据收集方法
+
+### 方法1:使用数据收集工具页面
+
+1. 启动项目开发服务器:`npm run dev`
+2. 打开数据收集页面:`/src/jianpu-renderer/__tests__/collect-baseline.html`
+3. 选择测试文件并点击"渲染"
+4. 点击"导出基准数据"下载数据包
+
+### 方法2:手动收集
+
+1. 在项目中加载测试XML文件
+2. 使用浏览器开发者工具导出数据:
+
+```javascript
+// 在控制台执行以下代码导出 state.times
+copy(JSON.stringify(state.times, null, 2));
+
+// 导出DOM结构
+copy(document.getElementById('osmdSvgPage').outerHTML);
+```
+
+3. 使用浏览器截图功能保存渲染效果
+
+---
+
+## 📊 基准数据格式
+
+### times.json 格式
+
+```json
+{
+  "metadata": {
+    "filename": "basic.xml",
+    "collectedAt": "2026-01-29T12:00:00Z",
+    "engineVersion": "OSMD 1.x + VexFlow 4.x",
+    "noteCount": 32,
+    "measureCount": 8
+  },
+  "times": [
+    {
+      "i": 0,
+      "id": "auto12345",
+      "noteId": 0,
+      "time": 0,
+      "endtime": 0.5,
+      "MeasureNumberXML": 1,
+      "frequency": 440,
+      "...": "其他字段"
+    }
+  ]
+}
+```
+
+### 关键字段清单
+
+以下字段必须包含在基准数据中:
+
+| 字段 | 类型 | 说明 |
+|------|------|------|
+| i | number | 索引 |
+| id | string | SVG元素ID |
+| time | number | 开始时间(秒) |
+| endtime | number | 结束时间(秒) |
+| MeasureNumberXML | number | 小节编号 |
+| frequency | number | 频率(Hz) |
+| halfTone | number | 半音值 |
+| svgElement.attrs.id | string | VexFlow元素ID |
+| bbox | object | 边界框 |
+
+---
+
+## ✅ 验收标准
+
+- [ ] 5个测试文件都有基准数据
+- [ ] 每个基准数据包含:截图、times.json、dom.html
+- [ ] times.json 包含所有关键字段
+- [ ] DOM结构完整可解析
+
+---
+
+## 📝 更新日志
+
+| 日期 | 说明 |
+|------|------|
+| 2026-01-29 | 创建基准数据目录结构 |

+ 21 - 0
src/jianpu-renderer/__tests__/baseline/basic/times.json

@@ -0,0 +1,21 @@
+{
+  "metadata": {
+    "filename": "basic.xml",
+    "collectedAt": "待收集",
+    "engineVersion": "OSMD + VexFlow",
+    "noteCount": 0,
+    "measureCount": 0,
+    "status": "TEMPLATE - 需要使用旧引擎实际渲染后填充数据"
+  },
+  "times": [],
+  "instructions": {
+    "description": "此文件为模板,需要使用以下步骤收集实际数据",
+    "steps": [
+      "1. 启动项目: npm run dev",
+      "2. 加载 fixtures/basic.xml 文件",
+      "3. 打开浏览器开发者工具 (F12)",
+      "4. 在控制台执行 collect-baseline.html 中的命令",
+      "5. 将导出的数据替换此文件内容"
+    ]
+  }
+}

+ 21 - 0
src/jianpu-renderer/__tests__/baseline/complex/times.json

@@ -0,0 +1,21 @@
+{
+  "metadata": {
+    "filename": "complex.xml",
+    "collectedAt": "待收集",
+    "engineVersion": "OSMD + VexFlow",
+    "noteCount": 0,
+    "measureCount": 0,
+    "status": "TEMPLATE - 需要使用旧引擎实际渲染后填充数据"
+  },
+  "times": [],
+  "instructions": {
+    "description": "此文件为模板,需要使用以下步骤收集实际数据",
+    "steps": [
+      "1. 启动项目: npm run dev",
+      "2. 加载 fixtures/complex.xml 文件",
+      "3. 打开浏览器开发者工具 (F12)",
+      "4. 在控制台执行 collect-baseline.html 中的命令",
+      "5. 将导出的数据替换此文件内容"
+    ]
+  }
+}

+ 21 - 0
src/jianpu-renderer/__tests__/baseline/mixed-durations/times.json

@@ -0,0 +1,21 @@
+{
+  "metadata": {
+    "filename": "mixed-durations.xml",
+    "collectedAt": "待收集",
+    "engineVersion": "OSMD + VexFlow",
+    "noteCount": 0,
+    "measureCount": 0,
+    "status": "TEMPLATE - 需要使用旧引擎实际渲染后填充数据"
+  },
+  "times": [],
+  "instructions": {
+    "description": "此文件为模板,需要使用以下步骤收集实际数据",
+    "steps": [
+      "1. 启动项目: npm run dev",
+      "2. 加载 fixtures/mixed-durations.xml 文件",
+      "3. 打开浏览器开发者工具 (F12)",
+      "4. 在控制台执行 collect-baseline.html 中的命令",
+      "5. 将导出的数据替换此文件内容"
+    ]
+  }
+}

+ 21 - 0
src/jianpu-renderer/__tests__/baseline/multi-voice/times.json

@@ -0,0 +1,21 @@
+{
+  "metadata": {
+    "filename": "multi-voice.xml",
+    "collectedAt": "待收集",
+    "engineVersion": "OSMD + VexFlow",
+    "noteCount": 0,
+    "measureCount": 0,
+    "status": "TEMPLATE - 需要使用旧引擎实际渲染后填充数据"
+  },
+  "times": [],
+  "instructions": {
+    "description": "此文件为模板,需要使用以下步骤收集实际数据",
+    "steps": [
+      "1. 启动项目: npm run dev",
+      "2. 加载 fixtures/multi-voice.xml 文件",
+      "3. 打开浏览器开发者工具 (F12)",
+      "4. 在控制台执行 collect-baseline.html 中的命令",
+      "5. 将导出的数据替换此文件内容"
+    ]
+  }
+}

+ 21 - 0
src/jianpu-renderer/__tests__/baseline/with-lyrics/times.json

@@ -0,0 +1,21 @@
+{
+  "metadata": {
+    "filename": "with-lyrics.xml",
+    "collectedAt": "待收集",
+    "engineVersion": "OSMD + VexFlow",
+    "noteCount": 0,
+    "measureCount": 0,
+    "status": "TEMPLATE - 需要使用旧引擎实际渲染后填充数据"
+  },
+  "times": [],
+  "instructions": {
+    "description": "此文件为模板,需要使用以下步骤收集实际数据",
+    "steps": [
+      "1. 启动项目: npm run dev",
+      "2. 加载 fixtures/with-lyrics.xml 文件",
+      "3. 打开浏览器开发者工具 (F12)",
+      "4. 在控制台执行 collect-baseline.html 中的命令",
+      "5. 将导出的数据替换此文件内容"
+    ]
+  }
+}

+ 589 - 0
src/jianpu-renderer/__tests__/collect-baseline.html

@@ -0,0 +1,589 @@
+<!DOCTYPE html>
+<html lang="zh-CN">
+<head>
+  <meta charset="UTF-8">
+  <meta name="viewport" content="width=device-width, initial-scale=1.0">
+  <title>基准数据收集工具</title>
+  <style>
+    :root {
+      --primary: #3b82f6;
+      --success: #22c55e;
+      --warning: #f59e0b;
+      --error: #ef4444;
+      --bg: #f1f5f9;
+      --card: #ffffff;
+      --border: #e2e8f0;
+      --text: #1e293b;
+      --text-secondary: #64748b;
+    }
+    
+    * { box-sizing: border-box; margin: 0; padding: 0; }
+    
+    body {
+      font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
+      background: var(--bg);
+      color: var(--text);
+      line-height: 1.6;
+      padding: 24px;
+    }
+    
+    .container {
+      max-width: 1200px;
+      margin: 0 auto;
+    }
+    
+    header {
+      text-align: center;
+      margin-bottom: 32px;
+      padding: 24px;
+      background: linear-gradient(135deg, #1e40af 0%, #7c3aed 100%);
+      border-radius: 16px;
+      color: white;
+    }
+    
+    header h1 {
+      font-size: 1.75rem;
+      margin-bottom: 8px;
+    }
+    
+    header p {
+      opacity: 0.9;
+    }
+    
+    .card {
+      background: var(--card);
+      border-radius: 12px;
+      box-shadow: 0 1px 3px rgba(0,0,0,0.1);
+      padding: 24px;
+      margin-bottom: 24px;
+    }
+    
+    .card h2 {
+      font-size: 1.1rem;
+      margin-bottom: 16px;
+      padding-bottom: 12px;
+      border-bottom: 1px solid var(--border);
+    }
+    
+    .form-group {
+      margin-bottom: 16px;
+    }
+    
+    .form-group label {
+      display: block;
+      font-weight: 500;
+      margin-bottom: 6px;
+      color: var(--text-secondary);
+    }
+    
+    select, input, textarea {
+      width: 100%;
+      padding: 10px 14px;
+      border: 1px solid var(--border);
+      border-radius: 8px;
+      font-size: 0.95rem;
+    }
+    
+    select:focus, input:focus, textarea:focus {
+      outline: none;
+      border-color: var(--primary);
+      box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
+    }
+    
+    .btn-group {
+      display: flex;
+      gap: 12px;
+      flex-wrap: wrap;
+    }
+    
+    button {
+      padding: 10px 20px;
+      border: none;
+      border-radius: 8px;
+      font-size: 0.95rem;
+      font-weight: 500;
+      cursor: pointer;
+      transition: all 0.2s;
+    }
+    
+    .btn-primary {
+      background: var(--primary);
+      color: white;
+    }
+    
+    .btn-primary:hover {
+      background: #2563eb;
+    }
+    
+    .btn-success {
+      background: var(--success);
+      color: white;
+    }
+    
+    .btn-success:hover {
+      background: #16a34a;
+    }
+    
+    .btn-secondary {
+      background: var(--card);
+      color: var(--text);
+      border: 1px solid var(--border);
+    }
+    
+    .btn-secondary:hover {
+      background: var(--bg);
+    }
+    
+    .render-area {
+      min-height: 300px;
+      border: 2px dashed var(--border);
+      border-radius: 8px;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      color: var(--text-secondary);
+      margin-bottom: 16px;
+    }
+    
+    .render-area.has-content {
+      border-style: solid;
+      display: block;
+      padding: 16px;
+      overflow: auto;
+    }
+    
+    .stats-grid {
+      display: grid;
+      grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
+      gap: 12px;
+      margin-bottom: 16px;
+    }
+    
+    .stat-item {
+      background: var(--bg);
+      padding: 12px;
+      border-radius: 8px;
+      text-align: center;
+    }
+    
+    .stat-item .label {
+      font-size: 0.8rem;
+      color: var(--text-secondary);
+    }
+    
+    .stat-item .value {
+      font-size: 1.25rem;
+      font-weight: 600;
+      color: var(--primary);
+    }
+    
+    .data-preview {
+      background: #1e293b;
+      color: #e2e8f0;
+      border-radius: 8px;
+      padding: 16px;
+      font-family: 'Monaco', 'Menlo', monospace;
+      font-size: 0.85rem;
+      max-height: 300px;
+      overflow: auto;
+    }
+    
+    .data-preview pre {
+      margin: 0;
+      white-space: pre-wrap;
+      word-break: break-all;
+    }
+    
+    .status-badge {
+      display: inline-block;
+      padding: 4px 12px;
+      border-radius: 20px;
+      font-size: 0.8rem;
+      font-weight: 500;
+    }
+    
+    .status-badge.pending {
+      background: #fef3c7;
+      color: #92400e;
+    }
+    
+    .status-badge.success {
+      background: #d1fae5;
+      color: #065f46;
+    }
+    
+    .status-badge.error {
+      background: #fee2e2;
+      color: #991b1b;
+    }
+    
+    .instructions {
+      background: #eff6ff;
+      border: 1px solid #bfdbfe;
+      border-radius: 8px;
+      padding: 16px;
+      margin-bottom: 16px;
+    }
+    
+    .instructions h3 {
+      color: #1e40af;
+      margin-bottom: 8px;
+      font-size: 0.95rem;
+    }
+    
+    .instructions ol {
+      margin-left: 20px;
+      color: #1e40af;
+    }
+    
+    .instructions li {
+      margin-bottom: 4px;
+    }
+    
+    .alert {
+      padding: 12px 16px;
+      border-radius: 8px;
+      margin-bottom: 16px;
+    }
+    
+    .alert.warning {
+      background: #fef3c7;
+      border: 1px solid #fcd34d;
+      color: #92400e;
+    }
+    
+    .alert.info {
+      background: #eff6ff;
+      border: 1px solid #93c5fd;
+      color: #1e40af;
+    }
+  </style>
+</head>
+<body>
+  <div class="container">
+    <header>
+      <h1>📊 基准数据收集工具</h1>
+      <p>使用OSMD旧引擎渲染测试文件,收集基准数据用于新引擎对比</p>
+    </header>
+    
+    <div class="alert warning">
+      <strong>⚠️ 注意:</strong> 此工具需要在项目开发环境中运行,确保已加载OSMD库。
+      如果看到渲染错误,请在主项目页面中使用控制台命令手动收集数据。
+    </div>
+    
+    <div class="card">
+      <h2>📁 选择测试文件</h2>
+      <div class="form-group">
+        <label for="file-select">测试XML文件</label>
+        <select id="file-select">
+          <option value="basic">basic.xml - 基础简谱(四分音符、休止符、高低音)</option>
+          <option value="mixed-durations">mixed-durations.xml - 混合时值(八分/十六分/附点)</option>
+          <option value="multi-voice">multi-voice.xml - 多声部(双声部、和弦)</option>
+          <option value="with-lyrics">with-lyrics.xml - 带歌词(中文歌词、多遍)</option>
+          <option value="complex">complex.xml - 复杂记号(升降号、三连音、装饰音)</option>
+        </select>
+      </div>
+      
+      <div class="btn-group">
+        <button class="btn-primary" onclick="loadAndRender()">🎨 加载并渲染</button>
+        <button class="btn-secondary" onclick="clearRender()">🗑️ 清除</button>
+      </div>
+    </div>
+    
+    <div class="card">
+      <h2>🎵 渲染结果</h2>
+      <div class="stats-grid">
+        <div class="stat-item">
+          <div class="label">状态</div>
+          <div class="value"><span class="status-badge pending" id="render-status">未渲染</span></div>
+        </div>
+        <div class="stat-item">
+          <div class="label">小节数</div>
+          <div class="value" id="stat-measures">-</div>
+        </div>
+        <div class="stat-item">
+          <div class="label">音符数</div>
+          <div class="value" id="stat-notes">-</div>
+        </div>
+        <div class="stat-item">
+          <div class="label">times长度</div>
+          <div class="value" id="stat-times">-</div>
+        </div>
+      </div>
+      
+      <div class="render-area" id="render-area">
+        选择测试文件并点击"加载并渲染"
+      </div>
+    </div>
+    
+    <div class="card">
+      <h2>💾 导出基准数据</h2>
+      
+      <div class="instructions">
+        <h3>📋 手动收集说明</h3>
+        <ol>
+          <li>在主项目页面加载测试XML文件</li>
+          <li>打开浏览器开发者工具(F12)</li>
+          <li>在控制台执行以下命令获取数据</li>
+          <li>使用下方按钮或手动保存数据</li>
+        </ol>
+      </div>
+      
+      <div class="form-group">
+        <label>控制台命令(复制到主项目页面执行)</label>
+        <div class="data-preview">
+          <pre id="console-commands">// 1. 导出 state.times 数据
+const timesData = {
+  metadata: {
+    filename: 'basic.xml',
+    collectedAt: new Date().toISOString(),
+    engineVersion: 'OSMD + VexFlow',
+    noteCount: state.times.length,
+    measureCount: state.osmd?.GraphicSheet?.MeasureList?.length || 0
+  },
+  times: state.times.map(t => ({
+    i: t.i,
+    id: t.id,
+    noteId: t.noteId,
+    time: t.time,
+    endtime: t.endtime,
+    duration: t.duration,
+    MeasureNumberXML: t.MeasureNumberXML,
+    measureListIndex: t.measureListIndex,
+    frequency: t.frequency,
+    halfTone: t.halfTone,
+    realKey: t.realKey,
+    isRestFlag: t.isRestFlag,
+    noteLength: t.noteLength,
+    measureSpeed: t.measureSpeed,
+    fixtime: t.fixtime,
+    svgElement: t.svgElement ? { attrs: { id: t.svgElement.attrs?.id } } : null,
+    bbox: t.bbox
+  }))
+};
+copy(JSON.stringify(timesData, null, 2));
+console.log('✅ times数据已复制到剪贴板');
+
+// 2. 导出 DOM 结构
+// copy(document.getElementById('osmdSvgPage')?.outerHTML || '');
+// console.log('✅ DOM结构已复制到剪贴板');</pre>
+        </div>
+      </div>
+      
+      <div class="btn-group">
+        <button class="btn-success" onclick="exportTimesData()">📥 导出 times.json</button>
+        <button class="btn-secondary" onclick="exportDOMData()">📥 导出 DOM</button>
+        <button class="btn-secondary" onclick="copyCommands()">📋 复制命令</button>
+      </div>
+    </div>
+    
+    <div class="card">
+      <h2>👀 数据预览</h2>
+      <div class="form-group">
+        <label>times 数据预览(前5条)</label>
+        <div class="data-preview">
+          <pre id="times-preview">// 渲染后显示数据预览</pre>
+        </div>
+      </div>
+    </div>
+  </div>
+  
+  <script>
+    // 获取选中的文件名
+    function getSelectedFile() {
+      return document.getElementById('file-select').value;
+    }
+    
+    // 更新控制台命令中的文件名
+    function updateCommands() {
+      const filename = getSelectedFile() + '.xml';
+      const commands = document.getElementById('console-commands');
+      commands.textContent = commands.textContent.replace(
+        /filename: '[^']+'/,
+        `filename: '${filename}'`
+      );
+    }
+    
+    document.getElementById('file-select').addEventListener('change', updateCommands);
+    
+    // 加载并渲染
+    async function loadAndRender() {
+      const filename = getSelectedFile();
+      const statusEl = document.getElementById('render-status');
+      const renderArea = document.getElementById('render-area');
+      
+      statusEl.textContent = '加载中...';
+      statusEl.className = 'status-badge pending';
+      
+      try {
+        // 加载XML文件
+        const response = await fetch(`./fixtures/${filename}.xml`);
+        if (!response.ok) throw new Error('文件加载失败');
+        const xmlContent = await response.text();
+        
+        // 解析XML获取统计信息
+        const parser = new DOMParser();
+        const xmlDoc = parser.parseFromString(xmlContent, 'text/xml');
+        const measures = xmlDoc.getElementsByTagName('measure').length;
+        const notes = xmlDoc.getElementsByTagName('note').length;
+        
+        document.getElementById('stat-measures').textContent = measures;
+        document.getElementById('stat-notes').textContent = notes;
+        
+        // 检查是否有OSMD可用
+        if (typeof OpenSheetMusicDisplay !== 'undefined' || window.state?.osmd) {
+          // 在实际环境中渲染
+          statusEl.textContent = '渲染中...';
+          // TODO: 调用OSMD渲染
+          statusEl.textContent = '已渲染';
+          statusEl.className = 'status-badge success';
+          
+          // 更新times长度
+          if (window.state?.times) {
+            document.getElementById('stat-times').textContent = state.times.length;
+            updateTimesPreview();
+          }
+        } else {
+          // 显示提示信息
+          renderArea.innerHTML = `
+            <div style="text-align: center; padding: 40px; color: #64748b;">
+              <p style="font-size: 1.5rem; margin-bottom: 16px;">⚠️</p>
+              <p><strong>OSMD未加载</strong></p>
+              <p style="margin-top: 8px; font-size: 0.9rem;">
+                请在主项目页面中加载此XML文件:<br>
+                <code style="background: #f1f5f9; padding: 4px 8px; border-radius: 4px;">
+                  fixtures/${filename}.xml
+                </code>
+              </p>
+              <p style="margin-top: 16px; font-size: 0.85rem;">
+                然后使用上方的控制台命令收集数据
+              </p>
+            </div>
+          `;
+          renderArea.classList.add('has-content');
+          
+          statusEl.textContent = '需手动收集';
+          statusEl.className = 'status-badge pending';
+          document.getElementById('stat-times').textContent = '-';
+        }
+        
+      } catch (error) {
+        statusEl.textContent = '失败';
+        statusEl.className = 'status-badge error';
+        renderArea.innerHTML = `<div style="color: #dc2626; padding: 20px;">错误: ${error.message}</div>`;
+        renderArea.classList.add('has-content');
+      }
+    }
+    
+    // 更新times预览
+    function updateTimesPreview() {
+      const preview = document.getElementById('times-preview');
+      if (window.state?.times) {
+        const sample = state.times.slice(0, 5).map(t => ({
+          i: t.i,
+          id: t.id,
+          time: t.time?.toFixed(3),
+          endtime: t.endtime?.toFixed(3),
+          MeasureNumberXML: t.MeasureNumberXML,
+          frequency: t.frequency?.toFixed(2)
+        }));
+        preview.textContent = JSON.stringify(sample, null, 2);
+      } else {
+        preview.textContent = '// state.times 不可用,请在主项目中收集';
+      }
+    }
+    
+    // 导出times数据
+    function exportTimesData() {
+      if (!window.state?.times) {
+        alert('state.times 不可用。请在主项目页面中使用控制台命令收集数据。');
+        return;
+      }
+      
+      const filename = getSelectedFile();
+      const data = {
+        metadata: {
+          filename: filename + '.xml',
+          collectedAt: new Date().toISOString(),
+          engineVersion: 'OSMD + VexFlow',
+          noteCount: state.times.length,
+          measureCount: state.osmd?.GraphicSheet?.MeasureList?.length || 0
+        },
+        times: state.times.map(t => ({
+          i: t.i,
+          id: t.id,
+          noteId: t.noteId,
+          time: t.time,
+          endtime: t.endtime,
+          duration: t.duration,
+          MeasureNumberXML: t.MeasureNumberXML,
+          measureListIndex: t.measureListIndex,
+          frequency: t.frequency,
+          halfTone: t.halfTone,
+          realKey: t.realKey,
+          isRestFlag: t.isRestFlag,
+          noteLength: t.noteLength,
+          measureSpeed: t.measureSpeed,
+          fixtime: t.fixtime,
+          svgElement: t.svgElement ? { attrs: { id: t.svgElement.attrs?.id } } : null,
+          bbox: t.bbox
+        }))
+      };
+      
+      downloadJSON(data, `${filename}-times.json`);
+    }
+    
+    // 导出DOM数据
+    function exportDOMData() {
+      const svgPage = document.getElementById('osmdSvgPage');
+      if (!svgPage) {
+        alert('osmdSvgPage 元素不存在。请在主项目页面中收集DOM数据。');
+        return;
+      }
+      
+      const filename = getSelectedFile();
+      const blob = new Blob([svgPage.outerHTML], { type: 'text/html' });
+      const url = URL.createObjectURL(blob);
+      const a = document.createElement('a');
+      a.href = url;
+      a.download = `${filename}-dom.html`;
+      a.click();
+      URL.revokeObjectURL(url);
+    }
+    
+    // 复制命令
+    function copyCommands() {
+      const commands = document.getElementById('console-commands').textContent;
+      navigator.clipboard.writeText(commands).then(() => {
+        alert('✅ 命令已复制到剪贴板!\n\n请在主项目页面的开发者工具控制台中粘贴执行。');
+      });
+    }
+    
+    // 下载JSON文件
+    function downloadJSON(data, filename) {
+      const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' });
+      const url = URL.createObjectURL(blob);
+      const a = document.createElement('a');
+      a.href = url;
+      a.download = filename;
+      a.click();
+      URL.revokeObjectURL(url);
+    }
+    
+    // 清除渲染
+    function clearRender() {
+      document.getElementById('render-area').innerHTML = '选择测试文件并点击"加载并渲染"';
+      document.getElementById('render-area').classList.remove('has-content');
+      document.getElementById('render-status').textContent = '未渲染';
+      document.getElementById('render-status').className = 'status-badge pending';
+      document.getElementById('stat-measures').textContent = '-';
+      document.getElementById('stat-notes').textContent = '-';
+      document.getElementById('stat-times').textContent = '-';
+      document.getElementById('times-preview').textContent = '// 渲染后显示数据预览';
+    }
+    
+    // 初始化
+    updateCommands();
+  </script>
+</body>
+</html>

+ 683 - 0
src/jianpu-renderer/__tests__/compare.html

@@ -0,0 +1,683 @@
+<!DOCTYPE html>
+<html lang="zh-CN">
+<head>
+  <meta charset="UTF-8">
+  <meta name="viewport" content="width=device-width, initial-scale=1.0">
+  <title>简谱渲染引擎对比测试</title>
+  <style>
+    :root {
+      --primary-color: #2563eb;
+      --success-color: #16a34a;
+      --warning-color: #d97706;
+      --error-color: #dc2626;
+      --bg-color: #f8fafc;
+      --card-bg: #ffffff;
+      --border-color: #e2e8f0;
+      --text-primary: #1e293b;
+      --text-secondary: #64748b;
+    }
+    
+    * {
+      box-sizing: border-box;
+      margin: 0;
+      padding: 0;
+    }
+    
+    body {
+      font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
+      background-color: var(--bg-color);
+      color: var(--text-primary);
+      line-height: 1.6;
+      padding: 20px;
+    }
+    
+    .container {
+      max-width: 1400px;
+      margin: 0 auto;
+    }
+    
+    header {
+      text-align: center;
+      margin-bottom: 30px;
+      padding: 20px;
+      background: linear-gradient(135deg, var(--primary-color) 0%, #7c3aed 100%);
+      border-radius: 12px;
+      color: white;
+    }
+    
+    header h1 {
+      font-size: 1.8rem;
+      font-weight: 600;
+      margin-bottom: 8px;
+    }
+    
+    header p {
+      opacity: 0.9;
+      font-size: 0.95rem;
+    }
+    
+    .controls {
+      display: flex;
+      gap: 16px;
+      margin-bottom: 24px;
+      flex-wrap: wrap;
+      align-items: center;
+    }
+    
+    .control-group {
+      display: flex;
+      align-items: center;
+      gap: 8px;
+    }
+    
+    .control-group label {
+      font-weight: 500;
+      color: var(--text-secondary);
+      font-size: 0.9rem;
+    }
+    
+    select, button {
+      padding: 10px 16px;
+      border: 1px solid var(--border-color);
+      border-radius: 8px;
+      font-size: 0.95rem;
+      background: var(--card-bg);
+      cursor: pointer;
+      transition: all 0.2s;
+    }
+    
+    select:hover, button:hover {
+      border-color: var(--primary-color);
+    }
+    
+    button {
+      background: var(--primary-color);
+      color: white;
+      border: none;
+      font-weight: 500;
+    }
+    
+    button:hover {
+      background: #1d4ed8;
+    }
+    
+    button.secondary {
+      background: var(--card-bg);
+      color: var(--text-primary);
+      border: 1px solid var(--border-color);
+    }
+    
+    button.secondary:hover {
+      background: var(--bg-color);
+    }
+    
+    .comparison-container {
+      display: grid;
+      grid-template-columns: 1fr 1fr;
+      gap: 24px;
+      margin-bottom: 24px;
+    }
+    
+    @media (max-width: 900px) {
+      .comparison-container {
+        grid-template-columns: 1fr;
+      }
+    }
+    
+    .render-panel {
+      background: var(--card-bg);
+      border-radius: 12px;
+      box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
+      overflow: hidden;
+    }
+    
+    .panel-header {
+      display: flex;
+      justify-content: space-between;
+      align-items: center;
+      padding: 16px 20px;
+      border-bottom: 1px solid var(--border-color);
+      background: var(--bg-color);
+    }
+    
+    .panel-header h2 {
+      font-size: 1.1rem;
+      font-weight: 600;
+      display: flex;
+      align-items: center;
+      gap: 8px;
+    }
+    
+    .panel-header .badge {
+      padding: 4px 10px;
+      border-radius: 20px;
+      font-size: 0.75rem;
+      font-weight: 500;
+    }
+    
+    .badge.old {
+      background: #fef3c7;
+      color: #92400e;
+    }
+    
+    .badge.new {
+      background: #d1fae5;
+      color: #065f46;
+    }
+    
+    .render-content {
+      min-height: 400px;
+      padding: 20px;
+      overflow: auto;
+    }
+    
+    .render-content.empty {
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      color: var(--text-secondary);
+      font-style: italic;
+    }
+    
+    /* 简谱渲染样式 */
+    .jianpu-score {
+      font-family: 'Noto Sans SC', 'Microsoft YaHei', sans-serif;
+    }
+    
+    .jianpu-measure {
+      display: inline-block;
+      border-right: 1px solid #333;
+      padding: 10px 15px;
+      vertical-align: top;
+    }
+    
+    .jianpu-note {
+      display: inline-block;
+      text-align: center;
+      margin: 0 5px;
+      position: relative;
+    }
+    
+    .note-number {
+      font-size: 24px;
+      font-weight: 500;
+    }
+    
+    .octave-dots {
+      font-size: 12px;
+      line-height: 1;
+    }
+    
+    .octave-dots.high {
+      position: absolute;
+      top: -8px;
+      left: 50%;
+      transform: translateX(-50%);
+    }
+    
+    .octave-dots.low {
+      position: absolute;
+      bottom: -8px;
+      left: 50%;
+      transform: translateX(-50%);
+    }
+    
+    .duration-lines {
+      height: 3px;
+      margin-top: 2px;
+    }
+    
+    .underline {
+      height: 2px;
+      background: #333;
+      margin-top: 2px;
+    }
+    
+    .extension-line {
+      display: inline-block;
+      width: 20px;
+      height: 2px;
+      background: #333;
+      margin: 0 3px;
+      vertical-align: middle;
+    }
+    
+    /* 状态面板 */
+    .status-panel {
+      background: var(--card-bg);
+      border-radius: 12px;
+      padding: 20px;
+      box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
+    }
+    
+    .status-panel h3 {
+      font-size: 1rem;
+      font-weight: 600;
+      margin-bottom: 12px;
+      color: var(--text-secondary);
+    }
+    
+    .status-grid {
+      display: grid;
+      grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
+      gap: 16px;
+    }
+    
+    .status-item {
+      padding: 12px;
+      background: var(--bg-color);
+      border-radius: 8px;
+    }
+    
+    .status-item .label {
+      font-size: 0.8rem;
+      color: var(--text-secondary);
+      margin-bottom: 4px;
+    }
+    
+    .status-item .value {
+      font-size: 1.1rem;
+      font-weight: 600;
+    }
+    
+    .status-item .value.success {
+      color: var(--success-color);
+    }
+    
+    .status-item .value.warning {
+      color: var(--warning-color);
+    }
+    
+    .status-item .value.error {
+      color: var(--error-color);
+    }
+    
+    /* 日志面板 */
+    .log-panel {
+      margin-top: 24px;
+      background: var(--card-bg);
+      border-radius: 12px;
+      box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
+      overflow: hidden;
+    }
+    
+    .log-panel header {
+      background: #1e293b;
+      padding: 12px 20px;
+      border-radius: 0;
+      margin: 0;
+    }
+    
+    .log-panel header h3 {
+      font-size: 0.95rem;
+      margin: 0;
+    }
+    
+    .log-content {
+      max-height: 300px;
+      overflow: auto;
+      padding: 16px;
+      font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
+      font-size: 0.85rem;
+      background: #0f172a;
+      color: #e2e8f0;
+    }
+    
+    .log-entry {
+      padding: 4px 0;
+      border-bottom: 1px solid #334155;
+    }
+    
+    .log-entry:last-child {
+      border-bottom: none;
+    }
+    
+    .log-entry .time {
+      color: #94a3b8;
+      margin-right: 8px;
+    }
+    
+    .log-entry.info { color: #60a5fa; }
+    .log-entry.success { color: #4ade80; }
+    .log-entry.warning { color: #fbbf24; }
+    .log-entry.error { color: #f87171; }
+    
+    /* 加载动画 */
+    .loading {
+      display: inline-block;
+      width: 20px;
+      height: 20px;
+      border: 2px solid #e2e8f0;
+      border-top-color: var(--primary-color);
+      border-radius: 50%;
+      animation: spin 1s linear infinite;
+    }
+    
+    @keyframes spin {
+      to { transform: rotate(360deg); }
+    }
+  </style>
+</head>
+<body>
+  <div class="container">
+    <header>
+      <h1>🎵 简谱渲染引擎对比测试</h1>
+      <p>对比新旧渲染引擎的输出效果,验证兼容性和正确性</p>
+    </header>
+    
+    <div class="controls">
+      <div class="control-group">
+        <label for="xml-select">测试文件:</label>
+        <select id="xml-select">
+          <option value="basic.xml">basic.xml - 基础简谱</option>
+          <option value="mixed-durations.xml">mixed-durations.xml - 混合时值</option>
+          <option value="multi-voice.xml">multi-voice.xml - 多声部</option>
+          <option value="with-lyrics.xml">with-lyrics.xml - 带歌词</option>
+          <option value="complex.xml">complex.xml - 复杂记号</option>
+        </select>
+      </div>
+      
+      <button id="btn-render" onclick="renderBoth()">
+        🎨 渲染对比
+      </button>
+      
+      <button id="btn-compare" class="secondary" onclick="compareResults()">
+        🔍 差异分析
+      </button>
+      
+      <button id="btn-baseline" class="secondary" onclick="loadBaseline()">
+        📂 加载基准
+      </button>
+      
+      <button id="btn-clear" class="secondary" onclick="clearAll()">
+        🗑️ 清除
+      </button>
+    </div>
+    
+    <div class="comparison-container">
+      <!-- 旧引擎面板 -->
+      <div class="render-panel">
+        <div class="panel-header">
+          <h2>
+            <span class="badge old">旧引擎</span>
+            OSMD + VexFlow
+          </h2>
+          <span id="old-status">未渲染</span>
+        </div>
+        <div class="render-content empty" id="old-render">
+          点击"渲染对比"按钮开始
+        </div>
+      </div>
+      
+      <!-- 新引擎面板 -->
+      <div class="render-panel">
+        <div class="panel-header">
+          <h2>
+            <span class="badge new">新引擎</span>
+            JianpuRenderer
+          </h2>
+          <span id="new-status">未渲染</span>
+        </div>
+        <div class="render-content empty" id="new-render">
+          点击"渲染对比"按钮开始
+        </div>
+      </div>
+    </div>
+    
+    <!-- 状态面板 -->
+    <div class="status-panel">
+      <h3>📊 渲染统计</h3>
+      <div class="status-grid">
+        <div class="status-item">
+          <div class="label">小节数量</div>
+          <div class="value" id="stat-measures">-</div>
+        </div>
+        <div class="status-item">
+          <div class="label">音符数量</div>
+          <div class="value" id="stat-notes">-</div>
+        </div>
+        <div class="status-item">
+          <div class="label">旧引擎耗时</div>
+          <div class="value" id="stat-old-time">-</div>
+        </div>
+        <div class="status-item">
+          <div class="label">新引擎耗时</div>
+          <div class="value" id="stat-new-time">-</div>
+        </div>
+        <div class="status-item">
+          <div class="label">DOM元素匹配</div>
+          <div class="value" id="stat-dom-match">-</div>
+        </div>
+        <div class="status-item">
+          <div class="label">times数组匹配</div>
+          <div class="value" id="stat-times-match">-</div>
+        </div>
+      </div>
+    </div>
+    
+    <!-- 日志面板 -->
+    <div class="log-panel">
+      <header>
+        <h3>📝 控制台日志</h3>
+      </header>
+      <div class="log-content" id="log-content">
+        <div class="log-entry info">
+          <span class="time">[00:00:00]</span>
+          <span>测试页面已就绪,等待操作...</span>
+        </div>
+      </div>
+    </div>
+  </div>
+  
+  <script>
+    // 日志工具
+    const logPanel = document.getElementById('log-content');
+    
+    function log(message, type = 'info') {
+      const now = new Date();
+      const time = now.toTimeString().split(' ')[0];
+      const entry = document.createElement('div');
+      entry.className = `log-entry ${type}`;
+      entry.innerHTML = `<span class="time">[${time}]</span><span>${message}</span>`;
+      logPanel.appendChild(entry);
+      logPanel.scrollTop = logPanel.scrollHeight;
+    }
+    
+    // 获取选中的XML文件
+    function getSelectedFile() {
+      return document.getElementById('xml-select').value;
+    }
+    
+    // 加载XML文件
+    async function loadXML(filename) {
+      const basePath = './fixtures/';
+      const response = await fetch(basePath + filename);
+      if (!response.ok) {
+        throw new Error(`无法加载文件: ${filename}`);
+      }
+      return await response.text();
+    }
+    
+    // 渲染对比
+    async function renderBoth() {
+      const filename = getSelectedFile();
+      log(`开始加载测试文件: ${filename}`, 'info');
+      
+      const oldRender = document.getElementById('old-render');
+      const newRender = document.getElementById('new-render');
+      const oldStatus = document.getElementById('old-status');
+      const newStatus = document.getElementById('new-status');
+      
+      oldRender.innerHTML = '<div class="loading"></div>';
+      newRender.innerHTML = '<div class="loading"></div>';
+      oldRender.classList.remove('empty');
+      newRender.classList.remove('empty');
+      oldStatus.textContent = '渲染中...';
+      newStatus.textContent = '渲染中...';
+      
+      try {
+        const xmlContent = await loadXML(filename);
+        log(`文件加载成功,大小: ${xmlContent.length} 字节`, 'success');
+        
+        // TODO: 旧引擎渲染
+        const oldStart = performance.now();
+        // await renderWithOSMD(oldRender, xmlContent);
+        const oldTime = performance.now() - oldStart;
+        
+        // 模拟渲染结果(实际开发时替换为真实渲染)
+        oldRender.innerHTML = `
+          <div class="jianpu-score">
+            <div style="color: #64748b; text-align: center; padding: 40px;">
+              <p>⚠️ 旧引擎渲染需要OSMD库</p>
+              <p style="font-size: 0.9rem; margin-top: 8px;">请在集成环境中测试</p>
+            </div>
+          </div>
+        `;
+        oldStatus.textContent = `已完成`;
+        document.getElementById('stat-old-time').textContent = `${oldTime.toFixed(2)} ms`;
+        log(`旧引擎渲染完成,耗时: ${oldTime.toFixed(2)} ms`, 'success');
+        
+        // TODO: 新引擎渲染
+        const newStart = performance.now();
+        // await renderWithJianpu(newRender, xmlContent);
+        const newTime = performance.now() - newStart;
+        
+        // 模拟渲染结果(实际开发时替换为真实渲染)
+        newRender.innerHTML = `
+          <div class="jianpu-score">
+            <div style="color: #64748b; text-align: center; padding: 40px;">
+              <p>🚧 新引擎开发中</p>
+              <p style="font-size: 0.9rem; margin-top: 8px;">完成阶段3后可用</p>
+            </div>
+          </div>
+        `;
+        newStatus.textContent = `已完成`;
+        document.getElementById('stat-new-time').textContent = `${newTime.toFixed(2)} ms`;
+        log(`新引擎渲染完成,耗时: ${newTime.toFixed(2)} ms`, 'success');
+        
+        // 解析XML获取统计信息
+        const parser = new DOMParser();
+        const xmlDoc = parser.parseFromString(xmlContent, 'text/xml');
+        const measures = xmlDoc.getElementsByTagName('measure').length;
+        const notes = xmlDoc.getElementsByTagName('note').length;
+        
+        document.getElementById('stat-measures').textContent = measures;
+        document.getElementById('stat-notes').textContent = notes;
+        
+        log(`统计: ${measures} 个小节, ${notes} 个音符`, 'info');
+        
+      } catch (error) {
+        log(`渲染失败: ${error.message}`, 'error');
+        oldRender.innerHTML = `<div style="color: #dc2626; padding: 20px;">错误: ${error.message}</div>`;
+        newRender.innerHTML = `<div style="color: #dc2626; padding: 20px;">错误: ${error.message}</div>`;
+        oldStatus.textContent = '失败';
+        newStatus.textContent = '失败';
+      }
+    }
+    
+    // 加载基准数据
+    async function loadBaseline() {
+      const filename = getSelectedFile().replace('.xml', '');
+      log(`加载基准数据: ${filename}`, 'info');
+      
+      try {
+        const response = await fetch(`./baseline/${filename}/times.json`);
+        if (!response.ok) throw new Error('基准数据文件不存在');
+        
+        const baseline = await response.json();
+        
+        if (baseline.metadata.status?.includes('TEMPLATE')) {
+          log(`⚠️ ${filename} 的基准数据尚未收集,请先使用 collect-baseline.html 收集`, 'warning');
+          document.getElementById('stat-times-match').textContent = '无基准';
+          document.getElementById('stat-times-match').className = 'value warning';
+        } else {
+          log(`✅ 基准数据加载成功: ${baseline.times.length} 条音符记录`, 'success');
+          window.baselineData = baseline;
+          
+          // 更新统计
+          document.getElementById('stat-measures').textContent = baseline.metadata.measureCount || '-';
+          document.getElementById('stat-notes').textContent = baseline.metadata.noteCount || '-';
+          
+          log(`基准数据收集时间: ${baseline.metadata.collectedAt}`, 'info');
+        }
+      } catch (error) {
+        log(`加载基准数据失败: ${error.message}`, 'error');
+        log('请先使用 collect-baseline.html 收集基准数据', 'warning');
+      }
+    }
+    
+    // 差异分析
+    function compareResults() {
+      log('开始差异分析...', 'info');
+      
+      if (!window.baselineData) {
+        log('请先加载基准数据', 'warning');
+        loadBaseline();
+        return;
+      }
+      
+      // TODO: 实现差异分析逻辑
+      // 1. 对比DOM结构
+      // 2. 对比times数组
+      // 3. 对比视觉位置
+      
+      const baseline = window.baselineData;
+      
+      if (window.state?.times) {
+        const newTimes = state.times;
+        const baselineTimes = baseline.times;
+        
+        // 简单对比:数量
+        if (newTimes.length === baselineTimes.length) {
+          document.getElementById('stat-times-match').textContent = `${newTimes.length}/${baselineTimes.length} ✓`;
+          document.getElementById('stat-times-match').className = 'value success';
+          log(`times数组长度匹配: ${newTimes.length}`, 'success');
+        } else {
+          document.getElementById('stat-times-match').textContent = `${newTimes.length}/${baselineTimes.length} ✗`;
+          document.getElementById('stat-times-match').className = 'value error';
+          log(`times数组长度不匹配: 新${newTimes.length} vs 基准${baselineTimes.length}`, 'error');
+        }
+        
+        // TODO: 更详细的字段对比
+      } else {
+        document.getElementById('stat-times-match').textContent = '待实现';
+        document.getElementById('stat-times-match').className = 'value warning';
+        log('state.times 不可用,请在集成环境中测试', 'warning');
+      }
+      
+      document.getElementById('stat-dom-match').textContent = '待实现';
+      document.getElementById('stat-dom-match').className = 'value warning';
+      
+      log('完整差异分析功能将在阶段4实现', 'info');
+    }
+    
+    // 清除所有
+    function clearAll() {
+      document.getElementById('old-render').innerHTML = '点击"渲染对比"按钮开始';
+      document.getElementById('old-render').classList.add('empty');
+      document.getElementById('new-render').innerHTML = '点击"渲染对比"按钮开始';
+      document.getElementById('new-render').classList.add('empty');
+      
+      document.getElementById('old-status').textContent = '未渲染';
+      document.getElementById('new-status').textContent = '未渲染';
+      
+      document.getElementById('stat-measures').textContent = '-';
+      document.getElementById('stat-notes').textContent = '-';
+      document.getElementById('stat-old-time').textContent = '-';
+      document.getElementById('stat-new-time').textContent = '-';
+      document.getElementById('stat-dom-match').textContent = '-';
+      document.getElementById('stat-dom-match').className = 'value';
+      document.getElementById('stat-times-match').textContent = '-';
+      document.getElementById('stat-times-match').className = 'value';
+      
+      log('已清除所有渲染结果', 'info');
+    }
+    
+    // 页面加载完成
+    document.addEventListener('DOMContentLoaded', () => {
+      log('对比测试页面已加载', 'success');
+      log('可用测试文件: basic.xml, mixed-durations.xml, multi-voice.xml, with-lyrics.xml, complex.xml', 'info');
+    });
+  </script>
+</body>
+</html>

+ 151 - 0
src/jianpu-renderer/__tests__/fixtures/README.md

@@ -0,0 +1,151 @@
+# 测试数据说明
+
+本目录包含用于简谱渲染引擎测试的MusicXML文件。
+
+## 文件列表
+
+| 文件名 | 用途 | divisions | 主要测试点 |
+|--------|------|-----------|------------|
+| `basic.xml` | 基础测试 | 256 | 四分音符、休止符、高低音、音阶 |
+| `mixed-durations.xml` | 时值测试 | 256 | 八分/十六分/四分/二分/全音符、附点 |
+| `multi-voice.xml` | 多声部测试 | 256 | 多声部对齐、和弦、双谱表 |
+| `with-lyrics.xml` | 歌词测试 | 256 | 歌词解析、多遍歌词、中文歌词 |
+| `complex.xml` | 复杂记号测试 | 480 | 升降号、三连音、装饰音、延音线、力度 |
+
+## 各文件详细说明
+
+### basic.xml (基础简谱测试)
+- **内容**: 只有四分音符的简单旋律
+- **小节数**: 4
+- **拍号**: 4/4
+- **调号**: C大调
+- **速度**: 120 BPM
+- **测试点**:
+  - 四分音符解析
+  - 休止符识别
+  - 高低音点(octave 3-5)
+  - 音高转换(C→1, D→2, ... B→7)
+
+### mixed-durations.xml (混合时值测试)
+- **内容**: 不同时值的音符混合
+- **小节数**: 6
+- **拍号**: 4/4
+- **调号**: C大调
+- **速度**: 100 BPM
+- **测试点**:
+  - 八分音符 → 1条减时线
+  - 十六分音符 → 2条减时线
+  - 二分音符 → 1条增时线
+  - 全音符 → 3条增时线
+  - 附点四分音符 → 数字+附点
+  - 附点二分音符 → 数字+附点+2条增时线
+
+### multi-voice.xml (多声部测试)
+- **内容**: 钢琴双手演奏风格的多声部
+- **小节数**: 4
+- **拍号**: 4/4
+- **调号**: C大调
+- **速度**: 90 BPM
+- **测试点**:
+  - 两个声部的垂直对齐
+  - `<backup>` 元素处理
+  - `<chord>` 和弦处理
+  - 声部1和声部2的独立布局
+  - 休止符在多声部中的对齐
+
+### with-lyrics.xml (带歌词测试)
+- **内容**: 小星星片段,带两遍歌词
+- **小节数**: 4
+- **拍号**: 4/4
+- **调号**: C大调
+- **速度**: 100 BPM
+- **歌词**:
+  - 第1遍: "小星星亮晶晶——满天都是小星——"
+  - 第2遍: "一闪一闪放光明好像那许多小眼睛"
+- **测试点**:
+  - `<lyric number="1">` 第一遍歌词
+  - `<lyric number="2">` 第二遍歌词
+  - 歌词与音符的对应关系
+  - `lyricIndex` 属性生成
+
+### complex.xml (复杂记号测试)
+- **内容**: 包含各种高级音乐记号
+- **小节数**: 6
+- **拍号**: 4/4 → 3/4 (变拍)
+- **调号**: G大调 (1个升号)
+- **速度**: 120 BPM
+- **divisions**: 480 (故意使用不同值测试divisions转换)
+- **测试点**:
+  - 升号 `#` (alter=1)
+  - 降号 `♭` (alter=-1)
+  - 还原号 `♮` (alter=0 + accidental)
+  - 三连音 (`<time-modification>`)
+  - 装饰音 (`<grace>`, duration=0)
+  - 颤音 (`<trill-mark>`)
+  - 顿音 (`<staccato>`)
+  - 重音 (`<accent>`)
+  - 延音线 (`<tie>` + `<tied>`)
+  - 变拍 (4/4 → 3/4)
+  - 力度记号 (pp, mf, ff, crescendo)
+
+## Divisions说明
+
+MusicXML中 `<divisions>` 定义了每个四分音符包含多少个"division"单位:
+
+```
+实际时值 = duration / divisions
+```
+
+示例(divisions=256):
+- 全音符: duration=1024 → 1024/256 = 4.0
+- 二分音符: duration=512 → 512/256 = 2.0
+- 四分音符: duration=256 → 256/256 = 1.0
+- 八分音符: duration=128 → 128/256 = 0.5
+- 十六分音符: duration=64 → 64/256 = 0.25
+- 附点四分音符: duration=384 → 384/256 = 1.5
+
+示例(divisions=480,用于complex.xml):
+- 四分音符: duration=480 → 480/480 = 1.0
+- 二分音符: duration=960 → 960/480 = 2.0
+- 八分音符: duration=240 → 240/480 = 0.5
+- 三连音八分: duration=320 → 320/480 ≈ 0.667 (2/3拍)
+
+## 使用方法
+
+```typescript
+// 在测试中加载XML文件
+import { readFileSync } from 'fs';
+import { join } from 'path';
+
+const xmlPath = join(__dirname, 'fixtures', 'basic.xml');
+const xmlContent = readFileSync(xmlPath, 'utf-8');
+
+// 使用JianpuRenderer解析和渲染
+const renderer = new JianpuRenderer(container);
+await renderer.load(xmlContent);
+renderer.render();
+```
+
+## 预期渲染结果
+
+### basic.xml 预期输出
+```
+小节1: 1 2 3 4        (do re mi fa)
+小节2: 5 6 7 ·1       (sol la si 高音do)
+小节3: 1 0 3 0        (do 休止 mi 休止)
+小节4: ·5 1 ·3 ·5     (低音sol do 高音mi 高音sol)
+       ·    ·  ·
+```
+
+### mixed-durations.xml 预期输出
+```
+小节1: 1 2 3 4 5 6 7 ·1   (8个八分音符,每个带1条减时线)
+       _ _ _ _ _ _ _ _
+小节2: 1 — 3 —              (2个二分音符,各带1条增时线)
+小节3: 5 — — —              (1个全音符,带3条增时线)
+小节4: 1· 2 3· 4            (附点四分+八分,附点四分+八分)
+       _    _
+小节5: 5· — 6               (附点二分+四分)
+小节6: 1234 5 — ·1          (4个十六分+二分+四分)
+       ==== 
+```

+ 254 - 0
src/jianpu-renderer/__tests__/fixtures/basic.xml

@@ -0,0 +1,254 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE score-partwise PUBLIC "-//Recordare//DTD MusicXML 4.0 Partwise//EN" "http://www.musicxml.org/dtds/partwise.dtd">
+<!--
+  基础简谱测试文件
+  内容:只有四分音符的简单旋律
+  用途:测试基本音符解析、音高转换、休止符
+  divisions: 256 (标准值)
+-->
+<score-partwise version="4.0">
+  <work>
+    <work-title>基础简谱测试</work-title>
+  </work>
+  <identification>
+    <creator type="composer">测试</creator>
+    <encoding>
+      <software>简谱渲染引擎测试</software>
+      <encoding-date>2026-01-29</encoding-date>
+    </encoding>
+  </identification>
+  
+  <part-list>
+    <score-part id="P1">
+      <part-name>Voice</part-name>
+    </score-part>
+  </part-list>
+
+  <part id="P1">
+    <!-- 第1小节:C大调 4/4拍 do re mi fa -->
+    <measure number="1">
+      <attributes>
+        <divisions>256</divisions>
+        <key>
+          <fifths>0</fifths>
+          <mode>major</mode>
+        </key>
+        <time>
+          <beats>4</beats>
+          <beat-type>4</beat-type>
+        </time>
+        <clef>
+          <sign>G</sign>
+          <line>2</line>
+        </clef>
+      </attributes>
+      
+      <direction placement="above">
+        <direction-type>
+          <metronome>
+            <beat-unit>quarter</beat-unit>
+            <per-minute>120</per-minute>
+          </metronome>
+        </direction-type>
+        <sound tempo="120"/>
+      </direction>
+      
+      <!-- 四分音符 do (C4) -->
+      <note>
+        <pitch>
+          <step>C</step>
+          <octave>4</octave>
+        </pitch>
+        <duration>256</duration>
+        <type>quarter</type>
+        <voice>1</voice>
+        <stem>up</stem>
+      </note>
+      
+      <!-- 四分音符 re (D4) -->
+      <note>
+        <pitch>
+          <step>D</step>
+          <octave>4</octave>
+        </pitch>
+        <duration>256</duration>
+        <type>quarter</type>
+        <voice>1</voice>
+        <stem>up</stem>
+      </note>
+      
+      <!-- 四分音符 mi (E4) -->
+      <note>
+        <pitch>
+          <step>E</step>
+          <octave>4</octave>
+        </pitch>
+        <duration>256</duration>
+        <type>quarter</type>
+        <voice>1</voice>
+        <stem>up</stem>
+      </note>
+      
+      <!-- 四分音符 fa (F4) -->
+      <note>
+        <pitch>
+          <step>F</step>
+          <octave>4</octave>
+        </pitch>
+        <duration>256</duration>
+        <type>quarter</type>
+        <voice>1</voice>
+        <stem>up</stem>
+      </note>
+    </measure>
+
+    <!-- 第2小节:sol la si do(高) -->
+    <measure number="2">
+      <!-- 四分音符 sol (G4) -->
+      <note>
+        <pitch>
+          <step>G</step>
+          <octave>4</octave>
+        </pitch>
+        <duration>256</duration>
+        <type>quarter</type>
+        <voice>1</voice>
+        <stem>up</stem>
+      </note>
+      
+      <!-- 四分音符 la (A4) -->
+      <note>
+        <pitch>
+          <step>A</step>
+          <octave>4</octave>
+        </pitch>
+        <duration>256</duration>
+        <type>quarter</type>
+        <voice>1</voice>
+        <stem>up</stem>
+      </note>
+      
+      <!-- 四分音符 si (B4) -->
+      <note>
+        <pitch>
+          <step>B</step>
+          <octave>4</octave>
+        </pitch>
+        <duration>256</duration>
+        <type>quarter</type>
+        <voice>1</voice>
+        <stem>up</stem>
+      </note>
+      
+      <!-- 四分音符 高音do (C5) -->
+      <note>
+        <pitch>
+          <step>C</step>
+          <octave>5</octave>
+        </pitch>
+        <duration>256</duration>
+        <type>quarter</type>
+        <voice>1</voice>
+        <stem>up</stem>
+      </note>
+    </measure>
+
+    <!-- 第3小节:带休止符 do 休止 mi 休止 -->
+    <measure number="3">
+      <!-- 四分音符 do (C4) -->
+      <note>
+        <pitch>
+          <step>C</step>
+          <octave>4</octave>
+        </pitch>
+        <duration>256</duration>
+        <type>quarter</type>
+        <voice>1</voice>
+        <stem>up</stem>
+      </note>
+      
+      <!-- 四分休止符 -->
+      <note>
+        <rest/>
+        <duration>256</duration>
+        <type>quarter</type>
+        <voice>1</voice>
+      </note>
+      
+      <!-- 四分音符 mi (E4) -->
+      <note>
+        <pitch>
+          <step>E</step>
+          <octave>4</octave>
+        </pitch>
+        <duration>256</duration>
+        <type>quarter</type>
+        <voice>1</voice>
+        <stem>up</stem>
+      </note>
+      
+      <!-- 四分休止符 -->
+      <note>
+        <rest/>
+        <duration>256</duration>
+        <type>quarter</type>
+        <voice>1</voice>
+      </note>
+    </measure>
+
+    <!-- 第4小节:低音和高音测试 低音sol do 高音mi 高音sol -->
+    <measure number="4">
+      <!-- 低音sol (G3) -->
+      <note>
+        <pitch>
+          <step>G</step>
+          <octave>3</octave>
+        </pitch>
+        <duration>256</duration>
+        <type>quarter</type>
+        <voice>1</voice>
+        <stem>up</stem>
+      </note>
+      
+      <!-- 中音do (C4) -->
+      <note>
+        <pitch>
+          <step>C</step>
+          <octave>4</octave>
+        </pitch>
+        <duration>256</duration>
+        <type>quarter</type>
+        <voice>1</voice>
+        <stem>up</stem>
+      </note>
+      
+      <!-- 高音mi (E5) -->
+      <note>
+        <pitch>
+          <step>E</step>
+          <octave>5</octave>
+        </pitch>
+        <duration>256</duration>
+        <type>quarter</type>
+        <voice>1</voice>
+        <stem>up</stem>
+      </note>
+      
+      <!-- 高音sol (G5) -->
+      <note>
+        <pitch>
+          <step>G</step>
+          <octave>5</octave>
+        </pitch>
+        <duration>256</duration>
+        <type>quarter</type>
+        <voice>1</voice>
+        <stem>up</stem>
+      </note>
+      
+      <barline location="right">
+        <bar-style>light-heavy</bar-style>
+      </barline>
+    </measure>
+  </part>
+</score-partwise>

+ 475 - 0
src/jianpu-renderer/__tests__/fixtures/complex.xml

@@ -0,0 +1,475 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE score-partwise PUBLIC "-//Recordare//DTD MusicXML 4.0 Partwise//EN" "http://www.musicxml.org/dtds/partwise.dtd">
+<!--
+  复杂记号测试文件
+  内容:装饰音、连音符、升降号、延音线、力度记号
+  用途:测试高级音乐符号的解析和渲染
+  divisions: 480 (故意使用不同的divisions值测试转换)
+-->
+<score-partwise version="4.0">
+  <work>
+    <work-title>复杂记号测试</work-title>
+  </work>
+  <identification>
+    <creator type="composer">测试</creator>
+  </identification>
+  
+  <part-list>
+    <score-part id="P1">
+      <part-name>Voice</part-name>
+    </score-part>
+  </part-list>
+
+  <part id="P1">
+    <!-- 第1小节:升降号测试 G大调 (1个升号) -->
+    <measure number="1">
+      <attributes>
+        <divisions>480</divisions>
+        <key>
+          <fifths>1</fifths>
+          <mode>major</mode>
+        </key>
+        <time>
+          <beats>4</beats>
+          <beat-type>4</beat-type>
+        </time>
+        <clef>
+          <sign>G</sign>
+          <line>2</line>
+        </clef>
+      </attributes>
+      
+      <direction placement="above">
+        <direction-type>
+          <dynamics>
+            <mf/>
+          </dynamics>
+        </direction-type>
+        <sound dynamics="80"/>
+      </direction>
+      
+      <direction placement="above">
+        <sound tempo="120"/>
+      </direction>
+      
+      <!-- sol (G4) - 普通音符 (divisions=480, 四分音符=480) -->
+      <note>
+        <pitch>
+          <step>G</step>
+          <octave>4</octave>
+        </pitch>
+        <duration>480</duration>
+        <type>quarter</type>
+        <voice>1</voice>
+        <stem>up</stem>
+      </note>
+      
+      <!-- #fa (F#4) - 升号 (在G大调中F是升的) -->
+      <note>
+        <pitch>
+          <step>F</step>
+          <alter>1</alter>
+          <octave>4</octave>
+        </pitch>
+        <duration>480</duration>
+        <type>quarter</type>
+        <accidental>sharp</accidental>
+        <voice>1</voice>
+        <stem>up</stem>
+      </note>
+      
+      <!-- ♭si (Bb4) - 降号 (临时记号) -->
+      <note>
+        <pitch>
+          <step>B</step>
+          <alter>-1</alter>
+          <octave>4</octave>
+        </pitch>
+        <duration>480</duration>
+        <type>quarter</type>
+        <accidental>flat</accidental>
+        <voice>1</voice>
+        <stem>up</stem>
+      </note>
+      
+      <!-- ♮fa (F♮4) - 还原号 -->
+      <note>
+        <pitch>
+          <step>F</step>
+          <alter>0</alter>
+          <octave>4</octave>
+        </pitch>
+        <duration>480</duration>
+        <type>quarter</type>
+        <accidental>natural</accidental>
+        <voice>1</voice>
+        <stem>up</stem>
+      </note>
+    </measure>
+
+    <!-- 第2小节:三连音测试 -->
+    <measure number="2">
+      <!-- 三连音组 (3个八分音符占2个八分音符的时间) -->
+      <!-- 每个音符时值 = 480 × 2 / 3 = 320 (而不是正常八分音符的240) -->
+      <note>
+        <pitch>
+          <step>C</step>
+          <octave>4</octave>
+        </pitch>
+        <duration>320</duration>
+        <type>eighth</type>
+        <time-modification>
+          <actual-notes>3</actual-notes>
+          <normal-notes>2</normal-notes>
+        </time-modification>
+        <voice>1</voice>
+        <stem>up</stem>
+        <beam number="1">begin</beam>
+        <notations>
+          <tuplet type="start" bracket="yes" number="1"/>
+        </notations>
+      </note>
+      
+      <note>
+        <pitch>
+          <step>D</step>
+          <octave>4</octave>
+        </pitch>
+        <duration>320</duration>
+        <type>eighth</type>
+        <time-modification>
+          <actual-notes>3</actual-notes>
+          <normal-notes>2</normal-notes>
+        </time-modification>
+        <voice>1</voice>
+        <stem>up</stem>
+        <beam number="1">continue</beam>
+      </note>
+      
+      <note>
+        <pitch>
+          <step>E</step>
+          <octave>4</octave>
+        </pitch>
+        <duration>320</duration>
+        <type>eighth</type>
+        <time-modification>
+          <actual-notes>3</actual-notes>
+          <normal-notes>2</normal-notes>
+        </time-modification>
+        <voice>1</voice>
+        <stem>up</stem>
+        <beam number="1">end</beam>
+        <notations>
+          <tuplet type="stop" number="1"/>
+        </notations>
+      </note>
+      
+      <!-- 普通四分音符 -->
+      <note>
+        <pitch>
+          <step>F</step>
+          <octave>4</octave>
+        </pitch>
+        <duration>480</duration>
+        <type>quarter</type>
+        <voice>1</voice>
+        <stem>up</stem>
+      </note>
+      
+      <!-- 又一组三连音 -->
+      <note>
+        <pitch>
+          <step>G</step>
+          <octave>4</octave>
+        </pitch>
+        <duration>320</duration>
+        <type>eighth</type>
+        <time-modification>
+          <actual-notes>3</actual-notes>
+          <normal-notes>2</normal-notes>
+        </time-modification>
+        <voice>1</voice>
+        <stem>up</stem>
+        <beam number="1">begin</beam>
+        <notations>
+          <tuplet type="start" bracket="yes" number="1"/>
+        </notations>
+      </note>
+      
+      <note>
+        <pitch>
+          <step>A</step>
+          <octave>4</octave>
+        </pitch>
+        <duration>320</duration>
+        <type>eighth</type>
+        <time-modification>
+          <actual-notes>3</actual-notes>
+          <normal-notes>2</normal-notes>
+        </time-modification>
+        <voice>1</voice>
+        <stem>up</stem>
+        <beam number="1">continue</beam>
+      </note>
+      
+      <note>
+        <pitch>
+          <step>B</step>
+          <octave>4</octave>
+        </pitch>
+        <duration>320</duration>
+        <type>eighth</type>
+        <time-modification>
+          <actual-notes>3</actual-notes>
+          <normal-notes>2</normal-notes>
+        </time-modification>
+        <voice>1</voice>
+        <stem>up</stem>
+        <beam number="1">end</beam>
+        <notations>
+          <tuplet type="stop" number="1"/>
+        </notations>
+      </note>
+      
+      <!-- 四分音符 -->
+      <note>
+        <pitch>
+          <step>C</step>
+          <octave>5</octave>
+        </pitch>
+        <duration>480</duration>
+        <type>quarter</type>
+        <voice>1</voice>
+        <stem>up</stem>
+      </note>
+    </measure>
+
+    <!-- 第3小节:装饰音测试 -->
+    <measure number="3">
+      <!-- 装饰音(倚音) - duration为0 -->
+      <note>
+        <grace/>
+        <pitch>
+          <step>D</step>
+          <octave>4</octave>
+        </pitch>
+        <type>eighth</type>
+        <voice>1</voice>
+        <stem>up</stem>
+      </note>
+      
+      <!-- 被装饰的主音符 -->
+      <note>
+        <pitch>
+          <step>C</step>
+          <octave>4</octave>
+        </pitch>
+        <duration>480</duration>
+        <type>quarter</type>
+        <voice>1</voice>
+        <stem>up</stem>
+      </note>
+      
+      <!-- 普通四分音符 + 颤音 -->
+      <note>
+        <pitch>
+          <step>E</step>
+          <octave>4</octave>
+        </pitch>
+        <duration>480</duration>
+        <type>quarter</type>
+        <voice>1</voice>
+        <stem>up</stem>
+        <notations>
+          <ornaments>
+            <trill-mark/>
+          </ornaments>
+        </notations>
+      </note>
+      
+      <!-- 顿音 -->
+      <note>
+        <pitch>
+          <step>G</step>
+          <octave>4</octave>
+        </pitch>
+        <duration>480</duration>
+        <type>quarter</type>
+        <voice>1</voice>
+        <stem>up</stem>
+        <notations>
+          <articulations>
+            <staccato/>
+          </articulations>
+        </notations>
+      </note>
+      
+      <!-- 重音 -->
+      <note>
+        <pitch>
+          <step>C</step>
+          <octave>5</octave>
+        </pitch>
+        <duration>480</duration>
+        <type>quarter</type>
+        <voice>1</voice>
+        <stem>up</stem>
+        <notations>
+          <articulations>
+            <accent/>
+          </articulations>
+        </notations>
+      </note>
+    </measure>
+
+    <!-- 第4小节:延音线测试 -->
+    <measure number="4">
+      <!-- 延音线开始 -->
+      <note>
+        <pitch>
+          <step>C</step>
+          <octave>4</octave>
+        </pitch>
+        <duration>960</duration>
+        <type>half</type>
+        <voice>1</voice>
+        <stem>up</stem>
+        <tie type="start"/>
+        <notations>
+          <tied type="start"/>
+        </notations>
+      </note>
+      
+      <!-- 延音线结束 -->
+      <note>
+        <pitch>
+          <step>C</step>
+          <octave>4</octave>
+        </pitch>
+        <duration>960</duration>
+        <type>half</type>
+        <voice>1</voice>
+        <stem>up</stem>
+        <tie type="stop"/>
+        <notations>
+          <tied type="stop"/>
+        </notations>
+      </note>
+    </measure>
+
+    <!-- 第5小节:变拍测试 (切换到3/4拍) -->
+    <measure number="5">
+      <attributes>
+        <time>
+          <beats>3</beats>
+          <beat-type>4</beat-type>
+        </time>
+      </attributes>
+      
+      <!-- 3/4拍:三个四分音符 -->
+      <note>
+        <pitch>
+          <step>D</step>
+          <octave>4</octave>
+        </pitch>
+        <duration>480</duration>
+        <type>quarter</type>
+        <voice>1</voice>
+        <stem>up</stem>
+      </note>
+      
+      <note>
+        <pitch>
+          <step>E</step>
+          <octave>4</octave>
+        </pitch>
+        <duration>480</duration>
+        <type>quarter</type>
+        <voice>1</voice>
+        <stem>up</stem>
+      </note>
+      
+      <note>
+        <pitch>
+          <step>F</step>
+          <octave>4</octave>
+        </pitch>
+        <duration>480</duration>
+        <type>quarter</type>
+        <voice>1</voice>
+        <stem>up</stem>
+      </note>
+    </measure>
+
+    <!-- 第6小节:力度变化测试 -->
+    <measure number="6">
+      <direction placement="below">
+        <direction-type>
+          <dynamics>
+            <pp/>
+          </dynamics>
+        </direction-type>
+        <sound dynamics="40"/>
+      </direction>
+      
+      <note>
+        <pitch>
+          <step>G</step>
+          <octave>4</octave>
+        </pitch>
+        <duration>480</duration>
+        <type>quarter</type>
+        <voice>1</voice>
+        <stem>up</stem>
+      </note>
+      
+      <direction placement="below">
+        <direction-type>
+          <wedge type="crescendo"/>
+        </direction-type>
+      </direction>
+      
+      <note>
+        <pitch>
+          <step>A</step>
+          <octave>4</octave>
+        </pitch>
+        <duration>480</duration>
+        <type>quarter</type>
+        <voice>1</voice>
+        <stem>up</stem>
+      </note>
+      
+      <direction placement="below">
+        <direction-type>
+          <wedge type="stop"/>
+        </direction-type>
+      </direction>
+      
+      <direction placement="below">
+        <direction-type>
+          <dynamics>
+            <ff/>
+          </dynamics>
+        </direction-type>
+        <sound dynamics="120"/>
+      </direction>
+      
+      <note>
+        <pitch>
+          <step>B</step>
+          <octave>4</octave>
+        </pitch>
+        <duration>480</duration>
+        <type>quarter</type>
+        <voice>1</voice>
+        <stem>up</stem>
+      </note>
+      
+      <barline location="right">
+        <bar-style>light-heavy</bar-style>
+      </barline>
+    </measure>
+  </part>
+</score-partwise>

+ 357 - 0
src/jianpu-renderer/__tests__/fixtures/mixed-durations.xml

@@ -0,0 +1,357 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE score-partwise PUBLIC "-//Recordare//DTD MusicXML 4.0 Partwise//EN" "http://www.musicxml.org/dtds/partwise.dtd">
+<!--
+  混合时值测试文件
+  内容:八分、四分、二分、全音符、附点音符
+  用途:测试增时线、减时线、附点渲染
+  divisions: 256
+-->
+<score-partwise version="4.0">
+  <work>
+    <work-title>混合时值测试</work-title>
+  </work>
+  <identification>
+    <creator type="composer">测试</creator>
+  </identification>
+  
+  <part-list>
+    <score-part id="P1">
+      <part-name>Voice</part-name>
+    </score-part>
+  </part-list>
+
+  <part id="P1">
+    <!-- 第1小节:八分音符测试 (8个八分音符) -->
+    <measure number="1">
+      <attributes>
+        <divisions>256</divisions>
+        <key>
+          <fifths>0</fifths>
+          <mode>major</mode>
+        </key>
+        <time>
+          <beats>4</beats>
+          <beat-type>4</beat-type>
+        </time>
+        <clef>
+          <sign>G</sign>
+          <line>2</line>
+        </clef>
+      </attributes>
+      
+      <direction placement="above">
+        <sound tempo="100"/>
+      </direction>
+      
+      <!-- 八分音符 do (duration=128, 因为256/2=128) -->
+      <note>
+        <pitch>
+          <step>C</step>
+          <octave>4</octave>
+        </pitch>
+        <duration>128</duration>
+        <type>eighth</type>
+        <voice>1</voice>
+        <stem>up</stem>
+        <beam number="1">begin</beam>
+      </note>
+      
+      <!-- 八分音符 re -->
+      <note>
+        <pitch>
+          <step>D</step>
+          <octave>4</octave>
+        </pitch>
+        <duration>128</duration>
+        <type>eighth</type>
+        <voice>1</voice>
+        <stem>up</stem>
+        <beam number="1">end</beam>
+      </note>
+      
+      <!-- 八分音符 mi -->
+      <note>
+        <pitch>
+          <step>E</step>
+          <octave>4</octave>
+        </pitch>
+        <duration>128</duration>
+        <type>eighth</type>
+        <voice>1</voice>
+        <stem>up</stem>
+        <beam number="1">begin</beam>
+      </note>
+      
+      <!-- 八分音符 fa -->
+      <note>
+        <pitch>
+          <step>F</step>
+          <octave>4</octave>
+        </pitch>
+        <duration>128</duration>
+        <type>eighth</type>
+        <voice>1</voice>
+        <stem>up</stem>
+        <beam number="1">end</beam>
+      </note>
+      
+      <!-- 八分音符 sol -->
+      <note>
+        <pitch>
+          <step>G</step>
+          <octave>4</octave>
+        </pitch>
+        <duration>128</duration>
+        <type>eighth</type>
+        <voice>1</voice>
+        <stem>up</stem>
+        <beam number="1">begin</beam>
+      </note>
+      
+      <!-- 八分音符 la -->
+      <note>
+        <pitch>
+          <step>A</step>
+          <octave>4</octave>
+        </pitch>
+        <duration>128</duration>
+        <type>eighth</type>
+        <voice>1</voice>
+        <stem>up</stem>
+        <beam number="1">end</beam>
+      </note>
+      
+      <!-- 八分音符 si -->
+      <note>
+        <pitch>
+          <step>B</step>
+          <octave>4</octave>
+        </pitch>
+        <duration>128</duration>
+        <type>eighth</type>
+        <voice>1</voice>
+        <stem>up</stem>
+        <beam number="1">begin</beam>
+      </note>
+      
+      <!-- 八分音符 do(高) -->
+      <note>
+        <pitch>
+          <step>C</step>
+          <octave>5</octave>
+        </pitch>
+        <duration>128</duration>
+        <type>eighth</type>
+        <voice>1</voice>
+        <stem>up</stem>
+        <beam number="1">end</beam>
+      </note>
+    </measure>
+
+    <!-- 第2小节:二分音符测试 (2个二分音符) -->
+    <measure number="2">
+      <!-- 二分音符 do (duration=512, 因为256*2=512) -->
+      <note>
+        <pitch>
+          <step>C</step>
+          <octave>4</octave>
+        </pitch>
+        <duration>512</duration>
+        <type>half</type>
+        <voice>1</voice>
+        <stem>up</stem>
+      </note>
+      
+      <!-- 二分音符 mi -->
+      <note>
+        <pitch>
+          <step>E</step>
+          <octave>4</octave>
+        </pitch>
+        <duration>512</duration>
+        <type>half</type>
+        <voice>1</voice>
+        <stem>up</stem>
+      </note>
+    </measure>
+
+    <!-- 第3小节:全音符测试 -->
+    <measure number="3">
+      <!-- 全音符 sol (duration=1024, 因为256*4=1024) -->
+      <note>
+        <pitch>
+          <step>G</step>
+          <octave>4</octave>
+        </pitch>
+        <duration>1024</duration>
+        <type>whole</type>
+        <voice>1</voice>
+      </note>
+    </measure>
+
+    <!-- 第4小节:附点音符测试 (附点四分+八分, 附点四分+八分) -->
+    <measure number="4">
+      <!-- 附点四分音符 do (duration=384, 因为256*1.5=384) -->
+      <note>
+        <pitch>
+          <step>C</step>
+          <octave>4</octave>
+        </pitch>
+        <duration>384</duration>
+        <type>quarter</type>
+        <dot/>
+        <voice>1</voice>
+        <stem>up</stem>
+      </note>
+      
+      <!-- 八分音符 re -->
+      <note>
+        <pitch>
+          <step>D</step>
+          <octave>4</octave>
+        </pitch>
+        <duration>128</duration>
+        <type>eighth</type>
+        <voice>1</voice>
+        <stem>up</stem>
+      </note>
+      
+      <!-- 附点四分音符 mi -->
+      <note>
+        <pitch>
+          <step>E</step>
+          <octave>4</octave>
+        </pitch>
+        <duration>384</duration>
+        <type>quarter</type>
+        <dot/>
+        <voice>1</voice>
+        <stem>up</stem>
+      </note>
+      
+      <!-- 八分音符 fa -->
+      <note>
+        <pitch>
+          <step>F</step>
+          <octave>4</octave>
+        </pitch>
+        <duration>128</duration>
+        <type>eighth</type>
+        <voice>1</voice>
+        <stem>up</stem>
+      </note>
+    </measure>
+
+    <!-- 第5小节:附点二分音符测试 -->
+    <measure number="5">
+      <!-- 附点二分音符 sol (duration=768, 因为256*3=768) -->
+      <note>
+        <pitch>
+          <step>G</step>
+          <octave>4</octave>
+        </pitch>
+        <duration>768</duration>
+        <type>half</type>
+        <dot/>
+        <voice>1</voice>
+        <stem>up</stem>
+      </note>
+      
+      <!-- 四分音符 la -->
+      <note>
+        <pitch>
+          <step>A</step>
+          <octave>4</octave>
+        </pitch>
+        <duration>256</duration>
+        <type>quarter</type>
+        <voice>1</voice>
+        <stem>up</stem>
+      </note>
+    </measure>
+
+    <!-- 第6小节:十六分音符测试 -->
+    <measure number="6">
+      <!-- 4个十六分音符 (duration=64, 因为256/4=64) -->
+      <note>
+        <pitch>
+          <step>C</step>
+          <octave>4</octave>
+        </pitch>
+        <duration>64</duration>
+        <type>16th</type>
+        <voice>1</voice>
+        <stem>up</stem>
+        <beam number="1">begin</beam>
+        <beam number="2">begin</beam>
+      </note>
+      
+      <note>
+        <pitch>
+          <step>D</step>
+          <octave>4</octave>
+        </pitch>
+        <duration>64</duration>
+        <type>16th</type>
+        <voice>1</voice>
+        <stem>up</stem>
+        <beam number="1">continue</beam>
+        <beam number="2">continue</beam>
+      </note>
+      
+      <note>
+        <pitch>
+          <step>E</step>
+          <octave>4</octave>
+        </pitch>
+        <duration>64</duration>
+        <type>16th</type>
+        <voice>1</voice>
+        <stem>up</stem>
+        <beam number="1">continue</beam>
+        <beam number="2">continue</beam>
+      </note>
+      
+      <note>
+        <pitch>
+          <step>F</step>
+          <octave>4</octave>
+        </pitch>
+        <duration>64</duration>
+        <type>16th</type>
+        <voice>1</voice>
+        <stem>up</stem>
+        <beam number="1">end</beam>
+        <beam number="2">end</beam>
+      </note>
+      
+      <!-- 二分音符 sol -->
+      <note>
+        <pitch>
+          <step>G</step>
+          <octave>4</octave>
+        </pitch>
+        <duration>512</duration>
+        <type>half</type>
+        <voice>1</voice>
+        <stem>up</stem>
+      </note>
+      
+      <!-- 四分音符 do -->
+      <note>
+        <pitch>
+          <step>C</step>
+          <octave>5</octave>
+        </pitch>
+        <duration>256</duration>
+        <type>quarter</type>
+        <voice>1</voice>
+        <stem>up</stem>
+      </note>
+      
+      <barline location="right">
+        <bar-style>light-heavy</bar-style>
+      </barline>
+    </measure>
+  </part>
+</score-partwise>

+ 465 - 0
src/jianpu-renderer/__tests__/fixtures/multi-voice.xml

@@ -0,0 +1,465 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE score-partwise PUBLIC "-//Recordare//DTD MusicXML 4.0 Partwise//EN" "http://www.musicxml.org/dtds/partwise.dtd">
+<!--
+  多声部测试文件
+  内容:两个声部同时演奏(类似钢琴双手)
+  用途:测试多声部对齐、Y坐标计算
+  divisions: 256
+-->
+<score-partwise version="4.0">
+  <work>
+    <work-title>多声部测试</work-title>
+  </work>
+  <identification>
+    <creator type="composer">测试</creator>
+  </identification>
+  
+  <part-list>
+    <score-part id="P1">
+      <part-name>Piano</part-name>
+    </score-part>
+  </part-list>
+
+  <part id="P1">
+    <!-- 第1小节:两声部基础对齐测试 -->
+    <measure number="1">
+      <attributes>
+        <divisions>256</divisions>
+        <key>
+          <fifths>0</fifths>
+          <mode>major</mode>
+        </key>
+        <time>
+          <beats>4</beats>
+          <beat-type>4</beat-type>
+        </time>
+        <staves>2</staves>
+        <clef number="1">
+          <sign>G</sign>
+          <line>2</line>
+        </clef>
+        <clef number="2">
+          <sign>F</sign>
+          <line>4</line>
+        </clef>
+      </attributes>
+      
+      <direction placement="above">
+        <sound tempo="90"/>
+      </direction>
+      
+      <!-- 声部1(高声部):四分音符 do mi sol do(高) -->
+      <note>
+        <pitch>
+          <step>C</step>
+          <octave>5</octave>
+        </pitch>
+        <duration>256</duration>
+        <type>quarter</type>
+        <voice>1</voice>
+        <staff>1</staff>
+        <stem>up</stem>
+      </note>
+      
+      <note>
+        <pitch>
+          <step>E</step>
+          <octave>5</octave>
+        </pitch>
+        <duration>256</duration>
+        <type>quarter</type>
+        <voice>1</voice>
+        <staff>1</staff>
+        <stem>up</stem>
+      </note>
+      
+      <note>
+        <pitch>
+          <step>G</step>
+          <octave>5</octave>
+        </pitch>
+        <duration>256</duration>
+        <type>quarter</type>
+        <voice>1</voice>
+        <staff>1</staff>
+        <stem>up</stem>
+      </note>
+      
+      <note>
+        <pitch>
+          <step>C</step>
+          <octave>6</octave>
+        </pitch>
+        <duration>256</duration>
+        <type>quarter</type>
+        <voice>1</voice>
+        <staff>1</staff>
+        <stem>up</stem>
+      </note>
+      
+      <!-- 声部2(低声部):二分音符 do sol -->
+      <backup>
+        <duration>1024</duration>
+      </backup>
+      
+      <note>
+        <pitch>
+          <step>C</step>
+          <octave>3</octave>
+        </pitch>
+        <duration>512</duration>
+        <type>half</type>
+        <voice>2</voice>
+        <staff>2</staff>
+        <stem>down</stem>
+      </note>
+      
+      <note>
+        <pitch>
+          <step>G</step>
+          <octave>3</octave>
+        </pitch>
+        <duration>512</duration>
+        <type>half</type>
+        <voice>2</voice>
+        <staff>2</staff>
+        <stem>down</stem>
+      </note>
+    </measure>
+
+    <!-- 第2小节:不同节奏的声部对齐 -->
+    <measure number="2">
+      <!-- 声部1(高声部):8个八分音符 -->
+      <note>
+        <pitch>
+          <step>D</step>
+          <octave>5</octave>
+        </pitch>
+        <duration>128</duration>
+        <type>eighth</type>
+        <voice>1</voice>
+        <staff>1</staff>
+        <stem>up</stem>
+        <beam number="1">begin</beam>
+      </note>
+      
+      <note>
+        <pitch>
+          <step>E</step>
+          <octave>5</octave>
+        </pitch>
+        <duration>128</duration>
+        <type>eighth</type>
+        <voice>1</voice>
+        <staff>1</staff>
+        <stem>up</stem>
+        <beam number="1">end</beam>
+      </note>
+      
+      <note>
+        <pitch>
+          <step>F</step>
+          <octave>5</octave>
+        </pitch>
+        <duration>128</duration>
+        <type>eighth</type>
+        <voice>1</voice>
+        <staff>1</staff>
+        <stem>up</stem>
+        <beam number="1">begin</beam>
+      </note>
+      
+      <note>
+        <pitch>
+          <step>G</step>
+          <octave>5</octave>
+        </pitch>
+        <duration>128</duration>
+        <type>eighth</type>
+        <voice>1</voice>
+        <staff>1</staff>
+        <stem>up</stem>
+        <beam number="1">end</beam>
+      </note>
+      
+      <note>
+        <pitch>
+          <step>A</step>
+          <octave>5</octave>
+        </pitch>
+        <duration>128</duration>
+        <type>eighth</type>
+        <voice>1</voice>
+        <staff>1</staff>
+        <stem>up</stem>
+        <beam number="1">begin</beam>
+      </note>
+      
+      <note>
+        <pitch>
+          <step>B</step>
+          <octave>5</octave>
+        </pitch>
+        <duration>128</duration>
+        <type>eighth</type>
+        <voice>1</voice>
+        <staff>1</staff>
+        <stem>up</stem>
+        <beam number="1">end</beam>
+      </note>
+      
+      <note>
+        <pitch>
+          <step>C</step>
+          <octave>6</octave>
+        </pitch>
+        <duration>128</duration>
+        <type>eighth</type>
+        <voice>1</voice>
+        <staff>1</staff>
+        <stem>up</stem>
+        <beam number="1">begin</beam>
+      </note>
+      
+      <note>
+        <pitch>
+          <step>D</step>
+          <octave>6</octave>
+        </pitch>
+        <duration>128</duration>
+        <type>eighth</type>
+        <voice>1</voice>
+        <staff>1</staff>
+        <stem>up</stem>
+        <beam number="1">end</beam>
+      </note>
+      
+      <!-- 声部2(低声部):全音符 -->
+      <backup>
+        <duration>1024</duration>
+      </backup>
+      
+      <note>
+        <pitch>
+          <step>D</step>
+          <octave>3</octave>
+        </pitch>
+        <duration>1024</duration>
+        <type>whole</type>
+        <voice>2</voice>
+        <staff>2</staff>
+      </note>
+    </measure>
+
+    <!-- 第3小节:和弦测试(同一声部多个音符同时发声) -->
+    <measure number="3">
+      <!-- 声部1:和弦 C-E-G -->
+      <note>
+        <pitch>
+          <step>C</step>
+          <octave>5</octave>
+        </pitch>
+        <duration>512</duration>
+        <type>half</type>
+        <voice>1</voice>
+        <staff>1</staff>
+        <stem>up</stem>
+      </note>
+      
+      <note>
+        <chord/>
+        <pitch>
+          <step>E</step>
+          <octave>5</octave>
+        </pitch>
+        <duration>512</duration>
+        <type>half</type>
+        <voice>1</voice>
+        <staff>1</staff>
+        <stem>up</stem>
+      </note>
+      
+      <note>
+        <chord/>
+        <pitch>
+          <step>G</step>
+          <octave>5</octave>
+        </pitch>
+        <duration>512</duration>
+        <type>half</type>
+        <voice>1</voice>
+        <staff>1</staff>
+        <stem>up</stem>
+      </note>
+      
+      <!-- 声部1:和弦 D-F-A -->
+      <note>
+        <pitch>
+          <step>D</step>
+          <octave>5</octave>
+        </pitch>
+        <duration>512</duration>
+        <type>half</type>
+        <voice>1</voice>
+        <staff>1</staff>
+        <stem>up</stem>
+      </note>
+      
+      <note>
+        <chord/>
+        <pitch>
+          <step>F</step>
+          <octave>5</octave>
+        </pitch>
+        <duration>512</duration>
+        <type>half</type>
+        <voice>1</voice>
+        <staff>1</staff>
+        <stem>up</stem>
+      </note>
+      
+      <note>
+        <chord/>
+        <pitch>
+          <step>A</step>
+          <octave>5</octave>
+        </pitch>
+        <duration>512</duration>
+        <type>half</type>
+        <voice>1</voice>
+        <staff>1</staff>
+        <stem>up</stem>
+      </note>
+      
+      <!-- 声部2:低音二分音符 -->
+      <backup>
+        <duration>1024</duration>
+      </backup>
+      
+      <note>
+        <pitch>
+          <step>C</step>
+          <octave>3</octave>
+        </pitch>
+        <duration>512</duration>
+        <type>half</type>
+        <voice>2</voice>
+        <staff>2</staff>
+        <stem>down</stem>
+      </note>
+      
+      <note>
+        <pitch>
+          <step>D</step>
+          <octave>3</octave>
+        </pitch>
+        <duration>512</duration>
+        <type>half</type>
+        <voice>2</voice>
+        <staff>2</staff>
+        <stem>down</stem>
+      </note>
+    </measure>
+
+    <!-- 第4小节:休止符与声部对齐 -->
+    <measure number="4">
+      <!-- 声部1:四分音符 + 休止符交替 -->
+      <note>
+        <pitch>
+          <step>E</step>
+          <octave>5</octave>
+        </pitch>
+        <duration>256</duration>
+        <type>quarter</type>
+        <voice>1</voice>
+        <staff>1</staff>
+        <stem>up</stem>
+      </note>
+      
+      <note>
+        <rest/>
+        <duration>256</duration>
+        <type>quarter</type>
+        <voice>1</voice>
+        <staff>1</staff>
+      </note>
+      
+      <note>
+        <pitch>
+          <step>G</step>
+          <octave>5</octave>
+        </pitch>
+        <duration>256</duration>
+        <type>quarter</type>
+        <voice>1</voice>
+        <staff>1</staff>
+        <stem>up</stem>
+      </note>
+      
+      <note>
+        <rest/>
+        <duration>256</duration>
+        <type>quarter</type>
+        <voice>1</voice>
+        <staff>1</staff>
+      </note>
+      
+      <!-- 声部2:连续的四分音符 -->
+      <backup>
+        <duration>1024</duration>
+      </backup>
+      
+      <note>
+        <pitch>
+          <step>E</step>
+          <octave>3</octave>
+        </pitch>
+        <duration>256</duration>
+        <type>quarter</type>
+        <voice>2</voice>
+        <staff>2</staff>
+        <stem>down</stem>
+      </note>
+      
+      <note>
+        <pitch>
+          <step>F</step>
+          <octave>3</octave>
+        </pitch>
+        <duration>256</duration>
+        <type>quarter</type>
+        <voice>2</voice>
+        <staff>2</staff>
+        <stem>down</stem>
+      </note>
+      
+      <note>
+        <pitch>
+          <step>G</step>
+          <octave>3</octave>
+        </pitch>
+        <duration>256</duration>
+        <type>quarter</type>
+        <voice>2</voice>
+        <staff>2</staff>
+        <stem>down</stem>
+      </note>
+      
+      <note>
+        <pitch>
+          <step>A</step>
+          <octave>3</octave>
+        </pitch>
+        <duration>256</duration>
+        <type>quarter</type>
+        <voice>2</voice>
+        <staff>2</staff>
+        <stem>down</stem>
+      </note>
+      
+      <barline location="right">
+        <bar-style>light-heavy</bar-style>
+      </barline>
+    </measure>
+  </part>
+</score-partwise>

+ 342 - 0
src/jianpu-renderer/__tests__/fixtures/with-lyrics.xml

@@ -0,0 +1,342 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE score-partwise PUBLIC "-//Recordare//DTD MusicXML 4.0 Partwise//EN" "http://www.musicxml.org/dtds/partwise.dtd">
+<!--
+  带歌词测试文件
+  内容:带中文歌词的简单旋律,包含多遍歌词
+  用途:测试歌词解析、歌词渲染、歌词高亮
+  divisions: 256
+  歌词:第一遍"小星星亮晶晶",第二遍"一闪一闪放光明"
+-->
+<score-partwise version="4.0">
+  <work>
+    <work-title>小星星(带歌词测试)</work-title>
+  </work>
+  <identification>
+    <creator type="composer">莫扎特</creator>
+    <creator type="lyricist">简·泰勒</creator>
+  </identification>
+  
+  <part-list>
+    <score-part id="P1">
+      <part-name>Voice</part-name>
+    </score-part>
+  </part-list>
+
+  <part id="P1">
+    <!-- 第1小节:do do sol sol -->
+    <measure number="1">
+      <attributes>
+        <divisions>256</divisions>
+        <key>
+          <fifths>0</fifths>
+          <mode>major</mode>
+        </key>
+        <time>
+          <beats>4</beats>
+          <beat-type>4</beat-type>
+        </time>
+        <clef>
+          <sign>G</sign>
+          <line>2</line>
+        </clef>
+      </attributes>
+      
+      <direction placement="above">
+        <sound tempo="100"/>
+      </direction>
+      
+      <!-- do "小"/"一" -->
+      <note>
+        <pitch>
+          <step>C</step>
+          <octave>4</octave>
+        </pitch>
+        <duration>256</duration>
+        <type>quarter</type>
+        <voice>1</voice>
+        <stem>up</stem>
+        <lyric number="1">
+          <syllabic>single</syllabic>
+          <text>小</text>
+        </lyric>
+        <lyric number="2">
+          <syllabic>single</syllabic>
+          <text>一</text>
+        </lyric>
+      </note>
+      
+      <!-- do "星"/"闪" -->
+      <note>
+        <pitch>
+          <step>C</step>
+          <octave>4</octave>
+        </pitch>
+        <duration>256</duration>
+        <type>quarter</type>
+        <voice>1</voice>
+        <stem>up</stem>
+        <lyric number="1">
+          <syllabic>single</syllabic>
+          <text>星</text>
+        </lyric>
+        <lyric number="2">
+          <syllabic>single</syllabic>
+          <text>闪</text>
+        </lyric>
+      </note>
+      
+      <!-- sol "星"/"一" -->
+      <note>
+        <pitch>
+          <step>G</step>
+          <octave>4</octave>
+        </pitch>
+        <duration>256</duration>
+        <type>quarter</type>
+        <voice>1</voice>
+        <stem>up</stem>
+        <lyric number="1">
+          <syllabic>single</syllabic>
+          <text>星</text>
+        </lyric>
+        <lyric number="2">
+          <syllabic>single</syllabic>
+          <text>一</text>
+        </lyric>
+      </note>
+      
+      <!-- sol "亮"/"闪" -->
+      <note>
+        <pitch>
+          <step>G</step>
+          <octave>4</octave>
+        </pitch>
+        <duration>256</duration>
+        <type>quarter</type>
+        <voice>1</voice>
+        <stem>up</stem>
+        <lyric number="1">
+          <syllabic>single</syllabic>
+          <text>亮</text>
+        </lyric>
+        <lyric number="2">
+          <syllabic>single</syllabic>
+          <text>闪</text>
+        </lyric>
+      </note>
+    </measure>
+
+    <!-- 第2小节:la la sol - -->
+    <measure number="2">
+      <!-- la "晶"/"放" -->
+      <note>
+        <pitch>
+          <step>A</step>
+          <octave>4</octave>
+        </pitch>
+        <duration>256</duration>
+        <type>quarter</type>
+        <voice>1</voice>
+        <stem>up</stem>
+        <lyric number="1">
+          <syllabic>single</syllabic>
+          <text>晶</text>
+        </lyric>
+        <lyric number="2">
+          <syllabic>single</syllabic>
+          <text>放</text>
+        </lyric>
+      </note>
+      
+      <!-- la "晶"/"光" -->
+      <note>
+        <pitch>
+          <step>A</step>
+          <octave>4</octave>
+        </pitch>
+        <duration>256</duration>
+        <type>quarter</type>
+        <voice>1</voice>
+        <stem>up</stem>
+        <lyric number="1">
+          <syllabic>single</syllabic>
+          <text>晶</text>
+        </lyric>
+        <lyric number="2">
+          <syllabic>single</syllabic>
+          <text>光</text>
+        </lyric>
+      </note>
+      
+      <!-- sol - (二分音符) "——"/"明——" -->
+      <note>
+        <pitch>
+          <step>G</step>
+          <octave>4</octave>
+        </pitch>
+        <duration>512</duration>
+        <type>half</type>
+        <voice>1</voice>
+        <stem>up</stem>
+        <lyric number="1">
+          <syllabic>single</syllabic>
+          <text>——</text>
+        </lyric>
+        <lyric number="2">
+          <syllabic>single</syllabic>
+          <text>明</text>
+        </lyric>
+      </note>
+    </measure>
+
+    <!-- 第3小节:fa fa mi mi -->
+    <measure number="3">
+      <!-- fa "满"/"好" -->
+      <note>
+        <pitch>
+          <step>F</step>
+          <octave>4</octave>
+        </pitch>
+        <duration>256</duration>
+        <type>quarter</type>
+        <voice>1</voice>
+        <stem>up</stem>
+        <lyric number="1">
+          <syllabic>single</syllabic>
+          <text>满</text>
+        </lyric>
+        <lyric number="2">
+          <syllabic>single</syllabic>
+          <text>好</text>
+        </lyric>
+      </note>
+      
+      <!-- fa "天"/"像" -->
+      <note>
+        <pitch>
+          <step>F</step>
+          <octave>4</octave>
+        </pitch>
+        <duration>256</duration>
+        <type>quarter</type>
+        <voice>1</voice>
+        <stem>up</stem>
+        <lyric number="1">
+          <syllabic>single</syllabic>
+          <text>天</text>
+        </lyric>
+        <lyric number="2">
+          <syllabic>single</syllabic>
+          <text>像</text>
+        </lyric>
+      </note>
+      
+      <!-- mi "都"/"那" -->
+      <note>
+        <pitch>
+          <step>E</step>
+          <octave>4</octave>
+        </pitch>
+        <duration>256</duration>
+        <type>quarter</type>
+        <voice>1</voice>
+        <stem>up</stem>
+        <lyric number="1">
+          <syllabic>single</syllabic>
+          <text>都</text>
+        </lyric>
+        <lyric number="2">
+          <syllabic>single</syllabic>
+          <text>那</text>
+        </lyric>
+      </note>
+      
+      <!-- mi "是"/"许" -->
+      <note>
+        <pitch>
+          <step>E</step>
+          <octave>4</octave>
+        </pitch>
+        <duration>256</duration>
+        <type>quarter</type>
+        <voice>1</voice>
+        <stem>up</stem>
+        <lyric number="1">
+          <syllabic>single</syllabic>
+          <text>是</text>
+        </lyric>
+        <lyric number="2">
+          <syllabic>single</syllabic>
+          <text>许</text>
+        </lyric>
+      </note>
+    </measure>
+
+    <!-- 第4小节:re re do - -->
+    <measure number="4">
+      <!-- re "小"/"多" -->
+      <note>
+        <pitch>
+          <step>D</step>
+          <octave>4</octave>
+        </pitch>
+        <duration>256</duration>
+        <type>quarter</type>
+        <voice>1</voice>
+        <stem>up</stem>
+        <lyric number="1">
+          <syllabic>single</syllabic>
+          <text>小</text>
+        </lyric>
+        <lyric number="2">
+          <syllabic>single</syllabic>
+          <text>多</text>
+        </lyric>
+      </note>
+      
+      <!-- re "星"/"小" -->
+      <note>
+        <pitch>
+          <step>D</step>
+          <octave>4</octave>
+        </pitch>
+        <duration>256</duration>
+        <type>quarter</type>
+        <voice>1</voice>
+        <stem>up</stem>
+        <lyric number="1">
+          <syllabic>single</syllabic>
+          <text>星</text>
+        </lyric>
+        <lyric number="2">
+          <syllabic>single</syllabic>
+          <text>小</text>
+        </lyric>
+      </note>
+      
+      <!-- do - (二分音符) "——"/"眼睛" -->
+      <note>
+        <pitch>
+          <step>C</step>
+          <octave>4</octave>
+        </pitch>
+        <duration>512</duration>
+        <type>half</type>
+        <voice>1</voice>
+        <stem>up</stem>
+        <lyric number="1">
+          <syllabic>single</syllabic>
+          <text>——</text>
+        </lyric>
+        <lyric number="2">
+          <syllabic>single</syllabic>
+          <text>睛</text>
+        </lyric>
+      </note>
+      
+      <barline location="right">
+        <bar-style>light-heavy</bar-style>
+      </barline>
+    </measure>
+  </part>
+</score-partwise>

+ 587 - 0
src/jianpu-renderer/__tests__/integration.test.ts

@@ -0,0 +1,587 @@
+/**
+ * 解析器集成测试
+ * 
+ * 使用真实的MusicXML文件测试完整的解析流程:
+ * 1. MusicXML解析 → 模拟OSMD对象
+ * 2. OSMDDataParser解析 → JianpuScore
+ * 3. TimeCalculator计算 → 时间数据
+ * 
+ * 测试文件:
+ * - basic.xml: 基础简谱
+ * - mixed-durations.xml: 混合时值
+ */
+
+import { describe, it, expect, beforeAll, vi } from 'vitest';
+import { readFileSync } from 'fs';
+import { join } from 'path';
+import { OSMDDataParser } from '../core/parser/OSMDDataParser';
+import { TimeCalculator } from '../core/parser/TimeCalculator';
+import { DivisionsHandler } from '../core/parser/DivisionsHandler';
+
+// ==================== MusicXML 解析器 ====================
+
+/**
+ * 简单的MusicXML解析器
+ * 将MusicXML转换为模拟的OSMD对象结构
+ */
+class SimpleMusicXMLParser {
+  private divisionsHandler = new DivisionsHandler();
+
+  /**
+   * 解析MusicXML字符串
+   */
+  parse(xmlString: string): any {
+    const parser = new DOMParser();
+    const doc = parser.parseFromString(xmlString, 'text/xml');
+    
+    // 检查解析错误
+    const parseError = doc.querySelector('parsererror');
+    if (parseError) {
+      throw new Error(`XML解析错误: ${parseError.textContent}`);
+    }
+
+    return this.buildOSMDObject(doc);
+  }
+
+  private buildOSMDObject(doc: Document): any {
+    const title = doc.querySelector('work-title')?.textContent ?? 'Untitled';
+    const composer = doc.querySelector('creator[type="composer"]')?.textContent ?? '';
+    
+    // 解析小节
+    const measureElements = doc.querySelectorAll('measure');
+    const sourceMeasures: any[] = [];
+    const notes: any[] = [];
+    
+    let currentDivisions = 256;
+    let currentTempo = 120;
+    let currentTimeSignature = { numerator: 4, denominator: 4 };
+    let currentKeySignature = { keyTypeOriginal: 0, Mode: 0 };
+    
+    measureElements.forEach((measureEl, measureIndex) => {
+      // 解析attributes
+      const attributes = measureEl.querySelector('attributes');
+      if (attributes) {
+        const divisions = attributes.querySelector('divisions');
+        if (divisions) {
+          currentDivisions = parseInt(divisions.textContent ?? '256');
+          this.divisionsHandler.setDivisions(currentDivisions);
+        }
+        
+        const time = attributes.querySelector('time');
+        if (time) {
+          currentTimeSignature = {
+            numerator: parseInt(time.querySelector('beats')?.textContent ?? '4'),
+            denominator: parseInt(time.querySelector('beat-type')?.textContent ?? '4'),
+          };
+        }
+        
+        const key = attributes.querySelector('key');
+        if (key) {
+          currentKeySignature = {
+            keyTypeOriginal: parseInt(key.querySelector('fifths')?.textContent ?? '0'),
+            Mode: key.querySelector('mode')?.textContent === 'minor' ? 1 : 0,
+          };
+        }
+      }
+      
+      // 解析速度
+      const sound = measureEl.querySelector('sound[tempo]');
+      if (sound) {
+        currentTempo = parseInt(sound.getAttribute('tempo') ?? '120');
+      }
+      
+      const metronome = measureEl.querySelector('metronome per-minute');
+      if (metronome) {
+        currentTempo = parseInt(metronome.textContent ?? '120');
+      }
+      
+      // 创建SourceMeasure
+      const sourceMeasure: any = {
+        MeasureNumberXML: measureIndex + 1,
+        measureListIndex: measureIndex,
+        tempoInBPM: currentTempo,
+        ActiveTimeSignature: currentTimeSignature,
+        ActiveKeySignature: currentKeySignature,
+        Duration: { RealValue: currentTimeSignature.numerator / currentTimeSignature.denominator },
+        lastRepetitionInstructions: [],
+        verticalMeasureList: [],
+      };
+      
+      sourceMeasures.push(sourceMeasure);
+      
+      // 解析音符
+      const noteElements = measureEl.querySelectorAll('note');
+      let timestamp = 0;
+      
+      noteElements.forEach((noteEl) => {
+        const note = this.parseNote(noteEl, measureIndex, sourceMeasure, timestamp);
+        if (note) {
+          notes.push({
+            note,
+            measureIndex,
+            timestamp,
+          });
+          
+          // 更新时间戳(除非是和弦音符)
+          if (!noteEl.querySelector('chord')) {
+            timestamp += note.length.realValue;
+          }
+        }
+      });
+    });
+
+    // 构建cursor迭代器
+    let noteIndex = 0;
+    const iterator = {
+      EndReached: notes.length === 0,
+      currentVoiceEntries: notes.length > 0 ? [{
+        Notes: [notes[0]?.note],
+        notes: [notes[0]?.note],
+        ParentVoice: { VoiceId: 0 },
+      }] : [],
+      CurrentVoiceEntries: [],
+      currentMeasureIndex: 0,
+      currentTimeStamp: { RealValue: 0, realValue: 0 },
+      moveToNextVisibleVoiceEntry: () => {
+        noteIndex++;
+        if (noteIndex >= notes.length) {
+          iterator.EndReached = true;
+        } else {
+          const { note, measureIndex, timestamp } = notes[noteIndex];
+          iterator.currentVoiceEntries = [{
+            Notes: [note],
+            notes: [note],
+            ParentVoice: { VoiceId: 0 },
+          }];
+          iterator.CurrentVoiceEntries = iterator.currentVoiceEntries;
+          iterator.currentMeasureIndex = measureIndex;
+          iterator.currentTimeStamp = { RealValue: timestamp, realValue: timestamp };
+        }
+      },
+    };
+    iterator.CurrentVoiceEntries = iterator.currentVoiceEntries;
+
+    return {
+      Sheet: {
+        Title: { text: title },
+        Composer: { text: composer },
+        SourceMeasures: sourceMeasures,
+      },
+      GraphicSheet: {
+        MeasureList: sourceMeasures.map(m => [{ parentSourceMeasure: m }]),
+      },
+      cursor: {
+        Iterator: iterator,
+        reset: () => {
+          noteIndex = 0;
+          iterator.EndReached = notes.length === 0;
+          if (notes.length > 0) {
+            const { note, measureIndex, timestamp } = notes[0];
+            iterator.currentVoiceEntries = [{
+              Notes: [note],
+              notes: [note],
+              ParentVoice: { VoiceId: 0 },
+            }];
+            iterator.CurrentVoiceEntries = iterator.currentVoiceEntries;
+            iterator.currentMeasureIndex = measureIndex;
+            iterator.currentTimeStamp = { RealValue: timestamp, realValue: timestamp };
+          }
+        },
+        next: () => iterator.moveToNextVisibleVoiceEntry(),
+      },
+    };
+  }
+
+  private parseNote(noteEl: Element, measureIndex: number, sourceMeasure: any, timestamp: number): any {
+    const isRest = noteEl.querySelector('rest') !== null;
+    const durationEl = noteEl.querySelector('duration');
+    const duration = parseInt(durationEl?.textContent ?? '256');
+    const realValue = this.divisionsHandler.toRealValue(duration);
+    
+    // 解析音高
+    let pitch: any = null;
+    let halfTone = 60; // 默认C4
+    
+    if (!isRest) {
+      const pitchEl = noteEl.querySelector('pitch');
+      if (pitchEl) {
+        const step = pitchEl.querySelector('step')?.textContent ?? 'C';
+        const octave = parseInt(pitchEl.querySelector('octave')?.textContent ?? '4');
+        const alter = parseInt(pitchEl.querySelector('alter')?.textContent ?? '0');
+        
+        pitch = {
+          step,
+          octave,
+          alter,
+          frequency: this.calculateFrequency(step, octave, alter),
+        };
+        
+        halfTone = this.calculateHalfTone(step, octave, alter);
+      }
+    }
+    
+    // 解析附点
+    const dots = noteEl.querySelectorAll('dot').length;
+    
+    // 解析类型
+    const typeEl = noteEl.querySelector('type');
+    const noteType = typeEl?.textContent ?? 'quarter';
+
+    return {
+      pitch,
+      halfTone,
+      length: { realValue, RealValue: realValue },
+      isRestFlag: isRest,
+      IsRest: isRest,
+      IsGraceNote: noteEl.querySelector('grace') !== null,
+      IsChordNote: noteEl.querySelector('chord') !== null,
+      dots,
+      DotsXml: dots,
+      noteTypeXml: noteType,
+      sourceMeasure,
+      duration,
+    };
+  }
+
+  private calculateHalfTone(step: string, octave: number, alter: number): number {
+    const stepToSemitone: Record<string, number> = {
+      'C': 0, 'D': 2, 'E': 4, 'F': 5, 'G': 7, 'A': 9, 'B': 11
+    };
+    return (octave + 1) * 12 + (stepToSemitone[step] ?? 0) + alter;
+  }
+
+  private calculateFrequency(step: string, octave: number, alter: number): number {
+    const halfTone = this.calculateHalfTone(step, octave, alter);
+    return 440 * Math.pow(2, (halfTone - 69) / 12);
+  }
+}
+
+// ==================== 测试工具函数 ====================
+
+/**
+ * 加载测试XML文件
+ */
+function loadTestXML(filename: string): string {
+  const filePath = join(__dirname, 'fixtures', filename);
+  return readFileSync(filePath, 'utf-8');
+}
+
+/**
+ * 解析MusicXML为模拟OSMD对象
+ */
+function parseXMLToOSMD(xmlString: string): any {
+  const parser = new SimpleMusicXMLParser();
+  return parser.parse(xmlString);
+}
+
+// ==================== 测试用例 ====================
+
+describe('解析器集成测试', () => {
+  let xmlParser: SimpleMusicXMLParser;
+  let osmdParser: OSMDDataParser;
+  let timeCalculator: TimeCalculator;
+
+  beforeAll(() => {
+    xmlParser = new SimpleMusicXMLParser();
+    osmdParser = new OSMDDataParser();
+    timeCalculator = new TimeCalculator();
+    vi.spyOn(console, 'log').mockImplementation(() => {});
+    vi.spyOn(console, 'warn').mockImplementation(() => {});
+  });
+
+  // ==================== basic.xml 测试 ====================
+
+  describe('basic.xml - 基础简谱', () => {
+    let xmlContent: string;
+    let osmdObject: any;
+
+    beforeAll(() => {
+      xmlContent = loadTestXML('basic.xml');
+      osmdObject = parseXMLToOSMD(xmlContent);
+    });
+
+    it('应该正确解析XML文件', () => {
+      expect(osmdObject).toBeDefined();
+      expect(osmdObject.Sheet).toBeDefined();
+      expect(osmdObject.cursor).toBeDefined();
+    });
+
+    it('应该正确解析标题和作曲家', () => {
+      expect(osmdObject.Sheet.Title.text).toBe('基础简谱测试');
+      expect(osmdObject.Sheet.Composer.text).toBe('测试');
+    });
+
+    it('应该正确解析4个小节', () => {
+      expect(osmdObject.Sheet.SourceMeasures.length).toBe(4);
+    });
+
+    it('应该正确解析拍号', () => {
+      const firstMeasure = osmdObject.Sheet.SourceMeasures[0];
+      expect(firstMeasure.ActiveTimeSignature.numerator).toBe(4);
+      expect(firstMeasure.ActiveTimeSignature.denominator).toBe(4);
+    });
+
+    it('应该正确解析速度', () => {
+      const firstMeasure = osmdObject.Sheet.SourceMeasures[0];
+      expect(firstMeasure.tempoInBPM).toBe(120);
+    });
+
+    describe('OSMDDataParser解析', () => {
+      let score: any;
+
+      beforeAll(() => {
+        osmdParser = new OSMDDataParser();
+        score = osmdParser.parse(osmdObject);
+      });
+
+      it('应该返回JianpuScore对象', () => {
+        expect(score).toBeDefined();
+        expect(score.measures).toBeDefined();
+        expect(score.tempo).toBeDefined();
+      });
+
+      it('应该解析出4个小节', () => {
+        expect(score.measures.length).toBe(4);
+      });
+
+      it('应该解析出正确的音符数量', () => {
+        const stats = osmdParser.getStats();
+        // basic.xml: 4小节,每小节4个音符,共16个(包含休止符)
+        expect(stats.noteCount).toBeGreaterThanOrEqual(14);
+      });
+
+      it('第1小节应该包含do re mi fa', () => {
+        const notes = score.measures[0].voices[0];
+        expect(notes.length).toBeGreaterThanOrEqual(4);
+        
+        // 验证音高
+        const pitches = notes.slice(0, 4).map((n: any) => n.pitch);
+        expect(pitches).toContain(1); // do
+        expect(pitches).toContain(2); // re
+        expect(pitches).toContain(3); // mi
+        expect(pitches).toContain(4); // fa
+      });
+
+      it('第3小节应该包含休止符', () => {
+        const notes = score.measures[2].voices[0];
+        const hasRest = notes.some((n: any) => n.isRest);
+        expect(hasRest).toBe(true);
+      });
+
+      it('第4小节应该包含不同八度的音符', () => {
+        const notes = score.measures[3].voices[0];
+        const octaves = notes.map((n: any) => n.octave);
+        
+        // 应该有低音(-1)、中音(0)、高音(1)
+        expect(octaves).toContain(-1); // G3 -> octave -1
+        expect(octaves).toContain(0);  // C4 -> octave 0
+        expect(octaves).toContain(1);  // E5, G5 -> octave 1
+      });
+    });
+
+    describe('TimeCalculator计算', () => {
+      let score: any;
+
+      beforeAll(() => {
+        osmdParser = new OSMDDataParser();
+        score = osmdParser.parse(osmdObject);
+        timeCalculator.calculateTimes(score);
+      });
+
+      it('应该为所有音符计算时间', () => {
+        for (const measure of score.measures) {
+          for (const voice of measure.voices) {
+            for (const note of voice) {
+              expect(note.startTime).toBeDefined();
+              expect(note.endTime).toBeDefined();
+              expect(note.endTime).toBeGreaterThan(note.startTime);
+            }
+          }
+        }
+      });
+
+      it('BPM=120时四分音符应该是0.5秒', () => {
+        const firstNote = score.measures[0].voices[0][0];
+        const duration = firstNote.endTime - firstNote.startTime;
+        expect(duration).toBeCloseTo(0.5, 2);
+      });
+
+      it('总时长应该约为8秒(4小节×4拍×0.5秒)', () => {
+        const result = timeCalculator.getResult();
+        expect(result.totalDuration).toBeCloseTo(8.0, 1);
+      });
+    });
+  });
+
+  // ==================== mixed-durations.xml 测试 ====================
+
+  describe('mixed-durations.xml - 混合时值', () => {
+    let xmlContent: string;
+    let osmdObject: any;
+
+    beforeAll(() => {
+      xmlContent = loadTestXML('mixed-durations.xml');
+      osmdObject = parseXMLToOSMD(xmlContent);
+    });
+
+    it('应该正确解析XML文件', () => {
+      expect(osmdObject).toBeDefined();
+      expect(osmdObject.Sheet.SourceMeasures.length).toBe(6);
+    });
+
+    describe('OSMDDataParser解析', () => {
+      let score: any;
+
+      beforeAll(() => {
+        osmdParser = new OSMDDataParser();
+        score = osmdParser.parse(osmdObject);
+      });
+
+      it('应该解析出6个小节', () => {
+        expect(score.measures.length).toBe(6);
+      });
+
+      it('第1小节应该包含8个八分音符', () => {
+        const notes = score.measures[0].voices[0];
+        expect(notes.length).toBe(8);
+        
+        // 验证时值都是0.5(八分音符)
+        notes.forEach((note: any) => {
+          expect(note.duration).toBeCloseTo(0.5, 2);
+        });
+      });
+
+      it('第2小节应该包含2个二分音符', () => {
+        const notes = score.measures[1].voices[0];
+        expect(notes.length).toBe(2);
+        
+        notes.forEach((note: any) => {
+          expect(note.duration).toBeCloseTo(2.0, 2);
+        });
+      });
+
+      it('第3小节应该包含1个全音符', () => {
+        const notes = score.measures[2].voices[0];
+        expect(notes.length).toBe(1);
+        expect(notes[0].duration).toBeCloseTo(4.0, 2);
+      });
+
+      it('第4小节应该包含附点音符', () => {
+        const notes = score.measures[3].voices[0];
+        
+        // 查找附点四分音符(时值1.5)
+        const dottedQuarter = notes.find((n: any) => Math.abs(n.duration - 1.5) < 0.1);
+        expect(dottedQuarter).toBeDefined();
+        expect(dottedQuarter.dots).toBe(1);
+      });
+
+      it('第6小节应该包含十六分音符', () => {
+        const notes = score.measures[5].voices[0];
+        
+        // 查找十六分音符(时值0.25)
+        const sixteenthNotes = notes.filter((n: any) => Math.abs(n.duration - 0.25) < 0.1);
+        expect(sixteenthNotes.length).toBe(4);
+      });
+    });
+
+    describe('TimeCalculator计算', () => {
+      let score: any;
+
+      beforeAll(() => {
+        osmdParser = new OSMDDataParser();
+        score = osmdParser.parse(osmdObject);
+        timeCalculator.calculateTimes(score);
+      });
+
+      it('八分音符时长应该是0.3秒(BPM=100)', () => {
+        const eighthNote = score.measures[0].voices[0][0];
+        const duration = eighthNote.endTime - eighthNote.startTime;
+        // BPM=100, 四分音符=0.6秒, 八分音符=0.3秒
+        expect(duration).toBeCloseTo(0.3, 2);
+      });
+
+      it('全音符时长应该是2.4秒(BPM=100)', () => {
+        const wholeNote = score.measures[2].voices[0][0];
+        const duration = wholeNote.endTime - wholeNote.startTime;
+        // BPM=100, 四分音符=0.6秒, 全音符=2.4秒
+        expect(duration).toBeCloseTo(2.4, 2);
+      });
+
+      it('总时长应该约为14.4秒(6小节×4拍×0.6秒)', () => {
+        const result = timeCalculator.getResult();
+        expect(result.totalDuration).toBeCloseTo(14.4, 1);
+      });
+    });
+  });
+
+  // ==================== 性能测试 ====================
+
+  describe('性能测试', () => {
+    it('解析basic.xml应该在100ms内完成', () => {
+      const xmlContent = loadTestXML('basic.xml');
+      
+      const startTime = performance.now();
+      const osmdObject = parseXMLToOSMD(xmlContent);
+      const parser = new OSMDDataParser();
+      const score = parser.parse(osmdObject);
+      const calc = new TimeCalculator();
+      calc.calculateTimes(score);
+      const endTime = performance.now();
+      
+      expect(endTime - startTime).toBeLessThan(100);
+    });
+
+    it('解析mixed-durations.xml应该在100ms内完成', () => {
+      const xmlContent = loadTestXML('mixed-durations.xml');
+      
+      const startTime = performance.now();
+      const osmdObject = parseXMLToOSMD(xmlContent);
+      const parser = new OSMDDataParser();
+      const score = parser.parse(osmdObject);
+      const calc = new TimeCalculator();
+      calc.calculateTimes(score);
+      const endTime = performance.now();
+      
+      expect(endTime - startTime).toBeLessThan(100);
+    });
+  });
+
+  // ==================== 边界情况测试 ====================
+
+  describe('边界情况', () => {
+    it('空XML应该抛出错误', () => {
+      expect(() => parseXMLToOSMD('')).toThrow();
+    });
+
+    it('无效XML应该抛出错误', () => {
+      expect(() => parseXMLToOSMD('<invalid>')).toThrow();
+    });
+  });
+});
+
+// ==================== DivisionsHandler 集成测试 ====================
+
+describe('DivisionsHandler 集成测试', () => {
+  it('应该正确处理basic.xml的divisions=256', () => {
+    const handler = new DivisionsHandler();
+    handler.setDivisions(256);
+    
+    // 四分音符 duration=256
+    expect(handler.toRealValue(256)).toBe(1.0);
+    
+    // 八分音符 duration=128
+    expect(handler.toRealValue(128)).toBe(0.5);
+    
+    // 二分音符 duration=512
+    expect(handler.toRealValue(512)).toBe(2.0);
+    
+    // 全音符 duration=1024
+    expect(handler.toRealValue(1024)).toBe(4.0);
+    
+    // 附点四分音符 duration=384
+    expect(handler.toRealValue(384)).toBe(1.5);
+    
+    // 十六分音符 duration=64
+    expect(handler.toRealValue(64)).toBe(0.25);
+  });
+});

+ 233 - 0
src/jianpu-renderer/__tests__/models.test.ts

@@ -0,0 +1,233 @@
+/**
+ * 数据模型单元测试
+ * 测试JianpuNote、JianpuMeasure、JianpuScore等数据模型
+ */
+import { describe, it, expect, beforeEach, afterEach } from 'vitest';
+import {
+  JianpuNote,
+  JianpuMeasure,
+  JianpuSystem,
+  JianpuScore,
+  createDefaultNote,
+  createDefaultMeasure,
+  Accidental,
+} from '../models';
+import { testUtils } from './setup';
+
+describe('JianpuNote 音符模型', () => {
+  describe('createDefaultNote()', () => {
+    it('应该创建默认音符对象', () => {
+      const note = createDefaultNote();
+      
+      expect(note).toBeDefined();
+      expect(note.id).toBeDefined();
+      expect(note.pitch).toBe(1);
+      expect(note.octave).toBe(0);
+      expect(note.duration).toBe(1);
+      expect(note.isRest).toBe(false);
+    });
+    
+    it('应该支持自定义属性覆盖', () => {
+      const note = createDefaultNote({
+        pitch: 5,
+        octave: 1,
+        duration: 2,
+        dots: 1,
+      });
+      
+      expect(note.pitch).toBe(5);
+      expect(note.octave).toBe(1);
+      expect(note.duration).toBe(2);
+      expect(note.dots).toBe(1);
+    });
+    
+    it('应该为每个音符生成唯一ID', () => {
+      const note1 = createDefaultNote();
+      const note2 = createDefaultNote();
+      
+      expect(note1.id).not.toBe(note2.id);
+    });
+  });
+  
+  describe('音高属性', () => {
+    it('pitch应该在1-7范围内(不包括休止符)', () => {
+      const validPitches = [1, 2, 3, 4, 5, 6, 7];
+      
+      validPitches.forEach(pitch => {
+        const note = createDefaultNote({ pitch });
+        expect(note.pitch).toBe(pitch);
+      });
+    });
+    
+    it('休止符的pitch应该为0', () => {
+      const rest = createDefaultNote({ pitch: 0, isRest: true });
+      expect(rest.pitch).toBe(0);
+      expect(rest.isRest).toBe(true);
+    });
+    
+    it('octave偏移应该正确表示高低音', () => {
+      // 低音(octave < 0)
+      const lowNote = createDefaultNote({ pitch: 5, octave: -1 });
+      expect(lowNote.octave).toBe(-1);
+      
+      // 中音(octave = 0)
+      const midNote = createDefaultNote({ pitch: 1, octave: 0 });
+      expect(midNote.octave).toBe(0);
+      
+      // 高音(octave > 0)
+      const highNote = createDefaultNote({ pitch: 3, octave: 1 });
+      expect(highNote.octave).toBe(1);
+    });
+  });
+  
+  describe('时值属性', () => {
+    it('duration应该表示以四分音符为单位的时值', () => {
+      // 四分音符
+      const quarter = createDefaultNote({ duration: 1 });
+      expect(quarter.duration).toBe(1);
+      
+      // 八分音符
+      const eighth = createDefaultNote({ duration: 0.5 });
+      expect(eighth.duration).toBe(0.5);
+      
+      // 二分音符
+      const half = createDefaultNote({ duration: 2 });
+      expect(half.duration).toBe(2);
+      
+      // 全音符
+      const whole = createDefaultNote({ duration: 4 });
+      expect(whole.duration).toBe(4);
+    });
+    
+    it('附点应该正确设置', () => {
+      // 单附点
+      const dottedQuarter = createDefaultNote({ duration: 1.5, dots: 1 });
+      expect(dottedQuarter.dots).toBe(1);
+      
+      // 双附点
+      const doubleDotted = createDefaultNote({ duration: 1.75, dots: 2 });
+      expect(doubleDotted.dots).toBe(2);
+    });
+  });
+  
+  describe('升降号属性', () => {
+    it('应该支持升号', () => {
+      const sharp = createDefaultNote({ accidental: Accidental.Sharp });
+      expect(sharp.accidental).toBe(Accidental.Sharp);
+    });
+    
+    it('应该支持降号', () => {
+      const flat = createDefaultNote({ accidental: Accidental.Flat });
+      expect(flat.accidental).toBe(Accidental.Flat);
+    });
+    
+    it('应该支持还原号', () => {
+      const natural = createDefaultNote({ accidental: Accidental.Natural });
+      expect(natural.accidental).toBe(Accidental.Natural);
+    });
+  });
+});
+
+describe('JianpuMeasure 小节模型', () => {
+  describe('createDefaultMeasure()', () => {
+    it('应该创建默认小节对象', () => {
+      const measure = createDefaultMeasure(1);
+      
+      expect(measure).toBeDefined();
+      expect(measure.index).toBe(0);
+      expect(measure.measureNumber).toBe(1);
+      expect(measure.voices).toHaveLength(1);
+      expect(measure.voices[0]).toHaveLength(0);
+    });
+    
+    it('应该设置默认拍号为4/4', () => {
+      const measure = createDefaultMeasure(1);
+      
+      expect(measure.timeSignature.beats).toBe(4);
+      expect(measure.timeSignature.beatType).toBe(4);
+    });
+  });
+  
+  describe('小节内音符管理', () => {
+    it('应该能添加音符到指定声部', () => {
+      const measure = createDefaultMeasure(1);
+      const note = createDefaultNote({ pitch: 1 });
+      
+      measure.voices[0].push(note);
+      
+      expect(measure.voices[0]).toHaveLength(1);
+      expect(measure.voices[0][0].pitch).toBe(1);
+    });
+    
+    it('应该支持多声部', () => {
+      const measure = createDefaultMeasure(1);
+      measure.voices.push([]); // 添加第二声部
+      
+      const note1 = createDefaultNote({ pitch: 1 });
+      const note2 = createDefaultNote({ pitch: 5 });
+      
+      measure.voices[0].push(note1);
+      measure.voices[1].push(note2);
+      
+      expect(measure.voices).toHaveLength(2);
+      expect(measure.voices[0][0].pitch).toBe(1);
+      expect(measure.voices[1][0].pitch).toBe(5);
+    });
+  });
+  
+  describe('拍号计算', () => {
+    it('应该正确计算小节总时值(4/4拍)', () => {
+      const measure = createDefaultMeasure(1, { beats: 4, beatType: 4 });
+      const totalBeats = measure.timeSignature.beats / (measure.timeSignature.beatType / 4);
+      expect(totalBeats).toBe(4);
+    });
+    
+    it('应该正确计算小节总时值(3/4拍)', () => {
+      const measure = createDefaultMeasure(1, { beats: 3, beatType: 4 });
+      const totalBeats = measure.timeSignature.beats / (measure.timeSignature.beatType / 4);
+      expect(totalBeats).toBe(3);
+    });
+    
+    it('应该正确计算小节总时值(6/8拍)', () => {
+      const measure = createDefaultMeasure(1, { beats: 6, beatType: 8 });
+      const totalBeats = measure.timeSignature.beats / (measure.timeSignature.beatType / 4);
+      expect(totalBeats).toBe(3); // 6/8拍 = 3个四分音符
+    });
+  });
+});
+
+describe('JianpuScore 总谱模型', () => {
+  it('应该能创建空的总谱', () => {
+    const score: JianpuScore = {
+      title: '测试曲谱',
+      composer: '测试作曲家',
+      tempo: 120,
+      systems: [],
+      measures: [],
+      metadata: {},
+    };
+    
+    expect(score.title).toBe('测试曲谱');
+    expect(score.tempo).toBe(120);
+    expect(score.measures).toHaveLength(0);
+  });
+  
+  it('应该能添加小节', () => {
+    const score: JianpuScore = {
+      title: '测试',
+      tempo: 120,
+      systems: [],
+      measures: [],
+      metadata: {},
+    };
+    
+    const measure1 = createDefaultMeasure(1);
+    const measure2 = createDefaultMeasure(2);
+    
+    score.measures.push(measure1, measure2);
+    
+    expect(score.measures).toHaveLength(2);
+    expect(score.measures[0].measureNumber).toBe(1);
+    expect(score.measures[1].measureNumber).toBe(2);
+  });
+});

+ 406 - 0
src/jianpu-renderer/__tests__/parser.test.ts

@@ -0,0 +1,406 @@
+/**
+ * 解析器单元测试
+ * 测试MusicXML解析、时值计算等功能
+ */
+import { describe, it, expect, beforeEach, afterEach } from 'vitest';
+import { testUtils } from './setup';
+
+describe('MusicXML 解析', () => {
+  describe('Divisions 处理', () => {
+    it('应该正确解析divisions值', () => {
+      const xml = `
+        <attributes>
+          <divisions>256</divisions>
+        </attributes>
+      `;
+      
+      const doc = testUtils.parseXML(`<root>${xml}</root>`);
+      const divisions = doc.querySelector('divisions');
+      
+      expect(divisions).not.toBeNull();
+      expect(parseInt(divisions!.textContent || '0')).toBe(256);
+    });
+    
+    it('应该正确转换duration到实际时值 (divisions=256)', () => {
+      const divisions = 256;
+      
+      // 四分音符
+      expect(256 / divisions).toBe(1.0);
+      
+      // 八分音符
+      expect(128 / divisions).toBe(0.5);
+      
+      // 二分音符
+      expect(512 / divisions).toBe(2.0);
+      
+      // 全音符
+      expect(1024 / divisions).toBe(4.0);
+      
+      // 附点四分音符
+      expect(384 / divisions).toBe(1.5);
+    });
+    
+    it('应该正确转换duration到实际时值 (divisions=480)', () => {
+      const divisions = 480;
+      
+      // 四分音符
+      expect(480 / divisions).toBe(1.0);
+      
+      // 八分音符
+      expect(240 / divisions).toBe(0.5);
+      
+      // 二分音符
+      expect(960 / divisions).toBe(2.0);
+    });
+    
+    it('应该正确转换duration到实际时值 (divisions=1)', () => {
+      const divisions = 1;
+      
+      // 四分音符
+      expect(1 / divisions).toBe(1.0);
+      
+      // 八分音符
+      expect(0.5 / divisions).toBe(0.5);
+    });
+  });
+  
+  describe('音高解析', () => {
+    const NOTE_TO_JIANPU: Record<string, number> = {
+      'C': 1, 'D': 2, 'E': 3, 'F': 4, 'G': 5, 'A': 6, 'B': 7,
+    };
+    
+    it('应该正确将音名转换为简谱数字', () => {
+      expect(NOTE_TO_JIANPU['C']).toBe(1);
+      expect(NOTE_TO_JIANPU['D']).toBe(2);
+      expect(NOTE_TO_JIANPU['E']).toBe(3);
+      expect(NOTE_TO_JIANPU['F']).toBe(4);
+      expect(NOTE_TO_JIANPU['G']).toBe(5);
+      expect(NOTE_TO_JIANPU['A']).toBe(6);
+      expect(NOTE_TO_JIANPU['B']).toBe(7);
+    });
+    
+    it('应该正确解析pitch元素', () => {
+      const xml = `
+        <pitch>
+          <step>G</step>
+          <octave>4</octave>
+        </pitch>
+      `;
+      
+      const doc = testUtils.parseXML(`<root>${xml}</root>`);
+      const step = doc.querySelector('step')?.textContent;
+      const octave = parseInt(doc.querySelector('octave')?.textContent || '4');
+      
+      expect(step).toBe('G');
+      expect(octave).toBe(4);
+      expect(NOTE_TO_JIANPU[step!]).toBe(5);
+    });
+    
+    it('应该正确计算八度偏移', () => {
+      // 中央C在octave=4
+      const baseOctave = 4;
+      
+      // octave=3 → 低音(偏移-1)
+      expect(3 - baseOctave).toBe(-1);
+      
+      // octave=4 → 中音(偏移0)
+      expect(4 - baseOctave).toBe(0);
+      
+      // octave=5 → 高音(偏移+1)
+      expect(5 - baseOctave).toBe(1);
+      
+      // octave=6 → 高两个八度(偏移+2)
+      expect(6 - baseOctave).toBe(2);
+    });
+    
+    it('应该正确解析升降号', () => {
+      const xmlSharp = `<pitch><step>F</step><alter>1</alter><octave>4</octave></pitch>`;
+      const xmlFlat = `<pitch><step>B</step><alter>-1</alter><octave>4</octave></pitch>`;
+      const xmlNatural = `<pitch><step>F</step><alter>0</alter><octave>4</octave></pitch>`;
+      
+      const docSharp = testUtils.parseXML(`<root>${xmlSharp}</root>`);
+      const docFlat = testUtils.parseXML(`<root>${xmlFlat}</root>`);
+      const docNatural = testUtils.parseXML(`<root>${xmlNatural}</root>`);
+      
+      expect(parseInt(docSharp.querySelector('alter')?.textContent || '0')).toBe(1);
+      expect(parseInt(docFlat.querySelector('alter')?.textContent || '0')).toBe(-1);
+      expect(parseInt(docNatural.querySelector('alter')?.textContent || '0')).toBe(0);
+    });
+  });
+  
+  describe('休止符解析', () => {
+    it('应该正确识别休止符', () => {
+      const xml = `
+        <note>
+          <rest/>
+          <duration>256</duration>
+          <type>quarter</type>
+        </note>
+      `;
+      
+      const doc = testUtils.parseXML(`<root>${xml}</root>`);
+      const rest = doc.querySelector('rest');
+      
+      expect(rest).not.toBeNull();
+    });
+    
+    it('休止符应该有duration', () => {
+      const xml = `
+        <note>
+          <rest/>
+          <duration>512</duration>
+          <type>half</type>
+        </note>
+      `;
+      
+      const doc = testUtils.parseXML(`<root>${xml}</root>`);
+      const duration = parseInt(doc.querySelector('duration')?.textContent || '0');
+      
+      expect(duration).toBe(512);
+    });
+  });
+  
+  describe('歌词解析', () => {
+    it('应该正确解析单行歌词', () => {
+      const xml = `
+        <note>
+          <pitch><step>C</step><octave>4</octave></pitch>
+          <duration>256</duration>
+          <lyric number="1">
+            <syllabic>single</syllabic>
+            <text>小</text>
+          </lyric>
+        </note>
+      `;
+      
+      const doc = testUtils.parseXML(`<root>${xml}</root>`);
+      const lyric = doc.querySelector('lyric');
+      const text = lyric?.querySelector('text')?.textContent;
+      const number = lyric?.getAttribute('number');
+      
+      expect(text).toBe('小');
+      expect(number).toBe('1');
+    });
+    
+    it('应该正确解析多遍歌词', () => {
+      const xml = `
+        <note>
+          <pitch><step>C</step><octave>4</octave></pitch>
+          <duration>256</duration>
+          <lyric number="1">
+            <text>小</text>
+          </lyric>
+          <lyric number="2">
+            <text>一</text>
+          </lyric>
+        </note>
+      `;
+      
+      const doc = testUtils.parseXML(`<root>${xml}</root>`);
+      const lyrics = doc.querySelectorAll('lyric');
+      
+      expect(lyrics.length).toBe(2);
+      expect(lyrics[0].querySelector('text')?.textContent).toBe('小');
+      expect(lyrics[1].querySelector('text')?.textContent).toBe('一');
+    });
+  });
+  
+  describe('拍号解析', () => {
+    it('应该正确解析4/4拍', () => {
+      const xml = `
+        <time>
+          <beats>4</beats>
+          <beat-type>4</beat-type>
+        </time>
+      `;
+      
+      const doc = testUtils.parseXML(`<root>${xml}</root>`);
+      const beats = parseInt(doc.querySelector('beats')?.textContent || '0');
+      const beatType = parseInt(doc.querySelector('beat-type')?.textContent || '0');
+      
+      expect(beats).toBe(4);
+      expect(beatType).toBe(4);
+    });
+    
+    it('应该正确解析3/4拍', () => {
+      const xml = `
+        <time>
+          <beats>3</beats>
+          <beat-type>4</beat-type>
+        </time>
+      `;
+      
+      const doc = testUtils.parseXML(`<root>${xml}</root>`);
+      const beats = parseInt(doc.querySelector('beats')?.textContent || '0');
+      const beatType = parseInt(doc.querySelector('beat-type')?.textContent || '0');
+      
+      expect(beats).toBe(3);
+      expect(beatType).toBe(4);
+    });
+    
+    it('应该正确计算小节总时值', () => {
+      // 4/4拍 = 4个四分音符
+      expect(4 / (4 / 4)).toBe(4);
+      
+      // 3/4拍 = 3个四分音符
+      expect(3 / (4 / 4)).toBe(3);
+      
+      // 6/8拍 = 3个四分音符
+      expect(6 / (8 / 4)).toBe(3);
+      
+      // 2/2拍 = 4个四分音符
+      expect(2 / (2 / 4)).toBe(4);
+    });
+  });
+  
+  describe('调号解析', () => {
+    it('应该正确解析C大调 (fifths=0)', () => {
+      const xml = `
+        <key>
+          <fifths>0</fifths>
+          <mode>major</mode>
+        </key>
+      `;
+      
+      const doc = testUtils.parseXML(`<root>${xml}</root>`);
+      const fifths = parseInt(doc.querySelector('fifths')?.textContent || '0');
+      
+      expect(fifths).toBe(0); // C大调
+    });
+    
+    it('应该正确解析G大调 (fifths=1)', () => {
+      const xml = `
+        <key>
+          <fifths>1</fifths>
+          <mode>major</mode>
+        </key>
+      `;
+      
+      const doc = testUtils.parseXML(`<root>${xml}</root>`);
+      const fifths = parseInt(doc.querySelector('fifths')?.textContent || '0');
+      
+      expect(fifths).toBe(1); // G大调(1个升号)
+    });
+    
+    it('应该正确解析F大调 (fifths=-1)', () => {
+      const xml = `
+        <key>
+          <fifths>-1</fifths>
+          <mode>major</mode>
+        </key>
+      `;
+      
+      const doc = testUtils.parseXML(`<root>${xml}</root>`);
+      const fifths = parseInt(doc.querySelector('fifths')?.textContent || '0');
+      
+      expect(fifths).toBe(-1); // F大调(1个降号)
+    });
+  });
+});
+
+describe('时值线计算', () => {
+  describe('增时线数量计算', () => {
+    /**
+     * 增时线数量 = Math.floor(时值) - 1
+     * 只有时值 >= 1(四分音符及以上)才有增时线
+     */
+    const calcExtensionLines = (realValue: number): number => {
+      if (realValue < 1) return 0;
+      return Math.floor(realValue) - 1;
+    };
+    
+    it('四分音符(1.0)应该有0条增时线', () => {
+      expect(calcExtensionLines(1.0)).toBe(0);
+    });
+    
+    it('二分音符(2.0)应该有1条增时线', () => {
+      expect(calcExtensionLines(2.0)).toBe(1);
+    });
+    
+    it('附点二分音符(3.0)应该有2条增时线', () => {
+      expect(calcExtensionLines(3.0)).toBe(2);
+    });
+    
+    it('全音符(4.0)应该有3条增时线', () => {
+      expect(calcExtensionLines(4.0)).toBe(3);
+    });
+    
+    it('附点四分音符(1.5)应该有0条增时线', () => {
+      expect(calcExtensionLines(1.5)).toBe(0);
+    });
+    
+    it('八分音符(0.5)应该有0条增时线', () => {
+      expect(calcExtensionLines(0.5)).toBe(0);
+    });
+  });
+  
+  describe('减时线数量计算', () => {
+    /**
+     * 减时线数量 = Math.log2(1 / 时值)
+     * 只有时值 < 1(短于四分音符)才有减时线
+     */
+    const calcUnderlines = (realValue: number): number => {
+      if (realValue >= 1) return 0;
+      return Math.round(Math.log2(1 / realValue));
+    };
+    
+    it('四分音符(1.0)应该有0条减时线', () => {
+      expect(calcUnderlines(1.0)).toBe(0);
+    });
+    
+    it('八分音符(0.5)应该有1条减时线', () => {
+      expect(calcUnderlines(0.5)).toBe(1);
+    });
+    
+    it('十六分音符(0.25)应该有2条减时线', () => {
+      expect(calcUnderlines(0.25)).toBe(2);
+    });
+    
+    it('三十二分音符(0.125)应该有3条减时线', () => {
+      expect(calcUnderlines(0.125)).toBe(3);
+    });
+    
+    it('二分音符(2.0)应该有0条减时线', () => {
+      expect(calcUnderlines(2.0)).toBe(0);
+    });
+  });
+});
+
+describe('时间计算', () => {
+  describe('BPM到时长转换', () => {
+    /**
+     * 一个四分音符的时长(秒)= 60 / BPM
+     */
+    const quarterDuration = (bpm: number): number => 60 / bpm;
+    
+    it('120 BPM时四分音符应该是0.5秒', () => {
+      expect(quarterDuration(120)).toBe(0.5);
+    });
+    
+    it('60 BPM时四分音符应该是1秒', () => {
+      expect(quarterDuration(60)).toBe(1);
+    });
+    
+    it('100 BPM时四分音符应该是0.6秒', () => {
+      expect(quarterDuration(100)).toBe(0.6);
+    });
+  });
+  
+  describe('音符时间计算', () => {
+    const calcNoteDuration = (realValue: number, bpm: number): number => {
+      return realValue * (60 / bpm);
+    };
+    
+    it('120 BPM时二分音符应该是1秒', () => {
+      expect(calcNoteDuration(2.0, 120)).toBe(1);
+    });
+    
+    it('120 BPM时八分音符应该是0.25秒', () => {
+      expect(calcNoteDuration(0.5, 120)).toBe(0.25);
+    });
+    
+    it('60 BPM时全音符应该是4秒', () => {
+      expect(calcNoteDuration(4.0, 60)).toBe(4);
+    });
+  });
+});

+ 0 - 18
src/jianpu-renderer/__tests__/setup.test.ts

@@ -1,18 +0,0 @@
-/**
- * 测试环境设置
- */
-
-describe('简谱渲染引擎 - 环境测试', () => {
-  it('应该能够导入主模块', () => {
-    expect(() => {
-      require('../index');
-    }).not.toThrow();
-  });
-  
-  it('应该能够创建JianpuRenderer实例', () => {
-    const { JianpuRenderer } = require('../JianpuRenderer');
-    const container = document.createElement('div');
-    const renderer = new JianpuRenderer(container);
-    expect(renderer).toBeDefined();
-  });
-});

+ 90 - 0
src/jianpu-renderer/__tests__/setup.ts

@@ -0,0 +1,90 @@
+/**
+ * Vitest 测试环境设置
+ * 在所有测试运行前执行
+ */
+
+// 模拟浏览器环境
+import { vi } from 'vitest';
+
+// 模拟SVG相关API(jsdom不完全支持SVG)
+if (typeof window !== 'undefined') {
+  // 模拟createElementNS
+  const originalCreateElementNS = document.createElementNS.bind(document);
+  document.createElementNS = (namespaceURI: string | null, qualifiedName: string) => {
+    const element = originalCreateElementNS(namespaceURI, qualifiedName);
+    
+    // 为SVG元素添加getBBox模拟
+    if (namespaceURI === 'http://www.w3.org/2000/svg') {
+      (element as any).getBBox = () => ({
+        x: 0,
+        y: 0,
+        width: 100,
+        height: 20,
+      });
+      
+      (element as any).getScreenCTM = () => ({
+        a: 1, b: 0, c: 0, d: 1, e: 0, f: 0,
+      });
+    }
+    
+    return element;
+  };
+}
+
+// 全局测试工具
+export const testUtils = {
+  /**
+   * 创建测试用的DOM容器
+   */
+  createContainer(): HTMLDivElement {
+    const container = document.createElement('div');
+    container.id = 'test-container';
+    document.body.appendChild(container);
+    return container;
+  },
+  
+  /**
+   * 清理测试容器
+   */
+  cleanupContainer(): void {
+    const container = document.getElementById('test-container');
+    if (container) {
+      container.remove();
+    }
+  },
+  
+  /**
+   * 加载测试XML文件内容
+   */
+  async loadTestXML(filename: string): Promise<string> {
+    // 在Node环境中使用fs
+    if (typeof window === 'undefined') {
+      const fs = await import('fs');
+      const path = await import('path');
+      const filePath = path.join(__dirname, 'fixtures', filename);
+      return fs.readFileSync(filePath, 'utf-8');
+    }
+    
+    // 在浏览器环境中使用fetch
+    const response = await fetch(`/src/jianpu-renderer/__tests__/fixtures/${filename}`);
+    return response.text();
+  },
+  
+  /**
+   * 解析XML字符串
+   */
+  parseXML(xmlString: string): Document {
+    const parser = new DOMParser();
+    return parser.parseFromString(xmlString, 'text/xml');
+  },
+  
+  /**
+   * 延迟等待
+   */
+  delay(ms: number): Promise<void> {
+    return new Promise(resolve => setTimeout(resolve, ms));
+  },
+};
+
+// 控制台输出美化
+console.log('🎵 简谱渲染引擎测试环境已初始化');

+ 385 - 12
src/jianpu-renderer/core/layout/MeasureLayoutEngine.ts

@@ -2,41 +2,414 @@
  * 小节布局引擎
  * 
  * @description 计算小节宽度和音符X坐标(固定时间比例算法)
+ * 
+ * 核心算法:
+ * - 小节宽度 = (拍数 / 单位拍 × 4) × 四分音符间距 + 左右padding
+ * - 音符X坐标 = 小节起始X + 左padding + 时间戳 × 单位间距
+ * 
+ * 固定时间比例原则:
+ * - 同一拍号的所有小节宽度完全相同
+ * - 音符间距严格按时值比例分配
+ * - 二分音符间距是四分音符的2倍
+ * - 八分音符间距是四分音符的0.5倍
  */
 
-import { JianpuMeasure } from '../../models';
+import { JianpuMeasure } from '../../models/JianpuMeasure';
+import { JianpuNote } from '../../models/JianpuNote';
+import { RenderConfig, DEFAULT_RENDER_CONFIG } from '../config/RenderConfig';
 
+// ==================== 类型定义 ====================
+
+/** 小节布局配置 */
 export interface MeasureLayoutConfig {
+  /** 四分音符的基准间距(像素) */
   quarterNoteSpacing: number;
+  /** 小节左右padding(像素) */
   measurePadding: number;
+  /** 最小音符间距(像素,防止音符重叠) */
   minNoteSpacing: number;
+  /** 音符字体大小(像素,用于计算音符宽度) */
+  noteFontSize: number;
+}
+
+/** 小节布局结果 */
+export interface MeasureLayoutResult {
+  /** 小节索引 */
+  measureIndex: number;
+  /** 小节起始X坐标 */
+  x: number;
+  /** 小节宽度 */
+  width: number;
+  /** 内容宽度(不含padding) */
+  contentWidth: number;
+  /** 音符位置数组 */
+  notePositions: NotePosition[];
+}
+
+/** 音符位置 */
+export interface NotePosition {
+  /** 音符ID */
+  noteId: string;
+  /** X坐标 */
+  x: number;
+  /** 音符宽度 */
+  width: number;
+  /** 声部索引 */
+  voiceIndex: number;
+  /** 在小节内的时间戳 */
+  timestamp: number;
 }
 
+/** 布局统计 */
+export interface LayoutStats {
+  /** 布局的小节数量 */
+  measureCount: number;
+  /** 布局的音符数量 */
+  noteCount: number;
+  /** 总宽度 */
+  totalWidth: number;
+  /** 最大小节宽度 */
+  maxMeasureWidth: number;
+  /** 最小小节宽度 */
+  minMeasureWidth: number;
+  /** 布局耗时(ms) */
+  layoutTime: number;
+}
+
+// ==================== 主类 ====================
+
+/**
+ * 小节布局引擎
+ */
 export class MeasureLayoutEngine {
+  /** 布局配置 */
   private config: MeasureLayoutConfig;
   
+  /** 布局统计 */
+  private stats: LayoutStats = {
+    measureCount: 0,
+    noteCount: 0,
+    totalWidth: 0,
+    maxMeasureWidth: 0,
+    minMeasureWidth: Infinity,
+    layoutTime: 0,
+  };
+
+  /**
+   * 构造函数
+   * @param config 布局配置(可选)
+   */
   constructor(config: Partial<MeasureLayoutConfig> = {}) {
     this.config = {
-      quarterNoteSpacing: 50,
-      measurePadding: 20,
-      minNoteSpacing: 10,
-      ...config,
+      quarterNoteSpacing: config.quarterNoteSpacing ?? DEFAULT_RENDER_CONFIG.quarterNoteSpacing,
+      measurePadding: config.measurePadding ?? DEFAULT_RENDER_CONFIG.measurePadding,
+      minNoteSpacing: config.minNoteSpacing ?? DEFAULT_RENDER_CONFIG.minNoteSpacing,
+      noteFontSize: config.noteFontSize ?? DEFAULT_RENDER_CONFIG.noteFontSize,
     };
   }
-  
+
   /**
    * 计算单个小节的布局
+   * 
+   * @param measure 小节对象
+   * @param startX 小节起始X坐标(默认0)
+   * @returns 布局结果
    */
-  layoutMeasure(measure: JianpuMeasure): void {
-    console.log(`[MeasureLayout] 布局小节 #${measure.measureNumber}`);
+  layoutMeasure(measure: JianpuMeasure, startX: number = 0): MeasureLayoutResult {
+    // 1. 计算小节宽度
+    const { width, contentWidth } = this.calculateMeasureWidth(measure);
+    
+    // 2. 设置小节位置
+    measure.x = startX;
+    measure.width = width;
+    
+    // 3. 计算音符位置
+    const notePositions = this.calculateNotePositions(measure);
     
-    // TODO: 实现布局计算逻辑
+    return {
+      measureIndex: measure.index,
+      x: startX,
+      width,
+      contentWidth,
+      notePositions,
+    };
   }
-  
+
   /**
    * 批量布局多个小节
+   * 
+   * @param measures 小节数组
+   * @param startX 起始X坐标(默认0)
+   * @returns 布局结果数组
    */
-  layoutMeasures(measures: JianpuMeasure[]): void {
-    measures.forEach(measure => this.layoutMeasure(measure));
+  layoutMeasures(measures: JianpuMeasure[], startX: number = 0): MeasureLayoutResult[] {
+    const startTime = performance.now();
+    
+    // 重置统计
+    this.resetStats();
+    
+    const results: MeasureLayoutResult[] = [];
+    let currentX = startX;
+    
+    for (const measure of measures) {
+      const result = this.layoutMeasure(measure, currentX);
+      results.push(result);
+      
+      // 更新统计
+      this.stats.measureCount++;
+      this.stats.noteCount += result.notePositions.length;
+      this.stats.maxMeasureWidth = Math.max(this.stats.maxMeasureWidth, result.width);
+      this.stats.minMeasureWidth = Math.min(this.stats.minMeasureWidth, result.width);
+      
+      // 累计X坐标
+      currentX += result.width;
+    }
+    
+    this.stats.totalWidth = currentX - startX;
+    this.stats.layoutTime = performance.now() - startTime;
+    
+    if (this.stats.minMeasureWidth === Infinity) {
+      this.stats.minMeasureWidth = 0;
+    }
+    
+    return results;
   }
+
+  /**
+   * 计算小节宽度
+   * 
+   * 公式:小节宽度 = (拍数 / 单位拍 × 4) × 四分音符间距 + 左右padding
+   * 
+   * @param measure 小节对象
+   * @returns 宽度信息
+   */
+  calculateMeasureWidth(measure: JianpuMeasure): { width: number; contentWidth: number } {
+    const { beats, beatType } = measure.timeSignature;
+    const { quarterNoteSpacing, measurePadding } = this.config;
+    
+    // 小节时值(以四分音符为单位)
+    // 例如:4/4拍 = 4 / 4 * 4 = 4个四分音符
+    // 例如:3/4拍 = 3 / 4 * 4 = 3个四分音符
+    // 例如:6/8拍 = 6 / 8 * 4 = 3个四分音符
+    const measureRealValue = (beats / beatType) * 4;
+    
+    // 内容宽度 = 时值 × 四分音符间距
+    const contentWidth = measureRealValue * quarterNoteSpacing;
+    
+    // 总宽度 = 内容宽度 + 左右padding
+    const width = contentWidth + measurePadding * 2;
+    
+    return { width, contentWidth };
+  }
+
+  /**
+   * 计算小节内所有音符的位置
+   * 
+   * 公式:音符X = 小节起始X + 左padding + 时间戳 × 单位间距
+   * 
+   * @param measure 小节对象
+   * @returns 音符位置数组
+   */
+  calculateNotePositions(measure: JianpuMeasure): NotePosition[] {
+    const positions: NotePosition[] = [];
+    const { quarterNoteSpacing, measurePadding, minNoteSpacing, noteFontSize } = this.config;
+    const { beatType } = measure.timeSignature;
+    
+    // 单位间距系数(根据拍号调整)
+    // 例如:4/4拍,beatType=4,系数=1
+    // 例如:6/8拍,beatType=8,系数=0.5
+    const spacingFactor = 4 / beatType;
+    
+    // 遍历所有声部
+    for (let voiceIndex = 0; voiceIndex < measure.voices.length; voiceIndex++) {
+      const voice = measure.voices[voiceIndex];
+      
+      // 按时间戳排序
+      const sortedNotes = [...voice].sort((a, b) => a.timestamp - b.timestamp);
+      
+      let prevX = -Infinity;
+      
+      for (const note of sortedNotes) {
+        // 计算X坐标
+        // 公式:音符X = 小节起始X + 左padding + 时间戳 × 单位间距
+        let x = measure.x + measurePadding + note.timestamp * spacingFactor * quarterNoteSpacing;
+        
+        // 应用最小间距限制(防止音符重叠)
+        if (x - prevX < minNoteSpacing && prevX !== -Infinity) {
+          x = prevX + minNoteSpacing;
+        }
+        
+        // 计算音符宽度(基于字体大小)
+        const noteWidth = this.calculateNoteWidth(note);
+        
+        // 更新音符位置
+        note.x = x;
+        note.width = noteWidth;
+        
+        positions.push({
+          noteId: note.id,
+          x,
+          width: noteWidth,
+          voiceIndex,
+          timestamp: note.timestamp,
+        });
+        
+        prevX = x;
+      }
+    }
+    
+    return positions;
+  }
+
+  /**
+   * 计算单个音符的宽度
+   * 
+   * @param note 音符对象
+   * @returns 音符宽度(像素)
+   */
+  calculateNoteWidth(note: JianpuNote): number {
+    const { noteFontSize } = this.config;
+    
+    // 基础宽度(单个数字的宽度约等于字体大小的0.6倍)
+    let width = noteFontSize * 0.6;
+    
+    // 如果有升降号,增加宽度
+    if (note.accidental) {
+      width += noteFontSize * 0.4;
+    }
+    
+    // 如果有附点,增加宽度
+    if (note.dots > 0) {
+      width += noteFontSize * 0.3 * note.dots;
+    }
+    
+    return width;
+  }
+
+  /**
+   * 根据拍号计算标准小节宽度
+   * 
+   * @param beats 拍数
+   * @param beatType 单位拍
+   * @returns 小节宽度
+   */
+  getStandardMeasureWidth(beats: number, beatType: number): number {
+    const { quarterNoteSpacing, measurePadding } = this.config;
+    const measureRealValue = (beats / beatType) * 4;
+    return measureRealValue * quarterNoteSpacing + measurePadding * 2;
+  }
+
+  /**
+   * 计算音符在小节内的X偏移
+   * 
+   * @param timestamp 音符时间戳(以四分音符为单位)
+   * @param beatType 单位拍
+   * @returns X偏移量
+   */
+  calculateNoteOffset(timestamp: number, beatType: number): number {
+    const { quarterNoteSpacing, measurePadding } = this.config;
+    const spacingFactor = 4 / beatType;
+    return measurePadding + timestamp * spacingFactor * quarterNoteSpacing;
+  }
+
+  /**
+   * 获取布局统计
+   */
+  getStats(): LayoutStats {
+    return { ...this.stats };
+  }
+
+  /**
+   * 重置统计
+   */
+  private resetStats(): void {
+    this.stats = {
+      measureCount: 0,
+      noteCount: 0,
+      totalWidth: 0,
+      maxMeasureWidth: 0,
+      minMeasureWidth: Infinity,
+      layoutTime: 0,
+    };
+  }
+
+  /**
+   * 获取当前配置
+   */
+  getConfig(): MeasureLayoutConfig {
+    return { ...this.config };
+  }
+
+  /**
+   * 更新配置
+   */
+  updateConfig(config: Partial<MeasureLayoutConfig>): void {
+    Object.assign(this.config, config);
+  }
+}
+
+// ==================== 工厂函数 ====================
+
+/**
+ * 创建小节布局引擎
+ */
+export function createMeasureLayoutEngine(config?: Partial<MeasureLayoutConfig>): MeasureLayoutEngine {
+  return new MeasureLayoutEngine(config);
+}
+
+// ==================== 工具函数 ====================
+
+/**
+ * 计算拍号对应的小节时值
+ * @param beats 拍数
+ * @param beatType 单位拍
+ * @returns 小节时值(以四分音符为单位)
+ */
+export function calculateMeasureRealValue(beats: number, beatType: number): number {
+  return (beats / beatType) * 4;
+}
+
+/**
+ * 计算时间戳对应的X偏移比例
+ * @param timestamp 时间戳
+ * @param beatType 单位拍
+ * @returns X偏移比例(0-1)
+ */
+export function calculateTimestampRatio(timestamp: number, beats: number, beatType: number): number {
+  const measureRealValue = calculateMeasureRealValue(beats, beatType);
+  return timestamp / measureRealValue;
+}
+
+/**
+ * 验证小节宽度是否符合固定比例
+ * @param measures 小节数组
+ * @param tolerance 容差(默认0.001)
+ * @returns 是否符合
+ */
+export function validateMeasureWidths(measures: JianpuMeasure[], tolerance: number = 0.001): boolean {
+  // 按拍号分组
+  const groupedByTimeSignature = new Map<string, JianpuMeasure[]>();
+  
+  for (const measure of measures) {
+    const key = `${measure.timeSignature.beats}/${measure.timeSignature.beatType}`;
+    if (!groupedByTimeSignature.has(key)) {
+      groupedByTimeSignature.set(key, []);
+    }
+    groupedByTimeSignature.get(key)!.push(measure);
+  }
+  
+  // 验证同一拍号的小节宽度是否相同
+  for (const [key, group] of groupedByTimeSignature) {
+    if (group.length < 2) continue;
+    
+    const firstWidth = group[0].width;
+    for (let i = 1; i < group.length; i++) {
+      if (Math.abs(group[i].width - firstWidth) > tolerance) {
+        console.warn(`[MeasureLayoutEngine] 拍号 ${key} 的小节宽度不一致: ${firstWidth} vs ${group[i].width}`);
+        return false;
+      }
+    }
+  }
+  
+  return true;
 }

+ 364 - 6
src/jianpu-renderer/core/layout/MultiVoiceAligner.ts

@@ -2,19 +2,377 @@
  * 多声部对齐器
  * 
  * @description 确保多声部在相同时间点垂直对齐
+ * 
+ * 核心功能:
+ * 1. 收集所有声部的时间戳
+ * 2. 为相同时间戳的音符分配统一的X坐标
+ * 3. 处理边界情况(单声部、休止符、不同时值)
+ * 
+ * 对齐原则:
+ * - 相同时间戳的音符必须垂直对齐(X坐标相同)
+ * - 取所有声部中该时间戳音符的最大X坐标作为统一坐标
+ * - 如果某声部在该时间戳没有音符,不影响其他声部
  */
 
-import { JianpuMeasure } from '../../models';
+import { JianpuMeasure } from '../../models/JianpuMeasure';
+import { JianpuNote } from '../../models/JianpuNote';
+
+// ==================== 类型定义 ====================
+
+/** 时间戳到音符的映射 */
+export interface TimestampNoteMap {
+  /** 时间戳(以四分音符为单位) */
+  timestamp: number;
+  /** 该时间戳的所有音符 */
+  notes: NoteWithVoice[];
+  /** 统一的X坐标 */
+  alignedX: number;
+}
+
+/** 带声部信息的音符 */
+export interface NoteWithVoice {
+  /** 音符对象 */
+  note: JianpuNote;
+  /** 声部索引 */
+  voiceIndex: number;
+}
 
+/** 对齐结果 */
+export interface AlignmentResult {
+  /** 小节索引 */
+  measureIndex: number;
+  /** 声部数量 */
+  voiceCount: number;
+  /** 时间戳数量 */
+  timestampCount: number;
+  /** 对齐的音符数量 */
+  alignedNoteCount: number;
+  /** 时间戳映射 */
+  timestampMap: TimestampNoteMap[];
+}
+
+/** 对齐配置 */
+export interface AlignmentConfig {
+  /** 时间戳精度(用于比较浮点数,默认0.001) */
+  timestampPrecision: number;
+  /** 是否包含休止符(默认true) */
+  includeRests: boolean;
+  /** 对齐策略(max取最大X,min取最小X,avg取平均) */
+  alignmentStrategy: 'max' | 'min' | 'avg';
+}
+
+// ==================== 主类 ====================
+
+/**
+ * 多声部对齐器
+ */
 export class MultiVoiceAligner {
+  /** 配置 */
+  private config: AlignmentConfig;
+
+  /**
+   * 构造函数
+   * @param config 对齐配置
+   */
+  constructor(config: Partial<AlignmentConfig> = {}) {
+    this.config = {
+      timestampPrecision: config.timestampPrecision ?? 0.001,
+      includeRests: config.includeRests ?? true,
+      alignmentStrategy: config.alignmentStrategy ?? 'max',
+    };
+  }
+
   /**
-   * 对齐多声部音符
+   * 对齐单个小节的多声部音符
+   * 
+   * @param measure 小节对象
+   * @returns 对齐结果
    */
-  alignVoices(measure: JianpuMeasure): void {
-    if (measure.notes.length <= 1) return;
+  alignVoices(measure: JianpuMeasure): AlignmentResult {
+    const result: AlignmentResult = {
+      measureIndex: measure.index,
+      voiceCount: measure.voices.length,
+      timestampCount: 0,
+      alignedNoteCount: 0,
+      timestampMap: [],
+    };
+
+    // 单声部不需要对齐
+    if (measure.voices.length <= 1) {
+      return result;
+    }
+
+    // 1. 收集所有时间戳
+    const timestampMap = this.collectTimestamps(measure);
+    result.timestampCount = timestampMap.size;
+
+    // 2. 对齐每个时间戳的音符
+    for (const [timestamp, notes] of timestampMap) {
+      // 只有当多个声部在同一时间戳有音符时才需要对齐
+      if (notes.length > 1) {
+        const alignedX = this.calculateAlignedX(notes);
+        
+        // 更新所有音符的X坐标
+        for (const { note } of notes) {
+          note.x = alignedX;
+        }
+        
+        result.alignedNoteCount += notes.length;
+        result.timestampMap.push({
+          timestamp,
+          notes,
+          alignedX,
+        });
+      }
+    }
+
+    return result;
+  }
+
+  /**
+   * 对齐多个小节
+   * 
+   * @param measures 小节数组
+   * @returns 对齐结果数组
+   */
+  alignMeasures(measures: JianpuMeasure[]): AlignmentResult[] {
+    return measures.map(measure => this.alignVoices(measure));
+  }
+
+  /**
+   * 收集小节内所有声部的时间戳
+   * 
+   * @param measure 小节对象
+   * @returns 时间戳到音符的映射
+   */
+  private collectTimestamps(measure: JianpuMeasure): Map<number, NoteWithVoice[]> {
+    const timestampMap = new Map<number, NoteWithVoice[]>();
+    const { timestampPrecision, includeRests } = this.config;
+
+    for (let voiceIndex = 0; voiceIndex < measure.voices.length; voiceIndex++) {
+      const voice = measure.voices[voiceIndex];
+      
+      for (const note of voice) {
+        // 可选:跳过休止符
+        if (!includeRests && note.isRest) {
+          continue;
+        }
+
+        // 使用精度处理的时间戳作为键
+        const normalizedTimestamp = this.normalizeTimestamp(note.timestamp);
+        
+        if (!timestampMap.has(normalizedTimestamp)) {
+          timestampMap.set(normalizedTimestamp, []);
+        }
+        
+        timestampMap.get(normalizedTimestamp)!.push({
+          note,
+          voiceIndex,
+        });
+      }
+    }
+
+    return timestampMap;
+  }
+
+  /**
+   * 标准化时间戳(处理浮点数精度问题)
+   * 
+   * @param timestamp 原始时间戳
+   * @returns 标准化后的时间戳
+   */
+  private normalizeTimestamp(timestamp: number): number {
+    const { timestampPrecision } = this.config;
+    // 将时间戳四舍五入到指定精度
+    const factor = 1 / timestampPrecision;
+    return Math.round(timestamp * factor) / factor;
+  }
+
+  /**
+   * 计算统一的X坐标
+   * 
+   * @param notes 相同时间戳的所有音符
+   * @returns 统一的X坐标
+   */
+  private calculateAlignedX(notes: NoteWithVoice[]): number {
+    const { alignmentStrategy } = this.config;
+    const xValues = notes.map(({ note }) => note.x);
+
+    switch (alignmentStrategy) {
+      case 'max':
+        return Math.max(...xValues);
+      case 'min':
+        return Math.min(...xValues);
+      case 'avg':
+        return xValues.reduce((sum, x) => sum + x, 0) / xValues.length;
+      default:
+        return Math.max(...xValues);
+    }
+  }
+
+  /**
+   * 检查小节是否需要对齐
+   * 
+   * @param measure 小节对象
+   * @returns 是否需要对齐
+   */
+  needsAlignment(measure: JianpuMeasure): boolean {
+    // 单声部不需要对齐
+    if (measure.voices.length <= 1) {
+      return false;
+    }
+
+    // 检查是否有多个声部在同一时间戳有音符
+    const timestampMap = this.collectTimestamps(measure);
     
-    console.log(`[MultiVoiceAlign] 对齐小节 #${measure.measureNumber} 的多声部`);
+    for (const notes of timestampMap.values()) {
+      if (notes.length > 1) {
+        return true;
+      }
+    }
+
+    return false;
+  }
+
+  /**
+   * 获取小节内所有唯一时间戳(已排序)
+   * 
+   * @param measure 小节对象
+   * @returns 排序后的时间戳数组
+   */
+  getUniqueTimestamps(measure: JianpuMeasure): number[] {
+    const timestampMap = this.collectTimestamps(measure);
+    return Array.from(timestampMap.keys()).sort((a, b) => a - b);
+  }
+
+  /**
+   * 获取指定时间戳的所有音符
+   * 
+   * @param measure 小节对象
+   * @param timestamp 时间戳
+   * @returns 音符数组
+   */
+  getNotesAtTimestamp(measure: JianpuMeasure, timestamp: number): NoteWithVoice[] {
+    const normalizedTimestamp = this.normalizeTimestamp(timestamp);
+    const timestampMap = this.collectTimestamps(measure);
+    return timestampMap.get(normalizedTimestamp) ?? [];
+  }
+
+  /**
+   * 验证对齐结果
+   * 
+   * @param measure 小节对象
+   * @returns 是否所有相同时间戳的音符X坐标相同
+   */
+  validateAlignment(measure: JianpuMeasure): boolean {
+    const timestampMap = this.collectTimestamps(measure);
     
-    // TODO: 实现多声部对齐逻辑
+    for (const notes of timestampMap.values()) {
+      if (notes.length > 1) {
+        const firstX = notes[0].note.x;
+        for (let i = 1; i < notes.length; i++) {
+          if (Math.abs(notes[i].note.x - firstX) > this.config.timestampPrecision) {
+            return false;
+          }
+        }
+      }
+    }
+
+    return true;
+  }
+
+  /**
+   * 获取当前配置
+   */
+  getConfig(): AlignmentConfig {
+    return { ...this.config };
+  }
+
+  /**
+   * 更新配置
+   */
+  updateConfig(config: Partial<AlignmentConfig>): void {
+    Object.assign(this.config, config);
   }
 }
+
+// ==================== 工厂函数 ====================
+
+/**
+ * 创建多声部对齐器
+ */
+export function createMultiVoiceAligner(config?: Partial<AlignmentConfig>): MultiVoiceAligner {
+  return new MultiVoiceAligner(config);
+}
+
+// ==================== 工具函数 ====================
+
+/**
+ * 快速对齐单个小节
+ * 
+ * @param measure 小节对象
+ * @returns 是否进行了对齐
+ */
+export function alignMeasureVoices(measure: JianpuMeasure): boolean {
+  const aligner = new MultiVoiceAligner();
+  const result = aligner.alignVoices(measure);
+  return result.alignedNoteCount > 0;
+}
+
+/**
+ * 快速对齐多个小节
+ * 
+ * @param measures 小节数组
+ * @returns 对齐的小节数量
+ */
+export function alignAllMeasureVoices(measures: JianpuMeasure[]): number {
+  const aligner = new MultiVoiceAligner();
+  let alignedCount = 0;
+  
+  for (const measure of measures) {
+    const result = aligner.alignVoices(measure);
+    if (result.alignedNoteCount > 0) {
+      alignedCount++;
+    }
+  }
+  
+  return alignedCount;
+}
+
+/**
+ * 检查音符是否在同一时间点
+ * 
+ * @param note1 音符1
+ * @param note2 音符2
+ * @param precision 精度(默认0.001)
+ * @returns 是否在同一时间点
+ */
+export function areNotesAtSameTime(
+  note1: JianpuNote, 
+  note2: JianpuNote, 
+  precision: number = 0.001
+): boolean {
+  return Math.abs(note1.timestamp - note2.timestamp) <= precision;
+}
+
+/**
+ * 获取声部统计信息
+ * 
+ * @param measure 小节对象
+ * @returns 声部统计
+ */
+export function getVoiceStats(measure: JianpuMeasure): {
+  voiceCount: number;
+  noteCountPerVoice: number[];
+  hasMultiVoice: boolean;
+  maxNotesInVoice: number;
+} {
+  const noteCountPerVoice = measure.voices.map(voice => voice.length);
+  
+  return {
+    voiceCount: measure.voices.length,
+    noteCountPerVoice,
+    hasMultiVoice: measure.voices.length > 1 && noteCountPerVoice.filter(c => c > 0).length > 1,
+    maxNotesInVoice: Math.max(...noteCountPerVoice, 0),
+  };
+}

+ 508 - 5
src/jianpu-renderer/core/layout/NotePositionCalculator.ts

@@ -1,15 +1,518 @@
 /**
  * 音符位置计算器
+ * 
+ * @description 计算音符的Y坐标,支持多声部垂直布局
+ * 
+ * 音符区域高度分配(单声部):
+ * ┌─────────────────────────────┐
+ * │    升降号区域 (12px)         │  ← accidental
+ * ├─────────────────────────────┤
+ * │    高音点区域 (8px)          │  ← octave dots (high)
+ * ├─────────────────────────────┤
+ * │    音符数字 (24px)           │  ← note number (基准线)
+ * ├─────────────────────────────┤
+ * │    低音点区域 (8px)          │  ← octave dots (low)
+ * ├─────────────────────────────┤
+ * │    减时线区域 (12px)         │  ← underlines
+ * ├─────────────────────────────┤
+ * │    歌词区域 (20px)           │  ← lyrics (可选)
+ * └─────────────────────────────┘
+ * 
+ * 多声部布局:
+ * - 多声部垂直居中分布
+ * - 声部间距默认60px
  */
 
-import { JianpuNote } from '../../models';
+import { JianpuNote } from '../../models/JianpuNote';
+import { JianpuMeasure } from '../../models/JianpuMeasure';
+import { JianpuSystem } from '../../models/JianpuSystem';
+import { RenderConfig, DEFAULT_RENDER_CONFIG } from '../config/RenderConfig';
 
+// ==================== 类型定义 ====================
+
+/** 音符位置计算配置 */
+export interface NotePositionConfig {
+  /** 声部间距(像素) */
+  voiceSpacing: number;
+  /** 音符字体大小(像素),用于计算基准高度 */
+  noteFontSize: number;
+  /** 歌词字体大小(像素) */
+  lyricFontSize: number;
+  /** 是否显示歌词 */
+  showLyrics: boolean;
+  /** 升降号区域高度(像素) */
+  accidentalAreaHeight: number;
+  /** 高音点区域高度(像素) */
+  highOctaveAreaHeight: number;
+  /** 低音点区域高度(像素) */
+  lowOctaveAreaHeight: number;
+  /** 减时线区域高度(像素) */
+  underlineAreaHeight: number;
+  /** 歌词区域高度(像素) */
+  lyricAreaHeight: number;
+}
+
+/** 音符垂直区域 */
+export interface NoteVerticalRegions {
+  /** 升降号Y坐标 */
+  accidentalY: number;
+  /** 高音点起始Y坐标 */
+  highOctaveY: number;
+  /** 音符数字Y坐标(基准线) */
+  noteY: number;
+  /** 低音点起始Y坐标 */
+  lowOctaveY: number;
+  /** 减时线起始Y坐标 */
+  underlineY: number;
+  /** 歌词Y坐标 */
+  lyricY: number;
+  /** 总高度 */
+  totalHeight: number;
+}
+
+/** 声部布局信息 */
+export interface VoiceLayout {
+  /** 声部索引 */
+  voiceIndex: number;
+  /** 声部基准Y坐标 */
+  baseY: number;
+  /** 声部区域 */
+  regions: NoteVerticalRegions;
+}
+
+/** 布局结果 */
+export interface NoteLayoutResult {
+  /** 更新后的音符数组 */
+  notes: JianpuNote[];
+  /** 声部布局信息 */
+  voiceLayouts: VoiceLayout[];
+  /** 布局统计 */
+  stats: {
+    noteCount: number;
+    voiceCount: number;
+    totalHeight: number;
+    layoutTime: number;
+  };
+}
+
+// ==================== 默认配置 ====================
+
+const DEFAULT_NOTE_POSITION_CONFIG: NotePositionConfig = {
+  voiceSpacing: 60,
+  noteFontSize: DEFAULT_RENDER_CONFIG.noteFontSize,
+  lyricFontSize: DEFAULT_RENDER_CONFIG.lyricFontSize,
+  showLyrics: DEFAULT_RENDER_CONFIG.showLyrics,
+  accidentalAreaHeight: 12,
+  highOctaveAreaHeight: 8,
+  lowOctaveAreaHeight: 8,
+  underlineAreaHeight: 12,
+  lyricAreaHeight: 20,
+};
+
+// ==================== 主类 ====================
+
+/**
+ * 音符位置计算器
+ */
 export class NotePositionCalculator {
+  /** 配置 */
+  private config: NotePositionConfig;
+
+  /**
+   * 构造函数
+   * @param config 配置选项
+   */
+  constructor(config: Partial<NotePositionConfig> = {}) {
+    this.config = { ...DEFAULT_NOTE_POSITION_CONFIG, ...config };
+  }
+
+  /**
+   * 计算单个音符的Y坐标
+   * 
+   * @param note 音符对象
+   * @param voiceCount 总声部数量
+   * @param baseY 基准Y坐标(行的Y坐标)
+   * @returns 音符Y坐标
+   */
+  calculateNoteY(note: JianpuNote, voiceCount: number, baseY: number = 0): number {
+    const voiceY = this.calculateVoiceY(baseY, note.voiceIndex, voiceCount);
+    return voiceY;
+  }
+
+  /**
+   * 计算声部的基准Y坐标
+   * 
+   * 多声部垂直居中分布:
+   * - 单声部:直接使用baseY
+   * - 多声部:以baseY为中心,声部均匀分布
+   * 
+   * @param baseY 行的基准Y坐标
+   * @param voiceIndex 声部索引(0开始)
+   * @param voiceCount 总声部数量
+   * @returns 声部Y坐标
+   */
+  calculateVoiceY(baseY: number, voiceIndex: number, voiceCount: number): number {
+    if (voiceCount <= 1) {
+      return baseY;
+    }
+
+    const { voiceSpacing } = this.config;
+    
+    // 多声部垂直居中分布
+    // 总高度 = (声部数-1) * 声部间距
+    const totalHeight = (voiceCount - 1) * voiceSpacing;
+    
+    // 起始Y = 基准Y - 总高度/2
+    const startY = baseY - totalHeight / 2;
+    
+    // 声部Y = 起始Y + 声部索引 * 声部间距
+    return startY + voiceIndex * voiceSpacing;
+  }
+
+  /**
+   * 计算音符的垂直区域
+   * 
+   * @param noteY 音符基准Y坐标
+   * @returns 垂直区域信息
+   */
+  calculateVerticalRegions(noteY: number): NoteVerticalRegions {
+    const {
+      noteFontSize,
+      accidentalAreaHeight,
+      highOctaveAreaHeight,
+      lowOctaveAreaHeight,
+      underlineAreaHeight,
+      lyricAreaHeight,
+      showLyrics,
+    } = this.config;
+
+    // 音符数字高度(约等于字体大小)
+    const noteHeight = noteFontSize;
+    
+    // 计算各区域Y坐标(从上到下)
+    // 音符Y是数字的中心点
+    const halfNoteHeight = noteHeight / 2;
+    
+    // 升降号区域:在音符上方
+    const accidentalY = noteY - halfNoteHeight - highOctaveAreaHeight - accidentalAreaHeight / 2;
+    
+    // 高音点区域:在音符上方,升降号下方
+    const highOctaveY = noteY - halfNoteHeight - highOctaveAreaHeight / 2;
+    
+    // 低音点区域:在音符下方
+    const lowOctaveY = noteY + halfNoteHeight + lowOctaveAreaHeight / 2;
+    
+    // 减时线区域:在低音点下方
+    const underlineY = noteY + halfNoteHeight + lowOctaveAreaHeight + underlineAreaHeight / 2;
+    
+    // 歌词区域:在减时线下方
+    const lyricY = noteY + halfNoteHeight + lowOctaveAreaHeight + underlineAreaHeight + lyricAreaHeight / 2;
+    
+    // 计算总高度
+    const topHeight = accidentalAreaHeight + highOctaveAreaHeight + halfNoteHeight;
+    const bottomHeight = halfNoteHeight + lowOctaveAreaHeight + underlineAreaHeight + (showLyrics ? lyricAreaHeight : 0);
+    const totalHeight = topHeight + bottomHeight;
+
+    return {
+      accidentalY,
+      highOctaveY,
+      noteY,
+      lowOctaveY,
+      underlineY,
+      lyricY,
+      totalHeight,
+    };
+  }
+
+  /**
+   * 计算单声部的完整高度
+   * 
+   * @param showLyrics 是否显示歌词
+   * @returns 单声部高度
+   */
+  calculateSingleVoiceHeight(showLyrics: boolean = this.config.showLyrics): number {
+    const {
+      noteFontSize,
+      accidentalAreaHeight,
+      highOctaveAreaHeight,
+      lowOctaveAreaHeight,
+      underlineAreaHeight,
+      lyricAreaHeight,
+    } = this.config;
+
+    let height = accidentalAreaHeight + highOctaveAreaHeight + noteFontSize + lowOctaveAreaHeight + underlineAreaHeight;
+    
+    if (showLyrics) {
+      height += lyricAreaHeight;
+    }
+    
+    return height;
+  }
+
+  /**
+   * 计算多声部的总高度
+   * 
+   * @param voiceCount 声部数量
+   * @param showLyrics 是否显示歌词
+   * @returns 总高度
+   */
+  calculateMultiVoiceHeight(voiceCount: number, showLyrics: boolean = this.config.showLyrics): number {
+    if (voiceCount <= 1) {
+      return this.calculateSingleVoiceHeight(showLyrics);
+    }
+
+    const { voiceSpacing } = this.config;
+    const singleVoiceHeight = this.calculateSingleVoiceHeight(showLyrics);
+    
+    // 多声部高度 = 单声部高度 + (声部数-1) * 声部间距
+    return singleVoiceHeight + (voiceCount - 1) * voiceSpacing;
+  }
+
+  /**
+   * 为小节内的所有音符计算Y坐标
+   * 
+   * @param measure 小节对象
+   * @param baseY 基准Y坐标
+   * @returns 布局结果
+   */
+  layoutMeasureNotes(measure: JianpuMeasure, baseY: number): NoteLayoutResult {
+    const startTime = performance.now();
+    const voiceCount = measure.voices.length;
+    const voiceLayouts: VoiceLayout[] = [];
+    let noteCount = 0;
+
+    // 为每个声部计算布局
+    for (let voiceIndex = 0; voiceIndex < voiceCount; voiceIndex++) {
+      const voice = measure.voices[voiceIndex];
+      const voiceBaseY = this.calculateVoiceY(baseY, voiceIndex, voiceCount);
+      const regions = this.calculateVerticalRegions(voiceBaseY);
+
+      voiceLayouts.push({
+        voiceIndex,
+        baseY: voiceBaseY,
+        regions,
+      });
+
+      // 更新该声部所有音符的Y坐标
+      for (const note of voice) {
+        note.y = voiceBaseY;
+        note.height = regions.totalHeight;
+        noteCount++;
+      }
+    }
+
+    // 收集所有音符
+    const notes = measure.voices.flat();
+
+    return {
+      notes,
+      voiceLayouts,
+      stats: {
+        noteCount,
+        voiceCount,
+        totalHeight: this.calculateMultiVoiceHeight(voiceCount),
+        layoutTime: performance.now() - startTime,
+      },
+    };
+  }
+
+  /**
+   * 为行内的所有小节计算音符Y坐标
+   * 
+   * @param system 行对象
+   * @returns 布局结果
+   */
+  layoutSystemNotes(system: JianpuSystem): NoteLayoutResult {
+    const startTime = performance.now();
+    const allNotes: JianpuNote[] = [];
+    const allVoiceLayouts: VoiceLayout[] = [];
+    let totalNoteCount = 0;
+    let maxVoiceCount = 0;
+
+    // 计算行内的基准Y坐标(行高度的中心)
+    const baseY = system.y + system.height / 2;
+
+    for (const measure of system.measures) {
+      const result = this.layoutMeasureNotes(measure, baseY);
+      allNotes.push(...result.notes);
+      
+      // 只记录第一个小节的声部布局(假设行内声部数量一致)
+      if (allVoiceLayouts.length === 0) {
+        allVoiceLayouts.push(...result.voiceLayouts);
+      }
+      
+      totalNoteCount += result.stats.noteCount;
+      maxVoiceCount = Math.max(maxVoiceCount, result.stats.voiceCount);
+    }
+
+    return {
+      notes: allNotes,
+      voiceLayouts: allVoiceLayouts,
+      stats: {
+        noteCount: totalNoteCount,
+        voiceCount: maxVoiceCount,
+        totalHeight: this.calculateMultiVoiceHeight(maxVoiceCount),
+        layoutTime: performance.now() - startTime,
+      },
+    };
+  }
+
+  /**
+   * 批量为所有行的音符计算Y坐标
+   * 
+   * @param systems 行数组
+   * @returns 所有布局结果
+   */
+  layoutAllSystems(systems: JianpuSystem[]): NoteLayoutResult[] {
+    return systems.map(system => this.layoutSystemNotes(system));
+  }
+
+  // ==================== 辅助方法 ====================
+
+  /**
+   * 获取音符各部分的Y坐标偏移
+   * 
+   * @param note 音符对象
+   * @returns 各部分的Y坐标
+   */
+  getNotePartPositions(note: JianpuNote): {
+    accidentalY: number;
+    highDotsY: number[];
+    noteY: number;
+    lowDotsY: number[];
+    underlineY: number;
+    lyricY: number;
+  } {
+    const regions = this.calculateVerticalRegions(note.y);
+    const { highOctaveAreaHeight, lowOctaveAreaHeight } = this.config;
+    
+    // 计算高音点位置(从下往上)
+    const highDotsY: number[] = [];
+    const highDotSpacing = highOctaveAreaHeight / 3; // 最多2个高音点
+    for (let i = 0; i < Math.abs(note.octave); i++) {
+      if (note.octave > 0) {
+        highDotsY.push(regions.highOctaveY + (i - 0.5) * highDotSpacing);
+      }
+    }
+    
+    // 计算低音点位置(从上往下)
+    const lowDotsY: number[] = [];
+    const lowDotSpacing = lowOctaveAreaHeight / 3;
+    for (let i = 0; i < Math.abs(note.octave); i++) {
+      if (note.octave < 0) {
+        lowDotsY.push(regions.lowOctaveY + (i + 0.5) * lowDotSpacing);
+      }
+    }
+
+    return {
+      accidentalY: regions.accidentalY,
+      highDotsY,
+      noteY: regions.noteY,
+      lowDotsY,
+      underlineY: regions.underlineY,
+      lyricY: regions.lyricY,
+    };
+  }
+
+  /**
+   * 获取当前配置
+   */
+  getConfig(): NotePositionConfig {
+    return { ...this.config };
+  }
+
   /**
-   * 计算音符Y坐标(根据声部)
+   * 更新配置
    */
-  calculateNoteY(note: JianpuNote, voiceCount: number): number {
-    // TODO: 实现
-    return 0;
+  updateConfig(config: Partial<NotePositionConfig>): void {
+    Object.assign(this.config, config);
   }
 }
+
+// ==================== 工厂函数 ====================
+
+/**
+ * 创建音符位置计算器
+ */
+export function createNotePositionCalculator(config?: Partial<NotePositionConfig>): NotePositionCalculator {
+  return new NotePositionCalculator(config);
+}
+
+// ==================== 工具函数 ====================
+
+/**
+ * 计算声部Y坐标(独立函数)
+ * 
+ * @param baseY 基准Y坐标
+ * @param voiceIndex 声部索引
+ * @param voiceCount 总声部数
+ * @param voiceSpacing 声部间距
+ * @returns 声部Y坐标
+ */
+export function calculateVoiceY(
+  baseY: number,
+  voiceIndex: number,
+  voiceCount: number,
+  voiceSpacing: number = DEFAULT_NOTE_POSITION_CONFIG.voiceSpacing
+): number {
+  if (voiceCount <= 1) {
+    return baseY;
+  }
+  
+  const totalHeight = (voiceCount - 1) * voiceSpacing;
+  const startY = baseY - totalHeight / 2;
+  return startY + voiceIndex * voiceSpacing;
+}
+
+/**
+ * 验证音符Y坐标布局
+ * 
+ * @param measures 小节数组
+ * @returns 验证结果
+ */
+export function validateNoteYPositions(measures: JianpuMeasure[]): {
+  valid: boolean;
+  errors: string[];
+} {
+  const errors: string[] = [];
+
+  for (const measure of measures) {
+    // 检查声部Y坐标是否正确分布
+    const voiceYs = measure.voices.map(voice => {
+      if (voice.length === 0) return null;
+      return voice[0].y;
+    }).filter(y => y !== null) as number[];
+
+    // 检查Y坐标是否递增(多声部时)
+    for (let i = 1; i < voiceYs.length; i++) {
+      if (voiceYs[i] <= voiceYs[i - 1]) {
+        errors.push(`小节 ${measure.index} 声部Y坐标未递增: 声部${i-1}=${voiceYs[i-1]}, 声部${i}=${voiceYs[i]}`);
+      }
+    }
+
+    // 检查同一声部的音符Y坐标是否一致
+    for (let v = 0; v < measure.voices.length; v++) {
+      const voice = measure.voices[v];
+      if (voice.length < 2) continue;
+
+      const firstY = voice[0].y;
+      for (let n = 1; n < voice.length; n++) {
+        if (voice[n].y !== firstY) {
+          errors.push(`小节 ${measure.index} 声部 ${v} 音符Y坐标不一致: 音符0=${firstY}, 音符${n}=${voice[n].y}`);
+        }
+      }
+    }
+  }
+
+  return {
+    valid: errors.length === 0,
+    errors,
+  };
+}
+
+/**
+ * 获取默认配置
+ */
+export function getDefaultNotePositionConfig(): NotePositionConfig {
+  return { ...DEFAULT_NOTE_POSITION_CONFIG };
+}

+ 619 - 5
src/jianpu-renderer/core/layout/SystemLayoutEngine.ts

@@ -1,19 +1,633 @@
 /**
  * 行布局引擎
  * 
- * @description 将小节分配到不同的行,支持自动换行
+ * @description 将小节分配到不同的行(System),支持自动换行
+ * 
+ * 核心功能:
+ * 1. 根据行宽度自动分配小节到不同行
+ * 2. 计算每行的Y坐标
+ * 3. 确保小节不被拆分
+ * 4. 支持多声部的行高计算
+ * 
+ * 换行策略:
+ * - 当累计小节宽度超过行宽度时,在上一个小节处换行
+ * - 确保每行至少有一个小节
+ * - 最后一行不做特殊处理(保持自然宽度)
  */
 
 import { JianpuMeasure, JianpuSystem } from '../../models';
+import { RenderConfig, DEFAULT_RENDER_CONFIG } from '../config/RenderConfig';
+
+// ==================== 类型定义 ====================
+
+/** 行布局配置 */
+export interface SystemLayoutConfig {
+  /** 行宽度(像素) */
+  systemWidth: number;
+  /** 行高度(像素,单声部基准高度) */
+  systemHeight: number;
+  /** 行间距(像素) */
+  systemSpacing: number;
+  /** 声部间距(像素) */
+  voiceSpacing: number;
+  /** 页边距左(像素) */
+  marginLeft: number;
+  /** 页边距上(像素) */
+  marginTop: number;
+  /** 是否拉伸最后一行(填满行宽) */
+  stretchLastSystem: boolean;
+  /** 是否在首行显示谱号 */
+  showClefOnFirstSystem: boolean;
+  /** 是否在首行显示声部名称 */
+  showPartNamesOnFirstSystem: boolean;
+  /** 最小每行小节数(防止单个小节占一行) */
+  minMeasuresPerSystem: number;
+}
+
+/** 行布局结果 */
+export interface SystemLayoutResult {
+  /** 所有行 */
+  systems: JianpuSystem[];
+  /** 布局统计 */
+  stats: SystemLayoutStats;
+}
+
+/** 行布局统计 */
+export interface SystemLayoutStats {
+  /** 总行数 */
+  systemCount: number;
+  /** 总小节数 */
+  measureCount: number;
+  /** 最大每行小节数 */
+  maxMeasuresPerSystem: number;
+  /** 最小每行小节数 */
+  minMeasuresPerSystem: number;
+  /** 平均每行小节数 */
+  avgMeasuresPerSystem: number;
+  /** 总高度 */
+  totalHeight: number;
+  /** 布局耗时(ms) */
+  layoutTime: number;
+}
 
+/** 行分配方案 */
+interface SystemAllocation {
+  /** 该行包含的小节索引(起始) */
+  startIndex: number;
+  /** 该行包含的小节索引(结束,不含) */
+  endIndex: number;
+  /** 该行小节总宽度 */
+  totalWidth: number;
+  /** 需要的拉伸比例 */
+  stretchRatio: number;
+}
+
+// ==================== 默认配置 ====================
+
+const DEFAULT_SYSTEM_CONFIG: SystemLayoutConfig = {
+  systemWidth: DEFAULT_RENDER_CONFIG.systemWidth,
+  systemHeight: DEFAULT_RENDER_CONFIG.systemHeight,
+  systemSpacing: DEFAULT_RENDER_CONFIG.systemSpacing,
+  voiceSpacing: 60,
+  marginLeft: 20,
+  marginTop: 50,
+  stretchLastSystem: false,
+  showClefOnFirstSystem: false,
+  showPartNamesOnFirstSystem: false,
+  minMeasuresPerSystem: 1,
+};
+
+// ==================== 主类 ====================
+
+/**
+ * 行布局引擎
+ */
 export class SystemLayoutEngine {
+  /** 布局配置 */
+  private config: SystemLayoutConfig;
+
+  /**
+   * 构造函数
+   * @param config 布局配置(可选)
+   */
+  constructor(config: Partial<SystemLayoutConfig> = {}) {
+    this.config = { ...DEFAULT_SYSTEM_CONFIG, ...config };
+  }
+
   /**
    * 将小节分配到行
+   * 
+   * @param measures 小节数组(已完成小节宽度计算)
+   * @returns 行布局结果
+   */
+  layoutSystems(measures: JianpuMeasure[]): SystemLayoutResult {
+    const startTime = performance.now();
+    
+    // 处理空数组
+    if (!measures || measures.length === 0) {
+      return {
+        systems: [],
+        stats: this.createEmptyStats(startTime),
+      };
+    }
+
+    // 1. 计算行分配方案
+    const allocations = this.calculateSystemAllocations(measures);
+
+    // 2. 创建行对象并计算Y坐标
+    const systems = this.createSystems(measures, allocations);
+
+    // 3. 更新小节的X、Y坐标
+    this.updateMeasurePositions(systems);
+
+    // 4. 计算统计信息
+    const stats = this.calculateStats(systems, startTime);
+
+    return { systems, stats };
+  }
+
+  /**
+   * 计算行分配方案
+   * 
+   * 算法:贪心算法,尽可能多地将小节放入当前行
+   * 
+   * @param measures 小节数组
+   * @returns 行分配方案数组
+   */
+  private calculateSystemAllocations(measures: JianpuMeasure[]): SystemAllocation[] {
+    const { systemWidth, marginLeft, minMeasuresPerSystem } = this.config;
+    const availableWidth = systemWidth - marginLeft * 2; // 可用宽度(减去左右边距)
+    
+    const allocations: SystemAllocation[] = [];
+    let currentStartIndex = 0;
+    let currentWidth = 0;
+
+    for (let i = 0; i < measures.length; i++) {
+      const measure = measures[i];
+      const measureWidth = measure.width;
+
+      // 检查是否需要换行
+      const wouldExceed = currentWidth + measureWidth > availableWidth;
+      const hasMinMeasures = i - currentStartIndex >= minMeasuresPerSystem;
+      
+      if (wouldExceed && hasMinMeasures && currentStartIndex < i) {
+        // 当前行已满,保存并开始新行
+        allocations.push(this.createAllocation(
+          currentStartIndex,
+          i,
+          currentWidth,
+          availableWidth
+        ));
+        
+        // 开始新行
+        currentStartIndex = i;
+        currentWidth = measureWidth;
+      } else {
+        // 继续添加到当前行
+        currentWidth += measureWidth;
+      }
+    }
+
+    // 处理最后一行
+    if (currentStartIndex < measures.length) {
+      allocations.push(this.createAllocation(
+        currentStartIndex,
+        measures.length,
+        currentWidth,
+        availableWidth
+      ));
+    }
+
+    return allocations;
+  }
+
+  /**
+   * 创建行分配对象
+   */
+  private createAllocation(
+    startIndex: number,
+    endIndex: number,
+    totalWidth: number,
+    availableWidth: number
+  ): SystemAllocation {
+    return {
+      startIndex,
+      endIndex,
+      totalWidth,
+      stretchRatio: availableWidth / totalWidth, // 拉伸比例
+    };
+  }
+
+  /**
+   * 创建行对象
+   * 
+   * @param measures 小节数组
+   * @param allocations 行分配方案
+   * @returns 行数组
+   */
+  private createSystems(
+    measures: JianpuMeasure[],
+    allocations: SystemAllocation[]
+  ): JianpuSystem[] {
+    const { 
+      systemWidth, 
+      systemHeight, 
+      systemSpacing, 
+      marginLeft, 
+      marginTop,
+      voiceSpacing,
+      stretchLastSystem,
+      showClefOnFirstSystem,
+      showPartNamesOnFirstSystem,
+    } = this.config;
+
+    const systems: JianpuSystem[] = [];
+    let currentY = marginTop;
+
+    for (let sysIndex = 0; sysIndex < allocations.length; sysIndex++) {
+      const allocation = allocations[sysIndex];
+      const isFirstSystem = sysIndex === 0;
+      const isLastSystem = sysIndex === allocations.length - 1;
+      
+      // 提取该行的小节
+      const systemMeasures = measures.slice(allocation.startIndex, allocation.endIndex);
+      
+      // 计算该行的声部数量(取该行所有小节的最大声部数)
+      const maxVoices = Math.max(
+        1,
+        ...systemMeasures.map(m => m.voices.length)
+      );
+      
+      // 计算行高度(基于声部数量)
+      const height = this.calculateSystemHeight(maxVoices);
+      
+      // 是否需要拉伸(非最后一行,或配置了拉伸最后一行)
+      const shouldStretch = !isLastSystem || stretchLastSystem;
+      
+      // 创建行对象
+      const system: JianpuSystem = {
+        index: sysIndex,
+        measures: systemMeasures,
+        x: marginLeft,
+        y: currentY,
+        width: shouldStretch ? systemWidth - marginLeft * 2 : allocation.totalWidth,
+        height,
+        showPartNames: isFirstSystem && showPartNamesOnFirstSystem,
+        showClef: isFirstSystem && showClefOnFirstSystem,
+      };
+
+      // 如果需要拉伸,更新小节宽度
+      if (shouldStretch && allocation.stretchRatio > 1) {
+        this.stretchSystemMeasures(system, allocation.stretchRatio);
+      }
+
+      systems.push(system);
+      
+      // 更新Y坐标(下一行的起始位置)
+      currentY += height + systemSpacing;
+    }
+
+    return systems;
+  }
+
+  /**
+   * 计算行高度
+   * 
+   * @param voiceCount 声部数量
+   * @returns 行高度
+   */
+  private calculateSystemHeight(voiceCount: number): number {
+    const { systemHeight, voiceSpacing } = this.config;
+    
+    if (voiceCount <= 1) {
+      return systemHeight;
+    }
+    
+    // 多声部:基础高度 + (声部数-1) * 声部间距
+    return systemHeight + (voiceCount - 1) * voiceSpacing;
+  }
+
+  /**
+   * 拉伸行内的小节以填满行宽
+   * 
+   * @param system 行对象
+   * @param stretchRatio 拉伸比例
+   */
+  private stretchSystemMeasures(system: JianpuSystem, stretchRatio: number): void {
+    for (const measure of system.measures) {
+      // 拉伸小节宽度
+      measure.width *= stretchRatio;
+      
+      // 拉伸音符位置(保持相对比例)
+      for (const voice of measure.voices) {
+        for (const note of voice) {
+          // 只拉伸相对于小节起点的偏移,不改变小节起始X
+          const relativeX = note.x - measure.x;
+          note.x = measure.x + relativeX * stretchRatio;
+        }
+      }
+    }
+  }
+
+  /**
+   * 更新小节的X、Y坐标
+   * 
+   * @param systems 行数组
+   */
+  private updateMeasurePositions(systems: JianpuSystem[]): void {
+    for (const system of systems) {
+      let currentX = system.x;
+      
+      for (const measure of system.measures) {
+        // 计算小节X坐标偏移(相对于原来的X坐标)
+        const xOffset = currentX - measure.x;
+        
+        // 更新小节X坐标
+        measure.x = currentX;
+        measure.y = system.y;
+        
+        // 更新小节内所有音符的X坐标
+        if (xOffset !== 0) {
+          for (const voice of measure.voices) {
+            for (const note of voice) {
+              note.x += xOffset;
+            }
+          }
+        }
+        
+        // 累加X坐标
+        currentX += measure.width;
+      }
+    }
+  }
+
+  /**
+   * 计算统计信息
    */
-  layoutSystems(measures: JianpuMeasure[]): JianpuSystem[] {
-    console.log('[SystemLayout] 开始行布局');
+  private calculateStats(systems: JianpuSystem[], startTime: number): SystemLayoutStats {
+    const measureCounts = systems.map(s => s.measures.length);
+    const totalMeasures = measureCounts.reduce((a, b) => a + b, 0);
     
-    // TODO: 实现行布局逻辑
-    return [];
+    return {
+      systemCount: systems.length,
+      measureCount: totalMeasures,
+      maxMeasuresPerSystem: measureCounts.length > 0 ? Math.max(...measureCounts) : 0,
+      minMeasuresPerSystem: measureCounts.length > 0 ? Math.min(...measureCounts) : 0,
+      avgMeasuresPerSystem: systems.length > 0 ? totalMeasures / systems.length : 0,
+      totalHeight: this.calculateTotalHeight(systems),
+      layoutTime: performance.now() - startTime,
+    };
+  }
+
+  /**
+   * 计算总高度
+   */
+  private calculateTotalHeight(systems: JianpuSystem[]): number {
+    if (systems.length === 0) return 0;
+    
+    const lastSystem = systems[systems.length - 1];
+    return lastSystem.y + lastSystem.height;
+  }
+
+  /**
+   * 创建空统计对象
+   */
+  private createEmptyStats(startTime: number): SystemLayoutStats {
+    return {
+      systemCount: 0,
+      measureCount: 0,
+      maxMeasuresPerSystem: 0,
+      minMeasuresPerSystem: 0,
+      avgMeasuresPerSystem: 0,
+      totalHeight: 0,
+      layoutTime: performance.now() - startTime,
+    };
+  }
+
+  // ==================== 公共方法 ====================
+
+  /**
+   * 获取当前配置
+   */
+  getConfig(): SystemLayoutConfig {
+    return { ...this.config };
+  }
+
+  /**
+   * 更新配置
+   */
+  updateConfig(config: Partial<SystemLayoutConfig>): void {
+    Object.assign(this.config, config);
+  }
+
+  /**
+   * 计算指定小节数所需的最小行数
+   * 
+   * @param measureWidths 小节宽度数组
+   * @returns 最小行数
+   */
+  estimateSystemCount(measureWidths: number[]): number {
+    const { systemWidth, marginLeft } = this.config;
+    const availableWidth = systemWidth - marginLeft * 2;
+    
+    let systemCount = 1;
+    let currentWidth = 0;
+    
+    for (const width of measureWidths) {
+      if (currentWidth + width > availableWidth && currentWidth > 0) {
+        systemCount++;
+        currentWidth = width;
+      } else {
+        currentWidth += width;
+      }
+    }
+    
+    return systemCount;
+  }
+
+  /**
+   * 计算给定小节在第几行
+   * 
+   * @param systems 行数组
+   * @param measureIndex 小节索引
+   * @returns 行索引(未找到返回-1)
+   */
+  findSystemForMeasure(systems: JianpuSystem[], measureIndex: number): number {
+    for (let i = 0; i < systems.length; i++) {
+      const system = systems[i];
+      for (const measure of system.measures) {
+        if (measure.index === measureIndex) {
+          return i;
+        }
+      }
+    }
+    return -1;
+  }
+
+  /**
+   * 获取指定行的小节范围
+   * 
+   * @param systems 行数组
+   * @param systemIndex 行索引
+   * @returns 小节范围 [start, end](未找到返回null)
+   */
+  getMeasureRange(systems: JianpuSystem[], systemIndex: number): [number, number] | null {
+    if (systemIndex < 0 || systemIndex >= systems.length) {
+      return null;
+    }
+    
+    const system = systems[systemIndex];
+    if (system.measures.length === 0) {
+      return null;
+    }
+    
+    const start = system.measures[0].index;
+    const end = system.measures[system.measures.length - 1].index;
+    return [start, end];
+  }
+}
+
+// ==================== 工厂函数 ====================
+
+/**
+ * 创建行布局引擎
+ */
+export function createSystemLayoutEngine(config?: Partial<SystemLayoutConfig>): SystemLayoutEngine {
+  return new SystemLayoutEngine(config);
+}
+
+// ==================== 工具函数 ====================
+
+/**
+ * 计算最优行分配
+ * 
+ * 使用动态规划找到最均衡的行分配方案
+ * 
+ * @param measureWidths 小节宽度数组
+ * @param systemWidth 行宽度
+ * @param marginLeft 左边距
+ * @returns 每行的小节数数组
+ */
+export function calculateOptimalSystemAllocation(
+  measureWidths: number[],
+  systemWidth: number,
+  marginLeft: number = 20
+): number[] {
+  const availableWidth = systemWidth - marginLeft * 2;
+  const n = measureWidths.length;
+  
+  if (n === 0) return [];
+  if (n === 1) return [1];
+  
+  // 计算前缀和,用于快速计算区间宽度
+  const prefixSum = [0];
+  for (const w of measureWidths) {
+    prefixSum.push(prefixSum[prefixSum.length - 1] + w);
+  }
+  
+  // 计算从i到j的小节总宽度
+  const getRangeWidth = (i: number, j: number) => prefixSum[j + 1] - prefixSum[i];
+  
+  // 贪心分配
+  const result: number[] = [];
+  let start = 0;
+  
+  while (start < n) {
+    let end = start;
+    let currentWidth = measureWidths[start];
+    
+    // 尽可能多地添加小节
+    while (end + 1 < n && currentWidth + measureWidths[end + 1] <= availableWidth) {
+      end++;
+      currentWidth += measureWidths[end];
+    }
+    
+    result.push(end - start + 1);
+    start = end + 1;
+  }
+  
+  return result;
+}
+
+/**
+ * 验证行布局结果
+ * 
+ * @param systems 行数组
+ * @param systemWidth 期望的行宽度
+ * @returns 验证结果
+ */
+export function validateSystemLayout(
+  systems: JianpuSystem[],
+  systemWidth: number
+): { valid: boolean; errors: string[] } {
+  const errors: string[] = [];
+  
+  for (let i = 0; i < systems.length; i++) {
+    const system = systems[i];
+    
+    // 检查行是否为空
+    if (system.measures.length === 0) {
+      errors.push(`行 ${i} 没有小节`);
+    }
+    
+    // 检查小节总宽度是否超过行宽度(允许1像素误差)
+    const totalMeasureWidth = system.measures.reduce((sum, m) => sum + m.width, 0);
+    if (totalMeasureWidth > system.width + 1) {
+      errors.push(`行 ${i} 小节总宽度 ${totalMeasureWidth.toFixed(2)} 超过行宽度 ${system.width}`);
+    }
+    
+    // 检查小节X坐标连续性
+    let expectedX = system.x;
+    for (const measure of system.measures) {
+      if (Math.abs(measure.x - expectedX) > 0.01) {
+        errors.push(`行 ${i} 小节 ${measure.index} X坐标不连续: 期望 ${expectedX.toFixed(2)}, 实际 ${measure.x.toFixed(2)}`);
+      }
+      expectedX += measure.width;
+    }
+    
+    // 检查小节Y坐标
+    for (const measure of system.measures) {
+      if (measure.y !== system.y) {
+        errors.push(`行 ${i} 小节 ${measure.index} Y坐标错误: 期望 ${system.y}, 实际 ${measure.y}`);
+      }
+    }
+  }
+  
+  // 检查行Y坐标递增
+  for (let i = 1; i < systems.length; i++) {
+    if (systems[i].y <= systems[i - 1].y) {
+      errors.push(`行 ${i} Y坐标 ${systems[i].y} 应该大于行 ${i - 1} Y坐标 ${systems[i - 1].y}`);
+    }
+  }
+  
+  return {
+    valid: errors.length === 0,
+    errors,
+  };
+}
+
+/**
+ * 获取小节所在行的信息
+ * 
+ * @param systems 行数组
+ * @param measureIndex 小节索引
+ * @returns 行信息(未找到返回null)
+ */
+export function getSystemInfoForMeasure(
+  systems: JianpuSystem[],
+  measureIndex: number
+): { system: JianpuSystem; systemIndex: number; measureIndexInSystem: number } | null {
+  for (let sysIndex = 0; sysIndex < systems.length; sysIndex++) {
+    const system = systems[sysIndex];
+    for (let i = 0; i < system.measures.length; i++) {
+      if (system.measures[i].index === measureIndex) {
+        return {
+          system,
+          systemIndex: sysIndex,
+          measureIndexInSystem: i,
+        };
+      }
+    }
   }
+  return null;
 }

+ 344 - 0
src/jianpu-renderer/core/parser/DivisionsHandler.ts

@@ -0,0 +1,344 @@
+/**
+ * Divisions处理器
+ * 
+ * @description MusicXML divisions是时值计算的基础
+ * divisions定义了每个四分音符包含多少个基本时值单位
+ * 
+ * 核心公式:realValue = duration / divisions
+ * 其中 realValue 以四分音符为单位(1.0 = 四分音符)
+ * 
+ * @example
+ * ```typescript
+ * const handler = new DivisionsHandler();
+ * handler.setDivisions(256);
+ * const realValue = handler.toRealValue(128); // 返回 0.5(八分音符)
+ * ```
+ */
+
+/** 默认divisions值(常见值:1, 256, 480, 960) */
+const DEFAULT_DIVISIONS = 256;
+
+/** 允许的最大divisions值(防止异常大数值导致计算问题) */
+const MAX_DIVISIONS = 10000;
+
+/** 允许的最小divisions值 */
+const MIN_DIVISIONS = 1;
+
+/**
+ * 时值转换结果
+ */
+export interface RealValueResult {
+  /** 实际时值(以四分音符为单位) */
+  value: number;
+  /** 是否有警告 */
+  hasWarning: boolean;
+  /** 警告信息 */
+  warningMessage?: string;
+}
+
+/**
+ * Divisions处理器
+ * 
+ * 负责管理MusicXML的divisions值并提供时值转换功能
+ */
+export class DivisionsHandler {
+  /** 当前divisions值 */
+  private currentDivisions: number = DEFAULT_DIVISIONS;
+  
+  /** 每个小节的divisions值缓存(小节索引 -> divisions值) */
+  private measureDivisionsMap: Map<number, number> = new Map();
+  
+  /** 是否启用严格模式(严格模式下会抛出错误而不是警告) */
+  private strictMode: boolean = false;
+
+  /**
+   * 创建Divisions处理器实例
+   * 
+   * @param strictMode 是否启用严格模式(默认false)
+   */
+  constructor(strictMode: boolean = false) {
+    this.strictMode = strictMode;
+  }
+
+  /**
+   * 设置当前divisions值
+   * 
+   * @param divisions divisions值
+   * @throws 当divisions为0且严格模式开启时抛出错误
+   */
+  setDivisions(divisions: number | null | undefined): void {
+    // 处理null/undefined
+    if (divisions === null || divisions === undefined) {
+      console.warn(`[DivisionsHandler] divisions为${divisions},使用默认值${DEFAULT_DIVISIONS}`);
+      this.currentDivisions = DEFAULT_DIVISIONS;
+      return;
+    }
+
+    // 处理0值
+    if (divisions === 0) {
+      const errorMsg = 'divisions值不能为0';
+      if (this.strictMode) {
+        throw new Error(`[DivisionsHandler] ${errorMsg}`);
+      }
+      console.error(`[DivisionsHandler] ${errorMsg},使用默认值${DEFAULT_DIVISIONS}`);
+      this.currentDivisions = DEFAULT_DIVISIONS;
+      return;
+    }
+
+    // 处理负数
+    if (divisions < 0) {
+      console.warn(`[DivisionsHandler] divisions为负数(${divisions}),已取绝对值`);
+      divisions = Math.abs(divisions);
+    }
+
+    // 处理过大值
+    if (divisions > MAX_DIVISIONS) {
+      console.warn(`[DivisionsHandler] divisions过大(${divisions}),可能存在数据问题`);
+    }
+
+    this.currentDivisions = divisions;
+  }
+
+  /**
+   * 获取当前divisions值
+   * 
+   * @returns 当前divisions值
+   */
+  getDivisions(): number {
+    return this.currentDivisions;
+  }
+
+  /**
+   * 设置指定小节的divisions值
+   * 
+   * @param measureIndex 小节索引(从0开始)
+   * @param divisions divisions值
+   */
+  setMeasureDivisions(measureIndex: number, divisions: number): void {
+    this.measureDivisionsMap.set(measureIndex, divisions);
+    // 同时更新当前值
+    this.setDivisions(divisions);
+  }
+
+  /**
+   * 获取指定小节的divisions值
+   * 
+   * @param measureIndex 小节索引(从0开始)
+   * @returns 该小节的divisions值,如果未设置则返回当前值
+   */
+  getMeasureDivisions(measureIndex: number): number {
+    return this.measureDivisionsMap.get(measureIndex) ?? this.currentDivisions;
+  }
+
+  /**
+   * 将MusicXML的duration转换为实际时值
+   * 
+   * @param duration MusicXML中的duration值
+   * @returns 实际时值(以四分音符为单位,1.0 = 四分音符)
+   * 
+   * @example
+   * ```typescript
+   * handler.setDivisions(256);
+   * handler.toRealValue(256);  // 1.0 (四分音符)
+   * handler.toRealValue(128);  // 0.5 (八分音符)
+   * handler.toRealValue(512);  // 2.0 (二分音符)
+   * handler.toRealValue(384);  // 1.5 (附点四分音符)
+   * ```
+   */
+  toRealValue(duration: number): number {
+    // 处理null/undefined
+    if (duration === null || duration === undefined) {
+      console.warn(`[DivisionsHandler] duration为${duration},返回0`);
+      return 0;
+    }
+
+    // 处理负数
+    if (duration < 0) {
+      console.warn(`[DivisionsHandler] duration为负数(${duration}),已取绝对值`);
+      duration = Math.abs(duration);
+    }
+
+    return duration / this.currentDivisions;
+  }
+
+  /**
+   * 将MusicXML的duration转换为实际时值(带详细结果)
+   * 
+   * @param duration MusicXML中的duration值
+   * @returns 包含时值和警告信息的结果对象
+   */
+  toRealValueWithInfo(duration: number): RealValueResult {
+    let hasWarning = false;
+    let warningMessage: string | undefined;
+
+    // 处理null/undefined
+    if (duration === null || duration === undefined) {
+      warningMessage = `duration为${duration},返回0`;
+      return { value: 0, hasWarning: true, warningMessage };
+    }
+
+    // 处理负数
+    if (duration < 0) {
+      warningMessage = `duration为负数(${duration}),已取绝对值`;
+      hasWarning = true;
+      duration = Math.abs(duration);
+    }
+
+    const value = duration / this.currentDivisions;
+
+    return { value, hasWarning, warningMessage };
+  }
+
+  /**
+   * 将实际时值转换回MusicXML的duration
+   * 
+   * @param realValue 实际时值(以四分音符为单位)
+   * @returns MusicXML中的duration值
+   */
+  toDuration(realValue: number): number {
+    return Math.round(realValue * this.currentDivisions);
+  }
+
+  /**
+   * 计算音符时长(秒)
+   * 
+   * @param duration MusicXML中的duration值
+   * @param bpm 每分钟节拍数(四分音符为单位)
+   * @returns 音符时长(秒)
+   * 
+   * @example
+   * ```typescript
+   * handler.setDivisions(256);
+   * // BPM=120,一个四分音符=0.5秒
+   * handler.toSeconds(256, 120);  // 0.5秒
+   * handler.toSeconds(128, 120);  // 0.25秒(八分音符)
+   * ```
+   */
+  toSeconds(duration: number, bpm: number): number {
+    if (bpm <= 0) {
+      console.warn(`[DivisionsHandler] BPM为${bpm},使用默认值120`);
+      bpm = 120;
+    }
+
+    const realValue = this.toRealValue(duration);
+    // 四分音符时长(秒)= 60 / BPM
+    const quarterNoteDuration = 60 / bpm;
+    return realValue * quarterNoteDuration;
+  }
+
+  /**
+   * 获取音符类型对应的realValue
+   * 
+   * @param noteType 音符类型(如 'quarter', 'eighth', 'half' 等)
+   * @returns 对应的realValue
+   */
+  getNoteTypeRealValue(noteType: string): number {
+    const noteTypeMap: Record<string, number> = {
+      'maxima': 32,      // 最长音符
+      'long': 16,        // 长音符
+      'breve': 8,        // 二全音符
+      'whole': 4,        // 全音符
+      'half': 2,         // 二分音符
+      'quarter': 1,      // 四分音符
+      'eighth': 0.5,     // 八分音符
+      '16th': 0.25,      // 十六分音符
+      '32nd': 0.125,     // 三十二分音符
+      '64th': 0.0625,    // 六十四分音符
+      '128th': 0.03125,  // 一百二十八分音符
+      '256th': 0.015625, // 二百五十六分音符
+      '512th': 0.0078125, // 五百一十二分音符
+      '1024th': 0.00390625, // 一千零二十四分音符
+    };
+
+    const value = noteTypeMap[noteType.toLowerCase()];
+    if (value === undefined) {
+      console.warn(`[DivisionsHandler] 未知的音符类型: ${noteType},默认返回1.0(四分音符)`);
+      return 1;
+    }
+
+    return value;
+  }
+
+  /**
+   * 计算附点后的时值
+   * 
+   * @param baseRealValue 基础时值
+   * @param dotCount 附点数量(1个附点增加50%,2个附点增加75%)
+   * @returns 附点后的时值
+   */
+  applyDots(baseRealValue: number, dotCount: number): number {
+    if (dotCount <= 0) {
+      return baseRealValue;
+    }
+
+    let result = baseRealValue;
+    let addition = baseRealValue / 2;
+
+    for (let i = 0; i < dotCount; i++) {
+      result += addition;
+      addition /= 2;
+    }
+
+    return result;
+  }
+
+  /**
+   * 重置处理器状态
+   */
+  reset(): void {
+    this.currentDivisions = DEFAULT_DIVISIONS;
+    this.measureDivisionsMap.clear();
+  }
+
+  /**
+   * 获取所有小节的divisions映射
+   * 
+   * @returns 小节索引到divisions值的映射
+   */
+  getAllMeasureDivisions(): Map<number, number> {
+    return new Map(this.measureDivisionsMap);
+  }
+}
+
+/**
+ * 创建DivisionsHandler实例的工厂函数
+ * 
+ * @param strictMode 是否启用严格模式
+ * @returns DivisionsHandler实例
+ */
+export function createDivisionsHandler(strictMode: boolean = false): DivisionsHandler {
+  return new DivisionsHandler(strictMode);
+}
+
+/**
+ * 快捷函数:将duration转换为realValue
+ * 
+ * @param duration MusicXML中的duration值
+ * @param divisions divisions值
+ * @returns 实际时值
+ */
+export function toRealValue(duration: number, divisions: number): number {
+  if (divisions === 0) {
+    throw new Error('divisions不能为0');
+  }
+  return duration / divisions;
+}
+
+/**
+ * 快捷函数:计算附点时值
+ * 
+ * @param baseValue 基础时值
+ * @param dots 附点数量
+ * @returns 附点后的时值
+ */
+export function applyDots(baseValue: number, dots: number): number {
+  let result = baseValue;
+  let addition = baseValue / 2;
+  
+  for (let i = 0; i < dots; i++) {
+    result += addition;
+    addition /= 2;
+  }
+  
+  return result;
+}

+ 794 - 16
src/jianpu-renderer/core/parser/OSMDDataParser.ts

@@ -2,53 +2,831 @@
  * OSMD数据解析器
  * 
  * @description 从OpenSheetMusicDisplay对象中解析简谱所需的数据
+ * 
+ * 核心功能:
+ * 1. 解析曲谱元数据(标题、作曲家等)
+ * 2. 解析小节信息(拍号、调号等)
+ * 3. 解析音符数据(音高、时值、八度等)
+ * 4. 保持OSMD原始数据引用(用于兼容层)
  */
 
-import { JianpuScore, JianpuMeasure, JianpuNote } from '../../models';
+import { JianpuScore } from '../../models/JianpuScore';
+import { JianpuMeasure, createDefaultMeasure } from '../../models/JianpuMeasure';
+import { JianpuNote, createDefaultNote } from '../../models/JianpuNote';
+import { DivisionsHandler } from './DivisionsHandler';
+import { DEFAULT_RENDER_CONFIG } from '../config/RenderConfig';
+
+// ==================== 常量定义 ====================
+
+/** 音名到简谱数字的映射 */
+const STEP_TO_JIANPU: Record<string, number> = {
+  'C': 1, 'D': 2, 'E': 3, 'F': 4, 'G': 5, 'A': 6, 'B': 7
+};
+
+/** 基准八度(中央C所在的八度) */
+const BASE_OCTAVE = 4;
+
+/** 音符类型到时值的映射 */
+const TYPE_TO_DURATION: Record<string, number> = {
+  '1024th': 1/256,
+  '512th': 1/128,
+  '256th': 1/64,
+  '128th': 1/32,
+  '64th': 1/16,
+  '32nd': 1/8,
+  '16th': 0.25,
+  'eighth': 0.5,
+  'quarter': 1.0,
+  'half': 2.0,
+  'whole': 4.0,
+  'breve': 8.0,
+  'long': 16.0,
+  'maxima': 32.0,
+};
+
+/** 升降号数量到调名的映射 */
+const FIFTHS_TO_KEY: Record<number, { key: string; mode: string }> = {
+  '-7': { key: 'Cb', mode: 'major' },
+  '-6': { key: 'Gb', mode: 'major' },
+  '-5': { key: 'Db', mode: 'major' },
+  '-4': { key: 'Ab', mode: 'major' },
+  '-3': { key: 'Eb', mode: 'major' },
+  '-2': { key: 'Bb', mode: 'major' },
+  '-1': { key: 'F', mode: 'major' },
+  '0': { key: 'C', mode: 'major' },
+  '1': { key: 'G', mode: 'major' },
+  '2': { key: 'D', mode: 'major' },
+  '3': { key: 'A', mode: 'major' },
+  '4': { key: 'E', mode: 'major' },
+  '5': { key: 'B', mode: 'major' },
+  '6': { key: 'F#', mode: 'major' },
+  '7': { key: 'C#', mode: 'major' },
+};
+
+// ==================== 类型定义 ====================
+
+/** OSMD实例类型(简化定义,避免依赖OSMD类型) */
+interface OSMDInstance {
+  GraphicSheet?: {
+    MeasureList?: any[][];
+  };
+  Sheet?: {
+    Title?: { text?: string };
+    Subtitle?: { text?: string };
+    Composer?: { text?: string };
+    Lyricist?: { text?: string };
+    Instruments?: any[];
+    SourceMeasures?: any[];
+  };
+  cursor?: {
+    Iterator?: any;
+    reset?: () => void;
+    next?: () => void;
+  };
+}
+
+/** 解析选项 */
+export interface ParseOptions {
+  /** 是否跳过装饰音 */
+  skipGraceNotes?: boolean;
+  /** 是否包含多声部 */
+  includeMultiVoice?: boolean;
+  /** 默认速度(BPM) */
+  defaultTempo?: number;
+}
+
+/** 解析结果统计 */
+export interface ParseStats {
+  measureCount: number;
+  noteCount: number;
+  voiceCount: number;
+  restCount: number;
+  graceNoteCount: number;
+  parseTime: number;
+}
 
+// ==================== 主类 ====================
+
+/**
+ * OSMD数据解析器
+ */
 export class OSMDDataParser {
+  /** Divisions处理器 */
+  private divisionsHandler: DivisionsHandler;
+  
+  /** 解析选项 */
+  private options: Required<ParseOptions>;
+  
+  /** 解析统计 */
+  private stats: ParseStats = {
+    measureCount: 0,
+    noteCount: 0,
+    voiceCount: 0,
+    restCount: 0,
+    graceNoteCount: 0,
+    parseTime: 0,
+  };
+
+  /** 音符ID计数器 */
+  private noteIdCounter: number = 0;
+
+  /**
+   * 构造函数
+   * @param options 解析选项
+   */
+  constructor(options: ParseOptions = {}) {
+    this.divisionsHandler = new DivisionsHandler();
+    this.options = {
+      skipGraceNotes: options.skipGraceNotes ?? false,
+      includeMultiVoice: options.includeMultiVoice ?? true,
+      defaultTempo: options.defaultTempo ?? 120,
+    };
+  }
+
   /**
    * 从OSMD对象解析简谱数据
    * 
    * @param osmd OpenSheetMusicDisplay实例
    * @returns 简谱数据结构
    */
-  parse(osmd: any): JianpuScore {
+  parse(osmd: OSMDInstance): JianpuScore {
+    const startTime = performance.now();
     console.log('[OSMDDataParser] 开始解析OSMD数据');
+
+    // 验证OSMD对象
+    if (!osmd) {
+      throw new Error('[OSMDDataParser] OSMD实例不能为空');
+    }
+
+    // 重置状态
+    this.resetState();
+
+    // 1. 提取元数据
+    const metadata = this.extractMetadata(osmd);
+    console.log(`[OSMDDataParser] 元数据: 标题="${metadata.title}", 作曲="${metadata.composer}"`);
+
+    // 2. 解析小节
+    const measures = this.parseMeasures(osmd);
+    console.log(`[OSMDDataParser] 解析了 ${measures.length} 个小节`);
+
+    // 3. 解析音符(填充到小节中)
+    this.parseNotes(osmd, measures);
+    console.log(`[OSMDDataParser] 解析了 ${this.stats.noteCount} 个音符`);
+
+    // 4. 构建JianpuScore
+    const score: JianpuScore = {
+      title: metadata.title,
+      subtitle: metadata.subtitle,
+      composer: metadata.composer,
+      lyricist: metadata.lyricist,
+      systems: [], // 布局引擎会填充
+      measures,
+      tempo: metadata.tempo,
+      initialTimeSignature: measures[0]?.timeSignature ?? { beats: 4, beatType: 4 },
+      initialKeySignature: measures[0]?.keySignature ?? { key: 'C', mode: 'major' },
+      config: { ...DEFAULT_RENDER_CONFIG },
+      totalMeasures: measures.length,
+      voiceCount: this.stats.voiceCount,
+      metadata: {
+        parseStats: { ...this.stats },
+      },
+    };
+
+    // 计算总时长
+    score.duration = this.calculateTotalDuration(measures, metadata.tempo);
+
+    this.stats.parseTime = performance.now() - startTime;
+    console.log(`[OSMDDataParser] 解析完成,耗时 ${this.stats.parseTime.toFixed(2)}ms`);
+
+    return score;
+  }
+
+  /**
+   * 获取解析统计
+   */
+  getStats(): ParseStats {
+    return { ...this.stats };
+  }
+
+  // ==================== 私有方法 ====================
+
+  /**
+   * 重置解析状态
+   */
+  private resetState(): void {
+    this.divisionsHandler.reset();
+    this.noteIdCounter = 0;
+    this.stats = {
+      measureCount: 0,
+      noteCount: 0,
+      voiceCount: 0,
+      restCount: 0,
+      graceNoteCount: 0,
+      parseTime: 0,
+    };
+  }
+
+  /**
+   * 生成唯一音符ID
+   */
+  private generateNoteId(): string {
+    return `note-${++this.noteIdCounter}`;
+  }
+
+  /**
+   * 提取曲谱元数据
+   */
+  private extractMetadata(osmd: OSMDInstance): {
+    title: string;
+    subtitle?: string;
+    composer?: string;
+    lyricist?: string;
+    tempo: number;
+  } {
+    const sheet = osmd.Sheet;
+    
+    let tempo = this.options.defaultTempo;
     
-    // TODO: 实现解析逻辑
-    throw new Error('OSMDDataParser.parse() not implemented yet');
+    // 尝试从第一个小节获取速度
+    if (sheet?.SourceMeasures?.[0]) {
+      const firstMeasure = sheet.SourceMeasures[0];
+      if (firstMeasure.tempoInBPM) {
+        tempo = firstMeasure.tempoInBPM;
+      } else if (firstMeasure.TempoExpressions?.[0]?.InstantaneousTempo?.tempoInBpm) {
+        tempo = firstMeasure.TempoExpressions[0].InstantaneousTempo.tempoInBpm;
+      }
+    }
+
+    return {
+      title: sheet?.Title?.text ?? 'Untitled',
+      subtitle: sheet?.Subtitle?.text,
+      composer: sheet?.Composer?.text,
+      lyricist: sheet?.Lyricist?.text,
+      tempo,
+    };
   }
-  
+
   /**
    * 解析小节信息
    */
-  private parseMeasures(osmd: any): JianpuMeasure[] {
-    // TODO: 实现
-    return [];
+  private parseMeasures(osmd: OSMDInstance): JianpuMeasure[] {
+    const measures: JianpuMeasure[] = [];
+    
+    // 优先使用SourceMeasures(更准确)
+    const sourceMeasures = osmd.Sheet?.SourceMeasures;
+    
+    if (sourceMeasures && sourceMeasures.length > 0) {
+      for (let i = 0; i < sourceMeasures.length; i++) {
+        const srcMeasure = sourceMeasures[i];
+        const measure = this.parseSourceMeasure(srcMeasure, i);
+        measures.push(measure);
+      }
+    } else {
+      // 回退到GraphicSheet.MeasureList
+      const measureList = osmd.GraphicSheet?.MeasureList;
+      
+      if (measureList && measureList.length > 0) {
+        for (let i = 0; i < measureList.length; i++) {
+          const graphicalMeasures = measureList[i];
+          if (graphicalMeasures && graphicalMeasures.length > 0) {
+            const measure = this.parseGraphicalMeasure(graphicalMeasures[0], i);
+            measures.push(measure);
+          }
+        }
+      }
+    }
+
+    this.stats.measureCount = measures.length;
+    return measures;
   }
-  
+
+  /**
+   * 解析SourceMeasure
+   */
+  private parseSourceMeasure(srcMeasure: any, index: number): JianpuMeasure {
+    // 获取拍号
+    const timeSig = srcMeasure.ActiveTimeSignature;
+    const timeSignature = {
+      beats: timeSig?.numerator ?? timeSig?.Numerator ?? 4,
+      beatType: timeSig?.denominator ?? timeSig?.Denominator ?? 4,
+    };
+
+    // 获取调号
+    const keySig = srcMeasure.ActiveKeySignature;
+    const keySignature = this.parseKeySignature(keySig);
+
+    // 获取速度
+    let tempo: number | undefined;
+    if (srcMeasure.tempoInBPM) {
+      tempo = srcMeasure.tempoInBPM;
+    } else if (srcMeasure.TempoExpressions?.[0]?.InstantaneousTempo?.tempoInBpm) {
+      tempo = srcMeasure.TempoExpressions[0].InstantaneousTempo.tempoInBpm;
+    }
+
+    // 创建小节
+    const measure = createDefaultMeasure(index + 1, timeSignature);
+    measure.keySignature = keySignature;
+    
+    if (tempo !== undefined) {
+      measure.tempo = tempo;
+    }
+
+    // 小节线类型
+    measure.barlineType = this.getBarlineType(srcMeasure);
+
+    // 是否显示拍号/调号(第一小节或变化时显示)
+    measure.showTimeSignature = index === 0;
+    measure.showKeySignature = index === 0;
+
+    return measure;
+  }
+
+  /**
+   * 解析GraphicalMeasure
+   */
+  private parseGraphicalMeasure(graphicalMeasure: any, index: number): JianpuMeasure {
+    const srcMeasure = graphicalMeasure.parentSourceMeasure;
+    
+    if (srcMeasure) {
+      return this.parseSourceMeasure(srcMeasure, index);
+    }
+
+    // 如果没有SourceMeasure,创建默认小节
+    return createDefaultMeasure(index + 1);
+  }
+
+  /**
+   * 解析调号
+   */
+  private parseKeySignature(keySig: any): {
+    key: string;
+    mode: 'major' | 'minor';
+    alterations?: number;
+  } {
+    if (!keySig) {
+      return { key: 'C', mode: 'major', alterations: 0 };
+    }
+
+    const fifths = keySig.keyTypeOriginal ?? keySig.Key ?? 0;
+    const mode = keySig.Mode ?? 0; // 0 = major, 1 = minor
+
+    const keyInfo = FIFTHS_TO_KEY[fifths.toString()] ?? { key: 'C', mode: 'major' };
+    
+    return {
+      key: keyInfo.key,
+      mode: mode === 1 ? 'minor' : 'major',
+      alterations: fifths,
+    };
+  }
+
+  /**
+   * 获取小节线类型
+   */
+  private getBarlineType(srcMeasure: any): JianpuMeasure['barlineType'] {
+    const instructions = srcMeasure.lastRepetitionInstructions;
+    
+    if (instructions && instructions.length > 0) {
+      for (const inst of instructions) {
+        const type = inst.type;
+        if (type === 'StartLine' || type === 2) return 'repeat-start';
+        if (type === 'BackJumpLine' || type === 3) return 'repeat-end';
+        if (type === 'Ending') return 'double';
+      }
+    }
+
+    // 检查是否是最后一个小节
+    if (srcMeasure.endingBarStyleEnum === 'light-heavy' || 
+        srcMeasure.endingBarStyle === 'light-heavy') {
+      return 'final';
+    }
+
+    return 'single';
+  }
+
   /**
    * 解析音符信息
    */
-  private parseNotes(osmd: any, measures: JianpuMeasure[]): JianpuNote[] {
-    // TODO: 实现
-    return [];
+  private parseNotes(osmd: OSMDInstance, measures: JianpuMeasure[]): void {
+    const cursor = osmd.cursor;
+    
+    if (!cursor?.Iterator) {
+      console.warn('[OSMDDataParser] 无法获取cursor.Iterator,尝试从MeasureList解析');
+      this.parseNotesFromMeasureList(osmd, measures);
+      return;
+    }
+
+    // 使用cursor遍历
+    cursor.reset?.();
+    
+    const iterator = cursor.Iterator;
+    let maxVoiceIndex = 0;
+
+    while (!iterator.EndReached) {
+      const voiceEntries = iterator.currentVoiceEntries ?? iterator.CurrentVoiceEntries ?? [];
+      const measureIndex = iterator.currentMeasureIndex ?? 0;
+      
+      if (measureIndex >= 0 && measureIndex < measures.length) {
+        const measure = measures[measureIndex];
+        const timestamp = iterator.currentTimeStamp?.RealValue ?? 
+                         iterator.currentTimeStamp?.realValue ?? 0;
+
+        for (const voiceEntry of voiceEntries) {
+          const voiceIndex = voiceEntry.ParentVoice?.VoiceId ?? 0;
+          maxVoiceIndex = Math.max(maxVoiceIndex, voiceIndex);
+
+          // 确保声部数组存在
+          while (measure.voices.length <= voiceIndex) {
+            measure.voices.push([]);
+          }
+
+          const notes = voiceEntry.Notes ?? voiceEntry.notes ?? [];
+          
+          for (const note of notes) {
+            const jianpuNote = this.parseNote(note, measureIndex, voiceIndex, timestamp);
+            
+            if (jianpuNote) {
+              measure.voices[voiceIndex].push(jianpuNote);
+              this.stats.noteCount++;
+              
+              if (jianpuNote.isRest) this.stats.restCount++;
+              if (jianpuNote.isGraceNote) this.stats.graceNoteCount++;
+            }
+          }
+        }
+      }
+
+      // 移动到下一个
+      try {
+        if (iterator.moveToNextVisibleVoiceEntry) {
+          iterator.moveToNextVisibleVoiceEntry(this.options.skipGraceNotes);
+        } else {
+          cursor.next?.();
+        }
+      } catch (e) {
+        console.warn('[OSMDDataParser] Iterator移动失败:', e);
+        break;
+      }
+    }
+
+    this.stats.voiceCount = maxVoiceIndex + 1;
+    
+    // 同步notes和voices引用
+    for (const measure of measures) {
+      measure.notes = measure.voices;
+    }
+  }
+
+  /**
+   * 从MeasureList解析音符(备用方案)
+   */
+  private parseNotesFromMeasureList(osmd: OSMDInstance, measures: JianpuMeasure[]): void {
+    const measureList = osmd.GraphicSheet?.MeasureList;
+    
+    if (!measureList) {
+      console.warn('[OSMDDataParser] 无法获取MeasureList');
+      return;
+    }
+
+    let maxVoiceIndex = 0;
+
+    for (let measureIdx = 0; measureIdx < measureList.length && measureIdx < measures.length; measureIdx++) {
+      const graphicalMeasures = measureList[measureIdx];
+      const measure = measures[measureIdx];
+
+      if (!graphicalMeasures) continue;
+
+      for (const gMeasure of graphicalMeasures) {
+        if (!gMeasure?.staffEntries) continue;
+
+        for (const staffEntry of gMeasure.staffEntries) {
+          const timestamp = staffEntry.relInMeasureTimestamp?.RealValue ?? 0;
+          const voiceEntries = staffEntry.graphicalVoiceEntries ?? [];
+
+          for (const gve of voiceEntries) {
+            const voiceIndex = gve.parentVoiceEntry?.ParentVoice?.VoiceId ?? 0;
+            maxVoiceIndex = Math.max(maxVoiceIndex, voiceIndex);
+
+            // 确保声部数组存在
+            while (measure.voices.length <= voiceIndex) {
+              measure.voices.push([]);
+            }
+
+            const notes = gve.notes ?? [];
+            
+            for (const graphicalNote of notes) {
+              const srcNote = graphicalNote.sourceNote;
+              if (!srcNote) continue;
+
+              const jianpuNote = this.parseNote(srcNote, measureIdx, voiceIndex, timestamp);
+              
+              if (jianpuNote) {
+                // 保存图形化音符的SVG引用
+                if (graphicalNote.vfnote?.[0]?.attrs?.id) {
+                  jianpuNote.osmdCompatible.svgElement.attrs.id = graphicalNote.vfnote[0].attrs.id;
+                }
+
+                measure.voices[voiceIndex].push(jianpuNote);
+                this.stats.noteCount++;
+                
+                if (jianpuNote.isRest) this.stats.restCount++;
+                if (jianpuNote.isGraceNote) this.stats.graceNoteCount++;
+              }
+            }
+          }
+        }
+      }
+    }
+
+    this.stats.voiceCount = maxVoiceIndex + 1;
+    
+    // 同步notes和voices引用
+    for (const measure of measures) {
+      measure.notes = measure.voices;
+    }
   }
-  
+
+  /**
+   * 解析单个音符
+   */
+  private parseNote(
+    note: any, 
+    measureIndex: number, 
+    voiceIndex: number,
+    timestamp: number
+  ): JianpuNote | null {
+    // 跳过装饰音(如果配置了)
+    if (this.options.skipGraceNotes && note.IsGraceNote) {
+      return null;
+    }
+
+    const isRest = note.isRestFlag ?? note.IsRest ?? note.isRest ?? false;
+    const isGraceNote = note.IsGraceNote ?? note.isGraceNote ?? false;
+
+    // 获取音高信息
+    let pitch = 0;
+    let octave = 0;
+    let accidental: JianpuNote['accidental'] = undefined;
+    let halfTone = 0;
+    let frequency = 0;
+
+    if (!isRest && note.pitch) {
+      pitch = this.getPitchNumber(note);
+      octave = this.getOctaveOffset(note);
+      accidental = this.getAccidental(note);
+      halfTone = note.halfTone ?? this.calculateHalfTone(note);
+      frequency = note.pitch?.frequency ?? this.halfToneToFrequency(halfTone);
+    }
+
+    // 获取时值
+    const duration = this.getNoteDuration(note);
+    const dots = this.getDotCount(note);
+
+    // 创建音符
+    const jianpuNote = createDefaultNote({
+      pitch,
+      octave,
+      duration,
+      accidental,
+      dots,
+      timestamp,
+      voiceIndex,
+      measureIndex,
+      isRest,
+      isGraceNote,
+      isStaccato: note.isStaccato ?? false,
+    });
+
+    // 设置ID
+    jianpuNote.id = this.generateNoteId();
+    
+    // 设置OSMD兼容数据
+    jianpuNote.osmdCompatible = {
+      noteElement: note,
+      svgElement: {
+        attrs: { id: `vf-${jianpuNote.id}` },
+        modifiers: note.modifiers,
+      },
+      halfTone,
+      frequency,
+      realKey: pitch > 0 ? pitch + octave * 7 : 0,
+    };
+
+    return jianpuNote;
+  }
+
   /**
    * 获取简谱音高(1-7)
    */
   private getPitchNumber(note: any): number {
-    // TODO: 实现
+    // 方式1:从pitch.step获取
+    const step = note.pitch?.step ?? note.pitch?.Step;
+    if (step !== undefined) {
+      const pitch = STEP_TO_JIANPU[step.toString().toUpperCase()];
+      if (pitch !== undefined) {
+        return pitch;
+      }
+    }
+
+    // 方式2:从halfTone计算
+    const halfTone = note.halfTone ?? note.HalfTone;
+    if (halfTone !== undefined) {
+      // halfTone % 12 得到音级,映射到1-7
+      const noteInOctave = ((halfTone % 12) + 12) % 12;
+      const halfToneToJianpu = [1, 1, 2, 2, 3, 4, 4, 5, 5, 6, 6, 7]; // C C# D D# E F F# G G# A A# B
+      return halfToneToJianpu[noteInOctave];
+    }
+
+    console.warn('[OSMDDataParser] 无法获取音高信息,返回默认值1');
     return 1;
   }
-  
+
   /**
    * 获取八度偏移
    */
   private getOctaveOffset(note: any): number {
-    // TODO: 实现
+    // 方式1:从pitch.octave获取
+    const octave = note.pitch?.octave ?? note.pitch?.Octave;
+    if (octave !== undefined) {
+      return octave - BASE_OCTAVE;
+    }
+
+    // 方式2:从halfTone计算
+    const halfTone = note.halfTone ?? note.HalfTone;
+    if (halfTone !== undefined) {
+      const octaveFromHalfTone = Math.floor(halfTone / 12) - 1; // MIDI标准
+      return octaveFromHalfTone - BASE_OCTAVE;
+    }
+
+    return 0;
+  }
+
+  /**
+   * 获取升降号
+   */
+  private getAccidental(note: any): JianpuNote['accidental'] | undefined {
+    // 方式1:从pitch.alter获取
+    const alter = note.pitch?.alter ?? note.pitch?.Alter;
+    
+    if (alter === 1 || alter === 2) return 'sharp';
+    if (alter === -1 || alter === -2) return 'flat';
+    if (alter === 0) return 'natural';
+
+    // 方式2:从accidental属性获取
+    const accidental = note.accidental ?? note.Accidental;
+    if (accidental) {
+      const accStr = accidental.toString().toLowerCase();
+      if (accStr.includes('sharp')) return 'sharp';
+      if (accStr.includes('flat')) return 'flat';
+      if (accStr.includes('natural')) return 'natural';
+    }
+
+    return undefined;
+  }
+
+  /**
+   * 获取音符时值
+   */
+  private getNoteDuration(note: any): number {
+    // 方式1:从length.realValue获取(最准确)
+    const realValue = note.length?.RealValue ?? note.length?.realValue;
+    if (realValue !== undefined && realValue > 0) {
+      return realValue;
+    }
+
+    // 方式2:从noteType获取
+    const noteType = note.noteTypeXml ?? note.TypeLength?.toString() ?? note.type;
+    if (noteType) {
+      const baseDuration = TYPE_TO_DURATION[noteType.toLowerCase()];
+      if (baseDuration !== undefined) {
+        // 应用附点
+        const dots = this.getDotCount(note);
+        return this.divisionsHandler.applyDots(baseDuration, dots);
+      }
+    }
+
+    // 方式3:从duration和divisions计算
+    const duration = note.duration ?? note.Duration;
+    if (duration !== undefined) {
+      const sourceMeasure = note.sourceMeasure ?? note.SourceMeasure;
+      const divisions = sourceMeasure?.divisions ?? 256;
+      this.divisionsHandler.setDivisions(divisions);
+      return this.divisionsHandler.toRealValue(duration);
+    }
+
+    console.warn('[OSMDDataParser] 无法获取音符时值,返回默认值1.0');
+    return 1.0;
+  }
+
+  /**
+   * 获取附点数量
+   */
+  private getDotCount(note: any): number {
+    // 方式1:直接获取dots数量
+    const dots = note.dots ?? note.Dots ?? note.DotsXml;
+    if (typeof dots === 'number') {
+      return dots;
+    }
+
+    // 方式2:从数组长度获取
+    if (Array.isArray(dots)) {
+      return dots.length;
+    }
+
     return 0;
   }
+
+  /**
+   * 计算半音值(从音高信息)
+   */
+  private calculateHalfTone(note: any): number {
+    const step = note.pitch?.step ?? note.pitch?.Step;
+    const octave = note.pitch?.octave ?? note.pitch?.Octave ?? 4;
+    const alter = note.pitch?.alter ?? note.pitch?.Alter ?? 0;
+
+    if (!step) return 60; // 默认C4
+
+    const stepToSemitone: Record<string, number> = {
+      'C': 0, 'D': 2, 'E': 4, 'F': 5, 'G': 7, 'A': 9, 'B': 11
+    };
+
+    const semitone = stepToSemitone[step.toString().toUpperCase()] ?? 0;
+    return (octave + 1) * 12 + semitone + alter;
+  }
+
+  /**
+   * 半音值转频率
+   */
+  private halfToneToFrequency(halfTone: number): number {
+    // A4 = 440Hz, MIDI note 69
+    return 440 * Math.pow(2, (halfTone - 69) / 12);
+  }
+
+  /**
+   * 计算总时长
+   */
+  private calculateTotalDuration(measures: JianpuMeasure[], tempo: number): number {
+    let totalBeats = 0;
+
+    for (const measure of measures) {
+      const { beats, beatType } = measure.timeSignature;
+      // 将节拍数转换为四分音符数量
+      const quarterNotesInMeasure = beats * (4 / beatType);
+      totalBeats += quarterNotesInMeasure;
+    }
+
+    // 四分音符时长 = 60 / BPM
+    const quarterNoteDuration = 60 / tempo;
+    return totalBeats * quarterNoteDuration;
+  }
+}
+
+// ==================== 工厂函数 ====================
+
+/**
+ * 创建OSMD数据解析器
+ */
+export function createOSMDDataParser(options?: ParseOptions): OSMDDataParser {
+  return new OSMDDataParser(options);
+}
+
+// ==================== 工具函数(导出供其他模块使用) ====================
+
+/**
+ * 音名转简谱数字
+ */
+export function stepToJianpu(step: string): number {
+  const pitch = STEP_TO_JIANPU[step.toUpperCase()];
+  if (pitch === undefined) {
+    throw new Error(`无效的音名: ${step}`);
+  }
+  return pitch;
+}
+
+/**
+ * 八度转偏移
+ */
+export function octaveToOffset(octave: number): number {
+  return octave - BASE_OCTAVE;
+}
+
+/**
+ * 升降号值转类型
+ */
+export function alterToAccidental(alter: number | null): 'sharp' | 'flat' | 'natural' | null {
+  if (alter === null || alter === undefined) return null;
+  if (alter > 0) return 'sharp';
+  if (alter < 0) return 'flat';
+  return 'natural';
+}
+
+/**
+ * 音符类型转时值
+ */
+export function noteTypeToRealValue(noteType: string): number {
+  const duration = TYPE_TO_DURATION[noteType.toLowerCase()];
+  if (duration === undefined) {
+    console.warn(`未知的音符类型: ${noteType}`);
+    return 1.0;
+  }
+  return duration;
 }

+ 485 - 7
src/jianpu-renderer/core/parser/TimeCalculator.ts

@@ -2,21 +2,499 @@
  * 时间计算器
  * 
  * @description 计算每个音符的绝对播放时间
+ * 
+ * 核心公式:
+ * - noteLength = realValue * beatUnitRatio * (60 / bpm)
+ * - time = relativeTime + fixtime
+ * - endtime = time + noteLength
+ * 
+ * 其中:
+ * - realValue: 音符时值(以四分音符为单位)
+ * - beatUnitRatio: 节拍单位比例(四分音符=1,八分音符=0.5)
+ * - bpm: 每分钟节拍数
+ * - fixtime: 节拍器前奏时间
  */
 
-import { JianpuNote, JianpuMeasure } from '../../models';
+import { JianpuScore } from '../../models/JianpuScore';
+import { JianpuNote } from '../../models/JianpuNote';
+import { JianpuMeasure } from '../../models/JianpuMeasure';
+
+// ==================== 常量定义 ====================
+
+/** 默认节拍器拍数 */
+const DEFAULT_METRONOME_BEATS = 2;
+
+/** 节拍单位到比例的映射 */
+const BEAT_UNIT_RATIO: Record<string, number> = {
+  'whole': 4.0,
+  'half': 2.0,
+  'quarter': 1.0,
+  'eighth': 0.5,
+  '16th': 0.25,
+  '32nd': 0.125,
+  '1/1': 4.0,
+  '1/2': 2.0,
+  '1/4': 1.0,
+  '1/8': 0.5,
+  '1/16': 0.25,
+  '1/32': 0.125,
+};
 
+// ==================== 类型定义 ====================
+
+/** 时间计算配置 */
+export interface TimeCalculatorConfig {
+  /** 是否启用节拍器前奏 */
+  enableMetronome?: boolean;
+  /** 节拍器拍数(默认2拍) */
+  metronomeBeatCount?: number;
+  /** 是否处理弱起小节 */
+  handlePickupMeasure?: boolean;
+  /** 速度单位(默认 '1/4') */
+  beatUnit?: string;
+  /** 音频播放倍率(默认1.0) */
+  playbackRate?: number;
+}
+
+/** 小节速度信息 */
+export interface MeasureTempoInfo {
+  /** 小节索引 */
+  measureIndex: number;
+  /** BPM速度 */
+  bpm: number;
+  /** 节拍单位 */
+  beatUnit: string;
+  /** 是否是渐变起点 */
+  isGradualStart?: boolean;
+  /** 是否是渐变终点 */
+  isGradualEnd?: boolean;
+  /** 渐变速度数组 */
+  gradualSpeeds?: number[];
+}
+
+/** 时间计算结果 */
+export interface TimeCalculationResult {
+  /** 是否有弱起 */
+  hasPickup: boolean;
+  /** 弱起补充时间(秒) */
+  pickupTime: number;
+  /** 节拍器时间(秒) */
+  metronomeTime: number;
+  /** 总fixtime(秒) */
+  fixtime: number;
+  /** 总时长(秒) */
+  totalDuration: number;
+  /** 小节速度信息 */
+  tempoMap: MeasureTempoInfo[];
+}
+
+// ==================== 主类 ====================
+
+/**
+ * 时间计算器
+ */
 export class TimeCalculator {
+  /** 配置 */
+  private config: Required<TimeCalculatorConfig>;
+
+  /** 计算结果 */
+  private result: TimeCalculationResult = {
+    hasPickup: false,
+    pickupTime: 0,
+    metronomeTime: 0,
+    fixtime: 0,
+    totalDuration: 0,
+    tempoMap: [],
+  };
+
+  /**
+   * 构造函数
+   * @param config 配置选项
+   */
+  constructor(config: TimeCalculatorConfig = {}) {
+    this.config = {
+      enableMetronome: config.enableMetronome ?? false,
+      metronomeBeatCount: config.metronomeBeatCount ?? DEFAULT_METRONOME_BEATS,
+      handlePickupMeasure: config.handlePickupMeasure ?? true,
+      beatUnit: config.beatUnit ?? '1/4',
+      playbackRate: config.playbackRate ?? 1.0,
+    };
+  }
+
   /**
    * 计算所有音符的绝对时间
+   * 
+   * @param score JianpuScore对象
+   * @returns 计算结果
    */
-  calculateTimes(
-    notes: JianpuNote[],
-    tempo: number,
-    measures: JianpuMeasure[]
-  ): void {
+  calculateTimes(score: JianpuScore): TimeCalculationResult {
     console.log('[TimeCalculator] 开始计算音符时间');
+    const startTime = performance.now();
+
+    // 重置结果
+    this.resetResult();
+
+    const { measures, tempo } = score;
     
-    // TODO: 实现时间计算逻辑
+    if (!measures || measures.length === 0) {
+      console.warn('[TimeCalculator] 没有小节数据');
+      return this.result;
+    }
+
+    // 1. 构建速度映射表
+    this.buildTempoMap(measures, tempo);
+
+    // 2. 计算节拍器时间
+    if (this.config.enableMetronome) {
+      this.result.metronomeTime = this.calculateMetronomeTime(tempo);
+    }
+
+    // 3. 检测弱起小节
+    if (this.config.handlePickupMeasure) {
+      this.result.pickupTime = this.detectPickupMeasure(measures[0], tempo);
+      this.result.hasPickup = this.result.pickupTime > 0;
+    }
+
+    // 4. 计算总fixtime
+    this.result.fixtime = this.result.metronomeTime + this.result.pickupTime;
+
+    // 5. 计算每个音符的时间
+    let cumulativeTime = 0;
+    
+    for (let measureIdx = 0; measureIdx < measures.length; measureIdx++) {
+      const measure = measures[measureIdx];
+      const tempoInfo = this.getTempoForMeasure(measureIdx, tempo);
+      const bpm = tempoInfo.bpm / this.config.playbackRate;
+      const beatUnitRatio = this.getBeatUnitRatio(tempoInfo.beatUnit);
+
+      // 计算小节时长
+      const measureDuration = this.calculateMeasureDuration(measure, bpm, beatUnitRatio);
+      
+      // 遍历所有声部
+      for (let voiceIdx = 0; voiceIdx < measure.voices.length; voiceIdx++) {
+        const voice = measure.voices[voiceIdx];
+        
+        // 按时间戳排序
+        const sortedNotes = [...voice].sort((a, b) => a.timestamp - b.timestamp);
+        
+        for (const note of sortedNotes) {
+          this.calculateNoteTime(
+            note, 
+            cumulativeTime, 
+            bpm, 
+            beatUnitRatio,
+            measureIdx,
+            measureDuration
+          );
+        }
+      }
+
+      // 累计小节时间
+      cumulativeTime += measureDuration;
+    }
+
+    this.result.totalDuration = cumulativeTime;
+
+    const elapsed = performance.now() - startTime;
+    console.log(`[TimeCalculator] 计算完成,耗时 ${elapsed.toFixed(2)}ms`);
+
+    return this.result;
   }
+
+  /**
+   * 获取计算结果
+   */
+  getResult(): TimeCalculationResult {
+    return { ...this.result };
+  }
+
+  // ==================== 私有方法 ====================
+
+  /**
+   * 重置计算结果
+   */
+  private resetResult(): void {
+    this.result = {
+      hasPickup: false,
+      pickupTime: 0,
+      metronomeTime: 0,
+      fixtime: 0,
+      totalDuration: 0,
+      tempoMap: [],
+    };
+  }
+
+  /**
+   * 构建速度映射表
+   */
+  private buildTempoMap(measures: JianpuMeasure[], defaultTempo: number): void {
+    let currentTempo = defaultTempo;
+
+    for (let i = 0; i < measures.length; i++) {
+      const measure = measures[i];
+      
+      // 检查小节是否有速度标记
+      if (measure.tempo !== undefined) {
+        currentTempo = measure.tempo;
+      }
+
+      this.result.tempoMap.push({
+        measureIndex: i,
+        bpm: currentTempo,
+        beatUnit: this.config.beatUnit,
+      });
+    }
+  }
+
+  /**
+   * 获取指定小节的速度信息
+   */
+  private getTempoForMeasure(measureIndex: number, defaultTempo: number): MeasureTempoInfo {
+    const info = this.result.tempoMap[measureIndex];
+    if (info) {
+      return info;
+    }
+    return {
+      measureIndex,
+      bpm: defaultTempo,
+      beatUnit: this.config.beatUnit,
+    };
+  }
+
+  /**
+   * 计算节拍器时间
+   */
+  private calculateMetronomeTime(bpm: number): number {
+    // 每拍时长 = 60 / bpm
+    const beatDuration = 60 / bpm;
+    return beatDuration * this.config.metronomeBeatCount;
+  }
+
+  /**
+   * 检测弱起小节
+   */
+  private detectPickupMeasure(firstMeasure: JianpuMeasure, tempo: number): number {
+    if (!firstMeasure) return 0;
+
+    const { beats, beatType } = firstMeasure.timeSignature;
+    
+    // 计算小节理论时值(以四分音符为单位)
+    const theoreticalRealValue = beats * (4 / beatType);
+    
+    // 计算实际音符时值
+    let actualRealValue = 0;
+    for (const voice of firstMeasure.voices) {
+      for (const note of voice) {
+        actualRealValue += note.duration;
+      }
+      // 只检查第一声部
+      break;
+    }
+
+    // 如果实际时值小于理论时值,说明是弱起
+    if (actualRealValue < theoreticalRealValue - 0.001) {
+      const missingRealValue = theoreticalRealValue - actualRealValue;
+      const beatDuration = 60 / tempo;
+      return missingRealValue * beatDuration;
+    }
+
+    return 0;
+  }
+
+  /**
+   * 计算小节时长
+   */
+  private calculateMeasureDuration(
+    measure: JianpuMeasure, 
+    bpm: number, 
+    beatUnitRatio: number
+  ): number {
+    const { beats, beatType } = measure.timeSignature;
+    
+    // 小节时值(以四分音符为单位)
+    const measureRealValue = beats * (4 / beatType);
+    
+    // 四分音符时长(秒)
+    const quarterNoteDuration = 60 / bpm;
+    
+    // 小节时长 = 小节时值 * 节拍单位比例 * 四分音符时长
+    return measureRealValue * beatUnitRatio * quarterNoteDuration;
+  }
+
+  /**
+   * 计算单个音符的时间
+   */
+  private calculateNoteTime(
+    note: JianpuNote,
+    measureStartTime: number,
+    bpm: number,
+    beatUnitRatio: number,
+    measureIndex: number,
+    measureDuration: number
+  ): void {
+    // 四分音符时长(秒)
+    const quarterNoteDuration = 60 / bpm;
+    
+    // 音符时值时长(秒)
+    const noteDuration = note.duration * beatUnitRatio * quarterNoteDuration;
+    
+    // 音符在小节内的相对位置时间
+    const noteOffsetInMeasure = note.timestamp * beatUnitRatio * quarterNoteDuration;
+    
+    // 相对时间(不含fixtime)
+    const relativeTime = measureStartTime + noteOffsetInMeasure;
+    
+    // 绝对时间(含fixtime)
+    const absoluteTime = relativeTime + this.result.fixtime;
+    
+    // 设置音符时间
+    note.startTime = absoluteTime;
+    note.endTime = absoluteTime + noteDuration;
+
+    // 更新OSMD兼容数据
+    if (note.osmdCompatible) {
+      // 添加兼容字段(如果需要)
+      const compat = note.osmdCompatible as any;
+      compat.time = absoluteTime;
+      compat.endtime = note.endTime;
+      compat.relativeTime = relativeTime;
+      compat.relaEndtime = relativeTime + noteDuration;
+      compat.duration = noteDuration;
+      compat.fixtime = this.result.fixtime;
+      compat.difftime = this.result.pickupTime;
+      compat.measureLength = measureDuration;
+      compat.noteLengthTime = noteDuration;
+      compat._noteLength = note.duration;
+    }
+  }
+
+  /**
+   * 获取节拍单位比例
+   */
+  private getBeatUnitRatio(beatUnit: string): number {
+    const ratio = BEAT_UNIT_RATIO[beatUnit.toLowerCase()];
+    if (ratio !== undefined) {
+      return ratio;
+    }
+    
+    // 尝试解析 "1/4" 格式
+    const match = beatUnit.match(/^(\d+)\/(\d+)$/);
+    if (match) {
+      const num = parseInt(match[1]);
+      const den = parseInt(match[2]);
+      return (num / den) * 4; // 转换为以四分音符为基准
+    }
+
+    console.warn(`[TimeCalculator] 未知的节拍单位: ${beatUnit},使用默认值1.0`);
+    return 1.0;
+  }
+
+  /**
+   * 更新配置
+   */
+  updateConfig(config: Partial<TimeCalculatorConfig>): void {
+    Object.assign(this.config, config);
+  }
+
+  /**
+   * 获取当前配置
+   */
+  getConfig(): Required<TimeCalculatorConfig> {
+    return { ...this.config };
+  }
+}
+
+// ==================== 工厂函数 ====================
+
+/**
+ * 创建时间计算器
+ */
+export function createTimeCalculator(config?: TimeCalculatorConfig): TimeCalculator {
+  return new TimeCalculator(config);
+}
+
+// ==================== 工具函数 ====================
+
+/**
+ * 计算节拍器时间
+ * @param bpm BPM速度
+ * @param beatCount 拍数(默认2)
+ * @returns 节拍器时间(秒)
+ */
+export function getFixTime(bpm: number, beatCount: number = DEFAULT_METRONOME_BEATS): number {
+  if (bpm <= 0) {
+    console.warn('[TimeCalculator] BPM必须大于0');
+    return 0;
+  }
+  return (60 / bpm) * beatCount;
+}
+
+/**
+ * 将时值转换为秒
+ * @param realValue 时值(以四分音符为单位)
+ * @param bpm BPM速度
+ * @param beatUnit 节拍单位(默认'1/4')
+ * @returns 秒数
+ */
+export function realValueToSeconds(
+  realValue: number, 
+  bpm: number, 
+  beatUnit: string = '1/4'
+): number {
+  const beatUnitRatio = BEAT_UNIT_RATIO[beatUnit] ?? 1.0;
+  const quarterNoteDuration = 60 / bpm;
+  return realValue * beatUnitRatio * quarterNoteDuration;
+}
+
+/**
+ * 将秒转换为时值
+ * @param seconds 秒数
+ * @param bpm BPM速度
+ * @param beatUnit 节拍单位(默认'1/4')
+ * @returns 时值(以四分音符为单位)
+ */
+export function secondsToRealValue(
+  seconds: number, 
+  bpm: number, 
+  beatUnit: string = '1/4'
+): number {
+  const beatUnitRatio = BEAT_UNIT_RATIO[beatUnit] ?? 1.0;
+  const quarterNoteDuration = 60 / bpm;
+  return seconds / (beatUnitRatio * quarterNoteDuration);
+}
+
+/**
+ * 格式化时间为 MM:SS.mmm 格式
+ * @param seconds 秒数
+ * @returns 格式化后的时间字符串
+ */
+export function formatTime(seconds: number): string {
+  const mins = Math.floor(seconds / 60);
+  const secs = Math.floor(seconds % 60);
+  const ms = Math.round((seconds % 1) * 1000);
+  return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}.${ms.toString().padStart(3, '0')}`;
+}
+
+/**
+ * 节拍单位转换
+ * @param fromUnit 源单位
+ * @param toBpm 目标BPM
+ * @param toUnit 目标单位
+ * @returns 转换后的BPM
+ */
+export function convertBeatUnit(fromBpm: number, fromUnit: string, toUnit: string): number {
+  const fromRatio = BEAT_UNIT_RATIO[fromUnit] ?? 1.0;
+  const toRatio = BEAT_UNIT_RATIO[toUnit] ?? 1.0;
+  return fromBpm * (fromRatio / toRatio);
+}
+
+/**
+ * 保留小数精度
+ * @param value 数值
+ * @param precision 精度(小数位数,默认6)
+ * @returns 保留精度后的数值
+ */
+export function retain(value: number, precision: number = 6): number {
+  const factor = Math.pow(10, precision);
+  return Math.round(value * factor) / factor;
 }

+ 1 - 0
src/jianpu-renderer/core/parser/index.ts

@@ -1,2 +1,3 @@
+export * from './DivisionsHandler';
 export * from './OSMDDataParser';
 export * from './TimeCalculator';

+ 569 - 0
src/jianpu-renderer/docs/API.md

@@ -0,0 +1,569 @@
+# 简谱渲染引擎 API 参考
+
+> **文档版本:** 1.0  
+> **更新日期:** 2026-01-29  
+> **状态:** 开发中(部分API尚未实现)
+
+---
+
+## 📦 安装和导入
+
+```typescript
+// 导入主渲染器
+import { JianpuRenderer } from '@/jianpu-renderer';
+
+// 导入数据模型
+import {
+  JianpuNote,
+  JianpuMeasure,
+  JianpuScore,
+  createDefaultNote,
+  createDefaultMeasure,
+  Accidental,
+} from '@/jianpu-renderer/models';
+
+// 导入配置
+import { RenderConfig, DEFAULT_RENDER_CONFIG } from '@/jianpu-renderer/core/config';
+
+// 导入工具函数
+import { SVGHelper, MathHelper, CONSTANTS } from '@/jianpu-renderer/utils';
+```
+
+---
+
+## 🎵 JianpuRenderer
+
+主渲染器类,提供简谱渲染的核心功能。
+
+### 构造函数
+
+```typescript
+constructor(container: HTMLElement, config?: Partial<RenderConfig>)
+```
+
+**参数:**
+| 参数 | 类型 | 必填 | 描述 |
+|------|------|------|------|
+| container | HTMLElement | ✅ | 渲染容器DOM元素 |
+| config | Partial<RenderConfig> | ❌ | 渲染配置(可选) |
+
+**示例:**
+```typescript
+const container = document.getElementById('score-container')!;
+const renderer = new JianpuRenderer(container, {
+  quarterNoteSpacing: 60,
+  measurePadding: 25,
+});
+```
+
+### 方法
+
+#### load()
+
+加载MusicXML或OSMD对象。
+
+```typescript
+async load(source: string | OSMD): Promise<void>
+```
+
+**参数:**
+| 参数 | 类型 | 描述 |
+|------|------|------|
+| source | string \| OSMD | MusicXML字符串或OSMD对象 |
+
+**示例:**
+```typescript
+// 加载MusicXML字符串
+await renderer.load(xmlString);
+
+// 加载OSMD对象
+await renderer.load(osmdInstance);
+```
+
+#### render()
+
+执行渲染。
+
+```typescript
+render(): void
+```
+
+**示例:**
+```typescript
+renderer.render();
+```
+
+#### generateTimesArray()
+
+生成兼容OSMD的times数组。
+
+```typescript
+generateTimesArray(): TimeEntry[]
+```
+
+**返回值:** 与原OSMD `state.times` 兼容的数据数组
+
+**示例:**
+```typescript
+const times = renderer.generateTimesArray();
+// 可直接赋值给业务层的state.times
+state.times = times;
+```
+
+#### clear()
+
+清除渲染内容。
+
+```typescript
+clear(): void
+```
+
+#### resize()
+
+调整渲染尺寸。
+
+```typescript
+resize(width?: number, height?: number): void
+```
+
+---
+
+## 📋 数据模型
+
+### JianpuNote
+
+音符数据模型。
+
+```typescript
+interface JianpuNote {
+  // 基础属性
+  id: string;              // 唯一ID
+  pitch: number;           // 音高 1-7,0表示休止符
+  octave: number;          // 八度偏移 (-2, -1, 0, 1, 2)
+  duration: number;        // 时值(四分音符为1.0)
+  
+  // 修饰符
+  accidental?: 'sharp' | 'flat' | 'natural';
+  dots: number;            // 附点数量
+  
+  // 时间信息
+  timestamp: number;       // 小节内时间戳
+  startTime: number;       // 绝对开始时间(秒)
+  endTime: number;         // 绝对结束时间(秒)
+  
+  // 位置信息(布局后填充)
+  x: number;
+  y: number;
+  width: number;
+  height: number;
+  
+  // 其他属性
+  voiceIndex: number;      // 声部索引
+  measureIndex: number;    // 小节索引
+  isRest: boolean;         // 是否休止符
+  isGraceNote: boolean;    // 是否装饰音
+  isStaccato: boolean;     // 是否顿音
+  
+  // OSMD兼容数据
+  osmdCompatible: {
+    noteElement: any;
+    svgElement: { attrs: { id: string } };
+    halfTone: number;
+    frequency: number;
+  };
+}
+```
+
+### createDefaultNote()
+
+创建默认音符。
+
+```typescript
+function createDefaultNote(options?: Partial<JianpuNote>): JianpuNote
+```
+
+**示例:**
+```typescript
+// 创建四分音符 do
+const note1 = createDefaultNote({ pitch: 1, duration: 1 });
+
+// 创建八分音符 高音sol
+const note2 = createDefaultNote({ pitch: 5, octave: 1, duration: 0.5 });
+
+// 创建附点二分音符
+const note3 = createDefaultNote({ pitch: 3, duration: 3, dots: 1 });
+
+// 创建休止符
+const rest = createDefaultNote({ pitch: 0, isRest: true });
+```
+
+### Accidental
+
+升降号枚举。
+
+```typescript
+enum Accidental {
+  Sharp = 'sharp',    // 升号 #
+  Flat = 'flat',      // 降号 ♭
+  Natural = 'natural' // 还原号 ♮
+}
+```
+
+### JianpuMeasure
+
+小节数据模型。
+
+```typescript
+interface JianpuMeasure {
+  index: number;           // 小节索引(0开始)
+  measureNumber: number;   // 小节号(1开始)
+  
+  timeSignature: {
+    beats: number;         // 拍数
+    beatType: number;      // 单位拍
+  };
+  
+  keySignature: {
+    key: string;           // 调号
+    mode: 'major' | 'minor';
+    alterations?: number;
+  };
+  
+  voices: JianpuNote[][];  // 所有声部的音符
+  
+  // 布局信息
+  x: number;
+  y: number;
+  width: number;
+  height: number;
+  
+  // 小节线
+  hasBarline: boolean;
+  barlineType: 'single' | 'double' | 'repeat-start' | 'repeat-end' | 'final';
+}
+```
+
+### createDefaultMeasure()
+
+创建默认小节。
+
+```typescript
+function createDefaultMeasure(
+  measureNumber: number,
+  timeSignature?: TimeSignature
+): JianpuMeasure
+```
+
+**示例:**
+```typescript
+// 创建4/4拍小节
+const measure1 = createDefaultMeasure(1);
+
+// 创建3/4拍小节
+const measure2 = createDefaultMeasure(2, { beats: 3, beatType: 4 });
+```
+
+### JianpuScore
+
+总谱数据模型。
+
+```typescript
+interface JianpuScore {
+  title?: string;
+  composer?: string;
+  tempo: number;           // BPM
+  
+  systems: JianpuSystem[]; // 所有行
+  measures: JianpuMeasure[]; // 所有小节
+  
+  config?: RenderConfig;
+  metadata?: Record<string, any>;
+}
+```
+
+---
+
+## ⚙️ 配置
+
+### RenderConfig
+
+渲染配置接口。
+
+```typescript
+interface RenderConfig {
+  // 间距配置
+  quarterNoteSpacing: number;  // 四分音符间距(像素)
+  measurePadding: number;      // 小节左右padding(像素)
+  systemSpacing: number;       // 行间距(像素)
+  voiceSpacing: number;        // 声部间距(像素)
+  
+  // 字体配置
+  noteFontSize: number;        // 音符字体大小
+  lyricFontSize: number;       // 歌词字体大小
+  noteFont: string;            // 音符字体
+  lyricFont: string;           // 歌词字体
+  
+  // 尺寸配置
+  octaveDotRadius: number;     // 高低音点半径
+  underlineHeight: number;     // 减时线高度
+  extensionLineHeight: number; // 增时线高度
+  
+  // 颜色配置
+  noteColor: string;
+  lineColor: string;
+  lyricColor: string;
+  
+  // 行为配置
+  autoWrap: boolean;           // 自动换行
+  maxWidth: number;            // 最大宽度(像素)
+}
+```
+
+### DEFAULT_RENDER_CONFIG
+
+默认配置值。
+
+```typescript
+const DEFAULT_RENDER_CONFIG: RenderConfig = {
+  quarterNoteSpacing: 50,
+  measurePadding: 20,
+  systemSpacing: 80,
+  voiceSpacing: 40,
+  
+  noteFontSize: 20,
+  lyricFontSize: 14,
+  noteFont: 'Arial, sans-serif',
+  lyricFont: 'Microsoft YaHei, sans-serif',
+  
+  octaveDotRadius: 2,
+  underlineHeight: 2,
+  extensionLineHeight: 2,
+  
+  noteColor: '#000000',
+  lineColor: '#000000',
+  lyricColor: '#333333',
+  
+  autoWrap: true,
+  maxWidth: 800,
+};
+```
+
+---
+
+## 🛠️ 工具函数
+
+### SVGHelper
+
+SVG操作工具类。
+
+```typescript
+class SVGHelper {
+  // 创建SVG元素
+  static createElement(tag: string, attrs?: Record<string, any>): SVGElement;
+  
+  // 创建SVG命名空间元素
+  static createElementNS(tag: string, attrs?: Record<string, any>): SVGElement;
+  
+  // 创建文本元素
+  static createText(text: string, x: number, y: number, attrs?: Record<string, any>): SVGTextElement;
+  
+  // 创建圆形
+  static createCircle(cx: number, cy: number, r: number, attrs?: Record<string, any>): SVGCircleElement;
+  
+  // 创建矩形
+  static createRect(x: number, y: number, width: number, height: number, attrs?: Record<string, any>): SVGRectElement;
+  
+  // 创建线条
+  static createLine(x1: number, y1: number, x2: number, y2: number, attrs?: Record<string, any>): SVGLineElement;
+  
+  // 创建分组
+  static createGroup(attrs?: Record<string, any>): SVGGElement;
+}
+```
+
+### MathHelper
+
+数学计算工具类。
+
+```typescript
+class MathHelper {
+  // 计算增时线数量
+  static calcExtensionLines(realValue: number): number;
+  
+  // 计算减时线数量
+  static calcUnderlines(realValue: number): number;
+  
+  // 计算音符时长(秒)
+  static calcNoteDuration(realValue: number, bpm: number): number;
+  
+  // 四舍五入到指定小数位
+  static round(value: number, decimals: number): number;
+  
+  // 限制值在范围内
+  static clamp(value: number, min: number, max: number): number;
+}
+```
+
+### CONSTANTS
+
+常量定义。
+
+```typescript
+const CONSTANTS = {
+  // SVG命名空间
+  SVG_NS: 'http://www.w3.org/2000/svg',
+  
+  // 音名到简谱数字映射
+  NOTE_TO_JIANPU: {
+    'C': 1, 'D': 2, 'E': 3, 'F': 4, 'G': 5, 'A': 6, 'B': 7
+  },
+  
+  // 基准八度
+  BASE_OCTAVE: 4,
+  
+  // CSS类名
+  CSS_CLASSES: {
+    NOTE: 'vf-note',
+    NOTE_HEAD: 'vf-numbered-note-head',
+    LYRIC: 'vf-lyric',
+    MEASURE: 'vf-measure',
+    SYSTEM: 'vf-system',
+    EXTENSION_LINE: 'vf-extension-line',
+    UNDERLINE: 'vf-underline',
+  },
+};
+```
+
+---
+
+## 🔌 兼容层API
+
+### OSMDCompatibilityAdapter
+
+OSMD兼容适配器,用于生成与业务层兼容的数据。
+
+```typescript
+class OSMDCompatibilityAdapter {
+  constructor(score: JianpuScore);
+  
+  // 生成times数组
+  generateTimesArray(): TimeEntry[];
+  
+  // 创建光标适配器
+  createCursorAdapter(): CursorAdapter;
+  
+  // 创建GraphicSheet适配器
+  createGraphicSheetAdapter(): GraphicSheetAdapter;
+}
+```
+
+### TimeEntry
+
+times数组元素类型(与业务层兼容)。
+
+```typescript
+interface TimeEntry {
+  i: number;              // 索引
+  noteId: string;         // 音符ID
+  id: string;             // 元素ID
+  
+  time: number;           // 开始时间(秒)
+  endtime: number;        // 结束时间(秒)
+  relativeTime: number;   // 相对时间
+  
+  MeasureNumberXML: number;   // 小节号
+  measureListIndex: number;   // 小节索引
+  
+  svgElement: {
+    attrs: { id: string };
+  };
+  noteElement: any;
+  
+  bbox: {
+    x: number;
+    y: number;
+    width: number;
+    height: number;
+  };
+  
+  halfTone: number;       // 半音值
+  frequency: number;      // 频率
+  isRestFlag: boolean;    // 是否休止符
+  realValue: number;      // 实际时值
+  
+  formatLyricsEntries?: any[];  // 歌词数据
+}
+```
+
+---
+
+## 📝 使用示例
+
+### 基础用法
+
+```typescript
+import { JianpuRenderer } from '@/jianpu-renderer';
+
+// 获取容器
+const container = document.getElementById('score')!;
+
+// 创建渲染器
+const renderer = new JianpuRenderer(container, {
+  quarterNoteSpacing: 55,
+  maxWidth: 900,
+});
+
+// 加载并渲染
+async function init() {
+  await renderer.load(xmlString);
+  renderer.render();
+  
+  // 获取兼容数据
+  const times = renderer.generateTimesArray();
+  console.log('音符数量:', times.length);
+}
+
+init();
+```
+
+### 与业务层集成
+
+```typescript
+import { JianpuRenderer } from '@/jianpu-renderer';
+
+// 在music-score业务组件中
+export function useMusicScore() {
+  const renderer = ref<JianpuRenderer | null>(null);
+  
+  const initRenderer = async (container: HTMLElement, xml: string) => {
+    renderer.value = new JianpuRenderer(container);
+    await renderer.value.load(xml);
+    renderer.value.render();
+    
+    // 生成兼容数据供播放、高亮等功能使用
+    state.times = renderer.value.generateTimesArray();
+  };
+  
+  return {
+    renderer,
+    initRenderer,
+  };
+}
+```
+
+---
+
+## 🚧 开发状态
+
+| 模块 | 状态 | 说明 |
+|------|------|------|
+| JianpuRenderer | 🚧 开发中 | 框架已完成 |
+| 数据模型 | ✅ 完成 | 已可使用 |
+| OSMDDataParser | 🚧 开发中 | 阶段1任务 |
+| MeasureLayoutEngine | 🚧 开发中 | 阶段2任务 |
+| NoteDrawer | 🚧 开发中 | 阶段3任务 |
+| OSMDCompatibilityAdapter | ⏸️ 待开始 | 阶段4任务 |
+
+---
+
+**文档持续更新中...**

+ 363 - 0
src/jianpu-renderer/docs/DEVELOPMENT.md

@@ -0,0 +1,363 @@
+# 简谱渲染引擎开发指南
+
+> **文档版本:** 1.0  
+> **更新日期:** 2026-01-29  
+> **适用范围:** 简谱渲染引擎开发团队
+
+---
+
+## 📋 开发环境设置
+
+### 依赖安装
+
+```bash
+# 进入项目目录
+cd d:\git\music-score
+
+# 安装所有依赖
+npm install
+
+# 或使用yarn
+yarn install
+```
+
+### 开发服务器
+
+```bash
+# 启动开发服务器
+npm run dev
+
+# 访问 http://localhost:5173
+```
+
+### 运行测试
+
+```bash
+# 运行所有测试
+npm test
+
+# 运行测试(单次执行)
+npm run test:run
+
+# 运行测试(带覆盖率)
+npm run test:coverage
+```
+
+---
+
+## 📁 项目结构
+
+```
+src/jianpu-renderer/
+├── index.ts                       # 主入口,导出公共API
+├── JianpuRenderer.ts              # 主渲染器类
+├── models/                        # 数据模型
+│   ├── JianpuNote.ts             # 音符模型
+│   ├── JianpuMeasure.ts          # 小节模型
+│   ├── JianpuSystem.ts           # 行模型
+│   ├── JianpuScore.ts            # 总谱模型
+│   └── index.ts                  # 模型导出
+├── core/
+│   ├── parser/                   # 解析器
+│   │   ├── OSMDDataParser.ts     # OSMD数据解析
+│   │   ├── TimeCalculator.ts     # 时间计算
+│   │   └── index.ts
+│   ├── layout/                   # 布局引擎
+│   │   ├── MeasureLayoutEngine.ts    # 小节布局
+│   │   ├── SystemLayoutEngine.ts     # 行布局
+│   │   ├── NotePositionCalculator.ts # 音符位置计算
+│   │   ├── MultiVoiceAligner.ts      # 多声部对齐
+│   │   └── index.ts
+│   ├── drawer/                   # 绘制引擎
+│   │   ├── NoteDrawer.ts         # 音符绘制
+│   │   ├── LineDrawer.ts         # 线条绘制
+│   │   ├── LyricDrawer.ts        # 歌词绘制
+│   │   ├── ModifierDrawer.ts     # 修饰符绘制
+│   │   └── index.ts
+│   └── config/                   # 配置
+│       ├── RenderConfig.ts       # 渲染配置
+│       └── index.ts
+├── adapters/                     # 兼容层
+│   ├── OSMDCompatibilityAdapter.ts  # OSMD兼容适配器
+│   ├── DOMAdapter.ts             # DOM适配器
+│   └── index.ts
+├── utils/                        # 工具函数
+│   ├── SVGHelper.ts              # SVG操作工具
+│   ├── MathHelper.ts             # 数学计算工具
+│   ├── Constants.ts              # 常量定义
+│   └── index.ts
+├── __tests__/                    # 测试文件
+│   ├── fixtures/                 # 测试数据
+│   │   ├── basic.xml
+│   │   ├── mixed-durations.xml
+│   │   ├── multi-voice.xml
+│   │   ├── with-lyrics.xml
+│   │   ├── complex.xml
+│   │   └── README.md
+│   ├── setup.ts                  # 测试环境设置
+│   ├── models.test.ts            # 模型测试
+│   ├── parser.test.ts            # 解析器测试
+│   └── compare.html              # 对比测试页面
+└── docs/                         # 文档
+    ├── DEVELOPMENT.md            # 开发指南(本文档)
+    └── API.md                    # API参考
+```
+
+---
+
+## 🔧 核心概念
+
+### 1. 数据流
+
+```
+MusicXML/OSMD对象
+      ↓
+  [OSMDDataParser]  解析
+      ↓
+  JianpuScore数据模型
+      ↓
+  [MeasureLayoutEngine]  布局计算
+      ↓
+  带位置信息的JianpuScore
+      ↓
+  [NoteDrawer/LineDrawer/...]  绘制
+      ↓
+  SVG DOM
+      ↓
+  [OSMDCompatibilityAdapter]  生成兼容数据
+      ↓
+  state.times数组(供业务层使用)
+```
+
+### 2. 时值单位
+
+**重要:** 整个系统使用**四分音符为单位**来表示时值。
+
+| 音符类型 | 时值(realValue) | 增时线数 | 减时线数 |
+|---------|----------------|---------|---------|
+| 全音符 | 4.0 | 3 | 0 |
+| 二分音符 | 2.0 | 1 | 0 |
+| 四分音符 | 1.0 | 0 | 0 |
+| 八分音符 | 0.5 | 0 | 1 |
+| 十六分音符 | 0.25 | 0 | 2 |
+| 三十二分音符 | 0.125 | 0 | 3 |
+
+**增时线计算:** `Math.floor(realValue) - 1`(当 realValue >= 1)  
+**减时线计算:** `Math.round(Math.log2(1 / realValue))`(当 realValue < 1)
+
+### 3. Divisions 转换
+
+MusicXML中的duration需要通过divisions转换:
+
+```typescript
+// 实际时值 = duration / divisions
+const realValue = duration / divisions;
+
+// 示例(divisions=256):
+// 四分音符:256 / 256 = 1.0
+// 八分音符:128 / 256 = 0.5
+// 二分音符:512 / 256 = 2.0
+```
+
+### 4. 音高转换
+
+```typescript
+// 音名到简谱数字的映射
+const NOTE_TO_JIANPU = {
+  'C': 1, 'D': 2, 'E': 3, 'F': 4, 'G': 5, 'A': 6, 'B': 7
+};
+
+// 八度偏移 = MusicXML octave - 4
+// octave=3 → 低音(点在下方)
+// octave=4 → 中音(无点)
+// octave=5 → 高音(点在上方)
+```
+
+---
+
+## 📝 编码规范
+
+### TypeScript规范
+
+1. **使用严格类型**:避免使用 `any`,尽量使用具体类型
+2. **接口优先**:使用 `interface` 定义数据结构
+3. **注释完整**:每个公共方法和属性都要有JSDoc注释
+4. **命名规范**:
+   - 类名:`PascalCase` (如 `NoteDrawer`)
+   - 方法名:`camelCase` (如 `calculateWidth`)
+   - 常量:`UPPER_SNAKE_CASE` (如 `DEFAULT_SPACING`)
+   - 文件名:`PascalCase` 或 `camelCase`
+
+### 代码示例
+
+```typescript
+/**
+ * 计算音符的X坐标
+ * @param timestamp 音符在小节中的时间戳(以四分音符为单位)
+ * @param measureX 小节起始X坐标
+ * @param config 渲染配置
+ * @returns X坐标(像素)
+ */
+function calculateNoteX(
+  timestamp: number,
+  measureX: number,
+  config: RenderConfig
+): number {
+  const { measurePadding, quarterNoteSpacing } = config;
+  return measureX + measurePadding + timestamp * quarterNoteSpacing;
+}
+```
+
+### Git提交规范
+
+```bash
+# 格式:[任务编号] 描述
+
+# 示例:
+git commit -m "[任务1.1] 实现OSMDDataParser基础解析功能"
+git commit -m "[任务2.1] 完成小节布局计算器"
+git commit -m "[修复] 修复增时线位置计算错误"
+git commit -m "[文档] 更新API文档"
+```
+
+---
+
+## 🧪 测试指南
+
+### 测试文件结构
+
+```
+__tests__/
+├── fixtures/            # 测试用MusicXML文件
+│   ├── basic.xml       # 基础测试(四分音符)
+│   ├── mixed-durations.xml  # 混合时值
+│   ├── multi-voice.xml # 多声部
+│   ├── with-lyrics.xml # 带歌词
+│   └── complex.xml     # 复杂记号
+├── setup.ts            # 测试环境配置
+├── models.test.ts      # 数据模型测试
+├── parser.test.ts      # 解析器测试
+├── layout.test.ts      # 布局引擎测试(待创建)
+├── drawer.test.ts      # 绘制引擎测试(待创建)
+└── compare.html        # 可视化对比测试
+```
+
+### 编写测试
+
+```typescript
+import { describe, it, expect } from 'vitest';
+import { createDefaultNote } from '../models';
+
+describe('NoteDrawer', () => {
+  describe('drawNote()', () => {
+    it('应该正确绘制四分音符', () => {
+      const note = createDefaultNote({ pitch: 1, duration: 1 });
+      const svg = drawer.drawNote(note);
+      
+      expect(svg).toBeDefined();
+      expect(svg.querySelector('.note-number')?.textContent).toBe('1');
+    });
+    
+    it('应该为八分音符绘制1条减时线', () => {
+      const note = createDefaultNote({ pitch: 5, duration: 0.5 });
+      const svg = drawer.drawNote(note);
+      
+      const underlines = svg.querySelectorAll('.underline');
+      expect(underlines.length).toBe(1);
+    });
+  });
+});
+```
+
+### 对比测试页面
+
+打开 `__tests__/compare.html` 可以进行可视化对比测试:
+
+1. 选择测试XML文件
+2. 点击"渲染对比"
+3. 左侧显示旧引擎(OSMD)渲染结果
+4. 右侧显示新引擎渲染结果
+5. 点击"差异分析"查看具体差异
+
+---
+
+## 🚨 常见问题
+
+### Q1: divisions不同导致时值计算错误
+
+**问题:** 不同的MusicXML文件有不同的divisions值
+
+**解决方案:**
+```typescript
+// ❌ 错误:直接判断duration
+if (duration === 256) { /* 四分音符 */ }
+
+// ✅ 正确:先转换为相对时值
+const realValue = duration / divisions;
+if (realValue === 1.0) { /* 四分音符 */ }
+```
+
+### Q2: 增时线位置不正确
+
+**问题:** 增时线应该按四分音符间距均匀分布
+
+**解决方案:**
+```typescript
+// 二分音符(2拍)的增时线应该在第2拍的位置
+const extensionLineX = noteX + quarterNoteSpacing;
+
+// 全音符(4拍)的3条增时线
+for (let i = 1; i <= 3; i++) {
+  const lineX = noteX + i * quarterNoteSpacing;
+  drawExtensionLine(lineX);
+}
+```
+
+### Q3: 多声部对齐问题
+
+**问题:** 同一时间点的不同声部音符X坐标不一致
+
+**解决方案:**
+使用 `MultiVoiceAligner` 统一分配X坐标:
+```typescript
+// 收集所有时间戳
+const timestamps = collectAllTimestamps(measure);
+
+// 为每个时间戳分配唯一的X坐标
+const timestampToX = new Map<number, number>();
+timestamps.forEach((ts, index) => {
+  timestampToX.set(ts, calculateX(ts));
+});
+
+// 更新所有声部的音符X坐标
+measure.voices.forEach(voice => {
+  voice.forEach(note => {
+    note.x = timestampToX.get(note.timestamp)!;
+  });
+});
+```
+
+---
+
+## 📚 参考资料
+
+- [MusicXML 4.0 官方文档](https://www.w3.org/2021/06/musicxml40/)
+- [VexFlow 官网](https://www.vexflow.com/)
+- 项目内文档:
+  - `docs/jianpu-renderer/01-TASKS_CHECKLIST.md` - 任务清单
+  - `docs/jianpu-renderer/02-PROGRESS.md` - 进度追踪
+  - `docs/jianpu-renderer/03-MUSICXML_KNOWLEDGE.md` - MusicXML知识
+
+---
+
+## 🤝 贡献指南
+
+1. **创建分支**:从 `main` 分支创建功能分支
+2. **编写代码**:遵循编码规范
+3. **编写测试**:确保新功能有测试覆盖
+4. **提交PR**:描述清楚改动内容
+5. **代码审查**:等待审查通过后合并
+
+---
+
+**祝开发顺利!** 🎉

+ 49 - 3
src/jianpu-renderer/models/JianpuMeasure.ts

@@ -4,6 +4,14 @@
 
 import { JianpuNote } from './JianpuNote';
 
+/** 拍号类型 */
+export interface TimeSignature {
+  /** 拍数(如4/4中的4) */
+  beats: number;
+  /** 单位拍(如4/4中的4,表示以四分音符为一拍) */
+  beatType: number;
+}
+
 export interface JianpuMeasure {
   // ===== 基础信息 =====
   /** 小节索引(0开始,用于内部索引) */
@@ -35,9 +43,15 @@ export interface JianpuMeasure {
   
   // ===== 音符数据 =====
   /** 
-   * 所有声部的音符
-   * 二维数组:notes[voiceIndex][noteIndex]
-   * 例如:notes[0] 是第一声部的所有音符
+   * 所有声部的音符(别名,与notes相同)
+   * 二维数组:voices[voiceIndex][noteIndex]
+   * 例如:voices[0] 是第一声部的所有音符
+   */
+  voices: JianpuNote[][];
+  
+  /**
+   * 所有声部的音符(notes是voices的别名,保持向后兼容)
+   * @deprecated 请使用 voices
    */
   notes: JianpuNote[][];
   
@@ -74,3 +88,35 @@ export interface JianpuMeasure {
   /** 速度变化标记(如 Allegro, Andante 等) */
   tempoText?: string;
 }
+
+/**
+ * 创建默认小节
+ * @param measureNumber 小节号(从1开始)
+ * @param timeSignature 可选的拍号
+ * @returns 新的小节对象
+ */
+export function createDefaultMeasure(
+  measureNumber: number,
+  timeSignature: TimeSignature = { beats: 4, beatType: 4 }
+): JianpuMeasure {
+  const voices: JianpuNote[][] = [[]];
+  
+  return {
+    index: measureNumber - 1,
+    measureNumber,
+    timeSignature,
+    keySignature: {
+      key: 'C',
+      mode: 'major',
+      alterations: 0,
+    },
+    voices,
+    notes: voices, // notes是voices的引用,保持同步
+    x: 0,
+    y: 0,
+    width: 0,
+    height: 0,
+    hasBarline: true,
+    barlineType: 'single',
+  };
+}

+ 57 - 0
src/jianpu-renderer/models/JianpuNote.ts

@@ -4,6 +4,13 @@
  * @description 定义简谱渲染所需的音符数据结构
  */
 
+/** 升降号枚举 */
+export enum Accidental {
+  Sharp = 'sharp',
+  Flat = 'flat',
+  Natural = 'natural',
+}
+
 export interface JianpuNote {
   // ===== 基础属性 =====
   /** 音符唯一ID(用于生成DOM id: vf-{id}) */
@@ -89,3 +96,53 @@ export interface JianpuNote {
     realKey?: number;
   };
 }
+
+/** 创建音符时的可选参数 */
+export type CreateNoteOptions = Partial<Omit<JianpuNote, 'id' | 'osmdCompatible'>>;
+
+/** ID计数器 */
+let noteIdCounter = 0;
+
+/**
+ * 创建默认音符
+ * @param options 可选的音符属性
+ * @returns 新的音符对象
+ */
+export function createDefaultNote(options: CreateNoteOptions = {}): JianpuNote {
+  const id = `note-${++noteIdCounter}`;
+  
+  return {
+    id,
+    pitch: 1,
+    octave: 0,
+    duration: 1,
+    accidental: undefined,
+    dots: 0,
+    timestamp: 0,
+    startTime: 0,
+    endTime: 0,
+    x: 0,
+    y: 0,
+    width: 0,
+    height: 0,
+    voiceIndex: 0,
+    measureIndex: 0,
+    isRest: false,
+    isGraceNote: false,
+    isStaccato: false,
+    osmdCompatible: {
+      noteElement: null,
+      svgElement: { attrs: { id } },
+      halfTone: 60,
+      frequency: 261.63, // C4
+    },
+    ...options,
+  };
+}
+
+/**
+ * 重置音符ID计数器(用于测试)
+ */
+export function resetNoteIdCounter(): void {
+  noteIdCounter = 0;
+}

+ 9 - 1
src/jianpu-renderer/models/JianpuScore.ts

@@ -5,6 +5,7 @@
  */
 
 import { JianpuSystem } from './JianpuSystem';
+import { JianpuMeasure } from './JianpuMeasure';
 import { RenderConfig } from '../core/config/RenderConfig';
 
 export interface JianpuScore {
@@ -24,6 +25,10 @@ export interface JianpuScore {
   // ===== 所有行 =====
   systems: JianpuSystem[];
   
+  // ===== 所有小节 =====
+  /** 所有小节的平铺列表 */
+  measures: JianpuMeasure[];
+  
   // ===== 音乐配置 =====
   /** 速度(BPM) */
   tempo: number;
@@ -41,7 +46,7 @@ export interface JianpuScore {
   };
   
   // ===== 渲染配置 =====
-  config: RenderConfig;
+  config?: RenderConfig;
   
   // ===== 元数据 =====
   /** 总小节数 */
@@ -52,4 +57,7 @@ export interface JianpuScore {
   
   /** 声部数量 */
   voiceCount?: number;
+  
+  /** 其他元数据 */
+  metadata?: Record<string, any>;
 }

+ 45 - 0
vitest.config.ts

@@ -0,0 +1,45 @@
+/**
+ * Vitest 测试配置
+ * 用于简谱渲染引擎的单元测试
+ */
+import { defineConfig } from 'vitest/config';
+import { resolve } from 'path';
+
+export default defineConfig({
+  test: {
+    // 测试环境
+    environment: 'jsdom',
+    
+    // 全局API(describe, it, expect等)
+    globals: true,
+    
+    // 测试文件匹配模式
+    include: [
+      'src/jianpu-renderer/**/*.test.ts',
+      'src/jianpu-renderer/**/*.spec.ts',
+    ],
+    
+    // 排除的文件
+    exclude: ['node_modules', 'dist'],
+    
+    // 测试超时时间(毫秒)
+    testTimeout: 10000,
+    
+    // 覆盖率配置
+    coverage: {
+      provider: 'v8',
+      reporter: ['text', 'json', 'html'],
+      include: ['src/jianpu-renderer/**/*.ts'],
+      exclude: ['src/jianpu-renderer/**/*.test.ts', 'src/jianpu-renderer/**/*.spec.ts'],
+    },
+    
+    // 设置文件
+    setupFiles: ['./src/jianpu-renderer/__tests__/setup.ts'],
+  },
+  
+  resolve: {
+    alias: {
+      '@': resolve(__dirname, 'src'),
+    },
+  },
+});

File diff suppressed because it is too large
+ 332 - 319
yarn.lock


Some files were not shown because too many files changed in this diff