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 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134
|
# SPDX-FileCopyrightText: 2013 Ole Martin Bjorndalen <ombdalen@gmail.com>
#
# SPDX-License-Identifier: MIT
"""
Mido ports for pygame.midi.
Pygame uses PortMidi, so this is perhaps not very useful.
http://www.pygame.org/docs/ref/midi.html
"""
from pygame import midi
from ..ports import BaseInput, BaseOutput
def _get_device(device_id):
keys = ['interface', 'name', 'is_input', 'is_output', 'opened']
info = dict(zip(keys, midi.get_device_info(device_id)))
# TODO: correct encoding?
info['name'] = info['name'].decode('utf-8')
info['id'] = device_id
return info
def _get_default_device(get_input):
if get_input:
device_id = midi.get_default_input_id()
else:
device_id = midi.get_default_output_id()
if device_id < 0:
raise OSError('no default port found')
return _get_device(device_id)
def _get_named_device(name, get_input):
# Look for the device by name and type (input / output)
for device in get_devices():
if device['name'] != name:
continue
# Skip if device is the wrong type
if get_input:
if device['is_output']:
continue
else:
if device['is_input']:
continue
if device['opened']:
raise OSError(f'port already opened: {name!r}')
return device
else:
raise OSError(f'unknown port: {name!r}')
def get_devices(**kwargs):
midi.init()
return [_get_device(device_id) for device_id in range(midi.get_count())]
class PortCommon:
"""
Mixin with common things for input and output ports.
"""
def _open(self, **kwargs):
if kwargs.get('virtual'):
raise ValueError('virtual ports are not supported'
' by the Pygame backend')
elif kwargs.get('callback'):
raise ValueError('callbacks are not supported'
' by the Pygame backend')
midi.init()
if self.name is None:
device = _get_default_device(self.is_input)
self.name = device['name']
else:
device = _get_named_device(self.name, self.is_input)
if device['opened']:
if self.is_input:
devtype = 'input'
else:
devtype = 'output'
raise OSError('{} port {!r} is already open'.format(devtype,
self.name))
if self.is_input:
self._port = midi.Input(device['id'])
else:
self._port = midi.Output(device['id'])
self._device_type = 'Pygame/{}'.format(device['interface'])
def _close(self):
self._port.close()
class Input(PortCommon, BaseInput):
"""
PortMidi Input port
"""
def _receive(self, block=True):
# I get hanging notes if MAX_EVENTS > 1, so I'll have to
# resort to calling Pm_Read() in a loop until there are no
# more pending events.
while self._port.poll():
bytes, time = self._port.read(1)[0]
self._parser.feed(bytes)
class Output(PortCommon, BaseOutput):
"""
PortMidi output port
"""
def _send(self, message):
if message.type == 'sysex':
# Python 2 version of Pygame accepts a bytes or list here
# while Python 3 version requires bytes.
# According to the docs it should accept both so this may be
# a bug in Pygame:
# https://www.pygame.org/docs/ref/midi.html#pygame.midi.Output.write_sys_ex
self._port.write_sys_ex(midi.time(), bytes(message.bin()))
else:
self._port.write_short(*message.bytes())
|