/*---------------------------------------------------------------------------*/
/*
  File: gain.cpp

  Gain linear and decibel.
*/
/*---------------------------------------------------------------------------*/

#include "platform.h"
#include "dyncompr.h"
#include "peaklim.h"
#include "gain.h"

#define FILE_ "gain.cpp"

/*---------------------------------------------------------------------------*/

void gain_exec(dword gain, word limit, const short *in_samples, short *out_samples, word sample_count)
{
    int32 l;
    unsigned int i;

    if (gain <= ((dword) limit)) {
        for (i = 0; i < sample_count; i++) {
            out_samples[i] = (short)(((((int32)(((dword) gain) * ((int32)(in_samples[i])))) >> 14) + 1) >> 1);
        }
    }
    else {
        for (i = 0; i < sample_count; i++) {
            l = (((int32)(((dword)(gain >> 16)) * ((int32)(in_samples[i])))) << 1) + (((int32)(((dword)(gain & 0xffff)) * ((int32)(in_samples[i])))) >> 15);
            if(l > limit) {
                out_samples[i] = limit;
            }
            else if(l < -limit) {
                out_samples[i] = -limit;
            }
            else {
                out_samples[i] = (short) l;
            }
        }
    }
}

void gain_peak_limited_init(t_gain_peak_limited * p_gainlim, dword gain, word limit)
{
    static short equalizer[1] = { 0x7fff };
    p_gainlim->gain = (gain < ((dword) limit) << 4) ? gain : (((dword) limit) << 4) - 1;
    p_gainlim->limit = limit;
    if (p_gainlim->gain > limit) {
        peak_limiter_init(&p_gainlim->limiter, (word)(((dword)((((dword) limit) << 16) / p_gainlim->gain)) >> 1), equalizer, 1);
    }
}

void gain_peak_limited_exec(t_gain_peak_limited * p_gainlim, const short *in_samples, short *out_samples, word sample_count)
{
    unsigned int i;

    if (p_gainlim->gain > p_gainlim->limit) {
        for (i = 0; i < sample_count; i++) {
            out_samples[i] = peak_limiter_exec(&p_gainlim->limiter, in_samples[i]);
        }
        gain_exec(p_gainlim->gain, 0x7fff, out_samples, out_samples, sample_count);
    }
    else {
        gain_exec(p_gainlim->gain, p_gainlim->limit, in_samples, out_samples, sample_count);
    }
}

void gain_dynamic_compressed_init(t_gain_dynamic_compressed * p_gaincompr, dword gain, word threshold, bool noise_suppress)
{
    p_gaincompr->gain = gain;
    p_gaincompr->threshold = threshold;
    if (gain > threshold) {
        if (gain >= 0x00010000) {
            p_gaincompr->norm = 0;
        }
        else {
            p_gaincompr->norm = 1;
            while (gain <= ((dword) 0x00008000) >> p_gaincompr->norm) {
                (p_gaincompr->norm)++;
            }
        }
        dword energy_threshold = ((dword)(((dword) threshold) * ((dword) threshold)));
        dynamic_compressor_init(&p_gaincompr->compressor, energy_threshold, gain << p_gaincompr->norm,
            noise_suppress ? 0x00000004 >> (2 * p_gaincompr->norm) : 0, 0x2000/*0x0800*/);
    }
}

void gain_dynamic_compressed_exec(t_gain_dynamic_compressed * p_gaincompr, const short *in_samples, short *out_samples, word sample_count)
{
    unsigned int i;

    if (p_gaincompr->gain > p_gaincompr->threshold) {
        for (i = 0; i < sample_count; i++) {
            out_samples[i] = dynamic_compressor_exec_for_gain(&p_gaincompr->compressor, in_samples[i] >> p_gaincompr->norm);
        }
    }
    else {
        gain_exec(p_gaincompr->gain, 0x7fff, in_samples, out_samples, sample_count);
    }
}

char gain_db_exp_table[0x80] =
{
    -22,-21,-21,-21,-20,-20,-20,-19,-19,-19,-18,-18,-18,-17,-17,-17,
    -16,-16,-16,-15,-15,-15,-14,-14,-14,-13,-13,-13,-12,-12,-12,-11,
    -11,-11,-10,-10,-10, -9, -9, -9, -8, -8, -8, -7, -7, -7, -6, -6,
     -6, -5, -5, -5, -4, -4, -4, -3, -3, -3, -2, -2, -2, -1, -1, -1,
      0,  0,  0,  1,  1,  1,  2,  2,  2,  3,  3,  3,  4,  4,  4,  5,
      5,  5,  6,  6,  6,  7,  7,  7,  8,  8,  8,  9,  9,  9, 10, 10,
     10, 11, 11, 11, 12, 12, 12, 13, 13, 13, 14, 14, 14, 15, 15, 15,
     16, 16, 16, 17, 17, 17, 18, 18, 18, 19, 19, 19, 20, 20, 20, 21,
};

word gain_db_mant_coarse_table[0x100] =
{
    0xcb30, 0xe412, 0x8000, 0x8fad, 0xa145, 0xb505, 0xcb30, 0xe412,
    0x8000, 0x8fad, 0xa145, 0xb505, 0xcb30, 0xe412, 0x8000, 0x8fad,
    0xa145, 0xb505, 0xcb30, 0xe412, 0x8000, 0x8fad, 0xa145, 0xb505,
    0xcb30, 0xe412, 0x8000, 0x8fad, 0xa145, 0xb505, 0xcb30, 0xe412,
    0x8000, 0x8fad, 0xa145, 0xb505, 0xcb30, 0xe412, 0x8000, 0x8fad,
    0xa145, 0xb505, 0xcb30, 0xe412, 0x8000, 0x8fad, 0xa145, 0xb505,
    0xcb30, 0xe412, 0x8000, 0x8fad, 0xa145, 0xb505, 0xcb30, 0xe412,
    0x8000, 0x8fad, 0xa145, 0xb505, 0xcb30, 0xe412, 0x8000, 0x8fad,
    0xa145, 0xb505, 0xcb30, 0xe412, 0x8000, 0x8fad, 0xa145, 0xb505,
    0xcb30, 0xe412, 0x8000, 0x8fad, 0xa145, 0xb505, 0xcb30, 0xe412,
    0x8000, 0x8fad, 0xa145, 0xb505, 0xcb30, 0xe412, 0x8000, 0x8fad,
    0xa145, 0xb505, 0xcb30, 0xe412, 0x8000, 0x8fad, 0xa145, 0xb505,
    0xcb30, 0xe412, 0x8000, 0x8fad, 0xa145, 0xb505, 0xcb30, 0xe412,
    0x8000, 0x8fad, 0xa145, 0xb505, 0xcb30, 0xe412, 0x8000, 0x8fad,
    0xa145, 0xb505, 0xcb30, 0xe412, 0x8000, 0x8fad, 0xa145, 0xb505,
    0xcb30, 0xe412, 0x8000, 0x8fad, 0xa145, 0xb505, 0xcb30, 0xe412,
    0x8000, 0x8fad, 0xa145, 0xb505, 0xcb30, 0xe412, 0x8000, 0x8fad,
    0xa145, 0xb505, 0xcb30, 0xe412, 0x8000, 0x8fad, 0xa145, 0xb505,
    0xcb30, 0xe412, 0x8000, 0x8fad, 0xa145, 0xb505, 0xcb30, 0xe412,
    0x8000, 0x8fad, 0xa145, 0xb505, 0xcb30, 0xe412, 0x8000, 0x8fad,
    0xa145, 0xb505, 0xcb30, 0xe412, 0x8000, 0x8fad, 0xa145, 0xb505,
    0xcb30, 0xe412, 0x8000, 0x8fad, 0xa145, 0xb505, 0xcb30, 0xe412,
    0x8000, 0x8fad, 0xa145, 0xb505, 0xcb30, 0xe412, 0x8000, 0x8fad,
    0xa145, 0xb505, 0xcb30, 0xe412, 0x8000, 0x8fad, 0xa145, 0xb505,
    0xcb30, 0xe412, 0x8000, 0x8fad, 0xa145, 0xb505, 0xcb30, 0xe412,
    0x8000, 0x8fad, 0xa145, 0xb505, 0xcb30, 0xe412, 0x8000, 0x8fad,
    0xa145, 0xb505, 0xcb30, 0xe412, 0x8000, 0x8fad, 0xa145, 0xb505,
    0xcb30, 0xe412, 0x8000, 0x8fad, 0xa145, 0xb505, 0xcb30, 0xe412,
    0x8000, 0x8fad, 0xa145, 0xb505, 0xcb30, 0xe412, 0x8000, 0x8fad,
    0xa145, 0xb505, 0xcb30, 0xe412, 0x8000, 0x8fad, 0xa145, 0xb505,
    0xcb30, 0xe412, 0x8000, 0x8fad, 0xa145, 0xb505, 0xcb30, 0xe412,
    0x8000, 0x8fad, 0xa145, 0xb505, 0xcb30, 0xe412, 0x8000, 0x8fad,
};

word gain_db_mant_fine_table[0x100] =
{
    0x8000, 0x800f, 0x801e, 0x802c, 0x803b, 0x804a, 0x8059, 0x8068,
    0x8077, 0x8085, 0x8094, 0x80a3, 0x80b2, 0x80c1, 0x80d0, 0x80df,
    0x80ed, 0x80fc, 0x810b, 0x811a, 0x8129, 0x8138, 0x8147, 0x8156,
    0x8165, 0x8174, 0x8183, 0x8192, 0x81a1, 0x81b0, 0x81bf, 0x81ce,
    0x81dd, 0x81ec, 0x81fb, 0x820a, 0x8219, 0x8228, 0x8237, 0x8246,
    0x8255, 0x8264, 0x8273, 0x8282, 0x8291, 0x82a0, 0x82af, 0x82be,
    0x82ce, 0x82dd, 0x82ec, 0x82fb, 0x830a, 0x8319, 0x8328, 0x8337,
    0x8347, 0x8356, 0x8365, 0x8374, 0x8383, 0x8393, 0x83a2, 0x83b1,
    0x83c0, 0x83cf, 0x83df, 0x83ee, 0x83fd, 0x840c, 0x841c, 0x842b,
    0x843a, 0x8449, 0x8459, 0x8468, 0x8477, 0x8487, 0x8496, 0x84a5,
    0x84b5, 0x84c4, 0x84d3, 0x84e3, 0x84f2, 0x8501, 0x8511, 0x8520,
    0x852f, 0x853f, 0x854e, 0x855e, 0x856d, 0x857c, 0x858c, 0x859b,
    0x85ab, 0x85ba, 0x85ca, 0x85d9, 0x85e9, 0x85f8, 0x8608, 0x8617,
    0x8627, 0x8636, 0x8646, 0x8655, 0x8665, 0x8674, 0x8684, 0x8693,
    0x86a3, 0x86b2, 0x86c2, 0x86d1, 0x86e1, 0x86f1, 0x8700, 0x8710,
    0x871f, 0x872f, 0x873f, 0x874e, 0x875e, 0x876e, 0x877d, 0x878d,
    0x879c, 0x87ac, 0x87bc, 0x87cc, 0x87db, 0x87eb, 0x87fb, 0x880a,
    0x881a, 0x882a, 0x883a, 0x8849, 0x8859, 0x8869, 0x8879, 0x8888,
    0x8898, 0x88a8, 0x88b8, 0x88c7, 0x88d7, 0x88e7, 0x88f7, 0x8907,
    0x8917, 0x8926, 0x8936, 0x8946, 0x8956, 0x8966, 0x8976, 0x8986,
    0x8995, 0x89a5, 0x89b5, 0x89c5, 0x89d5, 0x89e5, 0x89f5, 0x8a05,
    0x8a15, 0x8a25, 0x8a35, 0x8a45, 0x8a55, 0x8a65, 0x8a75, 0x8a85,
    0x8a95, 0x8aa5, 0x8ab5, 0x8ac5, 0x8ad5, 0x8ae5, 0x8af5, 0x8b05,
    0x8b15, 0x8b25, 0x8b35, 0x8b45, 0x8b55, 0x8b65, 0x8b76, 0x8b86,
    0x8b96, 0x8ba6, 0x8bb6, 0x8bc6, 0x8bd6, 0x8be6, 0x8bf7, 0x8c07,
    0x8c17, 0x8c27, 0x8c37, 0x8c48, 0x8c58, 0x8c68, 0x8c78, 0x8c88,
    0x8c99, 0x8ca9, 0x8cb9, 0x8cc9, 0x8cda, 0x8cea, 0x8cfa, 0x8d0b,
    0x8d1b, 0x8d2b, 0x8d3b, 0x8d4c, 0x8d5c, 0x8d6c, 0x8d7d, 0x8d8d,
    0x8d9e, 0x8dae, 0x8dbe, 0x8dcf, 0x8ddf, 0x8def, 0x8e00, 0x8e10,
    0x8e21, 0x8e31, 0x8e41, 0x8e52, 0x8e62, 0x8e73, 0x8e83, 0x8e94,
    0x8ea4, 0x8eb5, 0x8ec5, 0x8ed6, 0x8ee6, 0x8ef7, 0x8f07, 0x8f18,
    0x8f28, 0x8f39, 0x8f49, 0x8f5a, 0x8f6b, 0x8f7b, 0x8f8c, 0x8f9c,
};

