|
- package dsp
-
- import (
- "testing"
- )
-
- func TestDecimateStateful_ContinuousPhase(t *testing.T) {
- // Simulate what happens in the streaming pipeline:
- // Two consecutive frames with non-divisible lengths decimated by 3.
- // Stateful version must produce the same output as decimating the
- // concatenated input in one go.
-
- factor := 3
- // Frame lengths that don't divide evenly (like real WFM: 41666 % 3 = 2)
- frame1 := make([]complex64, 41666)
- frame2 := make([]complex64, 41666)
- for i := range frame1 {
- frame1[i] = complex(float32(i), 0)
- }
- for i := range frame2 {
- frame2[i] = complex(float32(len(frame1)+i), 0)
- }
-
- // Reference: concatenate and decimate in one shot
- combined := make([]complex64, len(frame1)+len(frame2))
- copy(combined, frame1)
- copy(combined[len(frame1):], frame2)
- ref := Decimate(combined, factor)
-
- // Stateful: decimate frame by frame
- phase := 0
- out1 := DecimateStateful(frame1, factor, &phase)
- out2 := DecimateStateful(frame2, factor, &phase)
-
- got := make([]complex64, len(out1)+len(out2))
- copy(got, out1)
- copy(got[len(out1):], out2)
-
- if len(got) != len(ref) {
- t.Fatalf("length mismatch: stateful=%d reference=%d", len(got), len(ref))
- }
- for i := range ref {
- if got[i] != ref[i] {
- t.Fatalf("sample %d: got %v want %v", i, got[i], ref[i])
- }
- }
- }
-
- func TestDecimateStateful_Factor4_NFM(t *testing.T) {
- // NFM scenario: 200kHz/48kHz → decim=4, frame=16666 samples
- // 16666 % 4 = 2 → phase slip every frame with stateless decimation
- factor := 4
- frameLen := 16666
- nFrames := 10
-
- // Build continuous signal
- total := make([]complex64, frameLen*nFrames)
- for i := range total {
- total[i] = complex(float32(i), float32(-i))
- }
- ref := Decimate(total, factor)
-
- // Stateful frame-by-frame
- phase := 0
- var got []complex64
- for f := 0; f < nFrames; f++ {
- chunk := total[f*frameLen : (f+1)*frameLen]
- out := DecimateStateful(chunk, factor, &phase)
- got = append(got, out...)
- }
-
- if len(got) != len(ref) {
- t.Fatalf("length mismatch: stateful=%d reference=%d (frames=%d)", len(got), len(ref), nFrames)
- }
- for i := range ref {
- if got[i] != ref[i] {
- t.Fatalf("frame-boundary glitch at sample %d: got %v want %v", i, got[i], ref[i])
- }
- }
- }
-
- func TestDecimateStateful_Factor1_Passthrough(t *testing.T) {
- in := []complex64{1 + 2i, 3 + 4i, 5 + 6i}
- phase := 0
- out := DecimateStateful(in, 1, &phase)
- if len(out) != len(in) {
- t.Fatalf("passthrough: got len %d want %d", len(out), len(in))
- }
- }
-
- func TestDecimateStateful_ExactDivisible(t *testing.T) {
- // When frame length is exactly divisible, phase should stay 0
- factor := 4
- frame := make([]complex64, 100) // 100 % 4 = 0
- for i := range frame {
- frame[i] = complex(float32(i), 0)
- }
- phase := 0
- out := DecimateStateful(frame, factor, &phase)
- if phase != 0 {
- t.Fatalf("exact divisible: phase should be 0, got %d", phase)
- }
- if len(out) != 25 {
- t.Fatalf("exact divisible: got %d samples, want 25", len(out))
- }
- }
-
- func TestDecimateStateful_VaryingFrameSizes(t *testing.T) {
- // Real-world: buffer jitter causes varying frame sizes
- factor := 3
- frameSizes := []int{41600, 41700, 41666, 41650, 41680}
-
- // Build total
- totalLen := 0
- for _, s := range frameSizes {
- totalLen += s
- }
- total := make([]complex64, totalLen)
- for i := range total {
- total[i] = complex(float32(i), float32(i*2))
- }
- ref := Decimate(total, factor)
-
- phase := 0
- var got []complex64
- offset := 0
- for _, sz := range frameSizes {
- chunk := total[offset : offset+sz]
- out := DecimateStateful(chunk, factor, &phase)
- got = append(got, out...)
- offset += sz
- }
-
- if len(got) != len(ref) {
- t.Fatalf("varying frames: stateful=%d reference=%d", len(got), len(ref))
- }
- for i := range ref {
- if got[i] != ref[i] {
- t.Fatalf("varying frames: mismatch at %d: got %v want %v", i, got[i], ref[i])
- }
- }
- }
|