#include "global.h"

/*====================================================================
 *
 * Copyright 1993, Silicon Graphics, Inc.
 * All Rights Reserved.
 *
 * This is UNPUBLISHED PROPRIETARY SOURCE CODE of Silicon Graphics,
 * Inc.; the contents of this file may not be disclosed to third
 * parties, copied or duplicated in any form, in whole or in part,
 * without the prior written permission of Silicon Graphics, Inc.
 *
 * RESTRICTED RIGHTS LEGEND:
 * Use, duplication or disclosure by the Government is subject to
 * restrictions as set forth in subdivision (c)(1)(ii) of the Rights
 * in Technical Data and Computer Software clause at DFARS
 * 252.227-7013, and/or in similar or successor clauses in the FAR,
 * DOD or NASA FAR Supplement. Unpublished - rights reserved under the
 * Copyright Laws of the United States.
 *====================================================================*/

#include "n_synthInternals.h"
#include <os.h>
#include <stdio.h>
#include <math.h>

#ifdef _DEBUG
#include <assert.h>
#endif

#ifdef AUD_PROFILE
extern u32 cnt_index, env_num, env_cnt, env_max, env_min, lastCnt[];
extern u32 rate_num, rate_cnt, rate_max, rate_min;
extern u32 vol_num, vol_cnt, vol_max, vol_min;
#endif

#define N_EQPOWER_LENGTH 128

s16 n_eqpower[N_EQPOWER_LENGTH] = {
    32767, 32764, 32757, 32744, 32727, 32704, 32677, 32644, 32607, 32564, 32517, 32464, 32407, 32344, 32277, 32205,
    32127, 32045, 31958, 31866, 31770, 31668, 31561, 31450, 31334, 31213, 31087, 30957, 30822, 30682, 30537, 30388,
    30234, 30075, 29912, 29744, 29572, 29395, 29214, 29028, 28838, 28643, 28444, 28241, 28033, 27821, 27605, 27385,
    27160, 26931, 26698, 26461, 26220, 25975, 25726, 25473, 25216, 24956, 24691, 24423, 24151, 23875, 23596, 23313,
    23026, 22736, 22442, 22145, 21845, 21541, 21234, 20924, 20610, 20294, 19974, 19651, 19325, 18997, 18665, 18331,
    17993, 17653, 17310, 16965, 16617, 16266, 15913, 15558, 15200, 14840, 14477, 14113, 13746, 13377, 13006, 12633,
    12258, 11881, 11503, 11122, 10740, 10357, 9971,  9584,  9196,  8806,  8415,  8023,  7630,  7235,  6839,  6442,
    6044,  5646,  5246,  4845,  4444,  4042,  3640,  3237,  2833,  2429,  2025,  1620,  1216,  810,   405,   0
};

#ifndef N_MICRO
extern f64 __pow(f64, f64);
extern f64 _frexpf(f64 value, s32* eptr);
extern f64 _ldexpf(f64 in, s32 ex);
#endif

/*
 * prototypes for private enveloper functions
 */
static Acmd* _pullSubFrame(N_PVoice* pv, s16* inp, s16* outp, s32 outCount, Acmd* p);

static s16 _getRate(f64 vol, f64 tgt, s32 count, u16* ratel);

#ifndef N_MICRO
static f32 _getVol(f32 ivol, s32 samples, s16 ratem, u16 ratel);
#else
static s16 _getVol(s16 ivol, s32 samples, s16 ratem, u16 ratel);
#endif

/***********************************************************************
 * Enveloper filter public interfaces
 ***********************************************************************/
Acmd* n_alEnvmixerPull(N_PVoice* filter, s32 sampleOffset, Acmd* p) {
    Acmd* ptr = p;

    N_PVoice* e = (N_PVoice*)filter;

    s16 inp;
    s32 lastOffset;
    s32 thisOffset = sampleOffset;
    s32 samples;
    s16 loutp = 0;
    s32 fVol;
    ALParam* thisParam;
    s32 outCount = FIXED_SAMPLE;

#ifdef AUD_PROFILE
    lastCnt[++cnt_index] = osGetCount();
#endif

    /*
     * Force the input to be the resampler output
     */
#ifndef N_MICRO
    inp = AL_RESAMPLER_OUT;
#else
    inp = N_AL_RESAMPLER_OUT;
#endif

    while (e->em_ctrlList != 0) {

        lastOffset = thisOffset;
        thisOffset = e->em_ctrlList->delta;

#ifdef FINAL_ROUND
        samples = SAMPLE184(thisOffset - lastOffset);
        if (!samples)
            thisOffset = lastOffset;
#else
        samples = thisOffset - lastOffset;
#endif

        if (samples > outCount) {
            break;
        }

#ifdef _DEBUG
        assert(samples >= 0);
#ifndef N_MICRO
        assert(samples <= AL_MAX_RSP_SAMPLES);
#else
        assert(samples <= FIXED_SAMPLE);
#endif
#endif /* _DEBUG */

        switch (e->em_ctrlList->type) {
            case (AL_FILTER_START_VOICE_ALT): {
                ALStartParamAlt* param = (ALStartParamAlt*)e->em_ctrlList;
                ALFilter* f = (ALFilter*)e;
                s32 tmp;

                if (param->unity) {
                    e->rs_upitch = 1;
                }

                n_alLoadParam(e, AL_FILTER_SET_WAVETABLE, param->wave);
                e->em_motion = AL_PLAYING;
                e->em_first = 1;
                e->em_delta = 0;

#ifdef FINAL_ROUND
                e->em_segEnd = SAMPLE184(param->samples);
#else
                e->em_segEnd = param->samples;
#endif

                tmp = ((s32)param->volume * (s32)param->volume) >> 15;

                e->em_volume = (s16)tmp;
                e->em_pan = param->pan;
                e->em_dryamt = n_eqpower[param->fxMix];
                e->em_wetamt = n_eqpower[N_EQPOWER_LENGTH - param->fxMix - 1];

                if (param->samples) {
                    e->em_cvolL = 1;
                    e->em_cvolR = 1;
                } else {
                    /*
                     * Attack time is zero. Simply set the
                     * volume. We don't want an attack segment.
                     */
                    e->em_cvolL = (e->em_volume * n_eqpower[e->em_pan]) >> 15;
                    e->em_cvolR = (e->em_volume * n_eqpower[N_EQPOWER_LENGTH - e->em_pan - 1]) >> 15;
                }
                e->rs_ratio = param->pitch;
            }

            break;

            case (AL_FILTER_SET_FXAMT):
            case (AL_FILTER_SET_PAN):
            case (AL_FILTER_SET_VOLUME):
                ptr = _pullSubFrame(e, &inp, &loutp, samples, ptr);

                if (e->em_delta >= e->em_segEnd) {

                    /*
                     * We should have reached our target, calculate
                     * target in case e->em_segEnd was 0
                     */
                    e->em_ltgt = (e->em_volume * n_eqpower[e->em_pan]) >> 15;
                    e->em_rtgt = (e->em_volume * n_eqpower[N_EQPOWER_LENGTH - e->em_pan - 1]) >> 15;
                    e->em_delta = e->em_segEnd; /* To prevent overflow */
                    e->em_cvolL = e->em_ltgt;
                    e->em_cvolR = e->em_rtgt;
                } else {
                    /*
                     * Estimate the current volume
                     */
                    e->em_cvolL = _getVol(e->em_cvolL, e->em_delta, e->em_lratm, e->em_lratl);
                    e->em_cvolR = _getVol(e->em_cvolR, e->em_delta, e->em_rratm, e->em_rratl);
                }

                /*
                 * We can't have volume of zero, because the envelope
                 * would never go anywhere from there
                 */
                if (e->em_cvolL == 0) {
                    e->em_cvolL = 1;
                }
                if (e->em_cvolR == 0) {
                    e->em_cvolR = 1;
                }

                if (e->em_ctrlList->type == AL_FILTER_SET_PAN)

                    /*
                     * This should result in a change to the current
                     * segment rate and target
                     */
                    e->em_pan = (s16)e->em_ctrlList->data.i;

                if (e->em_ctrlList->type == AL_FILTER_SET_VOLUME) {

                    /*
                     * Switching to a new segment
                     */
                    e->em_delta = 0;

                    /*
                     * Map volume non-linearly to give something close to
                     * loudness
                     */
                    fVol = (e->em_ctrlList->data.i);
                    fVol = (fVol * fVol) >> 15;
                    e->em_volume = (s16)fVol;

#ifdef FINAL_ROUND
                    e->em_segEnd = SAMPLE184(e->em_ctrlList->moredata.i);
#else
                    e->em_segEnd = e->em_ctrlList->moredata.i;
#endif
                }

                if (e->em_ctrlList->type == AL_FILTER_SET_FXAMT) {
                    e->em_dryamt = n_eqpower[e->em_ctrlList->data.i];
                    e->em_wetamt = n_eqpower[N_EQPOWER_LENGTH - e->em_ctrlList->data.i - 1];
                }

                /*
                 * Force a volume update
                 */
                e->em_first = 1;
                break;

            case (AL_FILTER_START_VOICE): {
                ALStartParam* p = (ALStartParam*)e->em_ctrlList;

                /*
                 * Changing to PLAYING (since the previous state was
                 * persumable STOPPED, we'll just bump the output
                 * pointer rather than pull a subframe of zeros).
                 */
                if (p->unity) {
                    e->rs_upitch = 1;
                }

                n_alLoadParam(e, AL_FILTER_SET_WAVETABLE, p->wave);
                e->em_motion = AL_PLAYING;
            } break;
            case (AL_FILTER_STOP_VOICE): {
                /*
                 * Changing to STOPPED and reset the filter
                 */
                ptr = _pullSubFrame(e, &inp, &loutp, samples, ptr);
                n_alEnvmixerParam(e, AL_FILTER_RESET, 0);
            } break;

            case (AL_FILTER_FREE_VOICE): {
                N_ALFreeParam* param = (N_ALFreeParam*)e->em_ctrlList;
                param->pvoice->offset = 0;
                _n_freePVoice((N_PVoice*)param->pvoice);
            } break;
#if 1
            case (AL_FILTER_SET_PITCH):
                ptr = _pullSubFrame(e, &inp, &loutp, samples, ptr);
                e->rs_ratio = e->em_ctrlList->data.f;
                break;
            case (AL_FILTER_SET_UNITY_PITCH):
                ptr = _pullSubFrame(e, &inp, &loutp, samples, ptr);
                e->rs_upitch = 1;
                break;
            case (AL_FILTER_SET_WAVETABLE):
                ptr = _pullSubFrame(e, &inp, &loutp, samples, ptr);
                n_alLoadParam(e, AL_FILTER_SET_WAVETABLE, (void*)e->em_ctrlList->data.i);
                break;
#endif
            default:
                /*
                 * Pull the reuired number of samples and then pass the message
                 * on down the chain
                 */
                ptr = _pullSubFrame(e, &inp, &loutp, samples, ptr);
                n_alEnvmixerParam(e, e->em_ctrlList->type, (void*)e->em_ctrlList->data.i);
                break;
        }
        loutp += (samples << 1);
        outCount -= samples;

        /*
         * put the param record back on the free list
         */
        thisParam = e->em_ctrlList;
        e->em_ctrlList = e->em_ctrlList->next;
        if (e->em_ctrlList == 0) {
            e->em_ctrlTail = 0;
        }

        _n_freeParam(thisParam);
    }

    ptr = _pullSubFrame(e, &inp, &loutp, outCount, ptr);

    /*
     * Prevent overflow in e->em_delta
     */
    if (e->em_delta > e->em_segEnd) {
        e->em_delta = e->em_segEnd;
    }

#ifdef AUD_PROFILE
    PROFILE_AUD(env_num, env_cnt, env_max, env_min);
#endif
    return ptr;
}

s32 n_alEnvmixerParam(N_PVoice* filter, s32 paramID, void* param) {
    N_PVoice* e = filter;

    switch (paramID) {
        case (AL_FILTER_ADD_UPDATE):
            if (e->em_ctrlTail) {
                e->em_ctrlTail->next = (ALParam*)param;
            } else {
                e->em_ctrlList = (ALParam*)param;
            }
            e->em_ctrlTail = (ALParam*)param;
            break;
        case (AL_FILTER_RESET):
            e->em_first = 1;
            e->em_motion = AL_STOPPED;
            e->em_volume = 1;
            e->em_segEnd = 0;
            e->rs_delta = 0.0;
            e->rs_first = 1;
            e->rs_upitch = 0;
            n_alLoadParam(e, AL_FILTER_RESET, param);
            break;
        case (AL_FILTER_START):
            e->em_motion = AL_PLAYING;
            break;
        default:
#if 1
            n_alLoadParam(e, paramID, param);
#else
            n_alResampleParam(e, paramID, param);
#endif
            break;
    }
    return 0;
}

static Acmd* _pullSubFrame(N_PVoice* filter, s16* inp, s16* outp, s32 outCount, Acmd* p) {
    Acmd* ptr = p;
    N_PVoice* e = filter;
#ifndef N_MICRO
    s16* inpp = AL_RESAMPLER_OUT;
#else
    s16* inpp = N_AL_RESAMPLER_OUT;
#endif

    /* filter must be playing and request non-zero output samples to pull. */
    if (e->em_motion != AL_PLAYING || !outCount)
        return ptr;

    /*
     * ask all filters upstream from us to build their command
     * lists.
     */

    ptr = n_alResamplePull(e, inp, p);

    /*
     * construct our portion of the command list
     */
#ifndef N_MICRO
    aSetBuffer(ptr++, A_MAIN, *inp, AL_MAIN_L_OUT, FIXED_SAMPLE << 1);
    aSetBuffer(ptr++, A_AUX, AL_MAIN_R_OUT, AL_AUX_L_OUT, AL_AUX_R_OUT);
#endif

    if (e->em_first) {
        e->em_first = 0;

        /*
         * Calculate derived parameters
         */
        e->em_ltgt = (e->em_volume * n_eqpower[e->em_pan]) >> 15;
        e->em_lratm = _getRate((f64)e->em_cvolL, (f64)e->em_ltgt, e->em_segEnd, &(e->em_lratl));
        e->em_rtgt = (e->em_volume * n_eqpower[N_EQPOWER_LENGTH - e->em_pan - 1]) >> 15;
        e->em_rratm = _getRate((f64)e->em_cvolR, (f64)e->em_rtgt, e->em_segEnd, &(e->em_rratl));

#ifndef N_MICRO
        aSetVolume(ptr++, A_LEFT | A_VOL, e->em_cvolL, 0, 0);
        aSetVolume(ptr++, A_RIGHT | A_VOL, e->em_cvolR, 0, 0);
        aSetVolume(ptr++, A_LEFT | A_RATE, e->em_ltgt, e->em_lratm, e->em_lratl);
        aSetVolume(ptr++, A_RIGHT | A_RATE, e->em_rtgt, e->em_rratm, e->em_rratl);
        aSetVolume(ptr++, A_AUX, e->em_dryamt, 0, e->em_wetamt);
        aEnvMixer(ptr++, A_INIT | A_AUX, osVirtualToPhysical(e->em_state));
    } else {
        EnvMixer(ptr++, A_CONTINUE | A_AUX, osVirtualToPhysical(e->em_state));
    }
#else
        // clang-format off
        // needs to be on same line.
        n_aSetVolume(ptr++, A_RATE, e->em_ltgt, e->em_lratm, e->em_lratl);            \
        n_aSetVolume(ptr++, A_LEFT | A_VOL, e->em_cvolL, e->em_dryamt, e->em_wetamt); \
        n_aSetVolume(ptr++, A_RIGHT | A_VOL, e->em_rtgt, e->em_rratm, e->em_rratl);   \
        n_aEnvMixer(ptr++, A_INIT, e->em_cvolR, osVirtualToPhysical(e->em_state));
        // clang-format on
    } else {
        n_aEnvMixer(ptr++, A_CONTINUE, 0, osVirtualToPhysical(e->em_state));
    }
