@@ -315,24 +315,38 @@ float getDisplacementPeriodSec() const {
315
315
z_imag = (1 .0f - alpha_env) * z_imag + alpha_env * y_imag;
316
316
}
317
317
318
- void updatePhaseCoherence () {
319
- float mag = std::hypot (z_real, z_imag);
320
- if (mag <= EPSILON) {
321
- if (coh_r.isReady () && coh_i.isReady ())
322
- R_phase = std::clamp (std::sqrt (coh_r.get () * coh_r.get () +
323
- coh_i.get () * coh_i.get ()), 0 .0f , 1 .0f );
324
- else
325
- R_phase = 0 .0f ;
326
- return ;
327
- }
328
- float u_r = z_real / mag;
329
- float u_i = z_imag / mag;
330
- coh_r.update (u_r, alpha_coh);
331
- coh_i.update (u_i, alpha_coh);
332
- R_phase = std::clamp (std::sqrt (coh_r.get () * coh_r.get () +
333
- coh_i.get () * coh_i.get ()), 0 .0f , 1 .0f );
318
+ // --- Power-weighted multi-bin phase coherence ---
319
+ void updatePhaseCoherence () {
320
+ float sum_r = 0 .0f , sum_i = 0 .0f ;
321
+ float sum_w = 0 .0f ;
322
+
323
+ // Accumulate weighted complex phasors across bins
324
+ for (int i = 0 ; i < NBINS; ++i) {
325
+ float w = last_S_eta_hat[i];
326
+ if (!(w > EPSILON)) continue ;
327
+
328
+ float zr = bin_zr[i];
329
+ float zi = bin_zi[i];
330
+ float mag = std::hypot (zr, zi);
331
+ if (mag <= EPSILON) continue ;
332
+
333
+ float ur = zr / mag;
334
+ float ui = zi / mag;
335
+
336
+ sum_r += w * ur;
337
+ sum_i += w * ui;
338
+ sum_w += w;
334
339
}
335
340
341
+ // Compute instantaneous coherence magnitude
342
+ float R_now = 0 .0f ;
343
+ if (sum_w > EPSILON)
344
+ R_now = std::sqrt (sum_r * sum_r + sum_i * sum_i) / sum_w;
345
+
346
+ // EMA update for phase regularity (uses short tau_coh)
347
+ R_phase = (1 .0f - alpha_coh) * R_phase + alpha_coh * R_now;
348
+ }
349
+
336
350
// Spectral moments: physically correct a→η conversion (1/ω⁴)
337
351
void updateSpectralMoments (float omega_inst) {
338
352
float w_obs = std::clamp (omega_inst, OMEGA_MIN_RAD, OMEGA_MAX_RAD);
0 commit comments