Skip to content

Commit 69b797b

Browse files
committed
Complete MIDI Studio V2 composition workflow with generation mapping regeneration sync and preview polish - PR_26146_085-088-midi-studio-v2-composition-workflow-lane
1 parent 96e2fda commit 69b797b

9 files changed

Lines changed: 316 additions & 47 deletions

File tree

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# PR_26146_085-088 Bundle Validation
2+
3+
## Scope
4+
- Completed MIDI Studio V2 composition workflow lane for section-to-lane generation, regenerate, edit sync, audition, and playback state polish.
5+
- Baseline: PR_26146_081-084.
6+
7+
## Changed Surface
8+
- `tools/midi-studio-v2/index.html`
9+
- `tools/midi-studio-v2/js/bootstrap.js`
10+
- `tools/midi-studio-v2/js/MidiStudioV2App.js`
11+
- `tools/midi-studio-v2/js/controls/SongSheetControl.js`
12+
- `tools/midi-studio-v2/js/controls/PlaybackControl.js`
13+
- `tests/playwright/tools/MidiStudioV2.spec.mjs`
14+
15+
## Validation
16+
- PASS: changed-file syntax checks
17+
- `node --check tools/midi-studio-v2/js/MidiStudioV2App.js`
18+
- `node --check tools/midi-studio-v2/js/bootstrap.js`
19+
- `node --check tools/midi-studio-v2/js/controls/SongSheetControl.js`
20+
- `node --check tools/midi-studio-v2/js/controls/PlaybackControl.js`
21+
- `node --check tests/playwright/tools/MidiStudioV2.spec.mjs`
22+
- PASS: targeted MIDI Studio Playwright
23+
- `npx playwright test tests/playwright/tools/MidiStudioV2.spec.mjs -g "validates PR085-088 composition mapping regenerate sync audition and playback states"`
24+
- Result: 1 passed.
25+
- PASS: baseline plus lane targeted MIDI Studio Playwright
26+
- `npx playwright test tests/playwright/tools/MidiStudioV2.spec.mjs -g "PR081-084|PR085-088"`
27+
- Result: 2 passed.
28+
- PASS: `git diff --check`
29+
- Only Git CRLF normalization warnings were printed.
30+
- TIMEOUT: `npm run test:workspace-v2`
31+
- First run timed out after 124 seconds.
32+
- Second run timed out after 604 seconds.
33+
- The runner left child Node/Playwright processes and generated cache report side effects; those processes were stopped and only the runner-generated side-effect files were restored.
34+
35+
## Playwright Coverage Notes
36+
- `docs/dev/reports/playwright_v8_coverage_report.txt` and `docs/dev/reports/coverage_changed_js_guardrail.txt` are present from the targeted MIDI Studio Playwright run.
37+
- Guardrail status is advisory and reports no changed runtime JS coverage warnings.
38+
39+
## Result
40+
PASS for changed-file syntax, targeted MIDI Studio behavior, and diff hygiene.
41+
Workspace V2 command was executed as required but did not complete within the available timeout window.
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# PR_26146_085-088 Generation Mapping
2+
3+
## Authoritative Inputs
4+
- Populated Song Sheet sections.
5+
- Song Sequence order.
6+
- Apply Song Sheet To selections.
7+
8+
## Target Mapping
9+
- Chords/Pad enabled:
10+
- Writes canonical `chords`.
11+
- Generates `pad` from the chord lane.
12+
- Bass enabled:
13+
- Generates `bass` from the chord lane.
14+
- Drums enabled:
15+
- Generates `drums` from the current section map.
16+
- Lead enabled:
17+
- Generates `lead` from the chord lane.
18+
19+
## Summary Output
20+
Song Sheet Summary now includes:
21+
- Generation targets.
22+
- Lane mapping.
23+
- Manual lanes preserved.
24+
- Target lanes affected.
25+
- Sections used, bars generated, and notes generated.
26+
27+
## Regenerate Arrangement
28+
- Added `Regenerate Arrangement`.
29+
- Regenerate reparses the current guided Song Sheet, uses the current sequence, and applies the current target selections.
30+
- Status reports the targets, generated bar count, generated note count, and preservation behavior.
31+
32+
## Manual Preservation
33+
- Targeted lanes are regenerated.
34+
- Untargeted lanes are preserved when their bar count matches the regenerated arrangement.
35+
- Untargeted lanes that no longer match the regenerated bar count are aligned with rest bars to keep the canonical instrument grid parseable.
36+
37+
## Validation
38+
- Playwright verified Chords/Pad and Bass generation while Drums and Lead were skipped.
39+
- Playwright verified an untargeted Lead lane edit survived Regenerate Arrangement.
40+
- Playwright verified enabling Lead caused Lead to regenerate from the Song Sheet.
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# PR_26146_085-088 Sync Audit
2+
3+
## Song Sheet Edits
4+
- Section editor edits call the existing Song Sheet field-change path.
5+
- The field-change path persists canonical `studioArrangement.songSheet`, reparses the guided sheet, reapplies generation targets, and refreshes diagnostics and JSON Details.
6+
- Sequence edits continue to drive playback/build order.
7+
8+
## Canonical Model
9+
- Generated section order is written to `music.songs[].studioArrangement.sections`.
10+
- Guided Song Sheet source is written to `music.songs[].studioArrangement.songSheet.sections`.
11+
- Song Sequence is written to `music.songs[].studioArrangement.songSheet.sequence`.
12+
- Apply targets are written to `music.songs[].studioArrangement.songSheet.applyTargets`.
13+
- Lane text is written to `music.songs[].studioArrangement.lanes`.
14+
15+
## Timeline Sync
16+
- Octave Timeline uses the normalized instrument grid after Song Sheet parse/regenerate.
17+
- Timeline section labels and colors continue to come from the normalized section map.
18+
- Current playback section highlighting is preserved through the canvas-backed timeline.
19+
20+
## Audition And Playback
21+
- Instrument previews now stop active Preview Synth audio before starting a new instrument audition.
22+
- Keyboard and edited-note auditions stop active Preview Synth/timing playback before auditioning, avoiding stale Play/Stop state.
23+
- Playback state now distinguishes playing, looping, stopped, and completed preview states.
24+
25+
## Validation
26+
- Playwright verified canonical model synchronization after a Song Sheet edit changed bar counts.
27+
- Playwright verified timeline section labels after regeneration.
28+
- Playwright verified keyboard audition status.
29+
- Playwright verified non-loop playback completion and loop playback stop behavior.

tests/playwright/tools/MidiStudioV2.spec.mjs

Lines changed: 92 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -761,6 +761,7 @@ async function visibleMidiStudioControlOwnership(page, activeTabId) {
761761
playButton: { canonical: "playback from selected canonical song model", kind: "action", owner: "Global NAV", wired: "wired" },
762762
playLoopButton: { canonical: "timing preview playback state", kind: "workflow-state", owner: "Octave Timeline", wired: "wired" },
763763
playSectionButton: { canonical: "timing preview playback state", kind: "workflow-state", owner: "Octave Timeline", wired: "wired" },
764+
regenerateArrangementButton: { canonical: "music.songs[].studioArrangement generated lanes from Song Sheet sequence", kind: "canonical-action", owner: "Song Setup", wired: "wired" },
764765
renderedExportSaveButton: { canonical: "future rendered audio renderer", kind: "unwired", owner: "Export", wired: "unwired" },
765766
renderedExportTargetTypeSelect: { canonical: "future rendered audio renderer", kind: "unwired", owner: "Export", wired: "unwired" },
766767
resetSongEditsButton: { canonical: "music.songs[] reset baseline", kind: "canonical-action", owner: "Global NAV", wired: "wired" },
@@ -5153,6 +5154,96 @@ test.describe("MIDI Studio V2", () => {
51535154
}
51545155
});
51555156

5157+
test("validates PR085-088 composition mapping regenerate sync audition and playback states", async ({ page }) => {
5158+
await page.setViewportSize({ width: 1600, height: 900 });
5159+
const server = await openMidiStudioForImport(page);
5160+
try {
5161+
await page.locator("#toolImportManifestInput").setInputFiles(uatManifestPath);
5162+
await selectMidiStudioTab(page, "song-setup");
5163+
await page.locator("#songSheetTempoInput").fill("960");
5164+
await fillSongSheetSectionBuilder(page, "Intro: G C\nVerse: G Em\nChorus: C G\nBridge:\nOutro:\nBreak: F");
5165+
await clearSongSheetSequence(page);
5166+
await addSongSheetSequenceLabels(page, ["Intro", "Verse", "Chorus"]);
5167+
await page.locator("#songSheetApplyChordsPadInput").setChecked(true);
5168+
await page.locator("#songSheetApplyBassInput").setChecked(true);
5169+
await page.locator("#songSheetApplyDrumsInput").setChecked(false);
5170+
await page.locator("#songSheetApplyLeadInput").setChecked(false);
5171+
await page.locator("#parseSongSheetButton").click();
5172+
5173+
await expect(page.locator("#songSheetSummary [data-song-sheet-summary-field='generation-targets'] dd")).toHaveText("Chords/Pad, Bass");
5174+
await expect(page.locator("#songSheetSummary [data-song-sheet-summary-field='lane-mapping'] dd")).toHaveText("Chords/Pad -> chords, pad; Bass -> bass; Drums -> skipped; Lead -> skipped");
5175+
await expect(page.locator("#songSheetSummary [data-song-sheet-summary-field='target-lanes-affected'] dd")).toHaveText("Chords/Pad, Bass");
5176+
await expect(page.locator("#songSheetSummary [data-song-sheet-summary-field='bars-generated'] dd")).toHaveText("6");
5177+
5178+
await selectMidiStudioTab(page, "auto-create-parts");
5179+
await page.locator(".midi-studio-v2__advanced-lane-source").evaluate((details) => {
5180+
details.open = true;
5181+
});
5182+
const manualLead = "C5 - - - | D5 - - - | E5 - - - | F5 - - - | G5 - - - | A5 - - -";
5183+
await page.locator("#instrumentGridLeadInput").fill(manualLead);
5184+
await page.locator("#normalizeInstrumentGridButton").click();
5185+
await selectMidiStudioTab(page, "song-setup");
5186+
await page.locator("#regenerateArrangementButton").click();
5187+
await expect(page.locator("#instrumentGridLeadInput")).toHaveValue(manualLead);
5188+
await expect(page.locator("#songSheetSummary [data-song-sheet-summary-field='manual-lanes-preserved'] dd")).toContainText("kept lead");
5189+
await expect(page.locator("#statusLog")).toHaveValue(/OK Regenerated arrangement for Chords\/Pad, Bass: 6 bars, \d+ notes\. Manual lanes .*kept lead/);
5190+
5191+
await page.locator("#songSheetApplyLeadInput").setChecked(true);
5192+
await page.locator("#regenerateArrangementButton").click();
5193+
await expect(page.locator("#songSheetSummary [data-song-sheet-summary-field='generation-targets'] dd")).toHaveText("Chords/Pad, Bass, Lead");
5194+
await expect(page.locator("#instrumentGridLeadInput")).not.toHaveValue(manualLead);
5195+
5196+
await page.locator("#songSheetSectionIntroInput").fill("G C D");
5197+
await expect(page.locator("#songSheetSummary [data-song-sheet-summary-field='bars-generated'] dd")).toHaveText("7");
5198+
await expect(page.locator("#instrumentGridSectionsInput")).toHaveValue("Intro:3, Verse:2, Chorus:2");
5199+
5200+
const synced = await page.evaluate(() => {
5201+
const app = window.__midiStudioV2App;
5202+
const song = app.selectedSong();
5203+
const gridResult = app.currentInstrumentGridResult();
5204+
return {
5205+
applyTargets: song.studioArrangement.songSheet.applyTargets,
5206+
barCount: gridResult.barCount,
5207+
leadEvents: gridResult.timeline.filter((event) => event.lane === "lead").length,
5208+
sections: song.studioArrangement.sections,
5209+
sequence: song.studioArrangement.songSheet.sequence
5210+
};
5211+
});
5212+
expect(synced).toEqual(expect.objectContaining({
5213+
applyTargets: { bass: true, chordsPad: true, drums: false, lead: true },
5214+
barCount: 7,
5215+
sections: "Intro:3, Verse:2, Chorus:2",
5216+
sequence: "Intro, Verse, Chorus"
5217+
}));
5218+
expect(synced.leadEvents).toBeGreaterThan(0);
5219+
5220+
await selectMidiStudioTab(page, "studio");
5221+
await waitForCanvasRender(page);
5222+
expect((await canvasTimelineState(page)).sections.map((section) => section.label)).toEqual(["Intro", "Verse", "Chorus"]);
5223+
await timelineQuickInstrumentRow(page, "lead").locator("[data-quick-instrument-label='lead']").click();
5224+
await clickCanvasKeyboardKey(page, "C5");
5225+
await expect(page.locator("#statusLog")).toHaveValue(/INFO Auditioned C5 for Lead/);
5226+
5227+
await page.locator("#loopToggle").setChecked(false);
5228+
await page.locator("#playButton").click();
5229+
await expect(page.locator("#playbackState")).toContainText("Playing audible preview");
5230+
await expect.poll(async () => (await page.locator("#playbackState").textContent()) || "", { timeout: 4000 }).toContain("Completed audible preview");
5231+
await expect(page.locator("#playButton")).toBeEnabled();
5232+
await expect(page.locator("#stopButton")).toBeDisabled();
5233+
5234+
await page.locator("#loopToggle").setChecked(true);
5235+
await page.locator("#playButton").click();
5236+
await expect(page.locator("#playbackState")).toContainText("(looping)");
5237+
await expect(page.locator("#stopButton")).toBeEnabled();
5238+
await page.locator("#stopButton").click();
5239+
await expect(page.locator("#playbackState")).toContainText("Stopped audible preview");
5240+
await expect(page.locator("#playButton")).toBeEnabled();
5241+
} finally {
5242+
await workspaceV2CoverageReporter.stop(page);
5243+
await server.close();
5244+
}
5245+
});
5246+
51565247
test("derives primary song, instrument, grid, playback, and diagnostics views from the canonical selected song", async ({ page }) => {
51575248
const server = await openMidiStudioForImport(page);
51585249
try {
@@ -5778,7 +5869,7 @@ test.describe("MIDI Studio V2", () => {
57785869
"Warnings": "none"
57795870
});
57805871
await expect(page.locator("#statusLog")).toHaveValue(/OK Song Sheet parsed: 2 sections, 6 bars, 6 chords\./);
5781-
await expect(page.locator("#statusLog")).toHaveValue(/OK Song Sheet updated the editable note grid\./);
5872+
await expect(page.locator("#statusLog")).toHaveValue(/OK Song Sheet updated the editable note grid for /);
57825873
} finally {
57835874
await workspaceV2CoverageReporter.stop(page);
57845875
await server.close();

tools/midi-studio-v2/index.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -352,6 +352,7 @@ <h3 id="exportStatusHeading">Export Status</h3>
352352
</fieldset>
353353
<div class="midi-studio-v2__song-sheet-actions">
354354
<button id="parseSongSheetButton" type="button">Parse Guided Song Sheet</button>
355+
<button id="regenerateArrangementButton" type="button">Regenerate Arrangement</button>
355356
</div>
356357
<dl id="songSheetSummary" class="midi-studio-v2__details"></dl>
357358
</div>

0 commit comments

Comments
 (0)