| @@ -0,0 +1,77 @@ | |||
| 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)) | |||
| } | |||