Skip to content

Commit 6bf1621

Browse files
committed
Separate MIDI Studio V2 game classification from musical song-sheet sections - PR_26146_070-midi-studio-v2-song-classification-and-musical-sections
1 parent 26f6217 commit 6bf1621

12 files changed

Lines changed: 415 additions & 19 deletions
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# PR_26146_070 MIDI Studio V2 Section Semantics Map
2+
3+
## Ownership Summary
4+
5+
| Concept | Owning UI | Canonical model field | Editable | Purpose |
6+
| --- | --- | --- | --- | --- |
7+
| Song Name | Song Setup > Song Details | `music.songs[].name` | Yes | Human song title. |
8+
| Classification | Song Setup > Song Details | `music.songs[].classification` | Yes | Game usage classification such as Menu, Loop, Boss, Flying, or Puzzle. |
9+
| Generated ID | Song Setup > Song Details | `music.songs[].id` | Read-only/generated | `camelCase(Name)-Classification`; not a musical section. |
10+
| Tempo | Song Setup > Song Details | `studioArrangement.tempo` and selected song tempo metadata | Yes | Playback and derived duration. |
11+
| Key | Song Setup > Song Details | `studioArrangement.key` and selected song key metadata | Yes | Song metadata. |
12+
| Style | Song Setup > Song Details | `studioArrangement.style` and selected song style metadata | Yes | Song metadata. |
13+
| Musical section definitions | Song Setup > Song Sheet | `studioArrangement.songSheet.sectionDefinitions` and `studioArrangement.songSheet.sections` | Yes through guided text | Reusable music structure definitions with chords/bars. |
14+
| Sequence | Song Setup > Song Sheet | `studioArrangement.songSheet.sequence` | Yes | Ordered musical form, for example Intro, Verse, Chorus, Verse, Chorus, Bridge, Chorus, Outro. |
15+
| Loop sections | Song Setup > Song Sheet | `studioArrangement.songSheet.loopSections` | Yes | Musical section labels used for loop playback. |
16+
| Timeline section colors | Octave Timeline | Derived from `studioArrangement.songSheet.sequence` and expanded grid sections | Read-only display | Color bars by musical section occurrences; repeated labels share a color. |
17+
| Section buttons | Octave Timeline | Derived from current musical section labels | Action when defined | Buttons operate only for musical sections present in the current song sheet. |
18+
| Boss/Victory buttons | Octave Timeline | Derived from current musical section labels | Disabled/unwired unless defined | No longer assumed musical sections because Boss/Victory are game classifications by default. |
19+
20+
## Semantics
21+
22+
Game classification answers where or how the song is used in a game. Examples include Menu, Boss, Victory, Game Over, Flying, Ice, Dungeon, Puzzle, and Chase. Classification is stored on the selected song and contributes to generated ID text only.
23+
24+
Musical sections describe the structure of the song. Examples include Intro, Verse, Chorus, Pre-Chorus, Bridge, Outro, Solo, Break, A, B, and C. These sections own chords, bars, and timeline order.
25+
26+
The Song Sheet Sequence expands reusable musical section definitions into the actual arrangement. A section such as Verse or Chorus can appear multiple times in Sequence while retaining one reusable definition. Octave Timeline colors and section shortcut availability come from this expanded musical sequence, not from Classification.
27+
28+
## Parse Guided Song Sheet Flow
29+
30+
1. Song Setup owns editable Classification and generated ID display.
31+
2. Song Sheet collects reusable musical section definitions and Sequence text.
32+
3. Parse Guided Song Sheet builds canonical Song Sheet data from those musical sections.
33+
4. The canonical arrangement refreshes derived bars, chord count, estimated duration, diagnostics, JSON Details, and Octave Timeline canvas data.
34+
5. Octave Timeline renders bar colors from expanded musical section occurrences and disables or marks missing musical section controls as unavailable.
35+
36+
## UAT Notes
37+
38+
PASS - Classification and musical sections are separate concepts.
39+
40+
PASS - Boss and Victory remain valid Classification examples, but they are not active musical section buttons unless explicitly defined in the Song Sheet.
41+
42+
PASS - Repeated musical sections retain matching color identity across the Octave Timeline.
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
# PR_26146_070 MIDI Studio V2 Song Classification and Musical Sections Validation
2+
3+
Status: PASS
4+
5+
## Scope Verified
6+
7+
- Added editable Song Details Classification as human text with a help icon listing game usage examples.
8+
- Generated Song ID from `camelCase(Name)-Classification` and kept ID read-only/generated.
9+
- Separated game usage classification from musical Song Sheet section semantics.
10+
- Added reusable musical section definitions plus Sequence ordering for the guided Song Sheet workflow.
11+
- Updated Parse Guided Song Sheet to expand musical sequence data into the canonical song model and Octave Timeline.
12+
- Updated Octave Timeline section colors to follow musical section sequence data, with repeated section names sharing colors.
13+
- Kept Boss/Victory as inactive or unwired musical section controls unless the Song Sheet defines those sections.
14+
15+
## Validation Commands
16+
17+
PASS - Project instructions read:
18+
19+
- `Get-Content docs/dev/PROJECT_INSTRUCTIONS.md`
20+
21+
PASS - Changed-file syntax checks:
22+
23+
- `node --check tools/midi-studio-v2/js/controls/SongDetailsControl.js`
24+
- `node --check tools/midi-studio-v2/js/controls/SongSheetControl.js`
25+
- `node --check src/engine/audio/SongSheetParser.js`
26+
- `node --check src/engine/audio/InstrumentGridParser.js`
27+
- `node --check tools/midi-studio-v2/js/MidiStudioV2App.js`
28+
- `node --check tools/midi-studio-v2/js/bootstrap.js`
29+
- `node --check tools/midi-studio-v2/js/services/MidiStudioStateSerializer.js`
30+
- `node --check tests/playwright/tools/MidiStudioV2.spec.mjs`
31+
32+
PASS - External JS/CSS only check:
33+
34+
- `Select-String -Path tools/midi-studio-v2/index.html -Pattern '<script(?![^>]*\bsrc=)|<style|\son[a-z]+='`
35+
- Result: no inline script, style, or event handler matches.
36+
37+
PASS - Targeted MIDI Studio V2 Playwright tests:
38+
39+
- `npx playwright test tests/playwright/tools/MidiStudioV2.spec.mjs --grep "PR069|PR070" --reporter=list`
40+
- Result: 2 passed.
41+
- Covered PR069 section color regression and PR070 classification/musical section sequence workflow.
42+
43+
PASS - Parser sequence smoke:
44+
45+
- Verified `SongSheetParser` preserves sequence order, expands repeated musical sections, and computes bar count from sequence.
46+
- Verified `InstrumentGridParser` gives repeated musical section labels the same color index.
47+
48+
PASS - Diff hygiene:
49+
50+
- `git diff --check`
51+
- Result: no whitespace errors.
52+
- Note: Git reported LF-to-CRLF normalization warnings for touched text files only.
53+
54+
SKIPPED - Full samples smoke test:
55+
56+
- Not run, per PR instructions.
57+
58+
## Requirement Matrix
59+
60+
PASS - Classification is editable human text.
61+
62+
PASS - Classification help icon tooltip lists requested examples including Menu, Intro, Loop, Boss, Victory, Game Over, Ambient, Cutscene, Underwater, Flying, Ice, Lava, Space, Castle, Town, Dungeon, Forest, Night, Stealth, Puzzle, and Chase.
63+
64+
PASS - Changing Name or Classification updates generated ID as `camelCase(Name)-Classification`.
65+
66+
PASS - Song Sheet supports musical section definitions such as Intro, Verse, Chorus, Bridge, and Outro.
67+
68+
PASS - Song Sheet supports Sequence ordering such as `Intro, Verse, Chorus, Verse, Chorus, Bridge, Chorus, Outro`.
69+
70+
PASS - Parse Guided Song Sheet updates canonical `studioArrangement.songSheet.sequence`, canonical section ordering, derived bars/chord count/duration, JSON details, and Octave Timeline data.
71+
72+
PASS - Octave Timeline colors bars by musical section sequence rather than game classification.
73+
74+
PASS - Boss and Victory are not assumed musical sections unless the current Song Sheet defines those labels.
75+
76+
PASS - Play and Stop still work in the targeted MIDI Studio V2 coverage.
77+
78+
## Result
79+
80+
PR PASS. The octave timeline remains playable, and musical section sequence data now owns timeline color semantics separately from game usage classification.

