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截图


原因分析
- 遍历对象错误:你用
t.flatten().notes遍历轨道内容,这个属性仅返回Note(音符)和Chord(和弦)对象,Rest(休止符)是独立类,不会被包含在内。 - 未处理休止符逻辑:后续构建新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')
关键修改点
- 遍历对象替换:将
t.flatten().notes改为t.flatten().notesAndRests,同时获取音符、和弦和休止符。 - 类型判断处理:用
isinstance()区分音符/和弦与休止符,分别存储对应的属性。 - 写入逻辑完善:构建新MIDI时,根据存储的类型分别创建
Note或Rest对象并添加到流中。
内容的提问来源于stack exchange,提问作者abeh
相关产品推荐
相关产品推荐

