- Fix use-after-free crash on hangup: set _call_state=IDLE before deleting
_lxst_audio, preventing pump_call_tx() (runs without LVGL lock) from
accessing freed memory
- Replace single-slot _call_signal_pending with 8-element ring buffer queue
to prevent signal loss when CONNECTING+ESTABLISHED arrive in rapid succession
- Extract TX pump into pump_call_tx() called right after reticulum->loop()
for low-latency audio TX without LVGL lock dependency (was buried at step 10)
- Tune ES7210 mic gain to 21dB (was 15dB) to improve Codec2 input level
without ADC clipping that occurred at 24dB
- I2S capture: use APLL for accurate 8kHz clock, direct 8kHz sampling
(no more 16→8kHz decimation), DMA 16x64 for encode burst headroom
- Reduce Reticulum log verbosity to LOG_INFO (was LOG_TRACE)
- BLE: add ble_hs_sched_reset() tiered recovery before reboot on desync,
widen supervision timeout to 4.0s for WiFi coexistence
- Add UDP multicast log broadcasting and OTA flash support
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Columba's native OboePlaybackEngine ring buffer expects exactly
frameSamples (1600 for Codec2 3200 mode) decoded samples per
writeEncodedPacket call = 10 sub-frames of 160 samples each.
Changes:
- Batch exactly 10 sub-frames per fixarray element (82 bytes each:
codec_type + mode_header + 10*8 raw bytes)
- Up to 2 batches per msgpack packet, matching Columba C2C format
- Proper fixarray wrapping for multi-batch, bare bin8 for single
- Add codec_type byte (0x02) prefix per batch element
- Respond to PREFERRED_PROFILE negotiation with LBW (Codec2 3200)
- Add capture diagnostics (raw PCM peaks, I2S dump, rate logging)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
PCM_RING_FRAMES 16→50 (320ms→1000ms capacity) and
PREBUFFER_FRAMES 3→15 (60ms→300ms prebuffer) to match
LXST-kt's buffering strategy. Interop test suite confirms
zero underruns with ±100ms jitter at these settings.
Also adds tests/interop/ with 48 Python tests verifying
wire format, codec round-trip, and pipeline compatibility
between Pyxis, Python LXST, and LXST-kt implementations.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace crude 2-tap averaging with proper 15-tap half-band FIR filter
for 16kHz→8kHz decimation (~60dB stopband attenuation, Kaiser beta=6).
Exploits symmetry + half-band zeros for only 5 MACs per output sample.
Separate TDM deinterleave (CH0 extraction at 16kHz) from FIR decimation
for cleaner signal processing pipeline.
Reduce ES7210 mic gain from 8 (24dB) to 5 (15dB) to avoid ADC clipping;
AGC in the voice filter chain compensates for quieter input.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Split Codec2 into separate encode/decode instances to eliminate mutex
contention between capture task (core 0) and main thread decode
- Fix TDM deinterleave: stride-4 (was stride-2) for [CH0,CH1] at 16kHz
to produce 8kHz mono output matching Codec2's expected sample rate
- Add 2-tap anti-aliasing average before decimation to reduce >4kHz alias
- Add hard limiter at ±16000 to prevent ADC clipping artifacts
- TX batching: send exactly 8 Codec2 frames per packet (2560 decoded
samples) to match Columba's PacketRingBuffer.frameSamples requirement
- Add capture diagnostics: sample rate, raw/downsampled peaks, hex dumps
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Both encoded ring buffer (capture) and PCM ring buffer (playback) are
SPSC (Single Producer Single Consumer) with lock-free atomics. The
overflow handlers were calling read() from the producer thread, racing
with the consumer thread on the read index. This caused frame
corruption, duplication, and skipping — resulting in distorted audio.
Fix: Drop new frames on overflow instead of evicting old ones. The
consumer (TX pump / playback task) will drain the buffer naturally.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace custom ES7210 register-level driver with the verbatim LilyGO
T-Deck Plus ES7210 library. The custom driver was writing MODE_CONFIG
and clock doubler registers (master mode path) which broke the ES7210's
clock chain, causing all-zero PCM output. The LilyGO library in slave
mode leaves those registers at power-on defaults, which is correct since
the ESP32 I2S master provides MCLK/BCLK/LRCK.
Also includes LXST voice call protocol improvements:
- LXST IN destination for receiving calls
- Announce handler for tracking voice-capable peers
- Path request before outgoing calls
- Throttled SPIFFS saves in microReticulum (dirty flag)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add ES7210 I2C address and I2S mic capture pin definitions
- Add ring/hangup tone helpers to Tone library
- Add lxst_audio library scaffold
- Add Codec2 dependency to platformio.ini
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>