"""Rendering tests for the waterfall, radial and braille visualisers.""" from __future__ import annotations import math from beefy_player import config from beefy_player.spectrum import Frame, SpectrumEngine from beefy_player.widgets.spectrum import ( VISUALIZER_LABELS, WATERFALL_HISTORY, SpectrumPanel, ) def make_frame(level: float = 0.7) -> Frame: bands = tuple(0.12 + 0.8 * max(0.0, math.sin(index / 4.0)) for index in range(72)) peaks = tuple(min(1.0, value + 0.05) for value in bands) wave = tuple(level * math.sin(point / 9.0) for point in range(192)) return Frame(bands=bands, peaks=peaks, wave=wave, level=level, status="monitor") def make_panel() -> SpectrumPanel: return SpectrumPanel(SpectrumEngine()) def test_every_visualizer_has_a_border_label() -> None: assert set(VISUALIZER_LABELS) == set(config.VISUALIZERS) def test_waterfall_records_one_column_per_tick() -> None: panel = make_panel() frame = make_frame() panel._record(frame, tick=1) panel._record(frame, tick=2) panel._record(frame, tick=2) # a second render in the same tick assert len(panel._history) == 2 for tick in range(3, WATERFALL_HISTORY + 20): panel._record(frame, tick) assert len(panel._history) == WATERFALL_HISTORY def test_waterfall_scrolls_spectrogram_with_bass_at_the_bottom() -> None: panel = make_panel() # Quieter highs, louder bass, so the bottom rows must be fuller. rising = Frame( bands=tuple((71 - index) / 71 for index in range(72)), peaks=tuple((71 - index) / 71 for index in range(72)), wave=(0.0,) * 192, level=0.5, status="monitor", ) for tick in range(30): panel._record(rising, tick) lines = panel._waterfall(40, 12, dim=False) assert len(lines) == 12 assert {len(line.plain) for line in lines} == {40} chars = "".join(line.plain for line in lines) assert any(char in chars for char in "░▒▓█") # History that does not fill the panel is padded blank on the left. assert lines[0].plain[:9].strip() == "" filled = [sum(1 for char in line.plain if char != " ") for line in lines] assert filled[-1] > filled[0] def test_radial_spectrum_draws_a_ring_inside_the_panel() -> None: panel = make_panel() lines = panel._radial(make_frame(), 61, 21, phase=0, dim=False) assert len(lines) == 21 assert {len(line.plain) for line in lines} == {61} text = "".join(line.plain for line in lines) assert text.count("█") > 10 assert text.count("·") > 4 # The corners are outside the circle. assert lines[2].plain[:6].strip() == "" assert lines[2].plain[-6:].strip() == "" def test_radial_falls_back_to_bars_when_too_small() -> None: panel = make_panel() lines = panel._radial(make_frame(), 12, 4, phase=0, dim=False) assert len(lines) == 4 assert {len(line.plain) for line in lines} == {12} assert any(char in "".join(line.plain for line in lines) for char in "█▄▅") def test_braille_oscilloscope_uses_subcell_dots() -> None: panel = make_panel() lines = panel._braille(make_frame(0.9), 40, 8, phase=0, dim=False) assert len(lines) == 8 assert {len(line.plain) for line in lines} == {40} chars = "".join(line.plain for line in lines) assert all(char == " " or 0x2800 <= ord(char) <= 0x28FF for char in chars) dots = [char for char in chars if 0x2800 <= ord(char) <= 0x28FF] assert len(dots) > 20 def test_braille_keeps_a_baseline_when_silent() -> None: panel = make_panel() silent = Frame((0.0,) * 72, (0.0,) * 72, (0.0,) * 192, 0.0, "idle") lines = panel._braille(silent, 20, 6, phase=0, dim=True) chars = "".join(line.plain for line in lines) assert any(0x2800 <= ord(char) <= 0x28FF for char in chars)