NEW: Control-device manager + controller device "drivers"

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 :grin:

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.


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 :wink:

You can check the full code here:

and some example drivers here:

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 ...
			# Init new selected device
			if idev > 0:
				self.idev = idev"Setting-up {} in slot {}".format(self.dev_id, self.idev))
				# Setup routing
				lib_zyncore.zmip_set_route_extdev(self.idev - 1, 0)
				# Initialize new selected device

	def release(self):
		if self.idev > 0:"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:
			# Restore routing
			lib_zyncore.zmip_set_route_extdev(self.idev - 1, 1)
		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):

	# Sleep On
	# It *COULD* be improved by child class
	def sleep_off(self):

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):
		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:
		if force:

	# It *SHOULD* be implemented by child class
	def refresh_zynpad_bank(self):

	def refresh_pads(self, force=False):
		if force:
		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(, pad)
			if changed_state or force:
				mode = self.zyngui.zynseq.libseq.getPlayMode(, 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(, pad)
		if changed_state or force:
			mode = self.zyngui.zynseq.libseq.getPlayMode(, 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):

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 :wink:

Yes. This part should be copied to the wiki.



Tee hee… I wonder what can be constructed around the motor 61?


I"m working on this, jejejeje!!
Remember I’ve one. Indeed, It would be nice to have several drivers for the same device, if you also want to try …


All fantastic and impressive… but… I see fewer and fewer black and white keys… maybe an era is coming to an end? :disappointed_relieved:



Joe Zawinul, master of synths!


jajaja! I will send another picture tomorrow :wink:

1 Like

Hi Mr @wyleu and other @zynthianers!

I’m been investigating an i’m pretty convinced that having a generic MCU driver would be a very good thing. It would cover a good amount of master controllers and keyboards, including Mötor 61/49.
Of course, we have to imagine a decent mapping for MCU <=> Zynthian, but we are not so far now, as zynthian has been slowly approaching to being a (u)DAW.

Of course, there is the little problem that MCU not being a public protocol, but as it’s a kind of standard for control surfaces, so you can find good sources from nice people that have reverse-engineered the protocol, so the most difficult part is already done. For instance:

I’m really tempted of going forward with this, but first, i would like to know how many of you own a MIDI controller that implements some kind of Mackie Control.

All the best!


Well there’s a motorc61 here…

1 Like

I have this Asparion controller DW700 and I use it with MCU (Ableton Live)

Would appreciate to use it with Zynthian.

I have a Behringer X-Touch Compact and X-Touch One, both of which have a Mackie Control mode which I believe is the same as MCU? I’d be particularly interested in getting the X-Touch One working with my Zynthian.

1 Like

Would the Novation Circuit/Circuit Tracks work too?

I have a Novation LaunchPad Pro and 2 Launch Control XL’s

This is marvelous news! I am mostly interested in building my own controller for a custom build and ditching the 4x5 keypad all together.
If I see this right, having access to the complete Zynthian UI API, it should be possible to control ZynSeq tracks with a (physical) Step Sequencer, maybe using the Midi and Sequencer functionalities of QMK or taking OpenDeck to cobble together a custom Zynthian UI interface with full functionality?


A Novation Launchpad X IS on his way to my home, so you will know if a newby is able to create a driver mostly similar at the ones you have made :wink:


I flashed a SD card just to try this. Changed repositories to testing and updated but nothing seems to happen when i plug my Launchpad MK3. I can select the controller in the options menu of zynpad but nothing happens again. Anyone was succesfull? Am i doing something wrong?

I have a M-audio Code keyboard and a Zoom R16, both of which implement Mackie Control. The R16 has been mostly just gathering dust in a closet for the last few years but these developments open up interesting possibilities…

Maybe - did you reboot after the update? At least once is often necessary, and twice occasionally.

Can I request Novation Launchkey 61 to the top of the list please? Is there a bribe fund? :>

1 Like

Yes, i updated and rebooted twice. No results.