TapTempo kept a rolling window of 8 taps, so a long tapping session chased the most recent taps instead of converging on the overall tempo (trav noticed the estimate never settled). Every tap since the session started now counts; a >2.5s gap still begins a fresh session. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
35 lines
1.1 KiB
Python
35 lines
1.1 KiB
Python
"""Tap-tempo math, kept Qt-free so it's unit-testable."""
|
|
|
|
|
|
class TapTempo:
|
|
RESET_GAP_SECONDS = 2.5
|
|
|
|
def __init__(self):
|
|
self._taps: list[float] = []
|
|
|
|
def tap(self, now: float) -> int | None:
|
|
"""Record a tap at monotonic time `now`; returns the current bpm
|
|
estimate (None until there are two taps in the series). Every tap
|
|
since the series started counts — the estimate converges on the
|
|
overall tempo instead of chasing the most recent few taps."""
|
|
if self._taps and now - self._taps[-1] > self.RESET_GAP_SECONDS:
|
|
self._taps = []
|
|
self._taps.append(now)
|
|
return self.bpm()
|
|
|
|
def bpm(self) -> int | None:
|
|
if len(self._taps) < 2:
|
|
return None
|
|
intervals = [b - a for a, b in zip(self._taps, self._taps[1:])]
|
|
average = sum(intervals) / len(intervals)
|
|
if average <= 0:
|
|
return None
|
|
return round(60.0 / average)
|
|
|
|
@property
|
|
def tap_count(self) -> int:
|
|
return len(self._taps)
|
|
|
|
def reset(self):
|
|
self._taps = []
|