Skip to content

About

A modern, minimalist web app that analyzes song lyrics to determine mood, vibe, and emotional insights. Built with Next.js, TypeScript, and Tailwind CSS.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

SongAnalyzer

Explore a track, read permitted local audio and lyrics, and follow listening links.

SongAnalyzer is a Next.js music workbench. Identify (/identify) computes constellation fingerprints in a browser worker and queries the existing catalog; it requires a configured, appropriately licensed index. Optional AudD recognition sends a clip to the external provider only after a named disclosure and explicit confirmation. Analyze (/analyze) reads user-pasted lyrics through the existing hybrid keyword/transformer pipeline and measures local audio through the MIR worker with a DSP fallback. Discover (/discover) currently offers track metadata and listening links. Remote preview analysis and new audio-derived persistence/indexing are disabled until recording permissions can be verified server-side. Existing records and access rules remain intact. Live: song-analyzer-roni-altshulers-projects.vercel.app


What you can do

Identify a song Choose a permitted clip or explicitly start the microphone. Browser-computed hashes query the existing catalog; raw audio is not sent for this lookup. Optional, configured AudD fallback names the provider and asks before sending the clip. A catalog outage is distinct from a genuine no-match.
Paste lyrics Hybrid transformer + keyword engine returns mood, vibe, energy, sentiment, themes, and a per-engine provenance trail. Heuristic confidence values, dominant emotion mapped, mood color computed server-side; confidence is not measured accuracy.
Upload audio Choose a local file you own or have permission to analyze. The real MIR worker estimates tempo, beat grid, key, timbre and valence/arousal, with the existing DSP fallback. The file and audio insights stay in the current browser session: no fingerprint indexing, server saves or sharing. These estimates are not validated accuracy scores.
Listen to a passage A local recording opens selectable timed windows, keyboard/pointer seeking and playback that stops at the selected window’s end. Passage detail measures RMS/peak from locally decoded waveform PCM and counts only the existing estimated beat-grid ticks. Windows are listening aids, not inferred verses or choruses.
Discover similar songs Fresh remote audio analysis and catalog growth are paused. Track selection still provides metadata and listening links. The existing similarity read endpoint and saved vectors are preserved; no new recommendations or features are fabricated.
Search a song Typeahead against Spotify (Client Credentials) returns attributed metadata, cover art and direct item links, without preview URLs. Genius enrichment for IDs and album info only — never lyrics, by ToS. MusicBrainz + AcousticBrainz fill in open audio features when available.
Follow artist credits Selected tracks with source artist IDs link each credited artist to a provider-qualified profile (/artists/<provider>/<id>). Profiles retain known track metadata in this tab, show an explicit Person/Group/Unknown type, and use an accessible fallback when permitted official imagery is unavailable. Legacy saved rows cannot yet connect artist identities or related readings.
Share a result Persisted lyrics readings and existing saved records can be shared by permalink (/share/<slug>). New audio readings are not persisted. Share images require configured backing services; the known missing-config OG/Twitter 500 remains a follow-up.
Mood Atlas A public dashboard (/atlas) aggregating every visible analysis into a global mood distribution, browseable genres, per-artist mood-over-time, and theme clouds.
Combined view Compare separately supplied text and local audio with visible input size, filename and projection basis. Estimated proximity describes distance between supported valence/arousal coordinates. It is withheld when coordinates are unavailable; evidence explains the mapping and its limits. Song identity and model accuracy are not verified.
Mood-color cascade When a result lands, --accent-from / --accent-to / --accent-glow are written to <html> and every primitive (cards, buttons, badges, charts, hero glow) repaints in the song's color.
Multi-language Built-in detection across 11+ languages; auto-translates via Helsinki-NLP through the Hugging Face Inference API when a token is configured.
History Local result previews persist to localStorage; restoring one clears the current song/audio context and Share id. Full lyrics and a server analysis id are not retained locally, so a restored result can be copied but must be re-analyzed from its lyrics to enable sharing.

Recognition to exploration

