Editing MIDI files with Python
Tags: python, midi, music, musicproduction
Published: 2020-07-11 01:10:25

---
title: Editing MIDI files with Python
published: true
description: Here's how to manipulate MIDI files with Python and automate music production tasks.
tags: Python, MIDI, Music, MusicProduction
cover_image: https://upload.wikimedia.org/wikipedia/commons/3/36/Roland_V-Drum_TD-12S_V-Stage_set_%2B_expansion.jpg
---

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 <a href="https://mido.readthedocs.io/en/latest/"">here.</a>

Pip provides it:
```
pip install mido
```

---

# 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 <a href="https://www.powerdrumkit.com/presets.php">their website</a> 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:


```txt
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
52,35,China
51,51,Ride
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
40,38,Snare
39,38,Snare
38,38,Snare
37,37,Sidestick
36,36,Kick
35,36,Kick
```

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

```py
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:
                new_track.append( 
                    msg.copy( note = note_map[origin_note]['target_note'] ))
                print(note_map[origin_note]['description'])
            else:
                print("Origin note",origin_note,"not mapped")
        else:
            print(msg.type)
            new_track.append(msg)

    # MIDI files are multitrack. Here we append
    # the new track with mapped notes to the output file
    output_midi.tracks.append(new_track)
    
# Finally, save the mapped file to disk
output_midi.save('./Murundu-remap.mid')
```

Thanks for reading!