3

I'm using PyGame to read a USB MIDI device, very similar to what is used here, except I run it as a background service on a Raspberry Pi.

I would like to be able to disconnect and reconnect the MIDI device, and still be able to read it.

I have tried two approaches:

  1. Regularly enumerate the MIDI devices using pygame.midi.get_count() and info().
  2. Use pyudev to monitor USB events, similar to this example.

The problem with (1) is that it seems that pygame.midi always returns the same values (both get_count and info), regardless of whether the device is still connected.

The problem with (2) is that it would never call the asynchronous function I registered for events (though the standalone example works fine, just changing the subsytem to usb). I figured this might be a problem with threading, so I called everything to register for events from a dedicated thread, which then ran glib.MainLoop.run() to idle wait, but discovered the pygame would not be able to read the midi device if I started any thread before running my AMK class, even just a thread that printed something and returned. (I'm using glib since the version of pyudev in the Pi repo is 0.13, but I guess the newer way is the gobject equivalent).

Thus I resorted to using udevd to detect the connect event and restart my service via a /etc/udev/rules.d/ trigger, which works okay, but is kludgy, and loses the state in my script (which I would like to save).

So, before I waste many more hours debugging (2), I was hoping someone could perhaps point me in the right direction.

Community
  • 1
  • 1
cwshep
  • 31
  • 3

3 Answers3

1

pygame uses PortMidi, which was originally designed for the Windows MIDI API and assumes that the set of MIDI ports never changes.

You have to use a separate monitor process that restarts your program whenever MIDI ports change.

CL.
  • 173,858
  • 17
  • 217
  • 259
  • Interesting, thanks. So that explains the issues with approach (1). It seems that it indicates (2) is the right way to go, but then I'm wondering if it's necessary to spawn a new thread after registering the USB monitor. If it is, then I'm wondering why PyGame doesn't play well with threads, and if not, then why my even handler isn't called when I run my PyGame class. – cwshep Jan 17 '15 at 10:46
1

I haven't tested this thoroughly yet but I believe that if you call quit and then again init, you can then get a properly updated list of MIDI devices. Here is an example:

import pygame, pygame.midi    
pygame.midi.init()    
print pygame.midi.get_count()    
a=raw_input('Connect or disconnect some MIDI devices')    
pygame.midi.quit()    
pygame.midi.init()
print pygame.midi.get_count()
Oliver W.
  • 13,169
  • 3
  • 37
  • 50
  • Thanks for the tip. Oddly though if I try the quit() and init() then reinitialize the device, subsequent calls to midi_in.poll() fail: `PortMidi call failed... PortMidi: 'Bad pointer' type ENTER...` And I verified that the new midi_in was a valid object (``). Regardless, this seems like a pretty ugly way to do what I want anyway. Since I would like to read the midi device in realtime, continuously quitting and reiniting seems like a bad idea. – cwshep Jan 19 '15 at 02:43
  • If you don't want to resort on external procedures, you could monitor the system log in your main loop and look for MIDI connect/disconnect events. For example, use `mesg=commands.getoutput('dmesg | grep -i midi')` and check if mesg has changed from the previous iteration. Only in that case you'd need to do the midi quit and init. I'm at the office now and can't check the problem with the midi_in.poll() but did you remember to reassociate the midi port again (with `midi_in=pygame.midi.Input(portnumber`)? – Hector Socas-Navarro Jan 19 '15 at 11:34
0

This is how I monitor for existing or newly added Midi devices - wait_for_midi() will block until a MIDI device appears in the system and returns the /dev/midi* path to it.

import re
import pyudev

def is_midi_device(dev_path):
    if dev_path is None: 
        return False
    if re.match(u"^/dev/midi[0-9]+$", dev_path):
        return True
    return False

# Return path to a MIDI device when found.
def wait_for_midi():
    context = pyudev.Context()

    #  Check for existing midi devices
    for device in context.list_devices():
        dev_path = device.device_node
        if is_midi_device(dev_path) :
            print('Found {}'.format(dev_path))
            return dev_path

    # Monitor for new midi devices as added
    monitor = pyudev.Monitor.from_netlink(context)
    monitor.filter_by(subsystem='sound')
    for action, device in monitor:
        if action != "add": 
            continue
        dev_path = device.device_node
        if is_midi_device(dev_path) :
            print('Just added: {}'.format(dev_path))
            return dev_path
elomage
  • 4,334
  • 2
  • 27
  • 23