Commit 853eeaf7 by PLN (Algolia)

feat(foundry): expose demucs model variants — instant 4-stem A/B (#9, easy win)

Groundwork toward PLN's "merge-of-stems multimodel": the finding that reshaped #9
is that there is NO 4-stem Roformer — all Roformer models are 2-stem; the only
4-stem (drums/bass/other/vocals) separators are the Demucs family. So the immediate,
zero-risk stem-quality A/B is between demucs variants, which the DemucsBackend already
supports via its `model` arg.

Backends now declare a `models` list (demucs: htdemucs / htdemucs_ft / hdemucs_mmi /
htdemucs_6s). /api/backends exposes it; the Foundry GUI gains a model dropdown beside
the engine picker (hidden when a backend has ≤1 model), passed through to /api/separate.
So PLN can now separate with htdemucs_ft (fine-tuned: vocals SDR 10.8 vs 9.9, bass 12.0
vs 11.6) or htdemucs_6s (adds guitar+piano stems) and compare — no new backend.

Verified: build_cmd respects the chosen model (-n htdemucs_ft), /api/backends returns
the list, the selector renders all four. The full multimodel engine2 (Roformer-vocals
+ demucs-rest merged, the door PLN asked to keep open) stays on #9 with the design.
parent e0635928
......@@ -28,6 +28,7 @@ class Backend:
name = "base"
default_model = ""
models: list[str] = [] # selectable model variants (for the GUI/CLI)
def available(self) -> tuple[bool, str]:
"""(is_runnable, human hint). Override."""
......@@ -46,6 +47,9 @@ class DemucsBackend(Backend):
name = "demucs"
default_model = "htdemucs"
# 4-stem variants (htdemucs_ft = fine-tuned, higher SDR; 6s adds guitar+piano).
# A drop-in stem-quality A/B — no new backend needed (#9).
models = ["htdemucs", "htdemucs_ft", "hdemucs_mmi", "htdemucs_6s"]
@property
def bin(self) -> str:
......
......@@ -156,7 +156,7 @@ class Handler(SimpleHTTPRequestHandler):
for name, be in S.REGISTRY.items():
ok, hint = be.available()
out.append({"name": name, "available": ok, "hint": hint,
"default_model": be.default_model})
"default_model": be.default_model, "models": be.models})
return self._json(out)
if p == "/api/catches":
return self._json([self._catch_dict(c) for c in self._catches()])
......
......@@ -108,6 +108,7 @@
<div class="opts">
<span>engine</span>
<select id="backend"></select>
<select id="model" title="separation model"></select>
<label>shifts <input type="number" id="shifts" value="4" min="0" max="10"></label>
<button class="ghost" id="refresh">↻ refresh</button>
</div>
......@@ -130,6 +131,17 @@ async function loadBackends(){
o.value=b.name; o.textContent=b.available?b.name:`${b.name} (soon)`;
o.disabled=!b.available; sel.appendChild(o);
});
sel.onchange=fillModels; fillModels();
}
function fillModels(){
const b=backends.find(x=>x.name===$("#backend").value)||backends[0];
const m=$("#model"); m.innerHTML="";
(b?.models||[]).forEach(name=>{
const o=document.createElement("option"); o.value=name;
o.textContent = name===b.default_model ? `${name} (default)` : name;
if(name===b.default_model)o.selected=true; m.appendChild(o);
});
m.style.display = (b?.models||[]).length>1 ? "" : "none";
}
function stemRow(slug, st){
......@@ -296,7 +308,8 @@ async function cast(){
async function separate(slug, btn){
btn.disabled=true; btn.textContent="…";
btn.insertAdjacentHTML("afterend",`<div class="bar"><div id="barfill"></div></div>`);
const body={slug, backend:$("#backend").value, shifts:Number($("#shifts").value)};
const body={slug, backend:$("#backend").value, model:$("#model").value,
shifts:Number($("#shifts").value)};
const j=await api("/api/separate",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(body)});
if(j.error){ btn.disabled=false; btn.textContent="Separate"; return toast(j.error,true); }
poll(j.id, ()=>render());
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment