MIGRATION_GUIDE.md 13 KB

乐谱渲染引擎迁移指南

版本:1.0
更新日期:2026-01-30
适用范围:从旧版渲染器迁移到新架构


目录

  1. 迁移概述
  2. 架构变化
  3. 模块迁移
  4. API 变更
  5. 数据模型变更
  6. 常见迁移场景
  7. 兼容性说明
  8. 故障排除

1. 迁移概述

1.1 新架构优势

新版乐谱渲染引擎相比旧版有以下改进:

特性 旧版 新版
数据解析 依赖 OSMD 独立 music-core 解析器
简谱渲染 仅支持固定调 支持首调/固定调切换
五线谱渲染 依赖 OSMD 独立五线谱渲染器
数据模型 与 OSMD 耦合 通用数据模型
性能优化 缓存、批量渲染、懒加载
可扩展性 较差 模块化设计,易扩展

1.2 迁移路径

旧版代码
    │
    ├─→ 步骤1: 更新数据解析(使用 music-core)
    │
    ├─→ 步骤2: 更新简谱渲染器调用
    │
    ├─→ 步骤3: 集成五线谱渲染器(可选)
    │
    └─→ 步骤4: 更新业务层适配

2. 架构变化

2.1 目录结构变化

旧版结构:
src/
├── jianpu-renderer/        # 简谱渲染(依赖 OSMD 数据)
└── ... (直接使用 OSMD)

新版结构:
src/
├── music-core/             # 【新增】通用数据模型和解析器
│   ├── models/             # 数据模型
│   ├── parser/             # MusicXML 解析器
│   └── utils/              # 工具函数
├── renderers/              # 【新增】渲染器层
│   ├── common/             # 共享类型
│   ├── jianpu/             # 简谱转换器
│   │   └── converter/      # 首调/固定调转换
│   └── staff/              # 【新增】五线谱渲染器
│       ├── drawer/         # 绘制器
│       ├── layout/         # 布局引擎
│       └── utils/          # 工具
└── jianpu-renderer/        # 简谱渲染器(已更新)

2.2 依赖关系变化

旧版依赖:
OSMD → JianpuRenderer

新版依赖:
MusicXML → music-core → JianpuRenderer
                      → StaffRenderer

3. 模块迁移

3.1 MusicXML 解析迁移

旧版(使用 OSMD)

// 旧版:依赖 OSMD 解析
import { OpenSheetMusicDisplay } from 'osmd-extended';

const osmd = new OpenSheetMusicDisplay(container);
await osmd.load(musicXmlString);
osmd.render();

// 获取数据需要访问 OSMD 内部结构
const measures = osmd.Sheet.SourceMeasures;

新版(使用 music-core)

// 新版:独立解析
import { MusicXMLParser } from '@/music-core/parser';

const parser = new MusicXMLParser();
const result = parser.parse(musicXmlString);

if (result.errors.length > 0) {
  console.error('解析错误:', result.errors);
}

// 获取标准化的数据模型
const score = result.score;
const measures = score.parts[0].measures;

3.2 简谱渲染器迁移

旧版(固定调)

// 旧版:仅支持固定调
import { JianpuRenderer } from '@/jianpu-renderer';

const renderer = new JianpuRenderer({
  container: '#score',
  quarterNoteSpacing: 50,
});

renderer.bindOSMD(osmd);
renderer.render();

新版(支持首调/固定调切换)

// 新版:支持模式切换
import { JianpuRenderer, SolfegeMode } from '@/jianpu-renderer';

const renderer = new JianpuRenderer({
  container: '#score',
  quarterNoteSpacing: 50,
  solfegeMode: 'movable',  // 【新增】首调模式
});

renderer.bindOSMD(osmd);
renderer.render();

// 运行时切换模式
renderer.setSolfegeMode('fixed');    // 切换到固定调
renderer.setSolfegeMode('movable');  // 切换到首调
renderer.toggleSolfegeMode();        // 切换模式

3.3 五线谱渲染器(新增)

// 新版:独立五线谱渲染
import { StaffRenderer, createStaffRenderer } from '@/renderers/staff';
import { MusicXMLParser } from '@/music-core/parser';

// 解析 MusicXML
const parser = new MusicXMLParser();
const { score } = parser.parse(musicXmlString);

// 创建渲染器
const renderer = createStaffRenderer({
  container: document.getElementById('score-container'),
  systemWidth: 800,
  staffLineSpacing: 10,
});

// 渲染
renderer.load(score);
renderer.render();

// 获取统计
const stats = renderer.getStats();
console.log(`渲染完成: ${stats.noteCount} 个音符`);

4. API 变更

4.1 JianpuRenderer API 变更

旧版 API 新版 API 说明
new JianpuRenderer(config) new JianpuRenderer(config) 配置项新增 solfegeMode
- setSolfegeMode(mode) 【新增】设置唱名法模式
- getSolfegeMode() 【新增】获取当前模式
- toggleSolfegeMode() 【新增】切换模式
render() render() 无变化
bindOSMD(osmd) bindOSMD(osmd) 无变化,保持兼容

4.2 RenderConfig 变更

// 旧版配置
interface RenderConfig {
  quarterNoteSpacing: number;
  noteFontSize: number;
  systemWidth: number;
  // ...
}

// 新版配置(新增字段)
interface RenderConfig {
  quarterNoteSpacing: number;
  noteFontSize: number;
  systemWidth: number;
  // ...
  
  // 【新增】唱名法模式
  solfegeMode: SolfegeMode;  // 'fixed' | 'movable'
}

// 唱名法模式类型
type SolfegeMode = 'fixed' | 'movable';

4.3 OSMDCompatibilityAdapter 变更

// 新版 TimesItem 新增字段
interface TimesItem {
  // 原有字段...
  
  // 【新增】首调相关字段
  solfegeMode?: SolfegeMode;
  jianpuNumber?: number;      // 简谱数字(1-7)
  jianpuOctave?: number;      // 八度偏移
  jianpuAccidental?: string;  // 简谱升降号
}

5. 数据模型变更

5.1 新增 music-core 数据模型

// 新增的核心数据模型
import { Score, Part, Measure, Note, Pitch } from '@/music-core/models';

// Score - 乐谱
interface Score {
  partList: PartListItem[];
  parts: Part[];
  work?: WorkInfo;
  identification?: Identification;
  defaults?: Defaults;
}

// Part - 声部
interface Part {
  id: string;
  measures: Measure[];
}

// Measure - 小节
interface Measure {
  number: string;
  elements: MeasureElement[];
  // 便捷属性
  get notes(): Note[];
  get attributes(): Attributes | undefined;
}

// Note - 音符
interface Note {
  pitch?: Pitch;
  rest?: Rest;
  duration: number;
  type?: NoteType;
  dots?: number;
  voice: number;
  staff: number;
  // ...
}

// Pitch - 音高
interface Pitch {
  step: StepName;      // 'C' | 'D' | 'E' | 'F' | 'G' | 'A' | 'B'
  octave: number;      // 0-9
  alter: AlterationType; // -2 到 +2
}

5.2 数据模型映射

OSMD 数据 music-core 数据 说明
osmd.Sheet score 乐谱顶层
osmd.Sheet.SourceMeasures score.parts[0].measures 小节列表
voiceEntry.Notes[0] measure.notes[i] 音符
note.Pitch note.pitch 音高(类名变化)
note.Length.RealValue note.duration / divisions 时值计算方式变化

6. 常见迁移场景

6.1 场景一:仅升级简谱渲染器

最小改动方案,保持 OSMD 依赖不变:

// 只需更新配置,添加 solfegeMode
const renderer = new JianpuRenderer({
  container: '#score',
  quarterNoteSpacing: 50,
  solfegeMode: 'fixed',  // 保持固定调行为
});

// 其他代码无需修改
renderer.bindOSMD(osmd);
renderer.render();

6.2 场景二:添加首调支持

// 1. 更新渲染器配置
const renderer = new JianpuRenderer({
  container: '#score',
  solfegeMode: 'movable',  // 启用首调
});

// 2. 添加模式切换 UI
document.getElementById('toggle-mode').onclick = () => {
  renderer.toggleSolfegeMode();
  updateModeLabel(renderer.getSolfegeMode());
};

// 3. 监听调号变化(首调模式下需要)
// 调号会自动从乐谱中读取,显示为 "1=G" 等格式

6.3 场景三:添加五线谱渲染

import { MusicXMLParser } from '@/music-core/parser';
import { JianpuRenderer } from '@/jianpu-renderer';
import { StaffRenderer } from '@/renderers/staff';

// 1. 解析 MusicXML(共用数据源)
const parser = new MusicXMLParser();
const { score } = parser.parse(musicXmlString);

// 2. 简谱渲染器(保持原有逻辑)
const jianpuRenderer = new JianpuRenderer({
  container: '#jianpu-container',
});
jianpuRenderer.bindOSMD(osmd);  // 仍可使用 OSMD 兼容模式
jianpuRenderer.render();

// 3. 五线谱渲染器(新增)
const staffRenderer = new StaffRenderer({
  container: document.getElementById('staff-container'),
});
staffRenderer.load(score);
staffRenderer.render();

// 4. 模式切换 - 显示/隐藏不同容器
function showJianpu() {
  document.getElementById('jianpu-container').style.display = 'block';
  document.getElementById('staff-container').style.display = 'none';
}

function showStaff() {
  document.getElementById('jianpu-container').style.display = 'none';
  document.getElementById('staff-container').style.display = 'block';
}

6.4 场景四:完全迁移到新架构

import { MusicXMLParser } from '@/music-core/parser';
import { JianpuConverter } from '@/renderers/jianpu/converter';
import { StaffRenderer } from '@/renderers/staff';

// 1. 独立解析 MusicXML
const parser = new MusicXMLParser();
const { score, warnings, errors } = parser.parse(musicXmlString);

if (errors.length > 0) {
  throw new Error(`解析失败: ${errors.join(', ')}`);
}

// 2. 使用转换器处理简谱数据
const converter = new JianpuConverter();
converter.setKeySignature(score.parts[0].measures[0].attributes?.primaryKey);

for (const measure of score.parts[0].measures) {
  for (const note of measure.notes) {
    if (note.pitch) {
      // 首调转换
      const jianpuData = converter.convertPitch(note.pitch, 'movable');
      console.log(`${note.pitch.step}${note.pitch.octave} → ${jianpuData.number}`);
    }
  }
}

// 3. 五线谱渲染
const staffRenderer = new StaffRenderer({
  container: document.getElementById('score'),
});
staffRenderer.load(score);
staffRenderer.render();

7. 兼容性说明

7.1 向后兼容

新版渲染器保持以下向后兼容:

功能 兼容性 说明
bindOSMD() ✅ 完全兼容 仍可绑定 OSMD 实例
render() ✅ 完全兼容 渲染接口不变
配置项 ✅ 完全兼容 原有配置项不变
TimesItem ✅ 完全兼容 新增字段为可选

7.2 废弃 API

目前没有废弃的 API,所有旧版 API 均保持可用。

7.3 TypeScript 类型

如果使用 TypeScript,需要更新类型导入:

// 新增类型导入
import type { SolfegeMode } from '@/jianpu-renderer/core/config/RenderConfig';
import type { Score, Part, Measure, Note } from '@/music-core/models';
import type { StaffRenderConfig, StaffRenderStats } from '@/renderers/staff';

8. 故障排除

8.1 常见问题

Q1: 首调模式下音符显示不正确

原因:调号信息未正确设置

解决

// 确保在渲染前调号已加载
renderer.load(osmd);  // load() 会自动读取调号
renderer.render();

// 或手动检查调号
console.log(renderer.getKeySignature());

Q2: 五线谱渲染器报错 "No score loaded"

原因:未调用 load() 方法

解决

const renderer = new StaffRenderer({ container });
renderer.load(score);  // 必须先加载
renderer.render();

Q3: 类型错误 "Property 'solfegeMode' does not exist"

原因:TypeScript 类型未更新

解决

// 确保导入最新类型
import { RenderConfig, SolfegeMode } from '@/jianpu-renderer/core/config/RenderConfig';

Q4: 大乐谱渲染卡顿

原因:未使用性能优化

解决

import { BatchRenderer, LazyRenderer, EdgeCaseHandler } from '@/renderers/staff';

// 检查是否为大乐谱
if (EdgeCaseHandler.isLargeScore(score)) {
  // 使用批量渲染
  const batchRenderer = new BatchRenderer({ batchSize: 50 });
  // ... 配置批量渲染
}

8.2 调试技巧

// 启用调试模式
const renderer = new StaffRenderer({
  container,
  debug: true,  // 输出调试日志
});

// 获取渲染统计
const stats = renderer.getStats();
console.log('渲染统计:', stats);

// 验证乐谱数据
import { EdgeCaseHandler } from '@/renderers/staff';
const validation = EdgeCaseHandler.validateScore(score);
console.log('验证结果:', validation);

附录

A. 相关文档

B. 更新记录

版本 日期 更新内容
1.0 2026-01-30 初始版本