#endif

    /*
     * bump the input buffer pointer
     */

    *inp += (FIXED_SAMPLE << 1);
    e->em_delta += FIXED_SAMPLE;

    return ptr;
}

#ifndef N_MICRO
#define EXP_MASK 0x7f800000
#define MANT_MASK 0x807fffff

static s16 _getRate(f64 vol, f64 tgt, s32 count, u16* ratel) {
    s16 s;

    f64 invn = 1.0 / count, eps, a, fs, mant;
    s32 i_invn, ex, indx;

#ifdef AUD_PROFILE
    lastCnt[++cnt_index] = osGetCount();
#endif

    if (count == 0) {
        if (tgt >= vol) {
            *ratel = 0xffff;
            return 0x7fff;
        } else {
            *ratel = 0;
            return 0;
        }
    }

    if (tgt < 1.0)
        tgt = 1.0;
    if (vol <= 0)
        vol = 1; /* zero and neg values not allowed */

#define NBITS (3)
#define NPOS (1 << NBITS)
#define NFRACBITS (30)
#define M_LN2 0.69314718055994530942
    /*
     * rww's parametric pow()
     Goal: compute a = (tgt/vol)^(1/count)

     Approach:
     (tgt/vol)^(1/count) =
     ((tgt/vol)^(1/2^30))^(2^30*1/count)

     (tgt/vol)^(1/2^30) ~= 1 + eps

     where

     eps ~= ln(tgt/vol)/2^30

     ln(tgt/vol) = ln2(tgt/vol) * ln(2)

     ln2(tgt/vol) = fp_exponent( tgt/vol ) +
     ln2( fp_mantissa( tgt/vol ) )

     fp_mantissa() and fp_exponent() are
     calculated via tricky bit manipulations of
     the floating point number. ln2() is
     approximated by a look up table.

     Note that this final (1+eps) value needs
     to be raised to the 2^30/count power. This
     is done by operating on the binary representaion
     of this number in the final while loop.

     Enjoy!
     */
    {
        f64 logtab[] = { -0.912537, -0.752072, -0.607683, -0.476438, -0.356144, -0.245112, -0.142019, -0.045804 };

        i_invn = (s32)_ldexpf(invn, NFRACBITS);
        mant = _frexpf(tgt / vol, &ex);
        indx = (s32)(_ldexpf(mant, NBITS + 1)); /* NPOS <= indx < 2*NPOS */
        eps = (logtab[indx - NPOS] + ex) * M_LN2;
        eps /= _ldexpf(1, NFRACBITS); /* eps / 2^NFRACBITS */
        fs = (1.0 + eps);
        a = 1.0;
        while (i_invn) {
            if (i_invn & 1)
                a = a * fs;
            fs *= fs;
            i_invn >>= 1;
        }
    }

    a *= (a *= (a *= a));
    s = (s16)a;
    *ratel = (s16)(0xffff * (a - (f32)s));

#ifdef AUD_PROFILE
    PROFILE_AUD(rate_num, rate_cnt, rate_max, rate_min);
#endif
    return (s16)a;
}
#else
static s16 _getRate(f64 vol, f64 tgt, s32 count, u16* ratel) {
    s16 s, tmp;
    f64 invn, a, f;

#ifdef AUD_PROFILE
    lastCnt[++cnt_index] = osGetCount();
#endif

    if (count == 0) {
        if (tgt >= vol) {
            *ratel = 0xffff;
            return 0x7fff;
        } else {
            *ratel = 0;
            return 0x8000;
        }
    }

    invn = 1.0 / count;

    if (tgt < 1.0)
        tgt = 1.0;
    if (vol <= 0.0)
        vol = 1.0; /* zero and neg values not allowed */

    a = (tgt - vol) * invn * 8.0;
    s = (s16)a;
    f = a - (f64)s;
    s -= 1;
    f += 1.0;
    tmp = (s16)f;
    s += tmp;
    f -= (f64)tmp;

#ifdef AUD_PROFILE
    PROFILE_AUD(rate_num, rate_cnt, rate_max, rate_min);
#endif

    *ratel = (u16)(0xffff * f);
    return s;
}
#endif

