Commit Graph
466 Commits
Author SHA1 Message Date
vicrodh ce72addd90 Fix database migration: remove alsa_hardware_volume from INSERT
User's existing database doesn't have the column yet, so INSERT fails.
Migration adds column after INSERT runs. Solution: only insert old columns.
2026-01-19 04:01:29 -06:00
vicrodh 61688bd1ea Integrate hardware volume control in PlaybackEngine
- Add hardware_volume parameter to PlaybackEngine::new_alsa_direct()
- Update all three call sites in player.rs to pass alsa_hardware_volume setting
- Implement conditional volume control: hardware mixer or DAC/amplifier
- Add device_id() getter to AlsaDirectStream for mixer access
- Hardware volume disabled by default for maximum compatibility
2026-01-19 03:59:28 -06:00
vicrodh c87b41894d Implement ALSA hardware mixer volume control
Added set_hardware_volume() to AlsaDirectStream:
- Tries common mixer control names (Master, PCM, Speaker, Headphone, Digital)
- Returns error if DAC doesn't support mixer (doesn't break playback)
- Stores device_id in struct for mixer access

This enables bit-perfect volume control via DAC hardware when supported.
Falls back gracefully if not supported.
2026-01-19 03:53:41 -06:00
vicrodh 31798432cb Fix database migration for alsa_hardware_volume column
PROBLEM: App crashed on startup with existing databases:
'table audio_settings has no column named alsa_hardware_volume'

CAUSE: ALTER TABLE with 'NOT NULL' constraint fails on SQLite migrations.

FIX:
- Removed NOT NULL from ALTER TABLE statement
- Handle NULL values with unwrap_or(0) in get_settings()
- Defaults to false (disabled) for old databases

Now works with both new and existing databases.
2026-01-19 03:52:01 -06:00
vicrodh 7177f248c9 Add alsa_hardware_volume setting (disabled by default)
Preparation for hybrid volume control (B+C option):
- New field: alsa_hardware_volume: bool (default: false)
- Database schema updated with migration support
- Setter/getter methods added

When enabled, will attempt to control DAC volume via ALSA mixer.
When disabled (default), in-app volume control is disabled for maximum
compatibility (user controls volume on DAC/amplifier).
2026-01-19 03:50:02 -06:00
vicrodh c7a3737fb5 Implement automatic format detection for ALSA Direct
PROBLEM: Hardcoding S32_LE only worked for Cambridge Audio DAC, not generic.

SOLUTION: Auto-detect best format at runtime:
- Try formats in priority order: FloatLE > S32LE > S24LE > S16LE
- Store detected format in AlsaDirectStream struct
- write() auto-converts from i16 based on detected format:
  * FloatLE: i16 -> f32 (normalize to -1.0..1.0)
  * S32LE: i16 -> i32 (bit-perfect: shift left 16 bits)
  * S16LE: i16 -> i16 (no conversion)

Now works with any DAC regardless of supported formats.
2026-01-19 03:38:28 -06:00
vicrodh dc9c7d0424 Fix format compatibility: use S32_LE instead of FloatLE
PROBLEM: Cambridge Audio DAC only supports S32_LE format, not FloatLE.
Previous code failed with 'Invalid argument (22)' when trying to set FloatLE.

SOLUTION:
- alsa_direct.rs: Changed format from FloatLE to S32_LE
- alsa_direct.rs: Changed write() to accept i32 samples instead of f32
- playback_engine.rs: Convert i16 to i32 using bit-perfect left shift:
  i32 = (i16 as i32) << 16

This maintains full bit-perfect precision while using hardware-supported format.
2026-01-19 03:35:43 -06:00
vicrodh dd03bcd09e Fix ALSA Direct streaming: process chunks instead of buffering entire file
PROBLEM: Original code tried to collect() entire audio source into memory
before playback, causing 600+ MB allocations for 192kHz tracks and silent
failures.

SOLUTION: Stream samples in 8192-frame chunks:
- Read chunk from source iterator
- Convert i16 to f32
- Write to ALSA immediately
- Repeat until end of stream

This reduces memory usage from 600+ MB to ~300 KB per chunk and fixes
the no-audio issue.
2026-01-19 03:28:42 -06:00
vicrodh 192b7e75e2 Complete PlaybackEngine integration with hybrid Rodio/ALSA Direct support
- Replace Sink with PlaybackEngine abstraction throughout player
- Update StreamType to use Arc<AlsaDirectStream> for sharing
- Adapt all player commands (Play, Pause, Resume, Seek, Stop, SetVolume, ReinitDevice)
- Add as_any() trait method for backend downcasting
- Fix all type mismatches and compilation errors
- Compilation successful with 0 errors, 30 warnings
2026-01-19 03:14:27 -06:00
vicrodh 63d3e8c043 WIP: Add PlaybackEngine abstraction and StreamType enum
- Create playback_engine.rs module wrapping Sink and AlsaDirectStream
- Add StreamType enum (Rodio or AlsaDirect)
- Modify try_init_stream_with_backend() to detect hw: devices
- Update player thread types (stream_opt, current_engine)

Next: Complete player.rs integration (extensive changes needed)
Status: Does not compile yet - work in progress
2026-01-19 02:51:48 -06:00
vicrodh f88de797ad Implement direct ALSA access with alsa-rs for hw: devices
- Create AlsaDirectStream module for bit-perfect PCM access
- Bypasses CPAL/rodio for hw:X,Y and plughw:X,Y devices
- Add try_create_direct_stream() method to AlsaBackend
- Configures optimal buffer sizes for high-res audio (500ms for 192kHz)
- Handles PCM errors with automatic recovery

Next: Integrate with player audio thread
2026-01-19 02:44:31 -06:00
vicrodh 902705f47f fix(phase4): add icon_background column migration for existing databases
- Detect missing icon_background column in favorites_preferences table
- Add ALTER TABLE migration to preserve existing user data
- Apply migration in both FavoritesPreferencesStore::new() and create_table()

cx
2026-01-18 15:42:11 -06:00
vicrodh 4009ae9b26 feat(phase4.3): add background color picker to favorites modal
- Add icon_background field to backend (Rust + TS types)
- Update modal to 60% wider (1150px)
- Implement two-column layout (icon left, tabs right)
- Add solid color picker (8 colors + none)
- Add gradient picker (6 gradients)
- Remove background from edit button to match PlaylistView style
- Update database migration for icon_background field

cp
2026-01-18 14:13:50 -06:00
vicrodh 9c76792741 Add alsa crate for direct ALSA hardware access
- Add alsa 0.9 as Linux-only dependency
- Will replace CPAL-based ALSA backend with direct alsa-rs implementation
- Required to properly access hw:X,Y devices for bit-perfect playback
2026-01-18 14:02:43 -06:00
vicrodh a6aa6aa936 feat(phase4.2): add favorites preferences backend
- Create favorites_preferences.rs with FavoritesPreferences struct
- Add database table (custom_icon_path, custom_icon_preset, tab_order)
- Implement State wrapper and Tauri commands
- Register state and commands in lib.rs
- Default preset: heart, default order: tracks/albums/artists/playlists

cc
2026-01-18 13:57:17 -06:00
vicrodh c2628b0076 fix(config): align autoplay mode serialization
cx
2026-01-18 05:32:49 -06:00
vicrodh fdf288b01e fix(migration): robust database migration for show_context_icon
Problem:
- Users updating from previous versions would crash on startup
- Table created with new column caused "no column named show_context_icon" error
- Existing users had old schema without the column

Solution:
- Step 1: Create table with old schema (backwards compatible)
- Step 2: Check if show_context_icon column exists using pragma_table_info
- Step 3: Add column ONLY if it doesn't exist (safe migration)
- Step 4: Insert default row

This ensures:
✅ New installs work (table created fresh)
✅ Existing users migrate seamlessly (column added, settings preserved)
✅ No data loss
✅ Idempotent (can run multiple times safely)

Tested scenarios:
- Fresh install (no DB) → creates table, adds column, inserts defaults
  Result: id=1, autoplay_mode='continue', show_context_icon=1
- Existing DB without column → adds column, preserves user settings
  Result: id=1, autoplay_mode='track_only' (preserved), show_context_icon=1
- Existing DB with column → no changes (idempotent)

Migration logs added for debugging.

cp
2026-01-18 03:57:28 -06:00
vicrodh dfd1eb588d feat(phase36): add show_context_icon setting (backend)
Database:
- Add show_context_icon column (INTEGER, default 1)
- Migration for existing databases

Backend:
- Add show_context_icon to PlaybackPreferences struct
- Default value: true (icon visible)
- Add set_show_context_icon method to store/state
- Add Tauri command: set_show_context_icon

Context tracking remains ALWAYS active regardless of icon visibility.

cp
2026-01-18 03:38:14 -06:00
vicrodh dbdd664415 feat(settings): add global autoplay mode preference (backend)
- Create PlaybackPreferences config module with SQLite persistence
- Add AutoplayMode enum (ContinueWithinSource | PlayTrackOnly)
- Register Tauri commands: get_playback_preferences, set_autoplay_mode
- Create playbackPreferencesStore for frontend state management
- Add loadPlaybackPreferences to SettingsView
- Default: Continue within source (creates context on track click)
- Settings UI pending (next commit)

cp
2026-01-18 00:49:45 -06:00
vicrodh 49fe9e6b11 feat(player): implement playback context system
- Add PlaybackContext module with ContextManager
- Context tracks semantic origin (album, playlist, artist top, etc.)
- Separate from queue - defines what 'next' means
- Backend: Rust struct with track IDs and position tracking
- Frontend: Svelte store with reactive subscriptions
- Tauri commands for context CRUD operations
- Context is nullable (single track playback has no context)

cp
2026-01-18 00:26:54 -06:00
vicrodh b224538088 Manually create hw: and plughw: devices - CPAL doesn't enumerate them
Critical Fix:
- CPAL only enumerates named ALSA devices (front:, sysdefault:, etc.)
- CPAL does NOT enumerate hw:X,Y or plughw:X,Y devices
- These must be created manually from aplay -l card list

Solution:
- Parse aplay -l to get card numbers and descriptions
- Manually create hw:X,0 and plughw:X,0 for each card
- Assume max_sample_rate: 384000 (devices are capable)
- Description: "Cambridge Audio USB Audio 2.0 (Direct Hardware - Bit-perfect)"

For user's system (based on aplay -l):
- hw:0,0 → CS201 (Direct Hardware - Bit-perfect)
- hw:2,0 → HDA NVidia (Direct Hardware - Bit-perfect)
- hw:3,0 → sof-hda-dsp (Direct Hardware - Bit-perfect)
- hw:4,0 → Cambridge Audio USB Audio 2.0 (Direct Hardware - Bit-perfect) ← USER NEEDS THIS
- Plus plughw: variants for each

These devices will now appear in the dropdown and can be selected for
true bit-perfect playback without resampling.
2026-01-17 21:29:17 -06:00
vicrodh 39a661417d Add hw: and plughw: devices for true bit-perfect playback
ALSA Backend Changes:
- Include hw:X,Y and plughw:X,Y devices from CPAL enumeration
- These devices bypass ALL ALSA plugins (dmix, resampling, etc.)
- Parse aplay -l to build card number → description map
- hw: devices get friendly names: "Cambridge Audio USB Audio 2.0 (Direct Hardware - Bit-perfect)"

Why This Matters:
- front:CARD=C20,DEV=0 → Has dmix plugin → resamples to 44.1kHz
- iec958:CARD=C20,DEV=0 → Has plugins → resamples to 44.1kHz
- hw:4,0 → Direct hardware access → TRUE bit-perfect → 96kHz playback

Device Description Examples:
- hw:4,0 → "Cambridge Audio USB Audio 2.0 (Direct Hardware - Bit-perfect)"
- plughw:4,0 → "Cambridge Audio USB Audio 2.0 (Plugin Hardware)"

Blacklist Updates:
- Filter surround*, usbstream, pipewire, pulse, bare sysdefault
- Keep hw:, plughw:, default, sysdefault:CARD=, front:CARD=, hdmi:CARD=, iec958:CARD=

User should now select hw:4,0 device for true bit-perfect playback that
changes sample rate to match the track (96kHz, 192kHz, etc.)
2026-01-17 21:21:01 -06:00
vicrodh 845d53c672 Revert to CPAL enumeration + aplay -L for descriptions only
Critical Fix - Use CPAL Device Names:
- CPAL enumerates devices → these IDs actually work for playback
- aplay -L provides friendly descriptions → update CPAL device descriptions
- Stop creating hw:X,Y devices that CPAL doesn't recognize
- Fixes: "Device not found → fallback to default → audio from laptop"

Flow:
1. enumerate_via_cpal() returns devices CPAL can actually use
2. enumerate_via_aplay() calls #1, then parses aplay -L for descriptions
3. Updates device.description with friendly names from aplay -L
4. Frontend shows: "Cambridge Audio USB Audio 2.0" not "front:CARD=C20,DEV=0"

Loading Overlay:
- Full-screen semi-transparent overlay during device enumeration
- Animated spinner (Loader2 icon)
- "Loading audio devices... / Parsing hardware information"
- Prevents user thinking app is frozen during 4-5 second aplay parse

For Bit-Perfect Playback:
- User should select devices like front:CARD=C20,DEV=0 (CPAL knows these)
- These are more direct than sysdefault:CARD= (which has dmix/plugins)
- Not as direct as hw:4,0 but CPAL can actually find them

PipeWire and PulseAudio backends unchanged - still working as before.
2026-01-17 21:12:18 -06:00
vicrodh 63b6809259 Fix hw: device format - use card numbers not names
Critical Fix:
- Changed hw: devices from hw:CARD=C20,DEV=0 to hw:4,0
- CPAL only accepts numeric format (hw:X,Y), not named format
- Parse aplay -l to get actual card numbers and device numbers

