Summary
Tester feedback (verbatim excerpts):
"you can start an editor with an empty canvas and I don't know what instrument that is. Is that guitar or bass?"
"if you start with one instrument [...] and you later want to add a bass I don't think you can do that. There are buttons to add drums and there are buttons to add keyboards, but there are no buttons to add any other instrument"
"There are no buttons to re-order the tracks [...] There is no way to rename a track if it says lead and it really should be lead 2. Some tracks are called combo when they're really a lead or rhythm guitar"
"it really feels like the editor was built to get off the ground with an import and not so much add a track, finish it, add another track, finish it [...] go as long as you want"
This RFC proposes reworking the editor from import-first to create-first: start a song from nothing, add tracks of any instrument one at a time (blank or imported), rename/reorder/delete them, and import stems/GP material later if wanted. It also closes the feedpak-spec gaps that make this fragile today, and brings back stem splitting (remote demucs) in-editor.
Note: #37 (merged) already added "+ Guitar/Bass" GP-track import into an open session, which addresses the narrow "add a bass later" case — but only via a Guitar Pro file. Everything else below is still open.
Current state (verified at 270d564, spec 1.13.0)
New-song flow is import-first, and the blank path is dead code. The toolbar "New…" button opens the GP-import modal directly (screen.html:23 → editorShowCreateModal, screen.js:5542). A genuine blank-song builder editorShowCreateSloppakModal (screen.js:5588 — audio drop + metadata + initial Lead/Rhythm/Bass + optional empty drum tab) and a "what are you making?" picker editorShowNewFormatPicker (screen.js:5477) both exist but are unreachable — the picker is never wired to any button. Dead HTML block #editor-create-blank-opts at screen.html:191-202.
Creating a song requires audio and locks the first track to guitar/bass. POST /create_sloppak (routes.py:3300) 400s without an audio file or audio_url, and rejects initial_arrangement outside {Lead, Rhythm, Bass} (routes.py:3371-3375). No blank-Keys or drums-first creation.
No instrument field anywhere — instrument is inferred from the display name. Bass = /bass/i on arr.name (screen.js:209, 230, 1725; backend _arrangement_string_count, routes.py:2238), keys = KEYS_PATTERN /^(keys|piano|keyboard|synth)/i (screen.js:64). The feedpak spec (§5.2) defines an optional arrangement type hint (guitar, bass, piano, …) for exactly this — the editor never writes or reads it. Manifests also omit feedpak_version (spec SHOULD). Consequence: renaming is semantically dangerous ("Bass Lead" → 4 lanes), which is presumably part of why there's no rename UI.
No rename, no reorder. The switcher is a bare <select> (screen.html:14) that is hidden entirely when the song has one arrangement (screen.js:3637) — so a single-track song shows no track UI at all, which is the tester's "I don't know what instrument that is". Good news, verified: the persistence plumbing for both rename and reorder already exists — sloppak save sends a full arrangements snapshot and the server prefers a client-supplied stable id over re-slugging from name (routes.py:2922-2928), and the sloppak loader preserves manifest order (lib/sloppak.py:402-467; the Lead>Combo>Rhythm sort is scanner-/archive-only). What's missing is purely UI + a couple of guards.
"Combo" is Rocksmith jargon leaking from the GP converter. lib/gp2rs.py:1418-1424: 1st guitar → "Lead", 2nd → "Rhythm", 3rd+ → "Combo" (repeated at gp2rs.py:2040-2043, gp2rs_gpx.py:1446). Bonus bug: the 4th+ guitar gets a duplicate "Combo" name (no counter on that branch).
Stem splitting is gone but the pieces to restore it exist. Local demucs was removed in the "Clean release snapshot" (scripts/split_stems.py is a stub). The remote feedBack-demucs-server is alive with POST /separate (htdemucs_ft / htdemucs_6s / bs_roformer_sw, per-stem caching, WS progress), core already has the demucs_server_url setting, and lib/lyrics_transcribe.py / lib/vocal_pitch.py establish the remote-client idiom — nobody calls /separate. The editor also carries a dead 🎚 Stems button (screen.html:33 → editorToggleStemMixer(), which no longer exists in screen.js).
Other bugs found on the way:
/replace-audio (routes.py:3849) rewrites manifest["stems"] but never updates manifest["duration"].
routes.py:2438-2440 builds the arrangement-id list from the manifest by index, but sloppak.load_song can skip malformed entries → misalignment; loaded.arrangement_ids (lib/sloppak.py:369) exists for this and is unused.
- Spec inconsistency: §5.2 lists
type: piano, but the §7.7 example at line 1178 says keys. Proposal: write piano, accept keys as an alias when reading (separate one-line spec errata).
Proposal
Three PRs. PR-A is in the main feedback repo, B and C here. Land A → B → C; each degrades gracefully alone (getattr guards; readers ignore unknown manifest keys per spec §1.2).
PR-A — feedback repo (lib/)
Arrangement.arr_type: str = "" (lib/song.py:155); arrangement_to_wire/from_wire carry type; sloppak loader passes entry["type"] through; FEEDPAK_VERSION constant in lib/sloppak.py; _tuning_for_meta prefers type == "guitar".
- gp2rs: replace "Combo" with a shared
_extra_guitar_name(track_name, used) — sanitized GP track name (rejecting names that would misroute convert_file's instrument dispatch at gp2rs.py:2024), falling back to "Guitar N", always de-duped (also fixes the duplicate-Combo bug). Set arr_type on converted arrangements. Untouched: the archive XML sort (song.py:1621-1629, load-bearing for test_arrangement_xml_alignment.py) and _NAME_FALLBACK's combo entry — instead add a guitar-ish-name → path_lead fallback there so GP-named tracks still classify for RS-XML export.
PR-B — this repo: create-first + tracks panel + spec type
B1. type foundation (makes rename safe). Backend: _infer_arr_type(name) helper; stamp type on load and in every manifest write (_write_sloppak_pak + snapshot-save entry build); write feedpak_version; _arrangement_string_count prefers type; switch id stamping to loaded.arrangement_ids. Frontend: pure _arrTypeOf(arr) (prefers arr.type, keys→piano alias, name-regex fallback for legacy paks) replacing the ~15 scattered regex sites; import paths set type explicitly.
B2. Rename / reorder / delete. Rename pins type first (so renaming on a legacy pak can't flip lane count), mutates name only, never id; undoable (RenameArrangementCmd, modeled on ReplaceArrangementChartCmd). Reorder = splice + currentArr fixup by object identity; undoable; persists automatically via snapshot + manifest order. Delete refactors editorRemoveArrangement to take an index.
B3. Tracks panel. One "Tracks" button → modal listing every track: instrument icon, name, tuning summary, active highlight, click-to-switch, ▲▼ / ✎ / ✕ per row. Drum tab appears as a pinned row (song-level side-file; renameable, deletable — note _buildSaveBody must send explicit drum_tab: null, since JSON.stringify drops undefined). "+ Add Track" → Guitar / Bass / Keys / Drums, each Blank (new editorAddEmptyArrangement(type) mirroring editorAddEmptyKeys, unique name, correct tuning length, explicit type) or Import… (routes to the existing three modals). The three per-instrument toolbar buttons are removed; their modals stay, invoked from the panel. The arrangement <select> stays for quick switching and becomes always-visible.
B4. New Song flow. "New…" → picker (reviving the dead editorShowNewFormatPicker skeleton): Start from scratch (zero files), Start from audio (promoted blank builder), Import GP / EOF (existing modal, demoted). Start-from-scratch: the spec requires non-empty stems + duration, so the server generates a silent OGG (ffmpeg -f lavfi -i anullsrc…) from bars × BPM × time-sig inputs, seeds the full beat grid over that length, and the existing Replace Audio swaps in real audio later (fixed to update manifest["duration"]). create_sloppak drops the Lead/Rhythm/Bass enum: any name ≤64 chars + arrangement_type ∈ {guitar, bass, piano}, drums-first supported (empty drum tab + one empty guitar arrangement, since spec §5.1 requires non-empty arrangements).
B5. Instrument clarity. Always-visible arrangement indicator (icon + name + tuning) in the top bar even with one track; empty-arrangement canvas hint ("{icon} {name} — {tuning} — double-click to place a note").
PR-C — this repo: stem splitting (remote demucs)
POST /api/plugins/editor/split-stems {session_id, model?} → uploads the session's mixed stem to the demucs server's /separate (URL from the existing demucs_server_url core setting; clear 400 when unset), transcodes results to OGG, rewrites manifest["stems"] to the split stems, adds stem_separation provenance (spec §5.3.1). full.ogg stays on disk unreferenced as backup so a split can be undone or re-run with a different model; the editor keeps using it for its own playback/waveform when present. UI: "Split stems…" in the Tracks panel footer with model dropdown + progress. The dead 🎚 Stems button/panel HTML is removed — playback mixing belongs to feedBack-plugin-stems.
Compatibility
- New editor + old lib: type inferred from names (getattr guards); editor still writes
type itself.
- Old editor + new lib:
arr_type unread; server re-stamps on save.
- Old paks: name-regex fallback; first save upgrades the manifest; rename pins type first.
- Old readers of new paks: unknown manifest keys ignored per spec.
Risks
- Reorder
currentArr fixup must use object identity, not index math.
- GP-track-name sanitization must not collide with
convert_file's name dispatch.
anullsrc/lavfi availability in the bundled ffmpeg (verify early).
- Untitled scratch songs →
create_sloppak filename 409; client retry-with-suffix.
- Split must be atomic: write stems to temp, swap manifest only when all stems landed.
Verification sketch
- Scratch → Drums, 32 bars @ 140: drum edit mode, canvas hint, duration ≈ 54.9 s, full grid; save/reopen.
- Scratch Guitar (silence) → place notes → Replace Audio with real mp3 → duration updates; save/reopen.
- Tracks panel: add blank Bass (4 lanes immediately), rename Lead → "Lead 2" (still 6-lane, id/file stable), reorder, delete a track and the drum tab; inspect manifest.yaml (order,
type, feedpak_version).
- GP with 3+ guitar tracks: GP names / "Guitar 3", no "Combo",
type set.
- Legacy pak: load, rename, save, open on the 3D highway.
- Split stems: manifest lists split stems only,
full.ogg still on disk, provenance present, song opens in feedBack-plugin-stems mixer.
node tests/*.test.js; pytest tests/; feedback repo: pytest tests/test_gp2rs.py tests/test_gp2rs_gpx.py tests/test_arrangement_xml_alignment.py.
Summary
Tester feedback (verbatim excerpts):
This RFC proposes reworking the editor from import-first to create-first: start a song from nothing, add tracks of any instrument one at a time (blank or imported), rename/reorder/delete them, and import stems/GP material later if wanted. It also closes the feedpak-spec gaps that make this fragile today, and brings back stem splitting (remote demucs) in-editor.
Note: #37 (merged) already added "+ Guitar/Bass" GP-track import into an open session, which addresses the narrow "add a bass later" case — but only via a Guitar Pro file. Everything else below is still open.
Current state (verified at 270d564, spec 1.13.0)
New-song flow is import-first, and the blank path is dead code. The toolbar "New…" button opens the GP-import modal directly (
screen.html:23→editorShowCreateModal,screen.js:5542). A genuine blank-song buildereditorShowCreateSloppakModal(screen.js:5588— audio drop + metadata + initial Lead/Rhythm/Bass + optional empty drum tab) and a "what are you making?" pickereditorShowNewFormatPicker(screen.js:5477) both exist but are unreachable — the picker is never wired to any button. Dead HTML block#editor-create-blank-optsatscreen.html:191-202.Creating a song requires audio and locks the first track to guitar/bass.
POST /create_sloppak(routes.py:3300) 400s without an audio file oraudio_url, and rejectsinitial_arrangementoutside{Lead, Rhythm, Bass}(routes.py:3371-3375). No blank-Keys or drums-first creation.No instrument field anywhere — instrument is inferred from the display name. Bass =
/bass/ionarr.name(screen.js:209, 230, 1725; backend_arrangement_string_count,routes.py:2238), keys =KEYS_PATTERN /^(keys|piano|keyboard|synth)/i(screen.js:64). The feedpak spec (§5.2) defines an optional arrangementtypehint (guitar,bass,piano, …) for exactly this — the editor never writes or reads it. Manifests also omitfeedpak_version(spec SHOULD). Consequence: renaming is semantically dangerous ("Bass Lead" → 4 lanes), which is presumably part of why there's no rename UI.No rename, no reorder. The switcher is a bare
<select>(screen.html:14) that is hidden entirely when the song has one arrangement (screen.js:3637) — so a single-track song shows no track UI at all, which is the tester's "I don't know what instrument that is". Good news, verified: the persistence plumbing for both rename and reorder already exists — sloppak save sends a fullarrangementssnapshot and the server prefers a client-supplied stableidover re-slugging from name (routes.py:2922-2928), and the sloppak loader preserves manifest order (lib/sloppak.py:402-467; the Lead>Combo>Rhythm sort is scanner-/archive-only). What's missing is purely UI + a couple of guards."Combo" is Rocksmith jargon leaking from the GP converter.
lib/gp2rs.py:1418-1424: 1st guitar → "Lead", 2nd → "Rhythm", 3rd+ → "Combo" (repeated atgp2rs.py:2040-2043,gp2rs_gpx.py:1446). Bonus bug: the 4th+ guitar gets a duplicate "Combo" name (no counter on that branch).Stem splitting is gone but the pieces to restore it exist. Local demucs was removed in the "Clean release snapshot" (
scripts/split_stems.pyis a stub). The remote feedBack-demucs-server is alive withPOST /separate(htdemucs_ft / htdemucs_6s / bs_roformer_sw, per-stem caching, WS progress), core already has thedemucs_server_urlsetting, andlib/lyrics_transcribe.py/lib/vocal_pitch.pyestablish the remote-client idiom — nobody calls/separate. The editor also carries a dead 🎚 Stems button (screen.html:33→editorToggleStemMixer(), which no longer exists in screen.js).Other bugs found on the way:
/replace-audio(routes.py:3849) rewritesmanifest["stems"]but never updatesmanifest["duration"].routes.py:2438-2440builds the arrangement-id list from the manifest by index, butsloppak.load_songcan skip malformed entries → misalignment;loaded.arrangement_ids(lib/sloppak.py:369) exists for this and is unused.type: piano, but the §7.7 example at line 1178 sayskeys. Proposal: writepiano, acceptkeysas an alias when reading (separate one-line spec errata).Proposal
Three PRs. PR-A is in the main
feedbackrepo, B and C here. Land A → B → C; each degrades gracefully alone (getattr guards; readers ignore unknown manifest keys per spec §1.2).PR-A — feedback repo (
lib/)Arrangement.arr_type: str = ""(lib/song.py:155);arrangement_to_wire/from_wirecarrytype; sloppak loader passesentry["type"]through;FEEDPAK_VERSIONconstant inlib/sloppak.py;_tuning_for_metapreferstype == "guitar"._extra_guitar_name(track_name, used)— sanitized GP track name (rejecting names that would misrouteconvert_file's instrument dispatch atgp2rs.py:2024), falling back to "Guitar N", always de-duped (also fixes the duplicate-Combo bug). Setarr_typeon converted arrangements. Untouched: the archive XML sort (song.py:1621-1629, load-bearing fortest_arrangement_xml_alignment.py) and_NAME_FALLBACK's combo entry — instead add a guitar-ish-name →path_leadfallback there so GP-named tracks still classify for RS-XML export.PR-B — this repo: create-first + tracks panel + spec
typeB1.
typefoundation (makes rename safe). Backend:_infer_arr_type(name)helper; stamptypeon load and in every manifest write (_write_sloppak_pak+ snapshot-save entry build); writefeedpak_version;_arrangement_string_countpreferstype; switch id stamping toloaded.arrangement_ids. Frontend: pure_arrTypeOf(arr)(prefersarr.type,keys→piano alias, name-regex fallback for legacy paks) replacing the ~15 scattered regex sites; import paths settypeexplicitly.B2. Rename / reorder / delete. Rename pins
typefirst (so renaming on a legacy pak can't flip lane count), mutatesnameonly, neverid; undoable (RenameArrangementCmd, modeled onReplaceArrangementChartCmd). Reorder = splice +currentArrfixup by object identity; undoable; persists automatically via snapshot + manifest order. Delete refactorseditorRemoveArrangementto take an index.B3. Tracks panel. One "Tracks" button → modal listing every track: instrument icon, name, tuning summary, active highlight, click-to-switch, ▲▼ / ✎ / ✕ per row. Drum tab appears as a pinned row (song-level side-file; renameable, deletable — note
_buildSaveBodymust send explicitdrum_tab: null, sinceJSON.stringifydropsundefined). "+ Add Track" → Guitar / Bass / Keys / Drums, each Blank (neweditorAddEmptyArrangement(type)mirroringeditorAddEmptyKeys, unique name, correct tuning length, explicittype) or Import… (routes to the existing three modals). The three per-instrument toolbar buttons are removed; their modals stay, invoked from the panel. The arrangement<select>stays for quick switching and becomes always-visible.B4. New Song flow. "New…" → picker (reviving the dead
editorShowNewFormatPickerskeleton): Start from scratch (zero files), Start from audio (promoted blank builder), Import GP / EOF (existing modal, demoted). Start-from-scratch: the spec requires non-emptystems+duration, so the server generates a silent OGG (ffmpeg -f lavfi -i anullsrc…) from bars × BPM × time-sig inputs, seeds the full beat grid over that length, and the existing Replace Audio swaps in real audio later (fixed to updatemanifest["duration"]).create_sloppakdrops the Lead/Rhythm/Bass enum: any name ≤64 chars +arrangement_type ∈ {guitar, bass, piano}, drums-first supported (empty drum tab + one empty guitar arrangement, since spec §5.1 requires non-emptyarrangements).B5. Instrument clarity. Always-visible arrangement indicator (icon + name + tuning) in the top bar even with one track; empty-arrangement canvas hint ("{icon} {name} — {tuning} — double-click to place a note").
PR-C — this repo: stem splitting (remote demucs)
POST /api/plugins/editor/split-stems {session_id, model?}→ uploads the session's mixed stem to the demucs server's/separate(URL from the existingdemucs_server_urlcore setting; clear 400 when unset), transcodes results to OGG, rewritesmanifest["stems"]to the split stems, addsstem_separationprovenance (spec §5.3.1).full.oggstays on disk unreferenced as backup so a split can be undone or re-run with a different model; the editor keeps using it for its own playback/waveform when present. UI: "Split stems…" in the Tracks panel footer with model dropdown + progress. The dead 🎚 Stems button/panel HTML is removed — playback mixing belongs to feedBack-plugin-stems.Compatibility
typeitself.arr_typeunread; server re-stamps on save.Risks
currentArrfixup must use object identity, not index math.convert_file's name dispatch.anullsrc/lavfi availability in the bundled ffmpeg (verify early).create_sloppakfilename 409; client retry-with-suffix.Verification sketch
type,feedpak_version).typeset.full.oggstill on disk, provenance present, song opens in feedBack-plugin-stems mixer.node tests/*.test.js;pytest tests/; feedback repo:pytest tests/test_gp2rs.py tests/test_gp2rs_gpx.py tests/test_arrangement_xml_alignment.py.