Go-based FM stereo transmitter with RDS, Windows-first and cross-platform
Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

113 lignes
3.4KB

  1. // cmd/wmtest — Ferrite watermark self-test tool.
  2. //
  3. // Generates a mono WAV file containing only the spread-spectrum watermark
  4. // signal (silence + watermark, scaled up for visibility). Run wmdecode on
  5. // the output to verify embedder and decoder work without FM transmission.
  6. //
  7. // Usage:
  8. //
  9. // wmtest --key <license-key> --output test.wav --duration 30s
  10. // wmdecode test.wav <license-key>
  11. package main
  12. import (
  13. "encoding/binary"
  14. "flag"
  15. "fmt"
  16. "math"
  17. "os"
  18. "time"
  19. "github.com/jan/fm-rds-tx/internal/watermark"
  20. )
  21. func main() {
  22. key := flag.String("key", "free", "License key to embed")
  23. output := flag.String("output", "wmtest.wav", "Output WAV file")
  24. duration := flag.Duration("duration", 60*time.Second, "Duration (min 45s for 2 full frames)")
  25. flag.Parse()
  26. const compRate = watermark.CompositeRate // 228000
  27. const recRate = watermark.RecordingRate // 48000
  28. nSamples := int(duration.Seconds() * float64(recRate))
  29. chipRate := watermark.ChipRate
  30. frameSeconds := float64(128 * watermark.PnChips) / float64(chipRate)
  31. nFrames := duration.Seconds() / frameSeconds
  32. fmt.Printf("Ferrite watermark self-test\n")
  33. fmt.Printf(" Key: %s\n", *key)
  34. fmt.Printf(" ChipRate: %d Hz (PN bandwidth 0–%d Hz)\n", chipRate, chipRate/2)
  35. fmt.Printf(" Frame: %.1fs (%d chips × 128 bits @ %d Hz)\n", frameSeconds, watermark.PnChips, chipRate)
  36. fmt.Printf(" Duration: %s (%d samples @ %dHz, %.1f frames)\n\n", *duration, nSamples, recRate, nFrames)
  37. embedder := watermark.NewEmbedder(*key)
  38. samples := make([]float64, 0, nSamples)
  39. // Drive embedder at composite rate, collect samples at recording rate.
  40. // Bresenham: accumulate recRate each composite step; when >= compRate,
  41. // emit one recording sample and subtract compRate.
  42. accum := 0
  43. var last float64
  44. for len(samples) < nSamples {
  45. last = embedder.NextSample()
  46. accum += recRate
  47. if accum >= compRate {
  48. accum -= compRate
  49. samples = append(samples, last)
  50. }
  51. }
  52. // RMS
  53. var rmsAcc float64
  54. for _, s := range samples {
  55. rmsAcc += s * s
  56. }
  57. rms := math.Sqrt(rmsAcc / float64(len(samples)))
  58. fmt.Printf("Watermark RMS: %.1f dBFS (nominal -48 dBFS)\n", 20*math.Log10(rms+1e-12))
  59. if err := writeMonoWAV(*output, samples, recRate); err != nil {
  60. fmt.Fprintf(os.Stderr, "write WAV: %v\n", err)
  61. os.Exit(1)
  62. }
  63. fmt.Printf("Written: %s\n\n", *output)
  64. fmt.Printf("Decode with:\n")
  65. fmt.Printf(" .\\wmdecode.exe %s %q\n\n", *output, *key)
  66. fmt.Printf("Expected: RS decode clean + MATCH\n")
  67. }
  68. func writeMonoWAV(path string, samples []float64, rate int) error {
  69. f, err := os.Create(path)
  70. if err != nil {
  71. return err
  72. }
  73. defer f.Close()
  74. le := binary.LittleEndian
  75. dataSz := uint32(len(samples) * 2)
  76. f.Write([]byte("RIFF"))
  77. binary.Write(f, le, 36+dataSz)
  78. f.Write([]byte("WAVE"))
  79. f.Write([]byte("fmt "))
  80. binary.Write(f, le, uint32(16))
  81. binary.Write(f, le, uint16(1)) // PCM
  82. binary.Write(f, le, uint16(1)) // mono
  83. binary.Write(f, le, uint32(rate))
  84. binary.Write(f, le, uint32(rate*2)) // byte rate
  85. binary.Write(f, le, uint16(2)) // block align
  86. binary.Write(f, le, uint16(16)) // bits/sample
  87. f.Write([]byte("data"))
  88. binary.Write(f, le, dataSz)
  89. for _, s := range samples {
  90. // Scale to int16 range — watermark at -48dBFS → ~0.004 amplitude
  91. // Multiply by 32767 to get full 16-bit range
  92. v := s * 32767.0
  93. if v > 32767 { v = 32767 }
  94. if v < -32768 { v = -32768 }
  95. binary.Write(f, le, int16(v))
  96. }
  97. return nil
  98. }