Go-based FM stereo transmitter with RDS, Windows-first and cross-platform
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

75 lines
1.8KB

  1. package audio
  2. import (
  3. "encoding/binary"
  4. "fmt"
  5. "io"
  6. "os"
  7. )
  8. type WAVSource struct {
  9. frames []Frame
  10. index int
  11. }
  12. func LoadWAVSource(path string) (*WAVSource, error) {
  13. f, err := os.Open(path)
  14. if err != nil {
  15. return nil, err
  16. }
  17. defer f.Close()
  18. header := make([]byte, 44)
  19. if _, err := io.ReadFull(f, header); err != nil {
  20. return nil, fmt.Errorf("read wav header: %w", err)
  21. }
  22. if string(header[0:4]) != "RIFF" || string(header[8:12]) != "WAVE" {
  23. return nil, fmt.Errorf("unsupported wav header")
  24. }
  25. channels := binary.LittleEndian.Uint16(header[22:24])
  26. bitsPerSample := binary.LittleEndian.Uint16(header[34:36])
  27. dataSize := binary.LittleEndian.Uint32(header[40:44])
  28. if bitsPerSample != 16 {
  29. return nil, fmt.Errorf("only 16-bit PCM wav supported")
  30. }
  31. if channels != 1 && channels != 2 {
  32. return nil, fmt.Errorf("only mono/stereo wav supported")
  33. }
  34. raw := make([]byte, dataSize)
  35. if _, err := io.ReadFull(f, raw); err != nil {
  36. return nil, fmt.Errorf("read wav data: %w", err)
  37. }
  38. step := int(channels) * 2
  39. frames := make([]Frame, 0, len(raw)/step)
  40. for i := 0; i+step <= len(raw); i += step {
  41. l := pcm16ToSample(int16(binary.LittleEndian.Uint16(raw[i : i+2])))
  42. r := l
  43. if channels == 2 {
  44. r = pcm16ToSample(int16(binary.LittleEndian.Uint16(raw[i+2 : i+4])))
  45. }
  46. frames = append(frames, NewFrame(l, r))
  47. }
  48. return &WAVSource{frames: frames}, nil
  49. }
  50. func (s *WAVSource) NextFrame() Frame {
  51. if len(s.frames) == 0 {
  52. return NewFrame(0, 0)
  53. }
  54. frame := s.frames[s.index]
  55. s.index++
  56. if s.index >= len(s.frames) {
  57. s.index = 0
  58. }
  59. return frame
  60. }
  61. func pcm16ToSample(v int16) Sample {
  62. return Sample(float64(v) / 32768.0).Clamp()
  63. }