MIDI Sample Code (Python)

MIDI is an established protocol for transmitting, recording, and playing back musical event data. It is low-bandwidth as it transmits just ‘keyboard data’ such as note on, note off, and after-pressure, leaving interpretation to the receiver. It includes a data protocol for real-time events, a file format for storage, and a (mostly obsolete) physical interface format. Most modern devices use a USB connection instead of the original five-pin optoisolated serial connection.

We can use MIDI as the basis for incorporating physical controller interfaces into performance and authoring systems. The following examples demonstrate basic use of the python-rtmidi module to send and receive events.

midi_send.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
#!/usr/bin/env python3
"""Example script to transmit a sequence of MIDI messages over time.  Runs from
the command line, no GUI.  Uses the rtmidi package for output.
"""
################################################################
# Written in 2018-2021 by Garth Zeglin <garthz@cmu.edu>

# To the extent possible under law, the author has dedicated all copyright
# and related and neighboring rights to this software to the public domain
# worldwide. This software is distributed without any warranty.

# You should have received a copy of the CC0 Public Domain Dedication along with this software.
# If not, see <http://creativecommons.org/publicdomain/zero/1.0/>.

################################################################
# Standard Python libraries.
import argparse, time, platform

# For documentation on python-rtmidi: https://pypi.org/project/python-rtmidi/
import rtmidi

################################################################
if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="""Demonstration of sending a timed sequence of MIDI messages.""")
    parser.add_argument( '-v', '--verbose', action='store_true', help='Enable more detailed output.' )
    parser.add_argument("--midi", type=str, default = "virtual MPD218", help = "Keyword identifying the MIDI output device (default: %(default)s).")
    args = parser.parse_args()

    # Initialize the MIDI output system and read the currently available ports.
    midi_out = rtmidi.MidiOut()
    for idx, name in enumerate(midi_out.get_ports()):
        if args.midi in name:
            print("Found preferred MIDI output device %d: %s" % (idx, name))
            midi_out.open_port(idx)
            break
        else:
            print("Ignoring unselected MIDI device: ", name)

    if not midi_out.is_port_open():
        if platform.system() == 'Windows':
            print("Virtual MIDI outputs are not currently supported on Windows, see python-rtmidi documentation.")
        else:
            print("Creating virtual MIDI output.")
            midi_out.open_virtual_port(args.midi)
    
    if not midi_out.is_port_open():
        print("No MIDI device opened, exiting.")
        exit(1)
        
    if args.verbose:
        print(f"Starting message sequence.")        

    chan = 9                          # MIDI channel 10 similar to the MPD218
    note_on  = [0x90 + chan, 36, 112] # note 36 is MPD218 pad 1 on bank A, with velocity 112
    note_off = [0x80 + chan, 36, 0]

    for i in range(10):
        midi_out.send_message(note_on)
        time.sleep(1.0)
        midi_out.send_message(note_off)
        time.sleep(0.25)
        
    if args.verbose:
        print(f"Sequence done.")

midi_receive.py

The midi_received callback function is called in real time as messages are received.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
#!/usr/bin/env python3
"""A demonstration MIDI receiver which displays events."""
################################################################
# Written in 2018-2021 by Garth Zeglin <garthz@cmu.edu>

# To the extent possible under law, the author has dedicated all copyright
# and related and neighboring rights to this software to the public domain
# worldwide. This software is distributed without any warranty.

# You should have received a copy of the CC0 Public Domain Dedication along with this software.
# If not, see <http://creativecommons.org/publicdomain/zero/1.0/>.

################################################################
# Standard Python libraries.
import argparse, time, platform

# For documentation on python-rtmidi: https://pypi.org/project/python-rtmidi/
import rtmidi

################################################################
def midi_received(data, unused):
    msg, delta_time = data
    if len(msg) > 2:
        if msg[0] == 153: # note on, channel 9
            key = (msg[1] - 36) % 16
            row = key // 4
            col = key % 4
            velocity = msg[2]
            print("MPD218 Pad (%d, %d): %d" % (row, col, velocity))
            return
    print("MIDI message: ", msg)
    
################################################################
if __name__ == "__main__":
    parser = argparse.ArgumentParser()
    parser.add_argument("--midi", type=str, default = "MPD218 Port A", help = "Keyword identifying the MIDI input device (default: %(default)s).")
    args = parser.parse_args()

    # Initialize the MIDI input system and read the currently available ports.
    midi_in = rtmidi.MidiIn()
    for idx, name in enumerate(midi_in.get_ports()):
        if args.midi in name:
            print("Found preferred MIDI input device %d: %s" % (idx, name))
            midi_in.open_port(idx)
            midi_in.set_callback(midi_received)
            break
        else:
            print("Ignoring unselected MIDI device: ", name)

    if not midi_in.is_port_open():
        if platform.system() == 'Windows':
            print("Virtual MIDI inputs are not currently supported on Windows, see python-rtmidi documentation.")
        else:
            print("Creating virtual MIDI input.")
            midi_in.open_virtual_port(args.midi)
    
    if not midi_in.is_port_open():
        print("No MIDI device opened, exiting.")

    else:
        print("Waiting for input.")
        while True:
            time.sleep(1)