#pragma once
// mp3enc-psy.h
// Psychoacoustic model for MP3 encoding.
// Computes masking thresholds per scalefactor band from MDCT coefficients.
//
// Based on ISO 11172-3 Annex D principles:
//   - Absolute threshold of hearing (ATH)
//   - Bark-domain asymmetric spreading function (Schroeder)
//   - Tonal vs noise masking detection (spectral flatness)
//   - Variable masking offset: tonal -14.5 dB, noise -5.5 dB
//
// Constants from ISO 11172-3 Annex D and Zwicker (1961).
// MIT license.

#include <cmath>

// Number of scalefactor bands for long blocks
#define MP3ENC_PSY_SFB_MAX 21

// Absolute threshold of hearing in dB SPL.
// ISO formula: ATH(f) = 3.64*(f/1000)^-0.8 - 6.5*exp(-0.6*(f/1000-3.3)^2) + 1e-3*(f/1000)^4
// Minimum clamped at -20 dB to avoid numerical issues.
static inline float mp3enc_ath_db(float freq_hz) {
    if (freq_hz < 10.0f) {
        freq_hz = 10.0f;
    }
    float fk  = freq_hz * 0.001f;
    float ath = 3.64f * powf(fk, -0.8f) - 6.5f * expf(-0.6f * (fk - 3.3f) * (fk - 3.3f)) + 0.001f * fk * fk * fk * fk;
    if (ath < -20.0f) {
        ath = -20.0f;
    }
    return ath;
}

// Convert frequency in Hz to Bark scale.
// Traunmuller (1990) approximation, accurate to ~0.05 Bark.
static inline float mp3enc_hz_to_bark(float f) {
    if (f < 1.0f) {
        f = 1.0f;
    }
    return 13.0f * atanf(0.00076f * f) + 3.5f * atanf((f / 7500.0f) * (f / 7500.0f));
}

// Schroeder spreading function in dB.
// dz = bark distance from masker to maskee (positive = maskee above masker).
// This models the asymmetric excitation pattern of the basilar membrane:
// steep below the masker (~27 dB/Bark), shallow above (~10-25 dB/Bark).
// From Schroeder, Atal, Hall (1979).
static inline float mp3enc_spreading_db(float dz) {
    float t = dz + 0.474f;
    return 15.81f + 7.5f * t - 17.5f * sqrtf(1.0f + t * t);
}

// Psychoacoustic model state.
struct mp3enc_psy {
    // Output: allowed distortion energy per SFB
    float xmin[MP3ENC_PSY_SFB_MAX];

    // Output: perceptual entropy for this granule/channel (ISO 11172-3 Annex D).
    // Higher PE = more complex signal = needs more bits.
    float pe;

    // Forward masking: previous granule's masking energy (per channel)
    float prev_mask[2][MP3ENC_PSY_SFB_MAX];

    // Pre-echo control: 2 previous granules of spread energy (per channel).
    // Used to prevent masking threshold from rising too fast on transients
    // (ISO 11172-3 Annex D, l3psy.c lines 610-616).
    float nb_1[2][MP3ENC_PSY_SFB_MAX];  // previous granule spread energy
    float nb_2[2][MP3ENC_PSY_SFB_MAX];  // 2 granules ago spread energy

    // Precomputed per-SFB data (set once per sample rate)
    float ath_energy[3][MP3ENC_PSY_SFB_MAX];  // [sr_index][sfb]: ATH in linear power
    float sfb_bark[3][MP3ENC_PSY_SFB_MAX];    // [sr_index][sfb]: center freq in Bark
    bool  ath_valid;

    void init() {
        ath_valid = false;
        pe        = 0.0f;
        memset(xmin, 0, sizeof(xmin));
        memset(prev_mask, 0, sizeof(prev_mask));
        memset(nb_1, 0, sizeof(nb_1));
        memset(nb_2, 0, sizeof(nb_2));
    }

    // Precompute ATH energy and Bark positions per SFB.
    // Must be called once before compute().
    void init_ath(int sr_index, const uint8_t * sfb_table, int sample_rate) {
        // MDCT has 576 lines covering 0 to samplerate/2.
        // Each line represents a frequency bin of width samplerate / (2*576).
        float freq_per_line = (float) sample_rate / (2.0f * 576.0f);

        int pos = 0;
        for (int sfb = 0; sfb < MP3ENC_PSY_SFB_MAX; sfb++) {
            int width = sfb_table[sfb];
            if (width == 0) {
                ath_energy[sr_index][sfb] = 1e-20f;
                sfb_bark[sr_index][sfb]   = 0.0f;
                continue;
            }

            // Center frequency of this SFB
            float center_freq       = ((float) pos + (float) width * 0.5f) * freq_per_line;
            sfb_bark[sr_index][sfb] = mp3enc_hz_to_bark(center_freq);

            // ATH: minimum of all lines in the band (most permissive)
            float ath_min_db = 200.0f;
            for (int j = 0; j < width; j++) {
                float freq = ((float) (pos + j) + 0.5f) * freq_per_line;
                float db   = mp3enc_ath_db(freq);
                if (db < ath_min_db) {
                    ath_min_db = db;
                }
            }

            // Convert dB SPL to linear power, scaled by band width.
            // Reference at 120 dB SPL (tuned empirically). A higher reference
            // makes the ATH floor less dominant relative to the spreading
            // function, so bits are spent on perceptual masking rather than
            // fighting the absolute hearing threshold in quiet passages.
            float ath_linear          = powf(10.0f, (ath_min_db - 120.0f) * 0.1f) * (float) width;
            ath_energy[sr_index][sfb] = ath_linear;

            pos += width;
        }
        ath_valid = true;
    }

