7.0 KiB
Codex task report - 2026-06-16
W2 - Per-peer EQ
- Added
src/audio/eq.rs: a 3-band listener-side RBJ biquad EQ (low shelf, mid peaking, high shelf) with per-peer state and flat bypass. - Added local config persistence in
AppConfig.peer_eq, keyed by peer node id string. - Added local
CoreCommand::SetPeerEqand mixer-side per-peerEqstate. EQ is applied after local volume and before pan/mix; raw multitrack stems remain pre-volume/pre-EQ. - Added participant-card controls for Low/Mid/High gain sliders (-12 dB to +12 dB). Changes apply live and persist on slider release.
- Tests added for flat identity, low/high boost energy, coefficient finiteness, clamping, and hot-signal processing.
Unverified: subjective voice quality and zipper/noise behavior on real devices.
W1 - Per-listener pan / stereo playback
- Added
src/audio/pan.rs: constant-powerpan_gains()with tests, plus playback gains that preserve the legacy default dual-mono center. - Converted playback mix to interleaved stereo in
src/core/mod.rs. - Switched PipeWire playback output to 2-channel S16LE and adjusted ring target/capacity/stride accounting in
src/audio/pipewire_impl.rs. - Kept capture, Opus encode/decode, jitter buffers, and network audio mono.
- Limiter now receives the interleaved stereo bus; shared limiter gain ducks both channels consistently.
- Mixed WAV and multitrack convenience mix fold the listener stereo mix back to mono before writing. Per-peer stems remain raw mono.
- Updated
audio_probeto send dual-mono stereo frames. - Added tests for exact center dual-mono behavior, hard-left pan contribution, and stereo fold-down.
Decision for senior sanity-check: pure pan law is constant-power, but playback scales it by sqrt(2) so pan=0 is exactly the old mono signal in both ears. This satisfies the "default behavior unchanged" guardrail at the cost of louder hard-panned extremes, which the existing limiter catches.
Unverified: real PipeWire stereo playback, underrun behavior on actual hardware, and recorded WAV listening checks.
W5 - Focused hotkeys + info popup
- Added
src/hotkeys.rs: serializableKeyBinding,HotkeyAction,HotkeyMap, parse/format/lookup, tier checks, and duplicate conflict detection. - Added
AppConfig.hotkeyswith defaults: F9 mute, F10 deafen, F2 Settings, Space push-to-talk, Leave unset. - Replaced the hard-coded PTT key capture with config-backed binding capture.
- Added Settings hotkey editor with Set/Clear per action and live conflict warnings.
- Added top-right hotkey info popup that lists every action and current binding, showing
unsetfor unbound actions. - Routed focused iced key events through the map. App-wide actions can fire from any screen while focused; room-only actions require an active call. PTT press/release still uses
SetPttActive. - Tests added for unset formatting, duplicate detection, room-tier lookup, defaults, and character parse/format.
Unverified: manual keyboard interaction in the GUI. No OS-global hooks were added.
W3 - PipeWire pro-routing plan (not implemented)
I stopped at design for W3. The current backend already supports simple target-node routing through PipeWire stream property node.target, but true "pro routing" (explicit ports / manual graph links / no-autoconnect patching) would require backend changes that are not safely verifiable offline.
Proposed future scope:
- Expose two advanced route targets: capture source node and playback sink node, with optional future per-port routing.
- Enumerate available nodes with the existing
pw-cli list-objects Nodeparser. For port-level routing, add a separate parser forpw-cli list-objects Portcollectingobject.id,node.id,port.name, direction, and channel position. - For node-level routing, continue using PipeWire stream property
node.targeton stream creation. This is the low-risk path and matches current backend behavior. - For explicit port routing, do not use
AUTOCONNECT; instead capture the created PeerSpeak stream node/port ids from the PipeWire registry, then link with PipeWire-native APIs orpw-link <source-port-id> <sink-port-id>. Degrade by falling back tonode.targetautoconnect if any selected node/port is missing. - Offline tests should cover pure routing-plan decisions: selected node exists/missing, selected port exists/missing, capture/playback direction mismatch, and fallback choice. Real-device tests still need a PipeWire graph.
Reason for not implementing: the current run_playback / run_capture code does not retain stream node or port ids, and changing AUTOCONNECT behavior plus adding manual pw-link calls could destabilize the working audio path. That matches the assignment's "bail if risky" instruction.
Backlog A21/A22 - correctness fixes
- Fixed A21 in
src/core/jitter.rs: implausibly large sequence discontinuities now reset the per-peer jitter stream instead of being treated as ordinary late packets or packet loss. - The reset threshold is
500frames, about 10 seconds at 20 ms/frame. That covers both same-identity sender restart back to sequence 0 and a faulty/malicious jump far ahead that would otherwise force a long PLC run. - Added jitter regression tests for both far-behind restart and far-ahead jump cases.
- Fixed A22 in
src/audio/recorder.rs:WavWriternow tracks data bytes asu64, checks additions before writing, and rejects data that cannot fit both the RIFF size field and thedatachunk size field. - Added a WAV overflow regression test that exercises the limit without creating a huge file.
Unverified: the same-identity peer restart has not been exercised in a live 2-machine call; the WAV fix is counter/size-field tested, not a real >12h recording.
Backlog A14 - orderly window-close shutdown
- Added
CoreCommand::ShutdownandUiEvent::ShutdownComplete. - Window close now saves config, marks the GUI as closing, asynchronously queues
Shutdown, and exits only after the core acknowledges completion or after a 5-second fallback timeout. - Core shutdown finalizes active mixed/multitrack recordings before session teardown, stops the standalone mic monitor, runs
ActiveSession::shutdown()for active calls, clears room presence/routing, closes the persistent network stack, sendsShutdownComplete, and ends the core loop. - The shutdown command is queued with an awaited
mpsc::Sender::sendtask instead of the best-efforttry_send, so a full command queue does not immediately drop the close command.
Unverified: actual GUI window-close behavior during a live call/recording still needs a manual run; tests/builds only prove the path compiles and existing unit coverage still passes.
Verification
cargo checkpassed.cargo test --libpassed: 288 passed, 0 failed, 2 ignored.cargo clippy --all-targetspassed.cargo build --releasepassed.- Formatted the touched Rust files with
rustfmt --edition 2024; I did not run repo-widecargo fmtto avoid unrelated formatting churn.
No new dependencies were added. Runtime/manual/field verification is still pending for audio-device and 2-machine behavior.