Installation: The package is currently not installable, but will be made into a Python package later.
Usage: python midictl.py [-c config.py] [midi_device]
. Note that
this opens the raw /dev/midi*
device, to avoid having to install
some compiled MIDI libraries.
- Restarts when config changes (but loses one event)
The main entry point is listen
, which parses arguments, opens the
device, listens to the MIDI device, and dispatches each event to the
handle
function.
The handle
function, for each
event, it searches the DISPATCHERS
list, which is pairs of
(dispatch_selector, callback_function). Any row which matches the
dispatch_selector is called as callback_function(msg)
, where msg
is the MIDI event from the mido library. Functions can do whatever.
The namedtuple class Dispatch
is the dispatch selector, which has
properties to select on midi events, for example "type" is t
,
"channel" is ch
, "note" is n
, and so on. All given selectors must
match (AND). To implement an OR,
one usually adds multiple similar selectors to DISPATCHERS
.
For an example configuration, see config.py
The following events can be triggered
-
mute
: Mute PulseAudio devices -
volume
: Adjust PulseAudio volumes -
pulse_move
: Move PulseAudio inputs/outputs to different cards -
V4L camera exposure
camera_exposure
: Set camera exposure to valuecamera_exposure_auto
: Set camera exposure to auto mode
-
keystroke
: Send a keystroke to an X11 application. Several pre-made bindings are:zoom_mute
: Toggle Zoom application mutezoom_video
: Toggle Zoom application videoteams_mute
: Toggle Microsoft Teams mute in Chrome web browser.
-
OBS
obs_switch
: Switch OBS scenes
-
PulseAudio mutes and volume changes.
Selector
is namedtuple which can select which pulse devices to operate on,
Config is by default read from a config.py
file, which gets executed
in the module namespace so has full control of everything. Examples
are below:
Turning control knob 5 adjusts the microphone volume for all items on
the PulseAudio source that contains USB_Advanced
in the name.
mic = Selector(t='source', name='USB_Advanced', it='*')
DISPATCHERS = [
(Dispatch(t=CC, c= 5), partial(volume, sel=mic)),
]
Mute the HDMI PluseAudio device (contains 'hdmi' in the PulseAudio name), when button 7 is pressed on channel 0. Instead of defining the button by note, set up an indirect BUTTONMAP, so that different MIDI channels have consistent button names, even if their notes are inconsistent. This saves re-programming the device just for this purpose.
BUTTONMAP = {
# channel: [note_for_button_id]
0: [None, 36, 37, 38, 39, 40, 41, 42, 43],
1: [None, 35, 36, 42, 39, 37, 38, 46, 44],
2: [None, 60, 62, 64, 65, 67, 69, 71, 72],
3: [None, 36, 38, 40, 41, 43, 45, 47, 48],
}
hdmi = Selector(t='sink', name='hdmi'),
DISPATCHERS = [
(Dispatch(t=ON, ch=0, b= 7), partial(mute, sel=hdmi)),
]
Toggle Zoom mute, when you push note 43 on channel 0.
DISPATCHERS = [
(Dispatch(t=ON, ch=0, n=43), zoom_mute),
]
Turning control knob 6 on channel 0 will adjust the camera exposure setting via the v4l2 Linux camera system. If the knob is turned to zero, it will go to auto-mode. If you turn it to anything else, it will adjust the exposure
DISPATCHERS = [
(Dispatch(t=CC, ch=0, c= 6, val=Not(0)), partial(camera_exposure, low=50, high=500)),
(Dispatch(t=CC, ch=0, c= 6, val=0), camera_exposure_auto),
]
Toggle OBS studio scenes when pushing buttons on channel 1, via the OBS websocket plugin-in.
OBS = obsws("localhost", 4444, "password")
DISPATCHERS = [
(Dispatch(t=ON, ch=1, b= 1), partial(obs_switch, scene='Title card')),
(Dispatch(t=ON, ch=1, b= 5), partial(obs_switch, scene='Gallery')),
]
Alpha, active development. But works and somewhat stable.