mirror of
https://github.com/got-feedBack/feedBack.git
synced 2026-07-21 12:21:49 +00:00
perf(tuner): idle the always-on tuner viz rAF when there's no signal (#647)
The v3 home tuner card runs continuously, and every tuner visualization drove a self-rescheduling 60fps requestAnimationFrame loop that never stopped — pinning a renderer core even on a silent home screen with the needle/strobe at rest. Make each viz idle its loop once there's nothing left to animate, and re-kick it from update() on the next reading that actually moves it: - analogue-gauge: stop when the needle + drum strip have settled on their targets (|target-current| below a sub-visible epsilon); restart when a new reading moves the target. - strobe / mace-fx-iii / chef-mt3: stop when there's no live signal and the strobe drift (and glow fade) have fully decayed; restart on the next note. - toilet-tuner: stop when silent and the plunger has eased back to centre; restart on the next reading (guarded so repeated no-signal updates don't re-kick a parked loop). Active tuning is unchanged — the loop runs whenever a note is sounding or the indicator is still moving. Bumps tuner 1.3.1 -> 1.3.2. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
dfa825b4ab
commit
a732e1f9d2
@ -1,7 +1,7 @@
|
||||
{
|
||||
"id": "tuner",
|
||||
"name": "Guitar/Bass Tuner",
|
||||
"version": "1.3.1",
|
||||
"version": "1.3.2",
|
||||
"bundled": true,
|
||||
"private": false,
|
||||
"script": "screen.js",
|
||||
|
||||
@ -18,6 +18,8 @@
|
||||
// ── Constants ─────────────────────────────────────────────────────
|
||||
var _TUNER_LABEL_H = 12; // px height of each drum label
|
||||
var _TUNER_NEEDLE_HALF_SWEEP = 90; // degrees — ±50 cents = horizontal (180° apart)
|
||||
var _SETTLE_A = 0.05; // deg — needle "settled" threshold (sub-visible)
|
||||
var _SETTLE_Y = 0.1; // px — drum-strip "settled" threshold
|
||||
var _TUNER_IN_TUNE_THRESHOLD = 2;
|
||||
var _TUNER_STRIP_START_MIDI = 14; // ~18 Hz — covers 20 Hz minimum
|
||||
var _TUNER_STRIP_END_MIDI = 84; // ~1047 Hz C6
|
||||
@ -321,9 +323,34 @@
|
||||
currentAngle += (targetAngle - currentAngle) * lf;
|
||||
_setNeedle(currentAngle);
|
||||
|
||||
// Stop once the needle has settled on its target — a static needle
|
||||
// needs no repaint. update() re-kicks the loop when a new reading
|
||||
// moves the target, so this idles the always-on tuner (no signal /
|
||||
// steady pitch) instead of pinning a core at 60 fps forever.
|
||||
if (Math.abs(targetDrumY - currentDrumY) <= _SETTLE_Y
|
||||
&& Math.abs(targetAngle - currentAngle) <= _SETTLE_A) {
|
||||
currentDrumY = targetDrumY; currentAngle = targetAngle;
|
||||
freqStrip.style.transform = 'translateY(' + currentDrumY + 'px)';
|
||||
noteStrip.style.transform = 'translateY(' + currentDrumY + 'px)';
|
||||
_setNeedle(currentAngle);
|
||||
rafId = null;
|
||||
return;
|
||||
}
|
||||
rafId = requestAnimationFrame(_animate);
|
||||
}
|
||||
|
||||
// Restart the loop only when there's actually something to animate toward
|
||||
// (a new target). Reset lastTime so the first frame after an idle gap
|
||||
// doesn't take one big easing step.
|
||||
function _kick() {
|
||||
if (rafId === null
|
||||
&& (Math.abs(targetDrumY - currentDrumY) > _SETTLE_Y
|
||||
|| Math.abs(targetAngle - currentAngle) > _SETTLE_A)) {
|
||||
lastTime = performance.now();
|
||||
rafId = requestAnimationFrame(_animate);
|
||||
}
|
||||
}
|
||||
|
||||
rafId = requestAnimationFrame(_animate);
|
||||
|
||||
// ── Public API ────────────────────────────────────────────────
|
||||
@ -360,6 +387,7 @@
|
||||
bulbEl.style.backgroundColor = '#2a1010';
|
||||
bulbEl.style.border = '2px solid #4a2020';
|
||||
bulbEl.style.boxShadow = 'none';
|
||||
_kick(); // animate back to rest, then the loop self-stops
|
||||
return;
|
||||
}
|
||||
|
||||
@ -376,6 +404,7 @@
|
||||
bulbEl.style.border = '2px solid #4a2020';
|
||||
bulbEl.style.boxShadow = 'none';
|
||||
}
|
||||
_kick(); // a new reading moved the target → run until it settles
|
||||
}
|
||||
|
||||
function destroy() {
|
||||
|
||||
@ -620,8 +620,20 @@
|
||||
if (_mt3Mode === 'strobe') { _computeStrobeStates(); }
|
||||
|
||||
_applyTickStates();
|
||||
|
||||
// No signal and both the glow and strobe drift have fully settled →
|
||||
// idle the loop. update() re-kicks it on the next note.
|
||||
if (!_mt3HasSignal && _mt3GlowOpacity < 0.004
|
||||
&& Math.abs(_mt3SmoothedCents) <= 0.1) {
|
||||
_mt3RafId = null;
|
||||
_mt3LastTime = null;
|
||||
return;
|
||||
}
|
||||
_mt3RafId = requestAnimationFrame(_animateStrobe);
|
||||
}
|
||||
function _kick() {
|
||||
if (_mt3RafId === null) { _mt3LastTime = null; _mt3RafId = requestAnimationFrame(_animateStrobe); }
|
||||
}
|
||||
_mt3RafId = requestAnimationFrame(_animateStrobe);
|
||||
|
||||
// ── MODE button ───────────────────────────────────────────────
|
||||
@ -677,6 +689,7 @@
|
||||
_renderNote(' ');
|
||||
_applyAccidental();
|
||||
}
|
||||
if (hasNote) { _kick(); } // new signal → restart the strobe loop if idled
|
||||
}
|
||||
|
||||
// ── Public: destroy ───────────────────────────────────────────
|
||||
|
||||
@ -354,10 +354,19 @@
|
||||
if (_smoothedCents > 0) { speed = -speed; }
|
||||
_strobeOffset = ((_strobeOffset + speed * dt) % _totalDash + _totalDash) % _totalDash;
|
||||
arcPath.setAttribute('stroke-dashoffset', String(_strobeOffset));
|
||||
} else if (_currentCents === 0) {
|
||||
// Fully decelerated and no live signal → idle the loop instead of
|
||||
// rescheduling forever. update() re-kicks it on the next note.
|
||||
_rafId = null;
|
||||
_lastTime = null;
|
||||
return;
|
||||
}
|
||||
|
||||
_rafId = requestAnimationFrame(_animateStrobe);
|
||||
}
|
||||
function _kick() {
|
||||
if (_rafId === null) { _lastTime = null; _rafId = requestAnimationFrame(_animateStrobe); }
|
||||
}
|
||||
_rafId = requestAnimationFrame(_animateStrobe);
|
||||
|
||||
// ── Helper: derive octave number from frequency ───────────────
|
||||
@ -439,6 +448,7 @@
|
||||
|
||||
// Strobe state — smoothed animation decelerates naturally when _currentCents → 0
|
||||
_currentCents = hasNote ? cents : 0;
|
||||
if (hasNote) { _kick(); } // new signal → restart the decel loop if idled
|
||||
}
|
||||
|
||||
// ── Public: destroy ───────────────────────────────────────────
|
||||
|
||||
@ -142,9 +142,20 @@ window._tunerViz_strobe = function (container) {
|
||||
strobeEl.style.opacity = '0';
|
||||
}
|
||||
|
||||
// Idle the loop when there's no live signal — the strobe only needs to
|
||||
// paint while a note is sounding. update() re-kicks it on the next note,
|
||||
// so a silent tuner stops repainting instead of spinning at 60 fps.
|
||||
if (!strobeActive) { rafId = null; return; }
|
||||
rafId = requestAnimationFrame(_animate);
|
||||
}
|
||||
|
||||
function _kick() {
|
||||
if (rafId === null) {
|
||||
lastAnimateTime = performance.now();
|
||||
rafId = requestAnimationFrame(_animate);
|
||||
}
|
||||
}
|
||||
|
||||
rafId = requestAnimationFrame(_animate);
|
||||
|
||||
// ── Public API ────────────────────────────────────────────────────
|
||||
@ -188,6 +199,7 @@ window._tunerViz_strobe = function (container) {
|
||||
const inTune = Math.abs(cents) < 5;
|
||||
strobeEl.style.opacity = inTune ? '1' : '0.6';
|
||||
strobeEl.style.filter = inTune ? _STROBE_GLOW_IN_TUNE : _STROBE_GLOW_OUT;
|
||||
_kick();
|
||||
}
|
||||
|
||||
function destroy() {
|
||||
|
||||
@ -122,6 +122,26 @@
|
||||
plungerEl.style.left = _leftPct.toFixed(2) + '%';
|
||||
plungerEl.style.top = _topPct.toFixed(2) + '%';
|
||||
|
||||
// No live signal and the plunger has eased back to its resting centre
|
||||
// → idle the loop. update() re-kicks it on the next note.
|
||||
if (_currentNote === null && !_plungerDipped
|
||||
&& Math.abs(targetLeft - _leftPct) < 0.05) {
|
||||
_leftPct = targetLeft;
|
||||
plungerEl.style.left = _leftPct.toFixed(2) + '%';
|
||||
_rafId = null;
|
||||
_lastTime = null;
|
||||
return;
|
||||
}
|
||||
|
||||
_rafId = requestAnimationFrame(_animate);
|
||||
}
|
||||
|
||||
function _kick() {
|
||||
if (_rafId !== null) return;
|
||||
// Already parked at rest with no signal → nothing to animate, stay idle.
|
||||
if (_currentNote === null && !_plungerDipped
|
||||
&& Math.abs(_TUNER_TT_CENTRE_PCT - _leftPct) < 0.05) return;
|
||||
_lastTime = null;
|
||||
_rafId = requestAnimationFrame(_animate);
|
||||
}
|
||||
|
||||
@ -130,6 +150,7 @@
|
||||
_currentNote = note;
|
||||
_currentCents = note === null ? 0 : cents;
|
||||
if (!_plungerDipped) { noteEl.textContent = note || '–'; }
|
||||
_kick(); // a new reading may move the plunger → ensure the loop runs
|
||||
}
|
||||
|
||||
function destroy() {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user