Skip to main content

Charts & songs

Where a song lives​

assets/preload/data/<song>/<song>-<difficulty>.json # chart
assets/preload/data/<song>/events.json # standalone events (optional)
assets/songs/<song>/Inst.ogg # instrumental
assets/songs/<song>/Voices.ogg # vocals (if needsVoices)
assets/preload/weeks/<week>.json # week definition

Mods mirror this under mods/<mod>/data/<song>/, mods/<mod>/songs/<song>/ and mods/<mod>/weeks/. Lookup order is handled by Paths.

The chart format​

data.Song.SwagSong is the root object:

FieldTypeMeaning
songStringdisplay name
notesArray<SwagSection>the sections
eventsArray<Dynamic>chart events
bpmFloatstarting BPM
speedFloatscroll speed
needsVoicesBoolwhether Voices.ogg is loaded
player1 / player2 / gfVersionStringboyfriend / opponent / girlfriend character ids
stageStringstage id
arrowSkin / splashSkinStringnote and splash skins
gameOverChar, gameOverSound, gameOverLoop, gameOverEndString (optional)game-over overrides
disableNoteRGBBool (optional)turn off the note colour shader
songCredit, songCreditBarPath, songCreditIconStringthe built-in song credits feature
windowNameStringwindow title override while the song plays
specialAudioName, specialEventsNameStringalternate audio/event file names
event7, event7ValueStringlegacy JS Engine event slot

Sections​

data.Section.SwagSection:

FieldTypeMeaning
sectionNotesArray<Dynamic>[strumTime, noteData, sustainLength, noteType] tuples
sectionBeatsFloatlength in beats (default 4)
mustHitSectionBoolcamera and lane ownership
gfSectionBoolgirlfriend sings this section
altAnimBooluse -alt animations
changeBPM + bpmBool, FloatBPM change at this section
crossFadeBooltrigger the crossfade effect
typeOfSectionIntlegacy field

A note entry is an array, not an object — index 0 is the strum time in ms, 1 is the lane (0–3 player, 4–7 opponent in the raw chart), 2 the sustain length in ms, and 3 the notetype name when present.

Loading and migration​

data.Song.loadFromJson(...) reads the chart through Paths (BOM-stripped) and runs onLoadJson(), which upgrades older formats in place:

  • generates an empty events array and extracts inline event notes from sectionNotes when a chart predates the events system,
  • normalises missing fields to defaults,
  • handles Psych 1.0 charts (Song.psychV1Chart), including the different note and event layout.

Stage resolution falls back through StageData.vanillaSongStage(songName), a central mapping of base-game songs to their stage, so charts with no stage field still work.

Conductor.mapBPMChanges(song) is called once after load to build the BPM map used by every later time conversion.

Events​

Events live either in the chart's events array or in a separate events.json (Paths.songEvents(song, difficulty)). Each entry is [strumTime, [[name, value1, value2], ...]].

Built-in events handled by play.helpers.PlayStateEvents:

EventWhat it does
Hey!bf/gf cheer animation
Set GF Speedgirlfriend dance rate
Add Camera Zoom, Camera Bopping, Enable/Disable Camera Bopcamera bop control
Tween Camera Zoom, Camera Twist, Camera Follow Poscamera movement
Change Characterswap bf/dad/gf mid-song
Play Animation, Alt Idle Animationforce or alter animations
Change Scroll Speed, Change Note Multipliernote speed
Change Song Name, Fake Song LengthHUD text and time bar
Screen Shakecamera shake
Kill Henchmenweek 7 tankmen
Enable/Disable Bot Energy, Set Bot Energy Speedsthe A-Bot speaker
Credits Popup, Popupon-screen popups
Rainbow Eyesoreshader effect
Set Propertyset any field reflectively
Windows Notificationdesktop notification
VS Nonsense V2, Leather Enginecompatibility shims

Anything else is forwarded to scripts as onEvent(name, value1, value2), and a matching mods/<mod>/custom_events/<name>.lua / .py is auto-loaded — see Custom events & notetypes.

Scripts also receive eventEarlyTrigger(name) so they can ask for an event to fire early (useful for effects that need lead-in time).

Weeks​

backend.WeekData loads weeks/<week>.json: song list, difficulties, week characters, week name/image, hidden/locked flags and the asset folder. StoryMenuState renders them; FreeplayState flattens every week's songs into one list. Mods add weeks by dropping JSON into mods/<mod>/weeks/.

Difficulties​

Difficulty names come from the week data (default Easy, Normal, Hard) and map to the chart suffix: <song>-hard.json, with the default difficulty using no suffix. Highscores are stored per song and difficulty.

Editing charts​

editors.ChartingState is the in-game chart editor (split into editors/charting/* for grid, sections, waveform, selection, save/load and events). Section starts use the same BPM-change map as playback and each section's step length, keeping scrubbing, navigation, note placement and grid previews aligned through tempo changes. The editor rebuilds that map after BPM or section-length edits. It writes the same JSON format and keeps multiple backups — see Editors.