From 751bbab1118344b8be82510c2eb15cce75811cbd Mon Sep 17 00:00:00 2001 From: Cam Gorrie Date: Thu, 2 Jul 2026 19:29:04 -0400 Subject: [PATCH 1/4] Footswitch preset fixes: snapshot name label, no plugin-name clobber, LED lights active snapshot Clear a footswitch's inherited midi_CC when preset: is configured (it's dead weight for its own press dispatch and was letting an unrelated plugin's MIDI-learned binding steal fs.parameter and clobber the label). Label now shows the snapshot name instead of the raw index, and the LCD/physical LED lights for whichever snapshot is currently active. Co-Authored-By: Claude Sonnet 5 --- modalapi/modhandler.py | 3 +- pistomp/footswitch.py | 8 +- pistomp/hardware.py | 14 +- pistomp/lcd320x240.py | 33 +++- .../test_v3_preset_change_via_lcd/nav_D.png | Bin 6270 -> 6277 bytes tests/test_lcd320x240.py | 2 + tests/v3/test_footswitch_presets.py | 172 ++++++++++++++++++ 7 files changed, 217 insertions(+), 15 deletions(-) create mode 100644 tests/v3/test_footswitch_presets.py diff --git a/modalapi/modhandler.py b/modalapi/modhandler.py index 86015b4bf..d601eae04 100755 --- a/modalapi/modhandler.py +++ b/modalapi/modhandler.py @@ -728,8 +728,9 @@ def preset_change(self, index): logging.error("Bad Rest request: %s" % url) self.current.preset_index = index - # Update name on lcd + # Update name on lcd, and relight any footswitch mapped to a snapshot self.lcd.draw_title() + self.lcd.update_footswitches() # Bypass/param changes from the snapshot arrive via the WS drain (source of truth). def preset_incr_and_change(self, *argv): diff --git a/pistomp/footswitch.py b/pistomp/footswitch.py index 9595ece83..1dbada1b8 100755 --- a/pistomp/footswitch.py +++ b/pistomp/footswitch.py @@ -149,10 +149,10 @@ def drives_display(self) -> bool: def set_value(self, bypass_value: float): self.toggled = (bypass_value < 1) - self._set_led(self.toggled) + self.set_led(self.toggled) self.refresh_callback(footswitch=self) - def _set_led(self, enabled): + def set_led(self, enabled): if self.led is not None: if self.taptempo: tempo = self.taptempo.get_bpm() @@ -228,7 +228,7 @@ def pressed(self, state): r.enable() else: r.disable() - self._set_led(self.toggled) + self.set_led(self.toggled) self.refresh_callback(True) # True means this is a bypass change only else: # TODO consider case where relay and longpress are specified @@ -257,7 +257,7 @@ def pressed(self, state): logging.debug("Sending CC event: %d" % self.midi_CC) self.midiout.send_message(cc) if self.drives_display: - self._set_led(self.toggled) + self.set_led(self.toggled) if self.drives_display: self.refresh_callback(footswitch=self) diff --git a/pistomp/hardware.py b/pistomp/hardware.py index fa385a18c..5f9d0b61c 100755 --- a/pistomp/hardware.py +++ b/pistomp/hardware.py @@ -347,6 +347,13 @@ def __init_footswitches_default(self): fs.clear_relays() self.__init_footswitches(self.cfg) + def __clear_footswitch_midi_cc(self, fs) -> None: + fs.set_midi_CC(None) + for k, v in self.controllers.items(): + if v == fs: + self.controllers.pop(k) + break + def __init_footswitches(self, cfg): if cfg is None or (Token.HARDWARE not in cfg) or (Token.FOOTSWITCHES not in cfg[Token.HARDWARE]): return @@ -380,11 +387,7 @@ def __init_footswitches(self, cfg): if Token.MIDI_CC in f: cc = f[Token.MIDI_CC] if cc == Token.NONE: - fs.set_midi_CC(None) - for k, v in self.controllers.items(): - if v == fs: - self.controllers.pop(k) - break + self.__clear_footswitch_midi_cc(fs) else: fs.set_midi_channel(self.midi_channel) fs.set_midi_CC(cc) @@ -393,6 +396,7 @@ def __init_footswitches(self, cfg): # Preset Control if Token.PRESET in f: + self.__clear_footswitch_midi_cc(fs) preset_value = f[Token.PRESET] if preset_value == Token.UP: fs.add_preset(callback=self.handler.preset_incr_and_change) diff --git a/pistomp/lcd320x240.py b/pistomp/lcd320x240.py index 77fa9612e..d779a5caa 100644 --- a/pistomp/lcd320x240.py +++ b/pistomp/lcd320x240.py @@ -478,7 +478,10 @@ def parameter_commit_enum(self, param_value_tuple): # Footswitches # def footswitch_label(self, footswitch): - """Label for a footswitch bound to a plugin param: the param name, or the plugin instance for a :bypass binding.""" + """Label for a footswitch bound to a snapshot index or a plugin param.""" + if footswitch.preset_callback_arg is not None: + name = self.current.presets.get(footswitch.preset_callback_arg) if self.current else None + return self.shorten_name(name, self.footswitch_width) if name else str(footswitch.preset_callback_arg) param = footswitch.parameter if param is None: return None @@ -489,6 +492,10 @@ def footswitch_label(self, footswitch): def draw_footswitch(self, plugin): for c in plugin.controllers: if isinstance(c, Footswitch): + if c.preset_callback_arg is not None: + # Preset-bound switches are drawn by draw_unbound_footswitches, + # regardless of any (stale) plugin binding. + continue fs_id = c.id #fss[fs_id] = None label = self.footswitch_label(c) @@ -509,12 +516,23 @@ def draw_unbound_footswitches(self): if fs.id in self.footswitch_slots: continue slot = fs.id - dl = fs.get_display_label() - label = "" if dl is None else dl + if fs.preset_callback_arg is not None: + label = self.footswitch_label(fs) + fs.set_display_label(label) + active = self.current is not None and self.current.preset_index == fs.preset_callback_arg + fs.toggled = active + fs.set_led(active) # a press never touches toggled for preset switches + color = self.foreground + is_bypassed = not fs.toggled + else: + dl = fs.get_display_label() + label = "" if dl is None else dl + color = None + is_bypassed = True y = 0 x = self.get_footswitch_pitch() * slot p = FootswitchWidget(Box.xywh(x, y, self.plugin_width, self.footswitch_height), self.small_font, - label, None, True, parent=self.footswitch_panel, object=fs) + label, color, is_bypassed, parent=self.footswitch_panel, object=fs) self.w_footswitches.append(p) self.footswitch_panel.add_widget(p) self.footswitch_panel.refresh() @@ -522,7 +540,12 @@ def draw_unbound_footswitches(self): def update_footswitch(self, footswitch): for wfs in self.w_footswitches: if wfs.object == footswitch: - if footswitch.parameter is not None: + if footswitch.preset_callback_arg is not None: + footswitch.set_display_label(self.footswitch_label(footswitch)) + active = self.current is not None and self.current.preset_index == footswitch.preset_callback_arg + footswitch.toggled = active + footswitch.set_led(active) + elif footswitch.parameter is not None: # Binding may be new (e.g. MIDI learn) — reflect label + color. footswitch.set_display_label(self.footswitch_label(footswitch)) wfs.color = self.get_category_color(footswitch.category) diff --git a/tests/snapshots/v3/test_presets/test_v3_preset_change_via_lcd/nav_D.png b/tests/snapshots/v3/test_presets/test_v3_preset_change_via_lcd/nav_D.png index ba358de3c3310244eee0ab3909b48a2a42606c64..8d2e45f57928254f47772abcc7ba94b950a6a797 100644 GIT binary patch literal 6277 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 Y(PSlE`;bJEakW(CnWjR8yhX@=0Vbz0*#H0l literal 6270 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 mbN9awj)EEfceO=LxDWk8`L4|q$^Y@` in config). + +Covers four behaviors: + 1. The label shows the snapshot name, not the raw index. + 2. `Hardware.__init_footswitches` clears any midi_CC (default-config or + override) when `preset:` is configured, since a preset footswitch's own + press dispatch always short-circuits on preset_callback before ever + reaching midi_CC emission (see Footswitch.pressed) -- an + inherited-but-unused CC otherwise sits in `hw.controllers` where + ControllerManager.bind() can match it against an unrelated plugin's + MIDI-learned binding and steal fs.parameter. + 3. The label survives even if `fs.parameter` still ends up set by some + other path -- defense in depth on top of (2), so + `draw_footswitch`/`draw_unbound_footswitches`/`update_footswitch` never + let a plugin/param name clobber a preset label. + 4. The footswitch's LED/indicator lights only when its mapped snapshot is + the currently active one. +""" + +import yaml +from unittest.mock import MagicMock + +from common.parameter import Parameter +from tests.types import SystemFixture + + +def _plugin_param() -> Parameter: + p = MagicMock(spec=Parameter) + p.symbol = ":bypass" + p.instance_id = "/Reverb" + p.value = 0 + p.minimum = 0 + p.maximum = 1 + return p + + +class TestPresetFootswitchLabel: + def test_shows_snapshot_name_not_index(self, v3_system: SystemFixture): + handler = v3_system.handler + fs = v3_system.hw.footswitches[0] + fs.add_preset(callback=handler.preset_set_and_change, callback_arg=1) + + # v3_system's snapshot/list mock returns {"0": "Clean", "1": "Lead"} + # (footswitch_label lowercases, same as it does for plugin names) + assert handler.lcd.footswitch_label(fs) == "lead" + + def test_falls_back_to_index_when_name_unknown(self, v3_system: SystemFixture): + handler = v3_system.handler + fs = v3_system.hw.footswitches[0] + fs.add_preset(callback=handler.preset_set_and_change, callback_arg=7) + + assert handler.lcd.footswitch_label(fs) == "7" + + +class TestPresetConfigClearsDefaultMidiCC: + """Root-cause coverage: a footswitch config'd with `preset:` must lose any + midi_CC (default-config or override) and drop out of hw.controllers, so it + can never be matched by ControllerManager.bind() against an unrelated + plugin's MIDI-learned parameter.""" + + def test_preset_override_clears_inherited_default_midi_cc(self, v3_system: SystemFixture, tmp_path): + handler = v3_system.handler + hw = v3_system.hw + # default_config_pistomptre.yml gives footswitch 1 midi_CC: 61 with no preset. + fs1 = hw.footswitches[1] + assert fs1.midi_CC == 61 + assert any(v is fs1 for v in hw.controllers.values()) + + bundle_dir = tmp_path / "preset_rig.pedalboard" + bundle_dir.mkdir() + (bundle_dir / "config.yml").write_text( + yaml.dump({"hardware": {"footswitches": [{"id": 1, "preset": 0}]}}) + ) + pb = handler.pedalboards["/path/to/new.pedalboard"] + pb.bundle = str(bundle_dir) + pb.plugins = [] + + handler.set_current_pedalboard(pb) + + assert fs1.midi_CC is None + assert all(v is not fs1 for v in hw.controllers.values()) + + +class TestPresetFootswitchLabelSurvivesParameterBinding: + def test_draw_unbound_footswitches_keeps_snapshot_label(self, v3_system: SystemFixture): + handler = v3_system.handler + hw = v3_system.hw + lcd = handler.lcd + fs = hw.footswitches[0] + fs.add_preset(callback=handler.preset_set_and_change, callback_arg=0) + fs.parameter = _plugin_param() # defense-in-depth: simulates fs.parameter + # getting set through some other path despite (2) above + + lcd.link_data(handler.pedalboard_list, handler.current, hw.footswitches) + lcd.draw_main_panel() + + assert fs.get_display_label() == "clean" + + def test_update_footswitch_keeps_snapshot_label(self, v3_system: SystemFixture): + handler = v3_system.handler + hw = v3_system.hw + lcd = handler.lcd + fs = hw.footswitches[0] + fs.add_preset(callback=handler.preset_set_and_change, callback_arg=0) + + lcd.link_data(handler.pedalboard_list, handler.current, hw.footswitches) + lcd.draw_main_panel() + + fs.parameter = _plugin_param() # bound after the fact (e.g. MIDI learn) + lcd.update_footswitch(fs) + + assert fs.get_display_label() == "clean" + + +class TestPresetFootswitchIndicator: + def test_active_snapshot_footswitch_drives_physical_led(self, v3_system: SystemFixture): + """A press never touches fs.toggled for preset footswitches + (Footswitch.pressed returns early on the preset_callback branch), so + the LCD redraw path is the only place that can also light the + physical LED/pixel.""" + handler = v3_system.handler + hw = v3_system.hw + lcd = handler.lcd + fs0, fs1 = hw.footswitches[0], hw.footswitches[1] + fs0.pixel = MagicMock() + fs1.pixel = MagicMock() + fs0.add_preset(callback=handler.preset_set_and_change, callback_arg=0) + fs1.add_preset(callback=handler.preset_set_and_change, callback_arg=1) + handler.current.preset_index = 1 + + lcd.link_data(handler.pedalboard_list, handler.current, hw.footswitches) + lcd.draw_main_panel() + + fs0.pixel.set_enable.assert_called_once_with(False) + fs1.pixel.set_enable.assert_called_once_with(True) + assert fs0.toggled is False + assert fs1.toggled is True + + def test_active_snapshot_footswitch_is_lit(self, v3_system: SystemFixture): + handler = v3_system.handler + hw = v3_system.hw + lcd = handler.lcd + fs0, fs1 = hw.footswitches[0], hw.footswitches[1] + fs0.add_preset(callback=handler.preset_set_and_change, callback_arg=0) + fs1.add_preset(callback=handler.preset_set_and_change, callback_arg=1) + handler.current.preset_index = 1 + + lcd.link_data(handler.pedalboard_list, handler.current, hw.footswitches) + lcd.draw_main_panel() + + w0 = next(w for w in lcd.w_footswitches if w.object is fs0) + w1 = next(w for w in lcd.w_footswitches if w.object is fs1) + assert w1.is_bypassed is False # active snapshot -> lit + assert w0.is_bypassed is True # inactive snapshot -> dark + + def test_preset_change_relights_indicator(self, v3_system: SystemFixture): + handler = v3_system.handler + hw = v3_system.hw + lcd = handler.lcd + fs0, fs1 = hw.footswitches[0], hw.footswitches[1] + fs0.add_preset(callback=handler.preset_set_and_change, callback_arg=0) + fs1.add_preset(callback=handler.preset_set_and_change, callback_arg=1) + + lcd.link_data(handler.pedalboard_list, handler.current, hw.footswitches) + lcd.draw_main_panel() + + handler.preset_change(1) + + w0 = next(w for w in lcd.w_footswitches if w.object is fs0) + w1 = next(w for w in lcd.w_footswitches if w.object is fs1) + assert w1.is_bypassed is False + assert w0.is_bypassed is True From ba4e52c190444f61f32be083824b630404d7c5f7 Mon Sep 17 00:00:00 2001 From: Cam Gorrie Date: Thu, 2 Jul 2026 19:54:21 -0400 Subject: [PATCH 2/4] Fix brightening bug --- modalapi/modhandler.py | 6 ++++-- .../test_v3_preset_change_via_lcd/nav_D.png | Bin 6277 -> 6270 bytes 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/modalapi/modhandler.py b/modalapi/modhandler.py index d601eae04..6075719ab 100755 --- a/modalapi/modhandler.py +++ b/modalapi/modhandler.py @@ -728,9 +728,11 @@ def preset_change(self, index): logging.error("Bad Rest request: %s" % url) self.current.preset_index = index - # Update name on lcd, and relight any footswitch mapped to a snapshot + # Update name on lcd, and relight any footswitch mapped to a snapshot. self.lcd.draw_title() - self.lcd.update_footswitches() + for fs in self.hardware.footswitches: + if fs.preset_callback_arg is not None: + self.lcd.update_footswitch(fs) # Bypass/param changes from the snapshot arrive via the WS drain (source of truth). def preset_incr_and_change(self, *argv): diff --git a/tests/snapshots/v3/test_presets/test_v3_preset_change_via_lcd/nav_D.png b/tests/snapshots/v3/test_presets/test_v3_preset_change_via_lcd/nav_D.png index 8d2e45f57928254f47772abcc7ba94b950a6a797..ba358de3c3310244eee0ab3909b48a2a42606c64 100644 GIT binary patch literal 6270 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 Y(PSlE`;bJEakW(CnWjR8yhX@=0Vbz0*#H0l From 72df2c1de9949c7b7717733547e13b825e60b3e7 Mon Sep 17 00:00:00 2001 From: Cam Gorrie Date: Thu, 2 Jul 2026 20:02:51 -0400 Subject: [PATCH 3/4] Fix preset footswitch label blanking after any relight get_display_label() treated midi_CC is None as "unbound, show nothing" -- true before preset switches existed, but Hardware.__init_footswitches now clears midi_CC for them by design. Any refresh that reads get_display_label() instead of the freshly computed label (e.g. update_footswitch on the next preset_change) wiped the snapshot name back to empty. Preset-bound switches are now exempt from the blank-out rule. Co-Authored-By: Claude Sonnet 5 --- pistomp/footswitch.py | 2 +- .../after_preset_change.png | Bin 0 -> 7157 bytes .../before_preset_change.png | Bin 0 -> 7638 bytes tests/v3/test_footswitch_presets.py | 41 ++++++++++++++++++ 4 files changed, 42 insertions(+), 1 deletion(-) create mode 100644 tests/snapshots/v3/test_footswitch_presets/test_preset_change_does_not_blank_the_label/after_preset_change.png create mode 100644 tests/snapshots/v3/test_footswitch_presets/test_preset_change_does_not_blank_the_label/before_preset_change.png diff --git a/pistomp/footswitch.py b/pistomp/footswitch.py index 1dbada1b8..cff6809b0 100755 --- a/pistomp/footswitch.py +++ b/pistomp/footswitch.py @@ -128,7 +128,7 @@ def __init__(self, id, led_pin, pixel, midi_CC, midi_channel, midiout, refresh_c def get_display_label(self): if self.taptempo and self.taptempo.is_enabled(): return str(round(self.taptempo.get_bpm())) - elif self.midi_CC is None: + elif self.midi_CC is None and self.preset_callback_arg is None: return "" else: return self.display_label diff --git a/tests/snapshots/v3/test_footswitch_presets/test_preset_change_does_not_blank_the_label/after_preset_change.png b/tests/snapshots/v3/test_footswitch_presets/test_preset_change_does_not_blank_the_label/after_preset_change.png new file mode 100644 index 0000000000000000000000000000000000000000..8476af2679c76ec19e9e62a1a8eed8e4209f31a6 GIT binary patch literal 7157 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 yW8M{n4)Okpz3{1<$}9UgZ>A-o$J^D literal 0 HcmV?d00001 diff --git a/tests/snapshots/v3/test_footswitch_presets/test_preset_change_does_not_blank_the_label/before_preset_change.png b/tests/snapshots/v3/test_footswitch_presets/test_preset_change_does_not_blank_the_label/before_preset_change.png new file mode 100644 index 0000000000000000000000000000000000000000..1a33b7cf04baa0df644889f299002cbc1000fb39 GIT binary patch literal 7638 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 Date: Thu, 2 Jul 2026 21:47:59 -0400 Subject: [PATCH 4/4] Nice fix --- pistomp/footswitch.py | 2 ++ tests/test_footswitch.py | 18 ++++++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/pistomp/footswitch.py b/pistomp/footswitch.py index cff6809b0..f4fef88c7 100755 --- a/pistomp/footswitch.py +++ b/pistomp/footswitch.py @@ -282,4 +282,6 @@ def clear_pedalboard_info(self): self.display_label = None self.set_category(None) self.preset_callback = None + self.preset_callback_arg = None + self.parameter = None self.clear_relays() diff --git a/tests/test_footswitch.py b/tests/test_footswitch.py index c504d5085..bf605b8ee 100644 --- a/tests/test_footswitch.py +++ b/tests/test_footswitch.py @@ -138,3 +138,21 @@ def test_clears_state(self): assert fs.toggled is False assert fs.display_label is None assert fs.preset_callback is None + + def test_clears_preset_callback_arg(self): + with _make_footswitch(midi_CC=None) as fs: + fs.add_preset(callback=MagicMock(), callback_arg=5) + fs.set_display_label("Lead") + + fs.clear_pedalboard_info() + + assert fs.preset_callback_arg is None + assert fs.get_display_label() == "" + + def test_clears_parameter(self): + with _make_footswitch(midi_CC=None) as fs: + fs.parameter = MagicMock() # pyright: ignore[reportAttributeAccessIssue] + + fs.clear_pedalboard_info() + + assert fs.parameter is None