Rename everything to Stemify, and add a README
- Python package stemroller/ -> stemify/, Android package com.stemroller.mobile -> com.stemify.mobile, app names, QML module, icons, desktop entries, MPRIS and Plex product names - Done-markers are now .stemify.json; stemd renames the old ones once at startup so songs separated before the rename aren't separated again - A top-level README covering the server, the Android and Android Auto app, the EQ, Stem Now and the desktop apps Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
1845f03c41
commit
45bc0eefe0
159 files changed
+1136
-542
No files matched your search
@@ -0,0 +1,147 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls.Basic
|
||||
|
||||
// One lane per stem: color strip, solo / mute / gain / export, waveform texture.
|
||||
Item {
|
||||
id: lane
|
||||
required property int index
|
||||
required property var modelData
|
||||
property string jobId: ""
|
||||
property real viewStart: 0
|
||||
property real viewEnd: 1
|
||||
property int waveW: 100
|
||||
property int waveH: 60
|
||||
property real waveX: 0
|
||||
property bool anySolo: false
|
||||
property bool solo: false
|
||||
property bool mute: false
|
||||
property real gainDb: 0
|
||||
signal soloToggled(bool on)
|
||||
readonly property string stem: modelData.name
|
||||
readonly property color tone: modelData.color
|
||||
readonly property bool audible: anySolo ? solo : !mute
|
||||
height: 92
|
||||
|
||||
Card {
|
||||
anchors.fill: parent
|
||||
clip: false
|
||||
|
||||
// color strip with soft glow
|
||||
Rectangle {
|
||||
x: 0
|
||||
width: 4
|
||||
height: parent.height
|
||||
topLeftRadius: 8
|
||||
bottomLeftRadius: 8
|
||||
color: lane.tone
|
||||
}
|
||||
Rectangle {
|
||||
x: 4
|
||||
width: 24
|
||||
height: parent.height
|
||||
gradient: Gradient {
|
||||
orientation: Gradient.Horizontal
|
||||
GradientStop { position: 0; color: Theme.alpha(lane.tone, 0.14) }
|
||||
GradientStop { position: 1; color: Theme.alpha(lane.tone, 0.0) }
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
id: controls
|
||||
x: 18
|
||||
width: lane.waveX - 30
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: 6
|
||||
Row {
|
||||
spacing: 6
|
||||
width: parent.width
|
||||
Txt {
|
||||
width: parent.width - 3 * 30 - 12
|
||||
text: lane.stem.charAt(0).toUpperCase() + lane.stem.slice(1)
|
||||
font.pixelSize: Theme.fs16
|
||||
font.weight: Font.DemiBold
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
IconButton {
|
||||
size: 30; iconSize: 16
|
||||
iconName: "headphones"
|
||||
checkable: true
|
||||
checked: lane.solo
|
||||
activeColor: Theme.warning
|
||||
tip: "Solo"
|
||||
onClicked: lane.soloToggled(checked)
|
||||
}
|
||||
IconButton {
|
||||
size: 30; iconSize: 16
|
||||
iconName: lane.mute ? "volume_off" : "volume"
|
||||
checkable: true
|
||||
checked: lane.mute
|
||||
activeColor: Theme.error
|
||||
tip: "Mute"
|
||||
onClicked: { lane.mute = checked; audio.setMute(lane.index, checked) }
|
||||
}
|
||||
IconButton {
|
||||
size: 30; iconSize: 16
|
||||
iconName: "download"
|
||||
tip: "Export " + lane.stem
|
||||
onClicked: app.exportStem(lane.stem)
|
||||
}
|
||||
}
|
||||
Row {
|
||||
spacing: 8
|
||||
width: parent.width
|
||||
AeroSlider {
|
||||
id: gain
|
||||
width: parent.width - 52
|
||||
from: -24
|
||||
to: 12
|
||||
stepSize: 0.5
|
||||
value: lane.gainDb
|
||||
tone: lane.tone
|
||||
onMoved: { lane.gainDb = value; audio.setGain(lane.index, value) }
|
||||
// double-click resets to unity
|
||||
TapHandler { onDoubleTapped: { lane.gainDb = 0; audio.setGain(lane.index, 0) } }
|
||||
}
|
||||
Txt {
|
||||
width: 44
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
horizontalAlignment: Text.AlignRight
|
||||
text: (lane.gainDb > 0 ? "+" : "") + lane.gainDb.toFixed(1) + " dB"
|
||||
font.pixelSize: 11
|
||||
font.features: { "tnum": 1 }
|
||||
color: lane.gainDb === 0 ? Theme.textLo : Theme.textHi
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle { // waveform well
|
||||
x: lane.waveX
|
||||
y: 8
|
||||
width: lane.waveW
|
||||
height: parent.height - 16
|
||||
radius: 6
|
||||
color: Qt.rgba(0, 0, 0, 0.22)
|
||||
border.color: Qt.rgba(1, 1, 1, 0.04)
|
||||
Rectangle {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: parent.width
|
||||
height: 1
|
||||
color: Theme.alpha(lane.tone, 0.18)
|
||||
}
|
||||
Image {
|
||||
anchors.fill: parent
|
||||
asynchronous: true
|
||||
retainWhileLoading: true
|
||||
cache: false
|
||||
smooth: true
|
||||
opacity: lane.audible ? 1 : 0.28
|
||||
Behavior on opacity { NumberAnimation { duration: Theme.med } }
|
||||
source: lane.jobId && lane.waveW > 4
|
||||
? "image://wave/" + lane.jobId + "/" + lane.stem + "/"
|
||||
+ Math.round(lane.waveW * Screen.devicePixelRatio) + "x" + Math.round((lane.height - 16) * Screen.devicePixelRatio)
|
||||
+ "/" + lane.viewStart.toFixed(3) + "/" + lane.viewEnd.toFixed(3) + "/" + Theme.hex(lane.tone)
|
||||
: ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user