// What a scanned record calls ITSELF — the name the catalog should be asked // about. // // The folder is the ripper's naming. It carries catalog numbers, rip // descriptors, years and initials: "[WARP CD 156] Battles - Mirrored - 2007", // "AJJ_ChristmasIsland", "ACPP". The tags are the record's own claim: // "Mirrored", "Christmas Island", "Prospect Park, Brooklyn NY (7.12.2011)". // Across the reference library 501 of 515 records carry a tag album and 315 of // those disagree with their folder — so asking a provider about the folder is // asking the wrong question, and no amount of cleaning turns "ACPP" into the // right one. // // This is deliberately NOT used for the Soulseek search. Peers are searched by // FILE NAME, where the ripper's descriptors are exactly what finds a match — // the folder that fails at Deezer is the one that found "[WARP 156CD] Battles" // on the network. Catalog lookups want the claim; file search wants the naming. /** Which generation of the identify logic produced a cached answer. * * A cached MATCH is good forever. A cached "nothing found" is only as good as * the thing that did the looking, so when identification learns a new way to * find a record, every stored negative becomes a stale opinion — and nothing * re-asks, because the cache is never revisited once written. * * Bump this whenever identify gains ground. Negatives stamped with an older * number are dropped on the next session load and asked again. * * 1. Deezer try-list, MusicBrainz fallback. * 2. + the artist's own catalogue, ranked by title similarity — finds records * whose folder name no release carries ("1989 Pauls Boutique"). * 3. + the artist the TAGS name, asked when the folder's artist found nothing * or the wrong record, and + a try-list that refuses to strip a title down * to its catalogue number ("Band Of Gypsys (SICP 2643)" → "(SICP 2643)"). * 4. + the title without a trailing release-type word ("Willoughby's Beach EP" * → "Willoughby's Beach"). Both providers require every word of the * phrase, so the one appended word hid a record each of them holds. * 5. + the FOLDER as a second witness, asked after the record's own claim. * Tags carry typos the folder does not ("Nonagon Infiniry" on all nine * files, in a correctly-spelled folder), and until now one wrong tag was * the only thing any provider was ever asked about. * 6. + the record's TRACK COUNT breaks the tie when one title names several * MusicBrainz release groups ("Murder of the Universe" is a 21-track Album * and a 1-track Single, both scored 100, in an unstable order), and a * provider request that FAILED no longer caches as "nothing found". * 7. The last resort stopped being a title search dressed as a comparison. It * ranks the artist's WHOLE MusicBrainz discography — complete, where * Deezer's listing omitted EPs entirely — and decides on every fact the * scan has: wording, track count, year, the TRACKLIST, and the record's own * ARTWORK hashed against the discography's covers. A name is one fact about * a record and the one most often wrong; `Rock Bottom 7"` scores 0.667 * against the release actually called "Rock Bottom / Octopus", and 2 of 2 * on its songs. * * BUMP THIS AFTER THE RUST BUILD LANDS, never before. Vite hot-reloads this * constant instantly while the capability it describes lives in * `metadata::identify_titles`, so bumping it against a running old binary * makes that binary sweep the negatives and re-stamp the record on screen as * freshly looked at by code that learned nothing. */ export const IDENTIFY_ENGINE = 7; /** The value most of the list agrees on, ignoring blanks. Ties go to the first * value seen, which keeps the result stable for a re-render. */ export function majority(values) { const tally = new Map(); for (const v of values) if (v != null && v !== "") tally.set(v, (tally.get(v) ?? 0) + 1); let best = null; let n = 0; for (const [v, c] of tally) if (c > n) { best = v; n = c; } return best; } /** The album name to ask a provider about: the tags' majority, the folder as * fallback. * * A FOLDED release is the exception and takes its folder's name. Its parts' * tags describe the parts ("Analord 01"…"Analord 11"), so a tag majority * would title the whole set after whichever volume carried the most tracks. * * `record` is a scanned album: { parts?, title, tracks: [{ album }] }. */ export function albumNameOf(record) { if (!record) return null; if (record.parts) return record.title; return majority((record.tracks ?? []).map((t) => t.album)) ?? record.title; } /** The artist to ask about. A record nested under an artist folder is named by * that folder; a loose one has only its tags to go on. */ export function artistNameOf(record) { if (!record) return null; if (record.depth >= 2) return record.artistFolder; return majority((record.tracks ?? []).map((t) => t.artist)) ?? record.artistFolder; }