DEV Community

Cover image for Editing MIDI files with Python
Varlen Pavani Neto
Varlen Pavani Neto

Posted on

Editing MIDI files with Python

Couple months ago, I was in the house of my band's guitar player doing pre-production of a new song. Our drummer had sent us a MIDI sequence made with MT PowerDrum Kit 2 VST plugin. I wanted to check how the grooves would sound with a sample pack I had.

I tried to load the samples using Kontakt and...

The sample pack had some sounds mapped differently from the original plugin. What now?

I could edit the drum line note by note in my daw, however this is a tedious and error prone process. Besides, if my drummer send me another version, I would need to change everything again. Manually.

We can create drum lines using one plugin and use it in another. Here I show you how to avoid the repetition and automate the translation between different drum plugins using Python.

MIDI and MIDI files

MIDI is communication protocol created way before my existence in this planet. In a nutshell, it empowers musicians by allowing multiple digital musical instruments to work together.

Usually, MIDI devices used a 5 pin DIN cable. Nowadays, the protocol still exists and is commonly found working over USB, connecting instruments to a computer.

MIDI uses messages to communicate events like the start or end of a note, its intensity and pitch, a change in tempo, etc.

These messages can be stored in a MIDI file. These files were famous on the early days of multimedia internet as background music for personal websites and as polyphonic ringtones.

These files can represent digitally the performance of a song.

How to handle MIDI files?

We could search the MIDI protocol specifications and implement the protocol ourselves, however since the idea here is to avoid a lot of manual work, a quick Google search for a library may be enough.

By googling "python midi library", I found mido. From skimming the docs, we can see that it should provide everything we need for this task. It is available here.

Pip provides it:

pip install mido
Enter fullscreen mode Exit fullscreen mode

Thinking about the script

Having our tools chosen, we now need to plan what the code must do.

The python script must read note sequences in the source MIDI file and map each note from the original plugin into their equivalents of another plugin.

For that, it is necessary to understand how each VST plugin maps their drum pieces into MIDI notes. For MT PowerDrum Kit 2, there are some files in their website documenting which note maps each sound.

The sample pack had these informations in its PDF manual.

It would be really convenient to do this mapping in a visual way, but I didn't find any tools for doing it, and creating a custom tool would go totally against the purpose of this whole thing. (It could be a nice side project, thou)

Since, there's no another way to do it, I did it by hand, trying the best match possible between both plugins.

Each row contains source, destination and description columns.

If you find yourself doing the exact same mapping, don't waste your time anymore, here it is:

65,44,Hihat Pedal
63,68,Hihat Edge Tight
62,69,Hihat Edge Closed
61,71,Hihat Edge Semi-open
60,72,Hihat Edge Open
58,54,Crash 1 Choke
57,55,Crash 1
55,55,Splash -> Crash
53,53,Ride Bell
50,50,Tom 1 - Hi
49,52,Crash 2
48,50,Tom 1 - Hi
47,48,Tom 2 - Mid
46,72,Hihat Edge Open
45,48,Tom 2 - Mid
44,71,Hihat Edge Semi-open
43,47,Tom 3 - Low
42,69,Hihat Edge Closed
41,47,Tom 3 - Low
Enter fullscreen mode Exit fullscreen mode

Here's the final script, commented line by line:

from mido import MidiFile, MidiFile, MidiTrack

# Opening the original MIDI sequence
input_midi = MidiFile('./Murundu.mid')

# Creating the destination file object
output_midi = MidiFile()

# Copying the time metrics between both files
output_midi.ticks_per_beat = input_midi.ticks_per_beat

note_map = {}
# Load the mapping file
with open('note_map.csv') as map_text:
    for line in map_text:
        elements = line.replace('\n','').split(',')
        # Each line in the mapping file will be loaded into a
        # dictionary with the original MIDI note as key and
        # another dictionary with target note 
        # and description as value
        note_map[int(elements[0])] = { 
            'target_note': int(elements[1]), 
            'description': elements[2] }

# Now, we iterate the source file and insert mapped notes
# into the destination file

# Notes are determined by note_on e note_off MIDI messages
# Other types of messages will be copied directly 
# Notes that does not exist in the mapping will not be copied

for original_track in input_midi.tracks:

    new_track = MidiTrack()

    for msg in original_track:
        if msg.type in ['note_on','note_off']:
            # mido's API allows to copy a MIDI message
            # changing only some of its parameters
            # Here, we use the mapping dictionary to create
            # the mapped note, keeping its properties like
            # intensity

            origin_note = msg.note

            if origin_note in note_map:
                    msg.copy( note = note_map[origin_note]['target_note'] ))
                print("Origin note",origin_note,"not mapped")

    # MIDI files are multitrack. Here we append
    # the new track with mapped notes to the output file

# Finally, save the mapped file to disk'./Murundu-remap.mid')
Enter fullscreen mode Exit fullscreen mode

Thanks for reading!

Discussion (2)

ju1973 profile image

hi I find your work very usefull, I don't know where I have to put the csv map ? would you help me ?

varlen profile image
Varlen Pavani Neto Author • Edited on

Thank you! The mapping csv should go in the same folder as the python script. Save it as note_map.csv and it should work properly. If this still doesn't work, it is possible to embed the csv using triple-double quotes. To try this approach, replace the line that opens the external csv file with the code below and replace the tag with the csv content.

map_text = """<paste the csv here>
Enter fullscreen mode Exit fullscreen mode

Then, fix the identation, since by doing this, you removed the with block.