ollama/recorder/recorder.go
2024-08-09 11:04:26 -07:00

138 lines
3.1 KiB
Go

package recorder
import (
"encoding/binary"
"fmt"
"os"
"os/signal"
"syscall"
"golang.org/x/sys/unix"
"golang.org/x/term"
"github.com/gordonklaus/portaudio"
)
const (
sampleRate = 16000
numChannels = 1
bitsPerSample = 16
)
func RecordAudio(f *os.File) error {
fmt.Print("Recording. Press any key to stop.\n\n")
sig := make(chan os.Signal, 1)
signal.Notify(sig, os.Interrupt, syscall.SIGTERM)
portaudio.Initialize()
defer portaudio.Terminate()
in := make([]int16, 64)
stream, err := portaudio.OpenDefaultStream(numChannels, 0, sampleRate, len(in), in)
if err != nil {
return err
}
defer stream.Close()
err = stream.Start()
if err != nil {
return err
}
// Write WAV header with placeholder sizes
writeWavHeader(f, sampleRate, numChannels, bitsPerSample)
var totalSamples uint32
// Set up terminal input reading
oldState, err := term.MakeRaw(int(os.Stdin.Fd()))
if err != nil {
return err
}
defer term.Restore(int(os.Stdin.Fd()), oldState)
// Create a channel to handle the stop signal
stop := make(chan struct{})
go func() {
_, err := unix.Read(int(os.Stdin.Fd()), make([]byte, 1))
if err != nil {
fmt.Println("Error reading from stdin:", err)
return
}
// Send signal to stop recording
stop <- struct{}{}
}()
loop:
for {
err = stream.Read()
if err != nil {
return err
}
err = binary.Write(f, binary.LittleEndian, in)
if err != nil {
return err
}
totalSamples += uint32(len(in))
select {
case <-stop:
break loop
case <-sig:
break loop
default:
}
}
err = stream.Stop()
if err != nil {
return err
}
// Update WAV header with actual sizes
updateWavHeader(f, totalSamples, numChannels, bitsPerSample)
return nil
}
func writeWavHeader(f *os.File, sampleRate int, numChannels int, bitsPerSample int) {
subchunk1Size := 16
audioFormat := 1
byteRate := sampleRate * numChannels * (bitsPerSample / 8)
blockAlign := numChannels * (bitsPerSample / 8)
// Write the RIFF header
f.Write([]byte("RIFF"))
binary.Write(f, binary.LittleEndian, uint32(0)) // Placeholder for file size
f.Write([]byte("WAVE"))
// Write the fmt subchunk
f.Write([]byte("fmt "))
binary.Write(f, binary.LittleEndian, uint32(subchunk1Size))
binary.Write(f, binary.LittleEndian, uint16(audioFormat))
binary.Write(f, binary.LittleEndian, uint16(numChannels))
binary.Write(f, binary.LittleEndian, uint32(sampleRate))
binary.Write(f, binary.LittleEndian, uint32(byteRate))
binary.Write(f, binary.LittleEndian, uint16(blockAlign))
binary.Write(f, binary.LittleEndian, uint16(bitsPerSample))
// Write the data subchunk header
f.Write([]byte("data"))
binary.Write(f, binary.LittleEndian, uint32(0)) // Placeholder for data size
}
func updateWavHeader(f *os.File, totalSamples uint32, numChannels int, bitsPerSample int) {
fileSize := 36 + (totalSamples * uint32(numChannels) * uint32(bitsPerSample/8))
dataSize := totalSamples * uint32(numChannels) * uint32(bitsPerSample/8)
// Seek to the start of the file and write updated sizes
f.Seek(4, 0)
binary.Write(f, binary.LittleEndian, uint32(fileSize))
f.Seek(40, 0)
binary.Write(f, binary.LittleEndian, uint32(dataSize))
}