How to read MIDI files with Micropython on a microcontroller

First of all: this article has a Translate button at the bottom, select your language there! Hay un botón de traducción más abajo, selecciona español allí.

MIDI files store note by note instructions

MIDI files are a wonderful way to store music. MIDI files are very compact and small, since the MIDI file only contains short instructions such as "turn this music note on", "turn this note off". 

There are many libraries out there to read MIDI files with Python, such as the excellent MIDO (MIDO Objects) library for Python https://mido.readthedocs.io/en/latest/

However, these libraries will not fit on a microcontroller with Micropython, where you have perhaps 100 kb of RAM available.

An easy way to read MIDI files with Micropython is with the umidiparser module, which you can get here:


The library is easy to install, you just download the umidiparser.py file from the Github site, and then you store that file on your microcontroller. For my ESP32 (on a DEVKIT-C module) I use the pyboard.py utility to copy the file from Windows to the ESP32l, through the USB connecter of the ESP32:

    pyboard.py -f cp umidiparser.py :umidiparser.py

Now the module is on the flash drive of the microcontroller and can be imported in Micropython.

Reading a MIDI file using Micropython

To test if the module works, just download any MIDI file from internet, and copy it also to flash memory of the microcontroller. There are many free MIDI file editors out there too, to make a MIDI file.

If your MIDI file is called example.mid, just copy it also to flash memory:

    pyboard.py -f cp umidiparser.py :umidiparser.py

Now you can use PUTTY to connect to the ESP32 to get the repl prompt and make a test:

    >>> import umidiparser
    >>> print(umidiparser.MidiFile("example.mid").length_us())

This will compute and print the playing length of the MIDI file in microseconds. Yes, you read that right, the time a MIDI file plays is stored nowhere in the MIDI file, so the only way to get it is to read the entire file, compute the playing length of all notes and silences in the file and add them.

To see the contents of a MIDI file using Micropython


To see the content of the file, a simple Micropython script is as follows:

    >>> import umidiparser
    >>> for event in umidiparser.MidiFile("example.mid"):
    ...    print(event)
    ...

and now something similar to this should appear:

    set_tempo delta[miditicks]=0 data=b'\x07S\x00' delta[usec]=0 status=81 tempo=480000
    program_change delta[miditicks]=0 data=b' ' delta[usec]=0 channel=1 program=32 status=192 
    note_on delta[miditicks]=0 data=b'<[' delta[usec]=0 channel=2 note=60 status=144 velocity=91
    note_off delta[miditicks]=60 data=b'JR' delta[usec]=60000 channel=3 note=74 status=128 velocity=82 
    ...etc... 

Each line is a MIDI event (also called MIDI message). There are many different types of MIDI events: SET TEMPO sets the tempo or speed at which the MIDI file plays back. NOTE ON commands a note to be turned on, NOTE OFF commands a note to be turned off. The delta is the time since the last event, both in MIDI ticks, which is a magnitude used in MIDI files and in microseconds (usec). Why microseconds? Because it makes it easy to use Micropython functions like utime.sleep_us(). 


In a future post I'll show what else can be done with this module:

  •  In my case I'll use this for an automatized organ. Each NOTE ON opens a solenoid that lets flow air through a pipe for that note, and the NOTE OFF turns the air flow off. If a pin of the microcontroller is used for each note, this is rather simple, I'll use the Pin class to turn a note on or off.
  • I'd like to connect this via USB cable to a MIDI sound module. Sounds cool!
  • Or better yet: connect via Bluetooth... imagine a microcontroller like the ESP32 that has bluetooth playing MIDI files as backing tracks,
  • MIDI is used to control lights and effects, this module could be used to control lighting with a small microcontroller only!
  • I don't have Circuitpython installed yet, a RP2040 is on it's way. I'd really like to see how this code would work with a RP2040.




Comments