package recorder import ( "math" "testing" ) func TestStereoDecodeStatefulPilotLock(t *testing.T) { const ( sampleRate = 192000 blockSize = 4096 blocks = 10 pilotAmp = 0.1 toneL = 440.0 toneR = 880.0 ) sess := &streamSession{} var out []float32 locked := false for b := 0; b < blocks; b++ { mono := make([]float32, blockSize) base := b * blockSize for i := 0; i < blockSize; i++ { t := float64(base+i) / float64(sampleRate) l := math.Sin(2 * math.Pi * toneL * t) r := math.Sin(2 * math.Pi * toneR * t) lpr := 0.5 * (l + r) lmr := 0.5 * (l - r) composite := lpr + lmr*math.Cos(2*math.Pi*38000*t) + pilotAmp*math.Sin(2*math.Pi*19000*t) mono[i] = float32(composite) } out, locked = sess.stereoDecodeStateful(mono, sampleRate) } if !locked { t.Fatalf("expected pilot lock after warmup blocks") } if len(out) != blockSize*2 { t.Fatalf("unexpected output size: got %d, want %d", len(out), blockSize*2) } left := make([]float32, blockSize) right := make([]float32, blockSize) for i := 0; i < blockSize; i++ { left[i] = out[i*2] right[i] = out[i*2+1] } magL440 := toneMagnitude(left, toneL, sampleRate) magL880 := toneMagnitude(left, toneR, sampleRate) magR440 := toneMagnitude(right, toneL, sampleRate) magR880 := toneMagnitude(right, toneR, sampleRate) if magL440 < 0.05 || magR880 < 0.05 { t.Fatalf("decoded tones too weak: L440=%.3f R880=%.3f", magL440, magR880) } leftIsL := magL440 >= magL880*1.3 && magR880 >= magR440*1.3 rightIsL := magL880 >= magL440*1.3 && magR440 >= magR880*1.3 if !leftIsL && !rightIsL { t.Fatalf( "channels not cleanly separated: L440=%.3f L880=%.3f R440=%.3f R880=%.3f", magL440, magL880, magR440, magR880, ) } } func toneMagnitude(x []float32, freq float64, sampleRate int) float64 { var iSum, qSum float64 for n, v := range x { angle := 2 * math.Pi * freq * float64(n) / float64(sampleRate) iSum += float64(v) * math.Cos(angle) qSum += float64(v) * math.Sin(angle) } return math.Hypot(iSum, qSum) / float64(len(x)) }