Files
stemify/stemroller/settings.py
T
ClericalErrorandClaude Opus 5.5 1845f03c41 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>
2026-10-14 21:17:17 -07:00

79 lines
2.3 KiB
Python

"""Persistent settings exposed to QML as plain properties."""
import json
import os
from PySide6.QtCore import Property, QObject, Signal
CONFIG_PATH = os.path.join(os.path.expanduser("~/.config/stemroller"), "settings.json")
DEFAULTS = {
"modelId": "htdemucs",
"twoStems": False,
"deviceId": "auto",
"outputMode": "beside", # "beside" the source file, or "folder" (stemsDir)
"stemsDir": os.path.expanduser("~/Music/Stems"),
"exportFormat": "wav", # wav | flac | mp3
"segment": 0.0, # seconds; 0 = model default
"shifts": 0,
"overlap": 0.25,
"fp16": True,
"autoExport": False,
"frameOverlay": False,
"cacheLimitGb": 8.0,
"masterVolume": 1.0, # slider position 0..1 (cubic taper in the audio engine)
}
class _SettingsBase(QObject):
def __init__(self, parent=None):
super().__init__(parent)
self._values = dict(DEFAULTS)
try:
with open(CONFIG_PATH) as f:
stored = json.load(f)
for k, v in stored.items():
if k not in DEFAULTS:
continue
want = type(DEFAULTS[k])
if type(v) is want or (want is float and type(v) is int):
self._values[k] = want(v)
except (OSError, ValueError):
pass
def save(self):
os.makedirs(os.path.dirname(CONFIG_PATH), exist_ok=True)
tmp = CONFIG_PATH + ".tmp"
with open(tmp, "w") as f:
json.dump(self._values, f, indent=2)
os.replace(tmp, CONFIG_PATH)
def get(self, name):
return self._values[name]
def _build():
# Qt only sees properties and signals present when the class is created,
# so generate one Property + <name>Changed signal per default up front.
ns = {}
for name, default in DEFAULTS.items():
type_ = type(default)
sig = Signal()
ns[f"{name}Changed"] = sig
def getter(self, _n=name):
return self._values[_n]
def setter(self, value, _n=name, _t=type_):
value = _t(value)
if self._values[_n] != value:
self._values[_n] = value
getattr(self, f"{_n}Changed").emit()
self.save()
ns[name] = Property(type_, getter, setter, notify=sig)
return type("Settings", (_SettingsBase,), ns)
Settings = _build()