Skip to content

Commit 7391cb1

Browse files
committed
Advance MIDI Studio V2 game music workflow with classification guidance section templates timeline navigation and song builder polish - PR_26146_097-100-midi-studio-v2-game-music-workflow-lane
1 parent 1b178bf commit 7391cb1

12 files changed

Lines changed: 467 additions & 16 deletions
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# PR_26146_097-100 Bundle Validation
2+
3+
## Scope
4+
- Baseline: current `PR_26146_093-096` branch state at task start.
5+
- Lane: `PR_26146_097-100-midi-studio-v2-game-music-workflow-lane`.
6+
- Target area: MIDI Studio V2 Song Details, Song Sheet, Song Sequence, generation summaries, and Octave Timeline section navigation.
7+
8+
## Implementation Summary
9+
- Kept Classification as a human-entered text field and preserved generated song IDs as `camelCase(Name)-Classification`.
10+
- Added Classification guidance that explains examples, template defaults, instrument suggestions, generation hints, and ID behavior.
11+
- Added classification-aware workflow profiles used by the section template preview/insertion and visible classification guidance.
12+
- Preserved custom section editors and changed template insertion so templates append into populated built-in section editors.
13+
- Added sequence summary output with section count, bar count, and estimated duration.
14+
- Added generated bars, generated notes, and generated instruments rows to the Song Sheet generation summary.
15+
- Wired Song Sequence selection to the Octave Timeline selected section, and preserved timeline-header to sequence selection.
16+
- Improved section color visibility for the sequence list and active/current timeline section rendering.
17+
18+
## Validation Commands
19+
- PASS: `node --check tools/midi-studio-v2/js/controls/SongSheetControl.js`
20+
- PASS: `node --check tools/midi-studio-v2/js/controls/SongDetailsControl.js`
21+
- PASS: `node --check tools/midi-studio-v2/js/MidiStudioV2App.js`
22+
- PASS: `node --check tools/midi-studio-v2/js/bootstrap.js`
23+
- PASS: `node --check tools/midi-studio-v2/js/controls/InstrumentGridControl.js`
24+
- PASS: `node --check tools/midi-studio-v2/js/controls/OctaveTimelineCanvasRenderer.js`
25+
- PASS: `node --check tests/playwright/tools/MidiStudioV2.spec.mjs`
26+
- PASS: `npx playwright test tests/playwright/tools/MidiStudioV2.spec.mjs -g "validates PR097-100 classification templates section navigation and song builder summaries" --project=playwright --workers=1 --reporter=list`
27+
- PASS: `git diff --check`
28+
- TIMEOUT: `npm run test:workspace-v2` timed out at 120 seconds, then again at 600 seconds with no terminal result.
29+
- TIMEOUT: full `npx playwright test tests/playwright/tools/MidiStudioV2.spec.mjs --project=playwright --workers=1 --reporter=list` timed out at 600 seconds. The lane-specific MIDI Studio Playwright test above passed.
30+
31+
## Playwright Coverage
32+
The targeted PR097-100 Playwright test verifies:
33+
- classification remains text-editable and updates generated ID behavior
34+
- classification help and guidance are visible
35+
- classification-specific template insertion appends into a populated section
36+
- sequence summary section/bar/duration outputs
37+
- generation summary generated bars/notes/instruments outputs
38+
- sequence selection highlights the matching timeline section
39+
- timeline section header selection updates the matching sequence item
40+
- active playback section visibility and frozen Bar/Beat dataset preservation
41+
- section preview Play/Stop and global Play/Stop correctness
42+
43+
## Residual Risk
44+
- The workspace-contract lane runner and full MIDI Studio Playwright spec did not finish within 600 seconds in this environment, so their final pass/fail state is not available from this run.
45+
- Coverage reports are advisory and may include `HEAD`-changed JavaScript files by reporter design.
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# PR_26146_097-100 Classification Workflow
2+
3+
## Ownership
4+
- Classification remains owned by `music.songs[].classification` in Song Details.
5+
- The generated ID remains read-only and is derived as `camelCase(Name)-Classification`.
6+
- Classification does not mutate instrument SSoT data directly.
7+
8+
## User-Facing Behavior
9+
- The Classification help icon now includes examples and guidance:
10+
- Classification is human-entered.
11+
- Classification seeds default section templates.
12+
- Classification surfaces instrument suggestions.
13+
- Classification surfaces generation hints.
14+
- ID behavior remains `camelCase(Name)-Classification`.
15+
16+
## Default Workflow Data
17+
`SongSheetControl` resolves a classification workflow from the current classification value:
18+
- `sectionTemplates`: default Intro, Verse, Chorus, Bridge, Outro progressions.
19+
- `instrumentSuggestions`: visible suggested instruments for the cue type.
20+
- `generationHints`: visible arrangement guidance for the current cue type.
21+
- `summary`: concise profile label used by the guide output.
22+
23+
Known classification profiles added:
24+
- Ambient
25+
- Boss
26+
- Chase
27+
- Flying
28+
- Loop
29+
- Menu
30+
- Puzzle
31+
- Underwater
32+
- Victory
33+
34+
Unknown or freeform classifications use the General defaults while preserving the typed classification label.
35+
36+
## Template Insertion
37+
- Built-in template options still target first-class section editors: Intro, Verse, Chorus, Bridge, Outro.
38+
- Template preview and template insertion use the same classification workflow resolver.
39+
- Applying a template into a populated built-in section appends the template chords instead of replacing existing chords.
40+
- Custom sections are preserved and remain outside the built-in template replacement flow.
41+
- If a template has no resolved chords, the Apply Template control is marked red/unwired with a status explaining the incomplete state.
42+
43+
## Verification
44+
- Playwright enters `Underwater`, checks the read-only ID becomes `camptownRacesUatReel-Underwater`, verifies the classification guide, previews the Underwater Verse template, and confirms insertion appends `Dm Bb C Dm` into the populated Verse editor.
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# PR_26146_097-100 Section Navigation Map
2+
3+
## Sequence To Timeline
4+
- `SongSheetControl` emits selected sequence detail from `songSheetSequenceList`.
5+
- `MidiStudioV2App.handleSongSheetSequenceSelection` receives the selected label and sequence index.
6+
- `InstrumentGridControl.selectSequenceSection` resolves the matching generated section by sequence occurrence when possible.
7+
- The Octave Timeline playhead and selected section are moved to the section start.
8+
- The timeline canvas re-renders with the selected section color and stronger selected-region contrast.
9+
10+
## Timeline To Sequence
11+
- Timeline section-header clicks continue to emit `select-section` transport actions.
12+
- `MidiStudioV2App` calls `SongSheetControl.selectSequenceItem(label, sequenceIndex)` for timeline-header selections.
13+
- `SongSheetControl` selects the matching sequence item, updates selected datasets, and reapplies section colors.
14+
15+
## Visual Synchronization
16+
- Song Sequence options use populated section colors.
17+
- The Song Sequence select receives a visible color stripe from the selected section color.
18+
- Timeline section regions, selected section region, and active playback section region use stronger alpha/stroke values.
19+
- The timeline canvas now exposes `data-active-playback-section-visible` for Playwright and diagnostics.
20+
- Frozen Bar/Beat behavior remains owned by the canvas renderer and is preserved.
21+
22+
## Sequence Summary
23+
- `songSheetSequenceSummary` displays:
24+
- section count
25+
- total bar/chord count from selected sequence sections
26+
- estimated duration from tempo and bar count
27+
- The output also exposes data attributes for section count, bar count, and duration seconds.
28+
29+
## Generation Summary
30+
- Song Sheet generation summary now includes:
31+
- Generated bars
32+
- Generated notes
33+
- Generated instruments
34+
- The existing generated arrangement still updates the canonical song model, diagnostics, JSON details, and Octave Timeline through the prior generation path.
35+
36+
## Verification
37+
- Playwright adds Intro, Verse, Chorus to Song Sequence, verifies `3 sections` and `14 bars`, parses the arrangement, verifies generated bars/notes/instruments, selects Verse from the sequence and checks timeline selection, then clicks the Chorus timeline header and checks the Song Sequence selection updates to Chorus.

