From 29f535dba1c955aa413843a482d94b4c1af535f5 Mon Sep 17 00:00:00 2001 From: Steve Pinkham Date: Mon, 13 Jul 2026 14:35:31 -0400 Subject: [PATCH] Fix Hilbert filter: quadrature branch used the in-phase (I) taps Hilbert::filter_block formed both the real and imaginary parts of the analytic output from the in-phase coefficient table (IHilbertBPFirCoef). The quadrature pointer QKptr was initialised but never used: the queue stores the real input duplicated into both cmplx components, and the loop multiplied the whole cmplx sample by the scalar I coefficient, so I and Q came out identical (a 45-degree-rotated real signal) instead of a 90-degree Hilbert pair. Because the "analytic" input to the fading stage was not truly analytic, the Rayleigh fading it produced was not circular. The faded envelope of a CW probe measured mean/rms ~= 0.78 (Hoyt/Nakagami-like, roughly double the deep-fade probability) rather than the Rayleigh value 0.886. Apply the I taps to the real accumulation and the Q taps to the imaginary accumulation. After the fix a CW tone's analytic envelope is essentially constant (|z| std/mean 0.52 -> 0.02) and the 2-path CCIR-Poor faded envelope is Rayleigh (mean/rms 0.90). Co-Authored-By: Claude Opus 4.8 --- Delay.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Delay.cpp b/Delay.cpp index fa0b4ea..eda70a6 100644 --- a/Delay.cpp +++ b/Delay.cpp @@ -23,8 +23,10 @@ void Hilbert::filter_block(const double* pIn, cmplx* pOut) const double* IKptr = IHilbertBPFirCoef+HILBPFIR_LENGTH-m_hilbert_ptr; const double* QKptr = QHilbertBPFirCoef+HILBPFIR_LENGTH-m_hilbert_ptr; cmplx acc{0., 0.}; - for (int j = 0; j < HILBPFIR_LENGTH; ++ j, ++ Firptr) - acc += (*Firptr) * (*IKptr++); + for (int j = 0; j < HILBPFIR_LENGTH; ++ j, ++ Firptr) { + acc.r += Firptr->r * (*IKptr++); // in-phase: band-pass (I) taps + acc.i += Firptr->i * (*QKptr++); // quadrature: Hilbert (Q) taps + } pOut[i] = acc; if (-- m_hilbert_ptr < 0) m_hilbert_ptr = HILBPFIR_LENGTH - 1;