So we were able to find a solution, and without having to touch the code!
Our setup consists of 3 midi controllers attached to the pi, two of which are Akai APC Mini MKIIs. We rely on a consistent mapping per USB port since each controller is the left or right side of a combined layout. Using a combination of udev rules and a script that creates alsa midi-through ports, our system boots with the correct mapping every time.
udev rules:
Code: Select all
# /etc/udev/rules.d/100-name-usb-ports.rules
SUBSYSTEM=="usb", KERNELS=="1-1.1", SYMLINK+="usb_top_back"
SUBSYSTEM=="usb", KERNELS=="1-1.3", SYMLINK+="usb_bottom_back"
These mount specific usb ports on the pi (specified by the KERNELS values) to /dev/usb_top_back and /dev/usb_bottom_back, which are named based on their position relative to the person running the lights. The kernel values were determined by running
udevadm monitor --environment --udev
and then inserting and removing a midi controller. The output is long but the relevant portions look like this:
Code: Select all
...
UDEV [130.844385] remove /devices/platform/scb/fd500000.pcie/pci0000:00/0000:00:00.0/0000:01:00.0/usb1/1-1/1-1.1/1-1.1:1.0/sound/card1/controlC1 (sound)
ACTION=remove
DEVPATH=/devices/platform/scb/fd500000.pcie/pci0000:00/0000:00:00.0/0000:01:00.0/usb1/1-1/1-1.1/1-1.1:1.0/sound/
...
ID_USB_MODEL=APC_mini_mk2
ID_USB_MODEL_ENC=APC\x20mini\x20mk2
ID_USB_MODEL_ID=004f
ID_USB_SERIAL=AKAI_professional_APC_mini_mk2_Ver00.10
...
UDEV [142.026521] add /devices/platform/scb/fd500000.pcie/pci0000:00/0000:00:00.0/0000:01:00.0/usb1/1-1/1-1.3/1-1.3:1.0/sound/card5 (sound)
ACTION=add
DEVPATH=/devices/platform/scb/fd500000.pcie/pci0000:00/0000:00:00.0/0000:01:00.0/usb1/1-1/1-1.3/1-1.3:1.0/sound/card5
...
Several lines such as DEVPATH contain the KERNELS value for the specific usb port.
Then here is our script that runs on startup before qlcplus runs:
Code: Select all
#!/bin/bash
# create dummy seq ports (2x input and 2x feedback)
sudo modprobe -r snd_seq_dummy
sudo modprobe snd_seq_dummy ports=4
# get devpath from udev mapped device symlinks
TOP_PATH=$(udevadm info -n /dev/usb_top_back | grep DEVPATH | cut -d= -f2)
BOTTOM_PATH=$(udevadm info -n /dev/usb_bottom_back | grep DEVPATH | cut -d= -f2)
# get alsa card numbers
TOP_CARD=$(udevadm info -n /dev/snd/midi* | grep -m1 "$TOP_PATH" | grep -o 'card[0-9]' | sed 's/card//')
BOTTOM_CARD=$(udevadm info -n /dev/snd/midi* | grep -m1 "$BOTTOM_PATH" | grep -o 'card[0-9]' | sed 's/card//')
# get alsa client ids
TOP_CLIENT=$(aconnect -l | grep -m1 "APC mini mk2.*card=$TOP_CARD" | grep -o 'client [0-9]*' | cut -d' ' -f2)
BOTTOM_CLIENT=$(aconnect -l | grep -m1 "APC mini mk2.*card=$BOTTOM_CARD" | grep -o 'client [0-9]*' | cut -d' ' -f2)
# clear existing connections
aconnect -x
# map top controller
aconnect "$TOP_CLIENT:0" "14:0" # Input
aconnect "14:2" "$TOP_CLIENT:0" # Feedback
# map bottom controller
aconnect "$BOTTOM_CLIENT:0" "14:1" # Input
aconnect "14:3" "$BOTTOM_CLIENT:0" # Feedback
This creates 4 midi through ports (needed two for each controller for feedback and input to work), uses the mounted devices corresponding to the specific usb ports to find the alsa clients associated with each, and connects them to the midi through ports. At this point the names of the midi through ports in qlcplus are consistently associated with the usb ports, so where we would originally map universes to Akai APC MkII / Akai APC MkII (1), we map to something like Virmidi Through Port 1/2/3/4 (I don't have the system in front of me right now but it's something along those lines).
Also, I think the 14:0, 14:1, etc values may not be the same on every system, these were found running aconnect -l while figuring out how to map the snd_seq_dummy ports. There's probably a more general way to implement that.
The final steps were to create and enable a new service to run before qlcplus and after udev:
Code: Select all
# /etc/systemd/system/consistent-usb.service
[Unit]
Description=USB MIDI Controller Mapping
Before=qlcplus.service
After=udev.service
[Service]
Type=oneshot
ExecStart=/usr/local/bin/consistent_usb_mapping.sh
RemainAfterExit=yes
[Install]
WantedBy=multi-user.target
and to modify the original qlcplus service to run after the usb mapping script:
Code: Select all
# /lib/systemd/system/qlcplus.service
[Unit]
Description=Q Light Controller Plus
Documentation=man:qlcplus(1)
After=consistent-usb.service
[Service]
Type=simple
User=pi
Restart=always
RestartSec=3
ExecStart=/usr/sbin/qlcplus-start.sh
[Install]
WantedBy=multi-user.target
Hope this helps smooth out the process for anyone who encounters this in the future!