#ifndef N_MICRO
static f32 _getVol(f32 ivol, s32 samples, s16 ratem, u16 ratel) {
    f32 r, a;
    s32 i;

#ifdef AUD_PROFILE
    lastCnt[++cnt_index] = osGetCount();
#endif

    /*
     * Rate values are actually rate^8
     */
    samples >>= 3;
    if (samples == 0) {
        return ivol;
    }
    r = ((f32)(ratem << 16) + (f32)ratel) / 65536;

    a = 1.0;
    for (i = 0; i < 32; i++) {
        if (samples & 1)
            a *= r;
        samples >>= 1;
        if (samples == 0) {
            break;
        }
        r *= r;
    }
    ivol *= a;
#ifdef AUD_PROFILE
    PROFILE_AUD(vol_num, vol_cnt, vol_max, vol_min);
#endif
    return ivol;
}
#else
static s16 _getVol(s16 ivol, s32 samples, s16 ratem, u16 ratel) {
    s32 tmp1;
#ifdef AUD_PROFILE
    lastCnt[++cnt_index] = osGetCount();
#endif

    samples >>= 3;
    if (samples == 0) {
        return ivol;
    }

    tmp1 = ratel * samples;
    tmp1 >>= 16;
    tmp1 += ratem * samples;
    ivol += tmp1;

#ifdef AUD_PROFILE
    PROFILE_AUD(vol_num, vol_cnt, vol_max, vol_min);
#endif
    return ivol;
}
#endif
