Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
a7f8430
Initial plan
Copilot May 20, 2026
e99af7f
Build client-only KeyBpmMap app
Copilot May 20, 2026
e5beaf6
Deploy Pages for pull requests
Copilot May 20, 2026
e4f5ad2
Gate PR Pages previews behind label
Copilot May 20, 2026
588a8dc
Clarify Pages PR deployment limits
Copilot May 20, 2026
f61da8a
Document local verification steps
Copilot May 20, 2026
3d28898
Make BPM band size configurable
Copilot May 20, 2026
6fde35b
Polish BPM band labels
Copilot May 20, 2026
b224bf5
Refine BPM band calculations
Copilot May 20, 2026
f0335b1
Add Engine DJ path hints
Copilot May 20, 2026
b54f95b
Polish Windows path copy
Copilot May 20, 2026
d7906c7
Clarify picker fallback behavior
Copilot May 20, 2026
042f97d
Polish database path guidance
Copilot May 20, 2026
c3ebf77
Improve path accessibility
Copilot May 20, 2026
cd1c763
Refine path labels
Copilot May 20, 2026
7e9e793
Adjust chart layout and key views
Copilot May 20, 2026
adcb8da
Polish chart controls
Copilot May 20, 2026
6601d11
Tidy polar key mode state
Copilot May 20, 2026
d63c506
Fix polar band label layering
Copilot May 20, 2026
fc9b1ed
Refactor polar band radii
Copilot May 20, 2026
10aee44
Group sparse BPM regions
Copilot May 20, 2026
aa6b62e
Polish sparse region summaries
Copilot May 20, 2026
34a5414
Refine heatmap and polar chart controls
Copilot May 21, 2026
2a7250c
Deduplicate key-filtered chart cells
Copilot May 21, 2026
7fbf67e
Name polar stroke scale constants
Copilot May 21, 2026
514aff3
Share chart key mode handler
Copilot May 21, 2026
fd788f9
Polish chart mode labels
Copilot May 21, 2026
162fb54
Scale polar BPM label font
Copilot May 21, 2026
ec8f050
Normalize polar label font cap
Copilot May 21, 2026
fc6a45d
Name polar label scale factor
Copilot May 21, 2026
6015dd0
Simplify polar label positioning
Copilot May 21, 2026
d281a6d
Clarify polar label scale constant
Copilot May 21, 2026
878b31f
Polish polar label y position
Copilot May 21, 2026
c6a632b
Remove playlist transitions panel
Copilot May 21, 2026
fe33036
Add track title spacing
Copilot May 21, 2026
2fad768
Add visible app version
Copilot May 21, 2026
62b1ea9
Read app version from package metadata
Copilot May 21, 2026
3daf4a4
Validate package version metadata
Copilot May 21, 2026
3baad9f
Add Copilot version review guidance
Copilot May 21, 2026
3d38569
Clarify version bump example
Copilot May 21, 2026
c30078e
Polish Copilot version review guidance
Copilot May 21, 2026
bbbf1cd
Apply suggestion from @samsta
samsta May 21, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Copilot review instructions

- The app version shown in the UI comes from `package.json`.
- Every pull request should bump `package.json` to a newer version before merge.
- Treat the project version as monotonically increasing: never reuse, lower, or skip back to an older version than the base branch.
- The project convention is to increment the minor version for each PR (for example `0.1.0` → `0.2.0` → `0.3.0` across consecutive PRs).
- Copilot reviews should flag any PR that does not update the version, or that updates it to a value that is not strictly greater than the version on the base branch.
58 changes: 58 additions & 0 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
name: Deploy Pages

on:
push:
branches:
- main
pull_request:
branches:
- main
workflow_dispatch:

concurrency:
group: pages-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true

jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 22
cache: npm

- name: Configure Pages
uses: actions/configure-pages@v5

- name: Install dependencies
run: npm ci

- name: Build app
run: npm run build

- name: Upload artifact
uses: actions/upload-pages-artifact@v3
with:
path: ./dist

deploy:
if: github.event_name == 'push'
needs: build
runs-on: ubuntu-latest
permissions:
pages: write
id-token: write
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
24 changes: 24 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*

node_modules
dist
dist-ssr
*.local

# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
78 changes: 77 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,77 @@
# KeyBpmMap
# KeyBpmMap

KeyBpmMap is a client-only React application for exploring the harmonic and tempo shape of an Engine DJ music library.
It reads a local SQLite database in the browser with `sql.js`, converts Engine numeric keys into Camelot notation, and renders both a polar density map and a BPM-vs-key heatmap for fast DJ library analysis.

## Features

- Browser-only Engine DJ database loading (`Track`, `Playlist`, `PlaylistEntity`)
- Engine numeric key → Camelot conversion (`0=8B ... 23=7A`)
- Polar harmonic density map and rectangular BPM heatmap
- Playlist, rating, BPM, and text filters
- Region inspection for exact track lists inside a selected cell
- Playlist transition summaries derived from playlist ordering
- Sparse / missing region surfacing
- Mock dataset mode for first-run exploration
- PNG export for both charts
- GitHub Pages-ready static deployment

## Local development

```bash
npm ci
npm run dev
```

Open the Vite dev server URL in your browser, then either:

1. Load a local Engine DJ SQLite database file (`.db`, `.sqlite`, `.sqlite3`, `.backup`), or
2. Use the built-in mock crate to explore the interface without a real library.

Typical Engine DJ database locations:

- macOS: `~/Music/Engine Library/Database2/m.db`
- Windows: `%USERPROFILE%\Music\Engine Library\Database2\m.db`

## Verifying a local checkout

Because browsers restrict some file loading behavior on plain `file://` pages, verify the app through a local web server instead of opening `dist/index.html` directly.

For an interactive local checkout:

```bash
npm ci
npm run dev
```

Then open the local URL printed by Vite (usually `http://localhost:5173/`).

For a production-like local check:

```bash
npm ci
npm run build
npm run preview
```

Then open the preview URL printed by Vite (usually `http://localhost:4173/`).

## Build and lint

```bash
npm run build
npm run lint
```

## Deployment

The repository includes a GitHub Pages workflow in `.github/workflows/deploy.yml`.
When `main` is updated, the workflow builds the Vite app and deploys the generated static site to Pages.
Pull requests targeting `main` run the same build so changes are validated before merge, but they do not publish a separate preview site.
With this single-site GitHub Pages setup, the custom Actions workflow still publishes one live Pages URL for the repository, so isolated PR previews would require separate hosting or a second Pages site/repository.

## Notes

- Processing happens locally in the browser; there is no upload endpoint or server component.
- The parser is intentionally tolerant of minor schema variations by matching common column names case-insensitively.
- If a database is missing the `Track` table, the app shows a validation error instead of failing silently.
22 changes: 22 additions & 0 deletions eslint.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import js from '@eslint/js'
import globals from 'globals'
import reactHooks from 'eslint-plugin-react-hooks'
import reactRefresh from 'eslint-plugin-react-refresh'
import tseslint from 'typescript-eslint'
import { defineConfig, globalIgnores } from 'eslint/config'

export default defineConfig([
globalIgnores(['dist']),
{
files: ['**/*.{ts,tsx}'],
extends: [
js.configs.recommended,
tseslint.configs.recommended,
reactHooks.configs.flat.recommended,
reactRefresh.configs.vite,
],
languageOptions: {
globals: globals.browser,
},
},
])
17 changes: 17 additions & 0 deletions index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta
name="description"
content="Client-only Engine DJ key and BPM visualization app."
/>
<title>KeyBpmMap</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
Loading
Loading