Skip to content

Commit b325b73

Browse files
committed
Add sub-bin carrier offset interpolation to v2, drop redundant tracker normalisation
FreqOffset::Estimate picked the carrier from the winning integer FFT bin. A bin is 46.9 Hz, so a receiver whose offset lands mid-bin keeps up to ~23 Hz of uncorrected carrier for the whole capture. Add a parabolic fit of the two-tone metric around the winning bin, behind SUBBIN_INTERP (off for now). Measured with frequency-shifted copies of the 10k synthetic stream: at a half-bin offset the integer-bin estimate drops 9656 -> 9499 messages and interpolation recovers it to 9627, while shifting by exactly one full bin changes nothing. On southwood (33 min, real hardware, offset -1.45 ppm) it is 6616 -> 6675 (+0.9%). Note the synthetic bulk stream is generated at exactly zero carrier offset, so it is blind to this and reports no change. PhaseTracker kept a unit-normalised copy of the decision line. It fed only proj, which is consumed through its sign alone, and the EMA recursion never read it, so the vector was redundant: dot against s directly and drop the member, removing a sqrt and two divides per sample. Output is byte-identical on kijkduin, airspy, bulk and southwood.
1 parent 1aa0b2c commit b325b73

2 files changed

Lines changed: 21 additions & 7 deletions

File tree

Source/DSP/Decoder/V2/V2Engine.cpp

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@
2222
#include "FFT.h"
2323
#include "Filters.h"
2424

25+
#define SUBBIN_INTERP 0
26+
2527
namespace V2
2628
{
2729
// tuning
@@ -108,7 +110,24 @@ namespace V2
108110
if (fz < 0)
109111
return 0.0f;
110112

111-
return (fft_length / 2 - (fz + delta / 2.0f)) / 2.0f / fft_length;
113+
float frac = 0.0f;
114+
115+
#if SUBBIN_INTERP
116+
if (fz > 0 && fz + delta + 1 < fft_length)
117+
{
118+
const float a = magnitude[fz - 1] + magnitude[fz - 1 + delta];
119+
const float c = magnitude[fz + 1] + magnitude[fz + 1 + delta];
120+
const float den = a - 2.0f * max_val + c;
121+
122+
if (den < 0.0f)
123+
{
124+
frac = 0.5f * (a - c) / den;
125+
frac = frac > 0.5f ? 0.5f : (frac < -0.5f ? -0.5f : frac);
126+
}
127+
}
128+
#endif
129+
130+
return (fft_length / 2 - (fz + frac + delta / 2.0f)) / 2.0f / fft_length;
112131
}
113132

114133
void FreqOffset::Derotate(float f, const CFLOAT32 *src, CFLOAT32 *dst, int len)
@@ -190,15 +209,11 @@ namespace V2
190209
const float alpha = training ? weight_train : weight;
191210
const float beta = 1.0f - alpha;
192211

193-
const float proj = z.real() * u.real() + z.imag() * u.imag();
212+
const float proj = z.real() * s.real() + z.imag() * s.imag();
194213
const float d = proj >= 0.0f ? 1.0f : -1.0f;
195214

196215
s = alpha * s + (beta * d) * z;
197216

198-
const float mag = sqrtf(norm2(s));
199-
if (mag > 1e-12f)
200-
u = s / mag;
201-
202217
// differential decision (NRZI-style, cancels any 180-degree offset)
203218
const int decision = proj > 0.0f ? 1 : 0;
204219
const int bit = decision ^ prev_decision;

Source/DSP/Decoder/V2/V2Engine.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,6 @@ namespace V2
6464
{
6565
unsigned rot = 0;
6666
CFLOAT32 s = CFLOAT32(0.0f, 0.0f); // decision-folded EMA
67-
CFLOAT32 u = CFLOAT32(1.0f, 0.0f); // decision line direction (unit)
6867
int prev_decision = 0;
6968
float weight = 0.86f; // tracking, after sync
7069
float weight_train = 0.75f; // acquisition, while training

0 commit comments

Comments
 (0)