// OpenCP Module Player
// copyright (c) '94-'98 Niklas Beisert <nbeisert@physik.tu-muenchen.de>
//
// SIDPlay ADSR envelops handlers
//
// revision history: (please note changes here)
//  -kb980717  Tammo Hinrichs <opencp@gmx.net>
//    -first release

//
// 1997/09/27 21:32:09
// 
//=========================================================================
// This source implements the ADSR volume envelope of the SID-chip.
// Two different envelope shapes are implemented, an exponential
// approximation and the linear shape, which can easily be determined 
// by reading the registers of the third SID operator.
//
// Accurate volume envelope times as of November 1994 are used 
// courtesy of George W. Taylor <aa601@cfn.cs.dal.ca>, <yurik@io.org>
// They are slightly modified.
// 
// To use the rounded envelope times from the C64 Programmers Reference
// Book define SID_REFTIMES at the Makefile level.
// 
// To perform realtime calculations with floating point precision define
// SID_FPUENVE at the Makefile level. On high-end FPUs (not Pentium !),
// this can result in speed improvement. Default is integer fixpoint.
// 
// Global Makefile definables:
//
//   DIRECT_FIXPOINT - use a union to access integer fixpoint operands 
//                     in memory. This makes an assumption about the
//                     hardware and software architecture and therefore 
//                     is considered a hack !
//
// Local (or Makefile) definables:
// 
//   SID_LINENVE  - use linear envelope attack shape (default)
//   SID_EXPENVE  - use exponential envelope shape
//   SID_REFTIMES - use rounded envelope times
//   SID_FPUENVE  - use floating point precision for calculations
//                  (will override the global DIRECT_FIXPOINT setting !)
//
//=========================================================================

#include <math.h>

#include "envelope.h"
#include "myendian.h"
#include "opstruct.h"

#include "enve_dl.h"

#define SIDEMU_ENVE_THRESHOLD 2

extern const ubyte masterVolumeLevels[16] =
{
    1,  17,  34,  51,  68,  85, 102, 119,
  136, 153, 170, 187, 204, 221, 238, 255
};
ubyte masterVolume;
uword masterVolumeAmplIndex;
static uword masterAmplModTable[16*256];            

static float attackTimes[16] =
{
  // milliseconds
  2.2528606, 8.0099577, 15.7696042, 23.7795619, 37.2963655, 55.0684591,
  66.8330845, 78.3473987,
  98.1219818, 244.554021, 489.108042, 782.472742, 977.715461, 2933.64701,
  4889.07793, 7822.72493
};

static float decayReleaseTimes[16] =
{
  // milliseconds
  8.91777693, 24.594051, 48.4185907, 73.0116639, 114.512475, 169.078356,
  205.199432, 240.551975, 
  301.266125, 750.858245, 1501.71551, 2402.43682, 3001.89298, 9007.21405,
  15010.998, 24018.2111
};

#ifdef SID_FPUENVE
  static float attackRates[16];
  static float decayReleaseRates[16];  
#elif defined(DIRECT_FIXPOINT)
  static udword attackRates[16];
  static udword decayReleaseRates[16];
#else
  static udword attackRates[16];
  static udword attackRatesP[16];
  static udword decayReleaseRates[16];  
  static udword decayReleaseRatesP[16];  
#endif

const udword attackTabLen = 255;
static udword releaseTabLen;
static udword releasePos[256];                  


