Two C libraries for Roland REAC (REAC Exposed Audio Communications, EtherType 0x8819):
libreac, the protocol, and libreac-transport, the sockets and threads that carry it.
Both build from this one repository and one release tarball.
libreac owns what the bytes mean, in both directions and for both roles.
Wire format. The REAC frame is rate-invariant: 40 channels × 12 samples × 3 bytes of
24-bit audio, braided into a channel-pair byte layout that is the same on the wire whichever
way it is read. reac_braid_pos() (<reac/reac_braid.h>) is the one oracle for that layout —
static inline, so encode and decode both call it and cannot drift apart. reac_s24le_to_f32()
/ reac_f32_to_s24le() (<reac/reac_sample.h>) are the exact-inverse sample codec.
reac_decode() / reac_frame_inspect() (<reac/reac_decode.h>) read the master's 40-channel
downstream broadcast; reac_upstream_channels() / reac_upstream_decode()
(<reac/reac_upstream.h>) read a stagebox's narrower upstream return, sized to the box's own
input count; reac_braid_encode() / reac_downstream_build() (<reac/reac_encode.h>) write
audio back onto the wire in both directions.
reac_frame_is_reac(), reac_frame_counter(), reac_counter_gap(), reac_rate_snap() and
reac_detect_rate_fd() recognise a frame, read its sequence counter and detect its sample rate
from live capture. reac_frame_clean_len() strips the +2-byte Ethernet FCS residue some
captures carry after the end marker — not a protocol field, never emitted.
Control plane. The REAC conversation itself: how one endpoint pairs with another. The
32-byte control block, its two nested checksums and the box-model matrix
(reac_ctrl_build_*, reac_master_stamp), head-amp records (reac_headamp_*,
reac_ports_parse), box identity, and the master/slave establishment, hunt and arbitration
state machines (<reac/reac_link.h>, reac_hunt, reac_master_fsm, reac_grant).
libreac is IO-free, allocation-free and clock-free: no socket, no thread, no SCHED_FIFO. That
is libreac-transport's job.
The pieces of a REAC endpoint that move frames but carry no opinion about their meaning:
AF_PACKET RX/TX over a lock-free SPSC ring (reac_rx, reac_tx, reac_ring), the SCHED_FIFO
cadence pacer and its clock discipline (reac_pacer), network interface enumeration and link
state (reac_ifscan, reac_linkmon, reac_ifname), VLAN sub-interface mint/adopt/release on a
trunk port (reac_topo, reac_vlan), a segment's identity and lock (reac_segment_ident,
reac_seglock), the layered-config precedence and the one door to SCHED_FIFO (reac_conf,
reac_rt), and the slave/master establishment orchestration that drives libreac's protocol
state machines (reac_slave joining, reac_pacer mastering). Its public headers carry no socket type in a call shape, so a
future backend other than userspace AF_PACKET could implement the same API; it holds no Linux
capability itself, since a library cannot — the binding process keeps CAP_NET_RAW /
CAP_NET_ADMIN and this library runs inside it. Every transport object reaches the OS through
one opaque struct reac_handle (<reac/transport/reac_handle.h>); no installed header names a
socket, so a kernel-module backend can sit under the same headers.
reac-pw, the PipeWire-native REAC endpoint, links both: libreac-transport for the wire and
libreac underneath it for what the frames mean.
REAC runs at exactly one of three packet rates, all carrying the same 40×12×3-byte frame
shape: 44.1 kHz, 48 kHz and 96 kHz. A master announces which one it is running in the control
block's console_field byte (cfea offset 19 in the wire spec) — 0 for 48 kHz, 1 for 96 kHz,
2 for 44.1 kHz — and reac_pace_code() (transport/src/reac_pacer.c) is the one place that
byte is derived, from the running rate in packets per second (≥ 8000 pps → 96 kHz, ≤ 3700 pps →
44.1 kHz, otherwise 48 kHz). See spec/reac.ksy in the
reac-protocol repository for the field itself.
Both libraries are published, signed, in the FreeMixer package channel for Fedora 44 (x86_64, aarch64) and for Debian bookworm and trixie, including Raspberry Pi OS (amd64, arm64).
Fedora:
sudo dnf config-manager addrepo --from-repofile=https://freemixer.github.io/rpm/freemixer.repo
sudo dnf install libreac-devel libreac-transport-devel
Debian and Raspberry Pi OS:
sudo install -d /etc/apt/keyrings
sudo curl -fsSL -o /etc/apt/keyrings/freemixer.asc https://freemixer.github.io/deb/freemixer.asc
echo "deb [signed-by=/etc/apt/keyrings/freemixer.asc] https://freemixer.github.io/deb/debian/$(. /etc/os-release && echo $VERSION_CODENAME) ./" | sudo tee /etc/apt/sources.list.d/freemixer.list
sudo apt update
sudo apt install libreac-dev libreac-transport-dev
To build from source, see BUILDING.md.
The analysis tools under tools/ (built as BUILDING.md describes):
corpus_check— decode a capture corpus with this build and report what libreac made of it;--self-test/--self-test-audioprove the corruption-detection arm can itself go red.headamp_trace FILE.pcap— every head-amp record in a capture, in order, with source MAC and truncation.wire_census FILE.pcap— who talks on a segment and in what frame shapes.ctrl_delta FILE.pcap— which control-block bytes change, per talker and message kind.upstream_watch FILE.pcap AA:BB:CC:DD:EE:FF— every byte one box's own frames change, classed by how often.slotmap_watch FILE.pcap— the sliding slot-map window unrolled into per-slot state.seq_gaps FILE.pcap— per-talker frame-counter holes, the control for any "nothing was sent" claim.conformance-headamp-base.sh— a source-shape gate: the head-amp base must have exactly one derivation in the code (the announced strap), never a per-width table.run-corpus.sh— decode the full FreeREAC capture corpus and diff the result againsttests/corpus-baseline.txt; the corpus itself is private and not in this repository.gen-facts-header.py— regeneratetests/reac_facts_assert.hfrom areac-protocolcheckout'sspec/protocol-facts.yaml; run by the Makefile, not by hand.