songprint’s fingerprinting pipeline converts raw PCM audio into a sparse set of spectral peaks that remain robust to noise, EQ changes, and lossy codecs. This is why the same algorithm identifies songs on live radio streams. The five-step pipeline — spectrogram, peak spreading, peak recognition, frequency banding, and binary encoding — runs in pure Kotlin with no native code or JVM-specific dependencies.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.
Step 1: Spectrogram
The pipeline starts by building a magnitude spectrogram from the raw audio.Input: 16kHz mono 16-bit PCM (With a hop size of 128 samples at 16kHz, each frame represents approximately 8ms of audio.
ShortArray). Use Pcm.resampleToMono16k to convert from other formats before calling SignatureGenerator.Audio is fed into a circular buffer (sampleRing) of FFT_SIZE samples. Every HOP_SIZE samples, a new FFT frame is computed:- The 2048-sample window is multiplied element-wise by a Hanning window to reduce spectral leakage.
- A 2048-point in-place DFT (pure-Kotlin Cooley-Tukey) transforms the windowed samples into the frequency domain.
- Per-bin magnitude is computed as
(re² + im²) / 2¹⁷, floored at1e-10to avoidln(0)later in the pipeline. - The resulting 1025-bin magnitude array (
FFT_OUTPUT_SIZE = FFT_SIZE / 2 + 1) is written into a ring buffer of 256 frames (fftOutputs).
Step 2: Peak spreading
Raw magnitudes alone are noisy peak detectors. Before recognition, the spectrogram is smeared into a smooth “local maximum” surface.For each new FFT frame:The result (
- Frequency spreading — starting from the freshly computed magnitude array, each bin
iis replaced withmax(spread[i], spread[i+1], spread[i+2]). This forward-spreads energy across the next two bins. - Time spreading — the spread values are propagated backwards into three earlier frames in the ring buffer at offsets -1, -3, and -6 relative to the current write position. Each earlier frame’s bin value is raised to the running maximum, so a strong peak in the current frame influences the surface of past frames.
spreadOutputs) is a rolling surface where every bin reflects the recent local maximum across its frequency neighbourhood and across a short time window. Peak recognition then checks candidates against this surface.Step 3: Peak recognition
Peak recognition runs once at least 46 spread frames have accumulated. It evaluates every bin in the frame from 46 frames ago (Candidates that pass all four gates then have their frequency bin refined to 1/64-bin precision using parabolic interpolation of the log-magnitudes of the three adjacent bins:The corrected bin is stored alongside the log-scaled magnitude and the FFT frame number as a
fftMinus46) against the spread surface built from surrounding frames.A bin at position b in frame f-46 is accepted as a peak only if all of the following conditions hold:Frequency dominance (left neighbour)
fftMinus46[b] > spreadMinus49[b - 1] — the candidate must exceed the spread surface at the adjacent lower bin, 3 frames earlier.Frequency neighbourhood
The candidate must exceed the spread surface at offsets -10, -7, -4, -3, +1, +2, +5, +8 bins (evaluated against
spreadMinus49). Any neighbour that is stronger eliminates the candidate.Peak(fftPassNumber, peakMagnitude, correctedFrequencyBin).Step 4: Frequency banding
Each accepted peak’s corrected bin is converted to Hz and routed into one of four bands. Peaks outside all bands are discarded.
The main processing loop stops consuming audio once both of the following are true:
| Band | Frequency range |
|---|---|
| 0 | 250–520 Hz |
| 1 | 520–1450 Hz |
| 2 | 1450–3500 Hz |
| 3 | 3500–5500 Hz |
- At least
MAX_TIME_SECONDS(3.1 s) of audio has been processed - At least
MAX_PEAKS(255) total peaks have been collected across all bands
Step 5: Binary encoding
The four per-band peak lists are serialised into the Shazam-compatible binary format. See Signature Format for a complete description of the header layout, band section structure, delta encoding scheme, and embedded CRC32.
Key constants
These constants fromSignatureGenerator.companion govern the entire pipeline. They are not configurable — changing them produces signatures incompatible with Shazam’s discovery endpoint.
| Constant | Value | Meaning |
|---|---|---|
SAMPLE_RATE | 16000 | Expected input sample rate (Hz) |
FFT_SIZE | 2048 | FFT window size (samples) |
HOP_SIZE | 128 | Samples advanced per frame (~8ms) |
FFT_OUTPUT_SIZE | 1025 | Magnitude bins per frame (FFT_SIZE / 2 + 1) |
MAX_TIME_SECONDS | 3.1 | Soft upper bound on audio processed (seconds) |
MAX_PEAKS | 255 | Maximum total peaks across all bands |
RING_SIZE | 256 | Depth of the FFT and spread output ring buffers (frames) |
The sparse constellation map is robust because the relative positions of peaks are preserved even when the audio is compressed, equalised, or transmitted over a lossy stream — which is exactly the environment in live radio. Shazam’s matching algorithm identifies songs by looking for constellations of peaks that appear in the same time-frequency arrangement, not by comparing raw waveforms or full spectrograms.