Files
clicktrack/README.md
AJ Avezzano 5b772655c6 feat: initial scaffold for ClickTrack monorepo
Full self-hosted click track generator for cover bands.

Core technical pieces implemented:
- CTP (Click Track Protocol) TypeScript schema, Zod validator, and WAV
  renderer (44.1 kHz, 16-bit PCM, accented downbeats, ramp sections)
- MusicBrainz API client with 1 req/s rate limiting
- PostgreSQL schema (songs, tempo_maps, registry_sync_log) with triggers
- Git registry sync logic (clone/pull → validate CTP → upsert DB)
- Next.js 14 App Router: search page, track page, API routes
  (/api/songs, /api/tracks, /api/generate)
- UI components: SearchBar, SongResult, TempoMapEditor, ClickTrackPlayer
  (Web Audio API in-browser playback + WAV download)
- Docker Compose stack: app + postgres + redis + nginx + registry-sync
- Multi-stage Dockerfile with standalone Next.js output
- .env.example documenting all configuration variables
- README with setup instructions, CTP format spec, and API reference

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

264 lines
7.0 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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
```bash
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
```bash
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:
```bash
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
```bash
# 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):
```bash
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
```json
{
"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:
```bash
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:**
```json
{ "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_in` — `true` 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`](.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