Music21移调时如何保留拍号?
解决Music21移调MIDI时丢失拍号的问题
你遇到的问题是因为Music21默认的transpose()方法在处理部分结构复杂的MIDI文件时,无法自动保留拍号这类元数据。以下是具体的解决方案和修改后的代码:
问题根源
像BWV 114这类多轨或分段的MIDI作品,拍号信息可能存储在轨道的特定位置,直接对整个Score对象执行transpose()时,这些元数据不会被正确复制到新生成的流中,最终导致输出MIDI丢失拍号。
解决方案
通过显式提取原曲的拍号,移调后重新插入到新流中,或者逐个轨道处理移调,确保所有元数据被完整保留。
修改后的完整代码
import os from pathlib import Path from music21 import converter, pitch, interval, stream def main(): print("cwd=", os.getcwd()) path = "midi/bachjs" with os.scandir(path) as it: for entry in it: if entry.name.startswith("Minuet") and entry.name.endswith(".mid") and entry.is_file(): filename = Path(entry.path) print(filename) # 解析原MIDI文件 piece = converter.parse(filename) # 提取原曲所有拍号 original_time_sigs = piece.flat.getTimeSignatures() # 分析原调式 key = piece.analyze('key') # 确定目标调式 target = pitch.Pitch('C') if key.type == 'major' else pitch.Pitch('A') if target.name != key.tonic.name: move_interval = interval.Interval(key.tonic, target) # 逐个轨道移调,适配多轨MIDI的元数据保留 newpiece = stream.Score() for part in piece.parts: transposed_part = part.transpose(move_interval) newpiece.append(transposed_part) # 重新添加拍号到新曲 if original_time_sigs: # 先清空新曲中可能存在的无效拍号 for ts in newpiece.flat.getTimeSignatures(): newpiece.remove(ts) # 插入原曲的拍号 for ts in original_time_sigs: newpiece.insert(0, ts) # 验证新调式(可选) new_key = newpiece.analyze('key') print(f"原调: {key}, 新调: {new_key}") # 保存移调后的MIDI output_filename = filename.with_suffix('') + f".in{target.name}.mid" newpiece.write('midi', fp=output_filename) if __name__ == "__main__": main()
关键改进点
- 逐个轨道移调:对每个轨道单独执行移调操作,确保每个轨道的元数据(包括轨道内的拍号标记)被完整保留。
- 显式处理拍号:提取原曲的拍号信息,移调后清空新曲中可能存在的无效拍号,再插入原曲的拍号,避免冲突或丢失。
- 调式判断优化:简化目标调式的判断逻辑,更清晰直观。
额外建议
- 确保使用最新版本的Music21,旧版本可能存在MIDI解析和处理的已知bug。
- 如果仍有异常,可以打印原曲和新曲的结构(
print(piece)、print(newpiece)),查看元数据的存储位置,针对性调整处理逻辑。
内容的提问来源于stack exchange,提问作者Maurice Calvert
相关产品推荐
相关产品推荐

