The cones move only on the plugin's own 25 fps frames again: driving them from fresh audio every refresh made bass-heavy songs twitch (a jump on 84% of refreshes against 41%). The continuous turn, texture crossfade and the GPU big window stay. The tile: a click opens it big (and it stays blank while big); hovering shows a colour wheel (colour / gray / off) and up/down arrows (previous / next visualizer). No more double-click or click-to-cycle. The big album art closes on a click or Esc, like the big visualizer.
96 lines
4.0 KiB
Python
96 lines
4.0 KiB
Python
"""Round 85: Kaleidostrobe at 60 fps, its big window on the GPU, gray mode
|
|
as two tones, and a sync slider that can also say "sooner".
|
|
|
|
1. Between the plugin's 25 fps frames: a smooth turn and crossfaded
|
|
textures. (The cone also followed a preview of the newest audio here;
|
|
Round 86 took that out — it made the picture twitch.)
|
|
2. Gray mode: the gray-mode colour on the panel background, nothing else.
|
|
3. The sync slider below zero.
|
|
4. The big window: an OpenGL canvas for Kaleidostrobe, QPainter without GL.
|
|
"""
|
|
import numpy as np
|
|
|
|
from PyQt6.QtGui import QColor
|
|
|
|
from lintunes.kaleidostrobe import engine as ks
|
|
from lintunes.gui import visualizer_styles as vs
|
|
from lintunes.gui.visualizer_kaleido import KaleidostrobeRenderer
|
|
from lintunes.preferences import Preferences
|
|
|
|
from test_round84 import _frame, _paint, _FakePlayer, _LastFm
|
|
|
|
|
|
SEED = 7
|
|
|
|
|
|
class TestBetweenFrames:
|
|
def test_gray_mode_is_two_tone(self, qapp):
|
|
"""Only the gray-mode colour and the panel's background, blended."""
|
|
r = KaleidostrobeRenderer()
|
|
r._engine = ks.Kaleidostrobe(seed=SEED, ticks=1000)
|
|
for _ in range(5):
|
|
r.advance(_frame(dt=0.04))
|
|
r.dimmed = True # the widget then passes its gray
|
|
r.panel = QColor(240, 236, 230)
|
|
img = _paint(r, 120, 80, color="#606060")
|
|
ground, ink = np.array([240, 236, 230.]), np.array([96, 96, 96.])
|
|
k = (img - ground) / (ink - ground) # 0 = ground, 1 = ink
|
|
assert np.ptp(k, axis=-1).max() < 0.03 # on the line between
|
|
assert k.min() > -0.02 and k.max() < 1.02
|
|
assert k.max() - k.min() > 0.15 # and still a picture
|
|
|
|
def test_draws_between_the_plugins_frames(self, qapp):
|
|
"""60 fps on screen: the turn moves every refresh, frames at 25."""
|
|
r = KaleidostrobeRenderer()
|
|
r.advance(_frame(dt=0.04))
|
|
turns, frames = [], []
|
|
for _ in range(12):
|
|
r.advance(_frame(dt=1 / 60))
|
|
turns.append(r._shown[0][0].turn)
|
|
frames.append(r._engine.frame_count)
|
|
assert len(set(turns)) == 12 # every refresh
|
|
assert np.all(np.diff(turns) > 0) # smoothly onward
|
|
assert frames[-1] - frames[0] == 4 # 12/60 s at 25 fps
|
|
|
|
class TestSyncSooner:
|
|
def test_negative_delay_is_kept(self):
|
|
assert vs.sync_delay_ms({"visualizer_sync_delay_ms": -30}) == -30
|
|
assert vs.sync_delay_ms({"visualizer_sync_delay_ms": -999}) == vs.SYNC_DELAY_LIMITS[0]
|
|
|
|
def test_slider_reads_sooner_and_later(self, qapp, tmp_path, monkeypatch):
|
|
from lintunes.gui import preferences_dialog as pd
|
|
monkeypatch.setattr(pd, "load_config", lambda: {})
|
|
dialog = pd.PreferencesDialog(Preferences(tmp_path), _LastFm())
|
|
assert dialog._sync_slider.minimum() < 0
|
|
assert pd._sync_text(-20) == "20 ms sooner"
|
|
assert pd._sync_text(40) == "40 ms later"
|
|
|
|
|
|
class TestBigWindow:
|
|
def _vis(self, tmp_path, style):
|
|
from lintunes.gui.visualizer import VisualizerWidget
|
|
prefs = Preferences(tmp_path)
|
|
prefs.set("visualizer_style", style)
|
|
return prefs, VisualizerWidget(_FakePlayer(), prefs)
|
|
|
|
def test_kaleidostrobe_gets_the_gl_canvas(self, qapp, tmp_path):
|
|
prefs, vis = self._vis(tmp_path, "kaleidostrobe")
|
|
vis.open_big()
|
|
try:
|
|
assert vis._big._canvas is not None and not vis._big._canvas.isHidden()
|
|
prefs.set("visualizer_style", "bars") # another style
|
|
vis._repaint()
|
|
assert vis._big._canvas.isHidden()
|
|
finally:
|
|
vis._big.close()
|
|
|
|
def test_without_opengl_it_paints_with_qpainter(self, qapp, tmp_path):
|
|
prefs, vis = self._vis(tmp_path, "kaleidostrobe")
|
|
vis.open_big()
|
|
try:
|
|
vis._big._canvas.unavailable.emit() # no GL driver
|
|
assert vis._big._canvas.isHidden()
|
|
assert not vis._big._wants_gl()
|
|
finally:
|
|
vis._big.close()
|