rox

Cue sheets, and rips that behave like albums

If you ripped CDs before splitting was the default, a chunk of your library is one FLAC per disc with a text file beside it. Most players either ignore the sheet or treat what it describes as second-class. Here's what rox does with it.

What a cue rip actually is

One audio file holding an entire disc, and a .cue sheet listing where each track starts. It's the accurate way to preserve a CD, because the gaps between tracks are part of the disc and splitting throws them away. It's also the format that breaks the assumption every music library is built on, which is that one file is one track.

Players resolve that in one of three ways. Ignore the sheet and show a seventy-minute track. Show the sheet's tracks but keep them apart from the real library, so search, sort and playlists behave differently for them. Or make the break real and absorb it once.

Real rows, not fragments

rox indexes each span in the sheet as an ordinary row in the library, identified by its file plus its track number. Everything downstream reads it as a track, because to everything downstream it is one. Playlists snapshot it, listens attach to it, search finds it, the sort columns sort it, and none of them know that eleven rows point at the same FLAC.

The alternative most implementations pick is a synthetic path, album.flac#3, which keeps the database tidy and moves the problem onto every piece of code that later opens that path. A missed strip there is a silent bug that reads tag bytes out of nothing. A library that owns no cue sheets pays nothing for any of this: the spans live in a side table, and nothing on the hot paths reads them.

Playing a span like a file

The engine takes a span as the whole world for that track: an accurate seek to its start, a sample-accurate trim at both edges, and an end boundary that takes the same path a real end-of-file does. Gapless, crossfade, stop-after-current and loop all work without knowing spans exist.

The head trim is the detail that matters. An accurate seek lands on a packet boundary rather than the exact sample, so without dropping the frames between the landing and the span start, every track opens on the tail of the one before it. That's the sound of a cue implementation nobody finished.

Consecutive tracks of one rip share an album group, which is what keeps crossfade from fading across a disc's own gapless splices. A rip plays like the record it was cut from.

Scanning, and changing your mind

The sheet claims its image. While a cue lists a file, that file gets no row of its own, so you don't end up with eleven tracks plus a seventy-minute duplicate. Freshness keys off whichever of the two was modified later, so editing either the sheet or the audio re-cuts the rip on the next scan. Delete the sheet and the image folds back into one plain row.

Metadata prefers the sheet and falls back to the image's own tags. Sheets written before UTF-8 discipline was a thing get a cp1252 fallback, because old rips are exactly the population this feature is for.

Ratings that don't stamp the whole disc

rox normally writes ratings into the files themselves, which is the right call when a file is a track. On a cue rip it isn't: the image belongs to all eleven tracks, so a per-track write would stamp every one of them with the same stars.

The writer refuses the file half for these rows and the database keeps the value. Same for tag edits. You get per-track ratings on a rip, and your image comes out of it byte-identical.

The parts you'd only notice later

  • m3u export writes spans as path#N, and import prefers an exact path match, so a list round-trips through other software without collapsing to the image.
  • Scrobbling and now-playing dedup on the pair, so a disc's eleven tracks scrobble as eleven tracks rather than one very long one.
  • Listens reattach per span after a rescan, so a disc keeps its play history per track even when every span carries identical tags.
  • Only the album pair of ReplayGain values carries over. Track values written against a whole-disc image describe the disc, so they're ignored rather than trusted.

The measure of this isn't whether a rip plays. It's whether, a month later, anything in the library still behaves differently because it came out of an image.

Point it at the shelf you never split

The scanner picks the sheets up on its first pass, alongside everything else. More on what breaks at library scale.

Download rox

On Arch or NixOS? Install it from the AUR or the Nix flake instead.

v1.26.4 · Linux, macOS, Windows · all downloads