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

music21处理MIDI时忽略休止符的问题排查求助

问题:music21读取MIDI丢失休止符的原因及解决办法

我编写了一段Python代码,借助music21库读取MIDI文件某轨道中的音符(包含起始时间、音高、时长)并写入新的MIDI文件。但原MIDI文件中包含的休止符并未出现在输出的MIDI文件中,想请教为何music21会忽略休止符,希望得到解决建议。

原代码

from music21 import *
import math
from music21 import stream, note, tempo

ruta_archivo_seleccionado = 'Original.mid'

print("\nLeyendo el MIDI")
#Cargar el archivo MIDI*
mf = converter.parse(ruta_archivo_seleccionado)

#Obtener el primer track*
t = mf.parts[0]

#Listas y Diccionarios
notas = []#Crear una lista para almacenar las notas con su altura, momento de inicio, duración y compás*
notas_por_compas = {}#Crear una lista o diccionario para almacenar las notas con su altura, momento de inicio, duración y compás*
melodia_padre = {}#Crear una lista o diccionario para almacenar las notas con su altura, momento de inicio, duración y compás*


#Iterar sobre las notas del track*
#tiempo_actual = 0
h = 0
#print("\nLista Notas:")
#print("      [nota compas tiempo altura nombre  duración]")

print("Extrayendo las notas y calculando las entropias\n")
for n in t.flatten().notes:
    altura_numerica = n.pitches[0].midi  # Altura de la nota en formato numérico (midi)
    altura_literal = n.pitches[0].nameWithOctave  # Altura de la nota en formato literal (do, re, etc.)
    momento_inicio = round(float(n.offset),2)
    #tick_inicio = int(n.offset * mf.ticksPerQuarter)
    duracion = round(float(n.duration.quarterLength),2)
    compas = n.measureNumber  # Compás donde está ubicada la nota
    notas.append({'altura_numerica': altura_numerica, 'altura_literal': altura_literal, 'inicio': momento_inicio, 'duracion': duracion, 'compas': compas})
    #print(compas, momento_inicio, altura_numerica, altura_literal, duracion )
    #print(f"Nota: {h:<3} {beat_evento:<7}    {altura:<3}     {duracion}      {compas_evento:<6} {beat_evento}") #ESTA
    print("Nota compas t_inicio alt_numerica alt_literal duracion")
    print(f" {h:<5} {compas:<5}  {momento_inicio:<8}   {altura_numerica:<11}  {altura_literal:<6}    {duracion} ")
    h = h + 1
    #Pasa la información al diccionario notas_por_compas
    if compas not in notas_por_compas:
        notas_por_compas[compas] = {'alturas': [], 'duraciones': []} # Solo esta linea debe ir dentro del if
    notas_por_compas[compas]['alturas'].append(altura_literal) # En cambio estas lineas van dentro del for
    notas_por_compas[compas]['duraciones'].append(duracion)
        #Cuenta de duraciones diferentes
    notas_por_compas[compas]['cuenta_alturas_diferentes'] = len(set(notas_por_compas[compas]['alturas']))
        #Cuenta de duraciones diferentes
    notas_por_compas[compas]['cuenta_duraciones_diferentes'] = len(set(notas_por_compas[compas]['duraciones']))
        #Total de elementos en el compas
    notas_por_compas[compas]['total_elementos'] = len(notas_por_compas[compas]['alturas'])
    if compas not in melodia_padre:
        melodia_padre[compas] = []
    melodia_padre[compas].append({'t_inicio': momento_inicio, 'altura': altura_numerica, 'duracion': duracion})

melodia_hijo = {}

for compas, notas in melodia_padre.items():
    melodia_hijo[compas] = []
    for nota in notas:
        nueva_altura = nota['altura'] + 0  # Sumar 2 a la altura_numerica
        melodia_hijo[compas].append({'t_inicio': nota['t_inicio'], 'altura': nueva_altura, 'duracion': nota['duracion']})

nuevo_midi = stream.Stream()

nuevo_tempo = tempo.MetronomeMark(number=100)  # Reemplaza 120 con el valor de tempo deseado

nuevo_midi.insert(0, nuevo_tempo)

for compas, notas in melodia_hijo.items():
    for nota in notas:
        if nota['altura'] is not None:
            nueva_nota = note.Note()
            nueva_nota.pitch.midi = nota['altura']  # Establecer la altura de la nota
            nueva_nota.offset = nota['t_inicio']  # Establecer el momento de inicio de la nota
            nueva_nota.duration.quarterLength = nota['duracion']  # Establecer la duración de la nota
            print("Valores de nueva_nota:", compas, nueva_nota.pitch.midi, nueva_nota.offset, nueva_nota.duration.quarterLength)  # Instrucción print para monitorear los valores
            nuevo_midi.append(nueva_nota)
            
nuevo_midi.write('midi', 'hijo.mid')

原MIDI与输出MIDI截图

原MIDI文件截图
输出MIDI文件截图


原因分析

  1. 遍历对象错误:你用t.flatten().notes遍历轨道内容,这个属性仅返回Note(音符)和Chord(和弦)对象,Rest(休止符)是独立类,不会被包含在内。
  2. 未处理休止符逻辑:后续构建新MIDI流时,仅添加了Note对象,完全没有处理休止符的读取、存储和写入步骤。

