Skip to content

Commit

Permalink
Merge branch 'vangelis' into feature/762_sooperlooper_per_loop_controls
Browse files Browse the repository at this point in the history
Remove default MIDI binding.
Add per-loop MIDI binding.
Add control to switch between selected chain or global MIDI binding.
Fixes some tallies.
Fixes zynthian/zynthian-issue-tracking#1285. Cannot select loop 1.
  • Loading branch information
riban committed Dec 10, 2024
2 parents 101617b + 73eb255 commit b7561b7
Show file tree
Hide file tree
Showing 48 changed files with 1,394 additions and 904 deletions.
Binary file added icons/audio_input.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added icons/audio_options.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added icons/audio_output.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added icons/audio_processor.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file removed icons/delete_icons.xcf
Binary file not shown.
Binary file added icons/folder.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added icons/metronome.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
12 changes: 12 additions & 0 deletions icons/metronome.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added icons/midi_input.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added icons/midi_output.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added icons/midi_processor.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added icons/note_range.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added icons/synth_processor.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
35 changes: 25 additions & 10 deletions zynautoconnect/zynthian_autoconnect.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,20 +25,19 @@
import os
import re
import usb
import json
import jack
import psutil
import pexpect
import logging
import alsaaudio
import json
from time import sleep
from threading import Thread, Lock
from subprocess import check_output
import pexpect
import psutil

# Zynthian specific modules
import zynconf
from zyncoder.zyncore import lib_zyncore
from zyngui import zynthian_gui_config
import zynconf

# -------------------------------------------------------------------------------
# Configure logging
Expand All @@ -56,6 +55,7 @@
# Fake port class
# -------------------------------------------------------------------------------


class fake_port:
def __init__(self, name):
self.name = name
Expand Down Expand Up @@ -133,6 +133,7 @@ def unset_alias(self, alias):

# MIDI port helper functions


def get_port_friendly_name(uid):
"""Get port friendly name
Expand All @@ -159,7 +160,7 @@ def set_port_friendly_name(port, friendly_name=None):

try:
alias1 = port.aliases[0]
if friendly_name is None:
if not friendly_name:
# Reset name
if alias1 in midi_port_names:
midi_port_names.pop(alias1)
Expand Down Expand Up @@ -349,6 +350,7 @@ def update_system_audio_aliases():
port.unset_alias(a)
port.set_alias(alias)


def add_sidechain_ports(jackname):
"""Add ports that should be treated as sidechain inputs
Expand Down Expand Up @@ -951,9 +953,11 @@ def audio_autoconnect():
# Release Mutex Lock
release_lock()


def get_hw_audio_dst_ports():
return jclient.get_ports("system:playback", is_input=True, is_audio=True, is_physical=True) + jclient.get_ports("zynaout", is_input=True, is_audio=True)


def update_hw_audio_ports():
global alsa_audio_srcs, alsa_audio_dests

Expand Down Expand Up @@ -1007,17 +1011,20 @@ def update_hw_audio_ports():

return dirty


def enable_hotplug():
zynthian_gui_config.hotplug_audio_enabled = True
zynconf.save_config({"ZYNTHIAN_HOTPLUG_AUDIO": str(zynthian_gui_config.hotplug_audio_enabled)})
update_hw_audio_ports()
audio_autoconnect()


def disable_hotplug():
zynthian_gui_config.hotplug_audio_enabled = False
zynconf.save_config({"ZYNTHIAN_HOTPLUG_AUDIO": str(zynthian_gui_config.hotplug_audio_enabled)})
stop_all_alsa_in_out()


def enable_audio_input_device(device, enable=True):
if enable:
if start_alsa_in(device):
Expand All @@ -1029,6 +1036,7 @@ def enable_audio_input_device(device, enable=True):
zynthian_gui_config.disabled_audio_in.append(device)
zynconf.save_config({"ZYNTHIAN_HOTPLUG_AUDIO_DISABLED_IN": ",".join(zynthian_gui_config.disabled_audio_in)})


def enable_audio_output_device(device, enable=True):
if enable:
if start_alsa_out(device):
Expand All @@ -1040,6 +1048,7 @@ def enable_audio_output_device(device, enable=True):
zynthian_gui_config.disabled_audio_out.append(device)
zynconf.save_config({"ZYNTHIAN_HOTPLUG_AUDIO_DISABLED_OUT": ",".join(zynthian_gui_config.disabled_audio_out)})


