v0.41.0: visualizer sync, fade sliders, laser look controls, custom highlight

Sync: decoding runs ahead of the speakers — by up to ~0.8 s after a GUI
stall, when buffers arrive in a burst — and the visualizer drew the newest
audio. It now keeps timestamped history and draws what the player says is
playing, plus a per-machine Sync delay (config.json) for latency Qt can't
see, like Bluetooth.

Every style has one Fade slider (0 = off); bars now leave fading steps.
Laser: afterimage time, line width down to 1 px, glow on/off, tip dot
on/off and size (a resting beam always shows its dot), and an oscilloscope
dot grid under the beam. Visualizer sliders run the full window width.

Highlight: click a swatch for a full color picker; the color picked
replaces that slot (right-click restores it).
This commit is contained in:
2026-10-10 01:11:09 -07:00
parent 751273b14c
commit 726f7c4a89
8 changed files with 682 additions and 135 deletions
+16 -2
View File
@@ -198,8 +198,22 @@ persistence) → GUI (Qt widgets that read the manager and connect to its signal
`distance`, turned by pitch/yaw/roll; the auto-gain holds the beam
*angle*, so distance is real, and `scale()` shrinks the drawing only when a
steep screen would push the loudest swing out of view. The path is drawn
brighter where it moves slower (dwell). It needs **every** sample, not the
newest FFT window, hence the widget's capped `_pending` queue.
brighter where it moves slower (dwell); past sweeps are kept for the
afterimage time as pre-built polygons in screen metres, drawn through a
transform with a cosmetic pen. Every style's fade is one seconds slider,
0 = off. **Sync** (Round 78): decoding runs ahead of the speakers, by a
buffer or two normally but by up to ~0.8 s when the GUI thread stalls and
buffers arrive in a burst. So the widget keeps 1.5 s of *timestamped*
history (`QAudioBuffer.startTime()`) and each frame draws the stretch
ending at `Player.position_ms()`, carried forward by the clock between
Qt's buffer-sized steps, minus `visualizer_sync_delay_ms`. That delay is
output latency Qt can't see (Bluetooth), this machine's hardware, so it
lives in **`config.json`**; the dialog emits `prefs.changed` after writing
it so the widget re-reads. The laser takes every heard sample exactly once
(`_drawn` index), never the newest-decoded ones.
The highlight swatches can be repainted with a color picker:
`highlight_palette` overrides a slot, and `theme.highlight_color` reads
through `theme.highlight_palette`, so nothing else needs to know.
- **`lintunes/importers/itunes_importer.py`** — parses the iTunes XML plist.
Remaps Mac `file:///Volumes/...` paths to the local `--music-root` with