Hi @zynthianers!
I’m proud of announcing the new control-device manager and driver subsystem is merged on testing branch. This is nice step forward in our road towards world domination
The concept behind this is simple to explain with some examples:
- You plug your “Novation Launchpad Mini MK3” to zynthian, and it 's recognized and configured to manage the zynpad. The pads are colored like the zynpad ones. You can play/stop sequences, change scene, etc. LED feedback works great and you have the experience of full integration. Similar to what you get with this kind of control devices in Ableton Live.
- You plug your “Akai MIDIMIX” controller and … voilaaaaa!! it’s recognized and configured to manage zynthian’s mixer. You can control main volume. Also volume, balance, mute & solo for each chain, you can change the active chain (on stage mode) or arm the multi-channel recorder (multi-timbral mode). You have LED feedback for everything. And the unassigned knobs are still available for MIDI learning. Just like it should be, a full integration experience.
- Etc.
The list of currently supported devices is this;
- Novation LaunchPad MINI MK3
- Novation LaunchPad MINI MK2
- Novation LaunchPad MINI MK1
- Novation LaunchKey MINI MK3
- Akai MiDI-Mix
Not too long yet, but i hope this list grows up fast!! Creating a driver is not too difficult and code-guys will find it quite fun. If you have the device and some idea about how it works, it’s a question of 1-2 hours to have something running. Novation, for instance, is a good example of well documented MIDI controllers, so you can find detailed documentation like this:
Each manufacturer has his own policy about this, and in the worst case, you would need to reverse engineering the device, if someone didn’t before, what is highly probable. Also, the Ableton Live’s unofficial python drivers are a very good starting point because you will find examples for most devices and brands in the market:
The goal should be creating generic drivers that manage devices with its “factory” configuration untouched. I mean, many MIDI controllers have some kind of “editor” software that allow to modify its factory configuration. Creating drivers for modified “non-factory” configurations is possible, of course, and it could be interesting in some cases, but it’s sub-optimal because. It’s always better to make the driver to work with the factory default configuration, so everybody can use it out-the-box.
Regarding the controller-driver model, it’s quite simple. Take a look to the examples and ask all you want. Of course, you would need some programming skills.
TECHNICAL INFO FOR DRIVER DEVELOPERS
Drivers are python module implementing a class. This class can call internal zynthian-ui API, so it has full access to zynthian functionality. It’s not limited by CUIA. No limits, so you can break the UI too
You can check the full code here:
zynthian-ui/zyngui/zynthian_ctrldev_manager.py at testing · zynthian/zynthian-ui · GitHub
and some example drivers here:
zynthian-ui/zyngui/ctrldev at testing · zynthian/zynthian-ui · GitHub
All drivers must inherit from zynthian_ctrldev_base, implementing the needed functionality as commented in the base code:
#------------------------------------------------------------------------------------------------------------------
# Control device base class
#------------------------------------------------------------------------------------------------------------------
class zynthian_ctrldev_base():
dev_id = None # String that identifies the device (class variable!)
dev_zynpad = False # Can act as a zynpad trigger device
dev_zynmixer = False # Can act as an audio mixer controller device
dev_pated = False # Can act as a pattern editor device
# Function to initialise class
def __init__(self):
self.idev = 0 # Slot index where the device is connected, starting from 1 (0 = None)
self.zyngui = zynthian_gui_config.zyngui
# Setup the device connected in slot #idev
# Before calling this, the caller (ctrldev-manager) should check that driver's ID string matches device's ID string
def setup(self, idev=None):
if idev != self.idev:
# Release currently selected device, if any ...
self.release()
# Init new selected device
if idev > 0:
self.idev = idev
logging.info("Setting-up {} in slot {}".format(self.dev_id, self.idev))
# Setup routing
lib_zyncore.zmip_set_route_extdev(self.idev - 1, 0)
zynautoconnect.midi_autoconnect(True)
# Initialize new selected device
self.init()
self.refresh(force=True)
def release(self):
if self.idev > 0:
logging.info("Releasing {} in slot {}".format(self.dev_id, self.idev))
# If device is still connected, call end
dev_id = zynautoconnect.get_midi_device_name(self.idev)
if dev_id and dev_id == self.dev_id:
self.end()
# Restore routing
lib_zyncore.zmip_set_route_extdev(self.idev - 1, 1)
zynautoconnect.midi_autoconnect(True)
self.idev = 0
# Refresh device status (LED feedback, etc)
# It *SHOULD* be implemented by child class
def refresh(self, force=False):
logging.debug("Refresh LEDs for {}: NOT IMPLEMENTED!".format(self.dev_id))
# Device MIDI event handler
# It *SHOULD* be implemented by child class
def midi_event(self, ev):
logging.debug("MIDI EVENT FROM '{}'".format(self.dev_id))
# Light-Off LEDs
# It *SHOULD* be implemented by child class
def light_off(self):
logging.debug("Lighting Off LEDs for {}: NOT IMPLEMENTED!".format(self.dev_id))
# Sleep On
# It *COULD* be improved by child class
def sleep_on(self):
self.light_off()
# Sleep On
# It *COULD* be improved by child class
def sleep_off(self):
self.refresh(True)
Quite simple, right? This is for basic “generic” drivers.
Zynpad enabled drivers inherit from this base class:
# ------------------------------------------------------------------------------------------------------------------
# Zynpad control device base class
# ------------------------------------------------------------------------------------------------------------------
class zynthian_ctrldev_zynpad(zynthian_ctrldev_base):
dev_zynpad = True # Can act as a zynpad trigger device
def __init__(self):
super().__init__()
self.zynpad = self.zyngui.screens["zynpad"]
def refresh(self, force=False):
# When zynpad is shown, this is done by refresh_status, so no need to refresh twice
if force or not self.zynpad.shown:
self.refresh_pads(force)
self.refresh_zynpad_bank()
if force:
self.refresh_zynpad_bank()
# It *SHOULD* be implemented by child class
def refresh_zynpad_bank(self):
pass
def refresh_pads(self, force=False):
if force:
self.light_off()
for pad in range(self.zyngui.zynseq.col_in_bank ** 2):
# It MUST be called for cleaning the dirty bit
changed_state = self.zyngui.zynseq.libseq.hasSequenceChanged(self.zynpad.bank, pad)
if changed_state or force:
mode = self.zyngui.zynseq.libseq.getPlayMode(self.zynpad.bank, pad)
state = self.zynpad.get_pad_state(pad)
self.update_pad(pad, state, mode)
def refresh_pad(self, pad, force=False):
# It MUST be called for cleaning the dirty bit!!
changed_state = self.zyngui.zynseq.libseq.hasSequenceChanged(self.zynpad.bank, pad)
if changed_state or force:
mode = self.zyngui.zynseq.libseq.getPlayMode(self.zynpad.bank, pad)
state = self.zynpad.get_pad_state(pad)
self.update_pad(pad, state, mode)
# It *SHOULD* be implemented by child class
def update_pad(self, pad, state, mode):
pass
#------------------------------------------------------------------------------
If you kind-of-understand this, you shouldn’t have problem to understand the driver examples and program your own drivers. Simply copy your driver file in the the zyngui/ctrldev folder and restart zynthian-ui. BTW, hot driver reload is on the way
Yes. This part should be copied to the wiki.
Enjoy!