//go:build windows package audio import ( "fmt" "sync" ) // Manager owns the DVK record/playback lifecycle: at most one recording and // one playback at a time. Device ids are passed per call so the host can route // recording to the mic and playback to the rig (or the preview speakers). type Manager struct { mu sync.Mutex recStop chan struct{} recDone chan recResult playStop chan struct{} onChange func() // fired on any record/playback state transition } type recResult struct { pcm []byte err error } // NewManager creates a DVK manager. onChange (optional) is called whenever the // recording/playback state changes, so the host can push an audio:status event. func NewManager(onChange func()) *Manager { return &Manager{onChange: onChange} } func (m *Manager) notify() { if m.onChange != nil { m.onChange() } } // StartRecording begins capturing from deviceID into memory. Finish with // StopRecording (which writes the WAV) or CancelRecording (which discards it). func (m *Manager) StartRecording(deviceID string) error { m.mu.Lock() if m.recStop != nil { m.mu.Unlock() return fmt.Errorf("already recording") } stop := make(chan struct{}) done := make(chan recResult, 1) m.recStop, m.recDone = stop, done m.mu.Unlock() // release BEFORE notify — onChange re-enters via IsRecording() go func() { pcm, err := recordPCM(deviceID, stop) done <- recResult{pcm, err} }() m.notify() return nil } // StopRecording ends the capture and writes it to path as a WAV file. func (m *Manager) StopRecording(path string) error { m.mu.Lock() stop, done := m.recStop, m.recDone m.recStop, m.recDone = nil, nil m.mu.Unlock() if stop == nil { return fmt.Errorf("not recording") } close(stop) res := <-done m.notify() if res.err != nil { return res.err } if len(res.pcm) == 0 { return fmt.Errorf("captured no audio (check the recording device)") } return writeWAV(path, res.pcm) } // CancelRecording aborts a recording without saving. func (m *Manager) CancelRecording() { m.mu.Lock() stop, done := m.recStop, m.recDone m.recStop, m.recDone = nil, nil m.mu.Unlock() if stop != nil { close(stop) <-done m.notify() } } func (m *Manager) IsRecording() bool { m.mu.Lock() defer m.mu.Unlock() return m.recStop != nil } func (m *Manager) IsPlaying() bool { m.mu.Lock() defer m.mu.Unlock() return m.playStop != nil } // Play renders a WAV file to deviceID. Any current playback is stopped first. // Returns immediately; playback runs in the background. func (m *Manager) Play(deviceID, path string) error { pcm, rate, ch, bits, err := readWAV(path) if err != nil { return err } m.StopPlayback() stop := make(chan struct{}) m.mu.Lock() m.playStop = stop m.mu.Unlock() go func() { _ = playPCM(deviceID, pcm, rate, ch, bits, stop) m.mu.Lock() if m.playStop == stop { m.playStop = nil } m.mu.Unlock() m.notify() }() m.notify() return nil } // StopPlayback halts any in-progress playback. func (m *Manager) StopPlayback() { m.mu.Lock() stop := m.playStop m.playStop = nil m.mu.Unlock() if stop != nil { close(stop) m.notify() } }