Improve phase wheel averaging

This commit is contained in:
Mikei386
2026-07-22 09:40:49 +02:00
parent 4379b78b8f
commit 97352ecfce
2 changed files with 91 additions and 6 deletions
+47 -6
View File
@@ -69,10 +69,12 @@ export function init() {
filteredR: new Float32Array(0),
phaseAngleBuffer: new Float32Array(0),
phaseAmplitudeBuffer: new Float32Array(0),
phaseWeightBuffer: new Float32Array(0),
phaseAnalysisSeq: -1,
phaseAnalysisLength: 0,
phaseAnalysisSampleRate: 0,
phaseAnalysisCount: 0,
phaseConfidence: 0,
currentPhase: null,
currentRadius: 0,
smoothPhase: null,
@@ -353,8 +355,6 @@ function buildWheelTrace(state, xyData, wheel, gain = 1, CONFIG, audio) {
: 0;
const radius = wheel.radius;
let ampIdx = 0;
let sumSin = 0;
let sumCos = 0;
let sumRadius = 0;
let levelAcc = 0;
const gainLinear = Number.isFinite(gain) ? gain : 1;
@@ -367,28 +367,34 @@ function buildWheelTrace(state, xyData, wheel, gain = 1, CONFIG, audio) {
? ppmRadiusNorm
: linearToRadiusNorm(ampScaled, ringDbValues);
ampIdx++;
sumSin += Math.sin(angle);
sumCos += Math.cos(angle);
sumRadius += radiusNorm;
levelAcc += amp * amp;
}
if (ampIdx > 0) {
const invCount = 1 / ampIdx;
const avgAngle = Math.atan2(sumSin * invCount, sumCos * invCount);
const phaseSummary = summarizeWeightedPhase(
analysis.angles,
analysis.weights,
analysis.count,
);
const avgRadius = amplitudeMode === 'ppm-din' ? ppmRadiusNorm : (sumRadius * invCount);
const blockLevel = Math.sqrt(levelAcc * invCount);
if (blockLevel >= PHASE_LEVEL_THRESHOLD) {
if (blockLevel >= PHASE_LEVEL_THRESHOLD && phaseSummary) {
const avgAngle = phaseSummary.angle;
const prevPhase = Number.isFinite(state.smoothPhase) ? state.smoothPhase : avgAngle;
const prevRadius = Number.isFinite(state.smoothRadius) ? state.smoothRadius : avgRadius;
state.currentPhase = avgAngle;
state.currentRadius = avgRadius;
state.phaseConfidence = phaseSummary.coherence;
state.smoothPhase = smoothAngle(prevPhase, avgAngle, PHASE_PHASE_SMOOTH_ALPHA);
state.smoothRadius = lerp(prevRadius, avgRadius, PHASE_RADIUS_SMOOTH_ALPHA);
} else {
state.phaseConfidence = 0;
decayPhasePointer(state);
}
} else {
state.phaseConfidence = 0;
decayPhasePointer(state);
}
return { count: ampIdx, radius };
@@ -810,6 +816,35 @@ function ensurePhaseAnalysisBuffers(state, length) {
if (!state.phaseAmplitudeBuffer || state.phaseAmplitudeBuffer.length < length) {
state.phaseAmplitudeBuffer = new Float32Array(length);
}
if (!state.phaseWeightBuffer || state.phaseWeightBuffer.length < length) {
state.phaseWeightBuffer = new Float32Array(length);
}
}
export function summarizeWeightedPhase(angles, weights, count) {
const limit = Math.max(0, Math.min(
Number.isFinite(count) ? Math.floor(count) : 0,
angles?.length || 0,
weights?.length || 0,
));
let sumSin = 0;
let sumCos = 0;
let sumWeight = 0;
for (let i = 0; i < limit; i++) {
const angle = Number(angles[i]);
const weight = Number(weights[i]);
if (!Number.isFinite(angle) || !Number.isFinite(weight) || weight <= 0) continue;
sumSin += Math.sin(angle) * weight;
sumCos += Math.cos(angle) * weight;
sumWeight += weight;
}
if (!(sumWeight > 1e-12)) return null;
const resultant = Math.hypot(sumSin, sumCos);
return {
angle: Math.atan2(sumSin, sumCos),
coherence: Math.max(0, Math.min(1, resultant / sumWeight)),
weight: sumWeight,
};
}
function preparePhaseAnalysis(state, xyData) {
@@ -823,6 +858,7 @@ function preparePhaseAnalysis(state, xyData) {
return {
angles: state.phaseAngleBuffer,
amplitudes: state.phaseAmplitudeBuffer,
weights: state.phaseWeightBuffer,
count: state.phaseAnalysisCount,
};
}
@@ -847,6 +883,10 @@ function preparePhaseAnalysis(state, xyData) {
const magR = Math.min(1, Math.hypot(rRe, rIm));
state.phaseAngleBuffer[count] = phaseDiff - Math.PI / 2;
state.phaseAmplitudeBuffer[count] = Math.min(1, 0.5 * (magL + magR));
// The phase of a strong component must contribute more than the phase of
// a quiet or one-sided component. magL * magR is the magnitude of the
// complex L/R cross power for this analytic sample.
state.phaseWeightBuffer[count] = magL * magR;
count++;
}
state.phaseAnalysisSeq = seq;
@@ -856,6 +896,7 @@ function preparePhaseAnalysis(state, xyData) {
return {
angles: state.phaseAngleBuffer,
amplitudes: state.phaseAmplitudeBuffer,
weights: state.phaseWeightBuffer,
count,
};
}