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

Okay. I’ve managed to re find the zynthian_ctrldev_launchpad_x.py and find a problem line 45 (the color definition is split in two) and updated some other lines.
Fine.
WinSCP is running but… I can’t find he zyngui/ctrldev folder :sweat_smile:

Ps : line 45 is broken for launchpad mini mk3 too, maybe others.

Look in /zynthian/zynthian-ui.

Yes i’m in, there is no “ctrldev” folder here
… i will create one just to test.

EDIT : found ! the wiki is not updated it is in “zyngine” now.
Edit 2 : ow…

Okay.
After a full hour of tweeking the file, controlling line by line and everything… it’s now working. Please find here the new code for /zynthian/zynthian-ui/zyngine/ctrldev/zynthian_ctrldev_launchpad_x.py

#!/usr/bin/python3
# -*- coding: utf-8 -*-
# ******************************************************************************
# ZYNTHIAN PROJECT: Zynthian Control Device Driver
#
# Zynthian Control Device Driver for "Novation Launchpad X"
#
# Copyright (C) 2015-2025 Fernando Moyano <jofemodo@zynthian.org>
#                         Brian Walton <brian@riban.co.uk>
#                         Wapata <wapata.31@gmail.com>
#
#
# ******************************************************************************
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License as
# published by the Free Software Foundation; either version 2 of
# the License, or any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# For a full copy of the GNU General Public License see the LICENSE.txt file.
#
# ******************************************************************************

import logging
from time import sleep

# Zynthian specific modules
from zynlibs.zynseq import zynseq
from zyncoder.zyncore import lib_zyncore
from zyngine.ctrldev.zynthian_ctrldev_base import zynthian_ctrldev_zynpad

# ------------------------------------------------------------------------------------------------------------------
# Novation Launchpad X
# ------------------------------------------------------------------------------------------------------------------


