feat(tap): TapAudioPipe + TapMetrics (spec §4.1)

- TapMetrics: atomic counters (drop + observe per slice-1 §3.8).
- TapAudioPipe: AudioSource + AudioSink impl over mpsc + VecDeque ring.
  Drop-oldest on overflow (lowest-latency-correct); silence on underflow.
- TAP_PLAYOUT_FRAMES = 5 (100ms @ 20ms/frame; tunable constant per spec §4.1).
- 6 unit tests cover underflow, overflow drops-oldest, sink-full drops,
  disconnected engine, frame round-trip, empty ring.

Spec ref: 2026-06-28-slice-2-agent-tap-design.md §4.1.
This commit is contained in:
opencode controller
2026-06-28 14:18:57 -04:00
parent 5b931d447b
commit 51d5e6b01e
3 changed files with 281 additions and 0 deletions

View File

@@ -27,18 +27,22 @@
//! — that would invert the canonical-home of `PcmFrame` and pull the
//! loopback peer into the tap story.
pub mod metrics;
pub mod protocol;
pub mod tap_audio_pipe;
// Re-export PcmFrame so `rutster-tap` consumers (the binary, the echo brain,
// future brains) get it from one canonical home (spec §3.1).
pub use rutster_media::PcmFrame;
pub use metrics::{MetricsSnapshot, TapMetrics, TAP_PLAYOUT_FRAMES};
pub use protocol::{
decode_envelope, decode_pcm, encode_audio_in, encode_audio_out, encode_bye, encode_error,
encode_hello, encode_pcm, encode_session_end, AudioPayload, DecodedFrame, DecodedPayload,
Envelope, ErrorPayload, FrameKind, HelloPayload, Payload, ReasonPayload, SessionEndPayload,
TapProtoError, PROTOCOL_VERSION, SAMPLES_PER_FRAME as WIRE_SAMPLES_PER_FRAME,
};
pub use tap_audio_pipe::TapAudioPipe;
#[cfg(test)]
mod tests {