Skip to content

Store unrecognized tracks and per-track unrecognized events - #91

Merged
Geomitron merged 5 commits into
Geomitron:masterfrom
elicwhite:unrecognized-tracks
Apr 19, 2026
Merged

Store unrecognized tracks and per-track unrecognized events#91
Geomitron merged 5 commits into
Geomitron:masterfrom
elicwhite:unrecognized-tracks

Conversation

@elicwhite

Copy link
Copy Markdown
Contributor

Stacked on #84 and #90. Merge those first.

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). Stored verbatim.
  • `trackData[].unrecognizedEvents: MidiEvent[]` — Events on recognized tracks that the typed parser didn't consume. 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 that was in the previously-closed PRs #81/#82/#83/#88 (Pro Guitar/Bass/Pro Keys/Elite Drums/GHL Keys/VENUE/BEAT/animations/hand maps). 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
elicwhite force-pushed the unrecognized-tracks branch 11 times, most recently from cdc056c to 1fc912e Compare April 18, 2026 17:38
Eliminate top-level notes[]/lyrics[] from NormalizedVocalPart. All vocal
content is accessed through phrase grouping.

- NormalizedVocalPart: removed notes[] and lyrics[]
- NormalizedVocalPhrase: added player?: 1 | 2 for versus mode
- NormalizedLyricEvent.text: stores original unstripped markup symbols
- NormalizedVocalNote.pitch: nonPitched keeps original MIDI pitch
- PART VOCALS merges note 105+106 phrases with player tags
- Harmonies keep separate 105/106 for lossless round-trip
- Pre-collect lyrics per phrase (lyricIdx advances to phraseEnd
  regardless of notes, preventing divergence on pitch slide round-trip)
- Pitch slide notes only skipped when lyric survives emptiness filter
- HARM3 CopyDown: clone staticLyricPhrases from HARM2
- Writer emits union of notes/lyrics from both phrase sets
- extractMidiVocalTextEvents for stance/facial anim events on vocal tracks

Generic chart-parser helpers (used elsewhere too):
- resolveChartTrackName: matches YARG's ChartReader logic for instrument+
  difficulty resolution from .chart section names ("ExpertDoubleDrums" etc.)
- parseChartSectionEventText: matches YARG's TextEvents.NormalizeTextEvent
  pipeline for section event detection
@elicwhite
elicwhite force-pushed the unrecognized-tracks branch from 1fc912e to 8146f70 Compare April 18, 2026 17:42
Verify that a lyric at tick T (where phrase 1 ends at T and phrase 2 starts at T)
ends up in the new phrase, including when the lyric event appears before the
phrase noteOn events in MIDI file order.
Two new fields on RawChartData / ParsedChart:

- 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
  for round-trip.

- 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 for round-trip.

- 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/etc. that was bundled in the old #7/#8/#9. 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.
@elicwhite
elicwhite force-pushed the unrecognized-tracks branch from 8146f70 to aa4c8d3 Compare April 19, 2026 00:05
Per PR review feedback: format-specific fields now spell out their format in
the name so consumers don't have to consult the docstring.

  unrecognizedTracks   → unrecognizedMidiTracks   (top-level RawChartData)
  unrecognizedSections → unrecognizedChartSections (top-level RawChartData)
  trackData[].unrecognizedEvents → unrecognizedMidiEvents
  VocalTrackData.unrecognizedEvents → unrecognizedMidiEvents

The top-level `unrecognizedEvents` (text-like events from the EVENTS track /
.chart [Events] section) keeps its name — it applies to both formats.
Per PR review feedback: removing the mutable boolean makes the structure
self-documenting — adding a new SysEx case won't require remembering to set
the flag, since the only way to reach `unrecognizedEvents.push` is through
the explicit `else` arm.

The Phase Shift header check is hoisted to a const, then the type lookup
short-circuits to null when the header doesn't match. The single
type !== null check then routes to either eventEnds (consumed) or
unrecognizedEvents (else).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants