Translate Mackie Control in the dongle: a freestanding C module, shared with the firmware - #5
Merged
Merged
Conversation
The Command|8 needed a host bridge on every platform because nothing else could speak its protocol. Moving that translation into the dongle removes the host side entirely: a DAW sees a Mackie Control surface and drives it with no software installed at all. src/mcu/c8_mcu.c is the same logic MackieBackend implements, with every dependency stripped -- no allocation, no floating point, no threads, no C++ runtime, no MIDI backend. Bytes in, bytes out through two callbacks. It compiles to 1674 bytes on a Cortex-M0+ with zero data and zero bss (all state is caller-owned), and its only external symbols are memset and the integer-division helpers. Fixed point rather than double is not an optimisation. The M0+ has no FPU, but more importantly integer arithmetic is exactly reproducible, so tests can pin real values instead of tolerances. Each conversion is written as (2N + d) / (2d) to round identically to the lround() it replaces; a test walks all 1024 fader positions and asserts the two agree exactly. The firmware compiles this file directly rather than a vendored copy, so the desktop tests cover the code that actually ships. tests/test_mcu.cpp adds 19 groups over the paths that are expensive to debug on hardware: heartbeat echoes swallowed rather than decoded as a Select press on a nonexistent strip, ring bits spilling into the SysEx address byte, LCD writes repainting only the cells touched, meter ballistics, and truncated input emitting nothing. One group cross-checks the module's private protocol constants against protocol.hpp so drift fails the build. Verified on hardware 2026-08-11 in both directions, and end to end with Reaper on Windows driving the surface with no host software running. Also rename the Windows MCU loopback pair from "A"/"B" to "Bridge"/"DAW". The old names gave no clue which end was which, and choosing wrong in the DAW fails silently in a way that looks like broken hardware. The names stay free of a bar deliberately: "Command|8" is the surface matcher's prefix, so a barred loopback could be matched as the device itself. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Uh oh!
There was an error while loading. Please reload this page.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for freeto join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Moves the Command|8 → Mackie Control translation out of the host and into a
freestanding C module the dongle firmware compiles directly. A DAW then sees a
Mackie Control surface with no host software at all — no bridge process, no
loopback pair, no OSC pattern file, on any operating system.
What's here
src/mcu/c8_mcu.cis the same logicMackieBackendimplements, with everydependency stripped: no allocation, no floating point, no threads, no C++
runtime, no MIDI backend. Bytes in, bytes out through two callbacks.
All state is caller-owned, so tests run independent instances and the firmware
needs no allocator. Fixed point rather than double is not an optimisation — the
M0+ has no FPU, but more importantly integer arithmetic is exactly reproducible,
so the tests pin real values instead of tolerances. Each conversion is written
as
(2N + d) / (2d)to round identically to thelround()it replaces, and atest walks all 1024 fader positions asserting the two agree exactly.
The firmware builds this file rather than a vendored copy, so
tests/test_mcu.cppcovers the code that actually ships. 19 groups over the paths that are expensive
to debug on hardware: heartbeat echoes swallowed rather than decoded as a Select
press on a nonexistent strip, ring bits spilling into the SysEx address byte,
LCD writes repainting only touched cells, meter ballistics, truncated input
emitting nothing. One group cross-checks the module's private protocol constants
against
protocol.hpp, so drift fails the build.Also
Renames the Windows MCU loopback pair from
A/BtoBridge/DAW. The oldnames gave no clue which end was which, and choosing wrong in the DAW fails
silently in a way that looks like broken hardware. The names deliberately stay
free of a bar:
Command|8is the surface matcher's prefix, so a barred loopbackcould be matched as the device itself.
Verified on hardware
Both directions, 2026-08-11, on a bare Pico and an Adafruit Feather RP2040 USB
Host. Fader to pitchbend (numerically exact against hand-worked arithmetic),
encoders both ways, transport and strip buttons, fader touch correctly dropped,
heartbeats swallowed; inbound LCD both rows, LEDs, motor fader, and encoder
rings including the 11-bit split across two bytes.
A clean 288-message burst translated 1:1 with nothing lost:
End to end with Reaper on Windows driving the surface with nothing else running.
Not in this PR
MackieBackendstill implements this logic separately, so it exists twice. TheC version is the tested one. Collapsing the C++ backend into a wrapper over it
is the obvious follow-up, kept separate because it touches the path that just
started working on Windows.