|
| 1 | +# CLAUDE.md |
| 2 | + |
| 3 | +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. |
| 4 | + |
| 5 | +## Project Overview |
| 6 | + |
| 7 | +Presto is an audio fingerprinting and recognition tool written in Go. It identifies song tracks from audio snippets using two selectable algorithms — constellation peak-pair hashing (Wang 2003, default) and Haitsma-Kalker sub-band mel-band energy comparison — with a persistent file-backed library and vote-based lookup that scales to thousands of songs. See [docs/algorithm.md](docs/algorithm.md) for the full algorithm walkthrough. |
| 8 | + |
| 9 | +## Build, Run, and Test |
| 10 | + |
| 11 | +```bash |
| 12 | +# Build |
| 13 | +go build ./cmd/presto |
| 14 | + |
| 15 | +# Index an audio directory into a library file |
| 16 | +./presto index <audio_dir> <store_file> <winSize> <hopSize> [windowFunc] |
| 17 | + |
| 18 | +# Index with sub-band algorithm instead of constellation |
| 19 | +./presto index <audio_dir> <store_file> <winSize> <hopSize> [windowFunc] --algo subband |
| 20 | + |
| 21 | +# Match a sample against an indexed library (one-shot CLI) |
| 22 | +./presto match <store_file> <sample_file> |
| 23 | + |
| 24 | +# Run the HTTP server (configured via PRESTO_* env vars) |
| 25 | +./presto serve |
| 26 | + |
| 27 | +# Example |
| 28 | +./presto index ./songs/ library.prfp 1024 512 hann |
| 29 | +./presto match library.prfp clip.wav |
| 30 | +PRESTO_STORE_PATH=./library.prfp ./presto serve |
| 31 | + |
| 32 | +# Window function options: hann, hamming, bartlett (omit for none) |
| 33 | + |
| 34 | +# Container build |
| 35 | +docker build -t presto:latest . |
| 36 | + |
| 37 | +# Run all tests |
| 38 | +go test ./... |
| 39 | + |
| 40 | +# Run a single test |
| 41 | +go test ./internal/fingerprint/ -run TestExactClipMatch -v |
| 42 | + |
| 43 | +# Run benchmarks |
| 44 | +go test ./internal/fingerprint/ -bench=. -benchmem |
| 45 | +go test ./internal/store/ -bench=. -benchmem |
| 46 | +``` |
| 47 | + |
| 48 | +Input files must be WAV format (PCM, 8/16/24/32-bit). |
| 49 | + |
| 50 | +## Architecture |
| 51 | + |
| 52 | +```text |
| 53 | +internal/ |
| 54 | + audio/ WAV file I/O |
| 55 | + wav.go Signal type, ReadWAV, DecodeWAV, EncodeWAV, WriteWAV |
| 56 | +
|
| 57 | + dsp/ Signal processing primitives |
| 58 | + fft.go Cooley-Tukey radix-2 FFT, per-call scratch buffer |
| 59 | + window.go Window functions (hann, hamming, bartlett) |
| 60 | + spectrogram.go STFT spectrogram, signal normalization, noise injection |
| 61 | + peaks.go 2D time-frequency local maximum detection |
| 62 | + mel.go Mel-frequency banding, sub-band energy comparison |
| 63 | +
|
| 64 | + fingerprint/ FP type, Strategy interface, registry |
| 65 | + fingerprint.go FP tagged union, Strategy interface, Get/Register, convenience funcs |
| 66 | + constellation/ Constellation strategy (peak-pair hashing, init-registers) |
| 67 | + subband/ Sub-band strategy (mel-band energy bits, init-registers) |
| 68 | +
|
| 69 | + store/ Persistent library with vote-based lookup |
| 70 | + store.go Store type, Load (mmap), Save, Match API, StoreStrategy dispatch |
| 71 | + strategy.go StoreStrategy interface, algorithm byte mapping |
| 72 | + format.go Binary file format (PRFP v2 with algorithm byte) |
| 73 | + constellation_index.go Constellation hash inverted index + store strategy |
| 74 | + subband_index.go LSH inverted index + sub-band store strategy |
| 75 | +
|
| 76 | + metrics/ Stdlib-only Prometheus metrics (Counter/Gauge/Histogram/Registry) |
| 77 | + metrics.go Metric types, registry, runtime collector |
| 78 | +
|
| 79 | +cmd/presto/ |
| 80 | + main.go Subcommand dispatch: index / match / serve |
| 81 | + server.go HTTP server (POST /v1/match, /v1/stats, /healthz, /readyz, /metrics) |
| 82 | + server_test.go httptest-based handler tests |
| 83 | +
|
| 84 | +deploy/k8s/ Plain YAML manifests for Kubernetes deployment |
| 85 | +Dockerfile Multi-stage build (golang:1.26-alpine -> distroless) |
| 86 | +docs/algorithm.md Algorithm walkthrough covering both strategies |
| 87 | +``` |
| 88 | + |
| 89 | +**Dependency graph**: `cmd/presto` -> `store`, `metrics` -> `fingerprint` -> `audio`, `dsp` |
| 90 | + |
| 91 | +**Strategy pattern**: Both algorithms register via `init()` + blank import (like `database/sql` drivers). The `Strategy` interface covers fingerprinting and similarity; the `StoreStrategy` interface covers indexing, matching, and serialization. The store header's algorithm byte auto-selects at load time. |
| 92 | + |
| 93 | +**Fingerprinting pipeline** (shared + per-algorithm): |
| 94 | + |
| 95 | +1. Read/decode WAV (`audio.ReadWAV` or `audio.DecodeWAV`) -> float64 samples |
| 96 | +2. Normalize + window + FFT (`dsp.Spectrogram`) -> magnitude spectrogram |
| 97 | +3a. *Constellation*: `dsp.FindPeaks` -> `constellation.GenerateHashes` -> `FP{Hashes, NumFrames}` |
| 98 | +3b. *Sub-band*: `dsp.MelBandsInto` -> `dsp.SubBandFPInto` -> `FP{Data, Stride, Frames}` |
| 99 | + |
| 100 | +## HTTP server (`presto serve`) |
| 101 | + |
| 102 | +Loads one `.prfp` library at startup (async — readyz reflects state). Auto-selects algorithm from header. Concurrent reads are safe (store is read-only after Load). |
| 103 | + |
| 104 | +**Endpoints**: `POST /v1/match` (JSON), `GET /v1/stats`, `GET /healthz`, `GET /readyz`, `GET /metrics` |
| 105 | + |
| 106 | +**Configuration** via environment variables: |
| 107 | + |
| 108 | +- `PRESTO_STORE_PATH` (default `/var/lib/presto/library.prfp`) |
| 109 | +- `PRESTO_LISTEN_ADDR` (default `:8080`) |
| 110 | +- `PRESTO_MAX_UPLOAD_BYTES` (default 10 MiB) |
| 111 | + |
| 112 | +**Graceful shutdown**: `SIGINT` / `SIGTERM` -> drain with 10 s timeout. Panic recovery middleware on all routes. |
| 113 | + |
| 114 | +## Store |
| 115 | + |
| 116 | +**File format** (single binary file, little-endian, version 2): |
| 117 | + |
| 118 | +```text |
| 119 | +Header (32 bytes): |
| 120 | + magic "PRFP", version (2), songCount, |
| 121 | + winSize, hopSize, windowFunc, algorithm (0=constellation, 1=subband), reserved |
| 122 | +
|
| 123 | +Per song: |
| 124 | + nameLen uint16, name, numFrames uint32, dataLen uint32, fpData |
| 125 | +``` |
| 126 | + |
| 127 | +`Load` memory-maps the file and zero-copy slices per-song data via `unsafe.Slice` (with runtime alignment guard). Index is rebuilt on load (not serialized). |
| 128 | + |
| 129 | +**Matching**: direct hash lookup (constellation) or LSH candidate filter + sliding-window Compare (sub-band), both using `(songID, delta)` vote accumulation. |
| 130 | + |
| 131 | +**Size limits**: 65,535 songs (uint16 songID); song length limited only by uint32 frame offset. |
0 commit comments