/*  SPU2null
 *  Copyright (C) 2002-2005  SPU2null Team
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program 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 General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 */

#include "SPU2.h"

#include <assert.h>
#include <stdlib.h>
#include <string>
using namespace std;

const u8 version  = PS2E_SPU2_VERSION;
const u8 revision = 0;
const u8 build    = 8;    // increase that with each version
const u32 minor = 0;    // increase that with each version

// ADSR constants
#define ATTACK_MS      494L
#define DECAYHALF_MS   286L
#define DECAY_MS       572L
#define SUSTAIN_MS     441L
#define RELEASE_MS     437L

#ifdef PCSX2_DEBUG
char *libraryName      = "SPU2null (Debug)";
#else
char *libraryName      = "SPU2null ";
#endif
string s_strIniPath="inis/";
string s_strLogPath="logs/";

FILE *spu2Log;
Config conf;

ADMA Adma4;
ADMA Adma7;

u32 MemAddr[2];
u32 g_nSpuInit = 0;
u16 interrupt = 0;
s8 *spu2regs = NULL;
u16* spu2mem = NULL;
u16* pSpuIrq[2] = {NULL};
u32 dwEndChannel2[2] = {0}; // keeps track of what channels have ended
u32 dwNoiseVal = 1;                        // global noise generator

s32 SPUCycles = 0, SPUWorkerCycles = 0;
s32 SPUStartCycle[2];
s32 SPUTargetCycle[2];

int ADMAS4Write();
int ADMAS7Write();

void InitADSR();

void (*irqCallbackSPU2)();                  // func of main emu, called on spu irq
void (*irqCallbackDMA4)() = 0;                // func of main emu, called on spu irq
void (*irqCallbackDMA7)() = 0;                // func of main emu, called on spu irq

const s32 f[5][2] = {
	{    0,  0  },
	{   60,  0  },
	{  115, -52 },
	{   98, -55 },
	{  122, -60 }
};

u32 RateTable[160];

// channels and voices
VOICE_PROCESSED voices[SPU_NUMBER_VOICES+1]; // +1 for modulation

EXPORT_C_(u32) PS2EgetLibType()
{
	return PS2E_LT_SPU2;
}

EXPORT_C_(char*) PS2EgetLibName()
{
	return libraryName;
}

EXPORT_C_(u32) PS2EgetLibVersion2(u32 type)
{
	return (version << 16) | (revision << 8) | build | (minor << 24);
}

void __Log(char *fmt, ...)
{
	va_list list;

	if (!conf.Log || spu2Log == NULL) return;

	va_start(list, fmt);
	vfprintf(spu2Log, fmt, list);
	va_end(list);
}

EXPORT_C_(void) SPU2setSettingsDir(const char* dir)
{
	s_strIniPath = (dir == NULL) ? "inis/" : dir;
}

bool OpenLog() {
    bool result = true;
#ifdef SPU2_LOG
    if(spu2Log) return result;

    const std::string LogFile(s_strLogPath + "/spu2null.log");

    spu2Log = fopen(LogFile.c_str(), "w");
    if (spu2Log != NULL)
        setvbuf(spu2Log, NULL,  _IONBF, 0);
    else {
        SysMessage("Can't create log file %s\n", LogFile.c_str());
        result = false;
    }
	SPU2_LOG("Spu2 null version %d,%d\n", revision, build);
	SPU2_LOG("SPU2init\n");
#endif
    return result;
}

EXPORT_C_(void)  SPU2setLogDir(const char* dir)
{
	// Get the path to the log directory.
	s_strLogPath = (dir==NULL) ? "logs/" : dir;

	// Reload the log file after updated the path
    if(spu2Log) {
        fclose(spu2Log);
        spu2Log = NULL;
    }
    OpenLog();
}

EXPORT_C_(s32) SPU2init()
{
    OpenLog();

	spu2regs = (s8*)malloc(0x10000);
	if (spu2regs == NULL)
	{
		SysMessage("Error allocating Memory\n");
		return -1;
	}
	memset(spu2regs, 0, 0x10000);

	spu2mem = (u16*)malloc(0x200000); // 2Mb
	if (spu2mem == NULL)
	{
		SysMessage("Error allocating Memory\n");
		return -1;
	}
	memset(spu2mem, 0, 0x200000);
	memset(dwEndChannel2, 0, sizeof(dwEndChannel2));

	InitADSR();

	memset(voices, 0, sizeof(voices));
	// last 24 channels have higher mem offset
	for (int i = 0; i < 24; ++i)
		voices[i+24].memoffset = 0x400;

	// init each channel
	for (u32 i = 0; i < ArraySize(voices); ++i)
	{

		voices[i].pLoop = voices[i].pStart = voices[i].pCurr = (u8*)spu2mem;

		voices[i].pvoice = (_SPU_VOICE*)((u8*)spu2regs + voices[i].memoffset) + (i % 24);
		voices[i].ADSRX.SustainLevel = 1024;                // -> init sustain
	}

	return 0;
}

EXPORT_C_(s32) SPU2open(void *pDsp)
{
	LoadConfig();
	SPUCycles = SPUWorkerCycles = 0;
	interrupt = 0;
	SPUStartCycle[0] = SPUStartCycle[1] = 0;
	SPUTargetCycle[0] = SPUTargetCycle[1] = 0;
	g_nSpuInit = 1;
	return 0;
}

EXPORT_C_(void) SPU2close()
{
	g_nSpuInit = 0;
}

EXPORT_C_(void) SPU2shutdown()
{
	free(spu2regs);
	spu2regs = NULL;
	free(spu2mem);
	spu2mem = NULL;
#ifdef SPU2_LOG
    if(spu2Log) {
        fclose(spu2Log);
        spu2Log = NULL;
    }
#endif
}

// simulate SPU2 for 1ms
void SPU2Worker();

#define CYCLES_PER_MS (36864000/1000)

EXPORT_C_(void) SPU2async(u32 cycle)
{
	SPUCycles += cycle;
	if (interrupt & (1 << 2))
	{
		if (SPUCycles - SPUStartCycle[1] >= SPUTargetCycle[1])
		{
			interrupt &= ~(1 << 2);
			irqCallbackDMA7();
		}

	}

	if (interrupt & (1 << 1))
	{
		if (SPUCycles - SPUStartCycle[0] >= SPUTargetCycle[0])
		{
			interrupt &= ~(1 << 1);
			irqCallbackDMA4();
		}
	}

	if (g_nSpuInit)
	{

		while (SPUCycles - SPUWorkerCycles > 0 && CYCLES_PER_MS < SPUCycles - SPUWorkerCycles)
		{
			SPU2Worker();
			SPUWorkerCycles += CYCLES_PER_MS;
		}
	}
}

void InitADSR()                                    // INIT ADSR
{
	u32 r, rs, rd;
	s32 i;
	memset(RateTable, 0, sizeof(u32)*160);      // build the rate table according to Neill's rules (see at bottom of file)

	r = 3;
	rs = 1;
	rd = 0;

	for (i = 32;i < 160;i++)                              // we start at pos 32 with the real values... everything before is 0
	{
		if (r < 0x3FFFFFFF)
		{
			r += rs;
			rd++;
			if (rd == 5)
			{
				rd = 1;
				rs *= 2;
			}
		}
		if (r > 0x3FFFFFFF) r = 0x3FFFFFFF;

		RateTable[i] = r;
	}
}

int MixADSR(VOICE_PROCESSED* pvoice)                             // MIX ADSR
{
	if (pvoice->bStop)                                 // should be stopped:
	{
		if (pvoice->bIgnoreLoop == 0)
		{
			pvoice->ADSRX.EnvelopeVol = 0;
			pvoice->bOn = false;
			pvoice->pStart = (u8*)(spu2mem + pvoice->iStartAddr);
			pvoice->pLoop = (u8*)(spu2mem + pvoice->iStartAddr);
			pvoice->pCurr = (u8*)(spu2mem + pvoice->iStartAddr);
			pvoice->bStop = true;
			pvoice->bIgnoreLoop = false;
			return 0;
		}
		if (pvoice->ADSRX.ReleaseModeExp)// do release
		{
			switch ((pvoice->ADSRX.EnvelopeVol >> 28)&0x7)
			{
				case 0:
					pvoice->ADSRX.EnvelopeVol -= RateTable[(4*(pvoice->ADSRX.ReleaseRate^0x1F))-0x18 +0 + 32];
					break;
				case 1:
					pvoice->ADSRX.EnvelopeVol -= RateTable[(4*(pvoice->ADSRX.ReleaseRate^0x1F))-0x18 +4 + 32];
					break;
				case 2:
					pvoice->ADSRX.EnvelopeVol -= RateTable[(4*(pvoice->ADSRX.ReleaseRate^0x1F))-0x18 +6 + 32];
					break;
				case 3:
					pvoice->ADSRX.EnvelopeVol -= RateTable[(4*(pvoice->ADSRX.ReleaseRate^0x1F))-0x18 +8 + 32];
					break;
				case 4:
					pvoice->ADSRX.EnvelopeVol -= RateTable[(4*(pvoice->ADSRX.ReleaseRate^0x1F))-0x18 +9 + 32];
					break;
				case 5:
					pvoice->ADSRX.EnvelopeVol -= RateTable[(4*(pvoice->ADSRX.ReleaseRate^0x1F))-0x18 +10+ 32];
					break;
				case 6:
					pvoice->ADSRX.EnvelopeVol -= RateTable[(4*(pvoice->ADSRX.ReleaseRate^0x1F))-0x18 +11+ 32];
					break;
				case 7:
					pvoice->ADSRX.EnvelopeVol -= RateTable[(4*(pvoice->ADSRX.ReleaseRate^0x1F))-0x18 +12+ 32];
					break;
			}
		}
		else
		{
			pvoice->ADSRX.EnvelopeVol -= RateTable[(4*(pvoice->ADSRX.ReleaseRate^0x1F))-0x0C + 32];
		}

		if (pvoice->ADSRX.EnvelopeVol < 0)
		{
			pvoice->ADSRX.EnvelopeVol = 0;
			pvoice->bOn = false;
			pvoice->pStart = (u8*)(spu2mem + pvoice->iStartAddr);
			pvoice->pLoop = (u8*)(spu2mem + pvoice->iStartAddr);
			pvoice->pCurr = (u8*)(spu2mem + pvoice->iStartAddr);
			pvoice->bStop = true;
			pvoice->bIgnoreLoop = false;
			//pvoice->bReverb=0;
			//pvoice->bNoise=0;
		}

		pvoice->ADSRX.lVolume = pvoice->ADSRX.EnvelopeVol >> 21;
		pvoice->ADSRX.lVolume = pvoice->ADSRX.EnvelopeVol >> 21;
		return pvoice->ADSRX.lVolume;
	}
	else                                                  // not stopped yet?
	{
		if (pvoice->ADSRX.State == 0)                    // -> attack
		{
			if (pvoice->ADSRX.AttackModeExp)
			{
				if (pvoice->ADSRX.EnvelopeVol < 0x60000000)
					pvoice->ADSRX.EnvelopeVol += RateTable[(pvoice->ADSRX.AttackRate^0x7F)-0x10 + 32];
				else
					pvoice->ADSRX.EnvelopeVol += RateTable[(pvoice->ADSRX.AttackRate^0x7F)-0x18 + 32];
			}
			else
			{
				pvoice->ADSRX.EnvelopeVol += RateTable[(pvoice->ADSRX.AttackRate^0x7F)-0x10 + 32];
			}

			if (pvoice->ADSRX.EnvelopeVol < 0)
			{
				pvoice->ADSRX.EnvelopeVol = 0x7FFFFFFF;
				pvoice->ADSRX.State = 1;
			}

			pvoice->ADSRX.lVolume = pvoice->ADSRX.EnvelopeVol >> 21;
			return pvoice->ADSRX.lVolume;
		}
		//--------------------------------------------------//
		if (pvoice->ADSRX.State == 1)                    // -> decay
		{
			switch ((pvoice->ADSRX.EnvelopeVol >> 28)&0x7)
			{
				case 0:
					pvoice->ADSRX.EnvelopeVol -= RateTable[(4*(pvoice->ADSRX.DecayRate^0x1F))-0x18+0 + 32];
					break;
				case 1:
					pvoice->ADSRX.EnvelopeVol -= RateTable[(4*(pvoice->ADSRX.DecayRate^0x1F))-0x18+4 + 32];
					break;
				case 2:
					pvoice->ADSRX.EnvelopeVol -= RateTable[(4*(pvoice->ADSRX.DecayRate^0x1F))-0x18+6 + 32];
					break;
				case 3:
					pvoice->ADSRX.EnvelopeVol -= RateTable[(4*(pvoice->ADSRX.DecayRate^0x1F))-0x18+8 + 32];
					break;
				case 4:
					pvoice->ADSRX.EnvelopeVol -= RateTable[(4*(pvoice->ADSRX.DecayRate^0x1F))-0x18+9 + 32];
					break;
				case 5:
					pvoice->ADSRX.EnvelopeVol -= RateTable[(4*(pvoice->ADSRX.DecayRate^0x1F))-0x18+10+ 32];
					break;
				case 6:
					pvoice->ADSRX.EnvelopeVol -= RateTable[(4*(pvoice->ADSRX.DecayRate^0x1F))-0x18+11+ 32];
					break;
				case 7:
					pvoice->ADSRX.EnvelopeVol -= RateTable[(4*(pvoice->ADSRX.DecayRate^0x1F))-0x18+12+ 32];
					break;
			}

			if (pvoice->ADSRX.EnvelopeVol < 0) pvoice->ADSRX.EnvelopeVol = 0;
			if (((pvoice->ADSRX.EnvelopeVol >> 27)&0xF) <= pvoice->ADSRX.SustainLevel)
			{
				pvoice->ADSRX.State = 2;
			}

			pvoice->ADSRX.lVolume = pvoice->ADSRX.EnvelopeVol >> 21;
			return pvoice->ADSRX.lVolume;
		}
		//--------------------------------------------------//
		if (pvoice->ADSRX.State == 2)                    // -> sustain
		{
			if (pvoice->ADSRX.SustainIncrease)
			{
				if (pvoice->ADSRX.SustainModeExp)
				{
					if (pvoice->ADSRX.EnvelopeVol < 0x60000000)
						pvoice->ADSRX.EnvelopeVol += RateTable[(pvoice->ADSRX.SustainRate^0x7F)-0x10 + 32];
					else
						pvoice->ADSRX.EnvelopeVol += RateTable[(pvoice->ADSRX.SustainRate^0x7F)-0x18 + 32];
				}
				else
				{
					pvoice->ADSRX.EnvelopeVol += RateTable[(pvoice->ADSRX.SustainRate^0x7F)-0x10 + 32];
				}

				if (pvoice->ADSRX.EnvelopeVol < 0)
				{
					pvoice->ADSRX.EnvelopeVol = 0x7FFFFFFF;
				}
			}
			else
			{
				if (pvoice->ADSRX.SustainModeExp)
				{
					switch ((pvoice->ADSRX.EnvelopeVol >> 28)&0x7)
					{
						case 0:
							pvoice->ADSRX.EnvelopeVol -= RateTable[((pvoice->ADSRX.SustainRate^0x7F))-0x1B +0 + 32];
							break;
						case 1:
							pvoice->ADSRX.EnvelopeVol -= RateTable[((pvoice->ADSRX.SustainRate^0x7F))-0x1B +4 + 32];
							break;
						case 2:
							pvoice->ADSRX.EnvelopeVol -= RateTable[((pvoice->ADSRX.SustainRate^0x7F))-0x1B +6 + 32];
							break;
						case 3:
							pvoice->ADSRX.EnvelopeVol -= RateTable[((pvoice->ADSRX.SustainRate^0x7F))-0x1B +8 + 32];
							break;
						case 4:
							pvoice->ADSRX.EnvelopeVol -= RateTable[((pvoice->ADSRX.SustainRate^0x7F))-0x1B +9 + 32];
							break;
						case 5:
							pvoice->ADSRX.EnvelopeVol -= RateTable[((pvoice->ADSRX.SustainRate^0x7F))-0x1B +10+ 32];
							break;
						case 6:
							pvoice->ADSRX.EnvelopeVol -= RateTable[((pvoice->ADSRX.SustainRate^0x7F))-0x1B +11+ 32];
							break;
						case 7:
							pvoice->ADSRX.EnvelopeVol -= RateTable[((pvoice->ADSRX.SustainRate^0x7F))-0x1B +12+ 32];
							break;
					}
				}
				else
				{
					pvoice->ADSRX.EnvelopeVol -= RateTable[((pvoice->ADSRX.SustainRate^0x7F))-0x0F + 32];
				}

				if (pvoice->ADSRX.EnvelopeVol < 0)
				{
					pvoice->ADSRX.EnvelopeVol = 0;
				}
			}
			pvoice->ADSRX.lVolume = pvoice->ADSRX.EnvelopeVol >> 21;
			return pvoice->ADSRX.lVolume;
		}
	}
	return 0;
}

// simulate SPU2 for 1ms
void SPU2Worker()
{
	u8* start;
	int ch, flags;

	VOICE_PROCESSED* pChannel = voices;
	for (ch = 0;ch < SPU_NUMBER_VOICES;ch++, pChannel++)        // loop em all... we will collect 1 ms of sound of each playing channel
	{
		if (pChannel->bNew)
		{
			pChannel->StartSound();                         // start new sound
			dwEndChannel2[ch/24] &= ~(1 << (ch % 24));            // clear end channel bit
		}

		if (!pChannel->bOn)
		{
			// fill buffer with empty data
			continue;
		}

		if (pChannel->iActFreq != pChannel->iUsedFreq)  // new psx frequency?
			pChannel->VoiceChangeFrequency();

		// loop until 1 ms of data is reached
		int ns = 0;
		while (ns < NSSIZE)
		{
			while (pChannel->spos >= 0x10000)
			{
				if (pChannel->iSBPos == 28)         // 28 reached?
				{
					start = pChannel->pCurr;        // set up the current pos

					// special "stop" sign
					if (start == (u8*) - 1) //!pChannel->bOn
					{
						pChannel->bOn = false;                      // -> turn everything off
						pChannel->ADSRX.lVolume = 0;
						pChannel->ADSRX.EnvelopeVol = 0;
						goto ENDX;                              // -> and done for this channel
					}

					pChannel->iSBPos = 0;

					// decode the 16 byte packet

					flags = (int)start[1];
					start += 16;

					// some callback and irq active?
					if (pChannel->GetCtrl()->irq)
					{
						// if irq address reached or irq on looping addr, when stop/loop flag is set
						u8* pirq = (u8*)pSpuIrq[ch>=24];
						if ((pirq > start - 16  && pirq <= start)
						        || ((flags&1) && (pirq >  pChannel->pLoop - 16 && pirq <= pChannel->pLoop)))
						{
							IRQINFO |= 4 << (int)(ch >= 24);
							irqCallbackSPU2();
						}
					}

					// flag handler
					if ((flags&4) && (!pChannel->bIgnoreLoop))
						pChannel->pLoop = start - 16;            // loop adress

					if (flags&1)                              // 1: stop/loop
					{
						// We play this block out first...
						dwEndChannel2[ch/24] |= (1 << (ch % 24));
						//if(!(flags&2))                          // 1+2: do loop... otherwise: stop
						if (flags != 3 || pChannel->pLoop == NULL)   // PETE: if we don't check exactly for 3, loop hang ups will happen (DQ4, for example)
						{                                      // and checking if pLoop is set avoids crashes, yeah
							start = (u8*) - 1;
							pChannel->bStop = true;
							pChannel->bIgnoreLoop = false;
						}
						else
						{
							start = pChannel->pLoop;
						}
					}

					pChannel->pCurr = start;                  // store values for next cycle
				}

				pChannel->iSBPos++;        // get sample data
				pChannel->spos -= 0x10000;
			}

			MixADSR(pChannel);

			// go to the next packet
			ns++;
			pChannel->spos += pChannel->sinc;
		}
ENDX:
		;
	}

	// mix all channels
	if ((spu2Ru16(REG_C0_MMIX) & 0xC0) && (spu2Ru16(REG_C0_ADMAS) & 0x1) && !(spu2Ru16(REG_C0_CTRL) & 0x30))
	{
		for (int ns = 0;ns < NSSIZE;ns++)
		{
			Adma4.Index += 1;

			if (Adma4.Index == 128 || Adma4.Index == 384)
			{
				if (ADMAS4Write())
				{
					spu2Ru16(REG_C0_SPUSTAT) &= ~0x80;
					irqCallbackDMA4();
				}
				else MemAddr[0] += 1024;
			}

			if (Adma4.Index == 512)
			{
				Adma4.Index = 0;
			}
		}
	}


	if ((spu2Ru16(REG_C1_MMIX) & 0xC0) && (spu2Ru16(REG_C1_ADMAS) & 0x2) && !(spu2Ru16(REG_C1_CTRL) & 0x30))
	{
		for (int ns = 0;ns < NSSIZE;ns++)
		{
			Adma7.Index += 1;

			if (Adma7.Index == 128 || Adma7.Index == 384)
			{
				if (ADMAS7Write())
				{
					spu2Ru16(REG_C1_SPUSTAT) &= ~0x80;
					irqCallbackDMA7();
				}
				else MemAddr[1] += 1024;
			}

			if (Adma7.Index == 512)	Adma7.Index = 0;
		}
	}
}

EXPORT_C_(void) SPU2readDMA4Mem(u16 *pMem, int size)
{
	u32 spuaddr = C0_SPUADDR;
	int i;

	SPU2_LOG("SPU2 readDMA4Mem size %x, addr: %x\n", size, pMem);

	for (i = 0;i < size;i++)
	{
		*pMem++ = *(u16*)(spu2mem + spuaddr);
		if ((spu2Rs16(REG_C0_CTRL)&0x40) && C0_IRQA == spuaddr)
		{
			spu2Ru16(SPDIF_OUT) |= 0x4;
			C0_SPUADDR_SET(spuaddr);
			IRQINFO |= 4;
			irqCallbackSPU2();
		}

		spuaddr++;           // inc spu addr
		if (spuaddr > 0x0fffff) // wrap at 2Mb
			spuaddr = 0;           // wrap
	}

	spuaddr += 19; //Transfer Local To Host TSAH/L + Data Size + 20 (already +1'd)
	C0_SPUADDR_SET(spuaddr);

	// got from J.F. and Kanodin... is it needed?
	spu2Ru16(REG_C0_SPUSTAT) &= ~0x80;                                    // DMA complete
	SPUStartCycle[0] = SPUCycles;
	SPUTargetCycle[0] = size;
	interrupt |= (1 << 1);
}

