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>
This commit is contained in:
AJ Avezzano
2026-04-01 11:14:46 -04:00
commit 5b772655c6
31 changed files with 2762 additions and 0 deletions

263
README.md Normal file
View File

@@ -0,0 +1,263 @@
# 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