src/engine/audio/InstrumentGridParser.js

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,13 +66,20 @@ export class InstrumentGridParser {
6666
timeline.push(...parsedLane.events);
6767
warnings.push(...parsedLane.warnings);
6868
}
69+
let nextSectionColorIndex = 0;
70+
const sectionColorIndices = new Map();
6971
let sectionStepCursor = 0;
70-
const normalizedSections = sections.value.map((section, index) => {
72+
const normalizedSections = sections.value.map((section) => {
73+
const colorKey = section.label.toLowerCase();
74+
if (!sectionColorIndices.has(colorKey)) {
75+
sectionColorIndices.set(colorKey, nextSectionColorIndex);
76+
nextSectionColorIndex += 1;
77+
}
7178
const steps = section.bars * stepsPerBar;
7279
const normalized = {
7380
...section,
7481
beatsPerBar: beatsPerBar.value,
75-
colorIndex: index % 5,
82+
colorIndex: sectionColorIndices.get(colorKey) % 5,
7683
endStep: sectionStepCursor + steps - 1,
7784
startStep: sectionStepCursor,
7885
subdivision: subdivision.value,

src/engine/audio/SongSheetParser.js

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
const CHORD_PATTERN = /^[A-G](?:#|b)?(?:m|min|maj|dim|aug|sus2|sus4|7|maj7|m7|min7|dim7|add9)?$/;
2-
const DIRECTIVES = new Set(["key", "style", "tempo"]);
2+
const DIRECTIVES = new Set(["key", "sequence", "style", "tempo"]);
33

44
export class SongSheetParser {
55
parse(sourceText) {
@@ -8,8 +8,8 @@ export class SongSheetParser {
88
return { ok: false, message: "Song Sheet is empty. Add tempo, key, style, and at least one section." };
99
}
1010
const lines = String(sourceText || "").split(/\r?\n/);
11-
const metadata = { key: "", style: "", tempo: 120 };
12-
const sections = [];
11+
const metadata = { key: "", sequence: [], style: "", tempo: 120 };
12+
const sectionDefinitions = [];
1313
const warnings = [];
1414
let activeSection = null;
1515
for (let index = 0; index < lines.length; index += 1) {
@@ -34,7 +34,7 @@ export class SongSheetParser {
3434
loop: label.toLowerCase() === "loop",
3535
timeline: []
3636
};
37-
sections.push(activeSection);
37+
sectionDefinitions.push(activeSection);
3838
continue;
3939
}
4040
if (line.includes("=")) {
@@ -69,15 +69,34 @@ export class SongSheetParser {
6969
activeSection.chords.push(chord);
7070
});
7171
}
72-
if (!sections.length) {
72+
if (!sectionDefinitions.length) {
7373
return { ok: false, message: "Song Sheet must include at least one [section]." };
7474
}
75-
sections.forEach((section) => {
75+
const duplicateDefinition = sectionDefinitions.find((section, index) => sectionDefinitions.findIndex((entry) => entry.label.toLowerCase() === section.label.toLowerCase()) !== index);
76+
if (duplicateDefinition) {
77+
return { ok: false, message: `Duplicate musical section definition: ${duplicateDefinition.label}` };
78+
}
79+
sectionDefinitions.forEach((section) => {
7680
section.bars = section.chords.length;
7781
if (!section.chords.length) {
7882
warnings.push(`Section ${section.label} is empty.`);
7983
}
8084
});
85+
const sectionLookup = new Map(sectionDefinitions.map((section) => [section.label.toLowerCase(), section]));
86+
const sequence = metadata.sequence.length ? metadata.sequence : sectionDefinitions.map((section) => section.label);
87+
const missingSequenceLabel = sequence.find((label) => !sectionLookup.has(label.toLowerCase()));
88+
if (missingSequenceLabel) {
89+
return { ok: false, message: `Song Sheet Sequence references missing musical section: ${missingSequenceLabel}` };
90+
}
91+
const sections = sequence.map((label, index) => {
92+
const definition = sectionLookup.get(label.toLowerCase());
93+
return {
94+
...definition,
95+
chords: definition.chords.slice(),
96+
occurrence: index + 1,
97+
timeline: definition.timeline.map((entry) => ({ ...entry }))
98+
};
99+
});
81100
const bars = sections.reduce((total, section) => total + section.bars, 0);
82101
const chordCount = sections.reduce((total, section) => total + section.chords.length, 0);
83102
const estimatedDurationSeconds = Number(((bars * 4 * 60) / metadata.tempo).toFixed(3));
@@ -88,7 +107,9 @@ export class SongSheetParser {
88107
key: metadata.key || "not declared",
89108
ok: true,
90109
sections,
110+
sectionDefinitions,
91111
sectionSummary: sections.map((section) => `${section.label}: ${section.bars} bars, ${section.chords.length} chords${section.loop ? ", loop" : ""}`).join("; "),
112+
sequence,
92113
style: metadata.style || "not declared",
93114
tempo: metadata.tempo,
94115
timeline: sections.flatMap((section) => section.timeline),
@@ -114,6 +135,13 @@ export class SongSheetParser {
114135
}
115136
return { key, ok: true, value: tempo };
116137
}
138+
if (key === "sequence") {
139+
return {
140+
key,
141+
ok: true,
142+
value: value.split(/[\n,;]+/).map((entry) => entry.trim()).filter(Boolean)
143+
};
144+
}
117145
return { key, ok: true, value };
118146
}
119147
}

0 commit comments

Comments
 (0)