Case Studies
Engineering Case Study

TuneFinder: Taste-Driven Music Discovery for DJs

Thousands of new releases processed every week. Scored against a taste profile built from years of actual mix history, by an engine that retunes its own weights from the verdicts I give it. The shortlist lands in a self-hosted web app, and every reason on every pick is composed from catalog facts. No LLM anywhere in the run.

Type Engineering Case Study
Domain AI · Automation · Data Pipelines
Stack Python · React · .NET · Azure
Status Live · Weekly
~4,000
candidates processed per week
15–25
relevant tracks surfaced
4
source fetchers enabled
0
LLM calls per run

The Problem

Crate digging is central to DJing. It is also, in 2026, a grind. Thousands of new releases land every week across Beatport, Juno Download, Bandcamp, Traxsource, and a dozen other platforms. The process is monotonous: open a genre page, scroll, click a track, listen for 20 seconds, decide if it fits, move on. A productive session might yield five to ten tracks worth buying.

Covering D&B, Breakbeat, UK Bass, UK Garage, House, and Electronica means tracking a wide scene. No single platform surfaces everything. And all of them optimise for what is commercially popular, not what fits a specific taste.

The question: can I teach a system what I like and have it do the digging for me?

The Insight

Years of mixes on SoundCloud already contain a detailed record of taste. Every track chosen to play, in any context, is a data point: artist, genre, BPM, energy level, all of it encoded in the tracklists.

Describing preferences to an AI in abstract terms is the obvious approach. It is also the wrong one. Point the system at what has already been played and let it infer the profile from real behaviour. The mix history is the taste profile.

System Architecture

TuneFinder has two halves: a taste layer that reads mix history and builds an artist profile, and a discovery layer that pulls new releases from the stores. These meet in a ranking pipeline, which feeds a deterministic report renderer, served through a self-hosted web app whose verdicts loop back into the scoring.

TASTE LAYER SOUNDCLOUD MIX HISTORY RSS + archived catalogue → .NET taste API on Azure DISCOVERY LAYER SOURCE FETCHERS ×4 Beatport v4 API · Bandcamp · Volumo · SoundCloud PROFILES + EXCLUSIONS ~4,000 CANDIDATES / WEEK TUNEFINDER ENGINE · MAC MINI M1 DEDUP & CROSS-SOURCE MERGE RANKER · 8 WEIGHTED SIGNALS weights retuned weekly from feedback REASONS · COMPOSED FROM CATALOG FACTS no LLM: every reason is a query, not prose DETERMINISTIC REPORT RENDERER LAUNCHD SUNDAY 09:00 · WEB-TRIGGERED RUNS · SERIALISED BY DATA LOCK WEB APP · PRIVATE, TUNNELLED React SPA served by FastAPI, through a Cloudflare Tunnel audition · verdicts · mix prep · explain traces · insights DISCORD report post · run + learning summaries VERDICTS RETUNE THE WEIGHTS
The current TuneFinder architecture. Mix history builds the taste profile; four fetchers feed the engine; the report is rendered deterministically and served through the web app. The teal loop is the point: verdicts marked during triage retune the ranker's weights every week.
Taste Layer
SoundCloud Mix History
Web API · Azure Blob · Artist profiles · Known-track exclusion set
Discovery Layer
Source Fetchers ×4
Beatport v4 API · Bandcamp · Volumo · SoundCloud · ~4,000 candidates/week
  1. Known-track filter
    Fuzzy match removes already-owned tracks, including variant suffixes
  2. Deduplication & cross-source merge
    Tracks appearing on multiple sources are merged; all metadata retained
  3. Ranker
    8 weighted signals - artist affinity, label match, chart position, cross-source validation, genre, recency, source discovery - retuned weekly from feedback
  4. Reason composer
    Deterministic - reasons built from catalog facts: play counts, prior titles, chart position, label and artist data
  5. Report renderer
    Deterministic - weekly and mix-prep reports, structured JSON artifact per run for the web app
  6. Runtime
    launchd schedule + web-triggered runs, serialised by a data lock
Web app - weekly report triage + mix prep
Discord #music-research - report post + run summaries

Building the Taste Profile

TuneFinder needs two things: which artists to look for, and which tracks to exclude. Both come from a .NET Web API built at changsta.com, deployed to Azure App Service.

The API maintains a structured catalogue of every published SoundCloud mix by merging two sources: a historical catalogue in Azure Blob Storage, and SoundCloud's RSS feed for recent mixes. New mixes discovered via RSS are automatically written back to the blob, so the catalogue grows without manual intervention. Results are cached in-memory to avoid hitting both sources on every request.

One endpoint returns a deduplicated track list across all mixes. Each entry carries a recurrenceCount (how many different mixes include that track), which becomes the primary signal.

From that, TuneFinder builds an artist profile for every artist in the history. Collaborative credits are split individually. Play count is weighted by recurrence: an artist whose tracks appear across four mixes contributes more weight than one played once. That naturally separates genuine preferences from passing experiments.

