You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何避免Music21分割跨小节音符?MIDI文件音符提取异常求助

解决Music21跨小节音符被拆分的问题

我完全懂你碰到的这个麻烦——music21默认会把跨小节线的音符拆成两个独立实例,这确实会让小节音符数量统计虚高,太影响结果了。下面给你几个实用的解决办法:

方法1:用noTie=True参数直接阻止拆分

这是最省心的办法,在调用flatten()的时候加上noTie=True参数,它会告诉music21不要把跨小节的音符拆分成带连线的两个部分,而是保留成一个完整的音符实例。

修改你代码里的这一行:

t = mf.parts[0].flatten(noTie=True)

这样处理后,跨小节的音符会以一个整体存在,它的measureNumber会显示为起始的那个小节。如果需要判断它是否跨小节,可以通过计算音符的起始偏移量+时长是否超过该小节的总长度来确认,但至少不会出现拆分导致的统计错误。

方法2:手动合并被拆分的连音音符

如果第一种方法不满足你的需求(比如你需要保留连线信息但又要合并统计),可以手动遍历音符列表,把通过tie连接的拆分音符合并成一个。

示例代码片段:

# 先获取扁平化后的音符列表
notes_list = list(t.flatten().notes)
merged_notes = []
i = 0

while i < len(notes_list):
    current_note = notes_list[i]
    # 检查当前音符是否是连音的起始
    if current_note.tie and current_note.tie.type == 'start':
        # 寻找对应的连音结束音符
        j = i + 1
        while j < len(notes_list):
            next_note = notes_list[j]
            # 匹配同音高的连音结束音符
            if next_note.tie and next_note.tie.type == 'stop' and next_note.pitch == current_note.pitch:
                # 合并两个音符的时长
                current_note.duration.quarterLength += next_note.duration.quarterLength
                merged_notes.append(current_note)
                i = j + 1  # 跳过已合并的结束音符
                break
            j += 1
    else:
        merged_notes.append(current_note)
        i += 1

# 之后用merged_notes来进行统计和输出
for h, n in enumerate(merged_notes):
    altura_numerica = n.pitches[0].midi
    altura_literal = n.pitches[0].nameWithOctave
    momento_inicio = n.offset
    duracion = n.duration.quarterLength
    compas = n.measureNumber
    print(f" {h:<5} {compas:<5} {momento_inicio:<8.4g} {altura_numerica:<3} {altura_literal:<3} {duracion:<6.4g} ")

方法3:按小节逐个处理,跳过跨小节延续的音符

如果你的核心需求是准确统计每个小节实际起始的音符(不包含从前面小节延续过来的),可以直接按小节遍历,而不是提前扁平化整个声部。

示例代码:

notas_por_compas = {}
part = mf.parts[0]
num_compases = len(part.getElementsByClass('Measure'))
tipo_compas = mf.getTimeSignatures()[0].ratioString
print("- Tipo de compás:", tipo_compas,"-")

for measure_num in range(1, num_compases + 1):
    measure = part.measure(measure_num)
    # 收集当前小节中不是跨小节延续的音符
    current_notes = []
    for n in measure.notes:
        # 如果是连音的结束(即来自前一小节的延续),就跳过
        if n.tie and n.tie.type == 'stop':
            continue
        current_notes.append(n)
    notas_por_compas[measure_num] = current_notes

# 输出统计结果
print("Nota compas t_inicio Alt_Num Alt_Lit Duracion")
h = 0
for compas, notas in notas_por_compas.items():
    for n in notas:
        altura_numerica = n.pitches[0].midi
        altura_literal = n.pitches[0].nameWithOctave
        momento_inicio = n.offset
        duracion = n.duration.quarterLength
        print(f" {h:<5} {compas:<5} {momento_inicio:<8.4g} {altura_numerica:<3} {altura_literal:<3} {duracion:<6.4g} ")
        h += 1

这三种方法可以根据你的具体需求选择:方法1最快捷,适合大多数场景;方法2更灵活,适合需要保留细节的情况;方法3则精准针对小节统计的需求。

内容的提问来源于stack exchange,提问作者abeh

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.27 10:47:33