Skip to content

Commit 72bef94

Browse files
committed
Make MIDI Studio V2 a testable playable Twinkle prototype - PR_26146_021-midi-studio-v2-testable-playable-build
1 parent 55c25b0 commit 72bef94

5 files changed

Lines changed: 122 additions & 25 deletions

File tree

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
# PR_26146_021 MIDI Studio V2 Testable Playable Build Validation
2+
3+
## Scope
4+
- Kept Load Example And Play as the primary manual/UAT path for Twinkle Twinkle Little Star.
5+
- Preserved audible Preview Synth playback after a user click, Stop All Audio, playhead movement, diagnostics, export WARN behavior, and table editing.
6+
- Added clip-style note blocks inside the visible note table cells so track rows display as playable note/clip blocks across time.
7+
- Collapsed the dense pipe-delimited lane source text into an advanced section so it is no longer part of the primary workflow.
8+
- No SoundFont playback, rendered export implementation, MIDI input/recording, inline style blocks, or inline event handlers were added.
9+
10+
## Changed Files
11+
- `tools/midi-studio-v2/js/controls/InstrumentGridControl.js`
12+
- `tools/midi-studio-v2/index.html`
13+
- `tools/midi-studio-v2/styles/midiStudioV2.css`
14+
- `tests/playwright/tools/MidiStudioV2.spec.mjs`
15+
- `docs/dev/reports/PR_26146_021-midi-studio-v2-testable-playable-build_validation.md`
16+
- `docs/dev/reports/codex_review.diff`
17+
- `docs/dev/reports/codex_changed_files.txt`
18+
- `docs/dev/reports/playwright_v8_coverage_report.txt`
19+
- `docs/dev/reports/coverage_changed_js_guardrail.txt`
20+
- `docs/dev/codex_commands.md`
21+
- `docs/dev/commit_comment.txt`
22+
23+
## Validation
24+
- PASS: `node --check tools/midi-studio-v2/js/controls/InstrumentGridControl.js`
25+
- PASS: `node --check tools/midi-studio-v2/js/MidiStudioV2App.js`
26+
- PASS: `node --check tests/playwright/tools/MidiStudioV2.spec.mjs`
27+
- PASS: HTML guard checked `tools/midi-studio-v2/index.html`; no inline style blocks or inline event handlers were found.
28+
- PASS: `npx.cmd playwright test tests/playwright/tools/MidiStudioV2.spec.mjs --grep "Twinkle|loads example and starts audible|edits spreadsheet"`
29+
- Result: 3 passed.
30+
- Validated Twinkle example load, note blocks, playable note count, active Preview Synth playback, playhead movement, Stop All Audio reset, and editing visible note cells into playback data.
31+
- PASS: `npx.cmd playwright test tests/playwright/tools/MidiStudioV2.spec.mjs`
32+
- Result: 37 passed.
33+
- PASS: `git diff --check`
34+
- Note: Git reported CRLF normalization warnings for touched files; no whitespace errors were reported.
35+
36+
## Coverage Reports
37+
- Updated `docs/dev/reports/playwright_v8_coverage_report.txt`.
38+
- Updated `docs/dev/reports/coverage_changed_js_guardrail.txt`.
39+
- Guardrail status: advisory PASS, no changed runtime JS coverage warnings.
40+
41+
## Environment Notes
42+
- Chromium was missing from the repo-local Playwright browser cache, so Chromium was installed under `node_modules/.cache/ms-playwright` for validation.
43+
- The repo-local Playwright browser cache was removed after validation.
44+
45+
## Manual Test Notes
46+
- Open MIDI Studio V2.
47+
- Click `Load Example And Play`.
48+
- Expected: Twinkle Twinkle Little Star loads, Preview Synth starts, the playhead moves across the note table, and Audio Diagnostics reports playable notes greater than zero.
49+
- Edit a visible Lead note block, click `Normalize Grid`, then `Play Section`.
50+
- Expected: the edited note appears in the normalized playback data and Preview Synth starts from the updated table.
51+
- Click `Stop All Audio`.
52+
- Expected: active preview lanes clear, Preview Synth playing state resets, and the status log reports stopped audio.
53+
54+
## Skips
55+
- Full samples smoke test: SKIPPED per request because sample JSON alignment is out of scope.
56+
- Workspace Manager V2 registration/handoff: SKIPPED because this PR did not touch Workspace V2 registration or handoff.
57+
58+
## ZIP
59+
- Repo-structured delta ZIP: `tmp/PR_26146_021-midi-studio-v2-testable-playable-build_delta.zip`

