song.ini file and a notes.chart file:
The SongHash is
qG95qDOk-5zH930U4qHe0n7tKQ2Vau73A2QkYlue2ho. Without the end event or song_length, it’s the same: neither changes a SongHash.
This page specifies version 20260801. The legacy SongHash is version 20251010, which Version 20251010 describes.
Layout
A SongHash is calculated from these fields, written one after another, with nothing between them. Every number is little-endian:
Text is its length in bytes, as a signed 32-bit number, then its UTF-8 bytes, with no byte order mark and nothing after them.
The SongHash is the BLAKE3 hash of these bytes, which is 32 bytes, written in base64url without padding as a TrackHash is. It has 43 characters.
Metadata
A scanner takes the seven values from the song’s metadata:- From
song.ini:name,artist,album,genre,year,charterandiconin its[song]section, read as Syntax says. When the section has nocharterline,fretsis read in its place. - From
[Song], only when nosong.iniwith a[song]section loads:Name,Artist,Album,Genre,YearandCharterin the.chartfile, read as[Song]says,Yearwithout a leading,. The icon is unset. - From a
.sngfile’s metadata, with the keys ofsong.ini.
song.ini doesn’t hold is unset, even when [Song] holds it. Then, for each value, in this order:
- A value that’s missing, blank,
0or-1is unset. - The value’s style tags are removed, and spaces and other white space at its ends with them, until neither changes it.
- The icon is written in lowercase. When it’s the charter’s name in lowercase, after step 2, it’s unset.
- An unset value is
Unknown Name,Unknown Artist,Unknown Album,Unknown Genre,Unknown YearorUnknown Charter, and an unset icon is empty text.
charter = <b>GeO</b> and icon = geo, the charter is GeO and the icon is empty.
Style tags
Step 2 removes each style tag with one of these names, in any letter case:align, allcaps, alpha, b, br, color, cspace, font, font-weight, gradient, i, indent, line-height, line-indent, link, lowercase, margin, mark, mspace, nobr, noparse, page, pos, rotate, s, size, smallcaps, space, sprite, strikethrough, style, sub, sup, u, uppercase, voffset and width, and each tag that starts with #, such as the color tag <#AEFFFF>.
A tag is <, then the name, optionally after / and white space, then anything up to the next >. A name ends where a character that isn’t a letter, a digit or _ follows it. So <b>, </b>, < / b > and <color=#AEFFFF> are tags, and <bold> isn’t. Text in angle brackets that isn’t a tag stays: Song <Live> keeps its <Live>. A < without a > after it isn’t a tag.
The white space trimmed is that of Syntax: U+0009 to U+000D, U+0020, U+0085, U+00A0, U+1680, U+2000 to U+200A, U+2028, U+2029, U+202F, U+205F and U+3000. A byte order mark, U+FEFF, isn’t white space.
Edge case: a value of only style tags. genre = <b></b> isn’t blank in step 1, so it becomes empty text in step 2, not Unknown Genre.
Edge case: 0 and -1. A game shows charter = 0 as “0”, but the SongHash holds Unknown Charter. Step 1 compares the value as written, so <b>0</b> stays 0.
Edge case: year. A leading , is removed from [Song]’s Year, but not from song.ini’s year: year = , 2002 stays , 2002.
Edge case: a folder Clone Hero doesn’t list. Clone Hero doesn’t list a song without a name, or a .mid file without song.ini, so it calculates no SongHash for one. scan-chart calculates one all the same, with Unknown Name and the other unset values.
Edge case: a .sng file. Clone Hero matches the keys of a .sng file’s metadata exactly, reads no frets, and never falls back to [Song]. scan-chart doesn’t read .sng files.
Duration
The duration is the chart’s length in milliseconds, from its notes alone:- Take the end of each note, its tick plus its length, in every track of the song. The latest of them is the last tick, or 0 when the chart has no notes.
- Add one beat,
resolutionticks, to the last tick. - Change that tick to seconds through the tempo map, with each tempo rounded to the nearest thousandth of a BPM, as for the TrackHash. A chart without a tempo marker at tick 0 has 120 BPM there.
- Multiply by 1000, and round up to a whole millisecond.
end events, phrases, practice sections, song_length, the audio and delay don’t count, and the duration is the same at any song speed.
Step 3 uses 64-bit floating point, in this order:
Tracks
The SongHash holds each track that has a BTrack: each instrument at each difficulty with at least one note. A track without notes, or an instrument without a track at a difficulty, has no record.- The records are in ascending order of instrument, then of difficulty.
- Each holds the 32 bytes of the track’s TrackHash, not its text.
- Drums have one record for each difficulty, whatever the drum type. Clone Hero’s leaderboards keep drums and pro drums apart by the instrument a score was played on.
- ID 11 is kept for vocals, which have no TrackHash, and is never written. A new instrument would take 12 or more.
Version 20251010
Clone Hero 1.1 calculates a SongHash of version 20251010, with the same layout. Clone Hero 1.1’s code is the reference for it. It differs from version 20260801 in these ways:
So in version 20251010,
Song <Live> is Song , with a space at its end, >_< Album is _< Album, and name = 0 is 0.
Edge case: the historical duration. A version 20251010 SongHash holds the duration Clone Hero found when it scanned the song, which may have come from audio that’s no longer in the folder. To check one, use the duration that was stored with it. scan-chart calculates a version 20251010 SongHash only from metadata and a duration given to it.