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

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()

关键改进点

  1. 逐个轨道移调:对每个轨道单独执行移调操作,确保每个轨道的元数据(包括轨道内的拍号标记)被完整保留。
  2. 显式处理拍号:提取原曲的拍号信息,移调后清空新曲中可能存在的无效拍号,再插入原曲的拍号,避免冲突或丢失。
  3. 调式判断优化:简化目标调式的判断逻辑,更清晰直观。

额外建议

  • 确保使用最新版本的Music21,旧版本可能存在MIDI解析和处理的已知bug。
  • 如果仍有异常,可以打印原曲和新曲的结构(print(piece)、print(newpiece)),查看元数据的存储位置,针对性调整处理逻辑。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.05 10:10:48