Skip to content

Fix DSD decoder respawn to match the audio engine's payload format - #318

Merged
ultraelectronica merged 2 commits into
mainfrom
versionF
Oct 16, 2026
Merged

ultraelectronica merged 2 commits into
mainfrom
versionF

Conversation

@ultraelectronica

Copy link
Copy Markdown
Member

Summary

Fixes silent DSD playback after seeking, track changes, and requeues on DAPs that
run a native-DSD engine, and corrects WavPack PCM level normalization.

DSD respawns no longer infer their encoding from pipeline flags

  • In-engine DSD decoders spawned for seek / next-track / requeue used to derive
    their output encoding from the pipeline mode. A native-DSD DAP engine runs its
    raw-bypass pipeline as PipelineMode::Dop, so a respawn inferred DoP and
    spawned a DoP decoder into a native stream. The native render loop reads each
    word's low byte, and a DoP word's low byte is always 0x00 — silence. The
    result was DSD audio that went quiet once you seeked or advanced.
  • Record the payload format (Pcm / Dop / Native) at engine creation in a
    new EngineDsdMode on AudioCallbackData, fixed for the engine's lifetime
    because the format is burned into the stream. spawn_decoder now takes the
    engine's callback data and reads that recorded mode instead of re-deriving it
    from the current preference.
  • Derive the initial mode from the requested output strategy: native and USB
    native DSD map to Native, DoP maps to Dop, and everything else (including
    the desktop/cpal engine) is Pcm.
  • Force engine recreation when the caller excludes the current strategy (for
    example the DoP-to-PCM decimation fallback), even when the rate/strategy
    checks would otherwise reuse the handle — a mismatched payload format cannot be
    swapped in place.

WavPack PCM normalized by native bit depth

  • libwavpack returns integer samples right-justified at the source's native bit
    depth (a 24-bit file yields ±2^23 even though it occupies three bytes), but the
    old scale keyed off the container width and normalized 3- and 4-byte samples by
    2^31. A 24-bit file was therefore scaled 256x too quiet.
  • Add wavpack_pcm_scale(bits_per_sample, bytes_per_sample), which normalizes by
    the real bit depth (falling back to the container width when depth is missing,
    clamped to 1..=32). This also handles wasted-bit files such as 20-bit audio
    stored in a 24-bit container, and fixes the log line to report both values.

Tests

  • engine_dsd_mode_roundtrips_and_defaults_to_pcm.
  • dsd_respawn_matches_engine_payload_format_not_pipeline_flags, which writes a
    minimal stereo DSD64 DSF fixture and asserts a native engine respawns at
    352800 Hz (not the 176400 Hz DoP carrier the old inference chose), a DoP engine
    still respawns DoP, and a PCM-decimation engine respawns PCM.
  • pcm_scale_normalizes_to_native_bit_depth covering 8/16/24/32-bit, 20-bit in
    a 24-bit container, and the missing-depth fallback.

Related issue

none

Checklist

  • flutter analyze passes
  • flutter test passes
  • Rust changes: cargo test --manifest-path rust/Cargo.toml passes
  • Built and ran the app if the change touches a playback path (flutter run --flavor full)
  • Generated code regenerated and committed if bridge/drift/freezed models changed
  • CHANGELOG.md updated for user-facing changes
  • No secrets, keys, tokens, or machine-local files committed
  • One change per PR; no unrelated reformatting in the same diff
  • Hardware tested if the Rust audio engine or USB/DAC output changed

On DAPs, only attempt DoP when DAP bit-perfect mode is enabled,
since the shared mixer route cannot verify a DoP carrier. Otherwise
fall back to PCM decimation to avoid misinterpreting decimated PCM.

Also hard-fail DoP pre-flight only when a direct USB session is
actually active, and exclude DsdDoP when reconfiguring the engine
for PCM decimation so a DoP-configured engine cannot be reused.
DSD decoders spawned for seek/next/requeue inferred their output
encoding from pipeline flags, which spawns a DoP decoder into a
native-DSD stream (DoP low byte 0x00 = silence on the native wire).
Record the encoding at engine creation and reuse it for respawns.

Also force engine recreation when a caller excludes the current
strategy, and normalize WavPack PCM samples by native bit depth
rather than container width.
@ultraelectronica ultraelectronica self-assigned this Oct 16, 2026
@ultraelectronica
ultraelectronica merged commit af7261a into main Oct 16, 2026
6 checks passed
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.

1 participant