Saturday, November 12, 2022

Real-time MIDI in Python Using sched and rtmidi Introduction

Wouldn't it be great to send and receive MIDI in realtime in a really simple language like python?  I've written some snappy sequencers using the Sony PSP, and numerous microcontrollers (from 4 Mhz to 600 MHz!), but they can't easily leverage the internet to let composers and performances collaborate via long distance.  And using a convenient language like python usually means serious performance limitations.  But let's give this python realtime MIDI library a try!  It could give us the best of both worlds!

BTW, this post is building on the work of Uğur Güney, found on his blog post.  I've been trying out real-time music in python and wanted to share my work so others can benefit from it.

My github repo contains the results of my experiments toward building a Simultaneous Multi-User Tracker.  I am primarily working on my i9 laptop running Ubuntu20.  My goal is to target a Raspberry Pi 3+ as well. 

This is how you can install the real-time library pymidi for Python:

python3 -m pip install rtmidi

 

Now if you try Ugur's first example, you'll get an error like this, because I believe It the API to rtmidi has developed since then:

dsp@nvawter-tempo:~/SMUT/test$ python3
Python 3.8.10 (default, Jun 22 2022, 20:18:18)
[GCC 9.4.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import rtmidi
>>> midi_out = rtmidi.MidiOut()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: module 'rtmidi' has no attribute 'MidiOut'

 


I noticed the name for MidiOut()  is changed to RtMidiOut():

dsp@nvawter-tempo:~/SMUT/test$  python3

Python 3.8.10 (default, Jun 22 2022, 20:18:18)[GCC 9.4.0] on linux
Type "help", "copyright", "credits" or "license" for more information.                                  
>>> import rtmidi
>>> rtmidi.
rtmidi.Collector(     rtmidi.RandomOut(     rtmidi.randomout                                            
rtmidi.CollectorBin(  rtmidi.RtMidiIn(      rtmidi.rtmidi                                               
rtmidi.DEBUG          rtmidi.RtMidiOut(     rtmidi.threading                                            
rtmidi.Error(         rtmidi.collector      rtmidi.time                                                 
rtmidi.MidiMessage(   rtmidi.random                                                                   
>>>x=rtmidi.RtMidiOut()                                                                               

 

So far that's an improvement, but I still got this error when RtMidiOut() runs:
ALSA lib seq_hw.c:466:(snd_seq_hw_open) open /dev/snd/seq failed: Permission denied
MidiOutAlsa::initialize: error creating ALSA sequencer client object.
Traceback (most recent call last):
File "<stdin>", line 1, in <module>                                                                 
rtmidi.Error: MidiOutAlsa::initialize: error creating ALSA sequencer client object.                     
                                                                                                        
                                                                                                        
This turns out to be a permissions issue with /dev/snd/seq.  It's easy to fix.  Just add yourself to the "audio" group on your computer, as found in this thread.  On my computer, I typed this.  Note that my username is dsp and the group is audio:

dsp@nvawter-tempo:$sudo usermod -a -G audio dsp

And remember to log out and then back in again!   It will not work if you don't.  This an security feature of Linux/GNU/Ubuntu.


This is the next line of Ugur's post:

vailable_ports = midi_out.get_ports()

However, I found that gave an error message, too:

AttributeError: 'midi.RtMidiOut' object has no attribute 'get_ports'

I see two similar calls from RtMidiOut(), but not sure how to use them, so I went looking for the manual.  It seems Gary's website is down at the moment?  But thankfully there's an archive.  Anyway, that's just for the C++ class, and doesn't mention the python.  Wrt python, the docs on PyPi are outdated, too.   They point to this github repo, but the docs look like an old version, too.  Let's just try some calls!

>>> x.getPortCount()
1
>>> x.getPortName(0)
'Midi Through 14:0'

Ok, that looks reasonable!  It probably refers to the internal MIDI soundcard on my laptop.  I don't know why it only lists 15 channels... Normally any MIDI connection has 16, but this is a good start!

I'll continue this topic soon!  Thanks for getting it started, Gary and Ugur!