Improve phase wheel averaging
This commit is contained in:
@@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user