Commit Graph
182 Commits
Author SHA1 Message Date
victorrhgap 016b0f6166 chore: remove unused required_i32_to_u64 function
cc
2026-03-31 11:09:32 -06:00
victorrhgap a99d558e37 fix: update lofty 0.18 API calls to lofty 0.23
- Imports reorganized: use lofty::prelude::*, lofty::tag::*, lofty::picture::*
- Picture::new_unchecked -> Picture::unchecked().pic_type().mime_type().build()
- set_year/remove_year -> set_date/remove_date with Timestamp
- ItemKey::Unknown("ISRC") -> ItemKey::Isrc
- save_to_path(path) -> save_to_path(path, WriteOptions::default())
- get_string/remove_key now take ItemKey by value
- Bump qbz-library crate lofty dep to 0.23

cc
2026-03-31 09:32:02 -06:00
Afonso Jorge Ramos 6bf397e0f3 feat(macos): auto-select SystemDefault backend on non-Linux
When backend_type is None (auto-detect), default to SystemDefault on
non-Linux platforms so macOS gets CoreAudio device probing and sample
rate switching instead of falling through to the legacy CPAL path.
2026-03-31 02:20:13 +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
victorrhgap 235bd08e2c fix(qconnect): tolerate missing queue_version major/minor fields
Qobuz changed their protocol to sometimes send QueueVersionRef
without major/minor populated. Our decoder treated these as
required fields, causing all cloud_type=6 messages to fail with
"missing required numeric field 'queue_version.major'".

Default to 0 when absent — matches the dev panel behavior.

cc
2026-03-24 21:14:44 -06: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 1ad16338fa feat: add Qobuz playlist follow/unfollow (#172)
Discovered undocumented Qobuz API endpoints from bundle analysis:
- POST /playlist/subscribe { playlist_id }
- POST /playlist/unsubscribe { playlist_id }

This is the native "follow playlist" feature that syncs across all
Qobuz clients, separate from QBZ's existing "Copy to Library" (local
copy) and "Favorite" (local flag) features.

Added:
- subscribe_playlist/unsubscribe_playlist in both qbz-qobuz and legacy client
- v2_qobuz_subscribe_playlist / v2_qobuz_unsubscribe_playlist commands
- Bookmark toggle button in PlaylistDetailView (next to Copy button)
- i18n keys in all 5 locales

cc
2026-03-19 11:36:56 -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 3b3dbc7ce1 fix(qconnect): trigger resync on reorder and remove server events
SrvrCtrlQueueTracksReordered and SrvrCtrlQueueTracksRemoved were not
triggering AskForQueueState, so the local queue never reflected
reorder/remove actions performed via QConnect. Same pattern as the
existing shuffle resync.

