diff --git a/icons/audio_input.png b/icons/audio_input.png
new file mode 100644
index 000000000..7db5701ca
Binary files /dev/null and b/icons/audio_input.png differ
diff --git a/icons/audio_options.png b/icons/audio_options.png
new file mode 100644
index 000000000..81d1d885e
Binary files /dev/null and b/icons/audio_options.png differ
diff --git a/icons/audio_output.png b/icons/audio_output.png
new file mode 100644
index 000000000..d9d02db80
Binary files /dev/null and b/icons/audio_output.png differ
diff --git a/icons/audio_processor.png b/icons/audio_processor.png
new file mode 100644
index 000000000..93738d954
Binary files /dev/null and b/icons/audio_processor.png differ
diff --git a/icons/delete_icons.xcf b/icons/delete_icons.xcf
deleted file mode 100644
index c562de529..000000000
Binary files a/icons/delete_icons.xcf and /dev/null differ
diff --git a/icons/folder.png b/icons/folder.png
new file mode 100644
index 000000000..937a0f061
Binary files /dev/null and b/icons/folder.png differ
diff --git a/icons/metronome.png b/icons/metronome.png
new file mode 100644
index 000000000..349b03b68
Binary files /dev/null and b/icons/metronome.png differ
diff --git a/icons/metronome.svg b/icons/metronome.svg
new file mode 100644
index 000000000..b0339a1ac
--- /dev/null
+++ b/icons/metronome.svg
@@ -0,0 +1,12 @@
+
+
+
diff --git a/icons/midi_input.png b/icons/midi_input.png
new file mode 100644
index 000000000..b61f223fe
Binary files /dev/null and b/icons/midi_input.png differ
diff --git a/icons/midi_output.png b/icons/midi_output.png
new file mode 100644
index 000000000..8526c2c92
Binary files /dev/null and b/icons/midi_output.png differ
diff --git a/icons/midi_processor.png b/icons/midi_processor.png
new file mode 100644
index 000000000..1c999eb8c
Binary files /dev/null and b/icons/midi_processor.png differ
diff --git a/icons/note_range.png b/icons/note_range.png
new file mode 100644
index 000000000..8b2830560
Binary files /dev/null and b/icons/note_range.png differ
diff --git a/icons/synth_processor.png b/icons/synth_processor.png
new file mode 100644
index 000000000..c3bfc606e
Binary files /dev/null and b/icons/synth_processor.png differ
diff --git a/zynautoconnect/zynthian_autoconnect.py b/zynautoconnect/zynthian_autoconnect.py
index 8294d18e0..b01f5c22b 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
@@ -159,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)
@@ -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/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/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 = ""
diff --git a/zyngine/zynthian_controller.py b/zyngine/zynthian_controller.py
index b02d4c3c8..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()
@@ -614,6 +610,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:
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_sooperlooper.py b/zyngine/zynthian_engine_sooperlooper.py
index 0c4b07f3c..46687b7b3 100644
--- a/zyngine/zynthian_engine_sooperlooper.py
+++ b/zyngine/zynthian_engine_sooperlooper.py
@@ -378,18 +378,18 @@ def __init__(self, state_manager=None):
loop_labels.append(str(i + 1))
self._ctrls = [
#symbol, {options}, midi_cc
- ['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],
+ ['record', {'name': 'record', 'value': 0, 'value_max': 1, 'labels': ['off', 'on'], 'is_toggle': True}],
+ ['overdub', {'name': 'overdub', 'value': 0, 'value_max': 1, 'labels': ['off', 'on'], 'is_toggle': True}],
+ ['multiply', {'name': 'multiply', 'value': 0, 'value_max': 1, 'labels': ['off', 'on'], 'is_toggle': True}],
+ ['replace', {'name': 'replace', 'value': 0, 'value_max': 1, 'labels': ['off', 'on'], 'is_toggle': True}],
+ ['substitute', {'name': 'substitute', 'value': 0, 'value_max': 1, 'labels': ['off', 'on'], 'is_toggle': True}],
+ ['insert', {'name': 'insert', 'value': 0, 'value_max': 1, 'labels': ['off', 'on'], 'is_toggle': True}],
['undo/redo', {'value': 1, 'labels': ['<', '<>', '>']}],
['prev/next', {'value': 63, 'value_max': 127, 'labels': ['<', '<>', '>']}],
- ['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],
+ ['trigger', {'name': 'trigger', 'value': 0, 'value_max': 1, 'labels': ['off', 'on'], 'is_toggle': True}],
+ ['mute', {'name': 'mute', 'value': 0, 'value_max': 1, 'labels': ['off', 'on'], 'is_toggle': True}],
+ ['oneshot', {'name': 'oneshot', 'value': 0, 'value_max': 1, 'labels': ['off', 'on'], 'is_toggle': True}],
+ ['pause', {'name': 'pause', 'value': 0, 'value_max': 1, 'labels': ['off', 'on'], 'is_toggle': True}],
['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}],
@@ -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': 'direct cc', 'value': 1, 'labels':['off', 'on']}]
+ ['global_cc', {'name': 'midi cc to selected loop', 'value': 1, 'labels':['off', 'on']}]
]
# Controller Screens
@@ -460,11 +460,6 @@ 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:
@@ -477,17 +472,6 @@ 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()
# ---------------------------------------------------------------------------
@@ -600,7 +584,6 @@ def get_controllers_dict(self, processor):
return processor.controllers_dict
def send_controller_value(self, zctrl):
- #logging.warning(f"{zctrl.symbol} {zctrl.value}")
if zctrl.symbol == "global_cc":
self.global_cc_binding = zctrl.value != 0
return
@@ -659,7 +642,7 @@ def send_controller_value(self, zctrl):
if self.pedal_taps:
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)
+ self.select_loop(zctrl.value - 1, True)
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
@@ -710,7 +693,6 @@ def cb_osc_all(self, path, args, types, src):
return
try:
processor = self.processors[0]
- 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])
@@ -769,7 +751,7 @@ def cb_osc_all(self, path, args, types, src):
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:
+ if self.selected_loop is not None and self.selected_loop > self.loop_count:
self.select_loop(self.loop_count - 1, True)
self.monitors_dict['loop_count'] = self.loop_count
@@ -802,8 +784,6 @@ def cb_osc_all(self, path, args, types, src):
self.monitors_dict[args[1]] = args[2]
else:
self.monitors_dict[f"{args[1]}_{args[0]}"] = args[2]
- #if args[1] in ['loop_len', 'rate_output', 'mute']:
- # logging.warning("Monitor: Loop %d %s=%0.2f", args[0], args[1], args[2])
elif path == 'error':
logging.error(f"SooperLooper daemon error: {args[0]}")
except Exception as e:
@@ -821,7 +801,7 @@ def update_state(self, loop):
return
try:
current_state = self.state[loop]
- logging.warning(f"loop: {loop} state: {current_state}")
+ #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:
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():
diff --git a/zyngine/zynthian_state_manager.py b/zyngine/zynthian_state_manager.py
index 891bdf35d..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:
@@ -1461,9 +1443,12 @@ 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
+ 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:
@@ -1597,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:
@@ -1632,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:
@@ -2000,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}")
@@ -2071,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:
@@ -2127,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(
@@ -2186,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
@@ -2204,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
@@ -2247,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)
@@ -2261,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):
@@ -2766,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:
@@ -2781,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
diff --git a/zyngui/__init__.py b/zyngui/__init__.py
index 00fd347cd..261a414af 100644
--- a/zyngui/__init__.py
+++ b/zyngui/__init__.py
@@ -35,7 +35,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 6fd396a88..0dee653cb 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
@@ -167,8 +165,7 @@ def __init__(self):
# 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()
@@ -190,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"),
@@ -228,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:
@@ -240,7 +233,12 @@ def write_capture_log(self, message):
# ---------------------------------------------------------------------------
def init_wsleds(self):
- if zynthian_gui_config.check_wiring_layout("Z2"):
+ if zynthian_gui_config.touch_keypad:
+ if zynthian_gui_config.touch_keypad_option == "V5":
+ 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()
@@ -261,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']
@@ -272,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):
@@ -290,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))
@@ -307,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))
@@ -324,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))
@@ -349,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:
@@ -368,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):
@@ -388,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
@@ -402,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}'")
@@ -681,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}")
@@ -702,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:
@@ -773,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()
@@ -983,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
@@ -1287,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:
@@ -1306,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()
@@ -1324,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)
@@ -1336,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)
@@ -1440,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
@@ -1470,15 +1435,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
@@ -1503,8 +1466,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! ")
@@ -1524,14 +1486,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
@@ -1593,8 +1553,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()
@@ -1684,8 +1643,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):
@@ -1709,11 +1667,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):
@@ -1755,16 +1711,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
@@ -1772,8 +1726,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
@@ -1885,8 +1838,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.
@@ -1904,10 +1856,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!")
@@ -1922,10 +1872,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!")
@@ -1940,10 +1888,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!")
@@ -1957,10 +1903,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!")
@@ -1994,8 +1938,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):
@@ -2163,16 +2106,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
@@ -2234,8 +2173,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)
@@ -2330,8 +2268,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:
@@ -2349,12 +2286,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)
@@ -2422,7 +2357,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):
@@ -2449,16 +2386,15 @@ 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)
+ zynswitch_cuia_ts[i] = monotonic()
+ zpi = zynthian_gui_config.zynpot2switch.index(i)
self.zynpot_pr_state[zpi] = 1
pr = 1
except:
@@ -2471,8 +2407,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
@@ -2491,8 +2426,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]
@@ -2526,10 +2460,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()
@@ -2613,12 +2545,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
diff --git a/zyngui/zynthian_gui_admin.py b/zyngui/zynthian_gui_admin.py
index 0f83d48bf..48d13e4d8 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
#
# ******************************************************************************
#
@@ -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,109 +109,95 @@ 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"))
+ 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.bluetooth, 0, "Bluetooth"))
self.list_data.append((None, 0, "> TEST"))
self.list_data.append((self.test_audio, 0, "Test Audio"))
@@ -222,11 +207,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"))
@@ -259,8 +242,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):
@@ -279,8 +261,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")
@@ -331,8 +312,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()
@@ -361,7 +341,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)
@@ -391,7 +370,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:
@@ -418,7 +396,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()
@@ -472,13 +450,31 @@ 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 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 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 touch_navigation_cb_confirmed(self, value=""):
+ zynconf.save_config({"ZYNTHIAN_UI_TOUCH_NAVIGATION2": value})
+ self.restart_gui()
+
# -------------------------------------------------------------------------
# Global Transpose editing
# -------------------------------------------------------------------------
@@ -605,13 +601,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")
@@ -665,8 +660,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")
@@ -675,8 +669,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_audio_in.py b/zyngui/zynthian_gui_audio_in.py
index 1cb4b7627..de76a19ef 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_input.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_input.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_base.py b/zyngui/zynthian_gui_base.py
index 2fdeae0a3..78b184436 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.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 // 7
- self.width = zynthian_gui_config.display_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 = zynthian_gui_config.screen_height - self.topbar_height
# Status Area Parameters
self.status_l = int(self.width * 0.25)
@@ -85,10 +84,10 @@ 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.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
self.select_path_dir = 2
@@ -100,7 +99,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)
@@ -119,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
@@ -131,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 = ""
@@ -215,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()
@@ -277,19 +273,14 @@ 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))
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:
@@ -378,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()
@@ -414,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()
@@ -458,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()
@@ -504,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=0, sticky='nsew')
+ self.grid(row=0, column=zynthian_gui_config.main_screen_column, sticky='nsew')
self.propagate(False)
self.main_frame.focus()
@@ -877,10 +864,10 @@ 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.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 = 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_chain_options.py b/zyngui/zynthian_gui_chain_options.py
index a2783ceeb..f37bdccd8 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,70 +58,81 @@ 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, "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"]):
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.export_chain, None, "Export chain as snapshot..."))
+ 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...", ["Save the selected chain as a snapshot which may then be imported into another snapshot.", None]))
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"))
+ 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()
@@ -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"Options for MIDI processor '{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"Options for synth processor '{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"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:
@@ -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"Options for post-fader audio processor '{name}'", "audio_processor.png"]))
indent += 1
return res
@@ -314,31 +329,31 @@ def chain_audio_routing(self):
def audio_options(self):
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, False, None)
self.zyngui.show_screen('option')
def audio_menu_cb(self, options, params):
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)
+ elif params == 'ms':
+ self.zyngui.state_manager.zynmixer.toggle_ms(self.chain.mixer_chan)
self.audio_options()
def chain_audio_capture(self):
@@ -377,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')
diff --git a/zyngui/zynthian_gui_config.py b/zyngui/zynthian_gui_config.py
index 0f501e036..f23a6ee86 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
@@ -52,10 +51,10 @@
# Wiring layout
# ------------------------------------------------------------------------------
-wiring_layout = os.environ.get('ZYNTHIAN_WIRING_LAYOUT', "DUMMIES")
-if wiring_layout == "DUMMIES":
- logging.info(
- "No Wiring Layout configured. Only touch interface is available.")
+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)
@@ -131,10 +130,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
@@ -245,6 +242,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():
@@ -327,15 +325,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 +366,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 +396,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 +451,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)
@@ -538,32 +512,67 @@ 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))
-
-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
+touch_navigation = os.environ.get('ZYNTHIAN_UI_TOUCH_NAVIGATION2', '_UNDEF_')
+
+# Backward compatibility
+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":
+ 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
+
+try:
+ force_enable_cursor = int(os.environ.get('ZYNTHIAN_UI_ENABLE_CURSOR', 0))
+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 =="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
# ------------------------------------------------------------------------------
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))
# ------------------------------------------------------------------------------
# Audio Options
@@ -592,8 +601,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
@@ -637,11 +645,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"
@@ -689,13 +695,37 @@ def color_variant(hex_color, brightness_offset=1):
if not font_size:
font_size = int(display_width / 40)
+ touch_keypad = None
+ # 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!
+ 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
+
# 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
@@ -704,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")
@@ -722,7 +752,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:
@@ -738,8 +768,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 eabb7c131..ac5155ac8 100644
--- a/zyngui/zynthian_gui_confirm.py
+++ b/zyngui/zynthian_gui_confirm.py
@@ -44,11 +44,13 @@ def __init__(self):
self.callback = None
self.callback_params = None
self.zyngui = zynthian_gui_config.zyngui
+ 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,
- 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()
@@ -56,7 +58,7 @@ def __init__(self):
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,
@@ -66,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,
@@ -75,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=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"),
+ zynthian_gui_config.font_family,
+ zynthian_gui_config.font_size*2, "normal"),
text="No",
width=3,
justify=tkinter.LEFT,
@@ -89,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=zynthian_gui_config.display_height, anchor=tkinter.SW)
+ self.no_text_label.place(x=0, y=self.height, anchor=tkinter.SW)
def hide(self):
if self.shown:
@@ -108,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()
+ 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.py b/zyngui/zynthian_gui_control.py
index 323321778..9ac3e7a37 100644
--- a/zyngui/zynthian_gui_control.py
+++ b/zyngui/zynthian_gui_control.py
@@ -460,6 +460,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()
@@ -481,7 +485,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
@@ -665,28 +669,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:
@@ -694,7 +700,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:
@@ -704,10 +710,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
@@ -768,8 +773,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:
diff --git a/zyngui/zynthian_gui_control_xy.py b/zyngui/zynthian_gui_control_xy.py
old mode 100644
new mode 100755
index 796fcef74..411f50469
--- 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
#
# ******************************************************************************
#
@@ -50,13 +50,13 @@ def __init__(self):
# Init X vars
self.padx = 24
- self.width = zynthian_gui_config.display_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
@@ -64,8 +64,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.screen_width,
+ height=zynthian_gui_config.screen_height,
bg=zynthian_gui_config.color_panel_bg)
# Create Canvas
@@ -81,23 +81,22 @@ 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(
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)
def build_view(self):
@@ -113,10 +112,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()
+ 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_details.py b/zyngui/zynthian_gui_details.py
index 570bec4ed..7db49e088 100644
--- a/zyngui/zynthian_gui_details.py
+++ b/zyngui/zynthian_gui_details.py
@@ -43,9 +43,9 @@ def __init__(self):
# Textarea
self.textarea = tkinter.Text(self.main_frame,
width=int(
- zynthian_gui_config.display_width/(zynthian_gui_config.font_size + 5)),
+ zynthian_gui_config.screen_width/(zynthian_gui_config.font_size + 5)),
height=int(
- zynthian_gui_config.display_height/(zynthian_gui_config.font_size + 8)),
+ 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',
diff --git a/zyngui/zynthian_gui_help.py b/zyngui/zynthian_gui_help.py
index eb666e308..41284533a 100644
--- a/zyngui/zynthian_gui_help.py
+++ b/zyngui/zynthian_gui_help.py
@@ -54,7 +54,13 @@ def __init__(self):
self.touch_last_release_ts = 0
# 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,
+ 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
# Bind events
@@ -88,7 +94,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=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 83352cb8d..c71495c52 100644
--- a/zyngui/zynthian_gui_info.py
+++ b/zyngui/zynthian_gui_info.py
@@ -41,14 +41,14 @@ 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.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.font_size + 8)),
+ 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"),
@@ -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=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 0c75bd524..4bbc3c8ee 100644
--- a/zyngui/zynthian_gui_keyboard.py
+++ b/zyngui/zynthian_gui_keyboard.py
@@ -58,33 +58,31 @@ 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.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))
+ 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.screen_width,
+ height=zynthian_gui_config.screen_height,
bg=zynthian_gui_config.color_bg)
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)
@@ -138,9 +136,9 @@ def refresh_keys(self):
elif self.alt:
if self.shift:
self.keys = ['\\', '|', '@', '/', '*', '=', '\"', '\'', '?', '¡',
- 'Ä', 'Ë', 'Ï', 'Ö', 'Ü', 'Â', 'Ê', 'Î', 'Ô', 'Û',
- 'Ñ', 'Ç', 'Ẅ', 'Ŵ', 'Ĉ', 'Ÿ', 'Ŷ', 'Ŝ', 'Ĝ', 'Ḧ',
- 'Ĥ', 'Ĵ', 'Ẑ', 'Ẍ', '{', '}', '~', '^', ':', '_']
+ 'Ä', 'Ë', 'Ï', 'Ö', 'Ü', 'Â', 'Ê', 'Î', 'Ô', 'Û',
+ 'Ñ', 'Ç', 'Ẅ', 'Ŵ', 'Ĉ', 'Ÿ', 'Ŷ', 'Ŝ', 'Ĝ', 'Ḧ',
+ 'Ĥ', 'Ĵ', 'Ẑ', 'Ẍ', '{', '}', '~', '^', ':', '_']
else:
self.keys = ['á', 'é', 'í', 'ó', 'ú', 'à', 'è', 'ì', 'ò', 'ù',
'ä', 'ë', 'ï', 'ö', 'ü', 'â', 'ê', 'î', 'ô', 'û',
@@ -159,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
@@ -171,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])
@@ -191,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(':')
@@ -239,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:
@@ -258,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)
@@ -275,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):
@@ -306,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()
+ 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 8d38e96bd..4b710580c 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.screen_width
+ self.height = zynthian_gui_config.screen_height
# Canvas for loading image animation
self.canvas = tkinter.Canvas(
zynthian_gui_config.top,
@@ -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()
+ 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
diff --git a/zyngui/zynthian_gui_midi_config.py b/zyngui/zynthian_gui_midi_config.py
index bb628382c..ad0497cae 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
@@ -63,15 +64,18 @@ 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):
+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 +128,41 @@ 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 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]}"))
+ 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]}"))
+ 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]}"))
+ 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]}"))
+ 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]}"))
+ 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]}"))
+ self.list_data.append((port.aliases[0], idev, f"\u2612 {port.aliases[1]}",
+ [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]}"))
+ self.list_data.append((port.aliases[0], idev, f"\u2610 {port.aliases[1]}",
+ [f"Chain's MIDI output disconnected from '{port.aliases[1]}'.\nBold select to show more options.", "midi_output.png"]))
- 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 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}", icon]))
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}", icon]))
def atoi(text):
return int(text) if text.isdigit() else text
@@ -196,10 +205,8 @@ 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. Converts audio input to MIDI note on/off commands.")
# Remove "Internal Devices" title if section is empty
if len(self.list_data) == nint:
@@ -212,13 +219,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 +231,20 @@ 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",
+ "NetUMP. Provides MIDI over an IP connection using NetUMP protocol (MIDI 2.0).")
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. Provides MIDI over an IP connection using RTP-MIDI protocol (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. Provides MIDI over an IP connection using UDP/IP multicast (ipMIDI).")
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"))
@@ -268,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()}"))
+ self.list_data.append((chain_id, None, f"\u2612 {prefix}{chain.get_name()}",
+ [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()}"))
+ self.list_data.append((chain_id, None, f"\u2610 {prefix}{chain.get_name()}",
+ [f"Chain's MIDI output disconnected from chain '{prefix}{chain.get_name()}'.",
+ "midi_output.png"]))
super().fill_list()
@@ -288,13 +278,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)
@@ -326,38 +316,52 @@ 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
- if zynautoconnect.get_midi_in_dev_mode(idev):
- options[f'\u2610 {ZMIP_MODE_ACTIVE} Multitimbral mode '] = "MULTI"
+ options = {}
+ if self.input:
+ options["MIDI Input Mode"] = None
+ mode_info = "Toggle input mode.\n\n"
+ if zynautoconnect.get_midi_in_dev_mode(idev):
+ 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:
- options[f'\u2612 {ZMIP_MODE_MULTI} Multitimbral mode '] = "ACTI"
-
- 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"
- else:
- options[f"\u2610 {ZMIP_MODE_CONTROLLER} Controller driver"] = "LOAD_DRIVER"
- port = zynautoconnect.devices_in[idev]
+ mode_info += f"{title}. Translate MIDI channel. Send to active chain only."
+ options[title] = ["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"
- options[f"Rename port '{port.aliases[0]}'"] = port
- # 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
+ 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} 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} 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[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, False, None)
+ self.zyngui.show_screen('option')
+ except:
+ pass # Port may have disappeared whilst building menu
def menu_cb(self, option, params):
try:
@@ -375,10 +379,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.update_list()
except:
pass # Ports may have changed since menu opened
@@ -414,6 +420,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:
diff --git a/zyngui/zynthian_gui_midi_key_range.py b/zyngui/zynthian_gui_midi_key_range.py
index faf369f5f..123706d19 100644
--- a/zyngui/zynthian_gui_midi_key_range.py
+++ b/zyngui/zynthian_gui_midi_key_range.py
@@ -67,7 +67,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 = self.width
self.piano_canvas_height = self.height // 4
self.main_frame.rowconfigure(2, weight=1)
@@ -176,7 +176,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:
diff --git a/zyngui/zynthian_gui_option.py b/zyngui/zynthian_gui_option.py
index df1a02d50..bc9d7031a 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,9 +45,9 @@ 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):
+ 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
@@ -93,7 +94,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()
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()
diff --git a/zyngui/zynthian_gui_splash.py b/zyngui/zynthian_gui_splash.py
index 66be8f4c6..cd123b17f 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 zynthian_gui_config.touch_keypad:
+ zynthian_gui_config.touch_keypad.show()
def show(self, text):
if self.zyngui.test_mode:
@@ -80,6 +82,8 @@ def show(self, text):
except:
pass
if not self.shown:
+ if zynthian_gui_config.touch_keypad:
+ zynthian_gui_config.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..29926242a
--- /dev/null
+++ b/zyngui/zynthian_gui_touchkeypad_v5.py
@@ -0,0 +1,369 @@
+#!/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 io import BytesIO
+from PIL import Image, ImageTk
+
+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
+ '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'),
+ ('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')
+}
+
+# ------------------------------------------------------------------------------
+# Zynthian Touchscreen Keypad V5 Class
+# ------------------------------------------------------------------------------
+
+
+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
+ 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
+
+ # 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 (avoid discarding 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]['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
+ 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]['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))
+
+ # 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
+
+ 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,
+ height=1,
+ bg=self.bg_color,
+ fg=self.text_color,
+ activebackground=self.bg_color,
+ activeforeground=self.border_color,
+ highlightbackground=self.border_color,
+ highlightcolor=self.border_color,
+ highlightthickness=1,
+ bd=0,
+ relief='flat')
+ # set default button state (<=color)
+ self.btnstate[n] = self.text_color
+ if label.startswith('_'):
+ # button contains an icon/image instead of a label
+ img_width = int(1.8 * self.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=img_width)
+ image = Image.open(png)
+ else:
+ png = img_name[:-4]+".png"
+ 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)
+ 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)
+ if label[0] >= '\uf000':
+ font = ("forkawesome", int(1.0 * self.font_size))
+ else:
+ font = (zynthian_gui_config.font_family, int(0.9 * self.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))
+ button.bind('', lambda e: self.cb_button_release(zynswitch, e))
+ return button
+
+ 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] == (mode or color):
+ 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]['default']
+ if label.startswith('_'):
+ # 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)
+ 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, activeforeground=color)
+
+ def refresh_button_label(self, n, mode):
+ text = self.btndefs[n][0].get(mode, self.btndefs[n][0]['default']).replace('/', "\n")
+ self.buttons[n].config(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
+
+ def apply_user_config(self):
+ for n in range(0, 20):
+ 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][0]['default'] = default
+ if alt:
+ self.btndefs[n][0]['alt'] = alt
+ if active:
+ self.btndefs[n][0]['active'] = active
+ 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]
+
diff --git a/zyngui/zynthian_gui_touchscreen_calibration.py b/zyngui/zynthian_gui_touchscreen_calibration.py
index fb7c5d43c..f0f095026 100644
--- a/zyngui/zynthian_gui_touchscreen_calibration.py
+++ b/zyngui/zynthian_gui_touchscreen_calibration.py
@@ -428,6 +428,8 @@ def hide(self):
self.setCalibration(self.device_id, self.ctm)
self.main_frame.grid_forget()
self.shown = False
+ if zynthian_gui_config.touch_keypad:
+ zynthian_gui_config.touch_keypad.show()
# Build display
def build_view(self):
@@ -448,6 +450,8 @@ def build_view(self):
# Show display
def show(self):
+ if zynthian_gui_config.touch_keypad:
+ zynthian_gui_config.touch_keypad.hide()
self.main_frame.grid()
self.onTimer()
self.detect_thread = Thread(
diff --git a/zyngui/zynthian_widget_sooperlooper.py b/zyngui/zynthian_widget_sooperlooper.py
index b474afa26..ee74645d0 100644
--- a/zyngui/zynthian_widget_sooperlooper.py
+++ b/zyngui/zynthian_widget_sooperlooper.py
@@ -63,7 +63,7 @@ def __init__(self, parent):
self.tri_size = int(0.5 * zynthian_gui_config.font_size)
# int(0.70 * self.font_size_sl)
- txt_y = zynthian_gui_config.display_height // 22
+ txt_y = zynthian_gui_config.screen_height // 22
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..0ea8118ae
--- /dev/null
+++ b/zyngui/zynthian_wsleds_v5touch.py
@@ -0,0 +1,128 @@
+#!/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
+ """
+
+ def __init__(self, wsleds):
+ self.wsleds = wsleds
+ self.zyngui = wsleds.zyngui
+ # 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'
+ self.mode_map[wsleds.wscolor_active] = 'active'
+ self.mode_map[wsleds.wscolor_active2] = 'active2'
+
+ 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}" # 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)
+ blended = self.ablend(1-self.wsleds.brightness, fgcolor, bgcolor)
+ color = self.rgb_to_hex(blended)
+ zynthian_gui_config.touch_keypad.set_button_color(index, color, mode)
+
+ def show(self):
+ # nothing to do here
+ pass
+
+ def ablend(self, a, fg, bg):
+ """
+ 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]),
+ 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)
+
+# ---------------------------------------------------------------------------
+# Zynthian WSLeds class for LED emulation on touchscreen keypad V5
+# ---------------------------------------------------------------------------
+
+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 = 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")
+ 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")
+ 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', "#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)
+ 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 = {
+ 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"
+}
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