| File was renamed from VoiceSnapGo/internal/audio/recorder.go |
| | |
| | | import ( |
| | | "math" |
| | | "sync" |
| | | "time" |
| | | "voicesnap/internal/logger" |
| | | |
| | | "github.com/gen2brain/malgo" |
| | |
| | | |
| | | // Stop stops recording and discards audio data. |
| | | func (r *Recorder) Stop() { |
| | | device := r.detachDevice() |
| | | stopAndUninitDevice(device) |
| | | |
| | | r.mu.Lock() |
| | | defer r.mu.Unlock() |
| | | r.pcmBuf = nil |
| | | device, _ := r.detachDeviceAndPCM() |
| | | stopAndUninitDeviceAsync(device) |
| | | } |
| | | |
| | | // StopAndGetSamples stops recording and returns the captured audio as float32 samples. |
| | | func (r *Recorder) StopAndGetSamples() []float32 { |
| | | device := r.detachDevice() |
| | | stopAndUninitDevice(device) |
| | | device, pcm := r.detachDeviceAndPCM() |
| | | stopAndUninitDeviceAsync(device) |
| | | |
| | | r.mu.Lock() |
| | | defer r.mu.Unlock() |
| | | |
| | | if len(r.pcmBuf) < 2 { |
| | | if len(pcm) < 2 { |
| | | return nil |
| | | } |
| | | |
| | | // Convert 16-bit PCM to float32 |
| | | numSamples := len(r.pcmBuf) / 2 |
| | | numSamples := len(pcm) / 2 |
| | | samples := make([]float32, numSamples) |
| | | for i := 0; i < numSamples; i++ { |
| | | sample := int16(r.pcmBuf[i*2]) | int16(r.pcmBuf[i*2+1])<<8 |
| | | sample := int16(pcm[i*2]) | int16(pcm[i*2+1])<<8 |
| | | samples[i] = float32(sample) / 32768.0 |
| | | } |
| | | |
| | | logger.Info("Recording stopped, %d samples captured", numSamples) |
| | | r.pcmBuf = nil |
| | | return samples |
| | | } |
| | | |
| | |
| | | return device |
| | | } |
| | | |
| | | func (r *Recorder) detachDeviceAndPCM() (*malgo.Device, []byte) { |
| | | r.mu.Lock() |
| | | defer r.mu.Unlock() |
| | | |
| | | device := r.device |
| | | r.device = nil |
| | | r.isRecording = false |
| | | pcm := append([]byte(nil), r.pcmBuf...) |
| | | r.pcmBuf = nil |
| | | return device, pcm |
| | | } |
| | | |
| | | func stopAndUninitDeviceAsync(device *malgo.Device) { |
| | | if device == nil { |
| | | return |
| | | } |
| | | done := make(chan struct{}) |
| | | go func() { |
| | | stopAndUninitDevice(device) |
| | | close(done) |
| | | }() |
| | | go func() { |
| | | select { |
| | | case <-done: |
| | | case <-time.After(2 * time.Second): |
| | | logger.Error("Audio device stop is still pending; continuing without blocking UI") |
| | | } |
| | | }() |
| | | } |
| | | |
| | | func stopAndUninitDevice(device *malgo.Device) { |
| | | if device == nil { |
| | | return |