mirror of
https://github.com/got-feedBack/feedBack.git
synced 2026-08-11 11:19:24 +00:00
Edit region: Loop-in-3D round-trip between player and Song Editor (#575)
* Add "Edit region" + Loop-in-3D handoff between player and Song Editor Wires the player half of the Editor ⇄ 3D Highway region round-trip (editor half is in feedback-plugin-editor). Highway → Editor: - New "✎ Edit region" button in the loop controls (v2 and v3) opens the Song Editor scrolled to the active A–B loop — or, when none is set, the section under the playhead (or a short window around it). - A "↩ Editor" button appears after a Loop-in-3D handoff to return to the exact edit position you came from. - Both are hidden unless the editor plugin is loaded (typeof window.editSong) and gated by _updateEditRegionBtn. Editor → Highway: - A one-shot song:ready listener consumes window._pendingHighwayLoop set by the editor's "Loop in 3D" button — after playSong()'s own clearLoop() has run — arming setLoop(a,b) over the region and auto-starting playback. Filename-guarded so a cancelled handoff can't arm a stale loop on an unrelated song. Reuses the existing A/B loop API; no new looping engine. Buttons added to both static/index.html (v2) and static/v3/index.html (separate file — v2 markup doesn't carry over), using already-scanned Tailwind classes. New globals editRegionInEditor / returnToEditorFromHighway; helpers _resolveEditRegion / _updateEditRegionBtn. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: topkoa <topkoa@gmail.com> * fix(loop-in-3d): use canonical window.feedBack namespace (post-#537) The new song:ready loop-applier landed on the legacy window.slopsmith alias because the branch predated the slopsmith->feedBack rename (#537). Normalize it to window.feedBack like the rest of core; the alias would have worked but leaves the lone slopsmith reference in the file. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Signed-off-by: topkoa <topkoa@gmail.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> Co-authored-by: byrongamatos <xasiklas@gmail.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
byrongamatos
parent
7fb568c727
commit
7399a2ac63
+110
@@ -5759,6 +5759,27 @@ window.feedBack.on('song:ready', () => {
|
||||
Promise.resolve(togglePlay()).catch((err) => console.warn('[app] autoplay failed:', err));
|
||||
});
|
||||
|
||||
// Editor → Highway handoff (Editor ⇄ 3D Highway region round-trip). The
|
||||
// editor's "Loop in 3D" button stashes a pending loop + return context, then
|
||||
// calls playSong(). Once the chart is ready (playSong's own clearLoop() has
|
||||
// already run, so the loop won't be wiped), arm the loop over the selected
|
||||
// region and start playback so the user lands inside the loop directly.
|
||||
window.feedBack.on('song:ready', () => {
|
||||
_updateEditRegionBtn();
|
||||
const pend = window._pendingHighwayLoop;
|
||||
if (!pend) return;
|
||||
// Only apply to the song it was set for — a cancelled/failed handoff
|
||||
// must not arm a stale loop on an unrelated song loaded later.
|
||||
const want = pend.returnCtx && pend.returnCtx.filename;
|
||||
if (want && currentFilename && want !== currentFilename) return;
|
||||
window._pendingHighwayLoop = null;
|
||||
window._highwayReturnCtx = pend.returnCtx || null;
|
||||
Promise.resolve(setLoop(pend.a, pend.b))
|
||||
.then((ok) => { if (ok && !isPlaying) return togglePlay(); })
|
||||
.catch((err) => console.warn('[app] loop-in-3d apply failed:', err));
|
||||
_updateEditRegionBtn();
|
||||
});
|
||||
|
||||
// Auto-exit: when the song ends, return to the launching menu. A scoring
|
||||
// plugin that shows an end-of-song results screen calls holdAutoExit() to
|
||||
// defer this; the user closing that screen (its Close button calls
|
||||
@@ -7219,8 +7240,97 @@ function updateLoopUI() {
|
||||
} else {
|
||||
label.textContent = '';
|
||||
}
|
||||
_updateEditRegionBtn();
|
||||
}
|
||||
|
||||
// ── Highway → Editor handoff ("Edit region") ────────────────────────────
|
||||
// The flip side of the editor's "Loop in 3D" button: jump from the player
|
||||
// to the Song Editor scrolled to the region you're looking at, edit, then
|
||||
// (via the editor's Loop-in-3D) come straight back. Reuses the existing
|
||||
// A/B loop as the region, falling back to the section under the playhead.
|
||||
|
||||
// Resolve the region to edit: the active A/B loop if set, else the section
|
||||
// containing the playhead, else a short window around it. All in seconds.
|
||||
function _resolveEditRegion() {
|
||||
if (loopA !== null && loopB !== null) return { a: loopA, b: loopB };
|
||||
const t = _audioTime();
|
||||
try {
|
||||
const secs = (highway && typeof highway.getSections === 'function')
|
||||
? highway.getSections() : [];
|
||||
if (Array.isArray(secs) && secs.length) {
|
||||
let start = null, end = null;
|
||||
for (let i = 0; i < secs.length; i++) {
|
||||
const st = _sectionPracticeStartTime(secs[i]);
|
||||
if (!Number.isFinite(st)) continue;
|
||||
if (st <= t + 1e-6) {
|
||||
start = st;
|
||||
const nx = secs[i + 1] ? _sectionPracticeStartTime(secs[i + 1]) : NaN;
|
||||
end = Number.isFinite(nx) ? nx : null;
|
||||
} else if (start !== null) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (start !== null) return { a: start, b: (end !== null && end > start) ? end : start + 8 };
|
||||
}
|
||||
} catch (_) { /* fall through to the window default */ }
|
||||
return { a: Math.max(0, t - 4), b: t + 4 };
|
||||
}
|
||||
|
||||
// Enable "Edit region" whenever the editor plugin is present and a song is
|
||||
// loaded; show "↩ Editor" only while a return context is pending.
|
||||
function _updateEditRegionBtn() {
|
||||
const hasEditor = typeof window.editSong === 'function';
|
||||
const editBtn = document.getElementById('btn-edit-region');
|
||||
if (editBtn) {
|
||||
editBtn.classList.toggle('hidden', !hasEditor);
|
||||
editBtn.disabled = !currentFilename;
|
||||
}
|
||||
const retBtn = document.getElementById('btn-return-editor');
|
||||
if (retBtn) {
|
||||
retBtn.classList.toggle('hidden', !(hasEditor && window._highwayReturnCtx));
|
||||
}
|
||||
}
|
||||
|
||||
// Open the Song Editor at the current region.
|
||||
function editRegionInEditor() {
|
||||
if (typeof window.editSong !== 'function' || !currentFilename) return;
|
||||
const region = _resolveEditRegion();
|
||||
let arrangement = 0;
|
||||
try {
|
||||
const si = highway && typeof highway.getSongInfo === 'function' ? highway.getSongInfo() : null;
|
||||
if (si && typeof si.arrangement_index === 'number' && si.arrangement_index >= 0) {
|
||||
arrangement = si.arrangement_index;
|
||||
}
|
||||
} catch (_) { /* default to 0 */ }
|
||||
window._editorPendingView = {
|
||||
filename: currentFilename,
|
||||
arrangement,
|
||||
barSel: { startTime: region.a, endTime: region.b },
|
||||
returnToHighway: true,
|
||||
};
|
||||
window.editSong(currentFilename);
|
||||
}
|
||||
window.editRegionInEditor = editRegionInEditor;
|
||||
|
||||
// Return from the editor to the highway loop we came from (set by the
|
||||
// song:ready applier above). The editor consumes _editorPendingView to
|
||||
// restore the exact edit position; here we just navigate back.
|
||||
function returnToEditorFromHighway() {
|
||||
const ctx = window._highwayReturnCtx;
|
||||
if (!ctx || typeof window.editSong !== 'function') return;
|
||||
window._highwayReturnCtx = null;
|
||||
window._editorPendingView = {
|
||||
filename: ctx.filename,
|
||||
arrangement: ctx.arrangement,
|
||||
scrollX: ctx.scrollX,
|
||||
zoom: ctx.zoom,
|
||||
cursorTime: ctx.cursorTime,
|
||||
barSel: ctx.barSel,
|
||||
};
|
||||
window.editSong(ctx.filename);
|
||||
}
|
||||
window.returnToEditorFromHighway = returnToEditorFromHighway;
|
||||
|
||||
// ── Section Practice Bar ────────────────────────────────────────────────
|
||||
// One-click looping over song section markers (highway.getSections —
|
||||
// same array as 3D highway bundle.sections / "Now / Up Next").
|
||||
|
||||
@@ -578,6 +578,13 @@
|
||||
<option value="">Saved Loops</option>
|
||||
</select>
|
||||
<button onclick="deleteSelectedLoop()" id="btn-loop-delete" class="px-2 py-1.5 bg-dark-600 hover:bg-red-900/50 rounded-lg text-xs text-gray-500 hover:text-red-400 transition hidden" title="Delete selected loop">✕</button>
|
||||
<!-- Editor ⇄ 3D Highway round-trip. "Edit region" opens the Song Editor
|
||||
scrolled to the active loop (or the section at the playhead).
|
||||
"↩ Editor" returns to the editing position you came from; it only
|
||||
appears after a Loop-in-3D handoff. Both are hidden when the editor
|
||||
plugin isn't loaded (state managed by _updateEditRegionBtn). -->
|
||||
<button onclick="editRegionInEditor()" id="btn-edit-region" class="px-3 py-1.5 bg-dark-600 hover:bg-dark-500 rounded-lg text-xs text-gray-300 transition hidden" title="Edit this region in the Song Editor">✎ Edit region</button>
|
||||
<button onclick="returnToEditorFromHighway()" id="btn-return-editor" class="px-3 py-1.5 bg-dark-600 hover:bg-dark-500 rounded-lg text-xs text-gray-300 transition hidden" title="Return to the editor where you left off">↩ Editor</button>
|
||||
<button onclick="showScreen('home')" class="ml-auto px-3 py-1.5 bg-dark-600 hover:bg-red-900/50 rounded-lg text-xs text-gray-400 hover:text-red-400 transition">✕ Close</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -806,6 +806,14 @@
|
||||
</select>
|
||||
<button onclick="deleteSelectedLoop()" id="btn-loop-delete" class="v3-pop-btn hidden" title="Delete selected loop">✕</button>
|
||||
</div>
|
||||
<!-- Editor ⇄ 3D Highway round-trip (mirrors the v2 loop cluster). -->
|
||||
<div class="v3-pop-row">
|
||||
<span class="v3-pop-label">Editor</span>
|
||||
<span class="flex items-center gap-1 flex-wrap">
|
||||
<button onclick="editRegionInEditor()" id="btn-edit-region" class="v3-pop-btn hidden" title="Edit this region in the Song Editor">✎ Edit region</button>
|
||||
<button onclick="returnToEditorFromHighway()" id="btn-return-editor" class="v3-pop-btn hidden" title="Return to the editor where you left off">↩ Editor</button>
|
||||
</span>
|
||||
</div>
|
||||
<button onclick="showScreen('home')" class="v3-pop-close" title="Close player">✕ Close player</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user