import reapy
import reapy.reascript_api as RPR
from reapy.core import ReapyObject, ReapyObjectList
class MIDIEvent(ReapyObject):
"""Abstract class for MIDI events."""
def __init__(self, parent, index):
"""
Create event.
Parameters
----------
parent : Take
Take to which the event belongs.
index : int
Event index. It is specific to the event type, which means
a Note and a CC can have the same index.
"""
self.index = index
self.parent = parent
@property
def _args(self):
return self.parent, self.index
@property
def _del_name(self):
return 'MIDI_DeleteEvt'
def delete(self):
"""Delete event from the take."""
f = getattr(RPR, self._del_name)
f(self.parent.id, self.index)
@reapy.inside_reaper()
def set(self, message=None, position=None, selected=None,
muted=None, unit="seconds", sort=True):
"""
Set properties of event if needed.
Parameters
----------
message : Iterable[int], optional
Can be any message buffer, for example: (0xb0, 64, 127)
which is CC64 val127 on channel 1
position : float, optional
position at take
selected : bool, optional
Whether to select new note (default=False).
muted : bool, optional
Whether to mute new note (default=False).
unit : str ("seconds as default")
"beats"|"ppq"|"seconds" (default are seconds)
sort : bool (True as default)
Whether to resort notes after creating new note
(default=True). If False, then the new note will be
``take.notes[-1]``. Otherwise it will be at its place in
the time-sorted list ``take.notes``. Set to False for
improved efficiency when adding several notes, then call
``Take.sort_events`` at the end.
"""
take = self.parent
if position:
position = take._resolve_midi_unit((position,), unit)[0]
if message:
message = take._midi_to_bytestr(message)
RPR.MIDI_SetEvt(
take.id, self.index, selected, muted, position, message,
len(message), not sort
)
class MIDIEventList(ReapyObjectList):
def __init__(self, parent):
"""
Create event list.
Parameters
----------
parent : Take
Take to which the event list belongs.
"""
self.parent = parent
def __getitem__(self, key):
with reapy.inside_reaper():
if key >= len(self):
raise IndexError
return self._elements_class(self.parent, key)
def __len__(self):
return getattr(self.parent, self._n_elements)
@property
def _args(self):
return self.parent,
@property
def _elements_class(self):
return MIDIEvent
@property
def _n_elements(self):
return 'n_midi_events'
[docs]class CC(MIDIEvent):
"""MIDI CC event."""
@property
def channel(self):
"""
CC channel between 0 and 15.
:type: int
See also
--------
CC.infos
For maximum efficiency when querying several properties of
a CC.
"""
return RPR.MIDI_GetCC(
self.parent.id, self.index, 0, 0, 0, 0, 0, 0, 0
)[7]
@property
def channel_message(self):
"""
CC channel message.
:type: int
See also
--------
CC.infos
For maximum efficiency when querying several properties of
a CC.
"""
return RPR.MIDI_GetCC(
self.parent.id, self.index, 0, 0, 0, 0, 0, 0, 0
)[6]
@property
def _del_name(self):
return 'MIDI_DeleteCC'
@reapy.inside_reaper()
@property
def infos(self):
"""
Return infos about CC.
Keys are {"selected", "muted", "position", "channel",
"channel_message", "messages"}.
:type: dict
"""
res = list(RPR.MIDI_GetCC(
self.parent.id, self.index, 0, 0, 0, 0, 0, 0, 0
))[3:]
res[0] = bool(res[0])
res[1] = bool(res[1])
res[2] = self.parent.ppq_to_time(res[2])
res[-2] = res[-2], res[-1]
res.pop()
keys = (
"selected", "muted", "position", "channel_message", "channel",
"messages"
)
return {k: r for k, r in zip(keys, res)}
@property
def messages(self):
"""
CC messages.
:type: 2-list of int
See also
--------
CC.infos
For maximum efficiency when querying several properties of
a CC.
"""
return RPR.MIDI_GetCC(
self.parent.id, self.index, 0, 0, 0, 0, 0, 0, 0
)[-2:]
@property
def muted(self):
"""
Whether CC is muted.
:type: bool
See also
--------
CC.infos
For maximum efficiency when querying several properties of
a CC.
"""
return bool(RPR.MIDI_GetCC(
self.parent.id, self.index, 0, 0, 0, 0, 0, 0, 0
)[4])
@property
def position(self):
"""
CC position in seconds.
:type: float
See also
--------
CC.infos
For maximum efficiency when querying several properties of
a CC.
"""
return self.infos["position"]
@property
def selected(self):
"""
Whether CC is selected.
:type: bool
See also
--------
CC.infos
For maximum efficiency when querying several properties of
a CC.
"""
return bool(RPR.MIDI_GetCC(
self.parent.id, self.index, 0, 0, 0, 0, 0, 0, 0
)[3])
[docs]class CCList(MIDIEventList):
_elements_class = CC
_n_elements = "n_cc"
[docs]class Note(MIDIEvent):
"""MIDI note."""
@property
def channel(self):
"""
Note channel between 0 and 15.
:type: int
See also
--------
Note.infos
For maximum efficiency when querying several properties of
a Note.
"""
return RPR.MIDI_GetNote(
self.parent.id, self.index, 0, 0, 0, 0, 0, 0, 0
)[7]
@property
def _del_name(self):
return 'MIDI_DeleteNote'
@property
def end(self):
"""
Note end in seconds.
:type: float
See also
--------
Note.infos
For maximum efficiency when querying several properties of
a Note.
"""
return self.infos["end"]
@property
def infos(self):
"""
Return infos about note.
Keys are {"selected", "muted", "start", "end", "channel",
"pitch", "velocity"}.
:type: dict
"""
res = list(RPR.MIDI_GetNote(
self.parent.id, self.index, 0, 0, 0, 0, 0, 0, 0
))[3:]
res[0] = bool(res[0])
res[1] = bool(res[1])
res[2] = self.parent.ppq_to_time(res[2])
res[3] = self.parent.ppq_to_time(res[3])
keys = (
"selected", "muted", "start", "end", "channel", "pitch",
"velocity"
)
return {k: r for k, r in zip(keys, res)}
@property
def muted(self):
"""
Whether note is muted.
:type: bool
See also
--------
Note.infos
For maximum efficiency when querying several properties of
a Note.
"""
return bool(RPR.MIDI_GetNote(
self.parent.id, self.index, 0, 0, 0, 0, 0, 0, 0
)[4])
@property
def pitch(self):
"""
Note pitch between 0 and 127.
:type: int
See also
--------
Note.infos
For maximum efficiency when querying several properties of
a Note.
"""
return RPR.MIDI_GetNote(
self.parent.id, self.index, 0, 0, 0, 0, 0, 0, 0
)[8]
@property
def selected(self):
"""
Whether note is selected.
:type: bool
See also
--------
Note.infos
For maximum efficiency when querying several properties of
a Note.
"""
return bool(RPR.MIDI_GetNote(
self.parent.id, self.index, 0, 0, 0, 0, 0, 0, 0
)[3])
@property
def start(self):
"""
Note start in seconds.
:type: float
See also
--------
Note.infos
For maximum efficiency when querying several properties of
a Note.
"""
return self.infos["start"]
@property
def velocity(self):
"""
Note velocity between 0 and 127.
:type: int
See also
--------
Note.infos
For maximum efficiency when querying several properties of
a Note.
"""
return RPR.MIDI_GetNote(
self.parent.id, self.index, 0, 0, 0, 0, 0, 0, 0
)[9]
[docs]class NoteList(MIDIEventList):
_elements_class = Note
_n_elements = "n_notes"