def get_alsa_hotplug_audio_devices(playback=True):
devices = []
for card in alsaaudio.pcms(alsaaudio.PCM_PLAYBACK if playback else alsaaudio.PCM_CAPTURE):
Expand All @@ -1051,6 +1060,7 @@ def get_alsa_hotplug_audio_devices(playback=True):
devices.append(device)
return devices


def start_alsa_in(device):
global alsa_audio_srcs
if device in alsa_audio_srcs:
Expand All @@ -1069,6 +1079,7 @@ def start_alsa_in(device):
logging.warning(f"Failed to set {device} aliases")
return True


def stop_alsa_in(device):
global alsa_audio_srcs
if device not in alsa_audio_srcs:
Expand All @@ -1077,6 +1088,7 @@ def stop_alsa_in(device):
alsa_audio_srcs.pop(device)
return True


def start_alsa_out(device):
global alsa_audio_dests
if device in alsa_audio_dests:
Expand All @@ -1095,6 +1107,7 @@ def start_alsa_out(device):
logging.warning(f"Failed to set {device} aliases")
return True


def stop_alsa_out(device):
global alsa_audio_dests
if device not in alsa_audio_dests:
Expand All @@ -1103,22 +1116,23 @@ def stop_alsa_out(device):
alsa_audio_dests.pop(device)
return True


def stop_all_alsa_in_out():
for device in get_alsa_hotplug_audio_devices(False):
stop_alsa_in(device)
for device in get_alsa_hotplug_audio_devices(True):
stop_alsa_out(device)


# Connect mixer to the ffmpeg recorder
def audio_connect_ffmpeg(timeout=2.0):
t = 0
while t < timeout:
try:
# TODO: Do we want post fader, post effects feed?
jclient.connect(
f"zynmixer:output_{MAIN_MIX_CHAN}a", "ffmpeg:input_1")
jclient.connect(
f"zynmixer:output_{MAIN_MIX_CHAN}b", "ffmpeg:input_2")
# => It's just for recording video tutorials, but if the recorded video is about post-fader effects ...
jclient.connect(f"zynmixer:output_{MAIN_MIX_CHAN}a", "ffmpeg:input_1")
jclient.connect(f"zynmixer:output_{MAIN_MIX_CHAN}b", "ffmpeg:input_2")
return
except:
sleep(0.1)
Expand Down Expand Up @@ -1360,6 +1374,7 @@ def init():
update_midi_in_dev_mode_all()
update_system_audio_aliases()


def start(sm):
"""Initialise autoconnect and start MIDI port checker
Expand Down
42 changes: 33 additions & 9 deletions zynconf/zynthian_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -205,11 +205,10 @@ def load_config(set_env=True, fpath=None):
res = pattern.match(line)
if res:
varnames.append(res.group(1))
# logging.debug("CONFIG VARNAME: %s" % res.group(1))
#logging.debug(f"CONFIG VARNAMES: {varnames}")

# Execute config script and dump environment
env = check_output("source \"{}\";env".format(
fpath), shell=True, universal_newlines=True, executable="/bin/bash")
env = check_output("source \"{}\";env".format(fpath), shell=True, universal_newlines=True, executable="/bin/bash")

# Parse environment dump
config = {}
Expand Down Expand Up @@ -281,11 +280,38 @@ def save_config(config, updsys=False, fpath=None):
update_sys()


def load_plain_envars(fpath, set_env=True):
# Get config file content
with open(fpath) as f:
lines = f.readlines()

# Parse plain envar assignment with or without export prefix
config = {}
pattern = re.compile("^([^#]*?)=(.*)")
for line in lines:
res = pattern.match(line)
if res:
parts = res.group(1).split(" ", maxsplit=1)
if len(parts) > 1:
if parts[0] == "export":
varname = parts[1]
else:
continue
else:
varname = res.group(1)
value = res.group(2).strip('\"').strip('\'')
config[varname] = value
# Set local environment
if set_env:
os.environ[varname] = value
#logging.debug(f"CONFIG: {config}")
return config


def update_sys():
try:
os.environ['ZYNTHIAN_FLAG_MASTER'] = "NONE"
check_output(os.environ.get('ZYNTHIAN_SYS_DIR') +
"/scripts/update_zynthian_sys.sh", shell=True)
check_output(os.environ.get('ZYNTHIAN_SYS_DIR') + "/scripts/update_zynthian_sys.sh", shell=True)
except Exception as e:
logging.error("Updating Sytem Config: %s" % e)

Expand Down Expand Up @@ -337,14 +363,12 @@ def get_wifi_list():
# and create it if needed
if "zynthian-ap" not in configured_wifi:
logging.info("Creating Wi-Fi Access Point connection 'zynthian'...")
check_output(
f"{sys_dir}/sbin/create_wifi_access_point.sh", encoding='utf-8')
check_output(f"{sys_dir}/sbin/create_wifi_access_point.sh", encoding='utf-8')

# Get list of available networks
wifi_data = []
ap_enabled = False
rows = check_output(["nmcli", "--terse", "dev", "wifi",
"list"], encoding='utf-8').split("\n")
rows = check_output(["nmcli", "--terse", "dev", "wifi", "list"], encoding='utf-8').split("\n")
for row in rows:
parts = row.split(":")
if len(parts) > 8:
Expand Down
2 changes: 1 addition & 1 deletion zyngine/zynthian_chain.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ def reset(self):
self.title = "Main"
self.audio_in = []
# Default use first two physical audio outputs
self.audio_out = ["system:playback_[1,2]$"]
self.audio_out = ["^system:playback_1$|^system:playback_2$"]
self.audio_thru = True
else:
self.title = ""
Expand Down
24 changes: 13 additions & 11 deletions zyngine/zynthian_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -160,11 +160,7 @@ def set_options(self, options):
if 'midi_chan' in options:
self.midi_chan = options['midi_chan']
if 'midi_cc' in options:
cc = options['midi_cc']
if isinstance(cc, str):
self.osc_path = cc
else:
self.midi_cc = cc
self.midi_cc = options['midi_cc']
if 'osc_port' in options:
self.osc_port = options['osc_port']
if 'osc_path' in options:
Expand Down Expand Up @@ -375,6 +371,10 @@ def set_value(self, val, send=True):
if old_val == self.value:
return

self.send_value(send)
self.is_dirty = True

def send_value(self, send=True):
mval = None
if self.engine and send:
# Send value using engine method...
Expand All @@ -385,23 +385,19 @@ def set_value(self, val, send=True):
try:
if self.osc_path:
# logging.debug("Sending OSC Controller '{}', {} => {}".format(self.symbol, self.osc_path, self.get_ctrl_osc_val()))
liblo.send(self.engine.osc_target,
self.osc_path, self.get_ctrl_osc_val())
liblo.send(self.engine.osc_target, self.osc_path, self.get_ctrl_osc_val())
elif self.midi_cc:
mval = self.get_ctrl_midi_val()
# logging.debug("Sending MIDI Controller '{}', CH{}#CC{}={}".format(self.symbol, self.midi_chan, self.midi_cc, mval))
self.send_midi_cc(mval)
except Exception as e:
logging.warning(
"Can't send controller '{}' => {}".format(self.symbol, e))
logging.warning("Can't send controller '{}' => {}".format(self.symbol, e))

# Send feedback to MIDI controllers => What MIDI controllers? Those selected as MIDI-out?
# TODO: Set midi_feeback to MIDI learn
if self.midi_feedback:
self.send_midi_feedback(mval)

self.is_dirty = True

def send_midi_cc(self, mval=None):
if mval is None:
mval = self.get_ctrl_midi_val()
Expand Down Expand Up @@ -614,6 +610,12 @@ def midi_cc_mode_detect(self, val):
#logging.debug(f"CC val={val} => current mode={self.midi_cc_mode}, detecting mode {self.midi_cc_mode_detecting}"
# f" (count {self.midi_cc_mode_detecting_count}, zero {self.midi_cc_mode_detecting_zero})\n")

# Always use absolute mode with toggle controllers
if self.is_toggle:
self.midi_cc_mode = 0
self.midi_cc_mode_detecting = 0
return

# Mode autodetection timeout
now = monotonic()
if now - self.midi_cc_mode_detecting_ts > MIDI_CC_MODE_DETECT_TIMEOUT:
Expand Down
Loading

0 comments on commit b7561b7

Please sign in to comment.