import fractions
from . import control
from .. util import log
try:
import mido
MESSAGE_TYPES = set(s['type'] for s in mido.messages.specs.SPECS)
except:
mido = None
MESSAGE_TYPES = set()
MIDO_ERROR = """You need to install mido. Try
pip install mido
"""
MIDO_BACKEND_ERROR = """You have the wrong rtmidi installed. Try
pip uninstall -y rtmidi
pip install -y python-rtmidi
"""
[docs]class Midi(control.ExtractedLoop):
EXTRACTOR = {
# There are more mido message types that we haven't used yet.
'keys_by_type': {
'aftertouch': (
'port', 'channel', 'type', 'value'),
'control_change': (
'port', 'channel', 'type', 'control', 'value'),
'note_off': (
'port', 'channel', 'type', 'note', 'velocity'),
'note_on': (
'port', 'channel', 'type', 'note', 'velocity'),
'pitchwheel': (
'port', 'channel', 'type', 'pitch'),
'program_change': (
'port', 'channel', 'type', 'program'),
},
# Some numeric fields (channel, control, program and note) don't get
# normalized because they are basically names.
'normalizers': {
'pitch': lambda x: fractions.Fraction(x - 8192) / 8192,
'value': lambda x: fractions.Fraction(x) / 127,
'velocity': lambda x: fractions.Fraction(x) / 127,
},
'omit': ('port', 'channel'),
}
def __init__(self, use_note_off=True, **kwds):
"""
:param use_note_off:
If False, map note_offs to note_ons with velocity 0
If True, map note_ons with velocity 0 to note_offs
If None, do not change none_ons or note_offs
"""
super().__init__(**kwds)
self.use_note_off = use_note_off
[docs] def messages(self):
if not mido:
raise ValueError(MIDO_ERROR)
try:
input_names = mido.get_input_names()
except AttributeError as e:
e.args = (MIDO_ERROR,) + e.args
raise
ports = [mido.open_input(i) for i in input_names]
if not ports:
log.error('control.midi: no MIDI ports found')
return
port_names = ', '.join('"%s"' % p.name for p in ports)
log.info('Starting to listen for MIDI on port%s %s',
'' if len(ports) == 1 else 's', port_names)
for port, msg in mido.ports.MultiPort(ports, yield_ports=True):
mdict = dict(vars(msg), port=port)
if self.use_note_off:
if msg.type == 'note_on' and not msg.velocity:
mdict.update(type='note_off')
elif self.use_note_off is False:
if msg.type == 'note_off':
mdict.update(type='note_on', velocity=0)
yield mdict