Added support for MIDI real-time events

This commit is contained in:
Fabio Manganiello 2018-05-23 19:49:54 +02:00
parent 306faff4af
commit 27b97e584f
2 changed files with 96 additions and 0 deletions

95
platypush/plugins/midi.py Normal file
View file

@ -0,0 +1,95 @@
import logging
import rtmidi
import time
from platypush.message.response import Response
from platypush.plugins import Plugin
class MidiPlugin(Plugin):
"""
Virtual MIDI controller plugin.
It requires python-rtmidi - https://pypi.org/project/python-rtmidi/
"""
_played_notes = set()
def __init__(self, device_name='Platypush virtual MIDI output',
*args, **kwargs):
super().__init__(*args, **kwargs)
self.device_name = device_name
self.midiout = rtmidi.MidiOut()
available_ports = self.midiout.get_ports()
if available_ports:
self.midiout.open_port(0)
logging.info('Initialized MIDI plugin on port 0')
else:
self.open_virtual_port(self.device_name)
logging.info('Initialized MIDI plugin on virtual device {}'.
format(self.device_name))
def send_message(self, values, *args, **kwargs):
"""
Values is expected to be a list containing the MIDI command code and
the command parameters - see reference at
https://ccrma.stanford.edu/~craig/articles/linuxmidi/misc/essenmidi.html
Available MIDI commands:
0x80 Note Off
0x90 Note On
0xA0 Aftertouch
0xB0 Continuous controller
0xC0 Patch change
0xD0 Channel Pressure
0xE0 Pitch bend
0xF0 Start of system exclusive message
0xF1 MIDI Time Code Quarter Frame (Sys Common)
0xF2 Song Position Pointer (Sys Common)
0xF3 Song Select
0xF6 Tune Request (Sys Common)
0xF7 End of system exclusive message
0xF8 Timing Clock (Sys Realtime)
0xFA Start (Sys Realtime)
0xFB Continue (Sys Realtime)
0xFC Stop (Sys Realtime)
0xFE Active Sensing (Sys Realtime)
0xFF System Reset (Sys Realtime)
"""
self.midiout.send_message(values, *args, **kwargs)
return Response(output={'status':'ok'})
def play_note(self, note, velocity, duration=0):
"""
Params:
- note: MIDI note in range 0-127 with #60 = C4
- velocity: MIDI note velocity in range 0-127
- duration: Note duration in (float) seconds. Pass 0 if you don't
want the note to get off
"""
self.send_message([0x90, note, velocity]) # Note on
self._played_notes.add(note)
if duration:
time.sleep(duration)
self.send_message([0x80, note, 0]) # Note off
self._played_notes.remove(note)
def release_note(self, note):
self.send_message([0x80, note, 0]) # Note off
self._played_notes.remove(note)
def release_all_notes(self):
played_notes = self._played_notes.copy()
for note in played_notes:
self.release_note(note)
# vim:sw=4:ts=4:et:

View file

@ -81,6 +81,7 @@ setup(
'Support for most of the HTTP poll backends': ['python-dateutil'],
'Support for Last.FM scrobbler plugin': ['pylast'],
'Support for custom hotword detection': ['snowboy'],
'Support for real-time MIDI events': ['rtmidi'],
'Support for GPIO pins access': ['RPi.GPIO'],
'Support for MCP3008 analog-to-digital converter plugin': ['adafruit-mcp3008'],
# 'Support for Leap Motion backend': ['git+ssh://git@github.com:BlackLight/leap-sdk-python3.git', 'redis'],