如何读取MIDI文件,更改其乐器并重新写入?

6
我想解析一个已经存在的 .mid 文件,将它的乐器从 'acoustic grand piano' 改为 'violin',然后保存回去或者另存为另一个 .mid 文件。
根据我在文档中看到的,乐器可以通过 program_change 或者 patch_change 指令进行修改,但是我找不到任何能够在已经存在的 MIDI 文件中实现这一点的库。它们似乎只支持从头开始创建 MIDI 文件。

你目前尝试了什么?你只需要这个标题格式吗? - Joel Cornett
我已经搜索了Perl和Python中的任何库,以便完成我所要求的功能,但是没有找到。看来这比我最初想象的更困难。至于标题,我希望不必手动编辑二进制文件。 - John Kornick
1
从我在文档中看到的内容来看,文档是关于什么的? - the Tin Man
我不知道如何为已存在的 MIDI 文件做到这一点。另外,我更喜欢 Python 或 Perl 的解决方案,因为我不熟悉 Ruby。 - John Kornick
已经检查过了,完全不知道它是否可能实现。 - John Kornick
显示剩余4条评论
2个回答

5
MIDI包可以帮您实现此功能,但确切的处理方法取决于midi文件的原始内容。
一个midi文件由一个或多个轨道组成,每个轨道都是十六个通道中的任何一个上的事件序列,例如音符关闭音符开启程序更改等。这些事件中的最后一个将更改分配给通道的乐器,这就是您需要更改或添加的乐器。
如果一个通道根本没有任何程序更改事件,它将使用程序号(声音号)为零的钢琴声。如果您想更改此类频道的乐器,则只需在轨道开头为该频道添加一个新的程序更改事件即可。
然而,如果通道已经有程序更改事件,则在开头添加一个新的事件将不起作用,因为它会立即被现有的事件覆盖。在这种情况下,您必须更改现有事件的参数以使用所需的乐器。
如果原来某个通道有多个程序更改事件,那么意味着乐器在整个轨道中换了几次。虽然这很少见,但如果你遇到这样的文件,你就必须决定如何改变它。
假设您有一个非常简单的midi文件,只有一个轨道,一个通道和没有现有的程序更改事件。该程序从文件中创建一个新的MIDI::Opus对象,访问轨道列表(只有一个成员),并获取对第一个轨道事件列表的引用。然后,在事件列表的开头插入了一个新的频道0的程序更改事件(此模块称其为patch_change)。新事件的程序号为40 - 小提琴 - 因此,该频道现在将使用小提琴而不是钢琴演奏。
对于多个轨道、多个频道和现有的程序更改事件,任务变得更加复杂,但原则是相同的——决定需要做什么,并根据需要修改事件列表。
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文件可供测试。https://dl.dropbox.com/u/109180167/sound.mid https://dl.dropbox.com/u/109180167/sound2.mid - John Kornick
你需要多少帮助?你能调试 Perl 吗?问题很可能是,就像我说的那样,在轨道上已经有一个程序更改了,所以在开头添加一个程序没有任何区别。 - Borodin
1
sound.mid 有三个音轨。第一个是速度轨道,不包含任何音符,因此在开头放置程序更改将没有任何效果。第二和第三个音轨都有一个程序更改(到声音零 - 钢琴)作为它们的第一个事件,因此您需要更改或删除这些事件或在它们之后放置自己的程序更改。sound2.mid 只有一个音轨,没有程序更改事件,因此它使用默认的声音零进行播放。但是由于某种原因它使用通道15,因此您需要在通道15上添加一个程序更改事件。重新连接通道0没有效果,因为它从未播放过。 - Borodin

4

使用 music21 库(介绍我自己的系统,希望没关系)。如果各个部分中定义了补丁,则需要执行以下操作:

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接