Skip to content

Commit c9cba6b

Browse files
committed
docs: tighten building and design pages
building-driftfm.md: 105 → 65 lines. Cut blog-post filler, kept the narrative voice. Removed duplication with README and architecture page. design.md: 272 → 127 lines. Collapsed thin subsections, removed token tables that just restate tokens.css, condensed components into dense paragraphs.
1 parent 2734978 commit c9cba6b

2 files changed

Lines changed: 64 additions & 249 deletions

File tree

docs/building-driftfm.md

Lines changed: 23 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -2,103 +2,63 @@
22
title: Building Drift FM
33
---
44

5-
The opinions, trade-offs, and philosophy behind how Drift FM is built. For the technical architecture (packages, data model, request flow), see [Architecture](architecture).
5+
The story behind Drift FM — why it exists, why it's built the way it is.
6+
7+
For the technical details, see [Architecture](architecture). For the frontend design system, see [Design Language](design).
68

79
---
810

911
## The Problem
1012

11-
Most music apps optimize for engagement. Algorithmic recommendations, social features, infinite scroll, notifications pulling you back in. The goal is retention, not listening.
13+
Every music app wants your attention. Recommendations, social feeds, notifications — they optimize for engagement, not listening. I wanted something that plays music for a mood and gets out of the way.
1214

13-
Drift FM optimizes for mood. You pick a feeling — focus, calm, energize, late night — and the music plays. No recommendations, no feed, no decisions after the first one. You drift.
15+
Drift FM has one interaction: pick a mood. After that, music plays. No decisions, no feed, no algorithm nudging you toward something else.
1416

1517
---
1618

1719
## Why Self-Hosted
1820

19-
Your music should live on your hardware. Not behind a subscription. Not gated by a service that might change its terms, raise its price, or shut down.
21+
Your files, your server. No subscription, no licensing gaps, no "this track is no longer available." No analytics, no cookies, no third-party scripts.
2022

21-
Self-hosting means:
22-
- **Your library, your rules.** No licensing gaps. No region locks. No "this track is no longer available."
23-
- **No tracking.** Zero analytics, zero cookies, zero third-party scripts. Listen events are stored locally in SQLite for playlist optimization only.
24-
- **Runs anywhere.** A $5 VPS, a Raspberry Pi, your laptop. The binary is ~15 MB. Deploy it next to your files and forget about it.
23+
It runs on a $5 VPS, a Raspberry Pi, or localhost. The binary is ~15 MB.
2524

2625
---
2726

28-
## Why Go + SQLite + Vanilla JS
29-
30-
The stack is deliberately boring.
31-
32-
### Go
33-
34-
Go compiles to a single binary. No runtime, no JVM, no dependency tree. Cross-compile to any platform with one command. The standard library includes a production-grade HTTP server — no framework needed.
35-
36-
For a music server that handles a handful of concurrent users serving files from disk, Go is wildly overqualified. That's the point. The server will never be the bottleneck.
27+
## The Stack
3728

38-
### SQLite
29+
Go + SQLite + vanilla JS. Boring on purpose.
3930

40-
A music library of a few thousand tracks is a small dataset. SQLite handles it trivially. WAL mode gives concurrent reads. The database is a single file you can copy, back up, or inspect with the SQLite CLI.
31+
**Go** compiles to a single binary. Cross-compile to any platform. The stdlib HTTP server is more than enough for serving files to a handful of users.
4132

