Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/hitenpatel/songprint/llms.txt

Use this file to discover all available pages before exploring further.

A songprint signature is a compact binary representation of a constellation map of spectral peaks. It’s the same format Shazam clients generate and send to the discovery endpoint — a few KB for 12 seconds of audio. Understanding the layout is useful when debugging signatures, building custom decoders, or verifying byte-level compatibility with other Shazam-compatible tools.

Header structure

The signature opens with a 48-byte fixed header followed by an 8-byte content header. All multi-byte integers are little-endian.
BytesValuePurpose
0–30xcafe2580Magic number identifying a Shazam signature
4–7CRC32 (u32)CRC32 of every byte from offset 8 to the end of the file
8–11Content lengthNumber of bytes from offset 8 to the end
12–150x94119c00Second magic constant
16–270x00000000Reserved / zeroed
28–313 << 27Fixed flag field
32–390x00000000Reserved / zeroed
40–43Sample countTotal PCM samples processed, plus 0.24s × 16000 samples
44–47(15 << 19) + 0x40000Fixed flag field
48–510x40000000Content header magic
52–55Content section lengthLength of the band data that follows, including these 8 bytes
The content section (bytes 56 onward) contains one section per non-empty frequency band, laid out back-to-back with no additional framing.
Bytes 16–27 and 32–39 are always zero in signatures produced by songprint. The reference SongRec implementation documents these as reserved fields with no defined meaning.

Band sections

Peaks are grouped into four frequency bands. Each band that contains at least one peak contributes a section to the content area. Bands with no peaks are omitted entirely.
BandFrequency rangeSection marker
0250–520 Hz0x60030040
1520–1450 Hz0x60030041
21450–3500 Hz0x60030042
33500–5500 Hz0x60030043
Each band section has the following layout:
1

Band marker (4 bytes)

Little-endian uint32 identifying the band — 0x60030040 through 0x60030043.
2

Section length (4 bytes)

Little-endian uint32 giving the byte length of the raw peak data that follows (not including the marker, this length field, or the alignment padding).
3

Peak data (variable)

The encoded peaks for this band. See Peak encoding below.
4

Alignment padding (0–3 bytes)

Zero bytes padded to bring the total section size (marker + length + peak data) to a 4-byte boundary.

Peak encoding

Within each band section, peaks are sorted by FFT frame number and encoded sequentially. Each peak occupies 5 or 10 bytes depending on the delta to the previous peak. Delta encoding of frame numbers:
  • Compute the delta between this peak’s FFT frame number and the previous peak’s frame number in the same band (first peak uses 0 as the prior value).
  • If delta < 255: write a single byte containing the delta.
  • If delta >= 255: write an escape byte 0xFF, then write the absolute FFT frame number as a little-endian uint32 (5 bytes for the escape sequence), then reset prevFftPass to peak.fftPassNumber and fall through to the normal delta byte — which is now always 0x00 (1 byte).
After the frame delta, write two little-endian uint16 fields:
FieldSizeContent
Peak magnitude2 bytesLog-scaled magnitude: ln(mag) × 1477.3 + 6144.0, truncated to Int
Corrected frequency bin2 bytesSub-bin position at 1/64-bin precision from parabolic interpolation
// From SignatureGenerator.encodeSignature()
var prevFftPass = 0
for (peak in bandPeaks.sortedBy { it.fftPassNumber }) {
    val delta = peak.fftPassNumber - prevFftPass
    if (delta >= 255) {
        addByte(0xFF)
        addIntLE(peak.fftPassNumber)
        prevFftPass = peak.fftPassNumber
    }
    addByte((peak.fftPassNumber - prevFftPass) and 0xFF)
    addShortLE(peak.peakMagnitude)
    addShortLE(peak.correctedFrequencyBin)
    prevFftPass = peak.fftPassNumber
}

CRC32

The 4-byte CRC32 at bytes 4–7 of the header covers every byte from offset 8 to the end of the signature — the content-length field, both magic constants, the band sections, and all padding. It is computed before the value is written back into the header. songprint uses a pure-Kotlin CRC32 implementation that carries no dependency on java.util.zip, making the core compile on any Kotlin Multiplatform target:
// From Crc32.kt
private val CRC32_TABLE = IntArray(256) { i ->
    var crc = i
    repeat(8) {
        crc = if (crc and 1 != 0) (crc ushr 1) xor 0xEDB88320.toInt() else crc ushr 1
    }
    crc
}

internal fun crc32(data: ByteArray, offset: Int, length: Int): Long {
    var crc = 0xFFFFFFFF.toInt()
    for (i in offset until offset + length) {
        crc = (crc ushr 8) xor CRC32_TABLE[(crc xor data[i].toInt()) and 0xFF]
    }
    return (crc xor 0xFFFFFFFF.toInt()).toLong() and 0xFFFFFFFFL
}
The result is stored at bytes 4–7 as a little-endian uint32:
val crc = crc32(result, 8, result.size - 8)
putIntLE(4, crc.toInt())
The format was reverse-engineered by the SongRec project. songprint is an independent Kotlin implementation of that documented format, with no shared code or affiliation.

Build docs developers (and LLMs) love