← Back to home

Orion

Spatial music discovery map

Role
Product Design · Interaction
Tools
Figma, React, React Flow, Three.js, Claude Code
Timeline
2025

Music tools organize by metadata — title, artist, date added. But that's not how we experience music. We experience it as feeling: this song is dark and driving, that one is bright and weightless. As an EDM listener, I kept wanting to answer questions my library couldn't: what does my taste actually look like? Which songs live near each other in vibe, not in a folder?

Orion maps a music library spatially. Every song is plotted on a 2D canvas by its audio features — energy, mood, BPM, key — so proximity means similarity. You explore your library by feeling, then build DJ sets by wiring songs together into a route across that space.

Quantifying vibe

The obvious data source was Spotify's audio features API. It no longer exists — Spotify deprecated it, and capped new developer apps at five users, killing any public product built on their data. I found a replacement in SoundNet, a name-based audio analysis API, and built a caching layer so every track is analyzed exactly once. A reconciliation pipeline handles the messiness of name-based search: retry cascades, duration cross-checks, corroboration against a second source.

But the more important decision was one of language. SoundNet returns terms like valence and danceability— API vocabulary, not human vocabulary. Nobody hears a song and thinks “high valence.” So the interface never shows a single API term. Every feature is expressed as a pair of semantic poles:

Asset needed

GRAPHIC — semantic vocabulary table as a designed visual, not a screenshot: Energy → Chill / Intense · Mood → Dark / Bright · BPM → Slow / Fast · Danceability → Mellow / Groovy · Acousticness → Electronic / Acoustic

The axes read as vibes, not measurements. That single translation is what makes the map feel like taste instead of data.

The overplotting problem

The map has one non-negotiable rule: songs always occupy their true positions. If the map lies about where a song lives, the entire thesis collapses.

Then I loaded 150 house tracks and watched the map collapse anyway — into a single dense blob. House music clusters: energy, mood, BPM, and danceability all sit in narrow ranges, and SoundNet rounds every feature to an integer, so songs with energy 75 and 76 sat 60px apart on a 6,000px canvas with nothing to break the tie.

The standard fix is a force-directed layout — let songs push each other apart until everything is legible. I rejected it immediately. Force-directed layouts displace points from their true positions, which is exactly the lie the product can't tell. Random spread was rejected for the same reason. This constraint made the problem genuinely hard: I had to add legibility without adding fiction.

What shipped is a three-layer system, and every layer is truthful:

Density-based axis scaling. Each half-axis is histogram-equalized, so concentrated value ranges get more canvas. The transformation is monotonic — order is always preserved — and pinned at the quadrant boundary so the four vibe zones hold. For the house library, this stretched the crowded energy 65–85 band from ~1,080px to ~1,900px.

Jitter as measurement uncertainty.SoundNet's integer rounding hides real sub-integer differences between songs. So each song gets a deterministic offset within ±0.5 — inside its own integer cell, seeded by a hash of its features, identical across reloads. This isn't fake positioning; it's visualizing the uncertainty the data source introduced. Secondary features (danceability, acousticness, BPM) blend into the offset, so the separation within a cell is itself meaningful.

Stack badges for songs with genuinely identical features — grouped honestly, never hidden, expandable via popover.

The result on the 150-song house library: zero order violations, minimum separation up from 0px to 3.8px, and no song ever displaced from where it truly belongs.

Asset needed

IMAGE — before/after: the clustered house-library blob vs. the equalized map. Side by side.

The final layer is interaction. Zooming in doesn't grow the songs — they counter-scale, holding constant screen size while the canvas spreads beneath them. Zoom becomes a control for separation rather than magnification: pinch in and songs gain breathing room while keeping their exact positions, the same technique map tools use for POI markers. I found it by accident, testing the app at 175% browser zoom and noticing the spacing suddenly felt right.

Asset needed

VIDEO — 10s: zooming into the dense cluster, nodes counter-scaling, space opening up between them, then a node morphing circle → pill → card.

Sets as routes, not playlists

Exploration alone wasn't the product. DJs don't just find songs — they sequence them, and a sequence has a shape: where it starts, how it builds, where it peaks. A playlist UI (search, add, reorder rows) throws all of that spatial information away the moment you commit a song to a list.

