Seeking help - Refactoring midi controller device drivers for Vangelis

As discussed, a number of drivers need attention in order to work with Vangelis. We need your help to bring more plug & play friendliness to this version. The only ones I know work are(Edit: add more):

  • APC40 (suggested to be used as reference driver for Vangelis)
  • Launchpad mk3 and soon (once I get to a pull request) launchpad pro mk2
  • APC Key 25 mk2 (maybe)
  • TE OP-1
  • NanoKontrol 2
Links to original driver dev guidance from Riban & Jofe

Zynbleton Vangelis - READ ME - #30 by riban
Zynbleton Vangelis - READ ME - #33 by jofemodo

I connected mine to my Vangelis machine a couple of days ago. It did not load the driver, and i could not figure out why (since the names where identical). Back from work now, i will give it another try.
(i really want to learn this driver stuff)

This one works fine in vangelis:

Teenage Engineering OP-1

Tested with V4.

I have found that checking the Webconf > UI Log > Debug log when loading/using the driver helps to find potential issues.

Best regards,
Bernard

My second PR is still pending approval.

2 Likes

AFAIK, this is the current status of migration for the ctrldev drivers:

Driver Name Vangelis Status
akai_apc_40_mk2 Fully Working
akai_apc_key25_mk2 Fully Working
akai_apc_key25_mk2_sooperlooper Fully Working
akai_apc_key25 Fully Working
akai_apc_key25_sooperlooper Fully Working
akai_mpk249 Fully Working
akai_midimix Fully Working
akai_mpk_mini_mk3_moder Fully Working
akai_mpk_mini_mk3 ???
arturia_keylab_61_mk2 ???
behringer_motor Fully Working
duopiano ???
fostex_mixtab ???
korg_nanokontrol2 Fully Working
launchkey_mini_mk3 Fully Working
launchkey_mini_mk4_37 Fully working
launchkey_mk4_37 ???
launchpad_mini_mk3 Fully Working
launchpad_mini Fully Working
launchpad_pro_mk2 ???
launchpad_pro_mk3 ???
launchpad_x ???
mackiecontrol Fully Working
riband ???
sinco_smk25_bt ???
sinco_smk25 ???
teenageengineering_op1 Fully Working
worlde_mini_moder Fully Working

Please, users with the targeted hardware, test the drivers and report on this thread and i will update the list as the migration goes forward.

Regards

2 Likes

Yeah José just merged the mentioned PR.

FYI this also fixes the sooperlooper ones. They needed no adjustment other than their parent classes working.

3 Likes

Thanks for all the work! :musical_notes: :keyboard: :musical_keyboard:

Nice! Then i will mark these as “Fully Working”.

Everyone who own the hardware, please, test and confirm!!

Regards

Both drivers for akai_apc_key25_mk2 available and seemingly working here. Thank you all!
EDIT, also Korg nanokontrol2 and o.c. Akai APC40MKII

1 Like

I’m struggling with one final (but important) part of the launchpad driver. When switching to note mode, I can’t get the notes to play through the active chain.

I’ve tried looking at the APC40 driver but I’m not even sure if this device has that functionality. It would be useful to have for other launchpad drivers too which appear to only function as launchers.

It should probably be something like below from APC40 driver which is the mute switch. I think the launchpad driver is failing with the chain information part (pos below?).

elif symbol == "mute": if mixbus and chan == 0: return # No control for main mixbus try: pos = self.chain_manager.get_pos_by_mixer_chan(chan, mixbus) - self.scroll_h if 0 <= pos < self.cols: lib_zyncore.dev_send_note_on(self.idev_out, pos, LED_ACTIVATOR, value) except TypeError: pass

Can anyone offer some advice?

Hi @LFO!

What “launcher” device is your target?

Don’t use this. You should never try to “send notes from the driver to the chains” using function calls:

  • First, notes are not going to be routed adequately and you would need to do bizarre things to receive these notes in your chains
  • Second, you would have high latency and jitter, as “normal” driver code is not real time code.

Ideally, the events reaching the chains should never leave the jack’s Real Time stream. You have 2 different ways to manage this:

  • The simple way is using the “unroute_from_chains” class var:

unroute_from_chains = 0b0000001000000000 # Unroute MIDI channel 10
or
unroute_from_chains = 0b0000000000001111" # Unroute MIDI channels 0 to 3.

