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
+15 -2
View File
@@ -114,9 +114,22 @@ def swatch_icon(color_hex: str, size: int = 18) -> QIcon:
return QIcon(pixmap)
def highlight_palette(prefs) -> dict:
"""The highlight swatches as this user has them: the stock colors, with
any slot they repainted (Preferences → a swatch → color picker) in its
place. Saved in ``highlight_palette`` as {slot name: "#rrggbb"}."""
palette = dict(HIGHLIGHT_COLORS)
saved = prefs.get("highlight_palette") or {}
if isinstance(saved, dict):
for name, value in saved.items():
if name in palette and QColor.isValidColorName(str(value)):
palette[name] = QColor(str(value)).name()
return palette
def highlight_color(prefs) -> QColor:
return QColor(HIGHLIGHT_COLORS.get(prefs.get("highlight"),
HIGHLIGHT_COLORS["blue"]))
palette = highlight_palette(prefs)
return QColor(palette.get(prefs.get("highlight"), palette["blue"]))
def scale_metrics(prefs) -> dict: