rename: slopsmith → feedBack, byron → got-feedBack (#537)

* Update GitHub repo references from feedback* to feedBack*

* rename: slopsmith -> feedBack, byron -> got-feedBack

Renames across the entire codebase:
- slopsmith/Slopsmith/SLOPSMITH/SlopSmith -> feedBack/FeedBack/FEEDBACK/FeedBack
- byron/Byron/Byrongamatos -> got-feedBack/got-feedBack/got-feedBack
- /home/byron/ -> /opt/got-feedBack/
- byron@ougsoft.com -> hi@got-feedBack.org
- github.com/byrongamatos/ -> github.com/got-feedback/
- com.byron. -> com.got-feedback.
- SLOPSMITH_ env vars -> FEEDBACK_ with backward-compat fallback
- Protocol/storage strings migrated with read-old/write-new pattern
- window.slopsmith JS API -> window.feedBack (canonical) + backward-compat alias

Refs: #rename-slopsmith

* rename: complete regen against current main + fix backward-compat alias

Regenerated the slopsmith->feedBack / byron->got-feedBack rename on top of
current main (3 commits had landed since the branch: #572/#554/#574),
resolving the four content conflicts in favour of main's newer content
(autoplay/auto-exit, accuracy-badge, Virtuoso re-home, feedpak badge).

Completion fixes on top of the mechanical rename:
- Re-apply rename to post-branch content the original rename never saw:
  window.slopsmith(.Tour) consumers in lessons.js / notifications.js /
  onboarding-tour.js, and the matching JS + python tests (autoplay_exit,
  progression_*, test_feedpak_extension FEEDBACK_* env vars). The test env
  vars now match server.py (which reads FEEDBACK_SYNC_STARTUP /
  FEEDBACK_SKIP_STARTUP_TASKS), so the sync-startup test exercises the real
  path again.
- Restore the window.slopsmith backward-compat alias dropped during conflict
  resolution, and move the bus aliases to AFTER the _feedBackExisting merge
  block so they reference the fully-assembled object (also fixes the
  loop_api.test.js API-surface regex, which the original PR latently broke).
- Drop the stray empty data/web_library.db (runtime DB lives in CONFIG_DIR)
  and gitignore it.
- Fix stale tone-source test: feed[dB]ack -> fee[dB]ack to match shipped
  source labels.

Verified locally (org CI billing-blocked): JS 819/819 pass; pytest 1669
passed / 1683 collected with 0 import errors; zero residual slopsmith/byron
except the two intentional window.slopsmith aliases.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* rename: implement advertised backward-compat + prune dead community plugins

Address gaps where PR #537's "Backward compatibility" section was advertised
but not implemented, and clean up the community plugin list.

Env vars (FEEDBACK_* canonical, legacy SLOPSMITH_* honoured):
- New lib/env_compat.py (getenv_compat / env_flag_compat) + tests. server.py
  (_env_flag + all FEEDBACK_* reads), diagnostics_hardware, gp2midi and
  tailwind_rebuild now resolve the legacy alias, so existing SLOPSMITH_UI /
  SLOPSMITH_PLUGINS_DIR / etc. deployments keep working.
- Fix the rename collapsing plugins/__init__.py and minigames/routes.py from
  `FEEDBACK_PLUGINS_DIR or SLOPSMITH_PLUGINS_DIR` into a redundant
  `FEEDBACK_ or FEEDBACK_` (the fallback was silently lost).

Storage (app.js update-channel):
- Read feedBack-update-channel, fall back to legacy slopsmith-update-channel,
  and clear the legacy key on write — so a user's update-channel preference
  survives the rename instead of resetting to "stable".

Community plugin list (README): the rename rewrote third-party repo URLs we
don't own. Probed every one; their owners never renamed, so:
- Restore the 13 live community plugins to their real slopsmith-* names.
- Prune 6 that are 404 to the public (topkoa splitscreen/stems, OmikronApex
  tuner, Jafz2001 nam-rig-builder, DeathlySin song-preview, Erikcb91 shuffle).
- Fix a pre-existing Guitar Theory clone-command typo (nam-tone -> guitar-theory).

Verified: env_compat 7/7, JS 819/819, pytest 1690 collected / 0 import errors,
rename-sensitive + startup suites green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: byrongamatos <xasiklas@gmail.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Bret Mogilefsky
2026-06-23 11:03:01 +02:00
committed by GitHub
co-authored by Claude Opus 4.8 byrongamatos
parent a8ad02739a
commit af2949677a
257 changed files with 2389 additions and 2293 deletions
+3 -3
View File
@@ -7,7 +7,7 @@ import shutil
import subprocess
from pathlib import Path
log = logging.getLogger("slopsmith.lib.audio")
log = logging.getLogger("feedBack.lib.audio")
# Maximum length of any single decoder-error fragment that we surface to
# the client. ffmpeg can emit multi-kB build-configuration / version
@@ -123,7 +123,7 @@ def _scrub_quoted_match(match: re.Match) -> str:
def _bundled_bin_dir() -> Path | None:
"""Resolve the desktop bundle's resources/bin/ directory if we're
running inside one. Layout: resources/slopsmith/lib/audio.py →
running inside one. Layout: resources/feedBack/lib/audio.py →
resources/bin/. Gate on vgmstream-cli's presence so we don't
misidentify random parent dirs (e.g. Docker's `/bin`, dev
layouts where parents[2] resolves to the repo root) — vgmstream-cli
@@ -284,7 +284,7 @@ def _scrub_paths(text: str, *paths: str) -> str:
"""Replace absolute filesystem paths in `text` with their basenames.
Decoder error strings get joined into the RuntimeError that
`convert_wem` raises, and slopsmith surfaces that text in the
`convert_wem` raises, and feedBack surfaces that text in the
browser as `audio_error`. Leaking install / user / DLC paths to the
client is a needless info disclosure, so before any decoder error
leaves this module we strip absolute paths down to their final
+24 -24
View File
@@ -130,7 +130,7 @@ ENV_ALLOWLIST = (
"LOG_LEVEL",
"LOG_FORMAT",
"LOG_FILE",
"SLOPSMITH_RUNTIME",
"FEEDBACK_RUNTIME",
"PORT",
"HOST",
"TZ",
@@ -154,13 +154,13 @@ def _safe_json_dumps(obj) -> str:
return json.dumps({"error": "unserializable payload"}, indent=2)
def _system_version(slopsmith_version: str, redactor=None) -> dict:
def _system_version(feedBack_version: str, redactor=None) -> dict:
executable = sys.executable
if redactor is not None:
executable = redactor.redact_text(executable)
return {
"schema": "system.version.v1",
"slopsmith_version": slopsmith_version,
"feedBack_version": feedBack_version,
"python": {
"version": platform.python_version(),
"implementation": platform.python_implementation(),
@@ -233,7 +233,7 @@ def _summarize_payload(path: str, parsed) -> dict | None:
py = parsed.get("python") or {}
os_ = parsed.get("os") or {}
return {
"slopsmith": parsed.get("slopsmith_version"),
"feedBack": parsed.get("feedBack_version"),
"python": py.get("version"),
"os": os_.get("system"),
}
@@ -338,7 +338,7 @@ def _git_info(plugin_dir: Path) -> dict | None:
"""Return git short SHA + remote URL for a plugin checkout.
Pure-Python — reads `.git/HEAD` and `.git/config` directly so this
works in containers without the `git` binary installed (slopsmith's
works in containers without the `git` binary installed (feedBack's
runtime image is minimal). Plugins are gitlinks (see CLAUDE.md);
the SHA is the most reliable "what build is this" identifier.
@@ -393,7 +393,7 @@ def _system_plugins(loaded_plugins: list[dict], plugins_root: "Path | list[Path]
show up in the bundle.
*plugins_root* accepts a single Path, a list of Paths (to cover both
the built-in ``plugins/`` directory and ``SLOPSMITH_PLUGINS_DIR``), or
the built-in ``plugins/`` directory and ``FEEDBACK_PLUGINS_DIR``), or
None to skip orphan detection entirely.
Plugin directories not in ``LOADED_PLUGINS`` appear in ``orphans``.
@@ -484,7 +484,7 @@ def _system_plugins(loaded_plugins: list[dict], plugins_root: "Path | list[Path]
# plugin failed to load — common when requirements.txt installs
# fail in a read-only container). Accepts a single Path, a list of
# Paths (to cover both the built-in plugins/ dir and
# SLOPSMITH_PLUGINS_DIR), or None.
# FEEDBACK_PLUGINS_DIR), or None.
orphans: list[dict] = []
if plugins_root is not None:
roots: list[Path] = plugins_root if isinstance(plugins_root, list) else [plugins_root]
@@ -840,11 +840,11 @@ def _redact_value(value: object, redactor: "Redactor") -> object:
README_TEMPLATE = """\
Slopsmith Diagnostics Bundle
FeedBack Diagnostics Bundle
============================
Generated: {exported_at}
Slopsmith: {slopsmith_version}
FeedBack: {feedBack_version}
Runtime: {runtime_kind}
Redacted: {redacted}
@@ -1005,7 +1005,7 @@ def _build_files_meta(files: dict[str, bytes]) -> list[dict]:
def _assemble_files_and_notes(
*,
slopsmith_version: str,
feedBack_version: str,
config_dir: Path,
dlc_dir: Path | None,
log_file: Path | None,
@@ -1038,7 +1038,7 @@ def _assemble_files_and_notes(
if include.get("system", True):
# Pass the redactor so python.executable is redacted when paths
# should be hidden (it often lives under $HOME or a per-user venv).
ver_payload = _safe_json_dumps(_system_version(slopsmith_version, redactor=redactor)).encode("utf-8")
ver_payload = _safe_json_dumps(_system_version(feedBack_version, redactor=redactor)).encode("utf-8")
files["system/version.json"] = ver_payload
env_payload = _safe_json_dumps(_system_env(redactor=redactor)).encode("utf-8")
files["system/env.json"] = env_payload
@@ -1125,7 +1125,7 @@ def _assemble_files_and_notes(
files.update(plugin_files)
# Per-plugin client-side contributions from
# window.slopsmith.diagnostics.contribute(plugin_id, payload).
# window.feedBack.diagnostics.contribute(plugin_id, payload).
# Gated on the same "plugins" toggle as backend plugin diagnostics.
if include.get("plugins", True) and client_contributions and isinstance(client_contributions, dict):
# Build the set of actually-loaded plugin IDs so we only accept
@@ -1160,7 +1160,7 @@ def _assemble_files_and_notes(
def _make_manifest(
*,
slopsmith_version: str,
feedBack_version: str,
runtime_kind: str,
redact: bool,
files: dict[str, bytes],
@@ -1170,7 +1170,7 @@ def _make_manifest(
return {
"schema": BUNDLE_SCHEMA,
"exported_at": _now_iso(),
"slopsmith_version": slopsmith_version,
"feedBack_version": feedBack_version,
"runtime": runtime_kind,
"redacted": redact,
"files": _build_files_meta(files),
@@ -1181,7 +1181,7 @@ def _make_manifest(
def build_bundle(
*,
slopsmith_version: str,
feedBack_version: str,
config_dir: Path,
dlc_dir: Path | None,
log_file: Path | None,
@@ -1198,7 +1198,7 @@ def build_bundle(
) -> tuple[bytes, str, dict]:
"""Returns (zip_bytes, filename, manifest_dict)."""
files, notes, runtime_kind, redactor = _assemble_files_and_notes(
slopsmith_version=slopsmith_version,
feedBack_version=feedBack_version,
config_dir=config_dir,
dlc_dir=dlc_dir,
log_file=log_file,
@@ -1215,7 +1215,7 @@ def build_bundle(
)
manifest = _make_manifest(
slopsmith_version=slopsmith_version,
feedBack_version=feedBack_version,
runtime_kind=runtime_kind,
redact=redact,
files=files,
@@ -1225,7 +1225,7 @@ def build_bundle(
readme = README_TEMPLATE.format(
exported_at=manifest["exported_at"],
slopsmith_version=slopsmith_version,
feedBack_version=feedBack_version,
runtime_kind=runtime_kind,
redacted=redact,
)
@@ -1259,13 +1259,13 @@ def build_bundle(
for path, payload in sorted(files.items()):
zf.writestr(path, payload)
filename = f"slopsmith-diag-{slopsmith_version}-{_now_filename_slug()}.zip"
filename = f"feedBack-diag-{feedBack_version}-{_now_filename_slug()}.zip"
return buf.getvalue(), filename, manifest
def preview_bundle(
*,
slopsmith_version: str,
feedBack_version: str,
config_dir: Path,
dlc_dir: Path | None,
log_file: Path | None,
@@ -1303,7 +1303,7 @@ def preview_bundle(
for p in loaded_plugins
]
files, notes, runtime_kind, redactor = _assemble_files_and_notes(
slopsmith_version=slopsmith_version,
feedBack_version=feedBack_version,
config_dir=config_dir,
dlc_dir=dlc_dir,
log_file=log_file,
@@ -1336,7 +1336,7 @@ def preview_bundle(
if key not in files:
files[key] = _CALLABLE_PREVIEW_PLACEHOLDER
# Frontend plugins (those with a screen or script) may call
# window.slopsmith.diagnostics.contribute() and produce a
# window.feedBack.diagnostics.contribute() and produce a
# plugins/<id>/client.json in the real export. Advertise a
# placeholder so the preview file tree is accurate.
if p.get("has_screen") or p.get("has_script"):
@@ -1377,14 +1377,14 @@ def preview_bundle(
}).encode("utf-8")
manifest = _make_manifest(
slopsmith_version=slopsmith_version,
feedBack_version=feedBack_version,
runtime_kind=runtime_kind,
redact=redact,
files=files,
notes=notes,
redactor=redactor,
)
filename = f"slopsmith-diag-{slopsmith_version}-{_now_filename_slug()}.zip"
filename = f"feedBack-diag-{feedBack_version}-{_now_filename_slug()}.zip"
return {
"filename": filename,
"manifest": manifest,
+4 -2
View File
@@ -16,6 +16,8 @@ import platform
import subprocess
from pathlib import Path
from env_compat import getenv_compat
SCHEMA = "system.hardware.v1"
@@ -41,7 +43,7 @@ def detect_runtime() -> dict:
nvidia-smi / psutil CPU probes.
"""
out: dict = {"kind": "bare", "in_docker": False, "in_kubernetes": False}
env_runtime = os.environ.get("SLOPSMITH_RUNTIME", "").strip().lower()
env_runtime = (getenv_compat("FEEDBACK_RUNTIME", "") or "").strip().lower()
if env_runtime in ("electron", "docker", "bare"):
out["kind"] = env_runtime
if Path("/.dockerenv").exists():
@@ -65,7 +67,7 @@ def detect_runtime() -> dict:
import psutil # type: ignore
parent = psutil.Process(os.getppid()).name().lower()
if "electron" in parent or "slopsmith" in parent:
if "electron" in parent or "feedBack" in parent:
out["kind"] = "electron"
except Exception:
pass
+1 -1
View File
@@ -8,7 +8,7 @@ different salts so tokens cannot be cross-correlated between exports.
Stable token grammar (see docs/diagnostics-bundle-spec.md):
<DLC_DIR> — DLC root path
<HOME> — user's home directory
<CONFIG_DIR> — slopsmith config dir
<CONFIG_DIR> — feedBack config dir
<song:hash8> — song filename / basename (8 hex chars)
<ip:hash6> — IPv4 / IPv6 address (6 hex chars)
<redacted> — bearer tokens, key=/token= query strings
+1 -1
View File
@@ -21,7 +21,7 @@ from __future__ import annotations
import logging
import math
log = logging.getLogger("slopsmith.lib.drums")
log = logging.getLogger("feedBack.lib.drums")
# ── Piece vocabulary ──────────────────────────────────────────────────────────
+38
View File
@@ -0,0 +1,38 @@
"""Backward-compatible environment lookup for the slopsmith -> feedBack rename.
Canonical configuration variables are now ``FEEDBACK_*``. Deployments that
predate the rename may still set the old ``SLOPSMITH_*`` names (docker-compose
overrides, shell profiles, CI), so we honour those as a fallback. New code
should always read the canonical ``FEEDBACK_*`` name and let this shim resolve
the legacy alias.
Flat-importable, no import-time IO or global state (constitution P-V).
"""
import os
_CANON_PREFIX = "FEEDBACK_"
_LEGACY_PREFIX = "SLOPSMITH_"
_TRUE_VALUES = {"1", "true", "yes", "on"}
def getenv_compat(name, default=None):
"""``os.environ.get`` with a legacy ``SLOPSMITH_*`` fallback.
For a canonical ``FEEDBACK_<X>`` name, returns the value of ``FEEDBACK_<X>``
if set, else ``SLOPSMITH_<X>`` if set, else ``default``. Names that do not
start with ``FEEDBACK_`` behave exactly like ``os.environ.get``.
"""
value = os.environ.get(name)
if value is not None:
return value
if name.startswith(_CANON_PREFIX):
legacy = os.environ.get(_LEGACY_PREFIX + name[len(_CANON_PREFIX):])
if legacy is not None:
return legacy
return default
def env_flag_compat(name):
"""Parse a conventional boolean env flag, honouring the legacy alias."""
return (getenv_compat(name, "") or "").strip().lower() in _TRUE_VALUES
+12 -10
View File
@@ -8,7 +8,9 @@ import sys
import tempfile
from pathlib import Path
log = logging.getLogger("slopsmith.lib.gp2midi")
from env_compat import getenv_compat
log = logging.getLogger("feedBack.lib.gp2midi")
import guitarpro
from midiutil import MIDIFile
@@ -152,15 +154,15 @@ def _find_soundfont() -> str | None:
"""Locate a .sf2 soundfont for MIDI rendering.
Precedence:
1. ``SLOPSMITH_SOUNDFONT`` env var (user override / desktop-app-supplied)
1. ``FEEDBACK_SOUNDFONT`` env var (user override / desktop-app-supplied)
2. Bundled ``<RESOURCESPATH>/soundfonts/*.sf2`` (Electron desktop builds)
3. Common system locations per OS.
"""
override = os.environ.get("SLOPSMITH_SOUNDFONT")
override = getenv_compat("FEEDBACK_SOUNDFONT")
if override:
if os.path.isfile(override):
return override
log.warning("SLOPSMITH_SOUNDFONT is set to %r but that file does not exist; falling back to other sources", override)
log.warning("FEEDBACK_SOUNDFONT is set to %r but that file does not exist; falling back to other sources", override)
resources = os.environ.get("RESOURCESPATH")
if resources:
@@ -187,10 +189,10 @@ def _find_soundfont() -> str | None:
elif sys.platform == "win32":
appdata = os.environ.get("APPDATA")
if appdata:
# "Slopsmith" matches slopsmith-desktop's Electron productName
# (app.getPath('userData') resolves to %APPDATA%\Slopsmith on Windows).
# "FeedBack" matches feedBack-desktop's Electron productName
# (app.getPath('userData') resolves to %APPDATA%\FeedBack on Windows).
for pattern in (
os.path.join(appdata, "Slopsmith", "soundfonts", "*.sf2"),
os.path.join(appdata, "FeedBack", "soundfonts", "*.sf2"),
os.path.join(appdata, "SoundFonts", "*.sf2"),
):
candidates += sorted(glob.glob(pattern))
@@ -218,16 +220,16 @@ def _soundfont_install_hint() -> str:
"or FluidR3_GM from musical-artifacts.com) and either place the .sf2 "
"file in /usr/local/share/sounds/sf2/ (Intel) or "
"/opt/homebrew/share/sounds/sf2/ (Apple Silicon), or set the "
"SLOPSMITH_SOUNDFONT environment variable to its full path."
"FEEDBACK_SOUNDFONT environment variable to its full path."
)
if sys.platform == "win32":
return (
"Download a soundfont (e.g. GeneralUser GS from schristiancollins.com or "
"FluidR3_GM from musical-artifacts.com) and either place the .sf2 file in "
"%APPDATA%\\Slopsmith\\soundfonts\\ or set the SLOPSMITH_SOUNDFONT "
"%APPDATA%\\FeedBack\\soundfonts\\ or set the FEEDBACK_SOUNDFONT "
"environment variable to its full path."
)
return "Set SLOPSMITH_SOUNDFONT to the full path of a .sf2 file."
return "Set FEEDBACK_SOUNDFONT to the full path of a .sf2 file."
def _fluidsynth_install_hint() -> str:
+3 -3
View File
@@ -19,8 +19,8 @@ bar-indexed tempo map, per-beat rhythm durations (dots + tuplets; see
``_beat_secs`` for the one deliberate double-dot divergence), and
``_note_midi`` — so the
notation beats line up with the RS-XML notes the highway plays (see
slopsmith#618 for the longer-term goal of sharing the note-building walk
itself, and slopsmith#261 for the time-signature-denominator pitfalls the
feedBack#618 for the longer-term goal of sharing the note-building walk
itself, and feedBack#261 for the time-signature-denominator pitfalls the
``beat_groups`` emission here exists to avoid re-introducing).
Where this plugs in: ``gp2rs_gpx.convert_file`` calls
@@ -43,7 +43,7 @@ from pathlib import Path
import notation as notation_mod
log = logging.getLogger("slopsmith.lib.gp2notation")
log = logging.getLogger("feedBack.lib.gp2notation")
# GPX NoteValue string → notation duration denominator (sloppak-spec §5.3:
+2 -2
View File
@@ -10,7 +10,7 @@ from pathlib import Path
import guitarpro
log = logging.getLogger("slopsmith.lib.gp2rs")
log = logging.getLogger("feedBack.lib.gp2rs")
_YEAR_RE = re.compile(r"\b(1[89]\d{2}|20\d{2})\b")
@@ -1122,7 +1122,7 @@ def _build_xml(
# Tuning. RS2014 schema names 6 string slots; we always emit those
# for compatibility, and emit additional string6+ attributes (up to
# `len(tuning)-1`) for 7+ string arrangements. Slopsmith parses
# `len(tuning)-1`) for 7+ string arrangements. FeedBack parses
# them; the format ignores them.
tuning_el = ET.SubElement(root, "tuning")
for i in range(max(6, len(tuning))):
+2 -2
View File
@@ -1,7 +1,7 @@
"""
lib/gp2rs_gpx.py — Guitar Pro 6 (.gpx) support shim for gp2rs.
Drop this file into slopsmith/lib/ alongside gp2rs.py.
Drop this file into feedBack/lib/ alongside gp2rs.py.
No third-party dependencies — pure Python stdlib only.
Public API mirrors the two functions that the editor plugin calls:
@@ -20,7 +20,7 @@ from pathlib import Path
from safepath import safe_join
_log = logging.getLogger("slopsmith.lib.gp2rs_gpx")
_log = logging.getLogger("feedBack.lib.gp2rs_gpx")
def _safe_filename_stem(name: str) -> str:
+2 -2
View File
@@ -3,7 +3,7 @@ lib/gp8_audio_sync.py — Extract embedded audio and sync data from GP8 (.gp) fi
Guitar Pro 8 can embed a backing track (OGG audio) into a .gp file alongside
sync points that map bar positions to exact audio timestamps. This module
extracts both, giving Slopsmith:
extracts both, giving FeedBack:
1. A real backing track audio file (OGG) no MIDI synthesis needed
2. A precise audio_offset (seconds) from the FramePadding value
@@ -45,7 +45,7 @@ import io
from dataclasses import dataclass, field
from pathlib import Path
_log = logging.getLogger("slopsmith.lib.gp8_audio_sync")
_log = logging.getLogger("feedBack.lib.gp8_audio_sync")
# GP8 embeds the backing track under Content/Assets/ as OGG *or* one of
# several other formats (MP3 is common — e.g. tracks rendered straight
+1 -1
View File
@@ -30,7 +30,7 @@ import zipfile
import io
from pathlib import Path
_log = logging.getLogger("slopsmith.lib.gp_autosync")
_log = logging.getLogger("feedBack.lib.gp_autosync")
# ── Dependency check ──────────────────────────────────────────────────────────
+11 -11
View File
@@ -1,10 +1,10 @@
"""Logging configuration for Slopsmith.
"""Logging configuration for FeedBack.
Call ``configure_logging()`` once at server startup, before any slopsmith
Call ``configure_logging()`` once at server startup, before any feedBack
module imports that might emit log records.
Environment variables:
LOG_LEVEL severity threshold for the ``slopsmith.*`` logger tree
LOG_LEVEL severity threshold for the ``feedBack.*`` logger tree
(default: INFO). Also accepted: DEBUG, WARNING, ERROR.
LOG_FORMAT "json" for structured output (Loki, ELK, Promtail);
"text" (default) for human-readable coloured console output.
@@ -43,7 +43,7 @@ def _add_correlation_id(
def configure_logging() -> None:
"""Wire up the slopsmith logger hierarchy.
"""Wire up the feedBack logger hierarchy.
Safe to call multiple times; always reflects the current LOG_LEVEL,
LOG_FORMAT, and LOG_FILE environment variables.
@@ -52,7 +52,7 @@ def configure_logging() -> None:
level = getattr(logging, raw_level, None)
if not isinstance(level, int):
sys.stderr.write(
f"[slopsmith] WARNING: unrecognised LOG_LEVEL={raw_level!r};"
f"[feedBack] WARNING: unrecognised LOG_LEVEL={raw_level!r};"
" falling back to INFO.\n"
)
level = logging.INFO
@@ -60,7 +60,7 @@ def configure_logging() -> None:
raw_fmt = os.environ.get("LOG_FORMAT", "text").lower()
if raw_fmt not in ("json", "text"):
sys.stderr.write(
f"[slopsmith] WARNING: unrecognised LOG_FORMAT={raw_fmt!r};"
f"[feedBack] WARNING: unrecognised LOG_FORMAT={raw_fmt!r};"
" falling back to 'text'.\n"
)
raw_fmt = "text"
@@ -137,17 +137,17 @@ def configure_logging() -> None:
handlers.append(fh)
except OSError as exc:
sys.stderr.write(
f"[slopsmith] WARNING: could not open LOG_FILE={log_file!r}: {exc}"
f"[feedBack] WARNING: could not open LOG_FILE={log_file!r}: {exc}"
" — continuing with console-only logging.\n"
)
_uvicorn_names = ("uvicorn", "uvicorn.error", "uvicorn.access")
all_loggers = [logging.getLogger("slopsmith")] + [
all_loggers = [logging.getLogger("feedBack")] + [
logging.getLogger(n) for n in _uvicorn_names
]
# Collect all unique old handlers across every logger *before* any close so
# that a shared handler (slopsmith and uvicorn* were intentionally given the
# that a shared handler (feedBack and uvicorn* were intentionally given the
# same objects) isn't closed while still attached to another logger tree.
old_handlers: set[logging.Handler] = set()
for lg in all_loggers:
@@ -160,8 +160,8 @@ def configure_logging() -> None:
for h in old_handlers:
h.close()
# Install fresh handlers on the slopsmith root.
root = logging.getLogger("slopsmith")
# Install fresh handlers on the feedBack root.
root = logging.getLogger("feedBack")
for h in handlers:
root.addHandler(h)
root.setLevel(level)
+4 -4
View File
@@ -23,14 +23,14 @@ Engine selection
Two transcription paths share a common output:
* `transcribe_vocals_remote(path, server_url, ...)` POST the vocal
stem to the `/align` endpoint on a slopsmith-demucs-server (Byron's
stem to the `/align` endpoint on a feedBack-demucs-server (got-feedBack's
reference server already hosts WhisperX alongside Demucs at the same
URL).
* `transcribe_vocals_local(path, ...)` load WhisperX in-process. Heavy
(~3 GB of model weights for `large-v2` + the wav2vec2 aligner) and
slow on CPU. Deferred imports of `whisperx`, `torch`, and `soundfile`
keep the rest of slopsmith free of those dependencies.
keep the rest of feedBack free of those dependencies.
Callers pick between them based on a `whisperx.server_url` config and
fall back as appropriate. This module does not read config both
@@ -58,7 +58,7 @@ import logging
from pathlib import Path
from typing import Callable, Optional
log = logging.getLogger("slopsmith.lib.lyrics_transcribe")
log = logging.getLogger("feedBack.lib.lyrics_transcribe")
ProgressCB = Optional[Callable[[float, str, str], None]]
@@ -179,7 +179,7 @@ _MIN_WORD_DURATION = 0.05
# Semver for the lyric-transcription artifact contract that gets stamped
# into the sloppak manifest's `lyric_transcription` block alongside the
# engine + model. Bump per the semantics defined in slopsmith#357 (the
# engine + model. Bump per the semantics defined in feedBack#357 (the
# parent `stem_separation` RFC):
# * patch — metadata-only or implementation fixes; no regeneration
# * minor — backward-compatible additions
+1 -1
View File
@@ -24,7 +24,7 @@ from __future__ import annotations
import logging
import math
log = logging.getLogger("slopsmith.lib.notation")
log = logging.getLogger("feedBack.lib.notation")
# ── Vocabulary ────────────────────────────────────────────────────────────────
+4 -4
View File
@@ -31,7 +31,7 @@ from tunings import tuning_name
import sloppak as sloppak_mod
import loosefolder as loosefolder_mod
log = logging.getLogger("slopsmith.scan_worker")
log = logging.getLogger("feedBack.scan_worker")
def _relpath(f: Path, dlc: Path) -> str:
@@ -53,7 +53,7 @@ def _extract_meta_sloppak(path: Path) -> dict:
meta["tuning_sort_key"] = sum(offsets)
meta["tuning_offsets"] = " ".join(str(o) for o in offsets)
meta["format"] = "sloppak"
# `extract_meta` already populates `stem_ids` (slopsmith#129);
# `extract_meta` already populates `stem_ids` (feedBack#129);
# default to empty for older callers / mocks.
meta.setdefault("stem_ids", [])
# Compute smart names for sloppak arrangements using name-based fallback
@@ -109,7 +109,7 @@ def _extract_meta_for_file(path: Path, dlc_root=None) -> dict:
the root it already resolved; in-process callers can pass the resolver
itself (e.g. `_get_dlc_dir`) to keep the lookup lazy.
Slopsmith reads only its own `.sloppak` format and loose-folder XML
FeedBack reads only its own `.sloppak` format and loose-folder XML
songs. Encrypted/proprietary archive formats are not supported and are
silently ignored (empty metadata) rather than decrypted.
"""
@@ -121,7 +121,7 @@ def _extract_meta_for_file(path: Path, dlc_root=None) -> dict:
if loosefolder_mod.is_loose_song(path):
root = dlc_root() if callable(dlc_root) else dlc_root
return _extract_meta_loosefolder(path, root)
# Unknown/unsupported shape — return empty metadata. Slopsmith never
# Unknown/unsupported shape — return empty metadata. FeedBack never
# reads encrypted archive formats.
return {
"title": "", "artist": "", "album": "", "year": "",
+2 -2
View File
@@ -22,7 +22,7 @@ import zipfile
from dataclasses import dataclass, field
from pathlib import Path
log = logging.getLogger("slopsmith.lib.sloppak")
log = logging.getLogger("feedBack.lib.sloppak")
# The feedpak format version this build targets / writes (manifest
# `feedpak_version`, a semver string per spec §4). Readers tolerate any version
@@ -892,6 +892,6 @@ def extract_meta(path: Path) -> dict:
"arrangements": arrangements,
"has_lyrics": has_lyrics,
"stem_count": stem_count,
# slopsmith#129: per-stem filter needs the id list, not just count.
# feedBack#129: per-stem filter needs the id list, not just count.
"stem_ids": stem_ids,
}
+8 -8
View File
@@ -8,7 +8,7 @@ import logging
import math
import xml.etree.ElementTree as ET
log = logging.getLogger("slopsmith.lib.song")
log = logging.getLogger("feedBack.lib.song")
@dataclass
@@ -124,10 +124,10 @@ class PhraseLevel:
"""One difficulty tier's worth of note/chord/anchor/hand-shape data for a
single phrase iteration. the arrangement XML stores these as `<level
difficulty="N">` blocks that repeat for every difficulty tier the chart
author wrote; slopsmith used to collapse them to the phrase's
author wrote; feedBack used to collapse them to the phrase's
maxDifficulty and throw the rest away. Keeping them around lets the
highway render a "master difficulty" slider that picks a per-phrase
difficulty tier at render time (slopsmith#48)."""
difficulty tier at render time (feedBack#48)."""
difficulty: int
notes: list[Note] = field(default_factory=list)
@@ -175,7 +175,7 @@ class Arrangement:
# `base`/`changes` drive the highway tone-change markers; `definitions`
# feed the Tones plugin gear panel.
tones: dict | None = None
# arrangement XML <arrangementProperties> flags for smart naming (slopsmith feat/arrangement).
# arrangement XML <arrangementProperties> flags for smart naming (feedBack feat/arrangement).
# Populated from the XML; default False/0 for sloppak / GP-imported sources.
path_lead: bool = False
path_rhythm: bool = False
@@ -622,7 +622,7 @@ def arrangement_string_count(arr: Arrangement) -> int:
"""Derive the active arrangement's string count.
Used by the server to emit ``stringCount`` in the song_info
WebSocket payload (slopsmith-plugin-3dhighway#7).
WebSocket payload (feedBack-plugin-3dhighway#7).
The arrangement XML schema always emits 6 ``<tuning>`` slots regardless
of instrument (bass charts populate `string0``string3` and pad
@@ -1276,7 +1276,7 @@ def parse_arrangement(xml_path: str) -> Arrangement:
def _collect_from_parsed(parsed, t_start, t_end):
"""Append a pre-parsed level's time-clipped slice to the flat
arrangement lists. Used for the max-mastery merge that preserves
the pre-slopsmith#48 behaviour for existing consumers."""
the pre-feedBack#48 behaviour for existing consumers."""
lv_notes, lv_chords, lv_anchors, lv_hand_shapes = _extract_level_slice(
parsed, t_start, t_end
)
@@ -1295,7 +1295,7 @@ def parse_arrangement(xml_path: str) -> Arrangement:
_collect_from_parsed(best, 0.0, float("inf"))
# Per-phrase difficulty data for the master-difficulty slider
# (slopsmith#48). Only populated when the XML has multiple levels AND
# (feedBack#48). Only populated when the XML has multiple levels AND
# phrase data — left as None for single-level sources so the frontend
# knows to disable the slider.
phrases: list[Phrase] | None = None
@@ -1445,7 +1445,7 @@ def _convert_sng_to_xml(extracted_dir: str):
"""No-op stub.
Historically this converted proprietary encrypted ``.notechart`` arrangement
files to XML via an external tool. That path has been removed: slopsmith
files to XML via an external tool. That path has been removed: feedBack
reads only its own ``.sloppak`` format and loose-folder/GP/MusicXML-derived
arrangement XML, and never decodes or decrypts proprietary archives. Kept
as a no-op so ``load_song`` (which loads plain arrangement XML/JSON from a
+1 -1
View File
@@ -10,7 +10,7 @@ source of truth, so the change survives both incremental and full rescans.
only the keys present are overwritten, so an edit of just the title can't blank
out the artist.
Only slopsmith's own ``.sloppak`` format (zip- or directory-form) is writable.
Only feedBack's own ``.sloppak`` format (zip- or directory-form) is writable.
Unknown / unsupported shapes return False and the caller keeps the DB-only
update.
"""
+6 -4
View File
@@ -1,9 +1,9 @@
"""Regenerate ``static/tailwind.min.css`` over the full installed-plugin set.
Core's committed (and image-baked) stylesheet is built scanning only the
in-tree plugins. A plugin installed at runtime into ``SLOPSMITH_PLUGINS_DIR``
in-tree plugins. A plugin installed at runtime into ``FEEDBACK_PLUGINS_DIR``
ships Tailwind classes the sheet never saw, so it renders unstyled. The
Play CDN's runtime JIT that used to cover this was removed (slopsmith#411),
Play CDN's runtime JIT that used to cover this was removed (feedBack#411),
so we rebuild the sheet ourselves with node + the pinned ``tailwindcss``,
scanning the baked-in plugins *and* the user plugins dir.
@@ -24,7 +24,9 @@ import tempfile
import threading
from pathlib import Path
log = logging.getLogger("slopsmith.tailwind")
from env_compat import getenv_compat
log = logging.getLogger("feedBack.tailwind")
# Pin matches scripts/build-tailwind.sh and the Dockerfile build stage so every
# sheet — committed, image-baked, and runtime-regenerated — comes from the same
@@ -45,7 +47,7 @@ APP_DIR = Path(__file__).resolve().parent.parent
def _user_plugins_dir() -> Path | None:
raw = os.environ.get("SLOPSMITH_PLUGINS_DIR", "").strip()
raw = (getenv_compat("FEEDBACK_PLUGINS_DIR", "") or "").strip()
if not raw:
return None
p = Path(raw)
+3 -3
View File
@@ -1,13 +1,13 @@
"""Tone helpers for sloppak playback.
A slopsmith arrangement may carry a tone block the initial tone name plus
A feedBack arrangement may carry a tone block the initial tone name plus
in-song tone switches embedded inline in the arrangement JSON (see
``lib/song.py`` ``arrangement_to_wire`` / the ``tones`` wire key). This module
turns that already-embedded block into the (base, changes) payload the highway
WebSocket sends to the client.
The proprietary-archive tone-extraction path (lifting tone definitions out of
an unpacked encrypted archive) has been removed. Slopsmith reads tones only
an unpacked encrypted archive) has been removed. FeedBack reads tones only
from its own ``.sloppak`` / arrangement JSON; it never reads or decrypts
proprietary archive formats.
"""
@@ -18,7 +18,7 @@ import logging
import math
import re
log = logging.getLogger("slopsmith.lib.tones")
log = logging.getLogger("feedBack.lib.tones")
def tokens(s: str) -> set[str]:
+6 -6
View File
@@ -5,7 +5,7 @@ isolated vocals + per-syllable lyric timing (both produced by the
WhisperX fallback or shipped in the source archive), the /pitch endpoint
runs CREPE over the vocals stem and returns one MIDI note per supplied
timing token. The result lands in `<sloppak>/vocal_pitch.json` in the
shape the got-feedback/feedback-plugin-lyrics-karaoke renderer
shape the got-feedback/feedBack-plugin-lyrics-karaoke renderer
already consumes:
{"version": 1, "notes": [{"t": float, "d": float, "midi": int}, ...]}
@@ -23,18 +23,18 @@ runs locally. Adding a local CREPE path here would mean pulling
`crepe` + `tensorflow` as plugin deps (~500 MB+ on top of the
existing torch/demucs/whisperx). Deferred until users hit the gap.
If you need a local fallback today, install
`got-feedback/feedback-plugin-lyrics-karaoke` and let its local
`got-feedback/feedBack-plugin-lyrics-karaoke` and let its local
pYIN run when the server isn't reachable.
Cache key parity with stem_separation / lyric_transcription
A `pitch_extraction` manifest block mirrors the shape introduced by
slopsmith#357: `{engine, model, version}`. Today engine is fixed at
feedBack#357: `{engine, model, version}`. Today engine is fixed at
`"crepe"` (the server's choice) and model at `"v1"` (server doesn't
yet expose the CREPE capacity dial it uses internally; this is the
requested value, same caveat as `lyric_transcription.model`). The
schema version is independent of the upstream CREPE version and bumps
per slopsmith's contract:
per feedBack's contract:
* patch metadata-only or implementation fixes
* minor backward-compatible additions
* major output shape / semantics changed; existing
@@ -50,7 +50,7 @@ import math
from pathlib import Path
from typing import Callable, Optional
log = logging.getLogger("slopsmith.lib.vocal_pitch")
log = logging.getLogger("feedBack.lib.vocal_pitch")
ProgressCB = Optional[Callable[[float, str, str], None]]
@@ -72,7 +72,7 @@ def extract_pitch_remote(
) -> list[dict]:
"""POST the vocal stem + lyric timings to `{server_url}/pitch`.
`lyrics` is the same `[{t, d, w}, ...]` list slopsmith writes to
`lyrics` is the same `[{t, d, w}, ...]` list feedBack writes to
`lyrics.json`. The endpoint only consumes `t` + `d` (it doesn't
need the word text), but we pass the full payload through
slimmer to forward what we already have than to project.
+1 -1
View File
@@ -6,7 +6,7 @@ import logging
import struct
import os
log = logging.getLogger("slopsmith.lib.wem_decode")
log = logging.getLogger("feedBack.lib.wem_decode")
def convert_wem_to_ogg(wem_path: str, output_path: str) -> bool: