- stemroller/: PySide6/QML desktop app and player for separating music into stems - android/: Plex music player that plays per-stem mixes, with Android Auto support, a graphic + parametric (Equalizer APO-style) EQ, and offline downloads - server/: stemserve (serves stems to the app) and stemd (separates the library on the server's GPU, with "Stem Now" priority requests) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
74 lines
2.6 KiB
QML
74 lines
2.6 KiB
QML
import QtQuick
|
|
|
|
// Stereo peak meter fed per frame from the audio ring buffer (via Main).
|
|
Item {
|
|
id: m
|
|
property real levelLeft: 0
|
|
property real levelRight: 0
|
|
property real holdL: 0
|
|
property real holdR: 0
|
|
implicitWidth: 150
|
|
implicitHeight: 22
|
|
|
|
function frac(v) {
|
|
if (v <= 0.000001) return 0
|
|
var db = 20 * Math.log(v) / Math.LN10
|
|
return Math.max(0, Math.min(1, (db + 54) / 54))
|
|
}
|
|
onLevelLeftChanged: holdL = Math.max(holdL, frac(levelLeft))
|
|
onLevelRightChanged: holdR = Math.max(holdR, frac(levelRight))
|
|
FrameAnimation {
|
|
running: m.holdL > 0.001 || m.holdR > 0.001
|
|
onTriggered: {
|
|
var d = frameTime * 0.35
|
|
m.holdL = Math.max(m.frac(m.levelLeft), m.holdL - d)
|
|
m.holdR = Math.max(m.frac(m.levelRight), m.holdR - d)
|
|
}
|
|
}
|
|
|
|
Column {
|
|
anchors.fill: parent
|
|
spacing: 4
|
|
Repeater {
|
|
model: 2
|
|
Item {
|
|
required property int index
|
|
readonly property real level: m.frac(index === 0 ? m.levelLeft : m.levelRight)
|
|
readonly property real hold: index === 0 ? m.holdL : m.holdR
|
|
width: m.width
|
|
height: (m.height - 4) / 2
|
|
Rectangle {
|
|
anchors.fill: parent
|
|
radius: 2
|
|
color: Qt.rgba(1, 1, 1, 0.07)
|
|
}
|
|
Item {
|
|
width: parent.width * parent.level
|
|
height: parent.height
|
|
clip: true
|
|
Rectangle {
|
|
width: m.width
|
|
height: parent.height
|
|
radius: 2
|
|
gradient: Gradient {
|
|
orientation: Gradient.Horizontal
|
|
GradientStop { position: 0.0; color: Theme.success }
|
|
GradientStop { position: 0.72; color: Theme.accentGlow }
|
|
GradientStop { position: 0.88; color: Theme.warning }
|
|
GradientStop { position: 1.0; color: Theme.error }
|
|
}
|
|
}
|
|
}
|
|
Rectangle {
|
|
visible: parent.hold > 0.01
|
|
x: Math.min(parent.width - 2, parent.width * parent.hold)
|
|
width: 2
|
|
height: parent.height
|
|
color: parent.hold > 0.95 ? Theme.error : Theme.textHi
|
|
opacity: 0.85
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|