StemRoller: desktop stem separator, Android stems player, and server daemons
- 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>
This commit is contained in:
1 parent
716c5499ff
commit
1845f03c41
184 files changed
+24648
No files matched your search
@@ -0,0 +1,74 @@
|
||||
import QtQuick
|
||||
|
||||
// Dev overlay: p50/p99 frame interval (and render-thread work with --render-hooks).
|
||||
// While visible, the heartbeat bar changes every frame so the window renders at
|
||||
// full refresh and the numbers describe real 240Hz cadence, not idle frames.
|
||||
Rectangle {
|
||||
id: o
|
||||
property var s: ({})
|
||||
property real acc: 0
|
||||
property var batch: []
|
||||
property real beat: 0
|
||||
width: 236
|
||||
height: col.implicitHeight + 24
|
||||
radius: 8
|
||||
color: Theme.alpha(Theme.surface1, 0.92)
|
||||
border.color: Theme.outline
|
||||
readonly property color good: Theme.success
|
||||
readonly property color bad: Theme.error
|
||||
|
||||
onVisibleChanged: frameStats.setHooks(visible)
|
||||
FrameAnimation {
|
||||
running: o.visible
|
||||
onTriggered: {
|
||||
o.batch.push(frameTime * 1000)
|
||||
if (frameTime * 1000 > 1.5 * (o.s.budget_ms || 4.17)) frameStats.miss(frameTime * 1000)
|
||||
o.beat = (o.beat + frameTime) % 1
|
||||
o.acc += frameTime
|
||||
if (o.acc > 0.25) {
|
||||
o.acc = 0
|
||||
frameStats.push(o.batch)
|
||||
o.batch = []
|
||||
o.s = frameStats.summary()
|
||||
}
|
||||
}
|
||||
}
|
||||
Rectangle { // heartbeat: moves every frame
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: 3
|
||||
x: 8 + (parent.width - 16 - width) * o.beat
|
||||
width: 24
|
||||
height: 2
|
||||
radius: 1
|
||||
color: Theme.accentGlow
|
||||
opacity: 0.6
|
||||
}
|
||||
Column {
|
||||
id: col
|
||||
anchors { left: parent.left; top: parent.top; margins: 10 }
|
||||
spacing: 3
|
||||
Txt {
|
||||
text: (o.s.refresh_hz || 0).toFixed(0) + " Hz panel · " + (o.s.fps || 0).toFixed(1) + " fps"
|
||||
font.pixelSize: Theme.fs12
|
||||
font.weight: Font.DemiBold
|
||||
font.features: { "tnum": 1 }
|
||||
color: (o.s.fps || 0) >= (o.s.refresh_hz || 240) * 0.97 ? o.good : Theme.warning
|
||||
}
|
||||
Txt {
|
||||
text: o.s.work_p50 === null || o.s.work_p50 === undefined ? "frame time measuring…"
|
||||
: "frame time p50 " + o.s.work_p50.toFixed(2) + " p99 " + o.s.work_p99.toFixed(2) + " ms"
|
||||
font.pixelSize: 11; font.features: { "tnum": 1 }
|
||||
color: (o.s.work_p99 || 0) < (o.s.budget_ms || 4.17) ? Theme.textHi : o.bad
|
||||
}
|
||||
Txt {
|
||||
text: "interval p50 " + (o.s.interval_p50 || 0).toFixed(2) + " p99 " + (o.s.interval_p99 || 0).toFixed(2) + " ms"
|
||||
font.pixelSize: 11; font.features: { "tnum": 1 }
|
||||
color: Theme.textLo
|
||||
}
|
||||
Txt {
|
||||
text: "budget " + (o.s.budget_ms || 0).toFixed(2) + " ms · missed " + (o.s.missed || 0) + "/" + (o.s.frames || 0)
|
||||
font.pixelSize: 11; font.features: { "tnum": 1 }
|
||||
color: (o.s.missed || 0) === 0 ? Theme.textLo : o.bad
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user