void gain_db_exec(short db_256th, short limit_db_256th, const short *in_samples, short *out_samples, word sample_count)
{
    int32 l, rnd;
    unsigned int i;

    int sft = 15 - GAIN_DB_EXP(db_256th);
    word mlt = GAIN_DB_MANT(db_256th);
    if (db_256th <= limit_db_256th) {
        if (sft >= 32) {
            memset(out_samples, 0, sample_count * sizeof(short));
        }
        else {
            rnd = (1 << sft) >> 1;
            for (i = 0; i < sample_count; i++) {
                out_samples[i] = (short)(((int32)(((dword) mlt) * ((int32)(in_samples[i])) + rnd)) >> sft);
            }
        }
    }
    else {
        if (sft < 0) {
            sft = 0;
            mlt = 0xffff;
        }
        word lim = (limit_db_256th >= 0) ? 0x7fff : GAIN_DB_MANT(limit_db_256th) >> -GAIN_DB_EXP(limit_db_256th);
        rnd = (1 << sft) >> 1;
        for (i = 0; i < sample_count; i++) {
            l = ((int32)(((dword) mlt) * ((int32)(in_samples[i])) + rnd)) >> sft;
            if(l > lim) {
                out_samples[i] = lim;
            }
            else if(l < -lim) {
                out_samples[i] = -lim;
            }
            else {
                out_samples[i] = (short) l;
            }
        }
    }
}

void gain_db_peak_limited_init(t_gain_db_peak_limited * p_gainlim, short db_256th, short limit_db_256th)
{
    static short equalizer[1] = { 0x7fff };
    p_gainlim->db_256th = (db_256th < limit_db_256th + 4 * 0x600) ? db_256th : limit_db_256th + 4 * 0x600 - 1;
    p_gainlim->limit_db_256th = limit_db_256th;
    if (db_256th > limit_db_256th) {
        peak_limiter_init(&p_gainlim->limiter, GAIN_DB_MANT(limit_db_256th - p_gainlim->db_256th) >> -GAIN_DB_EXP(limit_db_256th - p_gainlim->db_256th), equalizer, 1);
    }
}

void gain_db_peak_limited_exec(t_gain_db_peak_limited * p_gainlim, const short *in_samples, short *out_samples, word sample_count)
{
    unsigned int i;

    if (p_gainlim->db_256th > p_gainlim->limit_db_256th) {
        for (i = 0; i < sample_count; i++) {
            out_samples[i] = peak_limiter_exec(&p_gainlim->limiter, in_samples[i]);
        }
        gain_db_exec(p_gainlim->db_256th, 0, out_samples, out_samples, sample_count);
    }
    else {
        gain_db_exec(p_gainlim->db_256th, p_gainlim->limit_db_256th, in_samples, out_samples, sample_count);
    }
}

void gain_db_dynamic_compressed_init(t_gain_db_dynamic_compressed * p_gaincompr, short db_256th, short threshold_db_256th, bool noise_suppress)
{
    p_gaincompr->db_256th = (db_256th < 16 * 0x600) ? db_256th : 16 * 0x600 - 1;
    p_gaincompr->threshold_db_256th = threshold_db_256th;
    if (p_gaincompr->db_256th > threshold_db_256th) {
        if (p_gaincompr->db_256th >= 0x600) {
            p_gaincompr->norm = 0;
        }
        else {
            p_gaincompr->norm = 1;
            while (p_gaincompr->db_256th <= -(0x600 * p_gaincompr->norm)) {
                (p_gaincompr->norm)++;
            }
        }
        int sft = GAIN_DB_EXP(threshold_db_256th);
        word mlt = GAIN_DB_MANT(threshold_db_256th);
        dword energy_threshold = ((dword)(((dword) mlt) * ((dword) mlt))) >> (2 * -sft);
        dynamic_compressor_init(&p_gaincompr->compressor, energy_threshold, ((dword)(GAIN_DB_MANT(p_gaincompr->db_256th))) << (GAIN_DB_EXP(p_gaincompr->db_256th) + p_gaincompr->norm),
            noise_suppress ? 0x00000004 >> (2 * p_gaincompr->norm) : 0, 0x2000/*0x0800*/);
    }
}

void gain_db_dynamic_compressed_exec(t_gain_db_dynamic_compressed * p_gaincompr, const short *in_samples, short *out_samples, word sample_count)
{
    unsigned int i;

    if (p_gaincompr->db_256th > p_gaincompr->threshold_db_256th) {
        for (i = 0; i < sample_count; i++) {
            out_samples[i] = dynamic_compressor_exec_for_gain(&p_gaincompr->compressor, in_samples[i] >> p_gaincompr->norm);
        }
    }
    else {
        gain_db_exec(p_gaincompr->db_256th, 0, in_samples, out_samples, sample_count);
    }
}


/*---------------------------------------------------------------------------*/
