recorder.go 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137
  1. package recorder
  2. import (
  3. "encoding/binary"
  4. "fmt"
  5. "os"
  6. "os/signal"
  7. "syscall"
  8. "golang.org/x/sys/unix"
  9. "golang.org/x/term"
  10. "github.com/gordonklaus/portaudio"
  11. )
  12. const (
  13. sampleRate = 16000
  14. numChannels = 1
  15. bitsPerSample = 16
  16. )
  17. func RecordAudio(f *os.File) error {
  18. fmt.Print("Recording. Press any key to stop.\n\n")
  19. sig := make(chan os.Signal, 1)
  20. signal.Notify(sig, os.Interrupt, syscall.SIGTERM)
  21. portaudio.Initialize()
  22. defer portaudio.Terminate()
  23. in := make([]int16, 64)
  24. stream, err := portaudio.OpenDefaultStream(numChannels, 0, sampleRate, len(in), in)
  25. if err != nil {
  26. return err
  27. }
  28. defer stream.Close()
  29. err = stream.Start()
  30. if err != nil {
  31. return err
  32. }
  33. // Write WAV header with placeholder sizes
  34. writeWavHeader(f, sampleRate, numChannels, bitsPerSample)
  35. var totalSamples uint32
  36. // Set up terminal input reading
  37. oldState, err := term.MakeRaw(int(os.Stdin.Fd()))
  38. if err != nil {
  39. return err
  40. }
  41. defer term.Restore(int(os.Stdin.Fd()), oldState)
  42. // Create a channel to handle the stop signal
  43. stop := make(chan struct{})
  44. go func() {
  45. _, err := unix.Read(int(os.Stdin.Fd()), make([]byte, 1))
  46. if err != nil {
  47. fmt.Println("Error reading from stdin:", err)
  48. return
  49. }
  50. // Send signal to stop recording
  51. stop <- struct{}{}
  52. }()
  53. loop:
  54. for {
  55. err = stream.Read()
  56. if err != nil {
  57. return err
  58. }
  59. err = binary.Write(f, binary.LittleEndian, in)
  60. if err != nil {
  61. return err
  62. }
  63. totalSamples += uint32(len(in))
  64. select {
  65. case <-stop:
  66. break loop
  67. case <-sig:
  68. break loop
  69. default:
  70. }
  71. }
  72. err = stream.Stop()
  73. if err != nil {
  74. return err
  75. }
  76. // Update WAV header with actual sizes
  77. updateWavHeader(f, totalSamples, numChannels, bitsPerSample)
  78. return nil
  79. }
  80. func writeWavHeader(f *os.File, sampleRate int, numChannels int, bitsPerSample int) {
  81. subchunk1Size := 16
  82. audioFormat := 1
  83. byteRate := sampleRate * numChannels * (bitsPerSample / 8)
  84. blockAlign := numChannels * (bitsPerSample / 8)
  85. // Write the RIFF header
  86. f.Write([]byte("RIFF"))
  87. binary.Write(f, binary.LittleEndian, uint32(0)) // Placeholder for file size
  88. f.Write([]byte("WAVE"))
  89. // Write the fmt subchunk
  90. f.Write([]byte("fmt "))
  91. binary.Write(f, binary.LittleEndian, uint32(subchunk1Size))
  92. binary.Write(f, binary.LittleEndian, uint16(audioFormat))
  93. binary.Write(f, binary.LittleEndian, uint16(numChannels))
  94. binary.Write(f, binary.LittleEndian, uint32(sampleRate))
  95. binary.Write(f, binary.LittleEndian, uint32(byteRate))
  96. binary.Write(f, binary.LittleEndian, uint16(blockAlign))
  97. binary.Write(f, binary.LittleEndian, uint16(bitsPerSample))
  98. // Write the data subchunk header
  99. f.Write([]byte("data"))
  100. binary.Write(f, binary.LittleEndian, uint32(0)) // Placeholder for data size
  101. }
  102. func updateWavHeader(f *os.File, totalSamples uint32, numChannels int, bitsPerSample int) {
  103. fileSize := 36 + (totalSamples * uint32(numChannels) * uint32(bitsPerSample/8))
  104. dataSize := totalSamples * uint32(numChannels) * uint32(bitsPerSample/8)
  105. // Seek to the start of the file and write updated sizes
  106. f.Seek(4, 0)
  107. binary.Write(f, binary.LittleEndian, uint32(fileSize))
  108. f.Seek(40, 0)
  109. binary.Write(f, binary.LittleEndian, uint32(dataSize))
  110. }