Beispiel #1
0
__forceinline void UpdateSpdifMode()
{
	int OPM=PlayMode;

	if(Spdif.Out&0x4) // use 24/32bit PCM data streaming
	{
		PlayMode=8;
		ConLog("* SPU2-X: WARNING: Possibly CDDA mode set!\n");
		return;
	}

	if(Spdif.Out&SPDIF_OUT_BYPASS)
	{
		PlayMode=2;
		if(!(Spdif.Mode&SPDIF_MODE_BYPASS_BITSTREAM))
			PlayMode=4; //bitstream bypass
	}
	else
	{
		PlayMode=0; //normal processing
		if(Spdif.Out&SPDIF_OUT_PCM)
		{
			PlayMode=1;
		}
	}
	if(OPM!=PlayMode)
	{
		ConLog("* SPU2-X: Play Mode Set to %s (%d).\n",
			(PlayMode==0) ? "Normal" : ((PlayMode==1) ? "PCM Clone" : ((PlayMode==2) ? "PCM Bypass" : "BitStream Bypass")),PlayMode);
	}
}
Beispiel #2
0
void SndBuffer::_WriteSamples(StereoOut32 *bData, int nSamples)
{
    m_predictData = 0;

    // Problem:
    //  If the SPU2 gets out of sync with the SndOut device, the writepos of the
    //  circular buffer will overtake the readpos, leading to a prolonged period
    //  of hopscotching read/write accesses (ie, lots of staticy crap sound for
    //  several seconds).
    //
    // Compromise:
    //  When an overrun occurs, we adapt by discarding a portion of the buffer.
    //  The older portion of the buffer is discarded rather than incoming data,
    //  so that the overall audio synchronization is better.

    int free = m_size - _GetApproximateDataInBuffer(); // -1, but the <= handles that
    if( free <= nSamples )
    {
        // Disabled since the lock-free queue can't handle changing the read end from the write thread
#if 0
        // Buffer overrun!
        // Dump samples from the read portion of the buffer instead of dropping
        // the newly written stuff.

        s32 comp;

        if( SynchMode == 0 ) // TimeStrech on
        {
            comp = timeStretchOverrun();
        }
        else
        {
            // Toss half the buffer plus whatever's being written anew:
            comp = GetAlignedBufferSize( (m_size + nSamples ) / 16 );
            if( comp > (m_size-SndOutPacketSize) ) comp = m_size-SndOutPacketSize;
        }

        _DropSamples_Internal(comp);

        if( MsgOverruns() )
            ConLog(" * SPU2 > Overrun Compensation (%d packets tossed)\n", comp / SndOutPacketSize );
        lastPct = 0.0;		// normalize the timestretcher
#else
        if( MsgOverruns() )
            ConLog(" * SPU2 > Overrun! 1 packet tossed)\n");
        lastPct = 0.0;		// normalize the timestretcher
        return;
#endif
    }

    _WriteSamples_Safe(bData, nSamples);
}
Beispiel #3
0
// Core 1 Input is "CDDA mode" - Source audio data is 32 bits.
// PS2 note:  Very! few PS2 games use this mode.  Some PSX games used it, however no
// *known* PS2 game does since it was likely only available if the game was recorded to CD
// media (ie, not available in DVD mode, which almost all PS2 games use).  Plus PS2 games
// generally prefer to use ADPCM streaming audio since they need as much storage space as
// possible for FMVs and high-def textures.
//
StereoOut32 V_Core::ReadInput_HiFi()
{
    if (psxmode)
        ConLog("ReadInput_HiFi!!!!!\n");
    InputPosRead &= ~1;
    //
    //#ifdef PCM24_S1_INTERLEAVE
    //	StereoOut32 retval(
    //		*((s32*)(ADMATempBuffer+(InputPosRead<<1))),
    //		*((s32*)(ADMATempBuffer+(InputPosRead<<1)+2))
    //	);
    //#else
    //	StereoOut32 retval(
    //		(s32&)(ADMATempBuffer[InputPosRead]),
    //		(s32&)(ADMATempBuffer[InputPosRead+0x200])
    //	);
    //#endif

    StereoOut32 retval(
        (s32 &)(*GetMemPtr(0x2000 + (Index << 10) + InputPosRead)),
        (s32 &)(*GetMemPtr(0x2200 + (Index << 10) + InputPosRead)));

    if (Index == 1) {
        // CDDA Mode:
        // give 30 bit data (SndOut downsamples the rest of the way)
        // HACKFIX: 28 bits seems better according to rama.  I should take some time and do some
        //    bitcounting on this one.  --air
        retval.Left >>= 4;
        retval.Right >>= 4;
    }
Beispiel #4
0
EXPORT_C_(s32) SPU2init() 
{
#define MAKESURE(a,b) \
	/*fprintf(stderr,"%08p: %08p == %08p\n",&(regtable[a>>1]),regtable[a>>1],U16P(b));*/ \
	assert(regtable[(a)>>1]==U16P(b))

	MAKESURE(0x800,zero);

	s32 c=0,v=0;
	ReadSettings();

#ifdef SPU2_LOG
	if(AccessLog()) 
	{
		spu2Log = _wfopen( AccessLogFileName, _T("w") );
		setvbuf(spu2Log, NULL,  _IONBF, 0);
		FileLog("SPU2init\n");
	}
#endif
	srand((unsigned)time(NULL));

	disableFreezes=false;

	if (spu2init)
	{
		ConLog( " * SPU2: Already initialized - Ignoring SPU2init signal." );
		return 0;
	}

	spu2init=true;

	spu2regs  = (short*)malloc(0x010000);
	_spu2mem  = (short*)malloc(0x200000);

	// adpcm decoder cache:
	//  the cache data size is determined by taking the number of adpcm blocks
	//  (2MB / 16) and multiplying it by the decoded block size (28 samples).
	//  Thus: pcm_cache_data = 7,340,032 bytes (ouch!)
	//  Expanded: 16 bytes expands to 56 bytes [3.5:1 ratio]
	//    Resulting in 2MB * 3.5.

	pcm_cache_data = (PcmCacheEntry*)calloc( pcm_BlockCount, sizeof(PcmCacheEntry) );

	if( (spu2regs == NULL) || (_spu2mem == NULL) ||
		(pcm_cache_data == NULL) )
	{
		SysMessage("SPU2: Error allocating Memory\n"); return -1;
	}

	for(int mem=0;mem<0x800;mem++)
	{
		u16 *ptr=regtable[mem>>1];
		if(!ptr) {
			regtable[mem>>1] = &(spu2Ru16(mem));
		}
	}
Beispiel #5
0
void SPU_ps1_write(u32 mem, u16 value) 
{
	bool show=true;

	u32 reg = mem&0xffff;

	if((reg>=0x1c00)&&(reg<0x1d80))
	{
		//voice values
		u8 voice = ((reg-0x1c00)>>4);
		u8 vval = reg&0xf;
		switch(vval)
		{
			case 0: //VOLL (Volume L)
				Cores[0].Voices[voice].Volume.Left.Mode = 0;
				Cores[0].Voices[voice].Volume.Left.RegSet( value << 1 );
				Cores[0].Voices[voice].Volume.Left.Reg_VOL = value;
			break;

			case 1: //VOLR (Volume R)
				Cores[0].Voices[voice].Volume.Right.Mode = 0;
				Cores[0].Voices[voice].Volume.Right.RegSet( value << 1 );
				Cores[0].Voices[voice].Volume.Right.Reg_VOL = value;
			break;
			
			case 2:	Cores[0].Voices[voice].Pitch = value; break;
			case 3:	Cores[0].Voices[voice].StartA = (u32)value<<8; break;

			case 4: // ADSR1 (Envelope)
				Cores[0].Voices[voice].ADSR.AttackMode = (value & 0x8000)>>15;
				Cores[0].Voices[voice].ADSR.AttackRate = (value & 0x7F00)>>8;
				Cores[0].Voices[voice].ADSR.DecayRate = (value & 0xF0)>>4;
				Cores[0].Voices[voice].ADSR.SustainLevel = (value & 0xF);
				Cores[0].Voices[voice].ADSR.Reg_ADSR1 = value;
			break;

			case 5: // ADSR2 (Envelope)
				Cores[0].Voices[voice].ADSR.SustainMode = (value & 0xE000)>>13;
				Cores[0].Voices[voice].ADSR.SustainRate = (value & 0x1FC0)>>6;
				Cores[0].Voices[voice].ADSR.ReleaseMode = (value & 0x20)>>5;
				Cores[0].Voices[voice].ADSR.ReleaseRate = (value & 0x1F);
				Cores[0].Voices[voice].ADSR.Reg_ADSR2 = value;
			break;
			
			case 6:
				Cores[0].Voices[voice].ADSR.Value = ((s32)value<<16) | value;
				ConLog( "* SPU2: Mysterious ADSR Volume Set to 0x%x", value );
			break;

			case 7:	Cores[0].Voices[voice].LoopStartA = (u32)value <<8;	break;

			jNO_DEFAULT;
		}
	}
Beispiel #6
0
void DspCloseLibrary()
#ifdef USE_A_THREAD
{
	if(!dspPluginEnabled) return ;

	PostThreadMessage(UpdateThreadId,WM_QUIT,0,0);
	running=false;
	if(WaitForSingleObject(hUpdateThread,1000)==WAIT_TIMEOUT)
	{
		ConLog("SPU2-X: WARNING: DSP Thread did not close itself in time. Assuming hung. Terminating.\n");
		TerminateThread(hUpdateThread,1);
	}
}
Beispiel #7
0
void V_Core::StartADMAWrite(u16 *pMem, u32 sz)
{
#ifndef ENABLE_NEW_IOPDMA_SPU2
	int size = (sz)&(~511);

	if(MsgAutoDMA()) ConLog("* SPU2-X: DMA%c AutoDMA Transfer of %d bytes to %x (%02x %x %04x).\n",
		GetDmaIndexChar(), size<<1, TSA, DMABits, AutoDMACtrl, (~Regs.ATTR)&0x7fff);

	InputDataProgress = 0;
	if((AutoDMACtrl&(Index+1))==0)
	{
		TSA = 0x2000 + (Index<<10);
		DMAICounter = size;
	}
	else if(size>=512)
	{
		InputDataLeft = size;
		if(AdmaInProgress==0)
		{
#ifdef PCM24_S1_INTERLEAVE
			if((Index==1)&&((PlayMode&8)==8))
			{
				AutoDMAReadBuffer(Index,1);
			}
			else
			{
				AutoDMAReadBuffer(Index,0);
			}
#else
			if( ((PlayMode&4)==4) && (Index==0) )
				Cores[0].InputPosRead=0;

			AutoDMAReadBuffer(0);
#endif

			if(size==512)
				DMAICounter = size;
		}

		AdmaInProgress = 1;
	}
	else
	{
		InputDataLeft	= 0;
		DMAICounter		= 1;
	}
	TADR = MADR + (size<<1);
#endif
}
Beispiel #8
0
void SndBuffer::timeStretchWrite()
{
	bool progress = false;

	// data prediction helps keep the tempo adjustments more accurate.
	// The timestretcher returns packets in belated "clump" form.
	// Meaning that most of the time we'll get nothing back, and then
	// suddenly we'll get several chunks back at once.  Thus we use
	// data prediction to make the timestretcher more responsive.

	PredictDataWrite( (int)( SndOutPacketSize / eTempo ) );
	CvtPacketToFloat( sndTempBuffer );

	pSoundTouch->putSamples( (float*)sndTempBuffer, SndOutPacketSize );

	int tempProgress;
	while( tempProgress = pSoundTouch->receiveSamples( (float*)sndTempBuffer, SndOutPacketSize),
		tempProgress != 0 )
	{
		// Hint: It's assumed that pSoundTouch will return chunks of 128 bytes (it always does as
		// long as the SSE optimizations are enabled), which means we can do our own SSE opts here.

		CvtPacketToInt( sndTempBuffer, tempProgress );
		_WriteSamples( sndTempBuffer, tempProgress );
		progress = true;
	}

#ifdef SPU2X_USE_OLD_STRETCHER
	UpdateTempoChangeSoundTouch();
#else
	UpdateTempoChangeSoundTouch2();
#endif

	if( MsgOverruns() )
	{
		if( progress )
		{
			if( ++ts_stats_logcounter > 150 )
			{
				ts_stats_logcounter = 0;
				ConLog( " * SPU2 > Timestretch Stats > %d percent stretched. Total stretchblocks = %d.\n",
					( ts_stats_stretchblocks * 100 ) / ( ts_stats_normalblocks + ts_stats_stretchblocks ),
					ts_stats_stretchblocks);
				ts_stats_normalblocks = 0;
				ts_stats_stretchblocks = 0;
			}
		}
	}
}
Beispiel #9
0
// writes a signed value to the SPU2 ram
// Invalidates the ADPCM cache in the process.
__forceinline void spu2M_Write( u32 addr, s16 value )
{
	// Make sure the cache is invalidated:
	// (note to self : addr address WORDs, not bytes)

	addr &= 0xfffff;
	if( addr >= SPU2_DYN_MEMLINE )
	{
		const int cacheIdx = addr / pcm_WordsPerBlock;
		pcm_cache_data[cacheIdx].Validated = false;

		if(MsgToConsole()) ConLog( "* SPU2-X: PcmCache Block Clear at 0x%x (cacheIdx=0x%x)\n", addr, cacheIdx);
	}
	*GetMemPtr( addr ) = value;
}
Beispiel #10
0
void V_Core::WriteRegPS1( u32 mem, u16 value )
{
	jASSUME( Index == 0 );		// Valid on Core 0 only!

	bool show	= true;
	u32 reg		= mem & 0xffff;

	if((reg>=0x1c00)&&(reg<0x1d80))
	{
		//voice values
		u8 voice = ((reg-0x1c00)>>4);
		u8 vval = reg&0xf;
		switch(vval)
		{
			case 0: //VOLL (Volume L)
				Voices[voice].Volume.Left.Mode = 0;
				Voices[voice].Volume.Left.RegSet( value << 1 );
				Voices[voice].Volume.Left.Reg_VOL = value;
			break;

			case 1: //VOLR (Volume R)
				Voices[voice].Volume.Right.Mode = 0;
				Voices[voice].Volume.Right.RegSet( value << 1 );
				Voices[voice].Volume.Right.Reg_VOL = value;
			break;

			case 2:	Voices[voice].Pitch = value; break;
			case 3:	Voices[voice].StartA = (u32)value<<8; break;

			case 4: // ADSR1 (Envelope)
				Voices[voice].ADSR.regADSR1 = value;
			break;

			case 5: // ADSR2 (Envelope)
				Voices[voice].ADSR.regADSR2 = value;
			break;

			case 6:
				Voices[voice].ADSR.Value = ((s32)value<<16) | value;
				ConLog( "* SPU2: Mysterious ADSR Volume Set to 0x%x", value );
			break;

			case 7:	Voices[voice].LoopStartA = (u32)value <<8;	break;

			jNO_DEFAULT;
		}
	}
Beispiel #11
0
void StartADMAWrite(int core,u16 *pMem, u32 sz)
{
	int size=(sz)&(~511);

	if(MsgAutoDMA()) ConLog(" * SPU2: DMA%c AutoDMA Transfer of %d bytes to %x (%02x %x %04x).\n",
		(core==0)?'4':'7',size<<1,Cores[core].TSA,Cores[core].DMABits,Cores[core].AutoDMACtrl,(~Cores[core].Regs.ATTR)&0x7fff);

	Cores[core].InputDataProgress=0;
	if((Cores[core].AutoDMACtrl&(core+1))==0)
	{
		Cores[core].TSA=0x2000+(core<<10);
		Cores[core].DMAICounter=size;
	}
	else if(size>=512)
	{
		Cores[core].InputDataLeft=size;
		if(Cores[core].AdmaInProgress==0)
		{
#ifdef PCM24_S1_INTERLEAVE
			if((core==1)&&((PlayMode&8)==8))
			{
				AutoDMAReadBuffer(core,1);
			}
			else
			{
				AutoDMAReadBuffer(core,0);
			}
#else
			if(((PlayMode&4)==4)&&(core==0))
				Cores[0].InputPos=0;

			AutoDMAReadBuffer(core,0);
#endif

			if(size==512)
				Cores[core].DMAICounter=size;
		}

		Cores[core].AdmaInProgress=1;
	}
	else
	{
		Cores[core].InputDataLeft=0;
		Cores[core].DMAICounter=1;
	}
	Cores[core].TADR=Cores[core].MADR+(size<<1);
}
Beispiel #12
0
void V_Core::DoDMAwrite(u16* pMem, u32 size)
{
#ifndef ENABLE_NEW_IOPDMA_SPU2
	DMAPtr = pMem;

	if(size<2) {
		//if(dma7callback) dma7callback();
		Regs.STATX &= ~0x80;
		//Regs.ATTR |= 0x30;
		DMAICounter=1;

		return;
	}

	if( IsDevBuild )
	{
		DebugCores[Index].lastsize = size;
		DebugCores[Index].dmaFlag = 2;
	}

	TSA &= ~7;

	bool adma_enable = ((AutoDMACtrl&(Index+1))==(Index+1));

	if(adma_enable)
	{
		TSA&=0x1fff;
		StartADMAWrite(pMem,size);
	}
	else
	{
		if(MsgDMA()) ConLog("* SPU2-X: DMA%c Transfer of %d bytes to %x (%02x %x %04x). IRQE = %d IRQA = %x \n",
			GetDmaIndexChar(),size<<1,TSA,DMABits,AutoDMACtrl,(~Regs.ATTR)&0x7fff,
			Cores[0].IRQEnable, Cores[0].IRQA);

		PlainDMAWrite(pMem,size);
	}
	Regs.STATX &= ~0x80;
	//Regs.ATTR |= 0x30;
#endif
}
Beispiel #13
0
// Returns TRUE if there is data to be output, or false if no data
// is available to be copied.
bool SndBuffer::CheckUnderrunStatus( int& nSamples, int& quietSampleCount )
{
    quietSampleCount = 0;

    int data = _GetApproximateDataInBuffer();
    if( m_underrun_freeze )
    {
        int toFill = m_size / ( (SynchMode == 2) ? 32 : 400); // TimeStretch and Async off?
        toFill = GetAlignedBufferSize( toFill );

        // toFill is now aligned to a SndOutPacket

        if( data < toFill )
        {
            quietSampleCount = nSamples;
            return false;
        }

        m_underrun_freeze = false;
        if( MsgOverruns() )
            ConLog(" * SPU2 > Underrun compensation (%d packets buffered)\n", toFill / SndOutPacketSize );
        lastPct = 0.0;		// normalize timestretcher
    }
    else if( data < nSamples )
    {
        nSamples = data;
        quietSampleCount = SndOutPacketSize - data;
        m_underrun_freeze = true;

        if( SynchMode == 0 ) // TimeStrech on
            timeStretchUnderrun();

        return nSamples != 0;
    }

    return true;
}
Beispiel #14
0
s32 V_Core::NewDmaWrite(u32* data, u32 bytesLeft, u32* bytesProcessed)
{
#ifdef ENABLE_NEW_IOPDMA_SPU2
	bool DmaStarting = !DmaStarted;
	DmaStarted = true;

	if(bytesLeft<2)
	{
		// execute interrupt code early
		NewDmaInterrupt();

		*bytesProcessed = bytesLeft;
		return 0;
	}

	if( IsDevBuild )
	{
		DebugCores[Index].lastsize = bytesLeft;
		DebugCores[Index].dmaFlag = 1;
	}

	TSA &= ~7;

	bool adma_enable = ((AutoDMACtrl&(Index+1))==(Index+1));

	if(adma_enable)
	{
		TSA&=0x1fff;

		if(MsgAutoDMA() && DmaStarting) ConLog("* SPU2-X: DMA%c AutoDMA Transfer of %d bytes to %x (%02x %x %04x).\n",
			GetDmaIndexChar(), bytesLeft<<1, TSA, DMABits, AutoDMACtrl, (~Regs.ATTR)&0x7fff);

		u32 processed = 0;
		while((AutoDmaFree>0)&&(bytesLeft>=0x400))
		{
			// copy block

			LogAutoDMA( Index ? ADMA7LogFile : ADMA4LogFile );

			// HACKFIX!! DMAPtr can be invalid after a savestate load, so the savestate just forces it
			// to NULL and we ignore it here.  (used to work in old VM editions of PCSX2 with fixed
			// addressing, but new PCSX2s have dynamic memory addressing).

			s16* mptr = (s16*)data;

			if(false)//(mode)
			{
				//memcpy((ADMATempBuffer+(InputPosWrite<<1)),mptr,0x400);
				memcpy(GetMemPtr(0x2000+(Index<<10)+InputPosWrite),mptr,0x400);
				mptr+=0x200;

				// Flag interrupt?  If IRQA occurs between start and dest, flag it.
				// Important: Test both core IRQ settings for either DMA!

				u32 dummyTSA = 0x2000+(Index<<10)+InputPosWrite;
				u32 dummyTDA = 0x2000+(Index<<10)+InputPosWrite+0x200;

				for( int i=0; i<2; i++ )
				{
					if( Cores[i].IRQEnable && (Cores[i].IRQA >= dummyTSA) && (Cores[i].IRQA < dummyTDA) )
					{
						SetIrqCall(i);
					}
				}
			}
			else
			{
				//memcpy((ADMATempBuffer+InputPosWrite),mptr,0x200);
				memcpy(GetMemPtr(0x2000+(Index<<10)+InputPosWrite),mptr,0x200);
				mptr+=0x100;

				// Flag interrupt?  If IRQA occurs between start and dest, flag it.
				// Important: Test both core IRQ settings for either DMA!

				u32 dummyTSA = 0x2000+(Index<<10)+InputPosWrite;
				u32 dummyTDA = 0x2000+(Index<<10)+InputPosWrite+0x100;

				for( int i=0; i<2; i++ )
				{
					if( Cores[i].IRQEnable && (Cores[i].IRQA >= dummyTSA) && (Cores[i].IRQA < dummyTDA) )
					{
						SetIrqCall(i);
					}
				}

				//memcpy((ADMATempBuffer+InputPosWrite+0x200),mptr,0x200);
				memcpy(GetMemPtr(0x2200+(Index<<10)+InputPosWrite),mptr,0x200);
				mptr+=0x100;

				// Flag interrupt?  If IRQA occurs between start and dest, flag it.
				// Important: Test both core IRQ settings for either DMA!

				dummyTSA = 0x2200+(Index<<10)+InputPosWrite;
				dummyTDA = 0x2200+(Index<<10)+InputPosWrite+0x100;

				for( int i=0; i<2; i++ )
				{
					if( Cores[i].IRQEnable && (Cores[i].IRQA >= dummyTSA) && (Cores[i].IRQA < dummyTDA) )
					{
						SetIrqCall(i);
					}
				}

			}
			// See ReadInput at mixer.cpp for explanation on the commented out lines
			//

			InputPosWrite = (InputPosWrite + 0x100) & 0x1ff;
			AutoDmaFree -= 0x200;
			processed += 0x400;
			bytesLeft -= 0x400;
		}

		if(processed==0)
		{
			*bytesProcessed = 0;
			return 768*15; // pause a bit
		}
		else
		{
			*bytesProcessed = processed;
			return 0; // auto pause
		}
	}
	else
	{
		if(MsgDMA() && DmaStarting) ConLog("* SPU2-X: DMA%c Transfer of %d bytes to %x (%02x %x %04x).\n",
			GetDmaIndexChar(),bytesLeft,TSA,DMABits,AutoDMACtrl,(~Regs.ATTR)&0x7fff);

		if(bytesLeft> 2048)
			bytesLeft = 2048;

		// TODO: Sliced transfers?
		PlainDMAWrite((u16*)data,bytesLeft/2);
	}
	Regs.STATX &= ~0x80;
	Regs.STATX |= 0x400;

#endif
	*bytesProcessed = bytesLeft;
	return 0;
}
Beispiel #15
0
//actual stretch algorithm implementation
void SndBuffer::UpdateTempoChangeSoundTouch2()
{

    long targetSamplesReservoir = 48 * SndOutLatencyMS;  //48000*SndOutLatencyMS/1000
    //base aim at buffer filled %
    float baseTargetFullness = (double)targetSamplesReservoir;  ///(double)m_size;//0.05;

    //state vars
    static bool inside_hysteresis;       //=false;
    static int hys_ok_count;             //=0;
    static float dynamicTargetFullness;  //=baseTargetFullness;
    if (gRequestStretcherReset >= STRETCHER_RESET_THRESHOLD) {
        ConLog("______> stretch: Reset.\n");
        inside_hysteresis = false;
        hys_ok_count = 0;
        dynamicTargetFullness = baseTargetFullness;
    }

    int data = _GetApproximateDataInBuffer();
    float bufferFullness = (float)data;  ///(float)m_size;

#ifdef NEWSTRETCHER_USE_DYNAMIC_TUNING
    {  //test current iterations/sec every 0.5s, and change algo params accordingly if different than previous IPS more than 30%
        static long iters = 0;
        static wxDateTime last = wxDateTime::UNow();
        wxDateTime unow = wxDateTime::UNow();
        wxTimeSpan delta = unow.Subtract(last);
        if (delta.GetMilliseconds() > 500) {
            int pot_targetIPS = 1000.0 / delta.GetMilliseconds().ToDouble() * iters;
            if (!IsInRange(pot_targetIPS, int((float)targetIPS / 1.3f), int((float)targetIPS * 1.3f))) {
                if (MsgOverruns())
                    ConLog("Stretcher: setting iters/sec from %d to %d\n", targetIPS, pot_targetIPS);
                targetIPS = pot_targetIPS;
                AVERAGING_WINDOW = GetClamped((int)(50.0f * (float)targetIPS / 750.0f), 3, (int)AVERAGING_BUFFER_SIZE);
            }
            last = unow;
            iters = 0;
        }
        iters++;
    }
#endif

    //Algorithm params: (threshold params (hysteresis), etc)
    const float hys_ok_factor = 1.04f;
    const float hys_bad_factor = 1.2f;
    int hys_min_ok_count = GetClamped((int)(50.0 * (float)targetIPS / 750.0), 2, 100);  //consecutive iterations within hys_ok before going to 1:1 mode
    int compensationDivider = GetClamped((int)(100.0 * (float)targetIPS / 750), 15, 150);

    float tempoAdjust = bufferFullness / dynamicTargetFullness;
    float avgerage = addToAvg(tempoAdjust);
    tempoAdjust = avgerage;

    // Dampen the adjustment to avoid overshoots (this means the average will compensate to the other side).
    // This is different than simply bigger averaging window since bigger window also has bigger "momentum",
    // so it's slower to slow down when it gets close to the equilibrium state and can therefore resonate.
    // The dampening (sqrt was chosen for no very good reason) manages to mostly prevent that.
    tempoAdjust = sqrt(tempoAdjust);

    tempoAdjust = GetClamped(tempoAdjust, 0.05f, 10.0f);

    if (tempoAdjust < 1)
        baseTargetFullness /= sqrt(tempoAdjust);  // slightly increase latency when running slow.

    dynamicTargetFullness += (baseTargetFullness / tempoAdjust - dynamicTargetFullness) / (double)compensationDivider;
    if (IsInRange(tempoAdjust, 0.9f, 1.1f) && IsInRange(dynamicTargetFullness, baseTargetFullness * 0.9f, baseTargetFullness * 1.1f))
        dynamicTargetFullness = baseTargetFullness;

    if (!inside_hysteresis) {
        if (IsInRange(tempoAdjust, 1.0f / hys_ok_factor, hys_ok_factor))
            hys_ok_count++;
        else
            hys_ok_count = 0;

        if (hys_ok_count >= hys_min_ok_count) {
            inside_hysteresis = true;
            if (MsgOverruns())
                ConLog("======> stretch: None (1:1)\n");
        }

    } else if (!IsInRange(tempoAdjust, 1.0f / hys_bad_factor, hys_bad_factor)) {
        if (MsgOverruns())
            ConLog("~~~~~~> stretch: Dynamic\n");
        inside_hysteresis = false;
        hys_ok_count = 0;
    }

    if (inside_hysteresis)
        tempoAdjust = 1.0;

    if (MsgOverruns()) {
        static int iters = 0;
        static wxDateTime last = wxDateTime::UNow();
        wxDateTime unow = wxDateTime::UNow();
        wxTimeSpan delta = unow.Subtract(last);

        if (delta.GetMilliseconds() > 1000) {  //report buffers state and tempo adjust every second
            ConLog("buffers: %4d ms (%3.0f%%), tempo: %f, comp: %2.3f, iters: %d, (N-IPS:%d -> avg:%d, minokc:%d, div:%d) reset:%d\n",
                   (int)(data / 48), (double)(100.0 * bufferFullness / baseTargetFullness), (double)tempoAdjust, (double)(dynamicTargetFullness / baseTargetFullness), iters, (int)targetIPS, AVERAGING_WINDOW, hys_min_ok_count, compensationDivider, gRequestStretcherReset);
            last = unow;
            iters = 0;
        }
        iters++;
    }

    pSoundTouch->setTempo(tempoAdjust);
    if (gRequestStretcherReset >= STRETCHER_RESET_THRESHOLD)
        gRequestStretcherReset = 0;

    return;
}
Beispiel #16
0
    s32 Init()
    {
        numBuffers = Config_WaveOut.NumBuffers;

        MMRESULT woores;

        if (Test())
            return -1;

// TODO : Use dsound to determine the speaker configuration, and expand audio from there.

#if 0
		int speakerConfig;

		//if( StereoExpansionEnabled )
			speakerConfig = 2;  // better not mess with this in wavout :p (rama)

		// Any windows driver should support stereo at the software level, I should think!
		pxAssume( speakerConfig > 1 );
		LPTHREAD_START_ROUTINE threadproc;

		switch( speakerConfig )
		{
		case 2:
			ConLog( "* SPU2 > Using normal 2 speaker stereo output.\n" );
			threadproc = (LPTHREAD_START_ROUTINE)&RThread<StereoOut16>;
			speakerConfig = 2;
		break;

		case 4:
			ConLog( "* SPU2 > 4 speaker expansion enabled [quadraphenia]\n" );
			threadproc = (LPTHREAD_START_ROUTINE)&RThread<StereoQuadOut16>;
			speakerConfig = 4;
		break;

		case 6:
		case 7:
			ConLog( "* SPU2 > 5.1 speaker expansion enabled.\n" );
			threadproc = (LPTHREAD_START_ROUTINE)&RThread<Stereo51Out16>;
			speakerConfig = 6;
		break;

		default:
			ConLog( "* SPU2 > 7.1 speaker expansion enabled.\n" );
			threadproc = (LPTHREAD_START_ROUTINE)&RThread<Stereo51Out16>;
			speakerConfig = 8;
		break;
		}
#endif

        wformat.wFormatTag = WAVE_FORMAT_PCM;
        wformat.nSamplesPerSec = SampleRate;
        wformat.wBitsPerSample = 16;
        wformat.nChannels = 2;
        wformat.nBlockAlign = ((wformat.wBitsPerSample * wformat.nChannels) / 8);
        wformat.nAvgBytesPerSec = (wformat.nSamplesPerSec * wformat.nBlockAlign);
        wformat.cbSize = 0;

        qbuffer = new StereoOut16[BufferSize * numBuffers];

        woores = waveOutOpen(&hwodevice, WAVE_MAPPER, &wformat, 0, 0, 0);
        if (woores != MMSYSERR_NOERROR) {
            waveOutGetErrorText(woores, (wchar_t *)&ErrText, 255);
            SysMessage("WaveOut Error: %s", ErrText);
            return -1;
        }

        const int BufferSizeBytes = wformat.nBlockAlign * BufferSize;

        for (u32 i = 0; i < numBuffers; i++) {
            whbuffer[i].dwBufferLength = BufferSizeBytes;
            whbuffer[i].dwBytesRecorded = BufferSizeBytes;
            whbuffer[i].dwFlags = 0;
            whbuffer[i].dwLoops = 0;
            whbuffer[i].dwUser = 0;
            whbuffer[i].lpData = (LPSTR)QBUFFER(i);
            whbuffer[i].lpNext = 0;
            whbuffer[i].reserved = 0;
            waveOutPrepareHeader(hwodevice, whbuffer + i, sizeof(WAVEHDR));
            whbuffer[i].dwFlags |= WHDR_DONE;  //avoid deadlock
        }

        // Start Thread
        // [Air]: The waveout code does not use wait objects, so setting a time critical
        // priority level is a bad idea.  Standard priority will do fine.  The buffer will get the
        // love it needs and won't suck resources idling pointlessly.  Just don't try to
        // run it in uber-low-latency mode.
        waveout_running = true;
        thread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)RThread<StereoOut16>, this, 0, &tid);

        return 0;
    }
Beispiel #17
0
void DoDMAWrite(int core,u16 *pMem,u32 size)
{
	// Perform an alignment check.
	// Not really important.  Everything should work regardless,
	// but it could be indicative of an emulation foopah elsewhere.

#if 0
	uptr pa = ((uptr)pMem)&7;
	uptr pm = Cores[core].TSA&0x7;

	if( pa )
	{
		fprintf(stderr, "* SPU2 DMA Write > Missaligned SOURCE! Core: %d  TSA: 0x%x  TDA: 0x%x  Size: 0x%x\n", core, Cores[core].TSA, Cores[core].TDA, size);
	}

	if( pm )
	{
		fprintf(stderr, "* SPU2 DMA Write > Missaligned TARGET! Core: %d  TSA: 0x%x  TDA: 0x%x Size: 0x%x\n", core, Cores[core].TSA, Cores[core].TDA, size );
	}
#endif

	if(core==0)
		DMA4LogWrite(pMem,size<<1);
	else
		DMA7LogWrite(pMem,size<<1);

	if(MsgDMA()) ConLog(" * SPU2: DMA%c Transfer of %d bytes to %x (%02x %x %04x).\n",(core==0)?'4':'7',size<<1,Cores[core].TSA,Cores[core].DMABits,Cores[core].AutoDMACtrl,(~Cores[core].Regs.ATTR)&0x7fff);

	Cores[core].TSA &= 0xfffff;

	u32 buff1end = Cores[core].TSA + size;
	u32 buff2end=0;
	if( buff1end > 0x100000 )
	{
		buff2end = buff1end - 0x100000;
		buff1end = 0x100000;
	}

	const int cacheIdxStart = Cores[core].TSA / pcm_WordsPerBlock;
	const int cacheIdxEnd = (buff1end+pcm_WordsPerBlock-1) / pcm_WordsPerBlock;
	PcmCacheEntry* cacheLine = &pcm_cache_data[cacheIdxStart];
	PcmCacheEntry& cacheEnd = pcm_cache_data[cacheIdxEnd];

	do 
	{
		cacheLine->Validated = false;
		cacheLine++;
	} while ( cacheLine != &cacheEnd );

#if 0
	// Pcm Cache Invalidation!
	// It's a requirement that we mask bits for the blocks that are written to *only*,
	// because doing anything else can cause the cache to fail, thanks to the progressive
	// nature of the SPU2's ADPCM encoding.  (the same thing that makes it impossible
	// to use SSE optimizations on it).

	u8* cache = (u8*)pcm_cache_flags;

	// Step 1: Clear bits in the front remainder.

	const int pcmTSA = Cores[core].TSA / pcm_WordsPerBlock;
	const int pcmTDA = buff1end / pcm_WordsPerBlock;
	const int remFront = pcmTSA & 31;
	const int remBack = ((buff1end+pcm_WordsPerBlock-1)/pcm_WordsPerBlock) & 31;	// round up to get the end remainder

	int flagTSA = pcmTSA / 32;

	if( remFront )
	{
		// need to clear some upper bits of this u32
		uint mask = (1ul<<remFront)-1;
		cache[flagTSA++] &= mask;
	}

	// Step 2: Clear the middle run
	const int flagClearLen = pcmTDA-pcmTSA;
	memset( &cache[flagTSA], 0, flagClearLen );

	// Step 3: Clear bits in the end remainder.

	if( remBack )
	{
		// need to clear some lower bits in this u32
		uint mask = ~(1ul<<remBack)-1;
		cache[flagTSA + flagClearLen] &= mask;
	}
#endif

	//ConLog( " * SPU2 : Cache Clear Range!  TSA=0x%x, TDA=0x%x (low8=0x%x, high8=0x%x, len=0x%x)\n",
	//	Cores[core].TSA, buff1end, flagTSA, flagTDA, clearLen );


	// First Branch needs cleared:
	// It starts at TSA and goes to buff1end.

	const u32 buff1size = (buff1end-Cores[core].TSA);
	memcpy( GetMemPtr( Cores[core].TSA ), pMem, buff1size*2 );
	
	if( buff2end > 0 )
	{
		// second branch needs copied:
		// It starts at the beginning of memory and moves forward to buff2end

		// endpoint cache should be irrelevant, since it's almost certainly dynamic 
		// memory below 0x2800 (registers and such)
		//const u32 endpt2 = (buff2end + roundUp) / indexer_scalar;
		//memset( pcm_cache_flags, 0, endpt2 );

		memcpy( GetMemPtr( 0 ), &pMem[buff1size], buff2end*2 );

		Cores[core].TDA = (buff2end+1) & 0xfffff;

		if(Cores[core].IRQEnable)
		{
			// Flag interrupt?
			// If IRQA occurs between start and dest, flag it.
			// Since the buffer wraps, the conditional might seem odd, but it works.

			if( ( Cores[core].IRQA >= Cores[core].TSA ) ||
				( Cores[core].IRQA <= Cores[core].TDA ) )
			{
				Spdif.Info=4<<core;
				SetIrqCall();
			}
		}
	}
	else
	{
		// Buffer doesn't wrap/overflow!
		// Just set the TDA and check for an IRQ...
		
		Cores[core].TDA = buff1end;

		if(Cores[core].IRQEnable)
		{
			// Flag interrupt?
			// If IRQA occurs between start and dest, flag it:

			if( ( Cores[core].IRQA >= Cores[core].TSA ) &&
				( Cores[core].IRQA <= Cores[core].TDA ) )
			{
				Spdif.Info=4<<core;
				SetIrqCall();
			}
		}
	}

	Cores[core].TSA=Cores[core].TDA&0xFFFF0;
	Cores[core].DMAICounter=size;
	Cores[core].TADR=Cores[core].MADR+(size<<1);
}
Beispiel #18
0
void V_Core::Init( int index )
{
	ConLog( "* SPU2-X: Init SPU2 core %d \n", index );
	memset( this, 0, sizeof(V_Core) );

	const int c = Index = index;

	Regs.STATX		= 0;
	Regs.ATTR		= 0;
	ExtVol			= V_VolumeLR::Max;
	InpVol			= V_VolumeLR::Max;
	FxVol			= V_VolumeLR(0);

	MasterVol		= V_VolumeSlideLR(0,0);

	memset( &DryGate, -1, sizeof(DryGate) );
	memset( &WetGate, -1, sizeof(WetGate) );
	DryGate.ExtL = 0;
	DryGate.ExtR = 0;
	if (!c)
	{
		WetGate.ExtL = 0;
		WetGate.ExtL = 0;
	}

	Regs.MMIX		= c ? 0xFFC : 0xFF0; // PS2 confirmed (f3c and f30 after BIOS ran, ffc and ff0 after sdinit)
	Regs.VMIXL		= 0xFFFFFF;
	Regs.VMIXR		= 0xFFFFFF;
	Regs.VMIXEL		= 0xFFFFFF;
	Regs.VMIXER		= 0xFFFFFF;
	EffectsStartA	= c ? 0xFFFF8 : 0xEFFF8;
	EffectsEndA		= c ? 0xFFFFF : 0xEFFFF;
	ExtEffectsStartA = EffectsStartA;
	ExtEffectsEndA = EffectsEndA;

	FxEnable		= 0; // Uninitialized it's 0 for both cores. Resetting libs however may set this to 0 or 1.
	// These are real PS2 values, mainly constant apart from a few bits: 0x3220EAA4, 0x40505E9C.
	// These values mean nothing.  They do not reflect the actual address the SPU2 is testing,
	// it would seem that reading the IRQA register returns the last written value, not the
	// value of the internal register.  Rewriting the registers with their current values changes
	// whether interrupts fire (they do while uninitialised, but do not when rewritten).
	// The exact boot value is unknown and probably unknowable, but it seems to be somewhere
	// in the input or output areas, so we're using 0x800.
	// F1 2005 is known to rely on an uninitialised IRQA being an address which will be hit.
	IRQA			= 0x800;
	IRQEnable		= 0; // PS2 confirmed

	for( uint v=0; v<NumVoices; ++v )
	{
		VoiceGates[v].DryL = -1;
		VoiceGates[v].DryR = -1;
		VoiceGates[v].WetL = -1;
		VoiceGates[v].WetR = -1;

		Voices[v].Volume		= V_VolumeSlideLR(0,0); // V_VolumeSlideLR::Max;
		Voices[v].SCurrent		= 28;

		Voices[v].ADSR.Value	= 0;
		Voices[v].ADSR.Phase	= 0;
		Voices[v].Pitch			= 0x3FFF;
		Voices[v].NextA			= 0x2801;
		Voices[v].StartA		= 0x2800;
		Voices[v].LoopStartA	= 0x2800;
	}

	DMAICounter		= 0;
	AdmaInProgress	= 0;

	Regs.STATX		= 0x80;
	Regs.ENDX		= 0xffffff; // PS2 confirmed

	RevBuffers.NeedsUpdated = true;
	UpdateEffectsBufferSize();
}
Beispiel #19
0
void V_Core::AnalyzeReverbPreset()
{
	ConLog("Reverb Parameter Update for Core %d:\n", Index);
	ConLog("----------------------------------------------------------\n");
	
	ConLog("    IN_COEF_L, IN_COEF_R        0x%08x, 0x%08x\n", Revb.IN_COEF_L, Revb.IN_COEF_R);
	ConLog("    FB_SRC_A, FB_SRC_B          0x%08x, 0x%08x\n", Revb.FB_SRC_A, Revb.FB_SRC_B);
	ConLog("    FB_ALPHA, FB_X              0x%08x, 0x%08x\n", Revb.FB_ALPHA, Revb.FB_X);
	
	ConLog("    ACC_COEF_A                  0x%08x\n", Revb.ACC_COEF_A);
	ConLog("    ACC_COEF_B                  0x%08x\n", Revb.ACC_COEF_B);
	ConLog("    ACC_COEF_C                  0x%08x\n", Revb.ACC_COEF_C);
	ConLog("    ACC_COEF_D                  0x%08x\n", Revb.ACC_COEF_D);
	
	ConLog("    ACC_SRC_A0, ACC_SRC_A1      0x%08x, 0x%08x\n", Revb.ACC_SRC_A0, Revb.ACC_SRC_A1);
	ConLog("    ACC_SRC_B0, ACC_SRC_B1      0x%08x, 0x%08x\n", Revb.ACC_SRC_B0, Revb.ACC_SRC_B1);
	ConLog("    ACC_SRC_C0, ACC_SRC_C1      0x%08x, 0x%08x\n", Revb.ACC_SRC_C0, Revb.ACC_SRC_C1);
	ConLog("    ACC_SRC_D0, ACC_SRC_D1      0x%08x, 0x%08x\n", Revb.ACC_SRC_D0, Revb.ACC_SRC_D1);
	
	ConLog("    IIR_SRC_A0, IIR_SRC_A1      0x%08x, 0x%08x\n", Revb.IIR_SRC_A0, Revb.IIR_SRC_A1);
	ConLog("    IIR_SRC_B0, IIR_SRC_B1      0x%08x, 0x%08x\n", Revb.IIR_SRC_B0, Revb.IIR_SRC_B1);
	ConLog("    IIR_DEST_A0, IIR_DEST_A1    0x%08x, 0x%08x\n", Revb.IIR_DEST_A0, Revb.IIR_DEST_A1);
	ConLog("    IIR_DEST_B0, IIR_DEST_B1    0x%08x, 0x%08x\n", Revb.IIR_DEST_B0, Revb.IIR_DEST_B1);	
	ConLog("    IIR_ALPHA, IIR_COEF         0x%08x, 0x%08x\n", Revb.IIR_ALPHA, Revb.IIR_COEF);

	ConLog("    MIX_DEST_A0                 0x%08x\n", Revb.MIX_DEST_A0);
	ConLog("    MIX_DEST_A1                 0x%08x\n", Revb.MIX_DEST_A1);
	ConLog("    MIX_DEST_B0                 0x%08x\n", Revb.MIX_DEST_B0);
	ConLog("    MIX_DEST_B1                 0x%08x\n", Revb.MIX_DEST_B1);
	
    ConLog("    EffectsBufferSize           0x%x\n", EffectsBufferSize);
	ConLog("----------------------------------------------------------\n");
}
Beispiel #20
0
static int luasrc_ConLog (lua_State *L) {
  ConLog(luaL_checkstring(L, 1));
  return 0;
}
Beispiel #21
0
void __fastcall TimeUpdate(u32 cClocks)
{
	u32 dClocks = cClocks-lClocks;

	// [Air]: Sanity Check
	//  If for some reason our clock value seems way off base, just mix
	//  out a little bit, skip the rest, and hope the ship "rights" itself later on.

	if( dClocks > TickInterval*SanityInterval )
	{
		ConLog( " * SPU2 > TimeUpdate Sanity Check (Tick Delta: %d) (PS2 Ticks: %d)\n", dClocks/TickInterval, cClocks/TickInterval );
		dClocks = TickInterval*SanityInterval;
		lClocks = cClocks-dClocks;
	}

	//UpdateDebugDialog();

	//Update Mixing Progress
	while(dClocks>=TickInterval)
	{
		if(has_to_call_irq)
		{
			ConLog(" * SPU2: Irq Called (%04x).\n",Spdif.Info);
			has_to_call_irq=false;
			if(_irqcallback) _irqcallback();
		}

		if(Cores[0].InitDelay>0)
		{
			Cores[0].InitDelay--;
			if(Cores[0].InitDelay==0)
			{
				Cores[0].Reset();
			}
		}

		if(Cores[1].InitDelay>0)
		{
			Cores[1].InitDelay--;
			if(Cores[1].InitDelay==0)
			{
				Cores[1].Reset();
			}
		}

		//Update DMA4 interrupt delay counter
		if(Cores[0].DMAICounter>0) 
		{
			Cores[0].DMAICounter-=TickInterval;
			if(Cores[0].DMAICounter<=0)
			{
				Cores[0].MADR=Cores[0].TADR;
				Cores[0].DMAICounter=0;
				if(dma4callback) dma4callback();
			}
			else {
				Cores[0].MADR+=TickInterval<<1;
			}
		}

		//Update DMA7 interrupt delay counter
		if(Cores[1].DMAICounter>0) 
		{
			Cores[1].DMAICounter-=TickInterval;
			if(Cores[1].DMAICounter<=0)
			{
				Cores[1].MADR=Cores[1].TADR;
				Cores[1].DMAICounter=0;
				//ConLog( "* SPU2 > DMA 7 Callback!  %d\n", Cycles );
				if(dma7callback) dma7callback();
			}
			else {
				Cores[1].MADR+=TickInterval<<1;
			}
		}

		dClocks-=TickInterval;
		lClocks+=TickInterval;
		Cycles++;

		Mix();
	}
}
Beispiel #22
0
__forceinline void TimeUpdate(u32 cClocks)
{
	u32 dClocks = cClocks - lClocks;

	// Sanity Checks:
	//  It's not totally uncommon for the IOP's clock to jump backwards a cycle or two, and in
	//  such cases we just want to ignore the TimeUpdate call.

	if( dClocks > (u32)-15 ) return;

	//  But if for some reason our clock value seems way off base (typically due to bad dma
	//  timings from PCSX2), just mix out a little bit, skip the rest, and hope the ship
	//  "rights" itself later on.

	if( dClocks > (u32)(TickInterval*SanityInterval) )
	{
		if(MsgToConsole()) ConLog( " * SPU2 > TimeUpdate Sanity Check (Tick Delta: %d) (PS2 Ticks: %d)\n", dClocks/TickInterval, cClocks/TickInterval );
		dClocks = TickInterval * SanityInterval;
		lClocks = cClocks - dClocks;
	}

	// Visual debug display showing all core's activity! Disabled via #define on release builds.
#ifdef __WIN32__
	UpdateDebugDialog();
#endif

	if( SynchMode == 1 ) // AsyncMix on
		SndBuffer::UpdateTempoChangeAsyncMixing();
	else TickInterval = 768; // Reset to default, in case the user hotswitched from async to something else.

	//Update Mixing Progress
	while(dClocks>=TickInterval)
	{
		if(has_to_call_irq)
		{
			//ConLog("* SPU2-X: Irq Called (%04x) at cycle %d.\n", Spdif.Info, Cycles);
			has_to_call_irq=false;
			if(_irqcallback) _irqcallback();
		}

#ifndef ENABLE_NEW_IOPDMA_SPU2
		//Update DMA4 interrupt delay counter
		if(Cores[0].DMAICounter>0)
		{
			Cores[0].DMAICounter-=TickInterval;
			if(Cores[0].DMAICounter<=0)
			{
				Cores[0].MADR=Cores[0].TADR;
				Cores[0].DMAICounter=0;
				if(dma4callback) dma4callback();
			}
			else {
				Cores[0].MADR+=TickInterval<<1;
			}
		}

		//Update DMA7 interrupt delay counter
		if(Cores[1].DMAICounter>0)
		{
			Cores[1].DMAICounter-=TickInterval;
			if(Cores[1].DMAICounter<=0)
			{
				Cores[1].MADR=Cores[1].TADR;
				Cores[1].DMAICounter=0;
				//ConLog( "* SPU2 > DMA 7 Callback!  %d\n", Cycles );
				if(dma7callback) dma7callback();
			}
			else {
				Cores[1].MADR+=TickInterval<<1;
			}
		}
#endif

		dClocks -= TickInterval;
		lClocks += TickInterval;
		Cycles++;

		// Note: IOP does not use MMX regs, so no need to save them.
		//SaveMMXRegs();
		Mix();
		//RestoreMMXRegs();
	}
}