|
@@ -0,0 +1,137 @@
|
|
|
+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))
|
|
|
+}
|