A recognized or selected track keeps a visible exploration card across Identify, Analyze and Discover. It preserves track details, labels unavailable audio insights honestly, and offers a separate local-file reading, pasted lyrics and known listening links. Verified adapter source identity accompanies metadata; Spotify metadata includes the official full mark and a direct track link. Legacy catalog metadata has no verified source label. Clearing returns focus to search, and choosing a local file clears unrelated track attribution.

All remote recordings are denied before fetch or DSP, including similar results without provider IDs and direct hook calls. Server routes and ingestion helpers reject audio-derived writes even if a caller claims source: upload; the old preview seed command exits before any network/database access. Microphone and recognition operations are cancellable, reject double starts, and suppress late callbacks after navigation. Recognition controls stay disabled while the page prepares its event handlers, so a clip cannot be silently lost before hydration. AudD requires a separate disclosure/confirmation and a server consent marker. The footer describes each data path accurately.

The track exploration verification records the policy boundary, regression tests, browser checks and remaining live-catalog prerequisites.

Artist identity profiles

Artist credits preserve provider order, IDs, names and available artist-page links; comma-joined display names are never split into identities. Spotify and Genius credits have an Unknown person/group type. Only an explicit MusicBrainz artist type supplies Person or Group; artists from different providers are not merged by name. Profiles show tracks explored in this tab, with loading, empty, retryable storage-error and temporary-context states. The bounded tab cache stores metadata only, without audio, lyrics or analysis results.

No artist/biography/image API request was added. Track album art cannot become an artist portrait or band logo. The reviewed visual-asset registry is empty: real imagery needs matching artist identity, verified provenance, explicit display-permission evidence and attribution. The existing songs schema stores only a display artist string, so artist credits are retained in live source and resolver responses but are not persisted or used to join saved Atlas readings. Direct profile links opened without this tab’s context show that limitation.

The artist profile verification describes the asset contract, browser checks and remaining data prerequisites. Existing Atlas name routes and recording/privacy protections remain intact.

Local listening windows

The existing WaveSurfer player now synchronizes a selected passage with its waveform highlight, playback position and signal detail. It uses the installed WaveSurfer Regions/Timeline plugins and native media playback; no new runtime library or music service is required. Signal levels use unnormalized local PCM at 22.05 kHz across all channels. They describe digital amplitude, not perceived loudness. Clip-wide mood, key and tempo estimates remain below the player; no per-window musical model or accuracy claim is added.

Silent PCM is distinguished from unavailable measurements. The DSP fallback has no timed beat grid, so that count is unavailable rather than invented. Loading, waveform retry and playback-start recovery are explicit. Choosing a new file clears the previous player; leaving the page discards the session. Hiding the page pauses playback, and returning requires pressing Play again. Native media time updates also enforce the window boundary if animation frames are suspended. Timeline labels follow live light/dark theme changes.

The listening-window verification records limits, real browser interactions, screenshots and test results. The paired source-map-js patch evidence records the narrow lockfile repair for GHSA-68fv-2mgg-jv7q and remaining audit findings. The timeline theme readiness follow-up records the post-merge CI race, initialized-color test fix and fresh production browser checks. Runtime playback and analysis behavior are unchanged.

Comparison evidence

Combined view identifies the supplied text and local recording, and distinguishes weighted text-model emotion scores, preset mood-label positions and audio signal estimates. The word count describes analyzed text and can reflect a translation rather than the original input. Its estimated proximity describes geometric distance between these readings. It does not establish that they belong to the same song or measure accuracy or the songwriter's intended meaning. Missing, unsupported or invalid coordinates withhold the map and percentage while keeping each reading visible.

Open See comparison evidence for the projection basis, rounded coordinates and axis definitions. Audio coordinates describe the analyzed recording; selecting a listening window does not recalculate this point. Input filenames remain in the current browser session. The existing analysis engines, recording permissions and persistence boundaries are unchanged.

The comparison verification records the demonstrated clarity defect, regression coverage, production browser evidence and limits.

Architecture in one breath

The focused workbench interface pass documents the lyrics/audio start states, keyboard flow and desktop/mobile visual checks. See the October quality priorities for the next reliability, music evaluation, discovery and product-interface work.

┌──────────────────────────────────────────────────────────────────┐
│ Next.js 16 App Router · TypeScript · Tailwind v4 · React 19      │
├──────────────────────────────────────────────────────────────────┤
│  app/                                                            │
│    page.tsx              ── home (lyrics + audio modes)          │
│    share/[slug]/         ── permalink + edge-rendered OG image   │
│    atlas/                ── public Mood Atlas dashboard          │
│    artists/[provider]/[id]/ ── source identity + tab track context│
│    api/analyze           ── hybrid engine endpoint               │
│    api/songs/{search,id} ── Spotify/Genius/MB/AB orchestration   │
│    api/analyses/share    ── mark-public + slug return            │
│    api/auth/callback     ── Supabase OAuth code exchange         │
│    components/ui/        ── Card, Button, Tabs, Badge, Meter,    │
│                             Tooltip, Modal, Skeleton, Toast,     │
│                             Spectrum                             │
│    components/           ── SongHero, SongSearch, CombinedView,  │
│                             WaveformPlayer, EngineProvenance,    │
│                             MoodRadarV2, AnalysisResults, …      │
│    providers/            ── MoodThemeProvider, theme-provider    │
│  lib/                                                            │
│    analysis/             ── keyword + transformer engines, blend │
│    sources/              ── spotify, genius, musicbrainz,        │
│                             acousticbrainz, resolveSong          │
│    db/                   ── songs, analyses, shares, store-adapter│
│    supabase/             ── client, server, admin, middleware    │
│    seeds/                ── atlas-seed-lyrics + builder script   │
│  supabase/migrations/    ── schema, RLS, atlas_aggregates view   │
└──────────────────────────────────────────────────────────────────┘

Tech stack

  • Framework: Next.js 16.3.8 (App Router) on Turbopack
  • Language / runtime: TypeScript 5, React 19, Node 20+
  • Styling: Tailwind v4 with CSS-variable @theme tokens. Display: Instrument Serif. Body: Inter. Mono: JetBrains Mono.
  • Primitives: Radix UI (Dialog, Tabs, Tooltip, Slot, Popover) + Framer Motion (LazyMotion + domAnimation) + Sonner toasts
  • Persistence + auth: Supabase (Postgres, RLS, Auth, Storage) via @supabase/ssr
  • Analysis: Hugging Face Inference (@huggingface/inference) — j-hartmann/emotion-english-distilroberta-base primary, bhadresh-savani/distilbert-base-uncased-emotion fallback
  • Audio: Web Audio API + wavesurfer.js@7 (lazy-loaded)
  • External data: Spotify Web API (Client Credentials), Genius (metadata only — ToS), MusicBrainz, AcousticBrainz
  • Charts: Recharts (Atlas dashboard only)
  • Color extraction: node-vibrant
  • Testing: Vitest + Playwright (E2E smoke)
  • Deployment: Vercel (Edge runtime for OG images)

The October 9 Next.js patch verification records the bounded 16.3.6 → 16.3.8 update, detailed audit delta and fresh music-flow browser evidence. Six Next.js advisory entries are removed from the audit; 27 other reported package findings remain, including 18 high findings. The Image Optimization SSRF advisory states apps without images.remotePatterns are unaffected, and this app has none configured. No exposed SSRF path or incident was established. This maintenance update preserves the existing music interface, analysis estimates, recording permissions and access rules.

Getting started

Prerequisites

  • Node.js 20+ (Vite 7 requires ^20.19.0 || >=22.12.0)
  • npm
  • (Optional) Docker + Supabase CLI for local Postgres

Install

git clone https://github.com/roni-altshuler/SongAnalyzer.git
cd SongAnalyzer
npm install

Environment variables

Optional integrations can be left unconfigured — lyrics and local audio analysis remain usable:

cp .env.example .env.local
Variable Without it…
HUGGINGFACE_API_KEY Transformer engine is skipped; keyword fallback runs alone. No non-English translation.
NEXT_PUBLIC_SUPABASE_URL / NEXT_PUBLIC_SUPABASE_ANON_KEY No persistence, no auth, no share URLs (/share/<bad-slug> returns a themed 404). Atlas overview/genre pages show unavailable readings and hide unknown totals.
SUPABASE_SERVICE_ROLE_KEY Lyrics persistence, metadata upserts and Atlas refresh are unavailable. Audio-derived writes are disabled even with this key.
SPOTIFY_CLIENT_ID / SPOTIFY_CLIENT_SECRET /api/songs/search returns 503 (spotify_not_configured); SongSearch shows an inline notice.
GENIUS_ACCESS_TOKEN Genius enrichment skipped in resolveSong; everything else still resolves.
AUDD_API_TOKEN The external AudD action is absent. Catalog misses and catalog unavailability have distinct states. AudD is a paid/trial provider, not an unlimited free production service; no account or paid usage was added.
UPSTASH_REDIS_REST_URL / UPSTASH_REDIS_REST_TOKEN Rate limiting falls back to an in-memory per-instance sliding window (fine for dev).
SUPABASE_LOCAL=1 Enables the RLS test suite (__tests__/rls.test.ts). Requires a running supabase start.

Develop

npm run dev      # http://localhost:3000 — boots in ~1.5s
npm run build    # production build
npm run start    # serve production build
npm run lint     # ESLint
npm test         # Vitest; local Supabase tests are gated on SUPABASE_LOCAL=1

Visit /dev/components for the design-system showcase — every primitive in every variant, with an interactive mood-color picker that repaints the page live.

The hybrid analysis engine

The /api/analyze route blends two engines:

  1. Transformer (lib/analysis/transformer.ts) — calls the HF Inference API with an 8-second AbortSignal.timeout. On 503 it falls back from the primary model to the secondary; on any further failure (rate limit, timeout, missing token) it returns null and the route records engines.transformer.status accordingly.
  2. Keyword (lib/analysis/keyword.ts) — deterministic, synchronous, always succeeds. Inherited verbatim from v1 so the existing test fixtures still pass.

lib/analysis/blend.ts merges them: when the transformer succeeds, its top emotion drives mood and sentiment; the keyword engine always provides themes; confidence is a 70/30 weighted average. The result includes a moodColor: { from, to, glow } triplet derived from the dominant emotion via lib/analysis/palette.ts, which the front-end pushes into CSS variables.

A SHA-256-keyed cache (lib/analysis/cache.ts) lets future re-analyses of the same lyrics short-circuit. The in-memory store is the default; a Supabase-backed store can plug in via setAnalysisCache(...).

Design system

The music workbench uses shared dark surfaces and a warm cream/off-white light palette, built from CSS variables registered in app/globals.css with Tailwind v4's @theme directive:

  • Surface depths: --bg-base, --bg-elev1, --bg-elev2, --bg-elev3
  • Text: --text-hi, --text-med, --text-low
  • Accents (live, mood-driven): --accent-from, --accent-to, --accent-glow
  • State: --state-success, --state-warn, --state-error
  • Easings: --ease-out, --ease-in-out

<MoodThemeProvider> lets any component call setMoodColor({ from, to, glow }) and have the gradient cascade everywhere — the analyze flow uses the engine-derived color, SongHero overrides it with the cover-art palette via node-vibrant, and the showcase page lets you pick manually. Text, action and focus colors resolve against the theme; the lyrics radar uses these same tokens without changing its heuristic values.

A synchronous head script applies a valid saved theme before paint. Without a saved choice, the theme follows live system changes; explicit choices persist and synchronize across tabs. If theme storage is denied, the system preference still initializes and toggling works for that visit. Decorative spectra render static bars until hydration and remain static for reduced motion. Missing pages retain the shell, typography and chosen theme. See the cross-page theme verification for complete production browser journeys, accessibility results and limits.

Data layer

Supabase Postgres schema (in supabase/migrations/0001_init.sql):

  • profiles — mirrors auth.users
  • songs — canonical track records keyed by Spotify / Genius / MusicBrainz IDs
  • analyses — saved lyrics readings and existing records, with system_seed, is_public, share_slug, and the full result jsonb
  • shares — view-count + cached OG image path

