mirror of
https://github.com/got-feedBack/feedBack.git
synced 2026-07-25 14:21:21 +00:00
* 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>
157 lines
6.1 KiB
Python
157 lines
6.1 KiB
Python
"""Tuner plugin — persist last selected tuning and custom tunings in config_dir."""
|
|
|
|
import json
|
|
import logging
|
|
from pathlib import Path
|
|
from fastapi import FastAPI, Request
|
|
from fastapi.responses import Response
|
|
|
|
DEFAULT_TUNING = "Standard"
|
|
DEFAULT_INSTRUMENT = "guitar-6"
|
|
|
|
_INSTRUMENT_BY_STRING_COUNT = {4: "bass-4", 5: "bass-5", 7: "guitar-7", 8: "guitar-8"}
|
|
|
|
|
|
def _migrate_custom_tuning(name: str, value) -> dict:
|
|
"""Return {instrument, strings} for both old flat-list and new dict formats."""
|
|
if isinstance(value, list):
|
|
instrument = _INSTRUMENT_BY_STRING_COUNT.get(len(value), "guitar-6")
|
|
return {"instrument": instrument, "strings": value}
|
|
if isinstance(value, dict) and "strings" in value:
|
|
return value
|
|
return {"instrument": "guitar-6", "strings": []}
|
|
|
|
|
|
def setup(app: FastAPI, context: dict):
|
|
config_dir = Path(context["config_dir"])
|
|
config_file = config_dir / "tuner.json"
|
|
log = context.get("log") or logging.getLogger("feedBack.plugin.tuner")
|
|
|
|
def _read() -> dict:
|
|
defaults = {
|
|
"lastTuning": DEFAULT_TUNING,
|
|
"lastInstrument": DEFAULT_INSTRUMENT,
|
|
"freeTune": False,
|
|
"customTunings": {},
|
|
"disabledTunings": [],
|
|
"showFloatingButton": True,
|
|
"visualizationMode": "default",
|
|
"audioInputMode": "auto",
|
|
}
|
|
if not config_file.exists():
|
|
return defaults
|
|
try:
|
|
data = json.loads(config_file.read_text(encoding="utf-8"))
|
|
if not isinstance(data, dict):
|
|
return defaults
|
|
|
|
res = {}
|
|
res["lastTuning"] = str(data.get("lastTuning", DEFAULT_TUNING))
|
|
res["lastInstrument"] = str(data.get("lastInstrument", DEFAULT_INSTRUMENT))
|
|
res["freeTune"] = bool(data.get("freeTune", False))
|
|
res["customTunings"] = data.get("customTunings", {})
|
|
res["disabledTunings"] = data.get("disabledTunings", [])
|
|
res["showFloatingButton"] = bool(data.get("showFloatingButton", True))
|
|
res["visualizationMode"] = str(data.get("visualizationMode", "default"))
|
|
raw_mode = str(data.get("audioInputMode", "auto"))
|
|
res["audioInputMode"] = raw_mode if raw_mode in ("auto", "browser") else "auto"
|
|
|
|
if not isinstance(res["customTunings"], dict):
|
|
res["customTunings"] = {}
|
|
if not isinstance(res["disabledTunings"], list):
|
|
res["disabledTunings"] = []
|
|
|
|
# Migrate custom tunings from old flat-list format
|
|
res["customTunings"] = {
|
|
name: _migrate_custom_tuning(name, val)
|
|
for name, val in res["customTunings"].items()
|
|
}
|
|
|
|
# Strip legacy disabledTunings entries that lack compound "instrument:name" format
|
|
res["disabledTunings"] = [
|
|
e for e in res["disabledTunings"]
|
|
if isinstance(e, str) and ":" in e
|
|
]
|
|
|
|
return res
|
|
except Exception:
|
|
return defaults
|
|
|
|
def _write(data: dict) -> None:
|
|
config_dir.mkdir(parents=True, exist_ok=True)
|
|
current = _read()
|
|
# Strip keys that belong to core, not to this plugin's config.
|
|
for key in ("defaultTunings", "referencePitch"):
|
|
data = {k: v for k, v in data.items() if k != key}
|
|
current.update(data)
|
|
config_file.write_text(json.dumps(current, indent=2), encoding="utf-8")
|
|
|
|
def _get_custom_tunings() -> dict:
|
|
"""Return custom tunings in DEFAULT_TUNINGS format: {instrument: {name: [freqs]}}."""
|
|
cfg = _read()
|
|
result: dict[str, dict] = {}
|
|
for name, val in cfg.get("customTunings", {}).items():
|
|
inst = val.get("instrument", "guitar-6")
|
|
strings = val.get("strings", [])
|
|
if strings:
|
|
result.setdefault(inst, {})[name] = strings
|
|
return result
|
|
|
|
# Register this plugin as a tuning provider for its custom tunings.
|
|
context["register_tuning_provider"]("tuner", _get_custom_tunings)
|
|
log.info("tuner: registered custom tuning provider")
|
|
|
|
_viz_dir = Path(__file__).parent / "visualization"
|
|
_viz_assets_dir = Path(__file__).parent / "visualization" / "assets"
|
|
_workers_dir = Path(__file__).parent / "workers"
|
|
_utils_dir = Path(__file__).parent / "utils"
|
|
|
|
_ASSET_MEDIA_TYPES = {".svg": "image/svg+xml", ".png": "image/png"}
|
|
|
|
def _serve_js_from(base_dir: Path, filename: str) -> Response:
|
|
target = (base_dir / filename).resolve()
|
|
try:
|
|
target.relative_to(base_dir.resolve())
|
|
except ValueError:
|
|
return Response("", status_code=404)
|
|
if target.suffix == ".js" and target.is_file():
|
|
return Response(target.read_text(encoding="utf-8"), media_type="application/javascript")
|
|
return Response("", status_code=404)
|
|
|
|
def _serve_asset_from(base_dir: Path, filename: str) -> Response:
|
|
target = (base_dir / filename).resolve()
|
|
try:
|
|
target.relative_to(base_dir.resolve())
|
|
except ValueError:
|
|
return Response("", status_code=404)
|
|
media_type = _ASSET_MEDIA_TYPES.get(target.suffix.lower())
|
|
if media_type and target.is_file():
|
|
return Response(target.read_bytes(), media_type=media_type)
|
|
return Response("", status_code=404)
|
|
|
|
@app.get("/api/plugins/tuner/visualization/{filename}")
|
|
def get_viz_file(filename: str):
|
|
return _serve_js_from(_viz_dir, filename)
|
|
|
|
@app.get("/api/plugins/tuner/viz-assets/{filename}")
|
|
def get_viz_asset(filename: str):
|
|
return _serve_asset_from(_viz_assets_dir, filename)
|
|
|
|
@app.get("/api/plugins/tuner/workers/{filename}")
|
|
def get_worker_file(filename: str):
|
|
return _serve_js_from(_workers_dir, filename)
|
|
|
|
@app.get("/api/plugins/tuner/utils/{filename}")
|
|
def get_utils_file(filename: str):
|
|
return _serve_js_from(_utils_dir, filename)
|
|
|
|
@app.get("/api/plugins/tuner/config")
|
|
def get_config():
|
|
return _read()
|
|
|
|
@app.post("/api/plugins/tuner/config")
|
|
async def set_config(req: Request):
|
|
body = await req.json()
|
|
_write(body)
|
|
return {"ok": True}
|