# WASAPI PCM Implementation Safety Plan

This document is the guardrail before adding real Core Audio calls. It defines
how to introduce WASAPI shared and exclusive PCM support without creating rapid
open, stop, reset, release, or reinitialize loops that can destabilize USB DACs.

## Goals

- Add WASAPI PCM support in small, reviewable steps.
- Keep enumeration, format probing, shared playback, and exclusive playback as
  separate tasks.
- Require an explicit endpoint ID before any operation that can activate or
  initialize an audio client.
- Default stopped-but-selected devices to keep-warm or hold-open behavior.
- Log every lifecycle transition, requested value, negotiated value, and HRESULT.
- Treat device invalidation and resource invalidation as lifecycle events.

## Non-Goals

- Do not add WASAPI headers, COM calls, endpoint enumeration, or playback in the
  planning task that creates this document.
- Do not implement MME, DirectSound, ASIO, DoP, or native DSD as part of the
  first WASAPI PCM tasks.
- Do not add start/stop stress, sample-rate sweep, buffer ramp-down, or
  reinitialize loop tests until single-profile playback is stable.
- Do not change Windows global power settings, USB selective suspend settings,
  default device settings, endpoint volume, or vendor driver settings.
- Do not try to guarantee that a fully released device remains awake after the
  process stops owning it. The tool can keep a selected device warm only while
  the process is running and the user chose a hold mode that keeps ownership.

## Implementation Slices

### Slice 1: Endpoint Enumeration

Purpose:

- List WASAPI render endpoints and capture a read-only capability snapshot.

Allowed operations:

- Initialize COM for the current thread.
- Create `IMMDeviceEnumerator`.
- Enumerate render endpoints.
- Read endpoint ID and property store fields such as friendly name, state,
  data flow, role, form factor, and container/device identifiers.

Forbidden operations:

- No `IAudioClient` activation.
- No `IAudioClient::Initialize`.
- No stream start, stop, reset, render client access, or event handle setup.
- No automatic polling loop.

Safety gates:

- Enumeration should be user-triggered or narrowly scoped to a visible refresh.
- Enumeration failure must be logged and surfaced without retry loops.
- Enumeration must not select or initialize a default endpoint automatically for
  playback.

### Slice 2: Selected Endpoint Format Probe

Purpose:

- For one explicit endpoint ID, gather mix format, device period, and
  `IsFormatSupported` results without initializing a stream.

Allowed operations:

- Activate `IAudioClient` once for the selected endpoint.
- Call `GetMixFormat`.
- Call `GetDevicePeriod`.
- Call `IsFormatSupported` for a small user-visible candidate list.
- Release activated interfaces once after the probe is complete.

Forbidden operations:

- No `IAudioClient::Initialize`.
- No `GetBufferSize`, `GetCurrentPadding`, render client, or `Start`.
- No format sweep across many rates by default.
- No repeated activate/probe loop against the same endpoint.

Safety gates:

- Require an explicit endpoint ID.
- Use a bounded candidate list: mix format plus one or two user-selected PCM
  formats.
- Add a cooldown before probing the same endpoint again if a previous probe
  failed or returned invalidation/resource errors.
- Log closest-match formats without silently changing the requested test profile.

### Slice 3: WASAPI Shared PCM Playback

Purpose:

- Play one conservative PCM 1 kHz profile through one selected endpoint using
  shared mode.

Default profile:

- Shared mode.
- Mix format first.
- Conservative buffer duration.
- 1 kHz sine at the project safety default level.
- Ramp-in and ramp-out enabled.
- Drain before signal stop.
- Hold mode: keep warm after stop.

Allowed operations:

- Activate `IAudioClient` once for the selected endpoint and profile.
- Initialize once per selected stream profile.
- Set event handle when event-driven mode is selected.
- Get render client, buffer size, padding, stream latency, and clock values.
- Start once, render signal, then render valid PCM silence for keep-warm.
- Stop only when the user chooses release or the session ends.

