Commit 504b0366 by PLN (Algolia)

feat(midiviz): the master bus, painted behind the rain

PLN: "can we overlay behind a basic spectro? that id remove spectro VSTs
from ardour and wed have our super perf one as overlay on single win?"

So a coarse spectrum of the DEFAULT SINK's monitor -- the mix as the room
hears it, not one orbit -- painted first, under every glyph, in the window
that is already on top of everything. 40 log bands off a 2048-point FFT at
18 Hz: scenery, not an analyser. If something needs measuring, tidal-ears
is the tool and it is the right one.

Off by default and off both ways. `--spectro` or the `s` key acquires the
tap; `s` again hands it back -- the capture subprocess does not exist until
asked and does not survive being un-asked, closeEvent releases it, and the
unit deliberately carries no --spectro so login never acquires one. A live
rig does not get to grow an always-on audio consumer quietly.

Capture and FFT run in one thread whose entire contact with Qt is a single
frame swapped under a lock, depth ONE: a GUI at 5 fps reading an analyser at
18 fps shows the newest picture and discards the rest. A queue here would
only buy latency, which is what a 512-deep queue already cost the HUD once.
pw-record is asked for 100 ms latency explicitly -- a monitor client that
requests a tight buffer is how you talk the graph's quantum down and pay for
a decoration in xruns.

Measured, A/B/A, same -30 dBFS 1 kHz bed in every window, 120 s each: 0
added xruns with the tap on (2249 analysed frames, 18.7/s), 0 in either
control window, every node's counter unmoved. Nothing in the graph noticed.

Two things this cost, both recorded where they bit:

* `Popen(bufsize=0)` hands back a RAW FileIO, so `read(n)` is one os.read
  and a hop-sized read is short far more often than not. Treating that as
  EOF ended the capture on its first chunk -- blocks=0, no error recorded --
  and every pure-function test stayed green on top of a backdrop that could
  not receive a sample. Only running it against real audio found it.
  `_read_exact` now owns the distinction, with a dribbling-stream test.
* the first assertion about band placement was wrong about the code it
  tested: below ~750 Hz a 2048-point FFT has fewer bins than we have bands,
  so the edges are not the nominal log ramp, and checking 220 Hz against the
  ramp reported MISPLACED for a band that is exactly 211-234 Hz. Ask the
  edges where the band is.

And the reference was wrong before the code was: the first bed I labelled an
"80-226 Hz sweep" was 0.02*sin(2*pi*f(t)*t), whose instantaneous frequency
is f + t*df/dt -- broadband chirp garbage. The spectrum was reading it
correctly. A known 1 kHz tone lands in band 22, span 984-1148 Hz, every
other band at 0.00.

`_paint_spectrum` was checked against all 33 methods on the widget before it
was named: `_paint_chrome` had to become `_paint_controls` because a second
method quietly took the first one's name and the selftest went on passing
while a whole layer stopped being drawn.

selftest PASS (bars=40, tap released); 106 tests pass.
parent a4ad0752
...@@ -304,3 +304,234 @@ def test_user_close_exit_code_matches_the_unit(): ...@@ -304,3 +304,234 @@ def test_user_close_exit_code_matches_the_unit():
assert str(mv.USER_CLOSE_EXIT) in ok.group(1).split(), ( assert str(mv.USER_CLOSE_EXIT) in ok.group(1).split(), (
f"SuccessExitStatus={ok.group(1)!r} does not include " f"SuccessExitStatus={ok.group(1)!r} does not include "
f"{mv.USER_CLOSE_EXIT}") f"{mv.USER_CLOSE_EXIT}")
# ── the spectrum backdrop ──────────────────────────────────────────────────
#
# PLN asked for a spectro "behind" the rain so the Ardour spectro VSTs could
# go. The whole risk of the feature is that it is an AUDIO consumer on a box
# that performs live, so what is asserted here is mostly about restraint: it
# must not exist until asked, it must genuinely go away when un-asked, and
# its numbers must not be able to paint silence as signal.
def test_the_spectro_does_not_exist_until_it_is_asked_for(monkeypatch):
"""Off by default means NO PROCESS, not a hidden one.
A backdrop that spawned `pw-record` at startup and merely declined to
paint would be exactly the always-on consumer this is not allowed to be,
and nothing on screen would reveal it.
"""
import os
import pytest
os.environ.setdefault("QT_QPA_PLATFORM", "offscreen")
mv, w = _widget(1.0, 900)
if mv is None:
pytest.skip("no Qt available")
spawned = []
monkeypatch.setattr(mv.subprocess, "Popen",
lambda *a, **k: spawned.append(a) or (_ for _ in ()).throw(
AssertionError("the backdrop spawned a capture unasked")))
assert w.spectro is False
assert w.spec_src is None
w.tick() # a full frame with nobody asking
w.grab()
assert spawned == []
w.close()
def test_toggling_the_spectro_acquires_and_releases_the_tap():
"""`s` on, `s` off — and off must hand the capture back, not hide it."""
import os
import pytest
os.environ.setdefault("QT_QPA_PLATFORM", "offscreen")
mv, w = _widget(1.0, 900)
if mv is None:
pytest.skip("no Qt available")
closed = []
class FakeSource:
hop, rate, err, frames = 256, 48000, "", 0
def latest(self):
return tuple(0.5 for _ in range(mv.SPEC_BANDS))
def close(self):
closed.append(True)
w.set_spectro(True, FakeSource())
assert w.spectro and w.spec_src is not None
w.tick()
assert w._spec_frame is not None, "a published frame never reached the paint state"
w.set_spectro(False)
assert not w.spectro and w.spec_src is None
assert closed == [True], "switching the backdrop off left the capture running"
assert w._spec_frame is None
w.close()
def test_closing_the_window_releases_the_audio_tap():
"""A closed window must not outlive its own switch.
The X writes the close latch and systemd will not restart us, so nothing
would ever come back to turn a leaked `pw-record` off again.
"""
import os
import pytest
os.environ.setdefault("QT_QPA_PLATFORM", "offscreen")
mv, w = _widget(1.0, 900)
if mv is None:
pytest.skip("no Qt available")
closed = []
class FakeSource:
err, frames = "", 0
def latest(self):
return None
def close(self):
closed.append(True)
w.set_spectro(True, FakeSource())
w.close()
assert closed == [True], "closeEvent left the capture on the master bus"
def test_band_edges_never_leave_a_band_empty():
"""Strictly increasing, in range, at every width the window can be.
A 2048-point FFT has fewer bins than we have bands below ~750 Hz, so the
naive log ramp produces duplicate edges — and a duplicated edge is a band
that sums zero bins, which paints as a black hole in the bass. "Dark = not
mapped" is the reading a cockpit trains you into (the LED work settled
that), so an empty band is worse than a coarse one.
"""
import importlib.util
import sys as _sys
from pathlib import Path as _Path
root = _Path(__file__).resolve().parents[3]
spec = importlib.util.spec_from_file_location("_mv_bands",
root / "tools/bridge/midiviz.py")
mv = importlib.util.module_from_spec(spec)
_sys.modules["_mv_bands"] = mv
spec.loader.exec_module(mv)
for bands in (12, 24, mv.SPEC_BANDS, 64, 96):
e = mv._spec_band_edges(bands, mv.SPEC_FFT, mv.SPEC_RATE)
assert len(e) == bands + 1
assert all(e[i] < e[i + 1] for i in range(bands)), \
f"an empty band at bands={bands}: {list(e)}"
assert e[0] >= 1 and e[-1] <= mv.SPEC_FFT // 2, \
f"edges ran off the spectrum at bands={bands}: {e[0]}..{e[-1]}"
def test_the_analysis_seam_is_the_only_thing_the_paint_path_needs():
"""`push()` carries the whole feature; the subprocess carries none of it.
This is what lets the backdrop be tested at all on a box with no audio
server, and it is also the honest boundary: green here says nothing about
`pw-record`, only that everything downstream of a block of samples works.
"""
import math
import importlib.util
import sys as _sys
from pathlib import Path as _Path
import pytest
np = pytest.importorskip("numpy")
root = _Path(__file__).resolve().parents[3]
spec = importlib.util.spec_from_file_location("_mv_push",
root / "tools/bridge/midiviz.py")
mv = importlib.util.module_from_spec(spec)
_sys.modules["_mv_push"] = mv
spec.loader.exec_module(mv)
src = mv.SpectrumSource(target="__never__")
t = np.arange(src.fft_n) / float(src.rate)
for _ in range(30):
frame = src.push(0.25 * np.sin(2 * math.pi * 3000.0 * t))
assert len(frame) == mv.SPEC_BANDS
assert all(0.0 <= v <= 1.0 for v in frame), "a band escaped 0..1"
hi = int(np.argmax(frame))
bin_hz = src.rate / float(src.fft_n)
assert src.edges[hi] * bin_hz <= 3000.0 <= src.edges[hi + 1] * bin_hz, \
"a 3 kHz tone did not land in the band that contains 3 kHz"
for _ in range(120):
quiet = src.push(np.zeros(src.fft_n, dtype=np.float32))
assert all(v == v for v in quiet), "silence produced NaN, which paints full-height"
assert max(quiet) < 0.02, f"silence read as signal: {max(quiet):.3f}"
src.close() # never started; must still be safe
def test_the_backdrop_is_painted_behind_everything_else():
"""Ordering IS the feature — "overlay behind a basic spectro"."""
import os
import pytest
os.environ.setdefault("QT_QPA_PLATFORM", "offscreen")
mv, w = _widget(1.0, 900)
if mv is None:
pytest.skip("no Qt available")
order = []
for name in ("_paint_spectrum", "_paint_chrome", "_paint_header",
"_paint_matrix", "_paint_gutter", "_paint_stream"):
real = getattr(w, name)
def probe(*a, _n=name, _r=real, **k):
order.append(_n)
return _r(*a, **k)
setattr(w, name, probe)
class FakeSource:
err, frames = "", 0
def latest(self):
return tuple(0.6 for _ in range(mv.SPEC_BANDS))
def close(self):
pass
w.set_spectro(True, FakeSource())
w.tick()
w.grab()
assert order and order[0] == "_paint_spectrum", \
f"the spectrum is not the first layer painted: {order}"
w.set_spectro(False)
w.close()
def test_a_short_pipe_read_is_not_mistaken_for_end_of_stream():
"""The bug that made the whole backdrop dead while the selftest passed.
`Popen(bufsize=0)` hands back a raw `FileIO`: `read(n)` is one `os.read`
and returns whatever the pipe holds, so a hop-sized read is short far more
often than not. Treating that as EOF ended the capture on its first chunk
(`blocks=0`, no error recorded) — and every pure-function test still went
green, because none of them touched the pipe.
"""
import io
import importlib.util
import sys as _sys
from pathlib import Path as _Path
root = _Path(__file__).resolve().parents[3]
spec = importlib.util.spec_from_file_location("_mv_read",
root / "tools/bridge/midiviz.py")
mv = importlib.util.module_from_spec(spec)
_sys.modules["_mv_read"] = mv
spec.loader.exec_module(mv)
class Dribble(io.RawIOBase):
"""A pipe that never gives you as much as you asked for."""
def __init__(self, data, most=7):
self.buf, self.most = bytearray(data), most
def read(self, n=-1):
take = min(n if n and n > 0 else 1, self.most, len(self.buf))
out, self.buf = bytes(self.buf[:take]), self.buf[take:]
return out
payload = bytes(range(256)) * 8 # 2048 bytes
got = mv._read_exact(Dribble(payload), len(payload))
assert got == payload, "a dribbling stream lost or truncated bytes"
# and the real end of stream still reports itself as one
assert mv._read_exact(Dribble(b""), 16) is None
assert mv._read_exact(Dribble(payload[:100]), len(payload)) is None, \
"a stream that ends mid-block must read as EOF, not a partial frame"
...@@ -20,6 +20,11 @@ StartLimitIntervalSec=0 ...@@ -20,6 +20,11 @@ StartLimitIntervalSec=0
# Inherits the Wayland/Plasma session env from the user manager. # Inherits the Wayland/Plasma session env from the user manager.
Environment=PYTHONUNBUFFERED=1 Environment=PYTHONUNBUFFERED=1
ExecStart=/usr/bin/python3 %h/Work/Sound/Tidal/tools/bridge/midiviz.py ExecStart=/usr/bin/python3 %h/Work/Sound/Tidal/tools/bridge/midiviz.py
# No --spectro here ON PURPOSE. The spectrum backdrop is an AUDIO consumer,
# and this unit is Restart=always and starts with the session -- putting the
# flag here would make the rig acquire a permanent tap on the master bus at
# login, which is precisely what the feature is not allowed to be. It is a
# runtime choice: `s` in the window, or a hand-launched --spectro.
# 2026-09-05: an unplugged LCXL used to make midiviz.py exit CLEANLY (the # 2026-09-05: an unplugged LCXL used to make midiviz.py exit CLEANLY (the
# `aseqdump` it shelled out to hit EOF and nothing kept the window open), and # `aseqdump` it shelled out to hit EOF and nothing kept the window open), and
# `Restart=on-failure` never fires on a clean exit -- PLN's window was gone # `Restart=on-failure` never fires on a clean exit -- PLN's window was gone
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment