AJ Avezzano 6de0d98dee fix: add QueryResultRow constraint to db query generic
pg's Pool.query<T> requires T extends QueryResultRow. Added the
constraint and imported the type.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-01 11:28:38 -04:00

ClickTrack

A self-hosted, open-source click track generator for cover bands.

Search any song, browse community-contributed tempo maps, and download a metronomic WAV file — accented downbeats, time signature changes, and tempo ramps included.


Features

  • Song search via MusicBrainz, cached locally in PostgreSQL
  • Community tempo maps in the open CTP (Click Track Protocol) format
  • WAV generation — 44.1 kHz, 16-bit PCM, mono; 880 Hz accented / 440 Hz unaccented clicks
  • Count-in — configurable 18 bar count-in prepended before bar 1
  • Tempo ramps — linear BPM interpolation beat-by-beat for ritardando/accelerando sections
  • Federated registry — pull CTP files from any public Git repo on a cron schedule
  • Self-hosted — single docker compose up gets you running

Quick Start (Docker)

1. Clone and configure

git clone https://github.com/your-org/clicktrack.git
cd clicktrack
cp .env.example .env
# Edit .env — at minimum set a strong POSTGRES_PASSWORD

2. Start the stack

docker compose up -d --build

This starts:

Container Role
app Next.js 14 (port 3000, proxied via nginx)
postgres PostgreSQL 16 with persistent volume
redis Redis 7 for caching
nginx Reverse proxy on ports 80/443

3. Open the app

Navigate to http://localhost (or your server's IP/domain).

4. Enable registry sync (optional)

Set REGISTRY_REPO in .env to a public GitHub repo URL containing CTP files, then:

docker compose --profile registry up -d

The registry-sync container will pull and import CTP files every REGISTRY_SYNC_INTERVAL seconds (default: 1 hour).


Development Setup

# Prerequisites: Node 20+, a local PostgreSQL 16, Redis 7
cp .env.example .env
# Update DATABASE_URL and REDIS_URL to point at your local services
npm install

# Apply schema
psql $DATABASE_URL -f lib/db/schema.sql

# Start dev server with hot reload
npm run dev

Or with Docker (hot-reload via volume mount):

docker compose -f docker-compose.yml -f docker-compose.dev.yml up --build

CTP — Click Track Protocol

CTP is an open JSON format for describing a song's complete tempo map. A CTP file is a plain .ctp.json file that can be version-controlled and shared.

Full example

{
  "version": "1.0",
  "metadata": {
    "title": "Bohemian Rhapsody",
    "artist": "Queen",
    "mbid": "b1a9c0e0-9c4a-4a6b-8b5a-1234567890ab",
    "duration_seconds": 354,
    "contributed_by": "freddie_fan",
    "verified": false,
    "created_at": "2026-04-01T00:00:00Z"
  },
  "count_in": {
    "enabled": true,
    "bars": 1,
    "use_first_section_tempo": true
  },
  "sections": [
    {
      "label": "Intro (Ballad)",
      "start_bar": 1,
      "bpm": 72.0,
      "time_signature": { "numerator": 4, "denominator": 4 },
      "transition": "step"
    },
    {
      "label": "Rock Section",
      "start_bar": 45,
      "bpm": 152.0,
      "time_signature": { "numerator": 4, "denominator": 4 },
      "transition": "step"
    },
    {
      "label": "Outro (slowing)",
      "start_bar": 120,
      "bpm_start": 152.0,
      "bpm_end": 72.0,
      "time_signature": { "numerator": 4, "denominator": 4 },
      "transition": "ramp"
    }
  ]
}

Schema rules

Field Type Notes
version "1.0" Must be exactly "1.0"
metadata.mbid UUID or null MusicBrainz Recording ID
metadata.duration_seconds number Total song duration (excluding count-in)
count_in.bars 18 Number of count-in bars
sections[*].start_bar integer ≥ 1 Must start at 1, strictly ascending
sections[*].transition "step" | "ramp" Step = instant change; ramp = linear interpolation
sections[*].bpm 20400 Only for step sections
sections[*].bpm_start / bpm_end 20400 Only for ramp sections
sections[*].time_signature.denominator 1, 2, 4, 8, 16, 32 Must be a power of 2

Click sounds

Beat Frequency Amplitude
Beat 1 (downbeat) 880 Hz Accented
Other beats 440 Hz Normal

Both use a 12 ms sine wave with exponential decay (e^(-300t)).


Community Registry

The registry is a public Git repository containing .ctp.json files organised by artist:

registry-repo/
  q/
    queen/
      b1a9c0e0-9c4a-4a6b-8b5a-1234567890ab.ctp.json   # Bohemian Rhapsody
  t/
    the-beatles/
      ...

Contributing a tempo map

  1. Fork the community registry repo.
  2. Create a .ctp.json file for your song. Use the schema above.
  3. Validate your file: paste the JSON into a CTP validator (coming soon) or use the API:
    curl -X POST http://localhost/api/tracks \
      -H "Content-Type: application/json" \
      -d @your-song.ctp.json
    
  4. Open a pull request to the registry repo.

Once merged, any ClickTrack instance syncing that registry will import your map automatically.


API Reference

GET /api/songs?q=<query>&limit=<n>

Search for songs. Hits local DB first, falls back to MusicBrainz.

Response:

{ "songs": [...], "total": 5 }

GET /api/tracks?mbid=<uuid>

List all community tempo maps for a song.

POST /api/tracks

Submit a new CTP document. Body must be a valid CTP JSON.

Response: 201 Created with the stored map record.

GET /api/generate?id=<tempo-map-uuid>&count_in=true

Generate and download a WAV click track.

  • id — UUID of the tempo map (from /api/tracks)
  • count_intrue or false (default: true)

Returns audio/wav with Content-Disposition: attachment.


Architecture

Browser
  └── Next.js App Router (app/)
        ├── (web)/page.tsx         — song search
        ├── (web)/track/[id]/      — track page + player
        └── api/
              ├── songs/           — search + MB integration
              ├── tracks/          — CTP CRUD
              └── generate/        — WAV rendering

lib/
  ├── ctp/
  │     ├── schema.ts              — TypeScript types
  │     ├── validate.ts            — Zod validation
  │     └── render.ts              — CTP → WAV (Node.js)
  ├── db/client.ts                 — pg Pool + query helpers
  ├── musicbrainz/client.ts        — rate-limited MB API
  └── registry/sync.ts             — Git registry pull + upsert

Environment Variables

See .env.example for all variables with descriptions.

Variable Required Default Description
DATABASE_URL Yes PostgreSQL connection string
REDIS_URL Yes Redis connection URL
NEXT_PUBLIC_APP_NAME No ClickTrack UI display name
REGISTRY_REPO No GitHub repo URL for CTP registry
REGISTRY_SYNC_INTERVAL No 3600 Sync interval in seconds
MUSICBRAINZ_USER_AGENT No built-in User-Agent for MB API requests

License

MIT

Description
ClickTrack — community click track / tempo map platform
Readme 124 KiB
Languages
TypeScript 93.7%
PLpgSQL 4.5%
Dockerfile 1.6%
JavaScript 0.2%