RLS is enabled across all tables. Public reads are gated on is_public OR system_seed; writes go through the service-role client (lib/supabase/admin.ts) so permitted lyrics readings can be inserted by the existing API route. Audio/combined writes and fingerprint/feature ingestion now fail closed at the application boundary; no migrations or RLS changes were made. The SongRow ↔ Song adapter (lib/db/song-store-adapter.ts) bridges snake_case DB shapes to the camelCase resolver world.

To run Supabase locally:

npx supabase start              # boots a local stack via Docker
npx supabase db reset           # applies every migration + seed.sql
SUPABASE_LOCAL=1 npm test       # adds the RLS suite to the test run

Multi-language

Built-in detection for: Spanish, French, German, Italian, Portuguese, Russian, Chinese, Japanese, Korean, Arabic, and Hebrew. When a non-English language is detected and HUGGINGFACE_API_KEY is set, the lyrics are translated via Helsinki-NLP models before analysis.

Deployment

The app is Vercel-ready. The OG image route at /share/[slug]/opengraph-image runs on the Edge runtime; everything else on Node. Add the env vars above to your Vercel project; the Supabase migrations need to be pushed separately:

npx supabase db push            # against a hosted project

Or deploy with one click:

Deploy with Vercel

Mood Atlas

The Mood Atlas is a public, research-flavored dashboard at /atlas that aggregates every visible (public or system-seeded) analysis into cross-catalog views:

  • Overview (/atlas) — global mood distribution, browseable genre tiles, top artists.
  • Per-artist (/atlas/artist/[slug]) — mood-over-time area chart of the artist's discography, mood mix, and a clickable list of every analyzed song.
  • Per-genre (/atlas/genre/[name]) — mood distribution, top artists in the genre, and a weighted theme cloud.

The atlas reads from a materialized view (atlas_aggregates) plus an analyses_with_song convenience join, both created by supabase/migrations/0002_atlas_view.sql and 0003_atlas_view_helpers.sql. The overview and canonical genre pages distinguish a failed read from a successful empty catalog. Unknown totals are hidden, and both states offer the analysis workbench. The query helpers opt into reporting errors for these pages; query filters, existing callers, schema and access rules are preserved.

Seeding the atlas

The Mood Atlas ships with ~60 synthetic-artist analyses so the dashboard looks populated on day one. Seed lyrics live in lib/seeds/atlas-seed-lyrics.ts; a deterministic builder script emits the corresponding SQL.

Regenerate supabase/seed.sql:

# emit seed SQL (deterministic — same input produces identical output)
npx tsx lib/seeds/build-seed-sql.ts > supabase/seed.sql

Apply the seed to a local Supabase stack:

npx supabase db reset       # re-applies every migration + seed.sql

Refreshing the materialized view

atlas_aggregates does not auto-refresh. After seeding (or after any bulk insert of public analyses) run:

select public.refresh_atlas_aggregates();

in the Supabase SQL editor (or psql). The function is security definer, so the service role can call it from server code if/when we add an admin RPC.

Scheduling nightly refreshes (optional)

Supabase exposes pg_cron as an opt-in extension per project. We do not enable it in the migrations — flipping it on is a project-level decision. When you're ready:

-- in the Supabase SQL editor, project owner
create extension if not exists pg_cron;

select cron.schedule(
  'refresh-atlas-aggregates',
  '17 3 * * *',                            -- nightly 03:17 UTC
  $$ select public.refresh_atlas_aggregates(); $$
);

The seed corpus is intentionally synthetic (fictional artist names, original lyric snippets) — no copyrighted material is shipped or stored. Genius lyrics are never fetched or persisted by design; the Genius adapter only consumes IDs and metadata.

Contributing

Issues and PRs welcome. Run npm test before opening one — tsc --noEmit and npm run lint are part of CI.

Author

Roni Altshuler


Made with care using Next.js, Tailwind, Supabase, and a soft spot for editorial typography.

About

A modern, minimalist web app that analyzes song lyrics to determine mood, vibe, and emotional insights. Built with Next.js, TypeScript, and Tailwind CSS.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages