2009-02-15 05:15:39 +00:00
|
|
|
/* SPU2-X, A plugin for Emulating the Sound Processing Unit of the Playstation 2
|
|
|
|
* Developed and maintained by the Pcsx2 Development Team.
|
2009-09-13 06:13:22 +00:00
|
|
|
*
|
2009-02-15 05:15:39 +00:00
|
|
|
* Original portions from SPU2ghz are (c) 2008 by David Quintana [gigaherz]
|
|
|
|
*
|
2009-06-21 11:00:53 +00:00
|
|
|
* SPU2-X 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.
|
|
|
|
*
|
|
|
|
* SPU2-X 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 Lesser General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU Lesser General Public License
|
|
|
|
* along with SPU2-X. If not, see <http://www.gnu.org/licenses/>.
|
2009-02-15 05:15:39 +00:00
|
|
|
*/
|
|
|
|
|
2009-09-29 19:18:50 +00:00
|
|
|
// ======================================================================================
|
|
|
|
// spu2sys.cpp -- Emulation module for the SPU2 'virtual machine'
|
|
|
|
// ======================================================================================
|
|
|
|
// This module contains (most!) stuff which is directly related to SPU2 emulation.
|
|
|
|
// Contents should be cross-platform compatible whenever possible.
|
2009-02-15 05:15:39 +00:00
|
|
|
|
|
|
|
|
2009-09-29 19:18:50 +00:00
|
|
|
#include "Global.h"
|
2010-02-21 08:04:45 +00:00
|
|
|
#include "Dma.h"
|
2009-02-15 05:15:39 +00:00
|
|
|
|
2016-10-10 18:35:26 +00:00
|
|
|
#include "PS2E-spu2.h" // needed until I figure out a nice solution for irqcallback dependencies.
|
2009-02-15 05:15:39 +00:00
|
|
|
|
2016-10-10 18:35:26 +00:00
|
|
|
s16 *spu2regs = NULL;
|
|
|
|
s16 *_spu2mem = NULL;
|
2009-02-15 05:15:39 +00:00
|
|
|
|
2016-10-10 18:35:26 +00:00
|
|
|
V_CoreDebug DebugCores[2];
|
|
|
|
V_Core Cores[2];
|
|
|
|
V_SPDIF Spdif;
|
2009-02-15 05:15:39 +00:00
|
|
|
|
2016-10-10 18:35:26 +00:00
|
|
|
s16 OutPos;
|
|
|
|
s16 InputPos;
|
|
|
|
u32 Cycles;
|
2009-02-15 05:15:39 +00:00
|
|
|
|
2016-10-10 18:35:26 +00:00
|
|
|
int PlayMode;
|
2009-02-15 05:15:39 +00:00
|
|
|
|
2016-10-10 18:35:26 +00:00
|
|
|
bool has_to_call_irq = false;
|
2009-02-15 05:15:39 +00:00
|
|
|
|
2016-09-24 10:45:38 +00:00
|
|
|
bool psxmode = false;
|
|
|
|
|
2010-06-25 01:49:26 +00:00
|
|
|
void SetIrqCall(int core)
|
2009-02-15 05:15:39 +00:00
|
|
|
{
|
2016-10-10 18:35:26 +00:00
|
|
|
// reset by an irq disable/enable cycle, behaviour found by
|
|
|
|
// test programs that bizarrely only fired one interrupt
|
|
|
|
if (Spdif.Info & 4 << core)
|
|
|
|
return;
|
|
|
|
Spdif.Info |= 4 << core;
|
|
|
|
has_to_call_irq = true;
|
2009-02-15 05:15:39 +00:00
|
|
|
}
|
|
|
|
|
2016-10-10 18:35:26 +00:00
|
|
|
__forceinline s16 *GetMemPtr(u32 addr)
|
2009-02-15 05:15:39 +00:00
|
|
|
{
|
2009-06-18 08:20:19 +00:00
|
|
|
#ifndef DEBUG_FAST
|
2016-10-10 18:35:26 +00:00
|
|
|
// In case you're wondering, this assert is the reason SPU2-X
|
|
|
|
// runs so incrediously slow in Debug mode. :P
|
|
|
|
pxAssume(addr < 0x100000);
|
2009-02-15 05:15:39 +00:00
|
|
|
#endif
|
2016-10-10 18:35:26 +00:00
|
|
|
return (_spu2mem + addr);
|
2009-02-15 05:15:39 +00:00
|
|
|
}
|
|
|
|
|
2016-10-10 18:35:26 +00:00
|
|
|
__forceinline s16 spu2M_Read(u32 addr)
|
2009-02-15 05:15:39 +00:00
|
|
|
{
|
2016-10-10 18:35:26 +00:00
|
|
|
return *GetMemPtr(addr & 0xfffff);
|
2009-02-15 05:15:39 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// writes a signed value to the SPU2 ram
|
|
|
|
// Invalidates the ADPCM cache in the process.
|
2016-10-10 18:35:26 +00:00
|
|
|
__forceinline void spu2M_Write(u32 addr, s16 value)
|
2009-02-15 05:15:39 +00:00
|
|
|
{
|
2016-10-10 18:35:26 +00:00
|
|
|
// 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() && MsgCache())
|
|
|
|
ConLog("* SPU2-X: PcmCache Block Clear at 0x%x (cacheIdx=0x%x)\n", addr, cacheIdx);
|
|
|
|
}
|
|
|
|
*GetMemPtr(addr) = value;
|
2009-02-15 05:15:39 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// writes an unsigned value to the SPU2 ram
|
2016-10-10 18:35:26 +00:00
|
|
|
__forceinline void spu2M_Write(u32 addr, u16 value)
|
2009-02-15 05:15:39 +00:00
|
|
|
{
|
2016-10-10 18:35:26 +00:00
|
|
|
spu2M_Write(addr, (s16)value);
|
2009-02-15 05:15:39 +00:00
|
|
|
}
|
|
|
|
|
2016-10-10 18:35:26 +00:00
|
|
|
V_VolumeLR V_VolumeLR::Max(0x7FFFFFFF);
|
|
|
|
V_VolumeSlideLR V_VolumeSlideLR::Max(0x3FFF, 0x7FFFFFFF);
|
2009-02-18 13:36:20 +00:00
|
|
|
|
2016-10-10 18:35:26 +00:00
|
|
|
V_Core::V_Core(int coreidx)
|
|
|
|
: Index(coreidx)
|
|
|
|
//LogFile_AutoDMA( NULL )
|
2009-02-18 13:36:20 +00:00
|
|
|
{
|
2016-10-10 18:35:26 +00:00
|
|
|
/*char fname[128];
|
2009-09-29 19:18:50 +00:00
|
|
|
sprintf( fname, "logs/adma%d.raw", GetDmaIndex() );
|
|
|
|
LogFile_AutoDMA = fopen( fname, "wb" );*/
|
|
|
|
}
|
|
|
|
|
|
|
|
V_Core::~V_Core() throw()
|
|
|
|
{
|
2016-10-10 18:35:26 +00:00
|
|
|
// Can't use this yet because we dumb V_Core into savestates >_<
|
|
|
|
/*if( LogFile_AutoDMA != NULL )
|
2009-09-29 19:18:50 +00:00
|
|
|
{
|
|
|
|
fclose( LogFile_AutoDMA );
|
|
|
|
LogFile_AutoDMA = NULL;
|
|
|
|
}*/
|
2009-02-18 13:36:20 +00:00
|
|
|
}
|
|
|
|
|
2016-10-10 18:35:26 +00:00
|
|
|
void V_Core::Init(int index)
|
2010-06-27 00:44:00 +00:00
|
|
|
{
|
2016-10-10 18:35:26 +00:00
|
|
|
ConLog("* SPU2-X: Init SPU2 core %d \n", index);
|
|
|
|
memset(this, 0, sizeof(V_Core));
|
|
|
|
psxmode = false;
|
|
|
|
|
|
|
|
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.ExtR = 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();
|
2010-06-27 00:44:00 +00:00
|
|
|
}
|
|
|
|
|
2011-05-21 17:47:33 +00:00
|
|
|
void V_Core::AnalyzeReverbPreset()
|
|
|
|
{
|
2016-10-10 18:35:26 +00:00
|
|
|
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);
|
|
|
|
|
2011-05-28 15:03:49 +00:00
|
|
|
ConLog(" EffectsBufferSize 0x%x\n", EffectsBufferSize);
|
2016-10-10 18:35:26 +00:00
|
|
|
ConLog("----------------------------------------------------------\n");
|
2011-05-21 17:47:33 +00:00
|
|
|
}
|
2016-10-10 18:35:26 +00:00
|
|
|
s32 V_Core::EffectsBufferIndexer(s32 offset) const
|
2009-02-18 13:36:20 +00:00
|
|
|
{
|
2016-10-10 18:35:26 +00:00
|
|
|
// Should offsets be multipled by 4 or not? Reverse-engineering of IOP code reveals
|
|
|
|
// that it *4's all addresses before upping them to the SPU2 -- so our buffers are
|
|
|
|
// already x4'd. It doesn't really make sense that we should x4 them again, and this
|
|
|
|
// seems to work. (feedback-free in bios and DDS) --air
|
|
|
|
|
|
|
|
u32 pos = EffectsStartA + offset;
|
|
|
|
|
|
|
|
// Need to use modulus here, because games can and will drop the buffer size
|
|
|
|
// without notice, and it leads to offsets several times past the end of the buffer.
|
|
|
|
|
|
|
|
if (pos > EffectsEndA) {
|
|
|
|
pos = EffectsStartA + (offset % EffectsBufferSize);
|
|
|
|
} else if (pos < EffectsStartA) {
|
|
|
|
pos = EffectsEndA + 1 - (offset % EffectsBufferSize);
|
|
|
|
}
|
|
|
|
return pos;
|
2009-09-13 06:13:22 +00:00
|
|
|
}
|
2009-02-18 13:36:20 +00:00
|
|
|
|
|
|
|
void V_Core::UpdateFeedbackBuffersA()
|
|
|
|
{
|
2016-10-10 18:35:26 +00:00
|
|
|
RevBuffers.FB_SRC_A0 = EffectsBufferIndexer(Revb.MIX_DEST_A0 - Revb.FB_SRC_A);
|
|
|
|
RevBuffers.FB_SRC_A1 = EffectsBufferIndexer(Revb.MIX_DEST_A1 - Revb.FB_SRC_A);
|
2009-02-18 13:36:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void V_Core::UpdateFeedbackBuffersB()
|
|
|
|
{
|
2016-10-10 18:35:26 +00:00
|
|
|
RevBuffers.FB_SRC_B0 = EffectsBufferIndexer(Revb.MIX_DEST_B0 - Revb.FB_SRC_B);
|
|
|
|
RevBuffers.FB_SRC_B1 = EffectsBufferIndexer(Revb.MIX_DEST_B1 - Revb.FB_SRC_B);
|
2009-02-18 13:36:20 +00:00
|
|
|
}
|
2009-02-15 05:15:39 +00:00
|
|
|
|
|
|
|
void V_Core::UpdateEffectsBufferSize()
|
|
|
|
{
|
2016-10-10 18:35:26 +00:00
|
|
|
const s32 newbufsize = EffectsEndA - EffectsStartA + 1;
|
|
|
|
|
|
|
|
if ((newbufsize * 2) > 0x20000) // max 128kb per core
|
|
|
|
{
|
|
|
|
//printf("too big, returning\n");
|
|
|
|
//return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// bad optimization?
|
|
|
|
//if (newbufsize == EffectsBufferSize && EffectsStartA == EffectsBufferStart) return;
|
|
|
|
|
|
|
|
//printf("Rvb Area change: ESA = %x, EEA = %x, Size(dec) = %d, Size(hex) = %x FxEnable = %d\n", EffectsStartA, EffectsEndA, newbufsize * 2, newbufsize * 2, FxEnable);
|
|
|
|
|
|
|
|
RevBuffers.NeedsUpdated = false;
|
|
|
|
EffectsBufferSize = newbufsize;
|
|
|
|
EffectsBufferStart = EffectsStartA;
|
|
|
|
|
|
|
|
if (EffectsBufferSize <= 0)
|
|
|
|
return;
|
|
|
|
|
|
|
|
// debug: shows reverb parameters in console
|
|
|
|
if (MsgToConsole())
|
|
|
|
AnalyzeReverbPreset();
|
|
|
|
|
|
|
|
// Rebuild buffer indexers.
|
|
|
|
RevBuffers.ACC_SRC_A0 = EffectsBufferIndexer(Revb.ACC_SRC_A0);
|
|
|
|
RevBuffers.ACC_SRC_A1 = EffectsBufferIndexer(Revb.ACC_SRC_A1);
|
|
|
|
RevBuffers.ACC_SRC_B0 = EffectsBufferIndexer(Revb.ACC_SRC_B0);
|
|
|
|
RevBuffers.ACC_SRC_B1 = EffectsBufferIndexer(Revb.ACC_SRC_B1);
|
|
|
|
RevBuffers.ACC_SRC_C0 = EffectsBufferIndexer(Revb.ACC_SRC_C0);
|
|
|
|
RevBuffers.ACC_SRC_C1 = EffectsBufferIndexer(Revb.ACC_SRC_C1);
|
|
|
|
RevBuffers.ACC_SRC_D0 = EffectsBufferIndexer(Revb.ACC_SRC_D0);
|
|
|
|
RevBuffers.ACC_SRC_D1 = EffectsBufferIndexer(Revb.ACC_SRC_D1);
|
|
|
|
|
|
|
|
UpdateFeedbackBuffersA();
|
|
|
|
UpdateFeedbackBuffersB();
|
|
|
|
|
|
|
|
RevBuffers.IIR_DEST_A0 = EffectsBufferIndexer(Revb.IIR_DEST_A0);
|
|
|
|
RevBuffers.IIR_DEST_A1 = EffectsBufferIndexer(Revb.IIR_DEST_A1);
|
|
|
|
RevBuffers.IIR_DEST_B0 = EffectsBufferIndexer(Revb.IIR_DEST_B0);
|
|
|
|
RevBuffers.IIR_DEST_B1 = EffectsBufferIndexer(Revb.IIR_DEST_B1);
|
|
|
|
|
|
|
|
RevBuffers.IIR_SRC_A0 = EffectsBufferIndexer(Revb.IIR_SRC_A0);
|
|
|
|
RevBuffers.IIR_SRC_A1 = EffectsBufferIndexer(Revb.IIR_SRC_A1);
|
|
|
|
RevBuffers.IIR_SRC_B0 = EffectsBufferIndexer(Revb.IIR_SRC_B0);
|
|
|
|
RevBuffers.IIR_SRC_B1 = EffectsBufferIndexer(Revb.IIR_SRC_B1);
|
|
|
|
|
|
|
|
RevBuffers.MIX_DEST_A0 = EffectsBufferIndexer(Revb.MIX_DEST_A0);
|
|
|
|
RevBuffers.MIX_DEST_A1 = EffectsBufferIndexer(Revb.MIX_DEST_A1);
|
|
|
|
RevBuffers.MIX_DEST_B0 = EffectsBufferIndexer(Revb.MIX_DEST_B0);
|
|
|
|
RevBuffers.MIX_DEST_B1 = EffectsBufferIndexer(Revb.MIX_DEST_B1);
|
2009-02-15 05:15:39 +00:00
|
|
|
}
|
|
|
|
|
2012-09-21 15:17:16 +00:00
|
|
|
void V_Voice::QueueStart()
|
|
|
|
{
|
2016-10-10 18:35:26 +00:00
|
|
|
if (Cycles - PlayCycle < delayCycles) {
|
|
|
|
// Required by The Legend of Spyro: The Eternal Night (probably the other two legend games too)
|
|
|
|
ConLog(" *** KeyOn after less than %d T disregarded.\n", delayCycles);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
PlayCycle = Cycles;
|
2012-09-21 15:17:16 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
bool V_Voice::Start()
|
2009-02-16 20:00:31 +00:00
|
|
|
{
|
2016-10-10 18:35:26 +00:00
|
|
|
if ((Cycles - PlayCycle) >= delayCycles) {
|
|
|
|
if (StartA & 7) {
|
|
|
|
fprintf(stderr, " *** Misaligned StartA %05x!\n", StartA);
|
|
|
|
StartA = (StartA + 0xFFFF8) + 0x8;
|
|
|
|
}
|
|
|
|
|
|
|
|
ADSR.Releasing = false;
|
|
|
|
ADSR.Value = 1;
|
|
|
|
ADSR.Phase = 1;
|
|
|
|
SCurrent = 28;
|
|
|
|
LoopMode = 0;
|
|
|
|
LoopFlags = 0;
|
|
|
|
NextA = StartA | 1;
|
|
|
|
Prev1 = 0;
|
|
|
|
Prev2 = 0;
|
|
|
|
|
|
|
|
PV1 = PV2 = 0;
|
|
|
|
PV3 = PV4 = 0;
|
|
|
|
NextCrest = -0x8000;
|
|
|
|
return true;
|
|
|
|
} else
|
|
|
|
return false;
|
2009-02-16 20:00:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void V_Voice::Stop()
|
|
|
|
{
|
2016-10-10 18:35:26 +00:00
|
|
|
ADSR.Value = 0;
|
|
|
|
ADSR.Phase = 0;
|
2009-02-16 20:00:31 +00:00
|
|
|
}
|
|
|
|
|
2010-05-20 22:29:30 +00:00
|
|
|
uint TickInterval = 768;
|
2009-02-15 05:15:39 +00:00
|
|
|
static const int SanityInterval = 4800;
|
2010-06-17 11:06:28 +00:00
|
|
|
extern void UpdateDebugDialog();
|
2009-02-15 05:15:39 +00:00
|
|
|
|
2009-04-13 09:48:28 +00:00
|
|
|
__forceinline void TimeUpdate(u32 cClocks)
|
2009-02-15 05:15:39 +00:00
|
|
|
{
|
2016-10-10 18:35:26 +00:00
|
|
|
u32 dClocks = cClocks - lClocks;
|
2009-02-15 05:15:39 +00:00
|
|
|
|
2016-10-10 18:35:26 +00:00
|
|
|
// 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.
|
2009-10-09 15:17:53 +00:00
|
|
|
|
2016-10-10 18:35:26 +00:00
|
|
|
if (dClocks > (u32)-15)
|
|
|
|
return;
|
2010-04-25 00:31:27 +00:00
|
|
|
|
2016-10-10 18:35:26 +00:00
|
|
|
// 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.
|
2009-02-15 05:15:39 +00:00
|
|
|
|
2016-10-10 18:35:26 +00:00
|
|
|
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;
|
|
|
|
}
|
2009-02-15 05:15:39 +00:00
|
|
|
|
2016-10-10 18:35:26 +00:00
|
|
|
// Visual debug display showing all core's activity! Disabled via #define on release builds.
|
2016-01-27 22:49:14 +00:00
|
|
|
#ifdef _WIN32
|
2016-10-10 18:35:26 +00:00
|
|
|
UpdateDebugDialog();
|
2011-01-16 12:35:25 +00:00
|
|
|
#endif
|
2010-06-17 11:06:28 +00:00
|
|
|
|
2016-10-10 18:35:26 +00:00
|
|
|
if (SynchMode == 1) // AsyncMix on
|
|
|
|
SndBuffer::UpdateTempoChangeAsyncMixing();
|
|
|
|
else
|
|
|
|
TickInterval = 768; // Reset to default, in case the user hotswitched from async to something else.
|
2010-04-12 01:44:59 +00:00
|
|
|
|
2016-10-10 18:35:26 +00:00
|
|
|
//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();
|
|
|
|
}
|
2009-02-15 05:15:39 +00:00
|
|
|
|
2010-02-03 03:37:55 +00:00
|
|
|
#ifndef ENABLE_NEW_IOPDMA_SPU2
|
2016-10-10 18:35:26 +00:00
|
|
|
//Update DMA4 interrupt delay counter
|
|
|
|
if (Cores[0].DMAICounter > 0) {
|
|
|
|
Cores[0].DMAICounter -= TickInterval;
|
|
|
|
if (Cores[0].DMAICounter <= 0) {
|
|
|
|
//ConLog("counter set and callback!\n");
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
2010-02-03 03:37:55 +00:00
|
|
|
#endif
|
2009-02-15 05:15:39 +00:00
|
|
|
|
2016-10-10 18:35:26 +00:00
|
|
|
dClocks -= TickInterval;
|
|
|
|
lClocks += TickInterval;
|
|
|
|
Cycles++;
|
|
|
|
|
|
|
|
for (int i = 0; i < 2; i++)
|
|
|
|
if (Cores[i].KeyOn)
|
|
|
|
for (int j = 0; j < 24; j++)
|
|
|
|
if (Cores[i].KeyOn >> j & 1)
|
|
|
|
if (Cores[i].Voices[j].Start())
|
|
|
|
Cores[i].KeyOn &= ~(1 << j);
|
|
|
|
|
|
|
|
// Note: IOP does not use MMX regs, so no need to save them.
|
|
|
|
//SaveMMXRegs();
|
|
|
|
Mix();
|
|
|
|
//RestoreMMXRegs();
|
|
|
|
}
|
2009-02-15 05:15:39 +00:00
|
|
|
}
|
|
|
|
|
2009-09-30 05:35:37 +00:00
|
|
|
__forceinline void UpdateSpdifMode()
|
2009-02-15 05:15:39 +00:00
|
|
|
{
|
2016-10-10 18:35:26 +00:00
|
|
|
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);
|
|
|
|
}
|
2009-02-15 05:15:39 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Converts an SPU2 register volume write into a 32 bit SPU2-X volume. The value is extended
|
|
|
|
// properly into the lower 16 bits of the value to provide a full spectrum of volumes.
|
2016-10-10 18:35:26 +00:00
|
|
|
static s32 GetVol32(u16 src)
|
2009-02-15 05:15:39 +00:00
|
|
|
{
|
2016-10-10 18:35:26 +00:00
|
|
|
return (((s32)src) << 16) | ((src << 1) & 0xffff);
|
2009-02-15 05:15:39 +00:00
|
|
|
}
|
|
|
|
|
2016-10-10 18:35:26 +00:00
|
|
|
void V_VolumeSlide::RegSet(u16 src)
|
2009-02-18 13:36:20 +00:00
|
|
|
{
|
2016-10-10 18:35:26 +00:00
|
|
|
Value = GetVol32(src);
|
2009-02-18 13:36:20 +00:00
|
|
|
}
|
|
|
|
|
2016-10-08 22:11:50 +00:00
|
|
|
static u32 map_spu1to2(u32 addr)
|
|
|
|
{
|
2016-10-10 18:35:26 +00:00
|
|
|
return addr * 4 + (addr >= 0x200 ? 0xc0000 : 0);
|
2016-10-08 22:11:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static u32 map_spu2to1(u32 addr)
|
|
|
|
{
|
2016-10-10 18:35:26 +00:00
|
|
|
// if (addr >= 0x800 && addr < 0xc0000) oh dear
|
|
|
|
return (addr - (addr >= 0xc0000 ? 0xc0000 : 0)) / 4;
|
2016-10-08 22:11:50 +00:00
|
|
|
}
|
|
|
|
|
2016-10-10 18:35:26 +00:00
|
|
|
void V_Core::WriteRegPS1(u32 mem, u16 value)
|
2009-02-15 05:15:39 +00:00
|
|
|
{
|
2016-10-10 18:35:26 +00:00
|
|
|
pxAssume(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 0x0: //VOLL (Volume L)
|
|
|
|
case 0x2: //VOLR (Volume R)
|
|
|
|
{
|
|
|
|
V_VolumeSlide &thisvol = vval == 0 ? Voices[voice].Volume.Left : Voices[voice].Volume.Right;
|
|
|
|
thisvol.Reg_VOL = value;
|
|
|
|
|
|
|
|
if (value & 0x8000) // +Lin/-Lin/+Exp/-Exp
|
|
|
|
{
|
|
|
|
thisvol.Mode = (value & 0xF000) >> 12;
|
|
|
|
thisvol.Increment = (value & 0x7F);
|
|
|
|
// We're not sure slides work 100%
|
|
|
|
if (IsDevBuild)
|
|
|
|
ConLog("* SPU2: Voice uses Slides in Mode = %x, Increment = %x\n", thisvol.Mode, thisvol.Increment);
|
|
|
|
} else {
|
|
|
|
// Constant Volume mode (no slides or envelopes)
|
|
|
|
// Volumes range from 0x3fff to 0x7fff, with 0x4000 serving as
|
|
|
|
// the "sign" bit, so a simple bitwise extension will do the trick:
|
|
|
|
|
|
|
|
thisvol.RegSet(value << 1);
|
|
|
|
thisvol.Mode = 0;
|
|
|
|
thisvol.Increment = 0;
|
|
|
|
}
|
|
|
|
//ConLog("voice %x VOL%c write: %x\n", voice, vval == 0 ? 'L' : 'R', value);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case 0x4:
|
|
|
|
if (value > 0x3fff)
|
|
|
|
ConLog("* SPU2: Pitch setting too big: 0x%x\n", value);
|
|
|
|
Voices[voice].Pitch = value & 0x3fff;
|
|
|
|
//ConLog("voice %x Pitch write: %x\n", voice, Voices[voice].Pitch);
|
|
|
|
break;
|
|
|
|
case 0x6:
|
|
|
|
Voices[voice].StartA = map_spu1to2(value);
|
|
|
|
//ConLog("voice %x StartA write: %x\n", voice, Voices[voice].StartA);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0x8: // ADSR1 (Envelope)
|
|
|
|
Voices[voice].ADSR.regADSR1 = value;
|
|
|
|
//ConLog("voice %x regADSR1 write: %x\n", voice, Voices[voice].ADSR.regADSR1);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0xa: // ADSR2 (Envelope)
|
|
|
|
Voices[voice].ADSR.regADSR2 = value;
|
|
|
|
//ConLog("voice %x regADSR2 write: %x\n", voice, Voices[voice].ADSR.regADSR2);
|
|
|
|
break;
|
|
|
|
case 0xc: // Voice 0..23 ADSR Current Volume
|
|
|
|
// not commonly set by games
|
|
|
|
Voices[voice].ADSR.Value = value * 0x10001U;
|
|
|
|
ConLog("voice %x ADSR.Value write: %x\n", voice, Voices[voice].ADSR.Value);
|
|
|
|
break;
|
|
|
|
case 0xe:
|
|
|
|
Voices[voice].LoopStartA = map_spu1to2(value);
|
|
|
|
//ConLog("voice %x LoopStartA write: %x\n", voice, Voices[voice].LoopStartA);
|
|
|
|
break;
|
|
|
|
|
|
|
|
jNO_DEFAULT;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
else
|
|
|
|
switch (reg) {
|
|
|
|
case 0x1d80: // Mainvolume left
|
|
|
|
MasterVol.Left.Mode = 0;
|
|
|
|
MasterVol.Left.RegSet(value);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0x1d82: // Mainvolume right
|
|
|
|
MasterVol.Right.Mode = 0;
|
|
|
|
MasterVol.Right.RegSet(value);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0x1d84: // Reverberation depth left
|
|
|
|
FxVol.Left = GetVol32(value);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0x1d86: // Reverberation depth right
|
|
|
|
FxVol.Right = GetVol32(value);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0x1d88: // Voice ON (0-15)
|
|
|
|
SPU2_FastWrite(REG_S_KON, value);
|
|
|
|
break;
|
|
|
|
case 0x1d8a: // Voice ON (16-23)
|
|
|
|
SPU2_FastWrite(REG_S_KON + 2, value);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0x1d8c: // Voice OFF (0-15)
|
|
|
|
SPU2_FastWrite(REG_S_KOFF, value);
|
|
|
|
break;
|
|
|
|
case 0x1d8e: // Voice OFF (16-23)
|
|
|
|
SPU2_FastWrite(REG_S_KOFF + 2, value);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0x1d90: // Channel FM (pitch lfo) mode (0-15)
|
|
|
|
SPU2_FastWrite(REG_S_PMON, value);
|
|
|
|
if (value != 0)
|
|
|
|
ConLog("spu2x warning: wants to set Pitch Modulation reg1 to %x \n", value);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0x1d92: // Channel FM (pitch lfo) mode (16-23)
|
|
|
|
SPU2_FastWrite(REG_S_PMON + 2, value);
|
|
|
|
if (value != 0)
|
|
|
|
ConLog("spu2x warning: wants to set Pitch Modulation reg2 to %x \n", value);
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
case 0x1d94: // Channel Noise mode (0-15)
|
|
|
|
SPU2_FastWrite(REG_S_NON, value);
|
|
|
|
if (value != 0)
|
|
|
|
ConLog("spu2x warning: wants to set Channel Noise mode reg1 to %x\n", value);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0x1d96: // Channel Noise mode (16-23)
|
|
|
|
SPU2_FastWrite(REG_S_NON + 2, value);
|
|
|
|
if (value != 0)
|
|
|
|
ConLog("spu2x warning: wants to set Channel Noise mode reg2 to %x\n", value);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0x1d98: // 1F801D98h - Voice 0..23 Reverb mode aka Echo On (EON) (R/W)
|
|
|
|
//Regs.VMIXEL = value & 0xFFFF;
|
|
|
|
SPU2_FastWrite(REG_S_VMIXEL, value);
|
|
|
|
SPU2_FastWrite(REG_S_VMIXER, value);
|
|
|
|
//ConLog("spu2x warning: setting reverb mode reg1 to %x \n", Regs.VMIXEL);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0x1d9a: // 1F801D98h + 2 - Voice 0..23 Reverb mode aka Echo On (EON) (R/W)
|
|
|
|
//Regs.VMIXEL = value << 16;
|
|
|
|
SPU2_FastWrite(REG_S_VMIXEL + 2, value);
|
|
|
|
SPU2_FastWrite(REG_S_VMIXER + 2, value);
|
|
|
|
//ConLog("spu2x warning: setting reverb mode reg2 to %x \n", Regs.VMIXEL);
|
|
|
|
break;
|
|
|
|
|
|
|
|
// this was wrong? // edit: appears so!
|
|
|
|
//case 0x1d9c:// Channel Reverb mode (0-15)
|
|
|
|
// SPU2_FastWrite(REG_S_VMIXL,value);
|
|
|
|
// SPU2_FastWrite(REG_S_VMIXR,value);
|
|
|
|
//break;
|
|
|
|
|
|
|
|
//case 0x1d9e:// Channel Reverb mode (16-23)
|
|
|
|
// SPU2_FastWrite(REG_S_VMIXL+2,value);
|
|
|
|
// SPU2_FastWrite(REG_S_VMIXR+2,value);
|
|
|
|
//break;
|
|
|
|
case 0x1d9c: // Voice 0..15 ON/OFF (status) (ENDX) (R) // writeable but hw overrides it shortly after
|
|
|
|
//Regs.ENDX &= 0xff0000;
|
|
|
|
ConLog("spu2x warning: wants to set ENDX reg1 to %x \n", value);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0x1d9e: // // Voice 15..23 ON/OFF (status) (ENDX) (R) // writeable but hw overrides it shortly after
|
|
|
|
//Regs.ENDX &= 0xffff;
|
|
|
|
ConLog("spu2x warning: wants to set ENDX reg2 to %x \n", value);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0x1da2: // Reverb work area start
|
|
|
|
{
|
|
|
|
EffectsStartA = map_spu1to2(value);
|
|
|
|
//EffectsEndA = 0xFFFFF; // fixed EndA in psx mode
|
|
|
|
Cores[0].RevBuffers.NeedsUpdated = true;
|
|
|
|
ReverbX = 0;
|
|
|
|
} break;
|
|
|
|
|
|
|
|
case 0x1da4:
|
|
|
|
IRQA = map_spu1to2(value);
|
|
|
|
//ConLog("SPU2-X Setting IRQA to %x \n", IRQA);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0x1da6:
|
|
|
|
TSA = map_spu1to2(value);
|
|
|
|
//ConLog("SPU2-X Setting TSA to %x \n", TSA);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0x1da8: // Spu Write to Memory
|
|
|
|
//ConLog("SPU direct DMA Write. Current TSA = %x\n", TSA);
|
|
|
|
if (Cores[0].IRQEnable && (Cores[0].IRQA <= Cores[0].TSA)) {
|
|
|
|
SetIrqCall(0);
|
|
|
|
_irqcallback();
|
|
|
|
}
|
|
|
|
DmaWrite(value);
|
|
|
|
show = false;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0x1daa:
|
|
|
|
SPU2_FastWrite(REG_C_ATTR, value);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0x1dac: // 1F801DACh - Sound RAM Data Transfer Control (should be 0004h)
|
|
|
|
ConLog("SPU Sound RAM Data Transfer Control (should be 4) : value = %x \n", value);
|
|
|
|
psxSoundDataTransferControl = value;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0x1dae: // 1F801DAEh - SPU Status Register (SPUSTAT) (R)
|
|
|
|
// The SPUSTAT register should be treated read-only (writing is possible in so far that the written
|
|
|
|
// value can be read-back for a short moment, however, thereafter the hardware is overwriting that value).
|
|
|
|
//Regs.STATX = value;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0x1DB0: // 1F801DB0h 4 CD Volume Left/Right
|
|
|
|
break; // cd left?
|
|
|
|
case 0x1DB2:
|
|
|
|
break; // cd right?
|
|
|
|
case 0x1DB4: // 1F801DB4h 4 Extern Volume Left / Right
|
|
|
|
break; // Extern left?
|
|
|
|
case 0x1DB6:
|
|
|
|
break; // Extern right?
|
|
|
|
case 0x1DB8: // 1F801DB8h 4 Current Main Volume Left/Right
|
|
|
|
break; // Current left?
|
|
|
|
case 0x1DBA:
|
|
|
|
break; // Current right?
|
|
|
|
case 0x1DBC: // 1F801DBCh 4 Unknown? (R/W)
|
|
|
|
break;
|
|
|
|
case 0x1DBE:
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0x1DC0:
|
|
|
|
Revb.FB_SRC_A = value * 4;
|
|
|
|
break;
|
|
|
|
case 0x1DC2:
|
|
|
|
Revb.FB_SRC_B = value * 4;
|
|
|
|
break;
|
|
|
|
case 0x1DC4:
|
|
|
|
Revb.IIR_ALPHA = value;
|
|
|
|
break;
|
|
|
|
case 0x1DC6:
|
|
|
|
Revb.ACC_COEF_A = value;
|
|
|
|
break;
|
|
|
|
case 0x1DC8:
|
|
|
|
Revb.ACC_COEF_B = value;
|
|
|
|
break;
|
|
|
|
case 0x1DCA:
|
|
|
|
Revb.ACC_COEF_C = value;
|
|
|
|
break;
|
|
|
|
case 0x1DCC:
|
|
|
|
Revb.ACC_COEF_D = value;
|
|
|
|
break;
|
|
|
|
case 0x1DCE:
|
|
|
|
Revb.IIR_COEF = value;
|
|
|
|
break;
|
|
|
|
case 0x1DD0:
|
|
|
|
Revb.FB_ALPHA = value;
|
|
|
|
break;
|
|
|
|
case 0x1DD2:
|
|
|
|
Revb.FB_X = value;
|
|
|
|
break;
|
|
|
|
case 0x1DD4:
|
|
|
|
Revb.IIR_DEST_A0 = value * 4;
|
|
|
|
break;
|
|
|
|
case 0x1DD6:
|
|
|
|
Revb.IIR_DEST_A1 = value * 4;
|
|
|
|
break;
|
|
|
|
case 0x1DD8:
|
|
|
|
Revb.ACC_SRC_A0 = value * 4;
|
|
|
|
break;
|
|
|
|
case 0x1DDA:
|
|
|
|
Revb.ACC_SRC_A1 = value * 4;
|
|
|
|
break;
|
|
|
|
case 0x1DDC:
|
|
|
|
Revb.ACC_SRC_B0 = value * 4;
|
|
|
|
break;
|
|
|
|
case 0x1DDE:
|
|
|
|
Revb.ACC_SRC_B1 = value * 4;
|
|
|
|
break;
|
|
|
|
case 0x1DE0:
|
|
|
|
Revb.IIR_SRC_A0 = value * 4;
|
|
|
|
break;
|
|
|
|
case 0x1DE2:
|
|
|
|
Revb.IIR_SRC_A1 = value * 4;
|
|
|
|
break;
|
|
|
|
case 0x1DE4:
|
|
|
|
Revb.IIR_DEST_B0 = value * 4;
|
|
|
|
break;
|
|
|
|
case 0x1DE6:
|
|
|
|
Revb.IIR_DEST_B1 = value * 4;
|
|
|
|
break;
|
|
|
|
case 0x1DE8:
|
|
|
|
Revb.ACC_SRC_C0 = value * 4;
|
|
|
|
break;
|
|
|
|
case 0x1DEA:
|
|
|
|
Revb.ACC_SRC_C1 = value * 4;
|
|
|
|
break;
|
|
|
|
case 0x1DEC:
|
|
|
|
Revb.ACC_SRC_D0 = value * 4;
|
|
|
|
break;
|
|
|
|
case 0x1DEE:
|
|
|
|
Revb.ACC_SRC_D1 = value * 4;
|
|
|
|
break;
|
|
|
|
case 0x1DF0:
|
|
|
|
Revb.IIR_SRC_B0 = value * 4;
|
|
|
|
break; // IIR_SRC_B0 and IIR_SRC_B1 supposedly swapped on SPU2
|
|
|
|
case 0x1DF2:
|
|
|
|
Revb.IIR_SRC_B1 = value * 4;
|
|
|
|
break; // but I don't believe it! (games in psxmode sound better unswapped)
|
|
|
|
case 0x1DF4:
|
|
|
|
Revb.MIX_DEST_A0 = value * 4;
|
|
|
|
break;
|
|
|
|
case 0x1DF6:
|
|
|
|
Revb.MIX_DEST_A1 = value * 4;
|
|
|
|
break;
|
|
|
|
case 0x1DF8:
|
|
|
|
Revb.MIX_DEST_B0 = value * 4;
|
|
|
|
break;
|
|
|
|
case 0x1DFA:
|
|
|
|
Revb.MIX_DEST_B1 = value * 4;
|
|
|
|
break;
|
|
|
|
case 0x1DFC:
|
|
|
|
Revb.IN_COEF_L = value;
|
|
|
|
break;
|
|
|
|
case 0x1DFE:
|
|
|
|
Revb.IN_COEF_R = value;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (show)
|
|
|
|
FileLog("[%10d] (!) SPU write mem %08x value %04x\n", Cycles, mem, value);
|
|
|
|
|
|
|
|
spu2Ru16(mem) = value;
|
2009-02-15 05:15:39 +00:00
|
|
|
}
|
|
|
|
|
2009-09-29 19:18:50 +00:00
|
|
|
u16 V_Core::ReadRegPS1(u32 mem)
|
2009-02-15 05:15:39 +00:00
|
|
|
{
|
2016-10-10 18:35:26 +00:00
|
|
|
pxAssume(Index == 0); // Valid on Core 0 only!
|
|
|
|
|
|
|
|
bool show = true;
|
|
|
|
u16 value = spu2Ru16(mem);
|
|
|
|
|
|
|
|
u32 reg = mem & 0xffff;
|
|
|
|
|
|
|
|
if ((reg >= 0x1c00) && (reg < 0x1d80)) {
|
|
|
|
//voice values
|
|
|
|
u8 voice = ((reg - 0x1c00) >> 4);
|
|
|
|
u8 vval = reg & 0xf;
|
|
|
|
switch (vval) {
|
|
|
|
case 0x0: //VOLL (Volume L)
|
|
|
|
//value=Voices[voice].VolumeL.Mode;
|
|
|
|
//value=Voices[voice].VolumeL.Value;
|
|
|
|
value = Voices[voice].Volume.Left.Reg_VOL;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0x2: //VOLR (Volume R)
|
|
|
|
//value=Voices[voice].VolumeR.Mode;
|
|
|
|
//value=Voices[voice].VolumeR.Value;
|
|
|
|
value = Voices[voice].Volume.Right.Reg_VOL;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0x4:
|
|
|
|
value = Voices[voice].Pitch;
|
|
|
|
//ConLog("voice %d read pitch result = %x\n", voice, value);
|
|
|
|
break;
|
|
|
|
case 0x6:
|
|
|
|
value = map_spu2to1(Voices[voice].StartA);
|
|
|
|
//ConLog("voice %d read StartA result = %x\n", voice, value);
|
|
|
|
break;
|
|
|
|
case 0x8:
|
|
|
|
value = Voices[voice].ADSR.regADSR1;
|
|
|
|
break;
|
|
|
|
case 0xa:
|
|
|
|
value = Voices[voice].ADSR.regADSR2;
|
|
|
|
break;
|
|
|
|
case 0xc: // Voice 0..23 ADSR Current Volume
|
|
|
|
value = Voices[voice].ADSR.Value >> 16; // no clue
|
|
|
|
//if (value != 0) ConLog("voice %d read ADSR.Value result = %x\n", voice, value);
|
|
|
|
break;
|
|
|
|
case 0xe:
|
|
|
|
value = map_spu2to1(Voices[voice].LoopStartA);
|
|
|
|
//ConLog("voice %d read LoopStartA result = %x\n", voice, value);
|
|
|
|
break;
|
|
|
|
|
|
|
|
jNO_DEFAULT;
|
|
|
|
}
|
|
|
|
} else
|
|
|
|
switch (reg) {
|
|
|
|
case 0x1d80:
|
|
|
|
value = MasterVol.Left.Value >> 16;
|
|
|
|
break;
|
|
|
|
case 0x1d82:
|
|
|
|
value = MasterVol.Right.Value >> 16;
|
|
|
|
break;
|
|
|
|
case 0x1d84:
|
|
|
|
value = FxVol.Left >> 16;
|
|
|
|
break;
|
|
|
|
case 0x1d86:
|
|
|
|
value = FxVol.Right >> 16;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0x1d88:
|
|
|
|
value = 0;
|
|
|
|
break; // Voice 0..23 Key ON(Start Attack / Decay / Sustain) (W)
|
|
|
|
case 0x1d8a:
|
|
|
|
value = 0;
|
|
|
|
break;
|
|
|
|
case 0x1d8c:
|
|
|
|
value = 0;
|
|
|
|
break; // Voice 0..23 Key OFF (Start Release) (W)
|
|
|
|
case 0x1d8e:
|
|
|
|
value = 0;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0x1d90:
|
|
|
|
value = Regs.PMON & 0xFFFF;
|
|
|
|
break; // Voice 0..23 Channel FM(pitch lfo) mode(R / W)
|
|
|
|
case 0x1d92:
|
|
|
|
value = Regs.PMON >> 16;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0x1d94:
|
|
|
|
value = Regs.NON & 0xFFFF;
|
|
|
|
break; // Voice 0..23 Channel Noise mode (R/W)
|
|
|
|
case 0x1d96:
|
|
|
|
value = Regs.NON >> 16;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0x1d98:
|
|
|
|
value = Regs.VMIXEL & 0xFFFF;
|
|
|
|
break; // Voice 0..23 Channel Reverb mode (R/W)
|
|
|
|
case 0x1d9a:
|
|
|
|
value = Regs.VMIXEL >> 16;
|
|
|
|
break;
|
|
|
|
/*case 0x1d9c: value = Regs.VMIXL&0xFFFF; break;*/ // this is wrong?
|
|
|
|
/*case 0x1d9e: value = Regs.VMIXL >> 16; break;*/
|
|
|
|
case 0x1d9c:
|
|
|
|
value = Regs.ENDX & 0xFFFF;
|
|
|
|
break; // Voice 0..23 Channel ON / OFF(status) (R) (ENDX)
|
|
|
|
case 0x1d9e:
|
|
|
|
value = Regs.ENDX >> 16;
|
|
|
|
|
|
|
|
case 0x1da2:
|
|
|
|
value = map_spu2to1(EffectsStartA);
|
|
|
|
break;
|
|
|
|
case 0x1da4:
|
|
|
|
value = map_spu2to1(IRQA);
|
|
|
|
//ConLog("SPU2-X IRQA read: 0x1da4 = %x , (IRQA = %x)\n", value, IRQA);
|
|
|
|
break;
|
|
|
|
case 0x1da6:
|
|
|
|
value = map_spu2to1(TSA);
|
|
|
|
//ConLog("SPU2-X TSA read: 0x1da6 = %x , (TSA = %x)\n", value, TSA);
|
|
|
|
break;
|
|
|
|
case 0x1da8:
|
|
|
|
value = DmaRead();
|
|
|
|
show = false;
|
|
|
|
break;
|
|
|
|
case 0x1daa:
|
|
|
|
value = Cores[0].Regs.ATTR;
|
|
|
|
//ConLog("SPU2-X ps1 reg psxSPUCNT read return value: %x\n", value);
|
|
|
|
break;
|
|
|
|
case 0x1dac: // 1F801DACh - Sound RAM Data Transfer Control (should be 0004h)
|
|
|
|
value = psxSoundDataTransferControl;
|
|
|
|
break;
|
|
|
|
case 0x1dae:
|
|
|
|
value = Cores[0].Regs.STATX;
|
|
|
|
//ConLog("SPU2-X ps1 reg REG_P_STATX read return value: %x\n", value);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (show)
|
|
|
|
FileLog("[%10d] (!) SPU read mem %08x value %04x\n", Cycles, mem, value);
|
|
|
|
return value;
|
2009-02-15 05:15:39 +00:00
|
|
|
}
|
|
|
|
|
2016-10-09 14:15:19 +00:00
|
|
|
// Ah the joys of endian-specific code! :D
|
2016-10-10 18:35:26 +00:00
|
|
|
static __forceinline void SetHiWord(u32 &src, u16 value)
|
2016-10-09 14:15:19 +00:00
|
|
|
{
|
2016-10-10 18:35:26 +00:00
|
|
|
((u16 *)&src)[1] = value;
|
2016-10-09 14:15:19 +00:00
|
|
|
}
|
|
|
|
|
2016-10-10 18:35:26 +00:00
|
|
|
static __forceinline void SetLoWord(u32 &src, u16 value)
|
2016-10-09 14:15:19 +00:00
|
|
|
{
|
2016-10-10 18:35:26 +00:00
|
|
|
((u16 *)&src)[0] = value;
|
2016-10-09 14:15:19 +00:00
|
|
|
}
|
|
|
|
|
2016-10-10 18:35:26 +00:00
|
|
|
static __forceinline u16 GetHiWord(u32 &src)
|
2016-10-09 14:15:19 +00:00
|
|
|
{
|
2016-10-10 18:35:26 +00:00
|
|
|
return ((u16 *)&src)[1];
|
2016-10-09 14:15:19 +00:00
|
|
|
}
|
|
|
|
|
2016-10-10 18:35:26 +00:00
|
|
|
static __forceinline u16 GetLoWord(u32 &src)
|
2016-10-09 14:15:19 +00:00
|
|
|
{
|
2016-10-10 18:35:26 +00:00
|
|
|
return ((u16 *)&src)[0];
|
2016-10-09 14:15:19 +00:00
|
|
|
}
|
|
|
|
|
2016-10-10 18:35:26 +00:00
|
|
|
template <int CoreIdx, int VoiceIdx, int param>
|
|
|
|
static void __fastcall RegWrite_VoiceParams(u16 value)
|
2009-02-15 05:15:39 +00:00
|
|
|
{
|
2016-10-10 18:35:26 +00:00
|
|
|
const int core = CoreIdx;
|
|
|
|
const int voice = VoiceIdx;
|
|
|
|
|
|
|
|
V_Voice &thisvoice = Cores[core].Voices[voice];
|
|
|
|
|
|
|
|
switch (param) {
|
|
|
|
case 0: //VOLL (Volume L)
|
|
|
|
case 1: //VOLR (Volume R)
|
|
|
|
{
|
|
|
|
V_VolumeSlide &thisvol = (param == 0) ? thisvoice.Volume.Left : thisvoice.Volume.Right;
|
|
|
|
thisvol.Reg_VOL = value;
|
|
|
|
|
|
|
|
if (value & 0x8000) // +Lin/-Lin/+Exp/-Exp
|
|
|
|
{
|
|
|
|
thisvol.Mode = (value & 0xF000) >> 12;
|
|
|
|
thisvol.Increment = (value & 0x7F);
|
|
|
|
// We're not sure slides work 100%
|
|
|
|
if (IsDevBuild)
|
|
|
|
ConLog("* SPU2: Voice uses Slides in Mode = %x, Increment = %x\n", thisvol.Mode, thisvol.Increment);
|
|
|
|
} else {
|
|
|
|
// Constant Volume mode (no slides or envelopes)
|
|
|
|
// Volumes range from 0x3fff to 0x7fff, with 0x4000 serving as
|
|
|
|
// the "sign" bit, so a simple bitwise extension will do the trick:
|
|
|
|
|
|
|
|
thisvol.RegSet(value << 1);
|
|
|
|
thisvol.Mode = 0;
|
|
|
|
thisvol.Increment = 0;
|
|
|
|
}
|
|
|
|
} break;
|
|
|
|
|
|
|
|
case 2:
|
|
|
|
if (value > 0x3fff)
|
|
|
|
ConLog("* SPU2: Pitch setting too big: 0x%x\n", value);
|
|
|
|
thisvoice.Pitch = value & 0x3fff;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 3: // ADSR1 (Envelope)
|
|
|
|
thisvoice.ADSR.regADSR1 = value;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 4: // ADSR2 (Envelope)
|
|
|
|
thisvoice.ADSR.regADSR2 = value;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 5:
|
|
|
|
// [Air] : Mysterious ADSR set code. Too bad none of my games ever use it.
|
|
|
|
// (as usual... )
|
|
|
|
thisvoice.ADSR.Value = (value << 16) | value;
|
|
|
|
ConLog("* SPU2: Mysterious ADSR Volume Set to 0x%x\n", value);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 6:
|
|
|
|
thisvoice.Volume.Left.RegSet(value);
|
|
|
|
break;
|
|
|
|
case 7:
|
|
|
|
thisvoice.Volume.Right.RegSet(value);
|
|
|
|
break;
|
|
|
|
|
|
|
|
jNO_DEFAULT;
|
|
|
|
}
|
2009-09-30 05:35:37 +00:00
|
|
|
}
|
2009-09-13 06:13:22 +00:00
|
|
|
|
2016-10-10 18:35:26 +00:00
|
|
|
template <int CoreIdx, int VoiceIdx, int address>
|
|
|
|
static void __fastcall RegWrite_VoiceAddr(u16 value)
|
2009-09-30 05:35:37 +00:00
|
|
|
{
|
2016-10-10 18:35:26 +00:00
|
|
|
const int core = CoreIdx;
|
|
|
|
const int voice = VoiceIdx;
|
|
|
|
|
|
|
|
V_Voice &thisvoice = Cores[core].Voices[voice];
|
|
|
|
|
|
|
|
switch (address) {
|
|
|
|
case 0: // SSA (Waveform Start Addr) (hiword, 4 bits only)
|
|
|
|
thisvoice.StartA = ((value & 0x0F) << 16) | (thisvoice.StartA & 0xFFF8);
|
|
|
|
if (IsDevBuild)
|
|
|
|
DebugCores[core].Voices[voice].lastSetStartA = thisvoice.StartA;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 1: // SSA (loword)
|
|
|
|
thisvoice.StartA = (thisvoice.StartA & 0x0F0000) | (value & 0xFFF8);
|
|
|
|
if (IsDevBuild)
|
|
|
|
DebugCores[core].Voices[voice].lastSetStartA = thisvoice.StartA;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 2:
|
|
|
|
thisvoice.LoopStartA = ((value & 0x0F) << 16) | (thisvoice.LoopStartA & 0xFFF8);
|
|
|
|
thisvoice.LoopMode = 1;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 3:
|
|
|
|
thisvoice.LoopStartA = (thisvoice.LoopStartA & 0x0F0000) | (value & 0xFFF8);
|
|
|
|
thisvoice.LoopMode = 1;
|
|
|
|
break;
|
|
|
|
|
|
|
|
// Note that there's no proof that I know of that writing to NextA is
|
|
|
|
// even allowed or handled by the SPU2 (it might be disabled or ignored,
|
|
|
|
// for example). Tests should be done to find games that write to this
|
|
|
|
// reg, and see if they're buggy or not. --air
|
|
|
|
|
|
|
|
case 4:
|
|
|
|
thisvoice.NextA = ((value & 0x0F) << 16) | (thisvoice.NextA & 0xFFF8) | 1;
|
|
|
|
thisvoice.SCurrent = 28;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 5:
|
|
|
|
thisvoice.NextA = (thisvoice.NextA & 0x0F0000) | (value & 0xFFF8) | 1;
|
|
|
|
thisvoice.SCurrent = 28;
|
|
|
|
break;
|
|
|
|
}
|
2009-09-30 05:35:37 +00:00
|
|
|
}
|
|
|
|
|
2016-10-10 18:35:26 +00:00
|
|
|
template <int CoreIdx, int cAddr>
|
|
|
|
static void __fastcall RegWrite_Core(u16 value)
|
2009-09-30 05:35:37 +00:00
|
|
|
{
|
2016-10-10 18:35:26 +00:00
|
|
|
const int omem = cAddr;
|
|
|
|
const int core = CoreIdx;
|
|
|
|
V_Core &thiscore = Cores[core];
|
|
|
|
|
|
|
|
switch (omem) {
|
|
|
|
case REG__1AC:
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
// 0x1ac / 0x5ac : direct-write to DMA address : special register (undocumented)
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
// On the GS, DMAs are actually pushed through a hardware register. Chances are the
|
|
|
|
// SPU works the same way, and "technically" *all* DMA data actually passes through
|
|
|
|
// the HW registers at 0x1ac (core0) and 0x5ac (core1). We handle normal DMAs in
|
|
|
|
// optimized block copy fashion elsewhere, but some games will write this register
|
|
|
|
// directly, so handle those here:
|
|
|
|
|
|
|
|
// Performance Note: The PS2 Bios uses this extensively right before booting games,
|
|
|
|
// causing massive slowdown if we don't shortcut it here.
|
|
|
|
|
|
|
|
for (int i = 0; i < 2; i++) {
|
|
|
|
if (Cores[i].IRQEnable && (Cores[i].IRQA == thiscore.TSA)) {
|
|
|
|
SetIrqCall(i);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
thiscore.DmaWrite(value);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case REG_C_ATTR: {
|
|
|
|
bool irqe = thiscore.IRQEnable;
|
|
|
|
int bit0 = thiscore.AttrBit0;
|
|
|
|
bool fxenable = thiscore.FxEnable;
|
|
|
|
u8 oldDmaMode = thiscore.DmaMode;
|
|
|
|
|
|
|
|
thiscore.AttrBit0 = (value >> 0) & 0x01; //1 bit
|
|
|
|
thiscore.DMABits = (value >> 1) & 0x07; //3 bits
|
|
|
|
thiscore.DmaMode = (value >> 4) & 0x03; //2 bit (not necessary, we get the direction from the iop)
|
|
|
|
thiscore.IRQEnable = (value >> 6) & 0x01; //1 bit
|
|
|
|
thiscore.FxEnable = (value >> 7) & 0x01; //1 bit
|
|
|
|
thiscore.NoiseClk = (value >> 8) & 0x3f; //6 bits
|
|
|
|
//thiscore.Mute =(value>>14) & 0x01; //1 bit
|
|
|
|
thiscore.Mute = 0;
|
|
|
|
//thiscore.CoreEnabled=(value>>15) & 0x01; //1 bit
|
|
|
|
// no clue
|
|
|
|
if (value >> 15)
|
|
|
|
thiscore.Regs.STATX = 0;
|
|
|
|
thiscore.Regs.ATTR = value & 0x7fff;
|
|
|
|
|
|
|
|
if (fxenable && !thiscore.FxEnable && (thiscore.EffectsStartA != thiscore.ExtEffectsStartA || thiscore.EffectsEndA != thiscore.ExtEffectsEndA)) {
|
|
|
|
thiscore.EffectsStartA = thiscore.ExtEffectsStartA;
|
|
|
|
thiscore.EffectsEndA = thiscore.ExtEffectsEndA;
|
|
|
|
thiscore.ReverbX = 0;
|
|
|
|
thiscore.RevBuffers.NeedsUpdated = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (oldDmaMode != thiscore.DmaMode) {
|
|
|
|
// FIXME... maybe: if this mode was cleared in the middle of a DMA, should we interrupt it?
|
|
|
|
thiscore.Regs.STATX &= ~0x400; // ready to transfer
|
|
|
|
}
|
|
|
|
|
|
|
|
if (value & 0x000E) {
|
|
|
|
if (MsgToConsole())
|
|
|
|
ConLog("* SPU2-X: Core %d ATTR unknown bits SET! value=%04x\n", core, value);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (thiscore.AttrBit0 != bit0) {
|
|
|
|
if (MsgToConsole())
|
|
|
|
ConLog("* SPU2-X: ATTR bit 0 set to %d\n", thiscore.AttrBit0);
|
|
|
|
}
|
|
|
|
if (thiscore.IRQEnable != irqe) {
|
|
|
|
//ConLog("* SPU2-X: Core%d IRQ %s at cycle %d. Current IRQA = %x Current EffectA = %x\n",
|
|
|
|
// core, ((thiscore.IRQEnable==0)?"disabled":"enabled"), Cycles, thiscore.IRQA, thiscore.EffectsStartA);
|
|
|
|
|
|
|
|
if (!thiscore.IRQEnable)
|
|
|
|
Spdif.Info &= ~(4 << thiscore.Index);
|
|
|
|
}
|
|
|
|
|
|
|
|
} break;
|
|
|
|
|
|
|
|
case REG_S_PMON:
|
|
|
|
for (int vc = 1; vc < 16; ++vc)
|
|
|
|
thiscore.Voices[vc].Modulated = (value >> vc) & 1;
|
|
|
|
SetLoWord(thiscore.Regs.PMON, value);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case (REG_S_PMON + 2):
|
|
|
|
for (int vc = 0; vc < 8; ++vc)
|
|
|
|
thiscore.Voices[vc + 16].Modulated = (value >> vc) & 1;
|
|
|
|
SetHiWord(thiscore.Regs.PMON, value);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case REG_S_NON:
|
|
|
|
for (int vc = 0; vc < 16; ++vc)
|
|
|
|
thiscore.Voices[vc].Noise = (value >> vc) & 1;
|
|
|
|
SetLoWord(thiscore.Regs.NON, value);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case (REG_S_NON + 2):
|
|
|
|
for (int vc = 0; vc < 8; ++vc)
|
|
|
|
thiscore.Voices[vc + 16].Noise = (value >> vc) & 1;
|
|
|
|
SetHiWord(thiscore.Regs.NON, value);
|
|
|
|
break;
|
2009-02-15 05:15:39 +00:00
|
|
|
|
|
|
|
// Games like to repeatedly write these regs over and over with the same value, hence
|
|
|
|
// the shortcut that skips the bitloop if the values are equal.
|
2016-10-10 18:35:26 +00:00
|
|
|
#define vx_SetSomeBits(reg_out, mask_out, hiword) \
|
|
|
|
{ \
|
|
|
|
const u32 result = thiscore.Regs.reg_out; \
|
|
|
|
if (hiword) \
|
|
|
|
SetHiWord(thiscore.Regs.reg_out, value); \
|
|
|
|
else \
|
|
|
|
SetLoWord(thiscore.Regs.reg_out, value); \
|
|
|
|
if (result == thiscore.Regs.reg_out) \
|
|
|
|
break; \
|
|
|
|
\
|
|
|
|
const uint start_bit = (hiword) ? 16 : 0; \
|
|
|
|
const uint end_bit = (hiword) ? 24 : 16; \
|
|
|
|
for (uint vc = start_bit, vx = 1; vc < end_bit; ++vc, vx <<= 1) \
|
|
|
|
thiscore.VoiceGates[vc].mask_out = (value & vx) ? -1 : 0; \
|
|
|
|
}
|
|
|
|
|
|
|
|
case REG_S_VMIXL:
|
|
|
|
vx_SetSomeBits(VMIXL, DryL, false);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case (REG_S_VMIXL + 2):
|
|
|
|
vx_SetSomeBits(VMIXL, DryL, true);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case REG_S_VMIXEL:
|
|
|
|
vx_SetSomeBits(VMIXEL, WetL, false);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case (REG_S_VMIXEL + 2):
|
|
|
|
vx_SetSomeBits(VMIXEL, WetL, true);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case REG_S_VMIXR:
|
|
|
|
vx_SetSomeBits(VMIXR, DryR, false);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case (REG_S_VMIXR + 2):
|
|
|
|
vx_SetSomeBits(VMIXR, DryR, true);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case REG_S_VMIXER:
|
|
|
|
vx_SetSomeBits(VMIXER, WetR, false);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case (REG_S_VMIXER + 2):
|
|
|
|
vx_SetSomeBits(VMIXER, WetR, true);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case REG_P_MMIX: {
|
|
|
|
// Each MMIX gate is assigned either 0 or 0xffffffff depending on the status
|
|
|
|
// of the MMIX bits. I use -1 below as a shorthand for 0xffffffff. :)
|
|
|
|
|
|
|
|
const int vx = value & ((core == 0) ? 0xFF0 : 0xFFF);
|
|
|
|
thiscore.WetGate.ExtR = (vx & 0x001) ? -1 : 0;
|
|
|
|
thiscore.WetGate.ExtL = (vx & 0x002) ? -1 : 0;
|
|
|
|
thiscore.DryGate.ExtR = (vx & 0x004) ? -1 : 0;
|
|
|
|
thiscore.DryGate.ExtL = (vx & 0x008) ? -1 : 0;
|
|
|
|
thiscore.WetGate.InpR = (vx & 0x010) ? -1 : 0;
|
|
|
|
thiscore.WetGate.InpL = (vx & 0x020) ? -1 : 0;
|
|
|
|
thiscore.DryGate.InpR = (vx & 0x040) ? -1 : 0;
|
|
|
|
thiscore.DryGate.InpL = (vx & 0x080) ? -1 : 0;
|
|
|
|
thiscore.WetGate.SndR = (vx & 0x100) ? -1 : 0;
|
|
|
|
thiscore.WetGate.SndL = (vx & 0x200) ? -1 : 0;
|
|
|
|
thiscore.DryGate.SndR = (vx & 0x400) ? -1 : 0;
|
|
|
|
thiscore.DryGate.SndL = (vx & 0x800) ? -1 : 0;
|
|
|
|
thiscore.Regs.MMIX = value;
|
|
|
|
} break;
|
|
|
|
|
|
|
|
case (REG_S_KON + 2):
|
|
|
|
StartVoices(core, ((u32)value) << 16);
|
|
|
|
spu2regs[omem >> 1 | core * 0x200] = value;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case REG_S_KON:
|
|
|
|
StartVoices(core, ((u32)value));
|
|
|
|
spu2regs[omem >> 1 | core * 0x200] = value;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case (REG_S_KOFF + 2):
|
|
|
|
StopVoices(core, ((u32)value) << 16);
|
|
|
|
spu2regs[omem >> 1 | core * 0x200] = value;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case REG_S_KOFF:
|
|
|
|
StopVoices(core, ((u32)value));
|
|
|
|
spu2regs[omem >> 1 | core * 0x200] = value;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case REG_S_ENDX:
|
|
|
|
thiscore.Regs.ENDX &= 0xff0000;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case (REG_S_ENDX + 2):
|
|
|
|
thiscore.Regs.ENDX &= 0xffff;
|
|
|
|
break;
|
|
|
|
|
|
|
|
// Reverb Start and End Address Writes!
|
|
|
|
// * These regs are only writable when Effects are *DISABLED* (FxEnable is false).
|
|
|
|
// Writes while enabled should be ignored.
|
|
|
|
// NOTE: Above is false by testing but there are references saying this, so for
|
|
|
|
// now we think that writing is allowed but the internal register doesn't reflect
|
|
|
|
// the value until effects area writing is disabled.
|
|
|
|
// * Yes, these are backwards from all the volumes -- the hiword comes FIRST (wtf!)
|
|
|
|
// * End position is a hiword only! Loword is always ffff.
|
|
|
|
// * The Reverb buffer position resets on writes to StartA. It probably resets
|
|
|
|
// on writes to End too. Docs don't say, but they're for PSX, which couldn't
|
|
|
|
// change the end address anyway.
|
|
|
|
//
|
|
|
|
case REG_A_ESA:
|
|
|
|
SetHiWord(thiscore.ExtEffectsStartA, value);
|
|
|
|
if (!thiscore.FxEnable) {
|
|
|
|
thiscore.EffectsStartA = thiscore.ExtEffectsStartA;
|
|
|
|
thiscore.ReverbX = 0;
|
|
|
|
thiscore.RevBuffers.NeedsUpdated = true;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case (REG_A_ESA + 2):
|
|
|
|
SetLoWord(thiscore.ExtEffectsStartA, value);
|
|
|
|
if (!thiscore.FxEnable) {
|
|
|
|
thiscore.EffectsStartA = thiscore.ExtEffectsStartA;
|
|
|
|
thiscore.ReverbX = 0;
|
|
|
|
thiscore.RevBuffers.NeedsUpdated = true;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case REG_A_EEA:
|
|
|
|
thiscore.ExtEffectsEndA = ((u32)value << 16) | 0xFFFF;
|
|
|
|
if (!thiscore.FxEnable) {
|
|
|
|
thiscore.EffectsEndA = thiscore.ExtEffectsEndA;
|
|
|
|
thiscore.ReverbX = 0;
|
|
|
|
thiscore.RevBuffers.NeedsUpdated = true;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case REG_S_ADMAS:
|
|
|
|
if (MsgToConsole())
|
|
|
|
ConLog("* SPU2-X: Core %d AutoDMAControl set to %d (at cycle %d)\n", core, value, Cycles);
|
|
|
|
|
|
|
|
if (psxmode)
|
|
|
|
ConLog("* SPU2-X: Writing to REG_S_ADMAS while in PSX mode! value: %x", value);
|
|
|
|
// hack for ps1driver which writes -1 (and never turns the adma off after psxlogo).
|
|
|
|
// adma isn't available in psx mode either
|
|
|
|
if (value == 32767) {
|
|
|
|
psxmode = true;
|
|
|
|
//memset(_spu2mem, 0, 0x200000);
|
|
|
|
Cores[1].FxEnable = 0;
|
|
|
|
Cores[1].EffectsStartA = 0x7FFF8; // park core1 effect area in inaccessible mem
|
|
|
|
Cores[1].EffectsEndA = 0x7FFFF;
|
|
|
|
Cores[1].ExtEffectsStartA = 0x7FFF8; // park core1 ext effect area in high mem
|
|
|
|
Cores[1].ExtEffectsStartA = 0x7FFFF;
|
|
|
|
Cores[1].ReverbX = 0;
|
|
|
|
Cores[1].RevBuffers.NeedsUpdated = true;
|
|
|
|
Cores[0].ReverbX = 0;
|
|
|
|
Cores[0].RevBuffers.NeedsUpdated = true;
|
|
|
|
for (uint v = 0; v < 24; ++v) {
|
|
|
|
Cores[1].Voices[v].Volume = V_VolumeSlideLR(0, 0); // V_VolumeSlideLR::Max;
|
|
|
|
Cores[1].Voices[v].SCurrent = 28;
|
|
|
|
|
|
|
|
Cores[1].Voices[v].ADSR.Value = 0;
|
|
|
|
Cores[1].Voices[v].ADSR.Phase = 0;
|
|
|
|
Cores[1].Voices[v].Pitch = 0x0;
|
|
|
|
Cores[1].Voices[v].NextA = 0x6FFFF;
|
|
|
|
Cores[1].Voices[v].StartA = 0x6FFFF;
|
|
|
|
Cores[1].Voices[v].LoopStartA = 0x6FFFF;
|
|
|
|
Cores[1].Voices[v].Modulated = 0;
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
thiscore.AutoDMACtrl = value;
|
|
|
|
|
|
|
|
if (value == 0) {
|
|
|
|
thiscore.AdmaInProgress = 0;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
default: {
|
|
|
|
const int addr = omem | ((core == 1) ? 0x400 : 0);
|
|
|
|
*(regtable[addr >> 1]) = value;
|
|
|
|
} break;
|
|
|
|
}
|
2009-09-30 05:35:37 +00:00
|
|
|
}
|
2009-02-15 05:15:39 +00:00
|
|
|
|
2016-10-10 18:35:26 +00:00
|
|
|
template <int CoreIdx, int addr>
|
|
|
|
static void __fastcall RegWrite_CoreExt(u16 value)
|
2009-09-30 05:35:37 +00:00
|
|
|
{
|
2016-10-10 18:35:26 +00:00
|
|
|
V_Core &thiscore = Cores[CoreIdx];
|
|
|
|
const int core = CoreIdx;
|
|
|
|
|
|
|
|
switch (addr) {
|
|
|
|
// Master Volume Address Write!
|
|
|
|
|
|
|
|
case REG_P_MVOLL:
|
|
|
|
case REG_P_MVOLR: {
|
|
|
|
V_VolumeSlide &thisvol = (addr == REG_P_MVOLL) ? thiscore.MasterVol.Left : thiscore.MasterVol.Right;
|
|
|
|
|
|
|
|
if (value & 0x8000) // +Lin/-Lin/+Exp/-Exp
|
|
|
|
{
|
|
|
|
thisvol.Mode = (value & 0xF000) >> 12;
|
|
|
|
thisvol.Increment = (value & 0x7F);
|
|
|
|
//printf("slides Mode = %x, Increment = %x\n",thisvol.Mode,thisvol.Increment);
|
|
|
|
} else {
|
|
|
|
// Constant Volume mode (no slides or envelopes)
|
|
|
|
// Volumes range from 0x3fff to 0x7fff, with 0x4000 serving as
|
|
|
|
// the "sign" bit, so a simple bitwise extension will do the trick:
|
|
|
|
|
|
|
|
thisvol.Value = GetVol32(value << 1);
|
|
|
|
thisvol.Mode = 0;
|
|
|
|
thisvol.Increment = 0;
|
|
|
|
}
|
|
|
|
thisvol.Reg_VOL = value;
|
|
|
|
} break;
|
|
|
|
|
|
|
|
case REG_P_EVOLL:
|
|
|
|
thiscore.FxVol.Left = GetVol32(value);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case REG_P_EVOLR:
|
|
|
|
thiscore.FxVol.Right = GetVol32(value);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case REG_P_AVOLL:
|
|
|
|
thiscore.ExtVol.Left = GetVol32(value);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case REG_P_AVOLR:
|
|
|
|
thiscore.ExtVol.Right = GetVol32(value);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case REG_P_BVOLL:
|
|
|
|
thiscore.InpVol.Left = GetVol32(value);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case REG_P_BVOLR:
|
|
|
|
thiscore.InpVol.Right = GetVol32(value);
|
|
|
|
break;
|
|
|
|
|
|
|
|
default: {
|
|
|
|
const int raddr = addr + ((core == 1) ? 0x28 : 0);
|
|
|
|
*(regtable[raddr >> 1]) = value;
|
|
|
|
} break;
|
|
|
|
}
|
2009-02-15 05:15:39 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2016-10-10 18:35:26 +00:00
|
|
|
template <int core, int addr>
|
|
|
|
static void __fastcall RegWrite_Reverb(u16 value)
|
2009-09-30 05:35:37 +00:00
|
|
|
{
|
2016-10-10 18:35:26 +00:00
|
|
|
// Signal to the Reverb code that the effects buffers need to be re-aligned.
|
|
|
|
// This is both simple, efficient, and safe, since we only want to re-align
|
|
|
|
// buffers after both hi and lo words have been written.
|
|
|
|
|
|
|
|
// Update: This may have been written when it wasn't yet known that games
|
|
|
|
// have to disable the Reverb Engine to change settings.
|
|
|
|
// As such we only need to update buffers and parameters when we see
|
|
|
|
// the FxEnable bit go down, then high again. (rama)
|
|
|
|
*(regtable[addr >> 1]) = value;
|
|
|
|
//Cores[core].RevBuffers.NeedsUpdated = true; // See update above
|
2009-09-30 05:35:37 +00:00
|
|
|
}
|
|
|
|
|
2016-10-10 18:35:26 +00:00
|
|
|
template <int addr>
|
|
|
|
static void __fastcall RegWrite_SPDIF(u16 value)
|
2009-09-30 05:35:37 +00:00
|
|
|
{
|
2016-10-10 18:35:26 +00:00
|
|
|
*(regtable[addr >> 1]) = value;
|
|
|
|
UpdateSpdifMode();
|
2009-09-30 05:35:37 +00:00
|
|
|
}
|
|
|
|
|
2016-10-10 18:35:26 +00:00
|
|
|
template <int addr>
|
|
|
|
static void __fastcall RegWrite_Raw(u16 value)
|
2009-09-30 05:35:37 +00:00
|
|
|
{
|
2016-10-10 18:35:26 +00:00
|
|
|
*(regtable[addr >> 1]) = value;
|
2009-09-30 05:35:37 +00:00
|
|
|
}
|
|
|
|
|
2016-10-10 18:35:26 +00:00
|
|
|
static void __fastcall RegWrite_Null(u16 value)
|
2011-06-08 18:53:55 +00:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2009-09-30 05:35:37 +00:00
|
|
|
// --------------------------------------------------------------------------------------
|
|
|
|
// Macros for tbl_reg_writes
|
|
|
|
// --------------------------------------------------------------------------------------
|
2016-10-10 18:35:26 +00:00
|
|
|
#define VoiceParamsSet(core, voice) \
|
|
|
|
RegWrite_VoiceParams<core, voice, 0>, RegWrite_VoiceParams<core, voice, 1>, \
|
|
|
|
RegWrite_VoiceParams<core, voice, 2>, RegWrite_VoiceParams<core, voice, 3>, \
|
|
|
|
RegWrite_VoiceParams<core, voice, 4>, RegWrite_VoiceParams<core, voice, 5>, \
|
|
|
|
RegWrite_VoiceParams<core, voice, 6>, RegWrite_VoiceParams<core, voice, 7>
|
2009-09-30 05:35:37 +00:00
|
|
|
|
2016-10-10 18:35:26 +00:00
|
|
|
#define VoiceParamsCore(core) \
|
|
|
|
VoiceParamsSet(core, 0), VoiceParamsSet(core, 1), VoiceParamsSet(core, 2), VoiceParamsSet(core, 3), \
|
|
|
|
VoiceParamsSet(core, 4), VoiceParamsSet(core, 5), VoiceParamsSet(core, 6), VoiceParamsSet(core, 7), \
|
|
|
|
VoiceParamsSet(core, 8), VoiceParamsSet(core, 9), VoiceParamsSet(core, 10), VoiceParamsSet(core, 11), \
|
|
|
|
VoiceParamsSet(core, 12), VoiceParamsSet(core, 13), VoiceParamsSet(core, 14), VoiceParamsSet(core, 15), \
|
|
|
|
VoiceParamsSet(core, 16), VoiceParamsSet(core, 17), VoiceParamsSet(core, 18), VoiceParamsSet(core, 19), \
|
|
|
|
VoiceParamsSet(core, 20), VoiceParamsSet(core, 21), VoiceParamsSet(core, 22), VoiceParamsSet(core, 23)
|
2009-09-30 05:35:37 +00:00
|
|
|
|
2016-10-10 18:35:26 +00:00
|
|
|
#define VoiceAddrSet(core, voice) \
|
|
|
|
RegWrite_VoiceAddr<core, voice, 0>, RegWrite_VoiceAddr<core, voice, 1>, \
|
|
|
|
RegWrite_VoiceAddr<core, voice, 2>, RegWrite_VoiceAddr<core, voice, 3>, \
|
|
|
|
RegWrite_VoiceAddr<core, voice, 4>, RegWrite_VoiceAddr<core, voice, 5>
|
2009-09-30 05:35:37 +00:00
|
|
|
|
|
|
|
|
2016-10-10 18:35:26 +00:00
|
|
|
#define CoreParamsPair(core, omem) \
|
|
|
|
RegWrite_Core<core, omem>, RegWrite_Core<core, ((omem) + 2)>
|
2009-09-30 05:35:37 +00:00
|
|
|
|
2016-10-10 18:35:26 +00:00
|
|
|
#define ReverbPair(core, mem) \
|
|
|
|
RegWrite_Reverb<core, mem>, RegWrite_Core<core, ((mem) + 2)>
|
2009-09-30 05:35:37 +00:00
|
|
|
|
|
|
|
#define REGRAW(addr) RegWrite_Raw<addr>
|
|
|
|
|
|
|
|
// --------------------------------------------------------------------------------------
|
|
|
|
// tbl_reg_writes - Register Write Function Invocation LUT
|
|
|
|
// --------------------------------------------------------------------------------------
|
|
|
|
|
2016-10-10 18:35:26 +00:00
|
|
|
typedef void __fastcall RegWriteHandler(u16 value);
|
|
|
|
static RegWriteHandler *const tbl_reg_writes[0x401] =
|
|
|
|
{
|
|
|
|
VoiceParamsCore(0), // 0x000 -> 0x180
|
|
|
|
CoreParamsPair(0, REG_S_PMON),
|
|
|
|
CoreParamsPair(0, REG_S_NON),
|
|
|
|
CoreParamsPair(0, REG_S_VMIXL),
|
|
|
|
CoreParamsPair(0, REG_S_VMIXEL),
|
|
|
|
CoreParamsPair(0, REG_S_VMIXR),
|
|
|
|
CoreParamsPair(0, REG_S_VMIXER),
|
|
|
|
|
|
|
|
RegWrite_Core<0, REG_P_MMIX>,
|
|
|
|
RegWrite_Core<0, REG_C_ATTR>,
|
|
|
|
|
|
|
|
CoreParamsPair(0, REG_A_IRQA),
|
|
|
|
CoreParamsPair(0, REG_S_KON),
|
|
|
|
CoreParamsPair(0, REG_S_KOFF),
|
|
|
|
CoreParamsPair(0, REG_A_TSA),
|
|
|
|
CoreParamsPair(0, REG__1AC),
|
|
|
|
|
|
|
|
RegWrite_Core<0, REG_S_ADMAS>,
|
|
|
|
REGRAW(0x1b2),
|
|
|
|
|
|
|
|
REGRAW(0x1b4), REGRAW(0x1b6),
|
|
|
|
REGRAW(0x1b8), REGRAW(0x1ba),
|
|
|
|
REGRAW(0x1bc), REGRAW(0x1be),
|
|
|
|
|
|
|
|
// 0x1c0!
|
|
|
|
|
|
|
|
VoiceAddrSet(0, 0), VoiceAddrSet(0, 1), VoiceAddrSet(0, 2), VoiceAddrSet(0, 3), VoiceAddrSet(0, 4), VoiceAddrSet(0, 5),
|
|
|
|
VoiceAddrSet(0, 6), VoiceAddrSet(0, 7), VoiceAddrSet(0, 8), VoiceAddrSet(0, 9), VoiceAddrSet(0, 10), VoiceAddrSet(0, 11),
|
|
|
|
VoiceAddrSet(0, 12), VoiceAddrSet(0, 13), VoiceAddrSet(0, 14), VoiceAddrSet(0, 15), VoiceAddrSet(0, 16), VoiceAddrSet(0, 17),
|
|
|
|
VoiceAddrSet(0, 18), VoiceAddrSet(0, 19), VoiceAddrSet(0, 20), VoiceAddrSet(0, 21), VoiceAddrSet(0, 22), VoiceAddrSet(0, 23),
|
|
|
|
|
|
|
|
CoreParamsPair(0, REG_A_ESA),
|
|
|
|
|
|
|
|
ReverbPair(0, R_FB_SRC_A), // 0x02E4 // Feedback Source A
|
|
|
|
ReverbPair(0, R_FB_SRC_B), // 0x02E8 // Feedback Source B
|
|
|
|
ReverbPair(0, R_IIR_DEST_A0), // 0x02EC
|
|
|
|
ReverbPair(0, R_IIR_DEST_A1), // 0x02F0
|
|
|
|
ReverbPair(0, R_ACC_SRC_A0), // 0x02F4
|
|
|
|
ReverbPair(0, R_ACC_SRC_A1), // 0x02F8
|
|
|
|
ReverbPair(0, R_ACC_SRC_B0), // 0x02FC
|
|
|
|
ReverbPair(0, R_ACC_SRC_B1), // 0x0300
|
|
|
|
ReverbPair(0, R_IIR_SRC_A0), // 0x0304
|
|
|
|
ReverbPair(0, R_IIR_SRC_A1), // 0x0308
|
|
|
|
ReverbPair(0, R_IIR_DEST_B0), // 0x030C
|
|
|
|
ReverbPair(0, R_IIR_DEST_B1), // 0x0310
|
|
|
|
ReverbPair(0, R_ACC_SRC_C0), // 0x0314
|
|
|
|
ReverbPair(0, R_ACC_SRC_C1), // 0x0318
|
|
|
|
ReverbPair(0, R_ACC_SRC_D0), // 0x031C
|
|
|
|
ReverbPair(0, R_ACC_SRC_D1), // 0x0320
|
|
|
|
ReverbPair(0, R_IIR_SRC_B0), // 0x0324
|
|
|
|
ReverbPair(0, R_IIR_SRC_B1), // 0x0328
|
|
|
|
ReverbPair(0, R_MIX_DEST_A0), // 0x032C
|
|
|
|
ReverbPair(0, R_MIX_DEST_A1), // 0x0330
|
|
|
|
ReverbPair(0, R_MIX_DEST_B0), // 0x0334
|
|
|
|
ReverbPair(0, R_MIX_DEST_B1), // 0x0338
|
|
|
|
|
|
|
|
RegWrite_Core<0, REG_A_EEA>, RegWrite_Null,
|
|
|
|
|
|
|
|
CoreParamsPair(0, REG_S_ENDX), // 0x0340 // End Point passed flag
|
|
|
|
RegWrite_Core<0, REG_P_STATX>, // 0x0344 // Status register?
|
|
|
|
|
|
|
|
//0x346 here
|
|
|
|
REGRAW(0x346),
|
|
|
|
REGRAW(0x348), REGRAW(0x34A), REGRAW(0x34C), REGRAW(0x34E),
|
|
|
|
REGRAW(0x350), REGRAW(0x352), REGRAW(0x354), REGRAW(0x356),
|
|
|
|
REGRAW(0x358), REGRAW(0x35A), REGRAW(0x35C), REGRAW(0x35E),
|
|
|
|
REGRAW(0x360), REGRAW(0x362), REGRAW(0x364), REGRAW(0x366),
|
|
|
|
REGRAW(0x368), REGRAW(0x36A), REGRAW(0x36C), REGRAW(0x36E),
|
|
|
|
REGRAW(0x370), REGRAW(0x372), REGRAW(0x374), REGRAW(0x376),
|
|
|
|
REGRAW(0x378), REGRAW(0x37A), REGRAW(0x37C), REGRAW(0x37E),
|
|
|
|
REGRAW(0x380), REGRAW(0x382), REGRAW(0x384), REGRAW(0x386),
|
|
|
|
REGRAW(0x388), REGRAW(0x38A), REGRAW(0x38C), REGRAW(0x38E),
|
|
|
|
REGRAW(0x390), REGRAW(0x392), REGRAW(0x394), REGRAW(0x396),
|
|
|
|
REGRAW(0x398), REGRAW(0x39A), REGRAW(0x39C), REGRAW(0x39E),
|
|
|
|
REGRAW(0x3A0), REGRAW(0x3A2), REGRAW(0x3A4), REGRAW(0x3A6),
|
|
|
|
REGRAW(0x3A8), REGRAW(0x3AA), REGRAW(0x3AC), REGRAW(0x3AE),
|
|
|
|
REGRAW(0x3B0), REGRAW(0x3B2), REGRAW(0x3B4), REGRAW(0x3B6),
|
|
|
|
REGRAW(0x3B8), REGRAW(0x3BA), REGRAW(0x3BC), REGRAW(0x3BE),
|
|
|
|
REGRAW(0x3C0), REGRAW(0x3C2), REGRAW(0x3C4), REGRAW(0x3C6),
|
|
|
|
REGRAW(0x3C8), REGRAW(0x3CA), REGRAW(0x3CC), REGRAW(0x3CE),
|
|
|
|
REGRAW(0x3D0), REGRAW(0x3D2), REGRAW(0x3D4), REGRAW(0x3D6),
|
|
|
|
REGRAW(0x3D8), REGRAW(0x3DA), REGRAW(0x3DC), REGRAW(0x3DE),
|
|
|
|
REGRAW(0x3E0), REGRAW(0x3E2), REGRAW(0x3E4), REGRAW(0x3E6),
|
|
|
|
REGRAW(0x3E8), REGRAW(0x3EA), REGRAW(0x3EC), REGRAW(0x3EE),
|
|
|
|
REGRAW(0x3F0), REGRAW(0x3F2), REGRAW(0x3F4), REGRAW(0x3F6),
|
|
|
|
REGRAW(0x3F8), REGRAW(0x3FA), REGRAW(0x3FC), REGRAW(0x3FE),
|
|
|
|
|
|
|
|
// AND... we reached 0x400!
|
|
|
|
// Last verse, same as the first:
|
|
|
|
|
|
|
|
VoiceParamsCore(1), // 0x000 -> 0x180
|
|
|
|
CoreParamsPair(1, REG_S_PMON),
|
|
|
|
CoreParamsPair(1, REG_S_NON),
|
|
|
|
CoreParamsPair(1, REG_S_VMIXL),
|
|
|
|
CoreParamsPair(1, REG_S_VMIXEL),
|
|
|
|
CoreParamsPair(1, REG_S_VMIXR),
|
|
|
|
CoreParamsPair(1, REG_S_VMIXER),
|
|
|
|
|
|
|
|
RegWrite_Core<1, REG_P_MMIX>,
|
|
|
|
RegWrite_Core<1, REG_C_ATTR>,
|
|
|
|
|
|
|
|
CoreParamsPair(1, REG_A_IRQA),
|
|
|
|
CoreParamsPair(1, REG_S_KON),
|
|
|
|
CoreParamsPair(1, REG_S_KOFF),
|
|
|
|
CoreParamsPair(1, REG_A_TSA),
|
|
|
|
CoreParamsPair(1, REG__1AC),
|
|
|
|
|
|
|
|
RegWrite_Core<1, REG_S_ADMAS>,
|
|
|
|
REGRAW(0x5b2),
|
|
|
|
|
|
|
|
REGRAW(0x5b4), REGRAW(0x5b6),
|
|
|
|
REGRAW(0x5b8), REGRAW(0x5ba),
|
|
|
|
REGRAW(0x5bc), REGRAW(0x5be),
|
|
|
|
|
|
|
|
// 0x1c0!
|
|
|
|
|
|
|
|
VoiceAddrSet(1, 0), VoiceAddrSet(1, 1), VoiceAddrSet(1, 2), VoiceAddrSet(1, 3), VoiceAddrSet(1, 4), VoiceAddrSet(1, 5),
|
|
|
|
VoiceAddrSet(1, 6), VoiceAddrSet(1, 7), VoiceAddrSet(1, 8), VoiceAddrSet(1, 9), VoiceAddrSet(1, 10), VoiceAddrSet(1, 11),
|
|
|
|
VoiceAddrSet(1, 12), VoiceAddrSet(1, 13), VoiceAddrSet(1, 14), VoiceAddrSet(1, 15), VoiceAddrSet(1, 16), VoiceAddrSet(1, 17),
|
|
|
|
VoiceAddrSet(1, 18), VoiceAddrSet(1, 19), VoiceAddrSet(1, 20), VoiceAddrSet(1, 21), VoiceAddrSet(1, 22), VoiceAddrSet(1, 23),
|
|
|
|
|
|
|
|
CoreParamsPair(1, REG_A_ESA),
|
|
|
|
|
|
|
|
ReverbPair(1, R_FB_SRC_A), // 0x02E4 // Feedback Source A
|
|
|
|
ReverbPair(1, R_FB_SRC_B), // 0x02E8 // Feedback Source B
|
|
|
|
ReverbPair(1, R_IIR_DEST_A0), // 0x02EC
|
|
|
|
ReverbPair(1, R_IIR_DEST_A1), // 0x02F0
|
|
|
|
ReverbPair(1, R_ACC_SRC_A0), // 0x02F4
|
|
|
|
ReverbPair(1, R_ACC_SRC_A1), // 0x02F8
|
|
|
|
ReverbPair(1, R_ACC_SRC_B0), // 0x02FC
|
|
|
|
ReverbPair(1, R_ACC_SRC_B1), // 0x0300
|
|
|
|
ReverbPair(1, R_IIR_SRC_A0), // 0x0304
|
|
|
|
ReverbPair(1, R_IIR_SRC_A1), // 0x0308
|
|
|
|
ReverbPair(1, R_IIR_DEST_B0), // 0x030C
|
|
|
|
ReverbPair(1, R_IIR_DEST_B1), // 0x0310
|
|
|
|
ReverbPair(1, R_ACC_SRC_C0), // 0x0314
|
|
|
|
ReverbPair(1, R_ACC_SRC_C1), // 0x0318
|
|
|
|
ReverbPair(1, R_ACC_SRC_D0), // 0x031C
|
|
|
|
ReverbPair(1, R_ACC_SRC_D1), // 0x0320
|
|
|
|
ReverbPair(1, R_IIR_SRC_B0), // 0x0324
|
|
|
|
ReverbPair(1, R_IIR_SRC_B1), // 0x0328
|
|
|
|
ReverbPair(1, R_MIX_DEST_A0), // 0x032C
|
|
|
|
ReverbPair(1, R_MIX_DEST_A1), // 0x0330
|
|
|
|
ReverbPair(1, R_MIX_DEST_B0), // 0x0334
|
|
|
|
ReverbPair(1, R_MIX_DEST_B1), // 0x0338
|
|
|
|
|
|
|
|
RegWrite_Core<1, REG_A_EEA>, RegWrite_Null,
|
|
|
|
|
|
|
|
CoreParamsPair(1, REG_S_ENDX), // 0x0340 // End Point passed flag
|
|
|
|
RegWrite_Core<1, REG_P_STATX>, // 0x0344 // Status register?
|
|
|
|
|
|
|
|
REGRAW(0x746),
|
|
|
|
REGRAW(0x748), REGRAW(0x74A), REGRAW(0x74C), REGRAW(0x74E),
|
|
|
|
REGRAW(0x750), REGRAW(0x752), REGRAW(0x754), REGRAW(0x756),
|
|
|
|
REGRAW(0x758), REGRAW(0x75A), REGRAW(0x75C), REGRAW(0x75E),
|
|
|
|
|
|
|
|
// ------ -------
|
|
|
|
|
|
|
|
RegWrite_CoreExt<0, REG_P_MVOLL>, // 0x0760 // Master Volume Left
|
|
|
|
RegWrite_CoreExt<0, REG_P_MVOLR>, // 0x0762 // Master Volume Right
|
|
|
|
RegWrite_CoreExt<0, REG_P_EVOLL>, // 0x0764 // Effect Volume Left
|
|
|
|
RegWrite_CoreExt<0, REG_P_EVOLR>, // 0x0766 // Effect Volume Right
|
|
|
|
RegWrite_CoreExt<0, REG_P_AVOLL>, // 0x0768 // Core External Input Volume Left (Only Core 1)
|
|
|
|
RegWrite_CoreExt<0, REG_P_AVOLR>, // 0x076A // Core External Input Volume Right (Only Core 1)
|
|
|
|
RegWrite_CoreExt<0, REG_P_BVOLL>, // 0x076C // Sound Data Volume Left
|
|
|
|
RegWrite_CoreExt<0, REG_P_BVOLR>, // 0x076E // Sound Data Volume Right
|
|
|
|
RegWrite_CoreExt<0, REG_P_MVOLXL>, // 0x0770 // Current Master Volume Left
|
|
|
|
RegWrite_CoreExt<0, REG_P_MVOLXR>, // 0x0772 // Current Master Volume Right
|
|
|
|
|
|
|
|
RegWrite_CoreExt<0, R_IIR_ALPHA>, // 0x0774 //IIR alpha (% used)
|
|
|
|
RegWrite_CoreExt<0, R_ACC_COEF_A>, // 0x0776
|
|
|
|
RegWrite_CoreExt<0, R_ACC_COEF_B>, // 0x0778
|
|
|
|
RegWrite_CoreExt<0, R_ACC_COEF_C>, // 0x077A
|
|
|
|
RegWrite_CoreExt<0, R_ACC_COEF_D>, // 0x077C
|
|
|
|
RegWrite_CoreExt<0, R_IIR_COEF>, // 0x077E
|
|
|
|
RegWrite_CoreExt<0, R_FB_ALPHA>, // 0x0780 //feedback alpha (% used)
|
|
|
|
RegWrite_CoreExt<0, R_FB_X>, // 0x0782 //feedback
|
|
|
|
RegWrite_CoreExt<0, R_IN_COEF_L>, // 0x0784
|
|
|
|
RegWrite_CoreExt<0, R_IN_COEF_R>, // 0x0786
|
|
|
|
|
|
|
|
// ------ -------
|
|
|
|
|
|
|
|
RegWrite_CoreExt<1, REG_P_MVOLL>, // 0x0788 // Master Volume Left
|
|
|
|
RegWrite_CoreExt<1, REG_P_MVOLR>, // 0x078A // Master Volume Right
|
|
|
|
RegWrite_CoreExt<1, REG_P_EVOLL>, // 0x0764 // Effect Volume Left
|
|
|
|
RegWrite_CoreExt<1, REG_P_EVOLR>, // 0x0766 // Effect Volume Right
|
|
|
|
RegWrite_CoreExt<1, REG_P_AVOLL>, // 0x0768 // Core External Input Volume Left (Only Core 1)
|
|
|
|
RegWrite_CoreExt<1, REG_P_AVOLR>, // 0x076A // Core External Input Volume Right (Only Core 1)
|
|
|
|
RegWrite_CoreExt<1, REG_P_BVOLL>, // 0x076C // Sound Data Volume Left
|
|
|
|
RegWrite_CoreExt<1, REG_P_BVOLR>, // 0x076E // Sound Data Volume Right
|
|
|
|
RegWrite_CoreExt<1, REG_P_MVOLXL>, // 0x0770 // Current Master Volume Left
|
|
|
|
RegWrite_CoreExt<1, REG_P_MVOLXR>, // 0x0772 // Current Master Volume Right
|
|
|
|
|
|
|
|
RegWrite_CoreExt<1, R_IIR_ALPHA>, // 0x0774 //IIR alpha (% used)
|
|
|
|
RegWrite_CoreExt<1, R_ACC_COEF_A>, // 0x0776
|
|
|
|
RegWrite_CoreExt<1, R_ACC_COEF_B>, // 0x0778
|
|
|
|
RegWrite_CoreExt<1, R_ACC_COEF_C>, // 0x077A
|
|
|
|
RegWrite_CoreExt<1, R_ACC_COEF_D>, // 0x077C
|
|
|
|
RegWrite_CoreExt<1, R_IIR_COEF>, // 0x077E
|
|
|
|
RegWrite_CoreExt<1, R_FB_ALPHA>, // 0x0780 //feedback alpha (% used)
|
|
|
|
RegWrite_CoreExt<1, R_FB_X>, // 0x0782 //feedback
|
|
|
|
RegWrite_CoreExt<1, R_IN_COEF_L>, // 0x0784
|
|
|
|
RegWrite_CoreExt<1, R_IN_COEF_R>, // 0x0786
|
|
|
|
|
|
|
|
REGRAW(0x7B0), REGRAW(0x7B2), REGRAW(0x7B4), REGRAW(0x7B6),
|
|
|
|
REGRAW(0x7B8), REGRAW(0x7BA), REGRAW(0x7BC), REGRAW(0x7BE),
|
|
|
|
|
|
|
|
// SPDIF interface
|
|
|
|
|
|
|
|
RegWrite_SPDIF<SPDIF_OUT>, // 0x07C0 // SPDIF Out: OFF/'PCM'/Bitstream/Bypass
|
|
|
|
RegWrite_SPDIF<SPDIF_IRQINFO>, // 0x07C2
|
|
|
|
REGRAW(0x7C4),
|
|
|
|
RegWrite_SPDIF<SPDIF_MODE>, // 0x07C6
|
|
|
|
RegWrite_SPDIF<SPDIF_MEDIA>, // 0x07C8 // SPDIF Media: 'CD'/DVD
|
|
|
|
REGRAW(0x7CA),
|
|
|
|
RegWrite_SPDIF<SPDIF_PROTECT>, // 0x07CC // SPDIF Copy Protection
|
|
|
|
|
|
|
|
REGRAW(0x7CE),
|
|
|
|
REGRAW(0x7D0), REGRAW(0x7D2), REGRAW(0x7D4), REGRAW(0x7D6),
|
|
|
|
REGRAW(0x7D8), REGRAW(0x7DA), REGRAW(0x7DC), REGRAW(0x7DE),
|
|
|
|
REGRAW(0x7E0), REGRAW(0x7E2), REGRAW(0x7E4), REGRAW(0x7E6),
|
|
|
|
REGRAW(0x7E8), REGRAW(0x7EA), REGRAW(0x7EC), REGRAW(0x7EE),
|
|
|
|
REGRAW(0x7F0), REGRAW(0x7F2), REGRAW(0x7F4), REGRAW(0x7F6),
|
|
|
|
REGRAW(0x7F8), REGRAW(0x7FA), REGRAW(0x7FC), REGRAW(0x7FE),
|
|
|
|
|
|
|
|
NULL // should be at 0x400! (we assert check it on startup)
|
2009-09-30 05:35:37 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
|
2016-10-10 18:35:26 +00:00
|
|
|
void SPU2_FastWrite(u32 rmem, u16 value)
|
2009-09-30 05:35:37 +00:00
|
|
|
{
|
2016-10-10 18:35:26 +00:00
|
|
|
tbl_reg_writes[(rmem & 0x7ff) / 2](value);
|
2009-09-30 05:35:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2009-02-15 05:15:39 +00:00
|
|
|
void StartVoices(int core, u32 value)
|
|
|
|
{
|
2016-10-10 18:35:26 +00:00
|
|
|
// Optimization: Games like to write zero to the KeyOn reg a lot, so shortcut
|
|
|
|
// this loop if value is zero.
|
2009-02-15 05:15:39 +00:00
|
|
|
|
2016-10-10 18:35:26 +00:00
|
|
|
if (value == 0)
|
|
|
|
return;
|
2009-02-15 05:15:39 +00:00
|
|
|
|
2016-10-10 18:35:26 +00:00
|
|
|
Cores[core].Regs.ENDX &= ~value;
|
2009-09-13 06:13:22 +00:00
|
|
|
|
2016-10-10 18:35:26 +00:00
|
|
|
Cores[core].KeyOn |= value;
|
2012-09-21 15:17:16 +00:00
|
|
|
|
2016-10-10 18:35:26 +00:00
|
|
|
for (u8 vc = 0; vc < V_Core::NumVoices; vc++) {
|
|
|
|
if (!((value >> vc) & 1))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
Cores[core].Voices[vc].QueueStart();
|
|
|
|
|
|
|
|
if (IsDevBuild) {
|
|
|
|
V_Voice &thisvc(Cores[core].Voices[vc]);
|
|
|
|
|
|
|
|
if (MsgKeyOnOff())
|
|
|
|
ConLog("* SPU2-X: KeyOn: C%dV%02d: SSA: %8x; M: %s%s%s%s; H: %04x; P: %04x V: %04x/%04x; ADSR: %04x%04x\n",
|
|
|
|
core, vc, thisvc.StartA,
|
|
|
|
(Cores[core].VoiceGates[vc].DryL) ? "+" : "-", (Cores[core].VoiceGates[vc].DryR) ? "+" : "-",
|
|
|
|
(Cores[core].VoiceGates[vc].WetL) ? "+" : "-", (Cores[core].VoiceGates[vc].WetR) ? "+" : "-",
|
|
|
|
*(u16 *)GetMemPtr(thisvc.StartA),
|
|
|
|
thisvc.Pitch,
|
|
|
|
thisvc.Volume.Left.Value >> 16, thisvc.Volume.Right.Value >> 16,
|
|
|
|
thisvc.ADSR.regADSR1, thisvc.ADSR.regADSR2);
|
|
|
|
}
|
|
|
|
}
|
2009-02-15 05:15:39 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void StopVoices(int core, u32 value)
|
|
|
|
{
|
2016-10-10 18:35:26 +00:00
|
|
|
if (value == 0)
|
|
|
|
return;
|
|
|
|
for (u8 vc = 0; vc < V_Core::NumVoices; vc++) {
|
|
|
|
if (!((value >> vc) & 1))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
Cores[core].Voices[vc].ADSR.Releasing = true;
|
|
|
|
if (MsgKeyOnOff())
|
|
|
|
ConLog("* SPU2-X: KeyOff: Core %d; Voice %d.\n", core, vc);
|
|
|
|
}
|
2009-02-15 05:15:39 +00:00
|
|
|
}
|