import { describe, expect, test } from 'vitest'; import { dbToLinear, gainDb, playingAsAlbum, MAX_BOOST_DB } from './gain'; import type { NormalizationPrefs } from '$lib/api/normalization'; import type { TrackRef } from '$lib/api/types'; import { makeTrack } from '$test-utils/fixtures/track'; const prefs = (over: Partial = {}): NormalizationPrefs => ({ mode: 'auto', target_lufs: -18, boost: 'headroom', ...over }); const track = (id: string, album: string, n?: number, disc?: number): TrackRef => makeTrack({ id, album_id: album, track_number: n, disc_number: disc }); describe('gainDb', () => { const g = { track_gain: -6, track_peak: 1, album_gain: -4, album_peak: 1 }; test('off and unmeasured tracks play as mastered', () => { expect(gainDb(prefs({ mode: 'off' }), g, false)).toBe(0); expect(gainDb(prefs(), undefined, false)).toBe(0); expect(gainDb(prefs(), {}, false)).toBe(0); }); test('mode picks the gain; auto follows album play', () => { expect(gainDb(prefs({ mode: 'track' }), g, true)).toBe(-6); expect(gainDb(prefs({ mode: 'album' }), g, false)).toBe(-4); expect(gainDb(prefs({ mode: 'auto' }), g, true)).toBe(-4); expect(gainDb(prefs({ mode: 'auto' }), g, false)).toBe(-6); }); test('album gain falls back to track gain until the album is measured', () => { expect(gainDb(prefs({ mode: 'album' }), { track_gain: -6, track_peak: 1 }, true)).toBe(-6); }); test('a louder target raises every gain by the difference', () => { expect(gainDb(prefs({ mode: 'track', target_lufs: -14 }), g, false)).toBe(-2); }); test('headroom stops a boost 1 dB under the true peak; the limiter lets it through', () => { // +8 dB wanted; the peak at 0.5 (about -6 dBTP) leaves 5 dB of room. const quiet = { track_gain: 8, track_peak: 0.5 }; expect(gainDb(prefs({ mode: 'track' }), quiet, false)).toBeCloseTo(-1 - 20 * Math.log10(0.5), 5); expect(gainDb(prefs({ mode: 'track', boost: 'limiter' }), quiet, false)).toBe(8); }); test('no boost passes the cap, limiter or not', () => { const silent = { track_gain: 30, track_peak: 0.001 }; expect(gainDb(prefs({ mode: 'track', boost: 'limiter' }), silent, false)).toBe(MAX_BOOST_DB); }); test('cuts are never limited by the peak', () => { expect(gainDb(prefs({ mode: 'track' }), { track_gain: -9, track_peak: 1.4 }, false)).toBe(-9); }); }); describe('playingAsAlbum', () => { test('in-order neighbours from the same album', () => { const q = [track('1', 'x', 1), track('2', 'x', 2), track('3', 'x', 3)]; expect(playingAsAlbum(q, 0)).toBe(true); expect(playingAsAlbum(q, 1)).toBe(true); expect(playingAsAlbum(q, 2)).toBe(true); }); test('a shuffled album, a mix and a lone track are not album play', () => { expect(playingAsAlbum([track('3', 'x', 3), track('1', 'x', 1)], 0)).toBe(false); expect(playingAsAlbum([track('1', 'x', 1), track('2', 'y', 2)], 0)).toBe(false); expect(playingAsAlbum([track('1', 'x', 1)], 0)).toBe(false); }); test('disc order counts: disc 2 track 1 follows disc 1 track 12', () => { const q = [track('a', 'x', 12, 1), track('b', 'x', 1, 2)]; expect(playingAsAlbum(q, 1)).toBe(true); }); test('neighbours without track numbers are not evidence of order', () => { expect(playingAsAlbum([track('1', 'x'), track('2', 'x')], 0)).toBe(false); }); }); test('dbToLinear', () => { expect(dbToLinear(0)).toBe(1); expect(dbToLinear(-20)).toBeCloseTo(0.1, 6); expect(dbToLinear(6)).toBeCloseTo(1.995, 3); });