Before: hw:CARD=C20,DEV=0 → Device not found → fallback to default
After:  hw:4,0 → CPAL finds device → bit-perfect playback works

Example from user's system:
- aplay -l shows: card 4: C20 [Cambridge Audio USB Audio 2.0], device 0
- We now create: hw:4,0 (not hw:CARD=C20,DEV=0)
- Description still shows: "Cambridge Audio USB Audio 2.0 (Direct Hardware - Bit-perfect)"

This fixes the issue where selecting the DAC device would play through
laptop speakers instead because CPAL couldn't find the device.
2026-01-17 21:07:18 -06:00
vicrodh 8f3369a583 Filter ALSA devices, add hw: bit-perfect devices, and fix UI issues
ALSA Device Filtering (45 → ~12 devices):
- Aggressively filter to show only useful devices for audiophiles
- Keep: default, sysdefault:CARD=, front:CARD=, hdmi:CARD=, iec958:CARD=
- Blacklist: surround* variants, usbstream:, pipewire, pulse, bare sysdefault
- Reduces 45 listed devices to ~12 actually useful ones

Add hw:CARD=XXX,DEV=0 Devices for Bit-Perfect Playback:
- Auto-generate hw: devices for each detected card
- hw: = direct hardware access without ALSA plugins (true bit-perfect)
- sysdefault:CARD= has dmix/resampling plugins (NOT bit-perfect)
- Description shows "(Direct Hardware - Bit-perfect)" to distinguish
- This fixes user's issue: DAC was stuck at 44.1kHz with sysdefault:CARD=C20

Loading Indicator:
- Show "Loading devices..." while parsing aplay -L output
- Fixes 4-5 second frozen UI on ALSA Direct backend selection
- isLoadingDevices state added to SettingsView

Stop Playback on Device Change:
- Call stop_playback() in handleBackendDeviceChange
- Prevents stuck/dead audio streams when changing output device
- Fixes: "After any config change, song in player stops working"

User can now:
1. Select hw:CARD=C20,DEV=0 for true bit-perfect to DAC
2. See loading indicator instead of frozen UI
3. Change device without breaking playback
4. Only see relevant devices (not 7 surround variants per card)
2026-01-17 21:02:03 -06:00
vicrodh 037b8569d6 Add fallback to CPAL enumeration if aplay -L fails
ALSA Backend Fallback:
- enumerate_via_aplay() - Preferred method with real hardware descriptions
- enumerate_via_cpal() - Fallback without descriptions (frontend will translate)
- enumerate_alsa_devices() tries aplay first, falls back to CPAL on failure
- Warning log suggests installing alsa-utils if aplay fails

Edge Case Handled:
- System has ALSA installed but not alsa-utils package
- CPAL detects ALSA host → AlsaBackend available
- aplay -L command fails → fallback to CPAL enumeration
- User sees translated names instead of real hardware descriptions
- App still works, just with less friendly device names

Package Dependencies:
- Added alsa-utils as "Recommends" for .deb packages (Debian/Ubuntu)
- Added alsa-utils as "Recommends" for .rpm packages (Fedora/RHEL)
- "Recommends" = installs by default but not required (apt/dnf behavior)
- If user explicitly removes alsa-utils, app falls back gracefully

In practice alsa-utils is almost always installed with ALSA, but this
ensures we handle the edge case properly.
2026-01-17 20:00:13 -06:00
vicrodh c871f6becd Use aplay -L for real ALSA device descriptions instead of hardcoding
ALSA Backend Changes:
- Parse aplay -L output to get real hardware descriptions
- Description from hardware: "Cambridge Audio Cambridge Audio USB Audio 2.0, USB Audio"
- No more hardcoded CARD_NAMES for specific hardware
- Still uses CPAL to verify device exists and get max sample rate
- Blacklist filtering preserved (null, lavrate, samplerate, etc.)

Frontend Changes:
- Prefer device.description (from aplay -L) when available
- Falls back to getDevicePrettyName() translation only if no description
- ALSA Direct shows: "Cambridge Audio ... USB Audio, Front output / input"
- Not: "Cambridge Audio USB Front Output" (hardcoded translation)

