Skip to content

Instantly share code, notes, and snippets.

@hosackm
Created June 25, 2015 06:24
Show Gist options
  • Save hosackm/5727aa1912b6a09203b7 to your computer and use it in GitHub Desktop.
Save hosackm/5727aa1912b6a09203b7 to your computer and use it in GitHub Desktop.
Python wrapper for Portmidi
import sys
from _pymidi import ffi # , lib
'''Only initialize Portmidi once'''
SINGLETON = None
'''Detect OS and use correct file extension for shared library'''
if sys.platform == 'darwin':
ext = 'dylib'
elif sys.platform.startswith('linux'):
ext = 'so'
else: # win32 or cygwin
ext = 'dll'
lib = ffi.dlopen('.'.join(['libportmidi', ext]))
class PmEvent():
'''Represents a Portmidi event. A midi event is a 24 bit
hex value that contains a status, an 8 bit key value, and
an 8 bit velocity value.
PmEvent(message) returns an instance of PmEvent from a 24 bit integer
PmEvent also creates helper functions to instantiate events
without having to deal with bit masking:
create_note_on(key): returns a Note On PmEvent with velocity = 0
create_note_on(key, velocity): returns a Note On PmEvent
create_note_off(key): returns a Note Off PmEvent with velocity = 127
events_from_buffer(buf): returns a list of PmEvent instances
from a buffer of cdata PmEvent structs from the cffi interface
'''
def __init__(self, message):
self.status = message & 0xFF
self.key = (message >> 8) & 0xFF
self.vel = (message >> 16) & 0xFF
@classmethod
def events_from_buffer(cls, buf, gen=False):
'''Returns a list of PmEvent instances
from a buffer of cdata PmEvent structs from the cffi interface'''
return [cls(event.message) for event in buf]
@classmethod
def create_note_on(cls, key, velocity=0):
'''create_note_on(key) returns a Note On PmEvent with velocity = 0
create_note_on(key, velocity) returns a Note On PmEvent'''
message = ((0x90 << 16) & 0xFF0000)
message |= ((key << 8) & 0xFF00)
message |= (velocity & 0xFF)
return cls(message, 0)
@classmethod
def create_note_off(cls, key):
'''Returns a Note Off PmEvent instance'''
message = ((0x80 << 16) & 0xFF0000)
message |= ((key << 8) & 0xFF00)
message |= 0x7F
return cls(message, 0)
def is_note_on(self):
'''Returns True if a PmEvent is a Note On event
Otherwise returns False'''
return self.status == 0x90
def is_note_off(self):
'''Returns True if a PmEvent is a Note Off event
Otherwise returns False
'''
return self.status == 0x80
def is_control(self):
'''Returns True if a PmEvent is a Control event
Otherwise returns False'''
return self.status == 0xB0
def __str__(self):
return repr(self)
def __repr__(self):
return 'PmEvent(Status: {}, Note: {}, Velocity: {})'.format(
self.status,
self.key,
self.vel)
class Input(object):
'''_'''
PNULL = ffi.new('void **')[0]
def __init__(self, device_id, buffer_size=4096):
'''_'''
if device_id < 0:
raise MidiException('Device ID must be a positive integer')
if not _is_instantiated():
_instantiate_library()
self.buffer_size = buffer_size
self.buffer = ffi.new('PmEvent []', buffer_size)
pp_stream = ffi.new('PortMidiStream **')
err = lib.Pm_OpenInput(pp_stream,
device_id,
self.PNULL,
self.buffer_size,
self.PNULL,
self.PNULL)
if err:
raise MidiException(err)
self.stream = pp_stream[0]
def read(self, num_events=4096):
'''_'''
num_events = min(num_events, self.buffer_size)
num_events = lib.Pm_Read(self.stream, self.buffer, num_events)
if num_events < 0:
raise MidiException(num_events)
#events = self.buffer[0:num_events]
s = slice(0, num_events)
return PmEvent.events_from_buffer(self.buffer[s])
def poll(self):
'''_'''
ret = lib.Pm_Poll(self.stream)
if ret == 1:
return True
elif ret == 0:
return False
else:
raise MidiException(ret)
def close(self):
lib.Pm_Close(self.stream)
class Output(object):
def __init__(self):
pass
class MidiException(Exception):
'''Raise this exception when something goes wrong.
Uses Portmidi\'s Pm_GetErrorText() to describe the error that occured'''
def __init__(self, errno):
self.errno = errno
def __str__(self):
return lib.Pm_GetErrorText(self.errno)
def _is_instantiated():
'''Return True if Portmidi is already open else False'''
global SINGLETON
return SINGLETON is not None
def _instantiate_library():
'''Initialize Portmidi library'''
global SINGLETON
SINGLETON = ffi.new('PortMidiStream **')
num = lib.Pm_Initialize()
if num:
raise MidiException(num)
def main():
import time
print 'loading portmidi...'
try:
i = Input(0)
except MidiException:
print 'Unable to open MIDI device. Make sure it\'s connected'
sys.exit()
print 'done!'
while True:
try:
for e in i.read():
print e
time.sleep(0.1)
except KeyboardInterrupt:
break
if __name__ == '__main__':
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment