Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

7 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Signal — music recommendation engine

A full-stack app: search a track/artist/genre, or upload an audio file, and get back a ranked playlist with 30-second previews.

Why not the Spotify API?

Short answer: you can't build this on Spotify anymore. On 27 November 2024 Spotify locked Recommendations, Audio Features, Audio Analysis, and Related Artists behind grandfathered access only — any app created after that date gets a flat 403, and Spotify has stated this won't change. This build uses free alternatives instead:

Need Spotify (blocked) Used here
"Tracks similar to X" recommendations Last.fm track.getsimilar / artist.getsimilar (real collaborative-filtering data from actual listeners)
Genre/mood tags audio-features Last.fm tag data (track.gettoptags, tag.gettoptracks)
Analyze a raw audio file audio-analysis From-scratch DSP: tempo, RMS energy, spectral centroid, zero-crossing rate
Cover art + 30s preview Spotify preview URLs (also removed) iTunes Search API — free, no key, no auth

Last.fm's API key is free and instant (no approval wait) at https://www.last.fm/api/account/create.

How the recommendation engine actually works

Search path (backend/src/routes/recommend.js + services/ranking.js):

  1. Resolve the query to a real track via Last.fm search.
  2. Pull three independent signals: direct track-similarity (track.getsimilar), top tracks from similar artists (artist.getsimilarartist.gettoptracks), and top tracks sharing the seed's genre tags (tag.gettoptracks).
  3. Score every candidate on a weighted blend of: source reliability, Last.fm's own match score, tag overlap with the seed, and log-scaled popularity (so the playlist isn't just the 20 most famous songs on Earth).
  4. Deduplicate, cap tracks per artist at 2 for variety, return the top N.

This triangulates from three real, complementary data sources rather than one black-box score — which is the honest way to maximize accuracy without Spotify's proprietary model.

Upload path (services/audioAnalysis.js):

  1. ffmpeg decodes the file to mono 22.05kHz PCM (first 60 seconds only — plenty for stable features, keeps it fast).
  2. Real signal processing extracts BPM (autocorrelation-based tempo tracking), energy (RMS), brightness (spectral centroid via a small from-scratch FFT), and percussiveness (zero-crossing rate).
  3. A rule-based lookup maps that feature set to genre/mood tags (e.g. high tempo + high energy → dance, electronic).
  4. Those tags feed the same tag-based ranking used in the search path.

Being straight about the limitation: step 3 is a hand-written heuristic, not a trained classifier — there's no free equivalent of Spotify's ML audio model. It's honest signal processing, and it's genre-reasonable, but it won't match Spotify's old accuracy on subtle mood distinctions. The features (BPM, energy, brightness) are shown in the UI so you can see exactly what it detected. If you want to close that gap later, the natural upgrade is running a pretrained audio-tagging model (e.g. Essentia's TensorFlow genre models) in a small Python microservice — that's a real infra addition, not a free-tier drop-in, so it's left out of this build by design.

Project structure

backend/    Node + Express API (Last.fm/iTunes proxy, ranking, audio analysis)
frontend/   React + Vite app (the "Signal" UI)

Running it locally

Requires Node.js 18+ (for native fetch) and ffmpeg installed on your system (brew install ffmpeg / apt install ffmpeg / choco on Windows).

# Backend
cd backend
cp .env.example .env        # then paste in your Last.fm API key
npm install
npm run dev                 # http://localhost:8080

# Frontend (separate terminal)
cd frontend
cp .env.example .env
npm install
npm run dev                 # http://localhost:5173

Open http://localhost:5173.

Deploying

  • Backend: any Node host with ffmpeg available — Render, Railway, or a small VPS all work well. Set LASTFM_API_KEY and CORS_ORIGIN (your deployed frontend URL) as environment variables. On Render/Railway, ffmpeg needs to be present in the build image — Railway's Nixpacks and Render's Docker builds both support installing it via an apt buildpack/Dockerfile step (apt-get install -y ffmpeg).
  • Frontend: Vercel, Netlify, or Cloudflare Pages — plain static Vite build (npm run build → deploy frontend/dist). Set VITE_API_BASE to your deployed backend URL.

Design

Off-white/grey palette, zero border-radius, everything styled like analog audio equipment (VU-meter waveform, transport-style play button, mono data readouts for BPM/match %). No component libraries — hand-rolled CSS with variables in frontend/src/styles/theme.css if you want to retheme it.

Known limits worth knowing about

  • npm audit on the frontend will flag a moderate esbuild advisory pulled in by Vite 5 — it only affects the local dev server (a malicious site could read dev-server responses while npm run dev is running), not the production build. Fix it with npm audit fix --force (bumps to Vite 8) if you want; it wasn't force-upgraded here since that's an untested major version jump.

  • Last.fm and iTunes are both free tiers with real (generous but finite) rate limits — the backend caches lookups for 30 minutes by default (CACHE_TTL_SECONDS) to stay well within them.

  • Not every track has an iTunes preview match — those show a disabled play button rather than a broken one.

  • Audio analysis quality depends on the uploaded file's mix/mastering; very quiet or highly compressed masters can skew the energy reading.

About

Music Recommendation website that let users find new songs based on thier music taste!.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages