Skip to content

writeChartFile: emit instrument track sections#102

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

writeChartFile: emit instrument track sections#102
elicwhite wants to merge 4 commits into
Geomitron:masterfrom
elicwhite:chart-writer-tracks

Conversation

@elicwhite
Copy link
Copy Markdown
Contributor

Context

Completes `writeChartFile` with per-track section emission.

Features

  • Section naming: `[ExpertSingle]`, `[HardDrums]`, etc. from instrument + difficulty.
  • Drums: base notes (N 0..4 or 0..5 for 5-lane); cymbal markers (N 66/67/68) emitted only in fourLanePro; accent (N 34..38) and ghost (N 40..44) markers matching the emitted note's eventType; one N 109 flam per group; double-kick as N 32 only.
  • 5-lane round-trip quirks: `greenDrum + cymbal` emits as N 4 (orange) while plain `greenDrum` stays at N 5; blue drums coinciding with `greenDrum + cymbal` emit as N 5 to keep the parser's fiveLane detection from falsely downgrading to fourLane.
  • Fret / GHL: notes via the 5-fret and 6-fret maps; tap as N 6; `forceUnnatural` (N 5) emitted when the note's `hopo`/`strum` flag disagrees with the natural-HOPO state (this follows your `resolveFretModifiers` semantics).
  • Track-level sections: star power (S 2), activation lanes (S 64), flex lanes (S 65/66), versus phrases (S 0/1), solo sections as `E solo` / `E soloend` at `tick + length - 1`.
  • Per-track text events, including disco-flip `mix drums0[d|dnoflip]` emitted at state transitions from scanning the `disco` / `discoNoflip` flags on red/yellow drum notes.
  • Sort & dedup: events sort by tick → N/S/E priority, preserving insertion order within N-groups at the same tick (chord order is load-bearing for YARG's parent-note selection). Same-tick duplicate N/E events are deduplicated.

No `hasForcedNotes` backstop — per-note natural-HOPO comparison is sufficient to preserve `hasForcedNotes` state under the state-derived definition introduced in PR #98.

Depends on

PRs #97 + #98 + #99 + #100 + #101 — stacked above.

Builds a minimal valid ParsedChart from scratch: default 480 resolution,
120 BPM at tick 0, 4/4 time signature at tick 0, empty tracks/sections/
metadata/vocal parts/unrecognized events. chartBytes defaults to an empty
Uint8Array (no source bytes), format defaults to 'chart', iniChartModifiers
to the library defaults.

Options let callers override resolution, bpm, timeSignature, and format.

Useful for programmatic chart generation (e.g. downstream code that builds
charts up from scratch rather than parsing source bytes).
Serializes IniMetadata (Partial<typeof defaultMetadata> + extraIniFields)
back to song.ini text with:
- [song] header
- Known fields emitted in the canonical defaultMetadata order
- Undefined values skipped
- Booleans as True/False (Clone Hero convention)
- extraIniFields appended in insertion order
- CRLF line endings

Derives its field order from Object.keys(defaultMetadata) rather than
hardcoding a separate FIELD_ORDER list, so new fields added to
defaultMetadata automatically participate in writing.

Tests exercise writeIniFile only via round-trip through parseChartAndIni,
per reviewer feedback: build a metadata object, write it, re-parse, and
assert on parsedChart.metadata. No assertions about the serialized ini
text itself (CRLF, field order, True/False formatting, quoting) — those
are implementation details.
…emission

Port of the non-instrument-track half of the chart writer into scan-chart:
- [Song] emits the subset of metadata the chart body supports (Name,
  Artist, Charter, Album, Genre, Year, Resolution, Offset, PreviewStart,
  Difficulty). Other fields live exclusively in song.ini.
- [SyncTrack] emits tempos as `B millibeats` and time signatures as
  `TS numerator [denominator-exponent]`, sorted by tick with TS before B
  at the same tick.
- [Events] emits section markers (wrapped as [section name] to survive
  the parser's greedy trailing-\] regex), end events, unrecognized global
  events (with bracket-stripping when source is .mid so round-trip to
  .chart emits naked text), and vocal phrase_start/phrase_end/lyric
  events from the normalized vocalTracks.parts.vocals.
- Unrecognized chart sections are re-emitted verbatim.

Instrument tracks ([ExpertSingle] etc.) land in a follow-up PR.
Completes writeChartFile with per-track section emission:

- [ExpertSingle], [HardDrums], etc. named from instrument + difficulty.
- Drum tracks: base notes (N 0..4 or 0..5 for 5-lane), cymbal markers
  (N 66/67/68) only in fourLanePro, accent (N 34..38) and ghost (N 40..44)
  markers matching the emitted note's eventType, one N 109 flam per group,
  double kick as N 32 only.
- 5-lane round-trip: greenDrum+cymbal emits as N 4 (orange) while plain
  greenDrum stays at N 5; blueDrums coinciding with greenDrum+cymbal emit
  as N 5 to keep the parser's drumType detection on fiveLane.
- Fret/GHL tracks: notes via the 5-fret and 6-fret maps, tap as N 6,
  forceUnnatural (N 5) when the note's hopo/strum flag disagrees with
  the natural-HOPO state.
- Star power S 2, activation lanes S 64, flex lanes S 65/66, versus
  phrases S 0/1, solo sections as E 'solo'/'soloend' (tick + length - 1
  for soloend to round-trip).
- Per-track text events (including disco flip mix markers).
- Disco-flip state transitions per difficulty from red/yellow drum
  disco/discoNoflip flags → 'mix <diff> drums0[d|dnoflip]' text events.
- Coda events: if no 'coda' in unrecognizedEvents, synthesize from
  drumFreestyleSections where isCoda=true.
- hasForcedNotes backstop: if hasForcedNotes is set but no forceUnnatural
  emitted, append N 5 0 at a vacant tick in the first fret track to
  preserve the flag on round-trip.
@elicwhite elicwhite force-pushed the chart-writer-tracks branch from 143d91d to 914ea70 Compare April 21, 2026 23:18
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