如何读取MIDI文件、修改乐器并保存?

6 投票
2 回答
9933 浏览
提问于 2025-04-17 13:19

我想处理一个已经存在的 .mid 文件,把里面的乐器从“钢琴”改成“小提琴”,然后保存回去,或者另存为一个新的 .mid 文件。

根据我看到的文档,乐器的改变是通过 program_changepatch_change 指令来实现的,但我找不到任何可以对已经存在的 MIDI 文件进行这种操作的库。它们似乎只支持从头创建的 MIDI 文件。

2 个回答

5

MIDI这个包可以帮你完成这个任务,不过具体的方法要看你原来的midi文件内容。

一个midi文件由一个或多个音轨组成,每个音轨上有一系列的事件,这些事件可以在16个频道上发生,比如音符关闭音符开启程序变化等等。最后一种事件会改变分配给某个频道的乐器,这就是你需要更改或添加的内容。

如果一个频道根本没有程序变化事件,它会默认使用程序编号(音色编号)为零的乐器,这通常是钢琴。如果你想改变这个频道的乐器,只需要在音轨的开头添加一个新的程序变化事件就可以了。

但是,如果一个频道已经有了程序变化事件,那么在开头再添加一个就没有效果,因为新的事件会被已有的事件覆盖。在这种情况下,你需要修改现有事件的参数,以使用你想要的乐器。

如果一个频道原本有多个程序变化事件,意味着乐器在音轨中会不断变化,那事情就会更复杂。这种情况比较少见,但如果你遇到这样的文件,就需要决定你想怎么改变它。

假设你有一个非常简单的midi文件,只有一个音轨、一个频道,并且没有现有的程序变化事件。这个程序会从文件中创建一个新的MIDI::Opus对象,访问音轨列表(只有一个音轨),并获取第一个音轨事件的列表引用。然后,一个新的程序变化事件(这个模块称之为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');
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')

撰写回答