mirror of
https://github.com/got-feedBack/feedBack.git
synced 2026-09-11 03:04:30 +00:00
settings: add instrument pathway selection
Signed-off-by: ChrisBeWithYou <christian.a.cowan@gmail.com>
This commit is contained in:
+16
-2
@@ -149,6 +149,7 @@ def apply_reference_pitch(
|
||||
|
||||
|
||||
PROFILE_IDS = ("guitar-lead", "guitar-rhythm", "bass")
|
||||
PROFILE_PATHWAYS = ("songs", "practice", "learn", "studio")
|
||||
DEFAULT_ACTIVE_INSTRUMENT_PROFILE = "guitar-lead"
|
||||
PROFILE_DEFAULTS: dict[str, dict] = {
|
||||
"guitar-lead": {
|
||||
@@ -159,6 +160,7 @@ PROFILE_DEFAULTS: dict[str, dict] = {
|
||||
"string_count": 6,
|
||||
"tuning": "Standard",
|
||||
"reference_pitch": DEFAULT_REFERENCE_PITCH,
|
||||
"pathway": "songs",
|
||||
},
|
||||
"guitar-rhythm": {
|
||||
"id": "guitar-rhythm",
|
||||
@@ -168,6 +170,7 @@ PROFILE_DEFAULTS: dict[str, dict] = {
|
||||
"string_count": 6,
|
||||
"tuning": "Standard",
|
||||
"reference_pitch": DEFAULT_REFERENCE_PITCH,
|
||||
"pathway": "songs",
|
||||
},
|
||||
"bass": {
|
||||
"id": "bass",
|
||||
@@ -177,6 +180,7 @@ PROFILE_DEFAULTS: dict[str, dict] = {
|
||||
"string_count": 4,
|
||||
"tuning": "Standard",
|
||||
"reference_pitch": DEFAULT_REFERENCE_PITCH,
|
||||
"pathway": "songs",
|
||||
},
|
||||
}
|
||||
|
||||
@@ -250,6 +254,9 @@ def normalize_instrument_profile(profile_id: str, raw) -> tuple[dict | None, str
|
||||
role = raw.get("role", base["role"])
|
||||
if not isinstance(role, str) or len(role) > 32:
|
||||
return None, f"instrument_profiles.{profile_id}.role must be a short string"
|
||||
pathway = raw.get("pathway", base["pathway"])
|
||||
if not isinstance(pathway, str) or pathway not in PROFILE_PATHWAYS:
|
||||
return None, f"instrument_profiles.{profile_id}.pathway must be one of songs, practice, learn, studio"
|
||||
|
||||
out = dict(base)
|
||||
out.update({
|
||||
@@ -260,6 +267,7 @@ def normalize_instrument_profile(profile_id: str, raw) -> tuple[dict | None, str
|
||||
"string_count": string_count,
|
||||
"tuning": tuning,
|
||||
"reference_pitch": ref,
|
||||
"pathway": pathway,
|
||||
})
|
||||
return out, None
|
||||
|
||||
@@ -297,6 +305,7 @@ def profile_from_legacy_settings(cfg: dict) -> dict:
|
||||
key = instrument_key(instrument, sc)
|
||||
tuning = _valid_tuning_for_key(key, cfg.get("tuning", "Standard")) or "Standard"
|
||||
ref = _valid_reference_pitch(cfg.get("reference_pitch", DEFAULT_REFERENCE_PITCH)) or DEFAULT_REFERENCE_PITCH
|
||||
pathway = cfg.get("pathway") if cfg.get("pathway") in PROFILE_PATHWAYS else "songs"
|
||||
profile_id = "bass" if instrument == "bass" else DEFAULT_ACTIVE_INSTRUMENT_PROFILE
|
||||
profile = dict(PROFILE_DEFAULTS[profile_id])
|
||||
profile.update({
|
||||
@@ -304,6 +313,7 @@ def profile_from_legacy_settings(cfg: dict) -> dict:
|
||||
"string_count": sc,
|
||||
"tuning": tuning,
|
||||
"reference_pitch": ref,
|
||||
"pathway": pathway,
|
||||
})
|
||||
return profile
|
||||
|
||||
@@ -326,13 +336,14 @@ def settings_with_instrument_profiles(cfg: dict) -> dict:
|
||||
out["string_count"] = selected["string_count"]
|
||||
out["tuning"] = selected["tuning"]
|
||||
out["reference_pitch"] = selected["reference_pitch"]
|
||||
out["pathway"] = selected["pathway"]
|
||||
return out
|
||||
|
||||
|
||||
def apply_flat_instrument_patch_to_profiles(cfg: dict, updates: dict) -> dict:
|
||||
"""Mirror legacy flat instrument updates into the active host profile."""
|
||||
out = settings_with_instrument_profiles(cfg)
|
||||
if not any(k in updates for k in ("instrument", "string_count", "tuning", "reference_pitch")):
|
||||
if not any(k in updates for k in ("instrument", "string_count", "tuning", "reference_pitch", "pathway")):
|
||||
return out
|
||||
active = active_profile_id(out.get("active_instrument_profile"))
|
||||
if "instrument" in updates:
|
||||
@@ -348,6 +359,8 @@ def apply_flat_instrument_patch_to_profiles(cfg: dict, updates: dict) -> dict:
|
||||
current["string_count"] = updates["string_count"]
|
||||
if "reference_pitch" in updates:
|
||||
current["reference_pitch"] = updates["reference_pitch"]
|
||||
if "pathway" in updates:
|
||||
current["pathway"] = updates["pathway"]
|
||||
if "tuning" in updates:
|
||||
current["tuning"] = updates["tuning"]
|
||||
else:
|
||||
@@ -364,6 +377,7 @@ def apply_flat_instrument_patch_to_profiles(cfg: dict, updates: dict) -> dict:
|
||||
"string_count": profile["string_count"],
|
||||
"tuning": profile["tuning"],
|
||||
"reference_pitch": profile["reference_pitch"],
|
||||
"pathway": profile["pathway"],
|
||||
})
|
||||
return out
|
||||
|
||||
@@ -408,4 +422,4 @@ def tuning_name(offsets: list[int]) -> str:
|
||||
|
||||
if not offsets:
|
||||
return "Unknown"
|
||||
return "Custom Tuning"
|
||||
return "Custom Tuning"
|
||||
|
||||
@@ -43,7 +43,7 @@ from song import (
|
||||
)
|
||||
from audio import find_wem_files, convert_wem
|
||||
from tunings import (
|
||||
DEFAULT_REFERENCE_PITCH, DEFAULT_TUNINGS, PROFILE_IDS,
|
||||
DEFAULT_REFERENCE_PITCH, DEFAULT_TUNINGS, PROFILE_IDS, PROFILE_PATHWAYS,
|
||||
apply_flat_instrument_patch_to_profiles, apply_reference_pitch,
|
||||
normalize_instrument_profiles, settings_with_instrument_profiles, tuning_name,
|
||||
)
|
||||
@@ -9324,6 +9324,13 @@ def save_settings(data: dict):
|
||||
else:
|
||||
return {"error": "tuning must be a name (string) or a list of semitone offsets"}
|
||||
|
||||
if "pathway" in data:
|
||||
raw = data["pathway"]
|
||||
if raw is not None:
|
||||
if not isinstance(raw, str) or raw not in PROFILE_PATHWAYS:
|
||||
return {"error": "pathway must be one of songs, practice, learn, studio"}
|
||||
updates["pathway"] = raw
|
||||
|
||||
if "instrument_profiles" in data:
|
||||
raw = data["instrument_profiles"]
|
||||
if raw is not None:
|
||||
@@ -9369,7 +9376,7 @@ def save_settings(data: dict):
|
||||
_RESETTABLE_SETTINGS_KEYS = frozenset({
|
||||
"default_arrangement", "demucs_server_url", "master_difficulty",
|
||||
"av_offset_ms", "countdown_before_song", "miss_penalty", "fail_behavior",
|
||||
"reference_pitch", "instrument", "string_count", "tuning",
|
||||
"reference_pitch", "instrument", "string_count", "tuning", "pathway",
|
||||
"instrument_profiles", "active_instrument_profile",
|
||||
"achievements_enabled", "use_amp_sims",
|
||||
})
|
||||
@@ -9486,6 +9493,10 @@ def _validate_server_config_types(cfg: dict) -> str | None:
|
||||
return "server_config.tuning offsets must be ≤8 integers between -12 and 12"
|
||||
else:
|
||||
return "server_config.tuning must be a name (string) or a list of semitone offsets"
|
||||
if "pathway" in cfg:
|
||||
v = cfg["pathway"]
|
||||
if v is not None and (not isinstance(v, str) or v not in PROFILE_PATHWAYS):
|
||||
return "server_config.pathway must be one of songs, practice, learn, studio"
|
||||
if "instrument_profiles" in cfg:
|
||||
profiles, error = normalize_instrument_profiles(cfg["instrument_profiles"])
|
||||
if error:
|
||||
@@ -12076,4 +12087,3 @@ def index_v2():
|
||||
# Always serve the classic v2 UI, independent of the env var, so the
|
||||
# fallback is reachable without flipping FEEDBACK_UI.
|
||||
return FileResponse(str(STATIC_DIR / "index.html"))
|
||||
|
||||
|
||||
@@ -2758,6 +2758,12 @@ function goFavTreePage(p) {
|
||||
// ── Settings ─────────────────────────────────────────────────────────────
|
||||
let _defaultArrangement = '';
|
||||
|
||||
const INSTRUMENT_PATHWAYS = ['songs', 'practice', 'learn', 'studio'];
|
||||
|
||||
function _normalizeInstrumentPathway(value) {
|
||||
return INSTRUMENT_PATHWAYS.includes(value) ? value : 'songs';
|
||||
}
|
||||
|
||||
function _syncDefaultArrangementSelect(value) {
|
||||
const sel = document.getElementById('default-arrangement');
|
||||
if (!sel) return;
|
||||
@@ -3410,6 +3416,8 @@ async function loadSettings() {
|
||||
if (dlcEl) dlcEl.value = data.dlc_dir || '';
|
||||
_defaultArrangement = data.default_arrangement || '';
|
||||
_syncDefaultArrangementSelect(_defaultArrangement);
|
||||
const pathwayEl = document.getElementById('setting-instrument-pathway');
|
||||
if (pathwayEl) pathwayEl.value = _normalizeInstrumentPathway(data.pathway);
|
||||
const demucsEl = document.getElementById('demucs-server-url');
|
||||
if (demucsEl) demucsEl.value = data.demucs_server_url || '';
|
||||
const leftyEl = document.getElementById('setting-lefty');
|
||||
@@ -3901,6 +3909,18 @@ function persistSetting(key, value) {
|
||||
_settingSaveChain = next.catch(() => {});
|
||||
return next;
|
||||
}
|
||||
function setInstrumentPathway(value) {
|
||||
const pathway = _normalizeInstrumentPathway(value);
|
||||
const el = document.getElementById('setting-instrument-pathway');
|
||||
if (el) el.value = pathway;
|
||||
persistSetting('pathway', pathway).then(() => {
|
||||
if (window.v3Badges && typeof window.v3Badges.reload === 'function') {
|
||||
try { window.v3Badges.reload(); } catch (_) { /* noop */ }
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
async function _postSetting(key, value) {
|
||||
const status = document.getElementById('settings-status');
|
||||
try {
|
||||
|
||||
+41
-2
@@ -22,6 +22,12 @@
|
||||
{ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c]));
|
||||
|
||||
const STRING_COUNTS = { guitar: [6, 7, 8], bass: [4, 5, 6] };
|
||||
const PATHWAY_OPTIONS = [
|
||||
{ id: 'songs', label: 'Songs' },
|
||||
{ id: 'practice', label: 'Practice' },
|
||||
{ id: 'learn', label: 'Learn' },
|
||||
{ id: 'studio', label: 'Studio' },
|
||||
];
|
||||
// Tuning names per instrument key (e.g. 'guitar-6', 'bass-4'), loaded from
|
||||
// GET /api/tunings. Falls back to empty arrays until the fetch resolves.
|
||||
let _tuningsByKey = {};
|
||||
@@ -106,7 +112,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
let settings = { instrument: 'guitar', string_count: 6, tuning: 'Standard', reference_pitch: 440 };
|
||||
let settings = { instrument: 'guitar', string_count: 6, tuning: 'Standard', reference_pitch: 440, pathway: 'songs', instrument_profiles: {}, active_instrument_profile: 'guitar-lead' };
|
||||
|
||||
async function loadTunings() {
|
||||
try {
|
||||
@@ -126,6 +132,15 @@
|
||||
} catch (_) { /* non-fatal — TUNINGS falls back to empty, dropdown shows nothing */ }
|
||||
}
|
||||
|
||||
function pathwayForProfile(profiles, profileId, fallback) {
|
||||
const p = profiles && profiles[profileId];
|
||||
return p && PATHWAY_OPTIONS.some((o) => o.id === p.pathway) ? p.pathway : (fallback || 'songs');
|
||||
}
|
||||
|
||||
function profileIdForInstrument(inst) {
|
||||
return inst === 'bass' ? 'bass' : 'guitar-lead';
|
||||
}
|
||||
|
||||
async function loadSettings() {
|
||||
try {
|
||||
const r = await fetch('/api/settings');
|
||||
@@ -150,16 +165,34 @@
|
||||
if (typeof s.tuning === 'string') tuning = tunings.includes(s.tuning) ? s.tuning : (tunings[0] || 'Standard');
|
||||
else if (Array.isArray(s.tuning)) tuning = s.tuning;
|
||||
else tuning = tunings[0] || 'Standard';
|
||||
const profiles = s.instrument_profiles && typeof s.instrument_profiles === 'object' ? s.instrument_profiles : {};
|
||||
const pathway = PATHWAY_OPTIONS.some((o) => o.id === s.pathway) ? s.pathway : 'songs';
|
||||
settings = {
|
||||
instrument: instrument,
|
||||
string_count: scValid,
|
||||
tuning: tuning,
|
||||
reference_pitch: Math.min(450, Math.max(430, ref)),
|
||||
pathway: pathway,
|
||||
instrument_profiles: profiles,
|
||||
active_instrument_profile: typeof s.active_instrument_profile === 'string' ? s.active_instrument_profile : profileIdForInstrument(instrument),
|
||||
};
|
||||
}
|
||||
} catch (e) { /* settings endpoint always present */ }
|
||||
}
|
||||
|
||||
function syncLocalProfilePatch(patch) {
|
||||
const profileId = profileIdForInstrument(patch.instrument || settings.instrument);
|
||||
if (!settings.instrument_profiles || typeof settings.instrument_profiles !== 'object') settings.instrument_profiles = {};
|
||||
if (patch.instrument) settings.active_instrument_profile = profileId;
|
||||
const profile = Object.assign({}, settings.instrument_profiles[profileId] || {});
|
||||
let changed = false;
|
||||
if (patch.instrument) { profile.instrument = patch.instrument; changed = true; }
|
||||
if (patch.string_count != null) { profile.string_count = patch.string_count; changed = true; }
|
||||
if (patch.tuning != null) { profile.tuning = patch.tuning; changed = true; }
|
||||
if (patch.reference_pitch != null) { profile.reference_pitch = patch.reference_pitch; changed = true; }
|
||||
if (patch.pathway != null) { profile.pathway = patch.pathway; changed = true; }
|
||||
if (changed) settings.instrument_profiles[profileId] = profile;
|
||||
}
|
||||
async function saveSettings(patch) {
|
||||
// Only adopt the patch once the server accepts it. /api/settings returns
|
||||
// {error: ...} with HTTP 200 on a validation failure, so a rejected
|
||||
@@ -177,8 +210,9 @@
|
||||
} catch (e) { /* non-fatal — leave settings unchanged */ }
|
||||
if (!accepted) return false;
|
||||
Object.assign(settings, patch);
|
||||
syncLocalProfilePatch(patch);
|
||||
if (sm && sm.emit) sm.emit('instrument:changed', {
|
||||
instrument: settings.instrument, stringCount: settings.string_count, tuning: settings.tuning,
|
||||
instrument: settings.instrument, stringCount: settings.string_count, tuning: settings.tuning, pathway: settings.pathway,
|
||||
});
|
||||
pushToTuner();
|
||||
renderTuner(); // reflect new tuning on the tuner card
|
||||
@@ -424,6 +458,9 @@
|
||||
// (picking a named tuning still works and replaces the custom one).
|
||||
(typeof settings.tuning === 'string' ? '' : '<option selected disabled>Custom</option>') +
|
||||
_tuningsForInstrument(settings.instrument, settings.string_count).map((t) => '<option' + (t === settings.tuning ? ' selected' : '') + '>' + esc(t) + '</option>').join('') + '</select></div>' +
|
||||
'<div><div class="text-[0.625rem] uppercase tracking-wider text-fb-textDim mb-1">Pathway</div>' +
|
||||
'<select data-inst-pathway class="w-full bg-gray-800/50 border border-gray-700 rounded-md px-2 py-1.5 text-xs text-fb-text outline-none focus:border-fb-primary">' +
|
||||
PATHWAY_OPTIONS.map((p) => '<option value="' + esc(p.id) + '"' + (p.id === settings.pathway ? ' selected' : '') + '>' + esc(p.label) + '</option>').join('') + '</select></div>' +
|
||||
'<div><div class="flex justify-between text-[0.625rem] uppercase tracking-wider text-fb-textDim mb-1"><span>Reference pitch</span><span data-ref-val>' + settings.reference_pitch + ' Hz</span></div>' +
|
||||
'<input data-inst-ref type="range" min="430" max="450" step="1" value="' + settings.reference_pitch + '" class="w-full slider-input"></div>' +
|
||||
'</div></div>';
|
||||
@@ -454,6 +491,7 @@
|
||||
instrument: v,
|
||||
string_count: newSc,
|
||||
tuning: tunings.includes(settings.tuning) ? settings.tuning : (tunings[0] || settings.tuning),
|
||||
pathway: pathwayForProfile(settings.instrument_profiles, profileIdForInstrument(v), settings.pathway),
|
||||
});
|
||||
// Only move the working-tuning context once the switch was actually persisted —
|
||||
// otherwise the selector stays on the old instrument while the card shows the
|
||||
@@ -467,6 +505,7 @@
|
||||
renderInstrument(); keepOpen();
|
||||
}));
|
||||
menu.querySelector('[data-inst-tuning]').addEventListener('change', (e) => saveSettings({ tuning: e.target.value }));
|
||||
menu.querySelector('[data-inst-pathway]').addEventListener('change', (e) => saveSettings({ pathway: e.target.value }));
|
||||
const ref = menu.querySelector('[data-inst-ref]');
|
||||
ref.addEventListener('input', (e) => { menu.querySelector('[data-ref-val]').textContent = e.target.value + ' Hz'; });
|
||||
ref.addEventListener('change', (e) => saveSettings({ reference_pitch: Number(e.target.value) }));
|
||||
|
||||
@@ -429,6 +429,23 @@
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Instrument pathway -->
|
||||
<div class="fb-srow">
|
||||
<span class="fb-srow-icon"><svg fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 20l-5.447-2.724A1 1 0 013 16.382V5.618a1 1 0 011.447-.894L9 7m0 13l6-3m-6 3V7m6 10l4.553 2.276A1 1 0 0021 18.382V7.618a1 1 0 00-1.447-.894L15 4m0 13V4m0 0L9 7"/></svg></span>
|
||||
<div class="fb-srow-main">
|
||||
<div class="fb-srow-title">Instrument pathway</div>
|
||||
<div class="fb-srow-desc">Preferred path for the selected instrument. This is remembered per instrument profile.</div>
|
||||
</div>
|
||||
<div class="fb-srow-control">
|
||||
<select id="setting-instrument-pathway" onchange="setInstrumentPathway(this.value)"
|
||||
class="bg-dark-700 border border-gray-800 rounded-xl px-3 py-2.5 text-sm text-gray-300 outline-none">
|
||||
<option value="songs">Songs</option>
|
||||
<option value="practice">Practice</option>
|
||||
<option value="learn">Learn</option>
|
||||
<option value="studio">Studio</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Arrangement routes (naming mode) -->
|
||||
<div class="fb-srow">
|
||||
<span class="fb-srow-icon"><svg fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 20l-5.447-2.724A1 1 0 013 16.382V5.618a1 1 0 011.447-.894L9 7m0 13l6-3m-6 3V7m6 10l4.553 2.276A1 1 0 0021 18.382V7.618a1 1 0 00-1.447-.894L15 4m0 13V4m0 0L9 7"/></svg></span>
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
var RESET_MAP = {
|
||||
gameplay: {
|
||||
server: ['master_difficulty', 'av_offset_ms', 'miss_penalty',
|
||||
'fail_behavior', 'countdown_before_song', 'default_arrangement'],
|
||||
'fail_behavior', 'countdown_before_song', 'default_arrangement', 'pathway'],
|
||||
local: ['lefty', 'autoplayExit', 'showUpNext', 'confirmExitSong', 'arrangementNamingMode', 'countdownBeforeSong'],
|
||||
after: function () {
|
||||
// Left-handed is held on the highway object, not re-derived
|
||||
|
||||
@@ -752,17 +752,20 @@ def test_get_settings_exposes_default_instrument_profiles(client, tmp_path):
|
||||
assert data["instrument"] == "guitar"
|
||||
assert data["string_count"] == 6
|
||||
assert data["tuning"] == "Standard"
|
||||
assert data["pathway"] == "songs"
|
||||
|
||||
|
||||
def test_post_flat_instrument_updates_active_profile(client, tmp_path):
|
||||
r = client.post("/api/settings", json={"instrument": "bass"})
|
||||
r = client.post("/api/settings", json={"instrument": "bass", "pathway": "practice"})
|
||||
assert r.status_code == 200
|
||||
cfg = _read_cfg(tmp_path)
|
||||
assert cfg["active_instrument_profile"] == "bass"
|
||||
assert cfg["instrument"] == "bass"
|
||||
assert cfg["string_count"] == 4
|
||||
assert cfg["tuning"] == "Standard"
|
||||
assert cfg["pathway"] == "practice"
|
||||
assert cfg["instrument_profiles"]["bass"]["string_count"] == 4
|
||||
assert cfg["instrument_profiles"]["bass"]["pathway"] == "practice"
|
||||
|
||||
|
||||
def test_post_instrument_profiles_mirrors_active_profile(client, tmp_path):
|
||||
@@ -773,6 +776,7 @@ def test_post_instrument_profiles_mirrors_active_profile(client, tmp_path):
|
||||
"string_count": 7,
|
||||
"tuning": "Drop A",
|
||||
"reference_pitch": 432,
|
||||
"pathway": "studio",
|
||||
},
|
||||
"bass": {
|
||||
"string_count": 6,
|
||||
@@ -787,6 +791,14 @@ def test_post_instrument_profiles_mirrors_active_profile(client, tmp_path):
|
||||
assert cfg["string_count"] == 7
|
||||
assert cfg["tuning"] == "Drop A"
|
||||
assert cfg["reference_pitch"] == 432
|
||||
assert cfg["pathway"] == "studio"
|
||||
|
||||
|
||||
def test_post_pathway_rejects_bad_value(client, tmp_path):
|
||||
(tmp_path / "config.json").write_text(json.dumps({"pathway": "songs"}))
|
||||
r = client.post("/api/settings", json={"pathway": "invalid"})
|
||||
assert "error" in r.json()
|
||||
assert _read_cfg(tmp_path)["pathway"] == "songs"
|
||||
|
||||
|
||||
def test_post_instrument_profiles_rejects_bad_custom_string_count(client, tmp_path):
|
||||
@@ -803,18 +815,20 @@ def test_reset_clears_requested_keys(client, tmp_path):
|
||||
(tmp_path / "config.json").write_text(json.dumps({
|
||||
"master_difficulty": 40,
|
||||
"countdown_before_song": True,
|
||||
"pathway": "studio",
|
||||
"default_arrangement": "Lead",
|
||||
"demucs_server_url": "http://demucs.example:9000",
|
||||
}))
|
||||
r = client.post("/api/settings/reset",
|
||||
json={"keys": ["master_difficulty", "countdown_before_song"]})
|
||||
json={"keys": ["master_difficulty", "countdown_before_song", "pathway"]})
|
||||
assert r.status_code == 200
|
||||
body = r.json()
|
||||
assert set(body["reset"]) == {"master_difficulty", "countdown_before_song"}
|
||||
assert set(body["reset"]) == {"master_difficulty", "countdown_before_song", "pathway"}
|
||||
cfg = _read_cfg(tmp_path)
|
||||
# Reset removes the key so GET falls back to the default.
|
||||
assert "master_difficulty" not in cfg
|
||||
assert "countdown_before_song" not in cfg
|
||||
assert "pathway" not in cfg
|
||||
# Unlisted keys are untouched.
|
||||
assert cfg["default_arrangement"] == "Lead"
|
||||
assert cfg["demucs_server_url"] == "http://demucs.example:9000"
|
||||
@@ -841,4 +855,3 @@ def test_reset_with_no_config_is_noop(client, tmp_path):
|
||||
r = client.post("/api/settings/reset", json={"keys": ["master_difficulty"]})
|
||||
assert r.status_code == 200
|
||||
assert r.json()["reset"] == []
|
||||
|
||||
|
||||
+12
-2
@@ -186,6 +186,8 @@ def test_settings_profiles_default_to_lead_rhythm_and_bass():
|
||||
assert settings["instrument"] == "guitar"
|
||||
assert settings["string_count"] == 6
|
||||
assert settings["tuning"] == "Standard"
|
||||
assert settings["pathway"] == "songs"
|
||||
assert settings["instrument_profiles"]["guitar-lead"]["pathway"] == "songs"
|
||||
|
||||
|
||||
def test_settings_profiles_migrate_legacy_flat_bass_selection():
|
||||
@@ -194,11 +196,14 @@ def test_settings_profiles_migrate_legacy_flat_bass_selection():
|
||||
"string_count": 6,
|
||||
"tuning": "C Standard",
|
||||
"reference_pitch": 432,
|
||||
"pathway": "practice",
|
||||
})
|
||||
assert settings["active_instrument_profile"] == "bass"
|
||||
assert settings["instrument_profiles"]["bass"]["string_count"] == 6
|
||||
assert settings["instrument_profiles"]["bass"]["tuning"] == "C Standard"
|
||||
assert settings["reference_pitch"] == 432
|
||||
assert settings["pathway"] == "practice"
|
||||
assert settings["instrument_profiles"]["bass"]["pathway"] == "practice"
|
||||
|
||||
|
||||
def test_flat_patch_updates_active_profile_and_mirrors_legacy_keys():
|
||||
@@ -208,6 +213,13 @@ def test_flat_patch_updates_active_profile_and_mirrors_legacy_keys():
|
||||
assert patched["instrument_profiles"]["guitar-lead"]["tuning"] == "Drop D"
|
||||
|
||||
|
||||
def test_flat_pathway_patch_updates_active_profile_and_mirrors_legacy_key():
|
||||
settings = settings_with_instrument_profiles({})
|
||||
patched = apply_flat_instrument_patch_to_profiles(settings, {"pathway": "studio"})
|
||||
assert patched["pathway"] == "studio"
|
||||
assert patched["instrument_profiles"]["guitar-lead"]["pathway"] == "studio"
|
||||
|
||||
|
||||
def test_flat_instrument_patch_defaults_to_target_string_count():
|
||||
settings = settings_with_instrument_profiles({"instrument": "guitar", "string_count": 6, "tuning": "Drop D"})
|
||||
patched = apply_flat_instrument_patch_to_profiles(settings, {"instrument": "bass"})
|
||||
@@ -223,5 +235,3 @@ def test_flat_string_count_patch_resets_incompatible_named_tuning():
|
||||
patched = apply_flat_instrument_patch_to_profiles(settings, {"string_count": 7})
|
||||
assert patched["string_count"] == 7
|
||||
assert patched["tuning"] == "Standard"
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user