版本:1.0
更新日期:2026-01-30
适用范围:从旧版渲染器迁移到新架构
新版乐谱渲染引擎相比旧版有以下改进:
| 特性 | 旧版 | 新版 |
|---|---|---|
| 数据解析 | 依赖 OSMD | 独立 music-core 解析器 |
| 简谱渲染 | 仅支持固定调 | 支持首调/固定调切换 |
| 五线谱渲染 | 依赖 OSMD | 独立五线谱渲染器 |
| 数据模型 | 与 OSMD 耦合 | 通用数据模型 |
| 性能优化 | 无 | 缓存、批量渲染、懒加载 |
| 可扩展性 | 较差 | 模块化设计,易扩展 |
旧版代码
│
├─→ 步骤1: 更新数据解析(使用 music-core)
│
├─→ 步骤2: 更新简谱渲染器调用
│
├─→ 步骤3: 集成五线谱渲染器(可选)
│
└─→ 步骤4: 更新业务层适配
旧版结构:
src/
├── jianpu-renderer/ # 简谱渲染(依赖 OSMD 数据)
└── ... (直接使用 OSMD)
新版结构:
src/
├── music-core/ # 【新增】通用数据模型和解析器
│ ├── models/ # 数据模型
│ ├── parser/ # MusicXML 解析器
│ └── utils/ # 工具函数
├── renderers/ # 【新增】渲染器层
│ ├── common/ # 共享类型
│ ├── jianpu/ # 简谱转换器
│ │ └── converter/ # 首调/固定调转换
│ └── staff/ # 【新增】五线谱渲染器
│ ├── drawer/ # 绘制器
│ ├── layout/ # 布局引擎
│ └── utils/ # 工具
└── jianpu-renderer/ # 简谱渲染器(已更新)
旧版依赖:
OSMD → JianpuRenderer
新版依赖:
MusicXML → music-core → JianpuRenderer
→ StaffRenderer
// 旧版:依赖 OSMD 解析
import { OpenSheetMusicDisplay } from 'osmd-extended';
const osmd = new OpenSheetMusicDisplay(container);
await osmd.load(musicXmlString);
osmd.render();
// 获取数据需要访问 OSMD 内部结构
const measures = osmd.Sheet.SourceMeasures;
// 新版:独立解析
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;
// 旧版:仅支持固定调
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(); // 切换模式
// 新版:独立五线谱渲染
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} 个音符`);
| 旧版 API | 新版 API | 说明 |
|---|---|---|
new JianpuRenderer(config) |
new JianpuRenderer(config) |
配置项新增 solfegeMode |
| - | setSolfegeMode(mode) |
【新增】设置唱名法模式 |
| - | getSolfegeMode() |
【新增】获取当前模式 |
| - | toggleSolfegeMode() |
【新增】切换模式 |
render() |
render() |
无变化 |
bindOSMD(osmd) |
bindOSMD(osmd) |
无变化,保持兼容 |
// 旧版配置
interface RenderConfig {
quarterNoteSpacing: number;
noteFontSize: number;
systemWidth: number;
// ...
}
// 新版配置(新增字段)
interface RenderConfig {
quarterNoteSpacing: number;
noteFontSize: number;
systemWidth: number;
// ...
// 【新增】唱名法模式
solfegeMode: SolfegeMode; // 'fixed' | 'movable'
}
// 唱名法模式类型
type SolfegeMode = 'fixed' | 'movable';
// 新版 TimesItem 新增字段
interface TimesItem {
// 原有字段...
// 【新增】首调相关字段
solfegeMode?: SolfegeMode;
jianpuNumber?: number; // 简谱数字(1-7)
jianpuOctave?: number; // 八度偏移
jianpuAccidental?: string; // 简谱升降号
}
// 新增的核心数据模型
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
}
| 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 |
时值计算方式变化 |
最小改动方案,保持 OSMD 依赖不变:
// 只需更新配置,添加 solfegeMode
const renderer = new JianpuRenderer({
container: '#score',
quarterNoteSpacing: 50,
solfegeMode: 'fixed', // 保持固定调行为
});
// 其他代码无需修改
renderer.bindOSMD(osmd);
renderer.render();
// 1. 更新渲染器配置
const renderer = new JianpuRenderer({
container: '#score',
solfegeMode: 'movable', // 启用首调
});
// 2. 添加模式切换 UI
document.getElementById('toggle-mode').onclick = () => {
renderer.toggleSolfegeMode();
updateModeLabel(renderer.getSolfegeMode());
};
// 3. 监听调号变化(首调模式下需要)
// 调号会自动从乐谱中读取,显示为 "1=G" 等格式
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';
}
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();
新版渲染器保持以下向后兼容:
| 功能 | 兼容性 | 说明 |
|---|---|---|
bindOSMD() |
✅ 完全兼容 | 仍可绑定 OSMD 实例 |
render() |
✅ 完全兼容 | 渲染接口不变 |
| 配置项 | ✅ 完全兼容 | 原有配置项不变 |
| TimesItem | ✅ 完全兼容 | 新增字段为可选 |
目前没有废弃的 API,所有旧版 API 均保持可用。
如果使用 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';
原因:调号信息未正确设置
解决:
// 确保在渲染前调号已加载
renderer.load(osmd); // load() 会自动读取调号
renderer.render();
// 或手动检查调号
console.log(renderer.getKeySignature());
原因:未调用 load() 方法
解决:
const renderer = new StaffRenderer({ container });
renderer.load(score); // 必须先加载
renderer.render();
原因:TypeScript 类型未更新
解决:
// 确保导入最新类型
import { RenderConfig, SolfegeMode } from '@/jianpu-renderer/core/config/RenderConfig';
原因:未使用性能优化
解决:
import { BatchRenderer, LazyRenderer, EdgeCaseHandler } from '@/renderers/staff';
// 检查是否为大乐谱
if (EdgeCaseHandler.isLargeScore(score)) {
// 使用批量渲染
const batchRenderer = new BatchRenderer({ batchSize: 50 });
// ... 配置批量渲染
}
// 启用调试模式
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);
| 版本 | 日期 | 更新内容 |
|---|---|---|
| 1.0 | 2026-01-30 | 初始版本 |