import QtQuick import QtQuick.Layouts import "../constants" import "../services" Row { id: root property string artist: "" property string title: "" property string artUrl: "" property bool isPlaying: false property int maxTextLength: 50 property int numBands: 32 // if the artist - title text is too long, truncate it readonly property string displayText: { const fullText = artist !== "" ? (artist + " - " + title) : title; return fullText.length > maxTextLength ? fullText.substring(0, maxTextLength - 3) + "..." : fullText; } property string dancingLeft: "└(ಠ_ಠ )┐" property string dancingRight: "┌( ಠ_ಠ)┘" property string notDancing: "(ಠ ∩ ಠ)" visible: title !== "" spacing: 6 Text { id: littleFella text: root.dancingLeft color: Colors.md3.primary anchors.verticalCenter: parent.verticalCenter } //TODO add service that reads the data from cava // the fifo buffer is in /tmp/cava.fifo. Example data is: // 5;3;3;3;2;1;1;3;6;18;42;16;6;6;1;1;2;6;2;3;6;6;5;11;11;12;13;66;4;4;24;2;2;24;4;4;66;13;12;11;11;5;6;6;3;2;3;2;1;1;6;6;16;42;19;5;2;1;1;2;3;4;3;7; // where each number represents the amplitude of a frequency band. // this can be used to create a simple visualizer // also add cava to autostart Image { width: 18 height: 18 source: root.artUrl fillMode: Image.PreserveAspectCrop anchors.verticalCenter: parent.verticalCenter } Text { id: titleText text: root.displayText color: Colors.md3.primary font.family: Constants.fontFamily font.pixelSize: Constants.fontSize anchors.verticalCenter: parent.verticalCenter // z: 1 } Timer { interval: 400 running: true repeat: true onTriggered: { if (root.isPlaying) { littleFella.text = littleFella.text === root.dancingLeft ? root.dancingRight : root.dancingLeft; } else { littleFella.text = root.notDancing; } } } // Item { // id: titleArea // anchors.bottom: parent.bottom // width: titleText.implicitWidth // height: Math.max(titleText.implicitHeight, visualizer.height) // Canvas { // id: visualizer // anchors.fill: parent // property var cavaData: [] // property var smoothedData: [] // property real inputMax: 20 // property real attackAlpha: 1.0 // property real releaseAlpha: 0.9 // property real maxStepPerFrame: 50 // property bool needsInit: true // Component.onCompleted: { // for (let i = 0; i < root.numBands; i++) { // cavaData[i] = 0; // smoothedData[i] = 0; // } // needsInit = false; // } // function updateData(csvLine) { // // Fast in-place parse: split once, parse directly into pre-allocated array // let parts = csvLine.split(";"); // let idx = 0; // for (let j = 0; j < parts.length && idx < root.numBands; j++) { // let val = Number(parts[j]); // if (!isNaN(val)) // cavaData[idx++] = Math.min(100, Math.max(0, val)); // } // while (idx < root.numBands) // cavaData[idx++] = 0; // if (needsInit) { // for (let i = 0; i < root.numBands; i++) // smoothedData[i] = cavaData[i]; // needsInit = false; // } else { // // Two-speed smoothing in place with early exit if no significant change // let changed = false; // for (let i = 0; i < root.numBands; i++) { // let prev = smoothedData[i]; // let next = cavaData[i]; // let alpha = next >= prev ? attackAlpha : releaseAlpha; // let blended = prev + (next - prev) * alpha; // let delta = blended - prev; // if (Math.abs(delta) > 0.1) // changed = true; // if (delta > maxStepPerFrame) // blended = prev + maxStepPerFrame; // else if (delta < -maxStepPerFrame) // blended = prev - maxStepPerFrame; // smoothedData[i] = blended; // } // if (!changed) // return; // } // visualizer.requestPaint(); // } // onPaint: { // var ctx = getContext("2d"); // ctx.clearRect(0, 0, width, height); // if (smoothedData.length === 0) { // return; // } // var barWidth = width / root.numBands; // ctx.fillStyle = Colors.md3.on_primary; // for (let i = 0; i < root.numBands; i++) { // let normalized = (smoothedData[i] || 0) / inputMax; // if (normalized > 1) // normalized = 1; // let barHeight = normalized * height; // ctx.fillRect(Math.floor(i * barWidth), Math.floor(height - barHeight), Math.ceil(barWidth), Math.floor(barHeight)); // } // } // } // } // CavaService { // id: cavaService // onCavaDataChanged: data => { // visualizer.updateData(data); // } // } }