EXPORT_C_(void) SPU2readDMA7Mem(u16* pMem, int size)
{
	u32 spuaddr = C1_SPUADDR;
	int i;

	SPU2_LOG("SPU2 readDMA7Mem size %x, addr: %x\n", size, pMem);

	for (i = 0;i < size;i++)
	{
		*pMem++ = *(u16*)(spu2mem + spuaddr);
		if ((spu2Rs16(REG_C1_CTRL)&0x40) && C1_IRQA == spuaddr)
		{
			spu2Ru16(SPDIF_OUT) |= 0x8;
			C1_SPUADDR_SET(spuaddr);
			IRQINFO |= 8;
			irqCallbackSPU2();
		}
		spuaddr++;                            // inc spu addr
		if (spuaddr > 0x0fffff) // wrap at 2Mb
			spuaddr = 0;           // wrap
	}

	spuaddr += 19; //Transfer Local To Host TSAH/L + Data Size + 20 (already +1'd)
	C1_SPUADDR_SET(spuaddr);

	// got from J.F. and Kanodin... is it needed?
	spu2Ru16(REG_C1_SPUSTAT) &= ~0x80;                                   // DMA complete
	SPUStartCycle[1] = SPUCycles;
	SPUTargetCycle[1] = size;
	interrupt |= (1 << 2);
}

// WRITE

// AutoDMA's are used to transfer to the DIRECT INPUT area of the spu2 memory
// Left and Right channels are always interleaved together in the transfer so
// the AutoDMA's deinterleaves them and transfers them. An interrupt is
// generated when half of the buffer (256 short-words for left and 256
// short-words for right ) has been transferred. Another interrupt occurs at
// the end of the transfer.
int ADMAS4Write()
{
	u32 spuaddr;
	if (interrupt & 0x2) return 0;
	if (Adma4.AmountLeft <= 0) return 1;

	spuaddr = C0_SPUADDR;
	// SPU2 Deinterleaves the Left and Right Channels
	memcpy((s16*)(spu2mem + spuaddr + 0x2000), (s16*)Adma4.MemAddr, 512);
	Adma4.MemAddr += 256;
	memcpy((s16*)(spu2mem + spuaddr + 0x2200), (s16*)Adma4.MemAddr, 512);
	Adma4.MemAddr += 256;
	spuaddr = (spuaddr + 256) & 511;
	C0_SPUADDR_SET(spuaddr);

	Adma4.AmountLeft -= 512;
	if (Adma4.AmountLeft == 0)
	{
		SPUStartCycle[0] = SPUCycles;
		SPUTargetCycle[0] = 1;//512*48000;
		spu2Ru16(REG_C0_SPUSTAT) &= ~0x80;
		interrupt |= (1 << 1);
	}
	return 0;
}

int ADMAS7Write()
{
	u32 spuaddr;
	if (interrupt & 0x4) return 0;
	if (Adma7.AmountLeft <= 0) return 1;

	spuaddr = C1_SPUADDR;
	// SPU2 Deinterleaves the Left and Right Channels
	memcpy((s16*)(spu2mem + spuaddr + 0x2400), (s16*)Adma7.MemAddr, 512);
	Adma7.MemAddr += 256;
	memcpy((s16*)(spu2mem + spuaddr + 0x2600), (s16*)Adma7.MemAddr, 512);
	Adma7.MemAddr += 256;
	spuaddr = (spuaddr + 256) & 511;
	C1_SPUADDR_SET(spuaddr);

	Adma7.AmountLeft -= 512;
	if (Adma7.AmountLeft == 0)
	{
		SPUStartCycle[1] = SPUCycles;
		SPUTargetCycle[1] = 1;//512*48000;
		spu2Ru16(REG_C1_SPUSTAT) &= ~0x80;
		interrupt |= (1 << 2);
	}
	return 0;
}

EXPORT_C_(void) SPU2writeDMA4Mem(u16* pMem, int size)
{
	u32 spuaddr;

	SPU2_LOG("SPU2 writeDMA4Mem size %x, addr: %x\n", size, pMem);

	if ((spu2Ru16(REG_C0_ADMAS) & 0x1) && (spu2Ru16(REG_C0_CTRL) & 0x30) == 0 && size)
	{
		//fwrite(pMem,iSize<<1,1,LogFile);
		memset(&Adma4, 0, sizeof(ADMA));
		C0_SPUADDR_SET(0);
		Adma4.MemAddr = pMem;
		Adma4.AmountLeft = size;
		ADMAS4Write();
		return;
	}

	spuaddr = C0_SPUADDR;
	memcpy((u8*)(spu2mem + spuaddr), (u8*)pMem, size << 1);
	spuaddr += size;
	C0_SPUADDR_SET(spuaddr);

	if ((spu2Ru16(REG_C0_CTRL)&0x40) && C0_IRQA == spuaddr)
	{
		spu2Ru16(SPDIF_OUT) |= 0x4;
		IRQINFO |= 4;
		irqCallbackSPU2();
	}
	if (spuaddr > 0xFFFFE)
		spuaddr = 0x2800;
	C0_SPUADDR_SET(spuaddr);

	MemAddr[0] += size << 1;
	spu2Ru16(REG_C0_SPUSTAT) &= ~0x80;
	SPUStartCycle[0] = SPUCycles;
	SPUTargetCycle[0] = 1;//iSize;
	interrupt |= (1 << 1);
}

