pyxis voice-quality test harness (acoustic loopback)
Automated end-to-end test of the LXST voice pipeline using the Mac speaker next to
the T-Deck mic. The Mac plays a known signal; the T-Deck captures it through the real
pipeline (ES7210 mic → filters/AGC → Codec2 encode → call_send_audio_batch framing →
call_on_packet parse → Codec2 decode) and streams the decoded PCM back over UDP. The
harness re-aligns and scores the round-trip.
Physical setup
- Mac built-in speaker within a few cm of the T-Deck mic, quiet room.
- T-Deck on USB (serial control) and WiFi (UDP audio) — same LAN as the Mac.
- Set the Mac output volume to a consistent moderate level.
Firmware contract (T:LOOPBACK test mode)
- UDP multicast
239.0.99.99:9998, each datagram =[uint32 LE byte-offset][int16 LE mono @ 8 kHz PCM], ≤1284 B. - Serial hooks:
T:CALL_PROFILE 0x10(ULBW/Codec2-700C only),T:LOOPBACK on|off.
Run
cd tools/voice_test
.venv/bin/python run_voice_test.py --port /dev/cu.usbmodem101 --profiles ULBW
# options: --ref my.wav --text "..." --output "Mac mini Speakers" --tail 1.2
Outputs out/reference.wav + out/captured_<profile>.wav (listen to confirm) and a
summary table.
Metrics
- STOI (0–1): objective intelligibility (≥0.55 ≈ intelligible). Primary score.
- corr: time-aligned, gain-matched waveform correlation of the speech region.
- segSNR (dB): segmental SNR after alignment/gain-match.
- band: speech-band (300–3400 Hz) energy survival vs reference.
- hf: high-band (2–3.4 kHz) survival — Codec2 low profiles roll off the top.
- pkts / dropped: UDP delivery health.
Codec2-700C scores lower than wider codecs by design; compare before/after firmware
changes, not alternate profiles. The venv was created with uv venv --python 3.12 +
numpy scipy soundfile sounddevice pyserial pystoi.
Sideband/LXST end-to-end regression
sideband_e2e.py uses the Mac's real RNS/LXST/Sideband Python stack and the
T-Deck serial hooks to verify:
- Pyxis calls Sideband and exchanges synthetic Codec2 audio in both directions.
- Sideband calls Pyxis and exchanges synthetic Codec2 audio in both directions.
- Pyxis still accepts another incoming call after both calls and hangups.
- Every Pyxis decode succeeds and produces non-zero PCM.
- A raw LXST caller can hold the incoming reservation without identifying; a
second raw link receives
STATUS_BUSYand cannot replace it. - A local
T:CALLrequest cannot displace an incoming link that is still identifying. - Closing reserved caller A and then ringing caller B proves that closed-link
identify()is a no-op and that B remains stable while A's queued callbacks drain. The harness does not inject a fabricated stale callback. - A non-identifying caller is closed after the 15-second firmware timeout, and a
subsequent normal call still rings, answers, reaches
ACTIVE, exchanges audio in both directions, and hangs up cleanly.
Build the test firmware with the Mac TCP server baked in, then upload it:
export PYXIS_TEST_TCP_HOST="<RNSD-HOST>" PYXIS_TEST_TCP_PORT=4242
/opt/homebrew/bin/pio run -e tdeck -t upload --upload-port /dev/cu.usbmodem101
After testing, remove/disable PYXIS_TEST_HOOKS and the test TCP overrides and
restore the normal release firmware on the device. Do not leave test-hook
firmware deployed as the normal user build.
Run from the Mac with its Reticulum venv (defaults to the local TCP server on
127.0.0.1:4242):
~/.reticulum-host/venv/bin/python tools/voice_test/sideband_e2e.py
Optional overrides: PYXIS_SERIAL_PORT, PYXIS_RNS_HOST,
PYXIS_RNS_PORT, PYXIS_CALL_SECONDS, and
PYXIS_IDENTIFY_TIMEOUT_MARGIN (seconds beyond the fixed 15-second firmware
identification timeout; default 3, must be positive). Each run uses a fresh
Sideband identity and isolated RNS storage so stale cached paths cannot false-pass setup.
On Apple Silicon the harness re-executes itself with /opt/homebrew/lib on
DYLD_LIBRARY_PATH, allowing LXST/PyOgg to load Homebrew libopus for incoming
calls.
The contention cases use the ordinary Reticulum Identity, Destination, and
Link APIs directly and print the observed RNS.__version__ at startup. The
legacy Sideband environment was validated with RNS 1.3.8 for API compatibility;
that is an observation, not a pin or downgrade recommendation. Torlando's
security-patched deployments require RNS 1.3.9 or newer for Luthen. Use the
current security-patched version and do not force an insecure rollback merely
to run this harness.
The physical run requires current Pyxis and Sideband LXST announces, the TCP Reticulum hub, a serial-connected T-Deck running the test-hooks firmware, and the Mac Reticulum environment shown above. Run the host-native generation guard tests separately for the deterministic portable stale-callback model. The physical closed-link case proves close/B-redial stability plus callback drain; it does not inject a stale callback.