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:
ClericalErrorandClaude Opus 5.5 committed 2026-10-14 21:17:17 -07:00
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
}
}
}