void enveEmuInit( udword updateFreq, char measuredValues )
{
	udword i, j, k;
  
	releaseTabLen = sizeof(releaseTab);
	for ( i = 0; i < 256; i++ )
	{
		j = 0;
		while (( j < releaseTabLen ) && (releaseTab[j] > i) )
		{
			j++;
		}
		if ( j < releaseTabLen )
		{
			releasePos[i] = j;
		}
		else
		{
			releasePos[i] = releaseTabLen -1;
		}
	}

	k = 0;
	for ( i = 0; i < 16; i++ )
	{
		for ( j = 0; j < 256; j++ )
		{
			uword tmpVol = j;
			if (measuredValues)
			{
				tmpVol = (uword) ((293.0*(1-exp(j/-130.0)))+4.0);
				if (j == 0)
					tmpVol = 0;
				if (tmpVol > 255)
					tmpVol = 255;
			}
			// Want the modulated volume value in the high byte.
                        if (tmpVol<SIDEMU_ENVE_THRESHOLD)
                          tmpVol=SIDEMU_ENVE_THRESHOLD;
			masterAmplModTable[k++] = ((tmpVol * masterVolumeLevels[i]) / 255) << 8;
		}
	}
	
	for ( i = 0; i < 16; i++ )
	{
#ifdef SID_FPUENVE
		double scaledenvelen = floor(( attackTimes[i] * updateFreq ) / 1000UL );
		if (scaledenvelen == 0)
			scaledenvelen = 1;
		attackRates[i] = attackTabLen / scaledenvelen;

		scaledenvelen = floor(( decayReleaseTimes[i] * updateFreq ) / 1000UL );
		if (scaledenvelen == 0)
			scaledenvelen = 1;
		decayReleaseRates[i] = releaseTabLen / scaledenvelen;
#elif defined(DIRECT_FIXPOINT)
		udword scaledenvelen = (udword)floor(( attackTimes[i] * updateFreq ) / 1000UL );
		if (scaledenvelen == 0)
			scaledenvelen = 1;
		attackRates[i] = (attackTabLen << 16) / scaledenvelen;

		scaledenvelen = (udword)floor(( decayReleaseTimes[i] * updateFreq ) / 1000UL );
		if (scaledenvelen == 0)
			scaledenvelen = 1;
		decayReleaseRates[i] = (releaseTabLen << 16) / scaledenvelen;
#else
		udword scaledenvelen = (udword)floor(( attackTimes[i] * updateFreq ) / 1000UL );
		if (scaledenvelen == 0)
			scaledenvelen = 1;
		attackRates[i] = attackTabLen / scaledenvelen;
		attackRatesP[i] = (( attackTabLen % scaledenvelen ) * 65536UL ) / scaledenvelen;
		
		scaledenvelen = (udword)floor(( decayReleaseTimes[i] * updateFreq ) / 1000UL );
		if (scaledenvelen == 0)
			scaledenvelen = 1;
		decayReleaseRates[i] = releaseTabLen / scaledenvelen;
		decayReleaseRatesP[i] = (( releaseTabLen % scaledenvelen ) * 65536UL ) / scaledenvelen;
#endif
  }
}

// Reset op.

void enveEmuResetOperator(sidOperator*pVoice)
{
	// mute, end of R-phase
	pVoice->ADSRctrl = ENVE_MUTE;
	pVoice->gateOnCtrl = (pVoice->gateOffCtrl = false);
	
#ifdef SID_FPUENVE
	pVoice->fenveStep = (pVoice->fenveStepAdd = 0);
	pVoice->enveStep = 0;
#elif defined(DIRECT_FIXPOINT)
	pVoice->enveStep.l = (pVoice->enveStepAdd.l = 0);
#else
	pVoice->enveStep = (pVoice->enveStepPnt = 0);
	pVoice->enveStepAdd = (pVoice->enveStepAddPnt = 0);
#endif
        pVoice->enveSusVol = 1;
        pVoice->enveVol = 1;
	pVoice->enveShortAttackCount = 0;
}

inline uword enveEmuStartAttack(struct sidOperator*);
inline uword enveEmuStartDecay(struct sidOperator*);
inline uword enveEmuStartRelease(struct sidOperator*);
inline uword enveEmuAlterAttack(struct sidOperator*);
inline uword enveEmuAlterDecay(struct sidOperator*);
inline uword enveEmuAlterSustain(struct sidOperator*);
inline uword enveEmuAlterSustainDecay(struct sidOperator*);
inline uword enveEmuAlterRelease(struct sidOperator*);
inline uword enveEmuAttack(struct sidOperator*);
inline uword enveEmuDecay(struct sidOperator*);
inline uword enveEmuSustain(struct sidOperator*);
inline uword enveEmuSustainDecay(struct sidOperator*);
inline uword enveEmuRelease(struct sidOperator*);
inline uword enveEmuMute(struct sidOperator*);

inline uword enveEmuStartShortAttack(struct sidOperator*);
inline uword enveEmuAlterShortAttack(struct sidOperator*);
inline uword enveEmuShortAttack(struct sidOperator*);


ptr2sidUwordFunc enveModeTable[] =
{
	// 0
	&enveEmuStartAttack, &enveEmuStartRelease,
	&enveEmuAttack, &enveEmuDecay, &enveEmuSustain, &enveEmuRelease,
	&enveEmuSustainDecay, &enveEmuMute,
	// 16
	&enveEmuStartShortAttack, 
	&enveEmuMute, &enveEmuMute, &enveEmuMute, 
	&enveEmuMute, &enveEmuMute, &enveEmuMute, &enveEmuMute,
    // 32		
	&enveEmuStartAttack, &enveEmuStartRelease,
	&enveEmuAlterAttack, &enveEmuAlterDecay, &enveEmuAlterSustain, &enveEmuAlterRelease,
	&enveEmuAlterSustainDecay, &enveEmuMute,
    // 48		
	&enveEmuStartShortAttack, 
	&enveEmuMute, &enveEmuMute, &enveEmuMute, 
	&enveEmuMute, &enveEmuMute, &enveEmuMute, &enveEmuMute
};

// Real-time functions.
// Order is important because of inline optimizations.
//
// ADSRctrl is (index*2) to enveModeTable[], because of KEY-bit.

inline void enveEmuEnveAdvance(struct sidOperator* pVoice)
{
#ifdef SID_FPUENVE
	pVoice->fenveStep += pVoice->fenveStepAdd;
#elif defined(DIRECT_FIXPOINT)
	pVoice->enveStep.l += pVoice->enveStepAdd.l;
#else
	pVoice->enveStepPnt += pVoice->enveStepAddPnt;
	pVoice->enveStep += pVoice->enveStepAdd + ( pVoice->enveStepPnt > 65535 );
	pVoice->enveStepPnt &= 0xFFFF;
#endif
}

//
// Mute/Idle.
//

// Only used in the beginning.
inline uword enveEmuMute(struct sidOperator*)
{
	return 0;
}

//
// Release
//

inline uword enveEmuRelease(struct sidOperator* pVoice)
{
#ifdef SID_FPUENVE
	pVoice->enveStep = (uword)pVoice->fenveStep;
#endif
#if defined(DIRECT_FIXPOINT) && !defined(SID_FPUENVE)
	if ( pVoice->enveStep.w[HI] >= releaseTabLen )
#else
	if ( pVoice->enveStep >= releaseTabLen )
#endif
	{
		pVoice->enveVol = releaseTab[releaseTabLen -1];
		return masterAmplModTable[ masterVolumeAmplIndex + pVoice->enveVol ];
	}	
	else
	{
#if defined(DIRECT_FIXPOINT) && !defined(SID_FPUENVE)
		pVoice->enveVol = releaseTab[pVoice->enveStep.w[HI]];
#else
		pVoice->enveVol = releaseTab[pVoice->enveStep];
#endif
		enveEmuEnveAdvance(pVoice);
		return masterAmplModTable[ masterVolumeAmplIndex + pVoice->enveVol ];
	}
}

inline uword enveEmuAlterRelease(struct sidOperator* pVoice)
{
	ubyte release = pVoice->SIDSR & 0x0F;
#ifdef SID_FPUENVE
	pVoice->fenveStepAdd = decayReleaseRates[release];
#elif defined(DIRECT_FIXPOINT)
	pVoice->enveStepAdd.l = decayReleaseRates[release];
#else
	pVoice->enveStepAdd = decayReleaseRates[release];
	pVoice->enveStepAddPnt = decayReleaseRatesP[release];
#endif
	pVoice->ADSRproc = &enveEmuRelease;
	return enveEmuRelease(pVoice);
}

inline uword enveEmuStartRelease(struct sidOperator* pVoice)
{
	pVoice->ADSRctrl = ENVE_RELEASE;
#ifdef SID_FPUENVE
	pVoice->fenveStep = releasePos[pVoice->enveVol];
#elif defined(DIRECT_FIXPOINT)
	pVoice->enveStep.w[HI] = releasePos[pVoice->enveVol];
	pVoice->enveStep.w[LO] = 0;
#else
	pVoice->enveStep = releasePos[pVoice->enveVol];
	pVoice->enveStepPnt = 0;
#endif
	return enveEmuAlterRelease(pVoice);
}

//
// Sustain
//

inline uword enveEmuSustain(struct sidOperator* pVoice)
{
	return masterAmplModTable[masterVolumeAmplIndex+pVoice->enveVol];
}

inline uword enveEmuSustainDecay(struct sidOperator* pVoice)
{
#ifdef SID_FPUENVE
	pVoice->enveStep = (uword)pVoice->fenveStep;
#endif
#if defined(DIRECT_FIXPOINT) && !defined(SID_FPUENVE)
	if ( pVoice->enveStep.w[HI] >= releaseTabLen )
#else
	if ( pVoice->enveStep >= releaseTabLen )
#endif
	{
		pVoice->enveVol = releaseTab[releaseTabLen-1];
		return enveEmuAlterSustain(pVoice);
	}
	else
	{
#if defined(DIRECT_FIXPOINT) && !defined(SID_FPUENVE)
		pVoice->enveVol = releaseTab[pVoice->enveStep.w[HI]];
#else
		pVoice->enveVol = releaseTab[pVoice->enveStep];
#endif
		// Will be controlled from sidEmuSet2().
		if ( pVoice->enveVol <= pVoice->enveSusVol )
		{
			pVoice->enveVol = pVoice->enveSusVol;
			return enveEmuAlterSustain(pVoice);
		}
		else
		{
			enveEmuEnveAdvance(pVoice);
			return masterAmplModTable[ masterVolumeAmplIndex + pVoice->enveVol ];
		}
	}
}

// This is the same as enveEmuStartSustainDecay().
inline uword enveEmuAlterSustainDecay(struct sidOperator* pVoice)
{
	ubyte decay = pVoice->SIDAD & 0x0F ;
#ifdef SID_FPUENVE
	pVoice->fenveStepAdd = decayReleaseRates[decay];
#elif defined(DIRECT_FIXPOINT)
	pVoice->enveStepAdd.l = decayReleaseRates[decay];
#else
	pVoice->enveStepAdd = decayReleaseRates[decay];
	pVoice->enveStepAddPnt = decayReleaseRatesP[decay];
#endif
	pVoice->ADSRproc = &enveEmuSustainDecay;
	return enveEmuSustainDecay(pVoice);
}

// This is the same as enveEmuStartSustain().
inline uword enveEmuAlterSustain(struct sidOperator* pVoice)
{
	if ( pVoice->enveVol > pVoice->enveSusVol )
	{
		pVoice->ADSRctrl = ENVE_SUSTAINDECAY;
		pVoice->ADSRproc = &enveEmuSustainDecay;
		return enveEmuAlterSustainDecay(pVoice);
	}
	else
	{
		pVoice->ADSRctrl = ENVE_SUSTAIN;
		pVoice->ADSRproc = &enveEmuSustain;
		return enveEmuSustain(pVoice);
	}
}

//
// Decay
//

inline uword enveEmuDecay(struct sidOperator* pVoice)
{
#ifdef SID_FPUENVE
	pVoice->enveStep = (uword)pVoice->fenveStep;
#endif
#if defined(DIRECT_FIXPOINT) && !defined(SID_FPUENVE)
	if ( pVoice->enveStep.w[HI] >= releaseTabLen )
#else
	if ( pVoice->enveStep >= releaseTabLen )
#endif
	{
		pVoice->enveVol = pVoice->enveSusVol;
		return enveEmuAlterSustain(pVoice);  // start sustain
	}
	else
	{
#if defined(DIRECT_FIXPOINT) && !defined(SID_FPUENVE)
		pVoice->enveVol = releaseTab[pVoice->enveStep.w[HI]];
#else
		pVoice->enveVol = releaseTab[pVoice->enveStep];
#endif
		// Will be controlled from sidEmuSet2().
		if ( pVoice->enveVol <= pVoice->enveSusVol )
		{
			pVoice->enveVol = pVoice->enveSusVol;
			return enveEmuAlterSustain(pVoice);  // start sustain
		}
		else
		{
			enveEmuEnveAdvance(pVoice);
			return masterAmplModTable[ masterVolumeAmplIndex + pVoice->enveVol ];
		}
	}
}

inline uword enveEmuAlterDecay(struct sidOperator* pVoice)
{
	ubyte decay = pVoice->SIDAD & 0x0F ;
#ifdef SID_FPUENVE
	pVoice->fenveStepAdd = decayReleaseRates[decay];
#elif defined(DIRECT_FIXPOINT)
	pVoice->enveStepAdd.l = decayReleaseRates[decay];
#else
	pVoice->enveStepAdd = decayReleaseRates[decay];
	pVoice->enveStepAddPnt = decayReleaseRatesP[decay];
#endif
	pVoice->ADSRproc = &enveEmuDecay;
	return enveEmuDecay(pVoice);
}

inline uword enveEmuStartDecay(struct sidOperator* pVoice)
{
	pVoice->ADSRctrl = ENVE_DECAY;
#ifdef SID_FPUENVE
	pVoice->fenveStep = 0;
#elif defined(DIRECT_FIXPOINT)
	pVoice->enveStep.l = 0;
#else
	pVoice->enveStep = (pVoice->enveStepPnt = 0);
#endif
	return enveEmuAlterDecay(pVoice);
}

