Go-based FM stereo transmitter with RDS, Windows-first and cross-platform
Nelze vybrat více než 25 témat Téma musí začínat písmenem nebo číslem, může obsahovat pomlčky („-“) a může být dlouhé až 35 znaků.

80 řádky
2.2KB

  1. package stereo
  2. import (
  3. "math"
  4. "math/cmplx"
  5. "testing"
  6. "github.com/jan/fm-rds-tx/internal/audio"
  7. )
  8. func TestSSBSidebandSuppression(t *testing.T) {
  9. const sampleRate = 228000
  10. const testFreq = 5000.0 // 5 kHz test tone
  11. const nSamples = 228000 // 1 second
  12. for _, mode := range []Mode{ModeDSB, ModeSSB, ModeVSB} {
  13. enc := NewStereoEncoder(sampleRate)
  14. enc.SetMode(mode, sampleRate)
  15. // Generate 1 second of stereo: L=tone, R=-tone → mono=0, diff=tone
  16. composite := make([]float64, nSamples)
  17. for i := 0; i < nSamples; i++ {
  18. phase := 2 * math.Pi * testFreq * float64(i) / sampleRate
  19. l := audio.Sample(0.5 * math.Sin(phase))
  20. r := audio.Sample(-0.5 * math.Sin(phase))
  21. c := enc.Encode(audio.NewFrame(l, r))
  22. composite[i] = c.Mono + c.Stereo
  23. }
  24. // FFT to check spectrum
  25. n := len(composite)
  26. buf := make([]complex128, n)
  27. for i := range buf {
  28. buf[i] = complex(composite[i], 0)
  29. }
  30. // Simple DFT at key frequencies (not full FFT, just spot-check)
  31. freqBin := func(hz float64) float64 {
  32. bin := int(hz * float64(n) / sampleRate)
  33. if bin >= n/2 {
  34. return 0
  35. }
  36. var sum complex128
  37. for i := 0; i < n; i++ {
  38. angle := -2 * math.Pi * float64(bin) * float64(i) / float64(n)
  39. sum += complex(composite[i], 0) * cmplx.Rect(1, angle)
  40. }
  41. return cmplx.Abs(sum) / float64(n)
  42. }
  43. lsb := freqBin(38000 - testFreq) // 33 kHz — lower sideband
  44. usb := freqBin(38000 + testFreq) // 43 kHz — upper sideband
  45. suppression := 0.0
  46. if usb > 0 && lsb > 0 {
  47. suppression = 20 * math.Log10(usb/lsb)
  48. }
  49. t.Logf("Mode %s: LSB(%.0fHz)=%.4f USB(%.0fHz)=%.4f suppression=%.1f dB",
  50. mode, 38000-testFreq, lsb, 38000+testFreq, usb, suppression)
  51. switch mode {
  52. case ModeDSB:
  53. // Both sidebands should be roughly equal
  54. if math.Abs(suppression) > 3 {
  55. t.Errorf("DSB: sidebands should be balanced, got %.1f dB", suppression)
  56. }
  57. case ModeSSB:
  58. // USB should be suppressed by >20 dB
  59. if suppression > -20 {
  60. t.Errorf("SSB: USB suppression only %.1f dB (want >20 dB)", suppression)
  61. }
  62. case ModeVSB:
  63. // 5 kHz tone is above 200 Hz crossover → should behave like SSB
  64. if suppression > -15 {
  65. t.Errorf("VSB: USB suppression only %.1f dB at %0.f Hz (want >15 dB)", suppression, testFreq)
  66. }
  67. }
  68. }
  69. }