tests/playwright/tools/MidiStudioV2.spec.mjs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,9 @@ async function fillInstrumentGrid(page, {
273273
await page.locator("#instrumentGridSectionsInput").fill(sections);
274274
await page.locator("#instrumentGridBeatsInput").fill(beats);
275275
await page.locator("#instrumentGridSubdivisionInput").selectOption(subdivision);
276+
await page.locator(".midi-studio-v2__advanced-lane-source").evaluate((details) => {
277+
details.open = true;
278+
});
276279
await page.locator("#instrumentGridChordsInput").fill(chords);
277280
await page.locator("#instrumentGridBassInput").fill(bass);
278281
await page.locator("#instrumentGridPadInput").fill(pad);
@@ -397,7 +400,10 @@ test.describe("MIDI Studio V2", () => {
397400
await expect(page.locator(".midi-studio-v2__grid-cell").first()).toHaveText("Instrument");
398401
await expect(page.locator(".midi-studio-v2__note-table-column-header").first()).toContainText("Bar 1");
399402
await expect(page.locator(".midi-studio-v2__note-table-cell[data-lane]")).not.toHaveCount(0);
403+
await expect(page.locator(".midi-studio-v2__note-block")).not.toHaveCount(0);
404+
await expect(page.locator('.midi-studio-v2__note-table-cell[data-lane="lead"] .midi-studio-v2__note-block').first()).toHaveText("C4");
400405
await expect(page.locator("#instrumentGridOutput textarea")).toHaveCount(0);
406+
await expect(page.locator(".midi-studio-v2__advanced-lane-source")).not.toHaveAttribute("open", "");
401407
await expect(page.locator(".midi-studio-v2__lane-header-cell strong")).toContainText(["Chords", "Bass", "Pad", "Lead", "Drums"]);
402408
expect(await page.evaluate(() => {
403409
const output = document.querySelector("#instrumentGridOutput");
@@ -461,6 +467,7 @@ test.describe("MIDI Studio V2", () => {
461467
expect(await page.evaluate(() => window.__midiStudioPreviewSynthEvents.some((event) => event.action === "oscillator-start"))).toBe(true);
462468
await expect(page.locator(".midi-studio-v2__grid-cell--timing-header.midi-studio-v2__grid-cell--playhead-active")).not.toHaveAttribute("data-step-index", "0");
463469
await expect(spreadsheetCell(page, "bass", 0)).toHaveText("C2");
470+
await expect(page.locator('.midi-studio-v2__note-table-cell[data-lane="lead"] .midi-studio-v2__note-block').first()).toHaveText("C4");
464471
const diagnostics = await audioDiagnosticsRows(page);
465472
expect(Number(diagnostics["Playable note count"])).toBeGreaterThan(0);
466473
expect(diagnostics).toMatchObject({
@@ -788,6 +795,7 @@ Am F`);
788795
await expect(page.locator("#instrumentGridLeadInput")).toHaveValue("E4 G4 A4 B4 | C5 B4 G4 -");
789796
await page.locator("#normalizeInstrumentGridButton").click();
790797
await expect(spreadsheetCell(page, "lead", 3)).toHaveText("B4");
798+
await expect(spreadsheetCell(page, "lead", 3).locator(".midi-studio-v2__note-block")).toHaveText("B4");
791799
const editedModel = await page.evaluate(() => window.__midiStudioV2App.lastInstrumentGridResult);
792800
expect(editedModel.timeline.some((event) => event.lane === "lead" && event.value === "B4" && event.bar === 1)).toBe(true);
793801
expect(Number((await audioDiagnosticsRows(page))["Playable note count"])).toBeGreaterThan(beforeCount);

tools/midi-studio-v2/index.html

Lines changed: 25 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -236,28 +236,31 @@ <h2 class="tools-platform-frame__eyebrow">First-Class Tools Surface V2</h2>
236236
</div>
237237
<dl id="instrumentGridSummary" class="midi-studio-v2__details"></dl>
238238
<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">
240-
<label class="tool-starter__field" for="instrumentGridChordsInput">
241-
<span>Chords</span>
242-
<textarea id="instrumentGridChordsInput" rows="2" placeholder="Am F C G | Am F C G"></textarea>
243-
</label>
244-
<label class="tool-starter__field" for="instrumentGridBassInput">
245-
<span>Bass</span>
246-
<textarea id="instrumentGridBassInput" rows="2" placeholder="A2 - F2 - | C3 - G2 -"></textarea>
247-
</label>
248-
<label class="tool-starter__field" for="instrumentGridPadInput">
249-
<span>Pad</span>
250-
<textarea id="instrumentGridPadInput" rows="2" placeholder="Am - F - | C - G -"></textarea>
251-
</label>
252-
<label class="tool-starter__field" for="instrumentGridLeadInput">
253-
<span>Lead</span>
254-
<textarea id="instrumentGridLeadInput" rows="2" placeholder="E4 G4 A4 - | C5 B4 G4 -"></textarea>
255-
</label>
256-
<label class="tool-starter__field" for="instrumentGridDrumsInput">
257-
<span>Drums</span>
258-
<textarea id="instrumentGridDrumsInput" rows="2" placeholder="kick hat snare hat | kick hat snare hat"></textarea>
259-
</label>
260-
</div>
239+
<details class="midi-studio-v2__advanced-lane-source">
240+
<summary>Advanced lane source text</summary>
241+
<div class="midi-studio-v2__grid-lane-inputs midi-studio-v2__grid-lane-inputs--source" aria-label="Advanced pipe-delimited lane source inputs">
242+
<label class="tool-starter__field" for="instrumentGridChordsInput">
243+
<span>Chords</span>
244+
<textarea id="instrumentGridChordsInput" rows="2" placeholder="Am F C G | Am F C G"></textarea>
245+
</label>
246+
<label class="tool-starter__field" for="instrumentGridBassInput">
247+
<span>Bass</span>
248+
<textarea id="instrumentGridBassInput" rows="2" placeholder="A2 - F2 - | C3 - G2 -"></textarea>
249+
</label>
250+
<label class="tool-starter__field" for="instrumentGridPadInput">
251+
<span>Pad</span>
252+
<textarea id="instrumentGridPadInput" rows="2" placeholder="Am - F - | C - G -"></textarea>
253+
</label>
254+
<label class="tool-starter__field" for="instrumentGridLeadInput">
255+
<span>Lead</span>
256+
<textarea id="instrumentGridLeadInput" rows="2" placeholder="E4 G4 A4 - | C5 B4 G4 -"></textarea>
257+
</label>
258+
<label class="tool-starter__field" for="instrumentGridDrumsInput">
259+
<span>Drums</span>
260+
<textarea id="instrumentGridDrumsInput" rows="2" placeholder="kick hat snare hat | kick hat snare hat"></textarea>
261+
</label>
262+
</div>
263+
</details>
261264
<div class="midi-studio-v2__preview-lane-controls" aria-label="Preview Synth lane controls">
262265
<div class="midi-studio-v2__preview-lane-row" data-preview-lane="chords">
263266
<label class="tool-starter__field" for="previewInstrumentChordsSelect">

tools/midi-studio-v2/js/controls/InstrumentGridControl.js

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -440,7 +440,7 @@ export class InstrumentGridControl {
440440
outputCell.contentEditable = "true";
441441
outputCell.role = "textbox";
442442
outputCell.spellcheck = false;
443-
outputCell.textContent = cell.token || "-";
443+
outputCell.append(this.createNoteBlock(cell));
444444
outputCell.setAttribute("aria-label", `${LANE_LABELS[cell.lane] || cell.lane} note cell, section ${cell.section}, bar ${cell.bar}, beat ${cell.beat}`);
445445
outputCell.dataset.lane = cell.lane;
446446
outputCell.dataset.source = cell.source;
@@ -454,6 +454,13 @@ export class InstrumentGridControl {
454454
return outputCell;
455455
}
456456

457+
createNoteBlock(cell) {
458+
const block = document.createElement("span");
459+
block.className = "midi-studio-v2__note-block";
460+
block.textContent = cell.token || "-";
461+
return block;
462+
}
463+
457464
noteTableCellClass(cell) {
458465
const classes = ["midi-studio-v2__grid-cell", "midi-studio-v2__spreadsheet-note-cell", "midi-studio-v2__note-table-cell"];
459466
if (!REST_TOKENS.has(String(cell.token || "").trim().toLowerCase())) {

tools/midi-studio-v2/styles/midiStudioV2.css

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,11 +42,13 @@
4242
gap: 0.75rem;
4343
}
4444

45-
.midi-studio-v2__advanced-song-sheet[open] {
45+
.midi-studio-v2__advanced-song-sheet[open],
46+
.midi-studio-v2__advanced-lane-source[open] {
4647
padding-top: 0.25rem;
4748
}
4849

49-
.midi-studio-v2__advanced-song-sheet summary {
50+
.midi-studio-v2__advanced-song-sheet summary,
51+
.midi-studio-v2__advanced-lane-source summary {
5052
cursor: pointer;
5153
font-weight: 700;
5254
}
@@ -143,6 +145,12 @@
143145
padding-top: 0.75rem;
144146
}
145147

148+
.midi-studio-v2__advanced-lane-source {
149+
border: 1px solid var(--tool-starter-line);
150+
border-radius: 8px;
151+
padding: 0.65rem 0.75rem;
152+
}
153+
146154
.midi-studio-v2__instrument-grid-output {
147155
max-width: 100%;
148156
overflow: auto;
@@ -237,6 +245,18 @@
237245
outline-offset: -3px;
238246
}
239247

248+
.midi-studio-v2__note-block {
249+
align-items: center;
250+
background: rgba(14, 165, 233, 0.22);
251+
border: 1px solid rgba(125, 211, 252, 0.56);
252+
border-radius: 6px;
253+
display: inline-flex;
254+
justify-content: center;
255+
min-height: 2rem;
256+
min-width: 3.25rem;
257+
padding: 0.22rem 0.45rem;
258+
}
259+
240260
.midi-studio-v2__grid-cell--label,
241261
.midi-studio-v2__grid-cell--section {
242262
background: var(--tool-starter-surface);

0 commit comments

Comments
 (0)