So set building happens on the map. You wire songs together node-to-node, and the set becomes a literal route across vibe space — songs are places, the chain is the path, and you can see at a glance whether your set climbs steadily into intensity or whiplashes across quadrants. Google Maps for a DJ set.

Asset needed

VIDEO — 12s: dragging a wire from a socket, the dashed wire flashing compatibility color on target hover, latching, then two more connections forming a visible route across the map.

Wires created a design problem playlists never face: what happens when you cut one? A destructive delete punishes exploration — cut one connection mid-experiment and downstream work vanishes. Instead, cutting is non-destructive: downstream songs orphan as a group, dimmed with dashed borders, their internal wires intact, collected in a Disconnected panel. Re-wire them back anytime. Even deleting the head song just orphans the whole chain and offers song #2 as the new anchor, one tap to recover. No confirmation modals anywhere, because no action can lose work.

The same principle — never leave spatial context — shaped the Deck View. I explored a dedicated playback page and killed it: navigating away breaks the mental model of being somewhere in your library. Deck View is a side panel; the map stays under your feet.

Seeing the journey

Build mode is about decisions — every wire wears its compatibility color, every candidate song glows. But once a set exists, all that decision-support becomes noise. You don't want to evaluate the set; you want to see it.

Flow mode strips everything back. Non-chain songs fade away, wires turn to dark physical cable, and a single pulse of light travels from the head of the set through every song to the tail, tracing the journey in order across vibe space. It's the payoff moment of the whole product: the shape of your set, drawn as light, moving through the map of your taste.

Two modes, two jobs. Build mode informs the next decision; Flow mode shows you what you made.

Asset needed

VIDEO — 12s: toggling Flow on, the map dimming, the strobe pulse traveling head-to-tail along the chain. This is the most cinematic capture in the case study — worth extra takes.

Informing, not alarming

Compatibility scoring produced my favorite failure. I implemented the real DJ-standard formula — Camelot wheel key relationships plus BPM deltas, weakest-link rule — and wired the tiers to green, amber, and red. Then I built a set and watched the map turn overwhelmingly red. Accurate harmonic mixing rules are strict; most song pairs genuinely are weak matches.

The tempting fix was loosening the thresholds until the map looked friendlier. But then “musically accurate compatibility” would be a claim I couldn't stand behind. The scoring wasn't wrong — the communicationwas. Red means danger, and a weak key transition isn't danger; it's a characteristic. DJs make distinct key changes on purpose.

So the formula stayed strict and the visual language softened. Red became a muted coral. The compatibility card frames each tier as a description rather than a grade: a strong pair is a “smooth harmonic blend,” a weak one a “distinct key change.” The map went from alarming to informative without a single dishonest number.

Asset needed

IMAGE — side-by-side: the same set with red wires vs. coral, plus a close-up of the compatibility card.

From eye candy to identity

The Deck View's particle visualizer started as decoration — a Three.js flourish to make the playback panel feel alive. It bothered me that it meant nothing.

The answer came from physics. Chladni patterns are the geometric figures that form when a surface vibrates at a resonant frequency — different frequencies, different figures. I rebuilt the visualizer as a Chladni simulation driven by each song's cached audio features: the features set the pattern's complexity, and a hash of the track selects its specific mode, so every song resolves into its own stable figure. BPM drives motion, energy drives amplitude, mood shifts color.

The decoration became identity. Every song in Orion has a visual fingerprint derived from its actual sound — play two songs and you're looking at two different objects, not one animation with the colors swapped.

Asset needed

VIDEO — 10s: the particle field forming a pattern as a song plays.

Asset needed

IMAGE — 2×3 grid: six different songs' Chladni patterns, side by side. This is the money shot — it proves "fingerprint" instantly.

Reflection

Almost none of these problems had existing solutions to reference. Overplotting under a truthfulness constraint, non-destructive graph editing, honest scoring with humane communication — they only exist because Orion treats music as space, and they had to be identified before they could be solved.

The through-line is translation: valence becomes Dark/Bright, uncertainty becomes jitter, a setlist becomes a route, a song's audio becomes a resonance pattern. Turning abstract qualities into things you can see and touch is the design work I want to keep doing.