-- 0065_track_loudness.up.sql — measured loudness per track, for loudness -- normalization (Scribe milestone #464, #4995). -- -- Measured with ffmpeg's EBU R128 filter rather than read from ReplayGain tags: -- tags in the wild are written against four different reference levels, and -- most files have none. internal/library/loudness.go says how it is measured. -- -- A table of its own rather than columns on tracks, for the reason -- track_fingerprints is (0058): tracks is read with SELECT * on the hot path, -- and the block histogram is a few hundred integers only album loudness reads. -- -- What a row means, which the backfill depends on: -- no row never analyzed, or the file changed since -- analysis_version < current measured by an older method; measure again -- analysis_version = current settled until the file changes: -- integrated_lufs NOT NULL measured -- integrated_lufs NULL, unreadable false -- read fine, but no 400 ms block was above -- the -70 LUFS gate: silence, or too short -- unreadable true ffmpeg could not decode the file -- A failure that says nothing about the file (a timeout, a cancelled pass, a -- missing ffmpeg) writes no row, so the backfill tries again. CREATE TABLE track_loudness ( track_id uuid PRIMARY KEY REFERENCES tracks (id) ON DELETE CASCADE, -- Gated integrated loudness (ITU-R BS.1770), mono measured as dual mono. integrated_lufs real, -- Highest inter-sample peak, from 4x oversampling, in dB relative to full -- scale. NULL for digital silence, whose peak is -inf. true_peak_dbtp real, -- Loudness range (EBU Tech 3342): how much the loudness moves within the -- track. Not used for gain; kept because it costs nothing here. loudness_range_lu real, -- How many 400 ms gating blocks fell in each 0.1 LU bin. Bin i holds blocks -- measuring -70.0 + (block_hist_start + i) / 10 LUFS; the array is trimmed -- to the first and last non-empty bins. Album loudness is the gated loudness -- of every block on the album, so it is computed from these exactly, with no -- second decode (#4996). block_hist_start smallint, block_hist integer[], unreadable boolean NOT NULL DEFAULT false, analysis_version smallint NOT NULL, analyzed_at timestamptz NOT NULL DEFAULT now(), CONSTRAINT track_loudness_hist_pair CHECK ((block_hist IS NULL) = (block_hist_start IS NULL)) ); -- Loudness analysis's knobs, in admin Settings. Rule 25: an operator setting is -- a database row, changed without a restart. Singleton in the style of -- fingerprint_settings (0061). CREATE TABLE loudness_settings ( id boolean PRIMARY KEY DEFAULT true, -- Off stops the background analysis. Tracks already measured keep their -- values, so normalization keeps working for them. enabled boolean NOT NULL DEFAULT true, -- Files analyzed at once. Each is a full decode, competing with playback -- transcoding for CPU and with streaming for the mount. backfill_concurrency integer NOT NULL DEFAULT 2, updated_at timestamptz NOT NULL DEFAULT now(), CONSTRAINT loudness_settings_singleton CHECK (id = true), CONSTRAINT loudness_settings_concurrency_range CHECK (backfill_concurrency >= 1 AND backfill_concurrency <= 8) ); INSERT INTO loudness_settings (id) VALUES (true) ON CONFLICT (id) DO NOTHING;