From 56736871927ceefbda37ff34e7c9f12b59f9a8fa Mon Sep 17 00:00:00 2001 From: Cam Gorrie Date: Fri, 12 Jun 2026 17:13:09 -0400 Subject: [PATCH 1/4] Revert "Revert: remove WS suppression on pedalboard load (extracting to own branch)" This reverts commit d94fd9956ee0c128193547363b0604cb9c45b735. --- modalapi/mod.py | 27 +++++++++++++++--- modalapi/modhandler.py | 27 +++++++++++++++--- tests/v1/test_plugins.py | 26 ++++++++++++++++++ tests/v3/test_pedalboards.py | 53 ++++++++++++++++++++++++++++++++++++ 4 files changed, 125 insertions(+), 8 deletions(-) diff --git a/modalapi/mod.py b/modalapi/mod.py index 72a73b923..01d36bc22 100755 --- a/modalapi/mod.py +++ b/modalapi/mod.py @@ -31,7 +31,7 @@ from blend.snapshot import SnapshotManager from modalapi.websocket_bridge import AsyncWebSocketBridge -from modalapi.ws_protocol import parse_message, LoadingEndMessage, PedalSnapshotMessage, PluginBypassMessage, TransportMessage, AddPluginMessage, ParamSetMessage, MidiMapMessage, WebSocketMessage +from modalapi.ws_protocol import parse_message, LoadingEndMessage, LoadingStartMessage, PedalSnapshotMessage, PluginBypassMessage, TransportMessage, AddPluginMessage, ParamSetMessage, MidiMapMessage, WebSocketMessage from modalapi.pedalboard_monitor import FileChangeMonitor, read_pedalboard_bundle from pistomp.footswitch import Footswitch @@ -155,6 +155,11 @@ def __init__(self, audiocard, homedir): self.ws_bridge.start() logging.info("WebSocket bridge started") + # Suppress outbound WebSocket messages while a pedalboard change is in flight. + # Set on loading_start (inbound from MOD-UI) and on file-watch detection. + # Cleared after set_current_pedalboard() completes. + self._suppress_outbound_ws = False + # Callback function map. Key is the user specified name, value is function from this handler # Used for calling handler callbacks pointed to by names which may be user set in the config file self.callbacks = {"set_mod_tap_tempo": self.set_mod_tap_tempo, @@ -480,7 +485,15 @@ def poll_system_info(self): def _handle_ws_message(self, msg: WebSocketMessage): """Handle incoming WebSocket message from MOD-UI""" - if isinstance(msg, LoadingEndMessage): + if isinstance(msg, LoadingStartMessage): + logging.debug("WebSocket: Pedalboard loading started - suppressing outbound messages") + self._suppress_outbound_ws = True + if self.ws_bridge is not None: + cleared = self.ws_bridge.clear_queue() + if cleared: + logging.debug(f"Cleared {cleared} stale outbound messages on loading_start") + + elif isinstance(msg, LoadingEndMessage): logging.debug(f"WebSocket: Pedalboard loading finished, snapshot={msg.snapshot_id}") self.next_pedalboard_preset_index = msg.snapshot_id @@ -544,6 +557,7 @@ def poll_modui_changes(self): # Check for pedalboard change via last.json if self.last_json_monitor.check_for_change(): + self._suppress_outbound_ws = True self.lcd.draw_info_message("Loading...") mod_bundle = read_pedalboard_bundle(self.last_json_monitor.path) if mod_bundle and mod_bundle != self.current.pedalboard.bundle: @@ -680,6 +694,9 @@ def set_current_pedalboard(self, pedalboard): self.blend_modes = {} self.active_blend_mode = None + # Resume outbound WebSocket messages now that the new pedalboard is fully set up. + self._suppress_outbound_ws = False + # Selection info self.selectable_items.clear() self.selectable_items.append((SelectedType.PEDALBOARD, None)) @@ -901,7 +918,8 @@ def toggle_plugin_bypass(self): return # Non-footswitch plugin: emit only; the inbound echo updates state and LCD. target_bypass = not inst.is_bypassed() - self.ws_bridge.send_parameter(inst.instance_id, ":bypass", 1.0 if target_bypass else 0.0) + if not self._suppress_outbound_ws: + self.ws_bridge.send_parameter(inst.instance_id, ":bypass", 1.0 if target_bypass else 0.0) self.lcd.draw_plugin_select(inst) # selection highlight (navigation, not bypass) # @@ -1356,7 +1374,8 @@ def parameter_value_change(self, direction, commit_callback): def parameter_value_commit(self): param = self.deep.selected_parameter - self.ws_bridge.send_parameter(param.instance_id, param.symbol, param.value) + if not self._suppress_outbound_ws: + self.ws_bridge.send_parameter(param.instance_id, param.symbol, param.value) # # LCD Stuff diff --git a/modalapi/modhandler.py b/modalapi/modhandler.py index 510f89ebb..f490b60f2 100755 --- a/modalapi/modhandler.py +++ b/modalapi/modhandler.py @@ -37,7 +37,7 @@ import pistomp.settings as Settings from blend.snapshot import SnapshotManager from modalapi.websocket_bridge import AsyncWebSocketBridge -from modalapi.ws_protocol import parse_message, LoadingEndMessage, PedalSnapshotMessage, PluginBypassMessage, TransportMessage, AddPluginMessage, ParamSetMessage, MidiMapMessage, WebSocketMessage +from modalapi.ws_protocol import parse_message, LoadingEndMessage, LoadingStartMessage, PedalSnapshotMessage, PluginBypassMessage, TransportMessage, AddPluginMessage, ParamSetMessage, MidiMapMessage, WebSocketMessage from modalapi.pedalboard_monitor import FileChangeMonitor, read_pedalboard_bundle from pistomp.footswitch import Footswitch @@ -107,6 +107,11 @@ def __init__(self, audiocard: Audiocard, homedir, data_dir="/home/pistomp/data") self.ws_bridge.start() logging.info("WebSocket bridge started") + # Suppress outbound WebSocket messages while a pedalboard change is in flight. + # Set on loading_start (inbound from MOD-UI) and on file-watch detection. + # Cleared after set_current_pedalboard() completes. + self._suppress_outbound_ws = False + # Tuner state self._tuner_engine: TunerEngine | None = None self._tuner_panel: TunerPanel | None = None @@ -337,7 +342,15 @@ def _handle_blend_mode_snapshot_change(self, new_snapshot_index: int): def _handle_ws_message(self, msg: WebSocketMessage): """Handle incoming WebSocket message from MOD-UI.""" - if isinstance(msg, LoadingEndMessage): + if isinstance(msg, LoadingStartMessage): + logging.debug("WebSocket: Pedalboard loading started - suppressing outbound messages") + self._suppress_outbound_ws = True + if self.ws_bridge is not None: + cleared = self.ws_bridge.clear_queue() + if cleared: + logging.debug(f"Cleared {cleared} stale outbound messages on loading_start") + + elif isinstance(msg, LoadingEndMessage): logging.debug(f"WebSocket: Pedalboard loading finished, snapshot={msg.snapshot_id}") # Sometimes mod-ui sends us -1 for preset index, but shows 0 anyway ("Default") self.next_pedalboard_preset_index = max(0, msg.snapshot_id) @@ -421,6 +434,7 @@ def poll_modui_changes(self): # Check for pedalboard change via last.json if self.last_json_monitor.check_for_change(): + self._suppress_outbound_ws = True self.lcd.draw_info_message("Loading...") mod_bundle = read_pedalboard_bundle(self.last_json_monitor.path) if mod_bundle and self.current and mod_bundle != self.current.pedalboard.bundle: @@ -609,6 +623,9 @@ def set_current_pedalboard(self, pedalboard): self.blend_modes = {} self.active_blend_mode = None + # Resume outbound WebSocket messages now that the new pedalboard is fully set up. + self._suppress_outbound_ws = False + def bind_current_pedalboard(self): # "current" being the pedalboard mod-host says is current # The pedalboard data has already been loaded, but this will overlay @@ -770,7 +787,8 @@ def toggle_plugin_bypass(self, widget, plugin): # No echo arrives for WS-initiated bypass. Contrast with footswitches, # which send MIDI CC → mod-host internally → feedback → msg_callback. value = plugin.toggle_bypass() - self.ws_bridge.send_parameter(plugin.instance_id, ":bypass", value) + if not self._suppress_outbound_ws: + self.ws_bridge.send_parameter(plugin.instance_id, ":bypass", value) self.lcd.toggle_plugin(widget, plugin) def update_lcd_fs(self, footswitch=None, bypass_change=False): @@ -790,7 +808,8 @@ def parameter_value_commit(self, param, value): self.audio_parameter_commit(param.symbol, value) return - self.ws_bridge.send_parameter(param.instance_id, param.symbol, param.value) + if not self._suppress_outbound_ws: + self.ws_bridge.send_parameter(param.instance_id, param.symbol, param.value) def parameter_midi_change(self, param, direction): if param: diff --git a/tests/v1/test_plugins.py b/tests/v1/test_plugins.py index 26de73c56..58dc8aa4e 100644 --- a/tests/v1/test_plugins.py +++ b/tests/v1/test_plugins.py @@ -17,6 +17,7 @@ def _make_handler(selected_plugin): handler.ws_bridge = FakeWebSocketBridge() handler.lcd = MagicMock() handler.get_selected_instance = lambda: selected_plugin + handler._suppress_outbound_ws = False return handler @@ -27,6 +28,7 @@ def _make_drain_handler(plugins): handler.ws_bridge = FakeWebSocketBridge() handler.lcd = MagicMock() handler.current = SimpleNamespace(pedalboard=SimpleNamespace(plugins=plugins)) + handler._suppress_outbound_ws = False return handler @@ -61,3 +63,27 @@ def test_v1_add_dump_reseeds_bypass_on_reconnect(make_plugin): handler.poll_ws_messages() assert plugin.is_bypassed() + + +def test_v1_outbound_ws_suppressed_during_pedalboard_change(make_plugin): + """While a pedalboard change is in flight, outbound param_set messages are dropped.""" + plugin = make_plugin("fuzz", bypassed=False, has_footswitch=False) + handler = _make_handler(plugin) + handler.current = SimpleNamespace(pedalboard=SimpleNamespace(plugins=[plugin])) + handler._suppress_outbound_ws = True + + handler.toggle_plugin_bypass() + + # mod.py uses emit-only semantics (state unchanged until echo arrives). + # The key assertion is that NO ws message was sent while suppressed. + assert not plugin.is_bypassed() + assert handler.ws_bridge.sent_values_for("fuzz", ":bypass") == [] + + +def test_v1_loading_start_suppresses_outbound_ws(): + """Receiving loading_start from MOD-UI sets the suppression flag.""" + handler = _make_drain_handler([]) + assert not getattr(handler, "_suppress_outbound_ws", False) + handler.ws_bridge.inject("loading_start 0") + handler.poll_ws_messages() + assert handler._suppress_outbound_ws is True diff --git a/tests/v3/test_pedalboards.py b/tests/v3/test_pedalboards.py index ff6f1bdde..5c34439df 100644 --- a/tests/v3/test_pedalboards.py +++ b/tests/v3/test_pedalboards.py @@ -84,3 +84,56 @@ def get_side_effect(url, **kwargs): assert handler.current assert handler.current.pedalboard.title == "New Rig" snapshot() + + +def test_v3_outbound_ws_suppressed_during_pedalboard_change(v3_system: SystemFixture, make_plugin): + """While a pedalboard change is in flight, outbound param_set messages are dropped.""" + handler = v3_system.handler + ws_bridge = v3_system.ws_bridge + + # Start with a loaded pedalboard + old_plugin = make_plugin("old_fuzz", category="Distortion", bypassed=False) + handler.current.pedalboard.plugins = [old_plugin] + handler.lcd.link_data(handler.pedalboard_list, handler.current, handler.hardware.footswitches) + handler.lcd.draw_main_panel() + widget = next(w for w in handler.lcd.w_plugins if w.object is old_plugin) + ws_bridge.sent.clear() + + # Simulate a user tapping the bypass on the old pedalboard while a change is in flight + handler._suppress_outbound_ws = True + handler.toggle_plugin_bypass(widget, old_plugin) + + # The bypass should flip locally, but NO ws message should be sent + assert old_plugin.is_bypassed() + assert ws_bridge.sent_values_for("old_fuzz", ":bypass") == [] + + # After clearing suppression, sends resume + handler._suppress_outbound_ws = False + handler.toggle_plugin_bypass(widget, old_plugin) + assert not old_plugin.is_bypassed() + assert ws_bridge.sent_values_for("old_fuzz", ":bypass") == [0.0] + + +def test_v3_loading_start_suppresses_outbound_ws(v3_system: SystemFixture): + """Receiving loading_start from MOD-UI sets the suppression flag.""" + handler = v3_system.handler + ws_bridge = v3_system.ws_bridge + + assert not getattr(handler, "_suppress_outbound_ws", False) + ws_bridge.inject("loading_start 0") + handler.poll_ws_messages() + assert handler._suppress_outbound_ws is True + + +def test_v3_set_current_pedalboard_clears_suppression(v3_system: SystemFixture, make_plugin): + """After set_current_pedalboard completes, suppression is cleared so normal operation resumes.""" + handler = v3_system.handler + handler._suppress_outbound_ws = True + + pb = handler.current.pedalboard + new_plugin = make_plugin("new_fuzz", category="Distortion", bypassed=False) + pb.plugins = [new_plugin] + + handler.set_current_pedalboard(pb) + + assert handler._suppress_outbound_ws is False From 3fdda63d2cbcf2146d5d051062c46a3cd0da9318 Mon Sep 17 00:00:00 2001 From: Cam Gorrie Date: Fri, 12 Jun 2026 22:21:00 -0400 Subject: [PATCH 2/4] Terseness --- modalapi/mod.py | 23 ++++++++--------------- modalapi/modhandler.py | 17 +++++------------ 2 files changed, 13 insertions(+), 27 deletions(-) diff --git a/modalapi/mod.py b/modalapi/mod.py index 01d36bc22..203df46a8 100755 --- a/modalapi/mod.py +++ b/modalapi/mod.py @@ -156,8 +156,6 @@ def __init__(self, audiocard, homedir): logging.info("WebSocket bridge started") # Suppress outbound WebSocket messages while a pedalboard change is in flight. - # Set on loading_start (inbound from MOD-UI) and on file-watch detection. - # Cleared after set_current_pedalboard() completes. self._suppress_outbound_ws = False # Callback function map. Key is the user specified name, value is function from this handler @@ -175,15 +173,12 @@ def __del__(self): logging.info("Handler cleanup") if self.wifi_manager: del self.wifi_manager - if self.ws_bridge is not None: - self.ws_bridge.stop() def cleanup(self): if self.lcd is not None: self.lcd.cleanup() - if self.ws_bridge is not None: - self.ws_bridge.stop() - logging.info("WebSocket bridge stopped") + self.ws_bridge.stop() + logging.info("WebSocket bridge stopped") # Container for dynamic data which is unique to the "current" pedalboard # The self.current pointed above will point to this object which gets @@ -486,12 +481,10 @@ def poll_system_info(self): def _handle_ws_message(self, msg: WebSocketMessage): """Handle incoming WebSocket message from MOD-UI""" if isinstance(msg, LoadingStartMessage): - logging.debug("WebSocket: Pedalboard loading started - suppressing outbound messages") self._suppress_outbound_ws = True - if self.ws_bridge is not None: - cleared = self.ws_bridge.clear_queue() - if cleared: - logging.debug(f"Cleared {cleared} stale outbound messages on loading_start") + cleared = self.ws_bridge.clear_queue() + if cleared: + logging.debug(f"Cleared {cleared} stale outbound messages on loading_start") elif isinstance(msg, LoadingEndMessage): logging.debug(f"WebSocket: Pedalboard loading finished, snapshot={msg.snapshot_id}") @@ -648,6 +641,9 @@ def set_current_pedalboard(self, pedalboard): self.load_current_presets() self.update_lcd() + # Resume outbound WebSocket messages now that the new pedalboard is fully set up. + self._suppress_outbound_ws = False + # Prepare blend modes if configured (snapshot-based activation) try: blend_configs = cfg.get('blend_snapshots', []) if cfg else [] @@ -694,9 +690,6 @@ def set_current_pedalboard(self, pedalboard): self.blend_modes = {} self.active_blend_mode = None - # Resume outbound WebSocket messages now that the new pedalboard is fully set up. - self._suppress_outbound_ws = False - # Selection info self.selectable_items.clear() self.selectable_items.append((SelectedType.PEDALBOARD, None)) diff --git a/modalapi/modhandler.py b/modalapi/modhandler.py index f490b60f2..7e347ffd1 100755 --- a/modalapi/modhandler.py +++ b/modalapi/modhandler.py @@ -108,8 +108,6 @@ def __init__(self, audiocard: Audiocard, homedir, data_dir="/home/pistomp/data") logging.info("WebSocket bridge started") # Suppress outbound WebSocket messages while a pedalboard change is in flight. - # Set on loading_start (inbound from MOD-UI) and on file-watch detection. - # Cleared after set_current_pedalboard() completes. self._suppress_outbound_ws = False # Tuner state @@ -136,8 +134,6 @@ def __del__(self): logging.info("Handler cleanup") if self.wifi_manager: del self.wifi_manager - # ws_bridge.stop() lives in cleanup(), not here — join() in __del__ blows up - # during interpreter shutdown on Py 3.14. Daemon thread dies with the process. def cleanup(self): if self._tuner_engine is not None: @@ -150,9 +146,8 @@ def cleanup(self): self._lcd.cleanup() if self._hardware is not None: self._hardware.cleanup() - if self.ws_bridge is not None: - self.ws_bridge.stop() - logging.info("WebSocket bridge stopped") + self.ws_bridge.stop() + logging.info("WebSocket bridge stopped") # Container for dynamic data which is unique to the "current" pedalboard # The self.current pointed above will point to this object which gets @@ -343,12 +338,10 @@ def _handle_blend_mode_snapshot_change(self, new_snapshot_index: int): def _handle_ws_message(self, msg: WebSocketMessage): """Handle incoming WebSocket message from MOD-UI.""" if isinstance(msg, LoadingStartMessage): - logging.debug("WebSocket: Pedalboard loading started - suppressing outbound messages") self._suppress_outbound_ws = True - if self.ws_bridge is not None: - cleared = self.ws_bridge.clear_queue() - if cleared: - logging.debug(f"Cleared {cleared} stale outbound messages on loading_start") + cleared = self.ws_bridge.clear_queue() + if cleared: + logging.debug(f"Cleared {cleared} stale outbound messages on loading_start") elif isinstance(msg, LoadingEndMessage): logging.debug(f"WebSocket: Pedalboard loading finished, snapshot={msg.snapshot_id}") From 3597c7791d76b5970f73d8759b9d76481a9dc087 Mon Sep 17 00:00:00 2001 From: Cam Gorrie Date: Wed, 17 Jun 2026 17:01:36 -0400 Subject: [PATCH 3/4] bool --- modalapi/mod.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modalapi/mod.py b/modalapi/mod.py index 203df46a8..d73d393a7 100755 --- a/modalapi/mod.py +++ b/modalapi/mod.py @@ -156,7 +156,7 @@ def __init__(self, audiocard, homedir): logging.info("WebSocket bridge started") # Suppress outbound WebSocket messages while a pedalboard change is in flight. - self._suppress_outbound_ws = False + self._suppress_outbound_ws: bool = False # Callback function map. Key is the user specified name, value is function from this handler # Used for calling handler callbacks pointed to by names which may be user set in the config file From 6697c4d93695e5ff542363028ee28ce08e02da9b Mon Sep 17 00:00:00 2001 From: Cam Gorrie Date: Wed, 17 Jun 2026 17:06:33 -0400 Subject: [PATCH 4/4] better varnmae --- modalapi/mod.py | 12 ++++++------ modalapi/modhandler.py | 12 ++++++------ tests/v1/test_plugins.py | 11 ++++++----- tests/v3/test_pedalboards.py | 14 ++++++++------ 4 files changed, 26 insertions(+), 23 deletions(-) diff --git a/modalapi/mod.py b/modalapi/mod.py index d73d393a7..3fc6c5c76 100755 --- a/modalapi/mod.py +++ b/modalapi/mod.py @@ -156,7 +156,7 @@ def __init__(self, audiocard, homedir): logging.info("WebSocket bridge started") # Suppress outbound WebSocket messages while a pedalboard change is in flight. - self._suppress_outbound_ws: bool = False + self._is_pedalboard_loading: bool = False # Callback function map. Key is the user specified name, value is function from this handler # Used for calling handler callbacks pointed to by names which may be user set in the config file @@ -481,7 +481,7 @@ def poll_system_info(self): def _handle_ws_message(self, msg: WebSocketMessage): """Handle incoming WebSocket message from MOD-UI""" if isinstance(msg, LoadingStartMessage): - self._suppress_outbound_ws = True + self._is_pedalboard_loading = True cleared = self.ws_bridge.clear_queue() if cleared: logging.debug(f"Cleared {cleared} stale outbound messages on loading_start") @@ -550,7 +550,7 @@ def poll_modui_changes(self): # Check for pedalboard change via last.json if self.last_json_monitor.check_for_change(): - self._suppress_outbound_ws = True + self._is_pedalboard_loading = True self.lcd.draw_info_message("Loading...") mod_bundle = read_pedalboard_bundle(self.last_json_monitor.path) if mod_bundle and mod_bundle != self.current.pedalboard.bundle: @@ -642,7 +642,7 @@ def set_current_pedalboard(self, pedalboard): self.update_lcd() # Resume outbound WebSocket messages now that the new pedalboard is fully set up. - self._suppress_outbound_ws = False + self._is_pedalboard_loading = False # Prepare blend modes if configured (snapshot-based activation) try: @@ -911,7 +911,7 @@ def toggle_plugin_bypass(self): return # Non-footswitch plugin: emit only; the inbound echo updates state and LCD. target_bypass = not inst.is_bypassed() - if not self._suppress_outbound_ws: + if not self._is_pedalboard_loading: self.ws_bridge.send_parameter(inst.instance_id, ":bypass", 1.0 if target_bypass else 0.0) self.lcd.draw_plugin_select(inst) # selection highlight (navigation, not bypass) @@ -1367,7 +1367,7 @@ def parameter_value_change(self, direction, commit_callback): def parameter_value_commit(self): param = self.deep.selected_parameter - if not self._suppress_outbound_ws: + if not self._is_pedalboard_loading: self.ws_bridge.send_parameter(param.instance_id, param.symbol, param.value) # diff --git a/modalapi/modhandler.py b/modalapi/modhandler.py index 7e347ffd1..0081a796c 100755 --- a/modalapi/modhandler.py +++ b/modalapi/modhandler.py @@ -108,7 +108,7 @@ def __init__(self, audiocard: Audiocard, homedir, data_dir="/home/pistomp/data") logging.info("WebSocket bridge started") # Suppress outbound WebSocket messages while a pedalboard change is in flight. - self._suppress_outbound_ws = False + self._is_pedalboard_loading = False # Tuner state self._tuner_engine: TunerEngine | None = None @@ -338,7 +338,7 @@ def _handle_blend_mode_snapshot_change(self, new_snapshot_index: int): def _handle_ws_message(self, msg: WebSocketMessage): """Handle incoming WebSocket message from MOD-UI.""" if isinstance(msg, LoadingStartMessage): - self._suppress_outbound_ws = True + self._is_pedalboard_loading = True cleared = self.ws_bridge.clear_queue() if cleared: logging.debug(f"Cleared {cleared} stale outbound messages on loading_start") @@ -427,7 +427,7 @@ def poll_modui_changes(self): # Check for pedalboard change via last.json if self.last_json_monitor.check_for_change(): - self._suppress_outbound_ws = True + self._is_pedalboard_loading = True self.lcd.draw_info_message("Loading...") mod_bundle = read_pedalboard_bundle(self.last_json_monitor.path) if mod_bundle and self.current and mod_bundle != self.current.pedalboard.bundle: @@ -617,7 +617,7 @@ def set_current_pedalboard(self, pedalboard): self.active_blend_mode = None # Resume outbound WebSocket messages now that the new pedalboard is fully set up. - self._suppress_outbound_ws = False + self._is_pedalboard_loading = False def bind_current_pedalboard(self): # "current" being the pedalboard mod-host says is current @@ -780,7 +780,7 @@ def toggle_plugin_bypass(self, widget, plugin): # No echo arrives for WS-initiated bypass. Contrast with footswitches, # which send MIDI CC → mod-host internally → feedback → msg_callback. value = plugin.toggle_bypass() - if not self._suppress_outbound_ws: + if not self._is_pedalboard_loading: self.ws_bridge.send_parameter(plugin.instance_id, ":bypass", value) self.lcd.toggle_plugin(widget, plugin) @@ -801,7 +801,7 @@ def parameter_value_commit(self, param, value): self.audio_parameter_commit(param.symbol, value) return - if not self._suppress_outbound_ws: + if not self._is_pedalboard_loading: self.ws_bridge.send_parameter(param.instance_id, param.symbol, param.value) def parameter_midi_change(self, param, direction): diff --git a/tests/v1/test_plugins.py b/tests/v1/test_plugins.py index 58dc8aa4e..9465a8caf 100644 --- a/tests/v1/test_plugins.py +++ b/tests/v1/test_plugins.py @@ -1,3 +1,4 @@ +# pyright: reportAttributeAccessIssue=false """Basic v1/v2 (mod.py) coverage for source-of-truth bypass. Drives Mod.toggle_plugin_bypass directly with a hand-wired handler (no hardware, @@ -17,7 +18,7 @@ def _make_handler(selected_plugin): handler.ws_bridge = FakeWebSocketBridge() handler.lcd = MagicMock() handler.get_selected_instance = lambda: selected_plugin - handler._suppress_outbound_ws = False + handler._is_pedalboard_loading = False return handler @@ -28,7 +29,7 @@ def _make_drain_handler(plugins): handler.ws_bridge = FakeWebSocketBridge() handler.lcd = MagicMock() handler.current = SimpleNamespace(pedalboard=SimpleNamespace(plugins=plugins)) - handler._suppress_outbound_ws = False + handler._is_pedalboard_loading = False return handler @@ -70,7 +71,7 @@ def test_v1_outbound_ws_suppressed_during_pedalboard_change(make_plugin): plugin = make_plugin("fuzz", bypassed=False, has_footswitch=False) handler = _make_handler(plugin) handler.current = SimpleNamespace(pedalboard=SimpleNamespace(plugins=[plugin])) - handler._suppress_outbound_ws = True + handler._is_pedalboard_loading = True handler.toggle_plugin_bypass() @@ -83,7 +84,7 @@ def test_v1_outbound_ws_suppressed_during_pedalboard_change(make_plugin): def test_v1_loading_start_suppresses_outbound_ws(): """Receiving loading_start from MOD-UI sets the suppression flag.""" handler = _make_drain_handler([]) - assert not getattr(handler, "_suppress_outbound_ws", False) + assert not getattr(handler, "_is_pedalboard_loading", False) handler.ws_bridge.inject("loading_start 0") handler.poll_ws_messages() - assert handler._suppress_outbound_ws is True + assert handler._is_pedalboard_loading is True diff --git a/tests/v3/test_pedalboards.py b/tests/v3/test_pedalboards.py index 5c34439df..7d80669d5 100644 --- a/tests/v3/test_pedalboards.py +++ b/tests/v3/test_pedalboards.py @@ -93,6 +93,7 @@ def test_v3_outbound_ws_suppressed_during_pedalboard_change(v3_system: SystemFix # Start with a loaded pedalboard old_plugin = make_plugin("old_fuzz", category="Distortion", bypassed=False) + assert handler.current is not None handler.current.pedalboard.plugins = [old_plugin] handler.lcd.link_data(handler.pedalboard_list, handler.current, handler.hardware.footswitches) handler.lcd.draw_main_panel() @@ -100,7 +101,7 @@ def test_v3_outbound_ws_suppressed_during_pedalboard_change(v3_system: SystemFix ws_bridge.sent.clear() # Simulate a user tapping the bypass on the old pedalboard while a change is in flight - handler._suppress_outbound_ws = True + handler._is_pedalboard_loading = True handler.toggle_plugin_bypass(widget, old_plugin) # The bypass should flip locally, but NO ws message should be sent @@ -108,7 +109,7 @@ def test_v3_outbound_ws_suppressed_during_pedalboard_change(v3_system: SystemFix assert ws_bridge.sent_values_for("old_fuzz", ":bypass") == [] # After clearing suppression, sends resume - handler._suppress_outbound_ws = False + handler._is_pedalboard_loading = False handler.toggle_plugin_bypass(widget, old_plugin) assert not old_plugin.is_bypassed() assert ws_bridge.sent_values_for("old_fuzz", ":bypass") == [0.0] @@ -119,16 +120,17 @@ def test_v3_loading_start_suppresses_outbound_ws(v3_system: SystemFixture): handler = v3_system.handler ws_bridge = v3_system.ws_bridge - assert not getattr(handler, "_suppress_outbound_ws", False) + assert not getattr(handler, "_is_pedalboard_loading", False) ws_bridge.inject("loading_start 0") handler.poll_ws_messages() - assert handler._suppress_outbound_ws is True + assert handler._is_pedalboard_loading is True def test_v3_set_current_pedalboard_clears_suppression(v3_system: SystemFixture, make_plugin): """After set_current_pedalboard completes, suppression is cleared so normal operation resumes.""" handler = v3_system.handler - handler._suppress_outbound_ws = True + handler._is_pedalboard_loading = True + assert handler.current is not None pb = handler.current.pedalboard new_plugin = make_plugin("new_fuzz", category="Distortion", bypassed=False) @@ -136,4 +138,4 @@ def test_v3_set_current_pedalboard_clears_suppression(v3_system: SystemFixture, handler.set_current_pedalboard(pb) - assert handler._suppress_outbound_ws is False + assert handler._is_pedalboard_loading is False