Music genre alchemy — turn a messy local music library into a structured one, entirely offline.
▶ Try it in your browser — the real dictionary, running client-side. Pick a corner of the taxonomy and get recommendations, or throw a mangled ID3 tag at the classifier and watch which step catches it.
Alchemy ships a taxonomy of 3,545 artists across 12 pillars and 76 sub-categories, an artist classifier built for the tags real files actually carry, and a discovery engine that recommends by neighbourhood instead of by calling an API.
At runtime: no network, no accounts, no telemetry, zero dependencies. The dataset was built with LLMs — see How this was built, which is also why the script that built it ships with it.
Extracted from OCTOPUS, a hi-fi local-files player for iOS, where it classifies libraries on-device and powers the Artist Discovery Robot.
Naive artist matching breaks on the tags people's files actually have. Every row below is a real failure mode this library fixes:
| Tag | Naive matcher | Alchemy |
|---|---|---|
Daft Punk |
da& punk — an unbounded ft→& rewrite |
Electronic › Synthpop / Electronic Pop |
Kraftwerk |
kra&werk |
Electronic › Synthpop / Electronic Pop |
Earth, Wind & Fire |
split to earth → matched the metal band Earth |
R&B / Soul › Funk |
Calvin Harris feat. Rihanna |
unknown primary → unmatched | Pop › Pop R&B Crossover (guest rescue) |
Rihanna feat.Calvin Harris |
glued marker → unmatched | Pop › Pop R&B Crossover |
(1998) Lisa Gerrard |
year prefix → unmatched | Amazing Vocals › Atmospheric / Art Pop |
AC/DC |
split on / → unmatched |
Rock › Classic Rock / Arena Rock |
The rules that make this work — delimiter-aware featuring clauses, space-flanked joiners, NFC normalization, whole-string-before-tokenize lookup — are exported so you can reuse them on your own data.
node examples/classify.mjs # runs exactly the table aboveZero runtime dependencies. Ships compiled ESM plus full TypeScript declarations, and the source alongside it.
npm install github:frankie-muller/alchemyNot on the npm registry yet — it will publish as
music-alchemy. The GitHub install above works today:preparebuildsdist/on install, so you get the same compiled output and types either way.The API may still move before
1.0, so pin a commit if you depend on it.
# or work on it directly
git clone https://github.com/frankie-muller/alchemy.git
cd alchemy && npm install && npm testAlchemy classifies metadata — it doesn't read files. Pair it with TagLib (or any reader you like) and you have the full chain: TagLib pulls tags out of anything you throw at it — MP3, FLAC, ALAC, M4A, OGG, WV, AIFF, WMA, APE, ID3v1 through ID3v2.4, Vorbis comments, APE tags — and Alchemy makes structural sense of whatever comes out.
That's the pairing built for genuinely chaotic libraries: three decades of formats, five tagging conventions, half of it ripped by someone else. TagLib handles the container. Alchemy handles the mess inside the tag.
import { classifyTrack, classifyLibrary } from 'music-alchemy';
classifyTrack({ id: '1', title: 'Around the World', artist: 'Daft Punk' });
// → { pillar: 'Electronic', subCategory: 'Synthpop / Electronic Pop',
// classifiedBy: 'alchemy', isStudioPerfection: true, ... }
// Whole library, grouped Pillar → SubCategory → tracks
const library = classifyLibrary(tracks);classifyTrack runs a three-step waterfall and always tells you which step decided, via classifiedBy:
- Alias washer — normalizes a raw genre tag (
"Drum n Bass","DnB","drum & bass") to a canonical sub-category. 691 mappings. - Alchemy dictionary — artist lookup when the genre tag is absent or unrecognized. A known artist outranks a genre tag, because a curated pillar+sub is a matched pair.
- Unmatched — quarantined rather than guessed at.
import { DiscoveryEngine } from 'music-alchemy';
const engine = new DiscoveryEngine(); // remembers what it showed you
const { recommendations } = engine.discover('Electronic', 'Ambient / Downtempo');Four widening tiers, stopping as soon as the slots fill:
- The local neighbourhood — other artists in the same sub-category.
- The bridges — 62 curated cross-pillar connectors (artists who genuinely span worlds).
- The metropolitan area — the rest of the parent pillar.
- Ouroboros — everything here has been seen; wipe memory and walk once more.
node examples/discover.mjs "Electronic" "Detroit Techno / Chicago House"import { normalizeArtistKey, tokenizeArtistCredit } from 'music-alchemy';
tokenizeArtistCredit(normalizeArtistKey('Jay-Z feat. Alicia Keys'));
// → ['jay-z', 'alicia keys'] — "Jay-Z" survives; the feat clause splits12 pillars · 76 sub-categories · 3,545 artists. Every artist carries a one-line biography and its known spelling variants.
| Pillar | Artists | Sub-categories |
|---|---|---|
| Rock | 524 | Classic Rock / Arena Rock (92) · Alternative / Indie (87) · Progressive / Experimental (73) · Punk / Post-Hardcore (64) · Post-Punk / New Wave (60) · Rock en Español (51) · Grunge (50) · Shoegaze / Dream Pop (47) |
| Classical | 445 | Symphonic / Orchestral (125) · Solo Instrumental / Sonata (101) · Contemporary / Minimalist (98) · Opera / Choral (97) · Conductors (13) · Period Performance / Early Music (11) |
| Electronic | 398 | Synthpop / Electronic Pop (56) · Drum & Bass / Jungle (54) · Ambient / Downtempo (49) · Breakbeat / Big Beat (40) · Deep & Tech House (39) · Minimal / Microhouse (38) · Progressive / Melodic House (35) · IDM / Leftfield / Electronica (35) · Detroit Techno / Chicago House (14) · Trance (11) · Disco / Nu-Disco (11) · EBM / Industrial (9) · Acid House (7) |
| Jazz & Acoustics | 380 | Acoustic / Bossa Nova (98) · Bebop / Hard Bop (83) · Free / Avant-Garde (54) · Cool / Modal (43) · Fusion / Jazz-Funk (38) · Vocal Jazz (33) · Big Band / Swing (16) · Acoustic Guitar (9) · Latin Jazz (6) |
| Hip-Hop / Rap | 363 | Current / Trap (95) · Core / Golden Age (88) · West Coast (75) · Conscious / Alternative (68) · East Coast (37) |
| Latin | 299 | Cumbia / Norteño / Regional (52) · Salsa / Merengue (51) · Flamenco / Español (51) · Latin Pop (45) · Bachata / Bolero (45) · Pop en Español (32) · Brazilian / MPB (13) · Tango (10) |
| Pop | 273 | Mainstream Pop (102) · Indie Pop / Alt Pop (82) · Pop R&B Crossover (69) · K-Pop (20) |
| Amazing Vocals | 229 | Jazz & Soul Powerhouses (64) · Spoken / Poetic Flow (57) · Atmospheric / Art Pop (56) · Intimate Singer-Songwriter (52) |
| Metal / Hard Rock | 204 | Heavy Metal / Hard Rock (43) · Progressive Metal (40) · Doom / Stoner / Sludge (39) · Classic Heavy Metal / NWOBHM (36) · Death / Black / Extreme Metal (29) · Power / Symphonic Metal (17) |
| R&B / Soul | 183 | Modern R&B (49) · Classic Soul / Motown (46) · Neo Soul (40) · Contemporary R&B (24) · Funk (24) |
| Reggae / Dub | 155 | Roots Reggae (54) · Dancehall (39) · Lovers Rock (27) · Studio Dub (21) · Ska & Rocksteady (14) |
| Reggaeton / Urbano | 92 | Reggaeton / Urbano (42) · Latin Trap / Urbano Moderno (40) · Producers / Beatmakers (10) |
Studio Perfection is not a pillar. It's a cross-genre overlay of 137 artists (1,344 names including aliases) picked by hand for production quality — records worth hearing on good equipment, regardless of genre. It answers a different question than "what genre is this," so it never competes with the taxonomy:
import { isStudioPerfectionArtist } from 'music-alchemy';
isStudioPerfectionArtist('Daft Punk'); // → trueEvery classified track carries it as a boolean (isStudioPerfection), so Steely Dan stays in Jazz and gets the flag.
| Dataset | Size | Purpose |
|---|---|---|
ALIAS_MAP |
691 | Raw genre-tag strings → canonical sub-category |
CONNECTORS |
62 | Artists who authentically bridge two or more pillars |
SUB_CATEGORY_META |
61 | Mood, energy, decades, description per sub-category |
Being straight about this, because "hand-curated" is the easiest lie in a dataset README:
The dictionary was built with LLMs — Claude and Gemini APIs — under human review. Sub-category by sub-category, a model proposed artists and wrote the one-line biographies; a human kept, cut, or re-filed them. 3,545 artists is not a number anyone types by hand, and pretending otherwise would misrepresent what you're getting.
What genuinely is hand-picked:
- Studio Perfection (137 artists) — a personal list about production quality. No model chose these.
- Amazing Vocals (229 artists) — the pillar exists because of a specific listening bias, and it was assembled the same way.
- Every pillar and sub-category boundary — the shape of the taxonomy, what deserves to be its own category, and which artist wins a judgment call.
So: the skeleton and the taste are human. The volume is machine, reviewed. The biographies are model-written prose, spot-checked, not individually fact-checked — treat them as blurbs, not citations.
This is why the harness matters more than the curation claim. npm run verify is a mechanical guarantee that holds no matter who or what wrote a line: every artist resolves to itself, in its own pillar, or CI fails. That's a promise the word "curated" can't make.
The tool used to build this ships with it. It's provider-agnostic on purpose:
ALCHEMY_API_KEY=sk-... node scripts/grow.mjs --pillar Electronic --sub "Acid House" -n 15ALCHEMY_PROVIDER anthropic (default) · openai · gemini · grok · custom
ALCHEMY_API_KEY your key
ALCHEMY_MODEL overrides the per-provider default
ALCHEMY_API_URL for `custom` — any OpenAI-compatible endpoint
custom points at anything that speaks the OpenAI chat shape — Ollama, llama.cpp, vLLM, OpenRouter, Together — so you can grow the dictionary against a local model, no key and no network. Adding a provider is a dozen lines in PROVIDERS.
It sends the model the artists already in that sub-category (so proposals are additive and on register), drops anything that already resolves anywhere in the dictionary, and writes proposals/<pillar>__<sub>.json. It never edits the pillar files. You read the proposal, delete what you disagree with, paste the rest in, and run npm test.
The harness catches structural breakage. It does not catch an invented artist — that part is human, and always will be.
The dictionary is guarded by a roll-call harness. It bundles the real modules and asserts that every artist in the dictionary finds itself, lands in its own pillar, and that every Studio Perfection name passes:
npm run verifyROLL CALL 3545 / 3545 primaries OK (169 resolved by collision policy)
STUDIO 1344 / 1344 curated names OK
COLLISIONS 780 duplicate keys in dictionary (curation backlog)
✅ ALL GREEN
It runs in CI on every push. No change that breaks self-match can land. This is the contract that makes the dictionary safe to grow.
Stated plainly, because a dataset that hides these is not worth trusting on anything else:
- 780 contested keys. Names claimed by more than one entry (
Airthe French duo vsAirthe jazz trio). Resolution is deterministic, never iteration-order luck: a primary name beats an alias, otherwise first-in-dictionary-order wins.npm run verifynames every one. - 16 sub-categories have no metadata — no mood/energy/description, so discovery falls back to generic tags. Full list in
npm run verifyoutput. Includes Detroit Techno / Chicago House, Trance, Acid House, Funk, Tango, Latin Jazz. - Coverage is opinionated and uneven. Classical has 445 entries; Acid House has 7. It reflects one listener's library and the sub-categories that got grown first — not a canon.
- Model-written biographies. Reviewed, not fact-checked line by line. Expect occasional confident nonsense in the prose; the pillar/sub-category placement is the part that was actually scrutinized.
- Western-leaning. Strong on Latin, reggaeton and rock en español; thin on African, Middle Eastern, and most Asian traditions outside K-Pop.
Early public release — read-only for now. Issues are open and genuinely wanted, especially misclassification reports ("you filed X under Y") and biography corrections, which are the highest-value feedback this project can get. Code PRs aren't being accepted yet; see CONTRIBUTING.md.
Fork it and grow it in the meantime — that's what scripts/grow.mjs is for, and the harness travels with the fork.
MIT — code and dictionary data. Artist names and their genre associations are facts, claimed by no one; the biographies are model-generated text released under the same terms. NOTICE.md spells out the provenance.
Built by Frank Müller.