Autorepeat CUIA in a ctrl driver (work in progress)

Hello guys

Can someone explain how to turn an “if” statement into a “while” statement

lets say I have a button on a device that ,with help of the right ctrldriver sends a CUIA to zynthianOS

like this snippet of code:

            if ccnum == 0x5B:
                self.state_manager.send_cuia("ARROW_DOWN")

this CUIA is send only once at button press

I would like the CUIA to be send multiple times as long as the button is pressed
and stopped when the button is released

It must not be very hard  but my Pyton skills are very (very very) low

So if someone can help it would be nice

Thank You all

Alain

Hi @AL1-1956

I am also no Python expert. A missing part is how often it should be repeated, e.g. every 200ms. This helps to set the sleep time in the while loop.

Hi @AL1-1956. You can’t just put a while loop in here because that woul lock up the whole application, just servicing this one action. You need to use a timer thread to check whether the button has been released. Be aware though that this is a bit risky because you depend on definitely getting the CC off message.

You could do something like this:

import threading
import time

REPEAT_TIME = 0.3 # repeat in seconds

def init(self):
    ....
    self._stop_auto_repeat_event = threading.Event()
    self._auto_repeat_thread = None

def midi_event(self, ev):
     ...
    if ccnum == 0x5B:
        if ccval == 0 and self._auto_repeat_thread:
            self._stop_auto_repeat_event.set()
            if self._auto_repeat_thread:
                self._auto_repeat_thread.join()
        elif self._auto_repeat_thread and self._autot_repeat_thread.is_alive():
            self._stop_auto_repeat_event.clear()
            self._auto_repeat_thread = threading.Thread(target=self._auto_repeat, daemon=True)
            self._auto_repeat_thread.start()

def _auto_repeat(self):
    next_time = time.perf_counter()
    while not self._stop_event.is_set():
        self.state_manager.send_cuia("ARROW_DOWN")
        next_time += REPEAT_TIME
        time.sleep(max(0, next_time - time.perf_counter()))

I haven’t tested this and it has a bit of vibe coding but I tweaked to better fit zynthian so it should / may work.

A brief code explaination:

  • During init, a thread variable and a thread-safe state handler are defined.
  • When a CC with value=0 is received, if the thread exists, it is signalled to stop and then joined, i.e. the main code thread joins the thread until it ends.
  • When a CC with another value (>0) is received, the signal is cleared, and a new thread is created and started.
  • The thread runs _auto_repeat function that sends the CUIA every REPEAT_TIME (300ms) until it is signalled to stop.
1 Like

Hello riban

Thank you for your answer and the explaination.

I am trying to insert pieces of your code into the official LP mini MK3 ctrldriver

So I have now

import logging
from time import sleep
import threading
import time

from zynlibs.zynseq import zynseq
from zyncoder.zyncore import lib_zyncore
from zyngine.ctrldev.zynthian_ctrldev_base import zynthian_ctrldev_zynpad

class zynthian_ctrldev_launchpad_mini_mk3(zynthian_ctrldev_zynpad):

def __init__(self):
    self._stop_auto_repeat_event = threading.Event()
    self._auto_repeat_thread = None


    dev_ids = ["Launchpad Mini MK3 IN 1"]
    driver_description = "Zynpad + arrow keys integration"

    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


---------------------------------

and then the rest of the code unchanged

in the logs I get this error

ERROR:zynthian_ctrldev_manager.load_driver: 
Can't load ctrldev driver 'launchpad_mini_mk3' 
for 'Launchpad Mini MK3 IN 1' => 
zynthian_ctrldev_launchpad_mini_mk3.__init__() 
takes 1 positional argument but 4 were given

I merely understand what the __init__ method is used for 
but cannot find where the error is

Thank You

Alain




Sorry - I out the wrong function name. I have edited my code to correct this.

You have changed the init function definition. This must not be changed. You should add your code to the init function that is called from init.

The ‘__’ start of a python function has slightly different characteristics from vanilla functions, and the _init_ function is a informal equivalent of a constructor and as with all functions derived from another class by sub-classing ( sticking the parent name in the bracket of the class definition statement will allow functions in that parent class to be overridden by identically named functions in the sub class. . ..:– Inheritance. )

Super is used to provide a formal approach to doing this override trick to allow a similarly named function in the parent to be called, as well as any code you add around it.
In the early days of python this was a rather convoluted mess of underscores, inits and dots that simply called the superior class function. Super replaced that mess.

In summary you will need to call the super on the class above to make sure it does what it needs to do to construct the namespace environment your additions will run in.

Or if that all sounds like garbage . . . .

1 Like

Hello riban

I tried with the correction of error (init instead of __init__ )

I think there was another mistake

elif self._auto_repeat_thread and self._autot_repeat_thread.is_alive():

I guess it should be

elif self._auto_repeat_thread and self._auto_repeat_thread.is_alive():

There is no more error in logs but it does not work and there is no sign of CUIA for ARROW_DOWN in the logs

(other ARROWS still work)

I put here the driver I am testing so if you have a little time maybe you could check if I did it right

zynthian_ctrldev_launchpad_mini_mk3.py (8.4 KB)

Anyway , thank you for taking time to help me

I know you have a lot of other things to care with Vangelis OS

Regards

Alain

You don’t think they let me anywhere near that…?

I have spare time and love the sound of my own typing…

I see some issues with the code. (Maybe I should have checked more closely before posting… but who has time for that and you are there to learn!)

