fix: reduce master clock to 240 Hz + eliminate note-off timeouts

- Master clock 960→240 Hz: reduces CPU/GC pressure by 4x while still
  providing 12x headroom for 300 BPM sixteenths
- Remove Tone.getContext().setTimeout() for note-off scheduling —
  these accumulated over time causing periodic hiccups
- Note-off now happens at step boundary: previous gate turned off
  at the start of each new step (cleaner, zero accumulation)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Jose Luis
2026-03-21 17:52:38 +01:00
parent 1f941d7e39
commit 18661961a1
2 changed files with 9 additions and 8 deletions

View File

@@ -71,18 +71,23 @@ export default function SequencerWidget({ moduleId }) {
} }
let lastStepIdx = -1; let lastStepIdx = -1;
let noteOffTimeout = null; let lastGateOn = false;
subscribeTick(`seq-${moduleId}`, (time, elapsed) => { subscribeTick(`seq-${moduleId}`, (time, elapsed) => {
const currentBpm = bpmRef.current; const currentBpm = bpmRef.current;
const currentNumSteps = numStepsRef.current; const currentNumSteps = numStepsRef.current;
const sixteenthRate = (currentBpm * 4) / 60; const sixteenthRate = (currentBpm * 4) / 60;
const stepDuration = 1 / sixteenthRate;
const stepIdx = Math.floor(elapsed * sixteenthRate) % currentNumSteps; const stepIdx = Math.floor(elapsed * sixteenthRate) % currentNumSteps;
if (stepIdx === lastStepIdx) return; if (stepIdx === lastStepIdx) return;
lastStepIdx = stepIdx; lastStepIdx = stepIdx;
// Turn off previous note at step boundary (no setTimeout needed)
if (lastGateOn) {
setSequencerSignals(moduleId, 0, false);
lastGateOn = false;
}
const s = stepsRef.current[stepIdx]; const s = stepsRef.current[stepIdx];
if (!s) return; if (!s) return;
@@ -90,11 +95,7 @@ export default function SequencerWidget({ moduleId }) {
if (s.gate) { if (s.gate) {
setSequencerSignals(moduleId, midiToFreq(s.midi), true); setSequencerSignals(moduleId, midiToFreq(s.midi), true);
Tone.getContext().setTimeout(() => { lastGateOn = true;
setSequencerSignals(moduleId, midiToFreq(s.midi), false);
}, stepDuration * 0.8);
} else {
setSequencerSignals(moduleId, midiToFreq(s.midi), false);
} }
}); });

View File

@@ -15,7 +15,7 @@ const keyboardState = { frequency: 440, gate: false };
// ==================== Global Master Clock ==================== // ==================== Global Master Clock ====================
// Single high-resolution clock (960 ticks/sec ≈ 1ms precision). // Single high-resolution clock (960 ticks/sec ≈ 1ms precision).
// All sequencers/piano rolls derive their timing from this. // All sequencers/piano rolls derive their timing from this.
const MASTER_TICK_RATE = 960; // Hz const MASTER_TICK_RATE = 240; // Hz — enough for 300 BPM sixteenths (20 Hz) with 12x headroom
let _masterClock = null; let _masterClock = null;
let _masterTime = 0; // audio-context seconds at clock start let _masterTime = 0; // audio-context seconds at clock start
const _tickListeners = new Map(); // id → callback(audioTime, elapsed) const _tickListeners = new Map(); // id → callback(audioTime, elapsed)