6

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.

the Tin Man
  • 158,662
  • 42
  • 215
  • 303
John Kornick
  • 301
  • 1
  • 6
  • 14

2 Answers2

5

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');
Borodin
  • 126,100
  • 9
  • 70
  • 144
  • Borodin thank you for the answer, I appreciate it. The midi file is simple (with 2 tracks I think). Unfortunately, for some reason, the script isn't working. I get the same midi file as output (piano). Here are two .mid files to test it out. https://dl.dropbox.com/u/109180167/sound.mid https://dl.dropbox.com/u/109180167/sound2.mid – John Kornick Jan 28 '13 at 18:54
  • How much help do you need? Can you debug Perl? The problem is likely to be, as I said that there is already a program change in the tracks so adding one at the beginning makes no difference. – Borodin Jan 29 '13 at 03:45
  • 1
    `sound.mid` has three tracks. The first is a tempo track and contains no notes, so putting a program change at the beginning will have no effect. The second and third tracks both have a program change (to voice zero - piano) as their first events, so you need to change or delete these events or put your own program change after thwm. `sound2.mid` has just one track and no program change event, so it plays using the default voice zero. However it uses channel 15 for some reason, so you need to add a program change event on channel 15. Repatching channel 0 has no effect because it never plays. – Borodin Jan 29 '13 at 12:30
4

Use the music21 library (plugging my own system, hope that's okay). If there are patches defined in the parts, do:

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

or if there are no patch changes currently defined:

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')
ritiek
  • 2,477
  • 2
  • 18
  • 25