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:
@@ -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);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user