From 0b4bb2f74cf3feb45befde356b69fcf32236bab6 Mon Sep 17 00:00:00 2001 From: Colin Coleman Date: Sun, 26 Jul 2026 01:59:09 +0200 Subject: [PATCH] Acquire a lock from any window size, not just the smallest compute_update() hill-climbs the analysis-window size: from the last locked window it moves to a larger (more accurate) one while the lock holds, and drops to a smaller one when it stops. But from a cold start it began at the smallest (2s) window and, if that failed, dropped to -1 and gave up - it never tried the larger windows. A weak or jittery watch produces a cleaner, lower-jitter period in a larger window (more cycles to average), so it often can only lock at 8s or 16s, never at 2s. Such a watch therefore "usually doesn't register", yet "once locked, stays locked" - because acquisition needs the 2s window but tracking, once established at a larger window, sustains fine. Separate acquisition from tracking: while unlocked, probe one window per cycle, cycling through the sizes, until one locks; then the existing hill-climb tracks it. This acquires a normal watch on the first probe (every window locks) and a marginal watch on whichever window works. It does not add a new CPU cost regime: probing cycles through the sizes (the 16s FFT runs at most every fourth idle cycle), which is less work than tracking a locked watch already does (that runs the largest locked window every cycle). Co-Authored-By: Claude Opus 4.8 --- src/computer.c | 51 +++++++++++++++++++++++++++++++++++--------------- src/tg.h | 3 ++- 2 files changed, 38 insertions(+), 16 deletions(-) diff --git a/src/computer.c b/src/computer.c index 7010a3c..49f44a7 100644 --- a/src/computer.c +++ b/src/computer.c @@ -123,28 +123,47 @@ static void compute_update(struct computer *c) { struct processing_data *pd = c->pdata; struct processing_buffers *ps = pd->buffers; - int step = pd->last_step; - pd->last_step = 0; /* Do all buffers at once so that all computation interval(s) use the * same data. Buffers for some intervals will probably not be used, but * it's not expensive to fill them. Processing is the slow part. */ fill_buffers(ps); debug("\nSTART OF COMPUTATION CYCLE\n\n"); - unsigned int stepmask = BITMASK(NSTEPS); // Mask of available steps - do { - stepmask &= ~BIT(step); - analyze_processing_data(c->pdata, step, c->actv->bph, c->actv->la, c->actv->events_from); - if (ps[step].ready && ps[step].sigma < ps[step].period / 10000) { - // Try next step if it's available - if (stepmask & BIT(step+1)) step++; - } else { - // This step didn't pass, try a lesser step - step--; - } - } while(step >= 0 && stepmask & BIT(step)); + int step; + if (pd->last_step >= 0) { + /* Tracking an existing lock: hill-climb from the window we last locked + * on - move to a larger (more accurate) window while it still holds, + * drop to a smaller one if it stops. */ + step = pd->last_step; + unsigned int stepmask = BITMASK(NSTEPS); // Mask of available steps + do { + stepmask &= ~BIT(step); + analyze_processing_data(c->pdata, step, c->actv->bph, c->actv->la, c->actv->events_from); + + if (ps[step].ready && ps[step].sigma < ps[step].period / 10000) { + // Try next step if it's available + if (stepmask & BIT(step+1)) step++; + } else { + // This step didn't pass, try a lesser step + step--; + } + } while(step >= 0 && stepmask & BIT(step)); + } else { + /* Acquiring a lock. The hill-climb above can only reach a larger window + * by first locking the smaller one, but a weak or jittery watch may only + * yield a clean (low-jitter) period in a larger window. So while + * unlocked, probe one window per cycle, cycling through the sizes, until + * one locks - then the branch above tracks it. This lets a marginal + * watch be acquired on whichever window works, without running every + * (expensive) large FFT on every idle cycle. */ + step = pd->acquire_probe; + pd->acquire_probe = (pd->acquire_probe + 1) % NSTEPS; + analyze_processing_data(c->pdata, step, c->actv->bph, c->actv->la, c->actv->events_from); + if (!(ps[step].ready && ps[step].sigma < ps[step].period / 10000)) + step = -1; + } if (step >= 0) { debug("%f +- %f\n", ps[step].period/ps[step].sample_rate, ps[step].sigma/ps[step].sample_rate); @@ -158,6 +177,7 @@ static void compute_update(struct computer *c) c->actv->signal = step+1; } else { debug("---\n"); + pd->last_step = -1; // lost / not yet acquired - keep probing next cycle c->actv->is_old = 1; c->actv->signal = 0; } @@ -340,7 +360,8 @@ struct computer *start_computer(int nominal_sr, int bph, double la, int cal, int pd->buffers = p; pd->last_tic = 0; pd->is_light = light; - pd->last_step = 0; + pd->last_step = -1; /* start unlocked (acquiring) */ + pd->acquire_probe = 0; struct calibration_data *cd = malloc(sizeof(*cd)); setup_cal_data(cd); diff --git a/src/tg.h b/src/tg.h index 1bad9c1..7805ef0 100644 --- a/src/tg.h +++ b/src/tg.h @@ -118,7 +118,8 @@ struct processing_buffers { struct processing_data { struct processing_buffers *buffers; uint64_t last_tic; - int last_step; //!< Guess of step (buffers index) to try first, based on last iteration + int last_step; //!< Window (buffers index) currently locked, or -1 while acquiring + int acquire_probe; //!< Next window to probe while acquiring (cycles 0..NSTEPS-1) int is_light; };