Restore lenient tick-0 trackName matching in MIDI parser - #22
Draft
elicwhite wants to merge 3 commits into
Draft
Conversation
## What changed
**Type structure — single source of truth for vocal data**
Removed top-level `notes[]` and `lyrics[]` from `NormalizedVocalPart`. All vocal content is now accessed through phrase grouping:
- `NormalizedVocalPart.notePhrases[].notes[]` — vocal notes (was: `NormalizedVocalPart.notes` + redundant copy under each phrase)
- `NormalizedVocalPart.notePhrases[].lyrics[]` — lyrics belonging to each phrase
- `NormalizedVocalPart.staticLyricPhrases[]` — note 106 display-only phrases (HARM2/3)
Previously the same notes/lyrics existed in two places (top-level array + per-phrase). Consumers had to know which to use, and a malformed file could cause them to disagree. The new structure has one location per piece of data.
**New fields on `NormalizedVocalPart`**
- `rangeShifts[]` — vocal range shift markers (MIDI note 0). Per-part because PART VOCALS and HARM1 often have distinct sets that must be preserved separately for round-trip.
- `lyricShifts[]` — vocal lyric shift markers (MIDI note 1). Same per-part rationale.
- `textEvents[]` — raw text events on the vocal track (stance markers, `Band_PlayFacialAnim`, etc.). YARG considers a `VocalsPart` non-empty iff it has phrases OR text events; without storing these, vocal tracks with only stance markers (no notes/lyrics/phrases) round-trip to empty and the track disappears.
**New field on `NormalizedVocalPhrase`**
- `player?: 1 | 2` — versus player tag (PART VOCALS only). Note 105 → player 1, note 106 → player 2. Required for versus-mode rendering. Previously, note 106 phrases were stored separately in `staticLyricPhrases` on PART VOCALS, which lost the distinction between "this is a player 2 scoring phrase" and "this is a HARM2/3 static lyric phrase".
**PART VOCALS phrase merging**
PART VOCALS now merges note 105 + note 106 phrase boundaries into a single `notePhrases` list (with player tags), matching YARG's `MoonSongLoader.Vocals.cs` behavior. Both notes create scoring phrases in YARG; they only differ in versus-player attribution. Previously we treated 105 and 106 as separate phrase lists, which caused notes near 106 boundaries to appear "orphaned" (~10K charts in the corpus had this artifact).
**Harmonies keep 105/106 separate**
HARM1/2/3 keep distinct 105 (scoring) and 106 (static lyric) phrase lists for lossless round-trip. The writer needs to emit each on its original MIDI note number, and CopyDown relies on HARM1's `vocalPhrases` (note 105 only) to know which phrases to clone to HARM2/3.
**HARM3 `CopyDownPhrases`**
HARM3 now clones `staticLyricPhrases` from HARM2 (in addition to scoring phrases from HARM1), matching YARG `CopyDownPhrases` exactly. Previously HARM3 had no static lyric phrases unless authored directly on the HARM3 track.
**Lyric text preservation**
`NormalizedLyricEvent.text` now stores the original unstripped text including markup symbols (`#`, `^`, `+`, `=`, `$`, `_`, `§`, etc.). Consumers should use the `flags` bitmask for semantic interpretation rather than parsing the text directly. Previously we stripped flag symbols and replaced `=` with `-`, which made round-trip writing incorrect (writers couldn't reconstruct the original text).
**Pitch preservation for nonPitched notes**
`NormalizedVocalNote.pitch` keeps the original MIDI pitch for nonPitched notes (lyric flags `#`/`^`/`*`). Previously we set pitch to `-1` for both percussion and nonPitched, losing the distinction. Consumers check the associated lyric's `nonPitched` flag for semantic meaning.
**Pre-collect lyrics per phrase**
The phrase-grouping algorithm now advances the lyric index to the phrase end regardless of whether the phrase has notes. Previously, pitch-slide-only phrases (no notes) would leave their lyrics stranded between phrases, causing divergence on round-trip.
**Pitch-slide skip refinement**
Pitch-slide notes are only skipped when their associated lyric survives the emptiness filter. Prevents losing slides whose lyrics would otherwise be dropped.
**`extractMidiVocalTextEvents`**
New helper extracts bracketed control-event text events on vocal tracks (stance markers, `Band_PlayFacialAnim`, etc.) into `VocalTrackData.textEvents`. Filters out lyrics (handled separately) and events scan-chart consumes internally (`ENHANCED_OPENS`, `[mix N drumsM]`, `[range_shift ...]`).
## Generic chart-parser helpers (used by both .chart and .mid paths)
These aren't strictly vocal-related but are bundled because the vocal work needed them and they're load-bearing elsewhere:
- `resolveChartTrackName(sectionName)` — matches YARG `ChartReader` logic for resolving a `.chart` section name like `ExpertDoubleDrums` into an `{instrument, difficulty}` pair. Handles non-standard names (e.g. "ExpertDoubleDrums" in `Megadeth - Bite the Hand`) which YARG accepts via prefix+suffix matching.
- `parseChartSectionEventText(text)` — matches YARG's `TextEvents.NormalizeTextEvent → TryParseSectionEvent` pipeline for section event detection. Handles typo cases like `sections Pre-Chorus` that YARG's `StartsWith("section")` accepts.
## Why
The previous vocal data structure (PR #85 / `vocal-normalization`) correctly produced phrase-grouped output but left several issues unresolved:
1. **Ambiguity**: notes/lyrics existed in two places (top-level + per-phrase). Future code changes risked them drifting apart.
2. **Round-trip incorrectness**: lyric symbols were stripped, pitches were lost, range/lyric shifts and text events weren't exposed, so the writer couldn't reconstruct the original chart.
3. **Versus mode unsupported**: player attribution was lost when 105/106 phrases were merged on PART VOCALS, blocking versus-mode rendering.
4. **HARM2/3 round-trip**: needed the distinction between 105 (scoring, copied from HARM1) and 106 (static lyric, kept on the harmony track) for the writer to re-emit them on the correct MIDI note.
This refactor doesn't change YARG-comparable output — both before and after produce 0 vocal phrase diffs across **78,452 charts**. The value is enabling round-trip writing, exposing data consumers need (player tags, shifts, text events), and removing the dual-storage ambiguity.
## Validation
- 268 unit tests pass
- 0 hash regressions vs upstream master baseline (15,524 charts)
- 0 vocal phrase diffs vs YARG dumps across the **full corpus (78,452 charts)** — phrase boundaries, note ticks/pitches, lyric counts/text/flags all match YARG exactly
## Stack
Stacked on #18 (`unrecognized-ini`).
Two new fields on RawChartData / ParsedChart for round-trip preservation:
- \`unrecognizedTracks: { trackName, events: MidiEvent[] }[]\` — Whole MIDI tracks whose name isn't in the recognized set (PART REAL_GUITAR/BASS/KEYS, PART ELITE_DRUMS, PART REAL_DRUMS_PS, PART KEYS GHL, VENUE, BEAT, custom tracks, etc.). Stored verbatim.
- \`trackData[].unrecognizedEvents: MidiEvent[]\` — Events on recognized tracks that the typed parser didn't consume: noteOn/noteOff outside recognized note ranges, non-Phase-Shift sysEx, and any other event types (marker, lyrics, instrumentName, channel events, etc.). Stored verbatim.
- \`unrecognizedSections: { name, lines: string[] }[]\` — .chart equivalent of unrecognizedTracks for sections that aren't Song/SyncTrack/Events or a recognized track section.
Replaces the explicit per-instrument parsing for Pro Guitar/Bass/Pro Keys/Elite Drums/GHL Keys/Phase Shift Real Drums/VENUE/BEAT/animations/hand maps that was in the old #7/#8/#9 PRs. The unrecognized fallback preserves all that data for byte-equivalent round-trip without per-track schema cost.
PART REAL_DRUMS_PS now always lands as unrecognized — no special-case duplicateDrumsTrack parseIssue, no merge with PART DRUMS, just preserved verbatim alongside it.
13 new unit tests covering: whole-track capture, per-track unconsumed events, REAL_DRUMS_PS coexisting with PART DRUMS, .chart unrecognizedSections.
Validated: 0 hash regressions vs upstream master baseline. 268 unit tests pass.
elicwhite
added a commit
that referenced
this pull request
Apr 19, 2026
The first pass of this PR over-pruned. scanChartFolder did three jobs:
chart parsing, chart hashing+validation, and folder asset scanning. The
intent of the split is to cleave only the *parsing* off the front so
editor consumers can stop early — the rest stays with scanChart.
Restore the pieces that were dropped:
- ScannedChart interface (full chart-folder result shape) is back in
interfaces.ts and exported from the public API.
- legacyGetChartHash (kept as a historical reference for the older
fixed-struct hash format) is back in chart-scanner.ts.
- The ini ↔ chart cross-validation (`checkMissingDifficulty` /
`checkExtraDifficulty`) is back, running inside scanChart.
- `playable`, `chart_offset`, and the typed-metadata flattening
(name/artist/album/…) are back, computed inside scanChart.
- scanAudio, scanImage, scanVideo are now called by scanChart so
consumers get audio/image/video folderIssues, the album art, the
hasVideoBackground flag, and the playability gate (no audio →
playable=false) without composing the asset scanners themselves.
scanChart's signature is now:
scanChart(parseResult: ParseChartAndIniResult, files, options?): ScannedChart
ScanChartOptions: { includeMd5?, includeBTrack? } — same knobs the old
ScanChartFolderConfig had.
scanAudio / scanImage / scanVideo / scanIni / calculateChartFolderMd5
remain individually exported for callers that want to inspect a single
aspect of a chart folder without paying for the rest.
Validation: 78,452/78,453 chart-edit roundtrip deeply equal (matches
baseline). Hash baseline: same 3 pre-existing trackname-discovery
diffs unchanged (independently fixed in #22). vitest 278/278.
elicwhite
force-pushed
the
restore-leading-trackname-tolerance
branch
from
April 19, 2026 05:06
a5e3bd0 to
4f135cc
Compare
elicwhite
added a commit
that referenced
this pull request
Apr 19, 2026
Replace the single `scanChartFolder(files, config?)` entry point with
two functions so editor consumers can stop after parsing and skip the
expensive validation/hashing/asset-scanning step.
parseChartAndIni(files): ParseChartAndIniResult
- file discovery + parseChartFile + scanIni
- returns ParsedChart (with chartBytes/format/iniChartModifiers
attached for downstream hashing) plus the ini scan results
- no hashing, no asset I/O
scanChart(parseResult, files, options?): ScannedChart
- what scanChartFolder used to do, minus the parsing
- calls scanParsedChart for chart hashing + notesData
- then runs the same difficulty / playable / metadata-flatten /
audio / image / video logic, returning the same ScannedChart
shape as before
- ScanChartOptions: { includeMd5?, includeBTrack? }
scanParsedChart(parsedChart, includeBTrack?)
- chart-only hashing + notesData. Same body as the previous
module-level chart-scanner `scanChart`, just taking a parsed
chart instead of (files, ini, btrack) so the parsing step
isn't redone here.
scanChartFolder is removed; ScanChartFolderConfig is removed (replaced
by ScanChartOptions). All other helpers stay where they were —
findChartIssues, getChartHash, legacyGetChartHash, etc. remain in
chart-scanner.ts unchanged.
Validation:
- vitest 278/278 pass
- 78,452-chart chart-edit roundtrip: 78,452/78,453 deeply equal
- 78,046-chart hash baseline: same 3 pre-existing trackname-discovery
diffs vs scan-chart@8.0.1 (independently fixed in #22)
elicwhite
force-pushed
the
split-parse-and-scan
branch
from
April 19, 2026 05:38
a8de48f to
f94f917
Compare
elicwhite
force-pushed
the
restore-leading-trackname-tolerance
branch
from
April 19, 2026 05:38
4f135cc to
8e4c490
Compare
elicwhite
added a commit
that referenced
this pull request
Apr 19, 2026
Replace the single `scanChartFolder(files, config?)` entry point with
two functions so editor consumers can stop after parsing and skip the
expensive validation/hashing/asset-scanning step.
parseChartAndIni(files): ParseChartAndIniResult
- file discovery + parseChartFile + scanIni
- returns ParsedChart (with chartBytes/format/iniChartModifiers
attached for downstream hashing) plus the ini scan results
- no hashing, no asset I/O
scanChart(parseResult, files, options?): ScannedChart
- what scanChartFolder used to do, minus the parsing
- calls scanParsedChart for chart hashing + notesData
- then runs the same difficulty / playable / metadata-flatten /
audio / image / video logic, returning the same ScannedChart
shape as before
- ScanChartOptions: { includeMd5?, includeBTrack? }
scanParsedChart(parsedChart, includeBTrack?)
- chart-only hashing + notesData. Same body as the previous
module-level chart-scanner `scanChart`, just taking a parsed
chart instead of (files, ini, btrack) so the parsing step
isn't redone here.
scanChartFolder is removed; ScanChartFolderConfig is removed (replaced
by ScanChartOptions). All other helpers stay where they were —
findChartIssues, getChartHash, legacyGetChartHash, etc. remain in
chart-scanner.ts unchanged.
Validation:
- vitest 278/278 pass
- 78,452-chart chart-edit roundtrip: 78,452/78,453 deeply equal
- 78,046-chart hash baseline: same 3 pre-existing trackname-discovery
diffs vs scan-chart@8.0.1 (independently fixed in #22)
elicwhite
force-pushed
the
split-parse-and-scan
branch
from
April 19, 2026 05:54
f94f917 to
7b03a88
Compare
elicwhite
force-pushed
the
restore-leading-trackname-tolerance
branch
from
April 19, 2026 05:55
8e4c490 to
713b33b
Compare
Revert the vocal-phrase-refactor change that made getTracks() take only
the FIRST tick-0 trackName event (matching YARG.Core's
MidiExtensions.GetTrackName). That change dropped tracks from real
charts that have a bogus or descriptive name first, followed by the
real instrument name — also at tick 0:
[ENHANCED_OPENS] → PART BASS (Culture Killer - Blindfolded Death)
[ENHANCED_OPENS] → PART GUITAR (Culture Killer - Blindfolded Death)
TEMPO TRACK → PART DRUMS (Periphery - Ji)
school food punishment - close, down, back to → PART DRUMS (school food punishment)
YARG drops these tracks; we don't. Walk every tick-0 trackName event
and accept the first one that matches a recognized instrument. Capture
the first tick-0 trackName for unrecognized-track round-trip purposes
regardless of whether anything matched (preserves the
unrecognizedMidiTracks behavior added later in the stack).
Add unit tests covering the happy path (recognized name after a bogus
leading name), the all-recognized case (first match wins), the
unrecognized-track fallback (first tick-0 name captured), and the
"trackName past tick 0 doesn't count" edge case.
Validation:
- vitest: 283/283 (5 new) pass
- 78,046-chart hash baseline: zero diffs vs scan-chart@8.0.1
(the 3 previously-diffing charts are now matched)
- 78,452-chart chart-edit roundtrip corpus: 78,452/78,453 deeply
equal (one known by-design failure unchanged)
elicwhite
force-pushed
the
restore-leading-trackname-tolerance
branch
from
April 19, 2026 05:57
713b33b to
28d24e6
Compare
elicwhite
added a commit
that referenced
this pull request
Apr 19, 2026
Replace the single `scanChartFolder(files, config?)` entry point with
two functions so editor consumers can stop after parsing and skip the
expensive validation/hashing/asset-scanning step.
parseChartAndIni(files): ParseChartAndIniResult
- file discovery + parseChartFile + scanIni
- returns ParsedChart (with chartBytes/format/iniChartModifiers
attached for downstream hashing) plus the ini scan results
- no hashing, no asset I/O
scanChart(parseResult, files, options?): ScannedChart
- what scanChartFolder used to do, minus the parsing
- calls scanParsedChart for chart hashing + notesData
- then runs the same difficulty / playable / metadata-flatten /
audio / image / video logic, returning the same ScannedChart
shape as before
- ScanChartOptions: { includeMd5?, includeBTrack? }
scanParsedChart(parsedChart, includeBTrack?)
- chart-only hashing + notesData. Same body as the previous
module-level chart-scanner `scanChart`, just taking a parsed
chart instead of (files, ini, btrack) so the parsing step
isn't redone here.
scanChartFolder is removed; ScanChartFolderConfig is removed (replaced
by ScanChartOptions). All other helpers stay where they were —
findChartIssues, getChartHash, legacyGetChartHash, etc. remain in
chart-scanner.ts unchanged.
Validation:
- vitest 278/278 pass
- 78,452-chart chart-edit roundtrip: 78,452/78,453 deeply equal
- 78,046-chart hash baseline: same 3 pre-existing trackname-discovery
diffs vs scan-chart@8.0.1 (independently fixed in #22)
elicwhite
added a commit
that referenced
this pull request
Apr 19, 2026
Replace the single `scanChartFolder(files, config?)` entry point with
two functions so editor consumers can stop after parsing and skip the
expensive validation/hashing/asset-scanning step.
parseChartAndIni(files): ParseChartAndIniResult
- file discovery + parseChartFile + scanIni
- returns ParsedChart (with chartBytes/format/iniChartModifiers
attached for downstream hashing) plus the ini scan results
- no hashing, no asset I/O
scanChart(parseResult, files, options?): ScannedChart
- what scanChartFolder used to do, minus the parsing
- calls scanParsedChart for chart hashing + notesData
- then runs the same difficulty / playable / metadata-flatten /
audio / image / video logic, returning the same ScannedChart
shape as before
- ScanChartOptions: { includeMd5?, includeBTrack? }
scanParsedChart(parsedChart, includeBTrack?)
- chart-only hashing + notesData. Same body as the previous
module-level chart-scanner `scanChart`, just taking a parsed
chart instead of (files, ini, btrack) so the parsing step
isn't redone here.
scanChartFolder is removed; ScanChartFolderConfig is removed (replaced
by ScanChartOptions). All other helpers stay where they were —
findChartIssues, getChartHash, legacyGetChartHash, etc. remain in
chart-scanner.ts unchanged.
Validation:
- vitest 278/278 pass
- 78,452-chart chart-edit roundtrip: 78,452/78,453 deeply equal
- 78,046-chart hash baseline: same 3 pre-existing trackname-discovery
diffs vs scan-chart@8.0.1 (independently fixed in #22)
elicwhite
added a commit
that referenced
this pull request
Apr 19, 2026
Replace the single `scanChartFolder(files, config?)` entry point with
two functions so editor consumers can stop after parsing and skip the
expensive validation/hashing/asset-scanning step.
parseChartAndIni(files): ParseChartAndIniResult
- file discovery + parseChartFile + scanIni
- returns ParsedChart (with chartBytes/format/iniChartModifiers
attached for downstream hashing) plus the ini scan results
- no hashing, no asset I/O
scanChart(parseResult, files, options?): ScannedChart
- what scanChartFolder used to do, minus the parsing
- calls scanParsedChart for chart hashing + notesData
- then runs the same difficulty / playable / metadata-flatten /
audio / image / video logic, returning the same ScannedChart
shape as before
- ScanChartOptions: { includeMd5?, includeBTrack? }
scanParsedChart(parsedChart, includeBTrack?)
- chart-only hashing + notesData. Same body as the previous
module-level chart-scanner `scanChart`, just taking a parsed
chart instead of (files, ini, btrack) so the parsing step
isn't redone here.
scanChartFolder is removed; ScanChartFolderConfig is removed (replaced
by ScanChartOptions). All other helpers stay where they were —
findChartIssues, getChartHash, legacyGetChartHash, etc. remain in
chart-scanner.ts unchanged.
Validation:
- vitest 278/278 pass
- 78,452-chart chart-edit roundtrip: 78,452/78,453 deeply equal
- 78,046-chart hash baseline: same 3 pre-existing trackname-discovery
diffs vs scan-chart@8.0.1 (independently fixed in #22)
elicwhite
added a commit
that referenced
this pull request
Apr 19, 2026
Replace the single `scanChartFolder(files, config?)` entry point with
two functions so editor consumers can stop after parsing and skip the
expensive validation/hashing/asset-scanning step.
parseChartAndIni(files): ParseChartAndIniResult
- file discovery + parseChartFile + scanIni
- returns ParsedChart (with chartBytes/format/iniChartModifiers
attached for downstream hashing) plus the ini scan results
- no hashing, no asset I/O
scanChart(parseResult, files, options?): ScannedChart
- what scanChartFolder used to do, minus the parsing
- calls scanParsedChart for chart hashing + notesData
- then runs the same difficulty / playable / metadata-flatten /
audio / image / video logic, returning the same ScannedChart
shape as before
- ScanChartOptions: { includeMd5?, includeBTrack? }
scanParsedChart(parsedChart, includeBTrack?)
- chart-only hashing + notesData. Same body as the previous
module-level chart-scanner `scanChart`, just taking a parsed
chart instead of (files, ini, btrack) so the parsing step
isn't redone here.
scanChartFolder is removed; ScanChartFolderConfig is removed (replaced
by ScanChartOptions). All other helpers stay where they were —
findChartIssues, getChartHash, legacyGetChartHash, etc. remain in
chart-scanner.ts unchanged.
Validation:
- vitest 278/278 pass
- 78,452-chart chart-edit roundtrip: 78,452/78,453 deeply equal
- 78,046-chart hash baseline: same 3 pre-existing trackname-discovery
diffs vs scan-chart@8.0.1 (independently fixed in #22)
elicwhite
added a commit
that referenced
this pull request
Apr 19, 2026
Replace the single `scanChartFolder(files, config?)` entry point with
two functions so editor consumers can stop after parsing and skip the
expensive validation/hashing/asset-scanning step.
parseChartAndIni(files): ParseChartAndIniResult
- file discovery + parseChartFile + scanIni
- returns ParsedChart (with chartBytes/format/iniChartModifiers
attached for downstream hashing) plus the ini scan results
- no hashing, no asset I/O
scanChart(parseResult, files, options?): ScannedChart
- what scanChartFolder used to do, minus the parsing
- calls scanParsedChart for chart hashing + notesData
- then runs the same difficulty / playable / metadata-flatten /
audio / image / video logic, returning the same ScannedChart
shape as before
- ScanChartOptions: { includeMd5?, includeBTrack? }
scanParsedChart(parsedChart, includeBTrack?)
- chart-only hashing + notesData. Same body as the previous
module-level chart-scanner `scanChart`, just taking a parsed
chart instead of (files, ini, btrack) so the parsing step
isn't redone here.
scanChartFolder is removed; ScanChartFolderConfig is removed (replaced
by ScanChartOptions). All other helpers stay where they were —
findChartIssues, getChartHash, legacyGetChartHash, etc. remain in
chart-scanner.ts unchanged.
Validation:
- vitest 278/278 pass
- 78,452-chart chart-edit roundtrip: 78,452/78,453 deeply equal
- 78,046-chart hash baseline: same 3 pre-existing trackname-discovery
diffs vs scan-chart@8.0.1 (independently fixed in #22)
elicwhite
added a commit
that referenced
this pull request
Apr 19, 2026
Replace the single `scanChartFolder(files, config?)` entry point with
two functions so editor consumers can stop after parsing and skip the
expensive validation/hashing/asset-scanning step.
parseChartAndIni(files): ParseChartAndIniResult
- file discovery + parseChartFile + scanIni
- returns ParsedChart (with chartBytes/format/iniChartModifiers
attached for downstream hashing) plus the ini scan results
- no hashing, no asset I/O
scanChart(parseResult, files, options?): ScannedChart
- what scanChartFolder used to do, minus the parsing
- calls scanParsedChart for chart hashing + notesData
- then runs the same difficulty / playable / metadata-flatten /
audio / image / video logic, returning the same ScannedChart
shape as before
- ScanChartOptions: { includeMd5?, includeBTrack? }
scanParsedChart(parsedChart, includeBTrack?)
- chart-only hashing + notesData. Same body as the previous
module-level chart-scanner `scanChart`, just taking a parsed
chart instead of (files, ini, btrack) so the parsing step
isn't redone here.
scanChartFolder is removed; ScanChartFolderConfig is removed (replaced
by ScanChartOptions). All other helpers stay where they were —
findChartIssues, getChartHash, legacyGetChartHash, etc. remain in
chart-scanner.ts unchanged.
Validation:
- vitest 278/278 pass
- 78,452-chart chart-edit roundtrip: 78,452/78,453 deeply equal
- 78,046-chart hash baseline: same 3 pre-existing trackname-discovery
diffs vs scan-chart@8.0.1 (independently fixed in #22)
elicwhite
added a commit
that referenced
this pull request
Apr 19, 2026
Replace the single `scanChartFolder(files, config?)` entry point with
two functions so editor consumers can stop after parsing and skip the
expensive validation/hashing/asset-scanning step.
parseChartAndIni(files): ParseChartAndIniResult
- file discovery + parseChartFile + scanIni
- returns ParsedChart (with chartBytes/format/iniChartModifiers
attached for downstream hashing) plus the ini scan results
- no hashing, no asset I/O
scanChart(parseResult, files, options?): ScannedChart
- what scanChartFolder used to do, minus the parsing
- calls scanParsedChart for chart hashing + notesData
- then runs the same difficulty / playable / metadata-flatten /
audio / image / video logic, returning the same ScannedChart
shape as before
- ScanChartOptions: { includeMd5?, includeBTrack? }
scanParsedChart(parsedChart, includeBTrack?)
- chart-only hashing + notesData. Same body as the previous
module-level chart-scanner `scanChart`, just taking a parsed
chart instead of (files, ini, btrack) so the parsing step
isn't redone here.
scanChartFolder is removed; ScanChartFolderConfig is removed (replaced
by ScanChartOptions). All other helpers stay where they were —
findChartIssues, getChartHash, legacyGetChartHash, etc. remain in
chart-scanner.ts unchanged.
Validation:
- vitest 278/278 pass
- 78,452-chart chart-edit roundtrip: 78,452/78,453 deeply equal
- 78,046-chart hash baseline: same 3 pre-existing trackname-discovery
diffs vs scan-chart@8.0.1 (independently fixed in #22)
elicwhite
added a commit
that referenced
this pull request
Apr 19, 2026
Replace the single `scanChartFolder(files, config?)` entry point with
two functions so editor consumers can stop after parsing and skip the
expensive validation/hashing/asset-scanning step.
parseChartAndIni(files): ParseChartAndIniResult
- file discovery + parseChartFile + scanIni
- returns ParsedChart (with chartBytes/format/iniChartModifiers
attached for downstream hashing) plus the ini scan results
- no hashing, no asset I/O
scanChart(parseResult, files, options?): ScannedChart
- what scanChartFolder used to do, minus the parsing
- calls scanParsedChart for chart hashing + notesData
- then runs the same difficulty / playable / metadata-flatten /
audio / image / video logic, returning the same ScannedChart
shape as before
- ScanChartOptions: { includeMd5?, includeBTrack? }
scanParsedChart(parsedChart, includeBTrack?)
- chart-only hashing + notesData. Same body as the previous
module-level chart-scanner `scanChart`, just taking a parsed
chart instead of (files, ini, btrack) so the parsing step
isn't redone here.
scanChartFolder is removed; ScanChartFolderConfig is removed (replaced
by ScanChartOptions). All other helpers stay where they were —
findChartIssues, getChartHash, legacyGetChartHash, etc. remain in
chart-scanner.ts unchanged.
Validation:
- vitest 278/278 pass
- 78,452-chart chart-edit roundtrip: 78,452/78,453 deeply equal
- 78,046-chart hash baseline: same 3 pre-existing trackname-discovery
diffs vs scan-chart@8.0.1 (independently fixed in #22)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Reverts the change to
getTracks()insrc/chart/midi-parser.tsfrom the vocal-phrase-refactor commit (6ec85082) that made scan-chart drop tracks whose first tick-0trackNameevent isn't a recognized instrument.The vocal-phrase-refactor change matched YARG.Core's
MidiExtensions.GetTrackNameexactly: take the FIRST tick-0SequenceTrackName, even if it's not a known instrument name, and discard the track if so. That cost us three real charts in our 78,046-chart corpus, which all have a bogus or descriptive trackName immediately followed by the real instrument name — also at tick 0:[ENHANCED_OPENS]PART BASS[ENHANCED_OPENS]PART GUITARTEMPO TRACKPART DRUMSPART DRUMSYARG drops these. We choose not to: walk every tick-0 trackName and accept the first one that matches a recognized instrument. The first tick-0 trackName is still captured for
unrecognizedMidiTracksround-trip purposes when nothing matches.Tests
New file
src/__tests__/tick-zero-trackname.test.ts(5 tests):PART DRUMSeven when a descriptive name comes first at tick 0 (school food punishment pattern)unrecognizedMidiTracks[0].trackNamewhen nothing matches a known instrumentValidation
scan-chart@8.0.1(the 3 previously-diffing charts are now matched — no more tracks dropped)Stack
Stacked on top of #21 (
split-parse-and-scan).