__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); } }
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); }
// 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; }
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)); } }
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; } }
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); } }
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 }
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; } } } }
// 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; }
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; } }
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); }
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 }
// 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; }
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; }
//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; }
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; }
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); }
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(); }
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"); }
static int luasrc_ConLog (lua_State *L) { ConLog(luaL_checkstring(L, 1)); return 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(); } }
__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(); } }