// Which merge threshold does a recording itself suggest? Fits a 2-component // Gaussian mixture (EM) to the pairwise window-similarity histogram — the // "different speaker" and "same speaker" modes — and takes the point where the // two weighted densities cross. Compares that with the fixed thresholds. import fs from 'node:fs'; const f = x => x.toFixed(3); const norm = v => { let s = 0; for (const x of v) s += x * x; s = Math.sqrt(s) + 1e-9; return v.map(x => x / s); }; const dot = (a, b) => { let s = 0; for (let i = 0; i < a.length; i++) s += a[i] * b[i]; return s; }; for (const file of process.argv.slice(2)) { const d = JSON.parse(fs.readFileSync(file, 'utf8')); const wins = d.wins.filter(w => w.v); let X = wins.map(w => norm(w.v)); const dim = X[0].length, mu = new Array(dim).fill(0); for (const v of X) for (let i = 0; i < dim; i++) mu[i] += v[i] / X.length; X = X.map(v => norm(v.map((x, i) => x - mu[i]))); const n = X.length; // sample pairs (deterministic stride, no RNG) const vals = []; for (let k = 1; k < 400000 && vals.length < 60000; k++) { const i = (k * 7919) % n, j = (k * 104729 + 13) % n; if (i !== j) vals.push(dot(X[i], X[j])); } // EM for 2 Gaussians let m1 = -0.1, m2 = 0.5, s1 = 0.15, s2 = 0.15, w1 = 0.8; const N = (x, m, s) => Math.exp(-((x - m) ** 2) / (2 * s * s)) / (s * Math.sqrt(2 * Math.PI)); for (let it = 0; it < 100; it++) { let sw1 = 0, sm1 = 0, sm2 = 0, sv1 = 0, sv2 = 0, sw2 = 0; for (const x of vals) { const a = w1 * N(x, m1, s1), b = (1 - w1) * N(x, m2, s2); const r = a / (a + b + 1e-300); sw1 += r; sw2 += 1 - r; sm1 += r * x; sm2 += (1 - r) * x; } const nm1 = sm1 / sw1, nm2 = sm2 / sw2; for (const x of vals) { const a = w1 * N(x, m1, s1), b = (1 - w1) * N(x, m2, s2); const r = a / (a + b + 1e-300); sv1 += r * (x - nm1) ** 2; sv2 += (1 - r) * (x - nm2) ** 2; } m1 = nm1; m2 = nm2; s1 = Math.max(0.02, Math.sqrt(sv1 / sw1)); s2 = Math.max(0.02, Math.sqrt(sv2 / sw2)); w1 = sw1 / vals.length; } // crossing point between the means: w1*N1(x) = w2*N2(x) let lo = m1, hi = m2, t = (lo + hi) / 2; for (let i = 0; i < 60; i++) { t = (lo + hi) / 2; const g = w1 * N(t, m1, s1) - (1 - w1) * N(t, m2, s2); if (g > 0) lo = t; else hi = t; } console.log(`${file.split('.').slice(0, 2).join('.')}: diff N(${f(m1)},${f(s1)}) w=${f(w1)} | same N(${f(m2)},${f(s2)}) w=${f(1 - w1)} → crossing=${f(t)}`); }