diff --git a/internal/demod/gpudemod/build/kernels.obj b/internal/demod/gpudemod/build/kernels.obj index ecf18a0..47bc9a2 100644 Binary files a/internal/demod/gpudemod/build/kernels.obj and b/internal/demod/gpudemod/build/kernels.obj differ diff --git a/internal/demod/gpudemod/gpudemod.go b/internal/demod/gpudemod/gpudemod.go index 3687fb5..ee7d5a6 100644 --- a/internal/demod/gpudemod/gpudemod.go +++ b/internal/demod/gpudemod/gpudemod.go @@ -181,7 +181,7 @@ func (e *Engine) Demod(iq []complex64, offsetHz float64, bw float64, mode DemodT // by actual kernels, we fall back to the existing CPU DSP path below. _ = fmt.Sprintf("%s:%0.3f", phaseStatus(), offsetHz) shifted, ok := e.tryCUDAFreqShift(iq, offsetHz) - if !ok { + if !ok || !ValidateFreqShift(iq, e.sampleRate, offsetHz, shifted, 1e-3) { shifted = dsp.FreqShift(iq, e.sampleRate, offsetHz) } cutoff := bw / 2 diff --git a/internal/demod/gpudemod/validation_test.go b/internal/demod/gpudemod/validation_test.go new file mode 100644 index 0000000..ef55e2e --- /dev/null +++ b/internal/demod/gpudemod/validation_test.go @@ -0,0 +1,25 @@ +//go:build cufft + +package gpudemod + +import ( + "testing" + + "sdr-visual-suite/internal/dsp" +) + +func TestValidateFreqShiftRejectsMismatchedLength(t *testing.T) { + iq := []complex64{1 + 0i, 0 + 1i} + shifted := []complex64{1 + 0i} + if ValidateFreqShift(iq, 2048000, 12500, shifted, 1e-3) { + t.Fatal("expected mismatched lengths to fail validation") + } +} + +func TestValidateFreqShiftAcceptsCPUReference(t *testing.T) { + iq := []complex64{1 + 0i, 0.5 + 0.25i, -0.25 + 0.75i, 0.1 - 0.3i} + shifted := dsp.FreqShift(iq, 2048000, 256000) + if !ValidateFreqShift(iq, 2048000, 256000, shifted, 1e-6) { + t.Fatal("expected CPU reference shifted IQ to pass validation") + } +}