"""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, crossfaded textures, the cone driven by a preview of the newest audio — which must never change the original's own frames. 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 GOLDEN, _digest, _frame, _paint, _FakePlayer, _LastFm class TestBetweenFrames: def test_previews_between_frames_change_nothing(self): """The renderer asks for a preview every screen refresh; the frames must still be the original's.""" engine = ks.Kaleidostrobe(seed=1000, ticks=1000) for f, (spectrum, ticks) in enumerate(zip(GOLDEN["spectra"], GOLDEN["ticks"])): engine.preview(spectrum, int(ticks) + 1) engine.preview(GOLDEN["spectra"][-1 - f], int(ticks) + 2) frame = engine.step(spectrum, int(ticks)) assert _digest(frame.textures[0]) == GOLDEN["tex_digest"][f][0], f"frame {f}" assert _digest(frame.textures[1]) == GOLDEN["tex_digest"][f][1], f"frame {f}" def test_gray_mode_is_two_tone(self, qapp): """Only the gray-mode colour and the panel's background, blended.""" r = KaleidostrobeRenderer() 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.3 # 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()