Go-based FM stereo transmitter with RDS, Windows-first and cross-platform
Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.

129 linhas
3.3KB

  1. package offline
  2. import (
  3. "math"
  4. "os"
  5. "path/filepath"
  6. "strings"
  7. "testing"
  8. "time"
  9. cfgpkg "github.com/jan/fm-rds-tx/internal/config"
  10. )
  11. func TestGenerateFrame(t *testing.T) {
  12. g := NewGenerator(cfgpkg.Default())
  13. frame := g.GenerateFrame(50 * time.Millisecond)
  14. if frame == nil {
  15. t.Fatal("expected frame")
  16. }
  17. if len(frame.Samples) == 0 {
  18. t.Fatal("expected samples")
  19. }
  20. }
  21. func TestGenerateFrameFMIQ(t *testing.T) {
  22. cfg := cfgpkg.Default()
  23. cfg.FM.FMModulationEnabled = true
  24. g := NewGenerator(cfg)
  25. frame := g.GenerateFrame(10 * time.Millisecond)
  26. // With FM modulation, IQ samples should have magnitude ~1
  27. for i := 100; i < len(frame.Samples) && i < 200; i++ {
  28. s := frame.Samples[i]
  29. mag := math.Sqrt(float64(s.I)*float64(s.I) + float64(s.Q)*float64(s.Q))
  30. if math.Abs(mag-1.0) > 0.01 {
  31. t.Fatalf("sample %d: IQ magnitude=%.4f, expected ~1.0", i, mag)
  32. }
  33. }
  34. }
  35. func TestGenerateFrameCompositeOnly(t *testing.T) {
  36. cfg := cfgpkg.Default()
  37. cfg.FM.FMModulationEnabled = false
  38. g := NewGenerator(cfg)
  39. frame := g.GenerateFrame(10 * time.Millisecond)
  40. // Without FM modulation, Q should be 0
  41. for i := 0; i < len(frame.Samples) && i < 100; i++ {
  42. if frame.Samples[i].Q != 0 {
  43. t.Fatalf("sample %d: Q=%.6f, expected 0 in composite mode", i, frame.Samples[i].Q)
  44. }
  45. }
  46. }
  47. func TestWriteFile(t *testing.T) {
  48. cfg := cfgpkg.Default()
  49. out := filepath.Join(t.TempDir(), "test.iqf32")
  50. cfg.Backend.OutputPath = out
  51. g := NewGenerator(cfg)
  52. if err := g.WriteFile(out, 20*time.Millisecond); err != nil {
  53. t.Fatalf("WriteFile failed: %v", err)
  54. }
  55. info, err := os.Stat(out)
  56. if err != nil {
  57. t.Fatalf("expected output file: %v", err)
  58. }
  59. if info.Size() == 0 {
  60. t.Fatal("expected non-empty file")
  61. }
  62. }
  63. func TestSummaryUsesToneFallback(t *testing.T) {
  64. cfg := cfgpkg.Default()
  65. cfg.Audio.InputPath = ""
  66. g := NewGenerator(cfg)
  67. summary := g.Summary(10 * time.Millisecond)
  68. if !strings.Contains(summary, "source=tones") {
  69. t.Fatalf("unexpected summary: %s", summary)
  70. }
  71. }
  72. func TestSummaryUsesFallbackLabelOnBadWAV(t *testing.T) {
  73. cfg := cfgpkg.Default()
  74. cfg.Audio.InputPath = "missing.wav"
  75. g := NewGenerator(cfg)
  76. summary := g.Summary(10 * time.Millisecond)
  77. if !strings.Contains(summary, "source=tone-fallback") {
  78. t.Fatalf("unexpected summary: %s", summary)
  79. }
  80. }
  81. func TestSummaryContainsPreemph(t *testing.T) {
  82. cfg := cfgpkg.Default()
  83. cfg.FM.PreEmphasisUS = 50
  84. g := NewGenerator(cfg)
  85. summary := g.Summary(10 * time.Millisecond)
  86. if !strings.Contains(summary, "preemph=50µs") {
  87. t.Fatalf("unexpected summary: %s", summary)
  88. }
  89. }
  90. func TestSummaryContainsFMIQ(t *testing.T) {
  91. cfg := cfgpkg.Default()
  92. cfg.FM.FMModulationEnabled = true
  93. g := NewGenerator(cfg)
  94. summary := g.Summary(10 * time.Millisecond)
  95. if !strings.Contains(summary, "FM-IQ") {
  96. t.Fatalf("unexpected summary: %s", summary)
  97. }
  98. }
  99. func TestLimiterPreventsClipping(t *testing.T) {
  100. cfg := cfgpkg.Default()
  101. cfg.FM.LimiterEnabled = true
  102. cfg.FM.LimiterCeiling = 1.0
  103. cfg.FM.FMModulationEnabled = false // raw composite to check levels
  104. cfg.Audio.ToneAmplitude = 0.9 // high amplitude to exercise limiter
  105. cfg.Audio.Gain = 2.0
  106. cfg.FM.OutputDrive = 1.0
  107. g := NewGenerator(cfg)
  108. frame := g.GenerateFrame(50 * time.Millisecond)
  109. for i, s := range frame.Samples {
  110. if math.Abs(float64(s.I)) > 1.01 {
  111. t.Fatalf("sample %d: composite=%.4f exceeds ceiling", i, s.I)
  112. }
  113. }
  114. }