Skip to content

Add chart authoring + serialization API (writers + createEmptyChart) - #125

Open
elicwhite wants to merge 4 commits into
Geomitron:masterfrom
elicwhite:chart-writers
Open

Add chart authoring + serialization API (writers + createEmptyChart)#125
elicwhite wants to merge 4 commits into
Geomitron:masterfrom
elicwhite:chart-writers

Conversation

@elicwhite

Copy link
Copy Markdown
Contributor

Chart authoring + serialization API (the inverse of parseChartAndIni)

Adds the ability to build a chart from scratch or round-trip a parsed chart back to files:

  • createEmptyChart() — a minimal valid ParsedChart with no source bytes.
  • writeChartFile() / writeMidiFile() — serialize a ParsedChart to .chart text / Format-1 .mid bytes.
  • writeIniFile() — serialize metadata to a song.ini string.
  • writeChartFolder()ChartDocument → flat File[] (notes.{chart,mid} + song.ini + passthrough assets), the counterpart to parseChartAndIni's File[] input.
  • defaultIniChartModifiers — the parsing-relevant subset of defaultMetadata, used by createEmptyChart.

The writers are the inverse of the parser, so they re-derive normalization the parser applies (natural-HOPO force markers, sustain-chain length overrides, disco-flip/tom sentinels, ENHANCED_OPENS vs forceOpen, vocal phrase 105/106 split, etc.) so that parse → write → parse is faithful.

Validation

  • 120 new unit tests (writers + same-format round-trip), full suite green (435 tests).
  • parse → writeChartFolder → re-parse across 78,453 real charts: every instrument-track calculateTrackHash and vocal phrase/note/lyric count preserved, writers idempotent — 0 fidelity drift. The lone failure in 78K was a corrupt source BEAT track (non-monotonic ticks); buildUnrecognizedTrack now stable-sorts passthrough tracks so malformed input no longer fails the whole write.
  • A small validation harness is included at test/corpus/roundtrip.mjs.

Structure

midi-writer.ts is organized as a midi-writer/ module (index orchestrator + shared/events/drums/frets/vocals), with typed event builders to avoid repeated as MidiEvent casts and a writer-shared.ts for coda/bracket logic shared with the .chart writer.

Note for review

A few index.ts exports were added for downstream consumers that build/inspect ParsedChart data — the Normalized* vocal aliases and eventTypes / EventType / RawChartData / VocalTrackData. Happy to drop any you'd rather keep internal.

elicwhite added 4 commits June 3, 2026 10:17
The inverse of parseChartAndIni: build a chart from scratch or round-trip
a parsed chart back to files.

- createEmptyChart(): a minimal valid ParsedChart with no source bytes
- writeChartFile() / writeMidiFile(): serialize a ParsedChart to .chart
  text / Format-1 .mid bytes
- writeIniFile(): serialize metadata to a song.ini string
- writeChartFolder(): ChartDocument -> flat File[] (notes.{chart,mid} +
  song.ini + passthrough assets), the counterpart to parseChartAndIni's
  File[] input
- add defaultIniChartModifiers (the parsing-relevant subset of
  defaultMetadata), used by createEmptyChart
- export the new writer API from index.ts, along with
  eventTypes/EventType/RawChartData/VocalTrackData and Normalized* vocal
  aliases for consumers that build/inspect ParsedChart data
Port the writer unit + same-format round-trip tests onto the new
test/unit layout and src/ import convention. 120 tests covering
createEmptyChart, .chart/.mid/.ini writers, writeChartFolder, and
parse->write->reparse fidelity (incl. grouped unrecognized extras).
Addresses the code-quality review of the writer commits (no behavior
change — validated by 435 unit tests + an 8k-chart parse->write->parse
round-trip with zero fidelity drift).

- Decompose the 1458-line midi-writer.ts into a midi-writer/ module:
  index (orchestrator), shared (event model + builders + note helpers),
  events, drums, frets, vocals — each under 500 lines. The public
  `./midi-writer` import path is unchanged (folder index.ts).
- Add typed event builders (metaTextEvent / trackNameEvent / lyricsEvent
  / noteOnEvent / noteOffEvent) in shared.ts, removing ~27 repeated
  `... as MidiEvent` casts at call sites.
- Extract chart/writer-shared.ts (codaTicksFromFreestyle, wrap/unwrap
  event brackets) and use it from both the .chart and .mid writers,
  deleting the duplicated coda-derivation and bracket-normalization logic.
- Replace magic drumType integers (=== 1 / === 2) with the canonical
  drumTypes.fourLanePro / fiveLane constants and type the params DrumType.
- Harden buildUnrecognizedTrack: stable-sort a passthrough track's events
  by absolute tick before delta-encoding, so a malformed (non-monotonic)
  source track no longer crashes the whole write.
test/corpus/roundtrip.mjs walks chart folders and checks
parse -> writeChartFolder -> re-parse fidelity (per-track calculateTrackHash
+ vocal phrase/note/lyric counts) plus idempotence (P2 vs P3) to separate
real writer bugs from benign first-pass normalization. Loads the built
CJS dist so it runs under plain `node`.

  node test/corpus/roundtrip.mjs --input <dir> [--input <dir2>] [--limit N]
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.

1 participant