Some observations:
if self._auto_repeat_thread: is not required because it is already checked earlier.
In theory, if ccval >= 0: does nothing because ccval will always be positive.
Be sure that ccnum == 0x5C is true, e.g. add `logging.warning(f"ARROW CC: {ccval}") after it so that you see some logging when you press the button.

But here is the actual problem…

elif self._auto_repeat_thread and self._auto_repeat_thread.is_alive():
This is checking if the thead is running and then trying to start it! Well that is bonkers! (Stoopid AI!) Also the earlier condition is combined to break this. Code might look like this:

################################################################################
############ Autorepeat ARROW_DOWN
                elif ccnum == 0x5C:
                    if ccval == 0:
                        # Press
                        if self._auto_repeat_thread:
                            self._stop_auto_repeat_event.set()
                            self._auto_repeat_thread.join()
                            self._auto_repeat_thread = None
                    else:
                        # Release
                        if not self._auto_repeat_thread:
                            self._stop_auto_repeat_event.clear()
                            self._auto_repeat_thread = threading.Thread(target=self._auto_repeat, daemon=True)
                            self._auto_repeat_thread.start()                        
################################################################################

Good luck.

Okay

I have the new driver here

zynthian_ctrldev_launchpad_mini_mk3.py (8.5 KB)

When I press arrow down key ,I get in the logs

Exception in thread Thread-16 (_auto_repeat):
Traceback (most recent call last):

File “/usr/lib/python3.11/threading.py”, line 1038, in _bootstrap_inner
self.run()

File “/usr/lib/python3.11/threading.py”, line 975, in run
self._target(*self._args, **self._kwargs)

File “/zynthian/zynthian-ui/zyngine/ctrldev/zynthian_ctrldev_launchpad_mini_mk3.py”, line 211, in _auto_repeat
while not self._stop_event.is_set():
^^^^^^^^^^^^^^^^
AttributeError: ‘zynthian_ctrldev_launchpad_mini_mk3’ object has no attribute ‘_stop_event’

Thank You for reading and help

Alain

I think that should be while not self._stop_auto_repeat_event.is_set():.

Try to understand the code which will help you to diagnose these issues. The exception handler reports the cause and location of the issue.

OKay

Thanks A LOT :grinning_face: riban

It works great for down ARROW KEY

Now I will try to understand how it works

(without having to knock my head on walls with syntax)

and after making my baby steps on LP mini MK3 arrow keys

I am going to try to apply this new knowledge (with lot of help)

to buttons that can emulate zynpots CUIAs on LP Pro MK3

so that the value goes up or down as long as I push the button

I am very happy with people on this forum that help each other

Have a great day

and once again thank you

Alain

2 Likes

Not very elegant but it works for ARROW_DOWN and ARROW_UP

zynthian_ctrldev_launchpad_mini_mk3.py (9.9 KB)

I now must find a way to pass parameters (CCnum and CUIA) to methods

Anyway

Time to fill stomach and feed brain (what is left of it) :winking_face_with_tongue:

1 Like

I have renamed the topic for something more useful (I hope) for other people

2 Likes

Hello riban

I managed to have a working zynpot simulation on 4 pairs of the bottom buttons of the LaunchPAD pro MK3 (it is very bad coding but it works)

Here is the work in (very slow) progress driver with separate buttons for Short and Push Press

I know that there are (a lot of) repetitions but it is easier to see if something goes wrong with one function per button.

and I am not (for now) able to include parameters in functions (baby step , baby step….)

zynthian_ctrldev_launchpad_pro_mk3.py (25.1 KB)

I am now wondering if the threading system can be used for simulating zynswitches

I am thinking of something like:

I press a button for x seconds and release it and this simulates a Short Press

I press a button for y seconds (y > x) and release and this simulates a Bold Press

I press a button for z seconds (z>y) and release and this simulates a Long Press

No need. You just need to send a push event and a separate release event. Zynthian will turn these into short, bold and long press.

1 Like

There is a lot of repitition. Of course this is the right way to get something templated - make it work then optimise later. Some tips…

Where you have several lines of code that do the same thing on consecutive parameters, use a loop, e.g.

for i in range(1, 9):
    # Light-On Lower Thin Buttons Bar
    lib_zyncore.dev_send_ccontrol_change(self.idev_out, 0, i, 10)    
    # Light-On Upper Thin Buttons Bar    
    lib_zyncore.dev_send_ccontrol_change(self.idev_out, 0, 100 + i, 10)

I don’t think there is a need for the try/except handler in update_seq_state. I can’t see what error will be thrown by the enclosed code.

I wouldn’t have separate thread worker functions, each called from the same thread launcher. You could just use a variable to say which button is being repeated. The code currently only supports one button at a time. There could be an enhancement so to allow multiple held buttons to autorepeat, e.g. use a bitwise flag to indicate which buttons are pressed, repeat those buttons and end the thread when that flag is zero.

To use CUIA for switches with push and release events, you may do something like this:

# Handle button press, e.g. CC val > 0
self.state_manager.send_cuia("ZYNSWITCH", (switch_number, "P"))

# Handle button release, e.g. CC val == 0
self.state_manager.send_cuia("ZYNSWITCH", (switch_number, "R"))

Hopefully there is some useful stuff here. Good luck and have fun.

2 Likes

Hello riban

There IS useful stuff.

For the repetitions , at the moment I prefer to keep the choice of having different colors for some

buttons , but I take care of your advice

The option to have more than one autorepeat (zynpot)button could be useful in case of wanting to

,for example , open the filter while changing cutoff frequency of one synth , as long as parameters are

in the same group of 4 in the UI.

What I do not fully understand is

I suppose that it means that between the push and the release events

if the duration is less than 0,3 s it is seen by Zynthian as a Short

if the duration is between 0,3 and 2s it is seen by Zynthian as a Bold

if the duration is greater tha 2s it is seen by Zynthian as a Long

Am I right ?

Thank you a lot for you kind help

Alain

Yes!