class zynthian_ctrldev_launchpad_x(zynthian_ctrldev_zynpad):

    dev_ids = ["Launchpad X IN 1"]

    PAD_COLOURS = [6, 29, 17, 49, 66, 41, 23,
                   13, 96, 2, 81, 82, 83, 84, 85, 86, 87]
    STARTING_COLOUR = 21
    STOPPING_COLOUR = 5
    SELECTED_BANK_COLOUR = 29
    STOP_ALL_COLOUR = 5

    def send_sysex(self, data):
        if self.idev_out is not None:
            msg = bytes.fromhex(f"F0 00 20 29 02 0C {data} F7")
            lib_zyncore.dev_send_midi_event(self.idev_out, msg, len(msg))
            sleep(0.05)

    def get_note_xy(self, note):
        row = 8 - (note // 10)
        col = (note % 10) - 1
        return col, row

    def init(self):
        # Awake
        self.sleep_off()
        # self.send_sysex_universal_inquiry()
        # Enter DAW session mode
        self.send_sysex("10 01")
        # Select session layout (session = 0x00, faders = 0x0D)
        self.send_sysex("00 00")
        super().init()

    def end(self):
        super().end()
        # Exit DAW session mode
        self.send_sysex("10 00")
        # Select Keys layout (drums = 0x04, keys = 0x05, user = 0x06, prog = 0x7F)
        self.send_sysex("00 05")

    def update_seq_bank(self):
        if self.idev_out is None:
            return
        # logging.debug("Updating Launchpad X bank leds")
        for row in range(0, 7):
            note = 89 - 10 * row
            if row == self.zynseq.bank - 1:
                lib_zyncore.dev_send_ccontrol_change(
                    self.idev_out, 0, note, self.SELECTED_BANK_COLOUR)
            else:
                lib_zyncore.dev_send_ccontrol_change(self.idev_out, 0, note, 0)
        # Stop All button => Solid Red
        lib_zyncore.dev_send_ccontrol_change(
            self.idev_out, 0, 19, self.STOP_ALL_COLOUR)

    def update_seq_state(self, bank, seq, state, mode, group):
        if self.idev_out is None or bank != self.zynseq.bank:
            return
        # logging.debug(f"Updating Launchpad X bank {bank} pad {seq} => state {state}, mode {mode}")
        col, row = self.zynseq.get_xy_from_pad(seq)
        note = 10 * (8 - row) + col + 1
        try:
            if mode == 0:
                chan = 0
                vel = 0
            elif state == zynseq.SEQ_STOPPED:
                chan = 0
                vel = self.PAD_COLOURS[group]
            elif state == zynseq.SEQ_PLAYING:
                chan = 2
                vel = self.PAD_COLOURS[group]
            elif state == zynseq.SEQ_STOPPING:
                chan = 1
                vel = self.STOPPING_COLOUR
            elif state == zynseq.SEQ_STARTING:
                chan = 1
                vel = self.STARTING_COLOUR
            else:
                chan = 0
                vel = 0
        except:
            chan = 0
            vel = 0
        # logging.debug("Lighting PAD {}, group {} => {}, {}, {}".format(seq, group, chan, note, vel))
        lib_zyncore.dev_send_note_on(self.idev_out, chan, note, vel)

    # Light-Off the pad specified with column & row
    def pad_off(self, col, row):
        note = 10 * (8 - row) + col + 1
        lib_zyncore.dev_send_note_on(self.idev_out, 0, note, 0)

    def midi_event(self, ev):
        # logging.debug(f"Launchpad X MIDI handler => {ev}")
        evtype = (ev[0] >> 4) & 0x0F
        # Note ON => launch/stop sequence
        if evtype == 0x9:
            note = ev[1] & 0x7F
            vel = ev[2] & 0x7F
            if vel > 0:
                col, row = self.get_note_xy(note)
                pad = self.zynseq.get_pad_from_xy(col, row)
                if pad >= 0:
                    self.zynseq.libseq.togglePlayState(self.zynseq.bank, pad)
            return True
        # CC => arrows, scene change, stop all
        elif evtype == 0xB:
            ccnum = ev[1] & 0x7F
            ccval = ev[2] & 0x7F
            if ccval > 0:
                if ccnum == 0x5B:
                    self.state_manager.send_cuia("ARROW_UP")
                elif ccnum == 0x5C:
                    self.state_manager.send_cuia("ARROW_DOWN")
                elif ccnum == 0x5D:
                    self.state_manager.send_cuia("ARROW_LEFT")
                elif ccnum == 0x5E:
                    self.state_manager.send_cuia("ARROW_RIGHT")
                else:
                    col, row = self.get_note_xy(ccnum)
                    if col == 8:
                        if row < 7:
                            self.zynseq.select_bank(row + 1)
                        elif row == 7:
                            self.zynseq.libseq.stop()
            return True
        # SysEx
        elif ev[0] == 0xF0:
            logging.info(f"Received SysEx => {ev.hex(' ')}")
            return True

    # Light-Off LEDs
    def light_off(self):
        # logging.debug("Lighting Off LEDs Launchpad X")
        # Clean state of notes & CCs
        self.send_sysex("12 01 00 01")

    # Sleep On
    def sleep_on(self):
        # Sleep Mode (0 = sleep, 1 = awake)
        self.send_sysex("09 00")

    # Sleep On
    def sleep_off(self):
        # Sleep Mode (0 = sleep, 1 = awake)
        self.send_sysex("09 01")

# ------------------------------------------------------------------------------

In the end i would be fun if the blinking of an active scene could be sync to the beat.
And even by looking at zynthian_ctrldev_akai_apc_key25_mk2.py I have no clue how to dim sequences without content. If somebody else want to take a try, your are welcome.

1 Like

@LFO if I make you the first driver file, will you be able to put it in your Zynthian and then debug it ?
I think I can make the grid and the arrow working.

I’m happy to test, are you talking about fixing the launchpad pro driver specifically? That’s what I’ve got.

What version specifically?

It’s the 2015 launchpad pro. They call it a mk2 but it’s the ‘first’ one of the pro version.

Okay, you can give a try to the file attached
zynthian_ctrldev_launchpad_pro_mk2.py (6.7 KB)

If it does nothing, you will have to check this line first :
dev_ids = [“Launchpad Pro IN 1”]
After that it should work as mine (Lauchpad X). Please note that for now, only the buttons inside the red box should work :


Fell free to read “Launchpad Pro Programmers Reference Guide v1.01 English - EN.pdf” from the link you give me. Your device should be able to do a lot more. You may find how by reading “zynthian_ctrldev_akai_apc_key25_mk2.py” that look like to be the more complete “py”.

Ok great, I’ll give it a try over the weekend.

I found the reason my Launchpad wasn’t being picked up by the original driver and it’s because of that device ID line.

When I plug my launchpad pro into Zynthian it has the IDs:
launchpad pro 6 1
launchpad pro 6 2
launchpad pro 6 3

When I looked at the original driver file I realised it doesn’t have the ‘6’ in the device ID. When I modified the line to add the ‘6’ and rebooted, it worked! I was able to launch clips only, no bank control and no lights.

Anyway, I’ll try your driver and report back. Do you or @jofemodo know if there’s a way to connect when a Launchpad pro has a different number in the device ID?

Each driver has a list of the device IDs that it supports. When zynthian detects a USB MIDI device it checks which drivers support it’s device ID and offers them to be selected. This allows each device to be supported by several drivers and each driver to support several devices. Only one driver may be loaded for any device at one time.

2 Likes

In the bootloader mode you can change the ID in case you have multiple units. I don’t know how it reflect on the computer side.
For your 1-2-3, 1 should be the direct link to draw and 2 the pure midi controller. I don’t have a third one, it may be documented somewhere?
Ps : I have pushed the right file for Launchpad X on GitHub, I don’t know if I have anything else to do but yes, the lights doesn’t work on the actual one (and there Is minimum 7 others lines to change for the Launchpad family… If it is a success on your side,I may make all of them)

Thanks for the tip, I wasn’t aware of the bootloader device ID selection. Now I set it back to ‘1’ which makes the device ID: launchpad pro 1/2/3

There were a couple of adjustments for the driver to work, I fixed a typo in the class name and the sysex codes are different for this device compared to launchpad X I guess. It now plays and stops clips. They are unlit until there is a pattern in the clip then they light up. The arrow keys don’t appear to work yet (not changing banks).

Here’s the file with those couple of fixes if you want to keep working with this one:
zynthian_ctrldev_launchpad_pro_mk2.py (6.7 KB)

I can try and go further with it, I’m not very experienced with this stuff. Let’s see if I get anywhere…

1 Like

Damned, my light are always on even with empty patterns :frowning:
The arrows are navigation arrows, so in the vu-meters view you should be able to go right-left.
But yeah you can push the file into GitHub folder I guess.
I can’t do more without the device, but I think there is a great potential with the peripheral buttons.

I’m happy it was not so hard to fix! :smiley:

Hello, is it your last iteration ? I have no clue of why my light doesn’t turn off on empty patterns :frowning:

Actually I think the sysex on one of the entries (maybe the initialise/reset part) was incorrect so that left the first bank unlit until you update the tracks. But, if you change banks using the right side buttons then they all light up.

After I fixed all of the sysex they lit up as soon as the device boots up. So you want to keep this behaviour? I actually think it would be good if the pads were a bit dimmer and the active ones are brighter (not sure how that works in combination with the pulsing though).

I’m trying to make changes but as soon as I stuff something up my Zynthian won’t boot, webconf won’t start and it changes to hotspot mode. I connected directly via ethernet cable which gets around it but I have set myself up with vs code so I’ll have to learn all these things about git to make it easier to modify and make changes.

I’ve made a comparison of our files, and double-checked the 4 values changed… It seem right.
Dimmer could be cool, but “as it should be” a good start for me :slight_smile:

Is a pattern editor still on the roadmap at some point? I was looking at the …ctrldev_base.py file and the class (zyn_pated) is not defined.

Hi ? I see some potential for keyboard A-300Pro (and 500 and 800) with transport buttons and special Daw link.
… But don’t find other stuff that the classic manual. What keyword can I use to find more than that and prepare a driver ?

There is a section in the wiki:
https://wiki.zynthian.org/index.php/Contributing_to_Zynthian_Development
which includes 2.10How Do I start writing a ctrldev driver?

1 Like