Inspiration
Moodwave: Music That Feels Like You Do
Inspiration
We've all had that moment when you open Spotify, stare at the search bar, and draw a complete blank. You don't want a genre. You don't want a playlist someone else curated. You want something that gets how you're feeling right now.
That frustration is what sparked Moodwave. We wanted to build something that removes the friction between emotion and music entirely. Instead of browsing, you just describe yourself — and the music finds you.
We were also fascinated by the science behind it. Paul Ekman's research on basic human emotions gave us the foundation: the idea that human feeling can be distilled into a small, universal set of categories. That felt like the perfect bridge between natural language and music.
What We Learned
This project taught us how surprisingly well AI can understand human emotion from plain text. A few lines of feeling, messy, unstructured, and personal, can be mapped to something as precise as audio features on a $[0.0, 1.0]$ scale.
We also learned how to think about music mathematically. The Spotify API represents every track as a vector of audio features:
$$\vec{s} = (\text{valence},\ \text{energy},\ \text{danceability},\ \text{acousticness})$$
Each emotion maps to a target point in this 4-dimensional space. For example, joy targets:
$$\vec{s}_{\text{joy}} = (0.85,\ 0.80,\ 0.75,\ 0.25)$$
While sadness targets the opposite end:
$$\vec{s}_{\text{sadness}} = (0.20,\ 0.30,\ 0.25,\ 0.70)$$
The Spotify Recommendations API then finds the 10 tracks whose audio profiles sit closest to our target vector — essentially a nearest-neighbor search in audio feature space.
How We Built It
We split the work across four roles and two core pipelines that run in parallel after emotion detection.
Pipeline Overview
$$\text{User Input} \xrightarrow{\text{HuggingFace}} \text{Emotion} \xrightarrow{} \begin{cases} \text{mood_map} \rightarrow \text{Audio Features} \ \text{genre_map} \rightarrow \text{Genre Seeds} \end{cases} \xrightarrow{\text{Spotipy}} \text{10 Tracks}$$
Step 1 — Emotion Detection
We used the j-hartmann/emotion-english-distilroberta-base model from HuggingFace Transformers. It's a fine-tuned DistilRoBERTa classifier that outputs probabilities across 7 emotion classes based on Ekman's model: joy, sadness, anger, fear, disgust, surprise, and neutral.
Step 2 — Dual Mapping
The detected emotion feeds into two hand-crafted dictionaries simultaneously:
mood_map— maps each emotion to Spotify audio feature targets (valence, energy, danceability, acousticness)genre_map— maps each emotion to a list of genre seeds (e.g. sadness →["indie", "ambient", "acoustic"])
Step 3 — Spotify Recommendations
Both outputs are passed together into Spotify's Recommendations API via Spotipy. The API returns 10 tracks matching both the sonic profile and genre. We extract the track name, artist, Spotify URL, and 30-second preview URL from each result.
Step 4 — Flask Backend
A single Flask route /recommend accepts the user's mood as JSON, runs it through the full pipeline, and returns the 10 tracks to the frontend.
Step 5 — Frontend
Built with pure HTML, CSS, and vanilla JavaScript — styled with an Apple Music-inspired dark UI featuring a fixed sidebar, sticky topbar, scrollable track list, and a fully functional now-playing bar with seekable audio preview.
Challenges
Mapping emotion to sound is subjective. There's no ground truth for what "anger" should sound like musically. We iterated on the mood_map values multiple times, listening to results and adjusting. The final values reflect our best musical intuition, but a future version could learn these mappings from user feedback.
The Spotify API has strict rate limits. During testing, hitting the recommendations endpoint repeatedly led to 429 errors. We added error handling on the Flask side and rate-limit awareness in the frontend.
Emotion detection isn't perfect. Short or ambiguous inputs — like "I don't know" — sometimes return neutral when the user clearly has a stronger feeling. We handled edge cases by defaulting gracefully and always returning a playlist regardless.
Coordinating four people across AI, backend, Spotify, and frontend meant we had to define clean interfaces early. The contract between the backend and frontend — a simple JSON shape with emotion and tracks — kept everyone unblocked.
The Math Behind the Mood
The full recommendation score Spotify computes is proprietary, but conceptually it minimizes a distance in audio feature space:
$$d(\vec{s}{\text{track}},\ \vec{s}{\text{target}}) = \sqrt{\sum_{i=1}^{4}(s_i^{\text{track}} - s_i^{\text{target}})^2}$$
We provide the target $\vec{s}_{\text{target}}$ via our mood_map. Spotify does the rest.
Built With
- css
- github
- html
- huggingface-inference-(j-hartmann/emotion-english-distilroberta-base)-models-distilroberta-(emotion-english-distilroberta-base)-tools-&-platforms-git
- huggingface-transformers
- javascript-frameworks-&-libraries-flask
- languages-python
- pytorch-apis-spotify-recommendations-api
- spotipy
- studio
- visual
Log in or sign up for Devpost to join the conversation.