automix.jsjavascript
/**
* @file Auto-mix gain math — the pure arithmetic behind the AUTO-MIX button.
*
* Extracted from renderer.html's applyInitialGain() so it can be unit-tested in plain
* Node (test/automix.test.js) without a DOM. The renderer still owns all DOM reads/writes;
* this module owns only the numbers, so the two stay in lock-step by construction.
*
* Loaded two ways (UMD-lite): the Electron renderer loads it with a plain script tag,
* which attaches window.AutoMix (no nodeIntegration needed; `module` is undefined there);
* Node tests require('../src/automix.js') and use module.exports.
*
* Units: LUFS = integrated loudness; dBFS = sample peak; the dBTP target ceiling is -0.1.
* All gains are clamped to [-24, +6] dB to match the per-track trim slider range.
* @module automix
*/
(function (root, factory) {
const api = factory();
if (typeof module === 'object' && module.exports) {
module.exports = api;
} else {
root.AutoMix = api;
}
})(typeof self !== 'undefined' ? self : this, function () {
'use strict';
/**
* The -0.1 dBTP true-peak ceiling everything normalises to (matches the export limiter).
* @type {number}
*/
const PEAK_CEILING_DBFS = -0.1;
/** Lower bound of the gain clamp, in dB — the trim slider's minimum. @type {number} */
const GAIN_MIN_DB = -24;
/** Upper bound of the gain clamp, in dB — the trim slider's maximum. @type {number} */
const GAIN_MAX_DB = 6;
/**
* Loudness of the Original Audio once peak-normalised to the ceiling: how loud the OA
* would read with its peak pushed up to -0.1 dBTP.
* @param {number} oaLufs OA integrated loudness as measured, in LUFS.
* @param {number} oaPeakDbfs OA sample peak as measured, in dBFS.
* @returns {number} Normalised OA loudness, in LUFS.
*/
function normalizedOaLufs(oaLufs, oaPeakDbfs) {
return oaLufs + (PEAK_CEILING_DBFS - oaPeakDbfs);
}
/**
* Target track loudness in OA mode: tracks sit `offsetDb` below the normalised OA
* (the Music Level slider).
* @param {number} oaLufs OA integrated loudness as measured, in LUFS.
* @param {number} oaPeakDbfs OA sample peak as measured, in dBFS.
* @param {number} offsetDb Music Level offset, in dB (negative = below the OA).
* @returns {number} Shared track target loudness, in LUFS.
*/
function targetLufsOA(oaLufs, oaPeakDbfs, offsetDb) {
return normalizedOaLufs(oaLufs, oaPeakDbfs) + offsetDb;
}
/**
* Target track loudness in no-OA mode: peak-normalise the quietest track to the ceiling
* and make that the shared target, so every track moves by the same anchoring gain and
* the mix balance is preserved.
* @param {number} quietestLufs Integrated loudness of the quietest track, in LUFS.
* @param {number} quietestPeakDbfs Sample peak of the quietest track, in dBFS.
* @returns {number} Shared track target loudness, in LUFS.
*/
function targetLufsNoOA(quietestLufs, quietestPeakDbfs) {
return quietestLufs + (PEAK_CEILING_DBFS - quietestPeakDbfs);
}
/**
* Clamp a gain to the trim slider's range, [-24, +6] dB.
* @param {number} db Gain, in dB.
* @returns {number} Clamped gain, in dB.
*/
function clampGainDb(db) {
return Math.max(GAIN_MIN_DB, Math.min(GAIN_MAX_DB, db));
}
/**
* The gain to bring a track from its measured loudness to the target, clamped to the
* slider range.
* @param {number} targetLufs Target loudness, in LUFS.
* @param {number} trackLufs Track's measured integrated loudness, in LUFS.
* @returns {number} Clamped gain, in dB.
*/
function trackGainDb(targetLufs, trackLufs) {
return clampGainDb(targetLufs - trackLufs);
}
/**
* Convert dB to a linear amplitude multiplier (what a Web Audio GainNode or ffmpeg's
* `volume` filter wants).
* @param {number} db Gain, in dB.
* @returns {number} Linear amplitude multiplier.
*/
function dbToLinear(db) {
return Math.pow(10, db / 20);
}
return {
PEAK_CEILING_DBFS,
GAIN_MIN_DB,
GAIN_MAX_DB,
normalizedOaLufs,
targetLufsOA,
targetLufsNoOA,
clampGainDb,
trackGainDb,
dbToLinear,
};
});