Wave Bed (focus mode 6): centered vertical-mirror waveform. Spectral Ribbon (shader-mode 4): paint-as-you-play GPU spectrogram with green axes + header overlay, bass-at-bottom orientation aligned to the axes, and a real-time ceiling line tracking the highest active frequency. Line Bed (shader-mode 5): wgpu line-strip waterfall with a leveled camera, B-spline-smoothed continuous curves, a primary->accent height gradient, a bottom cutoff above the player, 2x supersampling, and a sampled-texture heights feed (no vertex storage buffer, so it runs on Slint's downlevel wgpu device). Also: Plasma reworked to be far more reactive (4 ink emitters, beat splats, faster turbulence, quicker decay); immersive song card no longer truncates the album/artist line; player heart reflects favorite state; player bar narrowed; sample-rate and bit-depth wired through for the spectral header. cc
96 lines
3.7 KiB
Plaintext
96 lines
3.7 KiB
Plaintext
// Spectral Ribbon overlay — green axes + labels + the stream header, drawn over
|
|
// the GPU spectrogram (shader-mode 4). Everything is positioned as FRACTIONS of
|
|
// the viewport so the axes grow/shrink with the window. The plot rectangle MUST
|
|
// match spectral_ribbon.wgsl (PLOT_X0..Y1) so the labels line up with the
|
|
// heatmap. A plain Rectangle with no TouchArea → input passes through to the
|
|
// player bar below.
|
|
|
|
import { NowPlayingState } from "../state.slint";
|
|
|
|
export component ImmersiveSpectralOverlay inherits Rectangle {
|
|
in property <length> viewport-width: 1280px;
|
|
in property <length> viewport-height: 720px;
|
|
|
|
// Plot rect — SAME fractions as spectral_ribbon.wgsl.
|
|
property <length> plot-x0: root.viewport-width * 0.055;
|
|
property <length> plot-x1: root.viewport-width * 0.970;
|
|
property <length> plot-y0: root.viewport-height * 0.070;
|
|
property <length> plot-y1: root.viewport-height * 0.780;
|
|
property <length> plot-w: root.plot-x1 - root.plot-x0;
|
|
property <length> plot-h: root.plot-y1 - root.plot-y0;
|
|
|
|
property <color> axis: #5fb87a;
|
|
// Nyquist in kHz (sample-rate / 2). Falls back to 22 (44.1k) when unknown.
|
|
property <float> nyq-khz: NowPlayingState.sample-rate-hz > 0
|
|
? NowPlayingState.sample-rate-hz / 2000.0
|
|
: 22.05;
|
|
property <int> dur: NowPlayingState.duration-secs;
|
|
|
|
width: root.viewport-width;
|
|
height: root.viewport-height;
|
|
|
|
// --- Stream header (top center, green box) ---------------------------
|
|
Rectangle {
|
|
x: (root.viewport-width - self.width) / 2;
|
|
y: root.viewport-height * 0.045;
|
|
width: hdr.width + 28px;
|
|
height: 22px;
|
|
border-radius: 5px;
|
|
border-width: 1px;
|
|
border-color: root.axis;
|
|
background: #0a140de6;
|
|
hdr := Text {
|
|
x: 14px;
|
|
y: (parent.height - self.height) / 2;
|
|
text: "Stream 1/1: Audio Stream, \{NowPlayingState.sample-rate-hz} Hz, \{NowPlayingState.bit-depth} bits, FFT:4096, Bands:512";
|
|
color: root.axis;
|
|
font-size: 12px;
|
|
}
|
|
}
|
|
|
|
// --- Axis frame (L-shape) --------------------------------------------
|
|
Rectangle { // vertical (frequency)
|
|
x: root.plot-x0;
|
|
y: root.plot-y0;
|
|
width: 1px;
|
|
height: root.plot-h;
|
|
background: root.axis;
|
|
}
|
|
Rectangle { // horizontal (time)
|
|
x: root.plot-x0;
|
|
y: root.plot-y1;
|
|
width: root.plot-w;
|
|
height: 1px;
|
|
background: root.axis;
|
|
}
|
|
|
|
// --- Frequency axis labels (Y, left) — 0 .. Nyquist ------------------
|
|
for f in [0.0, 0.25, 0.5, 0.75, 1.0]: Text {
|
|
x: root.plot-x0 - 46px;
|
|
y: root.plot-y1 - f * root.plot-h - self.height / 2;
|
|
width: 40px;
|
|
horizontal-alignment: right;
|
|
text: "\{Math.round(root.nyq-khz * f)}k";
|
|
color: root.axis;
|
|
font-size: 11px;
|
|
}
|
|
|
|
// --- Time axis labels (X, bottom) — 30 s ticks -----------------------
|
|
for i in [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30]: Text {
|
|
visible: root.dur > 0 && (i * 30) <= root.dur;
|
|
x: root.plot-x0 + (root.dur > 0 ? root.plot-w * ((i * 30) / root.dur) : 0) - self.width / 2;
|
|
y: root.plot-y1 + 6px;
|
|
text: "\{Math.floor((i * 30) / 60)}:\{Math.mod(i * 30, 60) < 10 ? "0" : ""}\{Math.mod(i * 30, 60)}";
|
|
color: root.axis;
|
|
font-size: 10px;
|
|
}
|
|
// Final duration marker at the right edge.
|
|
if root.dur > 0: Text {
|
|
x: root.plot-x1 - self.width / 2;
|
|
y: root.plot-y1 + 6px;
|
|
text: "\{Math.floor(root.dur / 60)}:\{Math.mod(root.dur, 60) < 10 ? "0" : ""}\{Math.mod(root.dur, 60)}";
|
|
color: root.axis;
|
|
font-size: 10px;
|
|
}
|
|
}
|