3.4.8 • Published 4 months ago

scan-chart v3.4.8

Weekly downloads
-
License
MIT
Repository
github
Last release
4 months ago

This package scans charts for rhythm games like Clone Hero and produces useful metadata about them.

API

/**
 * Scans the charts in the `chartsFolder` directory and returns an event emitter that emits the results.
 */
function scanCharts(chartsFolder: string, config?: ScanChartsConfig): ScanChartsResult

interface ScanChartsConfig {
	/** Ignore scanning all charts except ones found in .sng files. Defaults to `false`. */
	onlyScanSng?: boolean
}

interface ScanChartsResult {
	/**
	 * Registers `listener` to be called when a chart folder has been found.
	 * The name of the chart folder is passed to `listener`. No `chart` events are emitted before this.
	 */
	on(event: 'folder', listener: (folderName: string) => void): void
	/**
	 * Registers `listener` to be called when a chart has been scanned.
	 * The `ScannedChart` is passed to `listener`, along with the index of this chart and the total number of charts to be scanned.
	 * No `folder` events are emitted after this.
	 */
	on(event: 'chart', listener: (chart: ScannedChart, index: number, count: number) => void): void

	/**
	 * Registers `listener` to be called if the filesystem failed to read a file. If this is called, the "end" event won't happen.
	 */
	on(event: 'error', listener: (err: Error) => void): void

	/**
	 * Registers `listener` to be called when all charts in `chartsFolder` have been scanned.
	 * The `ScannedChart[]` is passed to `listener`.
	 * If this is called, the "error" event won't happen.
	 */
	on(event: 'end', listener: (charts: ScannedChart[]) => void): void
}

interface ScannedChart {
	/** Data scanned from the chart. */
	chart: Chart
	/**
	 * The relative path from `chartsFolder` to the folder containing the chart file(s).
	 *
	 * Doesn't contain `chartsFolder`, the .sng filename (if any), or leading/trailing slashes.
	 */
	chartPath: string
	/** The name of the .sng file. `null` if the chart is not in the .sng format. */
	chartFileName: string | null
}

interface Chart {
	/** An MD5 hash of the names and binary contents of every file in the chart. */
	md5: string
	/** An MD5 hash of just the chart file. If this changes, the score is reset. */
	chartMd5: string
	/** If the chart is able to be played in-game. This is `false` if `notesData` is `undefined`. */
	playable: boolean

	/** The song name. */
	name?: string
	/** The song artist. */
	artist?: string
	/** The song album. */
	album?: string
	/** The song genre. */
	genre?: string
	/** The song year. */
	year?: string
	/** The chart's charter(s). */
	charter?: string
	/** The length of the chart's audio, in milliseconds. If there are stems, this is the length of the longest stem. */
	song_length?: number
	/** The difficulty rating of the chart as a whole. Usually an integer between 0 and 6 (inclusive) */
	diff_band?: number
	/** The difficulty rating of the lead guitar chart. Usually an integer between 0 and 6 (inclusive) */
	diff_guitar?: number
	/** The difficulty rating of the co-op guitar chart. Usually an integer between 0 and 6 (inclusive) */
	diff_guitar_coop?: number
	/** The difficulty rating of the rhythm guitar chart. Usually an integer between 0 and 6 (inclusive) */
	diff_rhythm?: number
	/** The difficulty rating of the bass guitar chart. Usually an integer between 0 and 6 (inclusive) */
	diff_bass?: number
	/** The difficulty rating of the drums chart. Usually an integer between 0 and 6 (inclusive) */
	diff_drums?: number
	/** The difficulty rating of the Phase Shift "real drums" chart. Usually an integer between 0 and 6 (inclusive) */
	diff_drums_real?: number
	/** The difficulty rating of the keys chart. Usually an integer between 0 and 6 (inclusive) */
	diff_keys?: number
	/** The difficulty rating of the GHL (6-fret) lead guitar chart. Usually an integer between 0 and 6 (inclusive) */
	diff_guitarghl?: number
	/** The difficulty rating of the GHL (6-fret) co-op guitar chart. Usually an integer between 0 and 6 (inclusive) */
	diff_guitar_coop_ghl?: number
	/** The difficulty rating of the GHL (6-fret) rhythm guitar chart. Usually an integer between 0 and 6 (inclusive) */
	diff_rhythm_ghl?: number
	/** The difficulty rating of the GHL (6-fret) bass guitar chart. Usually an integer between 0 and 6 (inclusive) */
	diff_bassghl?: number
	/** The difficulty rating of the vocals chart. Usually an integer between 0 and 6 (inclusive) */
	diff_vocals?: number
	/** The number of milliseconds into the song where the chart's audio preview should start playing. */
	preview_start_time?: number
	/** The name of the icon to be displayed on the chart. Usually represents a charter or setlist. */
	icon?: string
	/** A text phrase that will be displayed before the chart begins. */
	loading_phrase?: string
	/** The ordinal position of the song on the album. This is `undefined` if it's not on an album. */
	album_track?: number
	/** The ordinal position of the chart in its setlist. This is `undefined` if it's not on a setlist. */
	playlist_track?: number
	/** `true` if the chart is a modchart. This only affects how the chart is filtered and displayed, and doesn't impact gameplay. */
	modchart?: boolean
	/** The amount of time the game should delay the start of the track in milliseconds. */
	delay?: number
	/** The amount of time the game should delay the start of the track in seconds. */
	chart_offset?: number
	/** Overrides the default HOPO threshold with a specified value in ticks. Only applies to .mid charts. */
	hopo_frequency?: number
	/** Sets the HOPO threshold to be a 1/8th step. Only applies to .mid charts. */
	eighthnote_hopo?: boolean
	/** Overrides the .mid note number for Star Power on 5-Fret Guitar. Valid values are 103 and 116. Only applies to .mid charts. */
	multiplier_note?: number
	/**
	 * The amount of time that should be skipped from the beginning of the video background in milliseconds.
	 * A negative value will delay the start of the video by that many milliseconds.
	 */
	video_start_time?: number
	/** `true` if the "drums" track should be interpreted as 5-lane drums. */
	five_lane_drums?: boolean
	/** `true` if the "drums" track should be interpreted as 4-lane pro drums. */
	pro_drums?: boolean
	/** `true` if the chart's end events should be used to end the chart early. Only applies to .mid charts. */
	end_events?: boolean

	/** The chart's album art. */
	albumArt?: AlbumArt
	/** Data describing properties of the .chart or .mid file. `undefined` if the .chart or .mid file couldn't be parsed. */
	notesData?: NotesData
	/** Issues with the chart files. */
	folderIssues: { folderIssue: FolderIssueType; description: string }[]
	/** Issues with the chart's metadata. */
	metadataIssues: MetadataIssueType[]
	/** `true` if the chart has a video background. */
	hasVideoBackground: boolean
}

interface AlbumArt {
	/** The binary buffer of the album art image, in the .jpg format (quality 75%), resized to 512x512. */
	data: Uint8Array
	/** The MD5 hash of `data`. */
	md5: string
}

