Commit Graph
75 Commits
Author SHA1 Message Date
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 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