Audiveris 颤音(Tremolo)识别与编辑实战指南:从 OMR 自动识别到手动校正
Audiveris 颤音(Tremolo)识别与编辑实战指南:从 OMR 自动识别到手动校正
本指南聚焦 Audiveris OMR 引擎对"单侧型"颤音记号(tremolo)的完整处理链路:识别(识别形状 TREMOLO_1/2/3)、关联(与带符干和弦或无符干和弦的水平对齐)、输出(MusicXML 中的 ornament 标记)以及手动校正(形状面板拖拽与吸附编辑)。读完本文,你将掌握如何让 Audiveris 正确处理颤音、判断引擎支持边界,并在识别失败时用
BeamsEtc形状集手动补录、微调颤音。
一、什么是 Audiveris 中的 Tremolo,能处理什么
自 5.3 版本起,Audiveris 正式支持颤音(tremolo)记号的处理。在乐谱符号学中,颤音通常以 1~3 条倾斜短线表示,表示某音或相邻两音的快速重复。Audiveris 目前支持的是"单侧型"颤音(single-type tremolo):
- 它由 1、2 或 3 条倾斜线段组成,对应三种精确形状;
- 它必须与某个和弦水平对齐(horizontally aligned),该和弦可以是基于符干的(stem-based chord),也可以是无符干的(stem-less chord);
- 它挂在单个符干或单个无符干符头上,这与乐谱中另一种"双侧型"(double-type,位于两符干之间)颤音相对。
在源码中,这三种形状定义在 Shape.java:
| 形状枚举 | 中文含义 | 线条数 |
|---|---|---|
TREMOLO_1 |
单颤音(Single tremolo) | 1 |
TREMOLO_2 |
双颤音(Double tremolo) | 2 |
TREMOLO_3 |
三颤音(Triple tremolo) | 3 |
而承载颤音的图形化解释对象(Inter)是 TremoloInter.java,其 Javadoc 明确指出:当前不支持超过 3 条线的颤音,且这些记号是"单侧型"——链接到单个符干或单个无符干符头,而非位于两符干之间的"双侧型"。
需要特别留意的是,Audiveris 对颤音的支持是有明确边界的(见 tremolo.md 的警告块):
暂不支持"两和弦之间"的颤音。即乐谱中那种横跨两个音符、位于两个符干之间的颤音(常见于八分音符/十六分音符的快速交替片段),当前版本无法自动识别,需要后续版本或手动处理。
这意味着,在实际应用中,你需要先判断手头乐谱中的颤音是"单侧型"(可自动识别)还是"双侧型"(不可自动识别),再决定走自动流程还是手动流程。
二、自动识别链路:从字形到 SIG 图
当 OMR 引擎在符号步(Symbols step)检测到候选字形后,会由 InterFactory.java 按形状分发处理:
// Tremolos
case TREMOLO_1:
case TREMOLO_2:
case TREMOLO_3:
return switches.getValue(ProcessingSwitch.tremolos) ? TremoloInter.createValidAdded(
glyph, shape, grade, system, systemStems, systemHeads) : null;
这里有两个关键点:
- 受开关控制:颤音识别受
ProcessingSwitch.tremolos开关(默认false)约束,定义于 ProcessingSwitches.java。若该开关关闭,即使检测到颤音字形也会被丢弃; - 必须"有效创建":
createValidAdded()并不会无条件创建颤音,而是先尝试寻找可关联的符干或无符干符头(见 TremoloInter.java):
final TremoloInter tremolo = new TremoloInter(glyph, glyph.getBounds(), shape, grade);
Link link = tremolo.lookupStemLink(systemStems, system, system.getProfile());
if (link != null) {
return createWithLink(tremolo, link, system);
}
link = tremolo.lookupWholeLink(systemHeads, system, system.getProfile());
if (link != null) {
return createWithLink(tremolo, link, system);
}
return null; // 找不到可对齐的符干/符头,颤音不成立
也就是说,一个"有效"的颤音必须满足"与附近符干或无符干符头对齐"的条件,否则即使字形识别成功,也不会被接纳为有效颤音——这正是"必须与和弦水平对齐"这一规则在源码层的实现。
2.1 与符干的对齐查找(lookupStemLink)
lookupStemLink 的实现逻辑:
- 以颤音中心为中心,按
TremoloStemRelation的容差参数(水平最大偏移xOutGapMax = 0.3个间隔单位、垂直最大间隙yGapMax = 0,见 TremoloStemRelation.java)构建查找区域; - 考虑颤音的斜率(
slope = -0.31,见 TremoloInter.java)对垂直区间做修正(yShift = |width × 0.5 × slope|); - 遍历区域内符干,计算颤音中心与符干中线(median)在水平方向的差值
dx、在垂直方向的交叠间隙dy; - 对每个候选构建
TremoloStemRelation并打分,取分数最高者作为最佳链接。
2.2 与无符干符头的对齐查找(lookupWholeLink)
lookupWholeLink 的逻辑与之对称,但有两个不同点:
- 只接受无符干符头(
inter.getShape().isStemLessHead(),典型如全音符 whole note),并在 TremoloWholeRelation.java 的isForbidden()中再次强制校验目标必须是ShapeSet.StemLessHeads中的形状; - 容差更宽松:垂直最大间隙
yGapMax = 2.0(因为全音符符头与颤音之间存在间隔距离),水平最大偏移同样为0.3(见 TremoloWholeRelation.java)。
两个查找方法的查找顺序是固定的:先找符干(stem),找不到再找无符干符头(whole head)(见 searchLinks(),TremoloInter.java)。dx、dy 均以间隔(interline)为单位的分数值(Fraction)存储,通过 Scale.pixelsToFrac() 归一化,从而保证对不同谱面缩放(scale)的自适应性。
2.3 颤音聚合:多线合成(aggregate)
颤音的 1~3 条线在图片中可能被拆成多个字形分别识别,为此 LinksStep.java 在符号链接步(SymbolsLinker)之后调用 TremoloInter.aggregate(system):
// Aggregate tremolos whenever needed
TremoloInter.aggregate(system);
aggregate() 分别沿"系统内所有符干"和"系统内所有无符干符头"两条路径,寻找同一个符干/符头上挂着多个颤音关系的情况。若某个符干/符头关联了多个 TremoloInter,则:
- 收集所有关联颤音,累加各自的线数(
getTremoloValue())与评分; - 以并集边界(union bounds)作为新边界,按总线数合成新的复合颤音(
getTremoloShape(count)将 1/2/3 映射回TREMOLO_1/2/3); - 建立复合颤音与符干/符头的关系,并将其作为和弦装饰(ornament)链接,然后移除原先的零散颤音。
这保证了"由多条线组成的颤音"在最终结果中以一个整体对象存在。
2.4 颤音与和弦的"装饰"关系
颤音本质上是一种装饰音(ornament)。TremoloInter 继承自 OrnamentInter,因此在 linkAsOrnament()(TremoloInter.java)中,颤音会通过 ChordOrnamentRelation 与相关符干所属的头和弦(HeadChord)或全音符的头和弦建立边。这样,颤音就正式进入了"和弦-装饰"的关系网络,为其后的节奏/声部推导与 MusicXML 导出提供了依据。
三、输出到 MusicXML:颤音如何落盘
在导出 MusicXML 时,MusicXML.java 将 TremoloInter 转写为标准 MusicXML 的 <ornaments><tremolo> 结构:
final Tremolo tremolo = factory.createTremolo();
tremolo.setDefaultY(defaultY);
tremolo.setType(TremoloType.SINGLE);
tremolo.setValue(TremoloInter.getTremoloValue(shape));
yield factory.createOrnamentsTremolo(tremolo);
type=SINGLE与源码"单侧型颤音"的定位一致;value取 1/2/3,即颤音线条数;defaultY传递颤音的垂直位置。
也就是说,识别成功的颤音最终会以标准 MusicXML 装饰音形式输出,可被 MuseScore 等下游软件正确渲染。
四、手动校正:形状面板补录与吸附编辑
当 OMR 引擎未能自动识别出颤音(例如受开关关闭、查找不到对齐目标、或字形质量差等因素影响)时,文档给出了明确的手动方案(见 tremolo.md):
总是可以从形状面板(shape palette)中**分配(assign)或拖拽(drag)**所需的颤音,使用
BeamsEtc形状集。
BeamsEtc 形状集在 ShapeSet.java 中定义,包含 BEAM、BEAM_SMALL、BEAM_HOOK、Tremolos(即 TREMOLO_1/2/3,见 ShapeSet.java)以及连音号 TUPLET_THREE、TUPLET_SIX。因此在面板中,颤音与符杠(beam)、连音号位于同一组,便于快速选取。
手动新增颤音时,用户从面板拖出形状(deriveFrom,TremoloInter.java)会触发吸附逻辑:
// For a tremolo, we snap center abscissa to nearby stem or whole head if any
if (staff != null) {
final Double x = getSnapAbscissa();
if (x != null) {
dropLocation.x = (int) Math.rint(x);
super.deriveFrom(symbol, sheet, font, dropLocation); // 用修正后的落点重新生成
}
}
getSnapAbscissa() 会在颤音附近查找候选链接:若是符干则取"符干中线在颤音中心高度处的 x 坐标"(LineUtil.xAtY(stem.getMedian(), getCenter().y)),若是无符干符头则取符头中心 x 坐标,然后把颤音中心的水平坐标吸附到该 x 值。
创建后还会通过 preAdd()(TremoloInter.java)把颤音以 ChordOrnamentRelation(标记为手动 setManual(true))链接到相应和弦上,使其成为和弦装饰。
4.1 编辑能力边界:仅平移 + 水平吸附
文档明确指出(见 tremolo.md):
Tremolo 编辑仅限于平移(translations),平移过程中会吸附(snap)到附近兼容的和弦上。
其编辑器实现于 TremoloInter.java 的内部类 Editor:
- 只提供一个位于颤音中心的中间句柄;
- 该句柄可在任意方向移动(
PointUtil.add(center, dx, dy)),但水平方向会尝试吸附到符干或无符干符头(getSnapAbscissa()返回非空时强制修正latestBounds.x); doit()/undo()分别提交最新边界或还原原始边界。
因此,拖拽颤音时它的垂直位置可以自由调整(例如对齐谱线),而水平位置会自动对齐到最近的兼容符干/符头,避免用户手动"瞄"位置的麻烦。请勿期待对颤音做旋转、倾斜或变形编辑——当前版本只支持平移。
4.2 异常检查:无关联即为异常
无论是自动识别还是手动添加,颤音在 SIG 图中都会受 checkAbnormal()(TremoloInter.java)约束:
setAbnormal(!sig.hasRelation(this, TremoloStemRelation.class)
&& !sig.hasRelation(this, TremoloWholeRelation.class));
即:一个颤音若既没有 TremoloStemRelation 也没有 TremoloWholeRelation 关联,就会被标记为异常(abnormal)。当 TremoloStemRelation/TremoloWholeRelation 被添加或移除时,都会触发对颤音的重检(见两个关系类的 added()/removed())。这也是用户在界面上看到"颤音报警"提示的判定依据,提醒你补齐缺失的对齐关系。
五、实战要点速查
综合文档与源码,落地使用 Audiveris 处理颤音时建议按以下顺序排查:
- 确认颤音类型:仅"单侧型"(单符干或单无符干符头)可自动识别;两和弦之间(两符干之间)的双侧型颤音当前不受支持,需等待后续版本或采用替代记录方式;
- 确认处理开关:颤音识别受
tremolos处理开关控制,默认关闭。若自动流程未产出颤音,可在处理开关配置中开启后再跑一遍(见 ProcessingSwitches.java); - 检查对齐条件:颤音必须与附近符干(水平偏移 ≤ 0.3 间隔、垂直间隙 0)或无符干符头(水平偏移 ≤ 0.3 间隔、垂直间隙 ≤ 2.0 间隔)对齐才会被创建,确保颤音字形与符干/符头在水平位置上接近;
- 手动补录:识别失败时,从形状面板的
BeamsEtc集合中选择TREMOLO_1、TREMOLO_2或TREMOLO_3拖入谱面,落点会自动吸附到兼容和弦;也可先 Assign 再拖动微调; - 编辑微调:仅支持平移;拖动中心句柄调整位置,水平方向自动吸附,垂直方向自由调整;
- 检查异常:若颤音被标记为异常,多半是缺少与符干/符头的关系,检查其水平位置是否真正与目标对齐;
- 导出验证:识别结果最终以 MusicXML
<ornaments><tremolo type="single" value="1|2|3">输出(见 MusicXML.java),可在导出后用支持 MusicXML 的播放器/打谱软件验证颤音渲染效果。
六、相关源码与文档索引
- 官方功能文档:docs/_pages/guides/specific/tremolo.md
- 颤音解释对象(Inter)与编辑器、聚合逻辑:app/src/main/java/org/audiveris/omr/sig/inter/TremoloInter.java
- 颤音形状定义(TREMOLO_1/2/3):app/src/main/java/org/audiveris/omr/glyph/Shape.java
- 颤音形状集 BeamsEtc:app/src/main/java/org/audiveris/omr/glyph/ShapeSet.java
- 颤音-符干关系:app/src/main/java/org/audiveris/omr/sig/relation/TremoloStemRelation.java
- 颤音-无符干符头关系:app/src/main/java/org/audiveris/omr/sig/relation/TremoloWholeRelation.java
- 识别分发与手动创建入口:app/src/main/java/org/audiveris/omr/sheet/symbol/InterFactory.java
- 符号链接步中的颤音聚合:app/src/main/java/org/audiveris/omr/sheet/symbol/LinksStep.java
- 处理开关 tremolos:app/src/main/java/org/audiveris/omr/sheet/ProcessingSwitches.java
- MusicXML 导出:app/src/main/java/org/audiveris/omr/score/MusicXML.java
- 颤音符号绘制(含装饰符干):app/src/main/java/org/audiveris/omr/ui/symbol/TremoloSymbol.java