This allow to set the MIDI channels you want to unroute from the chains when your driver is loaded, so the events on these MIDI channels only reach your driver. By default, ALL MIDI channels will be unrouted, so no events reach your chains.
You will find several examples in the current drivers. For instance, worlde_mini_moder.

  • The powerful (but more complex) way is using midiproc. Basically, the “midiproc_task” is a real-time process that is called by the Jack Audio daemon in real time It’s better explained in this thread;

and you will find some interesting examples in the drivers folder (base_moder) or inside the “examples” subfolder.

Cheers!

2 Likes

I see it is using mididings, what is the difference with midiproc?

Regards

Hi @jawn!

“midiproc” is a mechanism to “embed” a jack-client process in a ctrldev driver. It can spawn a mididings process or any other jack-client process.

Please, read this thread:

and the drivers in the example subfolder.

Indeed, you could program your jack-client process as a C library, although i didn’t test this yet.

Regards

1 Like

That sounds like a challenge gauntlet being thrown down! :grin:

:blush:
It’s something that should be tried …

1 Like

After reading in the other thread it looks like @brumby had the same problem with the push 1 as I’m having. That problem is that all modes output on midi channel 1. The launchpad driver has been setup with 4 modes similar to Oscar’s APC Key 25 driver.

I can’t see how the push 1 was resolved though. So can I send only grid pads’ note on/off messages via midiproc to midi channel 2 and leave everything else untouched on the (default) midi chan 1? Ideally, it only happens when note mode is active and switches off in the other modes.

Yes. This is the way i would go for your case. Something like this:

import logging
import multiprocessing as mp

# Zynthian specific modules
from zyngine.ctrldev.zynthian_ctrldev_base import zynthian_ctrldev_base


# ------------------------------------------------------------------------------------------------------------------
# A midiproc note filter that can be toggled from ctrldev driver code
# ------------------------------------------------------------------------------------------------------------------

class zynthian_ctrldev_your_driver_name(zynthian_ctrldev_base):

    dev_ids = ["YOUR_DEV_ID"]
    driver_description = "Your driver description"
    unroute_from_chains = False  # Route all MIDI channel to chains

    # IPC => multiprocessing.Value() object to share an integer variable across processes
    filter_enabled = mp.Value('i', 0)

    def midiproc_task(self, jackname):
        zynthian_ctrldev_base.midiproc_task_reset_signal_handlers()

        import jack
        import struct
        from threading import Event

        # First 4 bits of status byte:
        NOTEON = 0x9
        NOTEOFF = 0x8

        client = jack.Client(jackname)
        inport = client.midi_inports.register('in_1')
        outport = client.midi_outports.register('out_1')
        event = Event()

        @client.set_process_callback
        def process(frames):
            outport.clear_buffer()
            if self.filter_enabled.value:
               return
            for offset, indata in inport.incoming_midi_events():
                outport.write_midi_event(offset, indata)  # pass through
                # 3-bytes events
                if len(indata) == 3:
                    status, pitch, vel = struct.unpack('3B', indata)
                    # Note events in MIDI channel 1
                    if status >> 4 in (NOTEON, NOTEOFF) and (status & 0xF) == 0:
                        try:
                            outport.write_midi_event(offset, (status, pitch, vel))
                        except:
                            pass

        @client.set_shutdown_callback
        def shutdown(status, reason):
            logging.debug('JACK-CLIENT shutdown:', reason, status)
            event.set()

        with client:
            event.wait()

    def enable_note_filter(self):
        self.filter_enabled.value = 1

    def disable_note_filter(self):
        self.filter_enabled.value = 0

    def midi_event(self, ev):
        evtype = (ev[0] >> 4) & 0x0F
        evchan = ev[0] & 0x0F
        # Process midi events normally

        # [YOUR CODE]

        return False

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

I just added this to the examples folder.

Regards

1 Like

I gave it a try but it’s still outputting everything to chains.

I put the disable_note_filter into each mode switch area except for note mode which has the enable function.

There were a couple of things that vscode flagged like filter_enabled = which I set to 0 and the self.filter_enabled.value: changed to self.filter_enabled:

The jack import also was underlined as if it was not available.

I read the mididings docs a little but still not quite sure what to check next.

This fragment is obviously wrong in my code. It should be:

    def enable_note_filter(self):
        self.filter_enabled.value = 1

    def disable_note_filter(self):
        self.filter_enabled.value = 0

Regards