Added support for MIDI real-time events
This commit is contained in:
parent
306faff4af
commit
27b97e584f
2 changed files with 96 additions and 0 deletions
95
platypush/plugins/midi.py
Normal file
95
platypush/plugins/midi.py
Normal 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:
|
||||||
|
|
1
setup.py
1
setup.py
|
@ -81,6 +81,7 @@ setup(
|
||||||
'Support for most of the HTTP poll backends': ['python-dateutil'],
|
'Support for most of the HTTP poll backends': ['python-dateutil'],
|
||||||
'Support for Last.FM scrobbler plugin': ['pylast'],
|
'Support for Last.FM scrobbler plugin': ['pylast'],
|
||||||
'Support for custom hotword detection': ['snowboy'],
|
'Support for custom hotword detection': ['snowboy'],
|
||||||
|
'Support for real-time MIDI events': ['rtmidi'],
|
||||||
'Support for GPIO pins access': ['RPi.GPIO'],
|
'Support for GPIO pins access': ['RPi.GPIO'],
|
||||||
'Support for MCP3008 analog-to-digital converter plugin': ['adafruit-mcp3008'],
|
'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'],
|
# 'Support for Leap Motion backend': ['git+ssh://git@github.com:BlackLight/leap-sdk-python3.git', 'redis'],
|
||||||
|
|
Loading…
Reference in a new issue