Files
minstrel/web/src/lib/player/gain.test.ts
T
bvandeusenandClaude Opus 5.5 ca9917d8cd
release / govulncheck (push) Successful in 23s
release / web (push) Successful in 1m22s
release / go (push) Successful in 1m41s
release / integration (push) Successful in 4m49s
release / android (push) Successful in 5m32s
release / Build signed APK (releases and dev) (push) Successful in 5m39s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Successful in 28s
release / Verify release artifacts (tag releases only) (push) Skipped
chore(web): clear kit 3's alias and Vite's config-loader deprecations (#5021)
CI run 8789 warned on both:

- kit 3 deprecates `config.alias`. The $test-utils alias becomes a
  `#test-utils/*` subpath import, matching #lib, and its 43 import sites
  move with it.
- Vite's coming native config loader needs the extension on
  vitest.config.ts's import of vite.config.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 11:03:38 -04:00

88 lines
3.6 KiB
TypeScript

import { describe, expect, test } from 'vitest';
import { dbToLinear, gainDb, playingAsAlbum, MAX_BOOST_DB } from './gain';
import type { NormalizationPrefs } from '#lib/api/normalization.js';
import type { TrackRef } from '#lib/api/types.js';
import { makeTrack } from '#test-utils/fixtures/track.js';
const prefs = (over: Partial<NormalizationPrefs> = {}): 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);
expect(playingAsAlbum([track('1', 'x'), track('2', 'x', 3)], 1)).toBe(false);
});
});
test('dbToLinear', () => {
expect(dbToLinear(0)).toBe(1);
expect(dbToLinear(-20)).toBeCloseTo(0.1, 6);
expect(dbToLinear(6)).toBeCloseTo(1.995, 3);
});