From 061bc9a4757c48af58b6a51bbfbd99727f6d3e29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pavel=20Vond=C5=99i=C4=8Dka?= Date: Fri, 12 Jul 2024 22:39:54 +0200 Subject: [PATCH 001/117] V5touch initial commit. --- icons/metronome.png | Bin 0 -> 654 bytes icons/metronome.svg | 12 + zyngui/__init__.py | 3 +- zyngui/zynthian_gui.py | 25 +- zyngui/zynthian_gui_base.py | 18 +- zyngui/zynthian_gui_config.py | 3 + zyngui/zynthian_gui_confirm.py | 14 +- zyngui/zynthian_gui_control_xy.py | 8 +- zyngui/zynthian_gui_info.py | 8 +- zyngui/zynthian_gui_keyboard.py | 10 +- zyngui/zynthian_gui_loading.py | 6 +- zyngui/zynthian_gui_midi_key_range.py | 2 +- zyngui/zynthian_gui_splash.py | 4 + zyngui/zynthian_gui_touchkeypad_v5.py | 314 ++++++++++++++++++ .../zynthian_gui_touchscreen_calibration.py | 4 + zyngui/zynthian_widget_sooperlooper.py | 2 +- zyngui/zynthian_wsleds_v5touch.py | 64 ++++ 17 files changed, 463 insertions(+), 34 deletions(-) create mode 100644 icons/metronome.png create mode 100644 icons/metronome.svg create mode 100644 zyngui/zynthian_gui_touchkeypad_v5.py create mode 100644 zyngui/zynthian_wsleds_v5touch.py diff --git a/icons/metronome.png b/icons/metronome.png new file mode 100644 index 0000000000000000000000000000000000000000..51065c080187af0c4dd1240a3107b0a64b5a7b0a GIT binary patch literal 654 zcmV;90&)F`P)pF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H10vkz0 zK~y-6jg(Jo6hRQht9xd4B1RA94|w#TLQaB$_yPPRUcHElS^YK!yei_=i+B+{7zhX^ zQ4o20sH(O!8r@|| ze>EOInVrwAWqDb4^DN5<{0L-EW*1^n+(Zc7tSByqJYP#Gp_pB0s|4=f1wj4&TCHVp zpV0kSWRF0SRLst|p=<~M-S-!Dru12)B#FS+#awJbd2TUAxW8G{0lcyhru&rMow>AJ z3EdBuP-c-6aFNu}(dJ_A>___jbpgNG8k+$Fo9onV*&R+?Km@6fF{atcvaJZJC?L7QaU}o)xhzh=vs_Xz}saM z=J_LwF$j2X?mHHOq0@QVz#*{DsVnP^N`O6)(r|cNQb{1u{rD_SuXm+(<*;RfbCDAQ zCksE^6(CjB)*Q#nvRk|Ix`9$v4=jYj$dB{t$sLQ)V75KSVM^aPb!D?riTj7Tk@pw$ zg205p?V}z>Mt)jO33zEC z1jP8vn9?548L&Enq)RP&K_J2XXMp=C#@I3Gz(R17V% + + + + + + + + + + + diff --git a/zyngui/__init__.py b/zyngui/__init__.py index e77e1063e..6aaec38ac 100644 --- a/zyngui/__init__.py +++ b/zyngui/__init__.py @@ -32,7 +32,8 @@ "zynthian_gui_brightness_config", "zynthian_gui_cv_config", "zynthian_gui_wifi", - "zynthian_gui_bluetooth" + "zynthian_gui_bluetooth", + "zynthian_gui_touchkeypad_v5" ] import zyngui.zynthian_gui_config as zynthian_gui_config diff --git a/zyngui/zynthian_gui.py b/zyngui/zynthian_gui.py index 6877b8d6e..5de531fdb 100644 --- a/zyngui/zynthian_gui.py +++ b/zyngui/zynthian_gui.py @@ -89,6 +89,7 @@ from zyngui.zynthian_gui_wifi import zynthian_gui_wifi from zyngui.zynthian_gui_bluetooth import zynthian_gui_bluetooth from zyngui.zynthian_gui_control_test import zynthian_gui_control_test +from zyngui.zynthian_gui_touchkeypad_v5 import zynthian_gui_touchkeypad_v5 MIXER_MAIN_CHANNEL = 17 # TODO This constant should go somewhere else @@ -151,6 +152,13 @@ def __init__(self): self.capture_log_fname = None self.capture_ffmpeg_proc = None + # Init Touch keypad + self.touch_keypad = None + self.main_screen_column = 1 if zynthian_gui_config.touch_keypad_side_left else 0 + self.touch_keypad_side_width = 0 + self.touch_keypad_bottom_height = 0 + self.init_touchkeypad() + # Init LEDs self.wsleds = None self.init_wsleds() @@ -221,12 +229,27 @@ def write_capture_log(self, message): except Exception as e: logging.error("Can't write to capture log: {}".format(e)) + # --------------------------------------------------------------------------- + # Touch keypad + # --------------------------------------------------------------------------- + + def init_touchkeypad(self): + if zynthian_gui_config.touch_keypad == 'V5': + self.touch_keypad_side_width = zynthian_gui_config.display_height // 3 + self.touch_keypad_bottom_height = zynthian_gui_config.display_height // 6 + self.touch_keypad = zynthian_gui_touchkeypad_v5(zynthian_gui_config.top, side_width=self.touch_keypad_side_width, left_side=zynthian_gui_config.touch_keypad_side_left) + self.touch_keypad.show() + # --------------------------------------------------------------------------- # WSLeds Init # --------------------------------------------------------------------------- def init_wsleds(self): - if zynthian_gui_config.check_wiring_layout("Z2"): + if self.touch_keypad: + from zyngui.zynthian_wsleds_v5touch import zynthian_wsleds_v5touch + self.wsleds = zynthian_wsleds_v5touch(self) + self.wsleds.start() + elif zynthian_gui_config.check_wiring_layout("Z2"): from zyngui.zynthian_wsleds_z2 import zynthian_wsleds_z2 self.wsleds = zynthian_wsleds_z2(self) self.wsleds.start() diff --git a/zyngui/zynthian_gui_base.py b/zyngui/zynthian_gui_base.py index 51ca241c8..0db37f953 100644 --- a/zyngui/zynthian_gui_base.py +++ b/zyngui/zynthian_gui_base.py @@ -46,8 +46,8 @@ class zynthian_gui_base(tkinter.Frame): def __init__(self, has_backbutton = True): tkinter.Frame.__init__(self, zynthian_gui_config.top, - width=zynthian_gui_config.display_width, - height=zynthian_gui_config.display_height) + width=zynthian_gui_config.display_width - zynthian_gui_config.zyngui.touch_keypad_side_width, + height=zynthian_gui_config.display_height - zynthian_gui_config.zyngui.touch_keypad_bottom_height) self.grid_propagate(False) self.rowconfigure(1, weight=1) self.columnconfigure(0, weight=1) @@ -60,13 +60,14 @@ def __init__(self, has_backbutton = True): self.buttonbar_button = [] # Geometry vars - self.buttonbar_height = zynthian_gui_config.display_height // 7 - self.width = zynthian_gui_config.display_width + self.buttonbar_height = (zynthian_gui_config.display_height - zynthian_gui_config.zyngui.touch_keypad_bottom_height) // 7 + self.width = zynthian_gui_config.display_width - zynthian_gui_config.zyngui.touch_keypad_side_width #TODO: Views should use current height if they need dynamic changes else grow rows to fill main_frame if zynthian_gui_config.enable_onscreen_buttons and self.buttonbar_config: self.height = zynthian_gui_config.display_height - self.topbar_height - self.buttonbar_height else: self.height = zynthian_gui_config.display_height - self.topbar_height + self.height -= self.zyngui.touch_keypad_bottom_height # Status Area Parameters self.status_l = int(self.width * 0.25) @@ -84,7 +85,7 @@ def __init__(self, has_backbutton = True): self.backbutton_height = 0 # Title Area parameters - self.title_canvas_width = zynthian_gui_config.display_width - self.backbutton_width - self.status_l - self.status_lpad - 2 + self.title_canvas_width = self.width - self.backbutton_width - self.status_l - self.status_lpad - 2 self.select_path_font = tkFont.Font(family=zynthian_gui_config.font_topbar[0], size=zynthian_gui_config.font_topbar[1]) self.select_path_width = 0 self.select_path_offset = 0 @@ -97,7 +98,7 @@ def __init__(self, has_backbutton = True): # Topbar's frame self.tb_frame = tkinter.Frame(self, - width=zynthian_gui_config.display_width, + width=self.width, height=self.topbar_height, bg=zynthian_gui_config.color_bg) self.tb_frame.grid_propagate(False) @@ -253,7 +254,7 @@ def init_buttonbar(self, config=None): return self.buttonbar_frame = tkinter.Frame(self, - width=zynthian_gui_config.display_width, + width=self.width, height=self.buttonbar_height, bg=zynthian_gui_config.color_bg) self.buttonbar_frame.grid(row=2, padx=(0,0), pady=(0,0)) @@ -401,7 +402,7 @@ def show(self): logging.warning("TEST_MODE: {}".format(self.__class__.__module__)) self.shown = True self.refresh_status() - self.grid(row=0, column=0, sticky='nsew') + self.grid(row=0, column=self.zyngui.main_screen_column, sticky='nsew') self.propagate(False) self.main_frame.focus() @@ -748,6 +749,7 @@ def update_layout(self): self.height = zynthian_gui_config.display_height - self.topbar_height - self.buttonbar_height else: self.height = zynthian_gui_config.display_height - self.topbar_height + self.height -= self.zyngui.touch_keypad_bottom_height # Function to enable the top-bar parameter editor # engine: Object to recieve send_controller_value callback diff --git a/zyngui/zynthian_gui_config.py b/zyngui/zynthian_gui_config.py index c65bf26a0..26eeb4617 100644 --- a/zyngui/zynthian_gui_config.py +++ b/zyngui/zynthian_gui_config.py @@ -515,6 +515,9 @@ def get_external_storage_dirs(exdpath): control_test_enabled = int(os.environ.get('ZYNTHIAN_UI_CONTROL_TEST_ENABLED', 0)) power_save_secs = 60 * int(os.environ.get('ZYNTHIAN_UI_POWER_SAVE_MINUTES', 60)) +touch_keypad = os.environ.get('ZYNTHIAN_TOUCH_KEYPAD', "") +touch_keypad_side_left = int(os.environ.get('ZYNTHIAN_TOUCH_KEYPAD_SIDE_LEFT', True)) + # ------------------------------------------------------------------------------ # Audio Options # ------------------------------------------------------------------------------ diff --git a/zyngui/zynthian_gui_confirm.py b/zyngui/zynthian_gui_confirm.py index b701301ec..ca9fdd3a6 100644 --- a/zyngui/zynthian_gui_confirm.py +++ b/zyngui/zynthian_gui_confirm.py @@ -43,18 +43,20 @@ def __init__(self): self.callback = None self.callback_params = None self.zyngui = zynthian_gui_config.zyngui + self.width = zynthian_gui_config.display_width - zynthian_gui_config.zyngui.touch_keypad_side_width + self.height = zynthian_gui_config.display_height - zynthian_gui_config.zyngui.touch_keypad_bottom_height # Main Frame self.main_frame = tkinter.Frame(zynthian_gui_config.top, - width = zynthian_gui_config.display_width, - height = zynthian_gui_config.display_height, + width = self.width, + height = self.height, bg = zynthian_gui_config.color_bg) self.text = tkinter.StringVar() self.label_text = tkinter.Label(self.main_frame, font=(zynthian_gui_config.font_family,zynthian_gui_config.font_size,"normal"), textvariable=self.text, - wraplength=zynthian_gui_config.display_width-zynthian_gui_config.font_size*2, + wraplength=self.width-zynthian_gui_config.font_size*2, justify=tkinter.LEFT, padx=zynthian_gui_config.font_size, pady=zynthian_gui_config.font_size, @@ -72,7 +74,7 @@ def __init__(self): bg=zynthian_gui_config.color_ctrl_bg_off, fg=zynthian_gui_config.color_tx) self.yes_text_label.bind("",self.cb_yes_push) - self.yes_text_label.place(x=zynthian_gui_config.display_width, y=zynthian_gui_config.display_height, anchor=tkinter.SE) + self.yes_text_label.place(x=self.width, y=self.height, anchor=tkinter.SE) self.no_text_label=tkinter.Label(self.main_frame, font=(zynthian_gui_config.font_family,zynthian_gui_config.font_size*2,"normal"), @@ -84,7 +86,7 @@ def __init__(self): bg=zynthian_gui_config.color_ctrl_bg_off, fg=zynthian_gui_config.color_tx) self.no_text_label.bind("",self.cb_no_push) - self.no_text_label.place(x=0, y=zynthian_gui_config.display_height, anchor=tkinter.SW) + self.no_text_label.place(x=0, y=self.height, anchor=tkinter.SW) def hide(self): @@ -104,7 +106,7 @@ def show(self, text, callback=None, cb_params=None): self.callback_params = cb_params if not self.shown: self.shown=True - self.main_frame.grid() + self.main_frame.grid(row=0, column=self.zyngui.main_screen_column) def zynpot_cb(self, i, dval): diff --git a/zyngui/zynthian_gui_control_xy.py b/zyngui/zynthian_gui_control_xy.py index 8e2b192f2..c73ca58c3 100644 --- a/zyngui/zynthian_gui_control_xy.py +++ b/zyngui/zynthian_gui_control_xy.py @@ -49,7 +49,7 @@ def __init__(self): # Init X vars self.padx = 24 - self.width = zynthian_gui_config.display_width - 2 * self.padx + self.width = zynthian_gui_config.display_width - zynthian_gui_config.zyngui.touch_keypad_side_width - 2 * self.padx self.x = self.width / 2 self.x_zctrl = None self.xvalue = 64 @@ -65,8 +65,8 @@ def __init__(self): # Main Frame self.main_frame = tkinter.Frame(zynthian_gui_config.top, - width=zynthian_gui_config.display_width, - height=zynthian_gui_config.display_height, + width=zynthian_gui_config.display_width - zynthian_gui_config.zyngui.touch_keypad_side_width, + height=zynthian_gui_config.display_height - zynthian_gui_config.zyngui.touch_keypad_bottom_height, bg=zynthian_gui_config.color_panel_bg) # Create Canvas @@ -105,7 +105,7 @@ def show(self): if self.zyngui.test_mode: logging.warning("TEST_MODE: {}".format(self.__class__.__module__)) self.shown= True - self.main_frame.grid() + self.main_frame.grid(row=0, column=self.zyngui.main_screen_column) self.refresh() diff --git a/zyngui/zynthian_gui_info.py b/zyngui/zynthian_gui_info.py index ff63f9faa..5f96dc2dc 100644 --- a/zyngui/zynthian_gui_info.py +++ b/zyngui/zynthian_gui_info.py @@ -42,13 +42,13 @@ def __init__(self): # Main Frame self.main_frame = tkinter.Frame(zynthian_gui_config.top, - width=zynthian_gui_config.display_width, - height=zynthian_gui_config.display_height, + width=zynthian_gui_config.display_width - zynthian_gui_config.zyngui.touch_keypad_side_width, + height=zynthian_gui_config.display_height - zynthian_gui_config.zyngui.touch_keypad_bottom_height, bg=zynthian_gui_config.color_bg) #Textarea self.textarea = tkinter.Text(self.main_frame, - height=int(zynthian_gui_config.display_height/(zynthian_gui_config.font_size + 8)), + height=int((zynthian_gui_config.display_height - zynthian_gui_config.zyngui.touch_keypad_bottom_height)/(zynthian_gui_config.font_size + 8)), font=(zynthian_gui_config.font_family, zynthian_gui_config.font_size, "normal"), #font=("sans-serif", zynthian_gui_config.font_size, "normal"), #wraplength=80, @@ -90,7 +90,7 @@ def show(self, text): self.set(text) if not self.shown: self.shown = True - self.main_frame.grid() + self.main_frame.grid(row=0, column=self.zyngui.main_screen_column) def zynpot_cb(self, i, dval): return True diff --git a/zyngui/zynthian_gui_keyboard.py b/zyngui/zynthian_gui_keyboard.py index ac69d4364..4bdd3bf10 100644 --- a/zyngui/zynthian_gui_keyboard.py +++ b/zyngui/zynthian_gui_keyboard.py @@ -58,16 +58,16 @@ def __init__(self): self.ctrl_order = zynthian_gui_config.layout['ctrl_order'] # Geometry vars - self.width = zynthian_gui_config.display_width - self.height = zynthian_gui_config.display_height - zynthian_gui_config.topbar_height + self.width = zynthian_gui_config.display_width - zynthian_gui_config.zyngui.touch_keypad_side_width + self.height = zynthian_gui_config.display_height - zynthian_gui_config.topbar_height - zynthian_gui_config.zyngui.touch_keypad_bottom_height # Fonts self.font_button = (zynthian_gui_config.font_family, int(1.2*zynthian_gui_config.font_size)) # Create main frame self.main_frame = tkinter.Frame(zynthian_gui_config.top, - width=zynthian_gui_config.display_width, - height=zynthian_gui_config.display_height, + width=zynthian_gui_config.display_width - zynthian_gui_config.zyngui.touch_keypad_side_width, + height=zynthian_gui_config.display_height - zynthian_gui_config.zyngui.touch_keypad_bottom_height, bg=zynthian_gui_config.color_bg) self.main_frame.grid_propagate(False) @@ -292,7 +292,7 @@ def show(self, function, text="", max_len=None): self.highlight(self.selected_button) self.setup_zynpots() self.refresh_keys() - self.main_frame.grid() + self.main_frame.grid(row=0, column=self.zyngui.main_screen_column) self.shown = True # Function to register encoders diff --git a/zyngui/zynthian_gui_loading.py b/zyngui/zynthian_gui_loading.py index f55aa68e4..f4888b3cc 100644 --- a/zyngui/zynthian_gui_loading.py +++ b/zyngui/zynthian_gui_loading.py @@ -39,8 +39,8 @@ class zynthian_gui_loading: def __init__(self): self.shown = False self.zyngui = zynthian_gui_config.zyngui - self.width = zynthian_gui_config.display_width - self.height = zynthian_gui_config.display_height + self.width = zynthian_gui_config.display_width - zynthian_gui_config.zyngui.touch_keypad_side_width + self.height = zynthian_gui_config.display_height - zynthian_gui_config.zyngui.touch_keypad_bottom_height # Canvas for loading image animation self.canvas = tkinter.Canvas( zynthian_gui_config.top, @@ -80,7 +80,7 @@ def hide(self): def show(self): if not self.shown: self.shown = True - self.canvas.grid() + self.canvas.grid(row=0, column=self.zyngui.main_screen_column) def set_error(self, txt): self.set_title(txt, zynthian_gui_config.color_error) diff --git a/zyngui/zynthian_gui_midi_key_range.py b/zyngui/zynthian_gui_midi_key_range.py index 6660faa79..b70782915 100644 --- a/zyngui/zynthian_gui_midi_key_range.py +++ b/zyngui/zynthian_gui_midi_key_range.py @@ -66,7 +66,7 @@ def __init__(self): bg=zynthian_gui_config.color_panel_bg, bd=0, highlightthickness=0) - self.piano_canvas_width = zynthian_gui_config.display_width + self.piano_canvas_width = zynthian_gui_config.display_width - zynthian_gui_config.zyngui.touch_keypad_side_width self.piano_canvas_height = self.height // 4 self.main_frame.rowconfigure(2, weight=1) diff --git a/zyngui/zynthian_gui_splash.py b/zyngui/zynthian_gui_splash.py index 1d982fd27..93d4ab021 100644 --- a/zyngui/zynthian_gui_splash.py +++ b/zyngui/zynthian_gui_splash.py @@ -56,6 +56,8 @@ def hide(self): if self.shown: self.shown = False self.canvas.grid_forget() + if self.zyngui.touch_keypad: + self.zyngui.touch_keypad.show() def show(self, text): @@ -79,6 +81,8 @@ def show(self, text): except: pass if not self.shown: + if self.zyngui.touch_keypad: + self.zyngui.touch_keypad.hide() self.shown = True self.canvas.grid() diff --git a/zyngui/zynthian_gui_touchkeypad_v5.py b/zyngui/zynthian_gui_touchkeypad_v5.py new file mode 100644 index 000000000..6e0f83d41 --- /dev/null +++ b/zyngui/zynthian_gui_touchkeypad_v5.py @@ -0,0 +1,314 @@ + +import tkinter +from PIL import Image, ImageTk #, ImageDraw, ImageFont +from io import BytesIO +try: + import cairosvg +except: + cairosvg = None +# Zynthian specific modules +from zyngui import zynthian_gui_config + +BUTTONS = { + # primary label, ZYNSWITCH number, wsLED number, alternate labels, two state button (not used) + 'OPT_ADMIN': ('OPT/ADMIN', 4, 0, {}, True), + 'MIX_LEVEL': ('MIX/LEVEL', 5, 1, {}, True), + 'CTRL_PRESET': ('CTRL/PRESET', 6, 2, {}, True), + 'ZS3_SHOT': ('ZS3/SHOT', 7, 3, {}, True), + 'METRONOME': ('_icons/metronome.svg', 9, 6, {}, False), + 'PAD_STEP': ('PAD/STEP', 10, 5, {}, True), + 'ALT': ('Alt', 8, 4, {}, False), + + 'REC': ('\uf111', 12, 8, {}, False), + 'STOP': ('\uf04d', 13, 9, {}, False), + 'PLAY': ('\uf04b', 14, 10, {'active': '\uf04c'}, False), + + 'UP': ('\uf077', 17, 14, {}, False), + 'DOWN': ('\uf078', 21, 17, {}, False), + 'LEFT': ('\uf053', 20, 16, {}, False), + 'RIGHT': ('\uf054', 22, 18, {}, False), + 'SEL_YES': ('SEL/YES', 18, 13, {}, False), + 'BACK_NO': ('BACK/NO', 16, 15, {}, False), + + 'F1': ('F1', 11, 7, {'alt': 'F5'}, False), + 'F2': ('F2', 15, 11, {'alt': 'F6'}, False), + 'F3': ('F3', 19, 12, {'alt': 'F7'}, False), + 'F4': ('F4', 23, 19, {'alt': 'F8'}, False) +} + +LED2BUTTON = {btn[2]: btn[1]-4 for btn in BUTTONS.values()} + +LAYOUT_RIGHT = { + 'SIDE' : ( + ('OPT_ADMIN', 'MIX_LEVEL'), + ('CTRL_PRESET', 'ZS3_SHOT'), + ('METRONOME', 'PAD_STEP'), + ('BACK_NO', 'SEL_YES'), + ('UP', 'ALT'), + ('DOWN', 'RIGHT') + ), + 'BOTTOM': ('F1', 'F2', 'F3', 'F4', 'REC', 'STOP', 'PLAY', 'LEFT') +} + +LAYOUT_LEFT = { + 'SIDE' : ( + ('OPT_ADMIN', 'MIX_LEVEL'), + ('CTRL_PRESET', 'ZS3_SHOT'), + ('METRONOME', 'PAD_STEP'), + ('BACK_NO', 'SEL_YES'), + ('ALT', 'UP'), + ('LEFT', 'DOWN') + ), + 'BOTTOM': ('RIGHT', 'REC', 'STOP', 'PLAY', 'F1', 'F2', 'F3', 'F4') +} + + +class zynthian_gui_touchkeypad_v5: + + def __init__(self, parent, side_width, left_side=True): + """ + Parameters + ---------- + parent : tkinter widget + Parent widget + side_width : int + Width of the side panel: base for the geometry + left_side : bool + Left or right side layout for the side frame + """ + self.shown = False + self.side_frame_width = side_width + self.bottom_frame_width = zynthian_gui_config.display_width - self.side_frame_width + self.side_frame_col = 0 if left_side else 1 + self.bottom_frame_col = 1 if left_side else 0 + self.font_size = zynthian_gui_config.font_size + # only needed for composition of images from text labels + # self.font1 = ImageFont.truetype("Audiowide-Regular.ttf", size=int(1.4*self.font_size)) # zynthian_gui_config.font_buttonbar[0], size=zynthian_gui_config.font_buttonbar[1]) + # self.font2 = ImageFont.truetype("Audiowide-Regular.ttf", size=int(1.1*self.font_size)) + + # configure side frame for 2x6 buttons + self.side_frame = tkinter.Frame(parent, + width=self.side_frame_width, + height=zynthian_gui_config.display_height, + bg=zynthian_gui_config.color_bg) + for column in range(2): + self.side_frame.columnconfigure(column, weight=1) + for row in range(6): + self.side_frame.rowconfigure(row, weight=1) + + # 2 columns by 6 buttons at the full diplay height and requested side frame width + self.side_button_width = self.side_frame_width // 2 + self.side_button_height = zynthian_gui_config.display_height // 6 + + # configure bottom frame for a single row of 8 buttons + self.bottom_frame = tkinter.Frame(parent, + width=self.bottom_frame_width, + # the height must correspond to the height of buttons in the side frame + height=zynthian_gui_config.display_height // 6, + bg=zynthian_gui_config.color_bg) + for column in range(8): + self.bottom_frame.columnconfigure(column, weight=1) + self.bottom_frame.rowconfigure(0, weight=1) + + # select layout as requested + layout = LAYOUT_LEFT if left_side else LAYOUT_RIGHT + + # buffers to remember the buttons and their contents and state + self.buttons = [None] * 20 # actual button widgets + self.btndefs = [None] * 20 # original definition of the button parameters + self.images = [None] * 20 # original image/icon used (if any) + self.btnstate = [None] * 20 # last state of the button (<=color) + self.tkimages = [None] * 20 # current image in tkinter format (remember it or the image will be discarded by the garbage collector!) + + # create side frame buttons + for row in range(6): + for col in range(2): + btn = BUTTONS[layout['SIDE'][row][col]] + zynswitch = btn[1] + n = zynswitch - 4 + label = btn[0] + pady = (1,0) if row == 5 else (0,0) if row == 4 else (0,1) + padx = (0,1) if left_side else (1,0) + self.btndefs[n] = btn + self.buttons[n] = self.add_button(n, self.side_frame, row, col, zynswitch, label, padx, pady) + # create bottom frame buttons + for col in range(8): + btn = BUTTONS[layout['BOTTOM'][col]] + zynswitch = btn[1] + n = zynswitch - 4 + label = btn[0] + padx = (0,0) if col == 7 else (0,1) + self.btndefs[n] = btn + self.buttons[n] = self.add_button(n, self.bottom_frame, 0, col, zynswitch, label, padx, (1,0)) + + + def add_button(self, n, parent, row, column, zynswitch, label, padx, pady): + """ + Create button + + Parameters: + ----------- + n : int + Number of the button + parent : tkinter widget + Parent widget + row : int + column : int + Position of the button in the grid + zynswitch : int + Number of the zynswitch to emulate + label : str + Default label for the button + padx : (int, int) + pady : (int, int) + Button padding + """ + button = tkinter.Button( + parent, + width= 1, # if not label.startswith('_') else self.side_button_width, + height= 1, # if not label.startswith('_') else self.side_button_height, + bg=zynthian_gui_config.color_bg, + fg=zynthian_gui_config.color_header_tx, + activebackground=zynthian_gui_config.color_panel_bg, + activeforeground=zynthian_gui_config.color_header_tx, + highlightbackground=zynthian_gui_config.color_panel_bg, + highlightcolor=zynthian_gui_config.color_bg, + highlightthickness=1, + bd=0, + relief='flat') + # set default button state (<=color) + self.btnstate[n] = zynthian_gui_config.color_header_tx + if label.startswith('_'): + # button contains an icon/image instead of a label + img_name = label[1:] + if img_name.endswith('.svg'): + # convert SVG icon into PNG of appropriate size + if cairosvg: + png = BytesIO() + cairosvg.svg2png(url=img_name, write_to=png, output_width=int(1.2*zynthian_gui_config.font_size)) + else: + png = img_name[:-4]+".png" + image = Image.open(png) + else: + # PNG icons can be imported directly + image = Image.open(img_name) + # at least fo image icons, it is useful to store the original image for the purpose of later changes of color + self.images[n] = image + tkimage = ImageTk.PhotoImage(image) + # if we don't keep the image in the object, it will be discarded by garbage collection at the end of this method! + self.tkimages[n] = tkimage + button.config(image=tkimage, text='') + else: + # if "/" in label: + # # two level/lines text label - drawn as image + # line1, line2 = label.split('/') + # image = self.text2image(line1, line2, 'white', 'white', 'white') + # self.images[n] = image + # tkimage = ImageTk.PhotoImage(image) + # self.tkimages[n] = tkimage + # button.config(image=tkimage, text='') + # else: + # button has a simple text label: either standard text or an icon included in the "forkawesome" font (unicode char >= \uf000) + button.config(font=("forkawesome", int(1*zynthian_gui_config.font_size)) if label[0] >= '\uf000' else (zynthian_gui_config.font_family, int(0.9*zynthian_gui_config.font_size)), + text=label.replace('/', "\n")) + button.grid_propagate(False) + button.grid(row=row, column=column, sticky='nswe', padx=padx, pady=pady) + button.bind('', lambda e: self.cb_button_push(zynswitch, e)) + button.bind('', lambda e: self.cb_button_release(zynswitch, e)) + return button + + # def text2image(self, line1, line2, color, color1, color2): + # """ + # Create image from two lines of labels with a horizontal bar (line) inbetween + # """ + # sizex = self.side_button_width - 4 + # sizey = self.side_button_height - 4 # int(5*self.font_size) + # image = Image.new("RGBA", (sizex, sizey), "#00000000") + # d = ImageDraw.Draw(image) + # x = sizex // 2 + # d.text((x, (sizey//2)-(self.font_size//1.5)), line1, font=self.font1, fill=color1, anchor="ms") + # d.text((x, (sizey//2)+(self.font_size//1.5)), line2, font=self.font2, fill=color2, anchor="mt") + # d.line([(self.font_size//2,sizey//2), (sizex-(self.font_size//2),sizey//2)], fill=color, width=2) + # return image + + def cb_button_push(self, n, event): + """ + Call ZYNSWITCH Push CUIA on button push + """ + zynthian_gui_config.zyngui.cuia_queue.put_nowait(f"zynswitch {n},P") + + def cb_button_release(self, n, event): + """ + Call ZYNSWITCH Release CUIA on button release + """ + zynthian_gui_config.zyngui.cuia_queue.put_nowait(f"zynswitch {n},R") + + def set_button_color(self, led_num, color, mode): + """ + Change color of a button according to the wsleds signal + + Parameters + ---------- + + led_num : int + Number of the RGB wsled corresponding to the button + color : int + Color requested by the wsled system + mode : str + A wanna-be abstraction (string name) of the mode/state - currently just derived from the requested color + by the `wsleds_v5touch` "fake NeoPixel" emulator + """ + # get the button number associated with the wsled number + n = LED2BUTTON[led_num] + # don't bother with update if nothing has really changed (redrawing images causes visible blinking!) + if self.btnstate[n] == color: + return + self.btnstate[n] = color + # in case the color is still the original wsled integer number, convert it + if isinstance(color, int): + color = f"#{color:06x}" # conversion to the hex code of the color + # color = ((color >> 16) & 255, (color >> 8) & 255, color & 255) # conversion to (r, g, b) tuple + label = self.btndefs[n][0] + # twostate = self.btndefs[n][4] + if label.startswith('_'): # self.buttons[n].config()['image'][4]=='': + # image buttons must be recomposed to change the foreground color + image = self.images[n] + mask = image.convert("LA") + bgimage = Image.new("RGBA", image.size, color) + fgimage = Image.new("RGBA", image.size, (0,0,0,0)) + composed = Image.composite(bgimage, fgimage, mask) + tkimage = ImageTk.PhotoImage(composed) + self.tkimages[n] = tkimage + self.buttons[n].config(image=tkimage) + # elif '/' in label: + # # two level button labels with multiple colors must redraw the whole image + # line1, line2 = label.split('/') + # color0 = color1 = color2 = color + # if twostate and zynthian_gui_config.zyngui: + # color0 = zynthian_gui_config.zyngui.wsleds.wscolor_default + # color1 = zynthian_gui_config.zyngui.wsleds.wscolor_active if mode == 'active' else zynthian_gui_config.zyngui.wsleds.wscolor_default + # color2 = zynthian_gui_config.zyngui.wsleds.wscolor_active2 if mode == 'active2' else zynthian_gui_config.zyngui.wsleds.wscolor_default + # image = self.text2image(line1, line2, color0, color1, color2) + # self.images[n] = image + # tkimage = ImageTk.PhotoImage(image) + # self.tkimages[n] = tkimage + # self.buttons[n].config(image=tkimage, text='') + else: + # plain text labels may just change the color and possibly also its label if a special label + # is associated with the requested mode (<=color) in the button definition + text = self.btndefs[n][3].get(mode, self.btndefs[n][0]).replace('/', "\n") + self.buttons[n].config(fg=color, text=text) + + def show(self): + if not self.shown: + self.side_frame.grid_propagate(False) + self.side_frame.grid(row=0, column=self.side_frame_col, rowspan=2, sticky="nws") + self.bottom_frame.grid_propagate(False) + self.bottom_frame.grid(row=1, column=self.bottom_frame_col, sticky="wse") + self.shown = True + + def hide(self): + if self.shown: + self.side_frame.grid_remove() + self.bottom_frame.grid_remove() + self.shown = False diff --git a/zyngui/zynthian_gui_touchscreen_calibration.py b/zyngui/zynthian_gui_touchscreen_calibration.py index a365d0a94..59051c500 100644 --- a/zyngui/zynthian_gui_touchscreen_calibration.py +++ b/zyngui/zynthian_gui_touchscreen_calibration.py @@ -394,6 +394,8 @@ def hide(self): self.setCalibration(self.device_id, self.ctm) self.main_frame.grid_forget() self.shown=False + if self.zyngui.touch_keypad: + self.zyngui.touch_keypad.show() # Build display def build_view(self): @@ -413,6 +415,8 @@ def build_view(self): # Show display def show(self): + if self.zyngui.touch_keypad: + self.zyngui.touch_keypad.hide() self.main_frame.grid() self.onTimer() self.detect_thread = Thread(target=self.detectDevice, args=(), daemon=True) diff --git a/zyngui/zynthian_widget_sooperlooper.py b/zyngui/zynthian_widget_sooperlooper.py index a1fb8cb11..f44f51375 100644 --- a/zyngui/zynthian_widget_sooperlooper.py +++ b/zyngui/zynthian_widget_sooperlooper.py @@ -62,7 +62,7 @@ def __init__(self, parent): self.font_size_sl = int(0.7 * zynthian_gui_config.font_size) self.tri_size = int(0.5 * zynthian_gui_config.font_size) - txt_y = zynthian_gui_config.display_height // 22 #int(0.70 * self.font_size_sl) + txt_y = (zynthian_gui_config.display_height - zynthian_gui_config.zyngui.touch_keypad_bottom_height) // 22 #int(0.70 * self.font_size_sl) self.txt_x = 4 self.pos_canvas = [] diff --git a/zyngui/zynthian_wsleds_v5touch.py b/zyngui/zynthian_wsleds_v5touch.py new file mode 100644 index 000000000..cc1e9fca9 --- /dev/null +++ b/zyngui/zynthian_wsleds_v5touch.py @@ -0,0 +1,64 @@ +import os +from zyngui import zynthian_gui_config +from zyngui.zynthian_wsleds_v5 import zynthian_wsleds_v5 + + +class touchkeypad_button_colors: + """ + Fake NeoPixel emulation to change colors of onscreen touch keypad (instead of a wsled strip) + """ + + def __init__(self, wsleds): + self.zyngui = wsleds.zyngui + # A wanna-be abstraction: reverse derive a named "mode" from the requested colors + self.mode_map = {} + self.mode_map[wsleds.wscolor_alt] = 'alt' + self.mode_map[wsleds.wscolor_active] = 'active' + self.mode_map[wsleds.wscolor_active2] = 'active2' + + def __setitem__(self, index, value): + mode = self.mode_map.get(value, None) + # request color change on the onscreen touchkeypad + self.zyngui.touch_keypad.set_button_color(index, value, mode) + + def show(self): + # nothing to do here + pass + + +class zynthian_wsleds_v5touch(zynthian_wsleds_v5): + """ + Emulation of wsleds for onscreen touch keypad V5 + """ + + def start(self): + self.wsleds = touchkeypad_button_colors(self) + self.light_on_all() + + def setup_colors(self): + # Predefined colors + self.wscolor_off = zynthian_gui_config.color_panel_bg + self.wscolor_white = os.environ.get('ZYNTHIAN_TOUCH_KEYPAD_COLOR_WHITE', "#FCFCFC") + self.wscolor_red = os.environ.get('ZYNTHIAN_TOUCH_KEYPAD_COLOR_RED', "#FE2C2F") # #FF8A92 + self.wscolor_green = os.environ.get('ZYNTHIAN_TOUCH_KEYPAD_COLOR_GREEN', "#00FA00") + self.wscolor_yellow = os.environ.get('ZYNTHIAN_TOUCH_KEYPAD_COLOR_YELLOW', "#F0EA00") + self.wscolor_orange = os.environ.get('ZYNTHIAN_TOUCH_KEYPAD_COLOR_ORANGE', "#FF6A00") # #FFA200 + self.wscolor_blue = os.environ.get('ZYNTHIAN_TOUCH_KEYPAD_COLOR_BLUE', "#1070FE") # lighter: #5397FE, #38EBFF + self.wscolor_blue_light = os.environ.get('ZYNTHIAN_TOUCH_KEYPAD_COLOR_LIGHTBLUE', "#05FDFF") + self.wscolor_purple = os.environ.get('ZYNTHIAN_TOUCH_KEYPAD_COLOR_PURPLE', "#D662FE") # #FF80EB + self.wscolor_default = self.wscolor_blue + self.wscolor_alt = self.wscolor_purple + self.wscolor_active = self.wscolor_green + self.wscolor_active2 = self.wscolor_orange + self.wscolor_admin = self.wscolor_red + self.wscolor_low = os.environ.get('ZYNTHIAN_TOUCH_KEYPAD_COLOR_LOW', "#D9EB37") + # Color Codes + self.wscolors_dict = { + str(self.wscolor_off): "0", + str(self.wscolor_blue): "B", + str(self.wscolor_green): "G", + str(self.wscolor_red): "R", + str(self.wscolor_orange): "O", + str(self.wscolor_yellow): "Y", + str(self.wscolor_purple): "P" +} \ No newline at end of file From e91967034abe6910e284d0c3ca31bfeec5faeaa6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pavel=20Vond=C5=99i=C4=8Dka?= Date: Sun, 21 Jul 2024 20:55:08 +0100 Subject: [PATCH 002/117] V5touch: support wsleds brightness. --- zyngui/zynthian_gui_touchkeypad_v5.py | 3 --- zyngui/zynthian_wsleds_v5touch.py | 38 ++++++++++++++++++++++++--- 2 files changed, 34 insertions(+), 7 deletions(-) diff --git a/zyngui/zynthian_gui_touchkeypad_v5.py b/zyngui/zynthian_gui_touchkeypad_v5.py index 6e0f83d41..1993182e6 100644 --- a/zyngui/zynthian_gui_touchkeypad_v5.py +++ b/zyngui/zynthian_gui_touchkeypad_v5.py @@ -265,9 +265,6 @@ def set_button_color(self, led_num, color, mode): return self.btnstate[n] = color # in case the color is still the original wsled integer number, convert it - if isinstance(color, int): - color = f"#{color:06x}" # conversion to the hex code of the color - # color = ((color >> 16) & 255, (color >> 8) & 255, color & 255) # conversion to (r, g, b) tuple label = self.btndefs[n][0] # twostate = self.btndefs[n][4] if label.startswith('_'): # self.buttons[n].config()['image'][4]=='': diff --git a/zyngui/zynthian_wsleds_v5touch.py b/zyngui/zynthian_wsleds_v5touch.py index cc1e9fca9..679cf61d8 100644 --- a/zyngui/zynthian_wsleds_v5touch.py +++ b/zyngui/zynthian_wsleds_v5touch.py @@ -9,6 +9,7 @@ class touchkeypad_button_colors: """ def __init__(self, wsleds): + self.wsleds = wsleds self.zyngui = wsleds.zyngui # A wanna-be abstraction: reverse derive a named "mode" from the requested colors self.mode_map = {} @@ -16,15 +17,44 @@ def __init__(self, wsleds): self.mode_map[wsleds.wscolor_active] = 'active' self.mode_map[wsleds.wscolor_active2] = 'active2' - def __setitem__(self, index, value): - mode = self.mode_map.get(value, None) + def __setitem__(self, index, color): + mode = self.mode_map.get(color, None) # request color change on the onscreen touchkeypad - self.zyngui.touch_keypad.set_button_color(index, value, mode) + if isinstance(color, int): + color = f"#{color:06x}" # conversion to the hex code of the color + # color = ((color >> 16) & 255, (color >> 8) & 255, color & 255) # conversion to (r, g, b) tuple + # tkinter is not able to set RGBA/alpha color, we need to blend the foreground color with the backgroudn color + if zynthian_gui_config.zyngui: + fgcolor = self.hex_to_rgb(color) + bgcolor = self.hex_to_rgb(self.wsleds.wscolor_off) + blended = self.ablend(1-self.wsleds.brightness, fgcolor, bgcolor) + color = self.rgb_to_hex(blended) + self.zyngui.touch_keypad.set_button_color(index, color, mode) def show(self): # nothing to do here pass + def ablend(self, a, fg, bg): + """ + Bland foregground and background color to imitate alpha transparency + """ + return (int((1-a)*fg[0]+a*bg[0]), + int((1-a)*fg[1]+a*bg[1]), + int((1-a)*fg[2]+a*bg[2])) + + def hex_to_rgb(self, hexstr): + rgb = [] + hex = hexstr[1:] + for i in (0, 2, 4): + decimal = int(hex[i:i+2], 16) + rgb.append(decimal) + return tuple(rgb) + + def rgb_to_hex(self, rgb): + r, g, b = rgb + return '#{:02x}{:02x}{:02x}'.format(r, g, b) + class zynthian_wsleds_v5touch(zynthian_wsleds_v5): """ @@ -37,7 +67,7 @@ def start(self): def setup_colors(self): # Predefined colors - self.wscolor_off = zynthian_gui_config.color_panel_bg + self.wscolor_off = zynthian_gui_config.color_bg self.wscolor_white = os.environ.get('ZYNTHIAN_TOUCH_KEYPAD_COLOR_WHITE', "#FCFCFC") self.wscolor_red = os.environ.get('ZYNTHIAN_TOUCH_KEYPAD_COLOR_RED', "#FE2C2F") # #FF8A92 self.wscolor_green = os.environ.get('ZYNTHIAN_TOUCH_KEYPAD_COLOR_GREEN', "#00FA00") From 7a63ba09f4fc89e416cb3d02afb8b573f9b5f334 Mon Sep 17 00:00:00 2001 From: riban Date: Wed, 14 Aug 2024 17:58:17 +0100 Subject: [PATCH 003/117] Adds hotplug of USB audio inputs --- zynautoconnect/zynthian_autoconnect.py | 114 ++++++++++++++++++++++++- zyngine/zynthian_chain.py | 7 +- zyngui/zynthian_gui_admin.py | 38 +++++++++ zyngui/zynthian_gui_config.py | 1 + 4 files changed, 157 insertions(+), 3 deletions(-) diff --git a/zynautoconnect/zynthian_autoconnect.py b/zynautoconnect/zynthian_autoconnect.py index 8d33ae343..9187ba49e 100755 --- a/zynautoconnect/zynthian_autoconnect.py +++ b/zynautoconnect/zynthian_autoconnect.py @@ -28,10 +28,12 @@ import jack import logging import alsa_midi +import alsaaudio import json from time import sleep from threading import Thread, Lock from subprocess import check_output +import pexpect # Zynthian specific modules from zyncoder.zyncore import lib_zyncore @@ -72,6 +74,8 @@ hw_audio_dst_ports = [] # List of physical audio output ports sidechain_map = {} # Map of all audio target port names to use as sidechain inputs, indexed by jack client regex sidechain_ports = [] # List of currently active audio destination port names not to autoroute, e.g. sidechain inputs +alsa_audio_srcs = {} # Map of alsa_in processes, indexed by alsa device name +alsa_audio_dests = {} # Map of alsa_out processes, indexed by alsa device name # These variables are initialized in the init() function. These are "example values". max_num_devs = 16 # Max number of MIDI devices @@ -857,6 +861,104 @@ def get_hw_audio_dst_ports(): return hw_audio_dst_ports +def update_hw_audio_ports(): + global alsa_audio_srcs, alsa_audio_dests + + dirty = False + if zynthian_gui_config.hotplug_audio: + # Add new devices + for device in get_alsa_audio_devices(False): + dirty |= start_alsa_in(device) + for device in get_alsa_audio_devices(True): + dirty |= start_alsa_out(device) + + # Remove disconnected devices + for device in list(alsa_audio_srcs): + try: + while True: + proc = alsa_audio_srcs[device] + line = proc.readline() + if line.startswith("err"): + proc.terminate() + alsa_audio_srcs.pop(device) + dirty = True + break + elif not line: + break + except: + continue + for device in list(alsa_audio_dests): + try: + while True: + proc = alsa_audio_dests[device] + line = proc.readline() + if line.startswith("err"): + proc.terminate() + alsa_audio_dests.pop(device) + dirty = True + break + elif not line: + break + except: + continue + + return dirty + + +def get_alsa_audio_devices(playback=True): + devices = [] + for card in alsaaudio.pcms(alsaaudio.PCM_PLAYBACK if playback else alsaaudio.PCM_CAPTURE): + if card.startswith("hw:"): + devices.append(card[8:card.find(",")]) + return devices + + +def start_alsa_in(device): + global alsa_audio_srcs + if device in alsa_audio_srcs: + return False + proc = pexpect.spawn(f"alsa_in -d hw:{device} -j zynain_{device}", encoding="utf-8", timeout=0.1) + if proc.exitstatus: + return False + alsa_audio_srcs[device] = proc + return True + + +def stop_alsa_in(device): + global alsa_audio_srcs + if device not in alsa_audio_srcs: + return False + alsa_audio_srcs[device].terminate() + alsa_audio_srcs.pop(device) + return True + + +def stop_all_alsa_in(): + for device in get_alsa_audio_devices(False): + stop_alsa_in(device) + + +def start_alsa_out(device): + return False # TODO Disabled whilst getting hotplug audio inputs working. + global alsa_audio_dests + if device in alsa_audio_dests: + return False + proc = pexpect.spawn(f"alsa_out -d hw:{device} -j zynaout_{device}", encoding="utf-8", timeout=0.1) + if proc.exitstatus: + return False + alsa_audio_dests[device] = proc + return True + + +def stop_alsa_out(device): + global alsa_audio_dests + if device not in alsa_audio_dests: + return False + alsa_audio_dests[device].terminate() + alsa_audio_dests.pop(device) + return True + + # Connect mixer to the ffmpeg recorder def audio_connect_ffmpeg(timeout=2.0): t = 0 @@ -874,7 +976,7 @@ def audio_connect_ffmpeg(timeout=2.0): def get_audio_capture_ports(): """Get list of hardware audio inputs""" - return jclient.get_ports("system", is_output=True, is_audio=True, is_physical=True) + return jclient.get_ports("system", is_output=True, is_audio=True, is_physical=True) + jclient.get_ports("zynain", is_output=True, is_audio=True) def build_midi_port_name(port): @@ -1025,6 +1127,9 @@ def auto_connect_thread(): # Check if requested to run midi connect (slow) if deferred_midi_connect: do_midi = True + # Check if dynamic (hot-plug) audio changed + if update_hw_audio_ports(): + do_audio = True # Check if requested to run audio connect (slow) if deferred_audio_connect: do_audio = True @@ -1155,6 +1260,13 @@ def stop(): hw_audio_dst_ports = [] + for process in alsa_audio_srcs.values(): + process.terminate() + alsa_audio_srcs = {} + for process in alsa_audio_dests.values(): + process.terminate() + alsa_audio_dests = {} + if jclient: jclient.deactivate() jclient = None diff --git a/zyngine/zynthian_chain.py b/zyngine/zynthian_chain.py index c6b6a9634..2f818101b 100644 --- a/zyngine/zynthian_chain.py +++ b/zyngine/zynthian_chain.py @@ -370,13 +370,16 @@ def get_input_pairs(self): if self.chain_id == 0: return self.audio_in.copy() sources = [] + input_ports = zynautoconnect.get_audio_capture_ports() for i in range(0, len(self.audio_in), 2): a = self.audio_in[i] + if a > len(input_ports): + continue if i < len(self.audio_in) - 1: b = self.audio_in[i + 1] - sources.append(f"system:capture_({a}|{b})$") + sources.append(f"{input_ports[a-1].name}|{input_ports[b-1].name}") else: - sources.append(f"system:capture_({a})$") + sources.append(input_ports[a-1].name) return sources def rebuild_midi_graph(self): diff --git a/zyngui/zynthian_gui_admin.py b/zyngui/zynthian_gui_admin.py index 927fa79e5..3246e3192 100644 --- a/zyngui/zynthian_gui_admin.py +++ b/zyngui/zynthian_gui_admin.py @@ -158,6 +158,11 @@ def fill_list(self): else: self.list_data.append((self.start_rbpi_headphones, 0, "\u2610 RBPi Headphones")) + if zynthian_gui_config.hotplug_audio: + self.list_data.append((self.stop_hotplug_audio, 0, "\u2612 Hotplug USB Audio")) + else: + self.list_data.append((self.start_hotplug_audio, 0, "\u2610 Hotplug USB Audio")) + if zynthian_gui_config.snapshot_mixer_settings: self.list_data.append((self.toggle_snapshot_mixer_settings, 0, "\u2612 Audio Levels on Snapshots")) else: @@ -356,6 +361,39 @@ def default_rbpi_headphones(self): else: self.stop_rbpi_headphones(False) + def start_hotplug_audio(self, save_config=True): + logging.info("STARTING HOTPLUG AUDIO") + zynthian_gui_config.hotplug_audio = 1 + # Update Config + if save_config: + zynconf.save_config({ + "ZYNTHIAN_HOTPLUG_AUDIO": str(zynthian_gui_config.hotplug_audio) + }) + # Call autoconnect after a little time + zynautoconnect.request_audio_connect() + + self.update_list() + + def stop_hotplug_audio(self, save_config=True): + logging.info("STOPPING HOTPLUG AUDIO") + + zynthian_gui_config.hotplug_audio = 0 + zynautoconnect.stop_all_alsa_in() + # Update Config + if save_config: + zynconf.save_config({ + "ZYNTHIAN_HOTPLUG_AUDIO": str(int(zynthian_gui_config.hotplug_audio)) + }) + + self.update_list() + + # Start/Stop Hotplug Audio depending on configuration + def default_hotplug_audio(self): + if zynthian_gui_config.hotplug_audio: + self.start_hotplug_audio(False) + else: + self.stop_hotplug_audio(False) + def toggle_dpm(self): zynthian_gui_config.enable_dpm = not zynthian_gui_config.enable_dpm self.update_list() diff --git a/zyngui/zynthian_gui_config.py b/zyngui/zynthian_gui_config.py index a330910df..5402fe5ce 100644 --- a/zyngui/zynthian_gui_config.py +++ b/zyngui/zynthian_gui_config.py @@ -516,6 +516,7 @@ def get_external_storage_dirs(exdpath): rbpi_headphones = int(os.environ.get('ZYNTHIAN_RBPI_HEADPHONES', False)) enable_dpm = int(os.environ.get('ZYNTHIAN_DPM', True)) +hotplug_audio = int(os.environ.get('ZYNTHIAN_HOTPLUG_AUDIO', False)) # ------------------------------------------------------------------------------ # Networking Options From cdd18c39d7cf4190d41a28db712fb1244ad9039a Mon Sep 17 00:00:00 2001 From: riban Date: Fri, 16 Aug 2024 14:26:42 +0100 Subject: [PATCH 004/117] Fixes zynthian/zynthian-issue-tracking#1145. Force soundfile read to return 2D arrays (thanks @oscaracena). Fixes waveform display for short and invalid files. --- zyngui/zynthian_widget_audioplayer.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/zyngui/zynthian_widget_audioplayer.py b/zyngui/zynthian_widget_audioplayer.py index 6f78817c0..1196ac24a 100644 --- a/zyngui/zynthian_widget_audioplayer.py +++ b/zyngui/zynthian_widget_audioplayer.py @@ -440,7 +440,9 @@ def load_file(self): self.update() def draw_waveform(self, start, length): + self.widget_canvas.itemconfig(self.loading_text, text="Creating waveform...") if not self.channels: + self.widget_canvas.itemconfig(self.loading_text, text="No audio in file") return start = max(0, start) start = min(self.frames, start) @@ -457,6 +459,7 @@ def draw_waveform(self, start, length): block_size = min(frames_per_pixel, 1024) # Limit large file read blocks if frames_per_pixel < 1: self.refresh_waveform = False + self.widget_canvas.itemconfig(self.loading_text, text="Audio too short") return data = [[] for i in range(self.channels)] @@ -470,7 +473,7 @@ def draw_waveform(self, start, length): offset2 = frames_per_pixel step = max(1, frames_per_pixel // steps_per_peak) self.sf.seek(start) - a = self.sf.read(length) + a = self.sf.read(length, always_2d=True) v1 = [0.0 for i in range(self.channels)] v2 = [0.0 for i in range(self.channels)] @@ -478,7 +481,7 @@ def draw_waveform(self, start, length): for x in range(self.width): if large_file: self.sf.seek(start + x * frames_per_pixel) - a = self.sf.read(block_size) + a = self.sf.read(block_size, always_2d=True) if len(a) == 0: break else: @@ -519,6 +522,7 @@ def refresh_gui(self): if self.duration == 0.0: self.refreshing = False + self.widget_canvas.itemconfig(self.loading_text, text="No audio in file") return refresh_markers = False From 004e79b01e3c21b77b379960d00abeaeeb8e89c6 Mon Sep 17 00:00:00 2001 From: riban Date: Fri, 16 Aug 2024 19:01:36 +0100 Subject: [PATCH 005/117] Fixes zynthian/zynthian-issue-tracking#1144. Handle two different incarnations of legacy snapshots. Should now load okay but mixer CC mapping might not work. --- zyngine/zynthian_legacy_snapshot.py | 30 ++++++++++++++++++----------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/zyngine/zynthian_legacy_snapshot.py b/zyngine/zynthian_legacy_snapshot.py index c2f55e10a..769974df5 100644 --- a/zyngine/zynthian_legacy_snapshot.py +++ b/zyngine/zynthian_legacy_snapshot.py @@ -392,8 +392,13 @@ def convert_state(self, snapshot): state["chains"] = chains # ZS3 + state["zs3"]["zs3-0"]["mixer"] = {} try: - state["zs3"]["zs3-0"]["mixer"] = snapshot["mixer"] + if isinstance(snapshot["mixer"], dict): + state["zs3"]["zs3-0"]["mixer"] = snapshot["mixer"] + else: + for i,mixer_ch in enumerate(snapshot["mixer"]): + state["zs3"]["zs3-0"]["mixer"][f"chan_{i:02d}"] = mixer_ch except: pass @@ -488,16 +493,19 @@ def convert_state(self, snapshot): except: continue for symbol, params in config.items(): - if 'midi_learn_chan' in params: - chan = params.pop('midi_learn_chan') - else: - chan = None - if 'midi_learn_cc' in params: - cc = params.pop('midi_learn_cc') - else: - cc = None - if cc is not None and chan is not None: - zs3["mixer"]["midi_learn"][f"{chan},{cc}"] = [strip_id, symbol] + try: + if 'midi_learn_chan' in params: + chan = params.pop('midi_learn_chan') + else: + chan = None + if 'midi_learn_cc' in params: + cc = params.pop('midi_learn_cc') + else: + cc = None + if cc is not None and chan is not None: + zs3["mixer"]["midi_learn"][f"{chan},{cc}"] = [strip_id, symbol] + except: + pass try: config[symbol] = config[symbol]["value"] except: From c42975257f8e9baf19f1965ec82790fac676fe6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pavel=20Vond=C5=99i=C4=8Dka?= Date: Mon, 9 Sep 2024 23:03:49 +0100 Subject: [PATCH 006/117] Better integration by moving the touch keypad into zynthian_gui_config. --- zyngui/zynthian_gui.py | 19 +----------- zyngui/zynthian_gui_base.py | 18 +++++------ zyngui/zynthian_gui_config.py | 31 ++++++++++++++++--- zyngui/zynthian_gui_confirm.py | 4 +-- zyngui/zynthian_gui_control_xy.py | 12 +++---- zyngui/zynthian_gui_details.py | 4 +-- zyngui/zynthian_gui_info.py | 6 ++-- zyngui/zynthian_gui_keyboard.py | 8 ++--- zyngui/zynthian_gui_loading.py | 4 +-- zyngui/zynthian_gui_midi_key_range.py | 2 +- zyngui/zynthian_gui_splash.py | 8 ++--- .../zynthian_gui_touchscreen_calibration.py | 8 ++--- zyngui/zynthian_widget_sooperlooper.py | 2 +- zyngui/zynthian_wsleds_v5touch.py | 2 +- 14 files changed, 65 insertions(+), 63 deletions(-) diff --git a/zyngui/zynthian_gui.py b/zyngui/zynthian_gui.py index 2dc743cbf..2d93f92a8 100644 --- a/zyngui/zynthian_gui.py +++ b/zyngui/zynthian_gui.py @@ -90,7 +90,6 @@ from zyngui.zynthian_gui_wifi import zynthian_gui_wifi from zyngui.zynthian_gui_bluetooth import zynthian_gui_bluetooth from zyngui.zynthian_gui_control_test import zynthian_gui_control_test -from zyngui.zynthian_gui_touchkeypad_v5 import zynthian_gui_touchkeypad_v5 MIXER_MAIN_CHANNEL = 17 # TODO This constant should go somewhere else @@ -153,12 +152,7 @@ def __init__(self): self.capture_log_fname = None self.capture_ffmpeg_proc = None - # Init Touch keypad - self.touch_keypad = None self.main_screen_column = 1 if zynthian_gui_config.touch_keypad_side_left else 0 - self.touch_keypad_side_width = 0 - self.touch_keypad_bottom_height = 0 - self.init_touchkeypad() # Init LEDs self.wsleds = None @@ -229,23 +223,12 @@ def write_capture_log(self, message): except Exception as e: logging.error("Can't write to capture log: {}".format(e)) - # --------------------------------------------------------------------------- - # Touch keypad - # --------------------------------------------------------------------------- - - def init_touchkeypad(self): - if zynthian_gui_config.touch_keypad == 'V5': - self.touch_keypad_side_width = zynthian_gui_config.display_height // 3 - self.touch_keypad_bottom_height = zynthian_gui_config.display_height // 6 - self.touch_keypad = zynthian_gui_touchkeypad_v5(zynthian_gui_config.top, side_width=self.touch_keypad_side_width, left_side=zynthian_gui_config.touch_keypad_side_left) - self.touch_keypad.show() - # --------------------------------------------------------------------------- # WSLeds Init # --------------------------------------------------------------------------- def init_wsleds(self): - if self.touch_keypad: + if zynthian_gui_config.touch_keypad: from zyngui.zynthian_wsleds_v5touch import zynthian_wsleds_v5touch self.wsleds = zynthian_wsleds_v5touch(self) self.wsleds.start() diff --git a/zyngui/zynthian_gui_base.py b/zyngui/zynthian_gui_base.py index 9e4886c6c..5b7ffe180 100644 --- a/zyngui/zynthian_gui_base.py +++ b/zyngui/zynthian_gui_base.py @@ -46,8 +46,8 @@ class zynthian_gui_base(tkinter.Frame): def __init__(self, has_backbutton=True): tkinter.Frame.__init__(self, zynthian_gui_config.top, - width=zynthian_gui_config.display_width - zynthian_gui_config.zyngui.touch_keypad_side_width, - height=zynthian_gui_config.display_height - zynthian_gui_config.zyngui.touch_keypad_bottom_height) + width=zynthian_gui_config.screen_width , + height=zynthian_gui_config.screen_height) self.grid_propagate(False) self.rowconfigure(1, weight=1) self.columnconfigure(0, weight=1) @@ -60,14 +60,13 @@ def __init__(self, has_backbutton=True): self.buttonbar_button = [] # Geometry vars - self.buttonbar_height = (zynthian_gui_config.display_height - zynthian_gui_config.zyngui.touch_keypad_bottom_height) // 7 - self.width = zynthian_gui_config.display_width - zynthian_gui_config.zyngui.touch_keypad_side_width + self.buttonbar_height = zynthian_gui_config.screen_height // 7 + self.width = zynthian_gui_config.screen_width #TODO: Views should use current height if they need dynamic changes else grow rows to fill main_frame if zynthian_gui_config.enable_touch_navigation and self.buttonbar_config: - self.height = zynthian_gui_config.display_height - self.topbar_height - self.buttonbar_height + self.height = zynthian_gui_config.screen_height - self.topbar_height - self.buttonbar_height else: - self.height = zynthian_gui_config.display_height - self.topbar_height - self.height -= self.zyngui.touch_keypad_bottom_height + self.height = zynthian_gui_config.screen_height - self.topbar_height # Status Area Parameters self.status_l = int(self.width * 0.25) @@ -838,10 +837,9 @@ def set_select_path(self): # Override if required def update_layout(self): if zynthian_gui_config.enable_touch_navigation and self.buttonbar_config: - self.height = zynthian_gui_config.display_height - self.topbar_height - self.buttonbar_height + self.height = zynthian_gui_config.screen_height - self.topbar_height - self.buttonbar_height else: - self.height = zynthian_gui_config.display_height - self.topbar_height - self.height -= self.zyngui.touch_keypad_bottom_height + self.height = zynthian_gui_config.screen_height - self.topbar_height # Function to enable the top-bar parameter editor # engine: Object to recieve send_controller_value callback diff --git a/zyngui/zynthian_gui_config.py b/zyngui/zynthian_gui_config.py index ad5a7477d..e9e360b86 100644 --- a/zyngui/zynthian_gui_config.py +++ b/zyngui/zynthian_gui_config.py @@ -511,9 +511,11 @@ def get_external_storage_dirs(exdpath): control_test_enabled = int(os.environ.get('ZYNTHIAN_UI_CONTROL_TEST_ENABLED', 0)) power_save_secs = 60 * int(os.environ.get('ZYNTHIAN_UI_POWER_SAVE_MINUTES', 60)) -touch_keypad = os.environ.get('ZYNTHIAN_TOUCH_KEYPAD', "") +touch_keypad_option = os.environ.get('ZYNTHIAN_TOUCH_KEYPAD', "") touch_keypad_side_left = int(os.environ.get('ZYNTHIAN_TOUCH_KEYPAD_SIDE_LEFT', True)) +touch_keypad = None + # ------------------------------------------------------------------------------ # Audio Options # ------------------------------------------------------------------------------ @@ -626,18 +628,37 @@ def color_variant(hex_color, brightness_offset=1): logging.warning("Can't get screen height. Using default 240!") display_height = 240 + touch_keypad_side_width = 0 + touch_keypad_bottom_height = 0 + + # Reduce screen dimensions if touch keypad active + if touch_keypad_option == 'V5': + touch_keypad_side_width = display_height // 3 + touch_keypad_bottom_height = display_height // 6 + screen_width = display_width - touch_keypad_side_width + screen_height = display_height - touch_keypad_bottom_height + else: + screen_width = display_width + screen_height = display_height + # Global font size font_size = int(os.environ.get('ZYNTHIAN_UI_FONT_SIZE', None)) if not font_size: font_size = int(display_width / 40) + # Activate touch keypad (font size must be set first) + if touch_keypad_option == 'V5': + from zyngui.zynthian_gui_touchkeypad_v5 import zynthian_gui_touchkeypad_v5 + touch_keypad = zynthian_gui_touchkeypad_v5(top, side_width=touch_keypad_side_width, left_side=touch_keypad_side_left) + touch_keypad.show() + # Geometric params - button_width = display_width // 4 - if display_width >= 800: - topbar_height = display_height // 12 + button_width = screen_width // 4 + if screen_width >= 800: + topbar_height = screen_height // 12 topbar_fs = int(1.5*font_size) else: - topbar_height = display_height // 10 + topbar_height = screen_height // 10 topbar_fs = int(1.1*font_size) # Adjust Root Window Geometry diff --git a/zyngui/zynthian_gui_confirm.py b/zyngui/zynthian_gui_confirm.py index ad2f6b627..5f0fbdf06 100644 --- a/zyngui/zynthian_gui_confirm.py +++ b/zyngui/zynthian_gui_confirm.py @@ -43,8 +43,8 @@ def __init__(self): self.callback = None self.callback_params = None self.zyngui = zynthian_gui_config.zyngui - self.width = zynthian_gui_config.display_width - zynthian_gui_config.zyngui.touch_keypad_side_width - self.height = zynthian_gui_config.display_height - zynthian_gui_config.zyngui.touch_keypad_bottom_height + self.width = zynthian_gui_config.screen_width + self.height = zynthian_gui_config.screen_height # Main Frame self.main_frame = tkinter.Frame(zynthian_gui_config.top, diff --git a/zyngui/zynthian_gui_control_xy.py b/zyngui/zynthian_gui_control_xy.py index f75fb2a38..f473349fa 100644 --- a/zyngui/zynthian_gui_control_xy.py +++ b/zyngui/zynthian_gui_control_xy.py @@ -49,13 +49,13 @@ def __init__(self): # Init X vars self.padx = 24 - self.width = zynthian_gui_config.display_width - zynthian_gui_config.zyngui.touch_keypad_side_width - 2 * self.padx + self.width = zynthian_gui_config.screen_width - 2 * self.padx self.x = self.width / 2 self.xvalue = 64 # Init Y vars self.pady = 18 - self.height = zynthian_gui_config.display_height - 2 * self.pady + self.height = zynthian_gui_config.screen_height - 2 * self.pady self.y = self.height / 2 self.yvalue = 64 @@ -63,8 +63,8 @@ def __init__(self): # Main Frame self.main_frame = tkinter.Frame(zynthian_gui_config.top, - width=zynthian_gui_config.display_width - zynthian_gui_config.zyngui.touch_keypad_side_width, - height=zynthian_gui_config.display_height - zynthian_gui_config.zyngui.touch_keypad_bottom_height, + width=zynthian_gui_config.screen_width, + height=zynthian_gui_config.screen_height, bg=zynthian_gui_config.color_panel_bg) # Create Canvas @@ -88,14 +88,14 @@ def __init__(self): self.hline=self.canvas.create_line( 0, self.y, - zynthian_gui_config.display_width, + zynthian_gui_config.screen_width, self.y, fill=zynthian_gui_config.color_on) self.vline=self.canvas.create_line( self.x, 0, self.x, - zynthian_gui_config.display_width, + zynthian_gui_config.screen_width, fill=zynthian_gui_config.color_on) diff --git a/zyngui/zynthian_gui_details.py b/zyngui/zynthian_gui_details.py index 4e7901964..bd8166c28 100644 --- a/zyngui/zynthian_gui_details.py +++ b/zyngui/zynthian_gui_details.py @@ -42,8 +42,8 @@ def __init__(self): # Textarea self.textarea = tkinter.Text(self.main_frame, - width=int(zynthian_gui_config.display_width/(zynthian_gui_config.font_size + 5)), - height=int(zynthian_gui_config.display_height/(zynthian_gui_config.font_size + 8)), + width=int(zynthian_gui_config.screen_width/(zynthian_gui_config.font_size + 5)), + height=int(zynthian_gui_config.screen_height/(zynthian_gui_config.font_size + 8)), font=(zynthian_gui_config.font_family, zynthian_gui_config.font_size, "normal"), wrap='word', #justify=tkinter.LEFT, diff --git a/zyngui/zynthian_gui_info.py b/zyngui/zynthian_gui_info.py index a0f3d3d58..f8bb904f2 100644 --- a/zyngui/zynthian_gui_info.py +++ b/zyngui/zynthian_gui_info.py @@ -41,13 +41,13 @@ def __init__(self): # Main Frame self.main_frame = tkinter.Frame(zynthian_gui_config.top, - width=zynthian_gui_config.display_width - zynthian_gui_config.zyngui.touch_keypad_side_width, - height=zynthian_gui_config.display_height - zynthian_gui_config.zyngui.touch_keypad_bottom_height, + width=zynthian_gui_config.screen_width, + height=zynthian_gui_config.screen_height, bg=zynthian_gui_config.color_bg) # Textarea self.textarea = tkinter.Text(self.main_frame, - height=int((zynthian_gui_config.display_height - zynthian_gui_config.zyngui.touch_keypad_bottom_height)/(zynthian_gui_config.font_size + 8)), + height=int(zynthian_gui_config.screen_height/(zynthian_gui_config.font_size + 8)), font=(zynthian_gui_config.font_family, zynthian_gui_config.font_size, "normal"), #font=("sans-serif", zynthian_gui_config.font_size, "normal"), #wraplength=80, diff --git a/zyngui/zynthian_gui_keyboard.py b/zyngui/zynthian_gui_keyboard.py index 2290e153f..64239fca5 100644 --- a/zyngui/zynthian_gui_keyboard.py +++ b/zyngui/zynthian_gui_keyboard.py @@ -58,16 +58,16 @@ def __init__(self): self.ctrl_order = zynthian_gui_config.layout['ctrl_order'] # Geometry vars - self.width = zynthian_gui_config.display_width - zynthian_gui_config.zyngui.touch_keypad_side_width - self.height = zynthian_gui_config.display_height - zynthian_gui_config.topbar_height - zynthian_gui_config.zyngui.touch_keypad_bottom_height + self.width = zynthian_gui_config.screen_width + self.height = zynthian_gui_config.screen_height - zynthian_gui_config.topbar_height # Fonts self.font_button = (zynthian_gui_config.font_family, int(1.2*zynthian_gui_config.font_size)) # Create main frame self.main_frame = tkinter.Frame(zynthian_gui_config.top, - width=zynthian_gui_config.display_width - zynthian_gui_config.zyngui.touch_keypad_side_width, - height=zynthian_gui_config.display_height - zynthian_gui_config.zyngui.touch_keypad_bottom_height, + width=zynthian_gui_config.screen_width, + height=zynthian_gui_config.screen_height, bg=zynthian_gui_config.color_bg) self.main_frame.grid_propagate(False) diff --git a/zyngui/zynthian_gui_loading.py b/zyngui/zynthian_gui_loading.py index f4888b3cc..6951f4dc7 100644 --- a/zyngui/zynthian_gui_loading.py +++ b/zyngui/zynthian_gui_loading.py @@ -39,8 +39,8 @@ class zynthian_gui_loading: def __init__(self): self.shown = False self.zyngui = zynthian_gui_config.zyngui - self.width = zynthian_gui_config.display_width - zynthian_gui_config.zyngui.touch_keypad_side_width - self.height = zynthian_gui_config.display_height - zynthian_gui_config.zyngui.touch_keypad_bottom_height + self.width = zynthian_gui_config.screen_width + self.height = zynthian_gui_config.screen_height # Canvas for loading image animation self.canvas = tkinter.Canvas( zynthian_gui_config.top, diff --git a/zyngui/zynthian_gui_midi_key_range.py b/zyngui/zynthian_gui_midi_key_range.py index b70782915..7118aa07e 100644 --- a/zyngui/zynthian_gui_midi_key_range.py +++ b/zyngui/zynthian_gui_midi_key_range.py @@ -66,7 +66,7 @@ def __init__(self): bg=zynthian_gui_config.color_panel_bg, bd=0, highlightthickness=0) - self.piano_canvas_width = zynthian_gui_config.display_width - zynthian_gui_config.zyngui.touch_keypad_side_width + self.piano_canvas_width = zynthian_gui_config.screen_width self.piano_canvas_height = self.height // 4 self.main_frame.rowconfigure(2, weight=1) diff --git a/zyngui/zynthian_gui_splash.py b/zyngui/zynthian_gui_splash.py index 93d4ab021..cbbd36b50 100644 --- a/zyngui/zynthian_gui_splash.py +++ b/zyngui/zynthian_gui_splash.py @@ -56,8 +56,8 @@ def hide(self): if self.shown: self.shown = False self.canvas.grid_forget() - if self.zyngui.touch_keypad: - self.zyngui.touch_keypad.show() + if zynthian_gui_config.touch_keypad: + zynthian_gui_config.touch_keypad.show() def show(self, text): @@ -81,8 +81,8 @@ def show(self, text): except: pass if not self.shown: - if self.zyngui.touch_keypad: - self.zyngui.touch_keypad.hide() + if zynthian_gui_config.touch_keypad: + zynthian_gui_config.touch_keypad.hide() self.shown = True self.canvas.grid() diff --git a/zyngui/zynthian_gui_touchscreen_calibration.py b/zyngui/zynthian_gui_touchscreen_calibration.py index 59051c500..e349cd8d5 100644 --- a/zyngui/zynthian_gui_touchscreen_calibration.py +++ b/zyngui/zynthian_gui_touchscreen_calibration.py @@ -394,8 +394,8 @@ def hide(self): self.setCalibration(self.device_id, self.ctm) self.main_frame.grid_forget() self.shown=False - if self.zyngui.touch_keypad: - self.zyngui.touch_keypad.show() + if zynthian_gui_config.touch_keypad: + zynthian_gui_config.touch_keypad.show() # Build display def build_view(self): @@ -415,8 +415,8 @@ def build_view(self): # Show display def show(self): - if self.zyngui.touch_keypad: - self.zyngui.touch_keypad.hide() + if zynthian_gui_config.touch_keypad: + zynthian_gui_config.touch_keypad.hide() self.main_frame.grid() self.onTimer() self.detect_thread = Thread(target=self.detectDevice, args=(), daemon=True) diff --git a/zyngui/zynthian_widget_sooperlooper.py b/zyngui/zynthian_widget_sooperlooper.py index 04fb3f3d9..64224b4d1 100644 --- a/zyngui/zynthian_widget_sooperlooper.py +++ b/zyngui/zynthian_widget_sooperlooper.py @@ -62,7 +62,7 @@ def __init__(self, parent): self.font_size_sl = int(0.7 * zynthian_gui_config.font_size) self.tri_size = int(0.5 * zynthian_gui_config.font_size) - txt_y = (zynthian_gui_config.display_height - zynthian_gui_config.zyngui.touch_keypad_bottom_height) // 22 #int(0.70 * self.font_size_sl) + txt_y = zynthian_gui_config.screen_height // 22 #int(0.70 * self.font_size_sl) self.txt_x = 4 self.pos_canvas = [] diff --git a/zyngui/zynthian_wsleds_v5touch.py b/zyngui/zynthian_wsleds_v5touch.py index 679cf61d8..24d175963 100644 --- a/zyngui/zynthian_wsleds_v5touch.py +++ b/zyngui/zynthian_wsleds_v5touch.py @@ -29,7 +29,7 @@ def __setitem__(self, index, color): bgcolor = self.hex_to_rgb(self.wsleds.wscolor_off) blended = self.ablend(1-self.wsleds.brightness, fgcolor, bgcolor) color = self.rgb_to_hex(blended) - self.zyngui.touch_keypad.set_button_color(index, color, mode) + zynthian_gui_config.touch_keypad.set_button_color(index, color, mode) def show(self): # nothing to do here From 31db9d78da36ef1732de53d30ab1b1fe01343833 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pavel=20Vond=C5=99i=C4=8Dka?= Date: Mon, 9 Sep 2024 23:24:36 +0100 Subject: [PATCH 007/117] Fix also the 'Loading Logo Animation' width. --- zyngui/zynthian_gui_config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zyngui/zynthian_gui_config.py b/zyngui/zynthian_gui_config.py index e9e360b86..8a48c3024 100644 --- a/zyngui/zynthian_gui_config.py +++ b/zyngui/zynthian_gui_config.py @@ -685,7 +685,7 @@ def color_variant(hex_color, brightness_offset=1): loading_imgs = [] pil_frame = Image.open("./img/zynthian_gui_loading.gif") fw, fh = pil_frame.size - fw2 = display_width // 4 - 8 + fw2 = screen_width // 4 - 8 fh2 = int(fh * fw2 / fw) nframes = 0 while pil_frame: From c9b247347271a526479f0c37bf614972d8f0c4d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pavel=20Vond=C5=99i=C4=8Dka?= Date: Sun, 15 Sep 2024 23:12:50 +0100 Subject: [PATCH 008/117] wsleds_v5touch: more color configuration from env. --- zyngui/zynthian_wsleds_v5touch.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/zyngui/zynthian_wsleds_v5touch.py b/zyngui/zynthian_wsleds_v5touch.py index 24d175963..20636a459 100644 --- a/zyngui/zynthian_wsleds_v5touch.py +++ b/zyngui/zynthian_wsleds_v5touch.py @@ -37,7 +37,7 @@ def show(self): def ablend(self, a, fg, bg): """ - Bland foregground and background color to imitate alpha transparency + Blend foreground and background color to imitate alpha transparency """ return (int((1-a)*fg[0]+a*bg[0]), int((1-a)*fg[1]+a*bg[1]), @@ -67,7 +67,7 @@ def start(self): def setup_colors(self): # Predefined colors - self.wscolor_off = zynthian_gui_config.color_bg + self.wscolor_off = os.environ.get('ZYNTHIAN_TOUCH_KEYPAD_COLOR_OFF', zynthian_gui_config.color_bg) self.wscolor_white = os.environ.get('ZYNTHIAN_TOUCH_KEYPAD_COLOR_WHITE', "#FCFCFC") self.wscolor_red = os.environ.get('ZYNTHIAN_TOUCH_KEYPAD_COLOR_RED', "#FE2C2F") # #FF8A92 self.wscolor_green = os.environ.get('ZYNTHIAN_TOUCH_KEYPAD_COLOR_GREEN', "#00FA00") @@ -76,11 +76,11 @@ def setup_colors(self): self.wscolor_blue = os.environ.get('ZYNTHIAN_TOUCH_KEYPAD_COLOR_BLUE', "#1070FE") # lighter: #5397FE, #38EBFF self.wscolor_blue_light = os.environ.get('ZYNTHIAN_TOUCH_KEYPAD_COLOR_LIGHTBLUE', "#05FDFF") self.wscolor_purple = os.environ.get('ZYNTHIAN_TOUCH_KEYPAD_COLOR_PURPLE', "#D662FE") # #FF80EB - self.wscolor_default = self.wscolor_blue - self.wscolor_alt = self.wscolor_purple - self.wscolor_active = self.wscolor_green - self.wscolor_active2 = self.wscolor_orange - self.wscolor_admin = self.wscolor_red + self.wscolor_default = os.environ.get('ZYNTHIAN_TOUCH_KEYPAD_COLOR_DEFAULT', self.wscolor_blue) + self.wscolor_alt = os.environ.get('ZYNTHIAN_TOUCH_KEYPAD_COLOR_ALT', self.wscolor_purple) + self.wscolor_active = os.environ.get('ZYNTHIAN_TOUCH_KEYPAD_COLOR_ACTIVE', self.wscolor_green) + self.wscolor_active2 = os.environ.get('ZYNTHIAN_TOUCH_KEYPAD_COLOR_ACTIVE2', self.wscolor_orange) + self.wscolor_admin = os.environ.get('ZYNTHIAN_TOUCH_KEYPAD_COLOR_ADMIN', self.wscolor_red) self.wscolor_low = os.environ.get('ZYNTHIAN_TOUCH_KEYPAD_COLOR_LOW', "#D9EB37") # Color Codes self.wscolors_dict = { From 05e1ca6fd1b4bd93595ac6cd03cb9a85695c3b3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pavel=20Vond=C5=99i=C4=8Dka?= Date: Mon, 16 Sep 2024 01:30:28 +0100 Subject: [PATCH 009/117] v5touch: make labels customizable --- zyngui/zynthian_gui_touchkeypad_v5.py | 30 ++++++++++++++++++++++++--- zyngui/zynthian_wsleds_v5touch.py | 1 + 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/zyngui/zynthian_gui_touchkeypad_v5.py b/zyngui/zynthian_gui_touchkeypad_v5.py index 1993182e6..bc14bc099 100644 --- a/zyngui/zynthian_gui_touchkeypad_v5.py +++ b/zyngui/zynthian_gui_touchkeypad_v5.py @@ -1,4 +1,5 @@ +import os import tkinter from PIL import Image, ImageTk #, ImageDraw, ImageFont from io import BytesIO @@ -141,6 +142,9 @@ def __init__(self, parent, side_width, left_side=True): self.btndefs[n] = btn self.buttons[n] = self.add_button(n, self.bottom_frame, 0, col, zynswitch, label, padx, (1,0)) + # update with user settings from the environment + self.apply_user_config() + def add_button(self, n, parent, row, column, zynswitch, label, padx, pady): """ @@ -261,9 +265,9 @@ def set_button_color(self, led_num, color, mode): # get the button number associated with the wsled number n = LED2BUTTON[led_num] # don't bother with update if nothing has really changed (redrawing images causes visible blinking!) - if self.btnstate[n] == color: + if self.btnstate[n] == (mode or color): return - self.btnstate[n] = color + self.btnstate[n] = mode or color # in case the color is still the original wsled integer number, convert it label = self.btndefs[n][0] # twostate = self.btndefs[n][4] @@ -293,8 +297,12 @@ def set_button_color(self, led_num, color, mode): else: # plain text labels may just change the color and possibly also its label if a special label # is associated with the requested mode (<=color) in the button definition + self.refresh_button_label(n, mode) + self.buttons[n].config(fg=color) + + def refresh_button_label(self, n, mode): text = self.btndefs[n][3].get(mode, self.btndefs[n][0]).replace('/', "\n") - self.buttons[n].config(fg=color, text=text) + self.buttons[n].config(text=text) def show(self): if not self.shown: @@ -309,3 +317,19 @@ def hide(self): self.side_frame.grid_remove() self.bottom_frame.grid_remove() self.shown = False + + def apply_user_config(self): + for n in range(0, 20): + default = os.environ.get('ZYNTHIAN_TOUCH_KEYPAD_LABEL_'+str(n)+'_DEFAULT', None) + alt = os.environ.get('ZYNTHIAN_TOUCH_KEYPAD_LABEL_'+str(n)+'_ALT', None) + active = os.environ.get('ZYNTHIAN_TOUCH_KEYPAD_LABEL_'+str(n)+'_ACTIVE', None) + active2 = os.environ.get('ZYNTHIAN_TOUCH_KEYPAD_LABEL_'+str(n)+'_ACTIVE2', None) + if default: + self.btndefs[n][3]['default'] = default + if alt: + self.btndefs[n][3]['alt'] = alt + if active: + self.btndefs[n][3]['active'] = active + if active2: + self.btndefs[n][3]['active2'] = active2 + #self.refresh_button_label(n, self.btnstate[n])q \ No newline at end of file diff --git a/zyngui/zynthian_wsleds_v5touch.py b/zyngui/zynthian_wsleds_v5touch.py index 20636a459..5828820eb 100644 --- a/zyngui/zynthian_wsleds_v5touch.py +++ b/zyngui/zynthian_wsleds_v5touch.py @@ -13,6 +13,7 @@ def __init__(self, wsleds): self.zyngui = wsleds.zyngui # A wanna-be abstraction: reverse derive a named "mode" from the requested colors self.mode_map = {} + self.mode_map[wsleds.wscolor_default] = 'default' self.mode_map[wsleds.wscolor_alt] = 'alt' self.mode_map[wsleds.wscolor_active] = 'active' self.mode_map[wsleds.wscolor_active2] = 'active2' From 86b58af0a2ef62f4f3a4ca723dcef41d80ac1e44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pavel=20Vond=C5=99i=C4=8Dka?= Date: Mon, 16 Sep 2024 21:59:06 +0100 Subject: [PATCH 010/117] v5touch: Improve button label handling. --- zyngui/zynthian_gui_touchkeypad_v5.py | 74 +++++++++++++++------------ 1 file changed, 40 insertions(+), 34 deletions(-) diff --git a/zyngui/zynthian_gui_touchkeypad_v5.py b/zyngui/zynthian_gui_touchkeypad_v5.py index bc14bc099..d3588fb1b 100644 --- a/zyngui/zynthian_gui_touchkeypad_v5.py +++ b/zyngui/zynthian_gui_touchkeypad_v5.py @@ -11,30 +11,30 @@ from zyngui import zynthian_gui_config BUTTONS = { - # primary label, ZYNSWITCH number, wsLED number, alternate labels, two state button (not used) - 'OPT_ADMIN': ('OPT/ADMIN', 4, 0, {}, True), - 'MIX_LEVEL': ('MIX/LEVEL', 5, 1, {}, True), - 'CTRL_PRESET': ('CTRL/PRESET', 6, 2, {}, True), - 'ZS3_SHOT': ('ZS3/SHOT', 7, 3, {}, True), - 'METRONOME': ('_icons/metronome.svg', 9, 6, {}, False), - 'PAD_STEP': ('PAD/STEP', 10, 5, {}, True), - 'ALT': ('Alt', 8, 4, {}, False), + # labels, ZYNSWITCH number, wsLED number, two state button (not used) + 'OPT_ADMIN': ({'default': 'OPT/ADMIN'}, 4, 0, True), + 'MIX_LEVEL': ({'default': 'MIX/LEVEL'}, 5, 1, True), + 'CTRL_PRESET': ({'default': 'CTRL/PRESET'}, 6, 2, True), + 'ZS3_SHOT': ({'default': 'ZS3/SHOT'}, 7, 3, True), + 'METRONOME': ({'default': '_icons/metronome.svg'}, 9, 6, False), + 'PAD_STEP': ({'default': 'PAD/STEP'}, 10, 5, True), + 'ALT': ({'default': 'Alt'}, 8, 4, False), - 'REC': ('\uf111', 12, 8, {}, False), - 'STOP': ('\uf04d', 13, 9, {}, False), - 'PLAY': ('\uf04b', 14, 10, {'active': '\uf04c'}, False), + 'REC': ({'default': '\uf111'}, 12, 8, False), + 'STOP': ({'default': '\uf04d'}, 13, 9, False), + 'PLAY': ({'default': '\uf04b', 'active': '\uf04c'}, 14, 10, False), - 'UP': ('\uf077', 17, 14, {}, False), - 'DOWN': ('\uf078', 21, 17, {}, False), - 'LEFT': ('\uf053', 20, 16, {}, False), - 'RIGHT': ('\uf054', 22, 18, {}, False), - 'SEL_YES': ('SEL/YES', 18, 13, {}, False), - 'BACK_NO': ('BACK/NO', 16, 15, {}, False), + 'UP': ({'default': '\uf077'}, 17, 14, False), + 'DOWN': ({'default': '\uf078'}, 21, 17, False), + 'LEFT': ({'default': '\uf053'}, 20, 16, False), + 'RIGHT': ({'default': '\uf054'}, 22, 18, False), + 'SEL_YES': ({'default': 'SEL/YES'}, 18, 13, False), + 'BACK_NO': ({'default': 'BACK/NO'}, 16, 15, False), - 'F1': ('F1', 11, 7, {'alt': 'F5'}, False), - 'F2': ('F2', 15, 11, {'alt': 'F6'}, False), - 'F3': ('F3', 19, 12, {'alt': 'F7'}, False), - 'F4': ('F4', 23, 19, {'alt': 'F8'}, False) + 'F1': ({'default': 'F1', 'alt': 'F5'}, 11, 7, False), + 'F2': ({'default': 'F2', 'alt': 'F6'}, 15, 11, False), + 'F3': ({'default': 'F3', 'alt': 'F7'}, 19, 12, False), + 'F4': ({'default': 'F4', 'alt': 'F8'}, 23, 19, False) } LED2BUTTON = {btn[2]: btn[1]-4 for btn in BUTTONS.values()} @@ -127,7 +127,7 @@ def __init__(self, parent, side_width, left_side=True): btn = BUTTONS[layout['SIDE'][row][col]] zynswitch = btn[1] n = zynswitch - 4 - label = btn[0] + label = btn[0]['default'] pady = (1,0) if row == 5 else (0,0) if row == 4 else (0,1) padx = (0,1) if left_side else (1,0) self.btndefs[n] = btn @@ -137,7 +137,7 @@ def __init__(self, parent, side_width, left_side=True): btn = BUTTONS[layout['BOTTOM'][col]] zynswitch = btn[1] n = zynswitch - 4 - label = btn[0] + label = btn[0]['default'] padx = (0,0) if col == 7 else (0,1) self.btndefs[n] = btn self.buttons[n] = self.add_button(n, self.bottom_frame, 0, col, zynswitch, label, padx, (1,0)) @@ -269,7 +269,7 @@ def set_button_color(self, led_num, color, mode): return self.btnstate[n] = mode or color # in case the color is still the original wsled integer number, convert it - label = self.btndefs[n][0] + label = self.btndefs[n][0]['default'] # twostate = self.btndefs[n][4] if label.startswith('_'): # self.buttons[n].config()['image'][4]=='': # image buttons must be recomposed to change the foreground color @@ -301,7 +301,7 @@ def set_button_color(self, led_num, color, mode): self.buttons[n].config(fg=color) def refresh_button_label(self, n, mode): - text = self.btndefs[n][3].get(mode, self.btndefs[n][0]).replace('/', "\n") + text = self.btndefs[n][0].get(mode, self.btndefs[n][0]['default']).replace('/', "\n") self.buttons[n].config(text=text) def show(self): @@ -320,16 +320,22 @@ def hide(self): def apply_user_config(self): for n in range(0, 20): - default = os.environ.get('ZYNTHIAN_TOUCH_KEYPAD_LABEL_'+str(n)+'_DEFAULT', None) - alt = os.environ.get('ZYNTHIAN_TOUCH_KEYPAD_LABEL_'+str(n)+'_ALT', None) - active = os.environ.get('ZYNTHIAN_TOUCH_KEYPAD_LABEL_'+str(n)+'_ACTIVE', None) - active2 = os.environ.get('ZYNTHIAN_TOUCH_KEYPAD_LABEL_'+str(n)+'_ACTIVE2', None) + default = os.environ.get('ZYNTHIAN_TOUCH_KEYPAD_LABEL_{:02d}_DEFAULT'.format(n+1), None) + alt = os.environ.get('ZYNTHIAN_TOUCH_KEYPAD_LABEL_{:02d}_ALT'.format(n+1), None) + active = os.environ.get('ZYNTHIAN_TOUCH_KEYPAD_LABEL_{:02d}_ACTIVE'.format(n+1), None) + active2 = os.environ.get('ZYNTHIAN_TOUCH_KEYPAD_LABEL_{:02d}_ACTIVE2'.format(n+1), None) if default: - self.btndefs[n][3]['default'] = default + self.btndefs[n][0]['default'] = default if alt: - self.btndefs[n][3]['alt'] = alt + self.btndefs[n][0]['alt'] = alt if active: - self.btndefs[n][3]['active'] = active + self.btndefs[n][0]['active'] = active if active2: - self.btndefs[n][3]['active2'] = active2 - #self.refresh_button_label(n, self.btnstate[n])q \ No newline at end of file + self.btndefs[n][0]['active2'] = active2 + + # def set_button_label(self, n, mode, label): + # """ + # Dynamic label updates + # """ + # self.btndefs[n][mode] = label + # self.refresh_button_label(n, self.btnstate[n]) \ No newline at end of file From 0eed5a362d22768dd456c02e10d271e2ea0eef15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pavel=20Vond=C5=99i=C4=8Dka?= Date: Mon, 30 Sep 2024 00:56:34 +0200 Subject: [PATCH 011/117] Update zynthian_gui_control_xy.py --- zyngui/zynthian_gui_control_xy.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) mode change 100644 => 100755 zyngui/zynthian_gui_control_xy.py diff --git a/zyngui/zynthian_gui_control_xy.py b/zyngui/zynthian_gui_control_xy.py old mode 100644 new mode 100755 index 8abc7aada..11e08cd2c --- a/zyngui/zynthian_gui_control_xy.py +++ b/zyngui/zynthian_gui_control_xy.py @@ -115,9 +115,9 @@ def show(self): logging.warning("TEST_MODE: {}".format(self.__class__.__module__)) self.shown= True self.main_frame.grid(row=0, column=self.zyngui.main_screen_column) + self.get_controller_values() self.refresh() - def hide(self): if self.shown: self.shown = False From 1a559564105cea0cba16fb6fe4112343380a2c6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pavel=20Vond=C5=99i=C4=8Dka?= Date: Mon, 30 Sep 2024 00:59:54 +0200 Subject: [PATCH 012/117] Update zynthian_gui_control_xy.py --- zyngui/zynthian_gui_control_xy.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zyngui/zynthian_gui_control_xy.py b/zyngui/zynthian_gui_control_xy.py index 11e08cd2c..a7f527055 100755 --- a/zyngui/zynthian_gui_control_xy.py +++ b/zyngui/zynthian_gui_control_xy.py @@ -113,7 +113,7 @@ def show(self): if not self.shown: if self.zyngui.test_mode: logging.warning("TEST_MODE: {}".format(self.__class__.__module__)) - self.shown= True + self.shown = True self.main_frame.grid(row=0, column=self.zyngui.main_screen_column) self.get_controller_values() self.refresh() From 9b3a7edbb242297d4e854d9aa04eb3f36bf3ee3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pavel=20Vond=C5=99i=C4=8Dka?= Date: Mon, 7 Oct 2024 23:42:22 +0100 Subject: [PATCH 013/117] Fix zynthian_gui_midi_key_range.py --- zyngui/zynthian_gui_midi_key_range.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/zyngui/zynthian_gui_midi_key_range.py b/zyngui/zynthian_gui_midi_key_range.py index 7118aa07e..9f4e06f93 100644 --- a/zyngui/zynthian_gui_midi_key_range.py +++ b/zyngui/zynthian_gui_midi_key_range.py @@ -66,7 +66,7 @@ def __init__(self): bg=zynthian_gui_config.color_panel_bg, bd=0, highlightthickness=0) - self.piano_canvas_width = zynthian_gui_config.screen_width + self.piano_canvas_width = self.width self.piano_canvas_height = self.height // 4 self.main_frame.rowconfigure(2, weight=1) @@ -170,7 +170,7 @@ def update_piano(self): j += 1 midi_note += 1 - if self.black_keys_pattern[i % 7]: + if self.black_keys_pattern[i % 7] and j < len(self.piano_keys): if self.note_low > midi_note or self.note_high < midi_note: bgcolor = "#707070" else: From 78764f6aab058b0a7c18fd188dd2fb6cf40b1fe7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pavel=20Vond=C5=99i=C4=8Dka?= Date: Sat, 12 Oct 2024 00:31:15 +0100 Subject: [PATCH 014/117] V5touch: UI configuration of F-key labels. Storing labels in snapshot. --- zyngine/zynthian_state_manager.py | 15 +++++++ zyngui/zynthian_gui.py | 2 + zyngui/zynthian_gui_admin.py | 7 ++++ zyngui/zynthian_gui_touchkeypad_labels.py | 49 +++++++++++++++++++++++ zyngui/zynthian_gui_touchkeypad_v5.py | 19 +++++++++ 5 files changed, 92 insertions(+) create mode 100644 zyngui/zynthian_gui_touchkeypad_labels.py diff --git a/zyngine/zynthian_state_manager.py b/zyngine/zynthian_state_manager.py index 77f5e34d2..01a8d04e6 100644 --- a/zyngine/zynthian_state_manager.py +++ b/zyngine/zynthian_state_manager.py @@ -938,6 +938,14 @@ def get_state(self): b64_data = base64.b64encode(binary_riff_data) state['zynseq_riff_b64'] = b64_data.decode('utf-8') + # TouchKeypad config + if zynthian_gui_config.touch_keypad: + tk = { 'flabels': [] } + for i in range(8): + label = zynthian_gui_config.touch_keypad.get_fkey_label(i) + tk['flabels'].append(label) + state['touchkeypad'] = tk + return state def save_snapshot(self, fpath, extra_data=None): @@ -1034,6 +1042,13 @@ def load_snapshot(self, fpath, load_chains=True, load_sequences=True): binary_riff_data = base64.decodebytes(b64_bytes) self.zynseq.restore_riff_data(binary_riff_data) + # TouchKeypad config + if zynthian_gui_config.touch_keypad and 'touchkeypad' in state: + tk = state['touchkeypad'] + for i in range(8): + label = tk['flabels'][i] + zynthian_gui_config.touch_keypad.set_fkey_label(i, label) + if fpath == self.last_snapshot_fpath and "last_state_fpath" in state: self.last_snapshot_fpath = state["last_snapshot_fpath"] else: diff --git a/zyngui/zynthian_gui.py b/zyngui/zynthian_gui.py index cdc766d10..ca8f205bf 100644 --- a/zyngui/zynthian_gui.py +++ b/zyngui/zynthian_gui.py @@ -90,6 +90,7 @@ from zyngui.zynthian_gui_wifi import zynthian_gui_wifi from zyngui.zynthian_gui_bluetooth import zynthian_gui_bluetooth from zyngui.zynthian_gui_control_test import zynthian_gui_control_test +from zyngui.zynthian_gui_touchkeypad_labels import zynthian_gui_touchkeypad_labels # TODO This constants should go somewhere else MIXER_MAIN_CHANNEL = 17 @@ -443,6 +444,7 @@ def create_screens(self): self.screens['tempo'] = zynthian_gui_tempo() self.screens['admin'] = zynthian_gui_admin() self.screens['audio_mixer'] = zynthian_gui_mixer() + self.screens['touchkeypad_labels'] = zynthian_gui_touchkeypad_labels() # Create the right main menu screen if zynthian_gui_config.check_wiring_layout(["Z2", "V5"]): diff --git a/zyngui/zynthian_gui_admin.py b/zyngui/zynthian_gui_admin.py index 5eeee35aa..0a0ab774c 100644 --- a/zyngui/zynthian_gui_admin.py +++ b/zyngui/zynthian_gui_admin.py @@ -185,6 +185,10 @@ def fill_list(self): self.list_data.append((self.show_cv_config, 0, "CV Settings")) self.list_data.append((self.zyngui.calibrate_touchscreen, 0, "Calibrate Touchscreen")) + self.list_data.append((None, 0, "> TOUCH KEYPAD")) + if zynthian_gui_config.touch_keypad: + self.list_data.append((self.touchkeypad_labels, 0, "F-key labels")) + self.list_data.append((None, 0, "> TEST")) self.list_data.append((self.test_audio, 0, "Test Audio")) self.list_data.append((self.test_midi, 0, "Test MIDI")) @@ -393,6 +397,9 @@ def toggle_midi_sys(self): def bluetooth(self): self.zyngui.show_screen("bluetooth") + def touchkeypad_labels(self): + self.zyngui.show_screen("touchkeypad_labels") + # ------------------------------------------------------------------------- # Global Transpose editing # ------------------------------------------------------------------------- diff --git a/zyngui/zynthian_gui_touchkeypad_labels.py b/zyngui/zynthian_gui_touchkeypad_labels.py new file mode 100644 index 000000000..d27515195 --- /dev/null +++ b/zyngui/zynthian_gui_touchkeypad_labels.py @@ -0,0 +1,49 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- + +import os +import logging +from time import sleep +from threading import Thread +from subprocess import check_output + +# Zynthian specific modules +import zynconf +from zyngui import zynthian_gui_config +from zyngui.zynthian_gui_selector import zynthian_gui_selector + +# ------------------------------------------------------------------------------ +# ZynthianTouchKeypad label config GUI Class +# ----------------------------------------------------------------------------- + +class zynthian_gui_touchkeypad_labels(zynthian_gui_selector): + + def __init__(self): + super().__init__('Touchkeypad F-key labels', True) + self.list_data = [] + + def build_view(self): + return super().build_view() + + def fill_list(self): + self.list_data = [] + if zynthian_gui_config.touch_keypad: + for i in range(8): + label = zynthian_gui_config.touch_keypad.get_fkey_label(i) + self.list_data.append((self.edit_label, i, f"F{i+1}: {label}")) + super().fill_list() + + def select_action(self, i, t='S'): + if callable(self.list_data[i][0]): + self.list_data[i][0](self.list_data[i][1]) + + def edit_label(self, n): + label = zynthian_gui_config.touch_keypad.get_fkey_label(n) + self.zyngui.show_keyboard(self.rename_label, label) + + def rename_label(self, new_label): + n = self.list_data[self.index][1] + zynthian_gui_config.touch_keypad.set_fkey_label(n, new_label) + self.update_list() + +# ------------------------------------------------------------------------------ diff --git a/zyngui/zynthian_gui_touchkeypad_v5.py b/zyngui/zynthian_gui_touchkeypad_v5.py index d3588fb1b..35feb8eea 100644 --- a/zyngui/zynthian_gui_touchkeypad_v5.py +++ b/zyngui/zynthian_gui_touchkeypad_v5.py @@ -37,6 +37,8 @@ 'F4': ({'default': 'F4', 'alt': 'F8'}, 23, 19, False) } +FKEY2SWITCH = [ BUTTONS['F1'][1], BUTTONS['F2'][1], BUTTONS['F3'][1], BUTTONS['F4'][1]] + LED2BUTTON = {btn[2]: btn[1]-4 for btn in BUTTONS.values()} LAYOUT_RIGHT = { @@ -333,6 +335,23 @@ def apply_user_config(self): if active2: self.btndefs[n][0]['active2'] = active2 + def _fkey2btn(self, n): + mode = 'default' + if n >= 4: + mode = 'alt' + n -= 4 + return (FKEY2SWITCH[n]-4, mode) + + def set_fkey_label(self, n, label): + btn, mode = self._fkey2btn(n) + self.btndefs[btn][0][mode] = label + self.refresh_button_label(btn, label) + + def get_fkey_label(self, n): + btn, mode = self._fkey2btn(n) + return self.btndefs[btn][0][mode] + + # def set_button_label(self, n, mode, label): # """ # Dynamic label updates From dab2c372297c0fe9d7dc5a4f3fb88851f44dca39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pavel=20Vond=C5=99i=C4=8Dka?= Date: Sat, 12 Oct 2024 02:47:43 +0100 Subject: [PATCH 015/117] V5touch: UI option to turn on/off the V5 touch keypad. --- zyngui/zynthian_gui_admin.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/zyngui/zynthian_gui_admin.py b/zyngui/zynthian_gui_admin.py index 0a0ab774c..7053c066f 100644 --- a/zyngui/zynthian_gui_admin.py +++ b/zyngui/zynthian_gui_admin.py @@ -187,7 +187,10 @@ def fill_list(self): self.list_data.append((None, 0, "> TOUCH KEYPAD")) if zynthian_gui_config.touch_keypad: + self.list_data.append((self.toggle_touchkeypad, 0, "\u2612 V5 Touch Keypad")) self.list_data.append((self.touchkeypad_labels, 0, "F-key labels")) + else: + self.list_data.append((self.toggle_touchkeypad, 0, "\u2610 V5 Touch Keypad")) self.list_data.append((None, 0, "> TEST")) self.list_data.append((self.test_audio, 0, "Test Audio")) @@ -400,6 +403,13 @@ def bluetooth(self): def touchkeypad_labels(self): self.zyngui.show_screen("touchkeypad_labels") + def toggle_touchkeypad(self): + if zynthian_gui_config.touch_keypad: + zynconf.save_config({'ZYNTHIAN_TOUCH_KEYPAD': ''}) + else: + zynconf.save_config({'ZYNTHIAN_TOUCH_KEYPAD': 'V5'}) + self.restart_gui() + # ------------------------------------------------------------------------- # Global Transpose editing # ------------------------------------------------------------------------- From b74d1266dee196f7dad4efb5f80b357844063142 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pavel=20Vond=C5=99i=C4=8Dka?= Date: Sat, 12 Oct 2024 13:31:47 +0200 Subject: [PATCH 016/117] V5touch: udate zynthian_state_schema.py. --- zynthian_state_schema.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/zynthian_state_schema.py b/zynthian_state_schema.py index 7eca94a19..546ace159 100644 --- a/zynthian_state_schema.py +++ b/zynthian_state_schema.py @@ -172,5 +172,8 @@ "current_screen_index": 8 # Index of last selected controller view page } } + }, + "touckeypad": { # Optional configuration if UI Touch keypad (onscreen touch buttons) + "flabels": ["F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8"] # Array of 8 custom string labels for F1-F8 keys } } \ No newline at end of file From 990f83578aa3f4c63c174e430efe26b404fe205b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pavel=20Vond=C5=99i=C4=8Dka?= Date: Fri, 8 Nov 2024 00:03:34 +0000 Subject: [PATCH 017/117] V5touch: add confirmation for UI option to turn on/off the V5 touch keypad. --- zyngui/zynthian_gui_admin.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/zyngui/zynthian_gui_admin.py b/zyngui/zynthian_gui_admin.py index 7053c066f..3cdf68f9b 100644 --- a/zyngui/zynthian_gui_admin.py +++ b/zyngui/zynthian_gui_admin.py @@ -405,9 +405,16 @@ def touchkeypad_labels(self): def toggle_touchkeypad(self): if zynthian_gui_config.touch_keypad: - zynconf.save_config({'ZYNTHIAN_TOUCH_KEYPAD': ''}) + self.zyngui.show_confirm("Do you really want to restart UI with touch keypad off?", self.touchkeypad_off_confirmed) else: - zynconf.save_config({'ZYNTHIAN_TOUCH_KEYPAD': 'V5'}) + self.zyngui.show_confirm("Do you really want to restart UI with touch keypad on?", self.touchkeypad_on_confirmed) + + def touchkeypad_off_confirmed(self, params=None): + zynconf.save_config({'ZYNTHIAN_TOUCH_KEYPAD': ''}) + self.restart_gui() + + def touchkeypad_on_confirmed(self, params=None): + zynconf.save_config({'ZYNTHIAN_TOUCH_KEYPAD': 'V5'}) self.restart_gui() # ------------------------------------------------------------------------- From ce33c3ca8f947c174fb34765771653a5297c6460 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pavel=20Vond=C5=99i=C4=8Dka?= Date: Fri, 8 Nov 2024 15:38:00 +0100 Subject: [PATCH 018/117] V5touch: cleanup and formalities. --- zyngui/zynthian_gui_touchkeypad_labels.py | 24 +++- zyngui/zynthian_gui_touchkeypad_v5.py | 157 +++++++++++----------- zyngui/zynthian_wsleds_v5touch.py | 51 +++++-- 3 files changed, 140 insertions(+), 92 deletions(-) diff --git a/zyngui/zynthian_gui_touchkeypad_labels.py b/zyngui/zynthian_gui_touchkeypad_labels.py index f193c1ee3..038d6c7a9 100644 --- a/zyngui/zynthian_gui_touchkeypad_labels.py +++ b/zyngui/zynthian_gui_touchkeypad_labels.py @@ -1,5 +1,27 @@ #!/usr/bin/python3 # -*- coding: utf-8 -*- +# ****************************************************************************** +# ZYNTHIAN PROJECT: Zynthian GUI +# +# Zynthian Touchkeypad Label Configuration GUI Class +# +# Copyright (C) 2024 Pavel Vondřička +# +# ****************************************************************************** +# +# 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 os import logging @@ -13,7 +35,7 @@ from zyngui.zynthian_gui_selector import zynthian_gui_selector # ------------------------------------------------------------------------------ -# ZynthianTouchKeypad label config GUI Class +# Zynthian Touchkeypad Label Configuration GUI Class # ----------------------------------------------------------------------------- class zynthian_gui_touchkeypad_labels(zynthian_gui_selector): diff --git a/zyngui/zynthian_gui_touchkeypad_v5.py b/zyngui/zynthian_gui_touchkeypad_v5.py index 2d8963675..820fc9cf7 100644 --- a/zyngui/zynthian_gui_touchkeypad_v5.py +++ b/zyngui/zynthian_gui_touchkeypad_v5.py @@ -1,46 +1,79 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- +# ****************************************************************************** +# ZYNTHIAN PROJECT: Zynthian GUI +# +# Zynthian Touchscreen Keypad V5 Class +# +# Copyright (C) 2024 Pavel Vondřička +# +# ****************************************************************************** +# +# 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 os import tkinter -from PIL import Image, ImageTk #, ImageDraw, ImageFont +from PIL import Image, ImageTk from io import BytesIO try: import cairosvg except: cairosvg = None + # Zynthian specific modules from zyngui import zynthian_gui_config +# ------------------------------------------------------------------------------ +# Touchscreen V5 keypad configuration +# ------------------------------------------------------------------------------ + +# Button definitions and mapping + BUTTONS = { - # labels, ZYNSWITCH number, wsLED number, two state button (not used) - 'OPT_ADMIN': ({'default': 'OPT/ADMIN'}, 4, 0, True), - 'MIX_LEVEL': ({'default': 'MIX/LEVEL'}, 5, 1, True), - 'CTRL_PRESET': ({'default': 'CTRL/PRESET'}, 6, 2, True), - 'ZS3_SHOT': ({'default': 'ZS3/SHOT'}, 7, 3, True), - 'METRONOME': ({'default': '_icons/metronome.svg'}, 9, 6, False), - 'PAD_STEP': ({'default': 'PAD/STEP'}, 10, 5, True), - 'ALT': ({'default': 'Alt'}, 8, 4, False), - - 'REC': ({'default': '\uf111'}, 12, 8, False), - 'STOP': ({'default': '\uf04d'}, 13, 9, False), - 'PLAY': ({'default': '\uf04b', 'active': '\uf04c'}, 14, 10, False), - - 'UP': ({'default': '\uf077'}, 17, 14, False), - 'DOWN': ({'default': '\uf078'}, 21, 17, False), - 'LEFT': ({'default': '\uf053'}, 20, 16, False), - 'RIGHT': ({'default': '\uf054'}, 22, 18, False), - 'SEL_YES': ({'default': 'SEL/YES'}, 18, 13, False), - 'BACK_NO': ({'default': 'BACK/NO'}, 16, 15, False), - - 'F1': ({'default': 'F1', 'alt': 'F5'}, 11, 7, False), - 'F2': ({'default': 'F2', 'alt': 'F6'}, 15, 11, False), - 'F3': ({'default': 'F3', 'alt': 'F7'}, 19, 12, False), - 'F4': ({'default': 'F4', 'alt': 'F8'}, 23, 19, False) + # labels, ZYNSWITCH number, wsLED number + 'OPT_ADMIN': ({'default': 'OPT/ADMIN'}, 4, 0), + 'MIX_LEVEL': ({'default': 'MIX/LEVEL'}, 5, 1), + 'CTRL_PRESET': ({'default': 'CTRL/PRESET'}, 6, 2), + 'ZS3_SHOT': ({'default': 'ZS3/SHOT'}, 7, 3), + 'METRONOME': ({'default': '_icons/metronome.svg'}, 9, 6), + 'PAD_STEP': ({'default': 'PAD/STEP'}, 10, 5), + 'ALT': ({'default': 'Alt'}, 8, 4), + + 'REC': ({'default': '\uf111'}, 12, 8), + 'STOP': ({'default': '\uf04d'}, 13, 9), + 'PLAY': ({'default': '\uf04b', 'active': '\uf04c'}, 14, 10), + + 'UP': ({'default': '\uf077'}, 17, 14), + 'DOWN': ({'default': '\uf078'}, 21, 17), + 'LEFT': ({'default': '\uf053'}, 20, 16), + 'RIGHT': ({'default': '\uf054'}, 22, 18), + 'SEL_YES': ({'default': 'SEL/YES'}, 18, 13), + 'BACK_NO': ({'default': 'BACK/NO'}, 16, 15), + + 'F1': ({'default': 'F1', 'alt': 'F5'}, 11, 7), + 'F2': ({'default': 'F2', 'alt': 'F6'}, 15, 11), + 'F3': ({'default': 'F3', 'alt': 'F7'}, 19, 12), + 'F4': ({'default': 'F4', 'alt': 'F8'}, 23, 19) } FKEY2SWITCH = [ BUTTONS['F1'][1], BUTTONS['F2'][1], BUTTONS['F3'][1], BUTTONS['F4'][1]] LED2BUTTON = {btn[2]: btn[1]-4 for btn in BUTTONS.values()} +# Layout definitions + LAYOUT_RIGHT = { 'SIDE' : ( ('OPT_ADMIN', 'MIX_LEVEL'), @@ -65,6 +98,9 @@ 'BOTTOM': ('RIGHT', 'REC', 'STOP', 'PLAY', 'F1', 'F2', 'F3', 'F4') } +# ------------------------------------------------------------------------------ +# Zynthian Touchscreen Keypad V5 Class +# ------------------------------------------------------------------------------ class zynthian_gui_touchkeypad_v5: @@ -85,9 +121,6 @@ def __init__(self, parent, side_width, left_side=True): self.side_frame_col = 0 if left_side else 1 self.bottom_frame_col = 1 if left_side else 0 self.font_size = zynthian_gui_config.font_size - # only needed for composition of images from text labels - # self.font1 = ImageFont.truetype("Audiowide-Regular.ttf", size=int(1.4*self.font_size)) # zynthian_gui_config.font_buttonbar[0], size=zynthian_gui_config.font_buttonbar[1]) - # self.font2 = ImageFont.truetype("Audiowide-Regular.ttf", size=int(1.1*self.font_size)) # configure side frame for 2x6 buttons self.side_frame = tkinter.Frame(parent, @@ -121,7 +154,7 @@ def __init__(self, parent, side_width, left_side=True): self.btndefs = [None] * 20 # original definition of the button parameters self.images = [None] * 20 # original image/icon used (if any) self.btnstate = [None] * 20 # last state of the button (<=color) - self.tkimages = [None] * 20 # current image in tkinter format (remember it or the image will be discarded by the garbage collector!) + self.tkimages = [None] * 20 # current image in tkinter format (avoid discarding by the garbage collector!) # create side frame buttons for row in range(6): @@ -171,8 +204,8 @@ def add_button(self, n, parent, row, column, zynswitch, label, padx, pady): """ button = tkinter.Button( parent, - width= 1, # if not label.startswith('_') else self.side_button_width, - height= 1, # if not label.startswith('_') else self.side_button_height, + width= 1, + height= 1, bg=zynthian_gui_config.color_bg, fg=zynthian_gui_config.color_header_tx, activebackground=zynthian_gui_config.color_panel_bg, @@ -198,24 +231,19 @@ def add_button(self, n, parent, row, column, zynswitch, label, padx, pady): else: # PNG icons can be imported directly image = Image.open(img_name) - # at least fo image icons, it is useful to store the original image for the purpose of later changes of color + # store the original image for the purpose of later changes of color (useful for image icons) self.images[n] = image tkimage = ImageTk.PhotoImage(image) - # if we don't keep the image in the object, it will be discarded by garbage collection at the end of this method! + # if we don't keep the image in the object, + # it will be discarded by garbage collection at the end of this method! self.tkimages[n] = tkimage button.config(image=tkimage, text='') else: - # if "/" in label: - # # two level/lines text label - drawn as image - # line1, line2 = label.split('/') - # image = self.text2image(line1, line2, 'white', 'white', 'white') - # self.images[n] = image - # tkimage = ImageTk.PhotoImage(image) - # self.tkimages[n] = tkimage - # button.config(image=tkimage, text='') - # else: - # button has a simple text label: either standard text or an icon included in the "forkawesome" font (unicode char >= \uf000) - button.config(font=("forkawesome", int(1*zynthian_gui_config.font_size)) if label[0] >= '\uf000' else (zynthian_gui_config.font_family, int(0.9*zynthian_gui_config.font_size)), + # button has a simple text label: either standard text + # or an icon included in the "forkawesome" font (unicode char >= \uf000) + button.config(font=("forkawesome", + int(1*zynthian_gui_config.font_size)) if label[0] >= '\uf000' + else (zynthian_gui_config.font_family, int(0.9*zynthian_gui_config.font_size)), text=label.replace('/', "\n")) button.grid_propagate(False) button.grid(row=row, column=column, sticky='nswe', padx=padx, pady=pady) @@ -223,20 +251,6 @@ def add_button(self, n, parent, row, column, zynswitch, label, padx, pady): button.bind('', lambda e: self.cb_button_release(zynswitch, e)) return button - # def text2image(self, line1, line2, color, color1, color2): - # """ - # Create image from two lines of labels with a horizontal bar (line) inbetween - # """ - # sizex = self.side_button_width - 4 - # sizey = self.side_button_height - 4 # int(5*self.font_size) - # image = Image.new("RGBA", (sizex, sizey), "#00000000") - # d = ImageDraw.Draw(image) - # x = sizex // 2 - # d.text((x, (sizey//2)-(self.font_size//1.5)), line1, font=self.font1, fill=color1, anchor="ms") - # d.text((x, (sizey//2)+(self.font_size//1.5)), line2, font=self.font2, fill=color2, anchor="mt") - # d.line([(self.font_size//2,sizey//2), (sizex-(self.font_size//2),sizey//2)], fill=color, width=2) - # return image - def cb_button_push(self, n, event): """ Call ZYNSWITCH Push CUIA on button push @@ -261,8 +275,8 @@ def set_button_color(self, led_num, color, mode): color : int Color requested by the wsled system mode : str - A wanna-be abstraction (string name) of the mode/state - currently just derived from the requested color - by the `wsleds_v5touch` "fake NeoPixel" emulator + A wanna-be abstraction (string name) of the mode/state - currently + just derived from the requested color by the `wsleds_v5touch` "fake NeoPixel" emulator """ # get the button number associated with the wsled number n = LED2BUTTON[led_num] @@ -272,8 +286,7 @@ def set_button_color(self, led_num, color, mode): self.btnstate[n] = mode or color # in case the color is still the original wsled integer number, convert it label = self.btndefs[n][0]['default'] - # twostate = self.btndefs[n][4] - if label.startswith('_'): # self.buttons[n].config()['image'][4]=='': + if label.startswith('_'): # image buttons must be recomposed to change the foreground color image = self.images[n] mask = image.convert("LA") @@ -283,19 +296,6 @@ def set_button_color(self, led_num, color, mode): tkimage = ImageTk.PhotoImage(composed) self.tkimages[n] = tkimage self.buttons[n].config(image=tkimage) - # elif '/' in label: - # # two level button labels with multiple colors must redraw the whole image - # line1, line2 = label.split('/') - # color0 = color1 = color2 = color - # if twostate and zynthian_gui_config.zyngui: - # color0 = zynthian_gui_config.zyngui.wsleds.wscolor_default - # color1 = zynthian_gui_config.zyngui.wsleds.wscolor_active if mode == 'active' else zynthian_gui_config.zyngui.wsleds.wscolor_default - # color2 = zynthian_gui_config.zyngui.wsleds.wscolor_active2 if mode == 'active2' else zynthian_gui_config.zyngui.wsleds.wscolor_default - # image = self.text2image(line1, line2, color0, color1, color2) - # self.images[n] = image - # tkimage = ImageTk.PhotoImage(image) - # self.tkimages[n] = tkimage - # self.buttons[n].config(image=tkimage, text='') else: # plain text labels may just change the color and possibly also its label if a special label # is associated with the requested mode (<=color) in the button definition @@ -351,10 +351,3 @@ def get_fkey_label(self, n): btn, mode = self._fkey2btn(n) return self.btndefs[btn][0][mode] - - # def set_button_label(self, n, mode, label): - # """ - # Dynamic label updates - # """ - # self.btndefs[n][mode] = label - # self.refresh_button_label(n, self.btnstate[n]) diff --git a/zyngui/zynthian_wsleds_v5touch.py b/zyngui/zynthian_wsleds_v5touch.py index 7ddad1850..b9e65d6ec 100644 --- a/zyngui/zynthian_wsleds_v5touch.py +++ b/zyngui/zynthian_wsleds_v5touch.py @@ -1,17 +1,47 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- +# ****************************************************************************** +# ZYNTHIAN PROJECT: Zynthian GUI +# +# Zynthian WSLeds Class for LED emulation on touchscreen keypad V5 +# +# Copyright (C) 2024 Pavel Vondřička +# +# ****************************************************************************** +# +# 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 os + +# Zynthian specific modules from zyngui import zynthian_gui_config from zyngui.zynthian_wsleds_v5 import zynthian_wsleds_v5 +# --------------------------------------------------------------------------- +# Fake NeoPixel emulation for onscreen touch keypad "buttons" +# --------------------------------------------------------------------------- class touchkeypad_button_colors: """ - Fake NeoPixel emulation to change colors of onscreen touch keypad (instead of a wsled strip) + Fake NeoPixel emulation to change colors of onscreen touch keypad """ def __init__(self, wsleds): self.wsleds = wsleds self.zyngui = wsleds.zyngui - # A wanna-be abstraction: reverse derive a named "mode" from the requested colors + # A wanna-be abstraction: derive a named "mode" from the requested colors self.mode_map = {} self.mode_map[wsleds.wscolor_default] = 'default' self.mode_map[wsleds.wscolor_alt] = 'alt' @@ -22,9 +52,9 @@ def __setitem__(self, index, color): mode = self.mode_map.get(color, None) # request color change on the onscreen touchkeypad if isinstance(color, int): - color = f"#{color:06x}" # conversion to the hex code of the color - # color = ((color >> 16) & 255, (color >> 8) & 255, color & 255) # conversion to (r, g, b) tuple - # tkinter is not able to set RGBA/alpha color, we need to blend the foreground color with the backgroudn color + color = f"#{color:06x}" # color conversion to hex cod + # tkinter is not able to set RGBA/alpha color, + # so we need to blend the foreground color with the background color if zynthian_gui_config.zyngui: fgcolor = self.hex_to_rgb(color) bgcolor = self.hex_to_rgb(self.wsleds.wscolor_off) @@ -56,6 +86,9 @@ def rgb_to_hex(self, rgb): r, g, b = rgb return '#{:02x}{:02x}{:02x}'.format(r, g, b) +# --------------------------------------------------------------------------- +# Zynthian WSLeds class for LED emulation on touchscreen keypad V5 +# --------------------------------------------------------------------------- class zynthian_wsleds_v5touch(zynthian_wsleds_v5): """ @@ -70,13 +103,13 @@ def setup_colors(self): # Predefined colors self.wscolor_off = os.environ.get('ZYNTHIAN_TOUCH_KEYPAD_COLOR_OFF', zynthian_gui_config.color_bg) self.wscolor_white = os.environ.get('ZYNTHIAN_TOUCH_KEYPAD_COLOR_WHITE', "#FCFCFC") - self.wscolor_red = os.environ.get('ZYNTHIAN_TOUCH_KEYPAD_COLOR_RED', "#FE2C2F") # #FF8A92 + self.wscolor_red = os.environ.get('ZYNTHIAN_TOUCH_KEYPAD_COLOR_RED', "#FE2C2F") self.wscolor_green = os.environ.get('ZYNTHIAN_TOUCH_KEYPAD_COLOR_GREEN', "#00FA00") self.wscolor_yellow = os.environ.get('ZYNTHIAN_TOUCH_KEYPAD_COLOR_YELLOW', "#F0EA00") - self.wscolor_orange = os.environ.get('ZYNTHIAN_TOUCH_KEYPAD_COLOR_ORANGE', "#FF6A00") # #FFA200 - self.wscolor_blue = os.environ.get('ZYNTHIAN_TOUCH_KEYPAD_COLOR_BLUE', "#1070FE") # lighter: #5397FE, #38EBFF + self.wscolor_orange = os.environ.get('ZYNTHIAN_TOUCH_KEYPAD_COLOR_ORANGE', "#FF6A00") + self.wscolor_blue = os.environ.get('ZYNTHIAN_TOUCH_KEYPAD_COLOR_BLUE', "#1070FE") self.wscolor_blue_light = os.environ.get('ZYNTHIAN_TOUCH_KEYPAD_COLOR_LIGHTBLUE', "#05FDFF") - self.wscolor_purple = os.environ.get('ZYNTHIAN_TOUCH_KEYPAD_COLOR_PURPLE', "#D662FE") # #FF80EB + self.wscolor_purple = os.environ.get('ZYNTHIAN_TOUCH_KEYPAD_COLOR_PURPLE', "#D662FE") self.wscolor_default = os.environ.get('ZYNTHIAN_TOUCH_KEYPAD_COLOR_DEFAULT', self.wscolor_blue) self.wscolor_alt = os.environ.get('ZYNTHIAN_TOUCH_KEYPAD_COLOR_ALT', self.wscolor_purple) self.wscolor_active = os.environ.get('ZYNTHIAN_TOUCH_KEYPAD_COLOR_ACTIVE', self.wscolor_green) From 91109c680e0a70ef489a07a9228cf260ad3d8c77 Mon Sep 17 00:00:00 2001 From: riban Date: Wed, 20 Nov 2024 16:57:58 +0000 Subject: [PATCH 019/117] Hotplug audio fixes. Add hotplug audio outputs. Fix routing when more than 9 audio input/outputs (jack uses regex so will match _1 and _11). Fix shutdown hanging on stopping hotplug audio. Fix some PEP8 (remove tabs). --- zynautoconnect/zynthian_autoconnect.py | 188 ++++++++++++------------- zyngine/zynthian_chain.py | 4 +- zyngui/zynthian_gui_admin.py | 2 +- zyngui/zynthian_gui_audio_out.py | 15 +- 4 files changed, 100 insertions(+), 109 deletions(-) diff --git a/zynautoconnect/zynthian_autoconnect.py b/zynautoconnect/zynthian_autoconnect.py index 832e59eb5..07c3b1037 100755 --- a/zynautoconnect/zynthian_autoconnect.py +++ b/zynautoconnect/zynthian_autoconnect.py @@ -927,108 +927,109 @@ def audio_autoconnect(): # Release Mutex Lock release_lock() - def get_hw_audio_dst_ports(): - return hw_audio_dst_ports - + return jclient.get_ports("system:playback", is_input=True, is_audio=True, is_physical=True) + jclient.get_ports("zynaout", is_input=True, is_audio=True) def update_hw_audio_ports(): - global alsa_audio_srcs, alsa_audio_dests - - dirty = False - if zynthian_gui_config.hotplug_audio: - # Add new devices - for device in get_alsa_audio_devices(False): - dirty |= start_alsa_in(device) - for device in get_alsa_audio_devices(True): - dirty |= start_alsa_out(device) - - # Remove disconnected devices - for device in list(alsa_audio_srcs): - try: - while True: - proc = alsa_audio_srcs[device] - line = proc.readline() - if line.startswith("err"): - proc.terminate() - alsa_audio_srcs.pop(device) - dirty = True - break - elif not line: - break - except: - continue - for device in list(alsa_audio_dests): - try: - while True: - proc = alsa_audio_dests[device] - line = proc.readline() - if line.startswith("err"): - proc.terminate() - alsa_audio_dests.pop(device) - dirty = True - break - elif not line: - break - except: - continue - - return dirty + global alsa_audio_srcs, alsa_audio_dests + + dirty = False + if zynthian_gui_config.hotplug_audio: + # Add new devices + for device in get_alsa_audio_devices(False): + dirty |= start_alsa_in(device) + for device in get_alsa_audio_devices(True): + dirty |= start_alsa_out(device) + + # Remove disconnected devices + for device in list(alsa_audio_srcs): + try: + while True: + proc = alsa_audio_srcs[device] + line = proc.readline() + if line.startswith("err"): + proc.terminate() + alsa_audio_srcs.pop(device) + dirty = True + break + elif not line: + break + except: + continue + for device in list(alsa_audio_dests): + try: + while True: + proc = alsa_audio_dests[device] + line = proc.readline() + if line.startswith("err"): + proc.terminate() + alsa_audio_dests.pop(device) + dirty = True + break + elif not line: + break + except: + continue + if dirty: + # Rebuild chain audio routes + try: + sleep(0.5) # Have to wait for jack to finish registering ports + for chain in chain_manager.chains.values(): + chain.rebuild_audio_graph() + except Exception as e: + logging.error(e) + return dirty -def get_alsa_audio_devices(playback=True): - devices = [] - for card in alsaaudio.pcms(alsaaudio.PCM_PLAYBACK if playback else alsaaudio.PCM_CAPTURE): - if card.startswith("hw:"): - devices.append(card[8:card.find(",")]) - return devices +def get_alsa_audio_devices(playback=True): + devices = [] + for card in alsaaudio.pcms(alsaaudio.PCM_PLAYBACK if playback else alsaaudio.PCM_CAPTURE): + if card.startswith("hw:"): + devices.append(card[8:card.find(",")]) + return devices def start_alsa_in(device): - global alsa_audio_srcs - if device in alsa_audio_srcs: - return False - proc = pexpect.spawn(f"alsa_in -d hw:{device} -j zynain_{device}", encoding="utf-8", timeout=0.1) - if proc.exitstatus: - return False - alsa_audio_srcs[device] = proc - return True - + global alsa_audio_srcs + if device in alsa_audio_srcs: + return False + proc = pexpect.spawn(f"alsa_in -d hw:{device} -j zynain_{device}", encoding="utf-8", timeout=0.1) + if proc.exitstatus: + return False + alsa_audio_srcs[device] = proc + return True def stop_alsa_in(device): - global alsa_audio_srcs - if device not in alsa_audio_srcs: - return False - alsa_audio_srcs[device].terminate() - alsa_audio_srcs.pop(device) - return True - - -def stop_all_alsa_in(): - for device in get_alsa_audio_devices(False): - stop_alsa_in(device) - + global alsa_audio_srcs + if device not in alsa_audio_srcs: + return False + alsa_audio_srcs[device].terminate() + alsa_audio_srcs.pop(device) + return True def start_alsa_out(device): - return False # TODO Disabled whilst getting hotplug audio inputs working. - global alsa_audio_dests - if device in alsa_audio_dests: - return False - proc = pexpect.spawn(f"alsa_out -d hw:{device} -j zynaout_{device}", encoding="utf-8", timeout=0.1) - if proc.exitstatus: - return False - alsa_audio_dests[device] = proc - return True - + global alsa_audio_dests + if device in alsa_audio_dests: + return False + proc = pexpect.spawn(f"alsa_out -d hw:{device} -j zynaout_{device}", encoding="utf-8", timeout=0.1) + if proc.exitstatus: + return False + alsa_audio_dests[device] = proc + return True def stop_alsa_out(device): - global alsa_audio_dests - if device not in alsa_audio_dests: - return False - alsa_audio_dests[device].terminate() - alsa_audio_dests.pop(device) - return True + global alsa_audio_dests + if device not in alsa_audio_dests: + return False + alsa_audio_dests[device].terminate() + alsa_audio_dests.pop(device) + return True +def stop_all_alsa_in_out(): + for device in get_alsa_audio_devices(False): + stop_alsa_in(device) + for device in get_alsa_audio_devices(True): + stop_alsa_out(device) # Connect mixer to the ffmpeg recorder def audio_connect_ffmpeg(timeout=2.0): @@ -1307,9 +1308,7 @@ def start(sm): init() # Get System Playback Ports - hw_audio_dst_ports = jclient.get_ports( - "system:playback", is_input=True, is_audio=True, is_physical=True) - + hw_audio_dst_ports = get_hw_audio_dst_ports() try: with open(f"{zynconf.config_dir}/sidechain.json", "r") as file: sidechain_map = json.load(file) @@ -1329,7 +1328,7 @@ def start(sm): def stop(): """Reset state and stop autoconnect thread""" - global exit_flag, jclient, thread, lock + global exit_flag, jclient, thread, lock, hw_audio_dst_ports exit_flag = True if thread: thread.join() @@ -1341,12 +1340,7 @@ def stop(): hw_audio_dst_ports = [] - for process in alsa_audio_srcs.values(): - process.terminate() - alsa_audio_srcs = {} - for process in alsa_audio_dests.values(): - process.terminate() - alsa_audio_dests = {} + stop_all_alsa_in_out() if jclient: jclient.deactivate() diff --git a/zyngine/zynthian_chain.py b/zyngine/zynthian_chain.py index 6fd748db5..5e9f7fbdd 100644 --- a/zyngine/zynthian_chain.py +++ b/zyngine/zynthian_chain.py @@ -381,9 +381,9 @@ def get_input_pairs(self): continue if i < len(self.audio_in) - 1: b = self.audio_in[i + 1] - sources.append(f"{input_ports[a-1].name}|{input_ports[b-1].name}") + sources.append(f"^{input_ports[a-1].name}$|^{input_ports[b-1].name}$") else: - sources.append(input_ports[a-1].name) + sources.append(f"^{input_ports[a-1].name}$") return sources def rebuild_midi_graph(self): diff --git a/zyngui/zynthian_gui_admin.py b/zyngui/zynthian_gui_admin.py index 424c2b276..59ae1ccc4 100644 --- a/zyngui/zynthian_gui_admin.py +++ b/zyngui/zynthian_gui_admin.py @@ -410,7 +410,7 @@ def stop_hotplug_audio(self, save_config=True): logging.info("STOPPING HOTPLUG AUDIO") zynthian_gui_config.hotplug_audio = 0 - zynautoconnect.stop_all_alsa_in() + zynautoconnect.stop_all_alsa_in_out() # Update Config if save_config: zynconf.save_config({ diff --git a/zyngui/zynthian_gui_audio_out.py b/zyngui/zynthian_gui_audio_out.py index 6d23e4cbc..8d6f006a2 100644 --- a/zyngui/zynthian_gui_audio_out.py +++ b/zyngui/zynthian_gui_audio_out.py @@ -42,7 +42,6 @@ class zynthian_gui_audio_out(zynthian_gui_selector): def __init__(self): self.chain = None - self.playback_ports = zynautoconnect.get_hw_audio_dst_ports() super().__init__('Audio Out', True) def build_view(self): @@ -74,8 +73,8 @@ def refresh_status(self): if self.check_ports > 10: self.check_ports = 0 ports = zynautoconnect.get_hw_audio_dst_ports() - if self.playback_ports != zynautoconnect.get_hw_audio_dst_ports() != ports: - self.capture_ports = ports + if self.playback_ports != ports: + self.playback_ports = ports self.fill_list() def fill_list(self): @@ -109,13 +108,11 @@ def fill_list(self): # Direct physical outputs self.list_data.append((None, None, "> Direct Outputs")) port_count = len(self.playback_ports) - for i in range(1, port_count + 1, 2): + for i in range(0, port_count, 2): + port_names.append((f"Output {i + 1}", f"^{self.playback_ports[i].name}$")) if i < port_count: - port_names.append((f"Output {i}", f"system:playback_{i}$")) - port_names.append((f"Output {i + 1}", f"system:playback_{i + 1}$")) - port_names.append((f"Outputs {i}+{i + 1}", f"system:playback_[{i},{i + 1}]$")) - else: - port_names.append((f"Output {i}", f"system:playback_{i}$")) + port_names.append((f"Output {i + 2}", f"^{self.playback_ports[i + 1].name}$")) + port_names.append((f"Outputs {i + 1}+{i + 2}", f"^{self.playback_ports[i].name}$|^{self.playback_ports[i + 1].name}$")) for title, processor in port_names: if processor in self.chain.audio_out: self.list_data.append((processor, processor, "\u2612 " + title)) From 82e201395eb113dac7425c5c37efe21d3589352e Mon Sep 17 00:00:00 2001 From: jofemodo Date: Fri, 22 Nov 2024 09:37:45 +0100 Subject: [PATCH 020/117] Allow ALL MIDI channels in all chain types. KEEP AN EYE ON THIS CHANGE!! --- zyngui/zynthian_gui.py | 11 +++++------ zyngui/zynthian_gui_chain_options.py | 11 +++++------ 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/zyngui/zynthian_gui.py b/zyngui/zynthian_gui.py index eced07857..d9408714d 100644 --- a/zyngui/zynthian_gui.py +++ b/zyngui/zynthian_gui.py @@ -939,13 +939,12 @@ def modify_chain(self, status=None): else: # Select MIDI channel logging.debug(self.modify_chain_status) - if self.modify_chain_status["type"] == "MIDI Tool": + #if self.modify_chain_status["type"] == "MIDI Tool": # Enable "ALl Channels" option for MIDI chains - chan_all = True - else: - chan_all = False - self.screens["midi_chan"].set_mode( - "ADD", chan_all=chan_all) + # chan_all = True + #else: + # chan_all = False + self.screens["midi_chan"].set_mode("ADD", chan_all=True) self.show_screen("midi_chan") elif "type" in self.modify_chain_status: diff --git a/zyngui/zynthian_gui_chain_options.py b/zyngui/zynthian_gui_chain_options.py index edd851df4..7cb58d0f7 100644 --- a/zyngui/zynthian_gui_chain_options.py +++ b/zyngui/zynthian_gui_chain_options.py @@ -270,12 +270,11 @@ def processor_options(self, subchain, t='S'): self.zyngui.show_screen("processor_options") def chain_midi_chan(self): - if self.chain.get_type() == "MIDI Tool": - chan_all = True - else: - chan_all = False - self.zyngui.screens['midi_chan'].set_mode( - "SET", self.chain.midi_chan, chan_all=chan_all) + #if self.chain.get_type() == "MIDI Tool": + # chan_all = True + #else: + # chan_all = False + self.zyngui.screens['midi_chan'].set_mode("SET", self.chain.midi_chan, chan_all=True) self.zyngui.show_screen('midi_chan') def chain_midi_cc(self): From 2f1b1d54cdac342cb7d42e397c0f85fd9e54e8b6 Mon Sep 17 00:00:00 2001 From: jofemodo Date: Mon, 25 Nov 2024 11:45:21 +0100 Subject: [PATCH 021/117] Fix path when calling preset2lv2 tool. --- zyngine/zynthian_engine_jalv.py | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/zyngine/zynthian_engine_jalv.py b/zyngine/zynthian_engine_jalv.py index 9cccef96b..f070b5ffc 100644 --- a/zyngine/zynthian_engine_jalv.py +++ b/zyngine/zynthian_engine_jalv.py @@ -786,8 +786,7 @@ def zynapi_install(cls, dpath, bank_path): bpath, fname = os.path.split(f) head, bname = os.path.split(bpath) if bname: - shutil.rmtree(zynthian_engine.my_data_dir + - "/presets/lv2/" + bname, ignore_errors=True) + shutil.rmtree(zynthian_engine.my_data_dir +"/presets/lv2/" + bname, ignore_errors=True) shutil.move( bpath, zynthian_engine.my_data_dir + "/presets/lv2/") count += 1 @@ -797,25 +796,19 @@ def zynapi_install(cls, dpath, bank_path): # Else, try to convert from native format ... if os.path.isdir(dpath) or ext[1:].lower() == native_ext: - preset2lv2_cmd = "cd /tmp; /usr/local/bin/preset2lv2 {} \"{}\"".format( + preset2lv2_cmd = "cd /tmp; preset2lv2 {} \"{}\"".format( cls.zynapi_get_preset2lv2_format(), dpath) try: - res = check_output( - preset2lv2_cmd, stderr=STDOUT, shell=True).decode("utf-8") + res = check_output(preset2lv2_cmd, stderr=STDOUT, shell=True).decode("utf-8") for bname in re.compile("Bundle '(.*)' generated").findall(res): bpath = "/tmp/" + bname logging.debug("Copying LV2-Bundle '{}' ...".format(bpath)) - shutil.rmtree(zynthian_engine.my_data_dir + - "/presets/lv2/" + bname, ignore_errors=True) - shutil.move( - bpath, zynthian_engine.my_data_dir + "/presets/lv2/") - + shutil.rmtree(zynthian_engine.my_data_dir + "/presets/lv2/" + bname, ignore_errors=True) + shutil.move(bpath, zynthian_engine.my_data_dir + "/presets/lv2/") cls.refresh_zynapi_instance() - except Exception as e: raise Exception( "Conversion from {} to LV2 failed! => {}".format(native_ext, e)) - else: raise Exception("Unknown preset format: {}".format(native_ext)) @@ -825,7 +818,6 @@ def zynapi_get_formats(cls): fmt = cls.zynapi_get_native_ext() if fmt: formats = fmt + "," + formats - return formats @classmethod From f109cacdc08c9d041bc07017ad46bad7d09dcfb6 Mon Sep 17 00:00:00 2001 From: jofemodo Date: Mon, 25 Nov 2024 13:29:04 +0100 Subject: [PATCH 022/117] Add a custom command for running SooperLooper GUI and fix MIDI Bindings loading. --- zyngine/zynthian_engine_sooperlooper.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/zyngine/zynthian_engine_sooperlooper.py b/zyngine/zynthian_engine_sooperlooper.py index 9ae92489a..5ddd4745c 100644 --- a/zyngine/zynthian_engine_sooperlooper.py +++ b/zyngine/zynthian_engine_sooperlooper.py @@ -323,13 +323,20 @@ def __init__(self, state_manager=None): self.osc_target_port = self.SL_PORT - self.command = ["sooperlooper", "-q", "-l 0", "-D no", f"-p {self.osc_target_port}", f"-j{self.jackname}"] - # Load custom MIDI bindings custom_slb_fpath = self.config_dir + "/sooperlooper/zynthian.slb" if os.path.exists(custom_slb_fpath): - self.command += [f"-m {custom_slb_fpath}"] logging.info(f"loading sooperlooper custom MIDI bindings: {custom_slb_fpath}") + else: + custom_slb_fpath = None + + # Build SL command line + if self.config_remote_display(): + self.command = ["slgui", "-l 0", f"-P {self.osc_target_port}", f"-J {self.jackname}"] + else: + self.command = ["sooperlooper", "-q", "-l 0", "-D no", f"-p {self.osc_target_port}", f"-j {self.jackname}"] + if custom_slb_fpath: + self.command += ["-m", custom_slb_fpath] self.state = [-1] * self.MAX_LOOPS # Current SL state for each loop self.next_state = [-1] * self.MAX_LOOPS # Next SL state for each loop (-1 if no state change pending) @@ -413,9 +420,9 @@ def __init__(self, state_manager=None): # --------------------------------------------------------------------------- def start(self): - #logging.warning("Starting SooperLooper") + logging.debug(f"Starting SooperLooper with command: {self.command}") self.osc_init() - self.proc = Popen(self.command, stdout=DEVNULL, stderr=DEVNULL) + self.proc = Popen(self.command, stdout=DEVNULL, stderr=DEVNULL, env=self.command_env, cwd=self.command_cwd) sleep(1) # TODO: Cludgy wait - maybe should perform periodic check for server until reachable # Register for common events from sooperlooper server - request changes to the currently selected loop From 6b68e11338ce79bad67dc7efcf958d1f700b5bf2 Mon Sep 17 00:00:00 2001 From: jofemodo Date: Thu, 28 Nov 2024 10:23:37 +0100 Subject: [PATCH 023/117] Style adjustments to mixer's contextual help. --- help/audio_mixer.html | 24 ++++++++++--------- help/icons/white_icon_action_knob_rotate.png | Bin 6079 -> 19978 bytes help/img/mixer_strip_legend_bg_black.png | Bin 62117 -> 66446 bytes help/style.css | 5 +++- 4 files changed, 17 insertions(+), 12 deletions(-) diff --git a/help/audio_mixer.html b/help/audio_mixer.html index 81b5bbfb2..546fd2b7f 100644 --- a/help/audio_mixer.html +++ b/help/audio_mixer.html @@ -27,22 +27,23 @@ -

Audio Mixer

- -

The audio mixer is the zynthian UI's "home" view. You can always access - the mixer by pushing the MIX button.

-

It's composed of vertical strips associated with chains. Initially it will be empty, showing the main strip alone. - For each chain you create, a new strip is added to the mixer. There can be more mixer strips than can be displayed. - They will scroll left / right if you select a strip that is off screen.

-

Each strip is an interactive widget that can be manipulated in several ways and it shows status info about the - associated chain.

- -

From top to bottom:

+
+

Audio Mixer

+

The audio mixer is the zynthian UI's "home" view. You can always access + the mixer by pushing the MIX button.

+

It's composed of vertical strips associated with chains. Initially it will be empty, showing the main strip alone. + For each chain you create, a new strip is added to the mixer. There can be more mixer strips than can be displayed. + They will scroll left / right if you select a strip that is off screen.

+

Each strip is an interactive widget that can be manipulated in several ways and it shows status info about the + associated chain.

+

From top to bottom:

+
+
  • Solo: Solo flag. Push to toggle. This is a non-exclusive solo mode. When a chain has solo enabled, all other chains without solo enabled will be muted, allowing auditioning of one or more chains. The main solo @@ -58,6 +59,7 @@

    Audio Mixer

  • Legend: It shows an icon for the chain's type and a number for the assigned MIDI channel. Tap it to access the chain's control view.
+
diff --git a/help/icons/white_icon_action_knob_rotate.png b/help/icons/white_icon_action_knob_rotate.png index a4d805dccf72279a717aadbdd97f918ad48d75d8..fbc64a375e24963bdc4ceaee278b354d3f4bf704 100644 GIT binary patch literal 19978 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 FV1k*s%uWCR diff --git a/help/img/mixer_strip_legend_bg_black.png b/help/img/mixer_strip_legend_bg_black.png index 834f6c2b0c071011fd180b84b6ab672e64d110f5..c8d52ee4e2b956e2da3596b83af9ec4d297248e2 100644 GIT binary patch literal 66446 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 z≪hRaG%->sJP4p3}!aie&jnEnHmU8yg#G%R<|XZvx={f~Ld>+cIh6OdIeLBDdq2 zjh|#DcxrENPlNk}Rj>Aqp&><4QK_!c)~SykDR(dY!<2HHUz9Cgk zH^RK%tD74ee}qCsaKqcaJ-w(Nv);2QN4o-pGQ%@2uy)2o6F0Xn z8(9$Yg0Z^QuM$*mJw4C=`}$S!&8MA$PKJMhHGTj7sk)lS!ouQfMuvV35AZp2=so7W0s%`i@ixPvSQk*eDHjDVDWZ~&H`);5cwE+Aj;w<#`z~^f9N}o%UG#aoaBqXE@ zdB-QvD=oOyCAKYntFKQ@O(nLmwM87)a)1#}+AB7;g6!;%CAu}7qM}2st*!b`Oe9sn zQ~#Zubp7||0V5Mrvi_`};BOrkV7ZO(+kHdlu}~(>Qm31XL&+rzZsJ2PA-nnd#4d7Q zW!=l$5N+Ns})L?|Wkh2M5bN z6pfSlEJL%oy`9t4l#!X4`P_FvD)$+{CljEpY0<*IXTwudwPwp-Ta5m! z93E`u#+sCCd7VvLJ_#24)V5^)H0)1R_6qQrqbbwaa?2rd^ruggS!8jsLQ_&wmQJ?D zgpT{U+&n$mw(ATbgRQM%@J88AuPJp%7%u`c8^O=39v$6kdsT5O|G~JjM!AIuC1vkD}U%P=_zfQ z*m9e)ZP4uU!BvCarrDkDa z0k-Gk>$`8I?<_8kjJRrPDON(y@8$L%dSAbO{g;<)KouwR&xy~O#U#T zsE9Gs@8}({wjrTojBMpFE#_*5sOOT#*JryzcNZ(9LSCP@x3|rGTg$aDvWBF6eFY6K zdo^p_ws5nRt)^c(Ms|qV+A>MUkQz^x>c3Z4XW6d1KV7;nDVRTt7>T3lNMce_YC$AI zT*`^B|IeR46>WJnIg>ja#L?W`+)kUrF9BE&ZxM7!s`PeIel5o{pYAxe4>zh!wCkggYVtR$II7yC=(A=1(|j zpNopxSDfFg>sKeaN=iQNiXk&KF$vT(^nb0(=)&?-g9+RRSimpe^F7$0Y+n^MSGH|a z5Z_qH$Bz#({m-;2zS4^B7R@j z*VoNM|0VR$fCnxuWz7t@vK~z5Z#y|TiQp>dGDHIA(-}i1n3OU9pT#K)ky`@T2R`%e z-4pP9DnnBaHEL>V;=l+71_m&&vAOv9>+)^CBqde&o;whekN~a`DaI2ggM%K77A*GY z;RAU(JOHTt4G>baM}U*03wmOOwzVv^g>s|~XfY?j*na`IG-Chf(J&2gy2a>oVd1N* z>+2^dL9oTjiJDf9j+S0td<`O(QaU9(4bRO~ zRGzUW+m3{ZF%wXeV%=MX1%9{$00$Eg-@&Gv@T3t28|6<)g0+WYQ><4_8tA&>OioN3 zEu2G>J3BjRh3CD52K)Ol#>U0~NG`e5Iq{M5KV6nr_Hc7!7Z5;yV{T3h_FB$Dl_Xk9 zR#p}f@^wm*va?@;U}t4-Zw|k^?JIq*n5Wuq!VwZ0>b+OD;k8>eNkl|MiH}}VDdXlI53Q!fI7cW5Qz(o)KQC2pzFxGz(#l*zK@3M*p z(Adq*4JWcgL$5l)xN_R5VV;|Zr^5f*0|7aYoGEd>0SGKF=K#LNlV&dtBP7K@0bt*F zxt<323AxYaOOO49Xb{y2D0`wv0oO=POMAt_0!&$1N2mUKtLD1F4IqZQySoU~Ki(MP z;^Y+LBZY1W*h{MXnzpOeV~vtpiEqvx+Q=;~mb+(Ox$hYzX>g5gY|yiYLKpW7e*JRl zSN5?LU%BAyb#~1C21qon#VJ>+j^i~g;nVFqHU<(Ne;RAT|`t@ti)8qM4 zGtPIMDXaY{To_nbCfvk@B_$oZ9&Ck=ENW)UbxNiyk|(iu7rhkACi~>rn%a+tM6>j- z+=|)AQ;xoZ*!#|r$jHGVCME{!o@)MluMVCI5K$>g93ucN0B4yANtvESf-uZ9Q8W0-bsc)VHCDh6LI*W9^(V1x_47Lr?7}JSP#p5}E@>iHY1@7Orb2>UUR%!Ryj8PVZ z%|j4%RCRUJFhBPnmTN@;uSf7gM@P1DExYlmv`IDKqJa8=IL*n!6Ee2d&nn$}WAE^~ zO)y2i;@7V~B#w&ifKg5OWeJb<8SLq#q`*il)vG~HGaE0P_cb#U;S4LV>B>hz zEwo+0nMWrkqyVmWbaeD7$^eS{yrTLqPdzuitPJoBQ~r0wO+`C8}g#Ku#WO zIdZF=JIIuVtlkyi< zRXY6^T!7~wh->AvhJM5tz~1-oKaFj*+wtHSA6(gewa61qOaiXPw`vSuXO-f3FHLdL^C@GDNk4tK565ih4o`2W$ zsxj}?p7{Ism+OWi7MVc%-h2ay3rwuqz1@CKUIF)TsNv$^c*{;Cp@MHxKBd3^;vU)k zX^5jaK@NsUG~KuHL2T?;a$??IN*%D!(bauNm(V{nlsmao$yJm*dsNoYAgJ37=!jhy zx=sy17ZA`9E?ki5!+1UI@Vs@h&o1i%P{5}JWA8wAjgQX3&+pykD`cpIfVn;D!uJtY z+`YrYIP^-H?MqI-%=-vVeEh2miYIr#5~%^tIrUM&4+h*^k99KsTehD!JE_vt|>KvF*EA}FE4QZP68Pqc0c@x1oFsslk&7``bxuN*|&gT+#6f~K8P+ICV<)^(uiui zX)$|y7AF=YJ04w+n(sUGR_0=+US3}Ti%Hzu+w;}vUv}tWmIFFLPGMoVcqmr6>&BqD zr*K5q$|tdxr3OvCwq;FHhK7b7gs;FE0IqR)c^Nrq%b5ZYlA1XBsGB+#Xd4J;2jpYg zKilzp5hL&>8JY^1uzA{ml(~7{{qDrZ_t1dTLO83}(qlIPeV#Bbkk^I$jczgqK(M>J zR=1IvTT|mXKjYuPX~+tK;AME$#gl5sMMbcA73ZHo>I`BnP&X`#?StEl!43c>2%B%< zC260`S~OxuL<@j$B1cC@05|D~FaXB>vKuZclBL0YiG#-6FcQ+YmQz=!eQ!FNMMVIW zI{_x$y9m>Oe78gy<>mDa zMnM+<2?9(2ED)^Tqt71Cm4#~>7DRDF>3astE-iJKE+N4Se{_6&?B?!%2&)`An$UGD z|Mlzhtshw5^lb*0TqEb(^z3n)@@YGev_A#{)wErHb90sxU!xt4l9CdakWhbX>%+qL zHY3RtmD50OK!|H$*V+~p!--6qUQ5A*UO<>i!f3FdNq7eyHz+8GR5T!caBvW_*`oHx zk1)heoouSi%*+5A(XDVdwb)-jJakq}=W8i0gUW&c`SFd>$VMN`-0yH%lA9Rw;~S)) zmJeMkEeA;2`U>{%Yl(}yIeFfyFv-F}Oi{u3TI+x(>ZJVOvV#1Og z4SY$&VUi=Z90|x!V>2`Lb}3!`!}bt}D7|935pXqeHcjAY0I8i}Qyej*{9>-I$2t0W zoc2n2s=$#z*zmf$+~5zm;RBn-H0T9&Swr{t_Ximp09=sCX_ZV!Hfe+PwHSTN%X^&M z7hF_?*nVDKX+F}yOppX1)Ca&xe*eY?9%5jykmGuPfXII~j?H<4Yr*xv;nmLXNp{IA zDY@R=cmta7VQ8aaZ8TlarA`l#!2ri|25GsCjZM-VUdYK^k4RSRN>{Awa{H4~y}F9; z^nTRp>XkdeYK8bpLWhrZN(g|~V|FbAvM!)vp<V@N8;^RK{5Av^jRCt>iFM~sY3|CCOa7Xk4zSUT zw}YKHI#LR+-@>(8HKlE-oApH>KKe&D<1d4Qia-bfJbl{LODNMVJJI*J02hdvBOuKJ zK^gmdn^eIVAopQiD@evk_N(kYRP>4OoSf~6n) z7ulrIU_Uh?-i*%EAAY?EB%eb>0s*9_xa8y-voslSJ|L`v6oW2B-sNcZ9k^+;m6xB5 zO$3n7GFw{S0`IP{ZTOEchmH~y6&pLYxY%q(NGpT=IyO4Gq;Mluo^ESrhnfgu*`r~^ ziW~p=b8}zk0;R0=(LA~S{{Ej=3r@@grhsSwItX-K!1zaY#Kpz?2M0@Q?Q@EX0LdPn zoo#@xfH_NK+;Z$gZ6Wq%N!sI zI9WnAgZBM%%Ra;Uc?WsAv5I~0Nsw@ljEt18x(*HvAuLQvTKdI{7sfz91Z>!$>18e; zl)hV}!}W7dLDE`L!I`c|50b_a+e5GP@bD+V9FpX5>R*&-ME02F0I&-Td?-(+=7Hu3 z612m^!`G+8_BLSQP$IwS5q5{#|b@24h1I- z97`ro6-c>r4!j`E1RKRtXPFlF!hoEZ7$I0X@%hc3H*(1(0at#-!0^Y2{b#NBr^WrD zBouw?2ASLu8NksL3-AiYULoi?f3*otBx>7$rxZfzZx^OT{8DW7Ga|U4#fc+QwU-iD z2*(5OrTM@7;@(6ks3g>C9v$iZsB`$#{R-hg-Pye%B{I08Q;0}Q6?&9@`ArsAw0jjn zng9D5gxST%5)udl%RS;QRwOL`lsOL}(xhki0wSj>)wlbhv#_rqfMAJKw6s59jz z27X9{iGqP5tP@QjnG2*+murLy@~Q1vYaCjf48A*FQp9IOWy4v?`Zcj^MC6a347oXt zUzS_N?0A@fUKj0R`Bq4HH?N@}ITIRib%h$r`08Ae(RahH)^J2 z%6T&~{<@o|d@#r+Z7Fmx&EfM+Pa>%h|XLry!vOTEVgvYc}yRfV{+urgi ztz_`(M#BS;*MKaSDo#RO&GR>zD>FHLip*!(Jf2NtyYUN5w!r;U=I?k^kl}N|SWI5tDWLSj0QFuU@gX+f*Up#L|B~ z^hwD~0ht2|gz^n%3Y9x*+F}3TB@q#+MW>(~XG){)fT8ApyI)UIh`^EX9&8`roN&`^ zx*#MxrcXagN&cZHPfoflU3kaJH2Imuy?rj1a;3?y`p#+fuft{*w3s{p4Wp51))T!T zu?z@!*;O(}gfKI)Id^an(gx&o3D0B}31gsm?eoI^G`N#KWwafVu-P%$x6sh{vgUSv zfn$zM1L=+;n*UkQ++%@^7ooYhK`l~3CCC?g~9#u|@Gp~c&fNFu? zIf*}Vv{dZ8L10I`5;H;jJ_Rko)4AiFi0iL8tSN62Q#de{)%1G`AbX~;#Rl6RU3K-f z1~&2yNBCXVq0h!dUR@p4TjqBAQL7W!!tJY>84+1$XQqf8{ge8l{I4l>t^fWpr3+uv z($NjhpZJ}gD*b}M3kpj`A`~qL??e)1*@gV#YZmW(Sq*MDx5?z~v=zE+kv4Kxb>He;P<2g2iyhJta_%5v*Q(YlWK93+V{XPC;09qQwYya-%Zx$Tk0(7~~ z)8|YPt$6zZUz!?pFRK=>C{FZtb<6V01c47b1a-s@NsAoW%gUXXdPU_}F)&!=fBW|0 zgQNcrVUn<70oS3p=t-H~{8hHO;iQ$@dv&txqHQ$a^E)BZ871cR=C+}Oq2Ir6B+f4T<>|@D49x=qaB@f9g3lKg`(gv|)|zVV5;-F_ zyjdm>Y$+71FZE*oByOBv&@^RV4ePfKYI$StP8%!iLKI{GiR98t*mpkegP$hcbQt<` z2>&iA`X2uZx93r~M7#u5`72)EZs6@E(L$Ll+}uJ>-f-9d5xrXxmsil&JG=-Cw6E23 z?}K{$`(sHMd?3emOIOx()};q=yj<-W8W}x$CUWo*B9=Q6m(`qj-ncR3wO6M!e`YyV zzEVICe9)7SwUU<{;5u?}T&MUR1$*|MOBQ-5pV+vJ#*VdU7B*6}5||HVtySBW6cV<$41;2pZSpvhgqi;9WH!;%NN(9(KC3T~2&O4!NgW1{ZDEaK*FBL?)%<<;Z3zO!!}BrsLs&n2b}FWnCq%hubJoZ z9UTqZ@LGO<7poQadUS_nQn=wZ=Tt#~;*UuWDrFqn@lhXqUm;f1{|ZtGfm=H9F{@l`7lm#cmBE4Yl=qN#g&73}g-tNNd*LO-= z@rsI|UTlhuar)bvKc7nH);V?98IA3t2Db(sI>S3WeBqvBvwRCUt z$t$d2KiXeF)k3{r4gjW$ThpoCJfdiCzh%6FI-gTPH+TP_g$2UH%PURy%;n{b=`L#O z0?pIRxHz@Rre=40OvCm1+}z@(y{Z*-^wbfnlkcjY{)>|k75udX&+G3b^Vz2Rf}uqV z3!?oBe;jI-+*VaIbrtj#p6~N}e5t62pB2z#CYM$yB*O6dJ#*snwsKnHoyxPSs&P9K z66p~uv@=YzNk&H$#QIdIo-NrLUcU;s>1zIH;3%mAsw6@w8GqK!V@Jo1?d$6^bIbO9 znFv1z=gj8|=Jl#y9-f}%J}QGK=8d1RSjMemM*ZuNHt;(iO>mVc|MrA*}V z0gw-GC}o5)|L`_{7+d-m>cAB?lx|O-BD>_l@7$nQJFnPJhbNE`lN0E-ZEJflc(u7t zVRqd9OMU`(x%%Y1xt|dFD>9N0ZvXD|RYOx0z`>qa_yz9;%rbPUW}i@d&*12^gKPP4 zxTusQ){w+S4gYaaOZ|HF;+DIkP$*7KP7xz;PM^}V!?iM@fs_6L$2R5-6KQ(LaR+4G z5PoY7J#KZLVRHkrwf&Q&Rl)wYwYPWf5N|K zhgs=$08$T-615nGiNgDuSFj=bybmhiB|m{UX;RJyIU!(H^%TV2j3~)ukFESFFTbBewqHt~KK`@WVc^wt>av)3Jt?c5@N5esl zsa~1M97R_?MossUo!Cf;^$8*73~1?j{jvJb%le{J8d9hz6KVMAVed}CMH<`PSWyg+ zB5(F6{9>SYCp0})+?@xTM94vHpTs7m+)f0@c#1)-`cKABi|)rDZtPVkTcuW7Hg^*? z*V6l9YXDQ>5vkj(4G%wqtsEWQ77KWKUw(wG93G}kmKyY&zAo$Qod}pOER;Jp$w~71 zb2F>i-064+fpo9_?OoGp@b$U8G`zwz@V=d)k<3t(%bbrHiwnK0-`49fi?CvrqKaD` z^x!jq0AkuMjEgnoa=PtVS(R>Iy)0RVjRkFm`e&hhE8P?RIzZlPydQsTX2tE3ng($j zxpwS2-DhGG{m`cl-Inm|8EGkuV;;_)>kFQ z6caPMbT!A!wOv<2nfYIIQ{Q-%RrMsat)EyVyt=*ym3jtzD&k1{YEr=AOiCs=)uyd} zTT&Q`(qIgapJG1z_{6T-#7zLNE9SN(hf#x9!0SM>yH73|T|RFa@QSQiD`5;QXfKG2 zmzN3*5g}a^a5d=F_!@6Il9lZ>LvS$5^akJ_d9xrY{(7&Tga{)YCM|Y40ik(2(NQrR zoB~Py28Ox{{`RLirufk)2|Y3@Zy&6g)AEsdPhYp(8GX*q=4f1~qg`qm0U1iURIJF= z^^{#lI{&d}R1CC7*06bJdqF{Mtg61>-#;=^iZ+46zv3CLc9E>%~`lObs0dPkh~PNkG}x)y}G2j`pwe^ zC?O#!hl8@_EHC#wbxJZPCbF`tPAv~EQHaO`G71Z^0$W~j6O(!PKB=mTRZ%%Ch4~5y zK(7?7*v=+&A>#hGzE*aA=pS;WA<4V55RmqlRsLcR9M_Q%Z^LGtad37y5(2ddxT|#5 z($O0H-&`H!d&#aT5^NFvqT0?oqAxkCbA#lMorj9BgCp{gnItMnOAj>g)8ROtDukbqJ zMmvV^l;;NOm)n|c1_SpIFx1nrJj(*F(#)(gzYYm8=yRZRuKc4h zj*q|`R=o>N1ZCdd8N=%=b(|a?woMm>59!q&l>OLV-!>1sjx8#p!F?yT#4fTZw1Ss# zf{iscKPCqk>`UR%MGFF1v@Jn2>+skyi;oX9@ixQ2vBQB)QU#wX?sAq9&c}Cw9u!pe zje(ds=^=Q`dtQ{eQ5@1oO8 zNK+D-+4;rGaChFc&c8xRkSzp|d-D-VZH()Iaa_s#jZ2*J%q1zwtrmmrVO$B9Kv z$=I!9+E5+GZ~142Wy4?mC`b+b-SN|R7Tw<4qQ*fp2Fa~n&BD1yAyQE)d6FVcm_*K_ zz_&MdNG-}Pxj;q_1bE$P8y&L* zV>_LuLBJo38&q~kLYrc#n(L=2-5&-a4HK*$z3;bTsFjr>5AQSH_L3JTJ}?gwlqT}$Xra^>$PZL`)7}>s0iBAQ_%RUw zGv`AL3ffFK)RaFfLEx1Bw?X|^>)KieWwVxbc=wlZX|>{1)q5W{Zz1^@oEE6grlB}< zCQ=chEuEtR9Z-weqNtdWfr%yGe#2uhvms2eAa}zi>t6LKCk1JV@yF2AZCMYy#gMLD z(0%E=H2R#bAU}N3%!;2Nl8c8U+~w^{(iml3HA!_z{!4P+Ga-`Gs_{*C0XBUMJGUiQ=|umP$?z3Gkt;zLFEx{ZYh> zIx0NhKjeZh*#eV*KFvX@JGX`q#{}@j|pjRV%j+Qg*K%I z4`2g!9zf?eswtp=T#d2wr_7$AIgNfs zjg>SSwWt|P_czq7{;-p=5Nwb&TaD%%)cO#6T>Uw0eqMHVaMx>Y?{$-*y1H04``DNP zV5mzT0zZn10Fzfy*Nz!6`m@!&GIuH0=$)T2J({C(DpJ6T@7{d=nEkAiJ|F0- zB#Hi-o^|PMt9cE%*IX%${E5b6?C`x-Hh1sXeIHz z=IpG0fIf`j0BF!zBpC(+|i4~7= ziZ=iG88 zi{025OQ--xc(Exx7CW$R+YGA`d5~ zJq$z+1>&qe}S8K*flR_B)Q}Yk3#955S2`sQdd1d`F6*Pzvp%xO)lO$d*?kB1T1}W4;IN z55a|QeT$#pohU6Wg%VQiyS0jA=Rxi+vzCn-rzB zwK;Taxc|BFv8O3`-V&n(ee@)o>uSK-0}xHoMr{fNf$~{9uQ^45hSKGXG*dG(jO4Mm zYNo;{L7>y@Er5@z@kCHWyva4~@ArB^N)9@U;`W`@KJVxM`1N4{`mteQZx#ss%*`%+FVg@d&pgGLv9lc$`6zx*pJK#DEoEewT3LO%nz4z9M0aY?2QEm2 z5#qaWHV%rb*qO+vqc3L`u$oVGBZE2|l+JBpN#BXWgAG6Q_PU`ppAy`hITrX9yzutk zGB|Dt+S>@9D)KIari;zOo z01I-{wKcTw(y}3fDtczhDS!K6%%*D7ZHJHobUZhUe(13REx2D5Zu4DtW|5io&8+r zd7iQVhwXNB6er|l1{@}y<98}TKQnV1zx!psigrqu(4kyh0huWWGRVyS>MUh z#83Sskd=$*shkDtdcz(D0FDM1^p!&^L(9o-1p?w~pFc4ATwRC)TPh4N<2(7-pdZiuB_V06pv zk@!n6Wbyct9CXfsVhV`M>;A`}N(Y1{VC+H-WGjNX!sS}&%6#%dac@+>Znigq8_srhMeG)PVTC>)EZb7Zy6|uIgfI#(=8B#(kD0uE;BZ zz2#5X>~|cvfnMCK`dRw!ybBNA@u#d3uPK;KVe1=cUI8s$fYNpC(g40-G>oUz zt__+uL&Sa*;`?Nfr>O@WX88UAMLiHtI8!84xQR}DPJ94dxa@3?r=24`$j>ho-rK4> zu*)P$~>{GIrm85;~Su{+nBvpG6dI@b`DXW=0ljFWcRmwCnsQ1!`AA zP5%wcaUPk+C5Z?y9aVWoVSu7!gA@ zR-?jtl5Ztx6J_J-9a>kb#HbjHQZKd7*UP|&kC4}dK<=f%&<6uYZ|_O+w1LQ{fj=jj zw68|W7#4hsE%X^rn^u3Lti_BV!E9{U#T|iC zDq&sAAa^j?XI|kVI?-aL15~Z32sg3=w9!Nc3QU43jMp$IxR3*Z=sQz%$@tXNylpH_ zF(@Ljr$kBQsDOO)1sMrFX}AeDQO|j;6W`IWRTcRCy5I4q#TR*;DN$4QJSRS6pcpU1 z4VqB^FIfKQ=3i#{x^1#%Oidp%Yk%gzO_R`X*YlvUCX-e#iY@O%j?qx{%}_ zMZK4i$=;`{)~uc$$D`qsmwST600ErEVMzFkcB@Nb-*yvpO56=c*mWub}CL`}`M1f|lw#!2kg zW%&iS%aI4R%}F8?Tc4C5#CMyq8lEhNeMWVQd)p`tC%U$CbtNQ_7aNe+HJMt8F8)38aaG|d&q|+O%CddcJ?;kzt^IbDE38p zCY2vP36bsuKKWH%3jOI(;ZFw8Zu=~B>t7%p$F`rj}Ae$3r5@4!TSax z!C1i-3w6+2ul1DiDup@E)+IE{1(Bh|K?hJqhzRB=V(k5KUow^B33FV_+i&hozB}y0 zh|Q-*CdTlILIIl3mB5nIr!T3k#x-iX`Wexv_dY~wl*l>UOt++ju1iproN>Q zO}AlrS*7#ZSB(7y6**CzR|25Q z>nuPfLG>&oMB2OImdZa!T!Ko4k*Ux6XIWn3Pf#M2pu{mVafv8QdEs~bl*Fb2G1a%T z#Rlgjvl-a`*(T(&I-{sGc|9{ZdQDPB7iQ>9J1;))_X8`dfe-f8XkN!#jDv>ew9=&0 zO3^gd1r>wL1$)`B1|tALH#ZNFMy#T4eP=4(Hfn%LKyD(dAH~`3(p&?xsbIve#e@eh zS|TMSBk$UVqF})%t5qgY}+lSsG)xTeQSO0R~A_sv)5R~)(V*d8f1;9wO ze0ZYx?lU<#G*JsSbI4?mRX%zcmxkdT+;&hN+X9w;bAVm2k7wxU#@%#&2)VQLDQ@lO zcT}I>X@;M>4PAuOkGB2wxL$50>zj{M%o}YR84|_Lx}hNzu0Luo34ZK#JaAIZ*6ep& zjrP_@Rg*Kt%*>5cT^*EtzP+)2cgW(~$qV4FcAk}Z+jeSUPQv;f2M`C!Jd?8HI0PDe zYTfGa$~>_udt|h&IY0{lsLLDIOovHO0VT+J%Z0Gg6^i-1l@c&M{KZ>`E6b`(U)I}7 z9VO9>JMHNH7-`7e#uf|S)2CyGSe>2!dJ}EidTwtg&-e|ZCk>V0Kh>3#e3+z52&x@| z0|QSM+Tvt zpliVu>WvaA%{LhkKDp=>8Q$|Mr<|yxV;pAXkW&s#su(rF4X3HBbOiBcc-4=?9Q)FsL1hyDt#jRbA(bx5C&DwA!H?g+Uk-mn5Sb#>fEg{-S8}F`9^6KgACKrfXjxJLhTDJX z2fB2gMRyK^ZY4s(3V&ZbUyoh0#XSwsc?Y;l=7NY~Wm%U$oSH%3mMWg%=eS zldHlFgp$A(oT}IAtJC(N{5*Cw1UjyPXhJ}6BVob;)J~s_nASi)n_?zwyC0^uYRGGO zv_RJY0JynJu?k8$K|$0Pnw60{G53<379nUSEG&E>mhE&_H*WJ_v@d6L=Tq`NS%K;+ zpu;erTU7}F-p87p5K^ymwriaS6sV#IPJGFi_~>J^qTO3H3+u9)*rs@%{(XZDvj16Z zgj5a&Ry0WbpM+A^renQV&#P=m-ei$gB0fO@bi1&y^jn@c)9dj>(1Y~ZvIgY3At6$m z+tA*Q7)2my^(x}ecHg&J{4DmOYB`BY26T>{sMVzWTMj2LXw$R6010~FL+ofwci3W0 z`dVK8j@58$eSm9YlY-R5%goho^1fAxiXIFFuOz5}zLqz%#E}V&9i03n z8bJd#puf?0cn8E=&=4{T#3RjfuoBRyMY1@2{>kyaDTIn1SlHd`(jA3-)laR@fNpVe zhMmoT-quF#c6#DmS4pyX<99}WVsUQjnza>f?5aKLxwRQw~4M*vO7Z>)S90lJb$3BKL19cboOOCvh3qQrU)uQ z>A8Qy<>wjV>bkD;SaE{b^08VfU$l*QCU-$&)XUO>9(QDf`ih!zuiO3K->vokmCM3K zeLcx_tv#|l@2*)ro0C`9ppI3Zr#Tum6W!HFPY7^}o8DmAdBKn}tWlBMUOaW`j3?!H zHnDV;LNVb>;Xxtp3h8oYri|;1iRPoWC6kPwP5LYM+}4yuFGA4!e0P35wy$2_x4cLO zn*4JE`FE;W257LT{vP19aGt*S>`7dpmV^Y21iHzyXK^^M!Xzam#J*D!Jp12#;hL>9 z>c92^{14ys|G)6Re?$M@xmRYP!brdxJP5+4w31GhC;4I`H@pSwHQq8)QMq8pg$=Au zu_OD>3qtA89+vj&TiW|a_Kdtlx$@ncHTcha@{K<$(r)&6>|LZ%qG=h-|6#`*EnGz| zQ^$!63!4jC@zF~&G_j{X$I*PedPJ)?xwhSkYnyi7{9kSX-LFhx`jx^YG$TwLBJD-R2k2}8V0^->kF#7{Da=fI_U{Qu+& z-B`Xz;EtgxMvuIn5Q?tP*JL1k(G1(`Npdtrt>V)3z;(46VmYvnf6#wmYrG;G`fu;i zQjg3m!;BM|bYJwi_g!J5dV<1fMb*lTbH^H`m1p1p8-}CquXmBJ^ji0OX3>B9u-CKX z=q*~F{G|O^MZ|+v5Jr8FDw3*d@LIG$Z0rC0$uqz1%8lE&SBzMtqx+IlDN*%R1ioiH zrEOy;Hjb72J}<=aw(-}mgXShMpeIWB=v5%+hOe6;WbLMJx!C%;}GMU zD=4PycLnf%3`8qBID_l``Tc+zY@yQ+7K_udB#$zQzVj(t^oCLWiq74QuJEe zrfLSc3qP{M>vApmau#y!XiC&c2D$5HDm&ONE)zN7DFVH-p#3QJm|FCRx&)_b8Zk}< z`X`=4FFM$cDDK)I-&70dD$BCqmsyr3CJ#R-fo3ng=75Y?$jwSkqbVv{;G4J1ECq_B zE6Dr4ai-D*TQC{5k_qivXuB=sSYYvD!nU?FQ*1sG9QFNV_QK_$XEpTPKg*r4 zLoq?I;K48ENQ#>liq*}LT)(4)tVTy7>0A|&+byL>`R-8kuB!xxl_2t_0Dh8nF*6CB*yaV3V|VAxS;>Y0DRw-4 zCnww#pyqN}Wq4iH5U`ha3mdv)2J=c_SmK~=$UkP%5R#$CD$SF&cDa$&#U6!v_A*pc zAY)lm^wc>7W&cKU*W%V+*zXDt&IG@1z9k#%*PALBAtOaM;r}a}hx>;t*4FXn#sGSf z$94#=x0e}NKQS?Jkt=#d4b))FfZIt3SzpD)L8xdy87?jq!@ zGHXo{TQFK?k2y?m#kq2XXX zX2pHcs|jtLuC%-ySiDgX5ES%2>>%`>Y*^?pyd5{3uYrSs6U2558l!|&ljXZ+Nlw(tD`A&rcv06p#O8lx7~dy)Vcnk~0V3d@37E!^W-1;6pHFVS zF-M|`Lt}$pEbGtuuToRAaHqjN)4&&9G~Z3aeTGEfU_Ya{EC+=S7JvsQ8~7BS-a6h+ zIL<$7<<-^KUpoiNlFlL=0msPuV5;Q;EO>xyU~sMHs9%lVdoQJcd^`0$XHRUv3AHbG zPLSMN=A*A*Hs^F=63hlSfN%{xZz?H+-+(>^XsjzR3!wTBk$Wu?8hCvKgRDrU`VF0k z_n;aapO!(I)Zf9tS8z<7-BLvOU(YUD`1{{}yzNccK;DN;QtO}BEL>A1neQH1Vets3 z5_lu^RS=PonA+Jz0YGF-tT)dd?e3P@*#Z6}Sh^2`eQ$1VKD||Z=t^&|m?biQbrsFK z;AP}%HRwc0K!6wp1lG&R*;-{gxi@`RQnN6l(%e9U_o(G0VBr_p4QNc*$bE}5JUtD{ zI8S}~1ke8VG{8qMDSONU%i~^_4zj?G!mwB$mv;-BRcZ$;(+33ZUqkn)6MA5qsqY!x zyM`RFmOwr`>DgVwlqhRwZyz^cu|K`OZitlWz5YVvFkA^{6(uYzTvF!P|6|{r{yN5U zM9B_*DskDicgm7DiMQw=%@2J2STot;MTTjqq`0UF!NatTaKyd^FO=| z{OS8OkaLIgT0@*gq3WKVci{UgJm=0av1jN``8p)YTXz0HW`{_sgWyN-o6Ejasa#R= zSkVLZw}O}5p-_nQQ}q3Oo$=J5G_rK~gyh*ffsFOBtUEWRO)xhMzR&6s4@`4Sfv=-6 z?Kl-&yi^Q0)r7b&c6lE+|7O#x{R77N#Lvs?>bw_47dJ}YmAwc#?73|;7W9IL23$~& z$b1(~8zBEdEfA}yVql%*@o2TA+D#>go00 zU<+_x4cmxdsQCQ;cPZb|wYB6?>*-%^n(1 zA0$Zv@A4?VWn8~~8aZUZXY)7O<<**S$MnSo;wrntYe8%%++uG8oSZkoH%LFQ73^bY zw;oqPG_B^AEeUhGrteTG2)Qj*K)KzCHd`c+J`D?|82?d#Tr)5oW@`&^dxPk(YXNhx zLKewGEEI!pdd#lmIsa(nNc6>n;`iCw9WZ&JuD~6HQ?Mj%x6wOxA!~N7|Iv6#gGNSV zE2_97r>Au2^_=ioq1kzA&~@n5!(+JVGP>;dbDY|%0oaxJ&7b%ioOVQnVE;)`k0E2j z`Nd?%Fu~P#0+gW-`W;6q9o@*-R#jJrF1V%J0p!Ju8rUY&{xP0l>U;eDm=-`@&G@4eT}@Ie-BC zPX_$&?_#MQ0LXN1mUwR7f*S4DXI`8!hg)8#G09|4fY1q~PEZqtgHpsR3arw4-kV->RtVh{y7 zuD4Ga7HTdoTxEuKk$0T+(8f0yb05NP&!yML z^~~CAc&Z&TkUh*JBOWSP!c=&m#mub^zl<=!U6PM2(5%&Of8iFa@e*uf`!`#X3%tBv z3xLe?gH>7+VbBzfTqw{%2-wuhp?^wh>N(P$XHq_?vKkqsm5y^nM@JKEH-MhuQ(y@( zv9Uz)(7shqV{o!T7mZPQQXJ@nkL<~)j{yGnRgly^n13J|d?5&o5%lB!xs(cf`X)K~ zp{<>rXz0yVaHzlEr%S1y8rQ!ybV3f;Lix{M+vYyOUACjYw8b2}ls@WOh*e-jh}hqk z%puKLudc5@{$8Mk=#83eI+>@`)firA$fi>pAo<~A0zl}|ugM`8Y@x<)%atR!U+%Q6 zxjA6qX<6XhrG?cEs&R*jMlE1PlX_K@GD6rVw;X&t~L^IJgOn2 z__F&$IJlJ72D@a4*31f}+LE|yVY+{VjTJOl-^{vQ^}0Rlq>K2O#7-P__T3HT{8BLR zDu05pWiYzYImXPcJ<#rW<6aC3226FdX{x2*L_?KOLrpFC;hE#qG9gjIJ0{-XQWKUB za^kr}%hcXxw6h*w|8LpV{}bWp|AMIffBdJWEeK?uJucke~{ z#McQ&k+^qT0fD|9CZB<*l@M&9=o}saDw?+!Y6H(?W&U7(t8lAohNxF-_xh9tzUr9! z_c5YsrkGg#!{1R>fv@5ju&}VEX8LQ}n|h|b$Qy%fTw%Kn)JV0UhKG?TbAQAlC-`C| zXQ9lnOp7a-oh%x;&qOF)C}V=vJ8kiFu(B5=+8#8%FvM}=zi+)ok$`o1WI?z4zVqU$ z)}H%*K0XCcdHdS4u(NlKtlsdN4^e;m@3W=#RF%Q&cqzKN^i2cdpi1)RbUKc)nc;N#B7zcWoZHcc<-8@r{Rf_`y zYUByge^cY>>Q1B?HO}KMIvqMYui_u6o?#$Yx-P6@7+;+xd+DfzL?ZYC9VF6GV5cS8 zrGv(e-PCT`R{IzHO+j?9z_aofJ9oNPo^r9~(Xo%0wP%(VTRfi_|1*yC?o%|!da=Xk z$tJOe?@c(hT-=X=F}u&btC!yhLB1Gq>F_Wvk}m&_D##Lf_5PVexCMn@j$FMVauaVZ z#NDden?MxZ@O0t-<2dldB779MJZ6wQ!gI%Ssfi8Cn^=Yo_oO|)h?O) zD_F}`O!2`T;mf=YRZ}Oj&f!Qk)}LR$NJ)`ZO$aW}MfD(1#6?a3*zh!Y2;qwo-M&>E zU?2GjZFp8tpOX6XON+bZmnxau1fVrRvjogoB-qf#?&9_9zCS{+(kzu})YP^SM2Vj2ifJ_T_dn}a*48K6`P(Enoo{J7HB2PAaflx3 z7?;4P*5|5jZVpj|w_v|TG*i4#WXMw-tqut z(#I&i9-fT+p-(}X3I)yIF34kyi_5Q*)Gad|>;6IO#|>$sp$>YNb1uj=oF=~~NKfw8 zu@Wm^&>hgcv9qzZvR}{=0<5&Ex@r7G_S=pMh=jjI z1t$wk>6DR?DKwt0z3~Mi9l|ny zdRLxtNe!f3sr|)g-LhqW)2Xgt=eziX6K(;b-rO|zMPLgh&0o9E9jVs*vRq_c9yA=| zU~*LEHy7s`2t=0K?6Y_94c*7=XtD~!B#!W&Rg`Vy(;eTHQgHmwujszq{hktjAY-lI?NrWRVfoVEqP=w!; zVWfld;=W~INagv{OcysM@(~g95E&B5YD{Q>`(#gAF7WoTt>VH#f%68cal{sxO zFL{7D)9D!$1>$feF0w+V?4pL^%yQ!#&-xErLVCtl6olviy!~1mv;G$Vt@(fak&({M zro`>~tk1>YP}MEmaV^~X#49IGH9TscXtacRZdx71vYH(@`)}oLRbaUZ(bIb2Ed0ik zJ$ES+iTaJxo-^T@9^XT;0 zG6N%L{jk?az&NXoc&Rvm_wG&ZBhyh#Q*c>hH$isN0YH1-GJG~Hd&!;bVbb6eYe6xU zA`dBx-gQo4(jt-Y*`$EjjJVVEuX@5UtCfvE{GTSCkOupueAf7N7w=TXuaOmhsUFSr z*dZE}^QT^cNy{Q&IOTH zVqrigIsJT#&kd|(h(HYdF7q_=7T7Eme#MproD4Z*ZB@mW*!F}-EskUZL(}`< z*DQ$xj zN-^e2HkxE1egH<*Qqa@qiNnJzGJbRD9E33*D=@VZ)XDw(Cwera%DiyWQu_2Q)BGe9 zCjMFmcxHKvjf>&$+lD)UGa)(61&&2xln6#u@_^Usro{PEzAS=6!hdE3wrrhyF!6ae zB_$oBC44HSr@NaS7#hlK`_*RerPV-9l?%?8b32Qv5&RG6>u`4E*>tq~_vk{+bf?2; zW|9Z_30S~Z01N?yxOj|45%86I9a) znoU4m=s68mUgc+tr(f)@cHmFPg}B_CCF&LiSfs>wZDtmAeHg|cxJnfrt#StPJ-}yr zg%Ey(kvEc|0|jGkjT{u9J}Y=R*fJFP01MUKI6|Sz!pkcfL}rNRIT}&t9X}D$;?QGH z=H%iouM4rI5Js@J>wGW2j3)qMBEW<9;gBe3zDR{iTgVoqz`R9yVy^Sdm53(3C$h)t zcWFIjq4SI381n1_I4db>z`yZZa^XIv@A;N(_{i+=r@k+g+X$v~nA3 zOA88SVh=zn0eu5bEKfh*xTFs9B)v$sJMPz5l1-5Fd(ec;D=7+Q#{qoA0Wap!DXsq~ zJ^t<=b!p5|tT$xJ_N@9w0c;w~`xAJD9r{vF~xnode$#ngIgg0K#9P6LM^ zfUUK*}6jhqxpu6{k_q_xMO6d8V-hj0rF* z$tS~rO##fw3LlShfqxo!h}}a>Zdb|gwae&4ooNyJ4HKFBer}|1VzO>j8Jx=Sdhy3S zT&52;=`r{O3F<8WrSxl;>TDikTwk}fUWWPdKH*ak%kG3^dO(r~1b|HgX%m`hH@qyO z{xf&;2l6mR$bj*`@Nrqt8Tj4Dga(?YS)$nv{jmH>t9%^!n1f`|nrvD?wFM*u!K}`_ z0;VoNFqw(@XrP6)OkDthrF&x!I1ayW@B{^|$P{c0lM zlToxwpC}jdbCzvkvPM$lVM09`J$>*eefV(SE#}?(Y{0iQm7v55RebOkq!s{+o`NIZ zANCN}>Q#6E_PP+bj2nx_OVSR~zl@$()}s#UG?}*cRg}g}#yR!Jcr;K88=sx;S?Z+d zFW6Jk%o^1-d{ex|ddx%V=jKd+>z!b-@8YdeO;RrGzvyKJ%X%;|=ubwL>L4=w=DZbo zYDGyw7=jiRi||X{6I^I0N_?O@DfE;+v;XWdmHbT z#37DKV;uECBz^4N;TYBB*2vRmgpVtUCBJ3(4te~R!JIR7{a{?wcGzqQ&|~Z#O@KSB z@0_3!kOf^r-hesKy4=t|GW2oFHPz`>CkHweJe4mbU&j}?6(Lc$WU4ZYQulJaw+kV< z^O`3JtAyyF+z`0Nm4o{dFt>8jj)CM)1LKf|UL7^9;HW;L@PW%&5eu0t=L)$AUL%@D;HSTH(ChVZXU`(crA|F zR2XN5ehZ;9L^)QtMDQVD84c>Hax&*6*>hHsFG&wHOPCWP0R;rYkKcS3EHX7km)Bfx zzGh+zZ}Yg9`^ftY=?(DTXf_QN4(L5EBvy@y1-*BCF>4ltCDy2ZXEHmR+bCW3?XOer zSg0s^R(RK%ZJbK29`KqmxOk(JlWL4~Y-G_ER73ZT3C7pCY4bt69ZRx`!6mr&!c9=$ zY{A-#Q?XjxaSn+`H~KvCNB&mKPM!vHn<15kgNmaDy}OhDdbxQ8M?R~}3s^%D8&ovJ z`NM7nw-~_XtF%l><_IABB(M zy>@|r(E_!+SViY2X<#%LIH%s$yE@_*i+}_e- z08(suKJvmKKXN!`zf+LLRS!2+y7(S1giQ?<;gn7(WVB{eJ3yn4&lF^NSWhNmO_d%7x9G z@lo#>%za#z->mZE^bm(xZY1nlq_e*IVqgTX$$;0PiV(^_L)r-DGeyPlyf;!gfouQe zi1~v|uPh^77i33K%BGJO7X;*V)eL*Vr@+%{^DC9>+Bx_q#}1X7+>iz`kwt?%hb%u4 z8&Uil3!o@+mN_{)uXWPR2P2~@e&qZvmtrw823pux}8vbx>fBCy%j`4S+ zFL7tHTM$O_2kBbwC8!ru{6(;&M=oC?iNJWh&#+mVjlT^Z zP;DEED}fdY+8h{(T6COdPJ_u(Q~VDHMNx2Zxdy&eHeinNQ=;;3kVaX*{c$5D#-EO+ z@6%_y9F3JSn zS}6^?c?|R98ASg}>IZ3u|96Z2k{d~hL>V}eIjA#@P=0UW0p7Bz{1A4yYriFeU+5WT z-2R-uI?RFs#A$K-Ae23cc*lBWAd&-z*-GU!=f<-wzp83gDK?LJcf9gna8)-p;>vV+ zfFSVY1^bf7Fs;khF@{l-M78~=?*zQB0}5oDjGGF|fh&z>0g5-DT9UpXwv3cn&^8*2 zs~tt}Jo-M}aSQ$3Ve0N&A`Z0)A+R5|j4lL<@0)%XMFv#xMWkm2>W?-@)V;3(9qY&$cz>+Wn~CoBQAaGhSqUNv3qt3NZgE3=1pdFOLf5Cr<=9Ly%`DU-nzWk zB1*5$O5D0`0y}K??Wl@w6G|_7dw8^)>efQDPKynEh8mkN;43L&_V;!l%Z zPQGs0;l%Xe3QkW1eru#l((~^09GdkbEw)CHz_7`iwPO>bs?}!hXcf-Etd2U)>p2ww zdR7mBL|WZg*IIl!gLn59Aail}yU$9y>`98=q`e6Ks;3R~kz)o%)UzybLgLgl4wepK znr?z;TZqrZmbiyE|AE35M1M1lFi(#A=o3Ho0$Lr*fM$ZM`xdsCizP7!!xP zKfIo^_VWncgqG4d(ATchGxXo;V4)hrx_I$4`Xi^=^Rga|v9Hu4zZS!G}<1tliz&m{|kgU$I_s;TcdJ2wM z$fDR}s@|J5t8H!mAA5gLcqDFdq4BEViU>6z`chz=@0^r5QMZ^bY3|;|Pjp}gfL1dv zKTrtAjDlJ{6ChGhjDX+-$$(7YX)uUAK!it<&5;tNa16J>_nDaTgF`(0UN?*boMkPB z;NSTN5COVrzw9+Ih7}hu)wWIb=XX@5s*0)A>)LUq7LV8ur!DMnJE-T1sY7ZDo{(@S za{$#1253y{&4j^0*iuDGtJm@89Q1JDb)ZhR)}>0<{33Pnkn^mVp{~ng{^Dn!>iS(wn}o0+5Fj){^7Kb(fCu zjMR1-a#7y*i}B;akkRlZ8x!OkGQ#mj>Ko)KX`;Rf!t)sxF2RQtiX8p(NX|*aFH2a} zl)OCKGrd!;N?Bu)?fuI7wQWgXY^>TEd8(h&nnY^l;7j%t(#}UOk)MBh{xl&d?>R@p zXL0xg600v-N+_GhV$muC;B@{3IZqI3$qwvukveLEoU?;X>}2~Puylz`qw}@=3ePZ5 zBI)Vk`o^-F^n@_i5BVt}@~A05;}n}Ax@8{6GpH#JiL=R zilS@`Kmt$zq*l4edU$a$E&@b@H$|w8j6mbVY(1=s9C^-MDm2SqmduBijkKkKn3x8K zr@7{83X_YS!^`4MGaL*+IgcPgUZlJSZK`GyWv87T_eW0BJ>$w_-m9sEzO@(;OAx5; zeVwE-Zi9RW88h45@HY;Isxf#Zu?patMaCp`AOum+Y_lb|Ueay6Lh@$OZmuy#;%Y>| zUpYc_kpJKE7Xk9ZrH5J0xa=n{0iwcuW1{!=noX@+va>;AHzy}wUa#?>-f$0gqoD># zPvwkb!aMxZF5AEFy!=O`0l(DKV^b{Z^*`Cr0)cI0dR&%^%4J9f(Nn|4HDLWoHh%;Q zq>Kk@3`~sN9)FloyL|a*TfqVL4w9VMu+2HdN}_E0?{R?=G_icsN4Zb; zFEjyFN}2U}_i|d1Qnj(I#iAGhz(CLj9TiCqu4tu`)F7xqXJ%N#EnbvN{Qda_AP1m{ zG6KdIz)$tyTuYMjADFR82NEq`fU0>3vaa1>etwDW-`x}_z^%Kr`s}7SyP`<@R;vO*SGR1gzQU(q{B4+O2*#RSiz0MCuSH*yq;iDk|^)W%l=z@0wK(hNkb^8QoubjyUXu;p!e%vgYvkrEsHBV$%ZQ)JOf`yDK_ zW(j{sRX4~}fC7SDr>SX(ngkgOT|6j}ta1qqtUrQ#Y-38rEsxp*O8W%t+_3`Mf^=iv zw>y7K1O4Fv=A%3o3?)Pd8VZ1(5Su6QqO+zeIvhFxA1?v6P0nLd_=02S{0soP8ZJhN z8b>mq8cEYq|8d-5#7snFUj5lO(@L$9-|Q*Mq|q!N3i;~;XrUgNP)WL~^cti)cu-ZJ z4E2#zUd1N2t^ByN3EU-6r2q^E(h6s)osCcS>i84oE=GClTp{5PEV$v6aGXT+VZ6B=ZQ0SVz)>#NdN(cw&9ww=tO&``PKn(&{3K9Cf z3V0A=Q)D+sdM5Un^F=@)s3}=ZA7cX{Jin~$-806J0r9QuNChVAS`(TEmy3%4kx+{i zVOW+GC}aa#E?w~3xkBWwFn~2?rh1gDO1Qx7UJd5Yk$os^A%pqR09UA9m_|Xph%_LH zkoO=-8gAh%?a2MIOc>}CaHDM3xkPIUj1&h;_|P(~RW$|3H38TZ5ZJ

uyZmCCE;u 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 RXa)!TlwPRGf0HwN_diNcW+DIp diff --git a/help/style.css b/help/style.css index e110f70d9..363a627f5 100644 --- a/help/style.css +++ b/help/style.css @@ -27,6 +27,10 @@ div.clear { clear: both; } +div.right_container { + width: 65%; +} + div.knobs_action_container, div.left_float_container { clear: both; @@ -34,7 +38,6 @@ div.left_float_container { width: 30%; } div.knob_action { - margin-left: 1em; margin-bottom: 2px; padding-right: 4px; font-size: 100%; From 21f96129fa93c8aee453f2fe81ade2da6636c559 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pavel=20Vond=C5=99i=C4=8Dka?= Date: Thu, 28 Nov 2024 15:21:45 +0000 Subject: [PATCH 024/117] v5touch: remove UI configuration of F-key labels and storing the labels in snapshot. --- zyngine/zynthian_state_manager.py | 15 ----- zyngui/zynthian_gui.py | 2 - zyngui/zynthian_gui_admin.py | 4 -- zyngui/zynthian_gui_touchkeypad_labels.py | 71 ----------------------- zynthian_state_schema.py | 3 - 5 files changed, 95 deletions(-) delete mode 100644 zyngui/zynthian_gui_touchkeypad_labels.py diff --git a/zyngine/zynthian_state_manager.py b/zyngine/zynthian_state_manager.py index 2ae29ba2a..891bdf35d 100644 --- a/zyngine/zynthian_state_manager.py +++ b/zyngine/zynthian_state_manager.py @@ -988,14 +988,6 @@ def get_state(self): b64_data = base64.b64encode(binary_riff_data) state['zynseq_riff_b64'] = b64_data.decode('utf-8') - # TouchKeypad config - if zynthian_gui_config.touch_keypad: - tk = { 'flabels': [] } - for i in range(8): - label = zynthian_gui_config.touch_keypad.get_fkey_label(i) - tk['flabels'].append(label) - state['touchkeypad'] = tk - return state def export_chain(self, fpath, chain_id): @@ -1241,13 +1233,6 @@ def load_snapshot(self, fpath, load_chains=True, load_sequences=True, merge=Fals binary_riff_data = base64.decodebytes(b64_bytes) self.zynseq.restore_riff_data(binary_riff_data) - # TouchKeypad config - if zynthian_gui_config.touch_keypad and 'touchkeypad' in state: - tk = state['touchkeypad'] - for i in range(8): - label = tk['flabels'][i] - zynthian_gui_config.touch_keypad.set_fkey_label(i, label) - if fpath == self.last_snapshot_fpath and "last_state_fpath" in state: self.last_snapshot_fpath = state["last_snapshot_fpath"] else: diff --git a/zyngui/zynthian_gui.py b/zyngui/zynthian_gui.py index 5ab09317c..2924ae115 100644 --- a/zyngui/zynthian_gui.py +++ b/zyngui/zynthian_gui.py @@ -91,7 +91,6 @@ from zyngui.zynthian_gui_wifi import zynthian_gui_wifi from zyngui.zynthian_gui_bluetooth import zynthian_gui_bluetooth from zyngui.zynthian_gui_control_test import zynthian_gui_control_test -from zyngui.zynthian_gui_touchkeypad_labels import zynthian_gui_touchkeypad_labels # TODO This constants should go somewhere else MIXER_MAIN_CHANNEL = 17 @@ -482,7 +481,6 @@ def create_screens(self): self.screens['tempo'] = zynthian_gui_tempo() self.screens['admin'] = zynthian_gui_admin() self.screens['audio_mixer'] = zynthian_gui_mixer() - self.screens['touchkeypad_labels'] = zynthian_gui_touchkeypad_labels() # Create the right main menu screen if zynthian_gui_config.check_wiring_layout(["Z2", "V5"]): diff --git a/zyngui/zynthian_gui_admin.py b/zyngui/zynthian_gui_admin.py index 237c11ba4..d35f40bcb 100644 --- a/zyngui/zynthian_gui_admin.py +++ b/zyngui/zynthian_gui_admin.py @@ -220,7 +220,6 @@ def fill_list(self): self.list_data.append((None, 0, "> TOUCH KEYPAD")) if zynthian_gui_config.touch_keypad: self.list_data.append((self.toggle_touchkeypad, 0, "\u2612 V5 Touch Keypad")) - self.list_data.append((self.touchkeypad_labels, 0, "F-key labels")) else: self.list_data.append((self.toggle_touchkeypad, 0, "\u2610 V5 Touch Keypad")) @@ -470,9 +469,6 @@ def toggle_midi_sys(self): def bluetooth(self): self.zyngui.show_screen("bluetooth") - def touchkeypad_labels(self): - self.zyngui.show_screen("touchkeypad_labels") - def toggle_touchkeypad(self): if zynthian_gui_config.touch_keypad: self.zyngui.show_confirm("Do you really want to restart UI with touch keypad off?", self.touchkeypad_off_confirmed) diff --git a/zyngui/zynthian_gui_touchkeypad_labels.py b/zyngui/zynthian_gui_touchkeypad_labels.py deleted file mode 100644 index 038d6c7a9..000000000 --- a/zyngui/zynthian_gui_touchkeypad_labels.py +++ /dev/null @@ -1,71 +0,0 @@ -#!/usr/bin/python3 -# -*- coding: utf-8 -*- -# ****************************************************************************** -# ZYNTHIAN PROJECT: Zynthian GUI -# -# Zynthian Touchkeypad Label Configuration GUI Class -# -# Copyright (C) 2024 Pavel Vondřička -# -# ****************************************************************************** -# -# 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 os -import logging -from time import sleep -from threading import Thread -from subprocess import check_output - -# Zynthian specific modules -import zynconf -from zyngui import zynthian_gui_config -from zyngui.zynthian_gui_selector import zynthian_gui_selector - -# ------------------------------------------------------------------------------ -# Zynthian Touchkeypad Label Configuration GUI Class -# ----------------------------------------------------------------------------- - -class zynthian_gui_touchkeypad_labels(zynthian_gui_selector): - - def __init__(self): - super().__init__('Touchkeypad F-key labels', True) - self.list_data = [] - - def build_view(self): - return super().build_view() - - def fill_list(self): - self.list_data = [] - if zynthian_gui_config.touch_keypad: - for i in range(8): - label = zynthian_gui_config.touch_keypad.get_fkey_label(i) - self.list_data.append((self.edit_label, i, f"F{i+1}: {label}")) - super().fill_list() - - def select_action(self, i, t='S'): - if callable(self.list_data[i][0]): - self.list_data[i][0](self.list_data[i][1]) - - def edit_label(self, n): - label = zynthian_gui_config.touch_keypad.get_fkey_label(n) - self.zyngui.show_keyboard(self.rename_label, label) - - def rename_label(self, new_label): - n = self.list_data[self.index][1] - zynthian_gui_config.touch_keypad.set_fkey_label(n, new_label) - self.update_list() - -# ------------------------------------------------------------------------------ diff --git a/zynthian_state_schema.py b/zynthian_state_schema.py index 42de74472..cd8974b73 100644 --- a/zynthian_state_schema.py +++ b/zynthian_state_schema.py @@ -196,8 +196,5 @@ "current_screen_index": 8 # Index of last selected controller view page } } - }, - "touckeypad": { # Optional configuration if UI Touch keypad (onscreen touch buttons) - "flabels": ["F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8"] # Array of 8 custom string labels for F1-F8 keys } } From 3d345d1b7ded87d95d069522aceabb09b42409c7 Mon Sep 17 00:00:00 2001 From: jofemodo Date: Thu, 28 Nov 2024 20:08:59 +0100 Subject: [PATCH 025/117] + Implement a selector class with extended info: icon + text. + Modify chain menu to use the new selector with extended info, including text and some nice icons. --- icons/audio.png | Bin 0 -> 4798 bytes icons/midi_audio.png | Bin 0 -> 7116 bytes icons/midi_instrument.png | Bin 0 -> 4038 bytes icons/midi_logo.png | Bin 0 -> 9808 bytes zyngui/__init__.py | 1 + zyngui/zynthian_gui_chain_menu.py | 56 +++++++--- zyngui/zynthian_gui_chain_options.py | 27 ++--- zyngui/zynthian_gui_engine.py | 56 +++++----- zyngui/zynthian_gui_selector.py | 71 +++++++------ zyngui/zynthian_gui_selector_info.py | 152 +++++++++++++++++++++++++++ 10 files changed, 262 insertions(+), 101 deletions(-) create mode 100644 icons/audio.png create mode 100644 icons/midi_audio.png create mode 100644 icons/midi_instrument.png create mode 100644 icons/midi_logo.png create mode 100644 zyngui/zynthian_gui_selector_info.py diff --git a/icons/audio.png b/icons/audio.png new file mode 100644 index 0000000000000000000000000000000000000000..7794c07fe0a402ac8d72bd64a90f93961c752040 GIT binary patch literal 4798 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 SaKhoWhn>x3MD<0~o&N#ekOXo7 literal 0 HcmV?d00001 diff --git a/icons/midi_audio.png b/icons/midi_audio.png new file mode 100644 index 0000000000000000000000000000000000000000..a24bd55605804b41675b1f14fe301e55a58a0023 GIT binary patch literal 7116 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Π`g~! z{1hEYgtVJo81Z~pkq5Pmaz-f5gDSAd=8*&2^nwEQ$v4IsxfEyz4LrQO6cQ?fW?ZLRnF9=+Wt~Q1%JrH)4=%-tn~BW5)P1;1 z=t@?Yw`Yc!7Ot$UR0hr30|3zuU}3+(_&qQ^fag;$1XHrJubI2*FkBXcMS6I8%3rb7 zjSv2Q66H{K@!WE@cONdC8;s)Dj7s%85LP}|c#jWP5veH-uw4W(c#ov$MPV>WwWhR&Q-@$If^&pFKMxZYDe!uaViR=*gDD zX04?#aqHmlaP|6-)j)heK!D5s#;glK#%o_hi^@$;{V#ufPILxle^Di^UI3Vbs`2C* MdS6?$5N!GEAFBC@-T(jq literal 0 HcmV?d00001 diff --git a/icons/midi_logo.png b/icons/midi_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..3fb9f327bf760bed7507d083ef02b11b34add76e GIT binary patch literal 9808 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 ADD CHAIN")) if mixer_avail: - self.list_data.append( - (self.add_synth_chain, 0, "Add Instrument Chain")) - self.list_data.append((self.add_audiofx_chain, 0, "Add Audio Chain")) - self.list_data.append((self.add_midifx_chain, 0, "Add MIDI Chain")) + self.list_data.append((self.add_synth_chain, 0, + "Add Instrument Chain", + ["Create a new chain with a MIDI-controlled synth engine. The chain receives MIDI input and generates audio output.", + "midi_instrument.png"])) + self.list_data.append((self.add_audiofx_chain, 0, + "Add Audio Chain", + ["Create a new chain for audio FX processing. The chain receives audio input and generates audio output.", + "audio.png"])) + self.list_data.append((self.add_midifx_chain, 0, + "Add MIDI Chain", + ["Create a new chain for MIDI processing. The chain receives MIDI input and generates MIDI output.", + "midi_logo.png"])) if mixer_avail: - self.list_data.append( - (self.add_midiaudiofx_chain, 0, "Add MIDI+Audio Chain")) - self.list_data.append( - (self.add_generator_chain, 0, "Add Audio Generator Chain")) - self.list_data.append((self.add_special_chain, 0, "Add Special Chain")) + self.list_data.append((self.add_midiaudiofx_chain, 0, + "Add MIDI+Audio Chain", + ["Create a new chain for combined audio + MIDI processing. The chain receives audio & MIDI input and generates audio & MIDI output. Use it with vocoders, autotune, etc.", + "midi_audio.png"])) + self.list_data.append((self.add_generator_chain, 0, + "Add Audio Generator Chain", + ["Create a new chain for audio generation. The chain doesn't receive any input and generates audio output. Internet radio, test signals, etc.", + None])) + self.list_data.append((self.add_special_chain, 0, + "Add Special Chain", + ["Create a new chain for special processing. The chain receives audio & MIDI input and generates audio & MIDI output. use it for MOD-UI, puredata, etc.", + None])) self.list_data.append((None, 0, "> REMOVE")) - self.list_data.append((self.remove_sequences, 0, "Remove Sequences")) - self.list_data.append((self.remove_chains, 0, "Remove Chains")) - self.list_data.append((self.remove_all, 0, "Remove All")) - + self.list_data.append((self.remove_sequences, 0, + "Remove Sequences", + ["Clean all sequencer data while keeping existing chains.", + None])) + self.list_data.append((self.remove_chains, 0, + "Remove Chains", + ["Clean all chains while keeping sequencer data.", + None])) + self.list_data.append((self.remove_all, 0, + "Remove All", + ["Clean all chains and sequencer data. Start from scratch!", + None])) super().fill_list() def select_action(self, i, t='S'): diff --git a/zyngui/zynthian_gui_chain_options.py b/zyngui/zynthian_gui_chain_options.py index 7cb58d0f7..a2783ceeb 100644 --- a/zyngui/zynthian_gui_chain_options.py +++ b/zyngui/zynthian_gui_chain_options.py @@ -68,8 +68,7 @@ def fill_list(self): if self.chain.is_midi(): try: if synth_proc_count == 0 or self.chain.synth_slots[0][0].engine.options["midi_chan"]: - self.list_data.append( - (self.chain_midi_chan, None, "MIDI Channel")) + self.list_data.append((self.chain_midi_chan, None, "MIDI Channel")) except Exception as e: logging.error(e) @@ -84,8 +83,7 @@ def fill_list(self): self.list_data.append((self.chain_audio_capture, None, "Audio In")) if self.chain.is_audio(): - self.list_data.append( - (self.chain_audio_routing, None, "Audio Out")) + self.list_data.append((self.chain_audio_routing, None, "Audio Out")) if self.chain.is_audio(): self.list_data.append((self.audio_options, None, "Audio Options")) @@ -93,11 +91,9 @@ def fill_list(self): # TODO: Catch signal for Audio Recording status change if self.chain_id == 0 and not zynthian_gui_config.check_wiring_layout(["Z2", "V5"]): if self.zyngui.state_manager.audio_recorder.status: - self.list_data.append( - (self.toggle_recording, None, "■ Stop Audio Recording")) + self.list_data.append((self.toggle_recording, None, "■ Stop Audio Recording")) else: - self.list_data.append( - (self.toggle_recording, None, "⬤ Start Audio Recording")) + self.list_data.append((self.toggle_recording, None, "⬤ Start Audio Recording")) self.list_data.append((None, None, "> Processors")) @@ -109,28 +105,23 @@ def fill_list(self): if self.chain.is_audio(): # Add Audio-FX options - self.list_data.append( - (self.audiofx_add, None, "Add Pre-fader Audio-FX")) - self.list_data.append( - (self.postfader_add, None, "Add Post-fader Audio-FX")) + self.list_data.append((self.audiofx_add, None, "Add Pre-fader Audio-FX")) + self.list_data.append((self.postfader_add, None, "Add Post-fader Audio-FX")) if self.chain_id != 0: if synth_proc_count * midi_proc_count + audio_proc_count == 0: - self.list_data.append( - (self.remove_chain, None, "Remove Chain")) + self.list_data.append((self.remove_chain, None, "Remove Chain")) else: self.list_data.append((self.remove_cb, None, "Remove...")) self.list_data.append((self.export_chain, None, "Export chain as snapshot...")) elif audio_proc_count > 0: - self.list_data.append( - (self.remove_all_audiofx, None, "Remove all Audio-FX")) + self.list_data.append((self.remove_all_audiofx, None, "Remove all Audio-FX")) self.list_data.append((None, None, "> GUI")) self.list_data.append((self.rename_chain, None, "Rename chain")) if self.chain_id: if len(self.zyngui.chain_manager.ordered_chain_ids) > 2: - self.list_data.append( - (self.move_chain, None, "Move chain ⇦ ⇨")) + self.list_data.append((self.move_chain, None, "Move chain ⇦ ⇨")) super().fill_list() diff --git a/zyngui/zynthian_gui_engine.py b/zyngui/zynthian_gui_engine.py index 0ef68d7ed..50349a4b7 100644 --- a/zyngui/zynthian_gui_engine.py +++ b/zyngui/zynthian_gui_engine.py @@ -214,13 +214,10 @@ def get_info(self, eng_code=None): def update_info(self): eng_info = self.get_info() quality_stars = "★" * eng_info["QUALITY"] - self.info_canvas.itemconfigure( - self.quality_stars_label, text=quality_stars) + self.info_canvas.itemconfigure(self.quality_stars_label, text=quality_stars) complexity_stars = "⚈" * eng_info["COMPLEX"] - self.info_canvas.itemconfigure( - self.complexity_stars_label, text=complexity_stars) - self.info_canvas.itemconfigure( - self.description_label, text=eng_info["DESCR"]) + self.info_canvas.itemconfigure(self.complexity_stars_label, text=complexity_stars) + self.info_canvas.itemconfigure(self.description_label, text=eng_info["DESCR"]) # self.description_label.delete("1.0", tkinter.END) # self.description_label.insert("1.0", eng_info["DESCR"]) @@ -243,23 +240,20 @@ def get_engines_by_cat(self): self.chain_manager.get_engine_info() self.engine_info = self.chain_manager.engine_info self.proc_type = self.zyngui.modify_chain_status["type"] - self.engines_by_cat = self.chain_manager.filtered_engines_by_cat( - self.proc_type, all=self.show_all) + self.engines_by_cat = self.chain_manager.filtered_engines_by_cat(self.proc_type, all=self.show_all) self.engine_cats = list(self.engines_by_cat.keys()) logging.debug(f"CATEGORIES => {self.engine_cats}") # self.engines_by_cat = sorted(self.engines_by_cat.items(), key=lambda kv: "!" if kv[0] is None else kv[0]) def recall_context_index(self): try: - self.index = self.context_index[self.proc_type + - "#" + str(self.cat_index)] + self.index = self.context_index[self.proc_type + "#" + str(self.cat_index)] except: self.index = 0 self.update_context_index() def update_context_index(self): - self.context_index[self.proc_type + "#" + - str(self.cat_index)] = self.index + self.context_index[self.proc_type + "#" + str(self.cat_index)] = self.index def build_view(self): self.show_all = False @@ -296,8 +290,7 @@ def fill_list(self): # Add category header when showing several cats... if len(cats) > 1: - self.list_data.append( - (None, len(self.list_data), "> {}".format(cat))) + self.list_data.append((None, len(self.list_data), "> {}".format(cat))) # Split engines in standalone & plugins # standalone = [] @@ -315,14 +308,11 @@ def add_engines(engines): info = infos[eng] if self.show_all: if info["ENABLED"]: - self.list_data.append( - (eng, i, "\u2612 " + info["TITLE"], info["NAME"])) + self.list_data.append((eng, i, "\u2612 " + info["TITLE"], info["NAME"])) else: - self.list_data.append( - (eng, i, "\u2610 " + info["TITLE"], info["NAME"])) + self.list_data.append((eng, i, "\u2610 " + info["TITLE"], info["NAME"])) else: - self.list_data.append( - (eng, i, info["TITLE"], info["NAME"])) + self.list_data.append((eng, i, info["TITLE"], info["NAME"])) # if len(standalone) > 0: # self.list_data.append((None, None, "> Standalone")) @@ -335,8 +325,7 @@ def add_engines(engines): # Display help if no engines are enabled ... if len(self.list_data) == 0: - self.list_data.append( - (None, len(self.list_data), "Bold-push to enable some engines")) + self.list_data.append((None, len(self.list_data), "Bold-push to enable some engines")) self.index = 0 self.update_context_index() @@ -370,10 +359,8 @@ def select_action(self, i, t='S'): self.zyngui.modify_chain_status["chain_id"], self.zyngui.modify_chain_status["type"]) if self.zyngui.modify_chain_status["type"] == "Audio Effect": # Check for fader position - post_fader = "post_fader" in self.zyngui.modify_chain_status and self.zyngui.modify_chain_status[ - "post_fader"] - fader_pos = self.chain_manager.get_chain( - self.zyngui.modify_chain_status["chain_id"]).fader_pos + post_fader = "post_fader" in self.zyngui.modify_chain_status and self.zyngui.modify_chain_status["post_fader"] + fader_pos = self.chain_manager.get_chain(self.zyngui.modify_chain_status["chain_id"]).fader_pos if post_fader and slot_count > fader_pos or not post_fader and slot_count > 0: ask_parallel = True else: @@ -382,8 +369,9 @@ def select_action(self, i, t='S'): ask_parallel = slot_count > 0 if ask_parallel: # Adding to slot with existing processor - choose parallel/series - self.zyngui.screens['option'].config( - "Chain Mode", {"Series": False, "Parallel": True}, self.cb_add_parallel) + self.zyngui.screens['option'].config("Chain Mode", + {"Series": False, "Parallel": True}, + self.cb_add_parallel) self.zyngui.show_screen('option') return else: @@ -436,14 +424,18 @@ def set_selector(self, zs_hidden=False): self.zselector.zctrl.engine = self if self.zsel2: self.zsel2.zctrl.set_options({'symbol': "cat_index", 'name': "Category", 'short_name': "Category", - 'value_min': 0, 'value_max': len(self.engine_cats) - 1, 'value': self.cat_index}) + 'value_min': 0, 'value_max': len(self.engine_cats) - 1, + 'value': self.cat_index}) self.zsel2.config(self.zsel2.zctrl) self.zsel2.show() else: - zsel2_ctrl = zynthian_controller(self, "cat_index", { - 'name': "Category", 'short_name': "Category", 'value_min': 0, 'value_max': len(self.engine_cats) - 1, 'value': self.cat_index}) + zsel2_ctrl = zynthian_controller(self, "cat_index", + {'name': "Category", 'short_name': "Category", 'value_min': 0, + 'value_max': len(self.engine_cats) - 1, 'value': self.cat_index}) self.zsel2 = zynthian_gui_controller(zynthian_gui_config.select_ctrl - 1, self.main_frame, - zsel2_ctrl, zs_hidden, selcounter=True, orientation=self.layout['ctrl_orientation']) + zsel2_ctrl, zs_hidden, + selcounter=True, + orientation=self.layout['ctrl_orientation']) if not self.zselector_hidden: self.zsel2.grid(row=self.layout['ctrl_pos'][2][0], column=self.layout['ctrl_pos'][2][1], sticky="news", pady=(0, 1)) diff --git a/zyngui/zynthian_gui_selector.py b/zyngui/zynthian_gui_selector.py index f2a3f9de6..5c7276359 100644 --- a/zyngui/zynthian_gui_selector.py +++ b/zyngui/zynthian_gui_selector.py @@ -95,8 +95,7 @@ def __init__(self, selcap='Select', wide=False, loading_anim=True, info=True): for i in range(self.layout['rows']): self.main_frame.rowconfigure(i, weight=1, uniform='ctrl_row') self.main_frame.columnconfigure(self.layout['list_pos'][1], weight=3) - self.main_frame.columnconfigure( - self.layout['list_pos'][1] + 1, weight=1) + self.main_frame.columnconfigure(self.layout['list_pos'][1] + 1, weight=1) # Row 4 expands to fill unused space # self.main_frame.rowconfigure(4, weight=1) #TODO: Validate row 4 is still required after chagnes to layout implementation (BW) @@ -113,8 +112,12 @@ def __init__(self, selcap='Select', wide=False, loading_anim=True, info=True): pady = (0, 1) else: pady = (0, 0) - self.listbox.grid(row=self.layout['list_pos'][0], column=self.layout['list_pos'] - [1], rowspan=self.layout['rows'], padx=padx, pady=pady, sticky="news") + self.listbox.grid(row=self.layout['list_pos'][0], + column=self.layout['list_pos'][1], + rowspan=self.layout['rows'], + padx=padx, + pady=pady, + sticky="news") # Bind listbox events self.listbox_push_ts = 0 @@ -135,17 +138,16 @@ def __init__(self, selcap='Select', wide=False, loading_anim=True, info=True): highlightthickness=0, bg=zynthian_gui_config.color_bg) # Position at top of column containing selector - self.loading_canvas.grid( - row=0, column=self.layout['list_pos'][1] + 1, rowspan=2, sticky="news") + self.loading_canvas.grid(row=0, column=self.layout['list_pos'][1] + 1, rowspan=2, sticky="news") self.loading_push_ts = None self.loading_canvas.bind("", self.cb_loading_push) - self.loading_canvas.bind( - "", self.cb_loading_release) + self.loading_canvas.bind("", self.cb_loading_release) # Setup Loading Logo Animation self.loading_index = 0 - self.loading_item = self.loading_canvas.create_image( - 3, 3, image=zynthian_gui_config.loading_imgs[0], anchor=tkinter.NW) + self.loading_item = self.loading_canvas.create_image(3, 3, + image=zynthian_gui_config.loading_imgs[0], + anchor=tkinter.NW) else: self.loading_canvas = None self.loading_index = 0 @@ -158,8 +160,7 @@ def __init__(self, selcap='Select', wide=False, loading_anim=True, info=True): def update_layout(self): super().update_layout() - ctrl_width = self.width * \ - self.layout['ctrl_width'] * self.sidebar_shown + ctrl_width = self.width * self.layout['ctrl_width'] * self.sidebar_shown if self.layout['columns'] == 2: lb_width = int(self.width - ctrl_width) lb_weight = 3 @@ -167,10 +168,8 @@ def update_layout(self): lb_width = int(self.width - 2 * ctrl_width) lb_weight = 2 ctrl_width = int(ctrl_width) - self.main_frame.columnconfigure( - self.layout['list_pos'][1], minsize=lb_width, weight=lb_weight) - self.main_frame.columnconfigure( - self.layout['list_pos'][1] + 1, minsize=ctrl_width, weight=self.sidebar_shown) + self.main_frame.columnconfigure(self.layout['list_pos'][1], minsize=lb_width, weight=lb_weight) + self.main_frame.columnconfigure(self.layout['list_pos'][1] + 1, minsize=ctrl_width, weight=self.sidebar_shown) def build_view(self): self.fill_list() @@ -199,8 +198,8 @@ def refresh_loading(self): self.loading_index += 1 if self.loading_index > len(zynthian_gui_config.loading_imgs) + 1: self.loading_index = 0 - self.loading_canvas.itemconfig( - self.loading_item, image=zynthian_gui_config.loading_imgs[self.loading_index]) + self.loading_canvas.itemconfig(self.loading_item, + image=zynthian_gui_config.loading_imgs[self.loading_index]) else: self.reset_loading() except: @@ -209,8 +208,7 @@ def refresh_loading(self): def reset_loading(self, force=False): if self.loading_canvas and (self.loading_index > 0 or force): self.loading_index = 0 - self.loading_canvas.itemconfig( - self.loading_item, image=zynthian_gui_config.loading_imgs[0]) + self.loading_canvas.itemconfig(self.loading_item, image=zynthian_gui_config.loading_imgs[0]) def fill_listbox(self): self.listbox.delete(0, tkinter.END) @@ -222,8 +220,9 @@ def fill_listbox(self): label += item[5] self.listbox.insert(tkinter.END, label) if item[0] is None: - self.listbox.itemconfig( - i, {'bg': zynthian_gui_config.color_panel_hl, 'fg': zynthian_gui_config.color_tx_off}) + self.listbox.itemconfig(i, + {'bg': zynthian_gui_config.color_panel_hl, + 'fg': zynthian_gui_config.color_tx_off}) # Can't find any engine currently using this "format" feature: # last_param = item[len(item) - 1] # if isinstance(last_param, dict) and 'format' in last_param: @@ -234,18 +233,25 @@ def set_selector(self, zs_hidden=True): val = self.get_counter_from_index(self.index) vmax = self.get_counter_from_index(len(self.list_data) - 1) if self.zselector: - self.zselector.zctrl.set_options({'symbol': self.selector_caption, 'name': self.selector_caption, - 'short_name': self.selector_caption, 'value_min': 0, 'value_max': vmax, 'value': val}) + self.zselector.zctrl.set_options({'symbol': self.selector_caption, + 'name': self.selector_caption, + 'short_name': self.selector_caption, + 'value_min': 0, + 'value_max': vmax, + 'value': val}) self.zselector.config(self.zselector.zctrl) self.zselector.show() else: zselector_ctrl = zynthian_controller(None, self.selector_caption, { - 'value_min': 0, 'value_max': vmax, 'value': val}) + 'value_min': 0, + 'value_max': vmax, + 'value': val}) self.zselector = zynthian_gui_controller(zynthian_gui_config.select_ctrl, self.main_frame, - zselector_ctrl, zs_hidden, selcounter=True, orientation=self.layout['ctrl_orientation']) + zselector_ctrl, zs_hidden, + selcounter=True, + orientation=self.layout['ctrl_orientation']) if not self.zselector_hidden: - self.zselector.grid( - row=self.layout['ctrl_pos'][3][0], column=self.layout['ctrl_pos'][3][1], sticky="news") + self.zselector.grid(row=self.layout['ctrl_pos'][3][0], column=self.layout['ctrl_pos'][3][1], sticky="news") def plot_zctrls(self): self.swipe_update() @@ -256,8 +262,7 @@ def plot_zctrls(self): self.zselector.plot_value() def swipe_nudge(self, dts): - self.swipe_speed = int(len(self.swipe_roll_scale) - - ((dts - 0.02) / 0.06) * len(self.swipe_roll_scale)) + self.swipe_speed = int(len(self.swipe_roll_scale) - ((dts - 0.02) / 0.06) * len(self.swipe_roll_scale)) self.swipe_speed = min( self.swipe_speed, len(self.swipe_roll_scale) - 1) self.swipe_speed = max(self.swipe_speed, 0) @@ -265,8 +270,7 @@ def swipe_nudge(self, dts): def swipe_update(self): if self.swipe_speed > 0: self.swipe_speed -= 1 - self.listbox.yview_scroll( - self.swipe_dir * self.swipe_roll_scale[self.swipe_speed], tkinter.UNITS) + self.listbox.yview_scroll(self.swipe_dir * self.swipe_roll_scale[self.swipe_speed], tkinter.UNITS) def fill_list(self): self.fill_listbox() @@ -451,8 +455,7 @@ def cb_listbox_motion(self, event): self.swiping = True self.listbox.yview_scroll(offset_y, tkinter.UNITS) self.swipe_dir = abs(dy) // dy - self.listbox_y0 = event.y + self.swipe_dir * \ - (abs(dy) % self.list_entry_height) + self.listbox_y0 = event.y + self.swipe_dir * (abs(dy) % self.list_entry_height) # Use time delta between last motion and release to determine speed of swipe self.listbox_push_ts = event.time diff --git a/zyngui/zynthian_gui_selector_info.py b/zyngui/zynthian_gui_selector_info.py new file mode 100644 index 000000000..7ab6b643a --- /dev/null +++ b/zyngui/zynthian_gui_selector_info.py @@ -0,0 +1,152 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- +# ****************************************************************************** +# ZYNTHIAN PROJECT: Zynthian GUI +# +# Zynthian GUI Selector with Extended Info Class +# +# Copyright (C) 2015-2024 Fernando Moyano +# +# ****************************************************************************** +# +# 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 tkinter +import logging +from PIL import Image, ImageTk + +# Zynthian specific modules +from zyngui import zynthian_gui_config +from zyngui.zynthian_gui_selector import zynthian_gui_selector + +# ------------------------------------------------------------------------------ +# Zynthian Selector with Extended Info GUI Class +# ------------------------------------------------------------------------------ + + +class zynthian_gui_selector_info(zynthian_gui_selector): + + def __init__(self, selcap='Select'): + # Custom layout for GUI engine + self.layout = { + 'name': 'gui_selector_ext', + 'columns': 2, + 'rows': 4, + 'ctrl_pos': [ + (0, 1), + (1, 1), + (2, 1), + (3, 1) + ], + 'list_pos': (0, 0), + 'ctrl_orientation': 'horizontal', + 'ctrl_order': (0, 1, 2, 3), + 'ctrl_width': 0.25 + } + self.icon_canvas = None + self.info_canvas = None + super().__init__(selcap, True, False, True) + + # Canvas for extended info image + self.icon_canvas = tkinter.Canvas(self.main_frame, + width=1, # zynthian_gui_config.fw2, #self.width // 4 - 2, + height=1, # zynthian_gui_config.fh2, #self.height // 2 - 1, + bd=0, + highlightthickness=0, + bg=zynthian_gui_config.color_bg) + self.icon_canvas.bind('', self.cb_info_press) + # Position at top of column containing selector + self.icon_canvas.grid(row=0, column=self.layout['list_pos'][1] + 1, rowspan=2, sticky="news") + + # Canvas for extended info text + self.info_canvas = tkinter.Canvas( + self.main_frame, + width=1, # zynthian_gui_config.fw2, #self.width // 4 - 2, + height=1, # zynthian_gui_config.fh2, #self.height // 2 - 1, + bd=0, + highlightthickness=0, + bg=zynthian_gui_config.color_bg) + self.info_canvas.bind('', self.cb_info_press) + # Position at top of column containing selector + self.info_canvas.grid(row=2, column=self.layout['list_pos'][1] + 1, rowspan=2, sticky="news") + + # Info layout geometry + self.side_width = int(self.layout['ctrl_width'] * self.width) + + # Info icon layout + self.icons = {} + self.icon_size = (self.side_width, self.side_width) + self.icon_image = self.icon_canvas.create_image(self.side_width // 2, 0, anchor="n") + + # Info text layout + info_fs = int(0.8 * zynthian_gui_config.font_size) + xpos = int(0.8 * info_fs) + self.description_label = self.info_canvas.create_text( + xpos, 0, + anchor=tkinter.NW, + justify=tkinter.LEFT, + width=self.side_width - xpos, + text="", + # font=(zynthian_gui_config.font_family, int(0.8 * zynthian_gui_config.font_size)), + font=("sans-serif", info_fs), + fill=zynthian_gui_config.color_panel_tx) + + def update_layout(self): + super().update_layout() + if self.info_canvas: + self.icon_canvas.configure(height=int(0.5 * self.height)) + self.info_canvas.configure(height=int(0.5 * self.height)) + + def get_info(self): + try: + return self.list_data[self.index][3] + except: + return None + + def update_info(self): + info = self.get_info() + if info: + self.info_canvas.itemconfigure(self.description_label, text=info[0]) + self.icon_canvas.itemconfigure(self.icon_image, image=self.get_icon(info[1])) + + def get_icon(self, icon_fname): + if not icon_fname: + return zynthian_gui_config.loading_imgs[0] + elif icon_fname not in self.icons: + try: + img = Image.open(f"/zynthian/zynthian-ui/icons/{icon_fname}") + icon = ImageTk.PhotoImage(img.resize(self.icon_size)) + self.icons[icon_fname] = icon + return icon + except Exception as e: + logging.error(f"Can't load info icon {icon_fname} => {e}") + return zynthian_gui_config.loading_imgs[0] + else: + return self.icons[icon_fname] + + def select(self, index=None, set_zctrl=True): + super().select(index, set_zctrl) + self.update_info() + + def send_controller_value(self, zctrl): + if not self.shown: + return + if zctrl == self.zselector.zctrl: + self.select(zctrl.value) + + def cb_info_press(self, event): + self.zyngui.cuia_help() + +# ------------------------------------------------------------------------------ From 060d88a92e39c33e69df152b614b7e140f37d017 Mon Sep 17 00:00:00 2001 From: riban Date: Thu, 28 Nov 2024 19:15:02 +0000 Subject: [PATCH 026/117] Adds controls for each loop --- zyngine/zynthian_engine_sooperlooper.py | 149 ++++++++++++++++-------- zyngui/zynthian_gui_control.py | 4 +- zyngui/zynthian_widget_sooperlooper.py | 2 + 3 files changed, 106 insertions(+), 49 deletions(-) diff --git a/zyngine/zynthian_engine_sooperlooper.py b/zyngine/zynthian_engine_sooperlooper.py index 9ae92489a..f05934ecc 100644 --- a/zyngine/zynthian_engine_sooperlooper.py +++ b/zyngine/zynthian_engine_sooperlooper.py @@ -34,6 +34,7 @@ from . import zynthian_controller from zynconf import ServerPort +from zyngine.zynthian_signal_manager import zynsigman # ------------------------------------------------------------------------------ # Sooper Looper State Codes @@ -62,6 +63,8 @@ SL_STATE_REDO_ALL = 19 SL_STATE_OFF_MUTED = 20 +SS_GUI_CONTROL_MODE = 2 #TODO: This should be sourced from a common place but should not import gui classes here + # ------------------------------------------------------------------------------ # Sooper Looper Engine Class # ------------------------------------------------------------------------------ @@ -74,6 +77,21 @@ class zynthian_engine_sooperlooper(zynthian_engine): SL_PORT = ServerPort["sooperlooper_osc"] MAX_LOOPS = 6 + # SL_LOOP_SEL_PARAM act on the selected loop - send with osc command /sl/#/set where #=-3 for selected or index of loop (0..5) + SL_LOOP_SEL_PARAM = [ + 'record', + 'overdub', + 'multiply', + 'replace', + 'substitute', + 'insert', + 'trigger', + 'mute', + 'oneshot', + 'pause', + 'reverse' + ] + # SL_LOOP_PARAMS act on individual loops - sent with osc command /sl/#/set SL_LOOP_PARAMS = [ 'feedback', # range 0 -> 1 @@ -337,6 +355,7 @@ def __init__(self, state_manager=None): self.selected_loop = 0 self.loop_count = 1 self.channels = 2 + self.global_cc_binding = True # True for MIDI CC to control selected loop. False to target individual loops ui_dir = os.environ.get('ZYNTHIAN_UI_DIR', "/zynthian/zynthian-ui") self.custom_gui_fpath = f"{ui_dir}/zyngui/zynthian_widget_sooperlooper.py" @@ -354,18 +373,18 @@ def __init__(self, state_manager=None): loop_labels.append(str(i + 1)) self._ctrls = [ #symbol, {options}, midi_cc - ['record', {'value': 0, 'value_max': 1, 'labels': ['off', 'on'], 'is_toggle': True}, 102], - ['overdub', {'value': 0, 'value_max': 1, 'labels': ['off', 'on'], 'is_toggle': True}, 103], - ['multiply', {'value': 0, 'value_max': 1, 'labels': ['off', 'on'], 'is_toggle': True}, 104], - ['replace', {'value': 0, 'value_max': 1, 'labels': ['off', 'on'], 'is_toggle': True}, 105], - ['substitute', {'value': 0, 'value_max': 1, 'labels': ['off', 'on'], 'is_toggle': True}, 106], - ['insert', {'value': 0, 'value_max': 1, 'labels': ['off', 'on'], 'is_toggle': True}, 107], + ['record', {'name': 'record', 'value': 0, 'value_max': 1, 'labels': ['off', 'on'], 'is_toggle': True}, 102], + ['overdub', {'name': 'overdub', 'value': 0, 'value_max': 1, 'labels': ['off', 'on'], 'is_toggle': True}, 103], + ['multiply', {'name': 'multiply', 'value': 0, 'value_max': 1, 'labels': ['off', 'on'], 'is_toggle': True}, 104], + ['replace', {'name': 'replace', 'value': 0, 'value_max': 1, 'labels': ['off', 'on'], 'is_toggle': True}, 105], + ['substitute', {'name': 'substitute', 'value': 0, 'value_max': 1, 'labels': ['off', 'on'], 'is_toggle': True}, 106], + ['insert', {'name': 'insert', 'value': 0, 'value_max': 1, 'labels': ['off', 'on'], 'is_toggle': True}, 107], ['undo/redo', {'value': 1, 'labels': ['<', '<>', '>']}], ['prev/next', {'value': 63, 'value_max': 127, 'labels': ['<', '<>', '>']}], - ['trigger', {'value': 0, 'value_max': 1, 'labels': ['off', 'on'], 'is_toggle': True}, 108], - ['mute', {'value': 0, 'value_max': 1, 'labels': ['off', 'on'], 'is_toggle': True}, 109], - ['oneshot', {'value': 0, 'value_max': 1, 'labels': ['off', 'on'], 'is_toggle': True}, 110], - ['pause', {'value': 0, 'value_max': 1, 'labels': ['off', 'on'], 'is_toggle': True}, 111], + ['trigger', {'name': 'trigger', 'value': 0, 'value_max': 1, 'labels': ['off', 'on'], 'is_toggle': True}, 108], + ['mute', {'name': 'mute', 'value': 0, 'value_max': 1, 'labels': ['off', 'on'], 'is_toggle': True}, 109], + ['oneshot', {'name': 'oneshot', 'value': 0, 'value_max': 1, 'labels': ['off', 'on'], 'is_toggle': True}, 110], + ['pause', {'name': 'pause', 'value': 0, 'value_max': 1, 'labels': ['off', 'on'], 'is_toggle': True}, 111], ['reverse', {'name': 'direction', 'value': 0, 'labels': ['reverse', 'forward'], 'ticks':[1, 0], 'is_toggle': True}], ['rate', {'name': 'speed', 'value': 1.0, 'value_min': 0.25, 'value_max': 4.0, 'is_integer': False, 'nudge_factor': 0.01}], ['stretch_ratio', {'name': 'stretch', 'value': 1.0, 'value_min': 0.5, 'value_max': 4.0, 'is_integer': False, 'nudge_factor': 0.01}], @@ -389,21 +408,22 @@ def __init__(self, state_manager=None): ['input_gain', {'name': 'input gain', 'value': 1.0, 'value_max': 1.0, 'is_integer': False, 'is_logarithmic': True}], ['loop_count', {'name': 'loop count', 'value': 1, 'value_min': 1, 'value_max': self.MAX_LOOPS}], ['selected_loop_num', {'name': 'selected loop', 'value': 1, 'value_min': 1, 'value_max': 6}], - ['single_pedal', {'name': 'single pedal', 'value': 0, 'value_max': 1, 'labels': ['>', '<'], 'is_toggle': True}] + ['single_pedal', {'name': 'single pedal', 'value': 0, 'value_max': 1, 'labels': ['>', '<'], 'is_toggle': True}], + ['global_cc', {'name': 'direct cc', 'value': 1, 'labels':['off', 'on']}] ] # Controller Screens self._ctrl_screens = [ - ['Loop record 1', ['record', 'overdub', 'multiply', 'undo/redo']], - ['Loop record 2', ['replace', 'substitute', 'insert', 'undo/redo']], - ['Loop control', ['trigger', 'oneshot', 'mute', 'pause']], - ['Loop time/pitch', ['reverse', 'rate', 'stretch_ratio', 'pitch_shift']], + ['Loop record 1', ['record:0', 'overdub:0', 'multiply:0', 'undo/redo']], + ['Loop record 2', ['replace:0', 'substitute:0', 'insert:0', 'undo/redo']], + ['Loop control', ['trigger:0', 'oneshot:0', 'mute:0', 'pause:0']], + ['Loop time/pitch', ['reverse:0', 'rate', 'stretch_ratio', 'pitch_shift']], ['Loop levels', ['wet', 'dry', 'feedback', 'selected_loop_num']], ['Global loop', ['selected_loop_num', 'loop_count', 'prev/next', 'single_pedal']], ['Global levels', ['rec_thresh', 'input_gain']], ['Global quantize', ['quantize', 'mute_quantized', 'overdub_quantized', 'replace_quantized']], ['Global sync 1', ['sync_source', 'sync', 'playback_sync', 'relative_sync']], - ['Global sync 2', ['round', 'use_feedback_play']], + ['Global sync 2', ['round', 'use_feedback_play', 'global_cc']] ] self.start() @@ -544,20 +564,40 @@ def get_controllers_dict(self, processor): midi_chan = None for ctrl in self._ctrls: ctrl[1]['processor'] = processor - zctrl = zynthian_controller(self, ctrl[0], ctrl[1]) - processor.controllers_dict[zctrl.symbol] = zctrl - if midi_chan is not None and len(ctrl) > 2: - self.state_manager.chain_manager.add_midi_learn(midi_chan, ctrl[2], zctrl) + if ctrl[0] in self.SL_LOOP_SEL_PARAM: + # Create a zctrl for each loop + for i in range(self.MAX_LOOPS): + zctrl = zynthian_controller(self, f"{ctrl[0]}:{i}", ctrl[1]) + processor.controllers_dict[zctrl.symbol] = zctrl + if midi_chan is not None and len(ctrl) > 2: + self.state_manager.chain_manager.add_midi_learn(midi_chan, ctrl[2], zctrl) + else: + zctrl = zynthian_controller(self, ctrl[0], ctrl[1]) + processor.controllers_dict[zctrl.symbol] = zctrl + if midi_chan is not None and len(ctrl) > 2: + self.state_manager.chain_manager.add_midi_learn(midi_chan, ctrl[2], zctrl) return processor.controllers_dict def send_controller_value(self, zctrl): #logging.warning(f"{zctrl.symbol} {zctrl.value}") - if self.osc_server is None or zctrl.symbol in ['oneshot', 'trigger'] and zctrl.value == 0: + if zctrl.symbol == "global_cc": + self.global_cc_binding = zctrl.value != 0 + return + if ":" in zctrl.symbol: + symbol, chan = zctrl.symbol.split(":") + if not self.global_cc_binding: + if int(chan) != self.selected_loop: + return + chan = -3 + else: + symbol = zctrl.symbol + chan = -3 + if self.osc_server is None or symbol in ['oneshot', 'trigger'] and zctrl.value == 0: # Ignore off signals return - elif zctrl.symbol in ("mute", "pause"): - self.osc_server.send(self.osc_target, '/sl/-3/hit', ('s', zctrl.symbol)) - elif zctrl.symbol == 'single_pedal': + elif symbol in ("mute", "pause"): + self.osc_server.send(self.osc_target, f'/sl/{chan}/hit', ('s', symbol)) + elif symbol == 'single_pedal': """ Single pedal logic Idle -> Record Record->Play @@ -578,58 +618,58 @@ def send_controller_value(self, zctrl): self.pedal_taps = 0 # Triple tap if self.pedal_taps == 2: - self.osc_server.send(self.osc_target, '/sl/-3/hit', ('s', 'undo_all')) + self.osc_server.send(self.osc_target, f'/sl/{chan}/hit', ('s', 'undo_all')) # Double tap elif self.pedal_taps == 1: - self.osc_server.send(self.osc_target, '/sl/-3/hit', ('s', 'pause')) + self.osc_server.send(self.osc_target, f'/sl/{chan}/hit', ('s', 'pause')) # Single tap elif self.state[self.selected_loop] in (SL_STATE_UNKNOWN, SL_STATE_OFF, SL_STATE_OFF_MUTED): - self.osc_server.send(self.osc_target, '/sl/-3/hit', ('s', 'record')) + self.osc_server.send(self.osc_target, f'/sl/{chan}/hit', ('s', 'record')) elif self.state[self.selected_loop] == SL_STATE_RECORDING: - self.osc_server.send(self.osc_target, '/sl/-3/hit', ('s', 'record')) + self.osc_server.send(self.osc_target, f'/sl/{chan}/hit', ('s', 'record')) elif self.state[self.selected_loop] in (SL_STATE_PLAYING, SL_STATE_OVERDUBBING): - self.osc_server.send(self.osc_target, '/sl/-3/hit', ('s', 'overdub')) + self.osc_server.send(self.osc_target, f'/sl/{chan}/hit', ('s', 'overdub')) elif self.state[self.selected_loop] == SL_STATE_PAUSED: - self.osc_server.send(self.osc_target, '/sl/-3/hit', ('s', 'trigger')) + self.osc_server.send(self.osc_target, f'/sl/{chan}/hit', ('s', 'trigger')) # Pedal release: so check loop state, pedal press duration, etc. else: # Long press if pedal_dur > 1.5: if self.pedal_taps: - self.osc_server.send(self.osc_target, '/sl/-3/hit', ('s', 'undo_all')) - elif zctrl.symbol == 'selected_loop_num': - self.select_loop(zctrl.value - 1, True) - elif zctrl.symbol in self.SL_LOOP_PARAMS: # Selected loop - self.osc_server.send(self.osc_target, '/sl/-3/set', ('s', zctrl.symbol), ('f', zctrl.value)) - elif zctrl.symbol in self.SL_LOOP_GLOBAL_PARAMS: # All loops - self.osc_server.send(self.osc_target, '/sl/-1/set', ('s', zctrl.symbol), ('f', zctrl.value)) - elif zctrl.symbol in self.SL_GLOBAL_PARAMS: # Global params - self.osc_server.send(self.osc_target, '/set', ('s', zctrl.symbol), ('f', zctrl.value)) + self.osc_server.send(self.osc_target, f'/sl/{chan}/hit', ('s', 'undo_all')) + elif symbol == 'selected_loop_num': + self.select_loop(zctrl.value - 1, False) + elif symbol in self.SL_LOOP_PARAMS: # Selected loop + self.osc_server.send(self.osc_target, f'/sl/{chan}/set', ('s', symbol), ('f', zctrl.value)) + elif symbol in self.SL_LOOP_GLOBAL_PARAMS: # All loops + self.osc_server.send(self.osc_target, '/sl/-1/set', ('s', symbol), ('f', zctrl.value)) + elif symbol in self.SL_GLOBAL_PARAMS: # Global params + self.osc_server.send(self.osc_target, '/set', ('s', symbol), ('f', zctrl.value)) elif zctrl.is_toggle: # Use is_toggle to indicate the SL function is a toggle, i.e. press to engage, press to release - if zctrl.symbol == 'record' and zctrl.value == 0 and self.state[self.selected_loop] == SL_STATE_REC_STARTING: + if symbol == 'record' and zctrl.value == 0 and self.state[self.selected_loop] == SL_STATE_REC_STARTING: # TODO: Implement better toggle of pending state - self.osc_server.send(self.osc_target, '/sl/-3/hit', ('s', 'undo')) + self.osc_server.send(self.osc_target, f'/sl/{chan}/hit', ('s', 'undo')) return - self.osc_server.send(self.osc_target, '/sl/-3/hit', ('s', zctrl.symbol)) - #if zctrl.symbol == 'trigger': + self.osc_server.send(self.osc_target, f'/sl/{chan}/hit', ('s', symbol)) + #if symbol == 'trigger': #zctrl.set_value(0, False) # Make trigger a pulse - elif zctrl.symbol == 'undo/redo': + elif symbol == 'undo/redo': # Use single controller to perform undo (CCW) and redo (CW) if zctrl.value == 0: - self.osc_server.send(self.osc_target, '/sl/-3/hit', ('s', 'undo')) + self.osc_server.send(self.osc_target, f'/sl/{chan}/hit', ('s', 'undo')) elif zctrl.value == 2: - self.osc_server.send(self.osc_target, '/sl/-3/hit', ('s', 'redo')) + self.osc_server.send(self.osc_target, f'/sl/{chan}/hit', ('s', 'redo')) zctrl.set_value(1, False) - elif zctrl.symbol == 'prev/next': + elif symbol == 'prev/next': # Use single controller to perform prev(CCW) and next (CW) if zctrl.value < 63: self.select_loop(self.selected_loop - 1, True) elif zctrl.value > 63: self.select_loop(self.selected_loop + 1, True) zctrl.set_value(63, False) - elif zctrl.symbol == 'loop_count': + elif symbol == 'loop_count': for loop in range(self.loop_count, zctrl.value): self.osc_server.send(self.osc_target, '/loop_add', ('i', self.channels), ('f', 30), ('i', 0)) if zctrl.value < self.loop_count: @@ -762,16 +802,28 @@ def update_state(self): try: current_state = self.state[self.selected_loop] for symbol in self.SL_STATES[current_state]['ctrl_off']: + if symbol in self.SL_LOOP_SEL_PARAM: + symbol += f":{self.selected_loop}" processor.controllers_dict[symbol].set_readonly(False) processor.controllers_dict[symbol].set_value(0, False) for symbol in self.SL_STATES[current_state]['ctrl_on']: + if symbol in self.SL_LOOP_SEL_PARAM: + symbol += f":{self.selected_loop}" processor.controllers_dict[symbol].set_readonly(False) processor.controllers_dict[symbol].set_value(1, False) next_state = self.next_state[self.selected_loop] if self.SL_STATES[next_state]['next_state']: for symbol in self.SL_STATES[next_state]['ctrl_on']: + if symbol in self.SL_LOOP_SEL_PARAM: + symbol += f":{self.selected_loop}" processor.controllers_dict[symbol].set_value(1, False) processor.controllers_dict[symbol].set_readonly(True) + self._ctrl_screens[0][1] = [f'record:{self.selected_loop}', f'overdub:{self.selected_loop}', f'multiply:{self.selected_loop}', 'undo/redo'] + self._ctrl_screens[1][1] = [f'replace:{self.selected_loop}', f'substitute:{self.selected_loop}', f'insert:{self.selected_loop}', 'undo/redo'] + self._ctrl_screens[2][1] = [f'trigger:{self.selected_loop}', f'oneshot:{self.selected_loop}', f'mute:{self.selected_loop}', f'pause:{self.selected_loop}'] + self._ctrl_screens[3][1] = [f'reverse:{self.selected_loop}', 'rate', 'stretch_ratio', 'pitch_shift'] + processor.refresh_controllers() + except Exception as e: logging.error(e) #self.processors[0].status = self.SL_STATES[self.state]['icon'] @@ -790,6 +842,7 @@ def select_loop(self, loop, send=False): pass if send and self.osc_server: self.osc_server.send(self.osc_target, '/set', ('s', 'selected_loop_num'), ('f', self.selected_loop)) + zynsigman.send_queued(zynsigman.S_GUI, SS_GUI_CONTROL_MODE, mode='control') def prev_loop(self): self.processors[0].controllers_dict['prev/next'].nudge(-1) diff --git a/zyngui/zynthian_gui_control.py b/zyngui/zynthian_gui_control.py index e48d06b39..323321778 100644 --- a/zyngui/zynthian_gui_control.py +++ b/zyngui/zynthian_gui_control.py @@ -147,6 +147,8 @@ def backbutton_short_touch_action(self): def cb_control_mode(self, mode): self.set_button_status(2, (mode == "select")) + if mode == "control": + self.set_mode_control() def configure_processors(self, curproc=None): if not curproc: @@ -397,7 +399,7 @@ def set_mode_control(self): for i in range(0, len(self.zgui_controllers)): self.zgui_controllers[i].unset_hl() self.set_select_path() - zynsigman.send_queued(zynsigman.S_GUI, self.SS_GUI_CONTROL_MODE, mode=self.mode) + #zynsigman.send_queued(zynsigman.S_GUI, self.SS_GUI_CONTROL_MODE, mode=self.mode) def previous_page(self, wrap=False): i = self.index - 1 diff --git a/zyngui/zynthian_widget_sooperlooper.py b/zyngui/zynthian_widget_sooperlooper.py index b3e6c1e8d..b474afa26 100644 --- a/zyngui/zynthian_widget_sooperlooper.py +++ b/zyngui/zynthian_widget_sooperlooper.py @@ -342,6 +342,8 @@ def on_button(self, btn): if btn in ['undo', 'redo']: liblo.send(self.osc_url, '/sl/-3/hit', ('s', btn)) else: + if btn in self.processor.engine.SL_LOOP_SEL_PARAM: + btn += f":{self.processor.engine.selected_loop}" self.processor.controllers_dict[btn].toggle() def on_slider_wheel(self, event): From 4a8e0a76166de640790278c27b494271e4768e60 Mon Sep 17 00:00:00 2001 From: riban Date: Fri, 29 Nov 2024 08:51:29 +0000 Subject: [PATCH 027/117] Wait until html loaded before showing help --- zyngui/zynthian_gui.py | 5 +++-- zyngui/zynthian_gui_help.py | 7 ++++++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/zyngui/zynthian_gui.py b/zyngui/zynthian_gui.py index d9408714d..c0e3ae925 100644 --- a/zyngui/zynthian_gui.py +++ b/zyngui/zynthian_gui.py @@ -447,7 +447,7 @@ def osc_cb_all(self, path, args, types, src): def create_screens(self): # Create Core UI Screens self.screens['info'] = zynthian_gui_info() - self.screens['help'] = zynthian_gui_help() + self.screens['help'] = zynthian_gui_help(self) self.screens['splash'] = zynthian_gui_splash() self.screens['loading'] = zynthian_gui_loading() self.screens['confirm'] = zynthian_gui_confirm() @@ -854,7 +854,8 @@ def show_help(self, topic=None): if not topic: topic = self.current_screen if self.screens['help'].load_file(f"./help/{topic}.html"): - self.show_screen("help") + pass + #self.show_screen("help") elif topic != "help": logging.warning(f"No help for '{topic}'") diff --git a/zyngui/zynthian_gui_help.py b/zyngui/zynthian_gui_help.py index 9505da68b..69ec9a675 100644 --- a/zyngui/zynthian_gui_help.py +++ b/zyngui/zynthian_gui_help.py @@ -38,11 +38,16 @@ class zynthian_gui_help: ui_dir = os.environ.get('ZYNTHIAN_UI_DIR', "/zynthian/zynthian-ui") - def __init__(self): + def __init__(self, zyngui): + self.zyngui = zyngui self.shown = False self.zyngui = zynthian_gui_config.zyngui # Main Frame self.main_frame = HtmlFrame(zynthian_gui_config.top, messages_enabled=False) + self.main_frame.on_done_loading(self.done_loading) + + def done_loading(self): + self.zyngui.show_screen("help") def load_file(self, fpath): if os.path.isfile(fpath): From 0f49c18747419b56bf0f82b65ac979e59e8ee00b Mon Sep 17 00:00:00 2001 From: jofemodo Date: Fri, 29 Nov 2024 12:37:41 +0100 Subject: [PATCH 028/117] + Deploy side-panel extended info in main menu. + Adjust info font size to fit V4 layout. --- zyngui/zynthian_gui_main_menu.py | 73 ++++++++++++++++++---------- zyngui/zynthian_gui_selector_info.py | 5 +- 2 files changed, 51 insertions(+), 27 deletions(-) diff --git a/zyngui/zynthian_gui_main_menu.py b/zyngui/zynthian_gui_main_menu.py index b7a6e57f9..b8e2c461a 100644 --- a/zyngui/zynthian_gui_main_menu.py +++ b/zyngui/zynthian_gui_main_menu.py @@ -26,18 +26,17 @@ import logging # Zynthian specific modules -from zyngui import zynthian_gui_config -from zyngui.zynthian_gui_selector import zynthian_gui_selector +from zyngui.zynthian_gui_selector_info import zynthian_gui_selector_info # ------------------------------------------------------------------------------ # Zynthian App Selection GUI Class # ------------------------------------------------------------------------------ -class zynthian_gui_main_menu(zynthian_gui_selector): +class zynthian_gui_main_menu(zynthian_gui_selector_info): def __init__(self): - super().__init__('Menu', True) + super().__init__('Menu') def fill_list(self): self.list_data = [] @@ -50,37 +49,61 @@ def fill_list(self): mixer_avail = False self.list_data.append((None, 0, "> ADD CHAIN")) if mixer_avail: - self.list_data.append( - (self.add_synth_chain, 0, "Add Instrument Chain")) - self.list_data.append((self.add_audiofx_chain, 0, "Add Audio Chain")) - self.list_data.append((self.add_midifx_chain, 0, "Add MIDI Chain")) + self.list_data.append((self.add_synth_chain, 0, + "Add Instrument Chain", + ["Create a new chain with a MIDI-controlled synth engine. The chain receives MIDI input and generates audio output.", + "midi_instrument.png"])) + self.list_data.append((self.add_audiofx_chain, 0, + "Add Audio Chain", + ["Create a new chain for audio FX processing. The chain receives audio input and generates audio output.", + "audio.png"])) + self.list_data.append((self.add_midifx_chain, 0, + "Add MIDI Chain", + ["Create a new chain for MIDI processing. The chain receives MIDI input and generates MIDI output.", + "midi_logo.png"])) if mixer_avail: - self.list_data.append( - (self.add_midiaudiofx_chain, 0, "Add MIDI+Audio Chain")) - self.list_data.append( - (self.add_generator_chain, 0, "Add Audio Generator Chain")) - self.list_data.append((self.add_special_chain, 0, "Add Special Chain")) + self.list_data.append((self.add_midiaudiofx_chain, 0, + "Add MIDI+Audio Chain", + ["Create a new chain for combined audio + MIDI processing. The chain receives audio & MIDI input and generates audio & MIDI output. Use it with vocoders, autotune, etc.", + "midi_audio.png"])) + self.list_data.append((self.add_generator_chain, 0, + "Add Audio Generator Chain", + ["Create a new chain for audio generation. The chain doesn't receive any input and generates audio output. Internet radio, test signals, etc.", + None])) + self.list_data.append((self.add_special_chain, 0, + "Add Special Chain", + ["Create a new chain for special processing. The chain receives audio & MIDI input and generates audio & MIDI output. use it for MOD-UI, puredata, etc.", + None])) self.list_data.append((None, 0, "> REMOVE")) - self.list_data.append((self.remove_sequences, 0, "Remove Sequences")) - self.list_data.append((self.remove_chains, 0, "Remove Chains")) - self.list_data.append((self.remove_all, 0, "Remove All")) + self.list_data.append((self.remove_sequences, 0, + "Remove Sequences", + ["Clean all sequencer data while keeping existing chains.", + None])) + self.list_data.append((self.remove_chains, 0, + "Remove Chains", + ["Clean all chains while keeping sequencer data.", + None])) + self.list_data.append((self.remove_all, 0, + "Remove All", + ["Clean all chains and sequencer data. Start from scratch!", + None])) # Add list of Apps self.list_data.append((None, 0, "> MAIN")) - self.list_data.append((self.snapshots, 0, "Snapshots")) - self.list_data.append((self.step_sequencer, 0, "Sequencer")) - self.list_data.append((self.audio_recorder, 0, "Audio Recorder")) - self.list_data.append((self.midi_recorder, 0, "MIDI Recorder")) - self.list_data.append((self.tempo_settings, 0, "Tempo Settings")) - self.list_data.append((self.audio_levels, 0, "Audio Levels")) - self.list_data.append((self.audio_mixer_learn, 0, "Mixer Learn")) + self.list_data.append((self.snapshots, 0, "Snapshots", ["Show snapshots management menu.", None])) + self.list_data.append((self.step_sequencer, 0, "Sequencer", ["Show sequencer's zynpad view.", None])) + self.list_data.append((self.audio_recorder, 0, "Audio Recorder", ["Show audio recorder/player.", None])) + self.list_data.append((self.midi_recorder, 0, "MIDI Recorder", ["Show SMF recorder/player.", None])) + self.list_data.append((self.tempo_settings, 0, "Tempo Settings", ["Show tempo & sync options.", None])) + self.list_data.append((self.audio_levels, 0, "Audio Levels", ["Show audio levels view.", None])) + self.list_data.append((self.audio_mixer_learn, 0, "Mixer Learn", ["Enter mixer's MIDI learn mode", None])) # Add list of System / configuration views self.list_data.append((None, 0, "> SYSTEM")) - self.list_data.append((self.admin, 0, "Admin")) + self.list_data.append((self.admin, 0, "Admin", ["Show admin menu.", None])) self.list_data.append( - (self.all_sounds_off, 0, "PANIC! All Sounds Off")) + (self.all_sounds_off, 0, "PANIC! All Sounds Off", ["Stop all notes and sequences.", None])) super().fill_list() diff --git a/zyngui/zynthian_gui_selector_info.py b/zyngui/zynthian_gui_selector_info.py index 7ab6b643a..424dc17d1 100644 --- a/zyngui/zynthian_gui_selector_info.py +++ b/zyngui/zynthian_gui_selector_info.py @@ -91,10 +91,11 @@ def __init__(self, selcap='Select'): self.icon_image = self.icon_canvas.create_image(self.side_width // 2, 0, anchor="n") # Info text layout - info_fs = int(0.8 * zynthian_gui_config.font_size) + info_fs = min(int(0.8 * zynthian_gui_config.font_size), self.side_width // 16) xpos = int(0.8 * info_fs) + ypos = int(-0.3 * info_fs) self.description_label = self.info_canvas.create_text( - xpos, 0, + xpos, ypos, anchor=tkinter.NW, justify=tkinter.LEFT, width=self.side_width - xpos, From 77d7e2a30ae24473bd81b6f8f16e5e760d0d4f84 Mon Sep 17 00:00:00 2001 From: jofemodo Date: Fri, 29 Nov 2024 14:27:43 +0100 Subject: [PATCH 029/117] Add more nice icons for main/chain menu side-panel extended info. --- icons/audio.png | Bin 4798 -> 729 bytes icons/audio_generator.png | Bin 0 -> 3239 bytes icons/back.png | Bin 1769 -> 1072 bytes icons/delete.png | Bin 0 -> 974 bytes icons/delete_all.png | Bin 0 -> 1024 bytes icons/delete_chains.png | Bin 0 -> 1105 bytes icons/delete_icons.xcf | Bin 0 -> 184740 bytes icons/delete_sequences.png | Bin 0 -> 1186 bytes icons/midi_audio.png | Bin 7116 -> 862 bytes icons/midi_instrument.png | Bin 4038 -> 1957 bytes icons/midi_logo.png | Bin 9808 -> 290 bytes icons/special_chain.png | Bin 0 -> 3438 bytes icons/stopped.png | Bin 5392 -> 174 bytes icons/zynpad_mode_loop.png | Bin 13581 -> 821 bytes icons/zynpad_mode_loopall.png | Bin 9158 -> 902 bytes icons/zynpad_mode_loopsync.png | Bin 14271 -> 886 bytes icons/zynpad_mode_oneshot.png | Bin 10164 -> 349 bytes icons/zynpad_mode_oneshotall.png | Bin 10928 -> 559 bytes icons/zynpad_mode_oneshotsync.png | Bin 10854 -> 514 bytes zyngui/zynthian_gui_chain_menu.py | 10 +++++----- zyngui/zynthian_gui_main_menu.py | 10 +++++----- 21 files changed, 10 insertions(+), 10 deletions(-) create mode 100644 icons/audio_generator.png create mode 100644 icons/delete.png create mode 100644 icons/delete_all.png create mode 100644 icons/delete_chains.png create mode 100644 icons/delete_icons.xcf create mode 100644 icons/delete_sequences.png create mode 100644 icons/special_chain.png diff --git a/icons/audio.png b/icons/audio.png index 7794c07fe0a402ac8d72bd64a90f93961c752040..420c1730542ed72dab27ab3536e488c0dc18212a 100644 GIT binary patch delta 717 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 SaKhoWhn>x3MD<0~o&N#ekOXo7 diff --git a/icons/audio_generator.png b/icons/audio_generator.png new file mode 100644 index 0000000000000000000000000000000000000000..043542c0c9bfcf9c4fdf3c91b3c23dcaa579cff6 GIT binary patch literal 3239 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 literal 0 HcmV?d00001 diff --git a/icons/back.png b/icons/back.png index d161842bd1115dfa492dde9edc3bf125c6d8f6a3..d8837c1e3f47a0f05896ed78891ddec497800f89 100644 GIT binary patch delta 1063 zcmV+?1larO4X_B17=Hl+0001HygDTS000J1OjJcnJ68Yy|M=0on*aa+0d!JMQvg8b z*k%9#00Cl4M??UK1szBL000SaNLh0L01FcU01FcV0GgZ_00007bV*G`2j~kO4HYcz z)s(mZ00W~*L_t(&-kq9DvZF8zMg1y+46_85lBI>Eu@siTkbeP%8VDGFu6$D66S|PF zr4x|#c_D;$VmhtAk;G^w2X8LsaM;44cBSkOFaF z*f#$vj1GpVFn>81qQdN8*vvO`K)f@B)xi)I%)xLg^cushFlY?7!l*Ia3X{fgE6ffC zyS!N(3{hcqFkGl>5LPPm8p9g*8Z?F_DvTP#65W|JhFf9Q80JJ(Vz?DniDBFMvc@Ea z&GyTm9K?lT^FH~nz_34N3Pif`M}bI9e=87O;(Vt-q<>FI5b3@WL~6MLk@>DbPyNNRo`M}min4A z$5cGX?tiI>lC7#@Qf8NmYN=;3E~YniWKEXUu{kkJNB6`v85YyKGO(uW%FvuzCWCw8 zn?4>A5E?_WuyaJ+ra6b)?VQGtfOtC@M_l>ig z6o1U|IKTr1k0dS`g z8&h{m{;_<(0t8DKEP}9%!a@v7IVuLRoWz0@OIj?7v8=|z97}s@4q$r$8x+{$z$OW{ zS+G%qtsZI?VY>+%R@m~wrWv-~uyKd2KYwZ-V*3#rnAifvCMvdJv5||dU}`2~I~p6> z*b>L4I<(~{ZtUY|0EY`Wp1=XeuOkv1viM^f9Q6D;D#BqCj;nCsMI$pDx;4gjIQaW< zbcn;p#`qEjpev71aR`fJTpR@BC>e**IL?NF_&_7|{)eGFj`cgY5Py9oPq)^;Bp|In hPB`4_yi)&=`~!*|Fw;r|md5}9002ovPDHLkV1jZo)C~Xt literal 1769 zcmeAS@N?(olHy`uVBq!ia0y~yU<5K588}#g)VJz{w+swy8$4YcLn>~)y}LeGI$Eaf zqo^PdbaZgJxPkyHQ(&iqi-JH$N0+uhxQoJ!hx`soN&+3Hy1KeJTwGb08dr2U#DKIk zc2vB-`DEE05us1n=IIsl^L>{azp*_0+%uxA?6S(t$c7R z_vrc+t9F)0PyL_!Ew$IzT2t+z5cjXdPd)yBZYN96kIS!{`@?fXg3X+nHhx-O{H-E& zrSilE$^BmzoXVKUoNB2epk8nF^`g;qAx?=;-q#nm?gBBE>plIFH{Yg><qFLG;R*FLt{yE&FpRcF{Bt9i)D7FNjY3#NWW8^vIui$I1FYp^n!0yEXny zo(g*Z9(q1l3R3X$Xu@0%m4s9Jrk{3+_%IoM;@4*tzoHB@GE@8Rk*jXo6XqN^_Orfk zTcO6p2F3j)Mz=gP4@VyY`RAie=|)CODk= zGnv0FmIvnk40S>c^I0CKGyd?T zj0rLd!_D_S7&g}IIL-F&bCsGL^}>fxD8Gk=-J*x~eNURx_6T*f*YA*~b~Isi#&&Gs zKz>X*%zt3+kx+k&P5ae>{0R>6@8{o+2{ivOTcu$A+U3!^o9=5gW{awA-^#x~l4V}>8*^Bs&Yn*Zn(-r;+t>Appy-=gXtbA@lXFnn;I;xOrV zEW?N6Qyk1M@&i@;nEFBpsC%98+ynV@9F#An1I1P>Eoc7naJ%nn^iIEpF|5 zVD9^%WQ$Lv?GIjIi5G7xW?KDe+ox+dDz7G;Bm}b0G`fz;CfuB!=j&8M= zVyZvG`RDVKmkxU)U-vWqNcLN>p8ve=#?9OO*&FQNvG6U^4Q#%z(6~9$`_I-#yAL?T zH=HtPe056v-TAnvr0}~>^Yt(2c}S{%sB5j>@hkZLAN6TBuH6l4-+$T6!BSw1mG$-zvm1!fy=ywBmcs(+T8n5GwWE%L{vO8&Su)#n^t#rr+j zcg@)GYWo2t(W{4VpIvkL(0r@NPY=$#5}fjN`z+(yD8MM=Q8YaMb*hH{sWvf8TzZepz|;^QAxH30yZiSkA_T0jn?uPgg&ebxsLQ0OsC2 ANdN!< diff --git a/icons/delete.png b/icons/delete.png new file mode 100644 index 0000000000000000000000000000000000000000..770e1d662080f77bcc922facb1f2a744bc87527b GIT binary patch literal 974 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 literal 0 HcmV?d00001 diff --git a/icons/delete_icons.xcf b/icons/delete_icons.xcf new file mode 100644 index 0000000000000000000000000000000000000000..a411e883b50397e0fe216fbd3f006a1541fe4c9d GIT binary patch literal 184740 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

6lcZE| zgTy>BlBB^tI!r4>>=-Ke=Iex57UCmYfCG8=F0lAanj3@;a-6!39MfP-=Sl#8oNrPr(o%wcHURo+~B zFkIvgVX(P-Bs1v=PBiJsfFbD}m%zXi^N|#$sROKjB<&`dz(*%MGJxG{fPdyDDw~A{ zfXWoGTH~IPqT{Z>?#;an|7jBATOnAp1j45^j#b8ydyzp?#{+Z?7nC}j9%s62rd0Jb zM;y?-G~KHWZ;a%G3y zYy3?lsp3bTJ&-0DP$e&>twV44ljdF`*+ZO%Y=~XH_V%Ct_S3sk+4l`zdd{$y(!jkP2J)_bwuG8Z z!1X5+mM@KqK|wG^wW7~az&Cyas^t!r0CxV8)Y9TTP9PvQ0=A59(Lx~u^R7d>JftKP zt3$bqFrU$)`%QA#$qcePXGZ$c;@os91V5B?lS}4YRs(bUCf2i@u-+zNs8FqA`f zC97x1hP~n-r;BqJU+j#YZde4IVAb;`#geXI`kLtsFgiR;Pc4zn;s=h@v;(v08z?M5*{9oR!6B zPd`lMHVGa+aw9}X>AzJC{rFK8RU7{y~vC3mPP+qk}*G&Y_#ToCB%vGH_+Y%&xGhC}*R^3uqGK%!32 z(Yq=3d7ey>?;W7BenfKU^6(EndYd(tZ<>7#(CV$Pp z8-%FU4ERJ|w__!&0=HsZ-lM@kOAC*rKVH2*eG5p?>%f3sV;B(c$s_5*B)^g8Ljs86 z2kyULw@qX%H*bX;u*BmU-}7xyX^@P}@-m%c{%(x1;bIR8DiAde@bxwyr$S^hXagT_ zsm$^QC~!P8)yC5<`Rt>3uh1O!vhM1VHm8}(xuiTp&28+vbW3KpS`Z;L?VQhPyQNE8 z5Ms_-DQQIPr=&UCgXDNyH@@O2-buwZY%4i3XHGAj!^oRR-AKX=&BpPW8Rt%KhiHGckJ@|G zjK~p4l|wRKh#B5@)gZo=IN%gH<^_`Cxg;eU4&d3?J4r^(+8`(ia(AvNHJTeJ6*@_p z|G=c8!Q4)cp?y7w(?-;zea<~(HlkgkbmAD{aFh9o)EPCI7N<2)1m5WYo@SxM=)B8v z5X+{Z^p^~!%0y^4tAuIYL>sjpCXA%V$Q#BfRH%7~grj`rb@{E%Cbn4?ETr>xW9$(3 z5)P0LD?_`Tj>|K6DAk^HHku%ns(X8a;Pmo`a`{8K{GnX_P_kxpuk_0wN+*x5)qVLx zx%{C_p8N06D0kkn%W0HrfJ2-{dB9MR=0AEk1dVdnjiO*V2FeC$e^xD zrcn}P&A}m*krE{0-ls}pO2-~Q&r?kOw$W$G&|E2OKVp3XqL@M5K%R`iGtyH!1UkNG z2?4YpG0N`AXnu~yvw%_^NXJw*BT;OKq6eiO(yhb~*5MRoiCYRi0g190Q!ZwaLWKPr zYFMl=F|H61c-06=UyK%bX|^vRA073YmZm96BC(19AFK}g6uDfT&@Qm_A<^2XVDwQc&O0IoMu z+iuiF=Olsf_bV@E8 z1~_ix1`)*#E0Gds2#p{Kp(wJZix|`_yI2zFnp-YE6r$Li)zN4szmmz1>Nmmtl!#$) zoPRvY&u$O-*3F-q+)5Zoqx(QOW8{x6WBq zqeFoiH2?=qF02KiB&pjeHkweANz$Ev%#j*Hud0YMA5p32a>bStBT=5D0i8KCho3|8 zUZKIy#3(NmCdRHYHOHpY!|0S&M>c{Cc##NgJk_ccTftV-RVIvB6=Ef%NpAO=2S_+1 zosz3Z(~M=aM2_5*rrXgexjA_N?UA9?Im0qI$1pl2aF;w^;0$gTI_1Qpps%I*_T?*= zs`KrO9(K50u){eIJKTQzZDd`#a;Y+pf6WX@0Ciyep_+l?G7hx1ME>r)>(^ zkL%jb{jM!7EduO1M~4#D5xd~hO?OTSamqvqrS4TTJB=ml&Lg+rPW%}P)m z=$9lw8$?An+Q6Rz8&ue?!8FVL<({evbK^x^;vY|Sa=5{6!<6kdOq1I%FS`1ovY-(e z;@~gG#JUyZZ^0zG3M05710CGem`5w`BhFLG7Ry;r8~osO1qRj3vfFOp@hWYO{9&5@ znPuox+jch+^a~Sq>zxOK@+QvJbL7Dc^V&2l=9d;QE5{#A4;Jcc3DA3uQvSLAUF`dNC7CBL*H1G-|JHn^ekZb(i9 z5B!|A06ZZwUDOk%GFsd4n|@R!&r!t!zzToR2JmB`7U#5`H9^{D(|*||OIW~rRP8d= zxNHHKkY7ns?Ji;E#L4omP0^j$!bj~K%q#6KkZ2Rp8jX1kSYTCbmn4QBQ$H`I5?77+ zCHh3xdwGhbue)dRt2v#psO@@Q)vU9IcE*>_`K^Y{eOgb*bN}@K;s8fnq%7@=#F5S` zeVHdmz1)lwl>)T7P?Db!`?u3knXemnQgW93Wu~lfhs=w#_x-%2t}YSE%+*u%9lvj@tpr8n&zyM?z*@OTH-)M`=q(l)9m|aUR<5Y zpsO_bof>y*$2O<*v)1>63T8xA3>9%`$Q9#?s)7IE$PR|7vx7;2uOkPHywCQrOeHp@ zAR=A3i3E!G*mWo6EwLhC#P4IIC8RH3ihV0e-}>Z5Xu(1 zo+&HT-{RkX`#$1|X?x?X+NJCQP5L09iMG0L=J{jg``h;gvaYjJ%|ivTo%B~$*b;OH zCX-vSJj@guC+{@v5X+fGVPkucg(J%A1hS1dm00#VQhJ{;T z!jcGgUGdA<$0_!fT}w7(gQEblu{V#Z16^8K63vX_Ju0sKjD>FE^I**%1G2hNbhD*# zs{{`0r|}70$#^)ae^OQa=$?cEQ%)+ zY@YBT0M@jdzjS3K7oLqIJ6q=f&yS>z8eQX7S76Zvp+*760C{{!HLAN=k92Q=UZFg= zRBY^kh|^G(#UC4xK(uSl;4;Qh5tgI|M0R^dm*3?puCDv`a#5!@VwJ>)X45=Kf=(J; z<33V-JF{g2rB>7lip9QnH?h#z$$%z~kxCf0*;U#N&ng| zqFKQ3<&D7dM&N(pjetz-hUzS_&9`g0Mc_{@!z=>sDu>i5Fo@RM4+f>&A|OjU8R+HM z)uo^S48$M7A~4-$5eSo6JI`k=st`y+^L^u*ypcoS#e;jjLBOG1$v?@m%gq|p98Wy1+dc6@ zK;BK3F3r4C{OS(R$6Q%~L1~k}XVIK;QiX$!&8BPq`xrTKYj-o~{V1RVcf zj?Ut6fog)E?xB{yC*@AZxw7Qs#0ff)?tBFZWSiaGd3h-`i;E*#{LGp`z^a%*Kn#6Q z9o|7DXAls5BI`Xo#nKLE8)h_mU#eMt~JeX0=GknbKvQpCI8*Dy_pHFk>j;FZxP z{fG%##I;JhLvi#9YW%7z+ZkBlkzFv?zfdh$Qn->dLh!&wox(R_Ng{tAg%1!K4ayNxyvA`!#F2)r!id5kAORbibt$XS1#6KM3{hu0lVTu{K>+CL zy=D*y(|0*U%i~@VxR+#vLEz@&oPWEKK|pwy83Z1i#zP;GYHGKhz_FbS0;dk^hK%qb zIpQ;ez`c>(co9G%)u_|~zkUqRD+9%xML-w>z=4axBV^|Q7J(IphwuT{l^CggdZFz^ zoS+mPw%ka6LNDTjwE$oEz%yuZSpcvso|B7qgf5na=efF<%N|*h!aSh~ z#<0sRQrW>Vmc)+hiVOx>9@ufPloV-jrsg5R7lU-VBup3zU{%F+M$d$mwSJh9xl!0K z7x2oNCmgba6SU^hAjo6Qb;S^qHY`a>5w|cu00?&P=`aQX`6$b7$C$%BaHITkjzjh^ zp1Wu`7z9*xl(uxp^)OFq`Afq_k)c~HXznVApLq-d79>jzbBfSL)X7i=0q`pjDGUPR zTtrME$G&8C8G)?T- ztmco#(e75MxE#nSCQ*}sK>>l^!$fyz4CmaXni3v3r;9e_M^d2MHLjQiots77vC61! z11ZCThv+PlP$oNb2PoNz7-6Ux)@ib%Q^AXNoyQzNa`SMSSZ}e=KfMsTL}vKdJ`#?| zAOI=TTbV8LsWCE5=r2_Noy2a#AV3r~9l0!?0t+M+Z0~q6*p#;aDk|g2&Z6rG!|HPR zRtz>^&wqNglI0}~3<3{sdMJoYDOjH-&%BFSVRbFjZjLAvO*yzZZ7uxoZ^AaB06#Lt zDFP1i^sditN*Dj}TOYh~v%8paLmy;uW!~X*x7~jGnbLgo%<7>Pc7djg7&X{u5p9~4 zVLC?8IaFuig_bZT(p{h&N)P8Dmo&4v52FhuHzro4E7rCLr#c{#1E)HyK|m8^tP+Tl z>hOplCocRLSeMJtvPi{`hOa%X*>h^$>;Gz$N4 zf_+&LtcD%A9$U<31nv4U7h)yHa5)h^qEiPC^&@QPnaD*NxQ8`FFGgt2V_r7e9fDoM z!u^`1AxCnDzZVsEts5Xq@sXh+6EaTdY?cif?6w4|YgMdFQ;zDETny^vVvxqkL3O#u zx&i2E0_RobZO^>Ysby{ksqhUw*A?+xhg5*$97M;zTpqgKf1Mfwu}<^x$wubE6RZYB%!90uLE>{83Snc$R7IHg0Yq4yN!K=tJ!+H=bCpu(yt5PM zJMc~GqX?z_y{2mc#Lx-6Jjzk~&N_WWt?bIK=L6z!mdK_oJQb9aDC z8id0A$kn8H}r-cg!#pAs!_G1Q)(23nbLSEf-#0A_`iTx7v(uoo)Y!+0+@Y%SP z_>^|9#zi&DXNe=y{7TytvAM59QHBt~q}`8_H)NY1n*RCx@O8O4ZDEpZ_UBWBL^)(T z#BA@YF^!u=%`Jwuxs9j1Kw_69;!uQFJpk0vZKojt!BEP zhRX{rvRGbdIeO{eC#@yR3+?hk`#)l#?LSd7v(~hQgUIw-bJD+qjiJw_4DX<`253jC zqoG^*P?GNKTRex@KI*V;cFJ@+J6a!zBW2fRZvAzu5$6{*wgoa*B?Z@Trs5|hJ9Em8 z)$ExnjNC$$ZcHU*eOOe>~K46$7;FOEL))Xqv*R_EtaI%d03;`QelAL94}?bVn<6o+H@n1EMO(k5#D6W z2@}u`?PxAE;U!s}Yw$ z(KXt$6WY;+Er{Aq37Pn&WyMh_5spU0w;VctP}63Dz}AeuV~kC)oan@pr6A}w;7DfX z!bb;b%n}80H<|&-wDv<5u0*}NW7h>QJm*+Qn>N`QPhwIjbf&fTY&I9PtA<58Afk8~ zY@2K{I3}qbk75GF6ld0}d@3~ERB*i$pOPQAf5|69pQNYV7)@iIgW)46G1pCqj5&5v z1TA1ko6iV4FrJH#-4rqGPcz-GpB85QlS3xm0+{C3ay~okK)d$L-jF9lV!V$HYu8-2 z3F2&bkbZ~)+VW=7vse(?6`GqMvoOlKx1Ub@A^s(EgwsBhRf)Y4^wNo9c41@~!)NUP zJKE5Wqz`G5cCl&JP96Ap(G*E!p>wT6YCb=%W6*+s_{G6Zjcw2)mrpRK_oGdiC3dt0 zW~Uj1m6>FvOo#FIq(I+z6FN z0>>zWnahGf=cn2StDA$uutI*z?$Sd*>?Ai`5e-^Z6z6-~vU>|VeX*Tp++s7GC<1w` zBCJLjIV>JFExA7}7PyNgpxitdID)m{0ffMIb}t;nbj1Q!_yWRGcZ^1KYhGdW?8V{B rIJ}xsCMi--u>q<<0e6D5m-%CPwDj$O|32aW*YETGfBoL+{}28@>{8U< literal 0 HcmV?d00001 diff --git a/icons/delete_sequences.png b/icons/delete_sequences.png new file mode 100644 index 0000000000000000000000000000000000000000..54162921d9eeeb28ed1bcd48b7413c3505a7e85f GIT binary patch literal 1186 zcmV;T1YP@yP){Qdp?{{H>`{{H{}{{R2~Em5f600001bW%=J06^y0W&i*H z0b)x>L;#2d9Y_EG010qNS#tmY3ljhU3ljkVnw%H_000McNliru=nEYU5H@bWnTG%X z1NTWpK~#9!?VY=B+b|S{1++k5@FlwUB|3EJmZ6&;A&a-5@X|-nP@pLE1Vt7WsNf2G zxklE-x{#4CN8*X{{RkLKrl~LF(a&Qswvq%Pgb+dqA%qZGznoW*p*Aj{`>0r12rFuT zZG%+K#-qA(P9vAmiIu&CRj4}+<93(PiRC5)mVPWVg|!3g^g~Yp)`<3Z0Wr9<#UJKb9Ty zSWYZev2Wi#mydpadoB*g;<0_=_^A7m#S`RnrIX4PsYWW%h;yRSNhNiK$p~W!YdZrU1kofPja1QD@fbywiR>5{an_6g>AN7z1`IBwr0h!Y|_VSwyPQ)tkHH= zzu%g0Qq>$S>HMxR>cg9A*iTq>JuCZO&G@nEUK(S$>e$0xGFCQmJ)JzJ!K>RfRe8cP z4Zi7b*Y!ou#ZEgOEa$~ayjVsM%M99a{mAZNHG@_X^fB8(JT>=02x}B8L9FA?5X%kP zOQp>85-c;uvXU1t&2Ta^{SfxWR%Nt<*m8r7+pxA_tolr}wNG_1X2;4xSgmi_YJ_?i zb{fkGVI{pu=j8Ikn67b$laKYE**9l3L##H0tq034jMYX(CnK=+gASJKoO-r{WrPy? zsci1*Z0s;2mc2fAowK*1<;`HN0PABJou#K1zj}r16j@XJ&f3~pu=Z0`JK}!^A%svQ zjeRle6+QzEdu~~*_#&k|)8AS9o8S;ejkAiG_c^uy;BcXJ}Xw{A4TIk)EO1$ z`V864-*7P3_d(cD9G_KjrbXdr{rEE8SA6zV{5t-d9N)|W7FgFv{MI>k%~*HRSVA|) zR2f=Y??Wbyl?!1RjS-({y~mwaEYVR+bz&un%25==GAgOOiF0IkIO?6s&Jr6&@!MhO zq#AwmUBrw=cpUE&gb+dqA%qY@2qAb%707*qoM6N<$f(Ilp Ae*gdg literal 0 HcmV?d00001 diff --git a/icons/midi_audio.png b/icons/midi_audio.png index a24bd55605804b41675b1f14fe301e55a58a0023..f5f6da7e7dd3f6801c6e521c1f29488cb996ed40 100644 GIT binary patch delta 851 zcmV-Z1FZbaH{J%27=Hl+0000(vm7=6000J1OjJchH&*}u{|75D>Hq)$0d!JMQvg8b z*k%9#00Cl4M??UK1szBL000SaNLh0L01FcU01FcV0GgZ_00007bV*G`2j~kO4G}1f z)RmTB0w{G3Kb?f$&N#|{bZej133fKd9xpQL!rjsAXF}B^VbxiE{wu-5& zODJa2wxSq2?$;W|Pe%c~vDVFp_rh>0u+--(3@a+@M1^`RJZX`n^KM&vK15+>TPd?EqN zzK)^eM(-gXjK#i#75J4HF(Ave-!@_9BQRn>z8kY28mu1r=dj-&UbWA$h-rTwC3{E- zGjT_1$KWJy!zLPY0Q>7c69ePMzZ(Ms^2faz`uAaRT7Syzv5u(}g=N|OH(=t&*!U(a zNob{;KcI<~haR^lR@cL)~C{{rTKm>?-^4^NaPWT>igY#ainNX%@Q@v%}NDNsq(X0`)W z`|oK|Pk-2-1WCs@Tfn5xENN_SweZ!cBbMooJ%V_}XzUo#tE22LVBN9hk~-!+9CYcw zUR!}ZuStdAD$jR%EocPep$R-<#)B9;GI4o)>Rd(h!D44}XC6rwmR4<`BnOu)#Y|`r z2U#OeF&(-mK*1nVOocI(AXXB=Y>=I3OooO--G3y*guh}?&&$eK?w<;ntY#SVa*1+S z5Q5JK(I`(dC z{!G=i(6JBRYV;2e%HrwFu=g^h)A^>*(r5jDej_k>u<^ADJp#v%mlV002ovPDHLkV1jw}ewqLP literal 7116 zcmcIpi8s{W`=1%c*k#`-QT8=U+4_W#vd@^Y&QwMrjVwc1W*8+ROUM!`S%=2Qnz2ll zREVsJVNkL)Myi>b@SA?iIp2Ta_ndRjJ@+|xdG7Pv``q{I^}g%sY%jzw#}5Digd81g z-2nig_TR$G!_maXXGU=vgsV0-u8uY~hcP#JF`4d7ExYUjXi-GiuNn1y|#t*OeY;x=AVo|M&N0R2)>)4Lr&u2Lvq!XDs95 zx||r=+YyPGz-LzsDaQ|u>QhOnGk2>@bRO+mg+G9*rm#w4hRX%StZ(>5|H_D%MOlyD zlY6nH5dHwJj%}PDAYQEQ&+QMln^Ipa!y=YC2N3mB2zKe8l%*%Fo&8Zo1{IAsyVOI6 zpJr+Xld{U8M+a>4e|jkGqXzYIoQ`=(-c#89fa8C&lYH=UJ8Wd!cKiZ=6|PizZeIp? zhJ3mXHMcm}N<9SxjZ*Tacdnnnue*%PE9xY0+qUAzs8qX0ZwjcMsO)8fSv}v_1}%bUdLWPD80&LawQeqsGmxX_vVC! z=c1FnEvM$lFB@_qr{cpnAR+(&0r9^D2q-F%;V2=Iju-48YXS;~WX-;_;P(Ll**A{1 z|9HlH{7Q{|=nH-HYG?a7dBQR1h&;-OU;Pa4zr6kZq~uK7(**m=hqA&?^W}sidE$|` z?G*!k(VnM8Po#1Gkaaz6azv1eXUsnO&;vDIhr?L}wb0p9)b+(b;m$S_e2!462b4J- zZ#_EUBDU56Wlp}>w*0aXF$xs@|B-A%(%GKJm_QPoWG!t4dO;*xLxLby)gTr!( z;~!ICjSV6et^Pcv+%S*;J_Y^LE6?AVOUu7a4j2mA0^jpQPDyw_=W@b{(!)ARrx+MH zp)KK15e4oo-cas)wKqJRF5O>IqB+x!(UgpCIX){XlY+^Q$@+Np$y3#?WlO=XYxlM8 zi{sDK(T^Ad%@oX3e+rwNbG<$|V9iXkt{Px=K-xj=!^5Sn>!zk89%USV-N!Rda^jv4 zkUdw~lkgc|?0xHiXeJwfz!Nt1XoP=jRqR?@TYqSvNUZ_4(Q51@xD^a$!6m0vr1TBPm~UmqVD^c76a;KGxsdlP@P zLUrQ2Gqusdc&aSKcZ=S0gSqrq%~z|7JuqP8N)ef5++p{g(k0<4V3E3TN(*6$zdrc> z2fKL$Ud?@;RK)v7*sPqJOa|+ZQmeE`wrSEZao*AOuf9*HN&>#ybPox*$^c*862+pI ziy3HCgt-uc1?M~L_tgrt^M82UKcLUk-Y})xuSKemjEAsDY4{7cq4{_M$Vs)VzHS9S zTBo|8ugIzaY02A63{ATGYc~D3Go?$=KzypVTB09s?ae&#>29P^1U7)rLdfEEu{g^y zDu2+>cPgMkGsmfPuKrq6I_!u!$rj9S;H_-lL-0U=5aAHR#O-bfrC z_V)o6D=C|R4qD8EYHxNs~4=ikz>;NI4%I^02OJa;MC`9#w! zKk=}-xM?N8_ zs6_57)ZJ8X_=ZWXq~>@IeoEOjyc0GHaqJ+AXbXTJI{1UMlpZLat|DWCKq8KF_u|I+b{6|&NvgNr2v_7$_-RlY0(pdy zLw;`9WCfiy-5AZFu;c=)8rQsp=JytC=T1!yOG%*zizlB1r@w4AM(MOG#zaW`c`bAK znp!9-R$O5&!lhK9I{JQr%9Uacvjb04yvZ$R1? z>|CY8dK;FaPfwd)E(UI^V`L-6VbAI3o4fen-=Dj!T1KT|T=zrzZqOXFC{=u1v$+kj5w4RegTd2tI2L3e7%~IyCBXj#TqRBpt3; zaKdSp7V~k~-xGPaqKOORmGt$yRHwpi5cBxLY8%dIQcc1!$m3DGdeg(rPI502KroQ&j(5WBuB#mO>f&_56B8*N4}_l?>K6P=BU5wA{_ z>po6ZC)xgW$`gv1=o@pt$bw!B(~WXVJhZkB{Pv>Z)q+*;Q1mD#nU2p&x1Sxr0Y%gn zR!>rDHpJ3YL*doDZ!h50R5(kEa!mO<)i-A>-!q4O!IFnab7crxqFtc_uQ&NPl#?`T z9?pW0(bHes*>td88pi^^|FNLjsd^gsg3Q!od8h5z&=am@u!GUjslgfDbMBCQG8RzG zHG!|c^5>f~&z9gl4aY7k_?^~npB7TEfYwp%FU??#ItGBc%RVd5bI&tg+6@UE?_E#D z8+1EvFCHA7bva`WIIj9(vVpUxF+CxA`PxD-+Dn7F!?-h;2GIKUzpCu(^)V!Zn~Av*!gXVuTrh;5xPbp%K zuZMr*ZZ(AsQoWi_Bj&3(kx1nnPuu_TX7-==cGx%RQ6uFqZqlsh_qZq`;+-?qNd?raFC1HA z|I8sN<}XyQg`0nGa;->B!2mz8?WSluA4_1C9`+sJgqxYhj=jp-0!x8r0Bvm!P4NaK zPpXt-Xsp}2k3viIYm_Wj?p2S*<)8gQLz=OR&fY7TSD@?#hR7HHh`P~XVlQ(obFr$T z**FWvhYMAn}iCE3FUkdkX{#jwS*Nrc4_q~qq*qh!JgUY2sA7fv+3BDose zJZMOMq<<(H#TjJzBMfC%z*Xj+=dirr&*`72?u#4@RLso-Y>V$50UmH(LW>WxTUzf1%4wAT(U2(OVPdT z%-*S4IOw&o9lG*Tdd>m4-P>+3P2 zx(c;Uy%y`?1@X-!kkhxJ;nF8R&27F?7QKEIDWkV;=a1A;WF`0M*^^eYtBV)?T#=4+ zDkq$G+Sv~^LI3vLYC1dMpDi#FY9nGU-ji%&*g0DNgT>(2uDLU0)Y&RvMtlpSeirn$ zcbUFwAn8j^6HZ^Wt8ef?I=amZTa+#RI6lcJdiv%kBdTr_>}=9hu)$LN?qe4t_YYp} zR}gyWwFys92f3%`Ei+D-Wik+;SQA$7Z^XW!*0V_f0#!FL;en+m6qj zmr2KOO2XZn!wdh&i@4knZl8$P;KKClBmH**Y^c3+;tl{i2yN4m@D4#$-yxNaQ z$5%5P?7Llmq*)38abp9fVSIIZMYGTHx+&D-T=s!6qsE>;<1Q|NoQ#Ygln)PYq5sW@ zRyvz;barIRq*g)c{lx?{`U~_|(YE`qi-$MARcZP}qy(chnrrpyRPtxRmy3njHgx}q zPA_z{B15*fDI)MjL*XOe5rO$VUgUw`_N;Wz=JK7hCE?#KDUB3++|t|fB5lYjs7KY} zcNW`+MYnpO|7iD>tjVq!W8eFF9+^FeR|Xch#_X-}bYDUT!4OtQ`LJSbZx{|3dciGb!YEUiO zsg4I$OtyZwR&IG}=}XZkdch#fsnl|dZh!2<;#2YXQgf#GS6c$v|4zQM!ae~)elDTPii?k4fvyu(6QNN)T zB0{TVSqA;k)PGt!+Bk6X-F;xC7%X>|d+j{t8$x*ALB=1GQaJLwFK97?LFgyg7SAsZ zuNpsHfahw4B)SSgS^=&1t+xTM2FEtsk@bO43n%}gZ(DQyW53Z`H=i+77_mlV;y-2a zDyWg}H;vtNrOmC(Mt|ncYOjnQP;U?!FB5Z*(ck-M=hKAO;ApG_D)6xcfMG}GB0IUd1mi}A-?>Er zvDRP~Z0ltOMjnGJ{9zJOR%9^VmdiN&U6AIA3_^awWsR52Uw^I_Ex`WW*Yo;uAlj0i z5E^)Z3E#A$;9WHpq4J^xGKHZj(3<#6*&vLsQD55G=oiZo7)6RL?J40Zyy{Sc z*KGM{MOoW^b7;Cz^fha7GPx#BiS>xQ?b}QGSV+2f;Vo$cm{~p8Tm3?a7&Df^QwfA{ z<;HBp_0q!wpEn!oSbLY9;#Vw zsc0&bAn?Gh^r7?k?dbtRdY?uHaJ8L%jlN$hC0xsFXImhh=f6C7XWAsN`|tCNP?XSd zUwD~-Y$$Tz$+jgt?<}O9Df}HNtPLUJm3h`(m ze!Aw3up~%c*RAPWwFd+M>ETTAmGjkhLbZ?S2`U(u`6|( zMO^pawoTFlA;Pp~Z$w<%WTP%!Oyu|R`MvYlvaFeJ7$zS*;TVIP;n92P%#TxrnBt}H{JiNM>7X`lMR%)_eJ2B(IJMn zJ>kl3i!jsL#l+rhV zAE|q8NFd|HQe)ni*oc=Of_VEt3&w}JP1T0;8M?hO{f>k?Pw$-AG4uT=W`6bClSwXc zzJx||1D4sv*QXR6JiarYIK0(dIyz>*uh(XAsBZ(WG6nSO%XyMA-A94o0|c+UUu&;z zfc|ho-hAJC4zFe>RL}YPxJnZzq))gP22!+*%uSy6NlCzUB1>v{r}-PS@QS!u%?zR6 z#whQV^t4WKe&RAA?-i-uyt&l4Nt|y&Qpfc!F%XEF{GE;d8TuYu`C6Jf14zfSacXKO zA9{%@4aGcLYs#2D(yklM48bLGIaf4d#dfPrTP=DnqlbI~tJ37)&h1@)%uudNkJN(W ziO0TP#@0)C58)HB%ygU)?h;=g&)bSS`a``n=+^eB=!Vxi+V_L9KNAF=2l8q|eCz9k zcxF~8JM?`aS{)&Mx6eDSEkM=cRxC_(dp}Q&lnTKFAf!-GI1~zV0StDq+wcg3cJKN+ zmLbab<1h42*Sf?iJ3;LrMFHsA>HHtuGXc0ldIAqdf#E_Jyx76t;nYPal-C6$Tt(~U zeI`%(ozd{XbSV@7Zs{??X+e!z(2B4{JIW$3cVuhcFa`RaOi*dz@lr;$e3qwnY4K1{ zglAuFiz_9(OAaL1!S#*~dqjC43Uob?cL@w{K3~N|3{!((pJ)XNy#TjqYWhdHN*aFd z;cpfUSz1V~zwU@v`xXt1HpQ4S_GFNA;!@7B$5D^(ylHeQ92X)Oc_RH3zchjx^sPD{KC@LG}NQI$h%SoDc$QR@@P7-kNlrwy}`n=Z8_jYzI z(a1kdog_Ecu@P{ZcBnTrE0Dy)(prk`{Q`}*!$XOq-I7Zhd!?{#zE{>>pdK|2hm+KC z`w2x8rd5~w&0>Co5Ps5TrF(^5IVJ{%?IhI+qNWb2K2=6`#jgcJ-hfuI3% z1r^5gLKfWf7i2!`arQqJv-=8%Y~N-=w^v z90Fk5{=LJ2@SrtF-_ zDpUg5`T&Y*cdHyI^O%p<)t}%nbkEAYmvkTJeX?JWgAD>r+fKuccRyJ3vU15TP~_c7 zJ)#wO_^i=trM}X`P5L(HcvXnhjo>2i&Ut4IFX5H{@)zbAn3aFVi#F)&zpYbe?Odf5 zDWV*Px0(9#0#qz&mjm@2PKiUgqm2FWYB`w=ep5X@)f}XLIXkVq;WsB(>lR$vP=m>l zr<_5y!=W0Cpgdcu?=w!9%`cis9cw%Zf4gr=Q~We!nuc(lMR+|x1c+Zt5v zxEfyNW(5A(EW{IyIJ4=9bU}8XJK7WgydxFp-*PsSyax8?uuel!hhWco40X~2Kgf&B zk2@~}q3?IFC1{aIDN=T=BeazJ-H9fr$Z7u>(J$S6sAbX{!tt+?uqg=V9Vnr<70;$? z=9_N7`-kHDVy?OVXd(ZjYbAR`D@AsVFFTv5?S{`@~ z2mk=&!8Vq+0e};rW0#Q>XYAicSc?JXXa}_tqY?lR5D9N=iy?d|Oy9UY%Oed_M+?&<02 z?d_#dC{!x7zrUYGqYVrU3=R&`>GYwYq2b}-k&%(n(b2K7vGMWoiHV8H$w>x-F*P;C zWHP6xr)OqnW@l&T=H}+-=NA?hSS;4!;^NZM((>~1%F4>>>gw9s8k@~tUtizY*x+zD zTrPKWb8~BJYkPZ}$K!qZ@`cam@9gaC?(XjG?d|XH9~>MA1cJlEL!nT3baW&ViN@=A zV#xs~P`B+NfNM+R-pAl>Yi$XLKdEjbhOeI3AOZjYeXV0Z5nN>cOq`TPfp1w!ugI#) zD_?fLR(BR1p=7tLc3V6Qt#?UL8G`ECO zh~qXYNL#2&<`UA3&r+_ziwv$IeSzfNBhfv(v;l3F_ANIVaByBgodx~xzLCqt;2{kI zInDoV1(l&N6J_#7>9$)Uw8J@zr{GuUHR6&+CS;$1A#SFiDjpLGLK)p0kxAS{lJLta1NPb8A$YY`JUf9jWSiI8OFp%iVx|B!19J zjR`ajTP-vcFb8ZuOsK&s?^UMXjqo#H@S6V+YPqNrKx zFa6v)gFQ-?3k?^BBkpEqUy4VFwMd_}BrqCz>ff;t(xBgF}qm zBvYF5;z+Gj^A$d8cZR138d3NhO0HW69k}~;TyCm&eCWZnYv^J^Xim^_G^fOJB$NS> zHe6xQUrh|T`H8@HDDdh8mwj8GAiU)=qV4<-B@B759JU;yVQtf6-dP)}2^wryraBoY zjnnOZwyj<2L!f=iO==$!1XrFI9qLG0cYNRNg8|KKXSCvU8zFJ`o<7N2L}JVmAJdPF z->BMNy_jJZz?owU6ct^oKSQt|{k^dcW_KaI{ng&z2Z>87YC>Xj?SHBLYeQyCbZ%Qd zvhz}!Dmw{6U?Dq^;c5+;-xBAE&ngc%tYm=R&QCvaIFF3H??u)&JpdCMf7H3q;~Xd3 z^=ssQ3Zt3drPlAzT!s}@E7Ee_pNClQm@p{u#{2}XJZ-U^GIpMx)v1i^bWcboT>sZv zN|Ca{r4$H0Wq{6Ee)%_0`F8{aC5UVTk-`Ozl$&xB&`bwam{(TwiFBc*kM8$VPm<6vieLw7gN8 zX0hYrNGVxqbX8E6K2Ay&L=-=e{~LUg_!6KSzt7|)^|RbDt-t&QMASFM&HpN?^ z@9Pj%5x=d$L?V!eq~CAC^I99fI`fAkczYr=s!CHdYpsU&7y58_4M=_Qaku;BMz>CH zL<==FY;pV`1FfcRWv~V%*1dPlCO=JJ!^{_$l~OLj-S;m9Id#-etC=Th5Lt5@W;2%v zjpo=<&u7HK-TFr_1S`#+rud~$H%yN97k1#wqQV`&?H<%iTOb41RM+!xTKvXVn4*lU%GVq<33m$_v#Ibc+rCQDy^)Baa>u6nTr2o# z0>NdwnLZfDmp^a|bC!+C(HO2Vx%i1anX@*{+~?A+?9VheWiM5&)65Ox|HM{42)?oE X!uM$RU5*j|=>V{my=CQfWc0rQpp5fW literal 4038 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Π`g~! z{1hEYgtVJo81Z~pkq5Pmaz-f5gDSAd=8*&2^nwEQ$v4IsxfEyz4LrQO6cQ?fW?ZLRnF9=+Wt~Q1%JrH)4=%-tn~BW5)P1;1 z=t@?Yw`Yc!7Ot$UR0hr30|3zuU}3+(_&qQ^fag;$1XHrJubI2*FkBXcMS6I8%3rb7 zjSv2Q66H{K@!WE@cONdC8;s)Dj7s%85LP}|c#jWP5veH-uw4W(c#ov$MPV>WwWhR&Q-@$If^&pFKMxZYDe!uaViR=*gDD zX04?#aqHmlaP|6-)j)heK!D5s#;glK#%o_hi^@$;{V#ufPILxle^Di^UI3Vbs`2C* MdS6?$5N!GEAFBC@-T(jq diff --git a/icons/midi_logo.png b/icons/midi_logo.png index 3fb9f327bf760bed7507d083ef02b11b34add76e..dc0b893850d3909da1bfe0b646b7e2cc9c76a206 100644 GIT binary patch delta 247 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 literal 0 HcmV?d00001 diff --git a/icons/stopped.png b/icons/stopped.png index 48b10475b11b7aad402fb3b943ad997d9febd461..fe62ede4f33f45b4243d98afd9d1dc74740b724c 100644 GIT binary patch delta 97 zcmbQBwT^LugeoI50|P@2gNz7}Vh->LasB`Q|MDZ!CxBeWByV>Yh7ML)kICI4a$GOC xWqCzpE{abLoxE8@Ly^bR#W95AdUC=7KA>6;2FCxhl@)<322WQ%mvv4FO#tp|8aMy| literal 5392 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 Icuq$Y@3cw^#1Xv|FG&EYZTGV=|Xtrv#sI{wT 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 M>8jQ#-@W(00I(}EAOHXW diff --git a/icons/zynpad_mode_loopall.png b/icons/zynpad_mode_loopall.png index 0ef68eb0a4f93798fc33671aa8cc28dcd1ac9052..ffa4a0e99b7c50f93a0bdf9723c90fa57d64c55c 100644 GIT binary patch delta 891 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 literal 14271 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

M6i7 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 diff --git a/icons/zynpad_mode_oneshotall.png b/icons/zynpad_mode_oneshotall.png index a74e9f08e72f7f11a22eb21be8cae6d07293fd13..e5e8879658abd6ec484aca7532f9ea4c5766b58f 100644 GIT binary patch delta 520 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 W#(cf>I+w?`B}V$@s1n^%5&sJzygEJr diff --git a/icons/zynpad_mode_oneshotsync.png b/icons/zynpad_mode_oneshotsync.png index 5ea5515ab9c609c64cedec3f7ad647b0cc87a9a4..911a8df8f03a921a922a7121ae8d311160cd523b 100644 GIT binary patch delta 475 zcmV<10VMwBRDuMM7&QR{00000f+nQ^000J1OjJdd99IAT|0R$>egFUf0d!JMQvg8b z*k%9#00Cl4M??UK1szC}*&`l*=nEYU7!?G5V*LOB0d+}4K~z}7?U+rDgD?z*saUXN z4lq)V(xZ{1Ygb9|y8q6;r?M8)x*+QTP*w0>+LDjtk- z`*W1H{nHmmh-5|NtE*I;oqiWVM7anLV)c=zJ$y!EjjF7BA~7ykL+H7i54=ZEZyPi- znq)L^uFAHN_Xz01*(|6#K;vdC`I0m#v97+Mpn*{@i_%)76`hXIz!g|iPefw5T+O!* zO$}Wbop%H@SN?(Cj#00FN9T%0MwcBlc1*-_7WUDsM+rcK9<6~VM&lkD^=Lljt$?rL zjJyKX0?q(%vVildzXibE0&XnO2lU+q=vhD~ob~+`=-ORFGo$I%2?NY6U>fRYEHJsZ zdVn~?`BjwxXcxUA0(2Ij-T+MqD9uw;F`%{0%*-zEuQ(X`@#t(C=oQg7v>%6|V_v)B R#}WVl002ovPDHLkV1h)B&aD6d literal 10854 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 REMOVE")) self.list_data.append((self.remove_sequences, 0, "Remove Sequences", ["Clean all sequencer data while keeping existing chains.", - None])) + "delete_sequences.png"])) self.list_data.append((self.remove_chains, 0, "Remove Chains", ["Clean all chains while keeping sequencer data.", - None])) + "delete_chains.png"])) self.list_data.append((self.remove_all, 0, "Remove All", ["Clean all chains and sequencer data. Start from scratch!", - None])) + "delete_all.png"])) super().fill_list() def select_action(self, i, t='S'): diff --git a/zyngui/zynthian_gui_main_menu.py b/zyngui/zynthian_gui_main_menu.py index b8e2c461a..0a455a885 100644 --- a/zyngui/zynthian_gui_main_menu.py +++ b/zyngui/zynthian_gui_main_menu.py @@ -69,25 +69,25 @@ def fill_list(self): self.list_data.append((self.add_generator_chain, 0, "Add Audio Generator Chain", ["Create a new chain for audio generation. The chain doesn't receive any input and generates audio output. Internet radio, test signals, etc.", - None])) + "audio_generator.png"])) self.list_data.append((self.add_special_chain, 0, "Add Special Chain", ["Create a new chain for special processing. The chain receives audio & MIDI input and generates audio & MIDI output. use it for MOD-UI, puredata, etc.", - None])) + "special_chain.png"])) self.list_data.append((None, 0, "> REMOVE")) self.list_data.append((self.remove_sequences, 0, "Remove Sequences", ["Clean all sequencer data while keeping existing chains.", - None])) + "delete_sequences.png"])) self.list_data.append((self.remove_chains, 0, "Remove Chains", ["Clean all chains while keeping sequencer data.", - None])) + "delete_chains.png"])) self.list_data.append((self.remove_all, 0, "Remove All", ["Clean all chains and sequencer data. Start from scratch!", - None])) + "delete_all.png"])) # Add list of Apps self.list_data.append((None, 0, "> MAIN")) From 0ceda0b9a11dd884ec44f71e32eafc274a976f96 Mon Sep 17 00:00:00 2001 From: jofemodo Date: Fri, 29 Nov 2024 14:44:05 +0100 Subject: [PATCH 030/117] Optimize and improve icons image quality. --- icons/audio.png | Bin 729 -> 3364 bytes icons/audio_generator.png | Bin 3239 -> 3239 bytes icons/back.png | Bin 1072 -> 2083 bytes icons/delete.png | Bin 974 -> 15535 bytes icons/delete_all.png | Bin 1024 -> 15765 bytes icons/delete_chains.png | Bin 1105 -> 16217 bytes icons/delete_sequences.png | Bin 1186 -> 16656 bytes icons/midi_audio.png | Bin 862 -> 5324 bytes icons/midi_instrument.png | Bin 1957 -> 1957 bytes icons/midi_logo.png | Bin 290 -> 1810 bytes icons/special_chain.png | Bin 3438 -> 3438 bytes 11 files changed, 0 insertions(+), 0 deletions(-) diff --git a/icons/audio.png b/icons/audio.png index 420c1730542ed72dab27ab3536e488c0dc18212a..bea4d3767e5f9564b4ab9a93d4de8c8446b551d4 100644 GIT binary patch literal 3364 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 U`s4TqqqS<#|Bma delta 19 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


$lJ&b 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 delta 933 zcmV;W16usAdCmur7*hlT0001sQ z{r&y^{{8;`{{R2}|Ns9jQK;Sk0004WQchC9^kH7!5kZJ1+-_uW8FE*P7A%qY@2qATG_m|ke^|ZTWmtE(oF-O0BCzza+%c>bSZ@!@@0*$q;WDRp zb+Hz?D-Nu4u$B_J_4ofLZX~d}+oK@(U4zJBt-v}1D}aT|8)I!Ga;w*ze7c&W z^?p4J%zBtucFku0@p*Xk^V@y1eQ4JECmtR>eaYwxtzv3j9>o|Xqo|4HN@0D_#A-FM z!W6NVnpi$cUW%evab{S(Cf1hGBIUt4T_1<+96YuE^{k0c8voe*_WRyG+xvFcyW)Qa zA%svR9r~hw)}#zH@3})Wf8m>yjo1>f*oU&aDPwK47R+-?d9c8m8CI|9TX#%8qfgab ziC}?+%j5E;uvUrChEzQ*nFC#++)5n|sXE_FuQ$iq-S=FiPL=BDM#4%R52>=~kvEtH zR!9|$Q=LPuvJ6(Db6XV4zZPqi!75{?7YA|DSh*lBG*do_i1=ee1zO6!- zGS-zfmeJ;zlBuP-51BPqXN2VxGrm#X<4zUJG>WYzR?|pEN6`?=Nm3bZpkuqk!#l~r zG7F>mb{LveCY$gT+3x$|I9@jhA%qY@2qA- diff --git a/icons/delete_all.png b/icons/delete_all.png index 440ee8646b66e6be6b35cf85ed8bdb7ece796a24..8aca78972d593abd713bfdef7705bdea52c07f7f 100644 GIT binary patch literal 15765 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 l{lAZf|9{5F z{r&y^{{8;`{{R2}|Ns9jQK;Sk0004WQchC2=MjTkTV68>O*6aC+ClOe=YrP{3tLfY{u0go4HelU=rNP4GJ!9=e#8x45^2uYi z*2itxFxzrssf%6v_PKoY`^TZ!SH-qHQ9bHjGWh~|sAN)^B9%zxHR6m{f61isb%#lL z#`3fdGu|b#vHV26F%p|gg~0j}sp$UeK&$4M`+6M+>)4V>a)OW?ewh}v z|9bCtlh3jA8&=)#WlLVcN`r(}hoajuN35hDO9$ygeZ=ixRY#9ls>TX97KdV~E>==w zsbE;D533oGT`XO3>Z|_pe`$KG>Zge}6(Z&bV|7Em9E|PgR3Tzs4U4TaEd6$6n4!2H zg=IoxW#h1PFO~^`b?)+KAzz+vP*n&lGZIV3!O}fgy}nfh!Ma;}nOH(w%Ye0Xtj}e1 zmY!PtIVnu1$eQAB*4ECPwV&(S5&ts?A%r67(igLn!e*du&n=4?f8V4`ge?Jk-jt2k z%bc-xvKGus3wf}>S{PO#)3;ujd?uf&8Hr$lh0Ejep|CcVuGO~OfHem4K(S>ztZg}q zYu9_ns_%QQY%gU^!Ayi@J8n%p^*q5Mu(T~KUuJ#ZHB~e2&Xcj+(C+@-eaYxIA_W)`VD_B~CsWYx$U1IWCWHfrZPzvvdJ2KRp&nVJ082hGG50 z diff --git a/icons/delete_sequences.png b/icons/delete_sequences.png index 54162921d9eeeb28ed1bcd48b7413c3505a7e85f..d8b596be7c1a4e6c397b0eb137f802d261a9bce7 100644 GIT binary patch literal 16656 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 DemaKb delta 1148 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 NbTkZswQBZn{|5_-A9Mf! delta 820 zcmV-41Izr(Dc%N<7&QR{0000(vm7=6000J1OjJchH&*}u{|75D>Hq)$0d!JMQvg8b z*k%9#00Cl4M??UK1szC{FCu>tD2>#Wr2qf|(Md!>RA}DqnMj<|8m-K)xHZ z9~!J4`sc9UA6~W3v50?Ze;y@!NC`7>M{395ByYne8gl^q>pl|$TCRXAioOcKo%m04@=75+WDQpiuw z#xp$<#lZ3^V&d)C*oGmJa>qLMZfyS5wa~E--fHv@56a@{%&_+|rPKMQ(9&o9e|{q{ ycdUJQ8?nZiHqMX%OY@$81>CJ$w{G3KmFyq%O&^&mT(iIc0000X0%~bvJ~dOkiPjS;{#RzX diff --git a/icons/midi_logo.png b/icons/midi_logo.png index dc0b893850d3909da1bfe0b646b7e2cc9c76a206..7e9b0ba2365cd662edd4471fe731e65d78756354 100644 GIT binary patch literal 1810 zcmchXdsNZ~6vuyBijznwGf!nIZn2UyBj($zX<*K@3aEwKp2h%(eHO{He+VWm)F z^zeb8SZcmfsFqC;?J&egK4yupkeL!bW7M{@zxHqUp8L7ye9yV(e$V~mUJl-ehM2B1 z1polzkMTVK00xMUWn!#X&ZRJE`eqy*fc6D+AM^I(q8z>Q1qFkr0swgR$1(uex2*Ic z=(K;JA7~V84Y6BwjYEH@m2H##r3LbQGE#yQ1M(cx2+!yQ;iiR}mY_r^+hB zBL=7=E@z)}JfQlmpFb#jYcH=Y;r%Yp?@kJ7kaT~efFoO7$N2qOt?x$)7ixZZ?;MG} zjzzs(eYZqAO3F`(`;LVVgzI8zXi7idMGI(CjIwmjC;Uux6?eYjg~S6+zQl4X<#|A- zQhXZ@4jnzuWQ?%pC>8CY#hU9eZ)b)>Lt5XGs;f#w?%O8o#yT_DOIFZ<@0Am%@f>JE zS4ff;IsGonnBpLu^n13XZD@8@&@G{|PPN(gY4HQDeEhWobn5JnWX^kQoUFfyf{q`& zeQv8*PFBUCpQ(8B2xI}D-p?Ix>l&}~E|uJf*_$NURobuRjrTHUi@VDy8qTa!|cU$1{5yZFcZbFhc&X*v=nM`het`b)!AS}5^f9ZRHFhru;XD~-*ye$pBu4hmgx@YF>&Bl$~fbm+Azs}|$@w%3E5v=h5$Pi*TEu?Lc)b|Rge zYNKWndMliAACm&H1O_7&B0h=~h}+8ei#-D8^JWvt0}n5SxD#i^m}L|9gk2q+IJJeU zvN@vy)VXOu^`ba-rHmO9G75UfA$C;W3^;G&TULJz%%PgrjGpBud;bKEBYhG27?9{&#Cb&(ro z`(p!PL%~5Nh%P$It@qCW=}es{O_qm{@TCR&F^x>dM2nqpFdhHWGd!D-j`Un{l~G&> zsjfx2j!8SP4mDDDPk2jIrV%^?OMaPMZW)tmf^fAot<4}mv9tRh7n#Hlg1MbWmO=rO zu%5*#XWM|9wsA#ieLEtoC{1gQpk`dQ3L~{Khg9HV8*~Hyc88svkd~FUCjL-Ztw-TX zbDZ#S)OEpAaS>~d$J;~{hAy#$8#^gD;_(_gd1rXcTgcN+qypc zW{`^aw!|6)O=#mcP21`mkv5V?f;DDF3swuVzzyuU6BJL?n644`hntS}igV1RE4MNnZP1 zZJ0YqL5;v-C+Ry!GTmz*De%LsrwEUTa;LW%xl?O3aa7fnkRH!K*^>lFz$+-9Sx*=b)0gppqdD0T|6M-i$T6pyxn4x&+ z0+)(Ywx-k4Pap0-dvSi75))7h14E2d$@KfpEwy4VLod58TdpG+xn^@tO7dJ$W$#im z>6;~*!OEwvZ+fAjo$@_>c0sY)-Cti6K2Nc|z3D?#NeT#<{6Du(zR<{sf94;f*|#@c mn&#_P_N(?tG8@?VIgt!k*B|=KU%KWYi0|p@=d#Wzp$Pz?DPkZ1 diff --git a/icons/special_chain.png b/icons/special_chain.png index 62db20be280dd8fb257006e450a04f9edfa78755..9419026bfe4cc0335d1eae02e171d12d83a83965 100644 GIT binary patch delta 19 acmaDS^-gMo9V45Xib!hZuFWotT08(oiv}tH delta 19 acmaDS^-gMo9V45tURYz0&}J7#Egk?wPX(|5 From 476c75c12e4e659cc0bd68cce5b209e5f7be9569 Mon Sep 17 00:00:00 2001 From: jofemodo Date: Fri, 29 Nov 2024 19:27:10 +0100 Subject: [PATCH 031/117] Fix delete icons. --- icons/delete.png | Bin 15535 -> 1103 bytes icons/delete_all.png | Bin 15765 -> 1996 bytes icons/delete_chains.png | Bin 16217 -> 2469 bytes icons/delete_icons.xcf | Bin 184740 -> 96927 bytes icons/delete_sequences.png | Bin 16656 -> 2864 bytes 5 files changed, 0 insertions(+), 0 deletions(-) diff --git a/icons/delete.png b/icons/delete.png index 4d50e9ec01acccdad1278318dd8f6f213ee721ee..28b01de6fe68dddd3cda2e1b7cd0c11c4d1fa0dc 100644 GIT binary patch literal 1103 zcmeAS@N?(olHy`uVBq!ia0y~yVAKI&4rZW;-{LxVAY~cg6XMFiQ271(_xJDLU%!6+ z`0?Y+fq}W-)5S5Q;?~={iN&)F7#b3VSXf+d|NAe$``fjzQ`QQ-dgt+=(*Ag! z4LhT#XQ<~S6$A=$jP38_-93HRkqI$1x3`ANiiOSp?sfIphCQqAW;v#ZaZ7zqFa5JJ zv}5<}?QI`isxMpFHkaPG*=b;Nb)R47-QtM5>oy#kJo8@S-_6e=*GP%!L~IBBAb;HR?Xb8c;W|Lphg-E05< zZTkLe+u2!JZ=AwPYqy0MJ_+xM43c$P_VoMLQhDcXrut>8RpZ%GOP;?oeG+G9d|iNf zEpKG%V$o9?vu>Z>b-I%0=$3z?E;{VmRvf1-1=<5bnvD}R*^?3@7MdncHOw|$nz;5y z;n`@7b=R+M(0iJ=);zNpNPb^fqr<6naD46na8??3msUpc$4v;NEHX~*-bBc|IoKcDyM^VY0ecdSa|vlG5X zAKq2TaP;TH+TG^%95p{~pRf4r{i`lIV*2fe$4#yrk19X?ZEp_O>$*SRH=lVD$#nmH z^YY(~(T8T;38?kC%`DG5=TFV{`vTXFJTSfbz`}laKL6_q`+u{{gl&W7{^8e?J~V6J z0#Df=cXwEoDu9C+)1CAY32Tfv{Dk#mwkA?%{#G@`P;R)wN2)~ z&g5S{v+?Wm(qk9S=KgD7glN)_Idt7;8Bp>e6U*?UvkLT7Fi`*JGF zk$pPv)9l{RX}y+FUqhG4Mu@Hvy{+{;rbld7@z>CAvGJQK7T<_zTh-C`_*A~=;~QbB lSJy?TBJvY3)x7@BJtM!R(6=a(1(;hIJYD@<);T3K0RX{w0rda? literal 15535 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
$lJ&b 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 diff --git a/icons/delete_all.png b/icons/delete_all.png index 8aca78972d593abd713bfdef7705bdea52c07f7f..ecf012e51127d3cc6675743d6ad556a93ad4cb7b 100644 GIT binary patch literal 1996 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
?OXMk&@F8jB+KNr&LKM^K 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 Ms4{Q)voXZ}2i81XEdT%j delta 50163 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 WtgIW6VS5)?zIRXW^yxG4+W3E&Fl^cY diff --git a/icons/delete_sequences.png b/icons/delete_sequences.png index d8b596be7c1a4e6c397b0eb137f802d261a9bce7..633bb26e8b3e27a68404ed5c751990662b0e6dd7 100644 GIT binary patch literal 2864 zcmcImXHXLg5)L9Dp^1V5p*3 zklu;#q(wj=h8BVl#E^(!XaP>0o4L7rf8PDLncZ(^cXoGnc6MgJq~9z|1%MJj001Ch zcEjj*0DwdJhaNlnJu)6*a`4?8aWywJ0vslwhdRG|yn#1t5dZ++$sfuA$juk~9^?rz zyJgI?!Y6e?LK4n!$OHg@-DXDDZikcCX8n8}Jvh4755*1b?xe`uJ-L~X=qO)=N(5QC z2d$t6QjYT$tHos>KfM~;vYuq&N@A;@a=OInu6+%3H7C)Zk18J*V)S(6rPNVB!y|dg z*sqgkK>b4PpUKk*MsvVy)C1ySe^yWc6T#G>%_9mT80Oc~&zk*3mW)3t!iB@$BMGzE z6n>>Cv(Vb?Om8t{#_%Rhw?^{9?$z0?+iy@S9@Z99U$Bv?EOPjKy%(1h*4>ycJs34n z4}US(4s(Z%2b|+WG!`Jj&O1CL;bT?(lLau9q5bBdz#0|okfWUrpO1Gh16dn^^^8^} zeZcK820m=`I^0s^S!8j~?cG^6LQQ0tNVE~}4v&&YMvHKI6%~rJZnyCILH3Vjx{o6e z_ztg~R=M7E7lnDxR-mAGd7+&3NNfYavA!87p!C*E>(}Zmqf~>RjIT2dyCT&N$#X8(cKrX8h-8x&^u9gyL9D0Bh~2S!C-b&7YD82G3N)J1%i=9#occ7ntd$Exrv0!0idov9O$n!U z3V)eI?3DW4S$YOwXX9h`eLr&Cp{x~87Y;<_pgT9mDo6uw1m@}l4ilr6c~MrXlp=!9 z0dJSkj`4tsC4eHQO06}^>g*|j46H%AT4TIOTy4LFOh?g=$d0rn=#jW0pM#r2#^!=- z7CQQ<`_|O8Ew8kK*P99>!+S-z3wx4#VNjT_tx|U5j;j@nM}H*VL;95YeDCM@ryWC& z6%2x$Zo-zHd@tyh76i5Qud34GR!jiPfxM<7j(F#u&iQ^(sQ(8byDd zIxTkO{#QJ(qfl>6TJS-NIr6D7ZzEd(z+4zm=WcOo&15$BfXH%+ljA!rY;U;!z#eY(;}X_Hlq9N^~ltMb)UiKkwQE&w7f1* zK9Q+|S)ozu3gqQd?;MPslNOxnON}7HIC~X8YZ?-C*c_xTlvx(hMSS)ojCKB;=5F2@hY@k>{K%UU?K5RHtRyRh#hl*2Uah^h z;=dU>;R)({FJu_Yl*J*0};<6_{OPY z5p7r>G$&;~6keLQZQ+77X17~lD$C0PXqhPe%f6%{ToZPzdWbKpmP1r{52F#Qbfjmm;!^9KDN@shGe8OMIa9sG>474%bU0G6&(w_{* z$61fIegIo23?&Qd$1}NDzmhiO50TYE8z;V)`{!$db-LOCAxE;-Pg0y51})9~6Kp;9 zIcj9SaDz067S@*@u*ND@hn(OZmdeb9hfvjsc)xf&G*B5ptosMJg3i@Jkb`h(}Ssf0lL?&TH0QBA*oVj6?F57wAd66b(XfzWs@>D$<3SOUFuhd!j-`WZb}wqM4M&nIRr%)<^Ir zLZ6whj)}?^N@qRo?H_)QsjghPkkAv&*4|%$5B}?mjB#yD#6|^G*q3e=qR-K_s~*kCSjA>u9l;XHN>R*VlYxe@JIZqq`IJ9tD*ZHeuL^ew5k$Rw8e63sSAF)dnSsGbufrzg z(wfea$+um&Hp*lNDZF-A(P(brYRhLgEB_G;=nxo{$LEIxL!vK6SLtIPWg{ zHP~$)I($%-G1p?y=cy}s$qL@9<&ei>*Buby`m@gPaW!i0A9G;c{#=$0%%`u8o;1A* zaV|FS%ZG@`)=#J~Tf|7A~qf204OOm*cS)BYRNL;K5W0=&1uzJpB%!#r&en3 zWEH=`^-S~?gnxa;Vr*A7m7s*D#S>doOt4p!9$D-ro3zRKH(+IfOELWK>vzt9Jnlm3 zSuIbrhvwGbkyq8J$4eF?-s~Dr5M!@4tk9Psuix3ygr`?CpnjI}5D~>Ke)FGvA--tJ zZ}hg$sD5U-vB-#FX&Fplc0aI zb;+GrtSPJvMu2;7)~b@dWvt1o`0F&=;VYd$e>7Jh=DFrI7zZlU?R|iP23t++W6|-K z7Z`P!b!+Y}kL)l0nH6$MJL$r7nk*g|f?m3VVPi9ows1X;5`GYas2)<-LpkGt;b^VC zXF{Ls9?#v!Uw1+>8q>3YbsTJE;@LeQk}JHG{(8Cw{=y6znN EPh9=4*#H0l literal 16656 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 DemaKb From bfc2773203c9850720ebef2c027d78d447e1247a Mon Sep 17 00:00:00 2001 From: jofemodo Date: Fri, 29 Nov 2024 19:32:05 +0100 Subject: [PATCH 032/117] Fix again delete icons (sRGB is needed). --- icons/delete_all.png | Bin 1996 -> 2872 bytes icons/delete_chains.png | Bin 2469 -> 3505 bytes icons/delete_sequences.png | Bin 2864 -> 4070 bytes 3 files changed, 0 insertions(+), 0 deletions(-) diff --git a/icons/delete_all.png b/icons/delete_all.png index ecf012e51127d3cc6675743d6ad556a93ad4cb7b..3d50f5c5e9dce42c4162a9576bc27f2cf654736b 100644 GIT binary patch literal 2872 zcma)82{>C>8;-wfON(f!Dq_^oRz#_#mZ4Ru)Y2Ad7(ysIf>iB_c4()mrCO~umTA?P zHX=!>r4dGvG%}Gf8v9Z-jFys6k=p*7&i4P$GxN`X?sM-w_dVx1-~G<_e(!f~opE#A zDW@g}0)ciqow7d%0!hHcw~Q2kB&OVs1zu8trycDYO{gcJ3wmrY8O8pn>D6a&c~V-J{%U;wkQ!0IR~D9$)9=%qzJHL45%y z)OJ+27qaPh=u$r;2dF3RaU;}+pDJze*F?uYb0vP{PYE|ZCZcHh+Q8rZUAq>QcVv5Y zP0e?mfl-eb0~bvKF&GyA5b}cQxyo|~^Yiny%Q-onh1AhV>!jraVb6*aWL3P+lKLG} z9b)f>)b6EHsg#zM_p04@NQG&010y3YUlRKU7>BeUB4PBQAs_M5Ewu$3ONI{5WAct<5uy zjfuJM=IRUY^7IS^upI--J7H6qJ znat~&S-Q)aspy@3xUUr>_ zP4Mi2!NFTZqQOj6Kq)ph)|BXZNH^`l+c;`X#Gq0c$roOQ!C(M{HM7(c_AFe1NJeQA zoGT~4aj347#4IO5t?gO)mtdX*FG}E}hMVqX)=^nL=#;qrkL&Br`zNBW;&A>B9a!y2 z5`pXOe15dizTbH%cw_ojH$O71cPlry`T9-DD*Ipc>lZcm)eZ_0cROr_8DGPu?D#{SXfKP>44)tfU#a-(<9j>^+`y=)9*d-m zf>WEhb`M^c#;VHRZr>yFv-^}wim=~u>Ce0ZfKNFl3I5ALHAg)?JxXgU)G7Ck+g2@w z0s+4F8`0ya8VXRGWONpQ7qh2dX7Q`}pF8F5%S*H;(}yY?a{@y`bQ_wR*I!tHNF>rs zNJz-#Jm=u=gGL4haDaOwrB?CX$pw>5O-&>1Hx+JL(w!@ndV6~fQ}*zRii$=yH(R#3 zksF`TXtV_3XuM%EK_IQ5`Nqu5?BVddd~%kRl@%bPsS~OTBTe^)M@KbQSD%kYRuhSx z>Cy^t^(lei=|x$U^M2l=0RQ6$bNv1Np$UpBD=UG)!MvY?k{4(~DI2mfBcm4a814L1b? zsI2zdurkpum?S2go=^myZ`a&Uqu6fdo^AXlmiz8JM*|A2x?zWY#8`b!f|%mjg?RkF zStC@3DNO&rB=PsqiRJP4{@;Z0f5iAdg!NClY5ht)01!+1C)NF@!T)>WZ;bwH6Mw9- z{~;ih8`d`EoX}Kx@C`fvoYwdS)Z0_LVQ_S6Y71#sgksy!$Vjt57NE$+P|%qJy8{8S z)u0POZ7<%+Nn&;b^6V#~m}AG(MMiJ~gY0!xB3Uw1q67w1Zmpm>J*U}tF$B)|B zv@A0br2bx@_UOR?}+oOBLG>TM+g z%m&KJo;0&+nUc!UNCuP11Rvk`%It%2F*_uNI=TmuQK9+-D7RoRxVy(g*QISW93oBQ zQM)oO-Je{38Ud@n{;4*LW7v0X?*02>N%hRM?LR?<2_@)4a|^qUP+=x;BCxO}lHq+9 zOVj_Leekkgwne2|vf~&M!9N^^9y|O&p+m*e~ zJTUMD*3bwEBr)PV0!bSj8tT$eS1**23d3SKIwdEVhOd28(kF-V4AA3a$xG~ve&NaL za9PHSQEGg2Ss)$pQ~srDPXZS>DHO?|n$pwLk-ok$a33_y67UQDVySC>VIeb5IX_Qj z3PRxVf@i)}?tAC=RnvD4w!jX}&CTKRl=}jjcwZAv<#r^7-Y_lM#tF+r)n5w?zF`_e zAP{DOa`YpIle12xA4bl{S*-|#Q?Fl(6`%y0X6jSl&>#-yfjs94SBu)m!}BMJh)iHt x_4W0;joQ?pcs#x)Iv96M;t%!x=gv>9TM}-~$Mojp{0@j`J2|-7*VqN5{TrgmLyQ0b literal 1996 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 literal 2864 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 EPh9=4*#H0l From f37d7ebbb8ca4fe5a5c7545fa21ed9eeca823369 Mon Sep 17 00:00:00 2001 From: jofemodo Date: Fri, 29 Nov 2024 20:00:10 +0100 Subject: [PATCH 033/117] Fix audio & midi_audio icons. --- icons/audio.png | Bin 3364 -> 4398 bytes icons/midi_audio.png | Bin 5324 -> 6716 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/icons/audio.png b/icons/audio.png index bea4d3767e5f9564b4ab9a93d4de8c8446b551d4..87d8b30ed3168b6d8f63d35cd0ee2f228c9b49a8 100644 GIT binary patch literal 4398 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 DHc_K7 literal 5324 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 NbTkZswQBZn{|5_-A9Mf! From 7835560b6ab2869c949ac79b831204d4efe769dd Mon Sep 17 00:00:00 2001 From: riban Date: Fri, 29 Nov 2024 19:19:05 +0000 Subject: [PATCH 034/117] Fixes and enhancements to hotplug audio. Fixes creation of alsain/out for main jack audio interface. Allow disable of each hotplug input/output. --- zynautoconnect/zynthian_autoconnect.py | 73 +++++++++++++++++++---- zyngui/zynthian_gui_admin.py | 80 +++++++++++++++----------- zyngui/zynthian_gui_config.py | 4 +- 3 files changed, 112 insertions(+), 45 deletions(-) diff --git a/zynautoconnect/zynthian_autoconnect.py b/zynautoconnect/zynthian_autoconnect.py index 07c3b1037..8e3f98832 100755 --- a/zynautoconnect/zynthian_autoconnect.py +++ b/zynautoconnect/zynthian_autoconnect.py @@ -33,6 +33,7 @@ from threading import Thread, Lock from subprocess import check_output import pexpect +import psutil # Zynthian specific modules from zyncoder.zyncore import lib_zyncore @@ -55,7 +56,6 @@ # Fake port class # ------------------------------------------------------------------------------- - class fake_port: def __init__(self, name): self.name = name @@ -68,7 +68,6 @@ def set_alias(self, alias): def unset_alias(self, alias): pass - # ------------------------------------------------------------------------------- # Define some Constants and Global Variables # ------------------------------------------------------------------------------- @@ -117,11 +116,23 @@ def unset_alias(self, alias): # Map of user friendly names indexed by device uid (alias[0]) midi_port_names = {} +# Get the main jack audio device +jack_audio_device = "" +for proc in psutil.process_iter(['pid', 'name', 'cmdline']): + try: + if 'jackd' in proc.info['name']: + cmdline = proc.info['cmdline'] + for param in cmdline: + if param.startswith("hw:"): + jack_audio_device = param[3:] + break + except: + pass + # ------------------------------------------------------------------------------ # MIDI port helper functions - def get_port_friendly_name(uid): """Get port friendly name @@ -934,12 +945,14 @@ def update_hw_audio_ports(): global alsa_audio_srcs, alsa_audio_dests dirty = False - if zynthian_gui_config.hotplug_audio: + if zynthian_gui_config.hotplug_audio_enabled: # Add new devices - for device in get_alsa_audio_devices(False): - dirty |= start_alsa_in(device) - for device in get_alsa_audio_devices(True): - dirty |= start_alsa_out(device) + for device in get_alsa_hotplug_audio_devices(False): + if device not in zynthian_gui_config.disabled_audio_in: + dirty |= start_alsa_in(device) + for device in get_alsa_hotplug_audio_devices(True): + if device not in zynthian_gui_config.disabled_audio_out: + dirty |= start_alsa_out(device) # Remove disconnected devices for device in list(alsa_audio_srcs): @@ -981,12 +994,48 @@ def update_hw_audio_ports(): return dirty +def enable_hotplug(): + zynthian_gui_config.hotplug_audio_enabled = True + zynconf.save_config({"ZYNTHIAN_HOTPLUG_AUDIO": str(zynthian_gui_config.hotplug_audio_enabled)}) + update_hw_audio_ports() + audio_autoconnect() + +def disable_hotplug(): + zynthian_gui_config.hotplug_audio_enabled = False + zynconf.save_config({"ZYNTHIAN_HOTPLUG_AUDIO": str(zynthian_gui_config.hotplug_audio_enabled)}) + stop_all_alsa_in_out() + +def enable_audio_input_device(device, enable=True): + if enable: + if start_alsa_in(device): + if device in zynthian_gui_config.disabled_audio_in: + zynthian_gui_config.disabled_audio_in.remove(device) + else: + stop_alsa_in(device) + if device not in zynthian_gui_config.disabled_audio_in: + zynthian_gui_config.disabled_audio_in.append(device) + zynconf.save_config({"ZYNTHIAN_HOTPLUG_AUDIO_DISABLED_IN": ",".join(zynthian_gui_config.disabled_audio_in)}) + +def enable_audio_output_device(device, enable=True): + if enable: + if start_alsa_out(device): + if device in zynthian_gui_config.disabled_audio_out: + zynthian_gui_config.disabled_audio_out.remove(device) + else: + stop_alsa_out(device) + if device not in zynthian_gui_config.disabled_audio_out: + zynthian_gui_config.disabled_audio_out.append(device) + zynconf.save_config({"ZYNTHIAN_HOTPLUG_AUDIO_DISABLED_OUT": ",".join(zynthian_gui_config.disabled_audio_out)}) -def get_alsa_audio_devices(playback=True): +def get_alsa_hotplug_audio_devices(playback=True): devices = [] for card in alsaaudio.pcms(alsaaudio.PCM_PLAYBACK if playback else alsaaudio.PCM_CAPTURE): + if card == jack_audio_device: + continue if card.startswith("hw:"): - devices.append(card[8:card.find(",")]) + device = card[8:card.find(",")] + if device != jack_audio_device: + devices.append(device) return devices def start_alsa_in(device): @@ -1026,9 +1075,9 @@ def stop_alsa_out(device): return True def stop_all_alsa_in_out(): - for device in get_alsa_audio_devices(False): + for device in get_alsa_hotplug_audio_devices(False): stop_alsa_in(device) - for device in get_alsa_audio_devices(True): + for device in get_alsa_hotplug_audio_devices(True): stop_alsa_out(device) # Connect mixer to the ffmpeg recorder diff --git a/zyngui/zynthian_gui_admin.py b/zyngui/zynthian_gui_admin.py index 59ae1ccc4..0f83d48bf 100644 --- a/zyngui/zynthian_gui_admin.py +++ b/zyngui/zynthian_gui_admin.py @@ -176,10 +176,7 @@ def fill_list(self): self.list_data.append( (self.start_rbpi_headphones, 0, "\u2610 RBPi Headphones")) - if zynthian_gui_config.hotplug_audio: - self.list_data.append((self.stop_hotplug_audio, 0, "\u2612 Hotplug USB Audio")) - else: - self.list_data.append((self.start_hotplug_audio, 0, "\u2610 Hotplug USB Audio")) + self.list_data.append((self.hotplug_audio_menu, 0, "Hotplug USB Audio")) if zynthian_gui_config.snapshot_mixer_settings: self.list_data.append( @@ -394,36 +391,55 @@ def default_rbpi_headphones(self): else: self.stop_rbpi_headphones(False) - def start_hotplug_audio(self, save_config=True): - logging.info("STARTING HOTPLUG AUDIO") - zynthian_gui_config.hotplug_audio = 1 - # Update Config - if save_config: - zynconf.save_config({ - "ZYNTHIAN_HOTPLUG_AUDIO": str(zynthian_gui_config.hotplug_audio) - }) - # Call autoconnect after a little time - zynautoconnect.request_audio_connect() - self.update_list() - - def stop_hotplug_audio(self, save_config=True): - logging.info("STOPPING HOTPLUG AUDIO") - - zynthian_gui_config.hotplug_audio = 0 - zynautoconnect.stop_all_alsa_in_out() - # Update Config - if save_config: - zynconf.save_config({ - "ZYNTHIAN_HOTPLUG_AUDIO": str(int(zynthian_gui_config.hotplug_audio)) - }) - self.update_list() - # Start/Stop Hotplug Audio depending on configuration - def default_hotplug_audio(self): - if zynthian_gui_config.hotplug_audio: - self.start_hotplug_audio(False) + def get_hotplug_menu_options(self): + options = {} + if zynthian_gui_config.hotplug_audio_enabled: + options[f"\u2612 Hotplug Audio"] = "disable_hotplug" + options["Input Devices"] = None + for device in zynautoconnect.get_alsa_hotplug_audio_devices(False): + if device in zynthian_gui_config.disabled_audio_in: + options[f"\u2610 {device} in"] = "enable_input" + else: + options[f"\u2612 {device} in"] = "disable_input" + options["Output Devices"] = None + for device in zynautoconnect.get_alsa_hotplug_audio_devices(True): + if device in zynthian_gui_config.disabled_audio_out: + options[f"\u2610 {device} out"] = "enable_output" + else: + options[f"\u2612 {device} out"] = "disable_output" else: - self.stop_hotplug_audio(False) + options[f"\u2610 Hotplug Audio"] = "enable_hotplug" + return options + + def hotplug_audio_menu(self): + self.zyngui.screens['option'].config("Hotplug Audio", self.get_hotplug_menu_options(), self.hotplug_audio_cb, False) + self.zyngui.show_screen('option') + + def hotplug_audio_cb(self, option, value): + zynautoconnect.pause() + match(value): + case "enable_hotplug": + self.zyngui.state_manager.start_busy("hotplug", "Enabling hotplug audio") + zynautoconnect.enable_hotplug() + case "disable_hotplug": + self.zyngui.state_manager.start_busy("hotplug", "Disabling hotplug audio") + zynautoconnect.disable_hotplug() + case "enable_input": + self.zyngui.state_manager.start_busy("hotplug", f"Enabling {option[2:]}") + zynautoconnect.enable_audio_input_device(option[2:-3]) + case "disable_input": + self.zyngui.state_manager.start_busy("hotplug", f"Disabling {option[2:]}") + zynautoconnect.enable_audio_input_device(option[2:-3], False) + case "enable_output": + self.zyngui.state_manager.start_busy("hotplug", f"Enabling {option[2:]}") + zynautoconnect.enable_audio_output_device(option[2:-4]) + case "disable_output": + self.zyngui.state_manager.start_busy("hotplug", f"Disabling {option[2:]}") + zynautoconnect.enable_audio_output_device(option[2:-4], False) + self.zyngui.screens['option'].options = self.get_hotplug_menu_options() + self.zyngui.state_manager.end_busy("hotplug") + zynautoconnect.resume() def toggle_dpm(self): zynthian_gui_config.enable_dpm = not zynthian_gui_config.enable_dpm diff --git a/zyngui/zynthian_gui_config.py b/zyngui/zynthian_gui_config.py index 0b8f57159..0f501e036 100644 --- a/zyngui/zynthian_gui_config.py +++ b/zyngui/zynthian_gui_config.py @@ -571,7 +571,9 @@ def get_external_storage_dirs(exdpath): rbpi_headphones = int(os.environ.get('ZYNTHIAN_RBPI_HEADPHONES', 0)) enable_dpm = int(os.environ.get('ZYNTHIAN_DPM', True)) -hotplug_audio = int(os.environ.get('ZYNTHIAN_HOTPLUG_AUDIO', False)) +hotplug_audio_enabled = os.environ.get('ZYNTHIAN_HOTPLUG_AUDIO', False) == "True" +disabled_audio_in = os.environ.get('ZYNTHIAN_HOTPLUG_AUDIO_DISABLED_IN', "").split(',') +disabled_audio_out = os.environ.get('ZYNTHIAN_HOTPLUG_AUDIO_DISABLED_OUT', 'headphones,b1,b2').split(',') # ------------------------------------------------------------------------------ # Networking Options From 4df0b788d6c78108e101b4510b7af015e9ffc7d1 Mon Sep 17 00:00:00 2001 From: jofemodo Date: Fri, 29 Nov 2024 23:16:06 +0100 Subject: [PATCH 035/117] Implement touch swipe/scroll & up/down actions in help view. Bold-push to back. --- zyngui/multitouch.py | 21 ++++++-- zyngui/zynthian_gui.py | 2 +- zyngui/zynthian_gui_help.py | 85 +++++++++++++++++++++++++++++++-- zyngui/zynthian_gui_selector.py | 1 - 4 files changed, 99 insertions(+), 10 deletions(-) diff --git a/zyngui/multitouch.py b/zyngui/multitouch.py index ec365378d..bc4879999 100644 --- a/zyngui/multitouch.py +++ b/zyngui/multitouch.py @@ -26,6 +26,7 @@ # Based on code from https://github.com/pimoroni/python-multitouch import struct +import tkinter import logging from enum import Enum from glob import glob @@ -323,8 +324,21 @@ def _process_touch_events(self): event.time = now if event._type == MultitouchTypes.MULTI_PRESS: - event.widget = zynthian_gui_config.top.winfo_containing( - event.x_root, event.y_root) + # Find a widget for the touch event + try: + event.widget = zynthian_gui_config.top.winfo_containing(event.x_root, event.y_root) + except: + gui_obj = zynthian_gui_config.zyngui.get_current_screen_obj() + if isinstance(gui_obj, tkinter.Frame): + event.widget = gui_obj + #logging.debug("Using current screen object for touch event") + else: + try: + event.widget = gui_obj.main_frame + #logging.debug("Using main_frame for touch event") + except: + logging.error("Can't find a widget for touch event") + continue event.offset_x = event.widget.winfo_rootx() event.offset_y = event.widget.winfo_rooty() event.x = event.x_root - event.offset_x # Reassert because offset has changed @@ -472,8 +486,7 @@ def _on_touch_timeout(self, try_single_touch=True): event = self._g_pending self._g_pending = None try: - event.tag = event.widget.find_overlapping( - event.x, event.y, event.x, event.y)[0] + event.tag = event.widget.find_overlapping(event.x, event.y, event.x, event.y)[0] except: event.tag = None for ev_handler in self._on_press: diff --git a/zyngui/zynthian_gui.py b/zyngui/zynthian_gui.py index c0e3ae925..6fd396a88 100644 --- a/zyngui/zynthian_gui.py +++ b/zyngui/zynthian_gui.py @@ -447,7 +447,7 @@ def osc_cb_all(self, path, args, types, src): def create_screens(self): # Create Core UI Screens self.screens['info'] = zynthian_gui_info() - self.screens['help'] = zynthian_gui_help(self) + self.screens['help'] = zynthian_gui_help() self.screens['splash'] = zynthian_gui_splash() self.screens['loading'] = zynthian_gui_loading() self.screens['confirm'] = zynthian_gui_confirm() diff --git a/zyngui/zynthian_gui_help.py b/zyngui/zynthian_gui_help.py index 69ec9a675..eb666e308 100644 --- a/zyngui/zynthian_gui_help.py +++ b/zyngui/zynthian_gui_help.py @@ -38,13 +38,30 @@ class zynthian_gui_help: ui_dir = os.environ.get('ZYNTHIAN_UI_DIR', "/zynthian/zynthian-ui") - def __init__(self, zyngui): - self.zyngui = zyngui + # Scale for touch swipe action after-roll + touch_swipe_roll_scale = [1, 0, 1, 1, 2, 2, 2, 4, 4, 4, 4, 4] # 1, 0, 1, 0, 1, 0, 1, 0, + + def __init__(self): self.shown = False self.zyngui = zynthian_gui_config.zyngui + + self.touch_motion_step = int(1.8 * zynthian_gui_config.font_size) + self.touch_swipe_speed = 0 + # Set approx. here to avoid errors. Set accurately when list item selected + self.touch_motion_last_dy = 0 + self.touch_swiping = False + self.touch_push_ts = 0 + self.touch_last_release_ts = 0 + # Main Frame self.main_frame = HtmlFrame(zynthian_gui_config.top, messages_enabled=False) + # Patch HtmlFrame widget + self.main_frame.event_generate = self.main_frame.html.event_generate + # Bind events self.main_frame.on_done_loading(self.done_loading) + self.main_frame.bind("", self.cb_touch_push) + self.main_frame.bind("", self.cb_touch_release) + self.main_frame.bind("", self.cb_touch_motion) def done_loading(self): self.zyngui.show_screen("help") @@ -84,7 +101,67 @@ def refresh_loading(self): def switch_select(self, t='S'): pass - def cb_push(self, event): - self.zyngui.cuia_back() + def arrow_up(self): + self.main_frame.yview_scroll(-4, "units") + + def arrow_down(self): + self.main_frame.yview_scroll(4, "units") + + # -------------------------------------------------------------------------- + # Keyboard & Mouse/Touch Callbacks + # -------------------------------------------------------------------------- + + def cb_touch_push(self, event): + if self.zyngui.cb_touch(event): + return "break" + self.touch_push_ts = event.time # Timestamp of initial touch + # logging.debug("LISTBOX PUSH => %s" % (self.listbox_push_ts)) + self.touch_y0 = event.y # Touch y-coord of initial touch + self.touch_x0 = event.x # Touch x-coord of initial touch + # True if swipe action in progress (disables press action) + self.touch_swiping = False + self.touch_swipe_speed = 0 # Speed of swipe used for rolling after release + return "break" # Don't select entry on push + + def cb_touch_motion(self, event): + dy = self.touch_y0 - event.y + offset_y = int(dy / self.touch_motion_step) + if offset_y: + self.touch_swiping = True + self.main_frame.yview_scroll(offset_y, "units") + self.touch_swipe_dir = abs(dy) // dy + self.touch_y0 = event.y + self.touch_swipe_dir * (abs(dy) % self.touch_motion_step) + # Use time delta between last motion and release to determine speed of swipe + self.touch_push_ts = event.time + + def cb_touch_release(self, event): + if self.zyngui.cb_touch_release(event): + return "break" + dts = (event.time - self.touch_push_ts)/1000 + rdts = event.time - self.touch_last_release_ts + self.touch_last_release_ts = event.time + if self.touch_swiping: + self.touch_swipe_nudge(dts) + else: + if rdts < 30: + return # Debounce + if dts < zynthian_gui_config.zynswitch_bold_seconds: + pass + elif zynthian_gui_config.zynswitch_bold_seconds <= dts < zynthian_gui_config.zynswitch_long_seconds: + self.zyngui.cuia_back() + + def touch_swipe_nudge(self, dts): + self.touch_swipe_speed = int(len(self.touch_swipe_roll_scale) - ((dts - 0.02) / 0.06) * len(self.touch_swipe_roll_scale)) + self.touch_swipe_speed = min( + self.touch_swipe_speed, len(self.touch_swipe_roll_scale) - 1) + self.touch_swipe_speed = max(self.touch_swipe_speed, 0) + + def swipe_update(self): + if self.touch_swipe_speed > 0: + self.touch_swipe_speed -= 1 + self.main_frame.yview_scroll(self.touch_swipe_dir * self.touch_swipe_roll_scale[self.touch_swipe_speed], "units") + + def plot_zctrls(self): + self.swipe_update() # ------------------------------------------------------------------------------- diff --git a/zyngui/zynthian_gui_selector.py b/zyngui/zynthian_gui_selector.py index 5c7276359..8671af3fd 100644 --- a/zyngui/zynthian_gui_selector.py +++ b/zyngui/zynthian_gui_selector.py @@ -121,7 +121,6 @@ def __init__(self, selcap='Select', wide=False, loading_anim=True, info=True): # Bind listbox events self.listbox_push_ts = 0 - self.last_release = 0 self.listbox.bind("", self.cb_listbox_push) self.listbox.bind("", self.cb_listbox_release) self.listbox.bind("", self.cb_listbox_motion) From 657ce98341e25a7f4b0ca5201675054166921511 Mon Sep 17 00:00:00 2001 From: riban Date: Sat, 30 Nov 2024 07:23:26 +0000 Subject: [PATCH 036/117] Show audio input and output port names. --- zynautoconnect/zynthian_autoconnect.py | 31 +++++++++++++++++++++++++- zyngui/zynthian_gui_audio_in.py | 8 +++++-- zyngui/zynthian_gui_audio_out.py | 14 +++++++++--- 3 files changed, 47 insertions(+), 6 deletions(-) diff --git a/zynautoconnect/zynthian_autoconnect.py b/zynautoconnect/zynthian_autoconnect.py index 8e3f98832..8294d18e0 100755 --- a/zynautoconnect/zynthian_autoconnect.py +++ b/zynautoconnect/zynthian_autoconnect.py @@ -336,6 +336,19 @@ def reset_midi_in_dev_all(): # ------------------------------------------------------------------------------ # Audio port helpers +def update_system_audio_aliases(): + for dir in (False, True): + ports = jclient.get_ports("system:", is_audio=True, is_input=dir, is_output=not dir) + for i, port in enumerate(ports): + if port.aliases: + parts = port.aliases[0].split(":") + if len(parts) != 4 or parts[0] != "alsa_pcm" and parts[1] != "hw": + continue + alias = f"{parts[2]} {i + 1}" + for a in port.aliases: + port.unset_alias(a) + port.set_alias(alias) + def add_sidechain_ports(jackname): """Add ports that should be treated as sidechain inputs @@ -1046,6 +1059,14 @@ def start_alsa_in(device): if proc.exitstatus: return False alsa_audio_srcs[device] = proc + for i in range(10): + ports = jclient.get_ports(f"zynain_{device}") + if ports: + for i, port in enumerate(ports): + port.set_alias(f"{device} {i + 1}") + return True + sleep(0.1) + logging.warning(f"Failed to set {device} aliases") return True def stop_alsa_in(device): @@ -1064,6 +1085,14 @@ def start_alsa_out(device): if proc.exitstatus: return False alsa_audio_dests[device] = proc + for i in range(10): + ports = jclient.get_ports(f"zynaout_{device}") + if ports: + for i, port in enumerate(ports): + port.set_alias(f"{device} {i + 1}") + return True + sleep(0.1) + logging.warning(f"Failed to set {device} aliases") return True def stop_alsa_out(device): @@ -1329,7 +1358,7 @@ def init(): devices_out_name.append(None) update_midi_in_dev_mode_all() - + update_system_audio_aliases() def start(sm): """Initialise autoconnect and start MIDI port checker diff --git a/zyngui/zynthian_gui_audio_in.py b/zyngui/zynthian_gui_audio_in.py index b1b3d5bc6..1cb4b7627 100644 --- a/zyngui/zynthian_gui_audio_in.py +++ b/zyngui/zynthian_gui_audio_in.py @@ -62,12 +62,16 @@ def fill_list(self): self.list_data = [] for i, scp in enumerate(self.capture_ports): + if scp.aliases: + suffix = f" ({scp.aliases[0]})" + else: + suffix = "" if i + 1 in self.chain.audio_in: self.list_data.append( - (i + 1, scp.name, f"\u2612 Audio input {i + 1}")) + (i + 1, scp.name, f"\u2612 Audio input {i + 1}{suffix}")) else: self.list_data.append( - (i + 1, scp.name, f"\u2610 Audio input {i + 1}")) + (i + 1, scp.name, f"\u2610 Audio input {i + 1}{suffix}")) super().fill_list() diff --git a/zyngui/zynthian_gui_audio_out.py b/zyngui/zynthian_gui_audio_out.py index 8d6f006a2..33ec24d8f 100644 --- a/zyngui/zynthian_gui_audio_out.py +++ b/zyngui/zynthian_gui_audio_out.py @@ -109,10 +109,18 @@ def fill_list(self): self.list_data.append((None, None, "> Direct Outputs")) port_count = len(self.playback_ports) for i in range(0, port_count, 2): - port_names.append((f"Output {i + 1}", f"^{self.playback_ports[i].name}$")) + if self.playback_ports[i].aliases: + suffix = f" ({self.playback_ports[i].aliases[0]})" + else: + suffix = "" + port_names.append((f"Output {i + 1}{suffix}", f"^{self.playback_ports[i].name}$")) if i < port_count: - port_names.append((f"Output {i + 2}", f"^{self.playback_ports[i + 1].name}$")) - port_names.append((f"Outputs {i + 1}+{i + 2}", f"^{self.playback_ports[i].name}$|^{self.playback_ports[i + 1].name}$")) + if self.playback_ports[i + 1].aliases: + suffix = f" ({self.playback_ports[i + 1].aliases[0]})" + else: + suffix = "" + port_names.append((f"Output {i + 2}{suffix}", f"^{self.playback_ports[i + 1].name}$")) + port_names.append((f"Outputs {i + 1}+{i + 2} (stereo)", f"^{self.playback_ports[i].name}$|^{self.playback_ports[i + 1].name}$")) for title, processor in port_names: if processor in self.chain.audio_out: self.list_data.append((processor, processor, "\u2612 " + title)) From c0c0b364c9a4d160cab743b28ab44dd8772429bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pavel=20Vond=C5=99i=C4=8Dka?= Date: Sat, 30 Nov 2024 12:19:53 +0000 Subject: [PATCH 037/117] v5touch: adapt the help screen. --- zyngui/zynthian_gui_help.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/zyngui/zynthian_gui_help.py b/zyngui/zynthian_gui_help.py index 69ec9a675..20f7cf82c 100644 --- a/zyngui/zynthian_gui_help.py +++ b/zyngui/zynthian_gui_help.py @@ -43,7 +43,8 @@ def __init__(self, zyngui): self.shown = False self.zyngui = zynthian_gui_config.zyngui # Main Frame - self.main_frame = HtmlFrame(zynthian_gui_config.top, messages_enabled=False) + self.main_frame = HtmlFrame(zynthian_gui_config.top, width=zynthian_gui_config.screen_width, height=zynthian_gui_config.screen_height, messages_enabled=False) + self.main_frame.grid_propagate(False) self.main_frame.on_done_loading(self.done_loading) def done_loading(self): @@ -71,7 +72,8 @@ def show(self): logging.warning("TEST_MODE: {}".format(self.__class__.__module__)) if not self.shown: self.shown = True - self.main_frame.grid() + self.main_frame.grid_propagate(False) + self.main_frame.grid(row=0, column=self.zyngui.main_screen_column) def zynpot_cb(self, i, dval): if i == 3: From d7c1582f4a840b3ae969a59c99869c3007f6f446 Mon Sep 17 00:00:00 2001 From: jofemodo Date: Sun, 1 Dec 2024 10:26:32 +0100 Subject: [PATCH 038/117] Disable slgui in sooperlooper engine. --- zyngine/zynthian_engine_sooperlooper.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/zyngine/zynthian_engine_sooperlooper.py b/zyngine/zynthian_engine_sooperlooper.py index 5ddd4745c..8158db9a7 100644 --- a/zyngine/zynthian_engine_sooperlooper.py +++ b/zyngine/zynthian_engine_sooperlooper.py @@ -331,10 +331,10 @@ def __init__(self, state_manager=None): custom_slb_fpath = None # Build SL command line - if self.config_remote_display(): - self.command = ["slgui", "-l 0", f"-P {self.osc_target_port}", f"-J {self.jackname}"] - else: - self.command = ["sooperlooper", "-q", "-l 0", "-D no", f"-p {self.osc_target_port}", f"-j {self.jackname}"] + #if self.config_remote_display(): + # self.command = ["slgui", "-l 0", f"-P {self.osc_target_port}", f"-J {self.jackname}"] + #else: + self.command = ["sooperlooper", "-q", "-l 0", "-D no", f"-p {self.osc_target_port}", f"-j {self.jackname}"] if custom_slb_fpath: self.command += ["-m", custom_slb_fpath] From 95dee1b2abf36732f4eb50e238dc68df37fcf8ea Mon Sep 17 00:00:00 2001 From: jofemodo Date: Sun, 1 Dec 2024 18:47:32 +0100 Subject: [PATCH 039/117] Minor improvements to code style. --- zynautoconnect/zynthian_autoconnect.py | 33 +++++++++++++++++++------- zyngui/zynthian_gui_admin.py | 1 - 2 files changed, 24 insertions(+), 10 deletions(-) diff --git a/zynautoconnect/zynthian_autoconnect.py b/zynautoconnect/zynthian_autoconnect.py index 8294d18e0..ab52bfcb3 100755 --- a/zynautoconnect/zynthian_autoconnect.py +++ b/zynautoconnect/zynthian_autoconnect.py @@ -25,20 +25,19 @@ import os import re import usb +import json import jack +import psutil +import pexpect import logging import alsaaudio -import json from time import sleep from threading import Thread, Lock -from subprocess import check_output -import pexpect -import psutil # Zynthian specific modules +import zynconf from zyncoder.zyncore import lib_zyncore from zyngui import zynthian_gui_config -import zynconf # ------------------------------------------------------------------------------- # Configure logging @@ -56,6 +55,7 @@ # Fake port class # ------------------------------------------------------------------------------- + class fake_port: def __init__(self, name): self.name = name @@ -133,6 +133,7 @@ def unset_alias(self, alias): # MIDI port helper functions + def get_port_friendly_name(uid): """Get port friendly name @@ -349,6 +350,7 @@ def update_system_audio_aliases(): port.unset_alias(a) port.set_alias(alias) + def add_sidechain_ports(jackname): """Add ports that should be treated as sidechain inputs @@ -951,9 +953,11 @@ def audio_autoconnect(): # Release Mutex Lock release_lock() + def get_hw_audio_dst_ports(): return jclient.get_ports("system:playback", is_input=True, is_audio=True, is_physical=True) + jclient.get_ports("zynaout", is_input=True, is_audio=True) + def update_hw_audio_ports(): global alsa_audio_srcs, alsa_audio_dests @@ -1007,17 +1011,20 @@ def update_hw_audio_ports(): return dirty + def enable_hotplug(): zynthian_gui_config.hotplug_audio_enabled = True zynconf.save_config({"ZYNTHIAN_HOTPLUG_AUDIO": str(zynthian_gui_config.hotplug_audio_enabled)}) update_hw_audio_ports() audio_autoconnect() + def disable_hotplug(): zynthian_gui_config.hotplug_audio_enabled = False zynconf.save_config({"ZYNTHIAN_HOTPLUG_AUDIO": str(zynthian_gui_config.hotplug_audio_enabled)}) stop_all_alsa_in_out() + def enable_audio_input_device(device, enable=True): if enable: if start_alsa_in(device): @@ -1029,6 +1036,7 @@ def enable_audio_input_device(device, enable=True): zynthian_gui_config.disabled_audio_in.append(device) zynconf.save_config({"ZYNTHIAN_HOTPLUG_AUDIO_DISABLED_IN": ",".join(zynthian_gui_config.disabled_audio_in)}) + def enable_audio_output_device(device, enable=True): if enable: if start_alsa_out(device): @@ -1040,6 +1048,7 @@ def enable_audio_output_device(device, enable=True): zynthian_gui_config.disabled_audio_out.append(device) zynconf.save_config({"ZYNTHIAN_HOTPLUG_AUDIO_DISABLED_OUT": ",".join(zynthian_gui_config.disabled_audio_out)}) + def get_alsa_hotplug_audio_devices(playback=True): devices = [] for card in alsaaudio.pcms(alsaaudio.PCM_PLAYBACK if playback else alsaaudio.PCM_CAPTURE): @@ -1051,6 +1060,7 @@ def get_alsa_hotplug_audio_devices(playback=True): devices.append(device) return devices + def start_alsa_in(device): global alsa_audio_srcs if device in alsa_audio_srcs: @@ -1069,6 +1079,7 @@ def start_alsa_in(device): logging.warning(f"Failed to set {device} aliases") return True + def stop_alsa_in(device): global alsa_audio_srcs if device not in alsa_audio_srcs: @@ -1077,6 +1088,7 @@ def stop_alsa_in(device): alsa_audio_srcs.pop(device) return True + def start_alsa_out(device): global alsa_audio_dests if device in alsa_audio_dests: @@ -1095,6 +1107,7 @@ def start_alsa_out(device): logging.warning(f"Failed to set {device} aliases") return True + def stop_alsa_out(device): global alsa_audio_dests if device not in alsa_audio_dests: @@ -1103,22 +1116,23 @@ def stop_alsa_out(device): alsa_audio_dests.pop(device) return True + def stop_all_alsa_in_out(): for device in get_alsa_hotplug_audio_devices(False): stop_alsa_in(device) for device in get_alsa_hotplug_audio_devices(True): stop_alsa_out(device) + # Connect mixer to the ffmpeg recorder def audio_connect_ffmpeg(timeout=2.0): t = 0 while t < timeout: try: # TODO: Do we want post fader, post effects feed? - jclient.connect( - f"zynmixer:output_{MAIN_MIX_CHAN}a", "ffmpeg:input_1") - jclient.connect( - f"zynmixer:output_{MAIN_MIX_CHAN}b", "ffmpeg:input_2") + # => It's just for recording video tutorials, but if the recorded video is about post-fader effects ... + jclient.connect(f"zynmixer:output_{MAIN_MIX_CHAN}a", "ffmpeg:input_1") + jclient.connect(f"zynmixer:output_{MAIN_MIX_CHAN}b", "ffmpeg:input_2") return except: sleep(0.1) @@ -1360,6 +1374,7 @@ def init(): update_midi_in_dev_mode_all() update_system_audio_aliases() + def start(sm): """Initialise autoconnect and start MIDI port checker diff --git a/zyngui/zynthian_gui_admin.py b/zyngui/zynthian_gui_admin.py index 0f83d48bf..8239fc64e 100644 --- a/zyngui/zynthian_gui_admin.py +++ b/zyngui/zynthian_gui_admin.py @@ -391,7 +391,6 @@ def default_rbpi_headphones(self): else: self.stop_rbpi_headphones(False) - def get_hotplug_menu_options(self): options = {} if zynthian_gui_config.hotplug_audio_enabled: From 2b45c1d716215f4a9949b07fcc178206f2bf0858 Mon Sep 17 00:00:00 2001 From: jofemodo Date: Sun, 1 Dec 2024 21:10:07 +0100 Subject: [PATCH 040/117] + Small adjustments to v5touch code. + Some unrelated code re-styling. --- zyngui/zynthian_gui.py | 5 +- zyngui/zynthian_gui_admin.py | 16 ++-- zyngui/zynthian_gui_base.py | 42 ++++------ zyngui/zynthian_gui_config.py | 128 ++++++++++++------------------ zyngui/zynthian_gui_confirm.py | 14 ++-- zyngui/zynthian_gui_control_xy.py | 5 +- zyngui/zynthian_gui_help.py | 8 +- zyngui/zynthian_gui_info.py | 2 +- zyngui/zynthian_gui_keyboard.py | 63 +++++++-------- zyngui/zynthian_gui_loading.py | 16 ++-- 10 files changed, 127 insertions(+), 172 deletions(-) diff --git a/zyngui/zynthian_gui.py b/zyngui/zynthian_gui.py index 343ab4a15..ee64b3e75 100644 --- a/zyngui/zynthian_gui.py +++ b/zyngui/zynthian_gui.py @@ -161,16 +161,13 @@ def __init__(self): self.capture_log_fname = None self.capture_ffmpeg_proc = None - self.main_screen_column = 1 if zynthian_gui_config.touch_keypad_side_left else 0 - # Init LEDs self.wsleds = None self.init_wsleds() # Init multitouch driver if os.environ.get('DISPLAY_ROTATION', 'None') == 'Inverted' or zynthian_gui_config.check_wiring_layout(["Z2", "V5"]): - self.multitouch = MultiTouch( - invert_x_axis=True, invert_y_axis=True) + self.multitouch = MultiTouch(invert_x_axis=True, invert_y_axis=True) else: self.multitouch = MultiTouch() diff --git a/zyngui/zynthian_gui_admin.py b/zyngui/zynthian_gui_admin.py index 28c3e4df0..b4df1cbcb 100644 --- a/zyngui/zynthian_gui_admin.py +++ b/zyngui/zynthian_gui_admin.py @@ -213,12 +213,11 @@ def fill_list(self): self.list_data.append((self.show_cv_config, 0, "CV Settings")) self.list_data.append( (self.zyngui.calibrate_touchscreen, 0, "Calibrate Touchscreen")) - - self.list_data.append((None, 0, "> TOUCH KEYPAD")) - if zynthian_gui_config.touch_keypad: - self.list_data.append((self.toggle_touchkeypad, 0, "\u2612 V5 Touch Keypad")) - else: - self.list_data.append((self.toggle_touchkeypad, 0, "\u2610 V5 Touch Keypad")) + if not zynthian_gui_config.wiring_layout.startswith("V5"): + if zynthian_gui_config.touch_keypad: + self.list_data.append((self.toggle_touchkeypad, 0, "\u2612 V5 Touch Keypad")) + else: + self.list_data.append((self.toggle_touchkeypad, 0, "\u2610 V5 Touch Keypad")) self.list_data.append((None, 0, "> TEST")) self.list_data.append((self.test_audio, 0, "Test Audio")) @@ -397,7 +396,6 @@ def default_rbpi_headphones(self): else: self.stop_rbpi_headphones(False) - def get_hotplug_menu_options(self): options = {} if zynthian_gui_config.hotplug_audio_enabled: @@ -487,9 +485,9 @@ def bluetooth(self): def toggle_touchkeypad(self): if zynthian_gui_config.touch_keypad: - self.zyngui.show_confirm("Do you really want to restart UI with touch keypad off?", self.touchkeypad_off_confirmed) + self.zyngui.show_confirm("Do you really want to restart UI with touch-keypad disabled?", self.touchkeypad_off_confirmed) else: - self.zyngui.show_confirm("Do you really want to restart UI with touch keypad on?", self.touchkeypad_on_confirmed) + self.zyngui.show_confirm("Do you really want to restart UI with touch-keypad enabled?", self.touchkeypad_on_confirmed) def touchkeypad_off_confirmed(self, params=None): zynconf.save_config({'ZYNTHIAN_TOUCH_KEYPAD': ''}) diff --git a/zyngui/zynthian_gui_base.py b/zyngui/zynthian_gui_base.py index 238f15986..78b184436 100644 --- a/zyngui/zynthian_gui_base.py +++ b/zyngui/zynthian_gui_base.py @@ -64,8 +64,7 @@ def __init__(self, has_backbutton=True): self.width = zynthian_gui_config.screen_width # TODO: Views should use current height if they need dynamic changes else grow rows to fill main_frame if zynthian_gui_config.enable_touch_navigation and self.buttonbar_config: - self.height = zynthian_gui_config.screen_height - \ - self.topbar_height - self.buttonbar_height + self.height = zynthian_gui_config.screen_height - self.topbar_height - self.buttonbar_height else: self.height = zynthian_gui_config.screen_height - self.topbar_height @@ -85,10 +84,9 @@ def __init__(self, has_backbutton=True): self.backbutton_height = 0 # Title Area parameters - self.title_canvas_width = self.width - \ - self.backbutton_width - self.status_l - self.status_lpad - 2 - self.select_path_font = tkFont.Font( - family=zynthian_gui_config.font_topbar[0], size=zynthian_gui_config.font_topbar[1]) + self.title_canvas_width = self.width - self.backbutton_width - self.status_l - self.status_lpad - 2 + self.select_path_font = tkFont.Font(family=zynthian_gui_config.font_topbar[0], + size=zynthian_gui_config.font_topbar[1]) self.select_path_width = 0 self.select_path_offset = 0 @@ -120,8 +118,7 @@ def __init__(self, has_backbutton=True): self.backbutton_canvas.grid(row=0, column=col, sticky="wn", padx=(0, self.status_lpad)) self.backbutton_canvas.grid_propagate(False) self.backbutton_canvas.bind('', self.cb_backbutton) - self.backbutton_canvas.bind( - '', self.cb_backbutton_release) + self.backbutton_canvas.bind('', self.cb_backbutton_release) self.backbutton_timer = None col += 1 # Add back-arrow symbol @@ -132,8 +129,7 @@ def __init__(self, has_backbutton=True): fg=zynthian_gui_config.color_tx) self.label_backbutton.place(relx=0.3, rely=0.5, anchor='w') self.label_backbutton.bind('', self.cb_backbutton) - self.label_backbutton.bind( - '', self.cb_backbutton_release) + self.label_backbutton.bind('', self.cb_backbutton_release) # Title self.title = "" @@ -216,8 +212,7 @@ def __init__(self, has_backbutton=True): def show_back_button(self, show=True): if show: - self.backbutton_canvas.grid( - row=0, column=0, sticky="wn", padx=(0, self.status_lpad)) + self.backbutton_canvas.grid(row=0, column=0, sticky="wn", padx=(0, self.status_lpad)) self.backbutton_canvas.grid_propagate(False) else: self.backbutton_canvas.grid_remove() @@ -283,14 +278,9 @@ def init_buttonbar(self, config=None): bg=zynthian_gui_config.color_bg) self.buttonbar_frame.grid(row=2, padx=(0, 0), pady=(0, 0)) self.buttonbar_frame.grid_propagate(False) - self.buttonbar_frame.grid_rowconfigure( - 0, minsize=self.buttonbar_height, pad=0) + self.buttonbar_frame.grid_rowconfigure(0, minsize=self.buttonbar_height, pad=0) for i in range(max(4, len(config))): - self.buttonbar_frame.grid_columnconfigure( - i, - weight=1, - uniform='buttonbar', - pad=0) + self.buttonbar_frame.grid_columnconfigure(i, weight=1, uniform='buttonbar', pad=0) try: self.add_button(i, config[i][0], config[i][1]) except Exception as e: @@ -379,8 +369,7 @@ def set_button_status(self, column, status=False): # Default topbar touch callback def cb_topbar_press(self, params=None): - self.topbar_timer = Timer( - zynthian_gui_config.zynswitch_long_seconds, self.cb_topbar_long) + self.topbar_timer = Timer(zynthian_gui_config.zynswitch_long_seconds, self.cb_topbar_long) self.topbar_timer.start() self.topbar_press_time = time.monotonic() @@ -415,8 +404,7 @@ def topbar_long_touch_action(self): # Default status touch callback def cb_status_press(self, params=None): - self.status_timer = Timer( - zynthian_gui_config.zynswitch_long_seconds, self.cb_status_long) + self.status_timer = Timer(zynthian_gui_config.zynswitch_long_seconds, self.cb_status_long) self.status_timer.start() self.status_press_time = time.monotonic() @@ -459,8 +447,7 @@ def status_long_touch_action(self): # Default menu button touch callback def cb_backbutton(self, params=None): - self.backbutton_timer = Timer( - zynthian_gui_config.zynswitch_long_seconds, self.cb_backbutton_long) + self.backbutton_timer = Timer(zynthian_gui_config.zynswitch_long_seconds, self.cb_backbutton_long) self.backbutton_timer.start() self.backbutton_press_time = time.monotonic() @@ -505,11 +492,10 @@ def build_view(self): def show(self): if not self.shown: if self.zyngui.test_mode: - logging.warning("TEST_MODE: {}".format( - self.__class__.__module__)) + logging.warning("TEST_MODE: {}".format(self.__class__.__module__)) self.shown = True self.refresh_status() - self.grid(row=0, column=self.zyngui.main_screen_column, sticky='nsew') + self.grid(row=0, column=zynthian_gui_config.main_screen_column, sticky='nsew') self.propagate(False) self.main_frame.focus() diff --git a/zyngui/zynthian_gui_config.py b/zyngui/zynthian_gui_config.py index 6652b5ec8..750637af8 100644 --- a/zyngui/zynthian_gui_config.py +++ b/zyngui/zynthian_gui_config.py @@ -39,8 +39,7 @@ log_level = int(os.environ.get('ZYNTHIAN_LOG_LEVEL', logging.WARNING)) # log_level = logging.DEBUG -logging.basicConfig(format='%(levelname)s:%(module)s.%(funcName)s: %(message)s', - stream=sys.stderr, level=log_level) +logging.basicConfig(format='%(levelname)s:%(module)s.%(funcName)s: %(message)s', stream=sys.stderr, level=log_level) logging.getLogger().setLevel(level=log_level) # Reduce log level for other modules @@ -54,8 +53,7 @@ wiring_layout = os.environ.get('ZYNTHIAN_WIRING_LAYOUT', "DUMMIES") if wiring_layout == "DUMMIES": - logging.info( - "No Wiring Layout configured. Only touch interface is available.") + logging.info("No Wiring Layout configured. Only touch interface is available.") else: logging.info("Wiring Layout %s" % wiring_layout) @@ -131,10 +129,8 @@ def config_zynswitch_timing(): global zynswitch_bold_seconds global zynswitch_long_seconds try: - zynswitch_bold_us = 1000 * \ - int(os.environ.get('ZYNTHIAN_UI_SWITCH_BOLD_MS', 300)) - zynswitch_long_us = 1000 * \ - int(os.environ.get('ZYNTHIAN_UI_SWITCH_LONG_MS', 2000)) + zynswitch_bold_us = 1000 * int(os.environ.get('ZYNTHIAN_UI_SWITCH_BOLD_MS', 300)) + zynswitch_long_us = 1000 * int(os.environ.get('ZYNTHIAN_UI_SWITCH_LONG_MS', 2000)) zynswitch_bold_seconds = zynswitch_bold_us / 1000000 zynswitch_long_seconds = zynswitch_long_us / 1000000 @@ -327,15 +323,13 @@ def config_zynaptik(): if "4xAD" in zynaptik_config: for i in range(4): root_varname = "ZYNTHIAN_WIRING_ZYNAPTIK_AD{:02d}".format(i+1) - zynaptik_ad_midi_events.append( - get_zynsensor_config(root_varname)) + zynaptik_ad_midi_events.append(get_zynsensor_config(root_varname)) # Zynaptik DA Action Configuration if "4xDA" in zynaptik_config: for i in range(4): root_varname = "ZYNTHIAN_WIRING_ZYNAPTIK_DA{:02d}".format(i+1) - zynaptik_da_midi_events.append( - get_zynsensor_config(root_varname)) + zynaptik_da_midi_events.append(get_zynsensor_config(root_varname)) def config_zyntof(): @@ -370,39 +364,28 @@ def set_midi_config(): global master_midi_bank_change_down_ccnum, master_midi_bank_base # MIDI options - midi_fine_tuning = float(os.environ.get( - 'ZYNTHIAN_MIDI_FINE_TUNING', "440.0")) - active_midi_channel = int(os.environ.get( - 'ZYNTHIAN_MIDI_ACTIVE_CHANNEL', "0")) - midi_prog_change_zs3 = int(os.environ.get( - 'ZYNTHIAN_MIDI_PROG_CHANGE_ZS3', "1")) + midi_fine_tuning = float(os.environ.get('ZYNTHIAN_MIDI_FINE_TUNING', "440.0")) + active_midi_channel = int(os.environ.get('ZYNTHIAN_MIDI_ACTIVE_CHANNEL', "0")) + midi_prog_change_zs3 = int(os.environ.get('ZYNTHIAN_MIDI_PROG_CHANGE_ZS3', "1")) midi_bank_change = int(os.environ.get('ZYNTHIAN_MIDI_BANK_CHANGE', "0")) - preset_preload_noteon = int(os.environ.get( - 'ZYNTHIAN_MIDI_PRESET_PRELOAD_NOTEON', "1")) + preset_preload_noteon = int(os.environ.get('ZYNTHIAN_MIDI_PRESET_PRELOAD_NOTEON', "1")) midi_sys_enabled = int(os.environ.get('ZYNTHIAN_MIDI_SYS_ENABLED', "1")) midi_usb_by_port = int(os.environ.get("ZYNTHIAN_MIDI_USB_BY_PORT", "0")) - midi_network_enabled = int(os.environ.get( - 'ZYNTHIAN_MIDI_NETWORK_ENABLED', "0")) - midi_netump_enabled = int(os.environ.get( - 'ZYNTHIAN_MIDI_NETUMP_ENABLED', "0")) - midi_rtpmidi_enabled = int(os.environ.get( - 'ZYNTHIAN_MIDI_RTPMIDI_ENABLED', "0")) - midi_touchosc_enabled = int(os.environ.get( - 'ZYNTHIAN_MIDI_TOUCHOSC_ENABLED', "0")) + midi_network_enabled = int(os.environ.get('ZYNTHIAN_MIDI_NETWORK_ENABLED', "0")) + midi_netump_enabled = int(os.environ.get('ZYNTHIAN_MIDI_NETUMP_ENABLED', "0")) + midi_rtpmidi_enabled = int(os.environ.get('ZYNTHIAN_MIDI_RTPMIDI_ENABLED', "0")) + midi_touchosc_enabled = int(os.environ.get('ZYNTHIAN_MIDI_TOUCHOSC_ENABLED', "0")) bluetooth_enabled = int(os.environ.get('ZYNTHIAN_MIDI_BLE_ENABLED', "0")) ble_controller = os.environ.get('ZYNTHIAN_MIDI_BLE_CONTROLLER', "") - midi_aubionotes_enabled = int(os.environ.get( - 'ZYNTHIAN_MIDI_AUBIONOTES_ENABLED', "0")) - transport_clock_source = int(os.environ.get( - 'ZYNTHIAN_MIDI_TRANSPORT_CLOCK_SOURCE', "0")) + midi_aubionotes_enabled = int(os.environ.get('ZYNTHIAN_MIDI_AUBIONOTES_ENABLED', "0")) + transport_clock_source = int(os.environ.get('ZYNTHIAN_MIDI_TRANSPORT_CLOCK_SOURCE', "0")) # Filter Rules midi_filter_rules = os.environ.get('ZYNTHIAN_MIDI_FILTER_RULES', "") midi_filter_rules = midi_filter_rules.replace("\\n", "\n") # Master Channel Features - master_midi_channel = int(os. environ.get( - "ZYNTHIAN_MIDI_MASTER_CHANNEL", 0)) + master_midi_channel = int(os. environ.get("ZYNTHIAN_MIDI_MASTER_CHANNEL", 0)) master_midi_channel -= 1 if master_midi_channel > 15: master_midi_channel = 15 @@ -411,52 +394,42 @@ def set_midi_config(): else: mmc_hex = None - master_midi_change_type = os.environ.get( - "ZYNTHIAN_MIDI_MASTER_CHANGE_TYPE", "Roland") + master_midi_change_type = os.environ.get("ZYNTHIAN_MIDI_MASTER_CHANGE_TYPE", "Roland") # Use LSB Bank by default - master_midi_bank_change_ccnum = int(os.environ.get( - "ZYNTHIAN_MIDI_MASTER_BANK_CHANGE_CCNUM", 0x20)) + master_midi_bank_change_ccnum = int(os.environ.get("ZYNTHIAN_MIDI_MASTER_BANK_CHANGE_CCNUM", 0x20)) # Use MSB Bank by default # master_midi_bank_change_ccnum = int(os.environ.get("ZYNTHIAN_MIDI_MASTER_BANK_CHANGE_CCNUM", 0x00)) mmpcu = os.environ.get('ZYNTHIAN_MIDI_MASTER_PROGRAM_CHANGE_UP', "") if mmc_hex and len(mmpcu) == 4: - master_midi_program_change_up = int( - "{:<06}".format(mmpcu.replace("#", mmc_hex)), 16) + master_midi_program_change_up = int("{:<06}".format(mmpcu.replace("#", mmc_hex)), 16) else: master_midi_program_change_up = None mmpcd = os.environ.get('ZYNTHIAN_MIDI_MASTER_PROGRAM_CHANGE_DOWN', "") if mmc_hex and len(mmpcd) == 4: - master_midi_program_change_down = int( - "{:<06}".format(mmpcd.replace("#", mmc_hex)), 16) + master_midi_program_change_down = int("{:<06}".format(mmpcd.replace("#", mmc_hex)), 16) else: master_midi_program_change_down = None mmbcu = os.environ.get('ZYNTHIAN_MIDI_MASTER_BANK_CHANGE_UP', "") if mmc_hex and len(mmbcu) == 6: - master_midi_bank_change_up = int( - "{:<06}".format(mmbcu.replace("#", mmc_hex)), 16) + master_midi_bank_change_up = int("{:<06}".format(mmbcu.replace("#", mmc_hex)), 16) else: master_midi_bank_change_up = None mmbcd = os.environ.get('ZYNTHIAN_MIDI_MASTER_BANK_CHANGE_DOWN', "") if mmc_hex and len(mmbcd) == 6: - master_midi_bank_change_down = int( - "{:<06}".format(mmbcd.replace("#", mmc_hex)), 16) + master_midi_bank_change_down = int("{:<06}".format(mmbcd.replace("#", mmc_hex)), 16) else: master_midi_bank_change_down = None - logging.debug("MMC Bank Change CCNum: {}".format( - master_midi_bank_change_ccnum)) + logging.debug("MMC Bank Change CCNum: {}".format(master_midi_bank_change_ccnum)) logging.debug("MMC Bank Change UP: {}".format(master_midi_bank_change_up)) - logging.debug("MMC Bank Change DOWN: {}".format( - master_midi_bank_change_down)) - logging.debug("MMC Program Change UP: {}".format( - master_midi_program_change_up)) - logging.debug("MMC Program Change DOWN: {}".format( - master_midi_program_change_down)) + logging.debug("MMC Bank Change DOWN: {}".format(master_midi_bank_change_down)) + logging.debug("MMC Program Change UP: {}".format(master_midi_program_change_up)) + logging.debug("MMC Program Change DOWN: {}".format(master_midi_program_change_down)) # Master Note CUIA mmncuia_envar = os.environ.get('ZYNTHIAN_MIDI_MASTER_NOTE_CUIA', None) @@ -476,8 +449,7 @@ def set_midi_config(): else: raise Exception("Bad format!") except Exception as err: - logging.warning( - "Bad MIDI Master Note CUIA config {} => {}".format(cuianote, err)) + logging.warning("Bad MIDI Master Note CUIA config {} => {}".format(cuianote, err)) # ------------------------------------------------------------------------------ # External storage (removable disks) @@ -539,8 +511,7 @@ def get_external_storage_dirs(exdpath): # ------------------------------------------------------------------------------ enable_touch_widgets = int(os.environ.get('ZYNTHIAN_UI_TOUCH_WIDGETS', 0)) -enable_touch_navigation = int( - os.environ.get('ZYNTHIAN_UI_TOUCH_NAVIGATION', 0)) +enable_touch_navigation = int(os.environ.get('ZYNTHIAN_UI_TOUCH_NAVIGATION', 0)) force_enable_cursor = int(os.environ.get('ZYNTHIAN_UI_ENABLE_CURSOR', 0)) if check_wiring_layout(["Z2", "V5"]): @@ -554,22 +525,31 @@ def get_external_storage_dirs(exdpath): # ------------------------------------------------------------------------------ restore_last_state = int(os.environ.get('ZYNTHIAN_UI_RESTORE_LAST_STATE', 0)) -snapshot_mixer_settings = int(os.environ.get( - 'ZYNTHIAN_UI_SNAPSHOT_MIXER_SETTINGS', 0)) +snapshot_mixer_settings = int(os.environ.get('ZYNTHIAN_UI_SNAPSHOT_MIXER_SETTINGS', 0)) show_cpu_status = int(os.environ.get('ZYNTHIAN_UI_SHOW_CPU_STATUS', 0)) -visible_mixer_strips = int(os.environ.get( - 'ZYNTHIAN_UI_VISIBLE_MIXER_STRIPS', 0)) +visible_mixer_strips = int(os.environ.get('ZYNTHIAN_UI_VISIBLE_MIXER_STRIPS', 0)) ctrl_graph = int(os.environ.get('ZYNTHIAN_UI_CTRL_GRAPH', 1)) -control_test_enabled = int(os.environ.get( - 'ZYNTHIAN_UI_CONTROL_TEST_ENABLED', 0)) -power_save_secs = 60 * \ - int(os.environ.get('ZYNTHIAN_UI_POWER_SAVE_MINUTES', 60)) +control_test_enabled = int(os.environ.get('ZYNTHIAN_UI_CONTROL_TEST_ENABLED', 0)) +power_save_secs = 60 * int(os.environ.get('ZYNTHIAN_UI_POWER_SAVE_MINUTES', 60)) -touch_keypad_option = os.environ.get('ZYNTHIAN_TOUCH_KEYPAD', "") -touch_keypad_side_left = int(os.environ.get('ZYNTHIAN_TOUCH_KEYPAD_SIDE_LEFT', True)) + +# ------------------------------------------------------------------------------ +# Touch-keypad +# ------------------------------------------------------------------------------ touch_keypad = None +touch_keypad_option = os.environ.get('ZYNTHIAN_TOUCH_KEYPAD', "") +try: + touch_keypad_side_left = int(os.environ.get('ZYNTHIAN_TOUCH_KEYPAD_SIDE_LEFT', True)) +except: + touch_keypad_side_left = True + +if touch_keypad_side_left: + main_screen_column = 1 +else: + main_screen_column = 0 + # ------------------------------------------------------------------------------ # Audio Options # ------------------------------------------------------------------------------ @@ -597,8 +577,7 @@ def get_external_storage_dirs(exdpath): # Experimental features # ------------------------------------------------------------------------------ -experimental_features = os.environ.get( - 'ZYNTHIAN_EXPERIMENTAL_FEATURES', "").split(',') +experimental_features = os.environ.get('ZYNTHIAN_EXPERIMENTAL_FEATURES', "").split(',') # ------------------------------------------------------------------------------ # Sequence states @@ -642,11 +621,9 @@ def get_external_storage_dirs(exdpath): def color_variant(hex_color, brightness_offset=1): """ takes a color like #87c95f and produces a lighter or darker variant """ if len(hex_color) != 7: - raise Exception( - "Passed %s into color_variant(), needs to be in #87c95f format." % hex_color) + raise Exception("Passed %s into color_variant(), needs to be in #87c95f format." % hex_color) rgb_hex = [hex_color[x:x + 2] for x in [1, 3, 5]] - new_rgb_int = [int(hex_value, 16) + - brightness_offset for hex_value in rgb_hex] + new_rgb_int = [int(hex_value, 16) + brightness_offset for hex_value in rgb_hex] # make sure new values are between 0 and 255 new_rgb_int = [min([255, max([0, i])]) for i in new_rgb_int] # hex() produces "0x88", we want just "88" @@ -762,8 +739,7 @@ def color_variant(hex_color, brightness_offset=1): # loading_imgs.append(tkinter.PhotoImage(file="./img/zynthian_gui_loading.gif", format="gif -index "+str(i))) except Exception as e: - logging.error( - "ERROR initializing Tkinter graphic framework => {}".format(e)) + logging.error("ERROR initializing Tkinter graphic framework => {}".format(e)) # ------------------------------------------------------------------------------ # Initialize ZynCore low-level library diff --git a/zyngui/zynthian_gui_confirm.py b/zyngui/zynthian_gui_confirm.py index 28730d8bd..ac5155ac8 100644 --- a/zyngui/zynthian_gui_confirm.py +++ b/zyngui/zynthian_gui_confirm.py @@ -68,7 +68,8 @@ def __init__(self): self.yes_text_label = tkinter.Label(self.main_frame, font=( - zynthian_gui_config.font_family, zynthian_gui_config.font_size*2, "normal"), + zynthian_gui_config.font_family, + zynthian_gui_config.font_size*2, "normal"), text="Yes", width=3, justify=tkinter.RIGHT, @@ -77,12 +78,12 @@ def __init__(self): bg=zynthian_gui_config.color_ctrl_bg_off, fg=zynthian_gui_config.color_tx) self.yes_text_label.bind("", self.cb_yes_push) - self.yes_text_label.place(x=self.width, - y=self.height, anchor=tkinter.SE) + self.yes_text_label.place(x=self.width, y=self.height, anchor=tkinter.SE) self.no_text_label = tkinter.Label(self.main_frame, font=( - zynthian_gui_config.font_family, zynthian_gui_config.font_size*2, "normal"), + zynthian_gui_config.font_family, + zynthian_gui_config.font_size*2, "normal"), text="No", width=3, justify=tkinter.LEFT, @@ -91,8 +92,7 @@ def __init__(self): bg=zynthian_gui_config.color_ctrl_bg_off, fg=zynthian_gui_config.color_tx) self.no_text_label.bind("", self.cb_no_push) - self.no_text_label.place( - x=0, y=self.height, anchor=tkinter.SW) + self.no_text_label.place(x=0, y=self.height, anchor=tkinter.SW) def hide(self): if self.shown: @@ -110,7 +110,7 @@ def show(self, text, callback=None, cb_params=None): self.callback_params = cb_params if not self.shown: self.shown = True - self.main_frame.grid(row=0, column=self.zyngui.main_screen_column) + self.main_frame.grid(row=0, column=zynthian_gui_config.main_screen_column) def zynpot_cb(self, i, dval): pass diff --git a/zyngui/zynthian_gui_control_xy.py b/zyngui/zynthian_gui_control_xy.py index 2ca08b159..d895a5f8f 100755 --- a/zyngui/zynthian_gui_control_xy.py +++ b/zyngui/zynthian_gui_control_xy.py @@ -113,10 +113,9 @@ def build_view(self): def show(self): if not self.shown: if self.zyngui.test_mode: - logging.warning("TEST_MODE: {}".format( - self.__class__.__module__)) + logging.warning("TEST_MODE: {}".format(self.__class__.__module__)) self.shown = True - self.main_frame.grid(row=0, column=self.zyngui.main_screen_column) + self.main_frame.grid(row=0, column=zynthian_gui_config.main_screen_column) self.get_controller_values() self.refresh() diff --git a/zyngui/zynthian_gui_help.py b/zyngui/zynthian_gui_help.py index 787265c6e..41284533a 100644 --- a/zyngui/zynthian_gui_help.py +++ b/zyngui/zynthian_gui_help.py @@ -55,7 +55,11 @@ def __init__(self): # Main Frame - self.main_frame = HtmlFrame(zynthian_gui_config.top, width=zynthian_gui_config.screen_width, height=zynthian_gui_config.screen_height, messages_enabled=False) + self.main_frame = HtmlFrame(zynthian_gui_config.top, + width=zynthian_gui_config.screen_width, + height=zynthian_gui_config.screen_height, + vertical_scrollbar=False, + messages_enabled=False) self.main_frame.grid_propagate(False) # Patch HtmlFrame widget self.main_frame.event_generate = self.main_frame.html.event_generate @@ -91,7 +95,7 @@ def show(self): if not self.shown: self.shown = True self.main_frame.grid_propagate(False) - self.main_frame.grid(row=0, column=self.zyngui.main_screen_column) + self.main_frame.grid(row=0, column=zynthian_gui_config.main_screen_column) def zynpot_cb(self, i, dval): if i == 3: diff --git a/zyngui/zynthian_gui_info.py b/zyngui/zynthian_gui_info.py index 8a1f7f773..c71495c52 100644 --- a/zyngui/zynthian_gui_info.py +++ b/zyngui/zynthian_gui_info.py @@ -90,7 +90,7 @@ def show(self, text): self.set(text) if not self.shown: self.shown = True - self.main_frame.grid(row=0, column=self.zyngui.main_screen_column) + self.main_frame.grid(row=0, column=zynthian_gui_config.main_screen_column) def zynpot_cb(self, i, dval): return True diff --git a/zyngui/zynthian_gui_keyboard.py b/zyngui/zynthian_gui_keyboard.py index ea7ea7673..4bbc3c8ee 100644 --- a/zyngui/zynthian_gui_keyboard.py +++ b/zyngui/zynthian_gui_keyboard.py @@ -59,13 +59,11 @@ def __init__(self): # Geometry vars self.width = zynthian_gui_config.screen_width - self.height = zynthian_gui_config.screen_height - \ - zynthian_gui_config.topbar_height + self.height = zynthian_gui_config.screen_height - zynthian_gui_config.topbar_height # Fonts - self.font_button = (zynthian_gui_config.font_family, - int(1.2*zynthian_gui_config.font_size)) + self.font_button = (zynthian_gui_config.font_family, int(1.2*zynthian_gui_config.font_size)) # Create main frame self.main_frame = tkinter.Frame(zynthian_gui_config.top, @@ -75,17 +73,16 @@ def __init__(self): self.main_frame.grid_propagate(False) # Display string being edited - self.text_canvas = tkinter.Canvas( - self.main_frame, width=self.width, height=zynthian_gui_config.topbar_height) + self.text_canvas = tkinter.Canvas(self.main_frame, width=self.width, height=zynthian_gui_config.topbar_height) self.text_label = self.text_canvas.create_text(self.width / 2, zynthian_gui_config.topbar_height / 2, font=zynthian_gui_config.font_topbar, - # font=tkFont.Font(family=zynthian_gui_config.font_topbar[0], size= int(zynthian_gui_config.topbar_height * 0.8)) + # font=tkFont.Font(family=zynthian_gui_config.font_topbar[0], + # size= int(zynthian_gui_config.topbar_height * 0.8)) ) self.text_canvas.grid(column=0, row=0, sticky="nsew") # Display keyboard grid - self.key_canvas = tkinter.Canvas( - self.main_frame, width=self.width, height=self.height, bg="grey") + self.key_canvas = tkinter.Canvas(self.main_frame, width=self.width, height=self.height, bg="grey") self.key_canvas.grid_propagate(False) self.key_canvas.grid(column=0, row=1, sticky="nesw") self.set_mode(OSK_QWERTY) @@ -139,9 +136,9 @@ def refresh_keys(self): elif self.alt: if self.shift: self.keys = ['\\', '|', '@', '/', '*', '=', '\"', '\'', '?', '¡', - 'Ä', 'Ë', 'Ï', 'Ö', 'Ü', 'Â', 'Ê', 'Î', 'Ô', 'Û', - 'Ñ', 'Ç', 'Ẅ', 'Ŵ', 'Ĉ', 'Ÿ', 'Ŷ', 'Ŝ', 'Ĝ', 'Ḧ', - 'Ĥ', 'Ĵ', 'Ẑ', 'Ẍ', '{', '}', '~', '^', ':', '_'] + 'Ä', 'Ë', 'Ï', 'Ö', 'Ü', 'Â', 'Ê', 'Î', 'Ô', 'Û', + 'Ñ', 'Ç', 'Ẅ', 'Ŵ', 'Ĉ', 'Ÿ', 'Ŷ', 'Ŝ', 'Ĝ', 'Ḧ', + 'Ĥ', 'Ĵ', 'Ẑ', 'Ẍ', '{', '}', '~', '^', ':', '_'] else: self.keys = ['á', 'é', 'í', 'ó', 'ú', 'à', 'è', 'ì', 'ò', 'ù', 'ä', 'ë', 'ï', 'ö', 'ü', 'â', 'ê', 'î', 'ô', 'û', @@ -160,8 +157,9 @@ def refresh_keys(self): 'z', 'x', 'c', 'v', 'b', 'n', 'm', ',', '.', '#'] self.key_canvas.itemconfig("keycaps", text="") for index in range(len(self.keys)): - self.key_canvas.itemconfig(self.buttons[index][1], text=self.keys[index], tags=( - "key:%d" % (index), "keycaps")) + self.key_canvas.itemconfig(self.buttons[index][1], + text=self.keys[index], + tags=("key:%d" % (index), "keycaps")) # Function to add a button to the keyboard # label: Button label @@ -172,14 +170,18 @@ def refresh_keys(self): def add_button(self, label, col, row, colspan=1): index = len(self.buttons) tag = "key:%d" % (index) - r = self.key_canvas.create_rectangle(1 + self.key_width * col, 1 + self.key_height * row, self.key_width * ( - col + colspan) - 1, self.key_height * (row + 1) - 1, tags=(tag), fill="black") - l = self.key_canvas.create_text(1 + self.key_width * (col + colspan / 2), 1 + self.key_height * (row + 0.5), + r = self.key_canvas.create_rectangle(1 + self.key_width * col, + 1 + self.key_height * row, + self.key_width * (col + colspan) - 1, + self.key_height * (row + 1) - 1, + tags=(tag), + fill="black") + l = self.key_canvas.create_text(1 + self.key_width * (col + colspan / 2), + 1 + self.key_height * (row + 0.5), text=label, fill="white", font=self.font_button, - tags=(tag) - ) + tags=(tag)) self.key_canvas.tag_bind(tag, "", self.on_key_press) self.key_canvas.tag_bind(tag, "", self.on_key_release) self.buttons.append([r, l]) @@ -192,8 +194,7 @@ def bold_press(self): # Function to handle key press # event: Mouse event def on_key_press(self, event=None): - tags = self.key_canvas.gettags( - self.key_canvas.find_withtag(tkinter.CURRENT)) + tags = self.key_canvas.gettags(self.key_canvas.find_withtag(tkinter.CURRENT)) if not tags: return dummy, index = tags[0].split(':') @@ -240,11 +241,9 @@ def execute_key_press(self, key, bold=False): elif key == self.btn_alt: self.alt = not self.alt if self.alt: - self.key_canvas.itemconfig( - self.buttons[self.btn_alt][0], fill="red") + self.key_canvas.itemconfig(self.buttons[self.btn_alt][0], fill="red") else: - self.key_canvas.itemconfig( - self.buttons[self.btn_alt][0], fill="black") + self.key_canvas.itemconfig(self.buttons[self.btn_alt][0], fill="black") self.refresh_keys() if key == self.btn_shift: @@ -259,14 +258,11 @@ def execute_key_press(self, key, bold=False): if shift != self.shift: if self.shift == 1: - self.key_canvas.itemconfig( - self.buttons[self.btn_shift][0], fill="grey") + self.key_canvas.itemconfig(self.buttons[self.btn_shift][0], fill="grey") elif self.shift == 2: - self.key_canvas.itemconfig( - self.buttons[self.btn_shift][0], fill="red") + self.key_canvas.itemconfig(self.buttons[self.btn_shift][0], fill="red") else: - self.key_canvas.itemconfig( - self.buttons[self.btn_shift][0], fill="black") + self.key_canvas.itemconfig(self.buttons[self.btn_shift][0], fill="black") self.refresh_keys() self.text_canvas.itemconfig(self.text_label, text=self.text) @@ -276,8 +272,7 @@ def execute_key_press(self, key, bold=False): def highlight(self, key): box = self.key_canvas.bbox(self.buttons[key][0]) if box: - self.key_canvas.coords( - self.highlight_box, box[0]+1, box[1]+1, box[2], box[3]) + self.key_canvas.coords(self.highlight_box, box[0]+1, box[1]+1, box[2], box[3]) # Function to hide dialog def hide(self): @@ -307,7 +302,7 @@ def show(self, function, text="", max_len=None): self.highlight(self.selected_button) self.setup_zynpots() self.refresh_keys() - self.main_frame.grid(row=0, column=self.zyngui.main_screen_column) + self.main_frame.grid(row=0, column=zynthian_gui_config.main_screen_column) self.shown = True # Function to register encoders diff --git a/zyngui/zynthian_gui_loading.py b/zyngui/zynthian_gui_loading.py index ce8cb7c8a..4b710580c 100644 --- a/zyngui/zynthian_gui_loading.py +++ b/zyngui/zynthian_gui_loading.py @@ -62,14 +62,15 @@ def __init__(self): int(0.85 * self.height), anchor=tkinter.CENTER, justify=tkinter.CENTER, - font=(zynthian_gui_config.font_family, int( - 0.8*zynthian_gui_config.font_size)), + font=(zynthian_gui_config.font_family, int(0.8*zynthian_gui_config.font_size)), fill=zynthian_gui_config.color_tx_off, text="") # Setup Loading Logo Animation self.loading_index = 0 self.loading_item = self.canvas.create_image( - self.width//2, self.height//2, image=zynthian_gui_config.loading_imgs[0], anchor=tkinter.CENTER) + self.width//2, self.height//2, + image=zynthian_gui_config.loading_imgs[0], + anchor=tkinter.CENTER) def build_view(self): return True @@ -82,7 +83,7 @@ def hide(self): def show(self): if not self.shown: self.shown = True - self.canvas.grid(row=0, column=self.zyngui.main_screen_column) + self.canvas.grid(row=0, column=zynthian_gui_config.main_screen_column) def set_error(self, txt): self.set_title(txt, zynthian_gui_config.color_error) @@ -115,16 +116,15 @@ def refresh_loading(self): self.loading_index += 1 if self.loading_index >= len(zynthian_gui_config.loading_imgs): self.loading_index = 0 - self.canvas.itemconfig( - self.loading_item, image=zynthian_gui_config.loading_imgs[self.loading_index]) + self.canvas.itemconfig(self.loading_item, + image=zynthian_gui_config.loading_imgs[self.loading_index]) else: self.reset_loading() def reset_loading(self, force=False): if self.loading_index > 0 or force: self.loading_index = 0 - self.canvas.itemconfig( - self.loading_item, image=zynthian_gui_config.loading_imgs[0]) + self.canvas.itemconfig(self.loading_item, image=zynthian_gui_config.loading_imgs[0]) def zynpot_cb(self, i, dval): pass From 101617ba814cb83d730f7c2a4f9bc50d97fd24e3 Mon Sep 17 00:00:00 2001 From: riban Date: Mon, 2 Dec 2024 08:49:56 +0000 Subject: [PATCH 041/117] Improvements for sooperlooper --- zyngine/zynthian_engine_sooperlooper.py | 86 ++++++++++++++++--------- 1 file changed, 54 insertions(+), 32 deletions(-) diff --git a/zyngine/zynthian_engine_sooperlooper.py b/zyngine/zynthian_engine_sooperlooper.py index fdd92a321..0c4b07f3c 100644 --- a/zyngine/zynthian_engine_sooperlooper.py +++ b/zyngine/zynthian_engine_sooperlooper.py @@ -173,6 +173,7 @@ class zynthian_engine_sooperlooper(zynthian_engine): ] SL_STATES = { + # Dictionary of SL states with indication of which controllers are on/off in this SL state SL_STATE_UNKNOWN: { 'name': 'unknown', 'ctrl_off': [], @@ -182,7 +183,7 @@ class zynthian_engine_sooperlooper(zynthian_engine): }, SL_STATE_OFF: { 'name': 'off', - 'ctrl_off': [], + 'ctrl_off': ['mute'], 'ctrl_on': [], 'next_state': False, 'icon': '' @@ -349,17 +350,14 @@ def __init__(self, state_manager=None): custom_slb_fpath = None # Build SL command line - if self.config_remote_display(): - self.command = ["slgui", "-l 0", f"-P {self.osc_target_port}", f"-J {self.jackname}"] - else: - self.command = ["sooperlooper", "-q", "-l 0", "-D no", f"-p {self.osc_target_port}", f"-j {self.jackname}"] + self.command = ["sooperlooper", "-q", "-D no", f"-p {self.osc_target_port}", f"-j {self.jackname}"] if custom_slb_fpath: self.command += ["-m", custom_slb_fpath] self.state = [-1] * self.MAX_LOOPS # Current SL state for each loop self.next_state = [-1] * self.MAX_LOOPS # Next SL state for each loop (-1 if no state change pending) self.waiting = [0] * self.MAX_LOOPS # 1 if a change of state is pending - self.selected_loop = 0 + self.selected_loop = None self.loop_count = 1 self.channels = 2 self.global_cc_binding = True # True for MIDI CC to control selected loop. False to target individual loops @@ -462,6 +460,11 @@ def start(self): # Request current quantity of loops self.osc_server.send(self.osc_target, '/ping', ('s', self.osc_server_url), ('s', '/info')) + if self.config_remote_display(): + self.proc_gui = Popen("slgui", stdout=DEVNULL, stderr=DEVNULL, env=self.command_env, cwd=self.command_cwd) + else: + self.proc_gui = None + def stop(self): if self.proc: try: @@ -474,6 +477,17 @@ def stop(self): self.proc = None except Exception as err: logging.error(f"Can't stop engine {self.name} => {err}") + if self.proc_gui: + try: + logging.info("Stoping SLGUI") + self.proc_gui.terminate() + try: + self.proc_gui.wait(0.2) + except: + self.proc_gui.kill() + self.proc = None + except Exception as err: + logging.error(f"Can't stop engine {self.name} => {err}") self.osc_end() # --------------------------------------------------------------------------- @@ -696,7 +710,7 @@ def cb_osc_all(self, path, args, types, src): return try: processor = self.processors[0] - #logging.debug(f"Rx OSC => {path} {args}") + logging.warning(f"Rx OSC => {path} {args}") if path == '/state': # args: i:Loop index, s:control, f:value logging.debug("Loop State: %d %s=%0.1f", args[0], args[1], args[2]) @@ -723,7 +737,7 @@ def cb_osc_all(self, path, args, types, src): self.monitors_dict['state'] = self.state[loop] self.monitors_dict['next_state'] = self.next_state[loop] self.monitors_dict['waiting'] = self.waiting[loop] - self.update_state() + self.update_state(loop) elif path == '/info': # args: s:hosturl s:version i:loopcount @@ -752,8 +766,7 @@ def cb_osc_all(self, path, args, types, src): if self.loop_count > 1: # Set defaults for new loops self.osc_server.send(self.osc_target, f"/sl/{self.loop_count - 1 - i}/set", ('s', 'sync'), ('f', 1)) - if self.loop_count > 1: - self.select_loop(self.loop_count - 1, True) + self.select_loop(self.loop_count - 1, True) self.osc_server.send(self.osc_target, '/get', ('s', 'sync_source'), ('s', self.osc_server_url), ('s', '/control')) if self.selected_loop > self.loop_count: @@ -800,55 +813,64 @@ def cb_osc_all(self, path, args, types, src): # Specific functions # --------------------------------------------------------------------------- - # Update 'state' controllers to match state of selected loop - def update_state(self): + # Update 'state' controllers of loop + def update_state(self, loop): try: processor = self.processors[0] except: return try: - current_state = self.state[self.selected_loop] + current_state = self.state[loop] + logging.warning(f"loop: {loop} state: {current_state}") + # Turn off all controllers that are off in this state for symbol in self.SL_STATES[current_state]['ctrl_off']: if symbol in self.SL_LOOP_SEL_PARAM: - symbol += f":{self.selected_loop}" - processor.controllers_dict[symbol].set_readonly(False) - processor.controllers_dict[symbol].set_value(0, False) + symbol += f":{loop}" + processor.controllers_dict[symbol].set_readonly(False) + processor.controllers_dict[symbol].set_value(0, False) + # Turn on all controllers that are on in this state for symbol in self.SL_STATES[current_state]['ctrl_on']: if symbol in self.SL_LOOP_SEL_PARAM: - symbol += f":{self.selected_loop}" - processor.controllers_dict[symbol].set_readonly(False) - processor.controllers_dict[symbol].set_value(1, False) - next_state = self.next_state[self.selected_loop] + symbol += f":{loop}" + processor.controllers_dict[symbol].set_readonly(False) + processor.controllers_dict[symbol].set_value(1, False) + next_state = self.next_state[loop] + # Set next_state for controllers that are part of logical sequence if self.SL_STATES[next_state]['next_state']: for symbol in self.SL_STATES[next_state]['ctrl_on']: if symbol in self.SL_LOOP_SEL_PARAM: - symbol += f":{self.selected_loop}" - processor.controllers_dict[symbol].set_value(1, False) - processor.controllers_dict[symbol].set_readonly(True) - self._ctrl_screens[0][1] = [f'record:{self.selected_loop}', f'overdub:{self.selected_loop}', f'multiply:{self.selected_loop}', 'undo/redo'] - self._ctrl_screens[1][1] = [f'replace:{self.selected_loop}', f'substitute:{self.selected_loop}', f'insert:{self.selected_loop}', 'undo/redo'] - self._ctrl_screens[2][1] = [f'trigger:{self.selected_loop}', f'oneshot:{self.selected_loop}', f'mute:{self.selected_loop}', f'pause:{self.selected_loop}'] - self._ctrl_screens[3][1] = [f'reverse:{self.selected_loop}', 'rate', 'stretch_ratio', 'pitch_shift'] - processor.refresh_controllers() + symbol += f":{loop}" + processor.controllers_dict[symbol].set_value(1, False) + processor.controllers_dict[symbol].set_readonly(True) except Exception as e: logging.error(e) #self.processors[0].status = self.SL_STATES[self.state]['icon'] def select_loop(self, loop, send=False): + try: + processor = self.processors[0] + except: + return if loop < 0 or loop >= self.loop_count: return # TODO: Handle -1 == all loops self.selected_loop = int(loop) + """ self.monitors_dict['state'] = self.state[self.selected_loop] self.monitors_dict['next_state'] = self.next_state[self.selected_loop] self.monitors_dict['waiting'] = self.waiting[self.selected_loop] self.update_state() - try: - self.processors[0].controllers_dict['selected_loop_num'].set_value(loop + 1, False) - except: - pass + """ + processor.controllers_dict['selected_loop_num'].set_value(loop + 1, False) if send and self.osc_server: self.osc_server.send(self.osc_target, '/set', ('s', 'selected_loop_num'), ('f', self.selected_loop)) + + self._ctrl_screens[0][1] = [f'record:{self.selected_loop}', f'overdub:{self.selected_loop}', f'multiply:{self.selected_loop}', 'undo/redo'] + self._ctrl_screens[1][1] = [f'replace:{self.selected_loop}', f'substitute:{self.selected_loop}', f'insert:{self.selected_loop}', 'undo/redo'] + self._ctrl_screens[2][1] = [f'trigger:{self.selected_loop}', f'oneshot:{self.selected_loop}', f'mute:{self.selected_loop}', f'pause:{self.selected_loop}'] + self._ctrl_screens[3][1] = [f'reverse:{self.selected_loop}', 'rate', 'stretch_ratio', 'pitch_shift'] + processor.refresh_controllers() + zynsigman.send_queued(zynsigman.S_GUI, SS_GUI_CONTROL_MODE, mode='control') def prev_loop(self): From 282bce8e0962a33f3c2b36a700fdce6805fed62c Mon Sep 17 00:00:00 2001 From: jofemodo Date: Mon, 2 Dec 2024 11:09:24 +0100 Subject: [PATCH 042/117] Rationalize touch navigation config options. --- zyngui/zynthian_gui_admin.py | 156 ++++++++++++++-------------------- zyngui/zynthian_gui_config.py | 94 +++++++++++--------- 2 files changed, 119 insertions(+), 131 deletions(-) diff --git a/zyngui/zynthian_gui_admin.py b/zyngui/zynthian_gui_admin.py index b4df1cbcb..6ba45615c 100644 --- a/zyngui/zynthian_gui_admin.py +++ b/zyngui/zynthian_gui_admin.py @@ -90,8 +90,7 @@ def build_view(self): self.update_available = self.state_manager.update_available if not self.refresh_wifi_thread: self.refresh_wifi = True - self.refresh_wifi_thread = Thread( - target=self.refresh_wifi_task, name="wifi_refresh") + self.refresh_wifi_thread = Thread(target=self.refresh_wifi_task, name="wifi_refresh") self.refresh_wifi_thread.start() res = super().build_view() self.state_manager.check_for_updates() @@ -110,114 +109,94 @@ def fill_list(self): self.list_data = [] self.list_data.append((None, 0, "> MIDI")) - self.list_data.append( - (self.zyngui.midi_in_config, 0, "MIDI Input Devices")) - self.list_data.append( - (self.zyngui.midi_out_config, 0, "MIDI Output Devices")) + self.list_data.append((self.zyngui.midi_in_config, 0, "MIDI Input Devices")) + self.list_data.append((self.zyngui.midi_out_config, 0, "MIDI Output Devices")) # self.list_data.append((self.midi_profile, 0, "MIDI Profile")) if lib_zyncore.get_active_midi_chan(): - self.list_data.append( - (self.toggle_active_midi_channel, 0, "\u2612 Active MIDI channel")) + self.list_data.append((self.toggle_active_midi_channel, 0, "\u2612 Active MIDI channel")) else: - self.list_data.append( - (self.toggle_active_midi_channel, 0, "\u2610 Active MIDI channel")) + self.list_data.append((self.toggle_active_midi_channel, 0, "\u2610 Active MIDI channel")) if zynthian_gui_config.midi_prog_change_zs3: - self.list_data.append( - (self.toggle_prog_change_zs3, 0, "\u2612 Program Change for ZS3")) + self.list_data.append((self.toggle_prog_change_zs3, 0, "\u2612 Program Change for ZS3")) else: - self.list_data.append( - (self.toggle_prog_change_zs3, 0, "\u2610 Program Change for ZS3")) + self.list_data.append((self.toggle_prog_change_zs3, 0, "\u2610 Program Change for ZS3")) if zynthian_gui_config.midi_bank_change: - self.list_data.append( - (self.toggle_bank_change, 0, "\u2612 MIDI Bank Change")) + self.list_data.append((self.toggle_bank_change, 0, "\u2612 MIDI Bank Change")) else: - self.list_data.append( - (self.toggle_bank_change, 0, "\u2610 MIDI Bank Change")) + self.list_data.append((self.toggle_bank_change, 0, "\u2610 MIDI Bank Change")) if zynthian_gui_config.preset_preload_noteon: - self.list_data.append( - (self.toggle_preset_preload_noteon, 0, "\u2612 Note-On Preset Preload")) + self.list_data.append((self.toggle_preset_preload_noteon, 0, "\u2612 Note-On Preset Preload")) else: - self.list_data.append( - (self.toggle_preset_preload_noteon, 0, "\u2610 Note-On Preset Preload")) + self.list_data.append((self.toggle_preset_preload_noteon, 0, "\u2610 Note-On Preset Preload")) if zynthian_gui_config.midi_usb_by_port: - self.list_data.append( - (self.toggle_usbmidi_by_port, 0, "\u2612 MIDI-USB mapped by port")) + self.list_data.append((self.toggle_usbmidi_by_port, 0, "\u2612 MIDI-USB mapped by port")) else: - self.list_data.append( - (self.toggle_usbmidi_by_port, 0, "\u2610 MIDI-USB mapped by port")) + self.list_data.append((self.toggle_usbmidi_by_port, 0, "\u2610 MIDI-USB mapped by port")) if zynthian_gui_config.transport_clock_source == 0: if zynthian_gui_config.midi_sys_enabled: - self.list_data.append( - (self.toggle_midi_sys, 0, "\u2612 MIDI System Messages")) + self.list_data.append((self.toggle_midi_sys, 0, "\u2612 MIDI System Messages")) else: - self.list_data.append( - (self.toggle_midi_sys, 0, "\u2610 MIDI System Messages")) + self.list_data.append((self.toggle_midi_sys, 0, "\u2610 MIDI System Messages")) gtrans = lib_zyncore.get_global_transpose() if gtrans > 0: display_val = f"+{gtrans}" else: display_val = f"{gtrans}" - self.list_data.append( - (self.edit_global_transpose, 0, f"[{display_val}] Global Transpose")) + self.list_data.append((self.edit_global_transpose, 0, f"[{display_val}] Global Transpose")) self.list_data.append((None, 0, "> AUDIO")) if self.state_manager.allow_rbpi_headphones(): if zynthian_gui_config.rbpi_headphones: - self.list_data.append( - (self.stop_rbpi_headphones, 0, "\u2612 RBPi Headphones")) + self.list_data.append((self.stop_rbpi_headphones, 0, "\u2612 RBPi Headphones")) else: - self.list_data.append( - (self.start_rbpi_headphones, 0, "\u2610 RBPi Headphones")) + self.list_data.append((self.start_rbpi_headphones, 0, "\u2610 RBPi Headphones")) self.list_data.append((self.hotplug_audio_menu, 0, "Hotplug USB Audio")) if zynthian_gui_config.snapshot_mixer_settings: - self.list_data.append( - (self.toggle_snapshot_mixer_settings, 0, "\u2612 Audio Levels on Snapshots")) + self.list_data.append((self.toggle_snapshot_mixer_settings, 0, "\u2612 Audio Levels on Snapshots")) else: - self.list_data.append( - (self.toggle_snapshot_mixer_settings, 0, "\u2610 Audio Levels on Snapshots")) + self.list_data.append((self.toggle_snapshot_mixer_settings, 0, "\u2610 Audio Levels on Snapshots")) if zynthian_gui_config.enable_dpm: - self.list_data.append( - (self.toggle_dpm, 0, "\u2612 Mixer Peak Meters")) + self.list_data.append((self.toggle_dpm, 0, "\u2612 Mixer Peak Meters")) else: - self.list_data.append( - (self.toggle_dpm, 0, "\u2610 Mixer Peak Meters")) + self.list_data.append((self.toggle_dpm, 0, "\u2610 Mixer Peak Meters")) self.list_data.append((None, 0, "> NETWORK")) self.list_data.append((self.network_info, 0, "Network Info")) - self.list_data.append( - (self.wifi_config, 0, f"Wi-Fi Config ({self.wifi_status})")) + self.list_data.append((self.wifi_config, 0, f"Wi-Fi Config ({self.wifi_status})")) self.wifi_index = len(self.list_data) - 1 if zynconf.is_service_active("vncserver0"): - self.list_data.append( - (self.state_manager.stop_vncserver, 0, "\u2612 VNC Server")) + self.list_data.append((self.state_manager.stop_vncserver, 0, "\u2612 VNC Server")) else: - self.list_data.append( - (self.state_manager.start_vncserver, 0, "\u2610 VNC Server")) + self.list_data.append((self.state_manager.start_vncserver, 0, "\u2610 VNC Server")) self.list_data.append((None, 0, "> SETTINGS")) - self.list_data.append((self.bluetooth, 0, "Bluetooth")) + if not zynthian_gui_config.wiring_layout.startswith("V5"): + match zynthian_gui_config.touch_navigation: + case "touch_widgets": + touch_navigation_option = "touch-widgets" + case "v5_keypad_left": + touch_navigation_option = "V5 keypad at Left" + case "v5_keypad_right": + touch_navigation_option = "V5 keypad at right" + case _: + touch_navigation_option = "None" + self.list_data.append((self.touch_navigation_menu, 0, f"Touch Navigation: {touch_navigation_option}")) if "brightness_config" in self.zyngui.screens and self.zyngui.screens["brightness_config"].get_num_zctrls() > 0: - self.list_data.append( - (self.zyngui.brightness_config, 0, "Brightness")) + self.list_data.append((self.zyngui.brightness_config, 0, "Brightness")) if "cv_config" in self.zyngui.screens: self.list_data.append((self.show_cv_config, 0, "CV Settings")) - self.list_data.append( - (self.zyngui.calibrate_touchscreen, 0, "Calibrate Touchscreen")) - if not zynthian_gui_config.wiring_layout.startswith("V5"): - if zynthian_gui_config.touch_keypad: - self.list_data.append((self.toggle_touchkeypad, 0, "\u2612 V5 Touch Keypad")) - else: - self.list_data.append((self.toggle_touchkeypad, 0, "\u2610 V5 Touch Keypad")) + self.list_data.append((self.zyngui.calibrate_touchscreen, 0, "Calibrate Touchscreen")) + self.list_data.append((self.bluetooth, 0, "Bluetooth")) self.list_data.append((None, 0, "> TEST")) self.list_data.append((self.test_audio, 0, "Test Audio")) @@ -227,11 +206,9 @@ def fill_list(self): self.list_data.append((None, 0, "> SYSTEM")) if self.zyngui.capture_log_fname: - self.list_data.append( - (self.workflow_capture_stop, 0, "\u2612 Capture Workflow")) + self.list_data.append((self.workflow_capture_stop, 0, "\u2612 Capture Workflow")) else: - self.list_data.append( - (self.workflow_capture_start, 0, "\u2610 Capture Workflow")) + self.list_data.append((self.workflow_capture_start, 0, "\u2610 Capture Workflow")) if self.state_manager.update_available: self.list_data.append((self.update_software, 0, "Update Software")) # self.list_data.append((self.update_system, 0, "Update Operating System")) @@ -264,8 +241,7 @@ def execute_commands(self): self.zyngui.add_info("EXECUTING:\n", "EMPHASIS") self.zyngui.add_info("{}\n".format(cmd)) try: - self.proc = Popen(cmd, shell=True, stdout=PIPE, - stderr=STDOUT, universal_newlines=True) + self.proc = Popen(cmd, shell=True, stdout=PIPE, stderr=STDOUT, universal_newlines=True) self.zyngui.add_info("RESULT:\n", "EMPHASIS") for line in self.proc.stdout: if re.search("ERROR", line, re.IGNORECASE): @@ -284,8 +260,7 @@ def execute_commands(self): if error_counter > 0: logging.info("COMPLETED WITH {} ERRORS!".format(error_counter)) - self.zyngui.add_info( - "COMPLETED WITH {} ERRORS!".format(error_counter), "WARNING") + self.zyngui.add_info("COMPLETED WITH {} ERRORS!".format(error_counter), "WARNING") else: logging.info("COMPLETED OK!") self.zyngui.add_info("COMPLETED OK!", "SUCCESS") @@ -336,8 +311,7 @@ def killable_start_command(self, cmds): if not self.commands: logging.info("Starting Command Sequence") self.commands = cmds - self.thread = Thread( - target=self.killable_execute_commands, args=()) + self.thread = Thread(target=self.killable_execute_commands, args=()) self.thread.name = "killable command sequence" self.thread.daemon = True # thread dies with the program self.thread.start() @@ -366,7 +340,6 @@ def start_rbpi_headphones(self, save_config=True): }) # Call autoconnect after a little time zynautoconnect.request_audio_connect() - except Exception as e: logging.error(e) @@ -422,7 +395,7 @@ def hotplug_audio_menu(self): def hotplug_audio_cb(self, option, value): zynautoconnect.pause() - match(value): + match value: case "enable_hotplug": self.zyngui.state_manager.start_busy("hotplug", "Enabling hotplug audio") zynautoconnect.enable_hotplug() @@ -476,25 +449,29 @@ def toggle_midi_sys(self): "ZYNTHIAN_MIDI_SYS_ENABLED": str(int(zynthian_gui_config.midi_sys_enabled)) }) - lib_zyncore.set_midi_system_events( - zynthian_gui_config.midi_sys_enabled) + lib_zyncore.set_midi_system_events(zynthian_gui_config.midi_sys_enabled) self.update_list() def bluetooth(self): self.zyngui.show_screen("bluetooth") - def toggle_touchkeypad(self): - if zynthian_gui_config.touch_keypad: - self.zyngui.show_confirm("Do you really want to restart UI with touch-keypad disabled?", self.touchkeypad_off_confirmed) - else: - self.zyngui.show_confirm("Do you really want to restart UI with touch-keypad enabled?", self.touchkeypad_on_confirmed) + def touch_navigation_menu(self): + self.zyngui.screens['option'].config("Touch Navigation", + {"None": "", + "Touch-widgets": "touch_widgets", + "V5 keypad at left": "v5_keypad_left", + "V5 keypad at right": "v5_keypad_right"}, + self.touch_navigation_cb, + True) + self.zyngui.show_screen('option') - def touchkeypad_off_confirmed(self, params=None): - zynconf.save_config({'ZYNTHIAN_TOUCH_KEYPAD': ''}) - self.restart_gui() + def touch_navigation_cb(self, option, value): + if value != zynthian_gui_config.touch_navigation: + self.zyngui.show_confirm("Restart UI to apply touch-navigation settings?", + self.touch_navigation_cb_confirmed, value) - def touchkeypad_on_confirmed(self, params=None): - zynconf.save_config({'ZYNTHIAN_TOUCH_KEYPAD': 'V5'}) + def touch_navigation_cb_confirmed(self, value=""): + zynconf.save_config({"ZYNTHIAN_UI_TOUCH_NAVIGATION": value}) self.restart_gui() # ------------------------------------------------------------------------- @@ -623,13 +600,12 @@ def test_audio(self): self.zyngui.show_info("TEST AUDIO") # self.killable_start_command(["mpg123 {}/audio/test.mp3".format(self.data_dir)]) self.killable_start_command( - ["mplayer -nogui -noconsolecontrols -nolirc -nojoystick -really-quiet -ao jack {}/audio/test.mp3".format(self.data_dir)]) + [f"mplayer -nogui -noconsolecontrols -nolirc -nojoystick -really-quiet -ao jack {self.data_dir}/audio/test.mp3"]) zynautoconnect.request_audio_connect() def test_midi(self): logging.info("TESTING MIDI") - self.zyngui.alt_mode = self.state_manager.toggle_midi_playback( - f"{self.data_dir}/mid/test.mid") + self.zyngui.alt_mode = self.state_manager.toggle_midi_playback(f"{self.data_dir}/mid/test.mid") def control_test(self, t='S'): logging.info("TEST CONTROL HARDWARE") @@ -683,8 +659,7 @@ def exit_to_console(self): self.zyngui.exit(101) def reboot(self): - self.zyngui.show_confirm( - "Do you really want to reboot?", self.reboot_confirmed) + self.zyngui.show_confirm("Do you really want to reboot?", self.reboot_confirmed) def reboot_confirmed(self, params=None): logging.info("REBOOT") @@ -693,8 +668,7 @@ def reboot_confirmed(self, params=None): self.zyngui.exit(100) def power_off(self): - self.zyngui.show_confirm( - "Do you really want to power off?", self.power_off_confirmed) + self.zyngui.show_confirm("Do you really want to power off?", self.power_off_confirmed) def power_off_confirmed(self, params=None): logging.info("POWER OFF") diff --git a/zyngui/zynthian_gui_config.py b/zyngui/zynthian_gui_config.py index 750637af8..e5ef16c42 100644 --- a/zyngui/zynthian_gui_config.py +++ b/zyngui/zynthian_gui_config.py @@ -510,15 +510,46 @@ def get_external_storage_dirs(exdpath): # Touch Options # ------------------------------------------------------------------------------ -enable_touch_widgets = int(os.environ.get('ZYNTHIAN_UI_TOUCH_WIDGETS', 0)) -enable_touch_navigation = int(os.environ.get('ZYNTHIAN_UI_TOUCH_NAVIGATION', 0)) -force_enable_cursor = int(os.environ.get('ZYNTHIAN_UI_ENABLE_CURSOR', 0)) +touch_navigation = os.environ.get('ZYNTHIAN_UI_TOUCH_NAVIGATION', '') + +# Backward compatibility +if touch_navigation == "1": + touch_navigation = "touch_widgets" +elif touch_navigation == "0": + touch_keypad = os.environ.get('ZYNTHIAN_TOUCH_KEYPAD', '') + if touch_keypad == "V5": + touch_navigation = "v5_keypad_left" + +match touch_navigation: + case "touch_widgets": + enable_touch_navigation = True + touch_keypad_option = "" + touch_keypad_side_left = True + enable_touch_controller_switches = 1 + main_screen_column = 0 + case "v5_keypad_left": + enable_touch_navigation = False + touch_keypad_option = "V5" + touch_keypad_side_left = True + enable_touch_controller_switches = 1 + main_screen_column = 1 + case "v5_keypad_right": + enable_touch_navigation = False + touch_keypad_option = "V5" + touch_keypad_side_left = False + enable_touch_controller_switches = 1 + main_screen_column = 0 + case _: + enable_touch_navigation = False + touch_keypad_option = "" + touch_keypad_side_left = True + enable_touch_controller_switches = 0 + main_screen_column = 0 -if check_wiring_layout(["Z2", "V5"]): - # TODO: BW: Do we need to inhibit touch mimic of V5 encoders? - enable_touch_controller_switches = 0 -else: - enable_touch_controller_switches = 1 +try: + force_enable_cursor = int(os.environ.get('ZYNTHIAN_UI_ENABLE_CURSOR', 0)) +except: + force_enable_cursor = 0 # ------------------------------------------------------------------------------ # UI Options @@ -532,24 +563,6 @@ def get_external_storage_dirs(exdpath): control_test_enabled = int(os.environ.get('ZYNTHIAN_UI_CONTROL_TEST_ENABLED', 0)) power_save_secs = 60 * int(os.environ.get('ZYNTHIAN_UI_POWER_SAVE_MINUTES', 60)) - -# ------------------------------------------------------------------------------ -# Touch-keypad -# ------------------------------------------------------------------------------ - -touch_keypad = None - -touch_keypad_option = os.environ.get('ZYNTHIAN_TOUCH_KEYPAD', "") -try: - touch_keypad_side_left = int(os.environ.get('ZYNTHIAN_TOUCH_KEYPAD_SIDE_LEFT', True)) -except: - touch_keypad_side_left = True - -if touch_keypad_side_left: - main_screen_column = 1 -else: - main_screen_column = 0 - # ------------------------------------------------------------------------------ # Audio Options # ------------------------------------------------------------------------------ @@ -666,29 +679,30 @@ def color_variant(hex_color, brightness_offset=1): logging.warning("Can't get screen height. Using default 240!") display_height = 240 - touch_keypad_side_width = 0 - touch_keypad_bottom_height = 0 - - # Reduce screen dimensions if touch keypad active - if touch_keypad_option == 'V5': - touch_keypad_side_width = display_height // 3 - touch_keypad_bottom_height = display_height // 6 - screen_width = display_width - touch_keypad_side_width - screen_height = display_height - touch_keypad_bottom_height - else: - screen_width = display_width - screen_height = display_height - # Global font size font_size = int(os.environ.get('ZYNTHIAN_UI_FONT_SIZE', None)) if not font_size: font_size = int(display_width / 40) - # Activate touch keypad (font size must be set first) + # Touch Keypad enabled => if touch_keypad_option == 'V5': + # Screen dimensions < Display dimensions + touch_keypad_side_width = display_height // 3 + touch_keypad_bottom_height = display_height // 6 + screen_width = display_width - touch_keypad_side_width + screen_height = display_height - touch_keypad_bottom_height + # Create touch keypad frame and show it! from zyngui.zynthian_gui_touchkeypad_v5 import zynthian_gui_touchkeypad_v5 touch_keypad = zynthian_gui_touchkeypad_v5(top, side_width=touch_keypad_side_width, left_side=touch_keypad_side_left) touch_keypad.show() + # Touch Keypad disabled => + else: + # Screen dimensions = Display dimensions + touch_keypad_side_width = 0 + touch_keypad_bottom_height = 0 + screen_width = display_width + screen_height = display_height + touch_keypad = None # Geometric params button_width = screen_width // 4 From 5a5ecda19d2a9b9117e23bca626b7b59fa519778 Mon Sep 17 00:00:00 2001 From: jofemodo Date: Mon, 2 Dec 2024 11:43:34 +0100 Subject: [PATCH 043/117] Bugfix from latest commits. --- zyngui/zynthian_gui_control.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/zyngui/zynthian_gui_control.py b/zyngui/zynthian_gui_control.py index e48d06b39..c747b184a 100644 --- a/zyngui/zynthian_gui_control.py +++ b/zyngui/zynthian_gui_control.py @@ -766,8 +766,8 @@ def cb_listbox_release(self, event): now = monotonic() dts = now - self.listbox_push_ts - rdts = now - self.last_release - self.last_release = now + rdts = now - self.last_release_ts + self.last_release_ts = now if self.swiping: self.swipe_nudge(dts) else: From bcadda8c16e3327294aa371fa78c0208f54bcb5f Mon Sep 17 00:00:00 2001 From: riban Date: Mon, 2 Dec 2024 15:46:51 +0000 Subject: [PATCH 044/117] Fixes reset main audio output --- zyngine/zynthian_chain.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zyngine/zynthian_chain.py b/zyngine/zynthian_chain.py index 5e9f7fbdd..b6f552c16 100644 --- a/zyngine/zynthian_chain.py +++ b/zyngine/zynthian_chain.py @@ -95,7 +95,7 @@ def reset(self): self.title = "Main" self.audio_in = [] # Default use first two physical audio outputs - self.audio_out = ["system:playback_[1,2]$"] + self.audio_out = ["^system:playback_1$|^system:playback_2$"] self.audio_thru = True else: self.title = "" From ca2d156f4467c60ec3eafbea934c8af9dbcd759b Mon Sep 17 00:00:00 2001 From: riban Date: Mon, 2 Dec 2024 15:54:31 +0000 Subject: [PATCH 045/117] Always allow triple click to exit control-xy --- zyngui/zynthian_gui_control_xy.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/zyngui/zynthian_gui_control_xy.py b/zyngui/zynthian_gui_control_xy.py index d895a5f8f..411f50469 100755 --- a/zyngui/zynthian_gui_control_xy.py +++ b/zyngui/zynthian_gui_control_xy.py @@ -5,7 +5,7 @@ # # Zynthian GUI XY-Controller Class # -# Copyright (C) 2015-2022 Fernando Moyano +# Copyright (C) 2015-2024 Fernando Moyano # # ****************************************************************************** # @@ -81,10 +81,9 @@ def __init__(self): # Setup Canvas Callbacks self.canvas.bind("", self.cb_canvas) - if zynthian_gui_config.enable_touch_navigation: - self.last_tap = 0 - self.tap_count = 0 - self.canvas.bind("", self.cb_press) + self.last_tap = 0 + self.tap_count = 0 + self.canvas.bind("", self.cb_press) # Create Cursor self.hline = self.canvas.create_line( From f49d7ca34a4f2c07ec09146f585b6ae59ef10206 Mon Sep 17 00:00:00 2001 From: riban Date: Mon, 2 Dec 2024 16:33:58 +0000 Subject: [PATCH 046/117] Fixes output routing when loading snapshots --- zyngine/zynthian_state_manager.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/zyngine/zynthian_state_manager.py b/zyngine/zynthian_state_manager.py index 891bdf35d..926800e86 100644 --- a/zyngine/zynthian_state_manager.py +++ b/zyngine/zynthian_state_manager.py @@ -1464,7 +1464,11 @@ def load_zs3(self, zs3_id, autoconnect=True): try: chain.audio_out.append(f"{self.chain_manager.processors[out[0]].jackname}:{out[1]}") except: - chain.audio_out.append(out) + # Nasty temporary fix for change of output routing + if out.startswith("system:playback_["): + chain.audio_out.append("^system:playback_1$|^system:playback_2$") + elif out not in chain.audio_out: + chain.audio_out.append(out) if "audio_thru" in chain_state: chain.audio_thru = chain_state["audio_thru"] From 04b9b0dea4fba51c97f6879ea91934019c808f9e Mon Sep 17 00:00:00 2001 From: jofemodo Date: Mon, 2 Dec 2024 18:16:47 +0100 Subject: [PATCH 047/117] Fix touch navigation config to make it backward compatible when switching back to older branches. --- zyngui/zynthian_gui_admin.py | 2 +- zyngui/zynthian_gui_config.py | 16 +++++++++------- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/zyngui/zynthian_gui_admin.py b/zyngui/zynthian_gui_admin.py index 6ba45615c..98f64b257 100644 --- a/zyngui/zynthian_gui_admin.py +++ b/zyngui/zynthian_gui_admin.py @@ -471,7 +471,7 @@ def touch_navigation_cb(self, option, value): self.touch_navigation_cb_confirmed, value) def touch_navigation_cb_confirmed(self, value=""): - zynconf.save_config({"ZYNTHIAN_UI_TOUCH_NAVIGATION": value}) + zynconf.save_config({"ZYNTHIAN_UI_TOUCH_NAVIGATION2": value}) self.restart_gui() # ------------------------------------------------------------------------- diff --git a/zyngui/zynthian_gui_config.py b/zyngui/zynthian_gui_config.py index e5ef16c42..6ad58d648 100644 --- a/zyngui/zynthian_gui_config.py +++ b/zyngui/zynthian_gui_config.py @@ -510,15 +510,17 @@ def get_external_storage_dirs(exdpath): # Touch Options # ------------------------------------------------------------------------------ -touch_navigation = os.environ.get('ZYNTHIAN_UI_TOUCH_NAVIGATION', '') +touch_navigation = os.environ.get('ZYNTHIAN_UI_TOUCH_NAVIGATION2', '_UNDEF_') # Backward compatibility -if touch_navigation == "1": - touch_navigation = "touch_widgets" -elif touch_navigation == "0": - touch_keypad = os.environ.get('ZYNTHIAN_TOUCH_KEYPAD', '') - if touch_keypad == "V5": - touch_navigation = "v5_keypad_left" +if touch_navigation == "_UNDEF_": + touch_navigation = os.environ.get('ZYNTHIAN_UI_TOUCH_NAVIGATION', '') + if touch_navigation == "1": + touch_navigation = "touch_widgets" + elif touch_navigation == "0": + touch_keypad = os.environ.get('ZYNTHIAN_TOUCH_KEYPAD', '') + if touch_keypad == "V5": + touch_navigation = "v5_keypad_left" match touch_navigation: case "touch_widgets": From cee939e12b681287d15a758295fd2d7feeb75141 Mon Sep 17 00:00:00 2001 From: jofemodo Date: Mon, 2 Dec 2024 18:54:08 +0100 Subject: [PATCH 048/117] Fix audio routing when loading state. --- zyngine/zynthian_state_manager.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/zyngine/zynthian_state_manager.py b/zyngine/zynthian_state_manager.py index 926800e86..823289588 100644 --- a/zyngine/zynthian_state_manager.py +++ b/zyngine/zynthian_state_manager.py @@ -1461,14 +1461,13 @@ def load_zs3(self, zs3_id, autoconnect=True): chain.audio_out = [] if "audio_out" in chain_state: for out in chain_state["audio_out"]: - try: + if isinstance(out, list): chain.audio_out.append(f"{self.chain_manager.processors[out[0]].jackname}:{out[1]}") - except: + elif isinstance(out, str) and out.startswith("system:playback_["): # Nasty temporary fix for change of output routing - if out.startswith("system:playback_["): - chain.audio_out.append("^system:playback_1$|^system:playback_2$") - elif out not in chain.audio_out: - chain.audio_out.append(out) + chain.audio_out.append("^system:playback_1$|^system:playback_2$") + elif out not in chain.audio_out: + chain.audio_out.append(out) if "audio_thru" in chain_state: chain.audio_thru = chain_state["audio_thru"] From e3afc939581c09a1901ccd2aa56c08d9a6a7edbd Mon Sep 17 00:00:00 2001 From: jofemodo Date: Mon, 2 Dec 2024 19:06:43 +0100 Subject: [PATCH 049/117] Fix code styling from horrible PEP8 automatic formatting. --- zyngine/zynthian_state_manager.py | 127 +++++++++++------------------- 1 file changed, 48 insertions(+), 79 deletions(-) diff --git a/zyngine/zynthian_state_manager.py b/zyngine/zynthian_state_manager.py index 823289588..ed16c7f26 100644 --- a/zyngine/zynthian_state_manager.py +++ b/zyngine/zynthian_state_manager.py @@ -63,8 +63,7 @@ # ---------------------------------------------------------------------------- SNAPSHOT_SCHEMA_VERSION = 1 -capture_dir_sdc = os.environ.get( - 'ZYNTHIAN_MY_DATA_DIR', "/zynthian/zynthian-my-data") + "/capture" +capture_dir_sdc = os.environ.get('ZYNTHIAN_MY_DATA_DIR', "/zynthian/zynthian-my-data") + "/capture" ex_data_dir = os.environ.get('ZYNTHIAN_EX_DATA_DIR', "/media/root") @@ -100,11 +99,9 @@ def __init__(self): self.busy_details = None self.start_busy("zynthian_state_manager") - self.snapshot_dir = os.environ.get( - 'ZYNTHIAN_MY_DATA_DIR', "/zynthian/zynthian-my-data") + "/snapshots" + self.snapshot_dir = os.environ.get('ZYNTHIAN_MY_DATA_DIR', "/zynthian/zynthian-my-data") + "/snapshots" self.default_snapshot_fpath = join(self.snapshot_dir, "default.zss") - self.last_state_snapshot_fpath = join( - self.snapshot_dir, "last_state.zss") + self.last_state_snapshot_fpath = join(self.snapshot_dir, "last_state.zss") # Increments each time a snapshot is loaded - modules may use to update if required self.last_snapshot_count = 0 self.last_snapshot_fpath = "" @@ -152,10 +149,11 @@ def __init__(self): self.chain_manager = zynthian_chain_manager(self) self.reset_zs3() - self.alsa_mixer_processor = zynthian_processor( - "MX", {"NAME": "Mixer", "TITLE": "ALSA Mixer", "TYPE": "MIXER", "CAT": None, "ENGINE": zynthian_engine_alsa_mixer, "ENABLED": True}) - self.alsa_mixer_processor.engine = zynthian_engine_alsa_mixer( - self, self.alsa_mixer_processor) + self.alsa_mixer_processor = zynthian_processor("MX", { + "NAME": "Mixer", "TITLE": "ALSA Mixer", "TYPE": "MIXER", + "CAT": None, "ENGINE": zynthian_engine_alsa_mixer, "ENABLED": True + }) + self.alsa_mixer_processor.engine = zynthian_engine_alsa_mixer(self, self.alsa_mixer_processor) self.alsa_mixer_processor.refresh_controllers() self.audio_recorder = zynthian_audio_recorder(self) @@ -211,8 +209,7 @@ def start(self): logging.debug(f"Opened undervoltage sensor '{result[0]}'") except: try: - result = glob( - "/sys/devices/platform/soc/soc:firmware/raspberrypi-hwmon/hwmon/**/in0_lcrit_alarm')") + result = glob("/sys/devices/platform/soc/soc:firmware/raspberrypi-hwmon/hwmon/**/in0_lcrit_alarm')") self.hwmon_undervolt_file = open(result[0]) logging.debug(f"Opened undervoltage sensor '{result[0]}'") except: @@ -222,8 +219,7 @@ def start(self): # RBPi native sensors monitoring interface if self.hwmon_thermal_file is None or self.hwmon_undervolt_file is None: try: - self.get_throttled_file = open( - '/sys/devices/platform/soc/soc:firmware/get_throttled') + self.get_throttled_file = open('/sys/devices/platform/soc/soc:firmware/get_throttled') except: self.get_throttled_file = None @@ -249,8 +245,7 @@ def start(self): self.fast_thread.daemon = True # thread dies with the program self.fast_thread.start() - zynsigman.register(zynsigman.S_AUDIO_PLAYER, - self.SS_AUDIO_PLAYER_STATE, self.cb_status_audio_player) + zynsigman.register(zynsigman.S_AUDIO_PLAYER, self.SS_AUDIO_PLAYER_STATE, self.cb_status_audio_player) self.end_busy("start state") @@ -259,8 +254,7 @@ def stop(self): self.start_busy("stop state") - zynsigman.unregister(zynsigman.S_AUDIO_PLAYER, - self.SS_AUDIO_PLAYER_STATE, self.cb_status_audio_player) + zynsigman.unregister(zynsigman.S_AUDIO_PLAYER, self.SS_AUDIO_PLAYER_STATE, self.cb_status_audio_player) self.exit_flag = True if self.fast_thread and self.fast_thread.is_alive(): @@ -578,8 +572,7 @@ def slow_thread_task(self): if self.get_throttled_file: try: self.get_throttled_file.seek(0) - thr = int('0x%s' % - self.get_throttled_file.read(), 16) + thr = int('0x%s' % self.get_throttled_file.read(), 16) if thr & 0x1: self.status_undervoltage = True elif thr & (0x4 | 0x2): @@ -618,16 +611,14 @@ def slow_thread_task(self): status_midi_player = libsmf.getPlayState() if self.status_midi_player != status_midi_player: self.status_midi_player = status_midi_player - zynsigman.send( - zynsigman.S_STATE_MAN, self.SS_MIDI_PLAYER_STATE, state=status_midi_player) + zynsigman.send(zynsigman.S_STATE_MAN, self.SS_MIDI_PLAYER_STATE, state=status_midi_player) # MIDI Recorder # TODO: Add callback from MIDI recorder to avoid polling (and regular access to c-lib) status_midi_recorder = libsmf.isRecording() if self.status_midi_recorder != status_midi_recorder: self.status_midi_recorder = status_midi_recorder - zynsigman.send( - zynsigman.S_STATE_MAN, self.SS_MIDI_RECORDER_STATE, state=status_midi_recorder) + zynsigman.send(zynsigman.S_STATE_MAN, self.SS_MIDI_RECORDER_STATE, state=status_midi_recorder) # Sequencer Status => It must be improved using callbacks self.zynseq.update_state() @@ -810,11 +801,9 @@ def zynmidi_read(self): self.all_notes_off() else: if self.midi_learn_zctrl: - self.chain_manager.add_midi_learn( - chan, ccnum, self.midi_learn_zctrl, izmip) + self.chain_manager.add_midi_learn(chan, ccnum, self.midi_learn_zctrl, izmip) else: - self.zynmixer.midi_control_change( - chan, ccnum, ccval) + self.zynmixer.midi_control_change(chan, ccnum, ccval) # Master Note CUIA with ZynSwitch emulation elif evtype == 0x8 or evtype == 0x9: note = str(ev[1] & 0x7F) @@ -845,16 +834,12 @@ def zynmidi_read(self): # logging.debug("MIDI CONTROL CHANGE: CH{}, CC{} => {}".format(chan, ccnum, ccval)) if ccnum < 120: if not self.midi_learn_zctrl: - self.chain_manager.midi_control_change( - izmip, chan, ccnum, ccval) - self.zynmixer.midi_control_change( - chan, ccnum, ccval) - self.alsa_mixer_processor.midi_control_change( - chan, ccnum, ccval) - self.audio_player.midi_control_change( - chan, ccnum, ccval) - zynsigman.send_queued( - zynsigman.S_MIDI, zynsigman.SS_MIDI_CC, izmip=izmip, chan=chan, num=ccnum, val=ccval) + self.chain_manager.midi_control_change(izmip, chan, ccnum, ccval) + self.zynmixer.midi_control_change(chan, ccnum, ccval) + self.alsa_mixer_processor.midi_control_change(chan, ccnum, ccval) + self.audio_player.midi_control_change(chan, ccnum, ccval) + zynsigman.send_queued(zynsigman.S_MIDI, zynsigman.SS_MIDI_CC, + izmip=izmip, chan=chan, num=ccnum, val=ccval) # Special CCs >= Channel Mode elif ccnum == 120: self.all_sounds_off_chan(chan) @@ -886,11 +871,10 @@ def zynmidi_read(self): # Sends to active chain's MIDI channel when device uses ACTI mode if zynautoconnect.get_midi_in_dev_mode(izmip): chan = self.chain_manager.get_active_chain().midi_chan - send_signal = self.chain_manager.set_midi_prog_preset( - chan, pgm) + send_signal = self.chain_manager.set_midi_prog_preset(chan, pgm) if send_signal: - zynsigman.send_queued( - zynsigman.S_MIDI, zynsigman.SS_MIDI_PC, izmip=izmip, chan=chan, num=pgm) + zynsigman.send_queued(zynsigman.S_MIDI, zynsigman.SS_MIDI_PC, + izmip=izmip, chan=chan, num=pgm) # Note Off elif evtype == 0x8: @@ -1025,7 +1009,6 @@ def export_chain(self, fpath, chain_id): except: pass - for key in ["last_snapshot_fpath", "midi_profile_state", "engine_config", "audio_recorder_armed", "zynseq_riff_b64", "alsa_mixer", "zyngui"]: try: del state[key] @@ -1448,8 +1431,7 @@ def load_zs3(self, zs3_id, autoconnect=True): if "transpose_semitone" in chain_state: lib_zyncore.zmop_set_transpose_semitone(chain.zmop_index, chain_state["transpose_semitone"]) else: - lib_zyncore.zmop_set_transpose_semitone( - chain.zmop_index, 0) + lib_zyncore.zmop_set_transpose_semitone(chain.zmop_index, 0) if "midi_in" in chain_state: chain.midi_in = chain_state["midi_in"] if "midi_out" in chain_state: @@ -1600,12 +1582,10 @@ def save_zs3(self, zs3_id=None, title=None): note_high = lib_zyncore.zmop_get_note_high(chain.zmop_index) if note_high < 127: chain_state["note_high"] = note_high - transpose_octave = lib_zyncore.zmop_get_transpose_octave( - chain.zmop_index) + transpose_octave = lib_zyncore.zmop_get_transpose_octave(chain.zmop_index) if transpose_octave: chain_state["transpose_octave"] = transpose_octave - transpose_semitone = lib_zyncore.zmop_get_transpose_semitone( - chain.zmop_index) + transpose_semitone = lib_zyncore.zmop_get_transpose_semitone(chain.zmop_index) if transpose_semitone: chain_state["transpose_semitone"] = transpose_semitone if chain.midi_in: @@ -1635,8 +1615,7 @@ def save_zs3(self, zs3_id=None, title=None): chain_state["midi_cc"] = {} chain_state["midi_cc"][cc] = [] for zctrl in zctrls: - chain_state["midi_cc"][cc].append( - [zctrl.processor.id, zctrl.symbol]) + chain_state["midi_cc"][cc].append([zctrl.processor.id, zctrl.symbol]) if chain_state: chain_states[chain_id] = chain_state if chain_states: @@ -2003,22 +1982,18 @@ def init_midi(self): """Initialise MIDI configuration""" try: # Set active MIDI channel - lib_zyncore.set_active_midi_chan( - zynthian_gui_config.active_midi_channel) + lib_zyncore.set_active_midi_chan(zynthian_gui_config.active_midi_channel) # Set Global Tuning self.fine_tuning_freq = zynthian_gui_config.midi_fine_tuning lib_zyncore.set_tuning_freq(ctypes.c_double(self.fine_tuning_freq)) # Set MIDI Master Channel - lib_zyncore.set_midi_master_chan( - zynthian_gui_config.master_midi_channel) + lib_zyncore.set_midi_master_chan(zynthian_gui_config.master_midi_channel) # Set MIDI System Messages flag - lib_zyncore.set_midi_system_events( - zynthian_gui_config.midi_sys_enabled) + lib_zyncore.set_midi_system_events(zynthian_gui_config.midi_sys_enabled) # Setup MIDI filter rules if self.midi_filter_script: self.midi_filter_script.clean() - self.midi_filter_script = zynthian_midi_filter.MidiFilterScript( - zynthian_gui_config.midi_filter_rules) + self.midi_filter_script = zynthian_midi_filter.MidiFilterScript(zynthian_gui_config.midi_filter_rules) except Exception as e: logging.error(f"ERROR initializing MIDI : {e}") @@ -2074,8 +2049,7 @@ def set_transport_clock_source(self, val=None, save_config=False): if val > 0: lib_zyncore.set_midi_system_events(1) else: - lib_zyncore.set_midi_system_events( - zynthian_gui_config.midi_sys_enabled) + lib_zyncore.set_midi_system_events(zynthian_gui_config.midi_sys_enabled) # Save config if save_config: @@ -2130,8 +2104,7 @@ def reset_midi_profile(self): def create_audio_player(self): if not self.audio_player: try: - self.audio_player = zynthian_processor( - "AP", self.chain_manager.engine_info["AP"]) + self.audio_player = zynthian_processor("AP", self.chain_manager.engine_info["AP"]) self.chain_manager.start_engine(self.audio_player, "AP") except Exception as e: logging.error( @@ -2189,8 +2162,7 @@ def start_midi_record(self): if not libsmf.isRecording(): libsmf.unload(self.smf_recorder) libsmf.startRecording() - zynsigman.send(zynsigman.S_STATE_MAN, - self.SS_MIDI_RECORDER_STATE, state=True) + zynsigman.send(zynsigman.S_STATE_MAN, self.SS_MIDI_RECORDER_STATE, state=True) return True else: return False @@ -2207,8 +2179,7 @@ def stop_midi_record(self): self.last_midi_file = fpath result = True - zynsigman.send(zynsigman.S_STATE_MAN, - self.SS_MIDI_RECORDER_STATE, state=False) + zynsigman.send(zynsigman.S_STATE_MAN, self.SS_MIDI_RECORDER_STATE, state=False) return result @@ -2250,8 +2221,7 @@ def start_midi_playback(self, fpath): self.zynseq.transport_start("zynsmf") if libsmf.getPlayState() != zynsmf.PLAY_STATE_STOPPED: self.status_midi_player = True - zynsigman.send(zynsigman.S_STATE_MAN, - self.SS_MIDI_PLAYER_STATE, state=True) + zynsigman.send(zynsigman.S_STATE_MAN, self.SS_MIDI_PLAYER_STATE, state=True) self.status_midi_player = False self.last_midi_file = fpath # self.zynseq.libseq.transportLocate(0) @@ -2264,8 +2234,7 @@ def stop_midi_playback(self): if libsmf.getPlayState() != zynsmf.PLAY_STATE_STOPPED: libsmf.stopPlayback() self.status_midi_player = False - zynsigman.send(zynsigman.S_STATE_MAN, - self.SS_MIDI_PLAYER_STATE, state=False) + zynsigman.send(zynsigman.S_STATE_MAN, self.SS_MIDI_PLAYER_STATE, state=False) return self.status_midi_player def toggle_midi_playback(self, fname=None): @@ -2769,10 +2738,10 @@ def update_thread(): path = f"/zynthian/{repo}" branch = get_repo_branch(path) # Get last tag release - check_output(["git", "-C", path, "remote", "update", "origin", "--prune"], encoding="utf-8", - stderr=STDOUT) - stags = check_output(["git", "-C", path, "tag", "-l", f"{stable_branch}-*"], encoding="utf-8", - stderr=STDOUT).strip().split("\n") + check_output(["git", "-C", path, "remote", "update", "origin", "--prune"], + encoding="utf-8", stderr=STDOUT) + stags = check_output(["git", "-C", path, "tag", "-l", f"{stable_branch}-*"], + encoding="utf-8", stderr=STDOUT).strip().split("\n") last_stag = stags[-1].strip() #logging.debug(f"STABLE TAG RELEASES => {stags}") if branch != last_stag: @@ -2784,10 +2753,10 @@ def update_thread(): for repo in repos: path = f"/zynthian/{repo}" branch = get_repo_branch(path) - local_hash = check_output(["git", "-C", path, "rev-parse", "HEAD"], encoding="utf-8", - stderr=STDOUT).strip() - remote_hash = check_output(["git", "-C", path, "ls-remote", "origin", branch], encoding="utf-8", - stderr=STDOUT).strip().split("\t")[0] + local_hash = check_output(["git", "-C", path, "rev-parse", "HEAD"], + encoding="utf-8", stderr=STDOUT).strip() + remote_hash = check_output(["git", "-C", path, "ls-remote", "origin", branch], + encoding="utf-8", stderr=STDOUT).strip().split("\t")[0] #logging.debug(f"*********** BRANCH {branch} => local hash {local_hash}, remote hash {remote_hash} ****************") if local_hash != remote_hash: self.update_available = True From 5ea745be25385aab8c0b46e5bb441268c19103b2 Mon Sep 17 00:00:00 2001 From: riban Date: Tue, 3 Dec 2024 07:35:55 +0000 Subject: [PATCH 050/117] Remove button colouring on hover --- zyngui/zynthian_gui_touchkeypad_v5.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/zyngui/zynthian_gui_touchkeypad_v5.py b/zyngui/zynthian_gui_touchkeypad_v5.py index 820fc9cf7..a77f885c3 100644 --- a/zyngui/zynthian_gui_touchkeypad_v5.py +++ b/zyngui/zynthian_gui_touchkeypad_v5.py @@ -208,7 +208,7 @@ def add_button(self, n, parent, row, column, zynswitch, label, padx, pady): height= 1, bg=zynthian_gui_config.color_bg, fg=zynthian_gui_config.color_header_tx, - activebackground=zynthian_gui_config.color_panel_bg, + activebackground=zynthian_gui_config.color_bg, activeforeground=zynthian_gui_config.color_header_tx, highlightbackground=zynthian_gui_config.color_panel_bg, highlightcolor=zynthian_gui_config.color_bg, @@ -300,7 +300,7 @@ def set_button_color(self, led_num, color, mode): # plain text labels may just change the color and possibly also its label if a special label # is associated with the requested mode (<=color) in the button definition self.refresh_button_label(n, mode) - self.buttons[n].config(fg=color) + self.buttons[n].config(fg=color, activeforeground=color) def refresh_button_label(self, n, mode): text = self.btndefs[n][0].get(mode, self.btndefs[n][0]['default']).replace('/', "\n") From d42feb42c3ea145c76b8d60ed56164479298a2cd Mon Sep 17 00:00:00 2001 From: riban Date: Tue, 3 Dec 2024 09:52:34 +0000 Subject: [PATCH 051/117] Fix missing import --- zynthian_main.py | 1 + 1 file changed, 1 insertion(+) diff --git a/zynthian_main.py b/zynthian_main.py index c39156ae6..25fe86109 100755 --- a/zynthian_main.py +++ b/zynthian_main.py @@ -28,6 +28,7 @@ import ctypes import logging from tkinter import EventType +from time import sleep # Zynthian specific modules from zyngui import zynthian_gui_config From 90cd26bc092bd13d8b3609956db18ee7bc346ef8 Mon Sep 17 00:00:00 2001 From: jofemodo Date: Tue, 3 Dec 2024 13:14:55 +0100 Subject: [PATCH 052/117] + Improve robustness of touch-keypad GUI module. + Improve touch-keypad color scheme and icon resizing. --- icons/metronome.png | Bin 654 -> 2510 bytes zyngui/zynthian_gui.py | 7 ++- zyngui/zynthian_gui_config.py | 16 +++-- zyngui/zynthian_gui_touchkeypad_v5.py | 82 +++++++++++++++----------- 4 files changed, 63 insertions(+), 42 deletions(-) diff --git a/icons/metronome.png b/icons/metronome.png index 51065c080187af0c4dd1240a3107b0a64b5a7b0a..349b03b680e2718262ad91531a16030cca6fa4e8 100644 GIT binary patch delta 2499 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 O0000 if touch_keypad_option == 'V5': # Screen dimensions < Display dimensions @@ -694,17 +695,20 @@ def color_variant(hex_color, brightness_offset=1): screen_width = display_width - touch_keypad_side_width screen_height = display_height - touch_keypad_bottom_height # Create touch keypad frame and show it! - from zyngui.zynthian_gui_touchkeypad_v5 import zynthian_gui_touchkeypad_v5 - touch_keypad = zynthian_gui_touchkeypad_v5(top, side_width=touch_keypad_side_width, left_side=touch_keypad_side_left) - touch_keypad.show() - # Touch Keypad disabled => - else: + try: + from zyngui.zynthian_gui_touchkeypad_v5 import zynthian_gui_touchkeypad_v5 + touch_keypad = zynthian_gui_touchkeypad_v5(top, side_width=touch_keypad_side_width, left_side=touch_keypad_side_left) + touch_keypad.show() + except Exception as e: + logging.error(f"Can't start touch keypad {touch_keypad_option} => {e}") + + # Touch Keypad disabled or failed to start => + if not touch_keypad: # Screen dimensions = Display dimensions touch_keypad_side_width = 0 touch_keypad_bottom_height = 0 screen_width = display_width screen_height = display_height - touch_keypad = None # Geometric params button_width = screen_width // 4 diff --git a/zyngui/zynthian_gui_touchkeypad_v5.py b/zyngui/zynthian_gui_touchkeypad_v5.py index 820fc9cf7..0929f83f1 100644 --- a/zyngui/zynthian_gui_touchkeypad_v5.py +++ b/zyngui/zynthian_gui_touchkeypad_v5.py @@ -25,8 +25,9 @@ import os import tkinter -from PIL import Image, ImageTk from io import BytesIO +from PIL import Image, ImageTk + try: import cairosvg except: @@ -68,14 +69,14 @@ 'F4': ({'default': 'F4', 'alt': 'F8'}, 23, 19) } -FKEY2SWITCH = [ BUTTONS['F1'][1], BUTTONS['F2'][1], BUTTONS['F3'][1], BUTTONS['F4'][1]] +FKEY2SWITCH = [BUTTONS['F1'][1], BUTTONS['F2'][1], BUTTONS['F3'][1], BUTTONS['F4'][1]] LED2BUTTON = {btn[2]: btn[1]-4 for btn in BUTTONS.values()} # Layout definitions LAYOUT_RIGHT = { - 'SIDE' : ( + 'SIDE': ( ('OPT_ADMIN', 'MIX_LEVEL'), ('CTRL_PRESET', 'ZS3_SHOT'), ('METRONOME', 'PAD_STEP'), @@ -87,7 +88,7 @@ } LAYOUT_LEFT = { - 'SIDE' : ( + 'SIDE': ( ('OPT_ADMIN', 'MIX_LEVEL'), ('CTRL_PRESET', 'ZS3_SHOT'), ('METRONOME', 'PAD_STEP'), @@ -102,6 +103,7 @@ # Zynthian Touchscreen Keypad V5 Class # ------------------------------------------------------------------------------ + class zynthian_gui_touchkeypad_v5: def __init__(self, parent, side_width, left_side=True): @@ -163,8 +165,8 @@ def __init__(self, parent, side_width, left_side=True): zynswitch = btn[1] n = zynswitch - 4 label = btn[0]['default'] - pady = (1,0) if row == 5 else (0,0) if row == 4 else (0,1) - padx = (0,1) if left_side else (1,0) + pady = (1, 0) if row == 5 else (0, 0) if row == 4 else (0, 1) + padx = (0, 1) if left_side else (1, 0) self.btndefs[n] = btn self.buttons[n] = self.add_button(n, self.side_frame, row, col, zynswitch, label, padx, pady) # create bottom frame buttons @@ -173,14 +175,13 @@ def __init__(self, parent, side_width, left_side=True): zynswitch = btn[1] n = zynswitch - 4 label = btn[0]['default'] - padx = (0,0) if col == 7 else (0,1) + padx = (0, 0) if col == 7 else (0, 1) self.btndefs[n] = btn - self.buttons[n] = self.add_button(n, self.bottom_frame, 0, col, zynswitch, label, padx, (1,0)) + self.buttons[n] = self.add_button(n, self.bottom_frame, 0, col, zynswitch, label, padx, (1, 0)) # update with user settings from the environment self.apply_user_config() - def add_button(self, n, parent, row, column, zynswitch, label, padx, pady): """ Create button @@ -202,16 +203,20 @@ def add_button(self, n, parent, row, column, zynswitch, label, padx, pady): pady : (int, int) Button padding """ + bg_color = "#282C30" + #border_color = zynthian_gui_config.color_variant(bg_color, 30) + border_color = zynthian_gui_config.color_bg + text_color = zynthian_gui_config.color_header_tx button = tkinter.Button( parent, - width= 1, - height= 1, - bg=zynthian_gui_config.color_bg, - fg=zynthian_gui_config.color_header_tx, - activebackground=zynthian_gui_config.color_panel_bg, - activeforeground=zynthian_gui_config.color_header_tx, - highlightbackground=zynthian_gui_config.color_panel_bg, - highlightcolor=zynthian_gui_config.color_bg, + width=1, + height=1, + bg=bg_color, + fg=text_color, + activebackground=border_color, + activeforeground=border_color, + highlightbackground=border_color, + highlightcolor=border_color, highlightthickness=1, bd=0, relief='flat') @@ -219,32 +224,43 @@ def add_button(self, n, parent, row, column, zynswitch, label, padx, pady): self.btnstate[n] = zynthian_gui_config.color_header_tx if label.startswith('_'): # button contains an icon/image instead of a label + img_width = int(1.8 * zynthian_gui_config.font_size) img_name = label[1:] if img_name.endswith('.svg'): # convert SVG icon into PNG of appropriate size if cairosvg: png = BytesIO() - cairosvg.svg2png(url=img_name, write_to=png, output_width=int(1.2*zynthian_gui_config.font_size)) + cairosvg.svg2png(url=img_name, write_to=png, output_width=img_width) + image = Image.open(png) else: png = img_name[:-4]+".png" - image = Image.open(png) - else: + image = Image.open(png) + img_height = int(img_width * image.size[1] / image.size[0]) + image = image.resize((img_width, img_height), Image.Resampling.LANCZOS) + + elif img_name.endswith('.png'): # PNG icons can be imported directly image = Image.open(img_name) - # store the original image for the purpose of later changes of color (useful for image icons) - self.images[n] = image - tkimage = ImageTk.PhotoImage(image) - # if we don't keep the image in the object, - # it will be discarded by garbage collection at the end of this method! - self.tkimages[n] = tkimage - button.config(image=tkimage, text='') + img_height = int(img_width * image.size[1] / image.size[0]) + image = image.resize((img_width, img_height), Image.Resampling.LANCZOS) + else: + image = None + if image: + # store the original image for the purpose of later changes of color (useful for image icons) + self.images[n] = image + tkimage = ImageTk.PhotoImage(image) + # if we don't keep the image in the object, + # it will be discarded by garbage collection at the end of this method! + self.tkimages[n] = tkimage + button.config(image=tkimage, text='') else: # button has a simple text label: either standard text # or an icon included in the "forkawesome" font (unicode char >= \uf000) - button.config(font=("forkawesome", - int(1*zynthian_gui_config.font_size)) if label[0] >= '\uf000' - else (zynthian_gui_config.font_family, int(0.9*zynthian_gui_config.font_size)), - text=label.replace('/', "\n")) + if label[0] >= '\uf000': + font = ("forkawesome", int(1.0 * zynthian_gui_config.font_size)) + else: + font = (zynthian_gui_config.font_family, int(0.9 * zynthian_gui_config.font_size)) + button.config(font=font, text=label.replace('/', "\n")) button.grid_propagate(False) button.grid(row=row, column=column, sticky='nswe', padx=padx, pady=pady) button.bind('', lambda e: self.cb_button_push(zynswitch, e)) @@ -291,7 +307,7 @@ def set_button_color(self, led_num, color, mode): image = self.images[n] mask = image.convert("LA") bgimage = Image.new("RGBA", image.size, color) - fgimage = Image.new("RGBA", image.size, (0,0,0,0)) + fgimage = Image.new("RGBA", image.size, (0, 0, 0, 0)) composed = Image.composite(bgimage, fgimage, mask) tkimage = ImageTk.PhotoImage(composed) self.tkimages[n] = tkimage @@ -340,7 +356,7 @@ def _fkey2btn(self, n): if n >= 4: mode = 'alt' n -= 4 - return (FKEY2SWITCH[n]-4, mode) + return FKEY2SWITCH[n]-4, mode def set_fkey_label(self, n, label): btn, mode = self._fkey2btn(n) From f3584a49ec2d657863ae6826476888786f12d0c9 Mon Sep 17 00:00:00 2001 From: jofemodo Date: Tue, 3 Dec 2024 14:06:55 +0100 Subject: [PATCH 053/117] Fix touch-keypah on-over background. --- zyngui/zynthian_gui_touchkeypad_v5.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/zyngui/zynthian_gui_touchkeypad_v5.py b/zyngui/zynthian_gui_touchkeypad_v5.py index b16745ccf..a9cce55c9 100644 --- a/zyngui/zynthian_gui_touchkeypad_v5.py +++ b/zyngui/zynthian_gui_touchkeypad_v5.py @@ -123,7 +123,8 @@ def __init__(self, parent, side_width, left_side=True): self.side_frame_col = 0 if left_side else 1 self.bottom_frame_col = 1 if left_side else 0 self.font_size = zynthian_gui_config.font_size - self.bg_color = zynthian_gui_config.color_variant(zynthian_gui_config.color_panel_bg, -26) + self.bg_color = zynthian_gui_config.color_variant(zynthian_gui_config.color_panel_bg, -28) + self.bg_color_over = zynthian_gui_config.color_variant(zynthian_gui_config.color_panel_bg, -22) self.border_color = zynthian_gui_config.color_bg self.text_color = zynthian_gui_config.color_header_tx @@ -212,9 +213,9 @@ def add_button(self, n, parent, row, column, zynswitch, label, padx, pady): height=1, bg=self.bg_color, fg=self.text_color, - activebackground=self.border_color, + activebackground=self.bg_color_over, activeforeground=self.border_color, - highlightbackground=self.border_color, + highlightbackground=self.bg_color_over, highlightcolor=self.border_color, highlightthickness=1, bd=0, From a81efcb408a5ea86e8af66ddfbedcd652fa86205 Mon Sep 17 00:00:00 2001 From: jofemodo Date: Tue, 3 Dec 2024 16:30:23 +0100 Subject: [PATCH 054/117] Fix border color in touch keypad buttons. --- zyngui/zynthian_gui_touchkeypad_v5.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zyngui/zynthian_gui_touchkeypad_v5.py b/zyngui/zynthian_gui_touchkeypad_v5.py index a9cce55c9..8bbe984de 100644 --- a/zyngui/zynthian_gui_touchkeypad_v5.py +++ b/zyngui/zynthian_gui_touchkeypad_v5.py @@ -215,7 +215,7 @@ def add_button(self, n, parent, row, column, zynswitch, label, padx, pady): fg=self.text_color, activebackground=self.bg_color_over, activeforeground=self.border_color, - highlightbackground=self.bg_color_over, + highlightbackground=self.border_color, highlightcolor=self.border_color, highlightthickness=1, bd=0, From 916a20781bb7d754abb1c4fff249c7133714a6fa Mon Sep 17 00:00:00 2001 From: jofemodo Date: Tue, 3 Dec 2024 18:01:35 +0100 Subject: [PATCH 055/117] Code re-styling. Too many break lines from the PEP8 refact!! --- zyngui/zynthian_gui.py | 252 ++++++++++++++--------------------------- 1 file changed, 84 insertions(+), 168 deletions(-) diff --git a/zyngui/zynthian_gui.py b/zyngui/zynthian_gui.py index 9536b1c42..04e0b8ea4 100644 --- a/zyngui/zynthian_gui.py +++ b/zyngui/zynthian_gui.py @@ -112,10 +112,8 @@ class zynthian_gui: SCREEN_HMODE_RESET = 3 def __init__(self): - self.capture_dir_sdc = os.environ.get( - 'ZYNTHIAN_MY_DATA_DIR', "/zynthian/zynthian-my-data") + "/capture" - self.ex_data_dir = os.environ.get( - 'ZYNTHIAN_EX_DATA_DIR', "/media/root") + self.capture_dir_sdc = os.environ.get('ZYNTHIAN_MY_DATA_DIR', "/zynthian/zynthian-my-data") + "/capture" + self.ex_data_dir = os.environ.get('ZYNTHIAN_EX_DATA_DIR', "/media/root") self.test_mode = False self.alt_mode = False @@ -189,20 +187,17 @@ def __init__(self): def start_capture_log(self, title="ui_sesion"): now = datetime.now() self.capture_log_ts0 = now - self.capture_log_fname = "{}-{}".format( - title, now.strftime("%Y%m%d%H%M%S")) + self.capture_log_fname = "{}-{}".format(title, now.strftime("%Y%m%d%H%M%S")) self.start_capture_ffmpeg() if self.wsleds: self.wsleds.reset_last_state() - self.write_capture_log("LAYOUT: {}".format( - zynthian_gui_config.wiring_layout)) + self.write_capture_log("LAYOUT: {}".format(zynthian_gui_config.wiring_layout)) self.write_capture_log("TITLE: {}".format(self.capture_log_fname)) zynautoconnect.audio_connect_ffmpeg(timeout=2.0) def start_capture_ffmpeg(self): fbdev = os.environ.get("FRAMEBUFFER", "/dev/fb0") - fpath = "{}/{}.mp4".format(self.capture_dir_sdc, - self.capture_log_fname) + fpath = "{}/{}.mp4".format(self.capture_dir_sdc, self.capture_log_fname) self.capture_ffmpeg_proc = ffmpeg.output( ffmpeg.input(":0", r=20, f="x11grab"), # ffmpeg.input(fbdev, r=20, f="fbdev"), @@ -227,8 +222,7 @@ def write_capture_log(self, message): if self.capture_log_fname: try: rts = str(datetime.now() - self.capture_log_ts0) - fh = open("{}/{}.log".format(self.capture_dir_sdc, - self.capture_log_fname), 'a') + fh = open("{}/{}.log".format(self.capture_dir_sdc, self.capture_log_fname), 'a') fh.write("{} {}\n".format(rts, message)) fh.close() except Exception as e: @@ -265,10 +259,8 @@ def wiring_midi_setup(current_chan=None): if event is not None: swi = 4 + i if event['type'] >= 0xF8: - lib_zyncore.setup_zynswitch_midi( - swi, event['type'], 0, 0, 0) - logging.info("MIDI ZYNSWITCH {}: SYSRT {}".format( - swi, event['type'])) + lib_zyncore.setup_zynswitch_midi(swi, event['type'], 0, 0, 0) + logging.info("MIDI ZYNSWITCH {}: SYSRT {}".format(swi, event['type'])) else: if event['chan'] is not None: midi_chan = event['chan'] @@ -276,14 +268,11 @@ def wiring_midi_setup(current_chan=None): midi_chan = current_chan if midi_chan is not None: - lib_zyncore.setup_zynswitch_midi( - swi, event['type'], midi_chan, event['num'], event['val']) - logging.info("MIDI ZYNSWITCH {}: {} CH#{}, {}, {}".format( - swi, event['type'], midi_chan, event['num'], event['val'])) + lib_zyncore.setup_zynswitch_midi(swi, event['type'], midi_chan, event['num'], event['val']) + logging.info("MIDI ZYNSWITCH {}: {} CH#{}, {}, {}".format(swi, event['type'], midi_chan, event['num'], event['val'])) else: lib_zyncore.setup_zynswitch_midi(swi, 0, 0, 0, 0) - logging.info( - "MIDI ZYNSWITCH {}: DISABLED!".format(swi)) + logging.info("MIDI ZYNSWITCH {}: DISABLED!".format(swi)) # Configure Zynaptik Analog Inputs (CV-IN) for i, event in enumerate(zynthian_gui_config.zynaptik_ad_midi_events): @@ -294,10 +283,8 @@ def wiring_midi_setup(current_chan=None): midi_chan = current_chan if midi_chan is not None: - lib_zyncore.zynaptik_setup_cvin( - i, event['type'], midi_chan, event['num']) - logging.info("ZYNAPTIK CV-IN {}: {} CH#{}, {}".format(i, - event['type'], midi_chan, event['num'])) + lib_zyncore.zynaptik_setup_cvin(i, event['type'], midi_chan, event['num']) + logging.info("ZYNAPTIK CV-IN {}: {} CH#{}, {}".format(i, event['type'], midi_chan, event['num'])) else: lib_zyncore.zynaptik_disable_cvin(i) logging.info("ZYNAPTIK CV-IN {}: DISABLED!".format(i)) @@ -311,10 +298,8 @@ def wiring_midi_setup(current_chan=None): midi_chan = current_chan if midi_chan is not None: - lib_zyncore.zynaptik_setup_cvout( - i, event['type'], midi_chan, event['num']) - logging.info("ZYNAPTIK CV-OUT {}: {} CH#{}, {}".format(i, - event['type'], midi_chan, event['num'])) + lib_zyncore.zynaptik_setup_cvout(i, event['type'], midi_chan, event['num']) + logging.info("ZYNAPTIK CV-OUT {}: {} CH#{}, {}".format(i, event['type'], midi_chan, event['num'])) else: lib_zyncore.zynaptik_disable_cvout(i) logging.info("ZYNAPTIK CV-OUT {}: DISABLED!".format(i)) @@ -328,10 +313,8 @@ def wiring_midi_setup(current_chan=None): midi_chan = current_chan if midi_chan is not None: - lib_zyncore.setup_zyntof( - i, event['type'], midi_chan, event['num']) - logging.info("ZYNTOF {}: {} CH#{}, {}".format( - i, event['type'], midi_chan, event['num'])) + lib_zyncore.setup_zyntof(i, event['type'], midi_chan, event['num']) + logging.info("ZYNTOF {}: {} CH#{}, {}".format(i, event['type'], midi_chan, event['num'])) else: lib_zyncore.disable_zyntof(i) logging.info("ZYNTOF {}: DISABLED!".format(i)) @@ -353,18 +336,14 @@ def reload_wiring_layout(self): def osc_init(self): try: - self.osc_server = liblo.Server( - self.osc_server_port, self.osc_proto) + self.osc_server = liblo.Server(self.osc_server_port, self.osc_proto) self.osc_server_port = self.osc_server.get_port() - self.osc_server_url = liblo.Address( - 'localhost', self.osc_server_port, self.osc_proto).get_url() - logging.info( - "ZYNTHIAN-UI OSC server running in port {}".format(self.osc_server_port)) + self.osc_server_url = liblo.Address('localhost', self.osc_server_port, self.osc_proto).get_url() + logging.info("ZYNTHIAN-UI OSC server running in port {}".format(self.osc_server_port)) self.osc_server.add_method(None, None, self.osc_cb_all) # except liblo.AddressError as err: except Exception as err: - logging.error( - "ZYNTHIAN-UI OSC Server can't be started: {}".format(err)) + logging.error("ZYNTHIAN-UI OSC Server can't be started: {}".format(err)) def osc_end(self): if self.osc_server: @@ -372,8 +351,7 @@ def osc_end(self): self.osc_server.free() logging.info("ZYNTHIAN-UI OSC server stopped") except Exception as err: - logging.error( - "ZYNTHIAN-UI OSC server can't be stopped: {}".format(err)) + logging.error("ZYNTHIAN-UI OSC server can't be stopped: {}".format(err)) self.osc_server = None def osc_receive(self): @@ -392,8 +370,7 @@ def osc_cb_all(self, path, args, types, src): # Execute action cuia = parts[2].upper() if self.state_manager.is_busy(): - logging.debug( - "BUSY! Ignoring OSC CUIA '{}' => {}".format(cuia, args)) + logging.debug("BUSY! Ignoring OSC CUIA '{}' => {}".format(cuia, args)) return self.cuia_queue.put_nowait((cuia, args)) # Run autoconnect if needed @@ -406,38 +383,28 @@ def osc_cb_all(self, path, args, types, src): if src.hostname not in self.osc_clients: try: if self.state_manager.zynmixer.add_osc_client(src.hostname) < 0: - logging.warning( - "Failed to add OSC client registration {}".format(src.hostname)) + logging.warning("Failed to add OSC client registration {}".format(src.hostname)) return except: - logging.warning( - "Error trying to add OSC client registration {}".format(src.hostname)) + logging.warning("Error trying to add OSC client registration {}".format(src.hostname)) return self.osc_clients[src.hostname] = monotonic() - self.state_manager.zynmixer.enable_dpm( - 0, self.state_manager.zynmixer.MAX_NUM_CHANNELS - 2, True) + self.state_manager.zynmixer.enable_dpm(0, self.state_manager.zynmixer.MAX_NUM_CHANNELS - 2, True) else: if part2[:6] == "VOLUME": - self.state_manager.zynmixer.set_level( - int(part2[6:]), float(args[0])) + self.state_manager.zynmixer.set_level(int(part2[6:]), float(args[0])) if part2[:5] == "FADER": - self.state_manager.zynmixer.set_level( - int(part2[5:]), float(args[0])) + self.state_manager.zynmixer.set_level(int(part2[5:]), float(args[0])) if part2[:5] == "LEVEL": - self.state_manager.zynmixer.set_level( - int(part2[5:]), float(args[0])) + self.state_manager.zynmixer.set_level(int(part2[5:]), float(args[0])) elif part2[:7] == "BALANCE": - self.state_manager.zynmixer.set_balance( - int(part2[7:]), float(args[0])) + self.state_manager.zynmixer.set_balance(int(part2[7:]), float(args[0])) elif part2[:4] == "MUTE": - self.state_manager.zynmixer.set_mute( - int(part2[4:]), int(args[0])) + self.state_manager.zynmixer.set_mute(int(part2[4:]), int(args[0])) elif part2[:4] == "SOLO": - self.state_manager.zynmixer.set_solo( - int(part2[4:]), int(args[0])) + self.state_manager.zynmixer.set_solo(int(part2[4:]), int(args[0])) elif part2[:4] == "MONO": - self.state_manager.zynmixer.set_mono( - int(part2[4:]), int(args[0])) + self.state_manager.zynmixer.set_mono(int(part2[4:]), int(args[0])) else: logging.warning(f"Not supported OSC call '{path}'") @@ -685,16 +652,13 @@ def close_screen(self, screen=None): last_screen = "audio_mixer" if last_screen not in self.screens: - logging.error( - f"Can't back to screen '{last_screen}'. It doesn't exist!") + logging.error(f"Can't back to screen '{last_screen}'. It doesn't exist!") last_screen = "audio_mixer" - logging.debug( - f"CLOSE SCREEN '{self.current_screen}' => Back to '{last_screen}'") + logging.debug(f"CLOSE SCREEN '{self.current_screen}' => Back to '{last_screen}'") self.show_screen(last_screen) def purge_screen_history(self, screen): - self.screen_history = list( - filter(lambda i: i != screen, self.screen_history)) + self.screen_history = list(filter(lambda i: i != screen, self.screen_history)) def prune_screen_history(self, screen, soft=True): logging.debug(f"SCREEN HISTORY => {self.screen_history}") @@ -706,8 +670,7 @@ def prune_screen_history(self, screen, soft=True): self.screen_history.append(screen) except: pass - logging.debug( - f"PRUNE '{screen}' FROM SCREEN HISTORY => {self.screen_history}") + logging.debug(f"PRUNE '{screen}' FROM SCREEN HISTORY => {self.screen_history}") def back_screen(self): try: @@ -777,8 +740,7 @@ def hide_info(self): def hide_info_timer(self, tms=3000): if self.current_screen == 'info': self.cancel_screen_timer() - self.screen_timer_id = zynthian_gui_config.top.after( - tms, self.hide_info) + self.screen_timer_id = zynthian_gui_config.top.after(tms, self.hide_info) def show_splash(self, text): self.screen_lock.acquire() @@ -987,15 +949,13 @@ def chain_control(self, chain_id=None, processor=None, hmode=SCREEN_HMODE_RESET, custom_screen_name = module_name[len("zynthian_gui_"):] if custom_screen_name not in self.screens: try: - spec = importlib.util.spec_from_file_location( - module_name, module_path) + spec = importlib.util.spec_from_file_location(module_name, module_path) module = importlib.util.module_from_spec(spec) spec.loader.exec_module(module) class_ = getattr(module, module_name) self.screens[custom_screen_name] = class_() except Exception as e: - logging.error("Can't load custom control screen {} => {}".format( - custom_screen_name, e)) + logging.error("Can't load custom control screen {} => {}".format(custom_screen_name, e)) if custom_screen_name in self.screens: control_screen_name = custom_screen_name @@ -1291,16 +1251,14 @@ def cuia_set_tempo(self, params=None): def cuia_toggle_seq(self, params=None): try: - self.state_manager.zynseq.libseq.togglePlayState( - self.state_manager.zynseq.bank, int(params[0])) + self.state_manager.zynseq.libseq.togglePlayState(self.state_manager.zynseq.bank, int(params[0])) except (AttributeError, TypeError): pass def cuia_tempo_up(self, params=None): if params: try: - self.state_manager.zynseq.set_tempo( - self.state_manager.zynseq.get_tempo() + params[0]) + self.state_manager.zynseq.set_tempo(self.state_manager.zynseq.get_tempo() + params[0]) except (AttributeError, TypeError): pass else: @@ -1310,13 +1268,11 @@ def cuia_tempo_up(self, params=None): def cuia_tempo_down(self, params=None): if params: try: - self.state_manager.zynseq.set_tempo( - self.state_manager.zynseq.get_tempo() - params[0]) + self.state_manager.zynseq.set_tempo(self.state_manager.zynseq.get_tempo() - params[0]) except (AttributeError, TypeError): pass else: - self.state_manager.zynseq.set_tempo( - self.state_manager.zynseq.get_tempo() - 1) + self.state_manager.zynseq.set_tempo(self.state_manager.zynseq.get_tempo() - 1) def cuia_tap_tempo(self, params=None): self.screens["tempo"].tap() @@ -1328,8 +1284,7 @@ def cuia_zynpot(self, params=None): d = int(params[1]) self.get_current_screen_obj().zynpot_cb(i, d) except IndexError: - logging.error( - "zynpot requires 2 parameters: index, delta, not {params}") + logging.error("zynpot requires 2 parameters: index, delta, not {params}") return except Exception as e: logging.error(e) @@ -1340,8 +1295,7 @@ def cuia_zynswitch(self, params=None): d = params[1] self.cuia_queue.put_nowait(("zynswitch", (i, d))) except IndexError: - logging.error( - "zynswitch requires 2 parameters: index, delta, not {params}") + logging.error("zynswitch requires 2 parameters: index, delta, not {params}") return except Exception as e: logging.error(e) @@ -1474,15 +1428,13 @@ def cuia_chain_options(self, params=None): if params[0] == 0: chain_id = 0 else: - chain_id = self.chain_manager.get_chain_id_by_index( - params[0] - 1) + chain_id = self.chain_manager.get_chain_id_by_index(params[0] - 1) except: chain_id = self.chain_manager.active_chain_id if chain_id is not None: self.screens['chain_options'].setup(chain_id) - self.show_screen( - 'chain_options', hmode=zynthian_gui.SCREEN_HMODE_ADD) + self.show_screen('chain_options', hmode=zynthian_gui.SCREEN_HMODE_ADD) cuia_layer_options = cuia_chain_options @@ -1507,8 +1459,7 @@ def cuia_bank_preset(self, params=None): elif not self.is_shown_audio_player(): self.screens["control"].fill_list() try: - self.chain_manager.get_active_chain().set_current_processor( - self.screens['control'].screen_processor) + self.chain_manager.get_active_chain().set_current_processor(self.screens['control'].screen_processor) self.current_processor = None except: logging.warning("Can't set control screen processor! ") @@ -1528,14 +1479,12 @@ def cuia_bank_preset(self, params=None): else: if len(curproc.preset_list) > 0 and curproc.preset_list[0][0] != '': self.screens['preset'].index = curproc.get_preset_index() - self.show_screen( - 'preset', hmode=zynthian_gui.SCREEN_HMODE_ADD) + self.show_screen('preset', hmode=zynthian_gui.SCREEN_HMODE_ADD) if len(curproc.preset_list) == 0 or curproc.preset_list[0][0] == '': # Handle change of bank name, e.g. via webconf self.replace_screen('bank') elif len(bank_list) > 0 and bank_list[0][0] != '': - self.show_screen( - 'bank', hmode=zynthian_gui.SCREEN_HMODE_ADD) + self.show_screen('bank', hmode=zynthian_gui.SCREEN_HMODE_ADD) cuia_preset = cuia_bank_preset @@ -1597,8 +1546,7 @@ def cuia_midi_learn_control(self, params=None): def cuia_midi_unlearn_control(self, params=None): if self.current_screen in ("control", "alsa_mixer"): if params: - self.midi_learn_zctrl = self.screens[self.current_screen].get_zcontroller( - params[0]) + self.midi_learn_zctrl = self.screens[self.current_screen].get_zcontroller(params[0]) # if not parameter, unlearn selected learning control if self.midi_learn_zctrl: self.screens[self.current_screen].midi_unlearn_action() @@ -1688,8 +1636,7 @@ def cuia_midi_unlearn_chain(self, params=None): if params: self.chain_manager.clean_midi_learn(params[0]) else: - self.chain_manager.clean_midi_learn( - self.chain_manager.active_chain_id) + self.chain_manager.clean_midi_learn(self.chain_manager.active_chain_id) # MIDI CUIAs def cuia_program_change(self, params=None): @@ -1713,11 +1660,9 @@ def cuia_zyn_cc(self, params=None): cc = int(params[1]) if params[-1] == 'R': if len(params) > 3: - lib_zyncore.write_zynmidi_ccontrol_change( - chan, cc, int(params[3])) + lib_zyncore.write_zynmidi_ccontrol_change(chan, cc, int(params[3])) else: - lib_zyncore.write_zynmidi_ccontrol_change( - chan, cc, int(params[2])) + lib_zyncore.write_zynmidi_ccontrol_change(chan, cc, int(params[2])) # Common methods to control views derived from zynthian_gui_base def cuia_show_cursor(self, params=None): @@ -1759,16 +1704,14 @@ def cuia_hide_buttonbar(self, params=None): def cuia_show_sidebar(self, params=None): try: self.screens[self.current_screen].show_sidebar(True) - zynsigman.send_queued( - zynsigman.S_GUI, zynsigman.SS_GUI_SHOW_SIDEBAR, shown=True) + zynsigman.send_queued(zynsigman.S_GUI, zynsigman.SS_GUI_SHOW_SIDEBAR, shown=True) except (AttributeError, TypeError): pass def cuia_hide_sidebar(self, params=None): try: self.screens[self.current_screen].show_sidebar(False) - zynsigman.send_queued( - zynsigman.S_GUI, zynsigman.SS_GUI_SHOW_SIDEBAR, shown=False) + zynsigman.send_queued(zynsigman.S_GUI, zynsigman.SS_GUI_SHOW_SIDEBAR, shown=False) except (AttributeError, TypeError): pass @@ -1776,8 +1719,7 @@ def cuia_toggle_sidebar(self, params=None): try: show = not self.screens[self.current_screen].sidebar_shown self.screens[self.current_screen].show_sidebar(show) - zynsigman.send_queued( - zynsigman.S_GUI, zynsigman.SS_GUI_SHOW_SIDEBAR, shown=show) + zynsigman.send_queued(zynsigman.S_GUI, zynsigman.SS_GUI_SHOW_SIDEBAR, shown=show) except (AttributeError, TypeError): pass @@ -1889,8 +1831,7 @@ def check_current_screen_switch(self, action_config): # Init Standard Zynswitches def zynswitches_init(self): - logging.info( - f"INIT {zynthian_gui_config.num_zynswitches} ZYNSWITCHES ...") + logging.info(f"INIT {zynthian_gui_config.num_zynswitches} ZYNSWITCHES ...") self.dtsw = [datetime.now()] * zynthian_gui_config.num_zynswitches # Initialize custom switches, analog I/O, TOF sensors, etc. @@ -1908,10 +1849,8 @@ def zynswitches_midi_setup(self, current_chain_chan=None): midi_chan = current_chain_chan if midi_chan is not None: - lib_zyncore.setup_zynswitch_midi( - swi, event['type'], midi_chan, event['num'], event['val']) - logging.info( - f"MIDI ZYNSWITCH {swi}: {event['type']} CH#{midi_chan}, {event['num']}, {event['val']}") + lib_zyncore.setup_zynswitch_midi(swi, event['type'], midi_chan, event['num'], event['val']) + logging.info(f"MIDI ZYNSWITCH {swi}: {event['type']} CH#{midi_chan}, {event['num']}, {event['val']}") else: lib_zyncore.setup_zynswitch_midi(swi, 0, 0, 0, 0) logging.info(f"MIDI ZYNSWITCH {swi}: DISABLED!") @@ -1926,10 +1865,8 @@ def zynswitches_midi_setup(self, current_chain_chan=None): midi_chan = current_chain_chan if midi_chan is not None: - lib_zyncore.setup_zynaptik_cvin( - i, event['type'], midi_chan, event['num']) - logging.info( - f"ZYNAPTIK CV-IN {i}: {event['type']} CH#{midi_chan}, {event['num']}") + lib_zyncore.setup_zynaptik_cvin(i, event['type'], midi_chan, event['num']) + logging.info(f"ZYNAPTIK CV-IN {i}: {event['type']} CH#{midi_chan}, {event['num']}") else: lib_zyncore.disable_zynaptik_cvin(i) logging.info(f"ZYNAPTIK CV-IN {i}: DISABLED!") @@ -1944,10 +1881,8 @@ def zynswitches_midi_setup(self, current_chain_chan=None): midi_chan = current_chain_chan if midi_chan is not None: - lib_zyncore.setup_zynaptik_cvout( - i, event['type'], midi_chan, event['num']) - logging.info( - f"ZYNAPTIK CV-OUT {i}: {event['type']} CH#{midi_chan}, {event['num']}") + lib_zyncore.setup_zynaptik_cvout(i, event['type'], midi_chan, event['num']) + logging.info(f"ZYNAPTIK CV-OUT {i}: {event['type']} CH#{midi_chan}, {event['num']}") else: lib_zyncore.disable_zynaptik_cvout(i) logging.info(f"ZYNAPTIK CV-OUT {i}: DISABLED!") @@ -1961,10 +1896,8 @@ def zynswitches_midi_setup(self, current_chain_chan=None): midi_chan = current_chain_chan if midi_chan is not None: - lib_zyncore.setup_zyntof( - i, event['type'], midi_chan, event['num']) - logging.info( - f"ZYNTOF {i}: {event['type']} CH#{midi_chan}, {event['num']}") + lib_zyncore.setup_zyntof(i, event['type'], midi_chan, event['num']) + logging.info(f"ZYNTOF {i}: {event['type']} CH#{midi_chan}, {event['num']}") else: lib_zyncore.disable_zyntof(i) logging.info(f"ZYNTOF {i}: DISABLED!") @@ -1998,8 +1931,7 @@ def zynswitches(self): # dtus is 0 if switched pressed, dur of last press or -1 if already processed dtus = lib_zyncore.get_zynswitch(i, zs_long_us) if dtus >= 0: - self.cuia_queue.put_nowait( - ("zynswitch", (i, self.zynswitch_timing(dtus)))) + self.cuia_queue.put_nowait(("zynswitch", (i, self.zynswitch_timing(dtus)))) i += 1 def zynswitch_timing(self, dtus): @@ -2167,16 +2099,12 @@ def zynswitch_read(self): # ------------------------------------------------------------------ def register_signals(self): - zynsigman.register( - zynsigman.S_MIDI, zynsigman.SS_MIDI_NOTE_ON, self.cb_midi_note_on) - zynsigman.register( - zynsigman.S_MIDI, zynsigman.SS_MIDI_NOTE_OFF, self.cb_midi_note_off) + zynsigman.register(zynsigman.S_MIDI, zynsigman.SS_MIDI_NOTE_ON, self.cb_midi_note_on) + zynsigman.register(zynsigman.S_MIDI, zynsigman.SS_MIDI_NOTE_OFF, self.cb_midi_note_off) def unregister_signals(self): - zynsigman.unregister( - zynsigman.S_MIDI, zynsigman.SS_MIDI_NOTE_ON, self.cb_midi_note_on) - zynsigman.unregister( - zynsigman.S_MIDI, zynsigman.SS_MIDI_NOTE_OFF, self.cb_midi_note_off) + zynsigman.unregister(zynsigman.S_MIDI, zynsigman.SS_MIDI_NOTE_ON, self.cb_midi_note_on) + zynsigman.unregister(zynsigman.S_MIDI, zynsigman.SS_MIDI_NOTE_OFF, self.cb_midi_note_off) def cb_midi_note_on(self, izmip, chan, note, vel): """Handle MIDI_NOTE_ON signal @@ -2238,8 +2166,7 @@ def zynpot_thread_task(self): self.screens[self.current_screen].zynpot_cb(i, dval) self.state_manager.set_event_flag() if self.capture_log_fname: - self.write_capture_log( - "ZYNPOT:{},{}".format(i, dval)) + self.write_capture_log("ZYNPOT:{},{}".format(i, dval)) except Exception as err: pass # Some screens don't use controllers logging.exception(err) @@ -2334,8 +2261,7 @@ def busy_thread_task(self): else: busy_success = self.state_manager.get_busy_success() if busy_success: - self.screens['loading'].set_success( - busy_success) + self.screens['loading'].set_success(busy_success) elif busy_message: self.screens['loading'].set_title(busy_message) if busy_details: @@ -2353,12 +2279,10 @@ def busy_thread_task(self): if self.current_screen: self.screens[self.current_screen].refresh_loading() except Exception as err: - logging.error( - f"refresh_loading() on screen '{self.current_screen}' => {err}") + logging.error(f"refresh_loading() on screen '{self.current_screen}' => {err}") if busy_timeout == busy_warn_time: - logging.warning( - f"Clients have been busy for longer than {int(busy_warn_time / 10)}s: {self.state_manager.busy}") + logging.warning(f"Clients have been busy for longer than {int(busy_warn_time / 10)}s: {self.state_manager.busy}") sleep(0.1) @@ -2453,16 +2377,14 @@ def cuia_thread_task(self): del zynswitch_repeat[i] continue else: - dtus = int( - 1000000 * (monotonic() - zynswitch_cuia_ts[i])) + dtus = int(1000000 * (monotonic() - zynswitch_cuia_ts[i])) zynswitch_cuia_ts[i] = None t = self.zynswitch_timing(dtus) if t == 'P': pr = 0 if zynthian_gui_config.num_zynpots > 0: try: - zpi = zynthian_gui_config.zynpot2switch.index( - i) + zpi = zynthian_gui_config.zynpot2switch.index(i) self.zynpot_pr_state[zpi] = 1 pr = 1 except: @@ -2475,8 +2397,7 @@ def cuia_thread_task(self): else: if zynthian_gui_config.num_zynpots > 0: try: - zpi = zynthian_gui_config.zynpot2switch.index( - i) + zpi = zynthian_gui_config.zynpot2switch.index(i) if self.zynpot_pr_state[zpi] > 1: t = 'PR' self.zynpot_pr_state[zpi] = 0 @@ -2495,8 +2416,7 @@ def cuia_thread_task(self): zynswitch_cuia_ts[i] = None else: zynswitch_cuia_ts[i] = None - logging.warning( - "Unknown Action Type: {}".format(t)) + logging.warning("Unknown Action Type: {}".format(t)) if i in zynswitch_repeat: del zynswitch_repeat[i] @@ -2530,10 +2450,8 @@ def cuia_thread_task(self): self.cuia_zynpot(zynpot_repeat[i][1]) except Exception as e: - logging.error( - f"CUIA '{cuia}' failed with params: {params}\n{traceback.format_exc()}") - self.state_manager.set_busy_error( - f"ERROR CUIA {cuia}: {params}", e) + logging.error(f"CUIA '{cuia}' failed with params: {params}\n{traceback.format_exc()}") + self.state_manager.set_busy_error(f"ERROR CUIA {cuia}: {params}", e) sleep(3) self.state_manager.clear_busy() @@ -2617,12 +2535,10 @@ def osc_timeout(self): pass if not self.osc_clients and self.current_screen != "audio_mixer": - self.state_manager.zynmixer.enable_dpm( - 0, self.state_manager.zynmixer.MAX_NUM_CHANNELS - 2, False) + self.state_manager.zynmixer.enable_dpm(0, self.state_manager.zynmixer.MAX_NUM_CHANNELS - 2, False) # Poll - zynthian_gui_config.top.after( - self.osc_heartbeat_timeout * 1000, self.osc_timeout) + zynthian_gui_config.top.after(self.osc_heartbeat_timeout * 1000, self.osc_timeout) # ------------------------------------------------------------------ # Zynthian Config Info From 1a4452272071f721232def156b05d4f78368e2f2 Mon Sep 17 00:00:00 2001 From: jofemodo Date: Tue, 3 Dec 2024 18:02:14 +0100 Subject: [PATCH 056/117] Auto-config V5 switches wiring layout is "dummies" and V5 touch key-pad is enabled. --- zyngui/zynthian_gui_config.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/zyngui/zynthian_gui_config.py b/zyngui/zynthian_gui_config.py index 537c7fff2..fab7fecbc 100644 --- a/zyngui/zynthian_gui_config.py +++ b/zyngui/zynthian_gui_config.py @@ -553,6 +553,12 @@ def get_external_storage_dirs(exdpath): except: force_enable_cursor = 0 +# Configure switch actions for touch only configuration so it works with touch-keypad +if touch_keypad_option == "V5" and wiring_layout == "DUMMIES": + config_dir = os.environ.get("ZYNTHIAN_CONFIG_DIR", "/zynthian/config") + zynconf.load_config(True, f"{config_dir}/wiring-profiles/v5") + os.environ["ZYNTHIAN_WIRING_SWITCHES"] = ",".join(36 * ["-1"]) + # ------------------------------------------------------------------------------ # UI Options # ------------------------------------------------------------------------------ From a28b9ef1c78cd81cdbf893f97effeb92cfcfd3e2 Mon Sep 17 00:00:00 2001 From: jofemodo Date: Tue, 3 Dec 2024 23:07:43 +0100 Subject: [PATCH 057/117] + Implement new zynconf function: load_plain_envars(...) + Use load_plain_envars() instead of load_config() to load V5 wiring profile for dummies + touch-keypad config, so it effectively works. --- zynconf/zynthian_config.py | 42 +++++++++++++++++++++++++++-------- zyngui/zynthian_gui_config.py | 3 ++- 2 files changed, 35 insertions(+), 10 deletions(-) diff --git a/zynconf/zynthian_config.py b/zynconf/zynthian_config.py index 65624c26f..f0efd23cd 100755 --- a/zynconf/zynthian_config.py +++ b/zynconf/zynthian_config.py @@ -205,11 +205,10 @@ def load_config(set_env=True, fpath=None): res = pattern.match(line) if res: varnames.append(res.group(1)) - # logging.debug("CONFIG VARNAME: %s" % res.group(1)) + #logging.debug(f"CONFIG VARNAMES: {varnames}") # Execute config script and dump environment - env = check_output("source \"{}\";env".format( - fpath), shell=True, universal_newlines=True, executable="/bin/bash") + env = check_output("source \"{}\";env".format(fpath), shell=True, universal_newlines=True, executable="/bin/bash") # Parse environment dump config = {} @@ -281,11 +280,38 @@ def save_config(config, updsys=False, fpath=None): update_sys() +def load_plain_envars(fpath, set_env=True): + # Get config file content + with open(fpath) as f: + lines = f.readlines() + + # Parse plain envar assignment with or without export prefix + config = {} + pattern = re.compile("^([^#]*?)=(.*)") + for line in lines: + res = pattern.match(line) + if res: + parts = res.group(1).split(" ", maxsplit=1) + if len(parts) > 1: + if parts[0] == "export": + varname = parts[1] + else: + continue + else: + varname = res.group(1) + value = res.group(2).strip('\"').strip('\'') + config[varname] = value + # Set local environment + if set_env: + os.environ[varname] = value + #logging.debug(f"CONFIG: {config}") + return config + + def update_sys(): try: os.environ['ZYNTHIAN_FLAG_MASTER'] = "NONE" - check_output(os.environ.get('ZYNTHIAN_SYS_DIR') + - "/scripts/update_zynthian_sys.sh", shell=True) + check_output(os.environ.get('ZYNTHIAN_SYS_DIR') + "/scripts/update_zynthian_sys.sh", shell=True) except Exception as e: logging.error("Updating Sytem Config: %s" % e) @@ -337,14 +363,12 @@ def get_wifi_list(): # and create it if needed if "zynthian-ap" not in configured_wifi: logging.info("Creating Wi-Fi Access Point connection 'zynthian'...") - check_output( - f"{sys_dir}/sbin/create_wifi_access_point.sh", encoding='utf-8') + check_output(f"{sys_dir}/sbin/create_wifi_access_point.sh", encoding='utf-8') # Get list of available networks wifi_data = [] ap_enabled = False - rows = check_output(["nmcli", "--terse", "dev", "wifi", - "list"], encoding='utf-8').split("\n") + rows = check_output(["nmcli", "--terse", "dev", "wifi", "list"], encoding='utf-8').split("\n") for row in rows: parts = row.split(":") if len(parts) > 8: diff --git a/zyngui/zynthian_gui_config.py b/zyngui/zynthian_gui_config.py index fab7fecbc..81e814bfd 100644 --- a/zyngui/zynthian_gui_config.py +++ b/zyngui/zynthian_gui_config.py @@ -241,6 +241,7 @@ def config_custom_switches(): custom_switch_ui_actions.append(cuias) custom_switch_midi_events.append(midi_event) + #logging.debug(f"CUSTOM_SWITCH_UI_ACTIONS => \n {custom_switch_ui_actions}") def config_zynpot2switch(): @@ -556,7 +557,7 @@ def get_external_storage_dirs(exdpath): # Configure switch actions for touch only configuration so it works with touch-keypad if touch_keypad_option == "V5" and wiring_layout == "DUMMIES": config_dir = os.environ.get("ZYNTHIAN_CONFIG_DIR", "/zynthian/config") - zynconf.load_config(True, f"{config_dir}/wiring-profiles/v5") + zynconf.load_plain_envars(f"{config_dir}/wiring-profiles/v5", True) os.environ["ZYNTHIAN_WIRING_SWITCHES"] = ",".join(36 * ["-1"]) # ------------------------------------------------------------------------------ From 8d34b0a9e066de4d49023d9b2d9fe14a5fecc0f4 Mon Sep 17 00:00:00 2001 From: riban Date: Wed, 4 Dec 2024 07:28:54 +0000 Subject: [PATCH 058/117] Adds zynaudio8x driver --- zynlibs/zynaudio8x/LICENSE | 674 +++++++++++++++++++++++++++++ zynlibs/zynaudio8x/Makefile | 14 + zynlibs/zynaudio8x/README.md | 50 +++ zynlibs/zynaudio8x/build.sh | 24 + zynlibs/zynaudio8x/spdif_duplex.c | 95 ++++ zynlibs/zynaudio8x/zynaudio8x.dtbs | 54 +++ 6 files changed, 911 insertions(+) create mode 100644 zynlibs/zynaudio8x/LICENSE create mode 100644 zynlibs/zynaudio8x/Makefile create mode 100644 zynlibs/zynaudio8x/README.md create mode 100755 zynlibs/zynaudio8x/build.sh create mode 100644 zynlibs/zynaudio8x/spdif_duplex.c create mode 100644 zynlibs/zynaudio8x/zynaudio8x.dtbs diff --git a/zynlibs/zynaudio8x/LICENSE b/zynlibs/zynaudio8x/LICENSE new file mode 100644 index 000000000..f288702d2 --- /dev/null +++ b/zynlibs/zynaudio8x/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + 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 3 of the License, or + (at your option) 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. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/zynlibs/zynaudio8x/Makefile b/zynlibs/zynaudio8x/Makefile new file mode 100644 index 000000000..93f804f75 --- /dev/null +++ b/zynlibs/zynaudio8x/Makefile @@ -0,0 +1,14 @@ +obj-m += spdif_duplex.o + +all: + make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules + dtc -@ -H epapr -O dtb -o zynaudio8x.dtbo -Wno-unit_address_vs_reg zynaudio8x.dtbs + +clean: + make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean + +install: + cp zynaudio8x.dtbo /boot/firmware/overlays/ + xz -f spdif_duplex.ko + cp spdif_duplex.ko.xz /lib/modules/$(shell uname -r)/kernel/sound/soc/codecs/snd-soc-spdif_duplex.ko.xz + depmod -a diff --git a/zynlibs/zynaudio8x/README.md b/zynlibs/zynaudio8x/README.md new file mode 100644 index 000000000..de2702b9f --- /dev/null +++ b/zynlibs/zynaudio8x/README.md @@ -0,0 +1,50 @@ +# zynaudio8x +Provides requirements to enable zynthian 8 channel I2S soundcard. + +A device tree overlay to enable 8 channels of audio input and audio output. +A kernel device driver providing duplex SPDIF interface. + +This only works on Raspberry Pi 5. + +## Dependencies: + +``` +apt-get install raspberrypi-kernel-headers +``` + +@stojos says this is also required: +``` +wget https://raw.githubusercontent.com/RPi-Distro/rpi-source/master/rpi-source -O /usr/local/bin/rpi-source && sudo chmod +x /usr/local/bin/rpi-source && /usr/local/bin/rpi-source -q --tag-update +rpi-source +``` + +## Build: + +`make` + +## Install: + +`make install` + +Configure /boot/firmware/config.txt: + +`dtoverlay=zynaudio8x,inputs=4,outputs=2` + +`inputs` and `outputs` are optional parameters that limit the quantity of input (capture) and output (playback) ports. Range 0..8 and must be even. Invalid values will default to 8. + +The soundcard must create bit clock and word clock. Bit clock should be connected to Rasberry Pi pin 12 (GPIO 18). Word clock should be connected to Raspberry Pi pin 35 (GPIO 19). + +## Connections +|GPIO|Pin|Description| +|-|-|-| +|18|12|Bit Clock| +|19|35|Word Clock| +|20|38|I2S 1 In| +|21|40|I2S 1 Out| +|22|15|I2S 2 In| +|23|16|I2S 2 Out| +|24|18|I2S 3 In| +|25|22|I2S 3 Out| +|26|37|I2S 4 In| +|27|13|I2S 4 Out| + diff --git a/zynlibs/zynaudio8x/build.sh b/zynlibs/zynaudio8x/build.sh new file mode 100755 index 000000000..70b8c17b9 --- /dev/null +++ b/zynlibs/zynaudio8x/build.sh @@ -0,0 +1,24 @@ +#!/bin/bash + +# Check if RBPI_VERSION_NUMBER is set +if [ -z "$RBPI_VERSION_NUMBER" ]; then + echo "Error: RBPI_VERSION_NUMBER is not set." + exit 0 +fi + +# Check if RBPI_VERSION_NUMBER equals 5 +if [ "$RBPI_VERSION_NUMBER" -ne 5 ]; then + echo "Not installing zynaudio8x for < RPi5" + exit 0 +fi + +echo "Installing zynaudio8x" + +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" + +pushd $DIR + make && make install + success=$? +popd +exit $success + diff --git a/zynlibs/zynaudio8x/spdif_duplex.c b/zynlibs/zynaudio8x/spdif_duplex.c new file mode 100644 index 000000000..9e41bdf53 --- /dev/null +++ b/zynlibs/zynaudio8x/spdif_duplex.c @@ -0,0 +1,95 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * ALSA SoC SPDIF DID (Digital Interface Duplex) driver + * + * Based on ALSA SoC SPDIF DIT driver + * + * This driver is used by controllers which can operate in DUPLEX where + * no codec is needed. + * + * Author: Brian Walton based on code by Vipin Kumar, + */ + +#include + +#define STUB_RATES (SNDRV_PCM_RATE_8000_96000) +#define STUB_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S20_3LE | \ + SNDRV_PCM_FMTBIT_S24_LE | \ + SNDRV_PCM_FMTBIT_S32_LE | \ + SNDRV_PCM_FMTBIT_IEC958_SUBFRAME_LE) + +static const struct snd_soc_dapm_widget duplex_widgets[] = { + SND_SOC_DAPM_INPUT("spdif-in"), + SND_SOC_DAPM_OUTPUT("spdif-out"), +}; + +static const struct snd_soc_dapm_route duplex_routes[] = { + { "Capture", NULL, "spdif-in" }, + { "Playback", NULL, "spdif-out" }, +}; + +static const struct snd_soc_component_driver soc_codec_spdif_duplex = { + .dapm_widgets = duplex_widgets, + .num_dapm_widgets = ARRAY_SIZE(duplex_widgets), + .dapm_routes = duplex_routes, + .num_dapm_routes = ARRAY_SIZE(duplex_routes), + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, +}; + +static struct snd_soc_dai_driver duplex_stub_dai = { + .name = "did-hifi", + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 384, + .rates = STUB_RATES, + .formats = STUB_FORMATS, + }, + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 384, + .rates = STUB_RATES, + .formats = STUB_FORMATS, + }, +}; + +static int spdif_duplex_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + uint32_t val = 2; + of_property_read_u32(np, "capture-channels", &val); + if (val < 385 && (val % 2) == 0) + duplex_stub_dai.capture.channels_max = val; + val = 2; + of_property_read_u32(np, "playback-channels", &val); + if (val < 385 && (val % 2) == 0) + duplex_stub_dai.playback.channels_max = val; + + return devm_snd_soc_register_component(&pdev->dev, + &soc_codec_spdif_duplex, + &duplex_stub_dai, 1); +}; + +static const struct of_device_id spdif_duplex_dt_ids[] = { + { .compatible = "linux,spdif-did", }, + { } +}; + +static struct platform_driver spdif_duplex_driver = { + .probe = spdif_duplex_probe, + .driver = { + .name = "spdif-did", + .of_match_table = of_match_ptr(spdif_duplex_dt_ids), + }, +}; + +module_platform_driver(spdif_duplex_driver); + +MODULE_DEVICE_TABLE(of, spdif_duplex_dt_ids); +MODULE_DESCRIPTION("ASoC SPDIF DID driver"); +MODULE_AUTHOR("Brian Walton , Vipin Kumar "); +MODULE_LICENSE("GPL"); \ No newline at end of file diff --git a/zynlibs/zynaudio8x/zynaudio8x.dtbs b/zynlibs/zynaudio8x/zynaudio8x.dtbs new file mode 100644 index 000000000..f515a9f36 --- /dev/null +++ b/zynlibs/zynaudio8x/zynaudio8x.dtbs @@ -0,0 +1,54 @@ +// Definitions for zynthian 8 channel I2S soundcard +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2712"; // Raspberry Pi 5 + + fragment@0 { + // Configure the GPIO pins for clock consumer + target = <&i2s_clk_consumer>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@1 { + // Define CODEC + target-path = "/"; + __overlay__ { + duplex_codec: duplex-codec { + compatible = "linux,spdif-did"; + #sound-dai-cells = <0>; + capture-channels = <8>; + playback-channels = <8>; + status = "okay"; + }; + }; + }; + + fragment@2 { + // Configure and enable soundcard driver + target = <&sound>; + __overlay__ { + compatible = "simple-audio-card"; + simple-audio-card,name = "zynaudio8x"; + simple-audio-card,format = "i2s"; + simple-audio-card,bitclock-master = <&snd_codec>; + simple-audio-card,frame-master = <&snd_codec>; + status = "okay"; + cpu { + sound-dai = <&i2s_clk_consumer>; + }; + snd_codec: simple-audio-card,codec { + sound-dai = <&duplex_codec>; + }; + }; + }; + + __overrides__ { + inputs = <&duplex_codec>,"capture-channels:0"; + outputs = <&duplex_codec>,"playback-channels:0"; + }; + +}; From dc13c6eef900cd835dd504fbb36032190e704b9e Mon Sep 17 00:00:00 2001 From: jofemodo Date: Wed, 4 Dec 2024 09:42:34 +0100 Subject: [PATCH 059/117] Replace wiring layout "DUMMIES" by "TOUCH_ONLY". --- zyngui/zynthian_gui_config.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/zyngui/zynthian_gui_config.py b/zyngui/zynthian_gui_config.py index 81e814bfd..f23a6ee86 100644 --- a/zyngui/zynthian_gui_config.py +++ b/zyngui/zynthian_gui_config.py @@ -51,8 +51,9 @@ # Wiring layout # ------------------------------------------------------------------------------ -wiring_layout = os.environ.get('ZYNTHIAN_WIRING_LAYOUT', "DUMMIES") -if wiring_layout == "DUMMIES": +wiring_layout = os.environ.get('ZYNTHIAN_WIRING_LAYOUT', "TOUCH_ONLY") +if wiring_layout in ("TOUCH_ONLY", "DUMMIES"): + wiring_layout = "TOUCH_ONLY" logging.info("No Wiring Layout configured. Only touch interface is available.") else: logging.info("Wiring Layout %s" % wiring_layout) @@ -555,10 +556,11 @@ def get_external_storage_dirs(exdpath): force_enable_cursor = 0 # Configure switch actions for touch only configuration so it works with touch-keypad -if touch_keypad_option == "V5" and wiring_layout == "DUMMIES": - config_dir = os.environ.get("ZYNTHIAN_CONFIG_DIR", "/zynthian/config") - zynconf.load_plain_envars(f"{config_dir}/wiring-profiles/v5", True) - os.environ["ZYNTHIAN_WIRING_SWITCHES"] = ",".join(36 * ["-1"]) +if touch_keypad_option == "V5" and wiring_layout =="TOUCH_ONLY": + if os.environ.get("ZYNTHIAN_WIRING_LAYOUT_CUSTOM_PROFILE", "") != "v5": + config_dir = os.environ.get("ZYNTHIAN_CONFIG_DIR", "/zynthian/config") + zynconf.load_plain_envars(f"{config_dir}/wiring-profiles/v5", True) + os.environ["ZYNTHIAN_WIRING_SWITCHES"] = ",".join(36 * ["-1"]) # ------------------------------------------------------------------------------ # UI Options @@ -732,7 +734,7 @@ def color_variant(hex_color, brightness_offset=1): top.minsize(display_width, display_height) # Disable cursor for real Zynthian Boxes - if force_enable_cursor or wiring_layout == "EMULATOR" or wiring_layout == "DUMMIES": + if force_enable_cursor or wiring_layout == "EMULATOR" or wiring_layout == "TOUCH_ONLY": top.config(cursor="arrow") else: top.config(cursor="none") From 01b3393905cac791a9510f5a2544f16e588ea065 Mon Sep 17 00:00:00 2001 From: jofemodo Date: Wed, 4 Dec 2024 14:29:21 +0100 Subject: [PATCH 060/117] + Implement selector side-panel info in chain options menu. + Add related icons. --- icons/audio_input.png | Bin 0 -> 5303 bytes icons/audio_options.png | Bin 0 -> 5884 bytes icons/audio_output.png | Bin 0 -> 7846 bytes icons/audio_processor.png | Bin 0 -> 9713 bytes icons/delete_icons.xcf | Bin 96927 -> 0 bytes icons/midi_input.png | Bin 0 -> 3342 bytes icons/midi_output.png | Bin 0 -> 3342 bytes icons/midi_processor.png | Bin 0 -> 6529 bytes icons/note_range.png | Bin 0 -> 843 bytes icons/synth_processor.png | Bin 0 -> 11202 bytes zyngui/zynthian_gui_chain_options.py | 87 ++++++++++++++++----------- zyngui/zynthian_gui_selector_info.py | 2 +- 12 files changed, 52 insertions(+), 37 deletions(-) create mode 100644 icons/audio_input.png create mode 100644 icons/audio_options.png create mode 100644 icons/audio_output.png create mode 100644 icons/audio_processor.png delete mode 100644 icons/delete_icons.xcf create mode 100644 icons/midi_input.png create mode 100644 icons/midi_output.png create mode 100644 icons/midi_processor.png create mode 100644 icons/note_range.png create mode 100644 icons/synth_processor.png diff --git a/icons/audio_input.png b/icons/audio_input.png new file mode 100644 index 0000000000000000000000000000000000000000..7db5701ca636c85a27722cc77d8bf910ee3a333d GIT binary patch literal 5303 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 literal 0 HcmV?d00001 diff --git a/icons/audio_options.png b/icons/audio_options.png new file mode 100644 index 0000000000000000000000000000000000000000..81d1d885e729eeaa1ca90b4aafadd9e053d7dbec GIT binary patch literal 5884 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%0AjAf&y@{ z{l+3)m{BF1PMWHjm(Y38Hptt&#rJgMRHw(hg~v));H2u=6J={~(Bh1*$S)?OhSIo!$;IJhDd%gH zSO!T$_0%?gryDo=^zcWzk(yp;uUKMA+bsjT?lBwhP0zppeV|irBDE=anHhaEpS1%frG_FR=fnef;o(rhj#IF?c%5YmvvRzsRayyP|<;2S8J-4P&O zYKzrQTij~uAEDAEsY3A%@Cveo;3ghUUgvXtx*@*G<|S`6;kT1;@&_1N`@*qTGUVU9 zgx;pMI|WKG$Ahv^?R3V?fwKGfvsKi4Y0;pSpQcYFN%?2>9=ji_Cn-PYbAWeXCof6g#}pgFE5mur_%F9?oiv zK7;qyc}Z)EJVEirK9Ux!sj9A<3@SD5;@GK6gcIc_V-+({#T@Wg7`4*VK304fIi5HS z#4eSapv`rq8f>OO6oPRC3Z24`VU|NkW8WXA_7Vu58m9TF7VzT^Hm-WM*x7t%Wf|1( z&&V_|Emf5U!FFf)pMQ3TvaYxS#nnZALUD4HHdHgwsq0h)ILLVX_s5@wl~LM~ukO@&-?DC|6QhFY0R*Oy4;GC2CVv9khu!jA=qs%`HA@ypq60Pfm3#`wFy)E}E z5}qB?OhOF;l#RL+e_ePMV`Da_Z@c?Jg>TIsU1Lo$>ZdmSJj=A^8BcrXzb{PPG~)l{%}!Pok@l3+(g*aRMO^9{ zyk_3EWTrn{jamuzA3tFqQ6jvULhGw6KW@D2rlAWgC5olMlthz=_iMv9Exgrh9Bk2E z_Fnc7%8~_3^%}NUm^vy@Vd57Cd#MZ}M<6Q}q+b|}$JJ{nDzPpZy~{fk+{5s9b?9uK zgDK%~x?-ZOb4KAP@2!}FEXtG2;MZoa&%}>>7aK8Ebh(MxZ{+jf6S2eLF~iQu z%l;w%AznEO0%U%LKK5kZzuZI&SY)8ZaZ<_WD2wO-0~d+-{u zbBxorG+ay*_7zn_*f^}kx7R2o$(ymN7avl`l68}CPg7z!xgluG1tFR4p~?L>;V)?g zdh7>r4NyCbm2__#r=L)|=-T|06I!SDy;E+ga(O|!-Du;GqZ?c~Y4)E8FDJx)Wn07Z zM~oq?>qlxRKf0!y$2pAGZ>u%LhACWylGsQGCX?3{ zBmHxf*wE{iE>$&U_7V9`*c_Tfr^`Wk&a`(NPDCRF#7HpZ>i>Ycj*NN`HVDqUKDaKK z*PWD)m9piz;)maA@$)F78T#nl<255AaWIH^i`n>ykZu4t-|ni|Ee|Ee=2jys_GX}o zW~M22h%@NC>;W>Q=U6bycjJCsIdM#my?Gaj{<9WRv^jDq9j%a+aai>KJ0w2u0X6uF W>S~QDzw!Fi08=9?!^+Fo@B9ysi!BrY literal 0 HcmV?d00001 diff --git a/icons/audio_output.png b/icons/audio_output.png new file mode 100644 index 0000000000000000000000000000000000000000..d9d02db80673fdb1dcbfb296b5556d8a585c0dd2 GIT binary patch literal 7846 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 czWn3t^YT6%y2z>% literal 0 HcmV?d00001 diff --git a/icons/audio_processor.png b/icons/audio_processor.png new file mode 100644 index 0000000000000000000000000000000000000000..93738d954aafbef134dec2e872de013bbba5007a GIT binary patch literal 9713 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 B$i)Bv diff --git a/icons/midi_input.png b/icons/midi_input.png new file mode 100644 index 0000000000000000000000000000000000000000..b61f223fe76591b06aa94edb8c9f38700a5d7b05 GIT binary patch literal 3342 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 SA6S%c%sGFHKErh&-ux$H@;3hf literal 0 HcmV?d00001 diff --git a/icons/midi_processor.png b/icons/midi_processor.png new file mode 100644 index 0000000000000000000000000000000000000000..1c999eb8c2ee1da79f87e95d4113c7dd8cb08541 GIT binary patch literal 6529 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 I&MBb@03jXNzyJUM literal 0 HcmV?d00001 diff --git a/icons/synth_processor.png b/icons/synth_processor.png new file mode 100644 index 0000000000000000000000000000000000000000..c3bfc606e9dc96032756049f36060081f77bfff9 GIT binary patch literal 11202 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 CyK3P8 literal 0 HcmV?d00001 diff --git a/zyngui/zynthian_gui_chain_options.py b/zyngui/zynthian_gui_chain_options.py index a2783ceeb..1e88e20ff 100644 --- a/zyngui/zynthian_gui_chain_options.py +++ b/zyngui/zynthian_gui_chain_options.py @@ -28,17 +28,17 @@ # Zynthian specific modules from zyngui import zynthian_gui_config -from zyngui.zynthian_gui_selector import zynthian_gui_selector +from zyngui.zynthian_gui_selector_info import zynthian_gui_selector_info # ------------------------------------------------------------------------------ # Zynthian Chain Options GUI Class # ------------------------------------------------------------------------------ -class zynthian_gui_chain_options(zynthian_gui_selector): +class zynthian_gui_chain_options(zynthian_gui_selector_info): def __init__(self): - super().__init__('Option', True) + super().__init__('Option') self.index = 0 self.chain = None self.chain_id = None @@ -58,64 +58,75 @@ def fill_list(self): audio_proc_count = self.chain.get_processor_count("Audio Effect") if self.chain.is_midi(): - self.list_data.append( - (self.chain_note_range, None, "Note Range & Transpose")) - self.list_data.append((self.chain_midi_capture, None, "MIDI In")) + self.list_data.append((self.chain_note_range, None, "Note Range & Transpose", + ["Configure note range and transpose by octaves and semitones.", "note_range.png"])) + self.list_data.append((self.chain_midi_capture, None, "MIDI In", + ["Manage MIDI input sources. Enable/disable MIDI sources, toggle active/multi-timbral mode, load controller drivers, etc.", "midi_input.png"])) if self.chain.midi_thru: - self.list_data.append((self.chain_midi_routing, None, "MIDI Out")) + self.list_data.append((self.chain_midi_routing, None, "MIDI Out", + ["Manage MIDI output routing to external devices and other chains.", "midi_output.png"])) if self.chain.is_midi(): try: if synth_proc_count == 0 or self.chain.synth_slots[0][0].engine.options["midi_chan"]: - self.list_data.append((self.chain_midi_chan, None, "MIDI Channel")) + self.list_data.append((self.chain_midi_chan, None, "MIDI Channel", + ["Select MIDI channel to receive from.", "midi_logo.png"])) except Exception as e: logging.error(e) if synth_proc_count: - self.list_data.append((self.chain_midi_cc, None, "MIDI CC")) + self.list_data.append((self.chain_midi_cc, None, "MIDI CC", + ["Select MIDI CC numbers passed-thru to chain processors. It could interfere with MIDI-learning. Use with caution!", "midi_logo.png"])) if self.chain.get_processor_count() and not zynthian_gui_config.check_wiring_layout(["Z2", "V5"]): # TODO Disable midi learn for some chains??? - self.list_data.append((self.midi_learn, None, "MIDI Learn")) + self.list_data.append((self.midi_learn, None, "MIDI Learn", + ["Enter MIDI-learning mode for processor parameters.", ""])) if self.chain.audio_thru and self.chain_id != 0: - self.list_data.append((self.chain_audio_capture, None, "Audio In")) + self.list_data.append((self.chain_audio_capture, None, "Audio In", + ["Manage audio capture sources.", "audio_input.png"])) if self.chain.is_audio(): - self.list_data.append((self.chain_audio_routing, None, "Audio Out")) + self.list_data.append((self.chain_audio_routing, None, "Audio Out", + ["Manage audio output routing.", "audio_output.png"])) if self.chain.is_audio(): - self.list_data.append((self.audio_options, None, "Audio Options")) + self.list_data.append((self.audio_options, None, "Audio Options", + ["Manage audio ouput options.", "audio_options.png"])) # TODO: Catch signal for Audio Recording status change if self.chain_id == 0 and not zynthian_gui_config.check_wiring_layout(["Z2", "V5"]): if self.zyngui.state_manager.audio_recorder.status: - self.list_data.append((self.toggle_recording, None, "■ Stop Audio Recording")) + self.list_data.append((self.toggle_recording, None, "■ Stop Audio Recording", ["Stop audio recording", ""])) else: - self.list_data.append((self.toggle_recording, None, "⬤ Start Audio Recording")) + self.list_data.append((self.toggle_recording, None, "⬤ Start Audio Recording", ["Start audio recording", ""])) self.list_data.append((None, None, "> Processors")) if self.chain.is_midi(): # Add MIDI-FX options - self.list_data.append((self.midifx_add, None, "Add MIDI-FX")) + self.list_data.append((self.midifx_add, None, "Add MIDI-FX", + ["Add a new MIDI processor to process chain's MIDI input.", "midi_processor.png"])) self.list_data += self.generate_chaintree_menu() if self.chain.is_audio(): # Add Audio-FX options - self.list_data.append((self.audiofx_add, None, "Add Pre-fader Audio-FX")) - self.list_data.append((self.postfader_add, None, "Add Post-fader Audio-FX")) + self.list_data.append((self.audiofx_add, None, "Add Pre-fader Audio-FX", + ["Add a new audio processor to process chain's audio before the mixer's fader.", "audio_processor.png"])) + self.list_data.append((self.postfader_add, None, "Add Post-fader Audio-FX", + ["Add a new audio processor to process chain's audio after the mixer's fader.", "audio_processor.png"])) if self.chain_id != 0: if synth_proc_count * midi_proc_count + audio_proc_count == 0: - self.list_data.append((self.remove_chain, None, "Remove Chain")) + self.list_data.append((self.remove_chain, None, "Remove Chain", ["Remove this chain and all its processors.", "delete.png"])) else: - self.list_data.append((self.remove_cb, None, "Remove...")) + self.list_data.append((self.remove_cb, None, "Remove...", ["Remove chain or processors.", "delete.png"])) self.list_data.append((self.export_chain, None, "Export chain as snapshot...")) elif audio_proc_count > 0: - self.list_data.append((self.remove_all_audiofx, None, "Remove all Audio-FX")) + self.list_data.append((self.remove_all_audiofx, None, "Remove all Audio-FX", ["Remove all audio-FX processors in this chain.", "delete.png"])) self.list_data.append((None, None, "> GUI")) self.list_data.append((self.rename_chain, None, "Rename chain")) @@ -136,17 +147,21 @@ def generate_chaintree_menu(self): for index, processor in enumerate(procs): name = processor.get_name() if index == num_procs - 1: - res.append((self.processor_options, processor, - " " * indent + "╰─ " + name)) + text = " " * indent + "╰─ " + name else: - res.append((self.processor_options, processor, - " " * indent + "├─ " + name)) + text = " " * indent + "├─ " + name + + res.append((self.processor_options, processor, text, + [f"MIDI processor options for '{name}'", "midi_processor.png"])) + indent += 1 # Add synth processor for slot in self.chain.synth_slots: - for proc in slot: - res.append((self.processor_options, proc, " " * - indent + "╰━ " + proc.get_name())) + for processor in slot: + name = processor.get_name() + text = " " * indent + "╰━ " + name + res.append((self.processor_options, processor, text, + [f"Synth processor options for '{name}'", "synth_processor.png"])) indent += 1 # Build pre-fader audio effects chain for slot in range(self.chain.fader_pos): @@ -157,11 +172,11 @@ def generate_chaintree_menu(self): for index, processor in enumerate(procs): name = processor.get_name() if index == num_procs - 1: - res.append((self.processor_options, processor, - " " * indent + "┗━ " + name)) + text = " " * indent + "┗━ " + name else: - res.append((self.processor_options, processor, - " " * indent + "┣━ " + name)) + text = " " * indent + "┣━ " + name + res.append((self.processor_options, processor, text, + [f"Pre-fader audio processor options for '{name}'", "audio_processor.png"])) indent += 1 # Add FADER mark if self.chain.audio_thru or self.chain.synth_slots: @@ -174,11 +189,11 @@ def generate_chaintree_menu(self): for index, processor in enumerate(procs): name = processor.get_name() if index == num_procs - 1: - res.append((self.processor_options, processor, - " " * indent + "┗━ " + name)) + text = " " * indent + "┗━ " + name else: - res.append((self.processor_options, processor, - " " * indent + "┣━ " + name)) + text = " " * indent + "┣━ " + name + res.append((self.processor_options, processor, text, + [f"Post-fader audio processor options for '{name}'", "audio_processor.png"])) indent += 1 return res diff --git a/zyngui/zynthian_gui_selector_info.py b/zyngui/zynthian_gui_selector_info.py index 424dc17d1..2eb9f816c 100644 --- a/zyngui/zynthian_gui_selector_info.py +++ b/zyngui/zynthian_gui_selector_info.py @@ -114,7 +114,7 @@ def get_info(self): try: return self.list_data[self.index][3] except: - return None + return ["", ""] def update_info(self): info = self.get_info() From 088958cee92bc214eeee810fe41574b37cdff16d Mon Sep 17 00:00:00 2001 From: riban Date: Thu, 5 Dec 2024 09:54:46 +0000 Subject: [PATCH 061/117] Build spdif_duplex against correct kernel headers --- zynlibs/zynaudio8x/Makefile | 7 ++++--- zynlibs/zynaudio8x/build.sh | 16 +++++++++++++--- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/zynlibs/zynaudio8x/Makefile b/zynlibs/zynaudio8x/Makefile index 93f804f75..14e726c57 100644 --- a/zynlibs/zynaudio8x/Makefile +++ b/zynlibs/zynaudio8x/Makefile @@ -1,14 +1,15 @@ obj-m += spdif_duplex.o +KERNEL?=$(shell uname -r) all: - make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules + make -C /lib/modules/$(KERNEL)/build M=$(PWD) modules dtc -@ -H epapr -O dtb -o zynaudio8x.dtbo -Wno-unit_address_vs_reg zynaudio8x.dtbs clean: - make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean + make -C /lib/modules/$(KERNEL)/build M=$(PWD) clean install: cp zynaudio8x.dtbo /boot/firmware/overlays/ xz -f spdif_duplex.ko - cp spdif_duplex.ko.xz /lib/modules/$(shell uname -r)/kernel/sound/soc/codecs/snd-soc-spdif_duplex.ko.xz + cp spdif_duplex.ko.xz /lib/modules/$(KERNEL)/kernel/sound/soc/codecs/snd-soc-spdif_duplex.ko.xz depmod -a diff --git a/zynlibs/zynaudio8x/build.sh b/zynlibs/zynaudio8x/build.sh index 70b8c17b9..0713600ae 100755 --- a/zynlibs/zynaudio8x/build.sh +++ b/zynlibs/zynaudio8x/build.sh @@ -1,23 +1,33 @@ #!/bin/bash # Check if RBPI_VERSION_NUMBER is set -if [ -z "$RBPI_VERSION_NUMBER" ]; then +if [ -z "$RBPI_VERSION_NUMBER" ] +then echo "Error: RBPI_VERSION_NUMBER is not set." exit 0 fi # Check if RBPI_VERSION_NUMBER equals 5 -if [ "$RBPI_VERSION_NUMBER" -ne 5 ]; then +if [ "$RBPI_VERSION_NUMBER" -ne 5 ] +then echo "Not installing zynaudio8x for < RPi5" exit 0 fi +# Check if new kernel installed +if [ -f /tmp/kernel_ver ] +then + KERNEL=`cat /tmp/kernel_ver` +else + KERNEL=`uname -r` +fi + echo "Installing zynaudio8x" DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" pushd $DIR - make && make install + make KERNEL=$KERNEL && make install success=$? popd exit $success From d27aaf2439fa988b34130a86885ec07ecfd4c854 Mon Sep 17 00:00:00 2001 From: jofemodo Date: Thu, 5 Dec 2024 17:02:14 +0100 Subject: [PATCH 062/117] Fix zynthian/zynthian-issue-tracking#1292: When midi-learning toggle controllers, always use absolute mode. --- zyngine/zynthian_controller.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/zyngine/zynthian_controller.py b/zyngine/zynthian_controller.py index b02d4c3c8..1440b23f3 100644 --- a/zyngine/zynthian_controller.py +++ b/zyngine/zynthian_controller.py @@ -614,6 +614,12 @@ def midi_cc_mode_detect(self, val): #logging.debug(f"CC val={val} => current mode={self.midi_cc_mode}, detecting mode {self.midi_cc_mode_detecting}" # f" (count {self.midi_cc_mode_detecting_count}, zero {self.midi_cc_mode_detecting_zero})\n") + # Always use absolute mode with toggle controllers + if self.is_toggle: + self.midi_cc_mode = 0 + self.midi_cc_mode_detecting = 0 + return + # Mode autodetection timeout now = monotonic() if now - self.midi_cc_mode_detecting_ts > MIDI_CC_MODE_DETECT_TIMEOUT: From 8beda2e72e6bfda2681c4675483b7c90916f98a7 Mon Sep 17 00:00:00 2001 From: jofemodo Date: Thu, 5 Dec 2024 17:39:06 +0100 Subject: [PATCH 063/117] + Remove X-Y touchpad options from toggle controller options submenu. + Hide "relative mode" option if controller is not learned. --- zyngui/zynthian_gui_control.py | 51 +++++++++++++++++----------------- 1 file changed, 26 insertions(+), 25 deletions(-) diff --git a/zyngui/zynthian_gui_control.py b/zyngui/zynthian_gui_control.py index c747b184a..7d2299618 100644 --- a/zyngui/zynthian_gui_control.py +++ b/zyngui/zynthian_gui_control.py @@ -663,28 +663,30 @@ def midi_learn_options(self, i, unlearn_only=False): zctrl = self.zgui_controllers[i].zctrl if zctrl is None: return + mcparams = self.zyngui.chain_manager.get_midi_learn_from_zctrl(zctrl) if not unlearn_only: title = "Control options" - options["X-Y touchpad"] = None - # Only show X-Y if both zctrl are valid - if self.zyngui.state_manager.zctrl_x and self.zyngui.state_manager.zctrl_y: - options["Control"] = True - if self.zyngui.state_manager.zctrl_x: - xinfo = f" => {self.zyngui.state_manager.zctrl_x.name}" - else: - xinfo = "" - if zctrl == self.zyngui.state_manager.zctrl_x: - options[f"\u2612 X-axis{xinfo}"] = False - else: - options[f"\u2610 X-axis{xinfo}"] = zctrl - if self.zyngui.state_manager.zctrl_y: - yinfo = f" => {self.zyngui.state_manager.zctrl_y.name}" - else: - yinfo = "" - if zctrl == self.zyngui.state_manager.zctrl_y: - options[f"\u2612 Y-axis{yinfo}"] = False - else: - options[f"\u2610 Y-axis{yinfo}"] = zctrl + if not zctrl.is_toggle: + options["X-Y touchpad"] = None + # Only show X-Y if both zctrl are valid + if self.zyngui.state_manager.zctrl_x and self.zyngui.state_manager.zctrl_y: + options["Control"] = True + if self.zyngui.state_manager.zctrl_x: + xinfo = f" => {self.zyngui.state_manager.zctrl_x.name}" + else: + xinfo = "" + if zctrl == self.zyngui.state_manager.zctrl_x: + options[f"\u2612 X-axis{xinfo}"] = False + else: + options[f"\u2610 X-axis{xinfo}"] = zctrl + if self.zyngui.state_manager.zctrl_y: + yinfo = f" => {self.zyngui.state_manager.zctrl_y.name}" + else: + yinfo = "" + if zctrl == self.zyngui.state_manager.zctrl_y: + options[f"\u2612 Y-axis{yinfo}"] = False + else: + options[f"\u2610 Y-axis{yinfo}"] = zctrl options["MIDI learn"] = None if zctrl.is_toggle: @@ -692,7 +694,7 @@ def midi_learn_options(self, i, unlearn_only=False): options["\u2612 Momentary => Latch"] = i else: options["\u2610 Momentary => Latch"] = i - else: + elif mcparams: if zctrl.midi_cc_mode == 0: options["\u2610 Relative Mode"] = i else: @@ -702,10 +704,9 @@ def midi_learn_options(self, i, unlearn_only=False): else: title = "Control unlearn" - params = self.zyngui.chain_manager.get_midi_learn_from_zctrl(zctrl) - if params: - if params[1]: - dev_name = zynautoconnect.get_midi_in_devid(params[0] >> 24) + if mcparams: + if mcparams[1]: + dev_name = zynautoconnect.get_midi_in_devid(mcparams[0] >> 24) options[f"Unlearn '{zctrl.name}' from {dev_name}"] = zctrl else: options[f"Unlearn '{zctrl.name}'"] = zctrl From 4d3c4b9f43f5e3872b5617105809582be24f84ef Mon Sep 17 00:00:00 2001 From: jofemodo Date: Thu, 5 Dec 2024 17:53:27 +0100 Subject: [PATCH 064/117] Improve info tips for chain's processor options. --- zyngui/zynthian_gui_chain_options.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/zyngui/zynthian_gui_chain_options.py b/zyngui/zynthian_gui_chain_options.py index 1e88e20ff..4f7118d9d 100644 --- a/zyngui/zynthian_gui_chain_options.py +++ b/zyngui/zynthian_gui_chain_options.py @@ -152,7 +152,7 @@ def generate_chaintree_menu(self): text = " " * indent + "├─ " + name res.append((self.processor_options, processor, text, - [f"MIDI processor options for '{name}'", "midi_processor.png"])) + [f"Options for MIDI processor '{name}'", "midi_processor.png"])) indent += 1 # Add synth processor @@ -161,7 +161,7 @@ def generate_chaintree_menu(self): name = processor.get_name() text = " " * indent + "╰━ " + name res.append((self.processor_options, processor, text, - [f"Synth processor options for '{name}'", "synth_processor.png"])) + [f"Options for synth processor '{name}'", "synth_processor.png"])) indent += 1 # Build pre-fader audio effects chain for slot in range(self.chain.fader_pos): @@ -176,7 +176,7 @@ def generate_chaintree_menu(self): else: text = " " * indent + "┣━ " + name res.append((self.processor_options, processor, text, - [f"Pre-fader audio processor options for '{name}'", "audio_processor.png"])) + [f"Options for pre-fader audio processor '{name}'", "audio_processor.png"])) indent += 1 # Add FADER mark if self.chain.audio_thru or self.chain.synth_slots: @@ -193,7 +193,7 @@ def generate_chaintree_menu(self): else: text = " " * indent + "┣━ " + name res.append((self.processor_options, processor, text, - [f"Post-fader audio processor options for '{name}'", "audio_processor.png"])) + [f"Options for post-fader audio processor '{name}'", "audio_processor.png"])) indent += 1 return res From 5ac73101a15fabc80c5427d2964523863b5378d0 Mon Sep 17 00:00:00 2001 From: riban Date: Fri, 6 Dec 2024 07:37:08 +0000 Subject: [PATCH 065/117] Remove zynaudio8x from ui codebase --- zynlibs/zynaudio8x/LICENSE | 674 ----------------------------- zynlibs/zynaudio8x/Makefile | 15 - zynlibs/zynaudio8x/README.md | 50 --- zynlibs/zynaudio8x/build.sh | 34 -- zynlibs/zynaudio8x/spdif_duplex.c | 95 ---- zynlibs/zynaudio8x/zynaudio8x.dtbs | 54 --- 6 files changed, 922 deletions(-) delete mode 100644 zynlibs/zynaudio8x/LICENSE delete mode 100644 zynlibs/zynaudio8x/Makefile delete mode 100644 zynlibs/zynaudio8x/README.md delete mode 100755 zynlibs/zynaudio8x/build.sh delete mode 100644 zynlibs/zynaudio8x/spdif_duplex.c delete mode 100644 zynlibs/zynaudio8x/zynaudio8x.dtbs diff --git a/zynlibs/zynaudio8x/LICENSE b/zynlibs/zynaudio8x/LICENSE deleted file mode 100644 index f288702d2..000000000 --- a/zynlibs/zynaudio8x/LICENSE +++ /dev/null @@ -1,674 +0,0 @@ - GNU GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The GNU General Public License is a free, copyleft license for -software and other kinds of works. - - The licenses for most software and other practical works are designed -to take away your freedom to share and change the works. By contrast, -the GNU General Public License is intended to guarantee your freedom to -share and change all versions of a program--to make sure it remains free -software for all its users. We, the Free Software Foundation, use the -GNU General Public License for most of our software; it applies also to -any other work released this way by its authors. You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -them if you wish), that you receive source code or can get it if you -want it, that you can change the software or use pieces of it in new -free programs, and that you know you can do these things. - - To protect your rights, we need to prevent others from denying you -these rights or asking you to surrender the rights. Therefore, you have -certain responsibilities if you distribute copies of the software, or if -you modify it: responsibilities to respect the freedom of others. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must pass on to the recipients the same -freedoms that you received. You must make sure that they, too, receive -or can get the source code. And you must show them these terms so they -know their rights. - - Developers that use the GNU GPL protect your rights with two steps: -(1) assert copyright on the software, and (2) offer you this License -giving you legal permission to copy, distribute and/or modify it. - - For the developers' and authors' protection, the GPL clearly explains -that there is no warranty for this free software. For both users' and -authors' sake, the GPL requires that modified versions be marked as -changed, so that their problems will not be attributed erroneously to -authors of previous versions. - - Some devices are designed to deny users access to install or run -modified versions of the software inside them, although the manufacturer -can do so. This is fundamentally incompatible with the aim of -protecting users' freedom to change the software. The systematic -pattern of such abuse occurs in the area of products for individuals to -use, which is precisely where it is most unacceptable. Therefore, we -have designed this version of the GPL to prohibit the practice for those -products. If such problems arise substantially in other domains, we -stand ready to extend this provision to those domains in future versions -of the GPL, as needed to protect the freedom of users. - - Finally, every program is threatened constantly by software patents. -States should not allow patents to restrict development and use of -software on general-purpose computers, but in those that do, we wish to -avoid the special danger that patents applied to a free program could -make it effectively proprietary. To prevent this, the GPL assures that -patents cannot be used to render the program non-free. - - The precise terms and conditions for copying, distribution and -modification follow. - - TERMS AND CONDITIONS - - 0. Definitions. - - "This License" refers to version 3 of the GNU General Public License. - - "Copyright" also means copyright-like laws that apply to other kinds of -works, such as semiconductor masks. - - "The Program" refers to any copyrightable work licensed under this -License. Each licensee is addressed as "you". "Licensees" and -"recipients" may be individuals or organizations. - - To "modify" a work means to copy from or adapt all or part of the work -in a fashion requiring copyright permission, other than the making of an -exact copy. The resulting work is called a "modified version" of the -earlier work or a work "based on" the earlier work. - - A "covered work" means either the unmodified Program or a work based -on the Program. - - To "propagate" a work means to do anything with it that, without -permission, would make you directly or secondarily liable for -infringement under applicable copyright law, except executing it on a -computer or modifying a private copy. Propagation includes copying, -distribution (with or without modification), making available to the -public, and in some countries other activities as well. - - To "convey" a work means any kind of propagation that enables other -parties to make or receive copies. Mere interaction with a user through -a computer network, with no transfer of a copy, is not conveying. - - An interactive user interface displays "Appropriate Legal Notices" -to the extent that it includes a convenient and prominently visible -feature that (1) displays an appropriate copyright notice, and (2) -tells the user that there is no warranty for the work (except to the -extent that warranties are provided), that licensees may convey the -work under this License, and how to view a copy of this License. If -the interface presents a list of user commands or options, such as a -menu, a prominent item in the list meets this criterion. - - 1. Source Code. - - The "source code" for a work means the preferred form of the work -for making modifications to it. "Object code" means any non-source -form of a work. - - A "Standard Interface" means an interface that either is an official -standard defined by a recognized standards body, or, in the case of -interfaces specified for a particular programming language, one that -is widely used among developers working in that language. - - The "System Libraries" of an executable work include anything, other -than the work as a whole, that (a) is included in the normal form of -packaging a Major Component, but which is not part of that Major -Component, and (b) serves only to enable use of the work with that -Major Component, or to implement a Standard Interface for which an -implementation is available to the public in source code form. A -"Major Component", in this context, means a major essential component -(kernel, window system, and so on) of the specific operating system -(if any) on which the executable work runs, or a compiler used to -produce the work, or an object code interpreter used to run it. - - The "Corresponding Source" for a work in object code form means all -the source code needed to generate, install, and (for an executable -work) run the object code and to modify the work, including scripts to -control those activities. However, it does not include the work's -System Libraries, or general-purpose tools or generally available free -programs which are used unmodified in performing those activities but -which are not part of the work. For example, Corresponding Source -includes interface definition files associated with source files for -the work, and the source code for shared libraries and dynamically -linked subprograms that the work is specifically designed to require, -such as by intimate data communication or control flow between those -subprograms and other parts of the work. - - The Corresponding Source need not include anything that users -can regenerate automatically from other parts of the Corresponding -Source. - - The Corresponding Source for a work in source code form is that -same work. - - 2. Basic Permissions. - - All rights granted under this License are granted for the term of -copyright on the Program, and are irrevocable provided the stated -conditions are met. This License explicitly affirms your unlimited -permission to run the unmodified Program. The output from running a -covered work is covered by this License only if the output, given its -content, constitutes a covered work. This License acknowledges your -rights of fair use or other equivalent, as provided by copyright law. - - You may make, run and propagate covered works that you do not -convey, without conditions so long as your license otherwise remains -in force. You may convey covered works to others for the sole purpose -of having them make modifications exclusively for you, or provide you -with facilities for running those works, provided that you comply with -the terms of this License in conveying all material for which you do -not control copyright. Those thus making or running the covered works -for you must do so exclusively on your behalf, under your direction -and control, on terms that prohibit them from making any copies of -your copyrighted material outside their relationship with you. - - Conveying under any other circumstances is permitted solely under -the conditions stated below. Sublicensing is not allowed; section 10 -makes it unnecessary. - - 3. Protecting Users' Legal Rights From Anti-Circumvention Law. - - No covered work shall be deemed part of an effective technological -measure under any applicable law fulfilling obligations under article -11 of the WIPO copyright treaty adopted on 20 December 1996, or -similar laws prohibiting or restricting circumvention of such -measures. - - When you convey a covered work, you waive any legal power to forbid -circumvention of technological measures to the extent such circumvention -is effected by exercising rights under this License with respect to -the covered work, and you disclaim any intention to limit operation or -modification of the work as a means of enforcing, against the work's -users, your or third parties' legal rights to forbid circumvention of -technological measures. - - 4. Conveying Verbatim Copies. - - You may convey verbatim copies of the Program's source code as you -receive it, in any medium, provided that you conspicuously and -appropriately publish on each copy an appropriate copyright notice; -keep intact all notices stating that this License and any -non-permissive terms added in accord with section 7 apply to the code; -keep intact all notices of the absence of any warranty; and give all -recipients a copy of this License along with the Program. - - You may charge any price or no price for each copy that you convey, -and you may offer support or warranty protection for a fee. - - 5. Conveying Modified Source Versions. - - You may convey a work based on the Program, or the modifications to -produce it from the Program, in the form of source code under the -terms of section 4, provided that you also meet all of these conditions: - - a) The work must carry prominent notices stating that you modified - it, and giving a relevant date. - - b) The work must carry prominent notices stating that it is - released under this License and any conditions added under section - 7. This requirement modifies the requirement in section 4 to - "keep intact all notices". - - c) You must license the entire work, as a whole, under this - License to anyone who comes into possession of a copy. This - License will therefore apply, along with any applicable section 7 - additional terms, to the whole of the work, and all its parts, - regardless of how they are packaged. This License gives no - permission to license the work in any other way, but it does not - invalidate such permission if you have separately received it. - - d) If the work has interactive user interfaces, each must display - Appropriate Legal Notices; however, if the Program has interactive - interfaces that do not display Appropriate Legal Notices, your - work need not make them do so. - - A compilation of a covered work with other separate and independent -works, which are not by their nature extensions of the covered work, -and which are not combined with it such as to form a larger program, -in or on a volume of a storage or distribution medium, is called an -"aggregate" if the compilation and its resulting copyright are not -used to limit the access or legal rights of the compilation's users -beyond what the individual works permit. Inclusion of a covered work -in an aggregate does not cause this License to apply to the other -parts of the aggregate. - - 6. Conveying Non-Source Forms. - - You may convey a covered work in object code form under the terms -of sections 4 and 5, provided that you also convey the -machine-readable Corresponding Source under the terms of this License, -in one of these ways: - - a) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by the - Corresponding Source fixed on a durable physical medium - customarily used for software interchange. - - b) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by a - written offer, valid for at least three years and valid for as - long as you offer spare parts or customer support for that product - model, to give anyone who possesses the object code either (1) a - copy of the Corresponding Source for all the software in the - product that is covered by this License, on a durable physical - medium customarily used for software interchange, for a price no - more than your reasonable cost of physically performing this - conveying of source, or (2) access to copy the - Corresponding Source from a network server at no charge. - - c) Convey individual copies of the object code with a copy of the - written offer to provide the Corresponding Source. This - alternative is allowed only occasionally and noncommercially, and - only if you received the object code with such an offer, in accord - with subsection 6b. - - d) Convey the object code by offering access from a designated - place (gratis or for a charge), and offer equivalent access to the - Corresponding Source in the same way through the same place at no - further charge. You need not require recipients to copy the - Corresponding Source along with the object code. If the place to - copy the object code is a network server, the Corresponding Source - may be on a different server (operated by you or a third party) - that supports equivalent copying facilities, provided you maintain - clear directions next to the object code saying where to find the - Corresponding Source. Regardless of what server hosts the - Corresponding Source, you remain obligated to ensure that it is - available for as long as needed to satisfy these requirements. - - e) Convey the object code using peer-to-peer transmission, provided - you inform other peers where the object code and Corresponding - Source of the work are being offered to the general public at no - charge under subsection 6d. - - A separable portion of the object code, whose source code is excluded -from the Corresponding Source as a System Library, need not be -included in conveying the object code work. - - A "User Product" is either (1) a "consumer product", which means any -tangible personal property which is normally used for personal, family, -or household purposes, or (2) anything designed or sold for incorporation -into a dwelling. In determining whether a product is a consumer product, -doubtful cases shall be resolved in favor of coverage. For a particular -product received by a particular user, "normally used" refers to a -typical or common use of that class of product, regardless of the status -of the particular user or of the way in which the particular user -actually uses, or expects or is expected to use, the product. A product -is a consumer product regardless of whether the product has substantial -commercial, industrial or non-consumer uses, unless such uses represent -the only significant mode of use of the product. - - "Installation Information" for a User Product means any methods, -procedures, authorization keys, or other information required to install -and execute modified versions of a covered work in that User Product from -a modified version of its Corresponding Source. The information must -suffice to ensure that the continued functioning of the modified object -code is in no case prevented or interfered with solely because -modification has been made. - - If you convey an object code work under this section in, or with, or -specifically for use in, a User Product, and the conveying occurs as -part of a transaction in which the right of possession and use of the -User Product is transferred to the recipient in perpetuity or for a -fixed term (regardless of how the transaction is characterized), the -Corresponding Source conveyed under this section must be accompanied -by the Installation Information. But this requirement does not apply -if neither you nor any third party retains the ability to install -modified object code on the User Product (for example, the work has -been installed in ROM). - - The requirement to provide Installation Information does not include a -requirement to continue to provide support service, warranty, or updates -for a work that has been modified or installed by the recipient, or for -the User Product in which it has been modified or installed. Access to a -network may be denied when the modification itself materially and -adversely affects the operation of the network or violates the rules and -protocols for communication across the network. - - Corresponding Source conveyed, and Installation Information provided, -in accord with this section must be in a format that is publicly -documented (and with an implementation available to the public in -source code form), and must require no special password or key for -unpacking, reading or copying. - - 7. Additional Terms. - - "Additional permissions" are terms that supplement the terms of this -License by making exceptions from one or more of its conditions. -Additional permissions that are applicable to the entire Program shall -be treated as though they were included in this License, to the extent -that they are valid under applicable law. If additional permissions -apply only to part of the Program, that part may be used separately -under those permissions, but the entire Program remains governed by -this License without regard to the additional permissions. - - When you convey a copy of a covered work, you may at your option -remove any additional permissions from that copy, or from any part of -it. (Additional permissions may be written to require their own -removal in certain cases when you modify the work.) You may place -additional permissions on material, added by you to a covered work, -for which you have or can give appropriate copyright permission. - - Notwithstanding any other provision of this License, for material you -add to a covered work, you may (if authorized by the copyright holders of -that material) supplement the terms of this License with terms: - - a) Disclaiming warranty or limiting liability differently from the - terms of sections 15 and 16 of this License; or - - b) Requiring preservation of specified reasonable legal notices or - author attributions in that material or in the Appropriate Legal - Notices displayed by works containing it; or - - c) Prohibiting misrepresentation of the origin of that material, or - requiring that modified versions of such material be marked in - reasonable ways as different from the original version; or - - d) Limiting the use for publicity purposes of names of licensors or - authors of the material; or - - e) Declining to grant rights under trademark law for use of some - trade names, trademarks, or service marks; or - - f) Requiring indemnification of licensors and authors of that - material by anyone who conveys the material (or modified versions of - it) with contractual assumptions of liability to the recipient, for - any liability that these contractual assumptions directly impose on - those licensors and authors. - - All other non-permissive additional terms are considered "further -restrictions" within the meaning of section 10. If the Program as you -received it, or any part of it, contains a notice stating that it is -governed by this License along with a term that is a further -restriction, you may remove that term. If a license document contains -a further restriction but permits relicensing or conveying under this -License, you may add to a covered work material governed by the terms -of that license document, provided that the further restriction does -not survive such relicensing or conveying. - - If you add terms to a covered work in accord with this section, you -must place, in the relevant source files, a statement of the -additional terms that apply to those files, or a notice indicating -where to find the applicable terms. - - Additional terms, permissive or non-permissive, may be stated in the -form of a separately written license, or stated as exceptions; -the above requirements apply either way. - - 8. Termination. - - You may not propagate or modify a covered work except as expressly -provided under this License. Any attempt otherwise to propagate or -modify it is void, and will automatically terminate your rights under -this License (including any patent licenses granted under the third -paragraph of section 11). - - However, if you cease all violation of this License, then your -license from a particular copyright holder is reinstated (a) -provisionally, unless and until the copyright holder explicitly and -finally terminates your license, and (b) permanently, if the copyright -holder fails to notify you of the violation by some reasonable means -prior to 60 days after the cessation. - - Moreover, your license from a particular copyright holder is -reinstated permanently if the copyright holder notifies you of the -violation by some reasonable means, this is the first time you have -received notice of violation of this License (for any work) from that -copyright holder, and you cure the violation prior to 30 days after -your receipt of the notice. - - Termination of your rights under this section does not terminate the -licenses of parties who have received copies or rights from you under -this License. If your rights have been terminated and not permanently -reinstated, you do not qualify to receive new licenses for the same -material under section 10. - - 9. Acceptance Not Required for Having Copies. - - You are not required to accept this License in order to receive or -run a copy of the Program. Ancillary propagation of a covered work -occurring solely as a consequence of using peer-to-peer transmission -to receive a copy likewise does not require acceptance. However, -nothing other than this License grants you permission to propagate or -modify any covered work. These actions infringe copyright if you do -not accept this License. Therefore, by modifying or propagating a -covered work, you indicate your acceptance of this License to do so. - - 10. Automatic Licensing of Downstream Recipients. - - Each time you convey a covered work, the recipient automatically -receives a license from the original licensors, to run, modify and -propagate that work, subject to this License. You are not responsible -for enforcing compliance by third parties with this License. - - An "entity transaction" is a transaction transferring control of an -organization, or substantially all assets of one, or subdividing an -organization, or merging organizations. If propagation of a covered -work results from an entity transaction, each party to that -transaction who receives a copy of the work also receives whatever -licenses to the work the party's predecessor in interest had or could -give under the previous paragraph, plus a right to possession of the -Corresponding Source of the work from the predecessor in interest, if -the predecessor has it or can get it with reasonable efforts. - - You may not impose any further restrictions on the exercise of the -rights granted or affirmed under this License. For example, you may -not impose a license fee, royalty, or other charge for exercise of -rights granted under this License, and you may not initiate litigation -(including a cross-claim or counterclaim in a lawsuit) alleging that -any patent claim is infringed by making, using, selling, offering for -sale, or importing the Program or any portion of it. - - 11. Patents. - - A "contributor" is a copyright holder who authorizes use under this -License of the Program or a work on which the Program is based. The -work thus licensed is called the contributor's "contributor version". - - A contributor's "essential patent claims" are all patent claims -owned or controlled by the contributor, whether already acquired or -hereafter acquired, that would be infringed by some manner, permitted -by this License, of making, using, or selling its contributor version, -but do not include claims that would be infringed only as a -consequence of further modification of the contributor version. For -purposes of this definition, "control" includes the right to grant -patent sublicenses in a manner consistent with the requirements of -this License. - - Each contributor grants you a non-exclusive, worldwide, royalty-free -patent license under the contributor's essential patent claims, to -make, use, sell, offer for sale, import and otherwise run, modify and -propagate the contents of its contributor version. - - In the following three paragraphs, a "patent license" is any express -agreement or commitment, however denominated, not to enforce a patent -(such as an express permission to practice a patent or covenant not to -sue for patent infringement). To "grant" such a patent license to a -party means to make such an agreement or commitment not to enforce a -patent against the party. - - If you convey a covered work, knowingly relying on a patent license, -and the Corresponding Source of the work is not available for anyone -to copy, free of charge and under the terms of this License, through a -publicly available network server or other readily accessible means, -then you must either (1) cause the Corresponding Source to be so -available, or (2) arrange to deprive yourself of the benefit of the -patent license for this particular work, or (3) arrange, in a manner -consistent with the requirements of this License, to extend the patent -license to downstream recipients. "Knowingly relying" means you have -actual knowledge that, but for the patent license, your conveying the -covered work in a country, or your recipient's use of the covered work -in a country, would infringe one or more identifiable patents in that -country that you have reason to believe are valid. - - If, pursuant to or in connection with a single transaction or -arrangement, you convey, or propagate by procuring conveyance of, a -covered work, and grant a patent license to some of the parties -receiving the covered work authorizing them to use, propagate, modify -or convey a specific copy of the covered work, then the patent license -you grant is automatically extended to all recipients of the covered -work and works based on it. - - A patent license is "discriminatory" if it does not include within -the scope of its coverage, prohibits the exercise of, or is -conditioned on the non-exercise of one or more of the rights that are -specifically granted under this License. You may not convey a covered -work if you are a party to an arrangement with a third party that is -in the business of distributing software, under which you make payment -to the third party based on the extent of your activity of conveying -the work, and under which the third party grants, to any of the -parties who would receive the covered work from you, a discriminatory -patent license (a) in connection with copies of the covered work -conveyed by you (or copies made from those copies), or (b) primarily -for and in connection with specific products or compilations that -contain the covered work, unless you entered into that arrangement, -or that patent license was granted, prior to 28 March 2007. - - Nothing in this License shall be construed as excluding or limiting -any implied license or other defenses to infringement that may -otherwise be available to you under applicable patent law. - - 12. No Surrender of Others' Freedom. - - If conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot convey a -covered work so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you may -not convey it at all. For example, if you agree to terms that obligate you -to collect a royalty for further conveying from those to whom you convey -the Program, the only way you could satisfy both those terms and this -License would be to refrain entirely from conveying the Program. - - 13. Use with the GNU Affero General Public License. - - Notwithstanding any other provision of this License, you have -permission to link or combine any covered work with a work licensed -under version 3 of the GNU Affero General Public License into a single -combined work, and to convey the resulting work. The terms of this -License will continue to apply to the part which is the covered work, -but the special requirements of the GNU Affero General Public License, -section 13, concerning interaction through a network will apply to the -combination as such. - - 14. Revised Versions of this License. - - The Free Software Foundation may publish revised and/or new versions of -the GNU General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - - Each version is given a distinguishing version number. If the -Program specifies that a certain numbered version of the GNU General -Public License "or any later version" applies to it, you have the -option of following the terms and conditions either of that numbered -version or of any later version published by the Free Software -Foundation. If the Program does not specify a version number of the -GNU General Public License, you may choose any version ever published -by the Free Software Foundation. - - If the Program specifies that a proxy can decide which future -versions of the GNU General Public License can be used, that proxy's -public statement of acceptance of a version permanently authorizes you -to choose that version for the Program. - - Later license versions may give you additional or different -permissions. However, no additional obligations are imposed on any -author or copyright holder as a result of your choosing to follow a -later version. - - 15. Disclaimer of Warranty. - - THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY -APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT -HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY -OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM -IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF -ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. Limitation of Liability. - - IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS -THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY -GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE -USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF -DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD -PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), -EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF -SUCH DAMAGES. - - 17. Interpretation of Sections 15 and 16. - - If the disclaimer of warranty and limitation of liability provided -above cannot be given local legal effect according to their terms, -reviewing courts shall apply local law that most closely approximates -an absolute waiver of all civil liability in connection with the -Program, unless a warranty or assumption of liability accompanies a -copy of the Program in return for a fee. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -state the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - 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 3 of the License, or - (at your option) 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. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - -Also add information on how to contact you by electronic and paper mail. - - If the program does terminal interaction, make it output a short -notice like this when it starts in an interactive mode: - - Copyright (C) - This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, your program's commands -might be different; for a GUI interface, you would use an "about box". - - You should also get your employer (if you work as a programmer) or school, -if any, to sign a "copyright disclaimer" for the program, if necessary. -For more information on this, and how to apply and follow the GNU GPL, see -. - - The GNU General Public License does not permit incorporating your program -into proprietary programs. If your program is a subroutine library, you -may consider it more useful to permit linking proprietary applications with -the library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. But first, please read -. diff --git a/zynlibs/zynaudio8x/Makefile b/zynlibs/zynaudio8x/Makefile deleted file mode 100644 index 14e726c57..000000000 --- a/zynlibs/zynaudio8x/Makefile +++ /dev/null @@ -1,15 +0,0 @@ -obj-m += spdif_duplex.o -KERNEL?=$(shell uname -r) - -all: - make -C /lib/modules/$(KERNEL)/build M=$(PWD) modules - dtc -@ -H epapr -O dtb -o zynaudio8x.dtbo -Wno-unit_address_vs_reg zynaudio8x.dtbs - -clean: - make -C /lib/modules/$(KERNEL)/build M=$(PWD) clean - -install: - cp zynaudio8x.dtbo /boot/firmware/overlays/ - xz -f spdif_duplex.ko - cp spdif_duplex.ko.xz /lib/modules/$(KERNEL)/kernel/sound/soc/codecs/snd-soc-spdif_duplex.ko.xz - depmod -a diff --git a/zynlibs/zynaudio8x/README.md b/zynlibs/zynaudio8x/README.md deleted file mode 100644 index de2702b9f..000000000 --- a/zynlibs/zynaudio8x/README.md +++ /dev/null @@ -1,50 +0,0 @@ -# zynaudio8x -Provides requirements to enable zynthian 8 channel I2S soundcard. - -A device tree overlay to enable 8 channels of audio input and audio output. -A kernel device driver providing duplex SPDIF interface. - -This only works on Raspberry Pi 5. - -## Dependencies: - -``` -apt-get install raspberrypi-kernel-headers -``` - -@stojos says this is also required: -``` -wget https://raw.githubusercontent.com/RPi-Distro/rpi-source/master/rpi-source -O /usr/local/bin/rpi-source && sudo chmod +x /usr/local/bin/rpi-source && /usr/local/bin/rpi-source -q --tag-update -rpi-source -``` - -## Build: - -`make` - -## Install: - -`make install` - -Configure /boot/firmware/config.txt: - -`dtoverlay=zynaudio8x,inputs=4,outputs=2` - -`inputs` and `outputs` are optional parameters that limit the quantity of input (capture) and output (playback) ports. Range 0..8 and must be even. Invalid values will default to 8. - -The soundcard must create bit clock and word clock. Bit clock should be connected to Rasberry Pi pin 12 (GPIO 18). Word clock should be connected to Raspberry Pi pin 35 (GPIO 19). - -## Connections -|GPIO|Pin|Description| -|-|-|-| -|18|12|Bit Clock| -|19|35|Word Clock| -|20|38|I2S 1 In| -|21|40|I2S 1 Out| -|22|15|I2S 2 In| -|23|16|I2S 2 Out| -|24|18|I2S 3 In| -|25|22|I2S 3 Out| -|26|37|I2S 4 In| -|27|13|I2S 4 Out| - diff --git a/zynlibs/zynaudio8x/build.sh b/zynlibs/zynaudio8x/build.sh deleted file mode 100755 index 0713600ae..000000000 --- a/zynlibs/zynaudio8x/build.sh +++ /dev/null @@ -1,34 +0,0 @@ -#!/bin/bash - -# Check if RBPI_VERSION_NUMBER is set -if [ -z "$RBPI_VERSION_NUMBER" ] -then - echo "Error: RBPI_VERSION_NUMBER is not set." - exit 0 -fi - -# Check if RBPI_VERSION_NUMBER equals 5 -if [ "$RBPI_VERSION_NUMBER" -ne 5 ] -then - echo "Not installing zynaudio8x for < RPi5" - exit 0 -fi - -# Check if new kernel installed -if [ -f /tmp/kernel_ver ] -then - KERNEL=`cat /tmp/kernel_ver` -else - KERNEL=`uname -r` -fi - -echo "Installing zynaudio8x" - -DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" - -pushd $DIR - make KERNEL=$KERNEL && make install - success=$? -popd -exit $success - diff --git a/zynlibs/zynaudio8x/spdif_duplex.c b/zynlibs/zynaudio8x/spdif_duplex.c deleted file mode 100644 index 9e41bdf53..000000000 --- a/zynlibs/zynaudio8x/spdif_duplex.c +++ /dev/null @@ -1,95 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* - * ALSA SoC SPDIF DID (Digital Interface Duplex) driver - * - * Based on ALSA SoC SPDIF DIT driver - * - * This driver is used by controllers which can operate in DUPLEX where - * no codec is needed. - * - * Author: Brian Walton based on code by Vipin Kumar, - */ - -#include - -#define STUB_RATES (SNDRV_PCM_RATE_8000_96000) -#define STUB_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \ - SNDRV_PCM_FMTBIT_S20_3LE | \ - SNDRV_PCM_FMTBIT_S24_LE | \ - SNDRV_PCM_FMTBIT_S32_LE | \ - SNDRV_PCM_FMTBIT_IEC958_SUBFRAME_LE) - -static const struct snd_soc_dapm_widget duplex_widgets[] = { - SND_SOC_DAPM_INPUT("spdif-in"), - SND_SOC_DAPM_OUTPUT("spdif-out"), -}; - -static const struct snd_soc_dapm_route duplex_routes[] = { - { "Capture", NULL, "spdif-in" }, - { "Playback", NULL, "spdif-out" }, -}; - -static const struct snd_soc_component_driver soc_codec_spdif_duplex = { - .dapm_widgets = duplex_widgets, - .num_dapm_widgets = ARRAY_SIZE(duplex_widgets), - .dapm_routes = duplex_routes, - .num_dapm_routes = ARRAY_SIZE(duplex_routes), - .idle_bias_on = 1, - .use_pmdown_time = 1, - .endianness = 1, -}; - -static struct snd_soc_dai_driver duplex_stub_dai = { - .name = "did-hifi", - .capture = { - .stream_name = "Capture", - .channels_min = 1, - .channels_max = 384, - .rates = STUB_RATES, - .formats = STUB_FORMATS, - }, - .playback = { - .stream_name = "Playback", - .channels_min = 1, - .channels_max = 384, - .rates = STUB_RATES, - .formats = STUB_FORMATS, - }, -}; - -static int spdif_duplex_probe(struct platform_device *pdev) -{ - struct device_node *np = pdev->dev.of_node; - uint32_t val = 2; - of_property_read_u32(np, "capture-channels", &val); - if (val < 385 && (val % 2) == 0) - duplex_stub_dai.capture.channels_max = val; - val = 2; - of_property_read_u32(np, "playback-channels", &val); - if (val < 385 && (val % 2) == 0) - duplex_stub_dai.playback.channels_max = val; - - return devm_snd_soc_register_component(&pdev->dev, - &soc_codec_spdif_duplex, - &duplex_stub_dai, 1); -}; - -static const struct of_device_id spdif_duplex_dt_ids[] = { - { .compatible = "linux,spdif-did", }, - { } -}; - -static struct platform_driver spdif_duplex_driver = { - .probe = spdif_duplex_probe, - .driver = { - .name = "spdif-did", - .of_match_table = of_match_ptr(spdif_duplex_dt_ids), - }, -}; - -module_platform_driver(spdif_duplex_driver); - -MODULE_DEVICE_TABLE(of, spdif_duplex_dt_ids); -MODULE_DESCRIPTION("ASoC SPDIF DID driver"); -MODULE_AUTHOR("Brian Walton , Vipin Kumar "); -MODULE_LICENSE("GPL"); \ No newline at end of file diff --git a/zynlibs/zynaudio8x/zynaudio8x.dtbs b/zynlibs/zynaudio8x/zynaudio8x.dtbs deleted file mode 100644 index f515a9f36..000000000 --- a/zynlibs/zynaudio8x/zynaudio8x.dtbs +++ /dev/null @@ -1,54 +0,0 @@ -// Definitions for zynthian 8 channel I2S soundcard -/dts-v1/; -/plugin/; - -/ { - compatible = "brcm,bcm2712"; // Raspberry Pi 5 - - fragment@0 { - // Configure the GPIO pins for clock consumer - target = <&i2s_clk_consumer>; - __overlay__ { - status = "okay"; - }; - }; - - fragment@1 { - // Define CODEC - target-path = "/"; - __overlay__ { - duplex_codec: duplex-codec { - compatible = "linux,spdif-did"; - #sound-dai-cells = <0>; - capture-channels = <8>; - playback-channels = <8>; - status = "okay"; - }; - }; - }; - - fragment@2 { - // Configure and enable soundcard driver - target = <&sound>; - __overlay__ { - compatible = "simple-audio-card"; - simple-audio-card,name = "zynaudio8x"; - simple-audio-card,format = "i2s"; - simple-audio-card,bitclock-master = <&snd_codec>; - simple-audio-card,frame-master = <&snd_codec>; - status = "okay"; - cpu { - sound-dai = <&i2s_clk_consumer>; - }; - snd_codec: simple-audio-card,codec { - sound-dai = <&duplex_codec>; - }; - }; - }; - - __overrides__ { - inputs = <&duplex_codec>,"capture-channels:0"; - outputs = <&duplex_codec>,"playback-channels:0"; - }; - -}; From d708aee75c89536f48aa505466f6afc210517881 Mon Sep 17 00:00:00 2001 From: riban Date: Sat, 7 Dec 2024 11:00:40 +0000 Subject: [PATCH 066/117] Adds help to MIDI device menu --- zyngui/zynthian_gui_midi_config.py | 93 ++++++++++-------------------- 1 file changed, 31 insertions(+), 62 deletions(-) diff --git a/zyngui/zynthian_gui_midi_config.py b/zyngui/zynthian_gui_midi_config.py index bb628382c..93ccb3c9b 100644 --- a/zyngui/zynthian_gui_midi_config.py +++ b/zyngui/zynthian_gui_midi_config.py @@ -34,8 +34,9 @@ # Zynthian specific modules import zynautoconnect from zyncoder.zyncore import lib_zyncore -from zyngui.zynthian_gui_selector import zynthian_gui_selector +from zyngui.zynthian_gui_selector_info import zynthian_gui_selector_info from zyngui import zynthian_gui_config +import zynconf # ------------------------------------------------------------------------------ # Mini class to allow use of audio_in gui @@ -65,13 +66,13 @@ def toggle_audio_in(self, input): ZMIP_MODE_MULTI = "⇶" # \u21f6 -class zynthian_gui_midi_config(zynthian_gui_selector): +class zynthian_gui_midi_config(zynthian_gui_selector_info): def __init__(self): self.chain = None # Chain object self.input = True # True to process MIDI inputs, False for MIDI outputs self.thread = None - super().__init__('MIDI Devices', True) + super().__init__('Menu') def build_view(self): # Enable background scan for MIDI devices @@ -124,36 +125,30 @@ def append_port(idev): if self.input: port = zynautoconnect.devices_in[idev] mode = get_mode_str(idev) + input_mode_info = f"\n{ZMIP_MODE_ACTIVE} - Active Chain\n{ZMIP_MODE_MULTI} - Multitimbral\n{ZMIP_MODE_CONTROLLER} - Device Controller" if self.chain is None: - self.list_data.append((port.aliases[0], idev, f"{mode}{port.aliases[1]}")) + self.list_data.append((port.aliases[0], idev, f"{mode}{port.aliases[1]}", [f"Bold press to show options for MIDI input '{port.aliases[1]}'.{input_mode_info}", None])) elif not self.zyngui.state_manager.ctrldev_manager.is_input_device_available_to_chains(idev): - self.list_data.append((port.aliases[0], idev, f" {mode}{port.aliases[1]}")) + self.list_data.append((port.aliases[0], idev, f" {mode}{port.aliases[1]}", [f"Enable MIDI input '{port.aliases[1]}' for this chain. Bold press to show options for this MIDI input port.{input_mode_info}", None])) else: if lib_zyncore.zmop_get_route_from(self.chain.zmop_index, idev): - self.list_data.append((port.aliases[0], idev, f"\u2612 {mode}{port.aliases[1]}")) + self.list_data.append((port.aliases[0], idev, f"\u2612 {mode}{port.aliases[1]}", [f"Disconnect '{port.aliases[1]}' from the MIDI input of this chain. Bold press to show options for this MIDI input port.{input_mode_info}", None])) else: - self.list_data.append((port.aliases[0], idev, f"\u2610 {mode}{port.aliases[1]}")) + self.list_data.append((port.aliases[0], idev, f"\u2610 {mode}{port.aliases[1]}", [f"Connect '{port.aliases[1]}' to the MIDI input of this chain. Bold press to show options for this MIDI input port.{input_mode_info}", None])) else: port = zynautoconnect.devices_out[idev] if self.chain is None: - self.list_data.append((port.aliases[0], idev, f"{port.aliases[1]}")) + self.list_data.append((port.aliases[0], idev, f"{port.aliases[1]}", [f"Bold press to show options for MIDI output '{port.aliases[1]}'.", None])) elif port.aliases[0] in self.chain.midi_out: - self.list_data.append((port.aliases[0], idev, f"\u2612 {port.aliases[1]}")) + self.list_data.append((port.aliases[0], idev, f"\u2612 {port.aliases[1]}", [f"Disconnect this chain's MIDI output from '{port.aliases[1]}'. Bold press to show options for this MIDI output port.", None])) else: - self.list_data.append((port.aliases[0], idev, f"\u2610 {port.aliases[1]}")) + self.list_data.append((port.aliases[0], idev, f"\u2610 {port.aliases[1]}", [f"Connect this chain's MIDI output to '{port.aliases[1]}'. Bold press to show options for this MIDI output port.", None])) - def append_service_device(dev_name, obj): - """Add service (that is also a port) to list""" - if isinstance(obj, int): - if self.input: - port = zynautoconnect.devices_in[obj] - else: - port = zynautoconnect.devices_out[obj] - if port: - mode = get_mode_str(obj) - self.list_data.append((f"stop_{dev_name}", obj, f"\u2612 {mode}{port.aliases[1]}")) + def append_service(service, name, help_info=""): + if zynconf.is_service_active(service): + self.list_data.append((f"stop_{service}", None, f"\u2612 {name}", [f"Disable {help_info}.", None])) else: - self.list_data.append((f"start_{dev_name}", None, f"\u2610 {obj}")) + self.list_data.append((f"start_{service}", None, f"\u2610 {name}", [f"Enable {help_info}.", None])) def atoi(text): return int(text) if text.isdigit() else text @@ -196,10 +191,7 @@ def natural_keys(t): for i in aubio_devices: append_port(i) else: - if aubio_devices: - append_service_device("aubionotes", aubio_devices[0]) - else: - append_service_device("aubionotes", "Aubionotes (Audio \u2794 MIDI)") + append_service("aubionotes", "Aubionotes (Audio \u2794 MIDI)", "Aubionotes. This converts audio input to MIDI output") # Remove "Internal Devices" title if section is empty if len(self.list_data) == nint: @@ -212,13 +204,10 @@ def natural_keys(t): if self.chain is None or ble_devices: self.list_data.append((None, None, "Bluetooth Devices")) - if zynthian_gui_config.bluetooth_enabled: - if self.chain is None: - self.list_data.append(("stop_bluetooth", None, "\u2612 BLE MIDI")) - for x in sorted(ble_devices, key=natural_keys): - append_port(x[1]) - elif self.chain is None: - self.list_data.append(("start_bluetooth", None, "\u2610 BLE MIDI")) + if self.chain is None: + append_service("bluetooth", "BLE MIDI", "Bluetooth MIDI") + for x in sorted(ble_devices, key=natural_keys): + append_port(x[1]) if not self.chain or net_devices: self.list_data.append((None, None, "Network Devices")) @@ -227,36 +216,16 @@ def natural_keys(t): append_port(i) else: if os.path.isfile("/usr/local/bin/jacknetumpd"): - if "jacknetumpd:netump_in" in net_devices: - append_service_device("jacknetumpd", net_devices["jacknetumpd:netump_in"]) - elif "jacknetumpd:netump_out" in net_devices: - append_service_device("jacknetumpd", net_devices["jacknetumpd:netump_out"]) - else: - append_service_device("jacknetumpd", "NetUMP: MIDI 2.0") + append_service("jacknetumpd", "NetUMP: MIDI 2.0", "JACKNet MIDI UDP.. This provides MIDI over an IP (network) connection to other JACKNet devices") if os.path.isfile("/usr/local/bin/jackrtpmidid"): - if "jackrtpmidid:rtpmidi_in" in net_devices: - append_service_device("jackrtpmidid", net_devices["jackrtpmidid:rtpmidi_in"]) - elif "jackrtpmidid:rtpmidi_out" in net_devices: - append_service_device("jackrtpmidid", net_devices["jackrtpmidid:rtpmidi_out"]) - else: - append_service_device("jackrtpmidid", "RTP-MIDI") + append_service("jackrtpmidid", "RTP-MIDI", "RTP-MIDI. This provides MIDI over an IP (network) connection using the RTP-MIDI standard, also known as AppleMIDI") if os.path.isfile("/usr/local/bin/qmidinet"): - if "QmidiNet:in_1" in net_devices: - append_service_device("QmidiNet", net_devices["QmidiNet:in_1"]) - elif "QmidiNet:out_1" in net_devices: - append_service_device("QmidiNet", net_devices["QmidiNet:out_1"]) - else: - append_service_device("QmidiNet", "QmidiNet") + append_service("qmidinet", "QmidiNet", "QmidiNet. This provides MIDI over an IP (network) connection to other QmidiNet devies") if os.path.isfile("/zynthian/venv/bin/touchosc2midi"): - if "RtMidiIn Client:TouchOSC Bridge" in net_devices: - append_service_device("touchosc", net_devices["RtMidiIn Client:TouchOSC Bridge"]) - elif "RtMidiOut Client:TouchOSC Bridge" in net_devices: - append_service_device("touchosc", net_devices["RtMidiOut Client:TouchOSC Bridge"]) - else: - append_service_device("touchosc", "TouchOSC Bridge") + append_service("touchosc2midi", "TouchOSC Bridge", "interface with Hexler TouchOSC modular control surface") if not self.input and self.chain: self.list_data.append((None, None, "> Chain inputs")) @@ -269,10 +238,10 @@ def natural_keys(t): prefix = "" if chain_id in self.chain.midi_out: self.list_data.append( - (chain_id, None, f"\u2612 {prefix}{chain.get_name()}")) + (chain_id, None, f"\u2612 {prefix}{chain.get_name()}", [f"Disconnect this chain's MIDI output from the MIDI input of chain '{prefix}{chain.get_name()}'.", None])) else: self.list_data.append( - (chain_id, None, f"\u2610 {prefix}{chain.get_name()}")) + (chain_id, None, f"\u2610 {prefix}{chain.get_name()}", [f"Connect this chain's MIDI output to the MIDI input of chain '{prefix}{chain.get_name()}'.", None])) super().fill_list() @@ -288,13 +257,13 @@ def select_action(self, i, t='S'): self.zyngui.state_manager.stop_rtpmidi(wait=wait) elif action == "start_jackrtpmidid": self.zyngui.state_manager.start_rtpmidi(wait=wait) - elif action == "stop_QmidiNet": + elif action == "stop_qmidinet": self.zyngui.state_manager.stop_qmidinet(wait=wait) - elif action == "start_QmidiNet": + elif action == "start_qmidinet": self.zyngui.state_manager.start_qmidinet(wait=wait) - elif action == "stop_touchosc": + elif action == "stop_touchosc2midi": self.zyngui.state_manager.stop_touchosc2midi(wait=wait) - elif action == "start_touchosc": + elif action == "start_touchosc2midi": self.zyngui.state_manager.start_touchosc2midi(wait=wait) elif action == "stop_aubionotes": self.zyngui.state_manager.stop_aubionotes(wait=wait) From f9e903597091389502e07712991db23a687d5843 Mon Sep 17 00:00:00 2001 From: riban Date: Sat, 7 Dec 2024 11:30:03 +0000 Subject: [PATCH 067/117] Adds icons for MIDI device help --- zyngui/zynthian_gui_midi_config.py | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/zyngui/zynthian_gui_midi_config.py b/zyngui/zynthian_gui_midi_config.py index 93ccb3c9b..90d10d03e 100644 --- a/zyngui/zynthian_gui_midi_config.py +++ b/zyngui/zynthian_gui_midi_config.py @@ -64,6 +64,9 @@ def toggle_audio_in(self, input): ZMIP_MODE_CONTROLLER = "⌨" # \u2328 ZMIP_MODE_ACTIVE = "⇥" # \u21e5 ZMIP_MODE_MULTI = "⇶" # \u21f6 +SERVICE_ICONS = { + "aubionotes": "midi_audio.png" +} class zynthian_gui_midi_config(zynthian_gui_selector_info): @@ -127,28 +130,32 @@ def append_port(idev): mode = get_mode_str(idev) input_mode_info = f"\n{ZMIP_MODE_ACTIVE} - Active Chain\n{ZMIP_MODE_MULTI} - Multitimbral\n{ZMIP_MODE_CONTROLLER} - Device Controller" if self.chain is None: - self.list_data.append((port.aliases[0], idev, f"{mode}{port.aliases[1]}", [f"Bold press to show options for MIDI input '{port.aliases[1]}'.{input_mode_info}", None])) + self.list_data.append((port.aliases[0], idev, f"{mode}{port.aliases[1]}", [f"Bold press to show options for MIDI input '{port.aliases[1]}'.{input_mode_info}", "midi_input.png"])) elif not self.zyngui.state_manager.ctrldev_manager.is_input_device_available_to_chains(idev): - self.list_data.append((port.aliases[0], idev, f" {mode}{port.aliases[1]}", [f"Enable MIDI input '{port.aliases[1]}' for this chain. Bold press to show options for this MIDI input port.{input_mode_info}", None])) + self.list_data.append((port.aliases[0], idev, f" {mode}{port.aliases[1]}", [f"Connect '{port.aliases[1]}' to this chain's MIDI input. Bold press to show options for this MIDI input port.{input_mode_info}", None])) else: if lib_zyncore.zmop_get_route_from(self.chain.zmop_index, idev): - self.list_data.append((port.aliases[0], idev, f"\u2612 {mode}{port.aliases[1]}", [f"Disconnect '{port.aliases[1]}' from the MIDI input of this chain. Bold press to show options for this MIDI input port.{input_mode_info}", None])) + self.list_data.append((port.aliases[0], idev, f"\u2612 {mode}{port.aliases[1]}", [f"Disconnect '{port.aliases[1]}' from this chain's MIDI input. Bold press to show options for this MIDI input port.{input_mode_info}", "midi_input.png"])) else: - self.list_data.append((port.aliases[0], idev, f"\u2610 {mode}{port.aliases[1]}", [f"Connect '{port.aliases[1]}' to the MIDI input of this chain. Bold press to show options for this MIDI input port.{input_mode_info}", None])) + self.list_data.append((port.aliases[0], idev, f"\u2610 {mode}{port.aliases[1]}", [f"Connect '{port.aliases[1]}' to this chain's MIDI input. Bold press to show options for this MIDI input port.{input_mode_info}", "midi_input.png"])) else: port = zynautoconnect.devices_out[idev] if self.chain is None: - self.list_data.append((port.aliases[0], idev, f"{port.aliases[1]}", [f"Bold press to show options for MIDI output '{port.aliases[1]}'.", None])) + self.list_data.append((port.aliases[0], idev, f"{port.aliases[1]}", [f"Bold press to show options for MIDI output '{port.aliases[1]}'.", "midi_output.png"])) elif port.aliases[0] in self.chain.midi_out: - self.list_data.append((port.aliases[0], idev, f"\u2612 {port.aliases[1]}", [f"Disconnect this chain's MIDI output from '{port.aliases[1]}'. Bold press to show options for this MIDI output port.", None])) + self.list_data.append((port.aliases[0], idev, f"\u2612 {port.aliases[1]}", [f"Disconnect this chain's MIDI output from '{port.aliases[1]}'. Bold press to show options for this MIDI output port.", "midi_output.png"])) else: - self.list_data.append((port.aliases[0], idev, f"\u2610 {port.aliases[1]}", [f"Connect this chain's MIDI output to '{port.aliases[1]}'. Bold press to show options for this MIDI output port.", None])) + self.list_data.append((port.aliases[0], idev, f"\u2610 {port.aliases[1]}", [f"Connect this chain's MIDI output to '{port.aliases[1]}'. Bold press to show options for this MIDI output port.", "midi_output.png"])) def append_service(service, name, help_info=""): + if service in SERVICE_ICONS: + icon = SERVICE_ICONS[service] + else: + icon = "midi_logo.png" if zynconf.is_service_active(service): - self.list_data.append((f"stop_{service}", None, f"\u2612 {name}", [f"Disable {help_info}.", None])) + self.list_data.append((f"stop_{service}", None, f"\u2612 {name}", [f"Disable {help_info}.", icon])) else: - self.list_data.append((f"start_{service}", None, f"\u2610 {name}", [f"Enable {help_info}.", None])) + self.list_data.append((f"start_{service}", None, f"\u2610 {name}", [f"Enable {help_info}.", icon])) def atoi(text): return int(text) if text.isdigit() else text @@ -238,10 +245,10 @@ def natural_keys(t): prefix = "" if chain_id in self.chain.midi_out: self.list_data.append( - (chain_id, None, f"\u2612 {prefix}{chain.get_name()}", [f"Disconnect this chain's MIDI output from the MIDI input of chain '{prefix}{chain.get_name()}'.", None])) + (chain_id, None, f"\u2612 {prefix}{chain.get_name()}", [f"Disconnect this chain's MIDI output from the MIDI input of chain '{prefix}{chain.get_name()}'.", "midi_output.png"])) else: self.list_data.append( - (chain_id, None, f"\u2610 {prefix}{chain.get_name()}", [f"Connect this chain's MIDI output to the MIDI input of chain '{prefix}{chain.get_name()}'.", None])) + (chain_id, None, f"\u2610 {prefix}{chain.get_name()}", [f"Connect this chain's MIDI output to the MIDI input of chain '{prefix}{chain.get_name()}'.", "midi_output.png"])) super().fill_list() From b46565c1593fe81d86bed6a7a00eca5d193d159a Mon Sep 17 00:00:00 2001 From: riban Date: Sat, 7 Dec 2024 11:50:48 +0000 Subject: [PATCH 068/117] Change bold press to bold select in midi device help --- zyngui/zynthian_gui_midi_config.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/zyngui/zynthian_gui_midi_config.py b/zyngui/zynthian_gui_midi_config.py index 90d10d03e..e8d780657 100644 --- a/zyngui/zynthian_gui_midi_config.py +++ b/zyngui/zynthian_gui_midi_config.py @@ -130,22 +130,22 @@ def append_port(idev): mode = get_mode_str(idev) input_mode_info = f"\n{ZMIP_MODE_ACTIVE} - Active Chain\n{ZMIP_MODE_MULTI} - Multitimbral\n{ZMIP_MODE_CONTROLLER} - Device Controller" if self.chain is None: - self.list_data.append((port.aliases[0], idev, f"{mode}{port.aliases[1]}", [f"Bold press to show options for MIDI input '{port.aliases[1]}'.{input_mode_info}", "midi_input.png"])) + self.list_data.append((port.aliases[0], idev, f"{mode}{port.aliases[1]}", [f"Bold select to show options for MIDI input '{port.aliases[1]}'.{input_mode_info}", "midi_input.png"])) elif not self.zyngui.state_manager.ctrldev_manager.is_input_device_available_to_chains(idev): - self.list_data.append((port.aliases[0], idev, f" {mode}{port.aliases[1]}", [f"Connect '{port.aliases[1]}' to this chain's MIDI input. Bold press to show options for this MIDI input port.{input_mode_info}", None])) + self.list_data.append((port.aliases[0], idev, f" {mode}{port.aliases[1]}", [f"Connect '{port.aliases[1]}' to this chain's MIDI input. Bold select to show options for this MIDI input port.{input_mode_info}", None])) else: if lib_zyncore.zmop_get_route_from(self.chain.zmop_index, idev): - self.list_data.append((port.aliases[0], idev, f"\u2612 {mode}{port.aliases[1]}", [f"Disconnect '{port.aliases[1]}' from this chain's MIDI input. Bold press to show options for this MIDI input port.{input_mode_info}", "midi_input.png"])) + self.list_data.append((port.aliases[0], idev, f"\u2612 {mode}{port.aliases[1]}", [f"Disconnect '{port.aliases[1]}' from this chain's MIDI input. Bold select to show options for this MIDI input port.{input_mode_info}", "midi_input.png"])) else: - self.list_data.append((port.aliases[0], idev, f"\u2610 {mode}{port.aliases[1]}", [f"Connect '{port.aliases[1]}' to this chain's MIDI input. Bold press to show options for this MIDI input port.{input_mode_info}", "midi_input.png"])) + self.list_data.append((port.aliases[0], idev, f"\u2610 {mode}{port.aliases[1]}", [f"Connect '{port.aliases[1]}' to this chain's MIDI input. Bold select to show options for this MIDI input port.{input_mode_info}", "midi_input.png"])) else: port = zynautoconnect.devices_out[idev] if self.chain is None: - self.list_data.append((port.aliases[0], idev, f"{port.aliases[1]}", [f"Bold press to show options for MIDI output '{port.aliases[1]}'.", "midi_output.png"])) + self.list_data.append((port.aliases[0], idev, f"{port.aliases[1]}", [f"Bold select to show options for MIDI output '{port.aliases[1]}'.", "midi_output.png"])) elif port.aliases[0] in self.chain.midi_out: - self.list_data.append((port.aliases[0], idev, f"\u2612 {port.aliases[1]}", [f"Disconnect this chain's MIDI output from '{port.aliases[1]}'. Bold press to show options for this MIDI output port.", "midi_output.png"])) + self.list_data.append((port.aliases[0], idev, f"\u2612 {port.aliases[1]}", [f"Disconnect this chain's MIDI output from '{port.aliases[1]}'. Bold select to show options for this MIDI output port.", "midi_output.png"])) else: - self.list_data.append((port.aliases[0], idev, f"\u2610 {port.aliases[1]}", [f"Connect this chain's MIDI output to '{port.aliases[1]}'. Bold press to show options for this MIDI output port.", "midi_output.png"])) + self.list_data.append((port.aliases[0], idev, f"\u2610 {port.aliases[1]}", [f"Connect this chain's MIDI output to '{port.aliases[1]}'. Bold select to show options for this MIDI output port.", "midi_output.png"])) def append_service(service, name, help_info=""): if service in SERVICE_ICONS: From 9f135452693dc151044f2cd2647702b6fcefc6b3 Mon Sep 17 00:00:00 2001 From: riban Date: Sat, 7 Dec 2024 12:24:27 +0000 Subject: [PATCH 069/117] Tidy some info display in midi device menu --- zyngui/zynthian_gui_midi_config.py | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/zyngui/zynthian_gui_midi_config.py b/zyngui/zynthian_gui_midi_config.py index e8d780657..09279d9c6 100644 --- a/zyngui/zynthian_gui_midi_config.py +++ b/zyngui/zynthian_gui_midi_config.py @@ -132,20 +132,20 @@ def append_port(idev): if self.chain is None: self.list_data.append((port.aliases[0], idev, f"{mode}{port.aliases[1]}", [f"Bold select to show options for MIDI input '{port.aliases[1]}'.{input_mode_info}", "midi_input.png"])) elif not self.zyngui.state_manager.ctrldev_manager.is_input_device_available_to_chains(idev): - self.list_data.append((port.aliases[0], idev, f" {mode}{port.aliases[1]}", [f"Connect '{port.aliases[1]}' to this chain's MIDI input. Bold select to show options for this MIDI input port.{input_mode_info}", None])) + self.list_data.append((port.aliases[0], idev, f" {mode}{port.aliases[1]}", [f"Connect '{port.aliases[1]}' to this chain's MIDI input.\nBold select to show more options.{input_mode_info}", None])) else: if lib_zyncore.zmop_get_route_from(self.chain.zmop_index, idev): - self.list_data.append((port.aliases[0], idev, f"\u2612 {mode}{port.aliases[1]}", [f"Disconnect '{port.aliases[1]}' from this chain's MIDI input. Bold select to show options for this MIDI input port.{input_mode_info}", "midi_input.png"])) + self.list_data.append((port.aliases[0], idev, f"\u2612 {mode}{port.aliases[1]}", [f"Disconnect '{port.aliases[1]}' from this chain's MIDI input.\nBold select to show more options.{input_mode_info}", "midi_input.png"])) else: - self.list_data.append((port.aliases[0], idev, f"\u2610 {mode}{port.aliases[1]}", [f"Connect '{port.aliases[1]}' to this chain's MIDI input. Bold select to show options for this MIDI input port.{input_mode_info}", "midi_input.png"])) + self.list_data.append((port.aliases[0], idev, f"\u2610 {mode}{port.aliases[1]}", [f"Connect '{port.aliases[1]}' to this chain's MIDI input.\nBold select to show more options.{input_mode_info}", "midi_input.png"])) else: port = zynautoconnect.devices_out[idev] if self.chain is None: self.list_data.append((port.aliases[0], idev, f"{port.aliases[1]}", [f"Bold select to show options for MIDI output '{port.aliases[1]}'.", "midi_output.png"])) elif port.aliases[0] in self.chain.midi_out: - self.list_data.append((port.aliases[0], idev, f"\u2612 {port.aliases[1]}", [f"Disconnect this chain's MIDI output from '{port.aliases[1]}'. Bold select to show options for this MIDI output port.", "midi_output.png"])) + self.list_data.append((port.aliases[0], idev, f"\u2612 {port.aliases[1]}", [f"Disconnect this chain's MIDI output from '{port.aliases[1]}'.\nBold select to show more options.", "midi_output.png"])) else: - self.list_data.append((port.aliases[0], idev, f"\u2610 {port.aliases[1]}", [f"Connect this chain's MIDI output to '{port.aliases[1]}'. Bold select to show options for this MIDI output port.", "midi_output.png"])) + self.list_data.append((port.aliases[0], idev, f"\u2610 {port.aliases[1]}", [f"Connect this chain's MIDI output to '{port.aliases[1]}'.\nBold select to show more options.", "midi_output.png"])) def append_service(service, name, help_info=""): if service in SERVICE_ICONS: @@ -153,9 +153,9 @@ def append_service(service, name, help_info=""): else: icon = "midi_logo.png" if zynconf.is_service_active(service): - self.list_data.append((f"stop_{service}", None, f"\u2612 {name}", [f"Disable {help_info}.", icon])) + self.list_data.append((f"stop_{service}", None, f"\u2612 {name}", [f"Disable {help_info}", icon])) else: - self.list_data.append((f"start_{service}", None, f"\u2610 {name}", [f"Enable {help_info}.", icon])) + self.list_data.append((f"start_{service}", None, f"\u2610 {name}", [f"Enable {help_info}", icon])) def atoi(text): return int(text) if text.isdigit() else text @@ -198,7 +198,7 @@ def natural_keys(t): for i in aubio_devices: append_port(i) else: - append_service("aubionotes", "Aubionotes (Audio \u2794 MIDI)", "Aubionotes. This converts audio input to MIDI output") + append_service("aubionotes", "Aubionotes (Audio \u2794 MIDI)", "Aubionotes. Converts audio input to MIDI note on/off commands.") # Remove "Internal Devices" title if section is empty if len(self.list_data) == nint: @@ -212,7 +212,7 @@ def natural_keys(t): if self.chain is None or ble_devices: self.list_data.append((None, None, "Bluetooth Devices")) if self.chain is None: - append_service("bluetooth", "BLE MIDI", "Bluetooth MIDI") + append_service("bluetooth", "BLE MIDI", "Bluetooth MIDI.") for x in sorted(ble_devices, key=natural_keys): append_port(x[1]) @@ -223,16 +223,16 @@ def natural_keys(t): append_port(i) else: if os.path.isfile("/usr/local/bin/jacknetumpd"): - append_service("jacknetumpd", "NetUMP: MIDI 2.0", "JACKNet MIDI UDP.. This provides MIDI over an IP (network) connection to other JACKNet devices") + append_service("jacknetumpd", "NetUMP: MIDI 2.0", "JACKNet MIDI UDP.. This provides MIDI over an IP (network) connection to other JACKNet devices.") if os.path.isfile("/usr/local/bin/jackrtpmidid"): - append_service("jackrtpmidid", "RTP-MIDI", "RTP-MIDI. This provides MIDI over an IP (network) connection using the RTP-MIDI standard, also known as AppleMIDI") + append_service("jackrtpmidid", "RTP-MIDI", "RTP-MIDI. This provides MIDI over an IP (network) connection using the RTP-MIDI standard, also known as AppleMIDI.") if os.path.isfile("/usr/local/bin/qmidinet"): - append_service("qmidinet", "QmidiNet", "QmidiNet. This provides MIDI over an IP (network) connection to other QmidiNet devies") + append_service("qmidinet", "QmidiNet", "QmidiNet. This provides MIDI over an IP (network) connection to other QmidiNet devies.") if os.path.isfile("/zynthian/venv/bin/touchosc2midi"): - append_service("touchosc2midi", "TouchOSC Bridge", "interface with Hexler TouchOSC modular control surface") + append_service("touchosc2midi", "TouchOSC Bridge", "interface with Hexler TouchOSC modular control surface.") if not self.input and self.chain: self.list_data.append((None, None, "> Chain inputs")) From 2a553b169f9b5abe4fef177b0e6e0f2eb7ada9ec Mon Sep 17 00:00:00 2001 From: riban Date: Sun, 8 Dec 2024 08:07:29 +0000 Subject: [PATCH 070/117] Add info to option view. Optionally pass list of [title, [info, icon]] as option. (Old title-only still works.) Fix renaming MIDI port with empty name hiding port. Now resets to default name. Add info to MIDI input/output options. --- zynautoconnect/zynthian_autoconnect.py | 2 +- zyngui/zynthian_gui_midi_config.py | 14 +++++++------- zyngui/zynthian_gui_option.py | 13 ++++++++----- 3 files changed, 16 insertions(+), 13 deletions(-) diff --git a/zynautoconnect/zynthian_autoconnect.py b/zynautoconnect/zynthian_autoconnect.py index ab52bfcb3..b01f5c22b 100755 --- a/zynautoconnect/zynthian_autoconnect.py +++ b/zynautoconnect/zynthian_autoconnect.py @@ -160,7 +160,7 @@ def set_port_friendly_name(port, friendly_name=None): try: alias1 = port.aliases[0] - if friendly_name is None: + if not friendly_name: # Reset name if alias1 in midi_port_names: midi_port_names.pop(alias1) diff --git a/zyngui/zynthian_gui_midi_config.py b/zyngui/zynthian_gui_midi_config.py index 09279d9c6..f8fdfe282 100644 --- a/zyngui/zynthian_gui_midi_config.py +++ b/zyngui/zynthian_gui_midi_config.py @@ -309,25 +309,25 @@ def select_action(self, i, t='S'): options = {} if self.input: options["MIDI Input Mode"] = None + mode_info = f"Toggle input mode.\n\n{ZMIP_MODE_ACTIVE} Active mode. This input will only control the active chain.\n{ZMIP_MODE_MULTI} Multitimbral mode. Only MIDI input on this chain's MIDI channel will control this chain." if zynautoconnect.get_midi_in_dev_mode(idev): - options[f'\u2610 {ZMIP_MODE_ACTIVE} Multitimbral mode '] = "MULTI" + options[f'{ZMIP_MODE_ACTIVE} Active mode '] = ["MULTI", [mode_info, None]] else: - options[f'\u2612 {ZMIP_MODE_MULTI} Multitimbral mode '] = "ACTI" - + options[f'{ZMIP_MODE_MULTI} Multitimbral mode '] = ["ACTI", [mode_info, None]] options["Configuration"] = None dev_id = zynautoconnect.get_midi_in_devid(idev) if dev_id in self.zyngui.state_manager.ctrldev_manager.available_drivers: # TODO: Offer list of profiles if idev in self.zyngui.state_manager.ctrldev_manager.drivers: - options[f"\u2612 {ZMIP_MODE_CONTROLLER} Controller driver"] = "UNLOAD_DRIVER" + options[f"\u2612 {ZMIP_MODE_CONTROLLER} Controller driver"] = ["UNLOAD_DRIVER", ["Device controller enabled. MIDI input and output is processed to integrate the connected device.", None]] else: - options[f"\u2610 {ZMIP_MODE_CONTROLLER} Controller driver"] = "LOAD_DRIVER" + options[f"\u2610 {ZMIP_MODE_CONTROLLER} Controller driver"] = ["LOAD_DRIVER", ["Device controller disabled. Use normal, unprocessed MIDI input from this device.", None]] port = zynautoconnect.devices_in[idev] else: port = zynautoconnect.devices_out[idev] if self.list_data[i][0].startswith("AUBIO:") or self.list_data[i][0].endswith("aubionotes"): - options["Select aubio inputs"] = "AUBIO_INPUTS" - options[f"Rename port '{port.aliases[0]}'"] = port + options["Select aubio inputs"] = ["AUBIO_INPUTS", ["Select which audio inputs are connected to aubionotes Audio \u2794 MIDI.", "midi_audio.png"]] + options[f"Rename port '{port.aliases[0]}'"] = [port, ["Rename the MIDI port.\nClear name to reset to default name.", None]] # options[f"Reset name to '{zynautoconnect.build_midi_port_name(port)[1]}'"] = port self.zyngui.screens['option'].config( "MIDI Input Device", options, self.menu_cb) diff --git a/zyngui/zynthian_gui_option.py b/zyngui/zynthian_gui_option.py index df1a02d50..fe16710d7 100644 --- a/zyngui/zynthian_gui_option.py +++ b/zyngui/zynthian_gui_option.py @@ -5,7 +5,7 @@ # # Zynthian GUI Option Selector Class # -# Copyright (C) 2015-2020 Fernando Moyano +# Copyright (C) 2015-2024 Fernando Moyano # # ****************************************************************************** # @@ -29,14 +29,14 @@ from os.path import basename, splitext # Zynthian specific modules -from zyngui.zynthian_gui_selector import zynthian_gui_selector +from zyngui.zynthian_gui_selector_info import zynthian_gui_selector_info # ------------------------------------------------------------------------------ # Zynthian Option Selection GUI Class # ------------------------------------------------------------------------------ -class zynthian_gui_option(zynthian_gui_selector): +class zynthian_gui_option(zynthian_gui_selector_info): def __init__(self): self.title = "" @@ -45,7 +45,7 @@ def __init__(self): self.cb_select = None self.click_type = False self.close_on_select = True - super().__init__("Option", True) + super().__init__("Menu") def config(self, title, options, cb_select, close_on_select=True, click_type=False): self.title = title @@ -93,7 +93,10 @@ def fill_list(self): if self.options_cb: self.options = self.options_cb() for k, v in self.options.items(): - self.list_data.append((v, i, k)) + if isinstance(v, list): + self.list_data.append((v[0], i, k, v[1])) + else: + self.list_data.append((v, i, k)) i += 1 super().fill_list() From fefa6322a18678629b35d9341be0f3791cdcc2bf Mon Sep 17 00:00:00 2001 From: riban Date: Sun, 8 Dec 2024 08:43:03 +0000 Subject: [PATCH 071/117] Don't hide MIDI i/o options screen --- zyngui/zynthian_gui_midi_config.py | 63 ++++++++++++++++-------------- 1 file changed, 34 insertions(+), 29 deletions(-) diff --git a/zyngui/zynthian_gui_midi_config.py b/zyngui/zynthian_gui_midi_config.py index f8fdfe282..530be2b89 100644 --- a/zyngui/zynthian_gui_midi_config.py +++ b/zyngui/zynthian_gui_midi_config.py @@ -302,38 +302,41 @@ def select_action(self, i, t='S'): # Change mode elif t == 'B': - idev = self.list_data[i][1] + self.show_options() + + def show_options(self): + try: + idev = self.list_data[self.index][1] if idev is None: return - try: - options = {} - if self.input: - options["MIDI Input Mode"] = None - mode_info = f"Toggle input mode.\n\n{ZMIP_MODE_ACTIVE} Active mode. This input will only control the active chain.\n{ZMIP_MODE_MULTI} Multitimbral mode. Only MIDI input on this chain's MIDI channel will control this chain." - if zynautoconnect.get_midi_in_dev_mode(idev): - options[f'{ZMIP_MODE_ACTIVE} Active mode '] = ["MULTI", [mode_info, None]] - else: - options[f'{ZMIP_MODE_MULTI} Multitimbral mode '] = ["ACTI", [mode_info, None]] - options["Configuration"] = None - dev_id = zynautoconnect.get_midi_in_devid(idev) - if dev_id in self.zyngui.state_manager.ctrldev_manager.available_drivers: - # TODO: Offer list of profiles - if idev in self.zyngui.state_manager.ctrldev_manager.drivers: - options[f"\u2612 {ZMIP_MODE_CONTROLLER} Controller driver"] = ["UNLOAD_DRIVER", ["Device controller enabled. MIDI input and output is processed to integrate the connected device.", None]] - else: - options[f"\u2610 {ZMIP_MODE_CONTROLLER} Controller driver"] = ["LOAD_DRIVER", ["Device controller disabled. Use normal, unprocessed MIDI input from this device.", None]] - port = zynautoconnect.devices_in[idev] + options = {} + if self.input: + options["MIDI Input Mode"] = None + mode_info = f"Toggle input mode.\n\n{ZMIP_MODE_ACTIVE} Active mode. This input will only control the active chain.\n{ZMIP_MODE_MULTI} Multitimbral mode. Only MIDI input on this chain's MIDI channel will control this chain." + if zynautoconnect.get_midi_in_dev_mode(idev): + options[f'{ZMIP_MODE_ACTIVE} Active mode '] = ["MULTI", [mode_info, None]] else: - port = zynautoconnect.devices_out[idev] - if self.list_data[i][0].startswith("AUBIO:") or self.list_data[i][0].endswith("aubionotes"): - options["Select aubio inputs"] = ["AUBIO_INPUTS", ["Select which audio inputs are connected to aubionotes Audio \u2794 MIDI.", "midi_audio.png"]] - options[f"Rename port '{port.aliases[0]}'"] = [port, ["Rename the MIDI port.\nClear name to reset to default name.", None]] - # options[f"Reset name to '{zynautoconnect.build_midi_port_name(port)[1]}'"] = port - self.zyngui.screens['option'].config( - "MIDI Input Device", options, self.menu_cb) - self.zyngui.show_screen('option') - except: - pass # Port may have disappeared whilst building menu + options[f'{ZMIP_MODE_MULTI} Multitimbral mode '] = ["ACTI", [mode_info, None]] + options["Configuration"] = None + dev_id = zynautoconnect.get_midi_in_devid(idev) + if dev_id in self.zyngui.state_manager.ctrldev_manager.available_drivers: + # TODO: Offer list of profiles + if idev in self.zyngui.state_manager.ctrldev_manager.drivers: + options[f"\u2612 {ZMIP_MODE_CONTROLLER} Controller driver"] = ["UNLOAD_DRIVER", ["Device controller enabled. MIDI input and output is processed to integrate the connected device.", None]] + else: + options[f"\u2610 {ZMIP_MODE_CONTROLLER} Controller driver"] = ["LOAD_DRIVER", ["Device controller disabled. Use normal, unprocessed MIDI input from this device.", None]] + port = zynautoconnect.devices_in[idev] + else: + port = zynautoconnect.devices_out[idev] + if self.list_data[self.index][0].startswith("AUBIO:") or self.list_data[self.index][0].endswith("aubionotes"): + options["Select aubio inputs"] = ["AUBIO_INPUTS", ["Select which audio inputs are connected to aubionotes Audio \u2794 MIDI.", "midi_audio.png"]] + options[f"Rename port '{port.aliases[0]}'"] = [port, ["Rename the MIDI port.\nClear name to reset to default name.", None]] + # options[f"Reset name to '{zynautoconnect.build_midi_port_name(port)[1]}'"] = port + self.zyngui.screens['option'].config( + "MIDI Input Device", options, self.menu_cb, False) + self.zyngui.show_screen('option') + except: + pass # Port may have disappeared whilst building menu def menu_cb(self, option, params): try: @@ -355,6 +358,7 @@ def menu_cb(self, option, params): idev = self.list_data[self.index][1] lib_zyncore.zmip_set_flag_active_chain(idev, params == "ACTI") zynautoconnect.update_midi_in_dev_mode(idev) + self.show_options() self.update_list() except: pass # Ports may have changed since menu opened @@ -390,6 +394,7 @@ def rename_device(self, name): port = zynautoconnect.devices_out[self.list_data[self.index][1]] zynautoconnect.set_port_friendly_name(port, name) self.update_list() + self.zyngui.close_screen("option") def set_select_path(self): if self.chain: From 664c0fa050a33befce2f823c23b5ccf09b22a7fb Mon Sep 17 00:00:00 2001 From: riban Date: Sun, 8 Dec 2024 09:36:38 +0000 Subject: [PATCH 072/117] Adds info for audio in/out and mixer options --- zyngui/zynthian_gui_audio_in.py | 12 ++++++---- zyngui/zynthian_gui_audio_out.py | 36 +++++++++++++--------------- zyngui/zynthian_gui_chain_options.py | 30 +++++++++++++---------- 3 files changed, 41 insertions(+), 37 deletions(-) diff --git a/zyngui/zynthian_gui_audio_in.py b/zyngui/zynthian_gui_audio_in.py index 1cb4b7627..b980d54ad 100644 --- a/zyngui/zynthian_gui_audio_in.py +++ b/zyngui/zynthian_gui_audio_in.py @@ -27,18 +27,18 @@ # Zynthian specific modules import zynautoconnect -from zyngui.zynthian_gui_selector import zynthian_gui_selector +from zyngui.zynthian_gui_selector_info import zynthian_gui_selector_info # ------------------------------------------------------------------------------ # Zynthian Audio-In Selection GUI Class # ------------------------------------------------------------------------------ -class zynthian_gui_audio_in(zynthian_gui_selector): +class zynthian_gui_audio_in(zynthian_gui_selector_info): def __init__(self): self.chain = None - super().__init__('Audio In', True) + super().__init__('Audio In') def set_chain(self, chain): self.chain = chain @@ -68,10 +68,12 @@ def fill_list(self): suffix = "" if i + 1 in self.chain.audio_in: self.list_data.append( - (i + 1, scp.name, f"\u2612 Audio input {i + 1}{suffix}")) + (i + 1, scp.name, f"\u2612 Audio input {i + 1}{suffix}", + [f"Audio input {i + 1} is connected to this chain.", "audio_in.png"])) else: self.list_data.append( - (i + 1, scp.name, f"\u2610 Audio input {i + 1}{suffix}")) + (i + 1, scp.name, f"\u2610 Audio input {i + 1}{suffix}", + [f"Audio input {i + 1} is disconnected from this chain.", "audio_in.png"])) super().fill_list() diff --git a/zyngui/zynthian_gui_audio_out.py b/zyngui/zynthian_gui_audio_out.py index 33ec24d8f..79710111c 100644 --- a/zyngui/zynthian_gui_audio_out.py +++ b/zyngui/zynthian_gui_audio_out.py @@ -28,9 +28,7 @@ # Zynthian specific modules import zynautoconnect from zyngine.zynthian_signal_manager import zynsigman -from zyngui import zynthian_gui_config -from zyngui.zynthian_gui_selector import zynthian_gui_selector -from zyngine.zynthian_engine_modui import zynthian_engine_modui +from zyngui.zynthian_gui_selector_info import zynthian_gui_selector_info from zyngine.zynthian_audio_recorder import zynthian_audio_recorder # ------------------------------------------------------------------------------ @@ -38,11 +36,11 @@ # ------------------------------------------------------------------------------ -class zynthian_gui_audio_out(zynthian_gui_selector): +class zynthian_gui_audio_out(zynthian_gui_selector_info): def __init__(self): self.chain = None - super().__init__('Audio Out', True) + super().__init__('Audio Out') def build_view(self): self.check_ports = 0 @@ -81,7 +79,7 @@ def fill_list(self): self.list_data = [] if self.chain.chain_id: # Normal chain so add mixer / chain targets - port_names = [("Main mixbus", 0)] + port_names = [("Main mixbus", 0, ["Send audio from this chain to the main mixbus", "audio_output.png"])] self.list_data.append((None, None, "> Chain inputs")) for chain_id, chain in self.zyngui.chain_manager.chains.items(): if chain_id != 0 and chain != self.chain and chain.audio_thru or chain.is_synth() and chain.synth_slots[0][0].type == "Special": @@ -89,19 +87,19 @@ def fill_list(self): prefix = "∞ " else: prefix = "" - port_names.append((f"{prefix}{chain.get_name()}", chain_id)) + port_names.append((f"{prefix}{chain.get_name()}", chain_id, [f"Send audio from this chain to the input of chain {chain.get_name()}.", "audio_output.png"])) # Add side-chain targets for processor in chain.get_processors(): try: for port_name in zynautoconnect.get_sidechain_portnames(processor.jackname): - port_names.append((f"↣ side {port_name}", port_name)) + port_names.append((f"↣ side {port_name}", port_name), [f"Send audio from this chain to the sidechain input of processor {port_name}.", "audio_output.png"]) except: pass - for title, processor in port_names: + for title, processor, info in port_names: if processor in self.chain.audio_out: - self.list_data.append((processor, processor, "\u2612 " + title)) + self.list_data.append((processor, processor, "\u2612 " + title, info)) else: - self.list_data.append((processor, processor, "\u2610 " + title)) + self.list_data.append((processor, processor, "\u2610 " + title, info)) if self.chain.is_audio(): port_names = [] @@ -113,19 +111,19 @@ def fill_list(self): suffix = f" ({self.playback_ports[i].aliases[0]})" else: suffix = "" - port_names.append((f"Output {i + 1}{suffix}", f"^{self.playback_ports[i].name}$")) + port_names.append((f"Output {i + 1}{suffix}", f"^{self.playback_ports[i].name}$", [f"Send audio from this chain directly to physical audio output {i + 1} as mono.", "audio_output.png"])) if i < port_count: if self.playback_ports[i + 1].aliases: suffix = f" ({self.playback_ports[i + 1].aliases[0]})" else: suffix = "" - port_names.append((f"Output {i + 2}{suffix}", f"^{self.playback_ports[i + 1].name}$")) - port_names.append((f"Outputs {i + 1}+{i + 2} (stereo)", f"^{self.playback_ports[i].name}$|^{self.playback_ports[i + 1].name}$")) - for title, processor in port_names: + port_names.append((f"Output {i + 2}{suffix}", f"^{self.playback_ports[i + 1].name}$", [f"Send audio from this chain directly to physical audio output {i + 2} as mono.", "audio_output.png"])) + port_names.append((f"Outputs {i + 1}+{i + 2} (stereo)", f"^{self.playback_ports[i].name}$|^{self.playback_ports[i + 1].name}$", [f"Send audio from this chain directly to physical audio outputs {i + 1} & {i + 2} as stereo.", "audio_output.png"])) + for title, processor, info in port_names: if processor in self.chain.audio_out: - self.list_data.append((processor, processor, "\u2612 " + title)) + self.list_data.append((processor, processor, "\u2612 " + title, info)) else: - self.list_data.append((processor, processor, "\u2610 " + title)) + self.list_data.append((processor, processor, "\u2610 " + title, info)) self.list_data.append((None, None, "> Audio Recorder")) armed = self.zyngui.state_manager.audio_recorder.is_armed(self.chain.mixer_chan) @@ -134,9 +132,9 @@ def fill_list(self): else: locked = "record" if armed: - self.list_data.append((locked, 'record_disable', '\u2612 Record chain')) + self.list_data.append((locked, 'record_disable', '\u2612 Record chain', [f"The chain will be recorded as a stereo track within a multitrack audio recording.", "audio_output.png"])) else: - self.list_data.append((locked, 'record_enable', '\u2610 Record chain')) + self.list_data.append((locked, 'record_enable', '\u2610 Record chain', [f"The chain will be not be recorded as a stereo track within a multitrack audio recording.", "audio_output.png"])) super().fill_list() diff --git a/zyngui/zynthian_gui_chain_options.py b/zyngui/zynthian_gui_chain_options.py index 4f7118d9d..edcd708c0 100644 --- a/zyngui/zynthian_gui_chain_options.py +++ b/zyngui/zynthian_gui_chain_options.py @@ -93,8 +93,8 @@ def fill_list(self): ["Manage audio output routing.", "audio_output.png"])) if self.chain.is_audio(): - self.list_data.append((self.audio_options, None, "Audio Options", - ["Manage audio ouput options.", "audio_options.png"])) + self.list_data.append((self.audio_options, None, "Mixer Options", + ["Extra audio mixer options.", "audio_options.png"])) # TODO: Catch signal for Audio Recording status change if self.chain_id == 0 and not zynthian_gui_config.check_wiring_layout(["Z2", "V5"]): @@ -326,35 +326,39 @@ def chain_audio_routing(self): self.zyngui.screens['audio_out'].set_chain(self.chain) self.zyngui.show_screen('audio_out') - def audio_options(self): + def audio_options(self, index = 0): options = {} if self.zyngui.state_manager.zynmixer.get_mono(self.chain.mixer_chan): - options['\u2612 Mono'] = 'mono' + options['\u2612 Mono'] = ['mono', ["Chain is mono.\n\nLeft and right inputs are summed and fed as mono to left and right outputs", None]] else: - options['\u2610 Mono'] = 'mono' + options['\u2610 Mono'] = ['mono', ["Chain is stereo.\n\nLeft input feeds left output and right input feeds right output.", None]] if self.zyngui.state_manager.zynmixer.get_phase(self.chain.mixer_chan): - options['\u2612 Phase reverse'] = 'phase' + options['\u2612 Phase reverse'] = ['phase', ["Chain is phase reversed.\n\nRight output is inverted, making it 180° out of phase with its input.", None]] else: - options['\u2610 Phase reverse'] = 'phase' + options['\u2610 Phase reverse'] = ['phase', ["Chain is not phase reversed.\n\nLeft and right inputs feed left and right outputs without phase modification.", None]] if self.zyngui.state_manager.zynmixer.get_ms(self.chain.mixer_chan): - options['\u2612 M+S'] = 'ms' + options['\u2612 M+S'] = ['ms', ["Mid/Side mode is enabled.\n\nLeft output carries the 'Mid' signal. Right output carries the 'Side' signal.", None]] else: - options['\u2610 M+S'] = 'ms' + options['\u2610 M+S'] = ['ms', ["Mid/Side mode is disabled.\n\nLeft and right inputs feed left and right outputs.", None]] self.zyngui.screens['option'].config( - "Audio options", options, self.audio_menu_cb) + "Mixer options", options, self.audio_menu_cb, False) + self.zyngui.screens["option"].index = index self.zyngui.show_screen('option') def audio_menu_cb(self, options, params): + index = 0 if params == 'mono': self.zyngui.state_manager.zynmixer.toggle_mono( self.chain.mixer_chan) - elif params == 'ms': - self.zyngui.state_manager.zynmixer.toggle_ms(self.chain.mixer_chan) elif params == 'phase': self.zyngui.state_manager.zynmixer.toggle_phase( self.chain.mixer_chan) - self.audio_options() + index = 1 + elif params == 'ms': + self.zyngui.state_manager.zynmixer.toggle_ms(self.chain.mixer_chan) + index = 2 + self.audio_options(index) def chain_audio_capture(self): self.zyngui.screens['audio_in'].set_chain(self.chain) From 3b03dd84d7da3118147d69fb347801e2c7916196 Mon Sep 17 00:00:00 2001 From: riban Date: Mon, 9 Dec 2024 06:29:28 +0000 Subject: [PATCH 073/117] Fix wrong icon filename. Add option to configure option view with selected item. (None to keep current index.) Fix device controller mode not updating in menu when toggled. --- zyngui/zynthian_gui_audio_in.py | 4 ++-- zyngui/zynthian_gui_chain_options.py | 10 +++------- zyngui/zynthian_gui_midi_config.py | 5 +++-- zyngui/zynthian_gui_option.py | 5 +++-- 4 files changed, 11 insertions(+), 13 deletions(-) diff --git a/zyngui/zynthian_gui_audio_in.py b/zyngui/zynthian_gui_audio_in.py index b980d54ad..de76a19ef 100644 --- a/zyngui/zynthian_gui_audio_in.py +++ b/zyngui/zynthian_gui_audio_in.py @@ -69,11 +69,11 @@ def fill_list(self): if i + 1 in self.chain.audio_in: self.list_data.append( (i + 1, scp.name, f"\u2612 Audio input {i + 1}{suffix}", - [f"Audio input {i + 1} is connected to this chain.", "audio_in.png"])) + [f"Audio input {i + 1} is connected to this chain.", "audio_input.png"])) else: self.list_data.append( (i + 1, scp.name, f"\u2610 Audio input {i + 1}{suffix}", - [f"Audio input {i + 1} is disconnected from this chain.", "audio_in.png"])) + [f"Audio input {i + 1} is disconnected from this chain.", "audio_input.png"])) super().fill_list() diff --git a/zyngui/zynthian_gui_chain_options.py b/zyngui/zynthian_gui_chain_options.py index edcd708c0..1a054f023 100644 --- a/zyngui/zynthian_gui_chain_options.py +++ b/zyngui/zynthian_gui_chain_options.py @@ -326,7 +326,7 @@ def chain_audio_routing(self): self.zyngui.screens['audio_out'].set_chain(self.chain) self.zyngui.show_screen('audio_out') - def audio_options(self, index = 0): + def audio_options(self): options = {} if self.zyngui.state_manager.zynmixer.get_mono(self.chain.mixer_chan): options['\u2612 Mono'] = ['mono', ["Chain is mono.\n\nLeft and right inputs are summed and fed as mono to left and right outputs", None]] @@ -342,23 +342,19 @@ def audio_options(self, index = 0): options['\u2610 M+S'] = ['ms', ["Mid/Side mode is disabled.\n\nLeft and right inputs feed left and right outputs.", None]] self.zyngui.screens['option'].config( - "Mixer options", options, self.audio_menu_cb, False) - self.zyngui.screens["option"].index = index + "Mixer options", options, self.audio_menu_cb, False, False, None) self.zyngui.show_screen('option') def audio_menu_cb(self, options, params): - index = 0 if params == 'mono': self.zyngui.state_manager.zynmixer.toggle_mono( self.chain.mixer_chan) elif params == 'phase': self.zyngui.state_manager.zynmixer.toggle_phase( self.chain.mixer_chan) - index = 1 elif params == 'ms': self.zyngui.state_manager.zynmixer.toggle_ms(self.chain.mixer_chan) - index = 2 - self.audio_options(index) + self.audio_options() def chain_audio_capture(self): self.zyngui.screens['audio_in'].set_chain(self.chain) diff --git a/zyngui/zynthian_gui_midi_config.py b/zyngui/zynthian_gui_midi_config.py index 530be2b89..638d2f003 100644 --- a/zyngui/zynthian_gui_midi_config.py +++ b/zyngui/zynthian_gui_midi_config.py @@ -333,7 +333,7 @@ def show_options(self): options[f"Rename port '{port.aliases[0]}'"] = [port, ["Rename the MIDI port.\nClear name to reset to default name.", None]] # options[f"Reset name to '{zynautoconnect.build_midi_port_name(port)[1]}'"] = port self.zyngui.screens['option'].config( - "MIDI Input Device", options, self.menu_cb, False) + "MIDI Input Device", options, self.menu_cb, False, False, None) self.zyngui.show_screen('option') except: pass # Port may have disappeared whilst building menu @@ -354,11 +354,12 @@ def menu_cb(self, option, params): ain = aubio_inputs(self.zyngui.state_manager) self.zyngui.screens['audio_in'].set_chain(ain) self.zyngui.show_screen('audio_in') + return elif self.input: idev = self.list_data[self.index][1] lib_zyncore.zmip_set_flag_active_chain(idev, params == "ACTI") zynautoconnect.update_midi_in_dev_mode(idev) - self.show_options() + self.show_options() self.update_list() except: pass # Ports may have changed since menu opened diff --git a/zyngui/zynthian_gui_option.py b/zyngui/zynthian_gui_option.py index fe16710d7..bc9d7031a 100644 --- a/zyngui/zynthian_gui_option.py +++ b/zyngui/zynthian_gui_option.py @@ -47,7 +47,7 @@ def __init__(self): self.close_on_select = True super().__init__("Menu") - def config(self, title, options, cb_select, close_on_select=True, click_type=False): + def config(self, title, options, cb_select, close_on_select=True, click_type=False, index=0): self.title = title if callable(options): self.options_cb = options @@ -58,7 +58,8 @@ def config(self, title, options, cb_select, close_on_select=True, click_type=Fal self.cb_select = cb_select self.close_on_select = close_on_select self.click_type = click_type - self.index = 0 + if index is not None: + self.index = index def config_file_list(self, title, dpaths, fpat, cb_select, close_on_select=True, click_type=False): self.title = title From c624e2caf8027b2442ed5641a42ceb00967b7762 Mon Sep 17 00:00:00 2001 From: riban Date: Mon, 9 Dec 2024 08:17:39 +0000 Subject: [PATCH 074/117] Do not assign zynpot2switch for classic. This breaks zynswitch key binding. Fix error bold pressing LEARN in control view. Bold press SELECT in MIDI learn shows learn options. --- zyngui/zynthian_gui_config.py | 2 +- zyngui/zynthian_gui_control.py | 10 ++++++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/zyngui/zynthian_gui_config.py b/zyngui/zynthian_gui_config.py index f23a6ee86..fc91ba295 100644 --- a/zyngui/zynthian_gui_config.py +++ b/zyngui/zynthian_gui_config.py @@ -261,7 +261,7 @@ def config_zynpot2switch(): # Default configuration for "classic layouts" => It discards V5 partial configurations! if len(zynpot2switch) < num_zynpots: - zynpot2switch = [0, 1, 2, 3] + zynpot2switch = [] logging.info(f"zynpot2switch => {zynpot2switch}") diff --git a/zyngui/zynthian_gui_control.py b/zyngui/zynthian_gui_control.py index 7d2299618..94677923c 100644 --- a/zyngui/zynthian_gui_control.py +++ b/zyngui/zynthian_gui_control.py @@ -479,7 +479,7 @@ def switch(self, swi, t='S'): if self.mode == 'control': return False elif t == 'B': - if self.midi_learning and self.zyngui.state_manager.midi_learn_cc: + if self.midi_learning and self.zyngui.state_manager.midi_learn_zctrl: self.midi_unlearn_action() return True @@ -493,7 +493,13 @@ def switch_select(self, t): elif self.mode == 'select': self.click_listbox() elif t == 'B': - self.zyngui.cuia_chain_options() + if self.midi_learning and self.zyngui.state_manager.midi_learn_zctrl: + for i, zgctrl in enumerate(self.zgui_controllers): + if zgctrl and zgctrl.zctrl == self.zyngui.state_manager.midi_learn_zctrl: + self.midi_learn_options(i) + return True + else: + self.zyngui.cuia_chain_options() return True From 1fe959f995e0ec2e9ae7ce642b083593cef0197a Mon Sep 17 00:00:00 2001 From: riban Date: Mon, 9 Dec 2024 08:22:00 +0000 Subject: [PATCH 075/117] Do not navigate if bold select pressed whilst MIDI learning --- zyngui/zynthian_gui_control.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/zyngui/zynthian_gui_control.py b/zyngui/zynthian_gui_control.py index 94677923c..3198def67 100644 --- a/zyngui/zynthian_gui_control.py +++ b/zyngui/zynthian_gui_control.py @@ -493,11 +493,12 @@ def switch_select(self, t): elif self.mode == 'select': self.click_listbox() elif t == 'B': - if self.midi_learning and self.zyngui.state_manager.midi_learn_zctrl: - for i, zgctrl in enumerate(self.zgui_controllers): - if zgctrl and zgctrl.zctrl == self.zyngui.state_manager.midi_learn_zctrl: - self.midi_learn_options(i) - return True + if self.midi_learning: + if self.zyngui.state_manager.midi_learn_zctrl: + for i, zgctrl in enumerate(self.zgui_controllers): + if zgctrl and zgctrl.zctrl == self.zyngui.state_manager.midi_learn_zctrl: + self.midi_learn_options(i) + return True else: self.zyngui.cuia_chain_options() From 153e83a258c8b5f779db6c6e0d65520cd2959872 Mon Sep 17 00:00:00 2001 From: jofemodo Date: Mon, 9 Dec 2024 09:37:57 +0100 Subject: [PATCH 076/117] Fix button background hover + adjust purple color. --- zyngui/zynthian_gui_touchkeypad_v5.py | 2 +- zyngui/zynthian_wsleds_v5touch.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/zyngui/zynthian_gui_touchkeypad_v5.py b/zyngui/zynthian_gui_touchkeypad_v5.py index 8bbe984de..29926242a 100644 --- a/zyngui/zynthian_gui_touchkeypad_v5.py +++ b/zyngui/zynthian_gui_touchkeypad_v5.py @@ -213,7 +213,7 @@ def add_button(self, n, parent, row, column, zynswitch, label, padx, pady): height=1, bg=self.bg_color, fg=self.text_color, - activebackground=self.bg_color_over, + activebackground=self.bg_color, activeforeground=self.border_color, highlightbackground=self.border_color, highlightcolor=self.border_color, diff --git a/zyngui/zynthian_wsleds_v5touch.py b/zyngui/zynthian_wsleds_v5touch.py index b9e65d6ec..0ea8118ae 100644 --- a/zyngui/zynthian_wsleds_v5touch.py +++ b/zyngui/zynthian_wsleds_v5touch.py @@ -109,7 +109,7 @@ def setup_colors(self): self.wscolor_orange = os.environ.get('ZYNTHIAN_TOUCH_KEYPAD_COLOR_ORANGE', "#FF6A00") self.wscolor_blue = os.environ.get('ZYNTHIAN_TOUCH_KEYPAD_COLOR_BLUE', "#1070FE") self.wscolor_blue_light = os.environ.get('ZYNTHIAN_TOUCH_KEYPAD_COLOR_LIGHTBLUE', "#05FDFF") - self.wscolor_purple = os.environ.get('ZYNTHIAN_TOUCH_KEYPAD_COLOR_PURPLE', "#D662FE") + self.wscolor_purple = os.environ.get('ZYNTHIAN_TOUCH_KEYPAD_COLOR_PURPLE', "#D000E0") self.wscolor_default = os.environ.get('ZYNTHIAN_TOUCH_KEYPAD_COLOR_DEFAULT', self.wscolor_blue) self.wscolor_alt = os.environ.get('ZYNTHIAN_TOUCH_KEYPAD_COLOR_ALT', self.wscolor_purple) self.wscolor_active = os.environ.get('ZYNTHIAN_TOUCH_KEYPAD_COLOR_ACTIVE', self.wscolor_green) From 5afa0b5c8c11bd7552625124741bdb9aad8a8e82 Mon Sep 17 00:00:00 2001 From: jofemodo Date: Mon, 9 Dec 2024 13:43:58 +0100 Subject: [PATCH 077/117] Improve/rationalize side-panel info in MIDI config views. --- zyngui/zynthian_gui_midi_config.py | 71 ++++++++++++++++++++---------- 1 file changed, 48 insertions(+), 23 deletions(-) diff --git a/zyngui/zynthian_gui_midi_config.py b/zyngui/zynthian_gui_midi_config.py index 638d2f003..7993544cd 100644 --- a/zyngui/zynthian_gui_midi_config.py +++ b/zyngui/zynthian_gui_midi_config.py @@ -128,24 +128,31 @@ def append_port(idev): if self.input: port = zynautoconnect.devices_in[idev] mode = get_mode_str(idev) - input_mode_info = f"\n{ZMIP_MODE_ACTIVE} - Active Chain\n{ZMIP_MODE_MULTI} - Multitimbral\n{ZMIP_MODE_CONTROLLER} - Device Controller" + input_mode_info = f"\n\n{ZMIP_MODE_ACTIVE} Active Chain\n{ZMIP_MODE_MULTI} Multitimbral\n{ZMIP_MODE_CONTROLLER} Driver loaded" if self.chain is None: - self.list_data.append((port.aliases[0], idev, f"{mode}{port.aliases[1]}", [f"Bold select to show options for MIDI input '{port.aliases[1]}'.{input_mode_info}", "midi_input.png"])) + self.list_data.append((port.aliases[0], idev, f"{mode}{port.aliases[1]}", + [f"Bold select to show options for '{port.aliases[1]}'.{input_mode_info}", "midi_input.png"])) elif not self.zyngui.state_manager.ctrldev_manager.is_input_device_available_to_chains(idev): - self.list_data.append((port.aliases[0], idev, f" {mode}{port.aliases[1]}", [f"Connect '{port.aliases[1]}' to this chain's MIDI input.\nBold select to show more options.{input_mode_info}", None])) + self.list_data.append((port.aliases[0], idev, f" {mode}{port.aliases[1]}", + [f"Bold select to show options '{port.aliases[1]}'.{input_mode_info}", "midi_input.png"])) else: if lib_zyncore.zmop_get_route_from(self.chain.zmop_index, idev): - self.list_data.append((port.aliases[0], idev, f"\u2612 {mode}{port.aliases[1]}", [f"Disconnect '{port.aliases[1]}' from this chain's MIDI input.\nBold select to show more options.{input_mode_info}", "midi_input.png"])) + self.list_data.append((port.aliases[0], idev, f"\u2612 {mode}{port.aliases[1]}", + [f"'{port.aliases[1]}' connected to chain's MIDI input.\nBold select to show more options.{input_mode_info}", "midi_input.png"])) else: - self.list_data.append((port.aliases[0], idev, f"\u2610 {mode}{port.aliases[1]}", [f"Connect '{port.aliases[1]}' to this chain's MIDI input.\nBold select to show more options.{input_mode_info}", "midi_input.png"])) + self.list_data.append((port.aliases[0], idev, f"\u2610 {mode}{port.aliases[1]}", + [f"'{port.aliases[1]}' disconnected from chain's MIDI input.\nBold select to show more options.{input_mode_info}", "midi_input.png"])) else: port = zynautoconnect.devices_out[idev] if self.chain is None: - self.list_data.append((port.aliases[0], idev, f"{port.aliases[1]}", [f"Bold select to show options for MIDI output '{port.aliases[1]}'.", "midi_output.png"])) + self.list_data.append((port.aliases[0], idev, f"{port.aliases[1]}", + [f"Bold select to show options for '{port.aliases[1]}'.", "midi_output.png"])) elif port.aliases[0] in self.chain.midi_out: - self.list_data.append((port.aliases[0], idev, f"\u2612 {port.aliases[1]}", [f"Disconnect this chain's MIDI output from '{port.aliases[1]}'.\nBold select to show more options.", "midi_output.png"])) + self.list_data.append((port.aliases[0], idev, f"\u2612 {port.aliases[1]}", + [f"'{port.aliases[1]}' connected to chain's MIDI output.\nBold select to show more options.", "midi_output.png"])) else: - self.list_data.append((port.aliases[0], idev, f"\u2610 {port.aliases[1]}", [f"Connect this chain's MIDI output to '{port.aliases[1]}'.\nBold select to show more options.", "midi_output.png"])) + self.list_data.append((port.aliases[0], idev, f"\u2610 {port.aliases[1]}", + [f"'{port.aliases[1]}' disconnected from chain's MIDI output.\nBold select to show more options.", "midi_output.png"])) def append_service(service, name, help_info=""): if service in SERVICE_ICONS: @@ -198,7 +205,8 @@ def natural_keys(t): for i in aubio_devices: append_port(i) else: - append_service("aubionotes", "Aubionotes (Audio \u2794 MIDI)", "Aubionotes. Converts audio input to MIDI note on/off commands.") + append_service("aubionotes", "Aubionotes (Audio \u2794 MIDI)", + "Aubionotes. Converts audio input to MIDI note on/off commands.") # Remove "Internal Devices" title if section is empty if len(self.list_data) == nint: @@ -223,16 +231,20 @@ def natural_keys(t): append_port(i) else: if os.path.isfile("/usr/local/bin/jacknetumpd"): - append_service("jacknetumpd", "NetUMP: MIDI 2.0", "JACKNet MIDI UDP.. This provides MIDI over an IP (network) connection to other JACKNet devices.") + append_service("jacknetumpd", "NetUMP: MIDI 2.0", + "NetUMP. Provides MIDI over an IP connection using NetUMP protocol (MIDI 2.0).") if os.path.isfile("/usr/local/bin/jackrtpmidid"): - append_service("jackrtpmidid", "RTP-MIDI", "RTP-MIDI. This provides MIDI over an IP (network) connection using the RTP-MIDI standard, also known as AppleMIDI.") + append_service("jackrtpmidid", "RTP-MIDI", + "RTP-MIDI. Provides MIDI over an IP connection using RTP-MIDI protocol (AppleMIDI).") if os.path.isfile("/usr/local/bin/qmidinet"): - append_service("qmidinet", "QmidiNet", "QmidiNet. This provides MIDI over an IP (network) connection to other QmidiNet devies.") + append_service("qmidinet", "QmidiNet", + "QmidiNet. Provides MIDI over an IP connection using UDP/IP multicast (ipMIDI).") if os.path.isfile("/zynthian/venv/bin/touchosc2midi"): - append_service("touchosc2midi", "TouchOSC Bridge", "interface with Hexler TouchOSC modular control surface.") + append_service("touchosc2midi", "TouchOSC Bridge", + "Interface with Hexler TouchOSC modular control surface.") if not self.input and self.chain: self.list_data.append((None, None, "> Chain inputs")) @@ -244,11 +256,13 @@ def natural_keys(t): else: prefix = "" if chain_id in self.chain.midi_out: - self.list_data.append( - (chain_id, None, f"\u2612 {prefix}{chain.get_name()}", [f"Disconnect this chain's MIDI output from the MIDI input of chain '{prefix}{chain.get_name()}'.", "midi_output.png"])) + self.list_data.append((chain_id, None, f"\u2612 {prefix}{chain.get_name()}", + [f"Chain '{prefix}{chain.get_name()}' connected to chain's MIDI output.", + "midi_output.png"])) else: - self.list_data.append( - (chain_id, None, f"\u2610 {prefix}{chain.get_name()}", [f"Connect this chain's MIDI output to the MIDI input of chain '{prefix}{chain.get_name()}'.", "midi_output.png"])) + self.list_data.append((chain_id, None, f"\u2610 {prefix}{chain.get_name()}", + [f"Chain '{prefix}{chain.get_name()}' disconnected from chain's MIDI output.", + "midi_output.png"])) super().fill_list() @@ -312,24 +326,35 @@ def show_options(self): options = {} if self.input: options["MIDI Input Mode"] = None - mode_info = f"Toggle input mode.\n\n{ZMIP_MODE_ACTIVE} Active mode. This input will only control the active chain.\n{ZMIP_MODE_MULTI} Multitimbral mode. Only MIDI input on this chain's MIDI channel will control this chain." + mode_info = "Toggle input mode.\n\n" if zynautoconnect.get_midi_in_dev_mode(idev): - options[f'{ZMIP_MODE_ACTIVE} Active mode '] = ["MULTI", [mode_info, None]] + title = f"{ZMIP_MODE_ACTIVE} Active mode" + if lib_zyncore.get_active_midi_chan(): + mode_info += f"{title}. Translate MIDI channel. Send to chains matching active chain's MIDI channel." + else: + mode_info += f"{title}. Translate MIDI channel. Send to active chain only." + options[title] = ["MULTI", [mode_info, None]] else: - options[f'{ZMIP_MODE_MULTI} Multitimbral mode '] = ["ACTI", [mode_info, None]] + title = f"{ZMIP_MODE_MULTI} Multitimbral mode" + mode_info += f"{title}. Don't translate MIDI channel. Send to chains matching device's MIDI channel." + options[title] = ["ACTI", [mode_info, None]] options["Configuration"] = None dev_id = zynautoconnect.get_midi_in_devid(idev) if dev_id in self.zyngui.state_manager.ctrldev_manager.available_drivers: # TODO: Offer list of profiles if idev in self.zyngui.state_manager.ctrldev_manager.drivers: - options[f"\u2612 {ZMIP_MODE_CONTROLLER} Controller driver"] = ["UNLOAD_DRIVER", ["Device controller enabled. MIDI input and output is processed to integrate the connected device.", None]] + options[f"\u2612 {ZMIP_MODE_CONTROLLER} Device driver"] = ["UNLOAD_DRIVER", + ["Driver enabled. A specific driver manage the device, integrating UI functions and customized workflow.", None]] else: - options[f"\u2610 {ZMIP_MODE_CONTROLLER} Controller driver"] = ["LOAD_DRIVER", ["Device controller disabled. Use normal, unprocessed MIDI input from this device.", None]] + options[f"\u2610 {ZMIP_MODE_CONTROLLER} Device driver"] = ["LOAD_DRIVER", + ["Driver disabled. The device is used as MIDI input for chains and MIDI-learning.", None]] port = zynautoconnect.devices_in[idev] else: port = zynautoconnect.devices_out[idev] if self.list_data[self.index][0].startswith("AUBIO:") or self.list_data[self.index][0].endswith("aubionotes"): - options["Select aubio inputs"] = ["AUBIO_INPUTS", ["Select which audio inputs are connected to aubionotes Audio \u2794 MIDI.", "midi_audio.png"]] + options["Select aubio inputs"] = ["AUBIO_INPUTS", + ["Select which audio inputs are connected to aubionotes Audio \u2794 MIDI.", + "midi_audio.png"]] options[f"Rename port '{port.aliases[0]}'"] = [port, ["Rename the MIDI port.\nClear name to reset to default name.", None]] # options[f"Reset name to '{zynautoconnect.build_midi_port_name(port)[1]}'"] = port self.zyngui.screens['option'].config( From ed243038832e9e7d17541f011b7e015969a195be Mon Sep 17 00:00:00 2001 From: jofemodo Date: Mon, 9 Dec 2024 13:56:12 +0100 Subject: [PATCH 078/117] + Restore push+rotate action on V4 (fine-tunning). + Improve access to controller options menu in V4: Enter MIDI-learn and bold push encoder #i to access. --- zyngui/zynthian_gui_config.py | 2 +- zyngui/zynthian_gui_control.py | 13 +++++-------- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/zyngui/zynthian_gui_config.py b/zyngui/zynthian_gui_config.py index fc91ba295..f23a6ee86 100644 --- a/zyngui/zynthian_gui_config.py +++ b/zyngui/zynthian_gui_config.py @@ -261,7 +261,7 @@ def config_zynpot2switch(): # Default configuration for "classic layouts" => It discards V5 partial configurations! if len(zynpot2switch) < num_zynpots: - zynpot2switch = [] + zynpot2switch = [0, 1, 2, 3] logging.info(f"zynpot2switch => {zynpot2switch}") diff --git a/zyngui/zynthian_gui_control.py b/zyngui/zynthian_gui_control.py index 3198def67..5c5484b4e 100644 --- a/zyngui/zynthian_gui_control.py +++ b/zyngui/zynthian_gui_control.py @@ -458,6 +458,10 @@ def rotate_chain(self): # t: Press type ["S"=Short, "B"=Bold, "L"=Long] # returns True if action fully handled or False if parent action should be triggered def switch(self, swi, t='S'): + if t == 'B' and self.midi_learning: + self.midi_learn_options(swi) + return True + if swi == 0: if t == 'S': self.rotate_chain() @@ -493,14 +497,7 @@ def switch_select(self, t): elif self.mode == 'select': self.click_listbox() elif t == 'B': - if self.midi_learning: - if self.zyngui.state_manager.midi_learn_zctrl: - for i, zgctrl in enumerate(self.zgui_controllers): - if zgctrl and zgctrl.zctrl == self.zyngui.state_manager.midi_learn_zctrl: - self.midi_learn_options(i) - return True - else: - self.zyngui.cuia_chain_options() + self.zyngui.cuia_chain_options() return True From bfb9d7f94156a654affabaa8c3145cf5b91d60b7 Mon Sep 17 00:00:00 2001 From: jofemodo Date: Mon, 9 Dec 2024 14:08:37 +0100 Subject: [PATCH 079/117] Fix side-panel info text. --- zyngui/zynthian_gui_midi_config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zyngui/zynthian_gui_midi_config.py b/zyngui/zynthian_gui_midi_config.py index 7993544cd..a77c26d3a 100644 --- a/zyngui/zynthian_gui_midi_config.py +++ b/zyngui/zynthian_gui_midi_config.py @@ -128,7 +128,7 @@ def append_port(idev): if self.input: port = zynautoconnect.devices_in[idev] mode = get_mode_str(idev) - input_mode_info = f"\n\n{ZMIP_MODE_ACTIVE} Active Chain\n{ZMIP_MODE_MULTI} Multitimbral\n{ZMIP_MODE_CONTROLLER} Driver loaded" + input_mode_info = f"\n\n{ZMIP_MODE_ACTIVE} Active mode\n{ZMIP_MODE_MULTI} Multitimbral mode\n{ZMIP_MODE_CONTROLLER} Driver loaded" if self.chain is None: self.list_data.append((port.aliases[0], idev, f"{mode}{port.aliases[1]}", [f"Bold select to show options for '{port.aliases[1]}'.{input_mode_info}", "midi_input.png"])) From 56f40e442d5a54c4c44db01ca06aafe61ec4ada4 Mon Sep 17 00:00:00 2001 From: jofemodo Date: Mon, 9 Dec 2024 14:28:46 +0100 Subject: [PATCH 080/117] Fix zynswitch CUIA for PR enabled switches. --- zyngui/zynthian_gui.py | 1 + 1 file changed, 1 insertion(+) diff --git a/zyngui/zynthian_gui.py b/zyngui/zynthian_gui.py index 04e0b8ea4..b462c9189 100644 --- a/zyngui/zynthian_gui.py +++ b/zyngui/zynthian_gui.py @@ -2384,6 +2384,7 @@ def cuia_thread_task(self): pr = 0 if zynthian_gui_config.num_zynpots > 0: try: + zynswitch_cuia_ts[i] = monotonic() zpi = zynthian_gui_config.zynpot2switch.index(i) self.zynpot_pr_state[zpi] = 1 pr = 1 From 9ee94b762dd69bbe547a98218ecf4fbdc8568ce0 Mon Sep 17 00:00:00 2001 From: jofemodo Date: Mon, 9 Dec 2024 14:46:07 +0100 Subject: [PATCH 081/117] Fix long-push triggering when push+rotating for more than 2 seconds. --- zyngui/zynthian_gui.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/zyngui/zynthian_gui.py b/zyngui/zynthian_gui.py index b462c9189..76e226b3b 100644 --- a/zyngui/zynthian_gui.py +++ b/zyngui/zynthian_gui.py @@ -2350,7 +2350,9 @@ def cuia_thread_task(self): for i, ts in enumerate(zynswitch_cuia_ts): if ts is not None and ts < long_ts: zynswitch_cuia_ts[i] = None - self.zynswitch_long(i) + zpi = zynthian_gui_config.zynpot2switch.index(i) + if self.zynpot_pr_state[zpi] <= 1: + self.zynswitch_long(i) event = self.cuia_queue.get(True, repeat_interval) params = None if isinstance(event, str): From 803fec2a5c8caca83adace6cf0f1d4c245c1ee5a Mon Sep 17 00:00:00 2001 From: riban Date: Mon, 9 Dec 2024 14:07:34 +0000 Subject: [PATCH 082/117] Swap grammar for MIDI output routing help info. Add clean screen - a 10s countdown where touching screen does nothing. --- zyngui/zynthian_gui.py | 7 +++++++ zyngui/zynthian_gui_admin.py | 3 ++- zyngui/zynthian_gui_midi_config.py | 8 ++++---- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/zyngui/zynthian_gui.py b/zyngui/zynthian_gui.py index 76e226b3b..0dee653cb 100644 --- a/zyngui/zynthian_gui.py +++ b/zyngui/zynthian_gui.py @@ -1398,6 +1398,13 @@ def cuia_screen_preset(self, params=None): def cuia_screen_calibrate(self, params=None): self.calibrate_touchscreen() + def cuia_screen_clean(self, params=None): + self.state_manager.start_busy("clean_screen", "Clean screen") + for i in range(10, 0, -1): + self.state_manager.set_busy_details(f"Closing in {i}s") + sleep(1) + self.state_manager.end_busy("clean_screen") + def cuia_chain_control(self, params=None): try: # Select chain by index diff --git a/zyngui/zynthian_gui_admin.py b/zyngui/zynthian_gui_admin.py index 98f64b257..c82ab2409 100644 --- a/zyngui/zynthian_gui_admin.py +++ b/zyngui/zynthian_gui_admin.py @@ -5,7 +5,7 @@ # # Zynthian GUI Admin Class # -# Copyright (C) 2015-2023 Fernando Moyano +# Copyright (C) 2015-2024 Fernando Moyano # # ****************************************************************************** # @@ -196,6 +196,7 @@ def fill_list(self): if "cv_config" in self.zyngui.screens: self.list_data.append((self.show_cv_config, 0, "CV Settings")) self.list_data.append((self.zyngui.calibrate_touchscreen, 0, "Calibrate Touchscreen")) + self.list_data.append((self.zyngui.cuia_screen_clean, 0, "Clean Screen")) self.list_data.append((self.bluetooth, 0, "Bluetooth")) self.list_data.append((None, 0, "> TEST")) diff --git a/zyngui/zynthian_gui_midi_config.py b/zyngui/zynthian_gui_midi_config.py index a77c26d3a..ad0497cae 100644 --- a/zyngui/zynthian_gui_midi_config.py +++ b/zyngui/zynthian_gui_midi_config.py @@ -149,10 +149,10 @@ def append_port(idev): [f"Bold select to show options for '{port.aliases[1]}'.", "midi_output.png"])) elif port.aliases[0] in self.chain.midi_out: self.list_data.append((port.aliases[0], idev, f"\u2612 {port.aliases[1]}", - [f"'{port.aliases[1]}' connected to chain's MIDI output.\nBold select to show more options.", "midi_output.png"])) + [f"Chain's MIDI output connected to '{port.aliases[1]}'.\nBold select to show more options.", "midi_output.png"])) else: self.list_data.append((port.aliases[0], idev, f"\u2610 {port.aliases[1]}", - [f"'{port.aliases[1]}' disconnected from chain's MIDI output.\nBold select to show more options.", "midi_output.png"])) + [f"Chain's MIDI output disconnected from '{port.aliases[1]}'.\nBold select to show more options.", "midi_output.png"])) def append_service(service, name, help_info=""): if service in SERVICE_ICONS: @@ -257,11 +257,11 @@ def natural_keys(t): prefix = "" if chain_id in self.chain.midi_out: self.list_data.append((chain_id, None, f"\u2612 {prefix}{chain.get_name()}", - [f"Chain '{prefix}{chain.get_name()}' connected to chain's MIDI output.", + [f"Chain's MIDI output connected to chain '{prefix}{chain.get_name()}'.", "midi_output.png"])) else: self.list_data.append((chain_id, None, f"\u2610 {prefix}{chain.get_name()}", - [f"Chain '{prefix}{chain.get_name()}' disconnected from chain's MIDI output.", + [f"Chain's MIDI output disconnected from chain '{prefix}{chain.get_name()}'.", "midi_output.png"])) super().fill_list() From dc1a1cad7cb0ee3fd45be9cf0556891f7af0dcea Mon Sep 17 00:00:00 2001 From: riban Date: Mon, 9 Dec 2024 16:23:25 +0000 Subject: [PATCH 083/117] Adds help info --- icons/folder.png | Bin 0 -> 3187 bytes zyngui/zynthian_gui_chain_options.py | 8 ++++---- 2 files changed, 4 insertions(+), 4 deletions(-) create mode 100644 icons/folder.png diff --git a/icons/folder.png b/icons/folder.png new file mode 100644 index 0000000000000000000000000000000000000000..f87e5b2c0bcc8380af9c56df7923a9a8de8183d0 GIT binary patch literal 3187 zcmeHK`!^KY9^b=^M0u7F;fzYEp-4Bca45b_pgCeLB! zl-E=`6~^N*HHhk*!psmq)j1W%+LXc*AqN&pmN>tYK)bDsS75SRpSJ%8#< z0syWZKL(`YaP}8TP$SX7H_;0lk(d;6F&rc%B_X2XV-mtb&W9tg7cs^2mYM*_|K((F zi@Fpjn3V6=3g7wr*DH0pJD&&q)AuL8BZfrH`iCBw*V=DHD!C}%s5JfQhMu8*jS{w8 z*+moOenj&?PxcLE=WF-cTYstA8t0xYJ#4BOT4Cp(d|R}1!lZVnadIpTgBKl5+3f!G zsYC-q=Zr0L+Y?HllH&dgi#DB(JE>i5eildI?W9U`;ZE z=gVG4A&Gf;{x6XZVPRonG_5D*HZPtnM%hGl$`PU{ZsZ8^^KL``j8b>q??_fEn`kHx z(>*f@G4>LMv&p=b%$3W^MbAD!j)|7BaOv`@C+6G=%0k^px@Fl5-2<{}b*q)p4s>AP zjs*Rl0ks3&SnHgSawSgPvD{Le5#P`O>rg15E5Ixs$ z#Hl7O);GP7(tkt-mDClTDHiS@F)N7hmxjguwt7E3=uTwXdP#bA+^w#n20wng-pw~b zjpjXOGJYo}x(__**^YAS|rSlqGaPk2Oa%c7<+^qYlCEW)mUkMHwxTjyyMAB=y9I;T@JCUrTn8m9q+ zvgZvpE=<*|RAldV`85kS+qd+3;2uO2>xkI)Onx8LY({L|S}UZJYdDVThIkoF+d@rl zE24@($VR}f6m+R7t|t0Rp3yS7IqOR3N~-7j*IH|$UiD_!+kW4baj!CQg&*0k=1Gw7 zINEI|jCo@1&vze2;<#dhRpxy0s@AW!h*J46L0#5m4tMLYR^(|lwhQGXIkwuHS{Vkg zU(6i-I0cdZ+QI8hRdK2MU~P}9=tYnj9#6+8ehZ%t$q|kY&If%y{OgG2hV$Zmx2lRA zQX*I4<)Ty+E}GsX;{XY+z3piwA*fanlq;3)Q(9`R#olb8?L;MLq5-nm<(!W%41#+M zpw@NMA!wh1DMq`3E?Tf6Yeorq(g3x+EHa=La2y2yj2#=1!J#n7P^HoN9!NarYhD-czZ~&7Yz-=*6R=47!L<60$6dY z?{ZeQ613w-3$9tfvaXraJBt=V*><}0r4{X4V0THre{(F&yAOQW4~$diBB*>c_KjO(AId5PVgv!(j8a+_7-;;V z2pqjS0t$@3=ac7NGezcK{FH%>i6zD%XT;O}Y+15jlUqi7K#3nB>QWOC-)Zch=vMg!vb+ z2Y)PXVQj_ypi%i*txCWq7z&isApq(Kz*Wia|A+sZ+rUAY_ZeV~%J1^o0R}G@hhnji zZ*R|Gy<)%NGb(}aLR_|VCu}HaizJoFP~8Dznq$4RxQ7Xwh9k?Ri+^)I$7O3wNzFD^ zb-E05<*;54X8%p`PA=|In2X!vRxarx3JfCn#)?5tbnd1<6wZ}z2GWNcvK>J& zO7Zhu0e4AYg6N$A-(J7qrI2eO;=1uWZmy^uU>bq-I$Lgjs0=z3=7pjyz1ElSJRD9j zjJgrhI)8Y~8zc+77x3CqlL897rQXo@Q`ud6eozrsrW|Sg*W}@wZ_Bh!s8CqZhaOB>{-q{vpha;uza)AkGd<$Kcs!YRZe0mH93x$E?yw+L z3g*4o_}Y0>^dHx4>mw&pP?);F1}XRcwO2@#eCPVTnmB=ZDWlHZ@UGG>F0FfJgmjBY z9NX!XgI1(&HY7W?p<(uIwb3U@C6tu*=$f@NJ`4`EEX|vRTRoc+o+Y~^)LA}KCS!bV zbajSWNE*a>n}I|1Y6ei(R~X1PdEDkYn{(v2g=#@{uWd#eTC{#H*;+n$XmnNlX!;Uq zUZ z*Gt)83_kdLF3*%G%iE0AJ%mec@7OeBlMF3B_~ZoQJlo~CmZV9SOObS_O%!iM>=`yA zO0P^F&1aL}ihr3XIc0*zUt3gXo5Ji0F@@3 0: self.list_data.append((self.remove_all_audiofx, None, "Remove all Audio-FX", ["Remove all audio-FX processors in this chain.", "delete.png"])) self.list_data.append((None, None, "> GUI")) - self.list_data.append((self.rename_chain, None, "Rename chain")) + self.list_data.append((self.rename_chain, None, "Rename chain", ["Rename the chain. Clear name to reset to default name.", None])) if self.chain_id: if len(self.zyngui.chain_manager.ordered_chain_ids) > 2: - self.list_data.append((self.move_chain, None, "Move chain ⇦ ⇨")) + self.list_data.append((self.move_chain, None, "Move chain ⇦ ⇨", ["Reposition the chain in the mixer view.", None])) super().fill_list() @@ -392,7 +392,7 @@ def export_chain(self): for dir in dirs: if dir.startswith(".") or not os.path.isdir(f"{self.zyngui.state_manager.snapshot_dir}/{dir}"): continue - options[dir] = dir + options[dir] = [dir, ["Choose folder to store snapshot.", "folder.png"]] self.zyngui.screens['option'].config( "Select location for export", options, self.name_export) self.zyngui.show_screen('option') From bd5a394b3f9d62be4308f374d2b73bb0a8c1758a Mon Sep 17 00:00:00 2001 From: jofemodo Date: Tue, 10 Dec 2024 03:04:08 +0100 Subject: [PATCH 084/117] Fixes zynthian/zynthian-issue-tracking#1289: Improve and clean-up controller setup. --- zyngine/zynthian_controller.py | 18 ++--- zyngine/zynthian_engine.py | 91 +++++++++--------------- zyngine/zynthian_engine_zynaddsubfx.py | 95 +++++++++++++++----------- zyngine/zynthian_processor.py | 13 ++++ 4 files changed, 109 insertions(+), 108 deletions(-) diff --git a/zyngine/zynthian_controller.py b/zyngine/zynthian_controller.py index 1440b23f3..8fbc8fa7a 100644 --- a/zyngine/zynthian_controller.py +++ b/zyngine/zynthian_controller.py @@ -160,11 +160,7 @@ def set_options(self, options): if 'midi_chan' in options: self.midi_chan = options['midi_chan'] if 'midi_cc' in options: - cc = options['midi_cc'] - if isinstance(cc, str): - self.osc_path = cc - else: - self.midi_cc = cc + self.midi_cc = options['midi_cc'] if 'osc_port' in options: self.osc_port = options['osc_port'] if 'osc_path' in options: @@ -375,6 +371,10 @@ def set_value(self, val, send=True): if old_val == self.value: return + self.send_value(send) + self.is_dirty = True + + def send_value(self, send=True): mval = None if self.engine and send: # Send value using engine method... @@ -385,23 +385,19 @@ def set_value(self, val, send=True): try: if self.osc_path: # logging.debug("Sending OSC Controller '{}', {} => {}".format(self.symbol, self.osc_path, self.get_ctrl_osc_val())) - liblo.send(self.engine.osc_target, - self.osc_path, self.get_ctrl_osc_val()) + liblo.send(self.engine.osc_target, self.osc_path, self.get_ctrl_osc_val()) elif self.midi_cc: mval = self.get_ctrl_midi_val() # logging.debug("Sending MIDI Controller '{}', CH{}#CC{}={}".format(self.symbol, self.midi_chan, self.midi_cc, mval)) self.send_midi_cc(mval) except Exception as e: - logging.warning( - "Can't send controller '{}' => {}".format(self.symbol, e)) + logging.warning("Can't send controller '{}' => {}".format(self.symbol, e)) # Send feedback to MIDI controllers => What MIDI controllers? Those selected as MIDI-out? # TODO: Set midi_feeback to MIDI learn if self.midi_feedback: self.send_midi_feedback(mval) - self.is_dirty = True - def send_midi_cc(self, mval=None): if mval is None: mval = self.get_ctrl_midi_val() diff --git a/zyngine/zynthian_engine.py b/zyngine/zynthian_engine.py index 8c41a3d5b..2abf0fa50 100644 --- a/zyngine/zynthian_engine.py +++ b/zyngine/zynthian_engine.py @@ -26,18 +26,17 @@ import re import json import glob +import copy import liblo import logging import pexpect import fnmatch from time import sleep -from string import Template -from os.path import isfile, isdir, ismount, join +from os.path import isfile, isdir, join import zynautoconnect from . import zynthian_controller from zyngui import zynthian_gui_config -from zyncoder.zyncore import lib_zyncore # -------------------------------------------------------------------------------- # Basic Engine Class: Spawn a process & manage IPC communication using pexpect @@ -572,12 +571,36 @@ def load_preset_favs(self): # Controllers Management # --------------------------------------------------------------------------- + def get_ctrl_options(self, ctrl, processor): + if isinstance(ctrl[1], dict): + build_from_options = True + options = copy.copy(ctrl[1]) + else: + build_from_options = False + options = {} + if isinstance(ctrl[1], int) and ctrl[1] > 0: + options["midi_cc"] = ctrl[1] + + options["processor"] = processor + options["midi_chan"] = processor.get_midi_chan() + if build_from_options: + return options + + # Add extra options depending on array length ... + if len(ctrl) > 4 and ctrl[0] in processor.controllers_dict: + # optional param 4 is graph path + options['graph_path'] = ctrl[4] + if len(ctrl) > 3: + # optional param 3 is called value_max but actually could be a configuration object + options['value_max'] = ctrl[3] + if len(ctrl) > 2: + options['value'] = ctrl[2] + return options + # Get zynthian controllers dictionary. # Updates existing processor dictionary. # + Default implementation uses a static controller definition array def get_controllers_dict(self, processor): - midich = processor.get_midi_chan() - if self._ctrls is not None: # Remove controls that are no longer used for symbol in list(processor.controllers_dict): @@ -591,63 +614,15 @@ def get_controllers_dict(self, processor): else: processor.controllers_dict[symbol].reset(self, symbol) + # Regenerate / update controller dictionary for ctrl in self._ctrls: - cc = None - options = {} - build_from_options = False - if isinstance(ctrl[1], dict): - options = ctrl[1] - build_from_options = True - # OSC control => - elif isinstance(ctrl[1], str): - # replace variables ... - tpl = Template(ctrl[1]) - cc = tpl.safe_substitute(ch=midich) - try: - cc = tpl.safe_substitute(i=processor.part_i) - except: - pass - # set osc_port option ... - if self.osc_target_port > 0: - options['osc_port'] = self.osc_target_port - # debug message - logging.debug('CONTROLLER %s OSC PATH => %s' % - (ctrl[0], cc)) - # MIDI Control => - else: - cc = ctrl[1] - - options["processor"] = processor - options["midi_chan"] = midich - if cc is not None: - options["midi_cc"] = cc - - # Build controller depending on array length ... + options = self.get_ctrl_options(ctrl, processor) + # Controller already exists so reconfigure with new settings if ctrl[0] in processor.controllers_dict: - # Controller already exists so reconfigure with new settings zctrl = processor.controllers_dict[ctrl[0]] - if build_from_options: - zctrl.set_options(options) - elif len(ctrl) > 3: - options['value'] = ctrl[2] - options['value_max'] = ctrl[3] - zctrl.set_options(options) - elif len(ctrl) > 2: - options['value'] = ctrl[2] - zctrl.set_options(options) - + zctrl.set_options(options) + # Create new controller else: - if not build_from_options: - if len(ctrl) > 4: - # optional param 4 is graph path - options['graph_path'] = ctrl[4] - if len(ctrl) > 3: - # optional param 3 is called value_max but actually could be a configuration object - options['value_max'] = ctrl[3] - if len(ctrl) > 2: - # param 2 is zctrl value - options['value'] = ctrl[2] - # param 0 is symbol string, param 1 is options or midi cc or osc path zctrl = zynthian_controller(self, ctrl[0], options) processor.controllers_dict[zctrl.symbol] = zctrl if zctrl.midi_cc is not None: diff --git a/zyngine/zynthian_engine_zynaddsubfx.py b/zyngine/zynthian_engine_zynaddsubfx.py index 56db836f9..a20d65216 100644 --- a/zyngine/zynthian_engine_zynaddsubfx.py +++ b/zyngine/zynthian_engine_zynaddsubfx.py @@ -26,6 +26,7 @@ import shutil import logging from time import sleep +from string import Template from os.path import isfile, join from subprocess import check_output @@ -49,10 +50,10 @@ class zynthian_engine_zynaddsubfx(zynthian_engine): # MIDI Controllers _ctrls = [ - ['volume', 7, 115], + #['volume', 7, 115], # ['panning', 10, 64], # ['expression', 11, 127], - # ['volume', '/part$i/Pvolume', 96], + ['volume', '/part$i/Pvolume', 96, 127, {'midi_cc': 7}], ['panning', '/part$i/Ppanning', 64], ['filter cutoff', 74, 64], ['filter resonance', 71, 64], @@ -182,15 +183,12 @@ def reset(self): def add_processor(self, processor): self.processors.append(processor) - try: - processor.part_i = self.get_free_parts()[0] - processor.jackname = "{}:part{}/".format( - self.jackname, processor.part_i) - processor.refresh_controllers() - logging.debug("ADD processor => Part {} ({})".format( - processor.part_i, self.jackname)) - except Exception as e: - logging.error(f"Unable to add processor to engine - {e}") + processor.part_i = self.get_free_parts()[0] + processor.jackname = "{}:part{}/".format(self.jackname, processor.part_i) + processor.refresh_controllers() + self.enable_part(processor) + processor.send_controller_values() + logging.debug("ADD processor => Part {} ({})".format(processor.part_i, self.jackname)) def remove_processor(self, processor): self.disable_part(processor.part_i) @@ -204,7 +202,9 @@ def remove_processor(self, processor): def set_midi_chan(self, processor): if self.osc_server and processor.part_i is not None: lib_zyncore.zmop_set_midi_chan_trans( - processor.chain.zmop_index, processor.get_midi_chan(), processor.part_i) + processor.chain.zmop_index, + processor.get_midi_chan(), + processor.part_i) # ---------------------------------------------------------------------------- # Preset Managament @@ -219,7 +219,7 @@ def _get_preset_list(bank): for f in sorted(os.listdir(preset_dir)): preset_fpath = join(preset_dir, f) ext = f[-3:].lower() - if (isfile(preset_fpath) and (ext == 'xiz' or ext == 'xmz' or ext == 'xsz' or ext == 'xlz')): + if isfile(preset_fpath) and (ext == 'xiz' or ext == 'xmz' or ext == 'xsz' or ext == 'xlz'): try: index = int(f[0:4])-1 title = str.replace(f[5:-4], '_', ' ') @@ -229,8 +229,7 @@ def _get_preset_list(bank): bank_lsb = int(index/128) bank_msb = bank[1] prg = index % 128 - preset_list.append( - [preset_fpath, [bank_msb, bank_lsb, prg], title, ext, f]) + preset_list.append([preset_fpath, [bank_msb, bank_lsb, prg], title, ext, f]) return preset_list def get_preset_list(self, bank): @@ -241,12 +240,9 @@ def set_preset(self, processor, preset, preload=False): return self.state_manager.start_busy("zynaddsubfx") if preset[3] == 'xiz': - self.enable_part(processor) - self.osc_server.send( - self.osc_target, "/load-part", processor.part_i, preset[0]) + self.osc_server.send(self.osc_target, "/load-part", processor.part_i, preset[0]) # logging.debug("OSC => /load-part %s, %s" % (processor.part_i,preset[0])) elif preset[3] == 'xmz': - self.enable_part(processor) self.osc_server.send(self.osc_target, "/load_xmz", preset[0]) logging.debug("OSC => /load_xmz %s" % preset[0]) elif preset[3] == 'xsz': @@ -255,16 +251,7 @@ def set_preset(self, processor, preset, preload=False): elif preset[3] == 'xlz': self.osc_server.send(self.osc_target, "/load_xlz", preset[0]) logging.debug("OSC => /load_xlz %s" % preset[0]) - self.osc_server.send(self.osc_target, "/volume") - i = 0 - while self.state_manager.is_busy("zynaddsubfx"): - sleep(0.1) - if i > 100: - self.state_manager.end_busy("zynaddsubfx") - break - else: - i = i + 1 - processor.send_ctrl_midi_cc() + self.wait_busy() return True def cmp_presets(self, preset1, preset2): @@ -280,18 +267,38 @@ def cmp_presets(self, preset1, preset2): # Controller Managament # ---------------------------------------------------------------------------- + def get_ctrl_options(self, ctrl, processor): + options = super().get_ctrl_options(ctrl, processor) + + # OSC control => + if isinstance(ctrl[1], str): + # replace variables ... + tpl = Template(ctrl[1]) + try: + osc_path = tpl.safe_substitute(i=processor.part_i) + options['osc_path'] = osc_path + if self.osc_target_port > 0: + options['osc_port'] = self.osc_target_port + logging.debug(f"CONTROLLER {ctrl[0]} with OSC PATH => {osc_path}") + except Exception as e: + logging.error(f"Malformed OSC path => {ctrl[1]}") + + # Extra options => Pre-MIDI learning, etc. + if len(ctrl) > 4 and isinstance(ctrl[4], dict): + options.update(ctrl[4]) + + return options + def send_controller_value(self, zctrl): try: if self.osc_server and zctrl.osc_path: - self.osc_server.send( - self.osc_target, zctrl.osc_path, zctrl.get_ctrl_osc_val()) + self.osc_server.send(self.osc_target, zctrl.osc_path, zctrl.get_ctrl_osc_val()) else: izmop = zctrl.processor.chain.zmop_index if izmop is not None and izmop >= 0: mchan = zctrl.processor.part_i mval = zctrl.get_ctrl_midi_val() - lib_zyncore.zmop_send_ccontrol_change( - izmop, mchan, zctrl.midi_cc, mval) + lib_zyncore.zmop_send_ccontrol_change(izmop, mchan, zctrl.midi_cc, mval) except Exception as err: logging.error(err) @@ -301,12 +308,11 @@ def send_controller_value(self, zctrl): def enable_part(self, processor): if self.osc_server and processor.part_i is not None: - self.osc_server.send( - self.osc_target, "/part%d/Penabled" % processor.part_i, True) - self.osc_server.send(self.osc_target, "/part%d/Prcvchn" % - processor.part_i, processor.part_i) - lib_zyncore.zmop_set_midi_chan_trans( - processor.chain.zmop_index, processor.get_midi_chan(), processor.part_i) + self.osc_server.send(self.osc_target, f"/part{processor.part_i}/Penabled", True) + self.osc_server.send(self.osc_target, f"/part{processor.part_i}/Prcvchn", processor.part_i) + lib_zyncore.zmop_set_midi_chan_trans(processor.chain.zmop_index, + processor.get_midi_chan(), + processor.part_i) def disable_part(self, i): if self.osc_server: @@ -335,6 +341,17 @@ def cb_osc_all(self, path, args, types, src): except Exception as e: logging.warning(e) + def wait_busy(self): + self.osc_server.send(self.osc_target, "/volume") + i = 0 + while self.state_manager.is_busy("zynaddsubfx"): + sleep(0.1) + if i > 100: + self.state_manager.end_busy("zynaddsubfx") + break + else: + i = i + 1 + # --------------------------------------------------------------------------- # API methods # --------------------------------------------------------------------------- diff --git a/zyngine/zynthian_processor.py b/zyngine/zynthian_processor.py index f1d2926c7..c5c55a9c9 100644 --- a/zyngine/zynthian_processor.py +++ b/zyngine/zynthian_processor.py @@ -612,10 +612,23 @@ def build_ctrl_screen(self, ctrl_keys): logging.error("Controller %s is not defined" % k) return zctrls + def send_controller_values(self): + """Send all controller values to engines + + It should be called once when creating some processors that don't give controller feedback + or when loading presets that modify these controller values without giving feedback. + => fluidsynth, zynaddsubfx, linuxsampler, ... + """ + + for k, zctrl in self.controllers_dict.items(): + zctrl.send_value() + def send_ctrl_midi_cc(self): """Send MIDI CC for all controllers TODO: When is this required? Fluidsynth, linuxsampler and others calls this during set_preset + => It's used for setting MIDI controllers to a known value, avoiding "jumps" when moving knobs + => It should be replaced by send_controllers() (see above) and called one-time when creating the processor """ for k, zctrl in self.controllers_dict.items(): From 5ce99535f9c2214bb453bb44a5b54e9f66f2fffa Mon Sep 17 00:00:00 2001 From: jofemodo Date: Tue, 10 Dec 2024 03:19:41 +0100 Subject: [PATCH 085/117] Disable "Clean screen" entry from admin menu => What is this, @riban? --- zyngui/zynthian_gui_admin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zyngui/zynthian_gui_admin.py b/zyngui/zynthian_gui_admin.py index c82ab2409..48d13e4d8 100644 --- a/zyngui/zynthian_gui_admin.py +++ b/zyngui/zynthian_gui_admin.py @@ -196,7 +196,7 @@ def fill_list(self): if "cv_config" in self.zyngui.screens: self.list_data.append((self.show_cv_config, 0, "CV Settings")) self.list_data.append((self.zyngui.calibrate_touchscreen, 0, "Calibrate Touchscreen")) - self.list_data.append((self.zyngui.cuia_screen_clean, 0, "Clean Screen")) + #self.list_data.append((self.zyngui.cuia_screen_clean, 0, "Clean Screen")) # What the hell is this? self.list_data.append((self.bluetooth, 0, "Bluetooth")) self.list_data.append((None, 0, "> TEST")) From 73eb255bfb1051d5e7828831c7210b0431be543b Mon Sep 17 00:00:00 2001 From: jofemodo Date: Tue, 10 Dec 2024 03:25:38 +0100 Subject: [PATCH 086/117] Scale folder icon to the standard size 300x300, adding some marging. --- icons/folder.png | Bin 3187 -> 2802 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/icons/folder.png b/icons/folder.png index f87e5b2c0bcc8380af9c56df7923a9a8de8183d0..937a0f061a9f9abc27fa88b98679cb2bbeb8847b 100644 GIT binary patch literal 2802 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 Date: Tue, 10 Dec 2024 08:07:58 +0000 Subject: [PATCH 087/117] Fixes comment --- zyngui/zynthian_widget_sooperlooper.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/zyngui/zynthian_widget_sooperlooper.py b/zyngui/zynthian_widget_sooperlooper.py index ee74645d0..8b515cc56 100644 --- a/zyngui/zynthian_widget_sooperlooper.py +++ b/zyngui/zynthian_widget_sooperlooper.py @@ -3,9 +3,9 @@ # ****************************************************************************** # ZYNTHIAN PROJECT: Zynthian GUI # -# Zynthian Widget Class for "Zynthian Audio Player" (zynaudioplayer#one) +# Zynthian Widget Class for "Sooperlooper" # -# Copyright (C) 2022 Brian Walton +# Copyright (C) 2022-2024 Brian Walton # # ****************************************************************************** # From 57598d6e876aa7bc6afb8cb3ba311202f8868511 Mon Sep 17 00:00:00 2001 From: riban Date: Tue, 10 Dec 2024 08:34:20 +0000 Subject: [PATCH 088/117] Fix sooperlooper default value for global_cc. --- zyngine/zynthian_engine_sooperlooper.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/zyngine/zynthian_engine_sooperlooper.py b/zyngine/zynthian_engine_sooperlooper.py index 46687b7b3..c388e761e 100644 --- a/zyngine/zynthian_engine_sooperlooper.py +++ b/zyngine/zynthian_engine_sooperlooper.py @@ -414,7 +414,7 @@ def __init__(self, state_manager=None): ['loop_count', {'name': 'loop count', 'value': 1, 'value_min': 1, 'value_max': self.MAX_LOOPS}], ['selected_loop_num', {'name': 'selected loop', 'value': 1, 'value_min': 1, 'value_max': 6}], ['single_pedal', {'name': 'single pedal', 'value': 0, 'value_max': 1, 'labels': ['>', '<'], 'is_toggle': True}], - ['global_cc', {'name': 'midi cc to selected loop', 'value': 1, 'labels':['off', 'on']}] + ['global_cc', {'name': 'midi cc to selected loop', 'value': 127, 'labels':['off', 'on']}] ] # Controller Screens @@ -574,13 +574,9 @@ def get_controllers_dict(self, processor): for i in range(self.MAX_LOOPS): zctrl = zynthian_controller(self, f"{ctrl[0]}:{i}", ctrl[1]) processor.controllers_dict[zctrl.symbol] = zctrl - if midi_chan is not None and len(ctrl) > 2: - self.state_manager.chain_manager.add_midi_learn(midi_chan, ctrl[2], zctrl) else: zctrl = zynthian_controller(self, ctrl[0], ctrl[1]) processor.controllers_dict[zctrl.symbol] = zctrl - if midi_chan is not None and len(ctrl) > 2: - self.state_manager.chain_manager.add_midi_learn(midi_chan, ctrl[2], zctrl) return processor.controllers_dict def send_controller_value(self, zctrl): From 69a06638a73a2f10f02a4d4b038f447d444c736a Mon Sep 17 00:00:00 2001 From: riban Date: Tue, 10 Dec 2024 08:44:46 +0000 Subject: [PATCH 089/117] Fixes sooperlooper midi cc binding for selected loop logic --- zyngine/zynthian_engine_sooperlooper.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/zyngine/zynthian_engine_sooperlooper.py b/zyngine/zynthian_engine_sooperlooper.py index c388e761e..70ee63de8 100644 --- a/zyngine/zynthian_engine_sooperlooper.py +++ b/zyngine/zynthian_engine_sooperlooper.py @@ -360,7 +360,7 @@ def __init__(self, state_manager=None): self.selected_loop = None self.loop_count = 1 self.channels = 2 - self.global_cc_binding = True # True for MIDI CC to control selected loop. False to target individual loops + self.selected_loop_cc_binding = True # True for MIDI CC to control selected loop. False to target all loops ui_dir = os.environ.get('ZYNTHIAN_UI_DIR', "/zynthian/zynthian-ui") self.custom_gui_fpath = f"{ui_dir}/zyngui/zynthian_widget_sooperlooper.py" @@ -414,7 +414,7 @@ def __init__(self, state_manager=None): ['loop_count', {'name': 'loop count', 'value': 1, 'value_min': 1, 'value_max': self.MAX_LOOPS}], ['selected_loop_num', {'name': 'selected loop', 'value': 1, 'value_min': 1, 'value_max': 6}], ['single_pedal', {'name': 'single pedal', 'value': 0, 'value_max': 1, 'labels': ['>', '<'], 'is_toggle': True}], - ['global_cc', {'name': 'midi cc to selected loop', 'value': 127, 'labels':['off', 'on']}] + ['selected_loop_cc', {'name': 'midi cc to selected loop', 'value': 127, 'labels':['off', 'on']}] ] # Controller Screens @@ -428,7 +428,7 @@ def __init__(self, state_manager=None): ['Global levels', ['rec_thresh', 'input_gain']], ['Global quantize', ['quantize', 'mute_quantized', 'overdub_quantized', 'replace_quantized']], ['Global sync 1', ['sync_source', 'sync', 'playback_sync', 'relative_sync']], - ['Global sync 2', ['round', 'use_feedback_play', 'global_cc']] + ['Global sync 2', ['round', 'use_feedback_play', 'selected_loop_cc']] ] self.start() @@ -580,12 +580,12 @@ def get_controllers_dict(self, processor): return processor.controllers_dict def send_controller_value(self, zctrl): - if zctrl.symbol == "global_cc": - self.global_cc_binding = zctrl.value != 0 + if zctrl.symbol == "selected_loop_cc": + self.selected_loop_cc_binding = zctrl.value != 0 return if ":" in zctrl.symbol: symbol, chan = zctrl.symbol.split(":") - if not self.global_cc_binding: + if self.selected_loop_cc_binding: if int(chan) != self.selected_loop: return chan = -3 From 55628871ef7858cf623dde97803ef3dc0675cc52 Mon Sep 17 00:00:00 2001 From: jofemodo Date: Tue, 10 Dec 2024 13:02:14 +0100 Subject: [PATCH 090/117] Re-add "clean screen" entrie to admin menu. --- zyngui/zynthian_gui_admin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zyngui/zynthian_gui_admin.py b/zyngui/zynthian_gui_admin.py index 48d13e4d8..c82ab2409 100644 --- a/zyngui/zynthian_gui_admin.py +++ b/zyngui/zynthian_gui_admin.py @@ -196,7 +196,7 @@ def fill_list(self): if "cv_config" in self.zyngui.screens: self.list_data.append((self.show_cv_config, 0, "CV Settings")) self.list_data.append((self.zyngui.calibrate_touchscreen, 0, "Calibrate Touchscreen")) - #self.list_data.append((self.zyngui.cuia_screen_clean, 0, "Clean Screen")) # What the hell is this? + self.list_data.append((self.zyngui.cuia_screen_clean, 0, "Clean Screen")) self.list_data.append((self.bluetooth, 0, "Bluetooth")) self.list_data.append((None, 0, "> TEST")) From 177e226d35135ad872bf4531bfee00c3a5988d2b Mon Sep 17 00:00:00 2001 From: jofemodo Date: Wed, 18 Dec 2024 18:54:21 +0100 Subject: [PATCH 091/117] Disable debug message. --- zyngine/zynthian_engine_zynaddsubfx.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zyngine/zynthian_engine_zynaddsubfx.py b/zyngine/zynthian_engine_zynaddsubfx.py index a20d65216..0c8ab1d8b 100644 --- a/zyngine/zynthian_engine_zynaddsubfx.py +++ b/zyngine/zynthian_engine_zynaddsubfx.py @@ -279,7 +279,7 @@ def get_ctrl_options(self, ctrl, processor): options['osc_path'] = osc_path if self.osc_target_port > 0: options['osc_port'] = self.osc_target_port - logging.debug(f"CONTROLLER {ctrl[0]} with OSC PATH => {osc_path}") + #logging.debug(f"CONTROLLER {ctrl[0]} with OSC PATH => {osc_path}") except Exception as e: logging.error(f"Malformed OSC path => {ctrl[1]}") From 81ca721e5c9df1b0ce4c39230703bb24722620d9 Mon Sep 17 00:00:00 2001 From: jofemodo Date: Wed, 18 Dec 2024 18:56:47 +0100 Subject: [PATCH 092/117] + Select saved preset after saving. + Code re-style: remove some line breaks. --- zyngui/zynthian_gui_save_preset.py | 38 +++++++++++------------------- 1 file changed, 14 insertions(+), 24 deletions(-) diff --git a/zyngui/zynthian_gui_save_preset.py b/zyngui/zynthian_gui_save_preset.py index 68cfd0cf0..2421238c0 100644 --- a/zyngui/zynthian_gui_save_preset.py +++ b/zyngui/zynthian_gui_save_preset.py @@ -56,8 +56,7 @@ def save_preset(self): else: options["None"] = ["", None, "None", None] if len(options) > 0: - self.zyngui.screens['option'].config( - "Select bank...", options, self.save_preset_select_bank_cb) + self.zyngui.screens['option'].config("Select bank...", options, self.save_preset_select_bank_cb) self.zyngui.show_screen('option') self.zyngui.screens['option'].select(index) else: @@ -66,8 +65,7 @@ def save_preset(self): def save_preset_select_bank_cb(self, bank_name, bank_info): self.save_preset_bank_info = bank_info if bank_info == "NEW_BANK": - self.zyngui.show_keyboard( - self.save_preset_select_name_cb, "NewBank") + self.zyngui.show_keyboard(self.save_preset_select_name_cb, "NewBank") else: self.save_preset_select_name_cb() @@ -76,8 +74,7 @@ def save_preset_select_name_cb(self, create_bank_name=None): create_bank_name = create_bank_name.strip() self.save_preset_create_bank_name = create_bank_name if self.processor.preset_name: - self.zyngui.show_keyboard( - self.save_preset_cb, self.processor.preset_name + " COPY") + self.zyngui.show_keyboard(self.save_preset_cb, self.processor.preset_name + " COPY") else: self.zyngui.show_keyboard(self.save_preset_cb, "New Preset") @@ -85,41 +82,34 @@ def save_preset_cb(self, preset_name): preset_name = preset_name.strip() # If must create new bank, calculate URID if self.save_preset_create_bank_name: - create_bank_urid = self.processor.engine.get_user_bank_urid( - self.save_preset_create_bank_name) - self.save_preset_bank_info = ( - create_bank_urid, None, self.save_preset_create_bank_name, None) + create_bank_urid = self.processor.engine.get_user_bank_urid(self.save_preset_create_bank_name) + self.save_preset_bank_info = (create_bank_urid, None, self.save_preset_create_bank_name, None) if self.processor.engine.preset_exists(self.save_preset_bank_info, preset_name): - self.zyngui.show_confirm("Do you want to overwrite preset '{}'?".format( - preset_name), self.do_save_preset, preset_name) + self.zyngui.show_confirm(f"Do you want to overwrite preset '{preset_name}'?", + self.do_save_preset, preset_name) else: self.do_save_preset(preset_name) def do_save_preset(self, preset_name): preset_name = preset_name.strip() - self.zyngui.state_manager.start_busy( - "Save Preset", f"Saving preset {preset_name}") + self.zyngui.state_manager.start_busy("Save Preset", f"Saving preset {preset_name}") try: # Save preset - preset_uri = self.processor.engine.save_preset( - self.save_preset_bank_info, preset_name) + preset_uri = self.processor.engine.save_preset(self.save_preset_bank_info, preset_name) if preset_uri: # If must create new bank, do it! if self.save_preset_create_bank_name: - self.processor.engine.create_user_bank( - self.save_preset_create_bank_name) - logging.info("Created new bank '{}' => {}".format( - self.save_preset_create_bank_name, self.save_preset_bank_info[0])) + self.processor.engine.create_user_bank(self.save_preset_create_bank_name) + logging.info(f"Created new bank '{self.save_preset_create_bank_name}' => {self.save_preset_bank_info[0]}") if self.save_preset_bank_info: - self.processor.set_bank_by_id( - self.save_preset_bank_info[0]) + self.processor.set_bank_by_id(self.save_preset_bank_info[0]) self.processor.load_preset_list() self.processor.set_preset_by_id(preset_uri) + self.index = self.processor.get_preset_index() else: - logging.error("Can't save preset '{}' to bank '{}'".format( - preset_name, self.save_preset_bank_info[2])) + logging.error(f"Can't save preset '{preset_name}' to bank '{self.save_preset_bank_info[2]}'") except Exception as e: logging.error(e) From 7b5f99e7016c740affcf6ff59a90d9743b02170d Mon Sep 17 00:00:00 2001 From: jofemodo Date: Wed, 18 Dec 2024 18:59:33 +0100 Subject: [PATCH 093/117] + Add "proc_exit" object flag. + Disable "echo" when calling pexpect.spawn. --- zyngine/zynthian_engine.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/zyngine/zynthian_engine.py b/zyngine/zynthian_engine.py index 2abf0fa50..1a2494f6f 100644 --- a/zyngine/zynthian_engine.py +++ b/zyngine/zynthian_engine.py @@ -64,6 +64,7 @@ def __init__(self, name=None, command=None, prompt=None, cwd=None): self.proc = None self.proc_timeout = 30 self.proc_start_sleep = None + self.proc_exit = False self.command = command self.command_env = os.environ.copy() self.command_prompt = prompt @@ -80,6 +81,8 @@ def start(self): try: logging.debug("Command: {}".format(self.command)) + self.proc_exit = False + # Turns out that environment's PWD is not set automatically # when cwd is specified for pexpect.spawn(), so do it here. if self.command_cwd: @@ -88,8 +91,8 @@ def start(self): # Setting cwd is because we've set PWD above. Some engines doesn't # care about the process's cwd, but it is more consistent to set # cwd when PWD has been set. - self.proc = pexpect.spawn( - self.command, timeout=self.proc_timeout, env=self.command_env, cwd=self.command_cwd) + self.proc = pexpect.spawn(self.command, echo=False, timeout=self.proc_timeout, + env=self.command_env, cwd=self.command_cwd) self.proc.delaybeforesend = 0 output = self.proc_get_output() @@ -106,6 +109,7 @@ def stop(self): if self.proc: try: logging.info("Stopping Engine " + self.name) + self.proc_exit = True self.proc.terminate(True) self.proc = None except Exception as err: @@ -129,8 +133,7 @@ def proc_cmd(self, cmd): # logging.debug("proc output:\n{}".format(out)) except Exception as err: out = "" - logging.error( - "Can't exec engine command: {} => {}".format(cmd, err)) + logging.error("Can't exec engine command: {} => {}".format(cmd, err)) return out From b55d98225cbf1667b83039b517e5363f02c7a3e7 Mon Sep 17 00:00:00 2001 From: jofemodo Date: Wed, 18 Dec 2024 19:04:19 +0100 Subject: [PATCH 094/117] Refactorize jalv engine to work with the new async CLI, allowing bidirectional updates and property parameters. --- zyngine/zynthian_engine_jalv.py | 335 ++++++++++++++++++++++---------- 1 file changed, 236 insertions(+), 99 deletions(-) diff --git a/zyngine/zynthian_engine_jalv.py b/zyngine/zynthian_engine_jalv.py index f070b5ffc..8be22efa8 100644 --- a/zyngine/zynthian_engine_jalv.py +++ b/zyngine/zynthian_engine_jalv.py @@ -28,7 +28,10 @@ import copy import shutil import logging -from subprocess import check_output, STDOUT +from time import sleep +from datetime import datetime +from threading import Thread +from subprocess import Popen, check_output, STDOUT, PIPE from . import zynthian_lv2 from . import zynthian_engine @@ -152,6 +155,11 @@ class zynthian_engine_jalv(zynthian_engine): def __init__(self, eng_code, state_manager, dryrun=False, jackname=None): super().__init__(state_manager) + self.proc_poll_thread = None + + self.save_bank = None + self.save_preset_uri = None + if state_manager: self.eng_info = self.state_manager.chain_manager.engine_info[eng_code] else: @@ -189,18 +197,21 @@ def __init__(self, eng_code, state_manager, dryrun=False, jackname=None): jalv_bin = "jalv.qt5" elif self.native_gui == "Qt4UI": # jalv_bin = "jalv.qt4" - jalv_bin = "jalv.gtk" + jalv_bin = "jalv.gtk3" else: # elif self.native_gui=="X11UI": - jalv_bin = "jalv.gtk" - self.command = f"{jalv_bin} --jack-name {self.jackname} {self.plugin_url}" + jalv_bin = "jalv.gtk3" + self.command = [jalv_bin, "--jack-name", self.jackname, self.plugin_url] else: - self.command = f"jalv -n {self.jackname} {self.plugin_url}" + self.command = ["jalv", "-n", self.jackname, self.plugin_url] # Some plugins need a X11 display for running headless (QT5, QT6), # but some others can't run headless if there is a valid DISPLAY defined if not self.plugin_name.endswith("v1"): self.command_env['DISPLAY'] = "X" - self.command_prompt = "\n> " + # test jalv development => + self.command[0] = "/zynthian/zynthian-sw/jalv/build/" + self.command[0] + + self.command_prompt = ">" # Jalv which uses PWD as the root for presets self.command_cwd = zynthian_engine.my_data_dir + "/presets/lv2" @@ -271,6 +282,145 @@ def __init__(self, eng_code, state_manager, dryrun=False, jackname=None): def load_preset_info(self): self.preset_info = zynthian_lv2.get_plugin_presets_cache(self.plugin_name) + # --------------------------------------------------------------------------- + # Subprocess Management & IPC + # --------------------------------------------------------------------------- + + def start(self): + if not self.proc: + logging.info("Starting Engine {}".format(self.name)) + try: + logging.debug("Command: {}".format(self.command)) + self.proc_exit = False + # Turns out that environment's PWD is not set automatically + # when cwd is specified for pexpect.spawn(), so do it here. + if self.command_cwd: + self.command_env['PWD'] = self.command_cwd + # Setting cwd is because we've set PWD above. Some engines doesn't + # care about the process's cwd, but it is more consistent to set + # cwd when PWD has been set. + self.proc = Popen(self.command, env=self.command_env, cwd=self.command_cwd, shell=False, + text=True, bufsize=1, stdout=PIPE, stderr=STDOUT, stdin=PIPE) + output = self.proc_get_output() + self.start_proc_poll_thread() + return output + + except Exception as err: + logging.error( + "Can't start engine {} => {}".format(self.name, err)) + + def stop(self): + if self.proc: + try: + logging.info("Stopping Engine " + self.name) + self.proc_cmd("") + self.proc_exit = True + self.proc.terminate() + try: + self.proc.wait(timeout=5) + except TimeoutExpired: + self.proc.kill() + self.proc = None + except Exception as err: + logging.error(f"Can't stop engine {self.name} => {err}") + + def proc_cmd(self, cmd): + #a = datetime.now() + self.proc.stdin.writelines([cmd + "\n"]) + #tdus = (datetime.now() - a).microseconds + #logging.debug(f"COMMAND ({tdus}): {cmd}") + + def proc_get_output(self): + res = "" + while not self.proc_exit: + line = self.proc.stdout.readline().strip() + if line == self.command_prompt: + break + elif line: + res += line + return res + + def proc_poll_line(self): + return self.proc.stdout.readline() + + def proc_poll_thread_task(self): + while not self.proc_exit: + line = self.proc.stdout.readline().strip() + if line: + self.proc_poll_parse_line(line) + + def proc_poll_parse_line(self, line): + #logging.debug(f"{self.jackname} PARSE => " + line) + match line[0:5]: + case "#CTR>": + self.proc_parse_ctrl_value(line[6:]) + case "#MON>": + self.proc_parse_mon_value(line[6:]) + case "#PRS>": + self.proc_parse_preset(line[6:]) + case _: + if line == self.command_prompt: + logging.debug(f"PROMPT {self.jackname} >") + elif line: + logging.debug(f"LOG {self.jackname} > " + line) + + def proc_parse_ctrl_value(self, line): + parts = line.split("=") + if len(parts) == 2: + try: + val = float(parts[1]) + except Exception as e: + logging.warning(f"Wrong controller value when parsing jalv output => {line}") + return + symparts = parts[0].split("#", maxsplit=1) + #logging.debug(f"#CTR> {symparts[1]} ({symparts[0]}) = {val}") + try: + zctrl = self.lv2_zctrl_dict[symparts[1]] + zctrl.set_value(val, False) + if zctrl.graph_path is None: + try: + zctrl.graph_path = int(symparts[0]) + #logging.debug(f"UPDATING JALV ZCTRL INDEX FOR '{symparts[1]}' => {zctrl.graph_path}") + except: + logging.warning(f"Cant't parse controller index from jalv output: {line}") + except Exception as e: + # TODO This shouldn't happen when property parameters are fully implemented + logging.warning(f"Unknown controller when parsing jalv output => {line}") + else: + logging.warning(f"Wrong controller format when parsing jalv output => {line}") + + def proc_parse_mon_value(self, line): + parts = line.split("=") + if len(parts) == 2: + try: + val = float(parts[1]) + except Exception as e: + logging.warning(f"Wrong monitor value when parsing jalv output => {line}") + return + symparts = parts[0].split("#", maxsplit=1) + #logging.debug(f"#MON> {symparts[1]} ({symparts[0]}) = {val}") + try: + self.lv2_monitors_dict[symparts[1]] = val + except Exception as e: + # TODO This shouldn't happen when property parameters are fully implemented + logging.warning(f"Unknown monitor when parsing jalv output => {line}") + else: + logging.warning(f"Wrong monitor format when parsing jalv output => {line}") + + def proc_parse_preset(self, line): + parts = line.split(" ", maxsplit=1) + if len(parts) == 2 and parts[1][0] == "(" and parts[1][-1] == ")": + self.add_preset(parts[0], parts[1][1:-1]) + self.save_preset_uri = parts[0] + else: + logging.warning(f"Wrong preset format when parsing jalv output => {line}") + + def start_proc_poll_thread(self): + self.proc_poll_thread = Thread(target=self.proc_poll_thread_task, args=()) + self.proc_poll_thread.name = f"proc_poll_{self.jackname}" + self.proc_poll_thread.daemon = True # thread dies with the program + self.proc_poll_thread.start() + # --------------------------------------------------------------------------- # Processor Management # --------------------------------------------------------------------------- @@ -308,7 +458,11 @@ def set_midi_chan(self, processor): def get_bank_list(self, processor=None): bank_list = [] for bank_label, info in self.preset_info.items(): - bank_list.append((str(info['bank_url']), None, bank_label, None)) + if info['bank_url'] is None: + bank_uri = "" + else: + bank_uri = str(info['bank_url']) + bank_list.append((bank_uri, None, bank_label, None)) if len(bank_list) == 0: bank_list.append(("", None, "None", None)) return bank_list @@ -317,7 +471,9 @@ def set_bank(self, processor, bank): return True def get_user_bank_urid(self, bank_name): - return "file://{}/presets/lv2/{}.presets.lv2/{}".format(self.my_data_dir, zynthian_engine_jalv.sanitize_text(self.plugin_name), zynthian_engine_jalv.sanitize_text(bank_name)) + return "file://{}/presets/lv2/{}.presets.lv2/{}".format(self.my_data_dir, + zynthian_engine_jalv.sanitize_text(self.plugin_name), + zynthian_engine_jalv.sanitize_text(bank_name)) def create_user_bank(self, bank_name): bundle_path = "{}/presets/lv2/{}.presets.lv2".format( @@ -395,24 +551,7 @@ def get_preset_list(self, bank): def set_preset(self, processor, preset, preload=False): if not preset[0]: return - output = self.proc_cmd("preset {}".format(preset[0])) - - # Parse new controller values - for line in output.split("\n"): - try: - parts = line.split(" = ") - if len(parts) == 2: - try: - val = float(parts[1]) - except Exception as e: - logging.warning(f"Wrong parameter value when loading LV2 preset => {line}") - continue - self.lv2_zctrl_dict[parts[0]]._set_value(val) - except Exception as e: - # TODO This shouldn't happen when property parameters are fully implemented - #logging.warning(f"Unknown parameter when loading LV2 preset => {line}") - pass - + self.proc_cmd(f"preset {preset[0]}") return True def cmp_presets(self, preset1, preset2): @@ -425,7 +564,7 @@ def cmp_presets(self, preset1, preset2): return False def is_preset_user(self, preset): - return isinstance(preset[0], str) and preset[0].startswith("file://{}/presets/lv2/".format(self.my_data_dir)) + return isinstance(preset[0], str) and preset[0].startswith(f"file://{self.my_data_dir}/presets/lv2/") def preset_exists(self, bank, preset_name): # TODO: This would be more robust using URI but that is created dynamically by save_preset() @@ -440,58 +579,68 @@ def preset_exists(self, bank, preset_name): return False def save_preset(self, bank, preset_name): - # Save preset (jalv) if not bank: - bank = ["", None, "None", None] - res = self.proc_cmd("save preset %s,%s" % - (bank[0], preset_name)).split("\n") - - if res[-1].startswith("ERROR"): - logging.error("Can't save preset => {}".format(res)) + self.save_bank = ["", None, "None", None] else: - preset_uri = res[-1].strip() - logging.info("Saved preset '{}' => {}".format( - preset_name, preset_uri)) + self.save_bank = bank - # Add to cache - try: - # Add bank if needed - if bank[2] not in self.preset_info: - self.preset_info[bank[2]] = { - 'bank_url': bank[0], - 'presets': [] - } - # Add preset - if not self.preset_exists(bank, preset_name): - self.preset_info[bank[2]]['presets'].append( - {'label': preset_name, "url": preset_uri}) - # Save presets cache - zynthian_lv2.save_plugin_presets_cache( - self.plugin_name, self.preset_info) - # Return preset uri - return preset_uri - except Exception as e: - logging.error(e) + # Reset save_uri + self.save_preset_uri = None + # Send "save preset" command to jalv + if self.save_bank[0]: + cmd = f"save preset {self.save_bank[0]},{preset_name}" + else: + cmd = f"save preset {preset_name}" + #logging.debug(f"SAVE PRESET COMMAND => {cmd}") + self.proc_cmd(cmd) + # Wait for save preset feedback + i = 0 + while i < 20 and self.save_preset_uri == None: + sleep(0.1) + i += 1 + return self.save_preset_uri + + def add_preset(self, preset_uri, preset_name): + logging.info(f"Add preset '{preset_name}' => {preset_uri}") + # Add to cache + try: + # Add bank if needed + if self.save_bank[2] not in self.preset_info: + self.preset_info[self.save_bank[2]] = { + 'bank_url': self.save_bank[0], + 'presets': [] + } + # Add preset + if not self.preset_exists(self.save_bank, preset_name): + self.preset_info[self.save_bank[2]]['presets'].append( + {'label': preset_name, + "url": preset_uri}) + # Save presets cache + zynthian_lv2.save_plugin_presets_cache(self.plugin_name, self.preset_info) + # If added, return true + return True + except Exception as e: + logging.error(e) + return False def delete_preset(self, bank, preset): if self.is_preset_user(preset): try: # Remove from LV2 ttl zynthian_engine_jalv.lv2_remove_preset(preset[0]) - # Remove from cache for i, p in enumerate(self.preset_info[bank[2]]['presets']): if p['url'] == preset[0]: del self.preset_info[bank[2]]['presets'][i] - zynthian_lv2.save_plugin_presets_cache( - self.plugin_name, self.preset_info) + zynthian_lv2.save_plugin_presets_cache(self.plugin_name, self.preset_info) break - except Exception as e: logging.error(e) try: - return len(self.preset_info[bank[2]]['presets']) + n = len(self.preset_info[bank[2]]['presets']) + if n > 0: + return n except Exception as e: pass zynthian_engine_jalv.lv2_remove_bank(bank) @@ -501,18 +650,13 @@ def rename_preset(self, bank, preset, new_preset_name): if self.is_preset_user(preset): try: # Update LV2 ttl - zynthian_engine_jalv.lv2_rename_preset( - preset[0], new_preset_name) - + zynthian_engine_jalv.lv2_rename_preset(preset[0], new_preset_name) # Update cache for i, p in enumerate(self.preset_info[bank[2]]['presets']): if p['url'] == preset[0]: - self.preset_info[bank[2] - ]['presets'][i]['label'] = new_preset_name - zynthian_lv2.save_plugin_presets_cache( - self.plugin_name, self.preset_info) + self.preset_info[bank[2]]['presets'][i]['label'] = new_preset_name + zynthian_lv2.save_plugin_presets_cache(self.plugin_name, self.preset_info) break - except Exception as e: logging.error(e) @@ -539,7 +683,7 @@ def get_lv2_controllers_dict(self): 'name': info['name'], 'group_symbol': info['group_symbol'], 'group_name': info['group_name'], - 'graph_path': info['index'], + #'graph_path': info['index'], 'value': info['value'], 'labels': labels, 'ticks': values, @@ -564,7 +708,7 @@ def get_lv2_controllers_dict(self): 'name': info['name'], 'group_symbol': info['group_symbol'], 'group_name': info['group_name'], - 'graph_path': info['index'], + #'graph_path': info['index'], 'value': val, 'labels': ['off', 'on'], 'ticks': [int(info['range']['min']), int(info['range']['max'])], @@ -580,7 +724,7 @@ def get_lv2_controllers_dict(self): 'name': info['name'], 'group_symbol': info['group_symbol'], 'group_name': info['group_name'], - 'graph_path': info['index'], + #'graph_path': info['index'], 'value': int(info['value']), 'value_default': int(info['range']['default']), 'value_min': int(info['range']['min']), @@ -602,7 +746,7 @@ def get_lv2_controllers_dict(self): 'name': info['name'], 'group_symbol': info['group_symbol'], 'group_name': info['group_name'], - 'graph_path': info['index'], + #'graph_path': info['index'], 'value': val, 'labels': ['off', 'on'], 'ticks': [info['range']['min'], info['range']['max']], @@ -618,7 +762,7 @@ def get_lv2_controllers_dict(self): 'name': info['name'], 'group_symbol': info['group_symbol'], 'group_name': info['group_name'], - 'graph_path': info['index'], + #'graph_path': info['index'], 'value': info['value'], 'value_default': float(info['range']['default']), 'value_min': float(info['range']['min']), @@ -643,15 +787,8 @@ def get_lv2_controllers_dict(self): return zctrls def get_monitors_dict(self): - self.lv2_monitors_dict = {} - for line in self.proc_cmd("monitors").split("\n"): - try: - parts = line.split(" = ") - if len(parts) == 2: - self.lv2_monitors_dict[parts[0]] = float(parts[1]) - except Exception as e: - logging.error(e) - + self.proc_cmd("monitors") + # Return current monitor values => No wait for the asynchronous response! return self.lv2_monitors_dict def get_controllers_dict(self, processor): @@ -665,7 +802,10 @@ def get_controllers_dict(self, processor): def send_controller_value(self, zctrl): try: - self.proc_cmd("set %d %.6f" % (zctrl.graph_path, zctrl.value)) + if zctrl.graph_path is not None: + self.proc_cmd("set %d %.6f" % (zctrl.graph_path, zctrl.value)) + else: + self.proc_cmd("%s=%.6f" % (zctrl.symbol, zctrl.value)) except: if zctrl.midi_cc: lib_zyncore.zmop_send_ccontrol_change(zctrl.processor.chain.zmop_index, @@ -778,8 +918,8 @@ def zynapi_install(cls, dpath, bank_path): # Try to copy LV2 bundles ... if os.path.isdir(dpath): # Find manifest.ttl - manifest_files = check_output( - "find \"{}\" -type f -iname manifest.ttl".format(dpath), shell=True).decode("utf-8").split("\n") + manifest_files = check_output(f"find \"{dpath}\" -type f -iname manifest.ttl", + shell=True).decode("utf-8").split("\n") # Copy LV2 bundle directories to destiny ... count = 0 for f in manifest_files: @@ -787,8 +927,7 @@ def zynapi_install(cls, dpath, bank_path): head, bname = os.path.split(bpath) if bname: shutil.rmtree(zynthian_engine.my_data_dir +"/presets/lv2/" + bname, ignore_errors=True) - shutil.move( - bpath, zynthian_engine.my_data_dir + "/presets/lv2/") + shutil.move(bpath, zynthian_engine.my_data_dir + "/presets/lv2/") count += 1 if count > 0: cls.refresh_zynapi_instance() @@ -873,10 +1012,8 @@ def lv2_rename_bank(bank_path, new_bank_name): brre = re.compile(r"([\s]+rdfs:label[\s]+\").*(\" )") for i, p in enumerate(parts): if bmre1.search(p) and bmre2.search(p): - new_bank_name = zynthian_engine_jalv.sanitize_text( - new_bank_name) - parts[i] = brre.sub(lambda m: m.group( - 1)+new_bank_name+m.group(2), p) + new_bank_name = zynthian_engine_jalv.sanitize_text(new_bank_name) + parts[i] = brre.sub(lambda m: m.group(1)+new_bank_name+m.group(2), p) zynthian_engine_jalv.ttl_write_parts(man_fpath, parts) return @@ -898,10 +1035,8 @@ def lv2_rename_preset(preset_path, new_preset_name): renamed = False for i, p in enumerate(man_parts): if bmre1.search(p) and bmre2.search(p): - new_preset_name = zynthian_engine_jalv.sanitize_text( - new_preset_name) - man_parts[i] = brre.sub(lambda m: m.group( - 1) + new_preset_name + m.group(2), p) + new_preset_name = zynthian_engine_jalv.sanitize_text(new_preset_name) + man_parts[i] = brre.sub(lambda m: m.group(1) + new_preset_name + m.group(2), p) zynthian_engine_jalv.ttl_write_parts(man_fpath, man_parts) renamed = True # TODO: This overrides subsequent assertion in prs_parts break @@ -909,8 +1044,7 @@ def lv2_rename_preset(preset_path, new_preset_name): for i, p in enumerate(prs_parts): if bmre2.search(p): # new_preset_name = zynthian_engine_jalv.sanitize_text(new_preset_name) - prs_parts[i] = brre.sub(lambda m: m.group( - 1) + new_preset_name + m.group(2), p) + prs_parts[i] = brre.sub(lambda m: m.group(1) + new_preset_name + m.group(2), p) zynthian_engine_jalv.ttl_write_parts(preset_path, prs_parts) renamed = True break @@ -920,6 +1054,7 @@ def lv2_rename_preset(preset_path, new_preset_name): @staticmethod def lv2_remove_preset(preset_path): + logging.debug(f"Removing LV2 preset '{preset_path}'") preset_path = preset_path[7:] bundle_path, preset_fname = os.path.split(preset_path) @@ -933,13 +1068,15 @@ def lv2_remove_preset(preset_path): del parts[i] zynthian_engine_jalv.ttl_write_parts(man_fpath, parts) os.remove(preset_path) - return + return True + return False @staticmethod # Remove a preset bank # bank: Bank object to remove # Returns: True on success def lv2_remove_bank(bank): + logging.debug(f"Removing LV2 bank '{bank[0]}'") try: path = bank[0][7:bank[0].rfind("/")] except Exception as e: From 0e509b515a2da079219888351eb36087988f0bc6 Mon Sep 17 00:00:00 2001 From: jofemodo Date: Wed, 18 Dec 2024 19:20:40 +0100 Subject: [PATCH 095/117] Increase a little bit the efficiency of "remove_midi_learn()". It's called a lot when recalling ZS3. --- zyngine/zynthian_chain_manager.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/zyngine/zynthian_chain_manager.py b/zyngine/zynthian_chain_manager.py index a3691103d..f7980b9b1 100644 --- a/zyngine/zynthian_chain_manager.py +++ b/zyngine/zynthian_chain_manager.py @@ -1276,20 +1276,26 @@ def remove_midi_learn(self, proc, symbol): logging.debug(f"(symbol={symbol} => zctrl={zctrl.symbol})") for key in list(self.absolute_midi_cc_binding): zctrls = self.absolute_midi_cc_binding[key] - if zctrl in zctrls: + try: zctrls.remove(zctrl) + except: + pass if not zctrls: self.absolute_midi_cc_binding.pop(key) for key in list(self.chan_midi_cc_binding): zctrls = self.chan_midi_cc_binding[key] - if zctrl in zctrls: + try: zctrls.remove(zctrl) + except: + pass if not zctrls: self.chan_midi_cc_binding.pop(key) for key in list(self.chain_midi_cc_binding): zctrls = self.chain_midi_cc_binding[key] - if zctrl in zctrls: + try: zctrls.remove(zctrl) + except: + pass if not zctrls: self.chain_midi_cc_binding.pop(key) From 18fb1839afe09044425fcdfc4a39a204c41c25de Mon Sep 17 00:00:00 2001 From: jofemodo Date: Wed, 18 Dec 2024 19:20:40 +0100 Subject: [PATCH 096/117] Increase a little bit the efficiency of "remove_midi_learn()". It's called a lot when recalling ZS3. --- zyngine/zynthian_chain_manager.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/zyngine/zynthian_chain_manager.py b/zyngine/zynthian_chain_manager.py index a3691103d..f7980b9b1 100644 --- a/zyngine/zynthian_chain_manager.py +++ b/zyngine/zynthian_chain_manager.py @@ -1276,20 +1276,26 @@ def remove_midi_learn(self, proc, symbol): logging.debug(f"(symbol={symbol} => zctrl={zctrl.symbol})") for key in list(self.absolute_midi_cc_binding): zctrls = self.absolute_midi_cc_binding[key] - if zctrl in zctrls: + try: zctrls.remove(zctrl) + except: + pass if not zctrls: self.absolute_midi_cc_binding.pop(key) for key in list(self.chan_midi_cc_binding): zctrls = self.chan_midi_cc_binding[key] - if zctrl in zctrls: + try: zctrls.remove(zctrl) + except: + pass if not zctrls: self.chan_midi_cc_binding.pop(key) for key in list(self.chain_midi_cc_binding): zctrls = self.chain_midi_cc_binding[key] - if zctrl in zctrls: + try: zctrls.remove(zctrl) + except: + pass if not zctrls: self.chain_midi_cc_binding.pop(key) From 20aa70d351dda17f66e3822ce8f2ef5cda24785d Mon Sep 17 00:00:00 2001 From: jofemodo Date: Wed, 18 Dec 2024 20:37:00 +0100 Subject: [PATCH 097/117] Extract LV2 patch properties from plugin and add them to the controllers dictionary (POC => initial implementation). --- zyngine/zynthian_lv2.py | 62 +++++++++++++++++++++++++++++++++++++---- 1 file changed, 56 insertions(+), 6 deletions(-) diff --git a/zyngine/zynthian_lv2.py b/zyngine/zynthian_lv2.py index 6dde6afba..9f9085966 100755 --- a/zyngine/zynthian_lv2.py +++ b/zyngine/zynthian_lv2.py @@ -196,12 +196,11 @@ def init_lilv(): # world.set_option(lilv.OPTION_FILTER_LANG, world.new_bool(False)) world.load_all() world.ns.ev = lilv.Namespace(world, "http://lv2plug.in/ns/ext/event#") - world.ns.presets = lilv.Namespace( - world, "http://lv2plug.in/ns/ext/presets#") - world.ns.portprops = lilv.Namespace( - world, "http://lv2plug.in/ns/ext/port-props#") - world.ns.portgroups = lilv.Namespace( - world, "http://lv2plug.in/ns/ext/port-groups#") + world.ns.presets = lilv.Namespace(world, "http://lv2plug.in/ns/ext/presets#") + world.ns.portprops = lilv.Namespace(world, "http://lv2plug.in/ns/ext/port-props#") + world.ns.portgroups = lilv.Namespace(world, "http://lv2plug.in/ns/ext/port-groups#") + world.ns.parameters = lilv.Namespace(world, "http://lv2plug.in/ns/ext/parameters#") + world.ns.patch = lilv.Namespace(world, "http://lv2plug.in/ns/ext/patch#") # ------------------------------------------------------------------------------ @@ -854,6 +853,57 @@ def get_plugin_ports(plugin_url): ports_info[i] = info # logging.debug("PORT {} => {}".format(i, info)) + # Property parameters + i = 0 + for node in world.find_nodes(plugin.get_uri(), world.ns.patch.writable, None): + try: + symbol = str(world.get_symbol(node)) + name = str(world.get(node, world.ns.rdfs.label, None)) + range_type = str(world.get(node, world.ns.rdfs.range, None)) + vdef = get_node_value(world.get(node, world.ns.lv2.default, None)) + vmin = get_node_value(world.get(node, world.ns.lv2.minimum, None)) + vmax = get_node_value(world.get(node, world.ns.lv2.maximum, None)) + + group_index = None + group_name = None + group_symbol = None + + is_toggled = False + is_integer = False + is_enumeration = False + is_logarithmic = False + envelope = None + not_on_gui = False + display_priority = 0 + sp = [] + + info = { + 'index': i, + 'symbol': symbol, + 'name': name, + 'group_index': group_index, + 'group_name': group_name, + 'group_symbol': group_symbol, + 'value': vdef, + 'range': { + 'default': vdef, + 'min': vmin, + 'max': vmax + }, + 'is_toggled': is_toggled, + 'is_integer': is_integer, + 'is_enumeration': is_enumeration, + 'is_logarithmic': is_logarithmic, + 'envelope': envelope, + 'not_on_gui': not_on_gui, + 'display_priority': display_priority, + 'scale_points': sp + } + ports_info[i] = info + i += 1 + except Exception as e: + logging.error(e) + return ports_info From 09c7e8edcad6e7a350a0f9196eb9b450428f0c48 Mon Sep 17 00:00:00 2001 From: riban Date: Thu, 19 Dec 2024 06:21:44 +0000 Subject: [PATCH 098/117] Fixes control view select mode. Disable controllers when in page select mode to avoid unexpected navigation when touched. This also changes their appearence to better reflect their (temporarily) disabled state. --- zyngui/zynthian_gui_control.py | 21 ++++++++++----------- zyngui/zynthian_gui_controller.py | 12 ++++++++++-- 2 files changed, 20 insertions(+), 13 deletions(-) diff --git a/zyngui/zynthian_gui_control.py b/zyngui/zynthian_gui_control.py index 9ac3e7a37..7d3a7d8c7 100644 --- a/zyngui/zynthian_gui_control.py +++ b/zyngui/zynthian_gui_control.py @@ -5,7 +5,7 @@ # # Zynthian GUI Instrument-Control Class # -# Copyright (C) 2015-2023 Fernando Moyano +# Copyright (C) 2015-2024 Fernando Moyano # # ****************************************************************************** # @@ -106,6 +106,7 @@ def build_view(self): self.click_listbox() elif pending_click_listbox: self.click_listbox() + self.set_mode_control() return True def hide(self): @@ -373,7 +374,7 @@ def get_zcontroller(self, i): def set_selector_screen(self): for i in range(0, len(self.zgui_controllers)): - self.zgui_controllers[i].set_hl(zynthian_gui_config.color_ctrl_bg_off) + self.zgui_controllers[i].enable(False) self.set_selector() def set_mode_select(self): @@ -386,7 +387,7 @@ def set_mode_select(self): fg=zynthian_gui_config.color_ctrl_tx_off) self.select(self.index) self.set_select_path() - zynsigman.send_queued(zynsigman.S_GUI, self.SS_GUI_CONTROL_MODE, mode=self.mode) + self.set_button_status(2, True) def set_mode_control(self): self.mode = 'control' @@ -397,9 +398,9 @@ def set_mode_control(self): selectforeground=zynthian_gui_config.color_ctrl_tx, fg=zynthian_gui_config.color_ctrl_tx) for i in range(0, len(self.zgui_controllers)): - self.zgui_controllers[i].unset_hl() + self.zgui_controllers[i].enable() self.set_select_path() - #zynsigman.send_queued(zynsigman.S_GUI, self.SS_GUI_CONTROL_MODE, mode=self.mode) + self.set_button_status(2, False) def previous_page(self, wrap=False): i = self.index - 1 @@ -418,9 +419,6 @@ def next_page(self, wrap=False): self.select(i) self.click_listbox() - def select_action(self, i, t='S'): - self.set_mode_control() - def back_action(self): if self.mode == 'select': self.set_mode_control() @@ -498,6 +496,7 @@ def switch_select(self, t): self.next_page(True) elif self.mode == 'select': self.click_listbox() + self.set_mode_control() elif t == 'B': self.zyngui.cuia_chain_options() @@ -505,9 +504,9 @@ def switch_select(self, t): def select(self, index=None, set_zctrl=True): super().select(index, set_zctrl) - if self.mode == 'select': - self.set_controller_screen() - self.set_selector_screen() + #if self.mode == 'select': + self.set_controller_screen() + #self.set_selector_screen() def zynpot_cb(self, i, dval): if self.mode == 'control' and self.zcontrollers: diff --git a/zyngui/zynthian_gui_controller.py b/zyngui/zynthian_gui_controller.py index 49238f250..159346032 100644 --- a/zyngui/zynthian_gui_controller.py +++ b/zyngui/zynthian_gui_controller.py @@ -79,6 +79,7 @@ def __init__(self, index, parent, zctrl, hidden=False, selcounter=False, graph=z self.title_width = 1 self.index = index + self.enabled = True # Create Canvas if not hidden: @@ -298,6 +299,13 @@ def restore_color_graph(self): def set_color_readonly(self): self.itemconfig(self.graph, outline=zynthian_gui_config.color_ctrl_bg_off) + def enable(self, enable=True): + self.enabled = enable + if enable: + self.unset_hl() + else: + self.set_hl(zynthian_gui_config.color_ctrl_bg_off) + def set_hl(self, color=zynthian_gui_config.color_hl): try: self.color_graph = color @@ -603,7 +611,7 @@ def zynpot_cb(self, dval): def nudge(self, dval, fine=False): if self.preselection is not None: self.zyngui.screens["control"].zctrl_touch(self.preselection) - if self.zctrl: + elif self.enabled and self.zctrl: return self.zctrl.nudge(dval, fine=fine) else: return False @@ -621,7 +629,7 @@ def cb_canvas_push(self, event): #logging.debug(f"CONTROL {self.index} PUSH => {self.canvas_push_ts} ({self.canvas_motion_x0},{self.canvas_motion_y0})") def cb_canvas_release(self, event): - if self.canvas_push_ts: + if self.canvas_push_ts and self.enabled: dts = (datetime.now()-self.canvas_push_ts).total_seconds() self.canvas_push_ts = None #logging.debug(f"CONTROL {self.index} RELEASE => {dts}, {motion_rate}") From 68379c490297157e640dc802ad35410d85e82f46 Mon Sep 17 00:00:00 2001 From: riban Date: Thu, 19 Dec 2024 06:58:19 +0000 Subject: [PATCH 099/117] Revert breaking change echo=False in pexpect.spawn --- zyngine/zynthian_engine.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zyngine/zynthian_engine.py b/zyngine/zynthian_engine.py index 1a2494f6f..1f6d197f0 100644 --- a/zyngine/zynthian_engine.py +++ b/zyngine/zynthian_engine.py @@ -91,7 +91,7 @@ def start(self): # Setting cwd is because we've set PWD above. Some engines doesn't # care about the process's cwd, but it is more consistent to set # cwd when PWD has been set. - self.proc = pexpect.spawn(self.command, echo=False, timeout=self.proc_timeout, + self.proc = pexpect.spawn(self.command, timeout=self.proc_timeout, env=self.command_env, cwd=self.command_cwd) self.proc.delaybeforesend = 0 output = self.proc_get_output() From a57560e0aa989eb1718913f3a97fc125e49edf3b Mon Sep 17 00:00:00 2001 From: jofemodo Date: Fri, 20 Dec 2024 08:45:48 +0100 Subject: [PATCH 100/117] Implement groups, scalepoints and designated parameters in property parameters. --- zyngine/zynthian_lv2.py | 249 +++++++++++++++++++++++----------------- 1 file changed, 146 insertions(+), 103 deletions(-) diff --git a/zyngine/zynthian_lv2.py b/zyngine/zynthian_lv2.py index 9f9085966..879541ce5 100755 --- a/zyngine/zynthian_lv2.py +++ b/zyngine/zynthian_lv2.py @@ -746,69 +746,89 @@ def sanitize_fname(s): # LV2 Port management # ------------------------------------------------------------------------------ +def get_node_value(node): + if node is None: + return None + elif node.is_int(): + return int(node) + elif node.is_float(): + return float(node) + elif node.is_bool(): + return bool(node) + else: + return str(node) + def get_plugin_ports(plugin_url): wplugins = world.get_all_plugins() plugin = wplugins[plugin_url] ports_info = {} + # Control ports for i in range(plugin.get_num_ports()): - port = plugin.get_port_by_index(i) - if port.is_a(lilv.LILV_URI_INPUT_PORT) and port.is_a(lilv.LILV_URI_CONTROL_PORT): - port_name = str(port.get_name()) - port_symbol = str(port.get_symbol()) - - is_toggled = port.has_property(world.ns.lv2.toggled) - is_integer = port.has_property(world.ns.lv2.integer) - is_enumeration = port.has_property(world.ns.lv2.enumeration) - is_logarithmic = port.has_property(world.ns.portprops.logarithmic) + control = plugin.get_port_by_index(i) + if control.is_a(lilv.LILV_URI_INPUT_PORT) and control.is_a(lilv.LILV_URI_CONTROL_PORT): + name = str(control.get_name()) + symbol = str(control.get_symbol()) + + is_toggled = control.has_property(world.ns.lv2.toggled) + is_integer = control.has_property(world.ns.lv2.integer) + is_enumeration = control.has_property(world.ns.lv2.enumeration) + is_logarithmic = control.has_property(world.ns.portprops.logarithmic) + envelope = None for env_type in ["delay", "attack", "hold", "decay", "sustain", "fade", "release"]: - if str(port.get("http://lv2plug.in/ns/lv2core#designation")) == f"http://lv2plug.in/ns/ext/parameters#{env_type}": + # "http://lv2plug.in/ns/lv2core#designation" + if str(control.get(world.ns.lv2.designation)) == f"http://lv2plug.in/ns/ext/parameters#{env_type}": envelope = env_type - not_on_gui = port.has_property(world.ns.portprops.notOnGUI) - display_priority = port.get(world.ns.lv2.displayPriority) + + not_on_gui = control.has_property(world.ns.portprops.notOnGUI) + display_priority = control.get(world.ns.lv2.displayPriority) if display_priority is None: display_priority = 0 else: display_priority = int(display_priority) - # logging.debug("PORT {} properties =>".format(port.get_symbol())) - # for node in port.get_properties(): + # logging.debug("CONTROL {} properties =>".format(control.get_symbol())) + # for node in control.get_properties(): # logging.debug(" => {}".format(get_node_value(node))) - pgroup_index = None - pgroup_name = None - pgroup_symbol = None - pgroup = port.get(world.ns.portgroups.group) - if pgroup is not None: - # pgroup_key = str(pgroup).split("#")[-1] - pgroup_index = world.get(pgroup, world.ns.lv2.index, None) - if pgroup_index is not None: - pgroup_index = int(pgroup_index) - # logging.warning("Port group <{}> has no index.".format(pgroup_key)) - pgroup_name = world.get(pgroup, world.ns.lv2.name, None) - if pgroup_name is None: - pgroup_name = world.get(pgroup, world.ns.rdfs.label, None) - if pgroup_name is not None: - pgroup_name = str(pgroup_name) - # logging.warning("Port group <{}> has no name.".format(pgroup_key)) - pgroup_symbol = world.get(pgroup, world.ns.lv2.symbol, None) - if pgroup_symbol is not None: - pgroup_symbol = str(pgroup_symbol) - # logging.warning("Port group <{}> has no symbol.".format(pgroup_key)) + group_index = None + group_name = None + group_symbol = None + group = control.get(world.ns.portgroups.group) + if group is not None: + # group_key = str(group).split("#")[-1] + group_index = world.get(group, world.ns.lv2.index, None) + if group_index is not None: + group_index = int(group_index) + # logging.warning("Control group <{}> has no index.".format(group_key)) + group_name = world.get(group, world.ns.lv2.name, None) + if group_name is None: + group_name = world.get(group, world.ns.rdfs.label, None) + if group_name is not None: + group_name = str(group_name) + # logging.warning("Control group <{}> has no name.".format(group_key)) + group_symbol = world.get(group, world.ns.lv2.symbol, None) + if group_symbol is not None: + group_symbol = str(group_symbol) + # logging.warning("Control group <{}> has no symbol.".format(group_key)) # else: - # logging.debug("Port <{}> has no group.".format(port_symbol)) + # logging.debug("Control <{}> has no group.".format(symbol)) sp = [] - for p in port.get_scale_points(): + for p in control.get_scale_points(): sp.append({ 'label': str(p.get_label()), 'value': get_node_value(p.get_value()) }) sp = sorted(sp, key=lambda k: k['value']) - r = port.get_range() + r = control.get_range() + try: + vdef = get_node_value(r[0]) + except: + vdef = vmin try: vmin = get_node_value(r[1]) except: @@ -823,61 +843,8 @@ def get_plugin_ports(plugin_url): vmax = max(sp, key=lambda x: x['value']) else: vmax = 1.0 - try: - vdef = get_node_value(r[0]) - except: - vdef = vmin - info = { - 'index': i, - 'symbol': port_symbol, - 'name': port_name, - 'group_index': pgroup_index, - 'group_name': pgroup_name, - 'group_symbol': pgroup_symbol, - 'value': vdef, - 'range': { - 'default': vdef, - 'min': vmin, - 'max': vmax - }, - 'is_toggled': is_toggled, - 'is_integer': is_integer, - 'is_enumeration': is_enumeration, - 'is_logarithmic': is_logarithmic, - 'envelope': envelope, - 'not_on_gui': not_on_gui, - 'display_priority': display_priority, - 'scale_points': sp - } - ports_info[i] = info - # logging.debug("PORT {} => {}".format(i, info)) - - # Property parameters - i = 0 - for node in world.find_nodes(plugin.get_uri(), world.ns.patch.writable, None): - try: - symbol = str(world.get_symbol(node)) - name = str(world.get(node, world.ns.rdfs.label, None)) - range_type = str(world.get(node, world.ns.rdfs.range, None)) - vdef = get_node_value(world.get(node, world.ns.lv2.default, None)) - vmin = get_node_value(world.get(node, world.ns.lv2.minimum, None)) - vmax = get_node_value(world.get(node, world.ns.lv2.maximum, None)) - - group_index = None - group_name = None - group_symbol = None - - is_toggled = False - is_integer = False - is_enumeration = False - is_logarithmic = False - envelope = None - not_on_gui = False - display_priority = 0 - sp = [] - - info = { + ports_info[i] = { 'index': i, 'symbol': symbol, 'name': name, @@ -899,21 +866,97 @@ def get_plugin_ports(plugin_url): 'display_priority': display_priority, 'scale_points': sp } - ports_info[i] = info - i += 1 - except Exception as e: - logging.error(e) + # logging.debug("PORT {} => {}".format(i, info)) - return ports_info + # Property parameters + i = len(ports_info) + for control in world.find_nodes(plugin.get_uri(), world.ns.patch.writable, None): + symbol = world.get_symbol(control) + name = str(world.get(control, world.ns.rdfs.label, None)) + + range_type = str(world.get(control, world.ns.rdfs.range, None)) + vdef = get_node_value(world.get(control, world.ns.lv2.default, None)) + vmin = get_node_value(world.get(control, world.ns.lv2.minimum, None)) + vmax = get_node_value(world.get(control, world.ns.lv2.maximum, None)) + + is_toggled = world.get(control, world.ns.lv2.toggled, None) is not None + is_integer = world.get(control, world.ns.lv2.integer, None) is not None + is_enumeration = world.get(control, world.ns.lv2.enumeration, None) is not None + is_logarithmic = world.get(control, world.ns.portprops.logarithmic, None) is not None + + envelope = None + for env_type in ["delay", "attack", "hold", "decay", "sustain", "fade", "release"]: + if str(world.get(control, world.ns.lv2.designation, None)) == f"http://lv2plug.in/ns/ext/parameters#{env_type}": + envelope = env_type + + not_on_gui = world.get(control, world.ns.portprops.notOnGUI, None) is not None + display_priority = world.get(control, world.ns.lv2.displayPriority, None) + if display_priority is None: + display_priority = 0 + else: + display_priority = int(display_priority) + + # logging.debug("CONTROL {} properties =>".format(control.get_symbol())) + # for node in control.get_properties(): + # logging.debug(" => {}".format(get_node_value(node))) + + group_index = None + group_name = None + group_symbol = None + group = world.get(control, world.ns.portgroups.group, None) + if group is not None: + # group_key = str(group).split("#")[-1] + group_index = world.get(group, world.ns.lv2.index, None) + if group_index is not None: + group_index = int(group_index) + # logging.warning("Control group <{}> has no index.".format(group_key)) + group_name = world.get(group, world.ns.lv2.name, None) + if group_name is None: + group_name = world.get(group, world.ns.rdfs.label, None) + if group_name is not None: + group_name = str(group_name) + # logging.warning("Control group <{}> has no name.".format(group_key)) + group_symbol = world.get(group, world.ns.lv2.symbol, None) + if group_symbol is not None: + group_symbol = str(group_symbol) + # logging.warning("Control group <{}> has no symbol.".format(group_key)) + # else: + # logging.debug("Control <{}> has no group.".format(symbol)) + + sp = [] + for p in world.find_nodes(control, world.ns.lv2.scalePoint, None): + sp.append({ + 'label': str(world.get(p, world.ns.rdfs.label, None)), + 'value': get_node_value(world.get(p, world.ns.rdf.value, None)) + }) + sp = sorted(sp, key=lambda k: k['value']) + + ports_info[i] = { + 'index': i, + 'symbol': symbol, + 'name': name, + 'group_index': group_index, + 'group_name': group_name, + 'group_symbol': group_symbol, + 'value': vdef, + 'range': { + 'default': vdef, + 'min': vmin, + 'max': vmax + }, + 'is_toggled': is_toggled, + 'is_integer': is_integer, + 'is_enumeration': is_enumeration, + 'is_logarithmic': is_logarithmic, + 'envelope': envelope, + 'not_on_gui': not_on_gui, + 'display_priority': display_priority, + 'scale_points': sp + } + i += 1 + return ports_info -def get_node_value(node): - if node.is_int(): - return int(node) - elif node.is_float(): - return float(node) - else: - return str(node) # ------------------------------------------------------------------------------ # Main program From fe64d619e1116feb6e08d986c5395d12ab71366b Mon Sep 17 00:00:00 2001 From: jofemodo Date: Fri, 20 Dec 2024 17:48:08 +0100 Subject: [PATCH 101/117] Use "jalv_asyncli" version until it's merged in stable. --- zyngine/zynthian_engine_jalv.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/zyngine/zynthian_engine_jalv.py b/zyngine/zynthian_engine_jalv.py index 8be22efa8..3bca19454 100644 --- a/zyngine/zynthian_engine_jalv.py +++ b/zyngine/zynthian_engine_jalv.py @@ -208,8 +208,8 @@ def __init__(self, eng_code, state_manager, dryrun=False, jackname=None): if not self.plugin_name.endswith("v1"): self.command_env['DISPLAY'] = "X" - # test jalv development => - self.command[0] = "/zynthian/zynthian-sw/jalv/build/" + self.command[0] + # Use jalv_asyncli (development version) => + self.command[0] = "/zynthian/zynthian-sw/jalv_asyncli/build/" + self.command[0] self.command_prompt = ">" From e47a2e888cbec0e4ad89ec597ecbff2e17d088df Mon Sep 17 00:00:00 2001 From: riban Date: Sat, 21 Dec 2024 07:16:49 +0000 Subject: [PATCH 102/117] Adds icons --- icons/audio_recorder.png | Bin 0 -> 15254 bytes icons/effects_loop.png | Bin 0 -> 10373 bytes icons/meters.png | Bin 0 -> 10433 bytes icons/metronome.png | Bin 2510 -> 12077 bytes icons/midi_recorder.png | Bin 0 -> 14006 bytes icons/mixer.png | Bin 0 -> 7732 bytes icons/panic.png | Bin 0 -> 12442 bytes icons/sequencer.png | Bin 0 -> 7925 bytes icons/settings.png | Bin 0 -> 11781 bytes icons/snapshot.png | Bin 0 -> 15759 bytes zyngui/zynthian_gui_main_menu.py | 18 +++++++++--------- 11 files changed, 9 insertions(+), 9 deletions(-) create mode 100644 icons/audio_recorder.png create mode 100644 icons/effects_loop.png create mode 100644 icons/meters.png create mode 100644 icons/midi_recorder.png create mode 100644 icons/mixer.png create mode 100644 icons/panic.png create mode 100644 icons/sequencer.png create mode 100644 icons/settings.png create mode 100644 icons/snapshot.png diff --git a/icons/audio_recorder.png b/icons/audio_recorder.png new file mode 100644 index 0000000000000000000000000000000000000000..932fd490ef6ed8f5161b94a7627f813b9d781dd8 GIT binary patch literal 15254 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 literal 0 HcmV?d00001 diff --git a/icons/effects_loop.png b/icons/effects_loop.png new file mode 100644 index 0000000000000000000000000000000000000000..d7fba7dcb27507ff692240a7197c13a944ccdcc9 GIT binary patch literal 10373 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 b|7IJgp?5qy?mhs{6%svyX)0GLT88`&oUV7P literal 0 HcmV?d00001 diff --git a/icons/meters.png b/icons/meters.png new file mode 100644 index 0000000000000000000000000000000000000000..b63a3ce0cee1db24f4fe7c316ff69c7b2b11f956 GIT binary patch literal 10433 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 literal 0 HcmV?d00001 diff --git a/icons/metronome.png b/icons/metronome.png index 349b03b680e2718262ad91531a16030cca6fa4e8..4803c541a105f246b74ef341cae7f43306eac82e 100644 GIT binary patch literal 12077 zcmd^ld+3>|~CgbYJSN_Pl@lz^g?q<}O?2uKJcf{cR542l9uiAXm{$^cSI z7<89_v>>I#zIdMBK4)5VxT5KQ#+ zLxBMQ3BDH}0RF)IO>{J&;$F_55OfaG!(6<6*LKZ@a=7Z+fbDw|6PYV|>;#0~`6#J& zL3V=VwS6sqf~B5*R)T(10uOsu=PR1Lr@sv_na_S2+|uRHb*(vRwBC60!*_B|by4we z@b2Vd;XRwFAAXZAB>@|u>KjMv6+@FA7aJ7ey;$ih6mTKZcdd62uV5}3)}@F1K8RQM z9oT%Dg`M*`bET(gYU~e`r)d=6Z5O5^O}novUU4C^`!sI_fp5g)n?$mLmwA2}fY5a0 zj}7om!_ebl=DdGA#Lm<8g&9 z=Wf$Y|Bif^Kz5xFQlwWRZ@Oj;1saP`zoMJ{c^VqO%@X=!>pq|Dl!mO$zA%(y_5A{n zWmlVd;9sbJOiG=4+BRkRrslIvd0zdR+yl=p2L6V!3|=RgEQWH5DJx2z?ZW$Lp_zgT zXz^T*6Nu;x)~I^|Q?N^8-DEayIKUr(MXUnKt&H;~EAbD=Om72rNLu_%zof zutv@+YtK8t_w*sTp3A&^U2uV}WK?<9#R>uJ#*h2GS1-2%r{mo~bnQiFQ3;-{ae)t) zGVG5uql1%ht{?XDy~w7|o|5~}BK(W%Tx}|j)47xfZXd(gL4Z3GbYeSWXp$yiVUpyY z@i+YtVbP|YV;8r!QRVONheFsRs{83&mkH@eyfKG8IegzDP=hO>q*OnQ99VaSSAReJ z!M(+V^V_&nY6}ef{om~DBrS|6uC{pj6BpP3wL@i%tuUlz2aVG;$c0J06aznZV2do{ zUy*=Hy-^D5)y9ZJk(=q5x8ZT~CyzSI5gqP;>`}1~zF#xD@iE?r{5~pcBl*}o_1@xg z`p=US1}saIR~0xuWgvpz+&l;LxoWxp34ITidhiQVpd&UrDQ$>x)zpfFXgG0~mG|K1 zIi>Vv;?yzJZ(i_Z{w!vmwY}>#RM~L!#Zbch@7v#*ZmVv<(pt780~qTS9w$cn}xNY1H(V&3BkF80l1ZGmFsMk`H6cs%9chiT#OTy_mPG zv4L{XfO-C)j$~xy+l}K0_m6I5SrIf^0(w1vHdj(;jo~shc{wH2W@yxyjFulSvg}qa zH?%n`cW=;(zBJ~ow%(fUi*+5hUqe54OYpG>Uxfmc2!~KHRp8r+ze5HURndDvEJQdhTF#Q z;iiBNTFxlbH^uXw$$zR~*8Rl(Gd3-cy*f%Al)wFMq9=wKt_3%}1I5skXm!!g1F85` z<46ws#6QY*m}#V8&h2b> z9oUzqg^{WjGYdOfyBRf4-EnGSrlBr>UQEc2Pl;EWV*zFIwcozYk1xBPT?p}8D12UQ`wgUm4Nfu~{-IT4`p{#g_rNkj*< z+Q?59RO{`$P-V~6<75@EsH4A|!~RJd8cd7-UOM~QfA-5yCvrm!|q#*hF?3#M5Hpydhq?!eY+Aeux^$yW6@ClqS8Cv=pD7pBh{DG#MZ=LU-^3a zuJ_Xh_^A!PiX(8b7IwQNX6HQn+An2V&x3X5=q!I;*^`b6fl}McdB%+Hi-`8D6dz0!`Q#cK=Imwz3qH&$9Af0+KC@QkB z)R#b?_ReE7FFLj7~o~oY6oHQuV&k=2^432+qsZBz!=VH!K@M`qW zKW{>#u79qc_A0(+iWUd9d-o(eyy}b8j_DZhyoHj0`7ltc@=t}?{o+x1Xvwl5?a4d~ z%I_0eeQ-`N#p48Tz4-2qHITH=9>?gO?Pdt~*MeLYPgxEkP<}}>+p{16&rv5*CfPo^ z(RFyGWY@LM9XQp_Qj**HbkQJsb>$!uF^fevXr1C zl6~Ws&Q<`(+6JwQ6QJTwqa)v%`>hVOZ9|1aJJxX(>Zgv9KK}-_(=m*6K8u>bkU4->t>S=f6A?Us+*}-OnALB}!k@;5Kaz zaj?`mHo&Cayn9a#+hm!h=Z?P0_t>-^NhF+KaCmjE4%F7a=pB_lFT|`Wv|tPi{zzsZ zBHh%zStkS;@@_lv<(X!!arM5|^wLc&l4+}qdu_~G829eg{1(cu>i)hzR4r`avP3@I z`fbZmZY<~0fD9qYhIinx3PJvH=ULk1+we`t?QJjZ`McZOO}TercN;1weRPZgPJ8|< zR>LHg!`1gez74|uQhP#dzJV+0PZOl&#AY2gcDuW8XRFp$v*gB9o3LM60b^M$kkteI z{tSMO=HqvmXPTrFbD=~20^3C;LyVksgE#&3BW7kjW+`TwF1lp#5J%j)e5`NUJPED7q=rqOFNADKZ? z(qY#U51?OJ>i$*AU> z0R(>SLnxt)VZSZA8O}o|{Q-5Y#<)e;H(Hk0Vy9#(X_*-|tXe$&?xxc(Ac9gAdgW}h zI`xpYC#p#l&)U#Zr)K>i#Gl>-l!+j|iurri8mEtapV3_ZGox8-7#*fplkm6q@=SaS z)k^40)&Ym={sbd2uXbBaeE*cW8$p#`+L>yI%FH*2Qt$)gApOtfjtWThmGa_TR3Fe} zgii0rF0I}VPsI|6=xo3GTU0?zpq$clF$NX=NQFU<4Z9Pd%WXb8oua~vXM?U}gY)?p4RvsH;(#(}b5zUhEMU&|}bLN-Kw?f%< z#x*4)BIg~-3GR6dHKa}A7VN!@UBLq7WY?Ft$u?g-yP{Lo&DvFuamv)IdCiIyD^KyG z8h>xz)3wdTmK_#i&c~+RjLI9a-XXdU+=+HM!AT&d&24;F!*g7S<`&=m`wz~b%~SR{ zObvs*ud0NBQ~I=7xLawhqS;w;<_6{Ca%)DKdX3LDog9JwJvRZG$GCGcGfP zT3E7}&~{uhqv*I)T8^;Mlon3~5cgE{eO-ZMv9 zv@s*Th^FI)f@vb(XkQ?O8~$n6CNsO0E>O-$RnM@kzUh7-8&*{+>W_>rKc~mJS}g9c z8WC=TwA97~%BM$A5nc4B?^#~}DN^A(ul`*Oj&a>7V~ut?fhKd8vCuf0Ue-}-K#i98 z`1aP#gzLyZ*)TuH+)I_{u13tf{q_MK2&mB1p|d-l;M)*1~>`wxXx6X`{dA<6=P!$^bU4iZh_pGQ&=qWP!jqX`FsSuw;!!e zN}Nn?7Ig;kYW{9<2$G&ICkiKdBo_N*wY7@?U*3yV{TjK7a3rR+hA|3I&lL3gmizL;#f z9!Td9t-U3Ja;OmNCN15%ZcXUNJYr3D$yvMhX*`bPAJy^MX$O|w#2W$tyri&q=aGuU z{+2>7rU*^R#=z?NAu&C5=Hrm719l`|Dt!N&M{ZH+F`MS*c1j-$$rWR{7wpR?EnWQj zx+Z6SjWjk{LaX<`q;V3guV9>$dDIvH7W_-JLYzj(1xn_q1Pkx#1dH`4SzUinN!6ty z75Sf81Kr%KQinK7|B@`3Mr@v+-z}0&+&q%`Y4_M3UwAuZ#fO~A`nT@Zdk(0FWaU3RGOAlfWmfB7d8;Y&U{!y= zr182K8BmDxyp5|PB`TBi#%CuTeiK$Q9it)2lb6l(RiBPXl^f*B^-cO{evV}3zh)>- zaHZRt_rj`*PBfLfyap&zzdX(0a`2c!wqmum+q)l9&tQK;gy6g3+ta;JB?15bBQPs~ z8lzMz5OQQFY&RI<>7$sfykf!)vfWeMuX>`@6P4Xv26I#zUVo3?|qZ^gU;rJVWoH)n)iFAtr{@#ODg5yXQ$&oAM8SynFaoif?Dc9`}YYE@)x8_wqkxHcW}Ku{-=A6-3&os71}_uGFg@wg@rC*CT{@xN}? zP`2OM7Ef&^AT22zD!@fK$crj}mt42>h)(C0v1H!%G7az%DdZc!^>^^*QORc)mA_pcy9X`Asy=z+CoV20 zk+)WB|2FMD<;fer@;Z3)x7MnV&T4n^C`RkQZE5XSYl9h5U-a)ss=vL|UT)O3>4VhQTM!!i&FBJ1O83}0CZ#%q3Ci0i& zMc(Hye80Qq4bxnAY1r_x%2yXZCMn`2LH@Ffr$8}dUC%jPOXV#vUHPxQ*iKI8x@wvc z(bb91OVaFS0d0%JPmS~)9-O8`d(G1Fl5Yfy(m+zo^6ni!X-Y0F|{@9++AhwA+$DcvErO4cT{4L$OVei?Pirj(IjSIhlYzqRa56uPRx ztkG}V06HvN%lll*7&PYCH}HYl3?Z$xrGm5^(ijoFE_&>Y_=0%&CDPt4xk8pEq4(Q* z)boiPlwaMidumW+(?gU(QiWRa5deo`;FiI8T9F#qemgP_^`Bl$= z4Vf7%zWJmG%mzMQn{sqKcmdg_vza;WNs@}pBgRnuq$+LLFT&j+AiuT~IReqau{crZ zx}fTxQw&?WLH^D9DvjtVeiSP^ThSEW!@miTPhy~i50WabK5ws2U!VNHTQg^FiJ|>Ql4Rcpc z&qaG`^R?bOFDiz^KnK3Rxta9c0(Z_r7-7Z!dl6PjW~j`?*7Zh{?tm-qyDDrr^vUa4 z_8=GFZc|tWeia1_rB~JL5iCqevao$U23hE%?FK{U-F7XZo{ntCK3pAkREdcanwiu} z*;%Irpx<5By3iYaVvebn+zq4xeRg~KRMl%hDr&=YuE&vK<0J667;ZIp8{5wl;_}XsG_k9ZN;_lc~dhBO#l{CAh zm>^5|Gpp{Y%o{~PE=3q=r6v1ko@-+=tyg>$Pw}Q_{Y-Af~9rc zW%F3&xS7AOVZ+5GwJ|w`Tzh<274ER^p$7u{d9;1B*J?&r111cq;4iqBH~lVyEh*$}|I=TYY@Vy+yLv6%hO8w~;$A+MUb zA2*O-oohv_X;BvFbY>&`%JL5~XW|%aKg5VMp?|fcfaCHCRC7TvZZ5KoOa!zs{cxNh z8-?ZCgiNNhMsq&Tr-sw1`C;`)jMKfRYe&#;V#)h>-~r1oH%F-qT6aTa-?{6Mlny*O zljX4xqS0J;5i{we!o|SIx<1tHAKIwP&5KfIrw@gHDJh>+at2oyjx8RMZ(q1p>ADW6 zk!h5?V;cLzEJ_Huadla!DPNGtEVAe>_R23om-k?|Q07 z4!q%bLgxsw_zyYmw(oCVuCzfizq0;^Mk^~~x74f@F_bO>r+n1NRsc@lTQ2V{=g5jm zIl3Jft2%;N1-33V4y5u1cvhot7lX%J!%A|$xZ=6?5t!RiPjG7#yJ9hvk z`kXu=f%0c_u_93ja9tE*KX;&nJa{V^ArFah|Bv&!A^z!jw)bpSRD?c-o9oa$iX;TP zXL~Pz?LRg$B|pjmz`hNF5Z=E22C7s^{coW^O?=eXqRus2n}X7kDU4*^gBOd>*?Dk= zlMpX{g({tJ^G%p}_V&Z^^ZEOgG7S#hU;7a(nu#?LnjW+uTT3LQ&-p z@B4CM>F+=uuB_ql;l@C&@1!bPRtaOWFlX)cXTPkl(0Q#njb9$QZ*}?VXEch#bKMh zuJKlKm@OIPNOw)wIw9~glhe)9=VcNOYjDFo3nkFLD@x((_%5R;@~f~$$g}3^EV?d& z3V>5PKkq?d;{RKlCLvTk$LDmKt1W%)d;LXipiiXM<8)0Jr_$(?WB#uB%x4N@M zrqWYWYZG>GBM2~7c#ZMNs$Ju?aEa|iv^T#=@vfje8x{}h!vlHk; z3eI^4yx}|-P6ny#pB@l*q-;TToe~Llg8K17y`dI*N*uN8x7!CWub9`LJ7dPDH_2bE zE^Y;(w*2Oft9bP-B-#@wZ2aG=k`{P-QbH+wjkzpYQ6-dDTdZi8rr>wAuRdUdm4bXJ zAc>*mp}wlw<7QjMO7g|ML;F2AR~2aGxq=2ECFr;h@pldgv&z_EBIV3lNLn#8_v6H+ z#@MAz1eUKL{tj6fpml@A>JDH0_xQque%Hm-H@iAHe1EcPz#W+8oc*Pc{@Jt1C9Kyd z22Y`OTwX)k`Y&0v*yaAjDg_!lx}y0$2kbll!f zYyPiSGOq343#iQ^g9R;1lD1<<+0#jLvudriF=oY<^*1o$PZoGz^QszGNBOEV#@ze< zRk6l}wXj+7Y#n*mXYK4Isy?m63mk}DGW!zFC+)dX+|htm7jIthvn0dmQJeN#)0;j_ zw_el;OpIO!C7A!Ny5@+`5!L=f(&C9*)`&|e{wvn{qg4x$1L94pMKSpuJ6pC>g|D-W zu)=i)58b=|gqxnMbzDqT3#bn``(VGs(~V~=f9gz+c2nL#^Q{@B=MzyIE^)`*45#(| zqV4x$)o*Jb#LyKLKMAQRyWwI|4B~|nJSHk0P^LQ{ey~paQ&wtV^JAl*zY$vb1of!v z{MFM@tTHdts^^V=3vv1x>r%=@_}M#>0!L|f+7n{mIZUAGO*+)aezGy@(9o?@2-2byZ^``1+?x3{d7)wjCORNV z6Eu_nBMd#l8IHea#EP6v+4hEmg%8w7UcyT&Gd{S_i~A{vX*wV>=fO_xNC8*_nzP za_2yc3}=*E1)-2TPbBvb#pD`#G4J=H0*(q&(M?i(wfh{*bepVboGD9k+S9GN3A_tIPpQmysb0kq;ZSnY536SSg zSaiR`P1gh|BwbGlx@sB@Aub`vxjSMJuSGp9mGb_ZYy*J zYtKJzZ|8&3l;aFH##G_x6LFM3N1p^@Q)5rJy$_5lWBiS_c2Is5zKYbu7A{R6b0pij z;1@Vir^}}oz7#nVA7y|81nfUf=y#V=zaU;hwmC@0TT?(mlW_P5C+c{)h-5kjdAXjF zwJ>ZFMmQVWnT+@fZlrt3Gx>hTzzD35J8vwE7(!tM=^u`}eQ5TnJQR)*%Z6L6D2D=` zbW;1&`*HiNUF_$Avv8hX*j0+@+<9U<1g11GiQ{jCw=$vKWroQ5$3&=03*%($=pF&3 z$J>t@?whxq@~f-+W@ zbU?Zz-*-2#13>Hak;kYP(mPVhwWBWB^@8fue-_;W$8bYnI7KiIsriJ-%xev->f~xs ztP7LCfm57aulr?N#Y1AjAL%A9NekSC>jMh>-=*}osM$agEJNG0k@+Lk^;UcS2K*oi z%$OT8c$ugN$O>BX`10P3?^K2kupllJ{7VqM^Cg#?yJ!>+*>W~lrh-i!?hPQ$T*e-S zSLzv}`j*C$(4Sk_d4J42essG6miwY+V<=mFND%&^WjQhTCJ$ep{GSckKyJi^q0+#{ z2o8kqGMQ^5g-z7~e@c|8G8?f&$#26OC9Pp%aL=u)B%ceiZ|d|Oe^pZYt>M)8 z18G&u{sV#Jn{6_#<13|Lzs?XtCmk3MsvXzEs+FU6&fqeHtC1Jo$B3tmt1p7W-uy?~ zD(l9iqLR@ONG=X&dq5@9qYNTYTI-pQ_S`)~Xv*MU0lR`8ceb_ng1LOnH(<}ElG5_K zfs&B#ljF3=s& zwhwqgwiEX~g1(~Q=mFpWHq+>=fnXpcPj}#~J;4qfi%oo^8_4vwFt9C$ zmpE(Q0U!SGAF;RvQDf|^^^iit|6vFeR|i-({X#JviTz0voM{~;2S?L0;qS#wz=83n zhQr4vBThSwX|Hp^g}7nnfnsR&alwBcJl*$WukV2EYj~=A_mLHR4*~?6&SXUe_PKus zpsnf}!@8&v@QfO59AlaTO*;5#MJH$;k?wTr2n{r7SS{z0c){={G$w&7L3ROYvv9VeE!e)_|i z$Q@y1ZP9}J&nvPW3)hyRNA$xf0-%}d3VdLDNW5$A&PLoh0Gr9_w&vLCO zrKDVG_5-cFs+qYw%p!}{&ingbsZs>F?(!p7Rd%mb+q9!sIyB1Z@$Gbis0Tds9qh|5 zhSXux8SM`n9qm9`E|5r>Rq1zpZp!|_9J8LXALPM#+nb*ePDAD+yYs8%%*n#a%^`Dh zd}ms39QEKG8tvoYed?mW+;t~2UNUSo;)Mw>xSwRst5X3G>5tj6M~;X9qq4uauLTV| z&RNUkaUhOHMCuOxipl=c3`C`g--&Xv&%4%6HUe*A`Gae0jAK0dRAX z{ILgWAd literal 2510 zcmV;<2{HDGP)FMdNs#A4**!_RIt9xd; zr+3wH1RA!7`;%I1a8VR(}>hL(ki;|J<^ZJG{}^x%_x3FZSC^7+wiMum=Eu2?1i;wpFpRav>o0I+)rFM8t49O_p9JBC<+3 zH5|UF$cstx`%1s*^goO2S^lprK##{C3AS(FQF7E6BIjJea{$f>h!{BsObEt%y-lU? zAYdTnR{`L?8*Pm9xB7Eg#HSY7vn5V3#y;gs&noq}wDw)I##o}Slax0y&NoOv2yxlQ zb3GVbwjsR@kTKwt`sP;Hybv}`%vq9xql04GHrq0n%^m@0lAYjEd zXUwxr-X{+62o$y*008Ut-l2xWiE&jXa+s#SAXi#^vRS5RZEf4j?GKnJI^;{w=6Oa(kFGT3uxa{yW$)y;PR66h=33uYRgju#_=I0~#7}8@^WZlTuz$kzR{Lvkr9p_?dp-sr2FeJR)kG`8S_DicYtAUi$XI zJa=k1yr>JWj-u~4#QG)LTq;W@4DZ+~Uy76ez}G#d>B4f=BG@E(sZDi zlku~l;`n+V1gjn|Fo5fw)}~}=ueXX(^w}=+m0*U$Z@a4W*1PiRIR3LQGdz{R+R5bU z8VPP;HL2IThVgP&hxqrlPE;nB@`6gEbB_T4IIZs^ztSYcK_X(6F>OIdkFMZ!s!G8~ zYiHKiukONY!tg4qROzMXj3E`r*8u=f81AxpUYge-k`BX9bau*&MqlxxkxkREE3X=j z?yo6>WyO^+lgX351a*=u3{DH>N1T6n-h&*AO&&tg*UEwxz-&X~i+HmUWUgs6*cOo-EP zG`e=_$!!#Ux<-0&*UgHf(cS)w+-8|3qM%|!nm$p9f=Tl5JpTFelk%e#={fggNg~TY zhdV`QIPYwso6}Yh=`Gt2TN_n03m*_f!Q-bL;(cBIbI`>F~6(p zFSY)LFF_szJI!$Tp4OA=e*feC!L&Vj1llN7nFTc(-CB{fQbcn4e(d+JsqiZ0^!obS7mc-P zT9KfMFN$`UJLNGC2=*R|qgohmCBCNR!w)s((345|)X_OI7Rq@vv;W>!9QK=1Mu> zRxU?t{vK9!j`MY2Sc(3;XzZ^i;K3kW-E&}HHEl%rl zdcpaA>i4Vl_U!oa^Q=I=283F{@Ph0?EnZKpym( zT-mqpQ9>-GSG?C-(Z%a{I*KZv*O;a+beXsa3d3DG48L#E^lzA^$EL&K{;r%ljvuIz z;G>%zFiHODZ#*4EKkhp5%Ly|W9P;aic=Ahv0})A_PBSm+**uQVf29pygvzx%V@QSJ zbzDc$wWlt~Z-T6SX?54Gqpy^)O<}DqW%x`=;6i*%@*ud81?3mJGe+6b=r#*5;MRuZ z+|0uOB(j1i3078?z8F%3(RvpE)F!!%Xcch($ diff --git a/icons/midi_recorder.png b/icons/midi_recorder.png new file mode 100644 index 0000000000000000000000000000000000000000..0a6d6474cd57096807c5f7886be0151afb09c5bd GIT binary patch literal 14006 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 L(pDrWpfLXh)Gty% literal 0 HcmV?d00001 diff --git a/icons/panic.png b/icons/panic.png new file mode 100644 index 0000000000000000000000000000000000000000..b8e2b35ce833a0b6ca1dfd0ddca6c3e9bc09197f GIT binary patch literal 12442 zcmdseWm}X{7w$t0T_aMG4j_$$l1dIzf`F8Ch@gZth?IlC5TdB`5F!XjBS`lE(jh6` zjnW~)+4H{V!}}M``2bwk%=4_h_u4D&b+1K)jFO^gIcPa?=He*FO+G)@3n8V6BvERfog z^XvQ)%|OL@tGWcXBK1ii6G%Z5`jPTDv>4BdAG)uMonE=Ci>(sRK+zr3(oeC&>F|Q7 z;&uMDHS1lLzIMEW*zpv35dKA)fZzCOAYRpU1Gk|EUPi7d1vHKaQ5uxr5lMXV(4b^! zt4Ygg?47Y)Rc5B$g3b`3GE6{=>esLYWmH^bd|rq;Eel=2EoleZNv=$Jf+h_p*8%?~ zdAlD}1%qD-1Q)&&wN`)$+#p`>kU%>yLU#nKMqQCx*U`lTaC`8@0`XeHU!*4w)N^cI zUrXcmih6VH3j8tn1{Jgi zIzCXuO2`%EXsW!%qboqO=3^LtFqizg6j`A!LSJ!37T!5~S@ zz*5cEU-!Zg3l82f�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 N($RPXFITg|{1;{Sh+hBz literal 0 HcmV?d00001 diff --git a/icons/settings.png b/icons/settings.png new file mode 100644 index 0000000000000000000000000000000000000000..c98096cb4f08da807eda63ed802c641300f4f24c GIT binary patch literal 11781 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 literal 0 HcmV?d00001 diff --git a/icons/snapshot.png b/icons/snapshot.png new file mode 100644 index 0000000000000000000000000000000000000000..4c722e286d8963027022271fc10d4b7df6225423 GIT binary patch literal 15759 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 wgKBUnDyA522%0!3-45RWF#T6lu%&nqV=3@ MAIN")) - self.list_data.append((self.snapshots, 0, "Snapshots", ["Show snapshots management menu.", None])) - self.list_data.append((self.step_sequencer, 0, "Sequencer", ["Show sequencer's zynpad view.", None])) - self.list_data.append((self.audio_recorder, 0, "Audio Recorder", ["Show audio recorder/player.", None])) - self.list_data.append((self.midi_recorder, 0, "MIDI Recorder", ["Show SMF recorder/player.", None])) - self.list_data.append((self.tempo_settings, 0, "Tempo Settings", ["Show tempo & sync options.", None])) - self.list_data.append((self.audio_levels, 0, "Audio Levels", ["Show audio levels view.", None])) - self.list_data.append((self.audio_mixer_learn, 0, "Mixer Learn", ["Enter mixer's MIDI learn mode", None])) + self.list_data.append((self.snapshots, 0, "Snapshots", ["Show snapshots management menu.", "snapshot.png"])) + self.list_data.append((self.step_sequencer, 0, "Sequencer", ["Show sequencer's zynpad view.", "sequencer.png"])) + self.list_data.append((self.audio_recorder, 0, "Audio Recorder", ["Show audio recorder/player.", "audio_recorder.png"])) + self.list_data.append((self.midi_recorder, 0, "MIDI Recorder", ["Show SMF recorder/player.", "midi_recorder.png"])) + self.list_data.append((self.tempo_settings, 0, "Tempo Settings", ["Show tempo & sync options.", "metronome.png"])) + self.list_data.append((self.audio_levels, 0, "Audio Levels", ["Show audio levels view.", "meters.png"])) + self.list_data.append((self.audio_mixer_learn, 0, "Mixer Learn", ["Enter mixer's MIDI learn mode", "mixer.png"])) # Add list of System / configuration views self.list_data.append((None, 0, "> SYSTEM")) - self.list_data.append((self.admin, 0, "Admin", ["Show admin menu.", None])) + self.list_data.append((self.admin, 0, "Admin", ["Show admin menu.", "settings.png"])) self.list_data.append( - (self.all_sounds_off, 0, "PANIC! All Sounds Off", ["Stop all notes and sequences.", None])) + (self.all_sounds_off, 0, "PANIC! All Sounds Off", ["Stop all notes and sequences.", "panic.png"])) super().fill_list() From 2243a565fd857f7718d5f1da1dd21040b3e9fc6c Mon Sep 17 00:00:00 2001 From: riban Date: Sat, 21 Dec 2024 07:22:07 +0000 Subject: [PATCH 103/117] Fixes touch control of processor control page list --- zyngui/zynthian_gui_control.py | 22 +++------------------- zyngui/zynthian_gui_selector.py | 7 +++++-- 2 files changed, 8 insertions(+), 21 deletions(-) diff --git a/zyngui/zynthian_gui_control.py b/zyngui/zynthian_gui_control.py index 7d3a7d8c7..b9d57a020 100644 --- a/zyngui/zynthian_gui_control.py +++ b/zyngui/zynthian_gui_control.py @@ -766,25 +766,9 @@ def show_xy(self, params=None): # GUI Callback function # -------------------------------------------------------------------------- - def cb_listbox_release(self, event): - if self.zyngui.cb_touch_release(event): - return "break" - - now = monotonic() - dts = now - self.listbox_push_ts - rdts = now - self.last_release_ts - self.last_release_ts = now - if self.swiping: - self.swipe_nudge(dts) - else: - if rdts < 0.03: - return # Debounce - cursel = self.listbox.nearest(event.y) - if self.index != cursel: - self.select(cursel) - self.select_listbox(self.get_cursel(), False) - self.click_listbox() - return "break" + def cb_listbox_click(self, t): + # Override listbox click - we don't want short/bold press + return def cb_listbox_motion(self, event): return super().cb_listbox_motion(event) diff --git a/zyngui/zynthian_gui_selector.py b/zyngui/zynthian_gui_selector.py index 8671af3fd..8fee5e762 100644 --- a/zyngui/zynthian_gui_selector.py +++ b/zyngui/zynthian_gui_selector.py @@ -474,9 +474,9 @@ def cb_listbox_release(self, event): if self.index != cursel: self.select(cursel) if dts < zynthian_gui_config.zynswitch_bold_seconds: - self.zyngui.zynswitch_defered('S', 3) + self.cb_listbox_click('S') elif zynthian_gui_config.zynswitch_bold_seconds <= dts < zynthian_gui_config.zynswitch_long_seconds: - self.zyngui.zynswitch_defered('B', 3) + self.cb_listbox_click('B') def cb_listbox_wheel(self, event): if event.num == 5 or event.delta == -120: @@ -485,6 +485,9 @@ def cb_listbox_wheel(self, event): self.select(self.index - 1) return "break" # Consume event to stop scrolling of listbox + def cb_listbox_click(self, t): + self.zyngui.zynswitch_defered(t, 3) + def cb_loading_push(self, event): self.loading_push_ts = event.time # logging.debug("LOADING PUSH => %s" % self.loading_push_ts) From 337d70deeb1b7280759dec97abd19f68c7c4cefe Mon Sep 17 00:00:00 2001 From: riban Date: Sun, 22 Dec 2024 07:00:16 +0000 Subject: [PATCH 104/117] Fixes zynthian/zynthian-issue-tracking#1297. Truncating fader strip title was incorrectly acting on first line only. --- zyngui/zynthian_gui_mixer.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/zyngui/zynthian_gui_mixer.py b/zyngui/zynthian_gui_mixer.py index 53d913998..ba840c6a9 100644 --- a/zyngui/zynthian_gui_mixer.py +++ b/zyngui/zynthian_gui_mixer.py @@ -198,6 +198,8 @@ def __init__(self, parent, x, y, width, height): self.fader_bg), "press", self.on_fader_press) self.parent.zyngui.multitouch.tag_bind(self.parent.main_canvas, "fader:%s" % ( self.fader_bg), "motion", self.on_fader_motion) + self.parent.zyngui.multitouch.tag_bind(self.parent.main_canvas, "fader:%s" % ( + self.fader_bg), "motion", self.on_fader_motion) self.parent.main_canvas.tag_bind( f"fader:{self.fader_bg}", "", self.on_fader_press) self.parent.main_canvas.tag_bind( @@ -338,7 +340,7 @@ def draw_fader(self): label_parts = ["No info"] for i, label in enumerate(label_parts): - # self.parent.main_canvas.itemconfig(self.fader_text, text=label, state=tkinter.NORMAL) + self.parent.main_canvas.itemconfig(self.fader_text, text=label, state=tkinter.NORMAL) bounds = self.parent.main_canvas.bbox(self.fader_text) if bounds[1] < self.fader_text_limit: while bounds and bounds[1] < self.fader_text_limit: From e4418aec16629f4c062d6d0f6f335ba91c730d20 Mon Sep 17 00:00:00 2001 From: riban Date: Sun, 22 Dec 2024 07:33:56 +0000 Subject: [PATCH 105/117] Fixes zynthian/zynthian-issue-tracking#1294. Add per-loop MIDI binding for single pedal mode controller. --- zyngine/zynthian_engine_sooperlooper.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/zyngine/zynthian_engine_sooperlooper.py b/zyngine/zynthian_engine_sooperlooper.py index 70ee63de8..9a42251d7 100644 --- a/zyngine/zynthian_engine_sooperlooper.py +++ b/zyngine/zynthian_engine_sooperlooper.py @@ -89,7 +89,8 @@ class zynthian_engine_sooperlooper(zynthian_engine): 'mute', 'oneshot', 'pause', - 'reverse' + 'reverse', + 'single_pedal' ] # SL_LOOP_PARAMS act on individual loops - sent with osc command /sl/#/set @@ -424,7 +425,7 @@ def __init__(self, state_manager=None): ['Loop control', ['trigger:0', 'oneshot:0', 'mute:0', 'pause:0']], ['Loop time/pitch', ['reverse:0', 'rate', 'stretch_ratio', 'pitch_shift']], ['Loop levels', ['wet', 'dry', 'feedback', 'selected_loop_num']], - ['Global loop', ['selected_loop_num', 'loop_count', 'prev/next', 'single_pedal']], + ['Global loop', ['selected_loop_num', 'loop_count', 'prev/next', 'single_pedal:0']], ['Global levels', ['rec_thresh', 'input_gain']], ['Global quantize', ['quantize', 'mute_quantized', 'overdub_quantized', 'replace_quantized']], ['Global sync 1', ['sync_source', 'sync', 'playback_sync', 'relative_sync']], @@ -844,7 +845,8 @@ def select_loop(self, loop, send=False): self._ctrl_screens[0][1] = [f'record:{self.selected_loop}', f'overdub:{self.selected_loop}', f'multiply:{self.selected_loop}', 'undo/redo'] self._ctrl_screens[1][1] = [f'replace:{self.selected_loop}', f'substitute:{self.selected_loop}', f'insert:{self.selected_loop}', 'undo/redo'] self._ctrl_screens[2][1] = [f'trigger:{self.selected_loop}', f'oneshot:{self.selected_loop}', f'mute:{self.selected_loop}', f'pause:{self.selected_loop}'] - self._ctrl_screens[3][1] = [f'reverse:{self.selected_loop}', 'rate', 'stretch_ratio', 'pitch_shift'] + self._ctrl_screens[3][1][0] = f'reverse:{self.selected_loop}' + self._ctrl_screens[5][1][3] = f'single_pedal:{self.selected_loop}' processor.refresh_controllers() zynsigman.send_queued(zynsigman.S_GUI, SS_GUI_CONTROL_MODE, mode='control') From e62c4aa1579caa9d325fd9511db99b214588fae7 Mon Sep 17 00:00:00 2001 From: riban Date: Sun, 22 Dec 2024 08:12:36 +0000 Subject: [PATCH 106/117] Fixes zynthian/zynthian-issue-tracking#1288. Make each ZS3 restore option unique so that it is not omitted from the options list. --- zyngui/zynthian_gui_zs3_options.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/zyngui/zynthian_gui_zs3_options.py b/zyngui/zynthian_gui_zs3_options.py index 219e2cda5..8a24caf41 100644 --- a/zyngui/zynthian_gui_zs3_options.py +++ b/zyngui/zynthian_gui_zs3_options.py @@ -115,6 +115,9 @@ def zs3_restoring_options_cb(self): if chain is None: continue label = chain.get_name() + while f"\u2612 {label}" in options or f"\u2610 {label}" in options: + # Make each option title unique so that they are not omitted from the options menu + label += " " try: restore_flag = chain_state["restore"] except: From 8f2b7dc1b09e2362abd10149a91fce38f4059d04 Mon Sep 17 00:00:00 2001 From: riban Date: Sun, 22 Dec 2024 08:29:42 +0000 Subject: [PATCH 107/117] Fixes zynthian/zynthian-issue-tracking#1287. Reassert ZS3 chain restore flags after overwritting ZS3. --- zyngui/zynthian_gui_zs3_options.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/zyngui/zynthian_gui_zs3_options.py b/zyngui/zynthian_gui_zs3_options.py index 8a24caf41..a24a9801a 100644 --- a/zyngui/zynthian_gui_zs3_options.py +++ b/zyngui/zynthian_gui_zs3_options.py @@ -155,7 +155,15 @@ def zs3_rename_cb(self, title): def zs3_update(self): logging.info("Updating ZS3 '{}'".format(self.zs3_id)) + restore_chains = [] + state = self.zyngui.state_manager.zs3[self.zs3_id] + if "chains" in state: + for chain_id, chain_state in state["chains"].items(): + if "restore" in chain_state and not chain_state["restore"]: + restore_chains.append(chain_id) self.zyngui.state_manager.save_zs3(self.zs3_id) + for chain_id in restore_chains: + self.zyngui.state_manager.toggle_zs3_chain_restore_flag(self.zs3_id, chain_id) self.zyngui.close_screen() def zs3_delete(self): From b0ba42f2c635cd4a993b385cb15bb6fe142de7d2 Mon Sep 17 00:00:00 2001 From: riban Date: Sun, 22 Dec 2024 09:10:06 +0000 Subject: [PATCH 108/117] Fixes zynthian/zynthian-issue-tracking#1262. Trigger onscreen keyboard ENTER key on release. --- zyngui/zynthian_gui_keyboard.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/zyngui/zynthian_gui_keyboard.py b/zyngui/zynthian_gui_keyboard.py index 4bbc3c8ee..1304cfe20 100644 --- a/zyngui/zynthian_gui_keyboard.py +++ b/zyngui/zynthian_gui_keyboard.py @@ -56,6 +56,7 @@ def __init__(self): self.selected_button = 45 # Index of highlighted button self.mode = OSK_QWERTY # Keyboard layout mode self.ctrl_order = zynthian_gui_config.layout['ctrl_order'] + self.last_key = None # Last pressed key # Geometry vars self.width = zynthian_gui_config.screen_width @@ -198,16 +199,19 @@ def on_key_press(self, event=None): if not tags: return dummy, index = tags[0].split(':') - key = int(index) - if key == self.btn_delete: + self.last_key = int(index) + if self.last_key == self.btn_delete: self.hold_timer = Timer(0.8, self.bold_press) self.hold_timer.start() - self.deferred_key_press(key) + if self.last_key != self.btn_enter: + self.deferred_key_press(self.last_key) # Function to handle key release # event: Mouse event def on_key_release(self, event=None): self.hold_timer.cancel() + if self.last_key == self.btn_enter: + self.deferred_key_press(self.btn_enter) def deferred_key_press(self, key, bold=False): self.keypress_queue.append((key, bold)) From 31405db39630e987524063c77030ecc4c69b7013 Mon Sep 17 00:00:00 2001 From: riban Date: Sun, 22 Dec 2024 09:15:29 +0000 Subject: [PATCH 109/117] Trigger onscreen keyboard CANCEL on key release. --- zyngui/zynthian_gui_keyboard.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/zyngui/zynthian_gui_keyboard.py b/zyngui/zynthian_gui_keyboard.py index 1304cfe20..003945aeb 100644 --- a/zyngui/zynthian_gui_keyboard.py +++ b/zyngui/zynthian_gui_keyboard.py @@ -203,15 +203,15 @@ def on_key_press(self, event=None): if self.last_key == self.btn_delete: self.hold_timer = Timer(0.8, self.bold_press) self.hold_timer.start() - if self.last_key != self.btn_enter: + if self.last_key not in [self.btn_enter, self.btn_cancel]: self.deferred_key_press(self.last_key) # Function to handle key release # event: Mouse event def on_key_release(self, event=None): self.hold_timer.cancel() - if self.last_key == self.btn_enter: - self.deferred_key_press(self.btn_enter) + if self.last_key in [self.btn_enter, self.btn_cancel]: + self.deferred_key_press(self.last_key) def deferred_key_press(self, key, bold=False): self.keypress_queue.append((key, bold)) From dd914019a1e58488d815148ace518824cfc634c3 Mon Sep 17 00:00:00 2001 From: riban Date: Sun, 22 Dec 2024 09:28:46 +0000 Subject: [PATCH 110/117] Adds auto repeat to onscreen keyboard --- zyngui/zynthian_gui_keyboard.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/zyngui/zynthian_gui_keyboard.py b/zyngui/zynthian_gui_keyboard.py index 003945aeb..a0e267b87 100644 --- a/zyngui/zynthian_gui_keyboard.py +++ b/zyngui/zynthian_gui_keyboard.py @@ -190,7 +190,11 @@ def add_button(self, label, col, row, colspan=1): # Function to handle bold touchscreen press and hold def bold_press(self): - self.deferred_key_press(self.btn_delete, True) + self.deferred_key_press(self.last_key, True) + if self.last_key != self.btn_delete: + self.hold_timer = Timer(0.2, self.bold_press) + self.hold_timer.start() + # Function to handle key press # event: Mouse event @@ -200,11 +204,10 @@ def on_key_press(self, event=None): return dummy, index = tags[0].split(':') self.last_key = int(index) - if self.last_key == self.btn_delete: - self.hold_timer = Timer(0.8, self.bold_press) - self.hold_timer.start() if self.last_key not in [self.btn_enter, self.btn_cancel]: self.deferred_key_press(self.last_key) + self.hold_timer = Timer(0.8, self.bold_press) + self.hold_timer.start() # Function to handle key release # event: Mouse event From baa7b4cac95ed6f0c41c601035a0c692e6e4a22c Mon Sep 17 00:00:00 2001 From: riban Date: Sun, 22 Dec 2024 09:43:27 +0000 Subject: [PATCH 111/117] Do not save preset with empty name# --- zyngui/zynthian_gui_save_preset.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/zyngui/zynthian_gui_save_preset.py b/zyngui/zynthian_gui_save_preset.py index 2421238c0..1647a6a46 100644 --- a/zyngui/zynthian_gui_save_preset.py +++ b/zyngui/zynthian_gui_save_preset.py @@ -80,15 +80,16 @@ def save_preset_select_name_cb(self, create_bank_name=None): def save_preset_cb(self, preset_name): preset_name = preset_name.strip() - # If must create new bank, calculate URID - if self.save_preset_create_bank_name: - create_bank_urid = self.processor.engine.get_user_bank_urid(self.save_preset_create_bank_name) - self.save_preset_bank_info = (create_bank_urid, None, self.save_preset_create_bank_name, None) - if self.processor.engine.preset_exists(self.save_preset_bank_info, preset_name): - self.zyngui.show_confirm(f"Do you want to overwrite preset '{preset_name}'?", - self.do_save_preset, preset_name) - else: - self.do_save_preset(preset_name) + if preset_name: + # If must create new bank, calculate URID + if self.save_preset_create_bank_name: + create_bank_urid = self.processor.engine.get_user_bank_urid(self.save_preset_create_bank_name) + self.save_preset_bank_info = (create_bank_urid, None, self.save_preset_create_bank_name, None) + if self.processor.engine.preset_exists(self.save_preset_bank_info, preset_name): + self.zyngui.show_confirm(f"Do you want to overwrite preset '{preset_name}'?", + self.do_save_preset, preset_name) + else: + self.do_save_preset(preset_name) def do_save_preset(self, preset_name): preset_name = preset_name.strip() From 78a6b3c13fe2633384844bd32398d2eafac50ac3 Mon Sep 17 00:00:00 2001 From: riban Date: Sun, 22 Dec 2024 09:59:57 +0000 Subject: [PATCH 112/117] Fixes /zynthian/zynthian-issue-tracking#1251. Removes short touch of zctrl to toggle MIDI learn. (This is available in bold press menu.) Short touch now toggles if zctrl is a toggle. --- zyngui/zynthian_gui_controller.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/zyngui/zynthian_gui_controller.py b/zyngui/zynthian_gui_controller.py index 159346032..b0c56ff65 100644 --- a/zyngui/zynthian_gui_controller.py +++ b/zyngui/zynthian_gui_controller.py @@ -636,7 +636,9 @@ def cb_canvas_release(self, event): if self.active_motion_axis == 0: if zynthian_gui_config.enable_touch_controller_switches: if dts < zynthian_gui_config.zynswitch_bold_seconds: - self.zyngui.cuia_v5_zynpot_switch((self.index, 'S')) + if self.zctrl.is_toggle: + self.zctrl.toggle() + #self.zyngui.cuia_v5_zynpot_switch((self.index, 'S')) elif zynthian_gui_config.zynswitch_bold_seconds <= dts < zynthian_gui_config.zynswitch_long_seconds: self.zyngui.cuia_v5_zynpot_switch((self.index, 'B')) elif dts >= zynthian_gui_config.zynswitch_long_seconds: From 95adb749246b8579b7cd6aed005537b03b54cd90 Mon Sep 17 00:00:00 2001 From: riban Date: Mon, 23 Dec 2024 07:04:51 +0000 Subject: [PATCH 113/117] Fixes zynthian/zynthian-issue-tracking#1300. Fixes zynthian/zynthian-issue-tracking#1299. Adds menu info for presets options menu. --- zyngui/zynthian_gui_preset.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/zyngui/zynthian_gui_preset.py b/zyngui/zynthian_gui_preset.py index ce24b609c..09bb80fcf 100644 --- a/zyngui/zynthian_gui_preset.py +++ b/zyngui/zynthian_gui_preset.py @@ -78,19 +78,19 @@ def show_preset_options(self): preset[2] = preset[2][1:] preset_name = preset[2] if self.processor.engine.is_preset_fav(preset): - options["\u2612 Favourite"] = preset + options["\u2612 Favourite"] = [preset, ["Remove from favorites list", None]] else: - options["\u2610 Favourite"] = preset + options["\u2610 Favourite"] = [preset, ["Add to favorites list", None]] if engine.is_preset_user(preset): if hasattr(engine, "rename_preset"): - options["Rename"] = preset + options["Rename"] = [preset, ["Rename preset", None]] if hasattr(engine, "delete_preset"): - options["Delete"] = preset + options["Delete"] = [preset, ["Delete preset", None]] global_options = {} if hasattr(engine, "save_preset"): - global_options["Save new preset"] = True + global_options["Save new preset"] = [True, ["Save as new preset", None]] if self.processor.eng_code.startswith("JV/"): - global_options["Scan for new presets"] = True + global_options["Scan for new presets"] = [True, ["Scan new presets, e.g. added via webconf", None]] if global_options: options["Global"] = None options.update(global_options) From 7b149e0381c319bd1a4aff62cf9c625ccc1dc232 Mon Sep 17 00:00:00 2001 From: riban Date: Mon, 23 Dec 2024 07:52:52 +0000 Subject: [PATCH 114/117] Adds info to MIDI recorder menu --- zyngui/zynthian_gui_midi_recorder.py | 29 ++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/zyngui/zynthian_gui_midi_recorder.py b/zyngui/zynthian_gui_midi_recorder.py index df864754d..10f993d00 100644 --- a/zyngui/zynthian_gui_midi_recorder.py +++ b/zyngui/zynthian_gui_midi_recorder.py @@ -5,7 +5,7 @@ # # Zynthian GUI MIDI Recorder Class # -# Copyright (C) 2015-2023 Fernando Moyano +# Copyright (C) 2015-2024 Fernando Moyano # # ****************************************************************************** # @@ -31,7 +31,7 @@ # Zynthian specific modules import zynconf from zyngui import zynthian_gui_config -from zyngui.zynthian_gui_selector import zynthian_gui_selector +from zyngui.zynthian_gui_selector_info import zynthian_gui_selector_info from zyngui.zynthian_gui_controller import zynthian_gui_controller # Python wrapper for zynsmf (ensures initialised and wraps load() function) from zynlibs.zynsmf import zynsmf @@ -42,7 +42,7 @@ # ------------------------------------------------------------------------------ -class zynthian_gui_midi_recorder(zynthian_gui_selector): +class zynthian_gui_midi_recorder(zynthian_gui_selector_info): def __init__(self): self.capture_dir_sdc = os.environ.get( @@ -54,7 +54,7 @@ def __init__(self): self.smf_timer = None # 1s timer used to check end of SMF playback - super().__init__('MIDI Recorder', True) + super().__init__('MIDI Recorder') self.bpm_zgui_ctrl = None @@ -92,7 +92,7 @@ def fill_list(self): flist += self.get_filelist(exd, "USB") i = 1 for finfo in sorted(flist, key=lambda d: d['mtime'], reverse=True): - self.list_data.append((finfo['fpath'], i, finfo['title'])) + self.list_data.append((finfo['fpath'], i, finfo['title'], ["Play MIDI file.\nBold select to show more options.", None])) i += 1 super().fill_list() @@ -160,11 +160,11 @@ def update_status_playback(self): def update_status_recording(self, fill=False): if self.list_data: if self.zyngui.state_manager.status_midi_recorder: - self.list_data[0] = ("STOP_RECORDING", 0, - "■ Stop MIDI Recording") + self.list_data[0] = (("STOP_RECORDING", 0, + "■ Stop MIDI Recording", ["Toggle recording to MIDI file.", None])) else: - self.list_data[0] = ("START_RECORDING", 0, - "⬤ Start MIDI Recording") + self.list_data[0] = (("START_RECORDING", 0, + "⬤ Start MIDI Recording", ["Toggle recording to MIDI file", None])) if fill: self.listbox.delete(0) self.listbox.insert(0, self.list_data[0][2]) @@ -173,10 +173,10 @@ def update_status_recording(self, fill=False): def update_status_loop(self, fill=False): if self.list_data: if zynthian_gui_config.midi_play_loop: - self.list_data[1] = ("LOOP", 0, "\u2612 Loop Play") + self.list_data[1] = (("LOOP", 0, "\u2612 Loop Play", ["Toggle loop playback.", None])) libsmf.setLoop(True) else: - self.list_data[1] = ("LOOP", 0, "\u2610 Loop Play") + self.list_data[1] = (("LOOP", 0, "\u2610 Loop Play", ["Toggle loop playback.", None])) libsmf.setLoop(False) if fill: self.listbox.delete(1) @@ -213,8 +213,8 @@ def show_smf_options(self): smf = self.list_data[self.index] smf_fname = smf[2] options = {} - options["Rename"] = smf - options["Delete"] = smf + options["Rename"] = [smf, ["Rename MIDI file", None]] + options["Delete"] = [smf, ["Delete MIDI file", None]] self.zyngui.screens['option'].config( f"MIDI file {smf_fname}", options, self.smf_options_cb) self.zyngui.show_screen('option') @@ -230,7 +230,8 @@ def toggle_menu(self): def smf_options_cb(self, option, smf): if option == "Rename": - self.zyngui.show_keyboard(self.rename_smf, smf[2]) + name = os.path.basename(smf[0])[:-4] + self.zyngui.show_keyboard(self.rename_smf, name) elif option == "Delete": self.delete_smf(smf) From d029c312261ad3a97aaf5c2b2280b7f8e43fe362 Mon Sep 17 00:00:00 2001 From: riban Date: Mon, 23 Dec 2024 08:56:48 +0000 Subject: [PATCH 115/117] Adds info to admin menu --- icons/headphones.png | Bin 0 -> 7053 bytes icons/meter.png | Bin 0 -> 4869 bytes zyngui/zynthian_gui_admin.py | 88 +++++++++++++++++------------------ 3 files changed, 44 insertions(+), 44 deletions(-) create mode 100644 icons/headphones.png create mode 100644 icons/meter.png diff --git a/icons/headphones.png b/icons/headphones.png new file mode 100644 index 0000000000000000000000000000000000000000..6760a348bbe8f0c58b860cccd8aa11a7da96dea6 GIT binary patch literal 7053 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 F@E^naP_h63 literal 0 HcmV?d00001 diff --git a/icons/meter.png b/icons/meter.png new file mode 100644 index 0000000000000000000000000000000000000000..052242afa995959542a6f33a8df0c6dc309a3b9b GIT binary patch literal 4869 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 S8TzgTv;M};if-xg)4u?@F-ye& literal 0 HcmV?d00001 diff --git a/zyngui/zynthian_gui_admin.py b/zyngui/zynthian_gui_admin.py index c82ab2409..ed166f8ce 100644 --- a/zyngui/zynthian_gui_admin.py +++ b/zyngui/zynthian_gui_admin.py @@ -37,14 +37,14 @@ import zynautoconnect from zyncoder.zyncore import lib_zyncore from zyngui import zynthian_gui_config -from zyngui.zynthian_gui_selector import zynthian_gui_selector +from zyngui.zynthian_gui_selector_info import zynthian_gui_selector_info # ------------------------------------------------------------------------------- # Zynthian Admin GUI Class # ------------------------------------------------------------------------------- -class zynthian_gui_admin(zynthian_gui_selector): +class zynthian_gui_admin(zynthian_gui_selector_info): data_dir = os.environ.get('ZYNTHIAN_DATA_DIR', "/zynthian/zynthian-data") sys_dir = os.environ.get('ZYNTHIAN_SYS_DIR', "/zynthian/zynthian-sys") @@ -62,7 +62,7 @@ def __init__(self): self.wifi_status = "???" self.filling_list = False - super().__init__('Action', True) + super().__init__('Action') self.state_manager = self.zyngui.state_manager @@ -109,75 +109,75 @@ def fill_list(self): self.list_data = [] self.list_data.append((None, 0, "> MIDI")) - self.list_data.append((self.zyngui.midi_in_config, 0, "MIDI Input Devices")) - self.list_data.append((self.zyngui.midi_out_config, 0, "MIDI Output Devices")) + self.list_data.append((self.zyngui.midi_in_config, 0, "MIDI Input Devices", ["Configure MIDI input devices.", "midi_input.png"])) + self.list_data.append((self.zyngui.midi_out_config, 0, "MIDI Output Devices", ["Configure MIDI output devices.", "midi_output.png"])) # self.list_data.append((self.midi_profile, 0, "MIDI Profile")) if lib_zyncore.get_active_midi_chan(): - self.list_data.append((self.toggle_active_midi_channel, 0, "\u2612 Active MIDI channel")) + self.list_data.append((self.toggle_active_midi_channel, 0, "\u2612 Active MIDI channel", ["Send active MIDI channel messages to active chain only.", "midi_logo.png"])) else: - self.list_data.append((self.toggle_active_midi_channel, 0, "\u2610 Active MIDI channel")) + self.list_data.append((self.toggle_active_midi_channel, 0, "\u2610 Active MIDI channel", ["Send active MIDI channel messages to all chains with same MIDI channel.", "midi_logo.png"])) if zynthian_gui_config.midi_prog_change_zs3: - self.list_data.append((self.toggle_prog_change_zs3, 0, "\u2612 Program Change for ZS3")) + self.list_data.append((self.toggle_prog_change_zs3, 0, "\u2612 Program Change for ZS3", ["MIDI Program Change messages recall snapshots", "midi_logo.png"])) else: - self.list_data.append((self.toggle_prog_change_zs3, 0, "\u2610 Program Change for ZS3")) + self.list_data.append((self.toggle_prog_change_zs3, 0, "\u2610 Program Change for ZS3", ["MIDI Program Change messages recall ZS3.", "midi_logo.png"])) if zynthian_gui_config.midi_bank_change: - self.list_data.append((self.toggle_bank_change, 0, "\u2612 MIDI Bank Change")) + self.list_data.append((self.toggle_bank_change, 0, "\u2612 MIDI Bank Change", ["Select bank when MIDI Program Change received", "midi_logo.png"])) else: - self.list_data.append((self.toggle_bank_change, 0, "\u2610 MIDI Bank Change")) + self.list_data.append((self.toggle_bank_change, 0, "\u2610 MIDI Bank Change", ["Don't select bank when MIDI Program Change received", "midi_logo.png"])) if zynthian_gui_config.preset_preload_noteon: - self.list_data.append((self.toggle_preset_preload_noteon, 0, "\u2612 Note-On Preset Preload")) + self.list_data.append((self.toggle_preset_preload_noteon, 0, "\u2612 Note-On Preset Preload", ["Load preset for preview when a MIDI note-on command is received", "midi_logo.png"])) else: - self.list_data.append((self.toggle_preset_preload_noteon, 0, "\u2610 Note-On Preset Preload")) + self.list_data.append((self.toggle_preset_preload_noteon, 0, "\u2610 Note-On Preset Preload", ["Do not load preset for preview when a MIDI note-on command is received", "midi_logo.png"])) if zynthian_gui_config.midi_usb_by_port: - self.list_data.append((self.toggle_usbmidi_by_port, 0, "\u2612 MIDI-USB mapped by port")) + self.list_data.append((self.toggle_usbmidi_by_port, 0, "\u2612 MIDI-USB mapped by port", ["MIDI ports are indexed by their device name and the physical USB port to which they are plugged", "midi_logo.png"])) else: - self.list_data.append((self.toggle_usbmidi_by_port, 0, "\u2610 MIDI-USB mapped by port")) + self.list_data.append((self.toggle_usbmidi_by_port, 0, "\u2610 MIDI-USB mapped by port", ["MIDI ports are indexed by their device name only.", "midi_logo.png"])) if zynthian_gui_config.transport_clock_source == 0: if zynthian_gui_config.midi_sys_enabled: - self.list_data.append((self.toggle_midi_sys, 0, "\u2612 MIDI System Messages")) + self.list_data.append((self.toggle_midi_sys, 0, "\u2612 MIDI System Messages", ["System messages are sent to MIDI outputs.", "midi_logo.png"])) else: - self.list_data.append((self.toggle_midi_sys, 0, "\u2610 MIDI System Messages")) + self.list_data.append((self.toggle_midi_sys, 0, "\u2610 MIDI System Messages", ["System messages are not sent to MIDI outputs.", "midi_logo.png"])) gtrans = lib_zyncore.get_global_transpose() if gtrans > 0: display_val = f"+{gtrans}" else: display_val = f"{gtrans}" - self.list_data.append((self.edit_global_transpose, 0, f"[{display_val}] Global Transpose")) + self.list_data.append((self.edit_global_transpose, 0, f"[{display_val}] Global Transpose", ["MIDI note transpose.\nThis effects all MIDI messages and is in addition to individual chain transpose.", "midi_logo.png"])) self.list_data.append((None, 0, "> AUDIO")) if self.state_manager.allow_rbpi_headphones(): if zynthian_gui_config.rbpi_headphones: - self.list_data.append((self.stop_rbpi_headphones, 0, "\u2612 RBPi Headphones")) + self.list_data.append((self.stop_rbpi_headphones, 0, "\u2612 RBPi Headphones", ["Raspberry Pi onboard (low fidelity) headphone output is enabled", "headphone.png"])) else: - self.list_data.append((self.start_rbpi_headphones, 0, "\u2610 RBPi Headphones")) + self.list_data.append((self.start_rbpi_headphones, 0, "\u2610 RBPi Headphones", ["Raspberry Pi onboard (low fidelity) headphone output is disabled", "headphone.png"])) - self.list_data.append((self.hotplug_audio_menu, 0, "Hotplug USB Audio")) + self.list_data.append((self.hotplug_audio_menu, 0, "Hotplug USB Audio", ["Configure USB audio hotplug.\n\nWhen enabled, USB audio devices will be detected and available. This does not include any device that is already configured as the main audio device which must always reamain connected.", None])) if zynthian_gui_config.snapshot_mixer_settings: - self.list_data.append((self.toggle_snapshot_mixer_settings, 0, "\u2612 Audio Levels on Snapshots")) + self.list_data.append((self.toggle_snapshot_mixer_settings, 0, "\u2612 Audio Levels on Snapshots", ["Soundcard parameters are saved with snapshot", "meter.png"])) else: - self.list_data.append((self.toggle_snapshot_mixer_settings, 0, "\u2610 Audio Levels on Snapshots")) + self.list_data.append((self.toggle_snapshot_mixer_settings, 0, "\u2610 Audio Levels on Snapshots", ["Soundcard parameters are not saved with snapshot", "meter.png"])) if zynthian_gui_config.enable_dpm: - self.list_data.append((self.toggle_dpm, 0, "\u2612 Mixer Peak Meters")) + self.list_data.append((self.toggle_dpm, 0, "\u2612 Mixer Peak Meters", ["Peak programme meters are enabled.", "meter.png"])) else: - self.list_data.append((self.toggle_dpm, 0, "\u2610 Mixer Peak Meters")) + self.list_data.append((self.toggle_dpm, 0, "\u2610 Mixer Peak Meters", ["Peak programme meters are disabled.\nThis saves a little CPU power.", "meter.png"])) self.list_data.append((None, 0, "> NETWORK")) - self.list_data.append((self.network_info, 0, "Network Info")) - self.list_data.append((self.wifi_config, 0, f"Wi-Fi Config ({self.wifi_status})")) + self.list_data.append((self.network_info, 0, "Network Info", ["Show network details, e.g. IP address, etc.", None])) + self.list_data.append((self.wifi_config, 0, f"Wi-Fi Config ({self.wifi_status})", ["Configure Wi-Fi connections.", None])) self.wifi_index = len(self.list_data) - 1 if zynconf.is_service_active("vncserver0"): - self.list_data.append((self.state_manager.stop_vncserver, 0, "\u2612 VNC Server")) + self.list_data.append((self.state_manager.stop_vncserver, 0, "\u2612 VNC Server", ["Display of zynthian UI and processors' native GUI via VNC enabled.\nThis uses more CPU. It is advised to disable during performance.", None])) else: - self.list_data.append((self.state_manager.start_vncserver, 0, "\u2610 VNC Server")) + self.list_data.append((self.state_manager.start_vncserver, 0, "\u2610 VNC Server", ["Display of zynthian UI and processors' native GUI via VNC disabled.", None])) self.list_data.append((None, 0, "> SETTINGS")) if not zynthian_gui_config.wiring_layout.startswith("V5"): @@ -190,35 +190,35 @@ def fill_list(self): touch_navigation_option = "V5 keypad at right" case _: touch_navigation_option = "None" - self.list_data.append((self.touch_navigation_menu, 0, f"Touch Navigation: {touch_navigation_option}")) + self.list_data.append((self.touch_navigation_menu, 0, f"Touch Navigation: {touch_navigation_option}", ["Select touch interface mode.\n\nFor touch-only devices with 5\" screen or less, select touch-widgets.\nFor large touch screen, select V5...\nFor full hardware device, e.g. V5, select None", None])) if "brightness_config" in self.zyngui.screens and self.zyngui.screens["brightness_config"].get_num_zctrls() > 0: - self.list_data.append((self.zyngui.brightness_config, 0, "Brightness")) + self.list_data.append((self.zyngui.brightness_config, 0, "Brightness", ["Adjust display and LED brightness.", None])) if "cv_config" in self.zyngui.screens: - self.list_data.append((self.show_cv_config, 0, "CV Settings")) - self.list_data.append((self.zyngui.calibrate_touchscreen, 0, "Calibrate Touchscreen")) - self.list_data.append((self.zyngui.cuia_screen_clean, 0, "Clean Screen")) - self.list_data.append((self.bluetooth, 0, "Bluetooth")) + self.list_data.append((self.show_cv_config, 0, "CV Settings", ["Control Voltage configuration.", None])) + self.list_data.append((self.zyngui.calibrate_touchscreen, 0, "Calibrate Touchscreen", ["Show touchscreen calibration.\nTouch each crosshair until it changes color.\nScreen closes after 15s of inactivity.", None])) + self.list_data.append((self.zyngui.cuia_screen_clean, 0, "Clean Screen", ["10s countdown with no touch trigger. Allows screen to be cleaned without triggering any action.", None])) + self.list_data.append((self.bluetooth, 0, "Bluetooth", ["Scan, enable and configure Bluetooth devices.\n\nMust enable Bluetooth here to access BLE MIDI devices. Also supports HID devices.", None])) self.list_data.append((None, 0, "> TEST")) - self.list_data.append((self.test_audio, 0, "Test Audio")) - self.list_data.append((self.test_midi, 0, "Test MIDI")) + self.list_data.append((self.test_audio, 0, "Test Audio", ["Play an audio track to test audio output.\n\nPress BACK to cancel playback.", "headphones.png"])) + self.list_data.append((self.test_midi, 0, "Test MIDI", ["Play a MIDI track to test MIDI output.\n\nThis will play the MIDI through any loaded chains.\nPress BACK to cancel playback.", "midi_logo.png"])) if zynthian_gui_config.control_test_enabled: - self.list_data.append((self.control_test, 0, "Test control HW")) + self.list_data.append((self.control_test, 0, "Test control HW", ["Test system hardware.", None])) self.list_data.append((None, 0, "> SYSTEM")) if self.zyngui.capture_log_fname: - self.list_data.append((self.workflow_capture_stop, 0, "\u2612 Capture Workflow")) + self.list_data.append((self.workflow_capture_stop, 0, "\u2612 Capture Workflow", ["End workflow capture session", None])) else: - self.list_data.append((self.workflow_capture_start, 0, "\u2610 Capture Workflow")) + self.list_data.append((self.workflow_capture_start, 0, "\u2610 Capture Workflow", ["Start workflow capture session.\n\nZynthian display, encoder and button actions are saved to file until this option is deselected.", None])) if self.state_manager.update_available: - self.list_data.append((self.update_software, 0, "Update Software")) + self.list_data.append((self.update_software, 0, "Update Software", ["Updates zynthian firmware and software from Internet.\n\nThis option is only shown when there are updates availale, as indicated by the \u21bb icon in the topbar.\nUpdates may take several minutes. Do not poweroff during an update.", None])) # self.list_data.append((self.update_system, 0, "Update Operating System")) # self.list_data.append((None, 0, "> POWER")) # self.list_data.append((self.restart_gui, 0, "Restart UI")) if zynthian_gui_config.debug_thread: - self.list_data.append((self.exit_to_console, 0, "Exit")) - self.list_data.append((self.reboot, 0, "Reboot")) - self.list_data.append((self.power_off, 0, "Power Off")) + self.list_data.append((self.exit_to_console, 0, "Exit", ["Stop zynthian UI but do not reboot.", None])) + self.list_data.append((self.reboot, 0, "Reboot", ["Reboot (restart) zynthian.", None])) + self.list_data.append((self.power_off, 0, "Power Off", ["Turn off zynthian.\n\nPower is still fed to the device but it is effectively off.", None])) super().fill_list() self.filling_list = False From d1118cce0f979885fc472144a9f0e4bd2554ceaa Mon Sep 17 00:00:00 2001 From: riban Date: Mon, 23 Dec 2024 09:31:10 +0000 Subject: [PATCH 116/117] Adds Bluetooth menu info --- icons/bluetooth.png | Bin 0 -> 5876 bytes zyngui/zynthian_gui_admin.py | 2 +- zyngui/zynthian_gui_bluetooth.py | 16 ++++++++-------- 3 files changed, 9 insertions(+), 9 deletions(-) create mode 100644 icons/bluetooth.png diff --git a/icons/bluetooth.png b/icons/bluetooth.png new file mode 100644 index 0000000000000000000000000000000000000000..a753cba10aad9eb457974104c6b5e7a8eacd1463 GIT binary patch literal 5876 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 TEST")) self.list_data.append((self.test_audio, 0, "Test Audio", ["Play an audio track to test audio output.\n\nPress BACK to cancel playback.", "headphones.png"])) diff --git a/zyngui/zynthian_gui_bluetooth.py b/zyngui/zynthian_gui_bluetooth.py index dbf5a6678..95b596156 100644 --- a/zyngui/zynthian_gui_bluetooth.py +++ b/zyngui/zynthian_gui_bluetooth.py @@ -30,7 +30,7 @@ from subprocess import Popen, PIPE # Zynthian specific modules -from zyngui.zynthian_gui_selector import zynthian_gui_selector +from zyngui.zynthian_gui_selector_info import zynthian_gui_selector_info from zyngui import zynthian_gui_config import zynconf @@ -39,7 +39,7 @@ # ------------------------------------------------------------------------------ -class zynthian_gui_bluetooth(zynthian_gui_selector): +class zynthian_gui_bluetooth(zynthian_gui_selector_info): def __init__(self): self.proc = None @@ -49,7 +49,7 @@ def __init__(self): self.ble_controllers = {} self.ble_devices = {} # Map of BLE devices, indexed by device address self.pending_actions = [] # List of BLE commands to queue - super().__init__('Bluetooth', True) + super().__init__('Bluetooth') self.select_path.set("Bluetooth") def build_view(self): @@ -73,16 +73,16 @@ def fill_list(self): if zynthian_gui_config.bluetooth_enabled: self.list_data.append( - ("stop_bluetooth", None, "\u2612 Enable Bluetooth")) + ("stop_bluetooth", None, "\u2612 Enable Bluetooth", ["Bluetooth is enabled.\n\nSelect to disable Bluetooth.", "bluetooth.png"])) if len(self.ble_controllers) == 0: self.list_data.append( - (None, None, "No Bluetooth controllers detected!")) + (None, None, "No Bluetooth controllers detected!", ["There are not Bluetooth controllers attached to zynthian. You may connect a Bluetooth USB device.", "bluetooth.png"])) super().fill_list() return for ctrl in sorted(self.ble_controllers.keys()): chk = "\u2612" if self.ble_controllers[ctrl]["enabled"] else "\u2610" self.list_data.append( - ("enable_controller", ctrl, f" {chk} {self.ble_controllers[ctrl]['alias']}")) + ("enable_controller", ctrl, f" {chk} {self.ble_controllers[ctrl]['alias']}", ["Enable/disable Bluetooth controller.\n\nOnly enable a single controller. It is advised to use a USB Bluetooth adapter because the Raspberry Pi onboard adapter has poor range.", "bluetooth.png"])) self.list_data.append((None, None, "Devices")) for addr, data in self.ble_devices.items(): # [name, paired, trusted, connected, is_midi] @@ -93,10 +93,10 @@ def fill_list(self): if data[3]: title += "\uf293 " title += data[0] - self.list_data.append((f"BLE:{addr}", addr, title)) + self.list_data.append((f"BLE:{addr}", addr, title, ["Enable/disable this USB device.\n\nEnabling a device will pair it with zynthian. This state will be remembered.", "bluetooth.png"])) else: self.list_data.append( - ("start_bluetooth", None, "\u2610 Enable Bluetooth")) + ("start_bluetooth", None, "\u2610 Enable Bluetooth", ["Bluetooth is disabled.\n\nSelect to enable Bluetooth.", "bluetooth.png"])) super().fill_list() From c37cd1c2e84151d67ce233d63abb45d0cba27627 Mon Sep 17 00:00:00 2001 From: riban Date: Mon, 23 Dec 2024 11:27:49 +0000 Subject: [PATCH 117/117] Fix midi_instrument icon. (Where should those black keys be???) --- icons/midi_instrument.png | Bin 1957 -> 4094 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/icons/midi_instrument.png b/icons/midi_instrument.png index b67ec418c143efa1880791b2f3e12bcc22c39303..9b539f8eb75e2148a6e8e29510320caa382c3130 100644 GIT binary patch literal 4094 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 V%1@9DQ#oAlhMuWzxsJ=D{{W4@a7O?D literal 1957 zcmah}c{Cdc8c!W%oXbv2Yniv~=pb#`F*T~yZc$R|Xo7@oo4PtRRaHqHajUu}(m0|h zT6K;f5?83Y>Pm$O;)pooj3kc66_I84&0n)S`@Q#l@BO~_oxk_KFVWH7T2bz-8~^}N z1ld^L0RT<_k5yLk$YcLL!uklfINCw1_)(E606<`1AOe9vB9Xzt!66|bp`oED6e=t% zEId3sA|fI(GBPSEDmpqkE-o%HF%gYMr=+A{Fql`bUSYA=)YR1U^mH5!mzkOQ`t|Fa zoSfX;+`PQJ{QUg4Z{NOq_pYFzps=v8xVX5aq@=X8w5+VGyu2Kb$5&KTR903J2m~UL zNFtGHYHDh0YwPOj>g($p8XCxCa${p-OG`^@YinCuTYGzZM@Pq}PoKKGyL)iJ3Bi!H#a{&&t|h178Vv47nhcnmY0`TR#sM5S2-Nc+S(eI%jNNSd_I4D zeSKqNV{>zJYisMvmoEZ=V0(LeXJ==3cXw}ZZ-0MZC=?zX9Ee1s!^1ZD4@_zy+OSJrP`V?fH>Y1_8QlC9^EAsis zEd`aKE~ctvZt13b5~Ra9YfH($(0jx+ja-#|+68tCi>ipESFaAVr03emaLix{UEsEC z{gVucKGdOALLTqoiF{h>O!X*kH#z0&W%aCV#)~`AtK(Kva__Eb)&#M{B^$%2iHq>J zznjW(?0oY=?CT)nTziNMq?UCxq4`@W$eJ2Lxg-oW2FdOmRdaD3g<{;H+88J!p}9!3 zeqlG`DoXFeE*Q0~tbf*4iePE%qGgTyyM}wC+CAny#ESZhPoCsKT9Y(g)1s+aYcB(! zZB)_td7Nn&)o;ch<-5~pBG|T{xAUasgqA#UIKPboTYT&Q*%Qo_AM*$nz`-GAZnD{c z>cU8^RPz-9duMt}891W!Ih0(t2Hf}X>$u!h@A$}*W!KQf0uMMr%2B)$%aKqfSjK3X zNq;pylvSi|bN=bW=HL>oBRu1a+>R2ie& z{cKyi*at)TmYdc-suEs#YJ8w4Y2ERCw=Wtvy_wO9({BXF-+%TrZvl=rPkKT>G5d;3W~-v=FqR@8*V<=X#J`4bX#7#{e2;UyeAlm$ zdst?(pi86Qp}7nrCMgf(ygvuF-Zo`Y5=;b%e8qu89!7$t~o1D3)eNAsH1BILg1Cw(r<8zbdYCOyyicP-t!D9hIo zpo=7_m0lT3&k+uOs5))As1cR`-=LO-_^6l*!-Fyo2Rc2(VFn=G!z~!ePjf*8hy*5lPW4sqtq8lK&KYRb~)fepIo2bifhv; zXwP1T1an`V)WBB_VXTnEV^3p@<77Tf&5?Nn=Ft3-u!JQZ>gUZj?vo7Ux0)QX1KXuC3XHw4f+E; zf;#NqYp~!jxDoNUPWY|X#;;EQE(LE-fJ9eoi)XAg@Bt!U{*EEBFCqR;zXD_96gNhs zt!0Z71R3fyb*qB3C~+RWt2X&*LK{}T(7cp#3F@(TKFFz~eoEt7vKE0o%Qc_ARMmJ5 z!|-}eDBP)k{6e_g>}7^u40T86=zifKUltW^`)~FDAB054HcP8&ZMy-hZp{}5aBt_P z;C%w>?;5+f|LKSugDR-pe>TrJf%hc*c$TOU)A>a9_tXCYhp8Uu=*}JqgW<269&C0c z8xe1n!Os-PLbWvwJe?N4u@zxean;Dyz?Hq}=BDh$>a~Guh6#Tl%O8Z_Sasoh Ubo(yH9{qFx$jaWb@&-KSUsiAQQvd(}