// master_gain.cpp — see master_gain.h. Pure math; no LICE/VST3/REAPER includes.

#include "core/instrument/engine/master_gain.h"

#include "core/util/clamp01.h"

#include <algorithm>
#include <cmath>
#include <limits>

namespace reasampler::instrument::engine {

using util::clamp01;

double masterGainMaxLinear() { return std::pow(10.0, kMasterGainMaxDb / 20.0); }

double masterGainDbFromNorm(double norm) {
    norm = clamp01(norm);
    if (norm <= 0.0) return -std::numeric_limits<double>::infinity();
    // UNITY IS EXACT, and the argument is arithmetic rather than structural — a host's
    // reset-to-default arrives here as toPlain(defaultNormalized) and must land on 0.0 dB, not a
    // hair off it. fl(60/84) differs from 60/84 by δ ≈ 1.6e-17; 84·δ ≈ 1.33e-15 sits under the
    // half-ulp of 60 (3.55e-15), so -60 + fl(60/84)·84 rounds to exactly 60 and the sum to 0.
    // PRECONDITION: no FP contraction. Fused into a single FMA the residue survives as 1.33e-15.
    // Safe on the shipped MSVC/x64 default (no FMA without /arch:AVX2); a build that enables
    // contraction here breaks the exactness test in test_param_units, which is where it surfaces.
    return kMasterGainMinDb + norm * (kMasterGainMaxDb - kMasterGainMinDb);
}

double masterGainNormFromDb(double db) {
    if (!(db > kMasterGainMinDb)) return 0.0;  // -inf, NaN, and the floor all read 0
    return clamp01((db - kMasterGainMinDb) / (kMasterGainMaxDb - kMasterGainMinDb));
}

double masterGainLinearFromNorm(double norm) {
    norm = clamp01(norm);
    if (norm <= 0.0) return 0.0;  // TRUE silence at the bottom — not an epsilon
    return std::pow(10.0, masterGainDbFromNorm(norm) / 20.0);
}

double masterGainNormFromLinear(double linear) {
    if (!std::isfinite(linear) || linear <= 0.0) return 0.0;
    return masterGainNormFromDb(20.0 * std::log10(linear));
}

}  // namespace reasampler::instrument::engine
