Skip to content

Commit 6c15e3e

Browse files
committed
Replace MIDI Studio V2 dense grid with Signal-style spreadsheet note table - PR_26146_020-midi-studio-v2-signal-style-note-table
1 parent 4ac9269 commit 6c15e3e

5 files changed

Lines changed: 146 additions & 114 deletions

File tree

tests/playwright/tools/MidiStudioV2.spec.mjs

Lines changed: 53 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -379,27 +379,37 @@ test.describe("MIDI Studio V2", () => {
379379
}
380380
});
381381

382-
test("loads explicit demo test song data for UAT grid and Preview Synth timing preview", async ({ page }) => {
382+
test("loads explicit Twinkle Twinkle Little Star test song data for UAT note table and Preview Synth timing preview", async ({ page }) => {
383383
const server = await openMidiStudio(page);
384384
try {
385385
await page.locator("#useExampleButton").click();
386-
await expect(page.locator("#songList")).toContainText("Demo Test Song");
386+
await expect(page.locator("#songList")).toContainText("Twinkle Twinkle Little Star");
387387
await expect(page.locator("#songList")).toContainText("Demo Missing Target");
388-
await expect(page.locator("#songSheetKeyInput")).toHaveValue("A minor");
389-
await expect(page.locator("#songSheetStyleInput")).toHaveValue("retro-arcade");
390-
await expect(page.locator("#instrumentGridSectionsInput")).toHaveValue("intro:1, loop:1, victory:1");
391-
await expect(page.locator("#instrumentGridChordsInput")).toHaveValue("Am F C G | Am F C G | C G F Am");
388+
await expect(page.locator("#songSheetKeyInput")).toHaveValue("C major");
389+
await expect(page.locator("#songSheetStyleInput")).toHaveValue("chip");
390+
await expect(page.locator("#instrumentGridSectionsInput")).toHaveValue("intro:2, loop:2");
391+
await expect(page.locator("#instrumentGridChordsInput")).toHaveValue("C C G G | F F C C | F F C C | G G C C");
392392
await expect(page.locator("#previewInstrumentLeadSelect")).toHaveValue("retro-pulse-lead");
393393
await expect(page.locator("#previewInstrumentPadSelect")).toHaveValue("ambient-pad");
394-
await expect(page.locator("#statusLog")).toHaveValue(/OK Loaded explicit demo test song data\. Demo paths are declared for UAT only; they are not hidden fallback assets\./);
394+
await expect(page.locator("#statusLog")).toHaveValue(/OK Loaded explicit Twinkle Twinkle Little Star test song data\. Demo paths are declared for UAT only; they are not hidden fallback assets\./);
395395
await expect(page.locator("#instrumentGridSectionSelect")).toContainText("intro");
396-
await expect(page.locator(".midi-studio-v2__grid-cell").first()).toHaveText("Instrument/Lane");
397-
await expect(page.locator(".midi-studio-v2__spreadsheet-note-cell[data-lane]")).not.toHaveCount(0);
398-
await expect(spreadsheetCell(page, "chords", 0)).toHaveText("Am");
399-
await expect(spreadsheetCell(page, "bass", 0)).toHaveText("A2");
400-
await expect(spreadsheetCell(page, "pad", 0)).toHaveText("Am");
401-
await expect(spreadsheetCell(page, "lead", 0)).toHaveText("A4");
396+
await expect(page.locator(".midi-studio-v2__note-table")).toHaveCount(1);
397+
await expect(page.locator(".midi-studio-v2__grid-cell").first()).toHaveText("Instrument");
398+
await expect(page.locator(".midi-studio-v2__note-table-column-header").first()).toContainText("Bar 1");
399+
await expect(page.locator(".midi-studio-v2__note-table-cell[data-lane]")).not.toHaveCount(0);
400+
await expect(page.locator("#instrumentGridOutput textarea")).toHaveCount(0);
401+
await expect(page.locator(".midi-studio-v2__lane-header-cell strong")).toContainText(["Chords", "Bass", "Pad", "Lead", "Drums"]);
402+
expect(await page.evaluate(() => {
403+
const output = document.querySelector("#instrumentGridOutput");
404+
const sourceInputs = document.querySelector(".midi-studio-v2__grid-lane-inputs--source");
405+
return Boolean(output && sourceInputs && (output.compareDocumentPosition(sourceInputs) & Node.DOCUMENT_POSITION_FOLLOWING));
406+
})).toBe(true);
407+
await expect(spreadsheetCell(page, "chords", 0)).toHaveText("C");
408+
await expect(spreadsheetCell(page, "bass", 0)).toHaveText("C2");
409+
await expect(spreadsheetCell(page, "pad", 0)).toHaveText("C");
410+
await expect(spreadsheetCell(page, "lead", 0)).toHaveText("C4");
402411
await expect(spreadsheetCell(page, "drums", 0)).toHaveText("kick");
412+
expect(await page.locator("#instrumentGridOutput").evaluate((output) => !output.contains(document.querySelector("#audioDiagnostics")))).toBe(true);
403413
await page.locator("#playSectionButton").click();
404414
await expect(page.locator("#statusLog")).toHaveValue(/OK Preview Synth started for section intro with \d+ playable events\./);
405415
expect(await page.evaluate(() => window.__midiStudioPreviewSynthEvents.some((event) => event.action === "oscillator-start"))).toBe(true);
@@ -410,24 +420,23 @@ test.describe("MIDI Studio V2", () => {
410420
await page.locator("#generateArpeggioFromChordsButton").click();
411421
await page.locator("#generateBasicDrumsButton").click();
412422
await page.locator("#normalizeInstrumentGridButton").click();
413-
await expect(page.locator("#instrumentGridOutput")).toContainText("victory");
414-
await expect(page.locator(".midi-studio-v2__grid-cell--bar")).toHaveCount(3);
415-
await expect(page.locator("#instrumentGridSectionSelect")).toContainText("victory");
423+
await expect(page.locator(".midi-studio-v2__grid-cell--bar")).toHaveCount(4);
424+
await expect(page.locator("#instrumentGridSectionSelect")).toContainText("loop");
416425
const demoModel = await page.evaluate(() => window.__midiStudioV2App.lastInstrumentGridResult);
417-
expect(demoModel).toMatchObject({ barCount: 3, ok: true });
418-
expect(demoModel.timeline.some((event) => event.source === "generated" && event.lane === "bass")).toBe(true);
426+
expect(demoModel).toMatchObject({ barCount: 4, ok: true });
427+
expect(demoModel.timeline.some((event) => event.lane === "lead" && event.value === "C4")).toBe(true);
419428

420-
await page.locator("#instrumentGridLoopStartSelect").selectOption("loop");
421-
await page.locator("#instrumentGridLoopEndSelect").selectOption("victory");
429+
await page.locator("#instrumentGridLoopStartSelect").selectOption("intro");
430+
await page.locator("#instrumentGridLoopEndSelect").selectOption("loop");
422431
expect(await page.locator(".midi-studio-v2__grid-cell--loop-region").count()).toBeGreaterThan(0);
423432
await page.locator("#playLoopButton").click();
424-
await expect(page.locator("#statusLog")).toHaveValue(/OK Preview Synth started for loop loop to victory with \d+ playable events\./);
433+
await expect(page.locator("#statusLog")).toHaveValue(/OK Preview Synth started for loop intro to loop with \d+ playable events\./);
425434
await expect(page.locator("#statusLog")).toHaveValue(/Preview Synth uses temporary oscillator instruments for grid audition only; SoundFont playback is not implemented\./);
426-
await expect(page.locator("#instrumentGridTransportState")).toContainText("Playing loop Preview Synth timing preview: loop to victory");
435+
await expect(page.locator("#instrumentGridTransportState")).toContainText("Playing loop Preview Synth timing preview: intro to loop");
427436
expect(await page.evaluate(() => window.__midiStudioPreviewSynthEvents.some((event) => event.action === "oscillator-start"))).toBe(true);
428437
await expect(page.locator(".midi-studio-v2__grid-cell--lane-active")).not.toHaveCount(0);
429438
await page.locator("#exportOggButton").click();
430-
await expect(page.locator("#statusLog")).toHaveValue(/WARN Export rendering not implemented for OGG\. Planned target: assets\/music\/demo\/demo-test-song\.ogg\./);
439+
await expect(page.locator("#statusLog")).toHaveValue(/WARN Export rendering not implemented for OGG\. Planned target: assets\/music\/demo\/twinkle-twinkle-little-star\.ogg\./);
431440
await page.locator('[data-song-id="demo-missing-target"]').click();
432441
await page.locator("#exportWavButton").click();
433442
await expect(page.locator("#statusLog")).toHaveValue(/FAIL Missing rendered WAV export target for Demo Missing Target\. Add music\.songs\[\]\.rendered\.wav before exporting\./);
@@ -442,23 +451,23 @@ test.describe("MIDI Studio V2", () => {
442451
try {
443452
await page.locator("#loadExampleAndPlayButton").click();
444453
await expect(page.locator("#statusLog")).toHaveValue(/OK Load Example And Play started\./);
445-
await expect(page.locator("#statusLog")).toHaveValue(/OK Load Example And Play loaded explicit demo data\./);
454+
await expect(page.locator("#statusLog")).toHaveValue(/OK Load Example And Play loaded explicit Twinkle Twinkle Little Star data\./);
446455
await expect(page.locator("#statusLog")).toHaveValue(/OK Load Example And Play assigned Preview Synth instruments\./);
447-
await expect(page.locator("#statusLog")).toHaveValue(/OK Demo grid has \d+ playable Preview Synth notes after lane generation\./);
456+
await expect(page.locator("#statusLog")).toHaveValue(/OK Twinkle grid has \d+ playable Preview Synth notes after lane generation\./);
448457
await expect(page.locator("#statusLog")).toHaveValue(/OK Preview Synth started for section intro with \d+ playable events\./);
449458
await expect(page.locator("#statusLog")).toHaveValue(/OK Load Example And Play started audible Preview Synth playback and moved the playhead\./);
450459
await expect(page.locator("#instrumentGridTransportState")).toContainText("Playing section Preview Synth timing preview: intro");
451460
expect(await page.evaluate(() => window.__midiStudioPreviewSynthEvents.some((event) => event.action === "resume"))).toBe(true);
452461
expect(await page.evaluate(() => window.__midiStudioPreviewSynthEvents.some((event) => event.action === "oscillator-start"))).toBe(true);
453462
await expect(page.locator(".midi-studio-v2__grid-cell--timing-header.midi-studio-v2__grid-cell--playhead-active")).not.toHaveAttribute("data-step-index", "0");
454-
await expect(spreadsheetCell(page, "bass", 0)).toHaveText("A2");
463+
await expect(spreadsheetCell(page, "bass", 0)).toHaveText("C2");
455464
const diagnostics = await audioDiagnosticsRows(page);
456465
expect(Number(diagnostics["Playable note count"])).toBeGreaterThan(0);
457466
expect(diagnostics).toMatchObject({
458467
"Audio context state": "running",
459468
"Last playback error": "none",
460469
"Selected section": "intro",
461-
"Selected song": "Demo Test Song"
470+
"Selected song": "Twinkle Twinkle Little Star"
462471
});
463472
expect(diagnostics["Active lanes"]).toMatch(/bass|chords|drums|lead|pad/);
464473
expect(diagnostics["Current preview instrument pack"]).toContain("lead: Retro Pulse Lead");
@@ -739,8 +748,8 @@ Am F`);
739748
await page.locator("#normalizeInstrumentGridButton").click();
740749
await expect(page.locator("#instrumentGridSummary")).toContainText("intro: 1 bar; loop: 1 bar");
741750
await expect(page.locator("#instrumentGridSummary")).toContainText("30 normalized events");
742-
await expect(page.locator("#instrumentGridOutput")).toContainText("intro");
743-
await expect(page.locator("#instrumentGridOutput")).toContainText("loop");
751+
await expect(page.locator("#instrumentGridOutput")).not.toContainText("intro");
752+
await expect(page.locator("#instrumentGridOutput")).not.toContainText("loop");
744753
await expect(page.locator("#instrumentGridOutput")).toContainText("Am");
745754
await expect(page.locator("#instrumentGridOutput")).toContainText("A2");
746755
await expect(page.locator("#instrumentGridOutput")).toContainText("kick");
@@ -804,16 +813,16 @@ Am F`);
804813
});
805814
await page.locator("#normalizeInstrumentGridButton").click();
806815
expect(await page.locator(".midi-studio-v2__instrument-grid").evaluate((grid) => getComputedStyle(grid).gridTemplateColumns.split(" ").length)).toBe(9);
807-
await expect(page.locator(".midi-studio-v2__grid-cell").first()).toHaveText("Instrument/Lane");
816+
await expect(page.locator(".midi-studio-v2__grid-cell").first()).toHaveText("Instrument");
808817
expect(await page.locator(".midi-studio-v2__grid-cell--timing-header").evaluateAll((cells) => cells.map((cell) => cell.textContent))).toEqual([
809-
"Section introBar 1Beat 1",
810-
"Section introBar 1Beat 2",
811-
"Section introBar 1Beat 3",
812-
"Section introBar 1Beat 4",
813-
"Section loopBar 2Beat 1",
814-
"Section loopBar 2Beat 2",
815-
"Section loopBar 2Beat 3",
816-
"Section loopBar 2Beat 4"
818+
"Bar 1Beat 1",
819+
"Bar 1Beat 2",
820+
"Bar 1Beat 3",
821+
"Bar 1Beat 4",
822+
"Bar 2Beat 1",
823+
"Bar 2Beat 2",
824+
"Bar 2Beat 3",
825+
"Bar 2Beat 4"
817826
]);
818827
await expect(spreadsheetCell(page, "chords", 0)).toHaveText("Am");
819828
await expect(spreadsheetCell(page, "bass", 0)).toHaveText("A2");
@@ -854,7 +863,8 @@ Am F`);
854863
await fillInstrumentGrid(page, { drums: "kick zap snare hat | kick hat snare hat" });
855864
await page.locator("#normalizeInstrumentGridButton").click();
856865
await expect(page.locator("#instrumentGridSummary")).toContainText('Invalid drum token "zap"');
857-
await expect(page.locator("#instrumentGridOutput")).toContainText("intro");
866+
await expect(page.locator("#instrumentGridSummary")).toContainText("intro: 1 bar; loop: 1 bar");
867+
await expect(page.locator("#instrumentGridOutput")).not.toContainText("intro");
858868
await expect(page.locator("#statusLog")).toHaveValue(/WARN Instrument grid normalized with warnings: Invalid drum token "zap" at bar 1, beat 2; token was skipped\./);
859869
await expect(page.locator("#statusLog")).toHaveValue(/OK Instrument grid normalized: 2 sections, 2 bars, 29 events\./);
860870
} finally {
@@ -880,10 +890,10 @@ Am F`);
880890
await page.locator("#normalizeInstrumentGridButton").click();
881891
expect(await page.locator(".midi-studio-v2__instrument-grid").evaluate((grid) => getComputedStyle(grid).gridTemplateColumns.split(" ").length)).toBe(5);
882892
expect(await page.locator(".midi-studio-v2__grid-cell--timing-header").evaluateAll((cells) => cells.map((cell) => cell.textContent))).toEqual([
883-
"Section introBar 1Beat 1Subdivision 1",
884-
"Section introBar 1Beat 1Subdivision 2",
885-
"Section introBar 1Beat 2Subdivision 1",
886-
"Section introBar 1Beat 2Subdivision 2"
893+
"Bar 1Beat 1Subdivision 1",
894+
"Bar 1Beat 1Subdivision 2",
895+
"Bar 1Beat 2Subdivision 1",
896+
"Bar 1Beat 2Subdivision 2"
887897
]);
888898
await page.locator("#instrumentGridSubdivisionInput").selectOption("16");
889899
await expect(page.locator("#instrumentGridSnapIndicator")).toContainText("Snap: 1 bar / 2 beats / 1/16");

tools/midi-studio-v2/index.html

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,7 @@ <h2 class="tools-platform-frame__eyebrow">First-Class Tools Surface V2</h2>
199199

200200
<section class="accordion-v2 tool-starter__accordion is-open" data-accordion-v2-open="true">
201201
<button class="accordion-v2__header" type="button" aria-expanded="true" aria-controls="instrumentGridContent">
202-
<span>Multi-Instrument Grid</span>
202+
<span>Signal-Style Note Table</span>
203203
<span id="instrumentGridSnapIndicator" class="midi-studio-v2__snap-indicator">Snap: not set</span>
204204
<span class="accordion-v2__icon" aria-hidden="true">+</span>
205205
</button>
@@ -234,7 +234,9 @@ <h2 class="tools-platform-frame__eyebrow">First-Class Tools Surface V2</h2>
234234
</select>
235235
</label>
236236
</div>
237-
<div class="midi-studio-v2__grid-lane-inputs">
237+
<dl id="instrumentGridSummary" class="midi-studio-v2__details"></dl>
238+
<div id="instrumentGridOutput" class="midi-studio-v2__instrument-grid-output" aria-label="Signal-style MIDI note table"></div>
239+
<div class="midi-studio-v2__grid-lane-inputs midi-studio-v2__grid-lane-inputs--source" aria-label="Advanced pipe-delimited lane source inputs">
238240
<label class="tool-starter__field" for="instrumentGridChordsInput">
239241
<span>Chords</span>
240242
<textarea id="instrumentGridChordsInput" rows="2" placeholder="Am F C G | Am F C G"></textarea>
@@ -405,8 +407,6 @@ <h2 class="tools-platform-frame__eyebrow">First-Class Tools Surface V2</h2>
405407
</div>
406408
<p id="instrumentGridTransportState" class="tool-starter__hint">Preview Synth timing preview stopped.</p>
407409
</div>
408-
<dl id="instrumentGridSummary" class="midi-studio-v2__details"></dl>
409-
<div id="instrumentGridOutput" class="midi-studio-v2__instrument-grid-output" aria-label="Aligned multi-instrument grid"></div>
410410
</div>
411411
</section>
412412
</section>

0 commit comments

Comments
 (0)