Skip to content
PROJECTS/PITCHD

Pitchd

▲ 15K+ VISITORS

Two real-time ear-training games with a global leaderboard, daily mode, and score sharing — 15,000+ visitors and 30,000+ page views in 5 months with zero marketing spend.

ROLESolo Creator
DURATIONMarch 2026 - Present
TEAMSOLO
Pitchd — Two real-time ear-training games with a global leaderboard, daily mode, and score sharing — 15,000+ visitors and 30,000+ page views in 5 months with zero marketing spend.

MISSION BRIEF

Solo-built two real-time ear-training games targeting competitive musicians and audiophiles. The Pitchd game is a hardcore 5-round acoustic memory challenge where players recreate polyphonic sequences on a digital piano. The BPM game tests rhythmic intuition by challenging players to guess the beats per minute of dynamically generated tracks. Reached 15,000+ visitors and 30,000+ page views in 5 months with zero marketing spend. Features include a global leaderboard, daily mode with deterministic seeding, Wordle-style share loops, and a partial-credit exponential decay grading algorithm. Built on Next.js around a custom Web Audio API synthesis engine, with dynamic global percentiles powered by Supabase.

THE PROBLEM

Ear-training tools are mostly drills: play an interval, name it, repeat. They work, but nobody comes back to them every day. I wanted pitch memory practice that felt like Wordle: one shared puzzle a day, a score you can compare with friends, and a reason to return tomorrow. That meant two hard requirements. Every player has to get the exact same puzzle on the same date, and a leaderboard that says "Top 3% today" has to be trustworthy, which a purely client-side game can't guarantee.

APPROACH

Each daily puzzle is generated, not stored. A Mulberry32 PRNG is seeded from a string like "2026-10-06-R3" (date plus round), so the same date always produces the same 4-note sequence for everyone, with no answers sitting in a database. Unit tests pin known seed outputs so a refactor can't silently change everyone's puzzle. Scoring rewards musical understanding, not just exact matches. The exact note scores 2.5, the right note in the wrong octave 2.0, a perfect 5th 1.5, a perfect 4th 1.25, a semitone off 1.0, and anything else decays as 2.5 × 0.5^distance. A perfect game is exactly 50 points. The leaderboard verifies scores on the server. The client submits the 20 notes it played, and the server recomputes the score against the deterministic answer for that date, so a forged "score: 50" request is rejected. The server only accepts dates that are currently "today" somewhere on Earth, and endless-mode scores can't be posted to the daily board.

ARCHITECTURE

Next.js app with a single interactive shell: every game route renders one client tree, and view changes swap content with a short fade and history.pushState instead of full page navigations. In-app links are still real anchor tags, so crawlers and cmd-click work. The pitch game uses a Tone.js PolySynth through delay and reverb, initialised on the first user gesture with a silent warm-up chord so the first real sequence doesn't drift. The BPM game uses the Web Audio API directly: a 25 ms scheduler fills a 150 ms look-ahead buffer of oscillator clicks, which keeps the metronome steady even when the main thread is busy. Supabase (Postgres) stores sessions and scores and computes the global percentiles. Each shared result gets its own Open Graph image, so a link pasted into Discord or iMessage shows the score card.

RESULTS

15,000+ visitors and 30,000+ page views in the first 5 months with no marketing spend, mostly from people sharing results. Keeping three.js, Tone.js, and article bodies off the critical path cut the initial JavaScript from about 465 KB to about 228 KB gzipped.

WHAT I'D DO DIFFERENTLY

Sharing is the growth loop, so test it like a feature. For a while the share link redirected, which meant chat apps never showed the score card, and players could only share after submitting to the leaderboard. Fixing those mattered more than any new game mode. Use one definition of "today" everywhere. Streaks used UTC dates while puzzles used local dates, so evening players in the US saw streaks break or double-count, and a game started at 23:59 could switch puzzles mid-game. Everything now uses the player's local date, locked when the game starts.

TECH ARSENAL

Next.jsTailwindCSSWeb Audio APIGSAPThree.jsSupabasePostgreSQL