From 9c4bf6b1102fcda02b9b94cf9708c252932a6f3e Mon Sep 17 00:00:00 2001 From: Niels Giesen Date: Fri, 26 Dec 2025 15:31:35 +0000 Subject: [PATCH 1/3] Use dedicated method for loading last state --- zynthian_headless.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/zynthian_headless.py b/zynthian_headless.py index 537d91d6a..a1ebafd75 100644 --- a/zynthian_headless.py +++ b/zynthian_headless.py @@ -31,8 +31,7 @@ def __init__(self): self.chain_manager = self.state_manager.chain_manager if zynthian_gui_config.restore_last_state: - snapshot_loaded = self.state_manager.load_snapshot( - "/zynthian/zynthian-my-data/snapshots/last_state.zss") + snapshot_loaded = self.state_manager.load_last_state_snapshot() self.state_manager.init_midi() self.state_manager.init_midi_services() From e4e2c792928bd8fa472e118a1e5c601d8c149dfa Mon Sep 17 00:00:00 2001 From: Niels Giesen Date: Fri, 26 Dec 2025 15:33:28 +0000 Subject: [PATCH 2/3] Only load ctrldev drivers from zs3 with last state --- zyngine/zynthian_state_manager.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/zyngine/zynthian_state_manager.py b/zyngine/zynthian_state_manager.py index 98b91cea6..f16aeb54e 100644 --- a/zyngine/zynthian_state_manager.py +++ b/zyngine/zynthian_state_manager.py @@ -1087,7 +1087,7 @@ def save_snapshot(self, fpath, extra_data=None): self.end_busy("save snapshot") return True - def load_snapshot(self, fpath, load_chains=True, load_sequences=True, merge=False): + def load_snapshot(self, fpath, load_chains=True, load_sequences=True, load_ctrldev=False, merge=False): """Loads a snapshot from file fpath : Full path and filename of snapshot file @@ -1217,7 +1217,7 @@ def load_snapshot(self, fpath, load_chains=True, load_sequences=True, merge=Fals zs3 = self.sanitize_zs3_from_json(state["zs3"]) if not merge: self.zs3 = zs3 - self.load_zs3(zs3["zs3-0"], autoconnect=False) + self.load_zs3(zs3["zs3-0"], autoconnect=False, load_ctrldev=load_ctrldev) try: mute |= self.zs3["zs3-0"]["mixer"]["chan_16"]["mute"] except: @@ -1337,7 +1337,7 @@ def save_last_state_snapshot(self): def load_last_state_snapshot(self): if isfile(self.last_state_snapshot_fpath): - return self.load_snapshot(self.last_state_snapshot_fpath) + return self.load_snapshot(self.last_state_snapshot_fpath, load_ctrldev=True) def delete_last_state_snapshot(self): try: @@ -1377,7 +1377,7 @@ def toggle_zs3_chain_restore_flag(self, zs3_id, chain_id): except: tstate["restore"] = False - def load_zs3(self, zs3_id, autoconnect=True): + def load_zs3(self, zs3_id, autoconnect=True, load_ctrldev=False): """Restore a ZS3 zs3_id : ID of ZS3 to restore or zs3 dict @@ -1533,7 +1533,7 @@ def load_zs3(self, zs3_id, autoconnect=True): if "midi_capture" in zs3_state: self.set_busy_details("restoring midi capture state") - self.set_midi_capture_state(zs3_state['midi_capture']) + self.set_midi_capture_state(zs3_state['midi_capture'], load_ctrldev=load_ctrldev) if "global" in zs3_state: if "midi_transpose" in zs3_state["global"]: @@ -1951,7 +1951,7 @@ def get_midi_capture_state(self): return mcstate - def set_midi_capture_state(self, mcstate=None): + def set_midi_capture_state(self, mcstate=None, load_ctrldev=False): """Set midi input (capture) state: flags, chain routing, etc. mcstate : dictionary with state. None for reset state to defaults. @@ -1984,8 +1984,11 @@ def set_midi_capture_state(self, mcstate=None): # TODO: Use ctrldev_driver=None to disable driver if state["disable_ctrldev"]: self.ctrldev_manager.unload_driver(izmip, True) - else: + elif load_ctrldev: self.ctrldev_manager.load_driver(izmip, state["ctrldev_driver"]) + else: + pass + # self.ctrldev_manager.load_driver(izmip, state["ctrldev_driver"]) except: pass try: From 905d6044c4c7c778b6fac56b1c2cca2a776bc0ee Mon Sep 17 00:00:00 2001 From: Niels Giesen Date: Fri, 26 Dec 2025 15:34:25 +0000 Subject: [PATCH 3/3] Load ctrldev drivers explicitly from snapshot file --- zyngine/zynthian_state_manager.py | 78 +++++++++++++++++++++++++++++++ zyngui/zynthian_gui_snapshot.py | 10 ++++ 2 files changed, 88 insertions(+) diff --git a/zyngine/zynthian_state_manager.py b/zyngine/zynthian_state_manager.py index f16aeb54e..5339e5ef3 100644 --- a/zyngine/zynthian_state_manager.py +++ b/zyngine/zynthian_state_manager.py @@ -1240,6 +1240,12 @@ def load_snapshot(self, fpath, load_chains=True, load_sequences=True, load_ctrld for proc in self.chain_manager.processors.values(): proc.set_midi_autolearn(True) + if load_ctrldev and not load_chains: + zs3 = self.sanitize_zs3_from_json(state["zs3"]) + if not merge: + self.zs3 = zs3 + self.load_ctrldev(zs3["zs3-0"]) + # Save last snapshot info and get snapshot's program number self.last_snapshot_count += 1 if basename(fpath) != "last_state.zss": @@ -1570,6 +1576,78 @@ def load_zs3(self, zs3_id, autoconnect=True, load_ctrldev=False): zynautoconnect.request_audio_connect(True) return True + def load_ctrldev(self, zs3_id, autoconnect=True, load_ctrldev=True): + """Restore ctrldev from a zs3 + + zs3_id : ID of ZS3 to restore or zs3 dict + Returns : True on success + """ + + if isinstance(zs3_id, str): + # Try loading exact match + try: + zs3_state = self.zs3[zs3_id] + except: + # else ignore MIDI channel => try loading "program change" match + try: + zs3_id = f"*/{zs3_id.split('/')[1]}" + zs3_state = self.zs3[zs3_id] + except: + logging.info(f"Not found ZS3 matching '{zs3_id}'") + return False + else: + try: + zs3_state = zs3_id + zs3_id = self.last_zs3_id + if zs3_id is None: + zs3_id = "zs3-0" + except: + zs3_id = "zs3-0" + + if "midi_capture" in zs3_state: + self.set_busy_details("restoring midi capture state") + """Set midi input (capture) state: flags, chain routing, etc. + + mcstate : dictionary with state. None for reset state to defaults. + """ + mcstate = zs3_state['midi_capture'] + if mcstate: + ctrldev_state_drivers = {} + for uid, state in mcstate.items(): + #logging.debug(f"MCSTATE {uid} => {state}") + izmip = zynautoconnect.get_midi_in_devid_by_uid(uid, zynthian_gui_config.midi_usb_by_port) + if izmip is None: + continue + zynautoconnect.update_midi_in_dev_mode(izmip) + try: + # TODO: Use ctrldev_driver=None to disable driver + if state["disable_ctrldev"]: + self.ctrldev_manager.unload_driver(izmip, True) + else: + self.ctrldev_manager.load_driver(izmip, state["ctrldev_driver"]) + except: + pass + try: + ctrldev_state_drivers[uid] = state["ctrldev_state"] + except: + pass + + self.ctrldev_manager.set_state_drivers(ctrldev_state_drivers) + + else: + zynautoconnect.reset_midi_in_dev_all() + + + if zs3_id != 'zs3-0': + self.last_zs3_id = zs3_id + #self.zs3['zs3-0'] = self.zs3[zs3_id].copy() + zynsigman.send(zynsigman.S_STATE_MAN, self.SS_LOAD_ZS3, zs3_id=zs3_id) + + if autoconnect: + zynautoconnect.request_midi_connect(True) + zynautoconnect.request_audio_connect(True) + return True + def get_next_zs3_index(self): used_indexes = [] for zid, state in self.zs3.items(): diff --git a/zyngui/zynthian_gui_snapshot.py b/zyngui/zynthian_gui_snapshot.py index 2bb215154..773b4b409 100644 --- a/zyngui/zynthian_gui_snapshot.py +++ b/zyngui/zynthian_gui_snapshot.py @@ -295,6 +295,7 @@ def show_options(self, i, restrict_options): "Load Replace Chains": [param, ["Load chains from snapshot, replacing current state.", "snapshot_chains.png"]], "Load Merge Chains": [param, ["Load chains from snapshot, merging with current state.", "snapshot_chains.png"]], "Load Replace Sequences": [param, ["Load sequences from snapshot, replacing current state", "snapshot_sequences.png"]], + "Load MIDI device drivers": [param, ["Load MIDI device drivers from snapshot, replacing current drivers", "midi_input.png"]], "Save Overwriting": [param, ["Save current state, overwriting this snapshot.", "snapshot_overwrite.png"]] } budir = dirname(fpath) + "/.backup" @@ -327,6 +328,9 @@ def options_cb(self, option, param): elif option == "Load Replace Sequences": # self.zyngui.show_confirm("Loading sequences from '%s' will destroy current sequences..." % (fname), self.load_snapshot_sequences, fpath) self.load_snapshot_sequences(fpath) + elif option == "Load MIDI device drivers": + # self.zyngui.show_confirm("Loading ctrldev drivers from '%s' will destroy current drivers..." % (fname), self.load_snapshot_ctrldev, fpath) + self.load_snapshot_ctrldev(fpath) elif option == "Save Overwriting": # self.zyngui.show_confirm("Do you really want to overwrite '%s'?" % (fname), self.save_snapshot, fpath) self.save_snapshot(fpath) @@ -367,6 +371,12 @@ def load_snapshot_sequences(self, fpath): self.sm.load_snapshot(fpath, load_chains=False) self.zyngui.show_screen('zynpad', hmode=self.zyngui.SCREEN_HMODE_RESET) + def load_snapshot_ctrldev(self, fpath): + if self.is_not_empty_snapshot() and fpath != self.sm.last_state_snapshot_fpath: + self.sm.save_last_state_snapshot() + self.sm.load_snapshot(fpath, load_chains=False, load_sequences=False, load_ctrldev=True) + self.zyngui.show_screen('audio_mixer', hmode=self.zyngui.SCREEN_HMODE_RESET) + def restore_backup_cb(self, fname, fpath): logging.debug("Restoring snapshot backup '{}'".format(fname)) self.load_snapshot(fpath)