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,
  };
});