From 1b8ae08b0845bd84d1a472d5b392769dc3bbfd9f Mon Sep 17 00:00:00 2001 From: digital-garbage <133010426+digital-garbage@users.noreply.github.com> Date: Thu, 13 Aug 2026 15:15:58 +0300 Subject: [PATCH] feat(sampler): second pass gets an Enable switch; shorter hints The pass was derived from whether the schedule field had content, so turning it off meant deleting a schedule you wanted to keep. It has its own checkbox now and the schedule is just configuration; enabling without one is reported rather than silently ignored. Trims the second-pass and ALG hints from four paragraphs to a sentence each. The long-form detail stays behind the Details link, which is what it is for. --- movie_editor/frontend/engine_settings.js | 31 +++--------------- movie_editor/frontend/sampler_panel.js | 41 +++++++++++------------- 2 files changed, 23 insertions(+), 49 deletions(-) diff --git a/movie_editor/frontend/engine_settings.js b/movie_editor/frontend/engine_settings.js index fa34e5c4..43e91a61 100644 --- a/movie_editor/frontend/engine_settings.js +++ b/movie_editor/frontend/engine_settings.js @@ -242,7 +242,7 @@ { name: "v2a_grad_scale", label: "Video→audio coupling", kind: "float", default: 1.0, min: 0.0, max: 4.0, step: 0.25, dependsOn: "joyai_audio_memory", hint: "How much the carried audio follows the new shot's picture. 1.0 is the model's own behaviour and costs nothing; JoyAI uses 2.0; 0 makes audio ignore the video." }, { name: "alg_anchor", label: "Blur the i2v anchor (ALG)", kind: "bool", default: false, - hint: "Hides the anchor image's fine detail during the first, noisiest steps so the shot can't shortcut to a near-still that just matches it — the usual fix for an anchored scene that barely moves. Works with whatever sampler you have wired, including a plain KSampler, and does nothing on a scene with no anchor image." }, + hint: "Blurs the anchor's fine detail during the first, noisiest steps — the usual fix for an anchored scene that barely moves. No effect without an anchor image." }, { name: "alg_anchor_strength", label: "Anchor blur strength", kind: "float", default: 2.0, min: 1.0, max: 4.0, step: 0.1, dependsOn: "alg_anchor", hint: "How blurry the anchor gets while it's blurred. The paper says 2.5; 2.0 held character likeness noticeably better here." }, { name: "alg_anchor_sigma_threshold", label: "Anchor blur sigma threshold", kind: "float", default: 0.975, min: 0.5, max: 0.999, step: 0.005, dependsOn: "alg_anchor", @@ -634,22 +634,6 @@ const next = JSON.stringify({ ...cur, samplers: updatedSamplers }); if (quiet) S.setStudioInput("studio_settings", next); else S.setStudioInputNow("studio_settings", next); - syncSecondPassFromSchedule(updatedSamplers, quiet); - } - // The second pass is driven by ONE thing: the schedule. Giving it one turns it on; - // taking it away turns it off. There is no cut and no re-entry point to configure — - // pass 1 always runs the main schedule in full and pass 2 always runs this one in full. - // A schedule is either typed into the low pass's Sigmas field or COMPUTED by picking a - // scheduler for it; both must count here, or picking one would be an inert control. - function syncSecondPassFromSchedule(samplers, quiet) { - const raw = String(samplers?.low?.sigmas || "").replace(/;/g, ","); - const vals = raw.split(",").map((v) => parseFloat(v.trim())).filter((v) => !isNaN(v)); - const sched = String(samplers?.low?.scheduler || "use_user_sigmas"); - const computed = sched !== "use_user_sigmas" && Number(samplers?.low?.steps || 0) > 0; - const set = quiet ? S.setSamplerInput : S.setSamplerInputNow; - // one number is not a schedule - if (computed || vals.length >= 2) set("second_pass", true); - else S.unsetSamplerInput("second_pass"); } // ALG used to have two switches: the Distilled Flow panel's own alg_enabled, and the @@ -705,15 +689,10 @@ moved.style.color = "var(--accent)"; algG.append(moved); } - algG.append(hintEl("This is the only ALG anchor control — on Distilled Flow it runs inside " - + "the sampler loop, on every other sampler through a denoiser proxy. The same blur for " - + "guide and JoyAI-memory frames is under Experimental (“Blur i2v guides and JoyAI " - + "memory”) — it has its own strength and window.")); - // Second pass lives here rather than under Experimental: at its defaults the split is - // behaviour-neutral (pass 2 resumes from exactly the state pass 1 handed over), so it - // is a sampler setting, not a gamble. The only control is the schedule field in the - // panel above — enable and cut are derived from it (see syncSecondPassFromSchedule); - // all that is left here is the optional between-pass operation. + algG.append(hintEl("The same blur for guide and JoyAI-memory frames is under Experimental, " + + "with its own strength and window.")); + // Enable and schedule are in the panel above, with the sampler they belong to; this is + // the optional operation applied to the latent between the two passes. const g = group(pane, "Second pass"); renderKnobList(g, st, ["second_pass_op"]); const si = st.project.sampler_inputs || {}; diff --git a/movie_editor/frontend/sampler_panel.js b/movie_editor/frontend/sampler_panel.js index b30a39a4..ccee6bde 100644 --- a/movie_editor/frontend/sampler_panel.js +++ b/movie_editor/frontend/sampler_panel.js @@ -406,23 +406,19 @@ // reuses the sampler configured above, and rendering its algorithm settings here would // offer knobs that quietly do nothing. sectionTag(container, "Second pass"); + const spOn = !!(window.Store?.get()?.project?.sampler_inputs || {}).second_pass; + row(container, "Enable", checkCtrl(spOn, "sp-second-on", (v) => { + window.Store?.setSamplerInputNow?.("second_pass", v); + })); + hint(container, "Samples each scene twice. Pass 1 runs the main schedule in full, then " + + "pass 2 runs the one below in full — total steps are the two added up."); + const spSig = textCtrl(s.low.sigmas, "e.g. 0.812, 0.6, 0.35, 0.15, 0.0", "sp-second-sig", (v) => { s.low.sigmas = v; mkSave(true)(); }); - row(container, "Second pass schedule", spSig); -hint(container, "Fill this in and each scene is sampled in two passes; leave it empty and " - + "it isn't. Comma-separated floats, high to low, ending at 0. Pass 1 " - + "runs the main Sigmas schedule above in full, then pass 2 runs THIS " - + "one in full, so the total is simply the two added up (a 9-step main " - + "plus a 4-step second pass is 13). Pass 2 starts from the finished " - + "clip: it is handed in as the latent and the sampler noises it to your " - + "first sigma itself, exactly as any img2img does — no extra step in " - + "between. That first sigma is therefore the strength dial, and it is " - + "literal: at 0.8 pass 2 starts from 80% fresh noise over 20% of the " - + "pass-1 picture and will rework the shot (and look soft if it has few " - + "steps to resolve it); at 0.4 it is 40/60 and polishes; at 0.2 it is " - + "nearly pure detail work. The rest of the list sets how many steps it " - + "gets. To make pass 1 " - + "shorter, shorten the schedule above; nothing here cuts it short."); + row(container, "Schedule", spSig); + hint(container, "Comma-separated, high to low, ending at 0. The FIRST sigma is the strength " + + "dial: 0.8 reworks the shot, 0.4 polishes, 0.2 is detail work. The rest sets " + + "how many steps pass 2 gets."); // Pass 2 can be computed instead of typed, same switch as pass 1 — its SIGMAS is the // only thing of the low pass that is wired, so a schedule chosen here IS pass 2. @@ -430,19 +426,18 @@ hint(container, "Fill this in and each scene is sampled in two passes; leave it if (s.low.steps === undefined) s.low.steps = s.low.ksampler_steps === undefined ? 4 : s.low.ksampler_steps; const spScheds = ksamplerSchedulers(mkSave(false)); const spSchedCur = s.low.scheduler || KSAMPLER_USER_SIGMAS; - row(container, "Second pass schedule (computed)", + row(container, "Schedule (computed)", selCtrl(spScheds.includes(spSchedCur) ? spScheds : spScheds.concat([spSchedCur]), spSchedCur, "sp-second-sch", (v) => { s.low.scheduler = v; mkSave(false)(); })); - hint(container, "Leave on '" + KSAMPLER_USER_SIGMAS + "' to use the typed field above. Pick a " - + "scheduler and pass 2 runs a computed schedule of the step count below instead " - + "— which also TURNS THE SECOND PASS ON, whatever the field above says. A full " - + "computed schedule starts at the model's top sigma, so pass 2 reworks the shot " - + "rather than polishing it; for a polish, type the short low-sigma list above " - + "and leave this alone."); + hint(container, "Leave on '" + KSAMPLER_USER_SIGMAS + "' to use the field above. A computed " + + "schedule starts at the model's top sigma, so pass 2 reworks rather than polishes."); if (spSchedCur !== KSAMPLER_USER_SIGMAS) { - row(container, "Second pass steps", + row(container, "Steps", intCtrl(s.low.steps, 1, 200, "sp-second-steps", (v) => { s.low.steps = v; mkSave(true)(); })); } + if (spOn && spSchedCur === KSAMPLER_USER_SIGMAS && !String(s.low.sigmas || "").trim()) { + hint(container, "Pass 2 is on but has no schedule — give it one above, or pick a computed one."); + } } window.SamplerPanel = { render, defaultSamplers, defaultPass };