You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
After #33 (lower GUI-interaction CPU) and #35 (sleep mode fires even with DC-emitting plugins), sleep mode works - but a fully idle EffeTune still draws noticeable power on an always-on low-power host (Raspberry Pi 5 + external USB DAC), in two independent ways:
Main-thread CPU. Analyzer plugins (level meter, spectrum analyzer, oscilloscope, spectrogram, stereo meter) keep their per-frame requestAnimationFrame redraw loops running, repainting the same idle picture forever. On a Pi 5 with a typical pipeline visible this is ~37% main-thread CPU even though nothing is happening.
The output device never powers down. Even in sleep the AudioContext stays running, so the OS keeps a real-time output stream feeding the sink (silence). The downstream DAC/Amp therefore never sees an idle device and can't enter its own standby/power-save state.
Diagnosis
The worklet's sleep mode (audioLevelMonitoring in plugins/audio-processor.js) only frees the audio render thread by bypassing DSP. The main thread drives analyzer redraws independently via startAnimation(), whose guard (added in Reduce main-thread CPU during GUI interaction (especially on low-power devices) #33) checks (this.enabled && this._sectionEnabled) but has no notion of the worklet being asleep.
Sleep mode only changes what the worklet computes; it never touches the AudioContext. A running context keeps the output device open and fed, so neither the OS nor (on Linux) pipewire's suspend-on-idle ever releases it. The only thing that releases the device is audioContext.suspend() / close(). The catch: the worklet's process() - which also detects input returning and auto-wakes sleep - only runs while the context is running, so a naive suspend() would kill wake-on-input.
Proposed fix
Propagate the worklet's existing sleepModeChanged transition to every plugin (PluginBase._setSleepMode()), extending the redraw guard so analyzer loops also pause while asleep. Any user interaction already wakes the worklet via the existing userActivity channel, so editing while idle stays safe.
On sleep, suspend() the AudioContext to release the output device. Preserve auto-wake-on-audio by watching a clone of the live input track with a MediaStreamTrackProcessor - which opens no output device - and resuming on returning signal (plus user activity / window visibility). Gate the existing onstatechange auto-resume so it doesn't immediately undo the deliberate suspend.
This second part is intentionally limited to non-macOS. On macOS EffeTune carries a carefully-tuned audio-device recovery path for HDMI hotplug / CoreAudio behavior that, of necessity, treats AudioContext suspend/close transitions as failure signals; a deliberate suspend there would mean threading through that hard-won machinery for little gain, since the power-saving target is a low-power always-on Linux host. If MediaStreamTrackProcessor is unavailable or the input isn't a live MediaStream (e.g. file playback), it falls back to not suspending so wake-on-input is never lost.
Measured effect (Raspberry Pi 5)
Idle main-thread CPU: ~37% -> ~2%.
Output device: with the worklet asleep, the DAC node drops from RUNNING to suspended (verified in pw-top), so the USB DAC enters standby. It resumes within a beat on returning audio or any UI activity (a brief relock is expected as the device re-opens).
Symptom
After #33 (lower GUI-interaction CPU) and #35 (sleep mode fires even with DC-emitting plugins), sleep mode works - but a fully idle EffeTune still draws noticeable power on an always-on low-power host (Raspberry Pi 5 + external USB DAC), in two independent ways:
Main-thread CPU. Analyzer plugins (level meter, spectrum analyzer, oscilloscope, spectrogram, stereo meter) keep their per-frame
requestAnimationFrameredraw loops running, repainting the same idle picture forever. On a Pi 5 with a typical pipeline visible this is ~37% main-thread CPU even though nothing is happening.The output device never powers down. Even in sleep the
AudioContextstaysrunning, so the OS keeps a real-time output stream feeding the sink (silence). The downstream DAC/Amp therefore never sees an idle device and can't enter its own standby/power-save state.Diagnosis
The worklet's sleep mode (
audioLevelMonitoringinplugins/audio-processor.js) only frees the audio render thread by bypassing DSP. The main thread drives analyzer redraws independently viastartAnimation(), whose guard (added in Reduce main-thread CPU during GUI interaction (especially on low-power devices) #33) checks(this.enabled && this._sectionEnabled)but has no notion of the worklet being asleep.Sleep mode only changes what the worklet computes; it never touches the
AudioContext. A running context keeps the output device open and fed, so neither the OS nor (on Linux) pipewire's suspend-on-idle ever releases it. The only thing that releases the device isaudioContext.suspend()/close(). The catch: the worklet'sprocess()- which also detects input returning and auto-wakes sleep - only runs while the context isrunning, so a naivesuspend()would kill wake-on-input.Proposed fix
Propagate the worklet's existing
sleepModeChangedtransition to every plugin (PluginBase._setSleepMode()), extending the redraw guard so analyzer loops also pause while asleep. Any user interaction already wakes the worklet via the existinguserActivitychannel, so editing while idle stays safe.On sleep,
suspend()theAudioContextto release the output device. Preserve auto-wake-on-audio by watching a clone of the live input track with aMediaStreamTrackProcessor- which opens no output device - and resuming on returning signal (plus user activity / window visibility). Gate the existingonstatechangeauto-resume so it doesn't immediately undo the deliberate suspend.This second part is intentionally limited to non-macOS. On macOS EffeTune carries a carefully-tuned audio-device recovery path for HDMI hotplug / CoreAudio behavior that, of necessity, treats AudioContext suspend/close transitions as failure signals; a deliberate suspend there would mean threading through that hard-won machinery for little gain, since the power-saving target is a low-power always-on Linux host. If
MediaStreamTrackProcessoris unavailable or the input isn't a live MediaStream (e.g. file playback), it falls back to not suspending so wake-on-input is never lost.Measured effect (Raspberry Pi 5)
RUNNINGto suspended (verified inpw-top), so the USB DAC enters standby. It resumes within a beat on returning audio or any UI activity (a brief relock is expected as the device re-opens).Reference implementation
#37