Skip to content

Commit

Permalink
Implement chain's CC routing, so selected CC numbers can be routed th…
Browse files Browse the repository at this point in the history
…ru synth chains.

Note that MIDI chains route all CC and PC messages.
  • Loading branch information
jofemodo committed Mar 6, 2024
1 parent a4022e8 commit d8f18de
Show file tree
Hide file tree
Showing 6 changed files with 89 additions and 27 deletions.
15 changes: 14 additions & 1 deletion zyngine/zynthian_chain.py
Expand Up @@ -23,6 +23,7 @@
#
# *****************************************************************************

import ctypes
import logging

# Zynthian specific modules
Expand Down Expand Up @@ -144,11 +145,13 @@ def set_mixer_chan(self, chan):
self.rebuild_audio_graph()

def set_zmop_options(self):
if self.zmop_index is not None and (len(self.synth_slots) > 0 or len(self.audio_slots) > 0):
if self.zmop_index is not None and len(self.synth_slots) > 0:
# IMPORTANT!!! Synth chains drop CC & PC messages
#logging.info(f"Dropping MIDI CC & PC from chain {self.chain_id}")
lib_zyncore.zmop_set_flag_droppc(self.zmop_index, 1)
lib_zyncore.zmop_set_flag_dropcc(self.zmop_index, 1)
else:
# Audio & MIDI chains doesn't drop CC & PC messages
#logging.info(f"Routing MIDI CC & PC to chain {self.chain_id}")
lib_zyncore.zmop_set_flag_droppc(self.zmop_index, 0)
lib_zyncore.zmop_set_flag_dropcc(self.zmop_index, 0)
Expand All @@ -162,6 +165,7 @@ def set_zmop_index(self, iz):
if iz is not None and iz >= 0:
self.zmop_index = iz
self.midi_in = [f"ZynMidiRouter:ch{iz}_out"]
lib_zyncore.zmop_reset_cc_route(iz)
if self.midi_chan is not None:
if 0 <= self.midi_chan < 16:
lib_zyncore.zmop_set_midi_chan(iz, self.midi_chan)
Expand Down Expand Up @@ -769,13 +773,22 @@ def get_state(self):
if slot_state:
slots_states.append(slot_state)

# Get ZMOP CC route state
cc_route_ct = (ctypes.c_uint8 * 128)()
if self.zmop_index is not None and self.zmop_index >= 0:
lib_zyncore.zmop_get_cc_route(self.zmop_index, cc_route_ct)
cc_route = []
for ccr in cc_route_ct:
cc_route.append(ccr)

state = {
"title": self.title,
"midi_chan": self.midi_chan,
"midi_thru": self.midi_thru,
"audio_thru": self.audio_thru,
"mixer_chan": self.mixer_chan,
"zmop_index": self.zmop_index,
"cc_route": cc_route,
"slots": slots_states,
"fader_pos": self.fader_pos
}
Expand Down
23 changes: 17 additions & 6 deletions zyngine/zynthian_chain_manager.py
Expand Up @@ -162,7 +162,7 @@ def save_engine_info(cls):
# Chain Management
# ------------------------------------------------------------------------

