diff --git a/server.py b/server.py index 0bd1791..0dbf258 100644 --- a/server.py +++ b/server.py @@ -5531,6 +5531,15 @@ def _default_settings(): # until the user opts in. Read by the bundled achievements plugin to # gate its wall-sync enqueue. "achievements_enabled": False, + # Amp-sim opt-in (issue feedBack-desktop#46). Whether the desktop app may + # auto-load an in-app amp-sim / tone chain (NAM / IR / VST) for input + # monitoring. Default OFF — "own-rig first": players monitoring through + # their own external amp/rig never get a processed monitor (and never the + # idle distorted buzz) until they opt in. Set during onboarding (desktop + # only) and from the desktop Audio settings toggle; read by the desktop + # renderer to gate its saved-chain restore. Inert on the pure-web build, + # which has no native amp sims. + "use_amp_sims": False, } @@ -5673,6 +5682,12 @@ def save_settings(data: dict): if not isinstance(raw, bool): return {"error": "achievements_enabled must be a boolean"} updates["achievements_enabled"] = raw + if "use_amp_sims" in data: + raw = data["use_amp_sims"] + if raw is not None: + if not isinstance(raw, bool): + return {"error": "use_amp_sims must be a boolean"} + updates["use_amp_sims"] = raw if "miss_penalty" in data: raw = data["miss_penalty"] if raw is not None: @@ -5763,7 +5778,7 @@ _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", - "achievements_enabled", + "achievements_enabled", "use_amp_sims", }) diff --git a/static/v3/profile.js b/static/v3/profile.js index 60ba4b3..d8e1985 100644 --- a/static/v3/profile.js +++ b/static/v3/profile.js @@ -330,9 +330,16 @@ const editing = !!opts.editing; document.getElementById('v3-onboarding')?.remove(); + // The amp-sim opt-in step (step 5) only exists in the desktop app — the + // pure-web build has no native amp sims to monitor through, so the step + // is skipped there (calibration is the last step at index 5 on web, 6 on + // desktop). See feedBack-desktop#46. + const isDesktop = !!window.feedBackDesktop; + const lastStep = isDesktop ? 6 : 5; + const stepDots = editing ? '' : '
' + - [1, 2, 3, 4, 5].map((n) => '').join('') + + Array.from({ length: lastStep }, (_, i) => i + 1).map((n) => '').join('') + '
'; const overlay = document.createElement('div'); @@ -380,8 +387,20 @@ '' + '

Each path levels up by completing challenges — together they make up your Mastery Rank. You can add more later.

' + '
' + - // Step 5 — calibration offer (first-run only). + // Step 5 — amp-sim opt-in (DESKTOP ONLY; default OFF / own-rig first). + // Hidden div is always present in the DOM; setStep only navigates to + // it on desktop. See feedBack-desktop#46. '' + + // Step 6 — calibration offer (first-run only). + '' + @@ -441,7 +460,7 @@ function setStep(n) { step = n; errEl.classList.add('hidden'); - for (let i = 1; i <= 5; i++) { + for (let i = 1; i <= 6; i++) { overlay.querySelector('#v3-ob-step' + i).classList.toggle('hidden', i !== n); } overlay.querySelectorAll('#v3-ob-dots [data-dot]').forEach((d) => { @@ -454,12 +473,13 @@ : n === 2 ? 'Point us at your songs' : n === 3 ? 'Feats of Power (optional)' : n === 4 ? 'Choose your instrument paths' + : n === 5 ? 'How do you want to monitor?' : 'One last thing — calibrate your setup'; } - submit.textContent = n === 5 ? 'Play it now' : 'Next'; + submit.textContent = n === 6 ? 'Play it now' : 'Next'; // Skip is offered on the song-directory step (configure later) and - // the calibration challenge. - skipBtn.classList.toggle('hidden', !(n === 2 || n === 5)); + // the calibration challenge (the last step). + skipBtn.classList.toggle('hidden', !(n === 2 || n === 6)); refreshSubmit(); } @@ -695,11 +715,29 @@ // New step: input-device selection + calibration, between // path selection and the note-detect calibration challenge. await runInputSetup(selectedPaths); - setStep(5); + setStep(isDesktop ? 5 : 6); } catch (e) { showErr(e.message || 'Could not save profile.'); refreshSubmit(); } return; } - // Step 5 — "Play it now": leave calibration pending (it completes + if (step === 5) { + // Step 5 (desktop only) — persist the amp-sim opt-in (default OFF + // / own-rig). Best-effort: a failed write must not block onboarding; + // it's settable later from the desktop Audio settings. + submit.disabled = true; + try { + const ampEl = overlay.querySelector('#v3-ob-ampsims'); + const useAmpSims = !!(ampEl && ampEl.checked); + try { + await fetch('/api/settings', { + method: 'POST', headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ use_amp_sims: useAmpSims }), + }); + } catch (e) { /* best-effort — settable later */ } + setStep(6); + } finally { refreshSubmit(); } + return; + } + // Step 6 — "Play it now": leave calibration pending (it completes // through the normal scored-stats path) and launch the diagnostic. const target = diagnosticFilename; await finish({ launchingSong: !!target }); @@ -713,8 +751,8 @@ setStep(3); return; } - // Step 5 — skip: Mastery Rank 1 immediately, calibration stays - // replayable from the Progress screen. + // Calibration step (last) — skip: Mastery Rank 1 immediately, + // calibration stays replayable from the Progress screen. skipBtn.disabled = true; try { const res = await fetch('/api/progression/onboarding', {