How do I read a midi file, change its instrument, and write it back?

John Kornick picture John Kornick · Jan 21, 2013 · Viewed 7k times · Source

I want to parse an already existing .mid file, change its instrument, from 'acoustic grand piano' to 'violin' for example, and save it back or as another .mid file.

From what I saw in the documentation, the instrument gets altered with a program_change or patch_change directive but I cannot find any library that does this in MIDI files that exist already. They all seem to support it only MIDI files created from scratch.

Answer

Borodin picture Borodin · Jan 28, 2013

The MIDI package will do this for you, but the exact approach depends on the original contents of the midi file.

A midi file consists of one or more tracks, and each track is a sequence of events on any of sixteen channels, such as Note Off, Note On, Program Change etc. The last of these will change the instrument assigned to a channel, and that is what you need to change or add.

Without any Program Change events at all, a channel will use program number (voice number) zero, which is an acoustic grand piano. If you want to change the instrument for such a channel then all you need to do is add a new Program Change event for this channel at the beginning of the track.

However if a channel already has a Program Change event then adding a new one at the beginning will have no effect because it is immediately overridden by the pre-existing one. In this case you will have to change the parameters of the existing event to use the instrument that you want.

Things could be even more complicated if there are originally several Program Change events for a channel, meaning that the instrument changes throughout the track. This is unusual, but if you come across a file like this you will have to decide how you want to change it.

Supposing you have a very simple midi file with a single track, one channel, and no existing Program Change events. This program creates a new MIDI::Opus object from the file, accesses the list of tracks (with only a single member), and takes a reference to the list of the first track's events. Then a new Program Change event (this module calls it patch_change) for channel 0 is unshifted onto the beginning of the event list. The new event has a program number of 40 - violin - so this channel will now be played with a violin instead of a piano.

With multiple tracks, multiple channels, and existing Program Change events the task becomes more complex, but the principle is the same - decide what needs to be done and alter the list of events as necessary.

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