def add_chain(self, chain_id, midi_chan=None, midi_thru=False, audio_thru=False, mixer_chan=None, zmop_index=None, title="", chain_pos=None, fast_refresh=True):
def add_chain(self, chain_id, midi_chan=None, midi_thru=False, audio_thru=False, mixer_chan=None, title="", chain_pos=None, fast_refresh=True):
"""Add a chain
chain_id: UID of chain (None to get next available)
Expand Down Expand Up @@ -265,11 +265,22 @@ def add_chain_from_state(self, chain_id, chain_state):
mixer_chan = chain_state['mixer_chan']
else:
mixer_chan = None
if 'zmop_index' in chain_state:
zmop_index = chain_state['zmop_index']
else:
zmop_index = None
self.add_chain(chain_id, midi_chan=midi_chan, midi_thru=midi_thru, audio_thru=audio_thru, mixer_chan=mixer_chan, zmop_index=zmop_index, title=title, fast_refresh=False)

# This doen't have any effect because zmop_index is not restored
#if 'zmop_index' in chain_state:
# zmop_index = chain_state['zmop_index']
#else:
# zmop_index = None

self.add_chain(chain_id, midi_chan=midi_chan, midi_thru=midi_thru, audio_thru=audio_thru, mixer_chan=mixer_chan, title=title, fast_refresh=False)

# Set CC route state
zmop_index = self.chains[chain_id].zmop_index
if 'cc_route' in chain_state and zmop_index is not None and zmop_index >= 0:
cc_route_ct = (ctypes.c_uint8 * 128)()
for ccnum, ccr in enumerate(chain_state['cc_route']):
cc_route_ct[ccnum] = ccr
lib_zyncore.zmop_set_cc_route(zmop_index, cc_route_ct)

def remove_chain(self, chain_id, stop_engines=True, fast_refresh=True):
"""Removes a chain or resets main chain
Expand Down
2 changes: 1 addition & 1 deletion zyngine/zynthian_processor.py
Expand Up @@ -605,7 +605,7 @@ def send_ctrl_midi_cc(self):
for k, zctrl in self.controllers_dict.items():
mval = None
if zctrl.midi_cc:
mval = self.get_ctrl_midi_val()
mval = zctrl.get_ctrl_midi_val()
zctrl.send_midi_cc(mval)
#logging.debug("Sending MIDI CH{}#CC{}={} for {}".format(zctrl.midi_chan, zctrl.midi_cc, int(mval), k))
if zctrl.midi_feedback:
Expand Down
1 change: 1 addition & 0 deletions zyngine/zynthian_state_manager.py
Expand Up @@ -307,6 +307,7 @@ def clean_sequences(self):

# -------------------------------------------------------------------------
# Busy state management
# Busy state management
# -------------------------------------------------------------------------

def start_busy(self, clid, message=None, details=None):
Expand Down
17 changes: 14 additions & 3 deletions zyngui/zynthian_gui_chain_options.py
Expand Up @@ -53,7 +53,11 @@ def setup(self, chain_id=None, proc=None):

def fill_list(self):
self.list_data = []


synth_proc_count = self.chain.get_processor_count("Synth")
midi_proc_count = self.chain.get_processor_count("MIDI Tool")
audio_proc_count = self.chain.get_processor_count("Audio Effect")

if self.chain.is_midi():
self.list_data.append((self.chain_note_range, None, "Note Range & Transpose"))
self.list_data.append((self.chain_midi_capture, None, "MIDI In"))
Expand All @@ -64,6 +68,9 @@ def fill_list(self):
if self.chain.is_midi():
self.list_data.append((self.chain_midi_chan, None, "MIDI Channel"))

if synth_proc_count:
self.list_data.append((self.chain_midi_cc, None, "MIDI CC"))

if self.chain.get_processor_count() and not zynthian_gui_config.check_wiring_layout(["Z2", "V5"]):
# TODO Disable midi learn for some chains???
self.list_data.append((self.midi_learn, None, "MIDI Learn"))
Expand Down Expand Up @@ -97,11 +104,11 @@ def fill_list(self):
self.list_data.append((self.postfader_add, None, "Add Post-fader Audio-FX"))

if self.chain_id != 0:
if self.chain.get_processor_count("Synth") * self.chain.get_processor_count("MIDI Tool") + self.chain.get_processor_count("Audio Effect") == 0:
if synth_proc_count * midi_proc_count + audio_proc_count == 0:
self.list_data.append((self.remove_chain, None, "Remove Chain"))
else:
self.list_data.append((self.remove_cb, None, "Remove..."))
elif self.chain.get_processor_count("Audio Effect") > 0:
elif audio_proc_count > 0:
self.list_data.append((self.remove_all_audiofx, None, "Remove all Audio-FX"))

