Commit Graph
2707 Commits
Author SHA1 Message Date
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 aff45f98f0 fix: close secondary windows before main to allow app exit, hide miniplayer on tray
Previous commit removed the miniplayer close from CloseRequested,
but that kept the app alive because Tauri won't exit while windows
remain open. Now closes secondary windows with close() (not
destroy()) in CloseRequested before the main window closes.
Also hides the miniplayer when close-to-tray activates.
2026-02-28 07:48:03 -06:00
vicrodh b1e4f8db94 fix: prevent WebKit crash on close by eliminating double-destroy of miniplayer
The miniplayer window was being destroyed twice during shutdown:
once in CloseRequested and again in RunEvent::Exit. This caused
WebKit's TLS destructors to collide during process teardown,
resulting in SIGABRT in WebKitWebProcess.

Changes:
- Remove miniplayer destroy from CloseRequested (was premature)
- Guard global on_window_event to only handle main window
- In RunEvent::Exit, close all secondary windows (miniplayer, oauth)
  with close() instead of destroy() for graceful WebKit cleanup
- Add 100ms delay after closing secondary windows to let WebKit
  tear down EGL/GPU resources before process exit
2026-02-28 07:48:03 -06:00
vicrodh 21fc1dfea3 fix: validate window size on load to prevent GDK crash from corrupt DB values
Extends PR #136 (save-time validation) with load-time validation.
If the DB contains corrupt sizes (e.g. 9084748x62267212), get_settings()
now returns safe defaults instead of passing garbage to GDK/Cairo.
Also extracts validation into a shared helper and adds NaN/Inf checks.
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 199f19a426 fix: read hardware audio status from V2 player instead of legacy
v2_get_hardware_audio_status was reading sample_rate/bit_depth from the
legacy player state, which is never updated since playback runs through
the V2 player (CoreBridge). Now reads from V2 player first, falls back
to legacy only if CoreBridge isn't initialized yet.
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 e1d006e672 fix: sync audio settings on startup for OAuth login path
The OAuth login path in runtime_bootstrap was missing the audio settings
sync code that exists in the email/password path. Users logging in via
OAuth had backend_type remain None, causing legacy CPAL fallback and
requiring manual settings reconfiguration on every startup.
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 33e80b0836 fix: lower default visualizer FPS from 60 to 15 for better performance 2026-02-28 07:48:03 -06:00
vicrodh 99196db70f fix: graceful shutdown on RunEvent::Exit to prevent heap corruption
Handle RunEvent::Exit (fires on SIGTERM from tauri dev hot reload)
to stop audio playback and visualizer before process teardown. The
previous CloseRequested handler only fires on user-initiated close,
not on SIGTERM, leaving ALSA/CPAL C threads active during cleanup
which causes "free(): corrupted unsorted chunks" heap corruption.

- Switch from .run(context) to .build(context).run(callback)
- Stop visualizer and both legacy/V2 players on Exit event
- Destroy miniplayer window on Exit event
2026-02-28 07:48:03 -06:00
vicrodh 6c8354f825 feat: add booklet download button, remove pdfjs-dist dependency
- Add save/download button to booklet toolbar with native file dialog
- Add v2_booklet_save command to copy PDF to user-chosen location
- Remove pdfjs-dist npm package (replaced by MuPDF backend)
- Delete static/pdfjs/ files (~1.9MB: worker, WASM decoders, fallbacks)
- Add bookletDownload i18n keys to all 4 locales
2026-02-28 07:48:03 -06:00
vicrodh 53fc99446b fix: disable alpha in booklet renderer for opaque white background 2026-02-28 07:48:03 -06:00
vicrodh bb723c7ca5 feat: switch booklet viewer from pdfjs-dist to MuPDF backend
Replace browser-side pdfjs-dist canvas rendering with server-side
MuPDF rendering for high-quality PDF booklet display. MuPDF renders
pages to PNG at configurable DPI in Rust, producing much sharper
results than the JavaScript canvas approach which had persistent
JPEG 2000 WASM decoder issues.

- Add mupdf crate with sys-lib features (avoid GTK symbol conflicts)
- Create pdf_viewer.rs with v2_booklet_open/render_page/close commands
- Download PDF to temp file, render pages on demand via MuPDF
- Display rendered PNGs as img tags instead of canvas
- DPI scales with zoom and devicePixelRatio for crisp output
- Support rotation via MuPDF transformation matrix
2026-02-28 07:48:03 -06:00
vicrodh e78dc626ff fix: serve pdfjs worker from static/ for proper WASM resolution
The worker was loaded via Vite's blob: URL transform, which
prevented it from resolving sibling WASM files (openjpeg.wasm,
jbig2.wasm). Now the worker is served from static/pdfjs/ as a
real HTTP resource alongside the WASM files, so JPEG 2000 and
JBIG2 decoders initialize correctly.

Removed disableWorker hack — worker runs properly now.
2026-02-28 07:48:03 -06:00
vicrodh 93f4b1bf68 fix: auto-fit booklet page to viewer width on load
PDF was rendering tiny because zoom=1 means 1 PDF point = 1 CSS
pixel. Now calculates zoom to fill the container width on load.
Fit-to-width button does the same recalculation.
2026-02-28 07:48:03 -06:00
vicrodh 7d981ee641 fix: restore workerSrc — pdfjs v5 requires it at init time
pdfjs v5 checks GlobalWorkerOptions.workerSrc before reading
document options like disableWorker. Set it to satisfy the
init check even though the worker is disabled per-document.
2026-02-28 07:48:03 -06:00
vicrodh c438b8896c fix: disable pdfjs worker for reliable WASM image decoding
The web worker runs in a blob: URL context where /pdfjs/ WASM
paths don't resolve, causing JPEG 2000 images to decode at
degraded quality. Disable worker so all decoding runs on the
main thread where static assets are reachable.

Also bumped render scale from 2x to 3x for near-print quality.
2026-02-28 07:48:03 -06:00
vicrodh 8757108e00 fix: first page black screen and low quality rendering
First page was black because renderPage() ran while isLoading was
still true, so the canvas wasn't in the DOM yet. Fixed by setting
isLoading=false and awaiting tick() before rendering.

Increased render quality from 1.5x to 2x DPR multiplier for
sharper booklet pages.
2026-02-28 07:48:03 -06:00
vicrodh 4bafaac1cd fix: provide pdfjs WASM files for JPEG 2000 decoding in booklets
PDF booklets use JPEG 2000 images which require OpenJPEG WASM.
Copy pdfjs-dist WASM files to static/pdfjs/ so they are served
at a known URL, and set wasmUrl parameter on getDocument().
2026-02-28 07:48:03 -06:00
vicrodh 883f3546c5 fix: fetch booklet PDF through Tauri backend to bypass CORS
PDF loading was failing because pdfjs-dist cannot fetch from
static.qobuz.com due to CORS restrictions in the WebView.

Added v2_fetch_url_bytes command that fetches URL bytes through
Rust's reqwest (no CORS), then passes the binary data to pdfjs.

Also added rotation support (R key or toolbar button) for
vertical booklets.
2026-02-28 07:48:03 -06:00
vicrodh 7e34232823 feat: add booklet button and viewer to AlbumDetailView
Show a BookOpen icon button in album header when the album has
PDF goodies from Qobuz. Opens the BookletViewer modal to display
the booklet inline.
2026-02-28 07:48:03 -06:00
vicrodh f561e12850 feat: add booklet viewer i18n keys to all 4 locales 2026-02-28 07:48:03 -06:00
vicrodh 025aba7c1a feat: add BookletViewer component with pdfjs-dist
Modal PDF viewer for album booklets with page navigation,
zoom controls, and keyboard shortcuts. Uses pdfjs-dist to
render PDF pages from URL in canvas.
2026-02-28 07:48:03 -06:00
vicrodh cb86f402e6 feat: add goodies/booklet types to frontend and adapter
Add QobuzGoody interface, goodies field to QobuzAlbum and AlbumDetail,
and pass goodies through in convertQobuzAlbum adapter.
2026-02-28 07:48:02 -06:00
vicrodh 6a7c31220b feat: add Goody model to qbz-models for album booklets
Add Goody struct and goodies field to Album for Qobuz album
booklet/liner notes PDF support.
2026-02-28 07:48:02 -06:00
vicrodh dec2278624 feat: per-panel FPS settings for all immersive animations
All animations default to 60fps. Each panel reads its own setting
from userStorage via shared fpsConfig utility. Settings UI shows
a collapsed section with per-panel dropdowns:

- Background Blur, Spectrum, X/Y (Lissajous), Oscilloscope,
  Energy Bands, Transient Pulse, Album Reactive, Spectral Ribbon

Options: Disabled, 15, 30, 60, 120 FPS
i18n keys added to all 4 locales (en, es, de, fr)
2026-02-28 07:48:02 -06:00
vicrodh 8c179ecf00 feat: configurable ambient background FPS in settings
Add setting for immersive background animation frame rate:
- Disabled, 15 FPS (default), 30 FPS, 60 FPS, 120 FPS
- Persisted via userStorage, read at canvas init
- Disabled renders a single static frame
- Setting placed in Appearance section after immersive default view
- i18n keys added to all 4 locales (en, es, de, fr)

Visualizer panels reverted to 30fps (unrelated to ambient bg).
2026-02-28 07:48:02 -06:00
vicrodh fae4d912ab feat: bump all visualizer panels from 30fps to 60fps
Updated frame throttle in all immersive panels:
- LissajousPanel (X/Y)
- VisualizerPanel (spectrum)
- OscilloscopePanel
- EnergyBandsPanel
- TransientPulsePanel
- AlbumReactivePanel

SpectralRibbon was already at 60fps.
2026-02-28 07:48:02 -06:00
vicrodh 1ba4818228 feat: bump immersive visualizer to 60fps
Was hardcoded at 15fps (not the 30fps from config). Updated both
the canvas frame throttle and the default config to 60fps.
2026-02-28 07:48:02 -06:00
vicrodh 9ccc0537d4 fix: destroy miniplayer window before app exit
WebKit's WebProcess was crashing (SIGABRT) during EGL/GPU resource
cleanup when the app exited with the miniplayer window still alive.
Now explicitly destroy the miniplayer window before the main process
exits, giving WebKit time to clean up gracefully.
2026-02-28 07:48:02 -06:00
Victor RH 4563718f59 Merge pull request #137 from vicrodh/dependabot/npm_and_yarn/svelte-5.53.6
chore(deps-dev): bump svelte from 5.53.0 to 5.53.6
2026-02-28 07:47:52 -06:00
dependabot[bot] f63d8d7a0a chore(deps-dev): bump svelte from 5.53.0 to 5.53.6
Bumps [svelte](https://github.com/sveltejs/svelte/tree/HEAD/packages/svelte) from 5.53.0 to 5.53.6.
- [Release notes](https://github.com/sveltejs/svelte/releases)
- [Changelog](https://github.com/sveltejs/svelte/blob/main/packages/svelte/CHANGELOG.md)
- [Commits](https://github.com/sveltejs/svelte/commits/svelte@5.53.6/packages/svelte)

---
updated-dependencies:
- dependency-name: svelte
  dependency-version: 5.53.6
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-28 04:35:44 +00:00
Victor RH 0c94d79075 Merge pull request #136 from arminfelder/feat/validateWindowSize
feat(settings): validate window size before saving
2026-02-27 18:52:25 -06:00
Armin Felder db32a29550 feat(settings): validate window size before saving 2026-02-28 00:43:42 +01:00
vicrodh bfcde1006b fix: lower default visualizer FPS from 60 to 15 for better performance 2026-02-26 17:24:22 -06:00
vicrodh 8eca751a02 fix: graceful shutdown on RunEvent::Exit to prevent heap corruption
Handle RunEvent::Exit (fires on SIGTERM from tauri dev hot reload)
to stop audio playback and visualizer before process teardown. The
previous CloseRequested handler only fires on user-initiated close,
not on SIGTERM, leaving ALSA/CPAL C threads active during cleanup
which causes "free(): corrupted unsorted chunks" heap corruption.

- Switch from .run(context) to .build(context).run(callback)
- Stop visualizer and both legacy/V2 players on Exit event
- Destroy miniplayer window on Exit event
2026-02-26 17:04:11 -06:00
vicrodh a11826e912 feat: add booklet download button, remove pdfjs-dist dependency
- Add save/download button to booklet toolbar with native file dialog
- Add v2_booklet_save command to copy PDF to user-chosen location
- Remove pdfjs-dist npm package (replaced by MuPDF backend)
- Delete static/pdfjs/ files (~1.9MB: worker, WASM decoders, fallbacks)
- Add bookletDownload i18n keys to all 4 locales
2026-02-26 16:50:59 -06:00
vicrodh ad472c8cb3 fix: disable alpha in booklet renderer for opaque white background 2026-02-26 15:55:06 -06:00
vicrodh c66cb5b3f1 feat: switch booklet viewer from pdfjs-dist to MuPDF backend
Replace browser-side pdfjs-dist canvas rendering with server-side
MuPDF rendering for high-quality PDF booklet display. MuPDF renders
pages to PNG at configurable DPI in Rust, producing much sharper
results than the JavaScript canvas approach which had persistent
JPEG 2000 WASM decoder issues.

- Add mupdf crate with sys-lib features (avoid GTK symbol conflicts)
- Create pdf_viewer.rs with v2_booklet_open/render_page/close commands
- Download PDF to temp file, render pages on demand via MuPDF
- Display rendered PNGs as img tags instead of canvas
- DPI scales with zoom and devicePixelRatio for crisp output
- Support rotation via MuPDF transformation matrix
2026-02-26 15:44:01 -06:00