EXPORT_C_(void) SPU2writeDMA7Mem(u16* pMem, int size)
{
	u32 spuaddr;

	SPU2_LOG("SPU2 writeDMA7Mem size %x, addr: %x\n", size, pMem);

	if ((spu2Ru16(REG_C1_ADMAS) & 0x2) && (spu2Ru16(REG_C1_CTRL) & 0x30) == 0 && size)
	{
		//fwrite(pMem,iSize<<1,1,LogFile);
		memset(&Adma7, 0, sizeof(ADMA));
		C1_SPUADDR_SET(0);
		Adma7.MemAddr = pMem;
		Adma7.AmountLeft = size;
		ADMAS7Write();
		return;
	}

	spuaddr = C1_SPUADDR;
	memcpy((u8*)(spu2mem + spuaddr), (u8*)pMem, size << 1);
	spuaddr += size;
	C1_SPUADDR_SET(spuaddr);

	if ((spu2Ru16(REG_C1_CTRL)&0x40) && C1_IRQA == spuaddr)
	{
		spu2Ru16(SPDIF_OUT) |= 0x8;
		IRQINFO |= 8;
		irqCallbackSPU2();
	}
	if (spuaddr > 0xFFFFE)
		spuaddr = 0x2800;
	C1_SPUADDR_SET(spuaddr);

	MemAddr[1] += size << 1;
	spu2Ru16(REG_C1_SPUSTAT) &= ~0x80;
	SPUStartCycle[1] = SPUCycles;
	SPUTargetCycle[1] = 1;//iSize;
	interrupt |= (1 << 2);
}

EXPORT_C_(void) SPU2interruptDMA4()
{
	SPU2_LOG("SPU2 interruptDMA4\n");

	spu2Rs16(REG_C0_CTRL) &= ~0x30;
	spu2Ru16(REG_C0_SPUSTAT) |= 0x80;
}

EXPORT_C_(void) SPU2interruptDMA7()
{
	SPU2_LOG("SPU2 interruptDMA7\n");

//	spu2Rs16(REG_C1_CTRL)&= ~0x30;
//	//spu2Rs16(REG__5B0) = 0;
//	spu2Rs16(SPU2_STATX_DREQ)|= 0x80;
	spu2Rs16(REG_C1_CTRL) &= ~0x30;
	spu2Ru16(REG_C1_SPUSTAT) |= 0x80;
}

// turn channels on
void SoundOn(s32 start, s32 end, u16 val)   // SOUND ON PSX COMAND
{
	for (s32 ch = start;ch < end;ch++, val >>= 1)             // loop channels
	{
		if ((val&1) && voices[ch].pStart)                   // mmm... start has to be set before key on !?!
		{
			voices[ch].bNew = true;
			voices[ch].bIgnoreLoop = false;
		}
	}
}

// turn channels off
void SoundOff(s32 start, s32 end, u16 val)  // SOUND OFF PSX COMMAND
{
	for (s32 ch = start;ch < end;ch++, val >>= 1)             // loop channels
	{
		if (val&1)                                          // && s_chan[i].bOn)  mmm...
			voices[ch].bStop = true;
	}
}

void FModOn(s32 start, s32 end, u16 val)    // FMOD ON PSX COMMAND
{
	int ch;

	for (ch = start;ch < end;ch++, val >>= 1)             // loop channels
	{
		if (val&1)                                          // -> fmod on/off
		{
			if (ch > 0)
			{
			}
		}
		else
		{
			// turn fmod off
		}
	}
}