self.list_data.append((None, None, "> GUI"))
Expand Down Expand Up @@ -232,6 +239,10 @@ def chain_midi_chan(self):
self.zyngui.screens['midi_chan'].set_mode("SET", self.chain.midi_chan, chan_all=chan_all)
self.zyngui.show_screen('midi_chan')

def chain_midi_cc(self):
self.zyngui.screens['midi_cc'].set_chain(self.chain)
self.zyngui.show_screen('midi_cc')

def chain_note_range(self):
self.zyngui.screens['midi_key_range'].config(self.chain)
self.zyngui.show_screen('midi_key_range')
Expand Down
58 changes: 42 additions & 16 deletions zyngui/zynthian_gui_midi_cc.py
Expand Up @@ -4,7 +4,7 @@
#
# Zynthian GUI Midi-CC Selector Class
#
# Copyright (C) 2015-2020 Fernando Moyano <jofemodo@zynthian.org>
# Copyright (C) 2015-2024 Fernando Moyano <jofemodo@zynthian.org>
#
# ******************************************************************************
#
Expand All @@ -22,9 +22,11 @@
#
# ******************************************************************************

import ctypes
import logging

# Zynthian specific modules
from zyncoder.zyncore import lib_zyncore
from zyngui.zynthian_gui_selector import zynthian_gui_selector

# ------------------------------------------------------------------------------
Expand All @@ -35,31 +37,55 @@
class zynthian_gui_midi_cc(zynthian_gui_selector):

def __init__(self):
self.cc = [0] * 16
self.chain = None
self.zmop_index = None
self.cc_route = None
super().__init__('CC', True)

def set_chain(self, chain):
if chain.zmop_index is not None and chain.zmop_index >= 0:
self.chain = chain
self.zmop_index = chain.zmop_index
else:
self.chain = None
self.zmop_index = None

def build_layout(self):
if self.chain:
return super().build_layout()
else:
return False

def fill_list(self):
self.list_data = []
for i, ccnum in enumerate(self.cc):
if ccnum:
self.list_data.append((str(i), i, "\u2612 CC {}".format(str(i).zfill(2))))

self.cc_route = (ctypes.c_uint8 * 128)()
lib_zyncore.zmop_get_cc_route(self.zmop_index, self.cc_route)

for ccnum, enabled in enumerate(self.cc_route):
if enabled:
self.list_data.append((str(ccnum), ccnum, "\u2612 CC {}".format(str(ccnum).zfill(2))))
else:
self.list_data.append((str(i), i, "\u2610 CC {}".format(str(i).zfill(2))))
self.list_data.append((str(ccnum), ccnum, "\u2610 CC {}".format(str(ccnum).zfill(2))))
super().fill_list()

def select_action(self, i, t='S'):
cc_num = self.list_data[i][1]

if self.cc[cc_num]:
self.cc[cc_num] = 0
ccnum = self.list_data[i][1]
if self.cc_route[ccnum]:
self.cc_route[ccnum] = 0
bullet = "\u2610"
else:
self.cc[cc_num] = 1

# TODO: ACTION CALLBACK! => On close?

self.update_list()
self.cc_route[ccnum] = 1
bullet = "\u2612"
cctext = f"{bullet} CC {str(ccnum).zfill(2)}"
self.list_data[i] = (str(ccnum), ccnum, cctext)
self.listbox.delete(i)
self.listbox.insert(i, cctext)
self.select(i)
# Set CC route state in zyncore
lib_zyncore.zmop_set_cc_route(self.zmop_index, self.cc_route)

def set_select_path(self):
self.select_path.set("Select CC number...")
self.select_path.set("Routed CCs")

# ------------------------------------------------------------------------------

0 comments on commit d8f18de

Please sign in to comment.