// Package mood maps folksonomy tags (from the track_tags cache, #1490) to a // small set of canonical mood buckets (#1534, milestone #160 Opt 2b). Both the // taste-profile builder and the recommendation scorer classify a track's tags // through Of() so a track's mood is derived the same way on both sides. // // The vocabulary is intentionally narrow: only words that clearly denote mood // (not genre). A track with no mood-word tags contributes nothing — the mood // facet is a supplement whose coverage grows with folksonomy enrichment // (thin on MusicBrainz-only, richer once Last.fm is keyed). package mood import "strings" // vocab maps a lowercased folksonomy tag to its canonical mood bucket. Several // synonyms collapse to one bucket so "sad" / "melancholy" / "melancholic" all // score the same dimension. var vocab = map[string]string{ // melancholic "melancholic": "melancholic", "melancholy": "melancholic", "sad": "melancholic", "sombre": "melancholic", "somber": "melancholic", "wistful": "melancholic", "bittersweet": "melancholic", "mournful": "melancholic", // energetic "energetic": "energetic", "energy": "energetic", "upbeat": "energetic", "uplifting": "energetic", "party": "energetic", "anthemic": "energetic", // chill "chill": "chill", "chillout": "chill", "chilled": "chill", "mellow": "chill", "relaxing": "chill", "relaxed": "chill", "calm": "chill", "laid-back": "chill", "laidback": "chill", "soothing": "chill", // aggressive "aggressive": "aggressive", "angry": "aggressive", "intense": "aggressive", "heavy": "aggressive", "brutal": "aggressive", // dark "dark": "dark", "moody": "dark", "brooding": "dark", "haunting": "dark", "eerie": "dark", // dreamy "dreamy": "dreamy", "ethereal": "dreamy", "atmospheric": "dreamy", "ambient": "dreamy", "hypnotic": "dreamy", // happy "happy": "happy", "cheerful": "happy", "feel good": "happy", "feel-good": "happy", "feelgood": "happy", "joyful": "happy", "fun": "happy", // romantic "romantic": "romantic", "sensual": "romantic", "sexy": "romantic", "sentimental": "romantic", } // Of returns the distinct canonical mood buckets present in tags, lowercasing + // trimming each tag before lookup. Returns nil when no tag maps to a mood, so // callers get no mood signal for a track with only genre/descriptive tags. func Of(tags []string) []string { var out []string seen := make(map[string]bool) for _, t := range tags { key := strings.ToLower(strings.TrimSpace(t)) if m, ok := vocab[key]; ok && !seen[m] { seen[m] = true out = append(out, m) } } return out }