Multiple identical USB MIDI device naming/ordering

All the topics related to QLC+ on the Raspberry Pi
Post Reply
User avatar
emgemg
Posts: 2
Joined: Fri Sep 06, 2024 5:04 pm
Real Name: Erik Miller-Galow

I'm looking into streamlining our setup/crash recovery process, and one main difficulty we encounter is the inconsistent naming/ordering of our two Akai MkIIs. I see that this is a well known issue:

https://forum.qlcplus.org/viewtopic.php ... ber#p71880
The 'issue' is that USB devices do not reveal their serial number or any information which is unique to that device.
So when QLC+ (and other programs) list up the connected devices, it sees 2 identical devices and connects them. In which order is arbitrarily...
I'm hopefully going to be able to borrow our full setup from my buddy and find some time to poke around in a couple of weeks. I'm curious to see if I can implement something similar to these:

https://forum.qlcplus.org/viewtopic.php ... ber#p63347
So a few days ago I finally found some time to limit this behaviour and make QLC+ behave consistently over startups.
Where possible, I added a serial number to the device name, like this: USB-RS485 Cable (S/N: ABCD1234)
Network interface names, instead, are unique by definition.
MIDI controllers and HID devices, unfortunately, don't have any unique identifier, so two controllers with the same name will still have an issue.

These unique names will be saved into your project, so at the next QLC+ startup, a match by name will be looked up, instead of a dumb index lookup.
If no name match is found (e.g. device disconnected or replaced), the previous method by index is used, which in some cases might be useful.
https://github.com/mcallegari/qlcplus/pull/1571

Where instead of appending the nonexistent serial number, I want to append an identifier corresponding to the USB port the device is plugged into on the Pi. This way we can ensure some consistency by being mindful of which controller enters which port.

Does this sound feasible? I'm not familiar enough with raspberry pis to know if the usb ports are indentified consistently on boot. Also, we use the prebuilt pi image - if I make minor code changes and compile, is it straightforward to swap out the binary within the image?

Thanks!
User avatar
emgemg
Posts: 2
Joined: Fri Sep 06, 2024 5:04 pm
Real Name: Erik Miller-Galow

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! :lol:
User avatar
mcallegari
Posts: 4807
Joined: Sun Apr 12, 2015 9:09 am
Location: Italy
Real Name: Massimo Callegari
Contact:

I investigated USB dev paths too but remember...QLC+ is cross platform so a solution has to be found also on Windows and macOS.
Unfortunately I haven't found a reliable way to do this yet
User avatar
emgemg
Posts: 2
Joined: Fri Sep 06, 2024 5:04 pm
Real Name: Erik Miller-Galow

mcallegari wrote: Fri Jan 17, 2025 7:43 am I investigated USB dev paths too but remember...QLC+ is cross platform so a solution has to be found also on Windows and macOS.
Unfortunately I haven't found a reliable way to do this yet
Oh yeah, I don't think it would even make much sense for this to be active within the program, I think it would make all mappings dependent on individual ports which probably isn't desirable by default. It's too bad these devices don't report something like a serial number.

Thank you for the excellent software!
Last edited by emgemg on Fri Jan 17, 2025 6:06 pm, edited 1 time in total.
Post Reply