Removed Hardcoding:
- Removed CS201 → "Cambridge Audio USB" (was specific to user's DAC)
- Removed C20 → "Cambridge Audio"
- Generic CARD_NAMES remain (NVidia, Intel HD Audio, etc.)

This makes device names work for ANY hardware, not just known cards.
Each backend should use native tools:
- ALSA: aplay -L
- PipeWire: pw-cli/pactl (future)
- PulseAudio: pactl (future)
2026-01-17 19:55:22 -06:00
vicrodh 4486d1dd80 Fix ALSA device enumeration and Auto backend translation
Problem 1: ALSA Direct showed NO devices because aplay -L output doesn't
match CPAL device names, causing create_output_stream to fail.

Solution: Revert to CPAL enumeration but with blacklist filtering to skip
non-playback devices (null, lavrate, samplerate, speexrate, jack, oss,
speex, upmix, vdownmix).

Problem 2: Auto backend didn't apply fancy names because needsTranslation()
was too restrictive and didn't cover all ALSA device name patterns.

Solution: Simplify to: if device name starts with 'alsa_output.' it's from
PipeWire (already has friendly name), otherwise it's ALSA (needs translation).
This covers ALL ALSA naming patterns including front:CARD=, sysdefault:CARD=,
hw:, hdmi:, iec958:, etc.

Changes:
- alsa_backend.rs: Back to CPAL with blacklist filter
- SettingsView.svelte: Simplified needsTranslation() logic
2026-01-17 18:24:18 -06:00
vicrodh 01022f2eea Fix ALSA device filtering and quality badge hardware sample rate
Problem 1: ALSA backend showed only 1 device or listed plugins/virtual devices
that don't work for playback (null, lavrate, samplerate, etc.).

Solution: Filter aplay -L output to only include actual playback devices:
- Hardware devices (hw:, plughw:, sysdefault:CARD=, front:CARD=)
- System defaults (default, pipewire, pulse)
- Digital outputs (hdmi:, iec958:, surround)

Problem 2: Quality badge showed file sample rate (e.g., 44.1kHz) instead of
actual hardware sample rate (e.g., 192kHz) when DAC Passthrough was active.

Solution: ExpandedPlayer now polls hardware_sample_rate every second when
DAC Passthrough is enabled and displays the real hardware rate instead of
the file metadata rate.

Changes:
- alsa_backend.rs: Add is_valid_device() filter for playback devices
- ExpandedPlayer.svelte: Poll get_hardware_audio_status every 1s
- ExpandedPlayer.svelte: Use displaySamplingRate (hardware rate when DAC on)

This ensures the quality badge accurately reflects bit-perfect playback status.
2026-01-17 18:08:18 -06:00
vicrodh f0c0e00e7d Fix ALSA device enumeration and UI translation logic
Problem 1: ALSA backend only showed CPAL hardware devices, missing virtual
devices like 'default', 'plughw', USB DACs, etc.

Solution: Use 'aplay -L' to enumerate ALL ALSA devices including:
- Hardware devices (hw:X,Y)
- Virtual devices (default, plughw, dmix)
- USB audio devices
- Network audio devices

Problem 2: UI was applying getDevicePrettyName() to all backends, causing
PipeWire's already-friendly names to be translated incorrectly.

Solution: Only apply translator for ALSA/Auto backends. PipeWire names
pass through unchanged since pactl already provides friendly descriptions.

Changes:
- ALSA backend: Use aplay -L instead of CPAL enumeration
- Frontend: Conditional translation based on selected backend
- Map deviceByDisplayName for correct reverse lookup
- Load settings with correct translation logic

This fixes both issues: ALSA now shows all devices including DACs,
and PipeWire names are no longer mangled.
2026-01-17 17:54:51 -06:00
vicrodh d530b13497 Fix playlist import after API keys refactor merge
Remove unused ProviderCredentials parameters from playlist import functions.
The proxy now handles all provider credentials, making these parameters
obsolete.

Changes:
- preview_public_playlist: Remove spotify_creds and tidal_creds params
- import_public_playlist: Remove spotify_creds and tidal_creds params
- Update all callers to remove None, None arguments
- Remove unused ProviderCredentials import

This fixes compilation error:
error[E0061]: this function takes 1 argument but 3 arguments were supplied
2026-01-17 17:42:49 -06:00
vicrodh fccc7f781d Merge branch 'feature/api-keys-refactor' 2026-01-17 17:35:19 -06:00
vicrodh 89126446bb Implement ALSA Direct backend and smart toggles for incompatible features
ALSA Direct Backend Implementation:
- Fully functional ALSA backend using CPAL's ALSA host
- Enumerates ALSA devices with hardware names
- Supports custom sample rates for bit-perfect playback
- Supports exclusive mode (device blocking)
- Smaller buffer size (512 samples) for lower latency in exclusive mode
- Clear error messages when device is in use

Smart Toggles in UI:
- Exclusive Mode auto-disabled with PipeWire/Auto backends
  (PipeWire is multiplexed and cannot provide true exclusive access)
- Gapless Playback auto-disabled with DAC Passthrough
  (bit-perfect requires recreating streams for sample rate changes)
- Toggle component now supports 'disabled' prop with visual feedback
- Tooltips updated to explain why features are disabled
- Auto-disable exclusive mode when switching to PipeWire backend

User Benefits:
- ALSA Direct now appears in backend dropdown
- True exclusive mode available with ALSA Direct
- Prevents confusing feature combinations
- Clear explanations via tooltips
- No more wondering why exclusive mode doesn't work with PipeWire

Technical Notes:
- ALSA backend uses CPAL host selection (not alsa-rs directly)
- Disabled toggles shown as grayed out with cursor: not-allowed
- Backend switching auto-adjusts incompatible settings
2026-01-17 17:21:23 -06:00
vicrodh 30e8fef7a4 Force PipeWire sample rate via pw-metadata for bit-perfect playback
Problem: PipeWire was resampling 192kHz streams to 48kHz because the sink
was running at 48kHz. Creating a stream at 192kHz wasn't enough - PipeWire
would resample it down to match the sink's current rate.

Solution: Use pw-metadata to force PipeWire's global sample rate before
creating the stream:

  pw-metadata -n settings 0 clock.force-rate 192000

This tells PipeWire to switch the sink to 192kHz, preventing resampling.

Technical details:
- Executes AFTER setting default sink
- Waits 300ms for PipeWire to apply the change
- Creates stream AFTER sample rate is forced
- Each new stream updates the forced rate (supports rate switching)

This ensures:
- 192kHz track -> 192kHz hardware (no resampling)
- 44.1kHz track -> 44.1kHz hardware (no resampling)
- True bit-perfect playback

Evidence: pw-top now shows ALSA sink running at requested sample rate
2026-01-17 17:12:24 -06:00
vicrodh cac5a0c173 Remove unused API Keys UI and infrastructure
All API requests now go through Cloudflare Workers proxy.
User-provided credentials were collected but never used.

Removed:
- src-tauri/src/api_keys.rs (entire module)
- API Keys section from Settings UI
- ApiKeysState and related commands
- User credential parameters from providers
- Simplified DiscogsClient (removed with_user_credentials)
- Cleaned up imports and translations

Moved documentation:
- CHANGELOG-api-keys-refactor.md
- HANDOFF-MERGE.md
- MERGE-PREP.md
- POST-MERGE-CLEANUP.md
Moved to: qbz-nix-docs/feature-api-keys-refactor/

This cleanup reduces codebase by ~715 lines and removes
confusing UI that promised functionality that didn't work.
2026-01-17 17:09:49 -06:00
vicrodh 9516a1c1fa Merge feature/api-keys-refactor: Remove embedded API keys and add Discogs artwork selector
This major refactor achieves Flathub compliance by removing all embedded
API credentials from the binary. All API requests now go through a
Cloudflare Workers proxy for secure credential management.

New Features:
- Discogs artwork selection UI with multi-release support
- Catalog number support for audiophile libraries
- Carousel navigation for artwork options
- Release metadata display (title, year)

Technical Changes:
- Cloudflare Workers proxy for Last.fm, Spotify, Tidal, Discogs
- Database migration adds catalog_number column
- URI scheme registration (qbz://) for OAuth flows
- Rate limiting and User-Agent validation server-side

See CHANGELOG-api-keys-refactor.md for complete details.
See POST-MERGE-CLEANUP.md for removing legacy API Keys UI.
2026-01-17 16:48:40 -06:00
vicrodh e934894b6e Fix PipeWire backend to respect requested sample rate
PipeWire backend was using generic OutputStream::try_from_device() which
lets PipeWire decide the sample rate, causing resampling even when
DAC Passthrough is enabled.

Now uses OutputStream::try_from_device_config() with custom StreamConfig
that forces the exact sample rate requested (e.g. 192kHz), just like
the legacy CPAL path.

Changes:
- Import CPAL config types (StreamConfig, SampleRate, etc.)
- Create StreamConfig with requested sample_rate and channels
- Use try_from_device_config() instead of try_from_device()
- Verify device supports the configuration
- Support exclusive mode with lower buffer size (512 samples)

This ensures bit-perfect playback:
- 192kHz track -> 192kHz hardware output
- 44.1kHz track -> 44.1kHz hardware output
- No resampling by PipeWire
2026-01-17 16:25:40 -06:00
vicrodh b3f5c43031 Fix DAC Passthrough with backend system
Previously, init_device() used hardcoded 48000Hz when reinitializing
streams with backend system. This broke bit-perfect playback after
pause/resume or device changes.

Now init_device() accepts sample_rate and channels parameters and uses
the last known values from current_sample_rate/current_channels to
maintain the correct format across reinitializations.

This ensures DAC Passthrough works correctly:
- 192kHz track stays at 192kHz through pause/resume
- No unwanted resampling to 48kHz
- Bit-perfect playback maintained

Fixes apply to:
- Auto-reinitialization after sink failures
- Resume command when stream needs recreation
- ReinitDevice command (settings changes)
2026-01-17 16:03:14 -06:00
vicrodh 7dbeb0cd4f Fix PipeWire routing - don't restore default sink
Root cause: We were restoring the default sink immediately after
creating the OutputStream, but the actual sink-input is only created
when playback starts (500ms+ later). By that time, we had already
restored the default to Speaker.

Solution: Don't restore the default sink at all. When user selects
a device in QBZ, it becomes the system default (expected behavior,
matches Strawberry Music Player).

Added 200ms delay after changing default sink to give PipeWire
time to process the change before creating the CPAL host.
2026-01-17 15:48:27 -06:00
vicrodh de8fbe8c33 Fix PipeWire routing by temporarily changing default sink
PULSE_SINK environment variable approach was not working because
CPAL/rodio does not properly propagate it when creating streams.

New approach:
1. Save current default sink with pactl get-default-sink
2. Temporarily set default sink to target device
3. Create CPAL stream (will use current default)
4. Restore original default sink

This ensures audio routes to the selected device without permanently
changing the system default sink.

Tested approach is more reliable than PULSE_SINK env var.
2026-01-17 15:43:39 -06:00
vicrodh c65909930b Fix PipeWire backend PULSE_SINK timing issue
Critical fix: Create fresh CPAL host AFTER setting PULSE_SINK environment variable.

Problem:
- PULSE_SINK was set in create_output_stream()
- But host was already created and connected in new()
- Host reads PULSE_SINK when it connects, not when stream is created
- Result: audio routing failed despite PULSE_SINK being set

Solution:
- Create new CPAL host in create_output_stream() AFTER setting PULSE_SINK
- This ensures host reads the environment variable when connecting
- Added verification logging to confirm PULSE_SINK is set

This should fix device selection routing to Cambridge DAC.
2026-01-17 15:12:43 -06:00
vicrodh d5f2ec9030 Fix: Add public reload_settings() method to Player
COMPILATION ERROR FIXED:
- audio_settings field was private
- Can't access from commands module

SOLUTION:
- Added public method: Player::reload_settings(settings)
- Encapsulation preserved
- Commands use public API

VERIFIED: cargo check passes
2026-01-17 15:04:43 -06:00
vicrodh e74c642831 CRITICAL FIX: ReinitDevice now uses backend system
PROBLEMA ENCONTRADO:
1. Frontend guardaba backend_type correctamente ✅
2. Player leía settings al inicio (backend_type = None) ✅
3. Usuario cambiaba backend en UI → se guardaba en BD ✅
4. Pero Player en memoria tenía settings VIEJOS ❌
5. reinit_audio_device usaba legacy CPAL sin backend ❌

SÍNTOMAS:
- "Backend system not configured, using legacy CPAL path"
- "Device '...' not found, using default"
- Audio salía por laptop speakers, no por DAC

SOLUCIÓN IMPLEMENTADA:

1. init_device() ahora intenta backend system primero:
   - Lee thread_settings.backend_type
   - Si configurado, llama try_init_stream_with_backend()
   - Fallback a legacy CPAL si falla

2. reinit_audio_device() recarga settings desde BD:
   - Lee AudioSettings desde database
   - Actualiza Player.audio_settings en memoria
   - Luego llama reinit_device()
   - Garantiza que Player siempre tiene config fresca

AHORA FUNCIONARÁ:
- Cambiar backend → se guarda → se recarga → se usa
- Cambiar device → se guarda → se recarga → se usa
- PULSE_SINK se setea correctamente
- Audio debe salir por Cambridge DAC

TESTING REQUERIDO:
Ir a Settings → Audio → Backend: PipeWire → Device: DacMagic Plus
Reproducir música → debería salir por DAC
2026-01-17 15:02:14 -06:00
vicrodh 196b824999 Fase 4: Integrate backend system into player
PLAYER INTEGRATION:
- Added try_init_stream_with_backend() helper function
- Modified AudioCommand::Play to use backend system when configured
- Falls back to legacy CPAL path if backend_type is None
- Preserves existing behavior for users without backend config

BACKEND PRIORITY:
1. Try backend system (if backend_type is set in audio_settings)
2. Fall back to legacy CPAL direct access (backward compatible)

VERIFIED:
- cargo check passes
- Backward compatible with existing installations
- Ready for Phase 5 (UI components)
2026-01-17 13:06:51 -06:00
vicrodh d5a2991e2b Fase 3: Add backend and plugin persistence to audio settings
AUDIO SETTINGS EXTENDED:
- Added backend_type field (PipeWire/ALSA/Pulse, None=auto)
- Added alsa_plugin field (Hw/PlugHw/Pcm for ALSA backend)
- Default: backend_type=None (auto-detect), alsa_plugin=Hw

DATABASE MIGRATION:
- ALTER TABLE for existing databases (backward compatible)
- Stores enums as JSON strings for flexibility

NEW TAURI COMMANDS:
- set_audio_backend_type(backend_type)
- set_audio_alsa_plugin(plugin)

VERIFIED:
- cargo check passes
- Migration handles existing databases gracefully
- Ready for Phase 4 (player integration)
2026-01-17 13:04:30 -06:00
vicrodh 67ff0cab5a Fase 2: Add Tauri commands for audio backend system
COMMANDS CREATED (audio_backends.rs):
- get_available_backends() → List backends with availability status
- get_devices_for_backend(type) → Enumerate devices for selected backend
- get_alsa_plugins() → List ALSA plugin options (hw/plughw/pcm)

INTEGRATION:
- Registered commands in commands/mod.rs
- Added to invoke_handler in lib.rs
- All commands return pretty names for UI display

VERIFIED:
- cargo check passes successfully
- Ready for Phase 3 (settings persistence)
2026-01-17 12:59:18 -06:00
vicrodh 513a7f9ba9 Fase 1: Implement audio backend abstraction system
ARCHITECTURE:
- Created trait AudioBackend for unified interface
- Backends: PipeWire, ALSA (stub), PulseAudio

PIPEWIRE BACKEND (complete):
- Enumerates devices via pactl (pretty names)
- Sets PULSE_SINK env var for device routing
- Uses CPAL 'pulse'/'pipewire' device
- Does NOT change system default
- Only affects QBZ process

ALSA BACKEND (stub):
- To be implemented by Copilot
- Will provide direct hw access
- True exclusive mode

PULSE BACKEND (complete):
- Reuses PipeWire implementation
- Legacy fallback

BENEFITS:
- User can choose audio stack
- Pretty names from pactl
- Device selection only affects QBZ
- Preparation for Strawberry-style UI

Next: Tauri commands for enumeration
2026-01-17 12:55:21 -06:00
vicrodh cb696aca60 Add detailed logging for CPAL device enumeration
Logs now show:
- Device index
- Device name
- Is default
- Supported configs (channels/sample rates)

This helps identify devices when pretty names aren't clear.
2026-01-17 12:03:13 -06:00
vicrodh 1f13370073 Fix device selection: Use CPAL for enumeration AND playback
PREVIOUS WRONG APPROACH (commit 212aae5):
- Used pactl to enumerate devices (PipeWire sink names)
- Tried to open those sink names with CPAL
- CPAL couldn't find PipeWire sink names → fell back to default
- Attempted workaround: change system default with set_pipewire_default_sink
- UNACCEPTABLE: Changed system-wide default, affecting all apps

ROOT CAUSE (identified by ChatGPT research):
- CPAL can open specific devices by name (research was correct)
- BUT: Name mismatch between pactl sinks and CPAL device names
- pactl returns: "alsa_output.usb-Cambridge_Audio..." (PipeWire sink)
- CPAL sees: "DacMagic Plus Analog Stereo" (user-friendly name)

CORRECT SOLUTION:
- Use CPAL for BOTH enumeration AND opening devices
- CPAL device names are consistent: enumerate → save → find → open
- No need to change system default
- Device selection only affects QBZ

CHANGES:
1. Rewrote get_pipewire_sinks() on Linux to use CPAL instead of pactl
2. Removed all calls to set_pipewire_default_sink()
3. Pass CPAL device names directly to reinit_audio_device()
4. CPAL can now find and open devices because names match

BENEFITS:
- ✅ Device selection only affects QBZ (not system-wide)
- ✅ No regresions
- ✅ No features lost
- ✅ Works exactly like librespot (Spotify client in Rust)
- ✅ Consistent with rodio/CPAL architecture

TESTING REQUIRED:
1. Select Cambridge DAC in QBZ → audio plays through Cambridge
2. Open Firefox/other app → uses its own device (unaffected)
3. Toggle devices in QBZ → changes correctly
4. Restart QBZ → restores saved device
5. DAC passthrough bit-perfect → works
2026-01-17 11:51:25 -06:00
vicrodh 732a489a3e Improve Discogs artwork selection with multi-release images
- Added /release/{id} endpoint to Cloudflare proxy for detailed release info
- Modified search_artwork_options to fetch full image sets from top 2 releases
- Interleave images: up to 4 from release #1, 4 from #2, 2 from others
- Added release_title and release_year metadata to each image option
- UI now displays release title and year for each artwork option
- Better image diversity and context for users

Users now see which release variant each artwork comes from,
making it easier to choose the correct edition.
2026-01-17 00:42:18 -06:00