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

如何用Python Mido按小节分割MIDI文件并随机重组

MIDI分割与随机重组解决方案

问题背景

我有一个包含8小节、4/4拍、100BPM的MIDI文件,需要完成两个任务:

  • 将原文件分割为8个等长的单小节MIDI文件
  • 使用Python的Mido库将这些小节随机组合成新的MIDI文件

尝试了ChatGPT提供的代码但无法正常运行,原代码如下:

import mido
import os

# Load the MIDI file
mid = mido.MidiFile('input_file.mid')

# Set the time signature to 4/4
time_signature = mido.MetaMessage('time_signature', numerator=4, denominator=4, clocks_per_click=24, notated_32nd_notes_per_beat=8)
mid.tracks[0].insert(0, time_signature)

# Set the tempo to 100 BPM
tempo = mido.bpm2tempo(100)
set_tempo = mido.MetaMessage('set_tempo', tempo=tempo)
mid.tracks[0].insert(0, set_tempo)

# Calculate the number of ticks per beat and per bar
ticks_per_beat = mid.ticks_per_beat
ticks_per_bar = ticks_per_beat * 4

# Keep track of the current tick position
current_tick = 0
# Keep track of the messages for the current bar
current_bar_messages = []
# Iterate over each message in the MIDI file
for message in mid:
    # Append the message to the current bar
    current_bar_messages.append(message)
    # Increment the current tick position by the message time
    current_tick += message.time
    # If the current tick position is greater than or equal to the number of ticks per bar
    if current_tick >= ticks_per_bar:
        # Create a new MIDI file
        output_file_name = f"bar_{len(os.listdir())}.mid"
        output_mid = mido.MidiFile(type=mid.type)
        # Add a new track to the output MIDI file
        output_mid.add_track()
        # Append the time signature and tempo messages to the new track
        output_mid.tracks[0].append(time_signature)
        output_mid.tracks[0].append(set_tempo)
        # Append all messages up to the current tick position to the new track
        for msg in current_bar_messages:
            output_mid.tracks[0].append(msg)
        # Reset the current tick position and current bar messages
        current_tick = 0
        current_bar_messages = []
        # Save the new MIDI file
        output_mid.save(output_file_name)

原代码问题分析

  • 错误直接遍历mid对象,正确逻辑应遍历每个轨道内的消息
  • 使用os.listdir()生成文件名会受目录内其他文件干扰,导致编号混乱
  • 未处理消息时间超出小节长度的情况,小节边界划分不准确
  • 强制插入时间签名和Tempo消息可能覆盖原文件设置,引发冲突
  • 缺失多轨MIDI的处理逻辑,仅支持单轨道文件
  • 未处理最后剩余的消息片段

修正后的分割代码

import mido
import os

# 配置参数
INPUT_MIDI = 'input_file.mid'
OUTPUT_DIR = 'split_bars'
BARS_COUNT = 8
TIME_SIGNATURE = (4, 4)
TARGET_BPM = 100

# 创建输出目录
os.makedirs(OUTPUT_DIR, exist_ok=True)

# 加载原MIDI文件
mid = mido.MidiFile(INPUT_MIDI)
ticks_per_beat = mid.ticks_per_beat
ticks_per_bar = ticks_per_beat * TIME_SIGNATURE[0]

# 获取原文件的元消息(优先用原文件配置,无则用目标值)
def get_meta_messages(track):
    tempo_msg = None
    ts_msg = None
    for msg in track:
        if msg.type == 'set_tempo':
            tempo_msg = msg
        elif msg.type == 'time_signature':
            ts_msg = msg
        if tempo_msg and ts_msg:
            break
    if not tempo_msg:
        tempo_msg = mido.MetaMessage('set_tempo', tempo=mido.bpm2tempo(TARGET_BPM))
    if not ts_msg:
        ts_msg = mido.MetaMessage('time_signature', numerator=TIME_SIGNATURE[0], 
                                 denominator=TIME_SIGNATURE[1], clocks_per_click=24, 
                                 notated_32nd_notes_per_beat=8)
    return tempo_msg, ts_msg

# 按小节分割每个轨道的消息
tracks_bars = []
for track in mid.tracks:
    tempo_msg, ts_msg = get_meta_messages(track)
    bar_tracks = [[] for _ in range(BARS_COUNT)]
    current_tick = 0
    current_bar_idx = 0
    
    for msg in track:
        if current_bar_idx >= BARS_COUNT:
            break
        
        # 处理跨小节的消息
        if current_tick + msg.time > ticks_per_bar:
            time_in_current = ticks_per_bar - current_tick
            time_in_next = msg.time - time_in_current
            
            # 当前小节的消息片段
            current_msg = msg.copy(time=time_in_current)
            bar_tracks[current_bar_idx].append(current_msg)
            
            # 剩余部分放入下一小节
            next_msg = msg.copy(time=time_in_next)
            current_bar_idx += 1
            bar_tracks[current_bar_idx].append(next_msg)
            
            current_tick = time_in_next
        else:
            bar_tracks[current_bar_idx].append(msg)
            current_tick += msg.time
            
        # 切换到下一小节
        if current_tick >= ticks_per_bar:
            current_tick -= ticks_per_bar
            current_bar_idx += 1
    
    tracks_bars.append(bar_tracks)

# 保存每个小节的MIDI文件
for bar_idx in range(BARS_COUNT):
    bar_mid = mido.MidiFile(type=mid.type, ticks_per_beat=ticks_per_beat)
    # 保留原轨道结构
    for track_idx in range(len(mid.tracks)):
        track = bar_mid.add_track()
        # 添加元消息
        tempo_msg, ts_msg = get_meta_messages(mid.tracks[track_idx])
        track.append(ts_msg)
        track.append(tempo_msg)
        # 添加当前小节的消息
        for msg in tracks_bars[track_idx][bar_idx]:
            track.append(msg)
    # 生成有序文件名
    output_path = os.path.join(OUTPUT_DIR, f'bar_{bar_idx+1}.mid')
    bar_mid.save(output_path)

随机组合小节的代码

import mido
import os
import random

# 配置参数
SPLIT_DIR = 'split_bars'
OUTPUT_MIDI = 'random_combined.mid'
TARGET_BARS = 8  # 组合为8小节

# 获取所有分割后的小节文件
bar_files = [f for f in os.listdir(SPLIT_DIR) if f.endswith('.mid')]
if not bar_files:
    raise ValueError('未找到分割后的小节文件')

# 随机选择小节
selected_bars = random.sample(bar_files, TARGET_BARS)

# 以第一个小节为模板创建组合文件
first_bar = mido.MidiFile(os.path.join(SPLIT_DIR, selected_bars[0]))
combined_mid = mido.MidiFile(type=first_bar.type, ticks_per_beat=first_bar.ticks_per_beat)

# 为每个轨道构建组合内容
for track_idx in range(len(first_bar.tracks)):
    combined_track = combined_mid.add_track()
    # 仅在第一个轨道添加元消息(避免重复)
    if track_idx == 0:
        for msg in first_bar.tracks[track_idx]:
            if msg.is_meta:
                combined_track.append(msg)
    # 拼接选中小节的轨道消息(跳过元消息)
    for bar_file in selected_bars:
        bar_mid = mido.MidiFile(os.path.join(SPLIT_DIR, bar_file))
        for msg in bar_mid.tracks[track_idx]:
            if not msg.is_meta:
                combined_track.append(msg)

# 保存组合后的MIDI文件
combined_mid.save(OUTPUT_MIDI)

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.30 20:09:26