tests/playwright/tools/MidiStudioV2.spec.mjs

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -775,6 +775,7 @@ async function visibleMidiStudioControlOwnership(page, activeTabId) {
775775
songSheetApplyDrumsInput: { canonical: "music.songs[].studioArrangement.songSheet.applyTargets.drums", kind: "canonical", owner: "Song Setup", wired: "wired" },
776776
songSheetApplyLeadInput: { canonical: "music.songs[].studioArrangement.songSheet.applyTargets.lead", kind: "canonical", owner: "Song Setup", wired: "wired" },
777777
songSheetAvailableSectionsList: { canonical: "derived from populated Song Sheet section inputs", kind: "readonly", owner: "Song Setup", wired: "wired" },
778+
songSheetClassificationGuide: { canonical: "derived from music.songs[].classification defaults", kind: "readonly", owner: "Song Setup", wired: "wired" },
778779
songSheetCustomSectionsInput: { canonical: "music.songs[].studioArrangement.songSheet.sections", kind: "canonical", owner: "Song Setup", wired: "wired" },
779780
songSheetCustomSectionMetrics: { canonical: "derived from populated custom Song Sheet sections", kind: "readonly", owner: "Song Setup", wired: "wired" },
780781
songSheetDragDropSequenceButton: { canonical: "future Song Sequence drag/drop", kind: "unwired", owner: "Song Setup", wired: "unwired" },
@@ -795,6 +796,7 @@ async function visibleMidiStudioControlOwnership(page, activeTabId) {
795796
songSheetSequenceMoveDownButton: { canonical: "music.songs[].studioArrangement.songSheet.sequence", kind: "canonical-action", owner: "Song Setup", wired: "wired" },
796797
songSheetSequenceMoveUpButton: { canonical: "music.songs[].studioArrangement.songSheet.sequence", kind: "canonical-action", owner: "Song Setup", wired: "wired" },
797798
songSheetSequenceRemoveButton: { canonical: "music.songs[].studioArrangement.songSheet.sequence", kind: "canonical-action", owner: "Song Setup", wired: "wired" },
799+
songSheetSequenceSummary: { canonical: "derived from music.songs[].studioArrangement.songSheet.sequence", kind: "readonly", owner: "Song Setup", wired: "wired" },
798800
songSheetSectionsInput: { canonical: "music.songs[].studioArrangement.songSheet.sections", kind: "canonical", owner: "Song Setup", wired: "wired" },
799801
songSheetStyleInput: { canonical: "music.songs[].studioArrangement.style", kind: "canonical", owner: "Song Setup", wired: "wired" },
800802
songSheetApplySectionTemplateButton: { canonical: "music.songs[].studioArrangement.songSheet.sections", kind: "canonical-action", owner: "Song Setup", wired: "wired" },
@@ -5435,6 +5437,84 @@ test.describe("MIDI Studio V2", () => {
54355437
}
54365438
});
54375439

5440+
test("validates PR097-100 classification templates section navigation and song builder summaries", async ({ page }) => {
5441+
await page.setViewportSize({ width: 1600, height: 900 });
5442+
const server = await openMidiStudioForImport(page);
5443+
try {
5444+
await page.locator("#toolImportManifestInput").setInputFiles(uatManifestPath);
5445+
await selectMidiStudioTab(page, "song-setup");
5446+
5447+
const classificationInput = page.locator("#songDetails [data-song-detail-field='classification']");
5448+
await expect(classificationInput).toHaveAttribute("type", "text");
5449+
await expect(page.locator("[data-song-detail-help='classification']")).toHaveAttribute("title", /seeds default section templates/);
5450+
await classificationInput.fill("Underwater");
5451+
await expect(page.locator("#songDetails [data-song-detail-field='id']")).toHaveValue("camptownRacesUatReel-Underwater");
5452+
await expect(page.locator("#songSheetClassificationGuide")).toContainText("Underwater defaults");
5453+
await expect(page.locator("#songSheetClassificationGuide")).toHaveAttribute("data-classification-instrument-suggestions", /Warm Pad/);
5454+
await expect(page.locator("#songSheetClassificationGuide")).toHaveAttribute("data-classification-generation-hints", /Drums/);
5455+
5456+
await page.locator("#songSheetSectionIntroInput").fill("Dm Gm");
5457+
await page.locator("#songSheetSectionVerseInput").fill("G Em C D");
5458+
await page.locator("#songSheetSectionChorusInput").fill("Bb C Dm Dm");
5459+
await page.locator("#songSheetTemplateSectionSelect").selectOption("Verse");
5460+
await expect(page.locator("#songSheetTemplatePreview")).toHaveText("Verse template: Dm Bb C Dm");
5461+
await expect(page.locator("#songSheetTemplatePreview")).toHaveAttribute("data-song-sheet-template-classification", "Underwater");
5462+
await page.locator("#songSheetApplySectionTemplateButton").click();
5463+
await expect(page.locator("#songSheetSectionVerseInput")).toHaveValue("G Em C D Dm Bb C Dm");
5464+
5465+
await addSongSheetSequenceLabels(page, ["Intro", "Verse", "Chorus"]);
5466+
await expect(page.locator("#songSheetSequenceSummary")).toContainText("3 sections");
5467+
await expect(page.locator("#songSheetSequenceSummary")).toContainText("14 bars");
5468+
await expect(page.locator("#songSheetSequenceSummary")).toHaveAttribute("data-song-sheet-sequence-section-count", "3");
5469+
await expect(page.locator("#songSheetSequenceSummary")).toHaveAttribute("data-song-sheet-sequence-bar-count", "14");
5470+
await expect.poll(async () => Number(await page.locator("#songSheetSequenceSummary").getAttribute("data-song-sheet-sequence-duration-seconds"))).toBeGreaterThan(0);
5471+
5472+
await page.locator("#parseSongSheetButton").click();
5473+
await expect(page.locator("#songSheetSummary [data-song-sheet-summary-field='generated-bars'] dd")).toHaveText("14");
5474+
await expect.poll(async () => Number(await page.locator("#songSheetSummary [data-song-sheet-summary-field='generated-notes'] dd").textContent())).toBeGreaterThan(0);
5475+
await expect(page.locator("#songSheetSummary [data-song-sheet-summary-field='generated-instruments'] dd")).toContainText("Chords/Pad");
5476+
await expect(page.locator("#songSheetSummary [data-song-sheet-summary-field='generated-instruments'] dd")).toContainText("Bass");
5477+
5478+
await page.locator("#songSheetSequenceList").selectOption({ index: 1 });
5479+
await expect(page.locator("#songSheetSequenceList")).toHaveAttribute("data-song-sheet-selected-section", "Verse");
5480+
const selectedFromSequence = await page.evaluate(() => window.__midiStudioV2App.instrumentGrid.timelineCanvasState()?.selectedSection?.label || "");
5481+
expect(selectedFromSequence).toBe("Verse");
5482+
5483+
await selectMidiStudioTab(page, "studio");
5484+
await waitForCanvasRender(page);
5485+
await clickCanvasSectionHeader(page, "Chorus", 0);
5486+
await expect(page.locator("#songSheetSequenceList")).toHaveAttribute("data-song-sheet-selected-section", "Chorus");
5487+
const selectedOption = await page.locator("#songSheetSequenceList option").evaluateAll((options) => options.map((option) => ({
5488+
label: option.value,
5489+
selected: option.dataset.songSheetSequenceSelected
5490+
})));
5491+
expect(selectedOption).toEqual([
5492+
{ label: "Intro", selected: "false" },
5493+
{ label: "Verse", selected: "false" },
5494+
{ label: "Chorus", selected: "true" }
5495+
]);
5496+
5497+
await expect(octaveTimelineCanvas(page)).toHaveAttribute("data-playback-section", "Chorus");
5498+
await expect(octaveTimelineCanvas(page)).toHaveAttribute("data-active-playback-section-visible", "true");
5499+
await expect(octaveTimelineCanvas(page)).toHaveAttribute("data-frozen-header", /true|false/);
5500+
await page.locator("#playSectionButton").click();
5501+
await expect(page.locator("#instrumentGridTransportState")).toContainText("Playing section: Chorus");
5502+
await expect(octaveTimelineCanvas(page)).toHaveAttribute("data-playback-section", "Chorus");
5503+
await page.locator("#stopTimingPreviewButton").click();
5504+
await expect(page.locator("#instrumentGridTransportState")).toContainText("Preview Synth timing preview stopped.");
5505+
5506+
await page.locator("#playButton").click();
5507+
await expect(page.locator("#playButton")).toBeDisabled();
5508+
await expect(page.locator("#stopButton")).toBeEnabled();
5509+
await page.locator("#stopButton").click();
5510+
await expect(page.locator("#playButton")).toBeEnabled();
5511+
await expect(page.locator("#stopButton")).toBeDisabled();
5512+
} finally {
5513+
await workspaceV2CoverageReporter.stop(page);
5514+
await server.close();
5515+
}
5516+
});
5517+
54385518
test("derives primary song, instrument, grid, playback, and diagnostics views from the canonical selected song", async ({ page }) => {
54395519
const server = await openMidiStudioForImport(page);
54405520
try {

tools/midi-studio-v2/index.html

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,7 @@ <h3 id="exportStatusHeading">Export Status</h3>
271271
<input id="songSheetSectionsInput" type="hidden">
272272
<input id="songSheetSequenceInput" type="hidden">
273273
<div class="midi-studio-v2__song-sheet-template-library" aria-label="Section template library">
274+
<output id="songSheetClassificationGuide" class="midi-studio-v2__classification-guide" data-classification-workflow="General">General game music defaults: Warm Pad, Synth Bass, Basic Drums, Retro Lead. Use balanced Chords/Pad and Bass first; enable Drums or Lead when the arrangement needs motion.</output>
274275
<label class="tool-starter__field midi-studio-v2__field-card" data-midi-studio-field-state="editable" for="songSheetTemplateSectionSelect">
275276
<span>Section Template</span>
276277
<select id="songSheetTemplateSectionSelect" aria-label="Built-in section template">
@@ -335,6 +336,7 @@ <h3 id="exportStatusHeading">Export Status</h3>
335336
<span id="songSheetSequenceCount" class="midi-studio-v2__sequence-count">0 items</span>
336337
</span>
337338
<select id="songSheetSequenceList" size="6" aria-label="Song sequence"></select>
339+
<output id="songSheetSequenceSummary" class="midi-studio-v2__sequence-summary" data-song-sheet-sequence-section-count="0" data-song-sheet-sequence-bar-count="0" data-song-sheet-sequence-duration-seconds="0">0 sections / 0 bars / 0 seconds</output>
338340
</label>
339341
<div class="midi-studio-v2__sequence-actions">
340342
<button id="songSheetSequenceMoveUpButton" type="button">Move Up</button>

0 commit comments

Comments
 (0)