42-
Drift FM uses [modernc.org/sqlite](https://pkg.go.dev/modernc.org/sqlite)a pure Go SQLite implementation. No CGO, no C compiler, clean cross-compilation. It's slightly slower than the C driver, but "slightly slower" on a workload this small is immeasurable.
33+
**SQLite** because a few thousand tracks is a tiny dataset. WAL mode for concurrent reads. One file to back up. No Postgres, no connection pooling, no Docker Compose. We use [modernc.org/sqlite](https://pkg.go.dev/modernc.org/sqlite) — pure Go, no CGO, clean cross-compilation.
4334

44-
No Postgres means no connection pooling, no migrations server, no Docker Compose for development. `make db-init` and you're done.
45-
46-
### Vanilla JS
47-
48-
The frontend is vanilla JavaScript using ES6 modules — the core player logic in `app.js` is about 1000 lines. No React, no Vue, no build step, no node_modules, no bundler.
49-
50-
This isn't a dogmatic stance. It's a scope decision. The player has one page, a few interactive elements, and a straightforward state model. A framework would add a build pipeline, a package manager, and a layer of abstraction — all to solve problems this app doesn't have.
51-
52-
CSS variables handle theming. The `<audio>` element handles playback. The browser is the framework.
35+
**Vanilla JS** because the player is one page with a few interactive elements. A framework would add a build pipeline and a package manager to solve problems this app doesn't have. CSS variables handle theming. The `<audio>` element handles playback.
5336

5437
---
5538

5639
## Shuffle with Memory
5740

58-
Random shuffle has a problem: true randomness feels repetitive. In a library of 20 tracks, hearing the same song twice in an hour doesn't feel random — it feels broken.
59-
60-
Drift FM's shuffle uses **recency avoidance**:
41+
True randomness feels repetitive. Hearing the same track twice in an hour feels broken, not random.
6142

62-
1. Fetch all tracks for the mood from SQLite
63-
2. Partition into "not recently played" and "recently played" (last 3 track IDs)
64-
3. Fisher-Yates shuffle only the non-recent tracks
65-
4. Rebuild: shuffled non-recent first, recent appended at the end (unshuffled)
66-
5. When a track plays, its ID is added to the recent list (FIFO, capped at 3)
43+
The shuffle uses recency avoidance:
6744

68-
This is simple and works well for small libraries. You won't hear a recently played track again until at least 3 others have played. With larger libraries the recency window is barely noticeable — but it still prevents the jarring back-to-back repeat.
45+
1. Partition tracks into "not recently played" and "recently played" (last 3)
46+
2. Fisher-Yates shuffle only the non-recent tracks
47+
3. Append recent tracks at the end, unshuffled
48+
4. When a track plays, add it to the recent list (FIFO, capped at 3)
6949

70-
The algorithm is stateful per mood. Switching moods resets the recency window. This is intentional: if you switch to calm and back to focus, you might hear a recently played focus track — but the mood change creates enough perceptual distance that it doesn't feel like a repeat.
50+
Simple, works well for small libraries. Stateful per mood — switching moods resets the window.
7151

7252
---
7353

7454
## The .txt Convention
7555

76-
Drift FM needs to know if a track has vocals. Focus mood enforces instrumental-only — a vocal track in a focus playlist breaks concentration.
56+
Focus mood enforces instrumental-only. Rather than import flags, we use a file convention: if a `.txt` file exists next to an `.mp3` with the same name, the track is vocal. Content becomes displayable lyrics. Empty file still marks it as vocal.
7757

78-
Rather than adding flags to the import command, Drift FM uses a file convention: if a `.txt` file with the same base name exists next to an `.mp3`, the track is marked as vocal. If the `.txt` has content, it's imported as displayable lyrics. If it's empty, the track is still vocal — just without lyrics.
79-
80-
See [Quickstart — Vocals and lyrics](quickstart#vocals-and-lyrics) for the full convention and examples.
81-
82-
This convention is zero-config. You don't need to remember import flags. Drop your files in a directory, add `.txt` files for vocal tracks, and batch import. The script figures it out.
83-
84-
---
85-
86-
## Single Binary, Deploy Anywhere
87-
88-
`make build` produces one binary. Copy it to a server along with the `web/` directory, your `config.yaml`, and your audio files. Run it.
89-
90-
```bash
91-
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o bin/server ./cmd/server
92-
```
93-
94-
No container orchestration required. No process manager required (though systemd is sensible for production). No reverse proxy required (though Caddy or nginx in front for TLS is recommended).
95-
96-
The goal is the smallest operational footprint that still feels complete. One binary, one database file, one directory of audio files. Back it up by copying three things.
58+
Zero-config. Drop files, batch import, the script figures it out. See [Quickstart — Vocals and lyrics](quickstart#vocals-and-lyrics) for examples.
9759

9860
---
9961

100-
## What This Isn't
101-
102-
Drift FM is not a music discovery service. It doesn't fetch album art, it doesn't look up metadata from external APIs, it doesn't suggest tracks. It plays what you give it, in the mood you choose, with shuffle that respects your recent listening.
62+
## Deploy
10363

104-
It's a player, not a platform. The value is in the simplicity: mood selection, continuous playback, and getting out of the way.
64+
One binary, one database file, one directory of audio files. Copy three things, run it. See the [README](https://github.com/1mb-dev/driftfm#deploy) for the full deploy guide.

0 commit comments

Comments
 (0)