Forbidden operations:

- No default start/stop stress.
- No repeated `Initialize` attempts with different formats inside one playback
  action.
- No `Reset` for normal stop.
- No immediate release as the default stop behavior.
- No fallback to a different endpoint unless the user explicitly selects it.

Safety gates:

- Require explicit endpoint ID and accepted shared-mode format.
- Initialize only after the UI/session log shows requested format, accepted
  format, buffer duration, event/timer mode, hold mode, and release policy.
- If the signal duration ends, continue valid PCM silence while keep-warm is
  active. The device remains in `RunningSilence` or `HeldOpen`, not `Released`.
- `Release after drain` must wait for drain completion and release cooldown.
- `Immediate release` must be an expert/manual option with a visible warning.

### Slice 4: WASAPI Exclusive PCM Playback

Purpose:

- Add exclusive-mode PCM only after shared-mode lifecycle behavior is stable.

Default profile:

- Conservative exclusive buffer duration. Event-driven exclusive rendering is
  the default drive mode and keeps `hnsBufferDuration` and `hnsPeriodicity`
  equal as required by WASAPI. Timer/poll-driven rendering remains available
  for diagnostics and may request the device default period with periodicity
  set to zero.
- Explicit user-selected PCM format.
- Event-driven mode registers an event handle before stream start, fills the
  initial render buffer before start, and writes full event buffers rather than
  partial padding-sized packets.
- Keep-warm after stop for known-problem USB DAC profiles.

Additional safety gates:

- Require `IsFormatSupported` success for the exact requested format before
  `Initialize`.
- If event-driven `Initialize` reports `AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED`, the
  backend may perform one documented alignment correction: query the aligned
  frame count, release the failed audio client, activate a fresh audio client,
  and retry once with the aligned buffer/period. It must not enter a retry loop.
- Do not change sample rate repeatedly in one session.
- Treat exclusive-mode failure as a clear diagnostic result, not as a reason to
  retry different formats automatically.
- Keep sample-rate or buffer sweeps out of scope until a later explicit task.

## Device Lifecycle Rules

All WASAPI work must use the lifecycle model from
`docs/DEVICE_STABILITY_POLICY.md`.

Required transitions:

- `Discovered` for enumerated endpoints.
- `Reserved` after endpoint activation.
- `Prepared` after successful stream initialization.
- `RunningSignal` while rendering the 1 kHz signal.
- `RunningSilence` while keep-warm silence is being rendered.
- `Draining` before release-oriented stop.
- `HeldOpen` only when the backend can safely stay initialized without active
  signal rendering.
- `Releasing` before interface release.
- `Released` after interface release and cooldown.
- `Faulted` for unrecovered HRESULT or invalidation paths.

Required logging:

- Timestamp in local time and UTC.
- High-resolution monotonic elapsed time.
- Backend mode: WASAPI shared or WASAPI exclusive.
- Endpoint ID and friendly name.
- Requested format and accepted/closest-match format.
- Requested and actual buffer values.
- Event-driven/timer-driven mode, stream flags, event timeout, event-alignment
  retry status, device default/minimum period, and actual buffer frames.
- Hold mode and release policy.
- HRESULT numeric value and decoded label when available.
- Thread ID and MMCSS state once audio-thread scheduling is added.

## Error And Invalidation Handling

Handle these as first-class lifecycle events:

- `AUDCLNT_E_DEVICE_INVALIDATED`
- `AUDCLNT_E_RESOURCES_INVALIDATED`
- `AUDCLNT_E_SERVICE_NOT_RUNNING`
- `AUDCLNT_E_UNSUPPORTED_FORMAT`
- `AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED`
- `AUDCLNT_E_EXCLUSIVE_MODE_NOT_ALLOWED`

Recovery policy:

