mirror of
https://github.com/got-feedBack/feedBack.git
synced 2026-07-25 06:11:36 +00:00
feat(player): drum-part picker for multiple drum charts (feedpak 1.17.0)
The last mile of the multiple-drum-parts feature: let a player CHOOSE which drum chart plays. #1020 taught the loader + highway WS to carry several drum parts (song_info.drum_parts + ?drum_part=<id> + a part_id echo on drum_tab); this adds the host-chrome selector that drives it. A "Drum part" <select> sits beside the arrangement switcher in the advanced settings popover, shown only when a song has 2+ drum charts (drum_parts is always present — empty for non-drum songs — so single-drum / no-drum songs hide the row and nothing changes for them). Selecting a part re-streams that part's tab over the highway WS, exactly like an arrangement switch. - static/highway.js: - reconnect() gains a third `drumPart` arg → sets `?drum_part=<id>` on the WS URL (mirrors the existing `arrangement` param one line up). Empty/undefined → the primary part, i.e. byte-identical to today for any pack untouched. - song_info handler populates #drum-part-select from msg.drum_parts and shows/hides #v3-drum-part-row on `length > 1` (parallel to the #arr-select block right above it). - drum_tab handler carries msg.part_id onto hwState.drumTab (plugins can read bundle.drumTab.part_id) and reflects it as the picker's selected value, so the dropdown stays honest even when the server resolves an unknown/absent selection to the primary. - static/app.js: - changeArrangement() gains an optional `drumPart`; at reconnect it forwards the explicit part, else preserves the current picker selection — so an ARRANGEMENT switch keeps the chosen drum part (parts are song-level). - new changeDrumPart(id) delegates to changeArrangement with the current arrangement held + the new part applied (a part switch is the same re-stream, so it reuses all the transition ceremony). Exported on window. - static/v3/index.html: the #drum-part-select row (hidden by default). No plugin change: the drum renderers just draw whatever drum_tab streams. RUNTIME-VERIFIED (Playwright, the core player, a 2-drum pack + a no-drum pack): 10/10 — the picker populates with both parts and shows for the multi-drum song; song_info.drum_parts reaches getSongInfo(); the primary is pre-selected; selecting the 2nd part drives highway.reconnect with the id and the WS URL carries `?drum_part=drums-2`; the picker then reflects the server's part_id echo; a no-drum song hides the row; no page errors. ESLint 0 errors (the two max-lines warnings are pre-existing on these files). No pytest touched (JS-only). Stacked on #1020. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017xGPjDBF8NTwTK7VQvizix Signed-off-by: ChrisBeWithYou <christian.a.cowan@gmail.com>
This commit is contained in:
parent
e0f1e2b641
commit
9a2e98ed74
10
CHANGELOG.md
10
CHANGELOG.md
@ -8,6 +8,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
## [Unreleased]
|
||||
|
||||
### Added
|
||||
- **Drum-part picker (feedpak 1.17.0 "drums as arrangements").** When a song
|
||||
carries several drum charts, a **Drum part** selector appears beside the
|
||||
arrangement switcher (advanced settings) so a player can choose which drummer
|
||||
to play. Selecting one re-streams that part's tab over the highway WS
|
||||
(`?drum_part=<id>`, mirroring the arrangement switch); the choice persists
|
||||
across an arrangement change, and the picker reflects the server's
|
||||
authoritative part (unknown/absent selection falls back to the primary). The
|
||||
row hides for single-drum and non-drum songs, so nothing changes there. Builds
|
||||
on the loader below; no plugin change needed — the drum renderer just draws
|
||||
whatever tab streams.
|
||||
- **Multiple drum parts (feedpak 1.17.0 "drums as arrangements").** The sloppak
|
||||
loader now reads `type: drums` arrangement entries carrying per-arrangement
|
||||
`drum_tab` file pointers — a song can ship several drum charts (a second
|
||||
|
||||
@ -1150,7 +1150,7 @@ window.feedBack.on('song:ready', () => {
|
||||
let _arrBusyGen = 0;
|
||||
let _arrBusyTimeout = null;
|
||||
|
||||
async function changeArrangement(index) {
|
||||
async function changeArrangement(index, drumPart) {
|
||||
if (currentFilename) {
|
||||
// Tear down any pending fresh-load credits before switching: the
|
||||
// no-count-in hold timer would otherwise fire togglePlay() against the
|
||||
@ -1276,11 +1276,38 @@ async function changeArrangement(index) {
|
||||
_resetSectionPracticeLog();
|
||||
invalidateParentCount();
|
||||
|
||||
window.highway.reconnect(currentFilename, index);
|
||||
// Carry the selected drum part across the re-stream. An explicit
|
||||
// `drumPart` (a drum-part switch, from changeDrumPart) wins; otherwise
|
||||
// preserve the current picker selection so an ARRANGEMENT switch keeps
|
||||
// the chosen part (drum parts are song-level, not per-arrangement).
|
||||
const part = drumPart !== undefined
|
||||
? drumPart
|
||||
: (document.getElementById('drum-part-select')?.value || '');
|
||||
window.highway.reconnect(currentFilename, index, part);
|
||||
window.feedBack.emit('arrangement:changed', { index, filename: currentFilename });
|
||||
}
|
||||
}
|
||||
|
||||
// Switch which drum part plays (feedpak 1.17.0 "drums as arrangements"). A part
|
||||
// switch re-streams the same song with a different drum tab — the same
|
||||
// transition as an arrangement switch — so it delegates to changeArrangement
|
||||
// with the CURRENT arrangement held and the new part applied. Wired to
|
||||
// #drum-part-select's onchange; the select is populated + shown by
|
||||
// highway.js's song_info handler only when the song has 2+ drum parts.
|
||||
async function changeDrumPart(partId) {
|
||||
if (!currentFilename) return;
|
||||
let index = 0;
|
||||
const si = window.highway && typeof window.highway.getSongInfo === 'function'
|
||||
? window.highway.getSongInfo() : null;
|
||||
if (si && typeof si.arrangement_index === 'number' && si.arrangement_index >= 0) {
|
||||
index = si.arrangement_index;
|
||||
} else {
|
||||
const arrSel = document.getElementById('arr-select');
|
||||
if (arrSel && arrSel.value !== '') index = Number(arrSel.value) || 0;
|
||||
}
|
||||
return changeArrangement(index, partId);
|
||||
}
|
||||
|
||||
// Restart the current song from the beginning (or from loop A when an A–B
|
||||
// loop is armed). Uses the canonical _audioSeek funnel only — never touches
|
||||
// audio.currentTime directly and never reloads via playSong().
|
||||
@ -2325,7 +2352,7 @@ configureHost({
|
||||
Object.assign(window, {
|
||||
_confirmDialog, _getArrangementNamingMode, _libraryLocalFilename, _librarySongArtUrl,
|
||||
_librarySongId, _onHeaderClick, _onNamingModeChange, _trapFocusInModal,
|
||||
changeArrangement, checkPluginUpdates, clearLibFilters, clearLoop,
|
||||
changeArrangement, changeDrumPart, checkPluginUpdates, clearLibFilters, clearLoop,
|
||||
deleteSelectedLoop, esc, exportDiagnostics, exportSettings, filterFavorites,
|
||||
filterLibrary, fullRescanLibrary, goFavPage, handleSliderInput,
|
||||
hideScanBanner, importSettings, loadPlugins, loadSavedLoop,
|
||||
|
||||
@ -2298,6 +2298,31 @@ function createHighway() {
|
||||
sel.appendChild(opt);
|
||||
}
|
||||
}
|
||||
// Drum-part picker (feedpak 1.17.0 "drums as
|
||||
// arrangements"): a song can carry several drum
|
||||
// charts. Populate the picker beside the
|
||||
// arrangement switcher; show it only when there
|
||||
// are 2+ parts to choose between. `drum_parts`
|
||||
// is always present (empty for non-drum songs),
|
||||
// so a single-drum / no-drum song hides it. The
|
||||
// currently-streaming part is marked selected by
|
||||
// the `drum_tab` handler below (authoritative
|
||||
// `part_id`), so we don't guess here.
|
||||
{
|
||||
const dpSel = document.getElementById('drum-part-select');
|
||||
if (dpSel) {
|
||||
const parts = Array.isArray(msg.drum_parts) ? msg.drum_parts : [];
|
||||
dpSel.textContent = '';
|
||||
for (const p of parts) {
|
||||
const opt = document.createElement('option');
|
||||
opt.value = p.id;
|
||||
opt.textContent = p.name || p.id;
|
||||
dpSel.appendChild(opt);
|
||||
}
|
||||
const dpRow = document.getElementById('v3-drum-part-row');
|
||||
if (dpRow) dpRow.classList.toggle('hidden', parts.length <= 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Plugin context API — broadcast current song state
|
||||
if (window.feedBack) {
|
||||
@ -2380,7 +2405,22 @@ function createHighway() {
|
||||
name: (typeof msg.name === 'string' && msg.name) ? msg.name : 'Drums',
|
||||
kit: Array.isArray(msg.kit) ? msg.kit : [],
|
||||
hits: [],
|
||||
// Which drum part this stream carries (feedpak
|
||||
// 1.17.0). Present only for multi-part packs;
|
||||
// null otherwise. Plugins can read it via
|
||||
// bundle.drumTab.part_id.
|
||||
part_id: (typeof msg.part_id === 'string' && msg.part_id) ? msg.part_id : null,
|
||||
};
|
||||
// Reflect the authoritative streaming part in the
|
||||
// picker (the server resolves an unknown/absent
|
||||
// selection to the primary, so this keeps the
|
||||
// dropdown honest even after a fallback).
|
||||
if (hwState.drumTab.part_id) {
|
||||
const dpSel = document.getElementById('drum-part-select');
|
||||
if (dpSel && dpSel.value !== hwState.drumTab.part_id) {
|
||||
dpSel.value = hwState.drumTab.part_id;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'drum_hits':
|
||||
if (hwState.drumTab && Array.isArray(msg.data)) {
|
||||
@ -2773,7 +2813,7 @@ function createHighway() {
|
||||
localStorage.setItem('showFingerHints', String(hwState._showFingerHints));
|
||||
},
|
||||
|
||||
reconnect(filename, arrangement) {
|
||||
reconnect(filename, arrangement, drumPart) {
|
||||
// Close old WS but keep audio + animation running
|
||||
if (hwState.ws) { hwState.ws.close(); hwState.ws = null; }
|
||||
hwState.ready = false;
|
||||
@ -2799,6 +2839,11 @@ function createHighway() {
|
||||
_resetChordRenderState();
|
||||
const wsParams = new URLSearchParams();
|
||||
if (arrangement !== undefined) wsParams.set('arrangement', arrangement);
|
||||
// Multiple drum parts (feedpak 1.17.0 "drums as arrangements"):
|
||||
// carry the selected part id so the WS streams ITS drum tab. Empty
|
||||
// / undefined → the primary part (server default), i.e. today's
|
||||
// one-drum behavior for any pack the picker never touched.
|
||||
if (drumPart) wsParams.set('drum_part', drumPart);
|
||||
let namingMode = 'smart';
|
||||
if (typeof window._getArrangementNamingMode === 'function') {
|
||||
const v = window._getArrangementNamingMode();
|
||||
|
||||
@ -1194,6 +1194,10 @@
|
||||
<button id="arr-default-pin" type="button" onclick="pinCurrentArrangementDefault()" aria-pressed="false" aria-label="Select an arrangement to make it the default" class="v3-pop-pin" title="Select an arrangement to make it the default">☆</button>
|
||||
</span>
|
||||
</div>
|
||||
<div class="v3-pop-row hidden" id="v3-drum-part-row">
|
||||
<span class="v3-pop-label">Drum part</span>
|
||||
<select id="drum-part-select" onchange="changeDrumPart(this.value)" class="v3-pop-select max-w-[130px]" title="Which drum chart to play — a song can carry several"></select>
|
||||
</div>
|
||||
<div class="v3-pop-row">
|
||||
<span class="v3-pop-label" id="mastery-slider-label">Difficulty</span>
|
||||
<span class="flex items-center gap-2">
|
||||
|
||||
Loading…
Reference in New Issue
Block a user