Replace the handoff notes with a durable spec and ADRs

The handoff was written as a message to relay information; several
decisions lived only there. Split into permanent homes:

- docs/spec.md — scope, geometry model, pipeline order, detection,
  levels, editor, project file, markers, bundle format, noteman's
  changes, reference values
- ADR 0002 — raster only in release 1; SVG slices measured at 40x WebP
  naive, 2.6x with a bounding-box cull, deferred on risk not size
- ADR 0003 — lossless WebP with levels and 16-level alpha; every lossy
  option and alternative format measured larger for line art
- ADR 0004 — detection proposes, the human disposes; no unattended mode
- ADR 0005 — PyMuPDF for all PDF access, accepting AGPL
- ADR 0006 — systems are found by vertical brackets, not row-darkness
  gaps, which miscount every page of a 6-voice score

Also from testing against the hardest score in the repertoire: scanned
PDFs carry their scan as an embedded image and must be extracted at
native resolution rather than re-rendered at 600 DPI, and per-page
deskew is mandatory (skew varies -2.6 to +1.2 within one PDF).
This commit is contained in:
Esa Kataja
2026-07-28 22:05:09 +03:00
parent efc920fdc5
commit 46ae7e813a
8 changed files with 674 additions and 7 deletions
@@ -0,0 +1,58 @@
# Lossless WebP, with levels and alpha quantised to 16 levels
Slice images are encoded as **lossless WebP**, with the levels adjustment applied
and the alpha channel quantised to 16 levels. About 7 KB per slice, ~450 KB for a
65-system song. Every lossy option and every alternative format measured
*larger* for this content, which is the opposite of the usual intuition — hence
this record.
## The measurement
20 slices of one real song, levels applied throughout, relative to plain lossless
WebP:
| | vs baseline | |
|---|---|---|
| **WebP lossless + alpha quantised to 16** | **68%** | chosen |
| AVIF q60 | 90% | lossy, for 10% |
| WebP lossless | 100% | baseline |
| WebP lossy q85 (alpha) | 107% | |
| AVIF q85 | 114% | |
| JXL lossless | 130133% | |
| WebP lossy q85 (opaque ink-on-white) | 158% | |
| PNG grayscale + alpha | 165% | |
| AVIF lossless | 188% | |
Separately, before levels: applying levels alone takes 338 KB → 211 KB, a 38%
reduction.
## Four results that contradict an instinct
- **Lossy is bigger than lossless here.** Not a quality problem — the measured
difference between q85 and lossless is max 12/255, mean 0.33, i.e. invisible.
Lossy VP8 simply spends more bits on sharp black/white edges than VP8L's
palette and predictor transforms do, and notation is nothing but sharp edges.
The "q85 looks fine" intuition comes from photographs and inverts here.
- **AVIF and JXL both lose**, AVIF lossless by nearly 2×. Their lossless modes
are afterthoughts on photo codecs. WebP's VP8L is close to purpose-built for
flat two-tone line art — sheet music is the content type it is best at. JXL
additionally has no path forward in Chrome.
- **Alpha costs nothing.** Opaque ink-on-white and black-plus-alpha are within
0.1% at lossless, so paper-tint removal and future non-rectangular slices are
free.
- **Levels is the single biggest lever** — 38%, as a side effect of a control
that exists for quality reasons anyway. Pushing the white point below the
paper's luminance sets vast regions to exactly `alpha = 0`, which costs almost
nothing to encode.
Alpha quantisation to 16 levels is imperceptible: antialiased edges span 23 px
at 1920, and 16 steps across that is below notice. 8 levels starts to gamble on
thin strokes.
## Rejected as not worth it
- **Encoder effort tuning** — Pillow's `method=6` buys 3% and a dependency.
- **`alpha_quality=60`** — 24%, for less control than quantisation gives.
- **Grayscale WebP** — no such mode exists. It wouldn't help anyway: the RGB
channels are constant black and compress to nearly nothing, so alpha is the
entire payload.