|
|
|
@@ -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)) |
|
|
|
} |