package rds import ( "math" "strings" "testing" ) func TestCRC10KnownVector(t *testing.T) { // Verify the CRC polynomial produces 10-bit outputs c := crc10(0x1234) if c > 0x3FF { t.Fatalf("CRC exceeds 10 bits: %x", c) } } func TestEncodeBlockProduces26Bits(t *testing.T) { block := encodeBlock(0x1234, 'A') // Must fit in 26 bits if block>>26 != 0 { t.Fatalf("block exceeds 26 bits: %x", block) } // Data portion should be the original word data := uint16(block >> 10) if data != 0x1234 { t.Fatalf("data mismatch: got %x want %x", data, 0x1234) } } func TestBuildGroup0ABlockCount(t *testing.T) { g := buildGroup0A(0x1234, 0, false, false, 0, "TESTFM") // Block A must be PI if g[0] != 0x1234 { t.Fatalf("block A not PI: %x", g[0]) } // Block D should contain first two PS chars 'T','E' ch0 := byte(g[3] >> 8) ch1 := byte(g[3] & 0xFF) if ch0 != 'T' || ch1 != 'E' { t.Fatalf("unexpected PS chars: %c %c", ch0, ch1) } } func TestBuildGroup2ABlockCount(t *testing.T) { g := buildGroup2A(0x1234, 0, false, false, 0, "Hello World") if g[0] != 0x1234 { t.Fatalf("block A not PI: %x", g[0]) } // Group type field in block B should have type 2 in bits 15..12 groupType := (g[1] >> 12) & 0x0F if groupType != 2 { t.Fatalf("unexpected group type: %d", groupType) } } func TestBuildGroupUsesConfiguredPI(t *testing.T) { g0 := buildGroup0A(0xBEEF, 0, false, false, 0, "TESTFM") if g0[0] != 0xBEEF { t.Fatalf("group0A block A not configured PI: %x", g0[0]) } g2 := buildGroup2A(0xCAFE, 0, false, false, 0, "Hello World") if g2[0] != 0xCAFE { t.Fatalf("group2A block A not configured PI: %x", g2[0]) } } func TestEncoderGenerate(t *testing.T) { cfg := DefaultConfig() cfg.SampleRate = 228000 enc, err := NewEncoder(cfg) if err != nil { t.Fatalf("unexpected error: %v", err) } samples := enc.Generate(1024) if len(samples) != 1024 { t.Fatalf("expected 1024 samples, got %d", len(samples)) } var maxAbs float64 var energy float64 for _, s := range samples { a := math.Abs(s) energy += s * s if a > maxAbs { maxAbs = a } } if energy == 0 { t.Fatal("expected non-zero energy in RDS output") } if maxAbs > defaultAmplitude*1.01 { t.Fatalf("samples exceed configured amplitude: %.6f", maxAbs) } } func TestEncoderReset(t *testing.T) { cfg := DefaultConfig() cfg.SampleRate = 228000 enc, err := NewEncoder(cfg) if err != nil { t.Fatalf("unexpected error: %v", err) } sampleA := enc.Generate(1)[0] enc.Generate(100) enc.Reset() sampleB := enc.Generate(1)[0] if math.Abs(sampleA-sampleB) > 1e-9 { t.Fatalf("expected reset to replay initial sample: %v vs %v", sampleA, sampleB) } } func TestGroupSchedulerCycles(t *testing.T) { cfg := DefaultConfig() cfg.PS = "TESTPS" cfg.RT = "short" gs := newGroupScheduler(cfg) // Should get 4 PS groups then RT groups then cycle for i := 0; i < 40; i++ { _ = gs.NextGroup() } // No panic = success } func TestNormalizePS(t *testing.T) { got := normalizePS("radiox") if got != "RADIOX " { t.Fatalf("unexpected PS: %q", got) } } func TestNormalizeRT(t *testing.T) { long := strings.Repeat("a", 80) got := normalizeRT(long) if len(got) != 64 { t.Fatalf("unexpected RT length: %d", len(got)) } } func TestDifferentialEncoder(t *testing.T) { d := diffEncoder{} // Input: 0 -> out = 0^0 = 0, prev=0 // Input: 1 -> out = 0^1 = 1, prev=1 // Input: 0 -> out = 1^0 = 1, prev=1 // Input: 1 -> out = 1^1 = 0, prev=0 expected := []uint8{0, 1, 1, 0} input := []uint8{0, 1, 0, 1} for i, in := range input { got := d.encode(in) if got != expected[i] { t.Fatalf("step %d: input=%d expected=%d got=%d", i, in, expected[i], got) } } } func TestRTSegmentCount(t *testing.T) { if n := rtSegmentCount("Hi"); n != 1 { t.Fatalf("expected 1, got %d", n) } if n := rtSegmentCount("Hello World!"); n != 3 { t.Fatalf("expected 3, got %d", n) } if n := rtSegmentCount(strings.Repeat("x", 64)); n != 16 { t.Fatalf("expected 16, got %d", n) } }