From 8a2175aa1ccc1edafcbf8ca0f1eadc8833338717 Mon Sep 17 00:00:00 2001 From: Byron Gamatos Date: Mon, 29 Jun 2026 00:13:28 +0200 Subject: [PATCH] feat(onboarding): amp-sim opt-in choice + use_amp_sims setting (#631) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Second half of feedBack-desktop#46. The desktop app monitors through an in-app amp-sim/tone chain that, once loaded, auto-restores every launch — an idle high-gain amp on the input is a constant distorted buzz, and the dry-only monitor mute can't silence it. This adds the "own-rig first" opt-in so players using their own external amp/rig never get a processed monitor in the first place. Core changes: - New `use_amp_sims` setting (default OFF / own-rig first): GET default, POST boolean validation, and resettable key — mirroring achievements_enabled. - Onboarding wizard: a DESKTOP-ONLY step ("How do you want to hear yourself?") between instrument paths and the calibration challenge. The web build has no native amp sims, so the step is skipped there (5 steps on web, 6 on desktop) — gated on window.feedBackDesktop, dot count and setStep bounds are derived from it. Ticking "Use in-app amp simulations" POSTs use_amp_sims; default unticked. The desktop renderer consumes this setting to gate its saved-tone-chain restore (feedback-desktop PR, paired). Verified by booting core locally and walking the wizard with Playwright: web shows 5 dots/no amp step, desktop shows 6 dots, the amp step is reachable, calibration stays the final "Play it now" step, ticking the box persists use_amp_sims=true, and there are no page errors. Server-side GET default / POST validation / reset confirmed via curl. Co-authored-by: Claude Opus 4.8 (1M context) --- server.py | 17 ++++++++++++- static/v3/profile.js | 58 ++++++++++++++++++++++++++++++++++++-------- 2 files changed, 64 insertions(+), 11 deletions(-) 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', {