解决办法

修改代码,同时读取音符和休止符,在存储和写入时区分处理:

修改后的完整代码

from music21 import *
import math
from music21 import stream, note, tempo, rest

ruta_archivo_seleccionado = 'Original.mid'

print("\nLeyendo el MIDI")
# 加载MIDI文件
mf = converter.parse(ruta_archivo_seleccionado)

# 获取第一个轨道
t = mf.parts[0]

# 存储音符和休止符的结构
elementos_por_compas = {}
melodia_padre = {}

print("Extrayendo las notas、休止符 y calculando las entropias\n")
h = 0
# 改用notesAndRests同时获取音符和休止符
for elem in t.flatten().notesAndRests:
    momento_inicio = round(float(elem.offset), 2)
    duracion = round(float(elem.duration.quarterLength), 2)
    compas = elem.measureNumber
    
    # 区分音符和休止符
    if isinstance(elem, note.Note) or isinstance(elem, chord.Chord):
        # 处理音符/和弦(这里取第一个音高,和原逻辑一致)
        altura_numerica = elem.pitches[0].midi
        altura_literal = elem.pitches[0].nameWithOctave
        print("Nota compas t_inicio alt_numerica alt_literal duracion")
        print(f" {h:<5} {compas:<5}  {momento_inicio:<8}   {altura_numerica:<11}  {altura_literal:<6}    {duracion} ")
        # 存储到结构中,标记类型为nota
        if compas not in melodia_padre:
            melodia_padre[compas] = []
        melodia_padre[compas].append({
            'tipo': 'nota',
            't_inicio': momento_inicio,
            'altura': altura_numerica,
            'duracion': duracion
        })
        # 更新elementos_por_compas(原逻辑保留)
        if compas not in elementos_por_compas:
            elementos_por_compas[compas] = {'alturas': [], 'duraciones': []}
        elementos_por_compas[compas]['alturas'].append(altura_literal)
        elementos_por_compas[compas]['duraciones'].append(duracion)
    elif isinstance(elem, rest.Rest):
        # 处理休止符
        print("休止符 compas t_inicio duracion")
        print(f" {h:<5} {compas:<5}  {momento_inicio:<8}    {duracion} ")
        # 存储到结构中,标记类型为silencio
        if compas not in melodia_padre:
            melodia_padre[compas] = []
        melodia_padre[compas].append({
            'tipo': 'silencio',
            't_inicio': momento_inicio,
            'duracion': duracion
        })
        # 更新duraciones统计(原逻辑调整)
        if compas not in elementos_por_compas:
            elementos_por_compas[compas] = {'alturas': [], 'duraciones': []}
        elementos_por_compas[compas]['duraciones'].append(duracion)
    
    # 更新统计数据(原逻辑保留)
    if compas in elementos_por_compas:
        elementos_por_compas[compas]['cuenta_alturas_diferentes'] = len(set(elementos_por_compas[compas]['alturas']))
        elementos_por_compas[compas]['cuenta_duraciones_diferentes'] = len(set(elementos_por_compas[compas]['duraciones']))
        elementos_por_compas[compas]['total_elementos'] = len(elementos_por_compas[compas]['alturas']) + len([e for e in melodia_padre[compas] if e['tipo'] == 'silencio'])
    
    h += 1

# 生成新旋律(保留原逻辑,同时处理休止符)
melodia_hijo = {}
for compas, elementos in melodia_padre.items():
    melodia_hijo[compas] = []
    for elem in elementos:
        if elem['tipo'] == 'nota':
            nueva_altura = elem['altura'] + 0  # 这里可以调整音高,原逻辑是+0
            melodia_hijo[compas].append({
                'tipo': 'nota',
                't_inicio': elem['t_inicio'],
                'altura': nueva_altura,
                'duracion': elem['duracion']
            })
        else:
            # 休止符直接复制
            melodia_hijo[compas].append({
                'tipo': 'silencio',
                't_inicio': elem['t_inicio'],
                'duracion': elem['duracion']
            })

# 构建新MIDI流
nuevo_midi = stream.Stream()
nuevo_tempo = tempo.MetronomeMark(number=100)
nuevo_midi.insert(0, nuevo_tempo)

# 写入音符和休止符
for compas, elementos in melodia_hijo.items():
    for elem in elementos:
        if elem['tipo'] == 'nota':
            nueva_nota = note.Note()
            nueva_nota.pitch.midi = elem['altura']
            nueva_nota.offset = elem['t_inicio']
            nueva_nota.duration.quarterLength = elem['duracion']
            nuevo_midi.append(nueva_nota)
        else:
            nuevo_silencio = rest.Rest()
            nuevo_silencio.offset = elem['t_inicio']
            nuevo_silencio.duration.quarterLength = elem['duracion']
            nuevo_midi.append(nuevo_silencio)

nuevo_midi.write('midi', 'hijo_con_silencios.mid')

关键修改点

  1. 遍历对象替换:将t.flatten().notes改为t.flatten().notesAndRests,同时获取音符、和弦和休止符。
  2. 类型判断处理:用isinstance()区分音符/和弦与休止符,分别存储对应的属性。
  3. 写入逻辑完善:构建新MIDI时,根据存储的类型分别创建Note或Rest对象并添加到流中。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.21 08:15:53