EXPORT_C_(void) SPU2write(u32 mem, u16 value)
{
	u32 spuaddr;

	SPU2_LOG("SPU2 write mem %x value %x\n", mem, value);

	assert(C0_SPUADDR < 0x100000);
	assert(C1_SPUADDR < 0x100000);

	spu2Ru16(mem) = value;
	u32 r = mem & 0xffff;

	// channel info
	if ((r >= 0x0000 && r < 0x0180) || (r >= 0x0400 && r < 0x0580))  // some channel info?
	{
		int ch = 0;
		if (r >= 0x400) ch = ((r - 0x400) >> 4) + 24;
		else ch = (r >> 4);

		VOICE_PROCESSED* pvoice = &voices[ch];

		switch (r&0x0f)
		{
			case 0:
			case 2:
				pvoice->SetVolume(mem&0x2);
				break;
			case 4:
				{
					int NP;
					if (value > 0x3fff)
						NP = 0x3fff;                        // get pitch val
					else
						NP = value;

					pvoice->pvoice->pitch = NP;

					NP = (44100L * NP) / 4096L;                           // calc frequency
					if (NP < 1) NP = 1;                                   // some security
					pvoice->iActFreq = NP;                             // store frequency
					break;
				}
			case 6:
				{
					pvoice->ADSRX.AttackModeExp = (value & 0x8000) ? 1 : 0;
					pvoice->ADSRX.AttackRate = ((value >> 8) & 0x007f);
					pvoice->ADSRX.DecayRate = (((value >> 4) & 0x000f));
					pvoice->ADSRX.SustainLevel = (value & 0x000f);
					break;
				}
			case 8:
				pvoice->ADSRX.SustainModeExp = (value & 0x8000) ? 1 : 0;
				pvoice->ADSRX.SustainIncrease = (value & 0x4000) ? 0 : 1;
				pvoice->ADSRX.SustainRate = ((value >> 6) & 0x007f);
				pvoice->ADSRX.ReleaseModeExp = (value & 0x0020) ? 1 : 0;
				pvoice->ADSRX.ReleaseRate = ((value & 0x001f));
				break;
		}

		return;
	}

	// more channel info
	if ((r >= 0x01c0 && r <= 0x02E0) || (r >= 0x05c0 && r <= 0x06E0))
	{
		s32 ch = 0;
		u32 rx = r;
		if (rx >= 0x400)
		{
			ch = 24;
			rx -= 0x400;
		}

		ch += ((rx - 0x1c0) / 12);
		rx -= (ch % 24) * 12;
		VOICE_PROCESSED* pvoice = &voices[ch];

		switch (rx)
		{
			case 0x1C0:
				pvoice->iStartAddr = (((u32)value & 0x3f) << 16) | (pvoice->iStartAddr & 0xFFFF);
				pvoice->pStart = (u8*)(spu2mem + pvoice->iStartAddr);
				break;
			case 0x1C2:
				pvoice->iStartAddr = (pvoice->iStartAddr & 0x3f0000) | (value & 0xFFFF);
				pvoice->pStart = (u8*)(spu2mem + pvoice->iStartAddr);
				break;
			case 0x1C4:
				pvoice->iLoopAddr = (((u32)value & 0x3f) << 16) | (pvoice->iLoopAddr & 0xFFFF);
				pvoice->pLoop = (u8*)(spu2mem + pvoice->iLoopAddr);
				pvoice->bIgnoreLoop = pvoice->iLoopAddr > 0;
				break;
			case 0x1C6:
				pvoice->iLoopAddr = (pvoice->iLoopAddr & 0x3f0000) | (value & 0xFFFF);
				pvoice->pLoop = (u8*)(spu2mem + pvoice->iLoopAddr);
				pvoice->bIgnoreLoop = pvoice->iLoopAddr > 0;
				break;
			case 0x1C8:
				// unused... check if it gets written as well
				pvoice->iNextAddr = (((u32)value & 0x3f) << 16) | (pvoice->iNextAddr & 0xFFFF);
				break;
			case 0x1CA:
				// unused... check if it gets written as well
				pvoice->iNextAddr = (pvoice->iNextAddr & 0x3f0000) | (value & 0xFFFF);
				break;
		}

		return;
	}

	// process non-channel data
	switch (mem&0xffff)
	{
		case REG_C0_SPUDATA:
			spuaddr = C0_SPUADDR;
			spu2mem[spuaddr] = value;
			spuaddr++;
			if ((spu2Ru16(REG_C0_CTRL)&0x40) && C0_IRQA == spuaddr)
			{
				spu2Ru16(SPDIF_OUT) |= 0x4;
				IRQINFO |= 4;
				irqCallbackSPU2();
			}
			if (spuaddr > 0xFFFFE) spuaddr = 0x2800;

			C0_SPUADDR_SET(spuaddr);
			spu2Ru16(REG_C0_SPUSTAT) &= ~0x80;
			spu2Ru16(REG_C0_CTRL) &= ~0x30;
			break;
		case REG_C1_SPUDATA:
			spuaddr = C1_SPUADDR;
			spu2mem[spuaddr] = value;
			spuaddr++;
			if ((spu2Ru16(REG_C1_CTRL)&0x40) && C1_IRQA == spuaddr)
			{
				spu2Ru16(SPDIF_OUT) |= 0x8;
				IRQINFO |= 8;
				irqCallbackSPU2();
			}
			if (spuaddr > 0xFFFFE) spuaddr = 0x2800;

			C1_SPUADDR_SET(spuaddr);
			spu2Ru16(REG_C1_SPUSTAT) &= ~0x80;
			spu2Ru16(REG_C1_CTRL) &= ~0x30;
			break;
		case REG_C0_IRQA_HI:
		case REG_C0_IRQA_LO:
			pSpuIrq[0] = spu2mem + (C0_IRQA << 1);
			break;
		case REG_C1_IRQA_HI:
		case REG_C1_IRQA_LO:
			pSpuIrq[1] = spu2mem + (C1_IRQA << 1);
			break;

		case REG_C0_SPUADDR_HI:
		case REG_C1_SPUADDR_HI:
			spu2Ru16(mem) = value & 0xf;
			break;

		case REG_C0_SPUON1:
			SoundOn(0, 16, value);
			break;
		case REG_C0_SPUON2:
			SoundOn(16, 24, value);
			break;
		case REG_C1_SPUON1:
			SoundOn(24, 40, value);
			break;
		case REG_C1_SPUON2:
			SoundOn(40, 48, value);
			break;
		case REG_C0_SPUOFF1:
			SoundOff(0, 16, value);
			break;
		case REG_C0_SPUOFF2:
			SoundOff(16, 24, value);
			break;
		case REG_C1_SPUOFF1:
			SoundOff(24, 40, value);
			break;
		case REG_C1_SPUOFF2:
			SoundOff(40, 48, value);
			break;

			// According to manual all bits are cleared by writing an arbitary value
		case REG_C0_END1:
			dwEndChannel2[0] = 0;
			break;
		case REG_C0_END2:
			dwEndChannel2[0] = 0;
			break;
		case REG_C1_END1:
			dwEndChannel2[1] = 0;
			break;
		case REG_C1_END2:
			dwEndChannel2[1] = 0;
			break;
		case REG_C0_FMOD1:
			FModOn(0, 16, value);
			break;
		case REG_C0_FMOD2:
			FModOn(16, 24, value);
			break;
		case REG_C1_FMOD1:
			FModOn(24, 40, value);
			break;
		case REG_C1_FMOD2:
			FModOn(40, 48, value);
			break;
	}

	assert(C0_SPUADDR < 0x100000);
	assert(C1_SPUADDR < 0x100000);
}