interface NotesData {
	/** The list of instruments that contain more than zero track events. */
	instruments: Instrument[]
	/** The type of drums that are charted. `null` if drums are not charted. */
	drumType: DrumType | null
	/** If a solo section event occurs in any track. */
	hasSoloSections: boolean
	/** If the chart contains any lyric events. */
	hasLyrics: boolean
	/** If the chart contains a "vocals" track. */
	hasVocals: boolean
	/** If a forced note event occurs in any track. */
	hasForcedNotes: boolean
	/** If a tap note event occurs in any track. */
	hasTapNotes: boolean
	/** If an open note event occurs in any track. */
	hasOpenNotes: boolean
	/** If a 2xKick event occurs in any "drums" track. */
	has2xKick: boolean
	/** If a single or double roll lane event occurs in any "drums" track. */
	hasRollLanes: boolean
	/** Issues with individual notes in the chart. */
	noteIssues: {
		instrument: Instrument
		difficulty: Difficulty
		noteIssues: NoteIssue[]
	}[]
	/** Issues with specific tracks in the chart. */
	trackIssues: {
		instrument: Instrument
		difficulty: Difficulty
		trackIssues: TrackIssueType[]
	}[]
	/** Issues with the overall chart. */
	chartIssues: ChartIssueType[]
	/** The number of individual notes in the chart. Does not include star power, solo markers, or ativation lanes. */
	noteCounts: {
		instrument: Instrument
		difficulty: Difficulty
		count: number
	}[]
	/** The one-second region in each track where the notes-per-second is highest. */
	maxNps: {
		instrument: Instrument
		difficulty: Difficulty
		/** Time of the end of the high NPS region in milliseconds. Rounded to 3 decimal places. */
		time: number
		/** The notes-per-second in this region. Equivalent to `notes.length`. */
		nps: number
		/** The notes in the high NPS region, sorted by `TrackEvent.time` in ascending order. */
		notes: TrackEvent[]
	}[]
	/** MD5 hashes of each track. This only accounts for events in `EventType`. */
	hashes: {
		instrument: Instrument
		difficulty: Difficulty
		hash: string
	}[]
	/** MD5 hash of the chart's tempo map, including BPM markers and time signature markers. */
	tempoMapHash: string
	/** The number of BPM markers in the chart. */
	tempoMarkerCount: number
	/**
	 * The amount of time between the start of the chart and the last note in milliseconds. Rounded to 3 decimal places.
	 * If there are multiple tracks, the last note is the latest last note across all the tracks.
	 */
	length: number
	/**
	 * The amount of time between the chart's first and last notes in milliseconds. Rounded to 3 decimal places.
	 * If there are multiple tracks, the first note is the earliest first note across all the tracks,
	 * and the last note is the latest last note across all the tracks.
	 */
	effectiveLength: number
}

type Instrument =
	'guitar' |        // Lead Guitar
	'guitarcoop' |    // Co-op Guitar
	'rhythm' |        // Rhythm Guitar
	'bass' |          // Bass Guitar
	'drums' |         // Drums
	'keys' |          // Keys
	'guitarghl' |     // GHL (6-fret) Lead Guitar
	'guitarcoopghl' | // GHL (6-fret) Co-op Guitar
	'rhythmghl' |     // GHL (6-fret) Rhythm Guitar
	'bassghl'         // GHL (6-fret) Bass Guitar

type DrumType =
	'fourLane' |
	'fourLanePro' |
	'fiveLane'

type Difficulty =
	'expert' |
	'hard' |
	'medium' |
	'easy'

interface NoteIssue {
	issueType: NoteIssueType
	/** Time of the issue in milliseconds. Rounded to 3 decimal places. */
	time: number
}

type NoteIssueType =
	'fiveNoteChord' |           // This is a five-note chord
	'difficultyForbiddenNote' | // This is a note that isn't allowed on the track's difficulty
	'threeNoteDrumChord' |      // This is a three-note chord on the "drums" instrument
	'brokenNote' |              // This note is so close to the previous note that this was likely a charting mistake
	'badSustainGap' |           // This note is not far enough ahead of the previous sustain
	'babySustain'               // The sustain on this note is too short

type TrackIssueType =
	'noStarPower' |           // This track has no star power
	'noDrumActivationLanes' | // This drums track has no activation lanes
	'has4And5LaneFeatures' |  // This drums track mixes both 4-lane and 5-lane features, which is not supported
	'smallLeadingSilence' |   // This track has a note that is less than 2000ms after the start of the track
	'noNotesOnNonemptyTrack'  // This track has star power, solo markers, or drum lanes, but no notes

type ChartIssueType =
	'noResolution' |             // This chart has no resolution
	'noSyncTrackSection' |       // This chart has no tempo map information
	'noNotes' |                  // This chart has no notes
	'noExpert' |                 // One of this chart's instruments has Easy, Medium, or Hard charted but not Expert
	'isDefaultBPM' |             // This chart has only one 120 BPM marker and only one 4/4 time signature
	'misalignedTimeSignatures' | // This chart has a time signature marker that doesn't appear at the start of a measure
	'noSections'                 // This chart has no sections

interface TrackEvent {
	/** Time of the event in milliseconds. Rounded to 3 decimal places. */
	time: number
	/** Length of the event in milliseconds. Rounded to 3 decimal places. Some events have a length of zero. */
	length: number
	type: EventType
}

