Summary
A MIDI-CC binding can carry a sub-range (mod-ui calls this "advanced" addressing). We write that sub-range into Parameter.minimum and Parameter.maximum. This is wrong. The sub-range belongs to the CC mapping. It does not limit the port.
With Gain declared 0..1 and addressed to an encoder over 0.25..0.75, the pedal cannot set Gain to 0.0 or 1.0 from any screen. mod-ui can.
Evidence
mod-ui does not clamp. Host.param_set (mod/host.py:2716-2739) checks the designations list, writes pluginData['ports'][symbol] = value, and forwards it. There is no clamp to the addressing range and none to the port range.
The sub-range is an argument to the mapping. mod-ui sends it to mod-host as:
midi_map <instance> <symbol> <channel> <cc> <minimum> <maximum>
(mod/host.py:2205.) It says how the 128 CC codes spread across the port. It says nothing about which values the port accepts.
What is wrong today
Parameter.set_binding_range (common/parameter.py:230) replaces the declared extents. Three results follow.
- No screen can leave the sub-range.
ParameterSteps.for_parameter builds its grid from parameter.minimum and parameter.maximum, so every panel and dialog stops at the sub-range ends.
- Adding an address changes the audio.
set_binding_range clamps the current value into the new range (:234). mod-ui never moves a port value when you address it.
- An echo can leave the parameter out of range.
reconcile does not clamp. If mod-ui sets Gain to 0.9, _value is 0.9 while maximum is 0.75, and the widgets then disagree with the parameter.
Inconsistent behaviour today
The EQ panels are the exception, and they are correct. plugins/eq/graphic.py:482 and plugins/eq/parametric.py:905 build ParameterSteps from the band spec, which is the port's real travel. So an EQ band keeps its full range while every other panel is cut short.
The pinned arc slots in ParameterWindow cache the extents at construction, so they also show the wrong scale after a live re-address. Panels built on ArcKnobWidget (gx_cabinet, tap_reverb, mixer) pass literal ranges to the widget, so their rings draw the declared scale while a detent sweep covers only part of it.
These are three different symptoms of one cause. Do not fix them one at a time.
Proposed design
Keep the declared range on the Parameter. Store the sub-range with the mapping, not with the port. Then:
- A physical encoder sweeps the sub-range. This is the purpose of advanced addressing: the knob's travel covers 0.25..0.75.
- A panel or dialog sweeps the declared range, as mod-ui does.
to_midi uses the sub-range. A value outside it has no CC code, so it goes over the WebSocket instead.
The last rule is not new. Modhandler._publish_switch_cc already does this for a footswitch: the switch's CC carries only two codes, so a value between them takes the WebSocket. An encoder's CC carries only the sub-range, so a value outside it takes the WebSocket. Same reason, same machinery.
Scope
common/parameter.py — set_binding_range and clear_binding_range go away. minimum and maximum become the declared extents only.
pistomp/handler.py:242 — the MIDI-learn path gives the range to the controller, not the parameter.
pistomp/encoder_controller.py — to_midi and bar_midi_value read the sub-range from the controller.
modalapi/modhandler.py — _sink_for routes a value outside the sub-range over the WebSocket.
plugins/parameter_window.py, plugins/layouts/arc_knob.py — widgets read the declared range, which removes the stale-extent and literal-range problems.
tests/test_parameter_binding_range.py — rewrite. The current tests assert the behaviour this issue calls wrong.
Out of scope
PR #252 does not change any of this. It found the problem and left it.
Summary
A MIDI-CC binding can carry a sub-range (mod-ui calls this "advanced" addressing). We write that sub-range into
Parameter.minimumandParameter.maximum. This is wrong. The sub-range belongs to the CC mapping. It does not limit the port.With Gain declared 0..1 and addressed to an encoder over 0.25..0.75, the pedal cannot set Gain to 0.0 or 1.0 from any screen. mod-ui can.
Evidence
mod-ui does not clamp.
Host.param_set(mod/host.py:2716-2739) checks the designations list, writespluginData['ports'][symbol] = value, and forwards it. There is no clamp to the addressing range and none to the port range.The sub-range is an argument to the mapping. mod-ui sends it to mod-host as:
(
mod/host.py:2205.) It says how the 128 CC codes spread across the port. It says nothing about which values the port accepts.What is wrong today
Parameter.set_binding_range(common/parameter.py:230) replaces the declared extents. Three results follow.ParameterSteps.for_parameterbuilds its grid fromparameter.minimumandparameter.maximum, so every panel and dialog stops at the sub-range ends.set_binding_rangeclamps the current value into the new range (:234). mod-ui never moves a port value when you address it.reconciledoes not clamp. If mod-ui sets Gain to 0.9,_valueis 0.9 whilemaximumis 0.75, and the widgets then disagree with the parameter.Inconsistent behaviour today
The EQ panels are the exception, and they are correct.
plugins/eq/graphic.py:482andplugins/eq/parametric.py:905buildParameterStepsfrom the band spec, which is the port's real travel. So an EQ band keeps its full range while every other panel is cut short.The pinned arc slots in
ParameterWindowcache the extents at construction, so they also show the wrong scale after a live re-address. Panels built onArcKnobWidget(gx_cabinet,tap_reverb,mixer) pass literal ranges to the widget, so their rings draw the declared scale while a detent sweep covers only part of it.These are three different symptoms of one cause. Do not fix them one at a time.
Proposed design
Keep the declared range on the
Parameter. Store the sub-range with the mapping, not with the port. Then:to_midiuses the sub-range. A value outside it has no CC code, so it goes over the WebSocket instead.The last rule is not new.
Modhandler._publish_switch_ccalready does this for a footswitch: the switch's CC carries only two codes, so a value between them takes the WebSocket. An encoder's CC carries only the sub-range, so a value outside it takes the WebSocket. Same reason, same machinery.Scope
common/parameter.py—set_binding_rangeandclear_binding_rangego away.minimumandmaximumbecome the declared extents only.pistomp/handler.py:242— the MIDI-learn path gives the range to the controller, not the parameter.pistomp/encoder_controller.py—to_midiandbar_midi_valueread the sub-range from the controller.modalapi/modhandler.py—_sink_forroutes a value outside the sub-range over the WebSocket.plugins/parameter_window.py,plugins/layouts/arc_knob.py— widgets read the declared range, which removes the stale-extent and literal-range problems.tests/test_parameter_binding_range.py— rewrite. The current tests assert the behaviour this issue calls wrong.Out of scope
PR #252 does not change any of this. It found the problem and left it.