我想解析一个已经存在的 .mid 文件,将其乐器从“原声三角钢琴”更改为“小提琴”,然后将其保存回来或保存为另一个 .mid 文件。
从我在文档中看到的内容来看,该乐器使用program_change
orpatch_change
指令进行了更改,但我在已经存在的 MIDI 文件中找不到任何可以执行此操作的库。他们似乎都只支持从头开始创建的 MIDI 文件。
该MIDI
软件包将为您执行此操作,但确切的方法取决于 MIDI 文件的原始内容。
一个 midi 文件由一个或多个轨道组成,每个轨道是 16 个通道中任何一个通道上的一系列事件,例如Note Off、Note On、Program Change等。最后一个将更改分配给通道的乐器,并且这就是您需要更改或添加的内容。
根本没有任何 Program Change 事件,通道将使用程序编号(音色编号)零,这是一台原声三角钢琴。如果你想改变这样一个通道的乐器,那么你需要做的就是在轨道的开头为这个通道添加一个新的 Program Change 事件。
但是,如果一个频道已经有一个 Program Change 事件,那么在开头添加一个新事件将无效,因为它会立即被先前存在的事件覆盖。在这种情况下,您必须更改现有事件的参数才能使用您想要的工具。
如果一个通道最初有多个 Program Change 事件,则事情可能会更加复杂,这意味着乐器会在整个轨道中发生变化。这是不寻常的,但如果您遇到这样的文件,您将不得不决定如何更改它。
假设您有一个非常简单的 MIDI 文件,它只有一个轨道、一个通道,并且没有现有的 Program Change 事件。该程序从文件中创建一个新MIDI::Opus
对象,访问轨道列表(只有一个成员),并获取对第一个轨道事件列表的引用。patch_change
然后,通道 0的新程序更改事件(此模块称为它)未移动到事件列表的开头。新活动的节目编号为 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');
使用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')