package audio import "sync" // pcmRing is a thread-safe, latency-bounded FIFO of PCM bytes feeding a live // render stream. Producers (a USB-codec capture, or a decoded network audio // stream) Push freshly-arrived samples; the render loop Pulls. It is the shared // hand-off point between "where the audio comes from" (USB device / UDP 50003) // and "where it's heard" (any WASAPI output) — so the transport can be swapped // without touching the render side, mirroring the civTransport split on the CAT // side. On overflow the oldest audio is dropped to keep latency bounded; on // underrun Pull simply returns short and the render loop pads with silence. type pcmRing struct { mu sync.Mutex buf []byte max int // hard cap in bytes (drops oldest beyond this → bounded latency) } // newPCMRing makes a ring whose backlog is capped at maxBytes. Size it from the // acceptable latency: bytesPerSec (=32000) worth ≈ 1 s. func newPCMRing(maxBytes int) *pcmRing { if maxBytes <= 0 { maxBytes = bytesPerSec // 1 s default } return &pcmRing{max: maxBytes} } // Push appends samples, dropping the oldest audio if the backlog would exceed // the cap (a slow/absent consumer never makes the producer block or grow without // bound). A short glitch beats runaway latency for live monitoring. func (r *pcmRing) Push(p []byte) { if len(p) == 0 { return } r.mu.Lock() r.buf = append(r.buf, p...) if len(r.buf) > r.max { drop := len(r.buf) - r.max r.buf = append(r.buf[:0], r.buf[drop:]...) } r.mu.Unlock() } // pull removes and returns up to maxBytes of queued PCM (a private copy), or nil // when empty. The render loop pads any shortfall with silence. func (r *pcmRing) pull(maxBytes int) []byte { r.mu.Lock() defer r.mu.Unlock() if len(r.buf) == 0 || maxBytes <= 0 { return nil } n := maxBytes if n > len(r.buf) { n = len(r.buf) } out := make([]byte, n) copy(out, r.buf[:n]) r.buf = append(r.buf[:0], r.buf[n:]...) return out }