Reduce idle power in sleep mode: pause analyzer redraws and release the output device#37
Open
takaeda wants to merge 3 commits into
Open
Reduce idle power in sleep mode: pause analyzer redraws and release the output device#37takaeda wants to merge 3 commits into
takaeda wants to merge 3 commits into
Conversation
The worklet already enters a sleep mode after 60s of silence + no user activity (see audioLevelMonitoring in audio-processor.js): it stops running plugin DSP and just passes input to output. The main thread however kept analyzer plugins' per-frame redraw loops running, so a fully-idle EffeTune still consumed visible main-thread CPU on low-power hardware. Propagate the sleep state to every plugin via _setSleepMode() so the analyzer redraw loops can pause too. Each startAnimation() guard already short-circuits when (this.enabled && this._sectionEnabled) is false; this extends the same guard to also short-circuit when this._sleepMode is true. Editing while in sleep is safe: EventManager already forwards any mousedown / mousemove / wheel / click / key / touch event to the worklet as 'userActivity', which wakes the worklet immediately and emits 'sleepModeChanged: false' back, so the redraw loops resume before the first interaction frame the user sees.
EffeTune's worklet already enters sleep mode after 60s of input/output silence + no user activity, but the AudioContext stays running, which keeps the OS output stream (and any downstream DAC/Amp) fully awake - wasteful on an always-on low-power host (Raspberry Pi 5 + external DAC). Suspend the AudioContext on sleep so the output device is released and the DAC/Amp can enter its own standby. The catch is that the suspended worklet can no longer detect input returning (its process() stops), which is what normally auto-wakes sleep. To keep auto-wake-on-audio, watch a clone of the live input track with a MediaStreamTrackProcessor that opens no output device (new InputActivityWatcher); on signal - or on any user activity / window-visibility change - resume the context and nudge the worklet awake. Integration details: - AudioContextManager gains setIntentionalSuspend(); its onstatechange auto-resume (added for macOS device-change recovery) skips the resume while the suspend is deliberate, so it doesn't immediately undo us. - EventManager.handleUserActivity and app.js visibilitychange route through AudioManager.wakeFromSleep when sleep-suspended, since the frozen worklet can't act on a userActivity message. - _doReset tears down the watcher and clears the intentional-suspend flag before rebuilding the graph, so a reset during sleep can't leave the new context unable to auto-resume. Intentionally a no-op on macOS: that platform's carefully-tuned HDMI / CoreAudio recovery treats AudioContext suspend/close transitions as failure signals, so a deliberate suspend would collide with it for little gain. If MediaStreamTrackProcessor is unavailable or the input isn't a live MediaStream (e.g. file playback), we skip suspending and keep the status-quo wake-on-input. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Two independent idle-power drains while the worklet is asleep, kept as two commits so each is reviewable / revertible on its own:
Pause analyzer redraws - propagate the worklet's
sleepModeChangedto plugins viaPluginBase._setSleepMode(), extending the existingstartAnimation()guard (from Reduce main-thread CPU during GUI interaction (especially on low-power devices) #33) with the sleep state. ~37% -> ~2% main-thread CPU on a Pi 5.Release the output device -
suspend()theAudioContexton sleep so the OS output stream (and the downstream DAC/Amp) is released. Auto-wake is preserved by a device-freeMediaStreamTrackProcessorwatcher over a clone of the input track (newjs/audio/input-activity-watcher.js); user activity and window visibility also wake viaAudioManager.wakeFromSleep(). The context manager'sonstatechangeauto-resume is gated so the deliberate suspend isn't undone. No-op on macOS (stays clear of the HDMI/CoreAudio recovery path there); also falls back to the status quo whenMediaStreamTrackProcessoris unavailable or the input isn't a live MediaStream.Verified on a Raspberry Pi 5 (input = virtual mic, output = USB DAC): the DAC node goes from
RUNNINGto suspended in pipewire while asleep and resumes on returning audio or UI activity; idle main-thread CPU drops as above.Happy to split this into two PRs if you'd prefer to take them separately.