Audiveris 颤音(Tremolo)识别与编辑实战指南:从 OMR 自动识别到手动校正

原创2026-10-02 19:16:24318 阅读
文章标签:桌面应用计算机视觉图像处理OCR

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;

这里有两个关键点:

  1. 受开关控制:颤音识别受 ProcessingSwitch.tremolos 开关(默认 false)约束,定义于 ProcessingSwitches.java。若该开关关闭,即使检测到颤音字形也会被丢弃;
  2. 必须"有效创建":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,则:

  1. 收集所有关联颤音,累加各自的线数(getTremoloValue())与评分;
  2. 以并集边界(union bounds)作为新边界,按总线数合成新的复合颤音(getTremoloShape(count) 将 1/2/3 映射回 TREMOLO_1/2/3);
  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 处理颤音时建议按以下顺序排查:

  1. 确认颤音类型:仅"单侧型"(单符干或单无符干符头)可自动识别;两和弦之间(两符干之间)的双侧型颤音当前不受支持,需等待后续版本或采用替代记录方式;
  2. 确认处理开关:颤音识别受 tremolos 处理开关控制,默认关闭。若自动流程未产出颤音,可在处理开关配置中开启后再跑一遍(见 ProcessingSwitches.java);
  3. 检查对齐条件:颤音必须与附近符干(水平偏移 ≤ 0.3 间隔、垂直间隙 0)或无符干符头(水平偏移 ≤ 0.3 间隔、垂直间隙 ≤ 2.0 间隔)对齐才会被创建,确保颤音字形与符干/符头在水平位置上接近;
  4. 手动补录:识别失败时,从形状面板的 BeamsEtc 集合中选择 TREMOLO_1、TREMOLO_2 或 TREMOLO_3 拖入谱面,落点会自动吸附到兼容和弦;也可先 Assign 再拖动微调;
  5. 编辑微调:仅支持平移;拖动中心句柄调整位置,水平方向自动吸附,垂直方向自由调整;
  6. 检查异常:若颤音被标记为异常,多半是缺少与符干/符头的关系,检查其水平位置是否真正与目标对齐;
  7. 导出验证:识别结果最终以 MusicXML <ornaments><tremolo type="single" value="1|2|3"> 输出(见 MusicXML.java),可在导出后用支持 MusicXML 的播放器/打谱软件验证颤音渲染效果。

六、相关源码与文档索引

登录后查看全文
audiveris