|
- package stereo
-
- import (
- "math"
- "math/cmplx"
- "testing"
-
- "github.com/jan/fm-rds-tx/internal/audio"
- )
-
- func TestSSBSidebandSuppression(t *testing.T) {
- const sampleRate = 228000
- const testFreq = 5000.0 // 5 kHz test tone
- const nSamples = 228000 // 1 second
-
- for _, mode := range []Mode{ModeDSB, ModeSSB, ModeVSB} {
- enc := NewStereoEncoder(sampleRate)
- enc.SetMode(mode, sampleRate)
-
- // Generate 1 second of stereo: L=tone, R=-tone → mono=0, diff=tone
- composite := make([]float64, nSamples)
- for i := 0; i < nSamples; i++ {
- phase := 2 * math.Pi * testFreq * float64(i) / sampleRate
- l := audio.Sample(0.5 * math.Sin(phase))
- r := audio.Sample(-0.5 * math.Sin(phase))
- c := enc.Encode(audio.NewFrame(l, r))
- composite[i] = c.Mono + c.Stereo
- }
-
- // FFT to check spectrum
- n := len(composite)
- buf := make([]complex128, n)
- for i := range buf {
- buf[i] = complex(composite[i], 0)
- }
- // Simple DFT at key frequencies (not full FFT, just spot-check)
- freqBin := func(hz float64) float64 {
- bin := int(hz * float64(n) / sampleRate)
- if bin >= n/2 {
- return 0
- }
- var sum complex128
- for i := 0; i < n; i++ {
- angle := -2 * math.Pi * float64(bin) * float64(i) / float64(n)
- sum += complex(composite[i], 0) * cmplx.Rect(1, angle)
- }
- return cmplx.Abs(sum) / float64(n)
- }
-
- lsb := freqBin(38000 - testFreq) // 33 kHz — lower sideband
- usb := freqBin(38000 + testFreq) // 43 kHz — upper sideband
-
- suppression := 0.0
- if usb > 0 && lsb > 0 {
- suppression = 20 * math.Log10(usb/lsb)
- }
-
- t.Logf("Mode %s: LSB(%.0fHz)=%.4f USB(%.0fHz)=%.4f suppression=%.1f dB",
- mode, 38000-testFreq, lsb, 38000+testFreq, usb, suppression)
-
- switch mode {
- case ModeDSB:
- // Both sidebands should be roughly equal
- if math.Abs(suppression) > 3 {
- t.Errorf("DSB: sidebands should be balanced, got %.1f dB", suppression)
- }
- case ModeSSB:
- // USB should be suppressed by >20 dB
- if suppression > -20 {
- t.Errorf("SSB: USB suppression only %.1f dB (want >20 dB)", suppression)
- }
- case ModeVSB:
- // 5 kHz tone is above 200 Hz crossover → should behave like SSB
- if suppression > -15 {
- t.Errorf("VSB: USB suppression only %.1f dB at %0.f Hz (want >15 dB)", suppression, testFreq)
- }
- }
- }
- }
|