/* SPU2-X, A plugin for Emulating the Sound Processing Unit of the Playstation 2
 * Developed and maintained by the Pcsx2 Development Team.
 *
 * Original portions from SPU2ghz are (c) 2008 by David Quintana [gigaherz]
 *
 * SPU2-X is free software: you can redistribute it and/or modify it under the terms
 * of the GNU Lesser General Public License as published by the Free Software Found-
 * ation, either version 3 of the License, or (at your option) any later version.
 *
 * SPU2-X is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
 * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
 * PURPOSE.  See the GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with SPU2-X.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "Global.h"

static const s32 ADSR_MAX_VOL = 0x7fffffff;

static const int InvExpOffsets[] = {0, 4, 6, 8, 9, 10, 11, 12};
static u32 PsxRates[160];


void InitADSR() // INIT ADSR
{
    for (int i = 0; i < (32 + 128); i++) {
        int shift = (i - 32) >> 2;
        s64 rate = (i & 3) + 4;
        if (shift < 0)
            rate >>= -shift;
        else
            rate <<= shift;

        PsxRates[i] = (int)std::min(rate, (s64)0x3fffffffLL);
    }
}

bool V_ADSR::Calculate()
{
    pxAssume(Phase != 0);

    if (Releasing && (Phase < 5))
        Phase = 5;

    switch (Phase) {
        case 1: // attack
            if (Value == ADSR_MAX_VOL) {
                // Already maxed out.  Progress phase and nothing more:
                Phase++;
                break;
            }

            // Case 1 below is for pseudo exponential below 75%.
            // Pseudo Exp > 75% and Linear are the same.

            if (AttackMode && (Value >= 0x60000000))
                Value += PsxRates[(AttackRate ^ 0x7f) - 0x18 + 32];
            else
                Value += PsxRates[(AttackRate ^ 0x7f) - 0x10 + 32];

            if (Value < 0) {
                // We hit the ceiling.
                Phase++;
                Value = ADSR_MAX_VOL;
            }
            break;

        case 2: // decay
        {
            u32 off = InvExpOffsets[(Value >> 28) & 7];
            Value -= PsxRates[((DecayRate ^ 0x1f) * 4) - 0x18 + off + 32];

            // calculate sustain level as a factor of the ADSR maximum volume.

            s32 suslev = ((0x80000000 / 0x10) * (SustainLevel + 1)) - 1;

            if (Value <= suslev) {
                if (Value < 0)
                    Value = 0;
                Phase++;
            }
        } break;

        case 3: // sustain
        {
            // 0x7f disables sustain (infinite sustain)
            if (SustainRate == 0x7f)
                return true;

            if (SustainMode & 2) // decreasing
            {
                if (SustainMode & 4) // exponential
                {
                    u32 off = InvExpOffsets[(Value >> 28) & 7];
                    Value -= PsxRates[(SustainRate ^ 0x7f) - 0x1b + off + 32];
                } else // linear
                    Value -= PsxRates[(SustainRate ^ 0x7f) - 0xf + 32];

                if (Value <= 0) {
                    Value = 0;
                    Phase++;
                }
            } else { // increasing
                if ((SustainMode & 4) && (Value >= 0x60000000))
                    Value += PsxRates[(SustainRate ^ 0x7f) - 0x18 + 32];
                else
                    // linear / Pseudo below 75% (they're the same)
                    Value += PsxRates[(SustainRate ^ 0x7f) - 0x10 + 32];

                if (Value < 0) {
                    Value = ADSR_MAX_VOL;
                    Phase++;
                }
            }
        } break;

        case 4: // sustain end
            Value = (SustainMode & 2) ? 0 : ADSR_MAX_VOL;
            if (Value == 0)
                Phase = 6;
            break;

        case 5:              // release
            if (ReleaseMode) // exponential
            {
                u32 off = InvExpOffsets[(Value >> 28) & 7];
                Value -= PsxRates[((ReleaseRate ^ 0x1f) * 4) - 0x18 + off + 32];
            } else { // linear
                //Value-=PsxRates[((ReleaseRate^0x1f)*4)-0xc+32];
                if (ReleaseRate != 0x1f)
                    Value -= (1 << (0x1f - ReleaseRate));
            }

            if (Value <= 0) {
                Value = 0;
                Phase++;
            }
            break;

        case 6: // release end
            Value = 0;
            break;

            jNO_DEFAULT
    }

    // returns true if the voice is active, or false if it's stopping.
    return Phase != 6;
}

/////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////
//                                                                                     //

#define VOLFLAG_REVERSE_PHASE (1ul << 0)
#define VOLFLAG_DECREMENT (1ul << 1)
#define VOLFLAG_EXPONENTIAL (1ul << 2)
#define VOLFLAG_SLIDE_ENABLE (1ul << 3)

void V_VolumeSlide::Update()
{
    if (!(Mode & VOLFLAG_SLIDE_ENABLE))
        return;

    // Volume slides use the same basic logic as ADSR, but simplified (single-stage
    // instead of multi-stage)

    if (Increment == 0x7f)
        return;

    s32 value = abs(Value);

    if (Mode & VOLFLAG_DECREMENT) {
        // Decrement

        if (Mode & VOLFLAG_EXPONENTIAL) {
            u32 off = InvExpOffsets[(value >> 28) & 7];
            value -= PsxRates[(Increment ^ 0x7f) - 0x1b + off + 32];
        } else
            value -= PsxRates[(Increment ^ 0x7f) - 0xf + 32];

        if (value < 0) {
            value = 0;
            Mode = 0; // disable slide
        }
    } else {
        // Increment
        // Pseudo-exponential increments, as done by the SPU2 (really!)
        // Above 75% slides slow, below 75% slides fast.  It's exponential, pseudo'ly speaking.

        if ((Mode & VOLFLAG_EXPONENTIAL) && (value >= 0x60000000))
            value += PsxRates[(Increment ^ 0x7f) - 0x18 + 32];
        else
            // linear / Pseudo below 75% (they're the same)
            value += PsxRates[(Increment ^ 0x7f) - 0x10 + 32];

        if (value < 0) // wrapped around the "top"?
        {
            value = 0x7fffffff;
            Mode = 0; // disable slide
        }
    }

    Value = (Value < 0) ? -value : value;
}