EXPORT_C_(u16) SPU2read(u32 mem)
{
	u32 spuaddr;
	u16 ret;
	u32 r = mem & 0xffff;

	if ((r >= 0x0000 && r <= 0x0180) || (r >= 0x0400 && r <= 0x0580))  // some channel info?
	{
		s32 ch = 0;

		if (r >= 0x400)
			ch = ((r - 0x400) >> 4) + 24;
		else
			ch = (r >> 4);

		VOICE_PROCESSED* pvoice = &voices[ch];

		switch (r&0x0f)
		{
			case 10:
				return (u16)(pvoice->ADSRX.EnvelopeVol >> 16);
		}
	}

	if ((r > 0x01c0 && r <= 0x02E0) || (r > 0x05c0 && r <= 0x06E0))  // some channel info?
	{
		s32 ch = 0;
		u32 rx = r;

		if (rx >= 0x400)
		{
			ch = 24;
			rx -= 0x400;
		}

		ch += ((rx - 0x1c0) / 12);
		rx -= (ch % 24) * 12;
		VOICE_PROCESSED* pvoice = &voices[ch];

		switch (rx)
		{
			case 0x1C0:
				return (u16)(((pvoice->pStart - (u8*)spu2mem) >> 17)&0x3F);
			case 0x1C2:
				return (u16)(((pvoice->pStart - (u8*)spu2mem) >> 1)&0xFFFF);
			case 0x1C4:
				return (u16)(((pvoice->pLoop - (u8*)spu2mem) >> 17)&0x3F);
			case 0x1C6:
				return (u16)(((pvoice->pLoop - (u8*)spu2mem) >> 1)&0xFFFF);
			case 0x1C8:
				return (u16)(((pvoice->pCurr - (u8*)spu2mem) >> 17)&0x3F);
			case 0x1CA:
				return (u16)(((pvoice->pCurr - (u8*)spu2mem) >> 1)&0xFFFF);
		}
	}

	switch (mem&0xffff)
	{
		case REG_C0_SPUDATA:
			spuaddr = C0_SPUADDR;
			ret = spu2mem[spuaddr];
			spuaddr++;
			if (spuaddr > 0xfffff) spuaddr = 0;
			C0_SPUADDR_SET(spuaddr);
			break;

		case REG_C1_SPUDATA:
			spuaddr = C1_SPUADDR;
			ret = spu2mem[spuaddr];
			spuaddr++;
			if (spuaddr > 0xfffff) spuaddr = 0;
			C1_SPUADDR_SET(spuaddr);
			break;

		case REG_C0_END1:
			return (dwEndChannel2[0]&0xffff);
		case REG_C0_END2:
			return (dwEndChannel2[0] >> 16);
		case REG_C1_END1:
			return (dwEndChannel2[1]&0xffff);
		case REG_C1_END2:
			return (dwEndChannel2[1] >> 16);

		case REG_IRQINFO:
			ret = IRQINFO;
			IRQINFO = 0;
			break;
		default:
			ret = spu2Ru16(mem);
	}

	SPU2_LOG("SPU2 read mem %x: %x\n", mem, ret);

	return ret;
}

EXPORT_C_(void) SPU2WriteMemAddr(int core, u32 value)
{
	MemAddr[core] = value;
}

EXPORT_C_(u32) SPU2ReadMemAddr(int core)
{
	return MemAddr[core];
}

EXPORT_C_(void) SPU2irqCallback(void (*SPU2callback)(), void (*DMA4callback)(), void (*DMA7callback)())
{
	irqCallbackSPU2 = SPU2callback;
	irqCallbackDMA4 = DMA4callback;
	irqCallbackDMA7 = DMA7callback;
}

// VOICE_PROCESSED definitions
SPU_CONTROL_* VOICE_PROCESSED::GetCtrl()
{
	return ((SPU_CONTROL_*)(spu2regs + memoffset + REG_C0_CTRL));
}

void VOICE_PROCESSED::SetVolume(int iProcessRight)
{
	u16 vol = iProcessRight ? pvoice->right.word : pvoice->left.word;

	if (vol&0x8000) // sweep not working
	{
		s16 sInc = 1;                                     // -> sweep up?
		if (vol&0x2000) sInc = -1;                          // -> or down?
		if (vol&0x1000) vol ^= 0xffff;                      // -> mmm... phase inverted? have to investigate this
		vol = ((vol & 0x7f) + 1) / 2;                       // -> sweep: 0..127 -> 0..64
		vol += vol / (2 * sInc);                            // -> HACK: we don't sweep right now, so we just raise/lower the volume by the half!
		vol *= 128;
	}
	else                                                  // no sweep:
	{
		if (vol&0x4000)                                     // -> mmm... phase inverted? have to investigate this
			vol = 0x3fff - (vol & 0x3fff);
	}

	vol &= 0x3fff;
	// set volume
	//if( iProcessRight ) right = vol;
	//else left = vol;
}

void VOICE_PROCESSED::StartSound()
{
	ADSRX.lVolume = 1; // and init some adsr vars
	ADSRX.State = 0;
	ADSRX.EnvelopeVol = 0;

	if (bReverb && GetCtrl()->reverb)
	{
		// setup the reverb effects
	}

	pCurr = pStart; // set sample start
	iSBPos = 28;

	bNew = false;       // init channel flags
	bStop = false;
	bOn = true;
	spos = 0x10000L;
}

void VOICE_PROCESSED::VoiceChangeFrequency()
{
	iUsedFreq = iActFreq;             // -> take it and calc steps
	sinc = (u32)pvoice->pitch << 4;
	if (!sinc)
		sinc = 1;
}

void VOICE_PROCESSED::Stop()
{
}

// GUI Routines
EXPORT_C_(s32) SPU2test()
{
	return 0;
}

typedef struct
{
	u32 version;
	u8 spu2regs[0x10000];
} SPU2freezeData;

EXPORT_C_(s32) SPU2freeze(int mode, freezeData *data)
{
	SPU2freezeData *spud;

	if (mode == FREEZE_LOAD)
	{
		spud = (SPU2freezeData*)data->data;
		if (spud->version == 0x11223344)
		{
			memcpy(spu2regs, spud->spu2regs, 0x10000);
		}
		else
		{
			printf("SPU2null wrong format\n");
		}
	}
	else
		if (mode == FREEZE_SAVE)
		{
			spud = (SPU2freezeData*)data->data;
			spud->version = 0x11223344;
			memcpy(spud->spu2regs, spu2regs, 0x10000);
		}
		else
			if (mode == FREEZE_SIZE)
			{
				data->size = sizeof(SPU2freezeData);
			}

	return 0;
}