cc
2026-03-14 21:34:03 -06:00
vicrodh 52e3653dc3 fix(qconnect): complete repeat pending on loop ack
cx
2026-03-14 13:25:22 -06:00
vicrodh a59daf43dc fix(qconnect): resync queue after shuffle mode changes
cx
2026-03-13 23:13:16 -06:00
vicrodh cf2447f4b2 fix(qconnect): stop deriving remote shuffle locally
cx
2026-03-13 23:07:22 -06:00
vicrodh af7652c86b fix(qconnect): wait for authoritative shuffle queue
cx
2026-03-13 22:54:27 -06:00
vicrodh 1fc7a0a1a3 fix(qconnect): materialize remote shuffle atomically
cx
2026-03-13 20:43:56 -06:00
vicrodh 09c6d8f1f8 fix(qconnect): route shuffle and repeat through runtime
cx
2026-03-13 20:28:11 -06:00
vicrodh f18bd10aa4 fix(qconnect): complete active-renderer pending on session ack
cx
2026-03-13 18:50:04 -06:00
vicrodh de29f1e2e0 chore(qconnect): checkpoint current poc state
cx
2026-03-13 17:28:05 -06:00
vicrodh fcadb85e65 fix(qconnect): unblock rapid transport controls and stop local audio on handoff
cx
2026-03-13 13:19:53 -06:00
vicrodh 4098c18d89 fix(qconnect): clear pending active-renderer action on ack
cx
2026-03-13 11:08:28 -06:00
vicrodh c83aedac75 fix(qconnect): align renderer state with official queue semantics
cx
2026-03-12 23:10:31 -06:00
vicrodh e748ab6616 fix(qconnect): treat omitted cloud queue ids as zero
cx
2026-03-12 20:56:56 -06:00
vicrodh d3c2d4c6ca fix(qconnect): align queue load payload and inbound diagnostics
cx
2026-03-12 19:31:27 -06:00
vicrodh 441b74ac91 test(qconnect): lock seekbar workaround behavior
cx
2026-03-12 15:32:49 -06:00
vicrodh 02155f54fe merge(pre-release): sync qconnect-poc-v3 with latest local integration state
cx
2026-03-12 15:23:32 -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 c94dad9f33 fix(library): album names with Disc/CD suffix misidentified as disc folders (#147)
is_disc_folder() was matching any folder name containing "Disc N" or
"CDN" anywhere in the name. This caused albums like "100 Popular
Classics, Disc 1" or "Now 75 - CD1" under Various Artists to be treated
as disc subfolders, collapsing all tracks into a single album group.

Now is_disc_folder() only returns true when the folder name consists
exclusively of disc-related tokens (disc/cd/disk, digits, and modifiers
like "bonus"). Album names with extra words are correctly left as-is.

Users must re-scan their library for the fix to take effect.
2026-03-12 10:30:58 -06:00
vicrodh d7fd24b2e9 fix: add quality fallback when CDN fails at current quality level
When download fails after retry at the same quality (e.g. HiRes),
automatically try the next lower quality (e.g. Lossless). This handles
CDN edges that consistently serve broken responses for specific tracks
at certain quality levels. Applied to all three download paths: standard
download, streaming probe fallback, and prefetch. Also fixes prefetch
retry using wrong quality variable.
2026-03-12 09:15:57 -06:00
vicrodh d02090f30d fix: prevent ALSA device lock on streaming failure, add Drop for PlaybackEngine
Add Drop impl to PlaybackEngine so the ALSA Direct writer thread is
properly stopped when the engine is dropped (e.g. streaming timeout).
Previously, a failed streaming attempt would orphan the writer thread,
keeping the ALSA device locked and blocking all subsequent playback.

Also force HTTP/1.1 for audio downloads and add detailed error chain
logging to diagnose persistent "error decoding response body" failures
for specific tracks.
2026-03-11 20:15:11 -06:00
vicrodh c7f5dcf2f4 feat: true gapless playback for ALSA Direct mode
Refactor ALSA Direct engine from thread-per-source to a single
long-lived writer thread with a source queue. When one source ends,
the writer thread immediately picks up the next from the queue with
zero gap. Remove the ALSA Direct gapless rejection gate since the
engine now natively supports source queueing.
2026-03-11 19:43:47 -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 c7ab0ba129 fix: track number display for compilations and box sets (#147)
- Add filename-based track number inference as fallback when tags lack track_number
- Detect degenerate track numbering (all tracks same number) and use index fallback
- Flatten single-track-per-disc box sets into sequential numbering
- Replace fragile SELECT * with explicit column list in track queries
2026-03-10 15:08:10 -06:00
vicrodh f473065c21 feat: port full MusicBrainz API to V2 crate + add cache persistence
V2 MusicBrainz client now has all methods from legacy:
- search_recording, get_artist_with_relations, get_artist_tags
- search_artists_by_tag, search_artists_by_tag_and_area
- search_release_by_barcode, search_releases_extended
- get_release_with_tracks, browse_artists_by_area
- search_area, get_area_with_relations, resolve_parent_subdivision

V2 MusicBrainz cache now has:
- Settings persistence (is_enabled/set_enabled)
- All legacy cache tables (relations, metadata, scene, qobuz_validation)
- clear_all, cleanup_expired, get_stats

V2 ListenBrainz cache adds:
- mark_listens_sent (batch), get_queue_count, clear_queue

V2 state wrappers (integrations_v2.rs) now include:
- Cache field on both MusicBrainzV2State and ListenBrainzV2State
- init_cache_at, init_from_cache, teardown methods
- ListenBrainzV2State credentials auto-persist to V2 cache

V2 models include all types from legacy (ArtistRelationships,
ArtistMetadata, LocationDiscoveryResponse, ResolvedMusician, etc.)
2026-03-10 14:14:45 -06:00
vicrodh 572d23b935 feat: replace ListenBrainz co-listening with MusicBrainz tag-based discovery
ListenBrainz similar-artists returned mainstream crossover artists (SOAD,
Led Zeppelin for Metallica) instead of genre-accurate results. Now uses
MusicBrainz tag search to find artists sharing the seed's primary genre tag
(e.g. "thrash metal" returns Megadeth, Slayer, Kreator, etc).

- Add search_artists_by_tag to MusicBrainz client
- Deterministic shuffle using seed MBID hash for variety across artists
- Secondary tag fallback when primary yields fewer than 5 results
- Rename label to "You May Also Like", limit results to 6
- Add Last.fm get_similar_artists method (unused, for future use)
- Add tags field to ArtistFullResponse model
- Tighten local history exclusion to play_count > 2
2026-03-08 21:30:01 -06:00
vicrodh 786ce883ac fix(library): extract track/disc number from file tags during backfill import
insert_qobuz_cached_track_direct was hardcoding track_number to 0,
causing all imported tracks to show wrong numbers. Now accepts
track_number and disc_number params, and both callers (backfill
downloads and offline cache sync) extract metadata from the audio
file via MetadataExtractor before inserting.

Also fix LocalLibraryView to use || instead of ?? for track_number
fallback, so 0 correctly falls back to index-based numbering.

Closes #147
2026-03-06 11:08:32 -06:00
vicrodh 5b9f5a75c2 feat(ui): add explicit content badge across entire app
Display parental_warning badge (E icon) next to track titles in all
views: TrackRow, QueuePanel, NowPlayingBar, ImmersivePlayer with all
13 visualizer panels, MiniPlayer surfaces, and AlbumDetailView.

Propagate parental_warning field through the full data chain from
Rust backend (qbz-models, queue, commands_v2, session_store, API
server) to frontend stores, adapters, and UI components. Badge uses
CSS mask technique with explicit.svg for theme compatibility.
2026-03-06 08:48:51 -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 87321a0ea6 fix(library): detect qobuz_purchase source on scan and clean stale downloads
insert_track() now checks downloaded_purchases table to set source.
get_downloaded_purchase_track_ids() verifies file existence and
auto-removes stale entries. Closes #115
2026-03-05 11:13:59 -06:00
vicrodh ec3ecb35b2 fix(flatpak): restore pipewire stream matching and tray icon access
cx
2026-03-01 20:18:17 -06:00
vicrodh 683f97a5d5 fix: deduplicate albums with quality subfolders in local library
The library scanner's album_root_dir() only knew to skip disc folders
(Disc 1, CD2, etc.) when resolving the album directory. Files stored
in quality subfolders like artist/album/FLAC 24-bit - 96 kHz/track.flac
caused each encoding variant to appear as a separate album card.

Add is_encoding_folder() to detect common audio format directory names
(FLAC, MP3, AAC, WAV, etc.) and make album_root_dir() skip past both
encoding and disc subdirectories, handling chained nesting too.

Fixes the workaround from #116.
2026-03-01 15:40:22 -06:00
vicrodh 8e104a3f3e fix(audio): replace deprecated CPAL name() usage with description()
cx
2026-03-01 10:44:54 -06:00
vicrodh c17c01b02b fix(security): remove unused qobuz user_id accessor
cx
2026-03-01 09:49:57 -06:00
vicrodh 133defbafa fix(security): redact sensitive identifiers and secrets from backend logs
cx
2026-03-01 09:24:42 -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