type EventType =
	// 5 fret
	'starPower' |
	'tap' |
	'force' |
	'orange' |
	'blue' |
	'yellow' |
	'red' |
	'green' |
	'open' |
	'soloMarker' |

	// 6 fret
	'black3' |
	'black2' |
	'black1' |
	'white3' |
	'white2' |
	'white1' |

	// Drums
	'activationLane' |
	'kick' |
	'kick2x' |
	'rollLaneSingle' |
	'rollLaneDouble' |
	'yellowTomOrCymbalMarker' |
	'blueTomOrCymbalMarker' |
	'greenTomOrCymbalMarker'

type FolderIssueType =
	'noMetadata' |       // This chart doesn't have "song.ini"
	'invalidIni' |       // .ini file is not named "song.ini"
	'invalidMetadata' |  // "song.ini" doesn't have a "[Song]" section
	'badIniLine' |       // This line in "song.ini" couldn't be parsed
	'multipleIniFiles' | // This chart has multiple .ini files
	'noAlbumArt' |       // This chart doesn't have album art
	'albumArtSize' |     // This chart's album art is not 500x500 or 512x512
	'badAlbumArt' |      // This chart's album art couldn't be parsed
	'multipleAlbumArt' | // This chart has multiple album art files
	'noAudio' |          // This chart doesn't have an audio file
	'invalidAudio' |     // Audio file is not a valid audio stem name
	'badAudio' |         // This chart's audio couldn't be parsed
	'multipleAudio' |    // This chart has multiple audio files of the same stem
	'noChart' |          // This chart doesn't have "notes.chart"/"notes.mid"
	'invalidChart' |     // .chart/.mid file is not named "notes.chart"/"notes.mid"
	'badChart' |         // This chart's .chart/.mid file couldn't be parsed
	'multipleChart' |    // This chart has multiple .chart/.mid files
	'badVideo' |         // This chart has a video background that will not work on Linux
	'multipleVideo'      // This chart has multiple video background files

type MetadataIssueType =
	'noName' |                // Metadata is missing the "name" property
	'noArtist' |              // Metadata is missing the "artist" property
	'noAlbum' |               // Metadata is missing the "album" property
	'noGenre' |               // Metadata is missing the "genre" property
	'noYear' |                // Metadata is missing the "year" property
	'noCharter' |             // Metadata is missing the "charter" property
	'missingInstrumentDiff' | // Metadata is missing a "diff_" property
	'extraInstrumentDiff' |   // Metadata contains a "diff_" property for an uncharted instrument
	'nonzeroDelay' |          // Metadata contains a "delay" property that is not zero
	'drumsSetTo4And5Lane' |   // Metadata indicates the drum chart is both 4-lane and 5-lane
	'nonzeroOffset'           // Chart file contains an "Offset" property that is not zero
3.4.8

4 months ago

3.4.7

4 months ago

3.4.6

4 months ago

3.4.5

4 months ago

3.4.4

4 months ago

3.4.3

5 months ago

3.4.2

5 months ago

1.12.2

6 months ago

1.11.3

6 months ago

1.12.1

6 months ago

1.11.2

7 months ago

1.12.0

6 months ago

1.11.1

7 months ago

1.9.0

7 months ago

1.8.1

7 months ago

2.0.0

6 months ago

3.4.0

6 months ago

3.3.1

6 months ago

3.1.3

6 months ago

3.3.0

6 months ago

3.1.2

6 months ago

3.2.0

6 months ago

3.1.1

6 months ago

3.1.0

6 months ago

3.3.5

6 months ago

3.3.4

6 months ago

3.3.3

6 months ago

3.1.5

6 months ago

3.4.1

6 months ago

3.3.2

6 months ago

3.1.4

6 months ago

3.0.0

6 months ago

1.11.0

7 months ago

1.10.0

7 months ago

1.8.0

7 months ago

1.7.1

8 months ago

1.7.0

8 months ago

1.6.1

8 months ago

1.6.0

8 months ago

1.5.0

8 months ago

1.4.0

8 months ago

1.3.0

8 months ago

1.2.0

8 months ago

1.1.4

9 months ago

1.1.3

9 months ago

1.1.2

11 months ago

1.1.1

12 months ago

1.1.0

12 months ago