v0.42.0: laser speaker range, phosphor afterimage; line width box, radial dot size

Laser: a two-handle "Speaker range" sets what the speaker under the mirror
plays (36 dB/octave edges; the noise floor is measured per range so treble
alone still shows). Bass-only gives clean loops; treble-only is a shimmer
along one line, as a membrane far above its resonances really draws.

The afterimage is now a phosphor buffer, dimmed each frame and rounded
down so it always fades to nothing — constant cost however long the fade
(drawing every past sweep ran the big window past a frame).

Line width is a number box stepping by 0.5; radial dots have a size slider.
This commit is contained in:
2026-10-10 01:24:37 -07:00
parent 726f7c4a89
commit 8b42580f97
7 changed files with 648 additions and 41 deletions
+16 -3
View File
@@ -198,9 +198,22 @@ persistence) → GUI (Qt widgets that read the manager and connect to its signal
`distance`, turned by pitch/yaw/roll; the auto-gain holds the beam
*angle*, so distance is real, and `scale()` shrinks the drawing only when a
steep screen would push the loudest swing out of view. The path is drawn
brighter where it moves slower (dwell); past sweeps are kept for the
afterimage time as pre-built polygons in screen metres, drawn through a
transform with a cosmetic pen. Every style's fade is one seconds slider,
brighter where it moves slower (dwell). The afterimage is a **phosphor**
(`_Phosphor`, one per painted size, half resolution past 400 px wide):
each sweep is burned in once and the buffer is dimmed per frame, so cost
doesn't grow with the fade time. The dimming is a numpy LUT that rounds
*down*, because Qt's own fade rounds to nearest and stalls faint pixels
forever. The numpy array must be a **view of the QImage's `bits()`**, since
building a QImage *from* an array copies it. A **speaker range** (low/high
Hz, `gui/range_slider.py`) filters what drives the membrane, with
`one_pole` stages done exactly via the cumsum trick, 36 dB/octave. That is
steeper than a real speaker on purpose: the membrane answers bass ~80 dB
more than treble, so a gentler slope lets bass drown a treble-only
setting. For the same reason the auto-gain floor is *measured* per range
(reference hiss through that speaker and membrane), and the membrane is
simulated at full rate only once the bass is cut. Treble alone draws a
shimmer along one line: far above every resonance, all modes move in
phase. Every style's fade is one seconds slider,
0 = off. **Sync** (Round 78): decoding runs ahead of the speakers, by a
buffer or two normally but by up to ~0.8 s when the GUI thread stalls and
buffers arrive in a burst. So the widget keeps 1.5 s of *timestamped*