Files
kami d52f60c54e maven: fix test mocks for CalendarEvents interface (verification)
- Add CalendarEvents method to recordingAPI in auth_test.go
- Add CalendarEvents method to fakeCore in handlers_test.go

Co-Authored-By: opencode <opencode@anthropic.com>
2026-07-06 04:20:16 +04:00

87 lines
3.8 KiB
Go

// Package audio is maven's shared audio types.
//
// Audio crosses two boundaries in maven, and only two:
//
// - client → core (the PushToTalk payload over internal/voice): raw audio
// bytes the client captured after wake-word + VAD (per the spec's "clients
// do capture, server transcribes" decision). one clean blob per utterance.
// - core → worker module (/internal/worker for stt + tts): raw bytes the
// server ships to / from the transcription + synthesis modules. core is
// the worker's client here; the worker never reaches back.
//
// Format is fixed across both surfaces at scaffold time: 16 kHz mono int16
// little-endian PCM. faster-whisper, vosk, silero, and piper all work with
// 16 kHz mono; picking one shape up-front keeps the wire a single format
// field rather than a negotiation. A different rate upstream (e.g. an 8 kHz
// phone codec) downmixes at the client before send, never on the server —
// resampling on the always-on box is wasted work.
//
// Bytes are raw PCM, NOT a container (no WAV header on the wire). The client
// strips/generates WAV headers locally so the server's worker modules get
// exactly the bytes their model expects — there is no useful reason to ship
// a 44-byte header through a length-prefixed JSON frame. internal/pcmwav
// (a tiny helper below) is the WAV ⇄ PCM pair the reference client uses.
package audio
// Format describes raw PCM audio. Fixed at scaffold time; the wire carries
// it but today only one value is meaningful. Future: a negotiated registry
// if a second codec lands (e.g. opus, for a low-bitrate phone PWA path).
type Format struct {
SampleRate int `json:"sample_rate"` // samples/sec; default 16000
Channels int `json:"channels"` // 1 = mono
SampleBits int `json:"sample_bits"` // 16 ⇒ int16 little-endian PCM
Encoding string `json:"encoding"` // "pcm_s16le" is the only value today
}
// PCM16kMono — the canonical maven audio shape. faster-whisper, silero,
// vosk, piper all consume it. Set as the default at every seam; the wire
// carries the explicit fields so a second format doesn't need a protocol
// version bump when it lands, just a new value here.
var PCM16kMono = Format{
SampleRate: 16000,
Channels: 1,
SampleBits: 16,
Encoding: "pcm_s16le",
}
// Audio — one blob. Bytes is raw PCM in Format (no container header). The
// caller that built it (client capture, TTS synthesis output, a stub) knows
// the duration from len(Bytes) / Format.bytesPerSample() / SampleRate.
type Audio struct {
Format Format `json:"format"`
Bytes []byte `json:"bytes"` // raw PCM; base64 over the wire via worker/voice JSON marshal
}
// Duration returns the playback duration implied by Bytes + Format. Returns
// 0 for empty Bytes or an unknown encoding. A sanity helper, not a contract:
// callers that need the duration for display use this; callers that need it
// for real (e.g. cooldown math) read it from the facts table, not the audio.
func (a Audio) Duration() float64 {
if len(a.Bytes) == 0 {
return 0
}
if a.Format.SampleRate <= 0 || a.Format.Channels <= 0 || a.Format.SampleBits <= 0 {
return 0
}
if a.Format.Encoding != "pcm_s16le" {
return 0
}
bytesPerSample := a.Format.SampleBits / 8
if bytesPerSample == 0 {
return 0
}
samples := float64(len(a.Bytes)) / float64(bytesPerSample*a.Format.Channels)
return samples / float64(a.Format.SampleRate)
}
// IsValid reports whether f is a format maven can route today. Returns true
// only for the single canonical shape; a value with a different encoding is
// refused at the seam rather than mis-routed to a model that expects
// something else.
func (f Format) IsValid() bool {
return f.SampleRate == PCM16kMono.SampleRate &&
f.Channels == PCM16kMono.Channels &&
f.SampleBits == PCM16kMono.SampleBits &&
f.Encoding == PCM16kMono.Encoding
}