Cc learning/routing again

In Vangelis, if I chain learn a chain’s level control for, say, cc 107, it will react to any controller that sends cc 107, but only as long as the chain is selected. I think this is expected behavior. If I globally learn the same cc for the same chain’s level, it only reacts to the controller I learned from (but regardless of whether the chain is selected, as I would expect). So if two controllers on occasion send cc 107, only the one that I used to midi learn will be ‘heard’ by the control. I can also see that in control options for the chain’s level: it says NAME OF CONTROLLER, then cc 107.

What I would like is for any controller that sends cc 107 to change the chain’s level, regardless whether the chain is selected or not. Can I achieve that?

Thanks in advance!

Aye Captain, I don’t know of a way to achieve that directly in the Zynthian UI (other people may).

One way it could be achieved now is by Writing a ‘Controller Device Driver’:

Alternatively, if you can explain the motivation or ‘use-case’ you could write a feature request:

That was my answer above. I entered your original post to Claude, and Claude suggested another workaround using hardware:

Short answer: not with the stock Vangelis UI. A global binding always includes the source device and the MIDI channel, and each control can hold only one non-ZynStep binding at a time. You can’t add a second controller to it, and you can’t make it device-agnostic without a code change.

Raw extraction: zyngine/zynthian_chain_manager.py, vangelis branch, fetched just now

  • Binding tables (from __init__):
    • absolute_midi_cc_binding, keyed by 24-bit ZMOP,CHAN,CC. The comment says ZMOP, but the code actually stores the ZMIP (input device).
    • chain_midi_cc_binding, keyed by 24-bit CHAIN,CHAN,CC.
  • add_midi_learn(), absolute branch: key = (zmip << 16) | (midi_chan << 8) | midi_cc
  • add_midi_learn(), first action: calls remove_midi_learn_from_zctrl(zctrl, chain=True, abs=True, ...). This means every new learn wipes the previous chain and absolute bindings for that control. Only ZynStep mappings survive.
  • midi_control_change(), absolute lookup: key = (zmip << 16) | key_low. This is an exact match. There is no wildcard or “any device” fallback.
  • midi_control_change(), chain lookup: only checks self.active_chain.chain_id, using either (chain, chan, cc) or the channel-unbound (chain, 0xff, cc).

What that means

  • [verified-from-source] Global learn binds the specific input device + channel + CC. That explains why the control options page shows the controller’s name next to CC 107.
  • [verified-from-source] Learning again from the second controller replaces the first binding instead of adding to it. So “learn it from both” can’t work.
  • [verified-from-source] Chain learn ignores the device, but the binding still includes the MIDI channel, unless the binding was stored channel-unbound (0xff). So the poster’s “any controller sends CC 107” holds only if the controllers share a channel.
  • [inferred] The poster also needs both controllers on the same channel for any global solution, because channel sits in the absolute key too.

Workarounds

  1. Merge before Zynthian sees it. Put both controllers through one input device: a hardware MIDI merger into the DIN input, a controller’s MIDI-thru/merge feature, or a multi-port interface that presents one merged port. Zynthian then sees one zmip, and a single global learn covers both. Both controllers must send on the same channel. [inferred, untested]
  2. Code change to add a wildcard device. [sketch, untested]

python

   # in midi_control_change(), replacing the absolute-binding block
   for zm in ((zmip, 0xFF) if zmip != ZMIP_STEP_INDEX else (zmip,)):
       try:
           for zctrl in self.absolute_midi_cc_binding[(zm << 16) | key_low]:
               zctrl.midi_control_change(cc_val)
       except KeyError:
           pass

The lookup side is the easy part. Two pieces would still be missing:

  • UI: some option (“Global learn – any device”) that calls add_midi_learn(chan, cc, zctrl, zmip=0xFF).
  • Snapshots: a check that save/restore survives. [from memory/inferred] I haven’t read how the state manager serialises the zmip; it probably maps it to a device ID, and 0xFF would need special handling. I also haven’t verified that 0xFF can’t collide with a real zmip index.

This looks like a good feature request for riban/jofemodo, or a PR if you want one.

Draft forum reply

You’ve read the behaviour correctly. In Vangelis, a global (absolute) MIDI learn binds the source device + MIDI channel + CC, and learning the same control again from a second device replaces the first binding rather than adding to it. Chain learn ignores the device but only works on the selected chain. So there’s currently no UI way to get “any device, any chain selection.”

Practical workaround: merge the two controllers into a single MIDI input before they reach Zynthian (hardware merger, MIDI thru/merge on one controller, or an interface that presents a merged port), send on the same channel, then global-learn once.

A proper fix would be an “any device” option for global learn. The lookup in zynthian_chain_manager.midi_control_change() is an exact match on the device index, so it would need a wildcard fallback plus UI and snapshot support. Might be worth a feature request.

If you want, I can read the state manager’s snapshot code next to firm up the PR scope.

Sources:

1 Like

Not currently.

As you described above, currently we have 2 MIDI-learning modes:

  • Chain learning
  • Global learning

Adding more MIDI learning modes could be confusing and it should be considered carefully.

Could you explain your use-case? Why this “super-global” behavior is useful for you?

Regards,

Aye! The use case is simple: cc 107 is my main chain volume, and i want that quickly available on either controller (live playing). Similarly for other parameters.

In fact, I think the most general solution would be if more than one midi cc could be learned for the same parameter. This would allow to, for example, have a Midi mixer with a general mapping to all relevant controls in all ZS3s, while adding selected controls directly from the keyboard controller for each ZS3.

@tunagenes: Thanks for your answers, but I’m afraid your suggestions are way above my pay/programming grade.