Search for kalimba tabs and you get an ad-choked SEO farm with an unsorted pile of songs and no notion of which are hard. A nursery rhyme and a two-octave chromatic pop arrangement sit in the same list, formatted identically. A beginner who bought a 17-key kalimba three weeks ago wants to know one thing: what can I actually play tonight.
The Rating Is the Product
The catalog is the demo. The thing being built is a difficulty engine that can say:
Level 1 of 5. No leap larger than a third, fits inside 5 tines, unhurried at tempo, 30 notes, one repeated phrase.
That sentence is generated, not written. Nine factors are measured, each emits its weighted contribution, and the explainer renders the two or three that actually drove the score. The engine is a pure function with no clock, no randomness and no I/O, so the level computed at build time and the level computed in the browser cannot disagree.
The Instrument Has Opinions
A C-major kalimba puts its longest, lowest tine in the centre and alternates ascending pitches outward, left then right. Two consequences fall out of that, and they reshaped the whole model.
Each side is a chain of thirds. A scale step is one tine away on the opposite side, so stepwise melodies alternate thumbs and feel easy. A third is one tine away on the same side. Which means a triad is three consecutive tines under one thumb and rolls easily, the opposite of piano intuition. The original difficulty rubric had a rule penalising chords; measuring the physics showed it was wrong, and the rule became “penalise a gap in one thumb’s run” instead.
The layout fans outward, so reach depends on register. C4 to G4 hugs the centre across 4 tines. The same five-note shape an octave up straddles both outer edges across 10. That correction split one factor into two.
There are also melodies the instrument simply cannot play. Seven notes, no sharps or flats, so a piece only works if some transposition puts every note on a real tine. Für Elise alternates E and D sharp and there is no D sharp. The app says so on a dedicated page rather than quietly altering the tune and calling it the piece, which is what the tab farms do.
Copyright Is the Governing Constraint
A kalimba tab of a copyrighted song is a derivative of the composition. So every melody shipped is public domain, and every one carries a provenance block: composition title, composer, year, the legal basis, and a source URL you can check.
This is enforced by a build script that breaks the build, with no override flag. Flip verified to false on any song and the build stops with no catalog written. The Song schema is Zod strict with no lyrics field, so lyric text cannot ship by accident even for public-domain songs, because many carry copyrighted lyric translations.
Doing the research properly corrected five claims that had felt obviously true:
- The Mary Had a Little Lamb tune is not Lowell Mason’s 1831 setting. It is the chorus of E.P. Christy’s “Goodnight, Ladies”, attached to those words in 1868.
- Row, Row, Row Your Boat first appeared in 1852 with a completely different melody. The familiar tune is credited to Eliphalet Oram Lyte in 1881.
- London Bridge appears in a 1744 songbook, but the tune everyone sings first shows up in 1879.
- We Wish You a Merry Christmas was excluded outright. Arthur Warrell was awarded copyright on his arrangement in 1939, and the version everyone knows was unknown outside the West Country before he published it.
The pattern is worth naming: the famous publication and the familiar tune are routinely different events, decades apart. A date that looks like provenance often is not.
Note Names, Not Tine Numbers
Song files store C4 C4 G4 G4 A4 A4 G4, not 8 8 10 10 5 5 10. The build derives tine indices at compile time. Two reasons, and the second matters more.
A tine index is only meaningful against one layout, so if the physical tine order turns out to be wrong, note names re-derive and indices would all be garbage. And a human can check C4 C4 G4 G4 by humming it. Nobody can check a row of tine numbers, and a review gate nobody can perform is decoration.
Honest About What Is Not Verified
Thirty melodies shipped. Six are transcription-confident. The other twenty-four carry a flag, and that flag appears on the browse card in plain sight, not hidden behind a tap. The physical tine layout itself has not yet been confirmed against a real instrument, and that is recorded in the code, the roadmap and the app.
An app whose entire pitch is that it tells you the truth about difficulty does not get to be coy about its own uncertainty.
Bring Your Own Tab
The catalog can only ever contain what is legally free, which is the wrong shape for the songs people actually want. So the app accepts pasted tabs. Any kalimba number tab gets the same rating, the same generated explanation, the same best-fit transposition and the same player, and it never leaves the device.
The parser is permissive on purpose. It reads what it can and reports what it could not, token by token, rather than rejecting a whole paste over one stray character. Somebody copying from a video description should not be punished for a typo.
The Build
Vite, React, TypeScript and Tailwind on Cloudflare Pages, with Capacitor for a native shell. Zod validates the catalog at build time and is then deliberately kept out of the browser bundle, because the client validates nothing at runtime. That alone saved 67 kB.
React Router was removed. Every published version carries a high-severity advisory with no clean gap between them, and the standing rule is that security failures block deploys and get fixed at the source rather than worked around. The app used four of its APIs, so it got about ninety lines of routing with a pure, tested path matcher instead. npm audit now reports zero vulnerabilities.
Audio is synthesised rather than sampled: five partials with per-partial decay through the Web Audio API. No sample licence to clear, no asset weight, and the code says plainly that it is a plausible pluck rather than a measured model of any real instrument.
The app makes zero third-party requests. No ads, no accounts, no analytics, no backend, no fonts CDN. The content security policy has an empty allowlist because there is nothing to allow. Fonts are self-hosted as woff2. The full catalog is bundled, so it works offline after first load, and service worker registration is skipped on native where the assets are already local.
130 tests, every difficulty factor isolated by its own synthetic fixture, and a suite that pins the rendered explanation sentences character for character. That last one exists because two genuine copy defects survived a fully green test run and were only caught by reading real output.


