# Cc learning/routing again

**URL:** <https://discourse.zynthian.org/t/cc-learning-routing-again/13786>\
**Category:** Using Zynthian\
**Created:** [October 23, 2026, 3:31pm UTC](https://discourse.zynthian.org/t/cc-learning-routing-again/13786 "2026-10-23T15:31:10Z")\
**Posts on this page:** 6\
**Page:** 1

<div class="post-metadata">

**Author:** ![captainburek](https://discourse.zynthian.org/letter_avatar_proxy/v4/letter/c/e47c2d/32.png) [@captainburek](https://discourse.zynthian.org/u/captainburek)\
**Post date:** [October 23, 2026, 3:31pm UTC](https://discourse.zynthian.org/t/cc-learning-routing-again/13786/1 "2026-10-23T15:31:11Z")

</div>

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!

---

<div class="post-metadata">

**Author:** ![tunagenes](https://discourse.zynthian.org/user_avatar/discourse.zynthian.org/tunagenes/32/7279_2.png) [@tunagenes](https://discourse.zynthian.org/u/tunagenes)\
**Post date:** [October 23, 2026, 5:28pm UTC](https://discourse.zynthian.org/t/cc-learning-routing-again/13786/2 "2026-10-23T17:28:53Z")

</div>

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’:

> [@NEW: Control-device manager + controller device "drivers"](https://discourse.zynthian.org/t/new-control-device-manager-controller-device-drivers/8593):
>
> Hi @zynthianers! I’m proud of announcing the new control-device manager and driver subsystem is merged on testing branch. This is nice step forward in our road towards world domination grin The concept behind this is simple to explain with some examples: You plug your “Novation Launchpad Mini MK3” to zynthian, and it 's recognized and configured to manage the zynpad. The pads are colored like the zynpad ones. You can play/stop sequences, change scene, etc. LED feedback works great and you …

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

> **[GitHub - zynthian/zynthian-issue-tracking: Centralized Issue Tracking for Zynthian Project](https://github.com/zynthian/zynthian-issue-tracking)**
>
> Centralized Issue Tracking for Zynthian Project

---

<div class="post-metadata">

**Author:** ![tunagenes](https://discourse.zynthian.org/user_avatar/discourse.zynthian.org/tunagenes/32/7279_2.png) [@tunagenes](https://discourse.zynthian.org/u/tunagenes)\
**Post date:** [October 23, 2026, 5:44pm UTC](https://discourse.zynthian.org/t/cc-learning-routing-again/13786/3 "2026-10-23T17:44:55Z")

</div>

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

```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:

- [zynthian\_chain\_manager.py (vangelis)](https://raw.githubusercontent.com/zynthian/zynthian-ui/vangelis/zyngine/zynthian_chain_manager.py)
- [Zynthian help – Chain Control / MIDI Learn](https://help.zynthian.org/Z2/chain_control.html)
- [Forum: Midi CC per chain independent from chain selection](https://discourse.zynthian.org/t/midi-cc-per-chain-independant-from-chain-selection/13266)

---

<div class="post-metadata">

**Author:** ![jofemodo](https://discourse.zynthian.org/user_avatar/discourse.zynthian.org/jofemodo/32/2_2.png) [@jofemodo](https://discourse.zynthian.org/u/jofemodo)\
**Post date:** [October 23, 2026, 5:49pm UTC](https://discourse.zynthian.org/t/cc-learning-routing-again/13786/4 "2026-10-23T17:49:22Z")

</div>

> [@captainburek](#):
>
> 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?

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,

---

<div class="post-metadata">

**Author:** ![captainburek](https://discourse.zynthian.org/letter_avatar_proxy/v4/letter/c/e47c2d/32.png) [@captainburek](https://discourse.zynthian.org/u/captainburek)\
**Post date:** [October 23, 2026, 5:58pm UTC](https://discourse.zynthian.org/t/cc-learning-routing-again/13786/5 "2026-10-23T17:58:00Z")

</div>

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.

---

<div class="post-metadata">

**Author:** ![HansR](https://discourse.zynthian.org/user_avatar/discourse.zynthian.org/hansr/32/15102_2.png) [@HansR](https://discourse.zynthian.org/u/HansR)\
**Post date:** [October 24, 2026, 2:06pm UTC](https://discourse.zynthian.org/t/cc-learning-routing-again/13786/6 "2026-10-24T14:06:44Z")

</div>

> [@jofemodo](#):
>
> Why this “super-global” behavior

I get the impression he wants a master volume control…
