Description
While reviewing the audio effects cleanup flow in Music Blocks, I noticed that _performNotes() in:
creates temporary Tone.js effect nodes (Vibrato, Tremolo, Phaser, Chorus, Distortion, etc.) and schedules their cleanup using raw setTimeout() calls.
Current implementation:
setTimeout(
() => {
effectsToDispose.forEach(effect => effect.dispose());
temp_filters.forEach(filter => filter.dispose());
// Re-establish dry path
synth.toDestination();
},
beatValue * 1000 + 500
);
This cleanup path bypasses the project's centralized ManagedTimer system.
As a result, when playback is stopped through doStopTurtles() and _timerManager.clearAll() runs, these raw cleanup timeouts remain active because they are not tracked by ManagedTimer.
If playback is stopped before all notes complete, pending cleanup callbacks may still execute later against already-disposed synth/effect objects from the previous session.
This can lead to:
- stale cleanup callbacks firing after Stop
- cleanup logic running on already-disposed Tone.js nodes
- retained closures/timers during long playback sessions
- inconsistent cleanup behavior compared to other ManagedTimer-based systems
- unexpected audio behavior between playback sessions
Expected Behavior
Effect cleanup timers should participate in the same lifecycle management system as other playback timers.
Stopping playback should:
- cancel or safely coordinate pending cleanup callbacks
- avoid callbacks executing against disposed Tone.js nodes
- fully clean up playback-related timers/effects
- prevent stale cleanup callbacks from previous sessions
Proof / Observations
The current cleanup logic uses unmanaged raw timers:
instead of _timerManager.setGuardedTimeout() or another ManagedTimer-backed mechanism.
The project already uses centralized timer cleanup through:
but these cleanup timeouts are not registered there.
How to Reproduce
-
Open Music Blocks
-
Create a Timbre block using effects such as:
- Vibrato
- Tremolo
- Phaser
- Chorus
-
Add many notes using that timbre
-
Press Play
-
Press Stop before all notes finish
-
Start playback again
-
Inspect pending callbacks/timers using DevTools
Suggested Direction
Replace raw setTimeout() usage with a ManagedTimer-backed timeout so cleanup callbacks participate in the same playback lifecycle management system.
Example direction:
this._timerManager.setGuardedTimeout(
() => {
effectsToDispose.forEach(effect => {
try {
if (effect) effect.dispose();
} catch (e) {}
});
temp_filters.forEach(filter => {
try {
if (filter) filter.dispose();
} catch (e) {}
});
if (synth && typeof synth.toDestination === "function") {
try {
synth.toDestination();
} catch (e) {}
}
},
beatValue * 1000 + 500,
() => false
);
Checklist
Description
While reviewing the audio effects cleanup flow in Music Blocks, I noticed that
_performNotes()in:js/utils/synthutils.jscreates temporary Tone.js effect nodes (Vibrato, Tremolo, Phaser, Chorus, Distortion, etc.) and schedules their cleanup using raw
setTimeout()calls.Current implementation:
This cleanup path bypasses the project's centralized
ManagedTimersystem.As a result, when playback is stopped through
doStopTurtles()and_timerManager.clearAll()runs, these raw cleanup timeouts remain active because they are not tracked byManagedTimer.If playback is stopped before all notes complete, pending cleanup callbacks may still execute later against already-disposed synth/effect objects from the previous session.
This can lead to:
Expected Behavior
Effect cleanup timers should participate in the same lifecycle management system as other playback timers.
Stopping playback should:
Proof / Observations
The current cleanup logic uses unmanaged raw timers:
instead of
_timerManager.setGuardedTimeout()or another ManagedTimer-backed mechanism.The project already uses centralized timer cleanup through:
but these cleanup timeouts are not registered there.
How to Reproduce
Open Music Blocks
Create a Timbre block using effects such as:
Add many notes using that timbre
Press Play
Press Stop before all notes finish
Start playback again
Inspect pending callbacks/timers using DevTools
Suggested Direction
Replace raw
setTimeout()usage with aManagedTimer-backed timeout so cleanup callbacks participate in the same playback lifecycle management system.Example direction:
Checklist