karkowg headjar
Articles ยป Tuning into brainradio.fm
January 2, 2026

Tuning into brainradio.fm

brainradio.fm

It's 11:47 PM and I can't sleep. Not because of stress or caffeine, but because Dancing Queen has been playing on loop in my head for three hours straight. I haven't listened to ABBA in weeks. I don't even particularly like ABBA. And yet here I am, mentally belting out "you can dance, you can jive" while staring at the ceiling.

You know the feeling. Everyone does.

Scientists call them earworms. I didn't know that word until I started building this. "Earworm" apparently comes from the German Ohrwurm, those involuntary musical memories that hijack your attention and refuse to leave. I've always been hunted by them. They can be annoying, but they can reveal things about us. Why that song? Why now? Is there a pattern?

The spark came from Sarah Drasner's post:

Woke up pissed about something and then brain radio was playing Cyndi Lauper and, eh.

Thanks weird brain glitch ๐Ÿ‘

โ€” Sarah Drasner (@sarahedo.bsky.social) December 11, 2024 at 11:00 AM

Brain radio. I loved that framing. What if you could scrobble not what you listen to, but what you're inner radio is playing? Track the private radio station broadcasting inside your skull. It sounded fun. So I built it.

What It Does

The core is simple. You log:

  1. What song is playing in your head right now
  2. How you feel about it

That's it. But the details are where it gets interesting.

Finding Songs Across the Multiverse

The first real technical challenge was identity. If I search for "Hey Jude" on Spotify and my friend searches for it on Last.fm, are those the same song? What about "Hey Jude (Remastered 2015)" versus "Hey Jude - Live at Hollywood Bowl"?

For a scrobbling app to be useful, it needs to understand that these are all representations of the same underlying song. Otherwise your data fragments across dozens of "different" songs that are actually one thing.

I call this the fingerprinting problem. The solution I ported from teal.fm uses a three-step process:

// 1. Strip the guff
"The Beatles - Hey Jude (Remastered 2015)"
โ†’ "Beatles - Hey Jude"

// 2. Normalize everything
โ†’ "beatles|hey jude"

// 3. Generate deterministic UUID v5
โ†’ "448697e2-0b25-4764-b4c9-b39c8b29b7bc"

The "guff" is all the noise that doesn't affect song identity: remaster tags, live annotations, featured artists, even "The" prefix on band names. Strip it out, normalize what's left, and hash it into a UUID that will always be identical for the same song, regardless of where the data came from.

Now Spotify's version, Last.fm's version, and manually entered versions all collapse into one database record. Your earworm history stays clean.

The Tuner Pattern: Searching Multiple Services

But where do we search for songs in the first place?

The obvious answer is "everywhere": Spotify, Last.fm, MusicBrainz, even your own database of previously logged songs. Each source has strengths: Spotify has great metadata and cover art, Last.fm knows obscure tracks, MusicBrainz is exhaustively accurate for classical and jazz.

I built what I call the Tuner system. It uses the Laravel Manager pattern, like Cache or Filesystem drivers, abstracting music search behind a common interface:

interface Tuner
{
    public function search(string $song, ?string $artist = null): Collection;
}

The beauty is in the pluggability. Want to search Spotify first, then fall back to Last.fm if nothing's found? Configure a FailoverTuner. Want to randomly pick a source for variety? There's a LotteryTuner for that:

class LotteryTuner implements Tuner
{
    public function __construct(
        protected TunerManager $tuner,
        protected array $drivers
    ) {}

    public function search(string $song, ?string $artist = null): Collection
    {
        if (empty($this->drivers)) {
            return collect();
        }

        // Pick one driver at random
        $driverName = Arr::random($this->drivers);

        return $this->tuner->driver($driverName)->search($song, $artist);
    }
}

Just want to search your local database? LocalTuner with Laravel Scout. Swap drivers in config. Zero code changes.

Vibes: Capturing Emotional Context

This is the part that makes brainradio.fm different from a regular scrobbler.

When you log an earworm, you pick a "vibe", the emotional context of that moment. Not how you feel about the song in general, but how it's hitting you right now:

  • Neutral: You're not feeling anything in particular, it's just... there
  • Nostalgic: Warm memories, bittersweet connection to the past
  • Energized: Gets you pumped, makes you want to move
  • Happy: Pure joy, puts you in a great mood
  • Annoyed: Classic "get out of my head!" frustration
  • Melancholic: Brings up sad or wistful feelings
  • Obsessed: Can't stop thinking about it, almost manic

Three positive, three negative, and a neutral default. Balanced.

Over time, patterns emerge. Maybe "Don't Stop Believin'" is nostalgic for you in December but annoying in July. Maybe certain artists always correlate with certain moods. The data tells stories that listening history never could.

The Frequency

Here's a small detail I love: every song gets an FM frequency.

FM 98.2: "Dancing Queen" by ABBA
FM 103.7: "Bohemian Rhapsody" by Queen

It's deterministic. Same song, same frequency, always. The math:

$freq = 87.5 + (($song_id * 1337) % 206) / 10;

Breaking it down: we multiply the song ID by 1337, then use modulo 206 to get a value between 0 and 205. Divide by 10 to get a decimal offset (0.0 to 20.5), then add 87.5. This gives us a frequency in the real FM radio band: 87.5 MHz to 108.0 MHz. Every song lands somewhere on the dial.

Does this serve any real purpose? Not really. But it reinforces the "brain radio" metaphor. Your head has stations. Different songs broadcast on different frequencies. It's the kind of playful detail that makes it fun rather than just functional.

Tech Stack

  • Laravel 12 for the backend
  • Vue 3 + Inertia v2 for the frontend, server-driven but reactive
  • Tailwind CSS v4 because justfuckingusetailwind.com
  • Laravel Scout for search
  • Saloon for API integrations (Spotify, Last.fm, MusicBrainz)
  • Wayfinder for type-safe routes in TypeScript
  • ogkit.dev for OG images

The architecture follows Laravel conventions: Actions for business logic, DTOs for data normalization, Managers for pluggable systems. Nothing revolutionary, just clean, maintainable code that I won't hate in six months.

Deploying to Laravel Cloud

This was the first app I deployed to Laravel Cloud, and I was genuinely surprised by how smooth it was. Connect your repo, configure a few environment variables, and you're live. No Dockerfiles, no server provisioning, no "why isn't nginx working" debugging sessions at 2 AM. It just works.

For a side project, that's exactly what I wanted: spend time building the thing, not managing infrastructure.

What's Next

The long-term vision is social. Right now brainradio.fm tracks your earworms. But imagine discovering that your friends have the same song stuck in their heads. Or seeing what songs are collectively haunting the internet this week. There's something beautifully human about realizing we're all mentally singing the same chorus.

For now, I'm just trying to understand my own brain's playlist. I'm curious whether patterns will emerge over time. Do certain songs only appear in certain seasons? Does stress trigger specific artists? Do happy days correlate with faster tempos?

We'll see. That's the fun of building something like this.


brainradio.fm is probably too niche or too weird for most people. But if you've ever wondered why your brain plays "La Cucaracha" by The Mariachis at 3 AM, we should talk.