Skip to content

avitus/mankunku

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

812 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Mankunku

Jazz ear training progressive web app with call-and-response practice. The app plays a jazz phrase, you play it back on your instrument via microphone, and it scores your pitch and rhythm accuracy in real time.

Named after Winston "Mankunku" Ngozi's 1968 album Yakhal' Inkomo — one of the greatest South African jazz recordings. Under the hood: real-time pitch detection at 60fps, Dynamic Time Warping for score alignment, a custom AudioWorklet for onset detection, adaptive difficulty that grows with you, and a local-first architecture that works offline.

Features

  • Call-and-response practice with automatic scoring
  • Real-time pitch detection (McLeod method via Pitchy, 60fps)
  • Note onset detection via custom AudioWorklet (HFC algorithm)
  • DTW alignment-based scoring (pitch 60% + rhythm 40%, with latency correction)
  • 33 scales and 452 curated jazz licks (ii-V-I, blues, bebop, modal, and more)
  • Combinatorial phrase generation from scale patterns and rhythm templates
  • Adaptive difficulty: proficiency levels 1-100, 10 content tiers
  • Concert pitch canonical — transposition to written pitch at display time only
  • Fraction-based rhythm representation (no floating-point drift with triplets or dotted notes)
  • Local-first: writes to localStorage/IndexedDB, optional Supabase cloud sync
  • Offline-capable installable PWA with cached SoundFont samples
  • Cross-device progress sync via Supabase auth (optional)
  • Dark and light themes

Tech Stack

Technology Role
SvelteKit 2 + Svelte 5 (runes) Framework, SSR, file-based routing
TypeScript (strict mode) Type safety throughout
Tailwind CSS 4 Utility-first styling, dark/light theming
Tone.js + smplr Audio scheduling, SoundFont sample playback
Pitchy McLeod pitch detection
Custom AudioWorklet Real-time onset detection
abcjs Sheet music notation rendering
Supabase Auth + PostgreSQL cloud sync (optional)
Vitest + Playwright Unit and E2E testing
@vite-pwa/sveltekit PWA with Workbox service worker

Quick Start

Prerequisites: Node.js >= 22, a modern browser with Web Audio API support

git clone https://github.com/avitus/mankunku.git
cd mankunku
npm install
npm run dev

The app opens at http://localhost:5173. The onboarding flow will prompt for instrument selection and microphone access. A microphone is needed for full functionality but not required to explore the codebase.

Optional — Supabase cloud sync: Copy .env.example to .env and add your Supabase project URL and anon key. See Development Setup for full setup including database migrations.

Local Supabase stack (for auth/sync development): Development runs against a local Supabase instance so it never touches production data. Requires Docker.

npm run db:start   # boots local Postgres + Auth + Storage via Docker and applies all migrations
npm run dev        # dev server now talks to the local stack at http://127.0.0.1:54321

npx supabase migration up --local applies any migrations added since the stack was started (e.g. after pulling main); npm run db:reset re-applies them from a clean slate; npm run db:stop shuts the stack down. npm run db:types:check verifies the hand-maintained src/lib/supabase/types.ts still matches the schema. Point your local .env PUBLIC_SUPABASE_URL / PUBLIC_SUPABASE_ANON_KEY at the values from npx supabase status. Production is unaffected — its credentials are injected by CI at build time, not read from .env.

Project Structure

src/
  lib/
    audio/          Audio pipeline: playback, capture, pitch detection, onset detection
    scoring/        DTW alignment and scoring engine
    music/          Music theory: scales, keys, intervals, transposition, chords
    phrases/        Phrase generation, mutation, validation, library loading
    difficulty/     Adaptive difficulty algorithm and 10-tier profiles
    tonality/       Daily key/scale selection, progressive unlocking
    state/          Svelte 5 runes state modules (.svelte.ts)
    persistence/    localStorage/IndexedDB storage + Supabase sync
    components/     UI components (audio, practice, library, notation, onboarding)
    supabase/       Client setup and generated DB types
    types/          TypeScript interfaces grouped by domain
    data/           Curated lick library and static data
  routes/           SvelteKit pages: practice, library, progress, settings, auth
tests/
  unit/             Unit tests across 8 domains (audio, scoring, music, phrases, ...)
  integration/      Integration tests (auth route chain, etc.)
  e2e/              Playwright browser tests
supabase/
  migrations/       23 SQL migrations (profiles, progress, settings, licks, RLS, +evolutions)
documentation/      Architecture docs, API reference, contributing guides

Architecture Highlights

Concert pitch canonical — All MIDI note numbers, scale data, and lick data use concert pitch. Transposition to written pitch (Bb/Eb instruments) happens only at display time in two functions: phraseToAbc() and concertToWritten(). This eliminates an entire class of transposition bugs.

Fraction-based rhythm — Note durations use [numerator, denominator] tuples (e.g., [1, 8] = eighth note, [1, 12] = triplet eighth). No floating-point drift with triplets or dotted rhythms. Conversion to seconds happens only when computing audio timing.

DTW scoring with latency correction — Dynamic Time Warping aligns expected and detected note sequences, handling extra notes, missed notes, and tempo drift. The scorer computes the median timing offset across matched pairs and subtracts it, absorbing constant human and detection latency without affecting relative timing accuracy.

AudioWorklet onset detection — A custom AudioWorklet processor runs on the audio thread for low-latency onset detection using High-Frequency Content weighting with an adaptive threshold. Falls back to pitch-gap detection in browsers without AudioWorklet support.

Local-first with optional cloud — All writes go to localStorage and IndexedDB first for instant feedback and offline resilience. Supabase sync runs in the background when the user opts in. The app is fully functional without any backend.

See Architecture Overview for detailed system design documentation.

Contributing

Contributions are welcome — whether that is adding jazz licks, improving the scoring algorithm, fixing bugs, or improving documentation.

Good first contributions:

  • Add curated licks to the library (guide)
  • Add scales to the catalog (guide)
  • Improve test coverage across any of the 8 test domains
  • Report bugs or suggest features via issues

See the full Contributing Guide for code style, branch naming, commit conventions, and PR process. In short: Svelte 5 runes only, TypeScript strict mode, Conventional Commits.

Testing

npm test              # Run unit + integration tests (Vitest)
npm run test:watch    # Watch mode
npm run test:e2e      # Playwright E2E tests
npm run check         # TypeScript + svelte-check

See the Testing Guide for patterns, audio mocking strategies, and conventions.

Nginx deployment

The production nginx config at nginx/mankunku.conf is the single source of truth. It is deployed to /etc/nginx/sites-available/mankunku on the server by a dedicated CircleCI workflow.

When does it deploy? The nginx-deploy workflow runs only when a push to main changes any of:

  • nginx/**
  • deploy/nginx/**
  • .circleci/**

App-only changes (under src/, tests/, static/, etc.) do not trigger nginx-deploy. Path filtering is wired via the circleci/path-filtering orb from .circleci/config.yml; the actual jobs live in .circleci/continue-config.yml. Running this workflow requires "Dynamic config using setup workflows" enabled in CircleCI (Project Settings → Advanced).

What happens on the server? The job scp's nginx/mankunku.conf and deploy/nginx/deploy.sh to /tmp, then invokes the script over SSH. The script:

  1. Backs up the existing /etc/nginx/sites-available/mankunku to /etc/nginx/backups/mankunku.<timestamp>.conf.
  2. Installs the new config (mode 0644, owner root:root).
  3. Ensures the sites-enabled/mankunku symlink exists.
  4. Runs sudo nginx -t.
  5. On success, reloads nginx with sudo systemctl reload nginx.
  6. On failure, restores the backup and exits non-zero (no reload).

Required CircleCI environment variables (set in a context or the project):

Variable Purpose
DEPLOY_HOST Server hostname or IP
DEPLOY_USER SSH user (must have passwordless sudo for nginx, systemctl, install, mkdir, cp, ln, rm on the relevant paths)
DEPLOY_PORT (optional) SSH port; defaults to 22

The SSH key is attached via add_ssh_keys using the fingerprint already registered for the app deploy job.

Manual deploy. Copy nginx/mankunku.conf to the server (e.g. /tmp/mankunku.nginx.staged) and run:

bash deploy/nginx/deploy.sh /tmp/mankunku.nginx.staged

The script is idempotent and safe to re-run.

License

License pending. This project does not yet have a license file — one will be added before the first public release.

Third-party data

The lick-naming feature uses a derivative index of the Weimar Jazz Database (CC-BY-NC-SA 4.0). See docs/wjazzd-attribution.md for license details and rebuild instructions.

Acknowledgments

Named after Winston "Mankunku" Ngozi (1943-2009), South African jazz tenor saxophonist whose 1968 album Yakhal' Inkomo ("Cry of the Bull") remains a landmark of South African jazz.

Built with SvelteKit, Tone.js, Pitchy, smplr, abcjs, and Supabase.

About

Call and response ear training for jazz musicians

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages