如何读取 MIDI 文件、更改其乐器并将其写回?

Posted

技术标签:

【中文标题】如何读取 MIDI 文件、更改其乐器并将其写回?【英文标题】:How do I read a midi file, change its instrument, and write it back? 【发布时间】:2013-01-21 22:29:25 【问题描述】:

我想解析一个已经存在的 .mid 文件,将其乐器从“原声三角钢琴”更改为“小提琴”,然后将其保存回来或另存为另一个 .mid 文件。

根据我在文档中看到的内容,该乐器使用program_changepatch_change 指令进行了更改,但我在已经存在的 MIDI 文件中找不到执行此操作的任何库。他们似乎都只支持从头开始创建的 MIDI 文件。

【问题讨论】:

到目前为止你尝试过什么?你只需要header format吗? 我已经在 Perl 和 Python 中搜索了任何能够满足我要求的库,但我没有找到。似乎比我最初想象的要难。至于标题,我希望不必手动编辑二进制文件。 “根据我在文档中看到的...”文档是什么? MIDI 的规范和进行 MIDI 操作/编辑的库的文档。 你看过midilib吗?它似乎提供了您正在寻找的东西。 【参考方案1】:

MIDI 包会为您执行此操作,但具体方法取决于 MIDI 文件的原始内容。

一个 midi 文件由一个或多个轨道组成,每个轨道是 16 个通道中任意一个上的一系列 事件,例如 Note OffNote On, Program Change 等。最后一个将更改分配给通道的乐器,这就是您需要更改或添加的内容。

完全没有任何 Program Change 事件,通道将使用程序编号(声音编号)零,这是一台原声三角钢琴。如果您想为这样一个通道更改乐器,那么您只需在轨道的开头为该通道添加一个新的 Program Change 事件即可。

但是,如果频道已经有节目更改事件,那么在开头添加新事件将无效,因为它会立即被先前存在的事件覆盖。在这种情况下,您必须更改现有事件的参数才能使用所需的工具。

如果一个通道最初有多个 Program Change 事件,则事情可能会更加复杂,这意味着乐器会在整个轨道中发生变化。这是不寻常的,但如果您遇到这样的文件,您将不得不决定如何更改它。

假设您有一个非常简单的 MIDI 文件,它只有一个轨道、一个通道,并且没有现有的 Program Change 事件。该程序从文件中创建一个新的MIDI::Opus 对象,访问轨道列表(只有一个成员),并引用第一个轨道的事件列表。然后,频道 0 的新节目更改事件(此模块称为 patch_change未移位到事件列表的开头。新活动的节目编号为 40 - 小提琴 - 因此该频道现在将使用小提琴而不是钢琴演奏。

如果有多个轨道、多个频道和现有的 Program Change 事件,任务会变得更加复杂,但原理是一样的 - 决定需要做什么并根据需要更改事件列表。

use strict;
use warnings;

use MIDI;

my $opus = MIDI::Opus->new(  from_file => 'song.mid'  );

my $tracks = $opus->tracks_r;
my $track0_events = $tracks->[0]->events_r;

unshift @$track0_events, ['patch_change', 0, 0, 40];
$opus->write_to_file('newsong.mid');

【讨论】:

Borodin 谢谢你的回答,我很感激。 midi 文件很简单(我认为有 2 首曲目)。不幸的是,由于某种原因,该脚本无法正常工作。我得到与输出(钢琴)相同的midi文件。这里有两个 .mid 文件来测试它。 dl.dropbox.com/u/109180167/sound.middl.dropbox.com/u/109180167/sound2.mid 您需要多少帮助?你能调试 Perl 吗?问题很可能是,正如我所说,曲目中已经有程序更改,因此在开头添加一个没有任何区别。 sound.mid 有三个轨道。第一个是速度轨道,不包含音符,因此在开头更改程序将无效。第二和第三轨道都有一个程序变化(到声音零 - 钢琴)作为它们的第一个事件,因此您需要更改或删除这些事件,或者在 thwm 之后放置您自己的程序变化。 sound2.mid 只有一首曲目,没有节目更改事件,所以它使用默认的零声部播放。但是由于某种原因它使用了频道 15,因此您需要在频道 15 上添加节目更改事件。重新分配频道 0 没有效果,因为它从不播放。【参考方案2】:

使用music21 library(插入我自己的系统,希望没问题)。如果部件中定义了补丁,请执行以下操作:

from music21 import converter,instrument # or import *
s = converter.parse('/Users/cuthbert/Desktop/oldfilename.mid')

for el in s.recurse():
    if 'Instrument' in el.classes: # or 'Piano'
        el.activeSite.replace(el, instrument.Violin())

s.write('midi', '/Users/cuthbert/Desktop/newfilename.mid')

或者如果当前没有定义补丁更改:

from music21 import converter,instrument # or import *
s = converter.parse('/Users/cuthbert/Desktop/oldfilename.mid')

for p in s.parts:
    p.insert(0, instrument.Violin())

s.write('midi', '/Users/cuthbert/Desktop/newfilename.mid')

【讨论】:

以上是关于如何读取 MIDI 文件、更改其乐器并将其写回?的主要内容,如果未能解决你的问题,请参考以下文章

如何预填充 Django 更新表单并将其写回数据库

在音轨播放期间更改乐器 - MIDI/Java

如何在 Java MIDI 程序中更改乐器

Java MIDI - ControllerEventListener(如何更改乐器)

C ++创建不同扩展名的文件

如何访问和更改 Pygame.midi?