Skip to content
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 23 additions & 12 deletions src/chart/track-hasher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,60 +49,71 @@ export function calculateTrackHash(parsedChart: ParsedChart, instrument: Instrum
const uint8Array = new Uint8Array(buffer)
const view = new DataView(buffer, 0)

// For a chart, every `tick` and `length` is a non-negative 32-bit integer —
// max per-tick values are well below 2^31 for any real song. So we can write
// each 8-byte little-endian int64 slot as [low 32 bits, high 32 bits = 0],
// skipping the `BigInt(x)` allocation that `setBigInt64` forces. A full
// hash on a dense track fires tens of thousands of these — the BigInt
// allocations dominate GC in the hasher path.
function writeInt64LE(offset: number, v: number): void {
view.setUint32(offset, v >>> 0, true)
view.setUint32(offset + 4, 0, true)
}

view.setUint32(0, 0x43484e46, false) // Big endian for format header, little endian for everything else
view.setUint32(4, 20240320, true)
view.setUint32(8, parsedChart.resolution, true)
let i = 12
view.setUint32(i, tempoData.length, true)
i += 4
for (const tempo of tempoData) {
view.setBigInt64(i, BigInt(tempo.tick), true)
writeInt64LE(i, tempo.tick)
view.setFloat64(i + 8, tempo.beatsPerMinute, true)
i += 16
}
view.setUint32(i, timeSignatureData.length, true)
i += 4
for (const timeSignature of timeSignatureData) {
view.setBigInt64(i, BigInt(timeSignature.tick), true)
writeInt64LE(i, timeSignature.tick)
view.setUint32(i + 8, timeSignature.numerator, true)
view.setUint32(i + 12, timeSignature.denominator, true)
i += 16
}
view.setUint32(i, starPowerData.length, true)
i += 4
for (const starPower of starPowerData) {
view.setBigInt64(i, BigInt(starPower.tick), true)
view.setBigInt64(i + 8, BigInt(starPower.length), true)
writeInt64LE(i, starPower.tick)
writeInt64LE(i + 8, starPower.length)
i += 16
}
view.setUint32(i, soloSectionData.length, true)
i += 4
for (const soloSection of soloSectionData) {
view.setBigInt64(i, BigInt(soloSection.tick), true)
view.setBigInt64(i + 8, BigInt(soloSection.length), true)
writeInt64LE(i, soloSection.tick)
writeInt64LE(i + 8, soloSection.length)
i += 16
}
view.setUint32(i, flexLanesData.length, true)
i += 4
for (const flexLane of flexLanesData) {
view.setBigInt64(i, BigInt(flexLane.tick), true)
view.setBigInt64(i + 8, BigInt(flexLane.length), true)
writeInt64LE(i, flexLane.tick)
writeInt64LE(i + 8, flexLane.length)
view.setUint8(i + 16, flexLane.isDouble ? 1 : 0)
i += 17
}
view.setInt32(i, drumFreestyleSectionData.length, true)
i += 4
for (const drumFreestyleSection of drumFreestyleSectionData) {
view.setBigInt64(i, BigInt(drumFreestyleSection.tick), true)
view.setBigInt64(i + 8, BigInt(drumFreestyleSection.length), true)
writeInt64LE(i, drumFreestyleSection.tick)
writeInt64LE(i + 8, drumFreestyleSection.length)
view.setUint8(i + 16, drumFreestyleSection.isCoda ? 1 : 0)
i += 17
}
view.setInt32(i, notesData.length, true)
i += 4
for (const note of notesData) {
view.setBigInt64(i, BigInt(note.tick), true)
view.setBigInt64(i + 8, BigInt(note.length), true)
writeInt64LE(i, note.tick)
writeInt64LE(i + 8, note.length)
view.setUint32(i + 16, note.type, true)
view.setUint32(i + 20, note.flags, true)
i += 24
Expand Down