mirror of https://github.com/PCSX2/pcsx2.git
542 lines
11 KiB
C++
542 lines
11 KiB
C++
/* PCSX2 - PS2 Emulator for PCs
|
|
* Copyright (C) 2002-2021 PCSX2 Dev Team
|
|
*
|
|
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
|
|
* of the GNU Lesser General Public License as published by the Free Software Found-
|
|
* ation, either version 3 of the License, or (at your option) any later version.
|
|
*
|
|
* PCSX2 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 PCSX2.
|
|
* If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "PrecompiledHeader.h"
|
|
#include "Global.h"
|
|
#include "spu2.h"
|
|
#include "Dma.h"
|
|
#ifndef PCSX2_CORE
|
|
#if defined(_WIN32)
|
|
#include "Windows/Dialogs.h"
|
|
#else // BSD, Macos
|
|
#include "Linux/Dialogs.h"
|
|
#include "Linux/Config.h"
|
|
#endif
|
|
#else
|
|
#include "Host/Dialogs.h"
|
|
#endif
|
|
#include "R3000A.h"
|
|
#include "common/pxStreams.h"
|
|
|
|
using namespace Threading;
|
|
|
|
MutexRecursive mtx_SPU2Status;
|
|
|
|
int SampleRate = 48000;
|
|
|
|
static bool IsOpened = false;
|
|
static bool IsInitialized = false;
|
|
|
|
u32 lClocks = 0;
|
|
|
|
#ifndef PCSX2_CORE
|
|
#include "gui/AppCoreThread.h"
|
|
|
|
void SPU2configure()
|
|
{
|
|
ScopedCoreThreadPause paused_core(SystemsMask::System_SPU2);
|
|
|
|
configure();
|
|
paused_core.AllowResume();
|
|
}
|
|
|
|
#endif
|
|
|
|
// --------------------------------------------------------------------------------------
|
|
// DMA 4/7 Callbacks from Core Emulator
|
|
// --------------------------------------------------------------------------------------
|
|
|
|
|
|
void SPU2setSettingsDir(const char* dir)
|
|
{
|
|
CfgSetSettingsDir(dir);
|
|
}
|
|
|
|
void SPU2setLogDir(const char* dir)
|
|
{
|
|
CfgSetLogDir(dir);
|
|
}
|
|
|
|
void SPU2readDMA4Mem(u16* pMem, u32 size) // size now in 16bit units
|
|
{
|
|
TimeUpdate(psxRegs.cycle);
|
|
|
|
FileLog("[%10d] SPU2 readDMA4Mem size %x\n", Cycles, size << 1);
|
|
Cores[0].DoDMAread(pMem, size);
|
|
}
|
|
|
|
void SPU2writeDMA4Mem(u16* pMem, u32 size) // size now in 16bit units
|
|
{
|
|
TimeUpdate(psxRegs.cycle);
|
|
|
|
FileLog("[%10d] SPU2 writeDMA4Mem size %x at address %x\n", Cycles, size << 1, Cores[0].TSA);
|
|
|
|
Cores[0].DoDMAwrite(pMem, size);
|
|
}
|
|
|
|
void SPU2interruptDMA4()
|
|
{
|
|
FileLog("[%10d] SPU2 interruptDMA4\n", Cycles);
|
|
if (Cores[0].DmaMode)
|
|
Cores[0].Regs.STATX |= 0x80;
|
|
Cores[0].Regs.STATX &= ~0x400;
|
|
Cores[0].TSA = Cores[0].ActiveTSA;
|
|
}
|
|
|
|
void SPU2interruptDMA7()
|
|
{
|
|
FileLog("[%10d] SPU2 interruptDMA7\n", Cycles);
|
|
if (Cores[1].DmaMode)
|
|
Cores[1].Regs.STATX |= 0x80;
|
|
Cores[1].Regs.STATX &= ~0x400;
|
|
Cores[1].TSA = Cores[1].ActiveTSA;
|
|
}
|
|
|
|
void SPU2readDMA7Mem(u16* pMem, u32 size)
|
|
{
|
|
TimeUpdate(psxRegs.cycle);
|
|
|
|
FileLog("[%10d] SPU2 readDMA7Mem size %x\n", Cycles, size << 1);
|
|
Cores[1].DoDMAread(pMem, size);
|
|
}
|
|
|
|
void SPU2writeDMA7Mem(u16* pMem, u32 size)
|
|
{
|
|
TimeUpdate(psxRegs.cycle);
|
|
|
|
FileLog("[%10d] SPU2 writeDMA7Mem size %x at address %x\n", Cycles, size << 1, Cores[1].TSA);
|
|
|
|
Cores[1].DoDMAwrite(pMem, size);
|
|
}
|
|
|
|
s32 SPU2reset(PS2Modes isRunningPSXMode)
|
|
{
|
|
int requiredSampleRate = (isRunningPSXMode == PS2Modes::PSX) ? 44100 : 48000;
|
|
|
|
if (isRunningPSXMode == PS2Modes::PS2)
|
|
{
|
|
memset(spu2regs, 0, 0x010000);
|
|
memset(_spu2mem, 0, 0x200000);
|
|
memset(_spu2mem + 0x2800, 7, 0x10); // from BIOS reversal. Locks the voices so they don't run free.
|
|
memset(_spu2mem + 0xe870, 7, 0x10); // Loop which gets left over by the BIOS, Megaman X7 relies on it being there.
|
|
|
|
Spdif.Info = 0; // Reset IRQ Status if it got set in a previously run game
|
|
|
|
Cores[0].Init(0);
|
|
Cores[1].Init(1);
|
|
}
|
|
|
|
if (SampleRate != requiredSampleRate)
|
|
{
|
|
SampleRate = requiredSampleRate;
|
|
SndBuffer::Cleanup();
|
|
try
|
|
{
|
|
SndBuffer::Init();
|
|
}
|
|
catch (std::exception& ex)
|
|
{
|
|
fprintf(stderr, "SPU2 Error: Could not initialize device, or something.\nReason: %s", ex.what());
|
|
SPU2close();
|
|
return -1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
s32 SPU2init()
|
|
{
|
|
assert(regtable[0x400] == nullptr);
|
|
|
|
if (IsInitialized)
|
|
return 0;
|
|
|
|
IsInitialized = true;
|
|
|
|
ReadSettings();
|
|
|
|
#ifdef SPU2_LOG
|
|
if (AccessLog())
|
|
{
|
|
spu2Log = OpenLog(AccessLogFileName);
|
|
setvbuf(spu2Log, nullptr, _IONBF, 0);
|
|
FileLog("SPU2init\n");
|
|
}
|
|
#endif
|
|
srand((unsigned)time(nullptr));
|
|
|
|
spu2regs = (s16*)malloc(0x010000);
|
|
_spu2mem = (s16*)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 == nullptr) || (_spu2mem == nullptr) || (pcm_cache_data == nullptr))
|
|
{
|
|
SysMessage("SPU2: Error allocating Memory\n");
|
|
return -1;
|
|
}
|
|
|
|
// Patch up a copy of regtable that directly maps "nullptrs" to SPU2 memory.
|
|
|
|
memcpy(regtable, regtable_original, sizeof(regtable));
|
|
|
|
for (uint mem = 0; mem < 0x800; mem++)
|
|
{
|
|
u16* ptr = regtable[mem >> 1];
|
|
if (!ptr)
|
|
{
|
|
regtable[mem >> 1] = &(spu2Ru16(mem));
|
|
}
|
|
}
|
|
|
|
SPU2reset(PS2Modes::PS2);
|
|
|
|
DMALogOpen();
|
|
InitADSR();
|
|
|
|
return 0;
|
|
}
|
|
|
|
#if defined(_MSC_VER) && !defined(PCSX2_CORE)
|
|
// Bit ugly to have this here instead of in RealttimeDebugger.cpp, but meh :p
|
|
extern bool debugDialogOpen;
|
|
extern HWND hDebugDialog;
|
|
|
|
static INT_PTR CALLBACK DebugProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
int wmId;
|
|
|
|
switch (uMsg)
|
|
{
|
|
case WM_PAINT:
|
|
return FALSE;
|
|
case WM_INITDIALOG:
|
|
{
|
|
debugDialogOpen = true;
|
|
}
|
|
break;
|
|
|
|
case WM_COMMAND:
|
|
wmId = LOWORD(wParam);
|
|
// Parse the menu selections:
|
|
switch (wmId)
|
|
{
|
|
case IDOK:
|
|
case IDCANCEL:
|
|
debugDialogOpen = false;
|
|
EndDialog(hWnd, 0);
|
|
break;
|
|
default:
|
|
return FALSE;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
#endif
|
|
uptr gsWindowHandle = 0;
|
|
|
|
s32 SPU2open()
|
|
{
|
|
ScopedLock lock(mtx_SPU2Status);
|
|
if (IsOpened)
|
|
return 0;
|
|
|
|
FileLog("[%10d] SPU2 Open\n", Cycles);
|
|
|
|
#if defined(_MSC_VER) && !defined(PCSX2_CORE)
|
|
#ifdef PCSX2_DEVBUILD // Define may not be needed but not tested yet. Better make sure.
|
|
if (IsDevBuild && VisualDebug())
|
|
{
|
|
if (debugDialogOpen == 0)
|
|
{
|
|
hDebugDialog = CreateDialogParam(nullptr, MAKEINTRESOURCE(IDD_DEBUG), 0, DebugProc, 0);
|
|
ShowWindow(hDebugDialog, SW_SHOWNORMAL);
|
|
debugDialogOpen = 1;
|
|
}
|
|
}
|
|
else if (debugDialogOpen)
|
|
{
|
|
DestroyWindow(hDebugDialog);
|
|
debugDialogOpen = 0;
|
|
}
|
|
#endif
|
|
#endif
|
|
|
|
IsOpened = true;
|
|
lClocks = psxRegs.cycle;
|
|
|
|
try
|
|
{
|
|
SndBuffer::Init();
|
|
|
|
#if defined(_WIN32) && !defined(PCSX2_CORE)
|
|
DspLoadLibrary(dspPlugin, dspPluginModule);
|
|
#endif
|
|
WaveDump::Open();
|
|
}
|
|
catch (std::exception& ex)
|
|
{
|
|
fprintf(stderr, "SPU2 Error: Could not initialize device, or something.\nReason: %s", ex.what());
|
|
SPU2close();
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void SPU2close()
|
|
{
|
|
ScopedLock lock(mtx_SPU2Status);
|
|
if (!IsOpened)
|
|
return;
|
|
IsOpened = false;
|
|
|
|
FileLog("[%10d] SPU2 Close\n", Cycles);
|
|
|
|
#if defined(_WIN32) && !defined(PCSX2_CORE)
|
|
DspCloseLibrary();
|
|
#endif
|
|
|
|
SndBuffer::Cleanup();
|
|
}
|
|
|
|
void SPU2shutdown()
|
|
{
|
|
if (!IsInitialized)
|
|
return;
|
|
IsInitialized = false;
|
|
|
|
ConLog("* SPU2: Shutting down.\n");
|
|
|
|
SPU2close();
|
|
|
|
DoFullDump();
|
|
#ifdef STREAM_DUMP
|
|
fclose(il0);
|
|
fclose(il1);
|
|
#endif
|
|
#ifdef EFFECTS_DUMP
|
|
fclose(el0);
|
|
fclose(el1);
|
|
#endif
|
|
WaveDump::Close();
|
|
|
|
DMALogClose();
|
|
|
|
safe_free(spu2regs);
|
|
safe_free(_spu2mem);
|
|
safe_free(pcm_cache_data);
|
|
|
|
|
|
#ifdef SPU2_LOG
|
|
if (!AccessLog())
|
|
return;
|
|
FileLog("[%10d] SPU2shutdown\n", Cycles);
|
|
if (spu2Log)
|
|
fclose(spu2Log);
|
|
#endif
|
|
}
|
|
|
|
void SPU2SetOutputPaused(bool paused)
|
|
{
|
|
SndBuffer::SetPaused(paused);
|
|
}
|
|
|
|
#ifdef DEBUG_KEYS
|
|
static u32 lastTicks;
|
|
static bool lState[6];
|
|
#endif
|
|
|
|
void SPU2async(u32 cycles)
|
|
{
|
|
DspUpdate();
|
|
|
|
TimeUpdate(psxRegs.cycle);
|
|
|
|
#ifdef DEBUG_KEYS
|
|
u32 curTicks = GetTickCount();
|
|
if ((curTicks - lastTicks) >= 50)
|
|
{
|
|
int oldI = Interpolation;
|
|
bool cState[6];
|
|
for (int i = 0; i < 6; i++)
|
|
{
|
|
cState[i] = !!(GetAsyncKeyState(VK_NUMPAD0 + i) & 0x8000);
|
|
|
|
if ((cState[i] && !lState[i]) && i != 5)
|
|
Interpolation = i;
|
|
|
|
lState[i] = cState[i];
|
|
}
|
|
|
|
if (Interpolation != oldI)
|
|
{
|
|
printf("Interpolation set to %d", Interpolation);
|
|
switch (Interpolation)
|
|
{
|
|
case 0:
|
|
printf(" - Nearest.\n");
|
|
break;
|
|
case 1:
|
|
printf(" - Linear.\n");
|
|
break;
|
|
case 2:
|
|
printf(" - Cubic.\n");
|
|
break;
|
|
case 3:
|
|
printf(" - Hermite.\n");
|
|
break;
|
|
case 4:
|
|
printf(" - Catmull-Rom.\n");
|
|
break;
|
|
case 5:
|
|
printf(" - Gaussian.\n");
|
|
break;
|
|
default:
|
|
printf(" (unknown).\n");
|
|
break;
|
|
}
|
|
}
|
|
|
|
lastTicks = curTicks;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
u16 SPU2read(u32 rmem)
|
|
{
|
|
u16 ret = 0xDEAD;
|
|
u32 core = 0, mem = rmem & 0xFFFF, omem = mem;
|
|
|
|
if (mem & 0x400)
|
|
{
|
|
omem ^= 0x400;
|
|
core = 1;
|
|
}
|
|
|
|
if (omem == 0x1f9001AC)
|
|
{
|
|
Cores[core].ActiveTSA = Cores[core].TSA;
|
|
for (int i = 0; i < 2; i++)
|
|
{
|
|
if (Cores[i].IRQEnable && (Cores[i].IRQA == Cores[core].ActiveTSA))
|
|
{
|
|
SetIrqCall(i);
|
|
}
|
|
}
|
|
ret = Cores[core].DmaRead();
|
|
}
|
|
else
|
|
{
|
|
TimeUpdate(psxRegs.cycle);
|
|
|
|
if (rmem >> 16 == 0x1f80)
|
|
{
|
|
ret = Cores[0].ReadRegPS1(rmem);
|
|
}
|
|
else if (mem >= 0x800)
|
|
{
|
|
ret = spu2Ru16(mem);
|
|
ConLog("* SPU2: Read from reg>=0x800: %x value %x\n", mem, ret);
|
|
}
|
|
else
|
|
{
|
|
ret = *(regtable[(mem >> 1)]);
|
|
//FileLog("[%10d] SPU2 read mem %x (core %d, register %x): %x\n",Cycles, mem, core, (omem & 0x7ff), ret);
|
|
SPU2writeLog("read", rmem, ret);
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
void SPU2write(u32 rmem, u16 value)
|
|
{
|
|
// Note: Reverb/Effects are very sensitive to having precise update timings.
|
|
// If the SPU2 isn't in in sync with the IOP, samples can end up playing at rather
|
|
// incorrect pitches and loop lengths.
|
|
|
|
TimeUpdate(psxRegs.cycle);
|
|
|
|
if (rmem >> 16 == 0x1f80)
|
|
Cores[0].WriteRegPS1(rmem, value);
|
|
else
|
|
{
|
|
SPU2writeLog("write", rmem, value);
|
|
SPU2_FastWrite(rmem, value);
|
|
}
|
|
}
|
|
|
|
// returns a non zero value if successful
|
|
bool SPU2setupRecording(const std::string* filename)
|
|
{
|
|
return RecordStart(filename);
|
|
}
|
|
|
|
void SPU2endRecording()
|
|
{
|
|
if (WavRecordEnabled)
|
|
RecordStop();
|
|
}
|
|
|
|
s32 SPU2freeze(FreezeAction mode, freezeData* data)
|
|
{
|
|
pxAssume(data != nullptr);
|
|
if (!data)
|
|
{
|
|
printf("SPU2 savestate null pointer!\n");
|
|
return -1;
|
|
}
|
|
|
|
if (mode == FreezeAction::Size)
|
|
{
|
|
data->size = SPU2Savestate::SizeIt();
|
|
return 0;
|
|
}
|
|
|
|
pxAssume(mode == FreezeAction::Load || mode == FreezeAction::Save);
|
|
|
|
if (data->data == nullptr)
|
|
{
|
|
printf("SPU2 savestate null pointer!\n");
|
|
return -1;
|
|
}
|
|
|
|
auto& spud = (SPU2Savestate::DataBlock&)*(data->data);
|
|
|
|
switch (mode)
|
|
{
|
|
case FreezeAction::Load:
|
|
return SPU2Savestate::ThawIt(spud);
|
|
case FreezeAction::Save:
|
|
return SPU2Savestate::FreezeIt(spud);
|
|
|
|
jNO_DEFAULT;
|
|
}
|
|
|
|
// technically unreachable, but kills a warning:
|
|
return 0;
|
|
}
|