//
// Attack
//

inline uword enveEmuAttack(struct sidOperator* pVoice)
{
#ifdef SID_FPUENVE
	pVoice->enveStep = (uword)pVoice->fenveStep;
#endif
#if defined(DIRECT_FIXPOINT) && !defined(SID_FPUENVE)
	if ( pVoice->enveStep.w[HI] > attackTabLen )
#else
	if ( pVoice->enveStep >= attackTabLen )
#endif
		return enveEmuStartDecay(pVoice);
	else
	{
#if defined(DIRECT_FIXPOINT) && !defined(SID_FPUENVE)
		pVoice->enveVol = pVoice->enveStep.w[HI];
#else
		pVoice->enveVol = pVoice->enveStep;
#endif
		enveEmuEnveAdvance(pVoice);
		return masterAmplModTable[ masterVolumeAmplIndex + pVoice->enveVol ];
	}
}

inline uword enveEmuAlterAttack(struct sidOperator* pVoice)
{
	ubyte attack = pVoice->SIDAD >> 4;
#ifdef SID_FPUENVE
	pVoice->fenveStepAdd = attackRates[attack];
#elif defined(DIRECT_FIXPOINT)
	pVoice->enveStepAdd.l = attackRates[attack];
#else
	pVoice->enveStepAdd = attackRates[attack];
	pVoice->enveStepAddPnt = attackRatesP[attack];
#endif
	pVoice->ADSRproc = &enveEmuAttack;
	return enveEmuAttack(pVoice);
}

inline uword enveEmuStartAttack(struct sidOperator* pVoice)
{
	pVoice->ADSRctrl = ENVE_ATTACK;
#ifdef SID_FPUENVE
	pVoice->fenveStep = (float)pVoice->enveVol;
#elif defined(DIRECT_FIXPOINT)
	pVoice->enveStep.w[HI] = pVoice->enveVol;
	pVoice->enveStep.w[LO] = 0;
#else
	pVoice->enveStep = pVoice->enveVol;
	pVoice->enveStepPnt = 0;
#endif
	return enveEmuAlterAttack(pVoice);
}

//
// Experimental.
//

inline uword enveEmuShortAttack(struct sidOperator* pVoice)
{
#ifdef SID_FPUENVE
	pVoice->enveStep = (uword)pVoice->fenveStep;
#endif
#if defined(DIRECT_FIXPOINT) && !defined(SID_FPUENVE)
	if ((pVoice->enveStep.w[HI] > attackTabLen) ||
		(pVoice->enveShortAttackCount == 0))
#else
	if ((pVoice->enveStep >= attackTabLen) ||
		(pVoice->enveShortAttackCount == 0))
#endif
		return enveEmuStartDecay(pVoice);
	else
	{
#if defined(DIRECT_FIXPOINT) && !defined(SID_FPUENVE)
		pVoice->enveVol = pVoice->enveStep.w[HI];
#else
		pVoice->enveVol = pVoice->enveStep;
#endif
	    pVoice->enveShortAttackCount--;
		enveEmuEnveAdvance(pVoice);
		return masterAmplModTable[ masterVolumeAmplIndex + pVoice->enveVol ];
	}
}

inline uword enveEmuAlterShortAttack(struct sidOperator* pVoice)
{
	ubyte attack = pVoice->SIDAD >> 4;
#ifdef SID_FPUENVE
	pVoice->fenveStepAdd = attackRates[attack];
#elif defined(DIRECT_FIXPOINT)
	pVoice->enveStepAdd.l = attackRates[attack];
#else
	pVoice->enveStepAdd = attackRates[attack];
	pVoice->enveStepAddPnt = attackRatesP[attack];
#endif
	pVoice->ADSRproc = &enveEmuShortAttack;
	return enveEmuShortAttack(pVoice);
}

inline uword enveEmuStartShortAttack(struct sidOperator* pVoice)
{
	pVoice->ADSRctrl = ENVE_SHORTATTACK;
#ifdef SID_FPUENVE
	pVoice->fenveStep = (float)pVoice->enveVol;
#elif defined(DIRECT_FIXPOINT)
	pVoice->enveStep.w[HI] = pVoice->enveVol;
	pVoice->enveStep.w[LO] = 0;
#else
	pVoice->enveStep = pVoice->enveVol;
	pVoice->enveStepPnt = 0;
#endif
	pVoice->enveShortAttackCount = 65535;  // unused
	return enveEmuAlterShortAttack(pVoice);
}
