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. 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.
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
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.
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()
################################################################################
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’
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.