diff --git a/README.md b/README.md index 1a016ff..f168174 100644 --- a/README.md +++ b/README.md @@ -15,34 +15,28 @@ | [MIDI Amp Control](https://github.com/got-feedback/feedBack-plugin-midi) | Auto-switch amp/modeler presets via MIDI on tone changes | `git clone ...feedBack-plugin-midi.git midi_amp` | | [Section Map](https://github.com/got-feedback/feedBack-plugin-sectionmap) | Color-coded song structure minimap with clickable navigation | `git clone ...feedBack-plugin-sectionmap.git section_map` | | [Arrangement Editor](https://github.com/got-feedback/feedBack-plugin-editor) | DAW-like visual editor for creating and editing song note charts | `git clone ...feedBack-plugin-editor.git editor` | -| [MIDI Capo](https://github.com/masc0t/feedBack-plugin-midi-capo) | MIDI capo control for real-time transposition | `git clone ...feedBack-plugin-midi-capo.git midi_capo` | +| [MIDI Capo](https://github.com/masc0t/slopsmith-plugin-midi-capo) | MIDI capo control for real-time transposition | `git clone ...slopsmith-plugin-midi-capo.git midi_capo` | | [Note Detection](https://github.com/got-feedback/feedBack-plugin-notedetect) | Real-time pitch detection and scoring against highway notes | `git clone ...feedBack-plugin-notedetect.git note_detect` | -| [Find More](https://github.com/masc0t/feedBack-plugin-find-more) | Search for more songs by the same artist | `git clone ...feedBack-plugin-find-more.git find_more` | +| [Find More](https://github.com/masc0t/slopsmith-plugin-find-more) | Search for more songs by the same artist | `git clone ...slopsmith-plugin-find-more.git find_more` | | [Piano Highway](https://github.com/got-feedback/feedBack-plugin-piano) | Scrolling piano/keyboard view for Keys arrangements with MIDI input | `git clone ...feedBack-plugin-piano.git piano` | | [Studio](https://github.com/got-feedback/feedBack-plugin-studio) | Collaborative band recording and multi-track mixing | `git clone ...feedBack-plugin-studio.git studio` | | [Drum Highway](https://github.com/got-feedback/feedBack-plugin-drums) | Lane-based drum highway with MIDI drum pad input and built-in sounds | `git clone ...feedBack-plugin-drums.git drums` | -| [Split Screen](https://github.com/topkoa/feedBack-plugin-splitscreen) | 2-4 highway panels side-by-side for multi-arrangement practice | `git clone ...feedBack-plugin-splitscreen.git splitscreen` | -| [Stems Mixer](https://github.com/topkoa/feedBack-plugin-stems) | Per-stem mute/volume controls for .sloppak songs | `git clone ...feedBack-plugin-stems.git stems` | -| [Invert Highway](https://github.com/masc0t/feedBack-plugin-invert-highway) | Flip the highway note direction | `git clone ...feedBack-plugin-invert-highway.git invert_highway` | -| [Jumping Tab](https://github.com/renanboni/feedBack-plugin-jumpingtab) | Yousician-style 2D horizontal tab with trajectory arcs and hopping ball | `git clone ...feedBack-plugin-jumpingtab.git jumpingtab` | +| [Invert Highway](https://github.com/masc0t/slopsmith-plugin-invert-highway) | Flip the highway note direction | `git clone ...slopsmith-plugin-invert-highway.git invert_highway` | +| [Jumping Tab](https://github.com/renanboni/slopsmith-plugin-jumpingtab) | Yousician-style 2D horizontal tab with trajectory arcs and hopping ball | `git clone ...slopsmith-plugin-jumpingtab.git jumpingtab` | | [Step Mode](https://github.com/got-feedback/feedBack-plugin-stepmode) | Step-by-step practice mode — highway freezes at each note until played (via Note Detection) or Space | `git clone ...feedBack-plugin-stepmode.git step_mode` | | [Lyrics Sync](https://github.com/got-feedback/feedBack-plugin-lyrics-sync) | Generate synced LRC lyrics from text + vocals stem via Whisper alignment | `git clone ...feedBack-plugin-lyrics-sync.git lyrics_sync` | | [Lyrics Karaoke](https://github.com/got-feedback/feedBack-plugin-lyrics-karaoke) | Per-syllable karaoke pitch ribbon for sloppak songs (Whisper alignment + librosa pYIN) | `git clone ...feedBack-plugin-lyrics-karaoke.git lyrics_karaoke` | | [NAM Tone Engine](https://github.com/got-feedback/feedBack-plugin-nam-tone) | In-browser amp modeling with NAM WASM, cabinet IRs, tone auto-switching | `git clone ...feedBack-plugin-nam-tone.git nam_tone` | -| [Guitar Theory Lab](https://github.com/topkoa/feedBack-plugin-guitar-theory) | Explore scales, chords, intervals, tunings, and voicings on a fully interactive fretboard | `git clone ...feedBack-plugin-nam-tone.git guitar-theory-lab` | -| [Themes](https://github.com/masc0t/feedBack-plugin-themes) | Offers several basic recolorings of the interface | `git clone ...feedBack-plugin-themes.git themes` | -| [Update Manager](https://github.com/masc0t/feedBack-update-manager) | Installs, updates, and uninstalls other plugins and the feedBack core itself | `git clone ...feedBack-update-manager.git update_manager` | -| [Tuner](https://github.com/OmikronApex/feedBack-plugin-tuner) | Floating tuner with customizable tunings | `git clone ...feedBack-plugin-tuner.git tuner` | -| [Simplify Chords](https://github.com/bkranendonk/feedBack-plugin-simplify-chords) | Changes complex chords on the note highway to simpler ones. Inspired by Ultimate Guitar's Simplify button. | `git clone ...feedBack-plugin-simplify-chords.git simplify-chords` | -| [Key Bindings](https://github.com/jackipicco/feedBack-plugin-key-bindings) | Highway key bindings for keyboard and TV remote | `git clone ...feedBack-plugin-key-bindings.git key_bindings` | -| [Folder Organizer](https://github.com/Elit3d/feedBack-plugin-folder-organizer) | Organize your sloppak DLC songs into a folder tree view, grouped by subfolder name | `git clone ...feedBack-plugin-folder-organizer.git folder-organizer` | +| [Guitar Theory Lab](https://github.com/topkoa/slopsmith-plugin-guitar-theory) | Explore scales, chords, intervals, tunings, and voicings on a fully interactive fretboard | `git clone ...slopsmith-plugin-guitar-theory.git guitar-theory-lab` | +| [Themes](https://github.com/masc0t/slopsmith-plugin-themes) | Offers several basic recolorings of the interface | `git clone ...slopsmith-plugin-themes.git themes` | +| [Update Manager](https://github.com/masc0t/slopsmith-update-manager) | Installs, updates, and uninstalls other plugins and the feedBack core itself | `git clone ...slopsmith-update-manager.git update_manager` | +| [Simplify Chords](https://github.com/bkranendonk/slopsmith-plugin-simplify-chords) | Changes complex chords on the note highway to simpler ones. Inspired by Ultimate Guitar's Simplify button. | `git clone ...slopsmith-plugin-simplify-chords.git simplify-chords` | +| [Key Bindings](https://github.com/jackipicco/slopsmith-plugin-key-bindings) | Highway key bindings for keyboard and TV remote | `git clone ...slopsmith-plugin-key-bindings.git key_bindings` | +| [Folder Organizer](https://github.com/Elit3d/slopsmith-plugin-folder-organizer) | Organize your sloppak DLC songs into a folder tree view, grouped by subfolder name | `git clone ...slopsmith-plugin-folder-organizer.git folder-organizer` | | [Virtuoso](https://github.com/got-feedback/feedback-plugin-virtuoso) | Practice studio for guitar & bass — scale, technique, and rhythm drills, timed workouts, and jam backing that teach skills you take off the screen. | `git clone ...feedback-plugin-virtuoso.git virtuoso` | -| [NAM Rig Builder](https://github.com/Jafz2001/feedBack-plugin-nam-rig-builder) | Map tones to chained NAM neural-amp rigs (tone3000 captures + IRs) — full pedal→amp→cab playback, per-stage bypass, and a gear catalog | `git clone ...feedBack-plugin-nam-rig-builder.git nam_rig_builder` | -| [Audio Preview](https://github.com/saleemk/feedBack-plugin-audio-preview) | Quick audio previews from library cards with configurable start time, volume, and duration | `git clone ...feedBack-plugin-audio-preview.git audio_preview` | -| [Song Mastery](https://github.com/jamesgaiser/feedBack-plugin-song-mastery) | Auto-adjusts difficulty based on your rolling note accuracy and saves the slider position per song | `git clone ...feedBack-plugin-song-mastery.git song_mastery` | -| [Song Preview](https://github.com/DeathlySin/feedBack-plugin-song-preview) | Quickly hear previews of songs in your library with a clean visual indicator of what's playing. Supports .sloppak and loose folders song formats, with the visual indicator matching up to whatever theme you are using! | `git clone ...feedBack-plugin-song-preview.git song_preview` | -| [Mobile Note Highway](https://github.com/saleemk/feedBack-plugin-mobile-note-highway) | Touch-optimized player with collapsible controls, highway gestures, and device-adaptive layouts for phones and tablets | `git clone ...feedBack-plugin-mobile-note-highway.git mobile_note_highway` | -| [Shuffle](https://github.com/Erikcb91/FeedBack-Shuffle-Mode) | Random playback from your library — artist & tuning filters, auto-advance with countdown popup, note_detect compatible | `git clone https://github.com/Erikcb91/FeedBack-Shuffle-Mode.git shuffle` | +| [Audio Preview](https://github.com/saleemk/slopsmith-plugin-audio-preview) | Quick audio previews from library cards with configurable start time, volume, and duration | `git clone ...slopsmith-plugin-audio-preview.git audio_preview` | +| [Song Mastery](https://github.com/jamesgaiser/slopsmith-plugin-song-mastery) | Auto-adjusts difficulty based on your rolling note accuracy and saves the slider position per song | `git clone ...slopsmith-plugin-song-mastery.git song_mastery` | +| [Mobile Note Highway](https://github.com/saleemk/slopsmith-plugin-mobile-note-highway) | Touch-optimized player with collapsible controls, highway gestures, and device-adaptive layouts for phones and tablets | `git clone ...slopsmith-plugin-mobile-note-highway.git mobile_note_highway` | Install any plugin by cloning it into your `plugins/` directory and restarting: diff --git a/lib/diagnostics_hardware.py b/lib/diagnostics_hardware.py index ce5eafd..f4dee69 100644 --- a/lib/diagnostics_hardware.py +++ b/lib/diagnostics_hardware.py @@ -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("FEEDBACK_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(): diff --git a/lib/env_compat.py b/lib/env_compat.py new file mode 100644 index 0000000..c0b1e6e --- /dev/null +++ b/lib/env_compat.py @@ -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_`` name, returns the value of ``FEEDBACK_`` + if set, else ``SLOPSMITH_`` 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 diff --git a/lib/gp2midi.py b/lib/gp2midi.py index 2023bad..b7c8e78 100644 --- a/lib/gp2midi.py +++ b/lib/gp2midi.py @@ -8,6 +8,8 @@ import sys import tempfile from pathlib import Path +from env_compat import getenv_compat + log = logging.getLogger("feedBack.lib.gp2midi") import guitarpro @@ -156,7 +158,7 @@ def _find_soundfont() -> str | None: 2. Bundled ``/soundfonts/*.sf2`` (Electron desktop builds) 3. Common system locations per OS. """ - override = os.environ.get("FEEDBACK_SOUNDFONT") + override = getenv_compat("FEEDBACK_SOUNDFONT") if override: if os.path.isfile(override): return override diff --git a/lib/tailwind_rebuild.py b/lib/tailwind_rebuild.py index 0c5e924..cd957b5 100644 --- a/lib/tailwind_rebuild.py +++ b/lib/tailwind_rebuild.py @@ -24,6 +24,8 @@ import tempfile import threading from pathlib import Path +from env_compat import getenv_compat + log = logging.getLogger("feedBack.tailwind") # Pin matches scripts/build-tailwind.sh and the Dockerfile build stage so every @@ -45,7 +47,7 @@ APP_DIR = Path(__file__).resolve().parent.parent def _user_plugins_dir() -> Path | None: - raw = os.environ.get("FEEDBACK_PLUGINS_DIR", "").strip() + raw = (getenv_compat("FEEDBACK_PLUGINS_DIR", "") or "").strip() if not raw: return None p = Path(raw) diff --git a/plugins/__init__.py b/plugins/__init__.py index 6260749..aa8a5b3 100644 --- a/plugins/__init__.py +++ b/plugins/__init__.py @@ -1118,7 +1118,7 @@ def load_plugins(app: FastAPI, context: dict, progress_cb=None, route_setup_fn=N # Collect plugin directories — user plugins first so they override built-in plugin_dirs = [] - user_plugins_dir = os.environ.get("FEEDBACK_PLUGINS_DIR") or os.environ.get("FEEDBACK_PLUGINS_DIR") + user_plugins_dir = os.environ.get("FEEDBACK_PLUGINS_DIR") or os.environ.get("SLOPSMITH_PLUGINS_DIR") if user_plugins_dir: user_path = Path(user_plugins_dir) if user_path.is_dir() and user_path != PLUGINS_DIR: diff --git a/plugins/minigames/routes.py b/plugins/minigames/routes.py index c676270..061350a 100644 --- a/plugins/minigames/routes.py +++ b/plugins/minigames/routes.py @@ -336,7 +336,7 @@ def setup(app, context): # Duplicates are removed via a seen-set keyed on resolved paths. def _resolve_plugin_dirs(): roots = [] - env_dir = os.environ.get("FEEDBACK_PLUGINS_DIR") or os.environ.get("FEEDBACK_PLUGINS_DIR") + env_dir = os.environ.get("FEEDBACK_PLUGINS_DIR") or os.environ.get("SLOPSMITH_PLUGINS_DIR") if env_dir: roots.append(Path(env_dir)) # Built-in plugins/ next to server.py (one level above this file's diff --git a/server.py b/server.py index 809f6fc..2d3c266 100644 --- a/server.py +++ b/server.py @@ -15,6 +15,7 @@ from pathlib import Path from typing import Any, ClassVar from logging_setup import configure_logging +from env_compat import getenv_compat configure_logging() log = logging.getLogger("feedBack.server") @@ -230,7 +231,7 @@ _DEMO_BLOCKED: list[tuple[str, re.Pattern]] = [ @app.middleware("http") async def _demo_mode_guard(request: Request, call_next): - if os.environ.get("FEEDBACK_DEMO_MODE") or os.environ.get("FEEDBACK_DEMO_MODE") == "1": + if getenv_compat("FEEDBACK_DEMO_MODE") or getenv_compat("FEEDBACK_DEMO_MODE") == "1": path = request.url.path for method, pattern in _DEMO_BLOCKED: if request.method == method and pattern.match(path): @@ -275,8 +276,8 @@ SLOPPAK_CACHE_DIR = CONFIG_DIR / "sloppak_cache" def _env_flag(name: str) -> bool: - """Parse a conventional boolean env flag.""" - return os.environ.get(name, "").strip().lower() in {"1", "true", "yes", "on"} + """Parse a conventional boolean env flag (honours legacy SLOPSMITH_* alias).""" + return (getenv_compat(name, "") or "").strip().lower() in {"1", "true", "yes", "on"} # Canonical Tuning-filter grouping key (feedBack#867). tuning_name collapses @@ -3040,7 +3041,7 @@ def _make_scan_executor(): # A malformed override falls back to the core count rather than crashing. try: max_workers = int( - os.environ.get("FEEDBACK_MAX_SCAN_WORKERS") + getenv_compat("FEEDBACK_MAX_SCAN_WORKERS") or os.environ.get("SCAN_MAX_WORKERS") or (os.cpu_count() or 1) ) @@ -3090,7 +3091,7 @@ def _get_progression_content() -> dict: with _progression_content_lock: if _progression_content is None: import progression as progression_mod - root = os.environ.get("FEEDBACK_PROGRESSION_DATA") or ( + root = getenv_compat("FEEDBACK_PROGRESSION_DATA") or ( _feedBack_server_root() / "data" / "progression" ) content, warnings = progression_mod.load_content(root) @@ -3488,7 +3489,7 @@ async def startup_events(): # Load plugins asynchronously so HTTP routes and the desktop window can # come up immediately while heavy plugin imports/install steps continue. - _sync_mode = os.environ.get("FEEDBACK_SYNC_STARTUP", "").lower() in {"1", "true", "yes", "on"} + _sync_mode = getenv_compat("FEEDBACK_SYNC_STARTUP", "").lower() in {"1", "true", "yes", "on"} def _load_plugins_background(): try: @@ -3723,7 +3724,7 @@ async def startup_events(): threading.Thread(target=_load_plugins_background, daemon=True).start() global _DEMO_JANITOR_STARTED, _DEMO_JANITOR_THREAD - if os.environ.get("FEEDBACK_DEMO_MODE") or os.environ.get("FEEDBACK_DEMO_MODE") == "1" and not _DEMO_JANITOR_STARTED: + if getenv_compat("FEEDBACK_DEMO_MODE") or getenv_compat("FEEDBACK_DEMO_MODE") == "1" and not _DEMO_JANITOR_STARTED: _DEMO_JANITOR_STARTED = True _DEMO_JANITOR_STOP.clear() def _janitor(): @@ -6183,7 +6184,7 @@ def _diag_plugins_roots() -> list[Path]: orphans in the external dir are reflected in the bundle. """ roots: list[Path] = [] - user_dir = os.environ.get("FEEDBACK_PLUGINS_DIR", "").strip() + user_dir = getenv_compat("FEEDBACK_PLUGINS_DIR", "").strip() if user_dir: p = Path(user_dir) if p.is_dir(): @@ -7683,7 +7684,7 @@ def index(): # UI remains fully available as a fallback — opt back in with # FEEDBACK_UI=v2 (or =legacy), or hit the dedicated /v2 route below (which # serves it regardless of the env var). - if os.environ.get("FEEDBACK_UI") or os.environ.get("FEEDBACK_UI") in ("v2", "legacy"): + if getenv_compat("FEEDBACK_UI") or getenv_compat("FEEDBACK_UI") in ("v2", "legacy"): return FileResponse(str(STATIC_DIR / "index.html")) return FileResponse(str(STATIC_DIR / "v3" / "index.html")) diff --git a/static/app.js b/static/app.js index 9ec78b3..88c3c73 100644 --- a/static/app.js +++ b/static/app.js @@ -3310,7 +3310,9 @@ function setupAppUpdates() { // iframes, privacy modes, etc.); fall back to the default channel so the // panel still renders rather than aborting wiring entirely. let storedRaw = null; - try { storedRaw = localStorage.getItem('feedBack-update-channel'); } catch (_) { /* fall through */ } + // Read the canonical key, falling back to the pre-rename + // 'slopsmith-update-channel' so an existing channel preference survives. + try { storedRaw = localStorage.getItem('feedBack-update-channel') || localStorage.getItem('slopsmith-update-channel'); } catch (_) { /* fall through */ } const stored = APP_UPDATE_CHANNELS.includes(storedRaw) ? storedRaw : 'stable'; channelSelect.value = stored; @@ -3395,7 +3397,7 @@ function setupAppUpdates() { channelSelect.addEventListener('change', async () => { const val = channelSelect.value; if (!APP_UPDATE_CHANNELS.includes(val)) return; - try { localStorage.setItem('feedBack-update-channel', val); } catch (_) {} + try { localStorage.setItem('feedBack-update-channel', val); localStorage.removeItem('slopsmith-update-channel'); } catch (_) {} try { // Await setChannel so the status line reflects what actually // happened — rendering "Channel set" unconditionally would diff --git a/tests/test_env_compat.py b/tests/test_env_compat.py new file mode 100644 index 0000000..d41d201 --- /dev/null +++ b/tests/test_env_compat.py @@ -0,0 +1,50 @@ +"""Coverage for the slopsmith -> feedBack env backward-compat shim.""" + +from env_compat import getenv_compat, env_flag_compat + + +def test_canonical_wins_over_legacy(monkeypatch): + monkeypatch.setenv("FEEDBACK_UI", "v3") + monkeypatch.setenv("SLOPSMITH_UI", "v2") + assert getenv_compat("FEEDBACK_UI") == "v3" + + +def test_legacy_fallback_when_canonical_unset(monkeypatch): + monkeypatch.delenv("FEEDBACK_UI", raising=False) + monkeypatch.setenv("SLOPSMITH_UI", "v2") + assert getenv_compat("FEEDBACK_UI") == "v2" + + +def test_default_when_neither_set(monkeypatch): + monkeypatch.delenv("FEEDBACK_UI", raising=False) + monkeypatch.delenv("SLOPSMITH_UI", raising=False) + assert getenv_compat("FEEDBACK_UI", "default") == "default" + assert getenv_compat("FEEDBACK_UI") is None + + +def test_non_feedback_names_have_no_fallback(monkeypatch): + monkeypatch.delenv("CONFIG_DIR", raising=False) + monkeypatch.setenv("SLOPSMITH_CONFIG_DIR", "/tmp/x") + # A non-FEEDBACK_ name must behave like os.environ.get (no aliasing). + assert getenv_compat("CONFIG_DIR") is None + + +def test_empty_canonical_is_respected_not_overridden(monkeypatch): + # An explicitly-empty canonical value is still "set" and wins. + monkeypatch.setenv("FEEDBACK_UI", "") + monkeypatch.setenv("SLOPSMITH_UI", "v2") + assert getenv_compat("FEEDBACK_UI") == "" + + +def test_flag_parses_true_values(monkeypatch): + for raw in ("1", "true", "YES", " On "): + monkeypatch.setenv("FEEDBACK_SYNC_STARTUP", raw) + assert env_flag_compat("FEEDBACK_SYNC_STARTUP") is True + + +def test_flag_false_and_legacy(monkeypatch): + monkeypatch.delenv("FEEDBACK_SYNC_STARTUP", raising=False) + monkeypatch.setenv("SLOPSMITH_SYNC_STARTUP", "1") + assert env_flag_compat("FEEDBACK_SYNC_STARTUP") is True + monkeypatch.setenv("FEEDBACK_SYNC_STARTUP", "0") + assert env_flag_compat("FEEDBACK_SYNC_STARTUP") is False