- Stop rendering work for the invalidated stream.
- Release the affected COM interfaces once.
- Wait the configured cooldown before any reacquire attempt.
- Default automatic reacquire attempts to zero for the first playback task.
- Later automation may allow bounded reacquire attempts after explicit user
  confirmation and longer cooldowns for known-problem devices.
- Never enter an unbounded default-device follow, release, enumerate,
  reinitialize, or format-cycling loop.

## Manual Test Matrix

Run these manually only after the corresponding implementation slice exists.

### Safe Enumeration

- Target: any Windows render endpoint.
- Action: refresh WASAPI endpoints once.
- Pass: endpoint list appears; no stream initialization logs appear; failures are
  logged once with HRESULT.
- Fail: automatic playback, repeated refresh loop, or `IAudioClient::Initialize`
  appears in the log.

### Selected Endpoint Probe

- Target: explicit endpoint ID.
- Action: probe mix format and two selected PCM candidate formats.
- Pass: log contains mix format, device period, support result, closest match
  when supplied, and one release event.
- Fail: stream initialization, playback, or repeated probe loop occurs.

### Shared PCM Keep-Warm Smoke

- Target: explicit endpoint ID, preferably a non-critical output first.
- Action: play 1 kHz shared-mode PCM for a short duration, then keep valid PCM
  silence for at least 60 seconds.
- Pass: lifecycle reaches `RunningSignal`, then `RunningSilence`; padding and
  render timing are logged; no rapid release/reopen occurs.
- Fail: stream is fully released automatically at signal end, reset is used for
  normal stop, or the app silently retries another format/endpoint.

### Shared PCM Release-After-Drain

- Target: same endpoint after keep-warm smoke passes.
- Action: choose `Release after drain`, stop once, and wait through cooldown.
- Pass: lifecycle reaches `Draining`, `Releasing`, and `Released` once; cooldown
  is logged.
- Fail: immediate release, reset, or automatic reacquire occurs without explicit
  user action.

### Exclusive PCM Smoke

- Target: explicit endpoint ID with a known-supported format.
- Action: run one exclusive-mode profile after `IsFormatSupported` success.
- Pass: exact format acceptance is logged; unsupported formats fail before
  initialization; keep-warm/release policy is honored.
- Fail: sample-rate cycling, silent fallback to shared mode, or repeated
  exclusive initialize attempts.

### Known-Problem USB DAC Profile

- Target: user-marked XMOS/USB DAC or similar sensitive device.
- Action: enumerate, probe, then run shared-mode keep-warm only.
- Pass: no full release while the troubleshooting session remains active; no
  start/stop stress; invalidation produces a cooldown-gated stopped/faulted
  state.
- Fail: default release at signal end, repeated reacquire, sample-rate sweep, or
  aggressive reset.

## Acceptance Gate Before Code Review

For each WASAPI implementation task:

- Confirm the task is one slice only.
- Confirm `docs/DEVICE_STABILITY_POLICY.md` is still satisfied.
- Confirm real-device operations require explicit endpoint selection when they
  activate or initialize a stream.
- Confirm unit tests cover planning, state transitions, serialization, and
  formatting logic without hardware.
- Confirm manual hardware steps are documented for behavior that CI cannot
  verify.
- Confirm static source scan permits WASAPI references only in the intended
  WASAPI backend files and tests.

## References

- Microsoft WASAPI stream management:
  https://learn.microsoft.com/en-us/windows/win32/coreaudio/stream-management
- Microsoft `IAudioClient::Initialize`:
  https://learn.microsoft.com/en-us/windows/win32/api/audioclient/nf-audioclient-iaudioclient-initialize
- Microsoft recovering from `AUDCLNT_E_DEVICE_INVALIDATED`:
  https://learn.microsoft.com/en-us/windows/win32/coreaudio/recovering-from-an-invalid-device-error
- Microsoft USB selective suspend:
  https://learn.microsoft.com/en-us/windows-hardware/drivers/usbcon/usb-selective-suspend
