Commit Graph
43 Commits
Author SHA1 Message Date
vicrodh 9c28960600 fix(audio): clearer error when CPAL cannot open an enumerated device (#288)
The old 'Device not found' error fired for two very different
situations: the device truly missing (unplugged, renamed) vs. present
in /proc/asound but not loadable by CPAL at the requested sample rate
or by the stored name format. The latter sent users on wild goose
chases.

Now we distinguish the cases: if /proc/asound still reports the card,
the message points at the likely causes (rate/format mismatch or
ALSA name mismatch) and suggests the plughw plugin, which is the
workaround we already apply automatically in Fix 2.

cc
2026-04-13 19:20:35 -06:00
vicrodh cba5b76bb3 fix(audio): fallback to plughw instead of CPAL when DAC rate unsupported (#288)
When /proc/asound reports that the selected hw: device does not
natively support the track's sample rate (e.g., 96 kHz track on a
48 kHz-only Vanatoo Transparent Zero), the previous code returned
None from try_create_direct_stream. The caller then dispatched to
CPAL's create_output_stream, where the CPAL ALSA host could not
locate the device by its /proc/asound name and failed with the
misleading error 'Device not found'.

The same function already has a plughw: fallback path further down
that wraps the hw device with ALSA's plug plugin — the latter
resamples in ALSA space and is the correct behavior for rate
mismatches. This change makes the /proc/asound pre-check set a
local flag that skips the hw attempt, falling through to the
existing plughw branch instead of returning None.

Sound now comes out. Bit-perfect is lost (by necessity) and the
mode reported back to the player is PluginFallback, which lets the
UI surface the degradation (wired up in Fix 4).

cc
2026-04-13 19:17:59 -06:00
vicrodh c8a3a648f5 chore(alsa): lower device enumeration logs to debug, add 5th retry
Reduce log noise by demoting ALSA device enumeration from info to debug.
Add a 5th backoff retry at 800ms for both hw and plughw paths as a
safety net for any EBUSY cases that slip through the lifecycle fix.

cc
2026-04-07 11:53:23 -06:00
vicrodh 2c0f42b885 chore(deps): bump dirs 5 -> 6 across all crates
Zero API changes, only bumps internal deps (windows-sys, redox_users).
Eliminates duplicate dirs versions in Cargo.lock.

Closes #257

cc
2026-04-06 15:16:34 -06:00
vicrodh 4439070032 fix(alsa): progressive backoff retry on device busy during track skip
When skipping tracks quickly, the previous PCM handle may not be
fully released by the kernel before the new track tries to open
the same ALSA device (snd_pcm_open fails with EBUSY).

Previous behavior: single retry after 200ms PipeWire suspend, then
immediate failure. This didn't cover our own PCM race condition.

New behavior: 4 retries with progressive backoff (50/100/200/400ms)
for both hw and plughw paths. PipeWire suspend still attempted once
upfront. Total worst-case wait: 750ms (vs 200ms before), but the
early retries at 50-100ms cover the common fast-skip case.

cc
2026-04-06 11:23:35 -06:00
vicrodh 09fcb5eb7b chore(deps): upgrade alsa crate 0.9 -> 0.10
Remove dead alsa dependency from src-tauri/Cargo.toml (code lives in
qbz-audio since V2 migration) and bump qbz-audio to alsa 0.10.
Eliminates duplicate alsa versions in Cargo.lock.

Breaking changes in 0.10 (Format/ChmapPosition reorder) do not affect
QBZ — we use catch-all matches and never rely on PartialOrd.

Closes #260

cc
2026-04-06 09:18:27 -06:00
vicrodh 1934d501ed feat(audio): make quality fallback opt-in via allow_quality_fallback setting
Off by default. When disabled, if the requested quality is unavailable
the download/stream fails instead of silently downgrading. Persisted
to audio_settings DB. Applied to offline cache downloads.

cc
2026-04-05 10:44:05 -06:00
vicrodh 83e43949b7 feat(audio): add skip_sink_switch option for JACK/DAW routing (#263)
When enabled, skips pactl set-default-sink on stream creation.
Preserves external audio routing (JACK, qjackctl, Reaper).
Mutually exclusive with DAC passthrough.

cc
2026-04-04 12:08:22 -06:00
Afonso Jorge Ramos 7a1890ad57 fix(audio): use released coreaudio-rs 0.14.1 instead of fork 2026-04-03 14:01:25 +02:00
Afonso Jorge Ramos 4c15a43200 feat(macos): enhance CpalDefaultBackend with device capabilities
On macOS, enumerate_devices() now populates supported_sample_rates,
max_sample_rate, device_bus, and is_hardware via CoreAudio probing.
create_output_stream() switches the device sample rate to match the
track before opening the CPAL stream.
2026-03-31 02:20:08 +02:00
Afonso Jorge Ramos c51f07fe65 feat(macos): add CoreAudio device probing and sample rate switching
Add coreaudio_direct module using coreaudio-rs safe wrappers for:
- Device capability probing (supported sample rates, transport type)
- Automatic sample rate switching per-track
- Device lookup by name for CoreAudio↔CPAL bridging

Uses a fork branch of coreaudio-rs that adds get_available_sample_rates
and get_device_transport_type helpers (PR RustAudio/coreaudio-rs#149).
2026-03-31 02:19:59 +02:00
Afonso Jorge Ramos dad062d61b fix(audio): add docs and fix param names for non-Linux stubs 2026-03-20 01:13:55 +01:00
Afonso Jorge Ramos f854e09661 feat(audio): add SystemDefault variant to AudioBackendType 2026-03-20 01:13:29 +01:00
Afonso Jorge Ramos 5ed549d46b feat(macos): gate Linux-only audio backends with cfg
Add #[cfg(target_os = "linux")] to pipewire_backend and pulse_backend
modules. Add CpalDefaultBackend for non-Linux platforms using CPAL
default host (CoreAudio on macOS). Gate reset_pipewire_clock() calls.
2026-03-20 00:45:03 +01:00
vicrodh 1f1e729db8 fix: HiFi Wizard now detects real DAC sample rates from /proc/asound
v2_query_dac_capabilities was returning hardcoded sample rates
(44.1-192kHz including 176.4kHz) instead of querying the actual
hardware. This caused incorrect configuration for DACs that don't
support all rates (e.g. DacMagic Plus doesn't support 176.4kHz).

Now queries /proc/asound/cardN/stream0 via PipeWire sink mapping
or ALSA device ID to get the real discrete rates from the USB
descriptor. Falls back to defaults only if detection fails.

cc
2026-03-19 12:41:24 -06:00
vicrodh 5488839e97 fix: increase PipeWire rate switch delay and verify after stream creation (#180)
USB hubs/docks (e.g. Razer USB4 Dock) may need longer than 300ms for
sample rate switching. When PipeWire hasn't finished reconfiguring,
96K/192K tracks play at wrong speed (half/quarter) and sound distorted.

Changes:
- Increase initial clock.force-rate delay from 300ms to 500ms
- After stream creation, query pw-metadata clock.rate to verify the
  actual graph rate matches what was requested
- If mismatch detected, retry with additional 500ms delay and re-force
- Log warnings when rate can't be verified so users can report

cc
2026-03-19 10:57:21 -06:00
vicrodh de29f1e2e0 chore(qconnect): checkpoint current poc state
cx
2026-03-13 17:28:05 -06:00
vicrodh d4fa3cd28f feat(linebed): 4096-point FFT, 512 bands, linear scaling
Backend:
- FFT size 1024 → 4096 (10.7 Hz bin resolution vs 43 Hz before)
- Spectral bands 190 → 512 (closer to musicvid's 1024)
- Remove pow(0.55) compression, use linear scaling (rms * 25)
  so frontend exponential transform has full dynamic range
- Reduce temporal smoothing 0.80 → 0.30 for responsive peaks

Frontend:
- Remove power-law redistribution (backend bands already log-scaled,
  was applying double perceptual weighting causing uniform plateaus)
- Restore 9-point 3-pass smoothing (now proportional: 9/512 = 1.76%
  vs musicvid's 9/1024 = 0.88%)
- NUM_BANDS matches new backend count (512)
2026-03-12 12:27:40 -06:00
vicrodh b1424c8b68 fix: re-apply clock.force-rate after stream creation on resume (#165)
When PipeWire drops the stream during pause, the graph reverts to the
DAC's default rate (e.g. 44100Hz). The pre-stream force-rate metadata
can be ignored when no streams are active. Re-applying after the stream
is connected forces PipeWire to reconfigure the graph at the correct
sample rate.

Also fix unused import warning for MusicBrainzSharedState in commands_v2
by using the import instead of the full path in factory_reset.
2026-03-11 13:32:42 -06:00
vicrodh 5fae14d68d fix(audio): stop updating live gain during refinement passes
Only write to gain atomic on initial measurement. Refinements now
update the cache only, preventing audible volume changes within a
single track. Inter-track normalization still works via cached values.
2026-03-05 11:16:05 -06:00
vicrodh ec3ecb35b2 fix(flatpak): restore pipewire stream matching and tray icon access
cx
2026-03-01 20:18:17 -06:00
vicrodh 8e104a3f3e fix(audio): replace deprecated CPAL name() usage with description()
cx
2026-03-01 10:44:54 -06:00
vicrodh 7086110569 feat: smart quality downgrade for unsupported sample rates
When ALSA backend detects hardware doesn't support a track's sample rate
(e.g. 176400Hz on a DAC that supports up to 192000Hz but not 176.4kHz),
automatically re-request the track from Qobuz at Hi-Res quality instead
of Ultra Hi-Res. This avoids resampling and maintains bit-perfect playback
by getting a version at a rate the hardware natively supports.

Adds device_supports_sample_rate() and get_device_supported_rates()
public API to qbz-audio for hardware capability queries via /proc/asound.
2026-02-28 07:48:03 -06:00
vicrodh 3329d1d632 fix: suspend PipeWire before CPAL stream in exclusive mode
When transitioning from ALSA Direct to CPAL fallback (e.g. for rate
resampling), PipeWire auto-re-acquires the device after ALSA Direct
releases it. CPAL then can't open the device.

Suspend PipeWire sinks before creating CPAL stream in exclusive mode,
same pattern used in ALSA Direct DeviceBusy handler.
2026-02-28 07:48:03 -06:00
vicrodh b234828228 fix: pre-check hardware rates via /proc/asound before ALSA Direct
When ALSA Direct opens a PCM device and fails to set the rate, the
USB DAC doesn't release the device fast enough for CPAL to open it
afterwards. This caused "device busy" errors even with a delay.

New approach: read /proc/asound/cardN/stream0 to check supported
rates BEFORE opening the device. If the rate isn't supported, skip
ALSA Direct entirely and fall through to CPAL (which has rate
fallback logic). The device is never touched at the wrong rate.
2026-02-28 07:48:03 -06:00
vicrodh d667ace987 fix: add 100ms delay after ALSA Direct PCM release before CPAL fallback
When ALSA Direct opens a PCM device and fails to configure the rate,
the PCM handle is dropped but the kernel may not release the device
instantly. CPAL then fails with "Device busy" when trying to open it.

Add 100ms sleep after InvalidParams to let the kernel fully release
the device before CPAL attempts to open it. Also fix error messages
to show the effective (fallback) rate instead of the original rate.
2026-02-28 07:48:03 -06:00
vicrodh c99f0d342d fix: add sample rate fallback to ALSA backend CPAL path
When ALSA Direct can't handle a rate (e.g. 176.4kHz) and falls back
to CPAL, CPAL also fails because the hardware doesn't support it.

Port find_best_fallback_rate from PipeWire backend: queries supported
rates via CPAL, picks highest rate in same family (44.1k or 48k)
that's <= requested. 176.4kHz on a 96kHz-max DAC now falls back to
88.2kHz and rodio resamples, instead of failing entirely.
2026-02-28 07:48:03 -06:00
vicrodh 29343e8149 fix: fall back to CPAL when ALSA Direct doesn't support sample rate
When hardware doesn't natively support a sample rate (e.g. 176.4kHz),
return None from try_create_direct_stream instead of a hard error.
This lets the player fall through to CPAL/rodio which can resample.

Applied to both hw and plughw paths — InvalidParams on rate now
triggers graceful fallback instead of killing playback.
2026-02-28 07:48:03 -06:00
vicrodh fe3c5b0528 fix: suspend PipeWire sink before ALSA Direct to prevent DeviceBusy on first play
When switching from PipeWire to ALSA Direct, PipeWire holds the ALSA
device open causing EBUSY on the first snd_pcm_open. Now detects
DeviceBusy, suspends the PipeWire sink via pactl, waits 200ms for
release, then retries. Applied to both hw and plughw paths.
2026-02-28 07:48:03 -06:00
vicrodh cd4685820e fix: ALSA backend unavailable due to cpal 0.17 HostId::name() case change
cpal 0.15 returned "ALSA" from HostId::name(), cpal 0.17 returns "Alsa"
(now uses stringify! on the enum variant). Use eq_ignore_ascii_case()
for the comparison so it works regardless of cpal version.
2026-02-28 07:48:03 -06:00
vicrodh 7f6ac9bae6 fix: add sample rate fallback when DAC doesn't support requested rate
Query the DAC's supported sample rates from /proc/asound/cardN/stream0
(maps PipeWire sink to ALSA card via pactl). If the track's native rate
isn't supported (e.g., 176.4kHz), fall back to the nearest rate in the
same family (e.g., 88.2kHz). Rodio resamples from track rate to stream
rate automatically, so audio plays instead of producing silence.
2026-02-28 07:48:03 -06:00
vicrodh 5ef58f6121 fix: use with_buffer_size() after with_supported_config() to prevent underruns
rodio 0.22's with_supported_config() resets buffer_size to Default via
..Default::default(), so our BufferSize settings in StreamConfig were
never reaching CPAL. MixerDeviceSink has zero internal buffering, making
CPAL's buffer the ONLY buffer between the mixer and audio hardware.

Chain .with_buffer_size(Fixed(sample_rate/10)) AFTER with_supported_config()
to actually pass ~100ms buffer to CPAL, matching old vendored cpal behavior.
2026-02-28 07:48:03 -06:00
vicrodh b054f51410 fix: revert BufferSize::Fixed for PipeWire, use BufferSize::Default
PipeWire manages its own buffer scheduling via quantum. Forcing a fixed
buffer size (sample_rate / 10 = 19200 frames at 192kHz) conflicts with
PipeWire's internal scheduling and causes buffer underruns that produce
audible noise.

BufferSize::Fixed is kept only for:
- Exclusive mode (512 frames, low latency)
- ALSA backend (direct ALSA, not through PipeWire)
2026-02-28 07:48:03 -06:00
vicrodh 014becfb02 fix: disable PipeWire clock.force-quantum for rodio 0.22 compatibility
rodio 0.22's MixerDeviceSink has an internal mixer thread that cannot
synchronize with PipeWire's forced quantum scheduling. This caused
massive buffer underruns (600+) at sample rates >= 88.2kHz when Force
Bit Perfect was enabled.

clock.force-rate alone is sufficient for bit-perfect sample rate
switching. Logs confirm zero underruns at 88.2kHz and minimal underruns
at 192kHz (only during heavy I/O) when quantum is not forced.
2026-02-28 07:48:03 -06:00
vicrodh 3d8733181a chore: remove unused SampleRate imports
SampleRate is now a u32 type alias in cpal 0.17, so the import is no
longer needed anywhere.
2026-02-28 07:48:03 -06:00
vicrodh 6e8b8410c9 fix: set explicit 100ms buffer size to replace vendored cpal tuning
Replace BufferSize::Default with BufferSize::Fixed(sample_rate / 10)
for non-exclusive mode. This preserves the 100ms period tuning that was
previously applied in the vendored cpal ALSA backend, reducing CPU
wakeups during playback.
2026-02-28 07:48:03 -06:00
vicrodh b5ec5de785 refactor: migrate all code to rodio 0.22 API
- OutputStream/OutputStreamHandle -> DeviceSinkBuilder/MixerDeviceSink
- Sink -> Player (as RodioPlayer)
- current_frame_len -> current_span_len in all Source impls
- Source::channels() returns NonZero<u16>, sample_rate() returns NonZero<u32>
- SamplesBuffer no longer generic, constructor takes NonZero args
- SampleRate is now u32 type alias (no tuple struct)
- Decoder::new_mp4 no longer takes Mp4Type hint
- Applied across both crates/ workspace and src-tauri/ legacy code
2026-02-28 07:48:03 -06:00
vicrodh faf8844584 chore: upgrade rodio 0.19 to 0.22, remove vendored rodio/cpal
Remove vendored rodio and cpal from src-tauri/vendor/ since rodio 0.22
now includes upstream f32 decoder support (our vendored modification).
Update all Cargo.toml files to use rodio 0.22 from crates.io and remove
the [patch.crates-io] section.
2026-02-28 07:48:03 -06:00
vicrodh cbc2ff0b26 feat(audio): add PipeWire force bit-perfect mode
Add experimental toggle to force PipeWire clock.force-quantum alongside
clock.force-rate for true bit-perfect playback. Quantum is matched to
sample rate (1024 for <=48kHz, 2048 for <=96kHz, etc). Both values
reset to 0 on stop and pause-suspend to avoid affecting other apps.

Requires DAC Passthrough to be enabled, PipeWire backend only.
2026-02-19 18:01:42 -06:00
vicrodh ceaf299558 feat(immersive): add spectral ribbon and fix home blacklist filtering
- add Spectral Ribbon analyzer/render path and immersive panel
- tune ribbon density/interpolation and bars edge-bin rendering
- apply artist blacklist filtering to discover index home containers
- include supporting i18n/settings/player wiring changes

cx
2026-02-17 17:28:32 -06:00
vicrodh ee3a50af89 feat(qbz-player): implement Phase 3b - player module
Extract player/mod.rs, playback_engine.rs, and streaming_source.rs to
qbz-player crate. Also adds settings.rs and visualizer module to
qbz-audio (Tauri-specific code removed, core functionality retained).

Changes:
- Add settings.rs to qbz-audio with AudioSettings, AudioSettingsStore
- Add visualizer/ to qbz-audio with RingBuffer, TappedSource, VisualizerTap
- Add player module files to qbz-player
- Update all imports from crate::audio to qbz_audio
- Add serde_json dependency to qbz-audio

Full workspace compiles with cargo check.
2026-02-14 14:18:06 -06:00
vicrodh 3c8fc35fcb feat(qbz-audio): implement Phase 2 - audio backend system
CRITICAL: Copied audio modules from qbz-nix WITHOUT modifying logic.
This code handles bit-perfect playback and is IMMUTABLE.

Copied modules:
- backend.rs: Backend trait and manager
- pipewire_backend.rs: PipeWire/PulseAudio backend
- alsa_backend.rs: ALSA backend with device enumeration
- pulse_backend.rs: PulseAudio fallback
- alsa_direct.rs: Direct ALSA hardware access (hw:X,Y)
- diagnostic.rs: Audio diagnostic tools
- loudness.rs: ReplayGain extraction
- loudness_analyzer.rs: EBU R128 analysis
- loudness_cache.rs: SQLite cache for loudness data
- dynamic_amplify.rs: Dynamic volume amplification
- analyzer_tap.rs: Audio analyzer tap

Dependencies:
- Uses vendored rodio from src-tauri/vendor/rodio
- symphonia-all feature for f32 pipeline
- ebur128 for loudness analysis
- rusqlite for caching

Also updated qbz-models to remove incomplete audio types
(AudioBackendType, AudioDevice) - these stay in qbz-audio.
2026-02-14 13:10:41 -06:00
vicrodh 0911cf75df refactor(backend): Phase 0 - Initialize multi-crate workspace
Create the foundational workspace structure for the multi-crate
architecture refactor. This phase establishes placeholder crates
that will be populated in subsequent phases.

Crates created:
- qbz-models: Shared types, events, and traits (Tier 0)
- qbz-audio: Audio backend system (Tier 1)
- qbz-player: Playback engine and queue (Tier 1)
- qbz-qobuz: Qobuz API client (Tier 1)
- qbz-core: Orchestrator (Tier 3)

All crates compile successfully with placeholder implementations.
The workspace uses shared dependencies via workspace.dependencies.

cc
2026-02-14 12:48:52 -06:00