Commit c628415e by PLN (Algolia)

feat(surface): translate the LCXL3 instead of renumbering the corpus a second time

The v3 and the 169 tracks disagree about what a CC means, and only one of them
is cheap to change.

Paint is DAW-mode-only. Tested rather than believed, in three steps with PLN
watching the surface: magenta on the MIDI port in standalone did nothing; the
same paint on the DAW port with DAW mode off did nothing; `daw on` followed by
the same bytes lit all 24 rings and 16 buttons. So the guide's "only available
once DAW mode is enabled" is a hardware fact, not an artefact of how the
document is organised.

But DAW mode's CC map is fixed, and it overlaps v2's with DIFFERENT meanings.
The map was read off the hardware, one control per row, not off the PDF's
low-resolution diagram: fader1 -> cc5 ch16, A1 -> 13, B1 -> 21, C1 -> 29,
E1 -> 37 ch1, F1 -> 45 ch1, shift -> 63 ch7.

    row   v2 (lcxl_grid)   v3 DAW
    A     13-20            13-20     identical
    B     29-36            21-28
    C     49-56            29-36     <- v2's row B numbers
    D     77-84            5-12
    E     41-44, 57-60     37-44
    F     73-76, 89-92     45-52

Sixteen indices collide. The one that decides it: v3 #41 is button E5, while v2
#41 is button E1 — the kick gate the whole #94 migration just aligned 169 files
onto. Renumbering would move the kick gate four buttons to the right, silently,
with nothing erroring. That is the same failure class b5ad8b6c was written to
end, so the corpus keeps its numbering and software absorbs the difference.

Three things make this cheap rather than clever:

SuperCollider's binding is `MIDIFunc.cc({...})` with no cc, channel or src
filter — only the number matters, so a pure renumber is sufficient and no
channel juggling is needed.

Every encoder row is still in ABSOLUTE mode (queried: rows 1/2/3 all read 0),
so a v3 encoder already behaves like a v2 pot. Relative mode needs an
integrator that owns the value — genuinely better, since it kills pot-pickup
outright, and it is where paint-by-value belongs — but it is a separate change
and `--relative` is left as an experiment that honestly warns the map no longer
applies.

The v2 numbers are DERIVED from lcxl_grid.ROW_CCS positionally rather than
retyped, so if the authored table moves, the translation follows.

Publishing a virtual port rather than resolving SuperCollider's once: SC runs
MIDIIn.connectAll a single time at boot, so a port that appears later is never
connected, and a binding resolved once is the rig's most repeated failure mode.
The driver re-asserts the aconnect link on a timer, the same shape as the two
reconciler units.

midi-autoconnect had the same stale-name bug: it hardcoded "Launch Control XL",
which matches neither v3 port, and since it routes aconnect's complaints to
/dev/null the failure was completely silent — Ardour stopped hearing the surface
with every check green. It now resolves the source, preferring the driver's
translated port over the raw device, and echoes what it picked once per change
rather than every two seconds.
parent 8069657f
[Unit] [Unit]
Description=Enforce fixed ALSA-seq MIDI wiring (Launch Control XL <-> Midi Through -> aseqdump) Description=Enforce fixed ALSA-seq MIDI wiring (control surface -> Midi Through, return leg cut)
# Run alongside the sound/PipeWire session. # Run alongside the sound/PipeWire session.
After=pipewire.service After=pipewire.service
Wants=pipewire.service Wants=pipewire.service
......
...@@ -44,11 +44,25 @@ ...@@ -44,11 +44,25 @@
set -u set -u
LCXL="Launch Control XL" # The source is RESOLVED, not hardcoded, because the device name changed with
# the hardware: v2 advertises "Launch Control XL", v3 advertises "LCXL3 1".
# The old hardcoded name matched neither of the v3 ports, and because connect()
# sends aconnect's complaints to /dev/null, the failure was perfectly silent —
# Ardour simply stopped hearing the surface on 2026-08-28 with every check green.
#
# Preference order matters:
# 1. "ParVagues LCXL3" — the lcxl3-driver's virtual port. In DAW mode the v3
# reports on its DAW interface with a FIXED CC map that disagrees with the
# corpus, so the driver's translated, v2-numbered stream is the one Ardour
# learned its 12 CCs from. Prefer it whenever it exists.
# 2. "LCXL3" — the raw v3, for when the driver is not running.
# 3. "Launch Control XL" — v2, still correct if the old surface is plugged in.
LCXL_CANDIDATES=("ParVagues LCXL3" "LCXL3" "Launch Control XL")
THRU="Midi Through" THRU="Midi Through"
LCXL_PORT=0 # "first channel" LCXL_PORT=0 # "first channel"
THRU_PORT=0 THRU_PORT=0
INTERVAL="${MIDI_AUTOCONNECT_INTERVAL:-2}" INTERVAL="${MIDI_AUTOCONNECT_INTERVAL:-2}"
LAST_SRC=""
connect() { # $1 = source, $2 = dest ; silent unless it's a real failure connect() { # $1 = source, $2 = dest ; silent unless it's a real failure
aconnect "$1" "$2" 2>/dev/null aconnect "$1" "$2" 2>/dev/null
...@@ -58,9 +72,35 @@ disconnect() { # $1 = source, $2 = dest ; silent when there was nothing to cut ...@@ -58,9 +72,35 @@ disconnect() { # $1 = source, $2 = dest ; silent when there was nothing to cut
aconnect -d "$1" "$2" 2>/dev/null aconnect -d "$1" "$2" 2>/dev/null
} }
# Echo the resolved source ONCE per change, never every INTERVAL: a line every
# 2s is not a log, it is a way to make a real change invisible.
resolve_lcxl() {
local names; names="$(aconnect -i 2>/dev/null; aconnect -o 2>/dev/null)"
local cand
for cand in "${LCXL_CANDIDATES[@]}"; do
if printf '%s' "$names" | grep -qF "$cand"; then
printf '%s' "$cand"
return 0
fi
done
return 1
}
reconcile() { reconcile() {
connect "$LCXL:$LCXL_PORT" "$THRU:$THRU_PORT" # rule 1: controls out local src
disconnect "$THRU:$THRU_PORT" "$LCXL:$LCXL_PORT" # rule 2: kill the loop if ! src="$(resolve_lcxl)"; then
if [ "$LAST_SRC" != "__none__" ]; then
echo "midi-autoconnect: no surface present (looked for: ${LCXL_CANDIDATES[*]})"
LAST_SRC="__none__"
fi
return 0
fi
if [ "$src" != "$LAST_SRC" ]; then
echo "midi-autoconnect: surface = '$src'"
LAST_SRC="$src"
fi
connect "$src:$LCXL_PORT" "$THRU:$THRU_PORT" # rule 1: controls out
disconnect "$THRU:$THRU_PORT" "$src:$LCXL_PORT" # rule 2: kill the loop
} }
echo "midi-autoconnect: reconciling every ${INTERVAL}s" echo "midi-autoconnect: reconciling every ${INTERVAL}s"
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment