// 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"). */ export const IDENTIFY_ENGINE = 2; /** 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; }