    // Compute masking thresholds for one granule/channel.
    //
    // Algorithm:
    //   1. Compute energy per SFB from MDCT coefficients
    //   2. Estimate tonality per SFB (spectral flatness measure)
    //   3. Compute masking offset per SFB based on tonality
    //   4. Apply Bark-domain spreading function
    //   5. Combine spread masking with ATH
    //
    // mdct: 576 MDCT coefficients (after MS stereo if applicable)
    // sfb_table: SFB widths for this sample rate
    // sr_index: sample rate index
    void compute(const float * mdct, const uint8_t * sfb_table, int sr_index, int ch = 0) {
        float energy[MP3ENC_PSY_SFB_MAX];
        float tonality[MP3ENC_PSY_SFB_MAX];

        // Step 1: compute energy per SFB
        int pos = 0;
        for (int sfb = 0; sfb < MP3ENC_PSY_SFB_MAX; sfb++) {
            int   width = sfb_table[sfb];
            float e     = 0.0f;
            for (int j = 0; j < width; j++) {
                float x = mdct[pos + j];
                e += x * x;
            }
            energy[sfb] = e;
            pos += width;
        }

        // Step 2: estimate tonality per SFB using spectral flatness measure (SFM).
        // SFM = geometric_mean(power) / arithmetic_mean(power)
        // SFM = 1.0 for flat noise, SFM -> 0 for a single tone.
        // We use log domain to avoid overflow: log(geometric_mean) = mean(log(power)).
        pos = 0;
        for (int sfb = 0; sfb < MP3ENC_PSY_SFB_MAX; sfb++) {
            int width = sfb_table[sfb];
            if (width == 0 || energy[sfb] < 1e-20f) {
                tonality[sfb] = 0.5f;  // default: assume mixed
                pos += width;
                continue;
            }

            float log_sum  = 0.0f;
            float arith    = 0.0f;
            int   n_active = 0;
            for (int j = 0; j < width; j++) {
                float p = mdct[pos + j] * mdct[pos + j];
                if (p > 1e-20f) {
                    log_sum += logf(p);
                    n_active++;
                }
                arith += p;
            }

            if (n_active < 2) {
                // Single line or silence: treat as tonal
                tonality[sfb] = 0.0f;
            } else {
                float geom_log   = log_sum / (float) n_active;
                float arith_mean = arith / (float) n_active;
                // SFM in log domain: log(geom/arith) = geom_log - log(arith)
                float sfm_log    = geom_log - logf(arith_mean);
                // sfm_log is <= 0. For flat spectrum sfm_log ~ 0, for tonal sfm_log << 0.
                // Map to tonality index alpha in [0,1]:
                //   alpha = min(sfm_log / log(0.01), 1.0)
                // log(0.01) = -4.605; so if sfm_log < -4.6 we consider it fully tonal.
                float alpha      = sfm_log / -4.605f;
                if (alpha < 0.0f) {
                    alpha = 0.0f;
                }
                if (alpha > 1.0f) {
                    alpha = 1.0f;
                }
                tonality[sfb] = alpha;  // 0 = noise, 1 = tonal
            }
            pos += width;
        }

        // Step 3: compute masking offset per SFB.
        // TMN/NMT from ISO 11172-3, relaxed empirically for 128kbps.
        // TMN=13.0 (tonal masking noise), NMT=4.5 (noise masking tone).
        float offset_linear[MP3ENC_PSY_SFB_MAX];
        for (int sfb = 0; sfb < MP3ENC_PSY_SFB_MAX; sfb++) {
            float alpha        = tonality[sfb];
            float offset_db    = alpha * 13.0f + (1.0f - alpha) * 4.5f;
            offset_linear[sfb] = powf(10.0f, -offset_db * 0.1f);
        }

        // Step 4: Bark-domain spreading function.
        // For each target SFB, sum the spread contributions from all source SFBs.
        // The spreading function is asymmetric: steep below, shallow above.
        float spread_energy[MP3ENC_PSY_SFB_MAX];
        for (int j = 0; j < MP3ENC_PSY_SFB_MAX; j++) {
            float sum    = 0.0f;
            float bark_j = sfb_bark[sr_index][j];

            for (int i = 0; i < MP3ENC_PSY_SFB_MAX; i++) {
                if (energy[i] < 1e-20f) {
                    continue;
                }

                float bark_i = sfb_bark[sr_index][i];
                float dz_raw = bark_j - bark_i;

                // Asymmetric spreading (ISO 11172-3 psy model 2, L3para_read).
                // Upward masking (dz > 0): gentle slope, low freqs mask highs well.
                // Downward masking (dz < 0): steep slope, highs mask lows poorly.
                float dz = (dz_raw >= 0.0f) ? dz_raw * 1.35f : dz_raw * 2.7f;

                // Spreading function value in dB
                float sf_db = mp3enc_spreading_db(dz);

                // Only apply if spreading is above -60 dB (optimization)
                if (sf_db < -60.0f) {
                    continue;
                }

                float sf_linear = powf(10.0f, sf_db * 0.1f);
                sum += energy[i] * sf_linear;
            }
            spread_energy[j] = sum;
        }

        // Step 5: combine spreading + offset, then apply pre-echo control.
        // Pre-echo (ISO 11172-3 Annex D): prevent threshold from rising too fast
        // on transients. Clamp current nb by 2x previous and 16x two-back.
        // xmin = max(ath, min(nb, 2*nb_1, 16*nb_2))
        for (int sfb = 0; sfb < MP3ENC_PSY_SFB_MAX; sfb++) {
            float nb = spread_energy[sfb] * offset_linear[sfb];

            // Pre-echo clamp: use history to limit sudden threshold rises
            float clamped = nb;
            float lim1    = 2.0f * nb_1[ch][sfb];
            float lim2    = 16.0f * nb_2[ch][sfb];
            if (lim1 > 0.0f && lim1 < clamped) {
                clamped = lim1;
            }
            if (lim2 > 0.0f && lim2 < clamped) {
                clamped = lim2;
            }

            // Update history (store UN-clamped nb for future reference)
            nb_2[ch][sfb] = nb_1[ch][sfb];
            nb_1[ch][sfb] = nb;

            // ATH floor
            float ath = ath_energy[sr_index][sfb];
            xmin[sfb] = (clamped > ath) ? clamped : ath;
        }

        // Step 6: forward masking (temporal).
        // A loud granule raises the masking threshold for the next granule.
        // At 44.1kHz one granule = 13ms. Forward masking decays ~12dB over 13ms.
        // Decay factor: 10^(-12/10) = ~0.063.
        static const float fwd_decay = 0.063f;
        for (int sfb = 0; sfb < MP3ENC_PSY_SFB_MAX; sfb++) {
            float fwd = prev_mask[ch][sfb] * fwd_decay;
            if (fwd > xmin[sfb]) {
                xmin[sfb] = fwd;
            }
            // Save current mask (spread_energy, not xmin) for next granule
            prev_mask[ch][sfb] = spread_energy[sfb];
        }

        // Step 7: perceptual entropy (ISO 11172-3 Annex D).
        // PE = sum of width * log(energy/threshold) for bands where energy > threshold.
        // Used by the bit reservoir to give more bits to complex granules.
        pe = 0.0f;
        for (int sfb = 0; sfb < MP3ENC_PSY_SFB_MAX; sfb++) {
            if (energy[sfb] > xmin[sfb] && xmin[sfb] > 1e-20f) {
                pe += (float) sfb_table[sfb] * logf(energy[sfb] / xmin[sfb]);
            }
        }
    }
};
