Files
peerspeak/task-report.md
T

5.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::SetPeerEq and mixer-side per-peer Eq state. 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-power pan_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_probe to 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: serializable KeyBinding, HotkeyAction, HotkeyMap, parse/format/lookup, tier checks, and duplicate conflict detection.
  • Added AppConfig.hotkeys with 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 unset for 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 Node parser. For port-level routing, add a separate parser for pw-cli list-objects Port collecting object.id, node.id, port.name, direction, and channel position.
  • For node-level routing, continue using PipeWire stream property node.target on 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 or pw-link <source-port-id> <sink-port-id>. Degrade by falling back to node.target autoconnect 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.

Verification

  • cargo test --lib passed: 285 passed, 0 failed, 2 ignored.
  • cargo clippy --all-targets passed.
  • cargo build --release passed.
  • cargo fmt --check reports broad repo-wide formatting diffs, including untouched files; I did not run cargo fmt to avoid unrelated churn.

No new dependencies were added. I did not run git commands.