Skip to content

fix(audio): handle device disconnects and auto-resume on reconnect - #38

Merged
Horuse merged 2 commits into
mainfrom
fix/33-device-disconnect-stuck-state
Sep 6, 2026
Merged

fix(audio): handle device disconnects and auto-resume on reconnect#38
Horuse merged 2 commits into
mainfrom
fix/33-device-disconnect-stuck-state

Conversation

@Horuse

@Horuse Horuse commented Sep 5, 2026

Copy link
Copy Markdown
Owner

What does this PR do?

Fixes #33 where disconnecting a Bluetooth or USB audio device while the pipeline was running left the engine in a stuck "pipeline already running" zombie state that could neither be stopped nor restarted.

Specifically:

  • Idle Bluetooth device discovery (macOS): When connected Bluetooth headsets or headphones are idle (no audio actively playing through them), CoreAudio suspends their audio streams and reports 0 channels. Previously, Splitwave filtered these out as invalid devices (channels == 0), making connected but idle Bluetooth devices invisible in the picker. We now check if the device is alive and paired via CoreAudio transport properties so they remain selectable.
  • Pipeline continuity on disconnect: When an active input or output disconnects, the pipeline stays alive instead of crashing or hanging. The backend graph validator now allows 0 inputs if outputs exist, zero-filling audio buffers and streaming silence (0.0). File recordings stay open without splitting, speakers don't pop or drop out, and intermediate effects (EQ, gain, VSTs) keep their state.
  • Auto-reconnection: A background poll checks for missing devices every 3 seconds. Once the device reconnects, the engine seamlessly hot-swaps the audio stream back into the running pipeline.
  • Configurable in Settings: Added a toggle under Settings → Device disconnection: Keep pipeline running on disconnect (enabled by default). Users who prefer the pipeline to stop immediately on any device disconnect can turn it off.
  • Engine robustness: Made engine::Command::Stop idempotent, and if reconcile fails, the active state is cleanly reset (active = None) instead of leaving an orphaned running state.

Why is this the right approach?

  • Why keep running with silence? For live routing and especially file recording, a temporary Bluetooth blip shouldn't kill the recording or split it into separate files. Streaming silence keeps encoders, file handles, and speakers alive without tearing down the session.
  • Why in Settings? Having a global toggle in App Settings gives users the choice between continuity (auto-reconnect + silence) and strict DAW behavior (instant stop on error) without cluttering individual node cards on the canvas.
  • Why idempotent stop & reconcile reset? Previously, CoreAudio stream errors caused CPAL to fail asynchronously. Calling stop on an already-dead stream would return an error, keeping active = Some(...) forever. Resetting active state on failure and making stop idempotent prevents zombie states completely.

Checklist

  • Diff is limited to the change — no unrelated edits
  • bun run check passes
  • cargo check --manifest-path src-tauri/Cargo.toml passes
  • bun run format leaves the tree clean
  • Generated TS types are committed with the Rust change (if any)
  • No new dependency without a reason in the PR description
  • I read the RT audio path section of docs/CONCEPT.md and confirmed this change adds no allocations, locks, or syscalls to cpal / SCK callbacks or DspWorker::run

Platform coverage

  • Developed on: macOS (Apple Silicon)
  • Tested on: macOS
  • What I did to test:
    • Disconnected Bluetooth AirPods while audio was playing through speakers and recording to a WAV file. Verified file stays open, silence is recorded, and audio resumes once AirPods reconnect.
    • Tested with Keep pipeline running on disconnect toggle turned on and off.
    • Tested unrouted/single-node pipeline validation and device picker listing.
    • Ran cargo test --manifest-path src-tauri/Cargo.toml --lib (141 tests pass) and bun run check (0 errors).
  • Not tested: Windows, Linux (Windows received matching input/output error deduplication logic; Linux PipeWire untouched).

Related

@Horuse
Horuse merged commit 84bcf0e into main Sep 6, 2026
9 checks passed
@Horuse
Horuse deleted the fix/33-device-disconnect-stuck-state branch September 6, 2026 11:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[bug] Disconnecting device while active causes stuck state

1 participant