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#!/usr/bin/env python3
 2"""Example script to transmit a sequence of MIDI messages over time.  Runs from
 3the command line, no GUI.  Uses the rtmidi package for output.
 4"""
 5################################################################
 6# Written in 2018-2021 by Garth Zeglin <garthz@cmu.edu>
 7
 8# To the extent possible under law, the author has dedicated all copyright
 9# and related and neighboring rights to this software to the public domain
10# worldwide. This software is distributed without any warranty.
11
12# You should have received a copy of the CC0 Public Domain Dedication along with this software.
13# If not, see <http://creativecommons.org/publicdomain/zero/1.0/>.
14
15################################################################
16# Standard Python libraries.
17import argparse, time, platform
18
19# For documentation on python-rtmidi: https://pypi.org/project/python-rtmidi/
20import rtmidi
21
22################################################################
23if __name__ == "__main__":
24    parser = argparse.ArgumentParser(description="""Demonstration of sending a timed sequence of MIDI messages.""")
25    parser.add_argument( '-v', '--verbose', action='store_true', help='Enable more detailed output.' )
26    parser.add_argument("--midi", type=str, default = "virtual MPD218", help = "Keyword identifying the MIDI output device (default: %(default)s).")
27    args = parser.parse_args()
28
29    # Initialize the MIDI output system and read the currently available ports.
30    midi_out = rtmidi.MidiOut()
31    for idx, name in enumerate(midi_out.get_ports()):
32        if args.midi in name:
33            print("Found preferred MIDI output device %d: %s" % (idx, name))
34            midi_out.open_port(idx)
35            break
36        else:
37            print("Ignoring unselected MIDI device: ", name)
38
39    if not midi_out.is_port_open():
40        if platform.system() == 'Windows':
41            print("Virtual MIDI outputs are not currently supported on Windows, see python-rtmidi documentation.")
42        else:
43            print("Creating virtual MIDI output.")
44            midi_out.open_virtual_port(args.midi)
45    
46    if not midi_out.is_port_open():
47        print("No MIDI device opened, exiting.")
48        exit(1)
49        
50    if args.verbose:
51        print(f"Starting message sequence.")        
52
53    chan = 9                          # MIDI channel 10 similar to the MPD218
54    note_on  = [0x90 + chan, 36, 112] # note 36 is MPD218 pad 1 on bank A, with velocity 112
55    note_off = [0x80 + chan, 36, 0]
56
57    for i in range(10):
58        midi_out.send_message(note_on)
59        time.sleep(1.0)
60        midi_out.send_message(note_off)
61        time.sleep(0.25)
62        
63    if args.verbose:
64        print(f"Sequence done.")

midi_receive.py

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

 1#!/usr/bin/env python3
 2"""A demonstration MIDI receiver which displays events."""
 3################################################################
 4# Written in 2018-2021 by Garth Zeglin <garthz@cmu.edu>
 5
 6# To the extent possible under law, the author has dedicated all copyright
 7# and related and neighboring rights to this software to the public domain
 8# worldwide. This software is distributed without any warranty.
 9
10# You should have received a copy of the CC0 Public Domain Dedication along with this software.
11# If not, see <http://creativecommons.org/publicdomain/zero/1.0/>.
12
13################################################################
14# Standard Python libraries.
15import argparse, time, platform
16
17# For documentation on python-rtmidi: https://pypi.org/project/python-rtmidi/
18import rtmidi
19
20################################################################
21def midi_received(data, unused):
22    msg, delta_time = data
23    if len(msg) > 2:
24        if msg[0] == 153: # note on, channel 9
25            key = (msg[1] - 36) % 16
26            row = key // 4
27            col = key % 4
28            velocity = msg[2]
29            print("MPD218 Pad (%d, %d): %d" % (row, col, velocity))
30            return
31    print("MIDI message: ", msg)
32    
33################################################################
34if __name__ == "__main__":
35    parser = argparse.ArgumentParser()
36    parser.add_argument("--midi", type=str, default = "MPD218 Port A", help = "Keyword identifying the MIDI input device (default: %(default)s).")
37    args = parser.parse_args()
38
39    # Initialize the MIDI input system and read the currently available ports.
40    midi_in = rtmidi.MidiIn()
41    for idx, name in enumerate(midi_in.get_ports()):
42        if args.midi in name:
43            print("Found preferred MIDI input device %d: %s" % (idx, name))
44            midi_in.open_port(idx)
45            midi_in.set_callback(midi_received)
46            break
47        else:
48            print("Ignoring unselected MIDI device: ", name)
49
50    if not midi_in.is_port_open():
51        if platform.system() == 'Windows':
52            print("Virtual MIDI inputs are not currently supported on Windows, see python-rtmidi documentation.")
53        else:
54            print("Creating virtual MIDI input.")
55            midi_in.open_virtual_port(args.midi)
56    
57    if not midi_in.is_port_open():
58        print("No MIDI device opened, exiting.")
59
60    else:
61        print("Waiting for input.")
62        while True:
63            time.sleep(1)