From 1957eb9bb5cbd11cccb98009190733f3bb069578 Mon Sep 17 00:00:00 2001 From: riban Date: Tue, 24 Dec 2024 19:24:13 +0000 Subject: [PATCH 1/7] Fix software update --- zyngine/zynthian_state_manager.py | 64 ++++++++++++------------------- zyngui/zynthian_gui_admin.py | 17 ++++++++ 2 files changed, 42 insertions(+), 39 deletions(-) diff --git a/zyngine/zynthian_state_manager.py b/zyngine/zynthian_state_manager.py index ed16c7f26..61a0f94d3 100644 --- a/zyngine/zynthian_state_manager.py +++ b/zyngine/zynthian_state_manager.py @@ -2722,58 +2722,44 @@ def allow_rbpi_headphones(self): except: return False + def get_repo_info(self, repo): + path = f"/zynthian/{repo}" + try: + branch = check_output(f"git symbolic-ref -q --short HEAD", + encoding="utf-8", shell=True).strip() + except: + branch = check_output(f"git describe --tags --exact-match", + encoding="utf-8", shell=True).strip() + local_hash = check_output(f"git -C {path} rev-parse {branch}", + encoding="utf-8", shell=True).strip() + try: + remote_hash = check_output(f"git -C {path} ls-remote origin {branch}", + encoding="utf-8", shell=True).strip().split("\t")[0] + except: + remote_hash = "" + tags = check_output(f"git tag --points-at {local_hash}", encoding="utf-8", shell=True).split() + return [branch, local_hash, remote_hash, tags] + def check_for_updates(self): if self.checking_for_updates: return self.checking_for_updates = True def update_thread(): - logging.debug("************ CHECKING FOR UPDATES ... ************") + logging.debug("************ CHECKING FOR UPDATES... ************") try: repos = ["zynthian-ui", "zynthian-sys", "zynthian-webconf", "zynthian-data", "zyncoder"] # If attached to last stable => Detect if new tag relase available - if os.environ.get('ZYNTHIAN_STABLE_TAG', "") == "last": - stable_branch = os.environ.get('ZYNTHIAN_STABLE_BRANCH', "oram") - for repo in repos: - 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") - last_stag = stags[-1].strip() - #logging.debug(f"STABLE TAG RELEASES => {stags}") - if branch != last_stag: - #logging.info(f"For reposiroty '{repo}', current branch ({branch}) != last tag release ({last_stag})!") - self.update_available = True - break - # else => Check for commits to pull - else: - 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] - #logging.debug(f"*********** BRANCH {branch} => local hash {local_hash}, remote hash {remote_hash} ****************") - if local_hash != remote_hash: - self.update_available = True - break + for repo in repos: + branch, local_hash, remote_hash, tags = self.get_repo_info(repo) + #logging.debug(f"*********** BRANCH {branch} => local hash {local_hash}, remote hash {remote_hash} ****************") + if local_hash != remote_hash: + self.update_available = True + break except Exception as e: logging.warning(e) self.checking_for_updates = False - def get_repo_branch(path): - res = check_output(["git", "-C", path, "rev-parse", "--abbrev-ref", "HEAD"], - encoding="utf-8", stderr=STDOUT).strip() - parts = res.split("/", 1) - if len(parts) > 1 and parts[0] == 'heads': - return parts[1] - else: - return res - thread = Thread(target=update_thread, args=()) thread.name = "Check update" thread.daemon = True # thread dies with the program diff --git a/zyngui/zynthian_gui_admin.py b/zyngui/zynthian_gui_admin.py index a8f15ae19..18065fe40 100644 --- a/zyngui/zynthian_gui_admin.py +++ b/zyngui/zynthian_gui_admin.py @@ -210,6 +210,7 @@ def fill_list(self): self.list_data.append((self.workflow_capture_stop, 0, "\u2612 Capture Workflow", ["End workflow capture session", None])) else: self.list_data.append((self.workflow_capture_start, 0, "\u2610 Capture Workflow", ["Start workflow capture session.\n\nZynthian display, encoder and button actions are saved to file until this option is deselected.", None])) + self.list_data.append((self.about, 0, "About...", ["Show information about zynthian version, etc.", None])) if self.state_manager.update_available: self.list_data.append((self.update_software, 0, "Update Software", ["Updates zynthian firmware and software from Internet.\n\nThis option is only shown when there are updates availale, as indicated by the \u21bb icon in the topbar.\nUpdates may take several minutes. Do not poweroff during an update.", None])) # self.list_data.append((self.update_system, 0, "Update Operating System")) @@ -627,6 +628,22 @@ def workflow_capture_stop(self): self.zyngui.stop_capture_log() self.update_list() + def about(self): + self.zyngui.show_info("System Info\n\n") + for repo in ["zyncoder", "zynthian-ui", "zynthian-sys", "zynthian-data", "zynthian-webconf"]: + info = self.state_manager.get_repo_info(repo) + tag = "" + minor = 0 + for t in info[3]: + try: + x,y = t.split(".", 1) + if int(y) > minor: + minor = int(y) + tag = t + except: + pass + self.zyngui.add_info(f"{repo}: {info[0]} {tag}\n") + def update_software(self): logging.info("UPDATE SOFTWARE") self.last_state_action() From 8b0924b60858368a8da843adb429b68b57798f44 Mon Sep 17 00:00:00 2001 From: riban Date: Wed, 25 Dec 2024 13:58:40 +0000 Subject: [PATCH 2/7] Implement git functions in config module --- zynconf/zynthian_config.py | 146 +++++++++++++++++++++++++++++- zyngine/zynthian_state_manager.py | 22 +---- zyngui/zynthian_gui_admin.py | 31 ++++--- 3 files changed, 164 insertions(+), 35 deletions(-) diff --git a/zynconf/zynthian_config.py b/zynconf/zynthian_config.py index f0efd23cd..c5b1bdd6f 100755 --- a/zynconf/zynthian_config.py +++ b/zynconf/zynthian_config.py @@ -30,7 +30,7 @@ from time import sleep from stat import S_IWUSR from shutil import copyfile -from subprocess import check_output +from subprocess import check_output, DEVNULL # ------------------------------------------------------------------------------- # Configure logging @@ -170,11 +170,153 @@ sys_dir = os.environ.get('ZYNTHIAN_SYS_DIR', "/zynthian/zynthian-sys") config_dir = os.environ.get('ZYNTHIAN_CONFIG_DIR', '/zynthian/config') config_fpath = config_dir + "/zynthian_envars.sh" +zynthian_repositories = ["zynthian-sys", "zynthian-ui", "zyncoder", "zynthian-data", "zynthian-webconf"] # ------------------------------------------------------------------------------- -# Config management +# Version configuration # ------------------------------------------------------------------------------- +def is_git_behind(path): + # True if the git repository is behind the upstream version + check_output(f"git -C {path} remote update; git -C {path} status --porcelain -bs | grep behind | wc -l", + encoding="utf-8", shell=True) != '0\n' + +def get_git_branch(path): + # Get the current branch for a git repository or None if detached or invalid repo name + try: + return check_output(f"git -C {path} symbolic-ref -q --short HEAD", + encoding="utf-8", shell=True).strip() + except: + return None + +def get_git_tag(path): + # Get the current tag for a git repository or None if invalid repo name + try: + return check_output(f"git -C {path} describe --tags --exact-match", + encoding="utf-8", shell=True, stderr=DEVNULL).strip() + except: + return None + +def get_git_local_hash(path): + # Get the hash of the current commit for a git branch or None if invalid + try: + return check_output(f"git -C {path} rev-parse origin", + encoding="utf-8", shell=True).strip() + except: + return None + +def get_git_remote_hash(path, branch=None): + # Get the hash of the latest commit for a git branch or for a tag or None if invalid + if branch is None: + branch = get_git_tag(path) + if branch is None: + return None + try: + return check_output(f"git -C {path} ls-remote origin {branch}", + encoding="utf-8", shell=True).strip().split("\t")[0] + except: + return None + +def get_git_version_info(path): + # Get version information about a git repository + local_hash = get_git_local_hash(path) + branch = get_git_branch(path) + tag = get_git_tag(path) + release_name = None + version = None + major_version = 0 + minor_version = 0 + patch_version = 0 + frozen = False + if tag is not None: + # Check if it is a major release channel + frozen = True + parts = tag.split("-", 1) + if len(parts) == 2: + release_name = parts[0] + version = parts[1] + if version: + parts = version.split(".", 3) + major_version = parts[0] + if len(parts) > 2: + patch_version = parts[2] + if len(parts) > 1: + minor_version = parts[1] + else: + # On stable release channel. Check which point release we are on. + tags = check_output(f"git -C {path} tag --points-at {tag}", encoding="utf-8", shell=True).split() + for t in tags: + parts = t.split("-", 1) + if len(parts) != 2 or parts[0] != release_name: + continue + v_parts = parts[1].split(".", 3) + try: + x = int(v_parts[0]) + y = z = 0 + if len(v_parts) > 1: + y = v_parts[1] + if len(v_parts) > 2: + z = int(v_parts[2]) + if x > major_version: + major_version = x + minor_version = y + patch_version = z + elif y > minor_version: + minor_version = y + patch_version = z + elif z > patch_version: + patch_version = z + except: + pass + result = { + "branch": branch, + "tag": tag, + "name": release_name, + "major": major_version, + "minor": minor_version, + "patch": patch_version, + "frozen": frozen, + "local_hash": local_hash + } + return result + +def get_git_tags(path, refresh=False): + # Get list of tags in a git repository + try: + if refresh: + check_output(f"git -C {path} remote update origin --prune", shell=True) + return sorted(check_output(f"git -C {path} tag", encoding="utf-8", shell=True).split(), key=str.casefold) + except: + return [] + +def get_git_branches(path, refresh=False): + # Get list of branches in a git repository + result = [] + if refresh: + check_output(f"git -C {path} remote update origin --prune", shell=True) + for branch in check_output(f"git -C {path} branch -a", encoding="utf-8", shell=True).splitlines(): + branch = branch.strip() + if branch.startswith("*"): + branch = branch[2:] + if branch.startswith("remotes/origin/"): + branch = branch[15:] + if "->" in branch or branch.startswith("(HEAD detached at "): + continue + if branch not in result: + result.append(branch) + return sorted(result, key=str.casefold) + +def get_system_version(): + # Get the overall release version or None if inconsistent repository states + tag = get_git_version_info("/zynthian/zynthian-sys")["tag"] + for repo in zynthian_repositories: + if get_git_version_info(f"/zynthian/{repo}")["tag"] != tag: + return None + return tag + +# ------------------------------------------------------------------------------- +# Config management +# ------------------------------------------------------------------------------- def get_midi_config_fpath(fpath=None): if not fpath: diff --git a/zyngine/zynthian_state_manager.py b/zyngine/zynthian_state_manager.py index 61a0f94d3..4ee76d9d3 100644 --- a/zyngine/zynthian_state_manager.py +++ b/zyngine/zynthian_state_manager.py @@ -2722,24 +2722,6 @@ def allow_rbpi_headphones(self): except: return False - def get_repo_info(self, repo): - path = f"/zynthian/{repo}" - try: - branch = check_output(f"git symbolic-ref -q --short HEAD", - encoding="utf-8", shell=True).strip() - except: - branch = check_output(f"git describe --tags --exact-match", - encoding="utf-8", shell=True).strip() - local_hash = check_output(f"git -C {path} rev-parse {branch}", - encoding="utf-8", shell=True).strip() - try: - remote_hash = check_output(f"git -C {path} ls-remote origin {branch}", - encoding="utf-8", shell=True).strip().split("\t")[0] - except: - remote_hash = "" - tags = check_output(f"git tag --points-at {local_hash}", encoding="utf-8", shell=True).split() - return [branch, local_hash, remote_hash, tags] - def check_for_updates(self): if self.checking_for_updates: return @@ -2751,7 +2733,9 @@ def update_thread(): repos = ["zynthian-ui", "zynthian-sys", "zynthian-webconf", "zynthian-data", "zyncoder"] # If attached to last stable => Detect if new tag relase available for repo in repos: - branch, local_hash, remote_hash, tags = self.get_repo_info(repo) + path = f"/zynthian/{repo}" + local_hash = zynconf.get_git_local_hash(path) + remote_hash = zynconf.get_git_remote_hash(path, "HEAD") #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/zynthian_gui_admin.py b/zyngui/zynthian_gui_admin.py index 18065fe40..cef86bc6c 100644 --- a/zyngui/zynthian_gui_admin.py +++ b/zyngui/zynthian_gui_admin.py @@ -629,20 +629,23 @@ def workflow_capture_stop(self): self.update_list() def about(self): - self.zyngui.show_info("System Info\n\n") - for repo in ["zyncoder", "zynthian-ui", "zynthian-sys", "zynthian-data", "zynthian-webconf"]: - info = self.state_manager.get_repo_info(repo) - tag = "" - minor = 0 - for t in info[3]: - try: - x,y = t.split(".", 1) - if int(y) > minor: - minor = int(y) - tag = t - except: - pass - self.zyngui.add_info(f"{repo}: {info[0]} {tag}\n") + self.zyngui.show_info("System Info\n") + version = zynconf.get_system_version() + if version: + self.zyngui.add_info(f"System software version {version}.\n\n") + else: + self.zyngui.add_info("System software not on consistent version.\n\n") + self.zyngui.add_info("Software Repositories:\n") + for repo in zynconf.zynthian_repositories: + path = f"/zynthian/{repo}" + info = zynconf.get_git_version_info(path) + if info['tag']: + if info['name']: + self.zyngui.add_info(f"{repo}: {info['name']} {info['major']}.{info['minor']}.{info['patch']}\n") + else: + self.zyngui.add_info(f"{repo}: {info['tag']}\n") + else: + self.zyngui.add_info(f"{repo}: {info['branch']}\n") def update_software(self): logging.info("UPDATE SOFTWARE") From bffb4b6a6da91463d7a49d2c458066cb59938f48 Mon Sep 17 00:00:00 2001 From: riban Date: Wed, 25 Dec 2024 22:58:10 +0000 Subject: [PATCH 3/7] Fixes version info --- zynconf/zynthian_config.py | 14 +++++++++----- zyngine/zynthian_state_manager.py | 1 + 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/zynconf/zynthian_config.py b/zynconf/zynthian_config.py index c5b1bdd6f..b3109c096 100755 --- a/zynconf/zynthian_config.py +++ b/zynconf/zynthian_config.py @@ -200,7 +200,7 @@ def get_git_tag(path): def get_git_local_hash(path): # Get the hash of the current commit for a git branch or None if invalid try: - return check_output(f"git -C {path} rev-parse origin", + return check_output(f"git -C {path} rev-parse HEAD", encoding="utf-8", shell=True).strip() except: return None @@ -230,7 +230,6 @@ def get_git_version_info(path): frozen = False if tag is not None: # Check if it is a major release channel - frozen = True parts = tag.split("-", 1) if len(parts) == 2: release_name = parts[0] @@ -242,6 +241,7 @@ def get_git_version_info(path): patch_version = parts[2] if len(parts) > 1: minor_version = parts[1] + frozen = True else: # On stable release channel. Check which point release we are on. tags = check_output(f"git -C {path} tag --points-at {tag}", encoding="utf-8", shell=True).split() @@ -251,10 +251,11 @@ def get_git_version_info(path): continue v_parts = parts[1].split(".", 3) try: + major_version = int(major_version) x = int(v_parts[0]) y = z = 0 if len(v_parts) > 1: - y = v_parts[1] + y = int(v_parts[1]) if len(v_parts) > 2: z = int(v_parts[2]) if x > major_version: @@ -280,11 +281,14 @@ def get_git_version_info(path): } return result +def update_git(path): + check_output(f"git -C {path} remote update origin --prune", shell=True) + def get_git_tags(path, refresh=False): # Get list of tags in a git repository try: if refresh: - check_output(f"git -C {path} remote update origin --prune", shell=True) + update_git(path) return sorted(check_output(f"git -C {path} tag", encoding="utf-8", shell=True).split(), key=str.casefold) except: return [] @@ -293,7 +297,7 @@ def get_git_branches(path, refresh=False): # Get list of branches in a git repository result = [] if refresh: - check_output(f"git -C {path} remote update origin --prune", shell=True) + update_git(path) for branch in check_output(f"git -C {path} branch -a", encoding="utf-8", shell=True).splitlines(): branch = branch.strip() if branch.startswith("*"): diff --git a/zyngine/zynthian_state_manager.py b/zyngine/zynthian_state_manager.py index 4ee76d9d3..6df5d03c2 100644 --- a/zyngine/zynthian_state_manager.py +++ b/zyngine/zynthian_state_manager.py @@ -2734,6 +2734,7 @@ def update_thread(): # If attached to last stable => Detect if new tag relase available for repo in repos: path = f"/zynthian/{repo}" + zynconf.update_git(path) local_hash = zynconf.get_git_local_hash(path) remote_hash = zynconf.get_git_remote_hash(path, "HEAD") #logging.debug(f"*********** BRANCH {branch} => local hash {local_hash}, remote hash {remote_hash} ****************") From 1cc31a2c78c76b9f9caf83e156d6300c16662a6e Mon Sep 17 00:00:00 2001 From: riban Date: Thu, 26 Dec 2024 06:16:15 +0000 Subject: [PATCH 4/7] Fix get tag --- zynconf/zynthian_config.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/zynconf/zynthian_config.py b/zynconf/zynthian_config.py index b3109c096..3f9b8c4ae 100755 --- a/zynconf/zynthian_config.py +++ b/zynconf/zynthian_config.py @@ -192,8 +192,11 @@ def get_git_branch(path): def get_git_tag(path): # Get the current tag for a git repository or None if invalid repo name try: - return check_output(f"git -C {path} describe --tags --exact-match", - encoding="utf-8", shell=True, stderr=DEVNULL).strip() + status = check_output(f"git -C {path} status", + encoding="utf-8", shell=True, stderr=DEVNULL).split('\n') + for line in status: + if line.strip().startswith("HEAD detached at "): + return line.strip()[17:] except: return None From d27d98415985f56bac7d7d320b0d5d963c8f4d40 Mon Sep 17 00:00:00 2001 From: riban Date: Thu, 26 Dec 2024 07:04:12 +0000 Subject: [PATCH 5/7] Use repos list from zynconf --- zyngine/zynthian_state_manager.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/zyngine/zynthian_state_manager.py b/zyngine/zynthian_state_manager.py index 6df5d03c2..a94009cdb 100644 --- a/zyngine/zynthian_state_manager.py +++ b/zyngine/zynthian_state_manager.py @@ -2730,9 +2730,8 @@ def check_for_updates(self): def update_thread(): logging.debug("************ CHECKING FOR UPDATES... ************") try: - repos = ["zynthian-ui", "zynthian-sys", "zynthian-webconf", "zynthian-data", "zyncoder"] # If attached to last stable => Detect if new tag relase available - for repo in repos: + for repo in zynconf.zynthian_repositories: path = f"/zynthian/{repo}" zynconf.update_git(path) local_hash = zynconf.get_git_local_hash(path) From 43e7a526e54760011e284df84fbea6dbc17eec28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oscar=20Ace=C3=B1a?= Date: Thu, 26 Dec 2024 16:36:41 +0100 Subject: [PATCH 6/7] fix #1168, switching to a new pattern was wrong --- .gitignore | 2 +- .../zynthian_ctrldev_akai_apc_key25_mk2.py | 33 ++++++++++--------- .../zynthian_ctrldev_akai_mpk_mini_mk3.py | 4 +-- .../ctrldev/zynthian_ctrldev_base_extended.py | 2 +- zyngine/ctrldev/zynthian_ctrldev_base_ui.py | 13 ++++++-- zynlibs/zynseq/zynseq.py | 2 +- 6 files changed, 32 insertions(+), 24 deletions(-) diff --git a/.gitignore b/.gitignore index fc98cbec7..f4c1a659c 100644 --- a/.gitignore +++ b/.gitignore @@ -2,5 +2,5 @@ .*~ __pycache__ zynlibs/*/build -.vscode/launch.json +.vscode .idea \ No newline at end of file diff --git a/zyngine/ctrldev/zynthian_ctrldev_akai_apc_key25_mk2.py b/zyngine/ctrldev/zynthian_ctrldev_akai_apc_key25_mk2.py index df63cf5b1..dcd5d952f 100644 --- a/zyngine/ctrldev/zynthian_ctrldev_akai_apc_key25_mk2.py +++ b/zyngine/ctrldev/zynthian_ctrldev_akai_apc_key25_mk2.py @@ -188,8 +188,7 @@ def __init__(self, state_manager, idev_in, idev_out=None): self._device_handler = DeviceHandler(state_manager, self._leds) self._mixer_handler = MixerHandler(state_manager, self._leds) self._padmatrix_handler = PadMatrixHandler(state_manager, self._leds) - self._stepseq_handler = StepSeqHandler( - state_manager, self._leds, idev_in) + self._stepseq_handler = StepSeqHandler(state_manager, self._leds, idev_in) self._current_handler = self._mixer_handler self._is_shifted = False @@ -934,8 +933,11 @@ def __init__(self, state_manager, leds: FeedbackLEDs): def on_record_changed(self, state): self._is_record_pressed = state - if state and self._recording_seq: - self._stop_pattern_record() + + # Only STOP recording allowed, as START conflicts with RECORD + PAD + if state and self._recording_seq is not None: + if self._libseq.isMidiRecord(): + self._stop_pattern_record() def on_toggle_play(self): self._state_manager.send_cuia("TOGGLE_PLAY") @@ -1185,25 +1187,28 @@ def _refresh_tool_buttons(self): return # If seqman is disabled, show playing status in row launchers - playing_rows = {seq % - self._zynseq.col_in_bank for seq in self._playing_seqs} + playing_rows = { + seq % self._zynseq.col_in_bank for seq in self._playing_seqs} for row in range(5): state = row in playing_rows self._leds.led_state(BTN_SOFT_KEY_START + row, state) def _start_pattern_record(self, seq): + # Set pad's chain as active channel = self._libseq.getChannel(self._zynseq.bank, seq, 0) chain_id = self._chain_manager.get_chain_id_by_mixer_chan(channel) if chain_id is None: return - if self._libseq.isMidiRecord(): self._state_manager.send_cuia("TOGGLE_RECORD") self._chain_manager.set_active_chain_by_id(chain_id) - self._show_pattern_editor(seq) + # Open Pattern Editor + self._show_pattern_editor(seq, skip_arranger=True) + + # Start playing & recording if self._libseq.getPlayState(self._zynseq.bank, seq) == zynseq.SEQ_STOPPED: - self._libseq.togglePlayState(self._zynseq.bank, seq) + self._state_manager.send_cuia("TOGGLE_PLAY") if not self._libseq.isMidiRecord(): self._state_manager.send_cuia("TOGGLE_RECORD") @@ -1289,7 +1294,7 @@ def _copy_sequence(self, src_scene, src_seq, dst_scene, dst_seq): # Also copy StepSeq instrument pages self._request_action("stepseq", "sync-sequences", - src_scene, src_seq, dst_scene, dst_seq) + src_scene, src_seq, dst_scene, dst_seq) # -------------------------------------------------------------------------- @@ -1655,7 +1660,7 @@ def __init__(self, state_manager, leds: FeedbackLEDs, dev_idx): self._is_arranger_mode = False # We need to receive clock though MIDI - # TODO: Changing clock source from user preference seems wrong! + # FIXME: Changing clock source from user preference seems wrong! self._state_manager.set_transport_clock_source(1) # Pads ordered for cursor sliding + note pads @@ -1849,8 +1854,7 @@ def note_on(self, note, velocity, shifted_override=None): return True if note == BTN_PLAY: - self._libseq.togglePlayState( - self._zynseq.bank, self._selected_seq) + self._libseq.togglePlayState(self._zynseq.bank, self._selected_seq) elif BTN_PAD_START <= note <= BTN_PAD_END: self._pressed_pads[note] = time.time() @@ -2059,8 +2063,7 @@ def _update_step_velocity(self, step, delta): velocity = self._libseq.getNoteVelocity(step, note) + delta velocity = min(127, max(10, velocity)) self._libseq.setNoteVelocity(step, note, velocity) - self._leds.led_on(self._pads[step], COLOR_RED, - int((velocity * 6) / 127)) + self._leds.led_on(self._pads[step], COLOR_RED, int((velocity * 6) / 127)) self._play_step(step) def _update_step_stutter_count(self, step, delta): diff --git a/zyngine/ctrldev/zynthian_ctrldev_akai_mpk_mini_mk3.py b/zyngine/ctrldev/zynthian_ctrldev_akai_mpk_mini_mk3.py index c5ae48bd8..0fddacad0 100644 --- a/zyngine/ctrldev/zynthian_ctrldev_akai_mpk_mini_mk3.py +++ b/zyngine/ctrldev/zynthian_ctrldev_akai_mpk_mini_mk3.py @@ -30,9 +30,7 @@ from zyncoder.zyncore import lib_zyncore from zyngine.zynthian_signal_manager import zynsigman -from .zynthian_ctrldev_base import ( - zynthian_ctrldev_zynmixer -) +from .zynthian_ctrldev_base import zynthian_ctrldev_zynmixer from .zynthian_ctrldev_base_extended import ( CONST, KnobSpeedControl, IntervalTimer, ButtonTimer ) diff --git a/zyngine/ctrldev/zynthian_ctrldev_base_extended.py b/zyngine/ctrldev/zynthian_ctrldev_base_extended.py index eb7de28ce..153cfde11 100644 --- a/zyngine/ctrldev/zynthian_ctrldev_base_extended.py +++ b/zyngine/ctrldev/zynthian_ctrldev_base_extended.py @@ -217,7 +217,7 @@ def _run_callback(self, note, elapsed): # -------------------------------------------------------------------------- -# Helper class to handle knobs' speed +# Helper class to handle knobs' speed # -------------------------------------------------------------------------- class KnobSpeedControl: def __init__(self, steps_normal=3, steps_shifted=8): diff --git a/zyngine/ctrldev/zynthian_ctrldev_base_ui.py b/zyngine/ctrldev/zynthian_ctrldev_base_ui.py index 9cfb5bae0..bf8aa2449 100644 --- a/zyngine/ctrldev/zynthian_ctrldev_base_ui.py +++ b/zyngine/ctrldev/zynthian_ctrldev_base_ui.py @@ -44,6 +44,7 @@ def _show_pattern_editor(self, seq=None, skip_arranger=False): self._state_manager.send_cuia("SCREEN_ZYNPAD") if seq is not None: self._select_pad(seq) + self._refresh_pattern_editor() if not skip_arranger: zynthian_gui_config.zyngui.screens["zynpad"].show_pattern_editor() else: @@ -56,9 +57,15 @@ def _select_pad(self, pad): # This SHOULD not be coupled to UI! This is needed because when the pattern is changed in # zynseq, it is not reflected in pattern editor. def _refresh_pattern_editor(self): - index = self._zynseq.libseq.getPatternIndex() - zynthian_gui_config.zyngui.screens["pattern_editor"].load_pattern( - index) + zynpad = zynthian_gui_config.zyngui.screens["zynpad"] + patted = zynthian_gui_config.zyngui.screens['pattern_editor'] + pattern = self._zynseq.libseq.getPattern(zynpad.bank, zynpad.selected_pad, 0, 0) + + self._state_manager.start_busy("load_pattern", f"loading pattern {pattern}") + patted.bank = zynpad.bank + patted.sequence = zynpad.selected_pad + patted.load_pattern(pattern) + self._state_manager.end_busy("load_pattern") # FIXME: This SHOULD not be coupled to UI! def _get_selected_sequence(self): diff --git a/zynlibs/zynseq/zynseq.py b/zynlibs/zynseq/zynseq.py index 4af115b2e..891a53658 100644 --- a/zynlibs/zynseq/zynseq.py +++ b/zynlibs/zynseq/zynseq.py @@ -164,7 +164,7 @@ def update_progress(self): # Function to select a bank for edit / control # bank: Index of bank - # force: True to fore bank selection even if same as current bank + # force: True to force bank selection even if same as current bank def select_bank(self, bank=None, force=False): if self.changing_bank: return From 7ebb0f732307d694c55a5b13b55afeebf93fc60e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oscar=20Ace=C3=B1a?= Date: Fri, 27 Dec 2024 12:43:26 +0100 Subject: [PATCH 7/7] Fixes zynthian/zynthian-issue-tracking#1164 --- .../zynthian_ctrldev_akai_apc_key25_mk2.py | 32 ++++++++++--------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/zyngine/ctrldev/zynthian_ctrldev_akai_apc_key25_mk2.py b/zyngine/ctrldev/zynthian_ctrldev_akai_apc_key25_mk2.py index dcd5d952f..13250e991 100644 --- a/zyngine/ctrldev/zynthian_ctrldev_akai_apc_key25_mk2.py +++ b/zyngine/ctrldev/zynthian_ctrldev_akai_apc_key25_mk2.py @@ -711,19 +711,19 @@ def refresh(self): return query = { - FN_MUTE: self._zynmixer.get_mute, - FN_SOLO: self._zynmixer.get_solo, - FN_SELECT: self._is_active_chain, + FN_MUTE: lambda c: self._zynmixer.get_mute(c.mixer_chan), + FN_SOLO: lambda c: self._zynmixer.get_solo(c.mixer_chan), + FN_SELECT: lambda c: c.chain_id == self._active_chain, }[self._track_buttons_function] for i in range(8): - index = i + (8 if self._chains_bank == 1 else 0) - chain = self._chain_manager.get_chain_by_index(index) + pos = i + (8 if self._chains_bank == 1 else 0) + chain = self._chain_manager.get_chain_by_position(pos) if not chain: break # Main channel ignored if chain.chain_id == 0: continue - self._leds.led_state(BTN_TRACK_1 + i, query(index)) + self._leds.led_state(BTN_TRACK_1 + i, query(chain)) def on_shift_changed(self, state): retval = super().on_shift_changed(state) @@ -785,10 +785,18 @@ def cc_change(self, ccnum, ccval): def update_strip(self, chan, symbol, value): if {"mute": FN_MUTE, "solo": FN_SOLO}.get(symbol) != self._track_buttons_function: return - chan -= self._chains_bank * 8 - if 0 > chan > 8: + + # Mixer 'chan' may not be equal to its position (if re-arranged or a + # chain was deleted). Search the actual displayed position. + chain_id = self._chain_manager.get_chain_id_by_mixer_chan(chan) + for pos in range(self._chain_manager.get_chain_count()): + if self._chain_manager.get_chain_id_by_index(pos) == chain_id: + break + + pos -= self._chains_bank * 8 + if 0 > pos > 8: return - self._leds.led_state(BTN_TRACK_1 + chan, value) + self._leds.led_state(BTN_TRACK_1 + pos, value) return True def set_active_chain(self, chain, refresh): @@ -800,12 +808,6 @@ def set_active_chain(self, chain, refresh): if refresh: self.refresh() - def _is_active_chain(self, position): - chain = self._chain_manager.get_chain_by_position(position) - if chain is None: - return False - return chain.chain_id == self._active_chain - def _update_volume(self, ccnum, ccval): return self._update_control("level", ccnum, ccval, 0, 100)