The result is a dictionary of 1,000+ artist profiles, each with a weighted play count and associated genres. Alongside it, a known-track exclusion set fingerprints every track already played, so the system never recommends something already owned.

The API is backed by unit tests and full infrastructure-as-code in Bicep, with secrets injected at deploy time rather than stored in config.

Finding New Music

Four source fetchers run in parallel every week. The goal is breadth: no single platform covers the full scene, so casting a wide net and cross-referencing produces a much stronger signal than relying on any one store's algorithm.

  • Beatport - genre top-100 charts (D&B, Electronica, UKG, Breaks, five house sub-genres). Chart position, BPM, key, and label data now come from the v4 catalog API, which replaced the earlier __NEXT_DATA__ extraction and made the fetcher boring in the best way.
  • Bandcamp - queried by genre tag to surface independent releases that often appear here before hitting the main stores.
  • Volumo - an underground-focused store covering the end of the market the big platforms miss. No preview embeds, so its picks render list-only.
  • SoundCloud - the official API, fishing the free-download and bootleg lane. These route to their own report section, so a bootleg never competes with a store release for a slot.

A track on Beatport's chart and Bandcamp is a stronger signal than one platform alone. Cross-source agreement ends up being the pipeline's most reliable quality filter. Traxsource, Resident Advisor, and Mixupload fetchers exist behind config flags, currently disabled; Juno Download and the Subsurface newsletter archive served earlier eras of the pipeline.

The Ranking Pipeline

After fetching, ~4,000 raw candidates pass through several stages. First, deduplication: tracks appearing on multiple sources are merged into a single candidate, retaining all source metadata. Then the known-track filter removes anything matching the play history. A history filter also excludes tracks already recommended in previous reports.

Each surviving candidate is scored against weighted signals:

Signal Weight Trigger
Known artist 3.0 × play count Artist in mix history
Recurring artist +2.0 Artist with 3+ plays in history
Label match +2.5 Label associated with known artists
Chart position up to +1.5 Position 1–100, decays linearly
Cross-source +1.0 Track appeared on 2+ sources
Genre match +0.5 per tag Genre in known set
Fresh release +0.5 Released within the last 30 days

The chart position bonus matters. A track at #1 on the Juno chart gets the full 1.5 bonus, decaying to zero at position 100. This surfaces music the broader scene considers significant, not just what is algorithmically new.

Tracks that score well but miss the weekly cut are held in a persistent candidate pool and re-evaluated in future runs. A track appearing across multiple weeks without being recommended gradually accumulates credibility.

Top candidates split into four sections: Top Picks, Label Watch, Artist Watch, and Wildcards, with per-artist and per-genre caps to keep variety.

The weights started as intuition and manual experimentation. Now they retune themselves: every verdict marked in the web app feeds a weekly learning pass that adjusts signal weights, boosts liked artists, and decays what keeps getting skipped. An eighth signal, a source discovery bonus, rewards fetchers that keep finding tracks that get bought. The architecture accepts new signals without restructuring the pipeline, which is what made that evolution cheap.

The Report Went Deterministic

TuneFinder ran a two-stage LLM pipeline for most of its life: a cheap model writing a one-sentence reason per track, a frontier model writing the weekly report. It worked, and it cost about £10 a month.

Then the feedback loop landed and the reasons got better without a model. The engine already knows why it picked a track, because the score is the sum of named signals. "You play Calibre regularly and this is his first EP in two years" turns out to be a database query, not a paragraph of prose. The renderer now composes every reason from catalog facts (play counts, prior titles, chart position, label and artist data) and the report template does the rest. LLM calls per run: zero.

The LLM stages were scaffolding. Once the scoring signals were rich enough to explain themselves, generation became formatting, and formatting does not need a model.

From AI Agent to Web App

TuneFinder started as a Python script run manually from the terminal. It worked, but it was fragile - SSH to the server, activate the venv, remember the flags, babysit the output. Silent failures were hard to track down.

The first fix was OpenClaw, a self-hosted AI agent platform on the Mac Mini. It ran TuneFinder as a shell skill on a weekly schedule, posted failures to a #logs channel, and could be triggered conversationally: @mention the agent on Discord, ask for a mix-prep report, get it back in the channel. No terminal, no flags.

That era ended in 2026 when the agent platform was stood down. What replaced it is plainer and better. The schedule is a launchd agent (Sunday 09:00, London time). On-demand runs moved out of Discord chat and into the app itself, with a data lock serialising web-triggered and scheduled runs so they cannot interleave. The conversational layer turned out to be scaffolding too: once the app existed, "ask the agent to run mix prep" became a button.

Why a Mac Mini Under a Desk

The Mac Mini M1 draws about 7 watts at idle. It runs 24/7 and costs almost nothing beyond electricity. Running locally avoids paying for a cloud VM and keeps the whole thing simple. That is a deliberate architectural choice, not a limitation.

The worst realistic failure is a missed weekly report, which does not justify the operational overhead of cloud infrastructure or container orchestration. The complexity budget is better spent improving the pipeline logic than building resilience the system does not really need.

The Web App

The report used to arrive in Discord and get worked through by hand. Now it lands in a React app served by the same FastAPI backend that runs the pipeline - one origin, reached through an outbound-only Cloudflare Tunnel, behind a fail-closed bearer secret. Weekly triage happens track by track: audition through store embeds, then mark a verdict. Bought, liked, skip, own. Each mark is a scoring input, not a bookmark.

TuneFinder weekly report page, styled as a record pressing: large headline reading The Weekly Pressing beside a vinyl record labelled W28, with a stats strip showing 13 tracks, median 155 BPM, 5 known names, 412 fetched
The weekly report, pressed as a record. Each week is a numbered pressing with its side A and side B, and every stat on the sleeve is real pipeline output.

Every pick carries its reason. The score is the sum of the signal weights a track earned, and the app shows the signals and a full pipeline trace per track. An insights page shows per-signal lift, genre and label affinity, the candidate pool, and what the engine changed after each week's learning pass. Mix-prep runs launch from the app too, constrained by genre, tempo range with half and double-time flex, and Camelot key picked from a key wheel. Runs triggered here show live progress.

TuneFinder New Cut page: mix-prep run builder with genre chips, tempo range presets, and a circular Camelot key wheel for harmonic filtering, ending in a Press It button
New Cut: a mix-prep run scoped by genre, tempo, and the Camelot wheel. The button says Press It because the metaphor is load-bearing.
TuneFinder insights page titled Inside The Engine, showing The Desk: rows of mixing-desk faders visualising per-signal, per-source and per-genre lift against a 1.0x baseline
Inside the engine: per-signal lift drawn as a mixing desk. Label match runs hot at 1.55×, pool age is flatlined, and the learning pass moves the faders.
Two usage scenes drove the design: phone on the sofa, working through the week's report a verdict at a time; desktop at the machine, prepping a set with store tabs open alongside. Everything else was out of scope, deliberately.

Results

What previously took several hours of active browsing is now a five-minute triage on Sunday morning. Last run: 4,156 tracks fetched, 23 pressed into the report.

TuneFinder surfaces 15–20 relevant tracks per week from ~4,000 candidates, and growing as new sources are added. The known-artist signal is the strongest predictor by some margin. If an artist who gets regular play has a new release, it surfaces immediately. Cross-source agreement is the most reliable quality filter: a track independently validated by Juno's chart buyers and Bandcamp's independent listeners is almost always worth a listen.

Mix-prep runs launch from the web app on demand, narrowed by genre, tempo, and key, useful when building a set and wanting to narrow the focus. And since the feedback loop closed, the engine measures its own hit rate instead of me guessing at weights.

The label watch section has introduced several labels now followed directly. The system inferred label relevance from play history without any explicit label list being provided. That was a surprise.

There is nothing to remember. The report is waiting in the app every Sunday morning, and Discord gets a copy.

A Reusable Pattern

The most interesting outcome is not TuneFinder itself. It is that the architecture turned out to be a reusable template.

The same Mac Mini runs a second automation called Signal Monitor that tracks ~30 companies in the Exhibitions & Events industry for hiring, product, M&A, and expansion signals: the same watch, score, report pattern with scheduled execution and error routing. Both systems have since grown web front ends of their own.

The pattern: watch a domain, aggregate from multiple sources, score and filter, write a human-readable report, deliver it to where you already are. The domain-specific logic changes. The orchestration, delivery, and LLM economics stay the same.

What's Next

  • Seeded discovery everywhere - positive verdicts already seed targeted searches on Beatport and SoundCloud, so a liked artist gets actively hunted rather than passively awaited. Extending seeded queries to the remaining sources is the obvious next step.
  • Broader source coverage - the Traxsource, Resident Advisor, and Mixupload fetchers are built and sitting behind config flags; label-direct Bandcamp pages and artist reposts are signals still missed. Each new source adds noise, but also strengthens the cross-source validation that is already the ranker's most reliable quality filter.
  • Longer-horizon learning - the weekly pass tunes weights from recent verdicts. A season's worth of marks could say more: which genres fade, which labels earn a standing boost, whether the engine's hit rate is actually climbing.

Tech Stack

TuneFinder - Discovery Pipeline GitHub ↗
Python Beatport v4 API BeautifulSoup Deterministic reason composer Weekly weight auto-tuning Discord bot launchd
changsta.com Web API - Taste Profile GitHub ↗
.NET 10 ASP.NET Core Azure Blob Storage SoundCloud RSS OpenAI SDK Azure App Service Bicep NUnit + FluentAssertions
Web App - Triage & Insights
React 18 TypeScript Vite Tailwind FastAPI backend Playwright e2e Cloudflare Tunnel Mac Mini M1
Case Studies

Building something similar?

TuneFinder started as a personal tool, but the pattern it uses is domain-agnostic: aggregate from multiple sources, score and filter, deliver a readable report to where you already are.

If you are working on something similar (industry monitoring, research aggregation, personal tooling with a real ranking problem), feel free to get in touch.