Add real-time CAVA audio visualizer for VU bars
CAVA runs in background via cava-start.sh, writing bar values to /tmp/skeledance_bars. A new QML DataSource polls that file at 80ms and calls updateBars() in JS. Falls back to rest state when not playing. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
b2df95ed10
commit
5876701a64
4 changed files with 61 additions and 17 deletions
9
plugin/contents/ui/cava-start.sh
Executable file
9
plugin/contents/ui/cava-start.sh
Executable file
|
|
@ -0,0 +1,9 @@
|
|||
#!/usr/bin/env bash
|
||||
# Kill any previous instance tied to this config
|
||||
pkill -f "cava -p.*skeledance" 2>/dev/null
|
||||
sleep 0.2
|
||||
|
||||
CONF="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/cava.conf"
|
||||
cava -p "$CONF" | while IFS= read -r line; do
|
||||
printf '%s\n' "$line" > /tmp/skeledance_bars
|
||||
done
|
||||
14
plugin/contents/ui/cava.conf
Normal file
14
plugin/contents/ui/cava.conf
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
[general]
|
||||
bars = 38
|
||||
framerate = 60
|
||||
sleep_timer = 0
|
||||
|
||||
[input]
|
||||
method = pulse
|
||||
|
||||
[output]
|
||||
method = raw
|
||||
raw_target = /dev/stdout
|
||||
data_format = ascii
|
||||
ascii_max_range = 100
|
||||
bar_delimiter = 59
|
||||
|
|
@ -873,33 +873,30 @@ const VU_PALETTE = [
|
|||
'#00eebb','#00ddaa','#00cc99','#00bb88',
|
||||
];
|
||||
|
||||
const MAX_BAR_H = 70;
|
||||
|
||||
for (let i = 0; i < VU_COUNT; i++) {
|
||||
const bar = document.createElement('div');
|
||||
bar.className = 'vu-bar';
|
||||
bar.style.background = VU_PALETTE[i % VU_PALETTE.length];
|
||||
bar.style.boxShadow = `0 0 4px ${VU_PALETTE[i % VU_PALETTE.length]}`;
|
||||
bar._maxH = 15 + Math.random() * 55;
|
||||
bar._phase = Math.random() * Math.PI * 2;
|
||||
bar._speed = 1.5 + Math.random() * 4;
|
||||
bar.style.height = '3px';
|
||||
bar.style.opacity = '0.15';
|
||||
vuMeter.appendChild(bar);
|
||||
vuBars.push(bar);
|
||||
}
|
||||
|
||||
function animateVU(ts) {
|
||||
const t = ts * 0.001;
|
||||
vuBars.forEach(bar => {
|
||||
if (isPlaying) {
|
||||
const h = bar._maxH * Math.abs(Math.sin(t * bar._speed + bar._phase));
|
||||
bar.style.height = Math.max(3, h) + 'px';
|
||||
bar.style.opacity = 0.55 + 0.45 * (h / bar._maxH);
|
||||
} else {
|
||||
bar.style.height = '3px';
|
||||
bar.style.opacity = '0.15';
|
||||
}
|
||||
// Called by QML with semicolon-separated CAVA values (0–100 per bar)
|
||||
function updateBars(raw) {
|
||||
if (!isPlaying) return;
|
||||
const vals = raw.split(';');
|
||||
vuBars.forEach((bar, i) => {
|
||||
const v = Math.min(100, parseInt(vals[i] || 0, 10));
|
||||
const h = Math.max(3, (v / 100) * MAX_BAR_H);
|
||||
bar.style.height = h + 'px';
|
||||
bar.style.opacity = 0.3 + 0.7 * (v / 100);
|
||||
});
|
||||
requestAnimationFrame(animateVU);
|
||||
}
|
||||
requestAnimationFrame(animateVU);
|
||||
|
||||
/* ============================================================
|
||||
FLOATING MUSIC NOTES
|
||||
|
|
@ -945,6 +942,7 @@ function updateMusic(artist, title) {
|
|||
isPlaying = playing;
|
||||
document.querySelector('.robot-wrap').style.opacity = playing ? '1' : '0';
|
||||
document.querySelector('.music-ticker').style.opacity = playing ? '1' : '0';
|
||||
if (!playing) vuBars.forEach(b => { b.style.height = '3px'; b.style.opacity = '0.15'; });
|
||||
if (el.textContent === text) return;
|
||||
el.textContent = text;
|
||||
if (playing) pickSkeleton();
|
||||
|
|
|
|||
|
|
@ -24,6 +24,14 @@ WallpaperItem {
|
|||
}
|
||||
}
|
||||
|
||||
// Start CAVA on load
|
||||
Plasma5Support.DataSource {
|
||||
id: cavaStarter
|
||||
engine: "executable"
|
||||
connectedSources: ["bash " + Qt.resolvedUrl("cava-start.sh").toString().replace("file://", "") + " &"]
|
||||
interval: 0
|
||||
}
|
||||
|
||||
// Polls playerctl every 2s and injects music info into the page
|
||||
Plasma5Support.DataSource {
|
||||
id: musicSource
|
||||
|
|
@ -42,4 +50,19 @@ WallpaperItem {
|
|||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Polls CAVA bars at ~60fps
|
||||
Plasma5Support.DataSource {
|
||||
id: cavaSource
|
||||
engine: "executable"
|
||||
connectedSources: ["cat /tmp/skeledance_bars 2>/dev/null || echo ''"]
|
||||
interval: 80
|
||||
|
||||
onNewData: function(sourceName, data) {
|
||||
var raw = data["stdout"].trim()
|
||||
if (raw !== "") {
|
||||
webView.runJavaScript("updateBars(" + JSON.stringify(raw) + ")")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue