beetle-psx-libretro/mednafen/psx/spu.cpp
2021-11-18 01:05:31 +01:00

1639 lines
45 KiB
C++

/* Mednafen - Multi-system Emulator
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
/* TODO:
Note to self: Emulating the SPU at more timing accuracy than sample, and emulating the whole SPU RAM write port FIFO thing and hypothetical periodic FIFO commit to
SPU RAM(maybe every 32 CPU cycles, with exceptions?) will likely necessitate a much more timing-accurate CPU core, and emulation of the SPU delay register(or at least the
effects of the standard value written to it), to avoid game glitches. Probably more trouble than it's worth....
SPU IRQ emulation isn't totally correct, behavior is kind of complex; run more tests on PS1.
Test reverb upsampler on the real thing.
Alter reverb algorithm to process in the pattern of L,R,L,R,L,R on each input sample, instead of doing both L and R on every 2 input samples(make
sure the real thing does it this way too, I think it at least runs the downsampler this way).
Alter reverb algorithm to perform saturation more often, as occurs on the real thing.
See if sample flag & 0x8 does anything weird, like suppressing the program-readable block end flag setting.
Determine the actual purpose of global register 0x2C(is it REALLY an address multiplier? And if so, does it affect the reverb offsets too?)
For ADSR and volume sweep, should the divider be reset to 0 on &0x8000 == true, or should the upper bit be cleared?
Should shift occur on all stages of ADPCM sample decoding, or only at the end?
On the real thing, there's some kind of weirdness with ADSR when you voice on when attack_rate(raw) = 0x7F; the envelope level register is repeatedly
reset to 0, which you can see by manual writes to the envelope level register. Normally in the attack phase when attack_rate = 0x7F, enveloping is effectively stuck/paused such that the value you write is sticky and won't be replaced or reset. Note that after you voice on, you can write a new attack_rate < 0x7F, and enveloping will work "normally" again shortly afterwards. You can even write an attack_rate of 0x7F at that point to pause enveloping clocking. I doubt any games rely on this, but it's something to keep in mind if we ever need greater insight as to how the SPU functions at a low-level in order to emulate it at cycle granularity rather than sample granularity, and it may not be a bad idea to investigate this oddity further and emulate it in the future regardless.
Voice 1 and 3 waveform output writes to SPURAM might not be correct(noted due to problems reading this area of SPU RAM on the real thing
based on my expectations of how this should work).
*/
/*
Notes:
All addresses(for 16-bit access, at least) within the SPU address space appear to be fully read/write as if they were RAM, though
values at some addresses(like the envelope current value) will be "overwritten" by the sound processing at certain times.
32-bit and 8-bit reads act as if it were RAM(not tested with all addresses, but a few, assuming the rest are the same), but 8-bit writes
to odd addresses appear to be ignored, and 8-bit writes to even addresses are treated as 16-bit writes(most likely, but, need to code custom assembly to
fully test the upper 8 bits). NOTE: the preceding information doesn't necessarily cover accesses with side effects, they still need to be tested; and it
of course covers reads/writes from the point of view of software running on the CPU.
It doesn't appear to be possible to enable FM on the first channel/voice(channel/voice 0).
Lower bit of channel start address appears to be masked out to 0(such that ADPCM block decoding is always 8 16-bit units, 16 bytes, aligned), as far as
block-decoding and flag-set program-readable loop address go.
*/
/*
Update() isn't called on Read and Writes for performance reasons, it's called with sufficient granularity from the event
system, though this will obviously need to change if we ever emulate the SPU with better precision than per-sample(pair).
*/
#include "psx.h"
#include "cdc.h"
#include "spu.h"
#include <libretro.h>
#include "../state_helpers.h"
uint32_t IntermediateBufferPos;
int16_t IntermediateBuffer[4096][2];
static const int16 FIR_Table[256][4] =
{
#include "spu_fir_table.inc"
};
PS_SPU::PS_SPU()
{
IntermediateBufferPos = 0;
memset(IntermediateBuffer, 0, sizeof(IntermediateBuffer));
}
PS_SPU::~PS_SPU()
{
}
void PS_SPU::Power(void)
{
clock_divider = 768;
memset(SPURAM, 0, sizeof(SPURAM));
for(int i = 0; i < 24; i++)
{
memset(Voices[i].DecodeBuffer, 0, sizeof(Voices[i].DecodeBuffer));
Voices[i].DecodeM2 = 0;
Voices[i].DecodeM1 = 0;
Voices[i].DecodePlayDelay = 0;
Voices[i].DecodeWritePos = 0;
Voices[i].DecodeReadPos = 0;
Voices[i].DecodeAvail = 0;
Voices[i].DecodeShift = 0;
Voices[i].DecodeWeight = 0;
Voices[i].DecodeFlags = 0;
Voices[i].IgnoreSampLA = false;
Voices[i].Sweep[0].Power();
Voices[i].Sweep[1].Power();
Voices[i].Pitch = 0;
Voices[i].CurPhase = 0;
Voices[i].StartAddr = 0;
Voices[i].CurAddr = 0;
Voices[i].ADSRControl = 0;
Voices[i].LoopAddr = 0;
Voices[i].PreLRSample = 0;
memset(&Voices[i].ADSR, 0, sizeof(SPU_ADSR));
}
GlobalSweep[0].Power();
GlobalSweep[1].Power();
NoiseDivider = 0;
NoiseCounter = 0;
LFSR = 0;
FM_Mode = 0;
Noise_Mode = 0;
Reverb_Mode = 0;
ReverbWA = 0;
ReverbVol[0] = ReverbVol[1] = 0;
CDVol[0] = CDVol[1] = 0;
ExternVol[0] = ExternVol[1] = 0;
IRQAddr = 0;
RWAddr = 0;
SPUControl = 0;
VoiceOn = 0;
VoiceOff = 0;
BlockEnd = 0;
CWA = 0;
memset(Regs, 0, sizeof(Regs));
memset(AuxRegs, 0, sizeof(AuxRegs));
memset(RDSB, 0, sizeof(RDSB));
memset(RUSB, 0, sizeof(RUSB));
RvbResPos = 0;
ReverbCur = ReverbWA;
IRQAsserted = false;
}
static INLINE void CalcVCDelta(const uint8 zs, uint8 speed, bool log_mode, bool dec_mode, bool inv_increment, int16 Current, int &increment, int &divinco)
{
increment = (7 - (speed & 0x3));
if(inv_increment)
increment = ~increment;
divinco = 32768;
if(speed < 0x2C)
increment = (unsigned)increment << ((0x2F - speed) >> 2);
if(speed >= 0x30)
divinco >>= (speed - 0x2C) >> 2;
if(log_mode)
{
if(dec_mode) // Log decrement mode
increment = (Current * increment) >> 15;
else // Log increment mode
{
if((Current & 0x7FFF) >= 0x6000)
{
if(speed < 0x28)
increment >>= 2;
else if(speed >= 0x2C)
divinco >>= 2;
else // 0x28 ... 0x2B
{
increment >>= 1;
divinco >>= 1;
}
}
}
} // end if(log_mode)
if(divinco == 0 && speed < zs) //0x7F)
divinco = 1;
}
INLINE void SPU_Sweep::Power(void)
{
Control = 0;
Current = 0;
Divider = 0;
}
INLINE void SPU_Sweep::WriteControl(uint16 value)
{
Control = value;
}
INLINE int16 SPU_Sweep::ReadVolume(void)
{
return((int16)Current);
}
void SPU_Sweep::Clock()
{
const bool log_mode = (bool)(Control & 0x4000);
const bool dec_mode = (bool)(Control & 0x2000);
const bool inv_mode = (bool)(Control & 0x1000);
const bool inv_increment = (dec_mode ^ inv_mode) | (dec_mode & log_mode);
const uint16 vc_cv_xor = (inv_mode & !(dec_mode & log_mode)) ? 0xFFFF : 0x0000;
const uint16 TestInvert = inv_mode ? 0xFFFF : 0x0000;
int increment;
int divinco;
CalcVCDelta(0x7F, Control & 0x7F, log_mode, dec_mode, inv_increment, (int16)(Current ^ vc_cv_xor), increment, divinco);
if((dec_mode & !(inv_mode & log_mode)) && ((Current & 0x8000) == (inv_mode ? 0x0000 : 0x8000) || (Current == 0)))
{
//
// Not sure if this condition should stop the Divider adding or force the increment value to 0.
//
Current = 0;
}
else
{
Divider += divinco;
if(Divider & 0x8000)
{
Divider = 0;
if(dec_mode || ((Current ^ TestInvert) != 0x7FFF))
{
uint16 PrevCurrent = Current;
Current = Current + increment;
if(!dec_mode && ((Current ^ PrevCurrent) & 0x8000) && ((Current ^ TestInvert) & 0x8000))
Current = 0x7FFF ^ TestInvert;
}
}
}
}
INLINE void SPU_Sweep::WriteVolume(int16 value)
{
Current = value;
}
//
// Take care not to trigger SPU IRQ for the next block before its decoding start.
//
void PS_SPU::RunDecoder(SPU_Voice *voice)
{
// 5 through 0xF appear to be 0 on the real thing.
static const int32 Weights[16][2] =
{
// s-1 s-2
{ 0, 0 },
{ 60, 0 },
{ 115, -52 },
{ 98, -55 },
{ 122, -60 },
};
if(voice->DecodeAvail >= 11)
{
if(SPUControl & 0x40)
{
unsigned test_addr = (voice->CurAddr - 1) & 0x3FFFF;
if(IRQAddr == test_addr || IRQAddr == (test_addr & 0x3FFF8))
{
IRQAsserted = true;
IRQ_Assert(IRQ_SPU, IRQAsserted);
}
}
return;
}
if((voice->CurAddr & 0x7) == 0)
{
// Handle delayed flags from the previously-decoded block.
//
// NOTE: The timing of setting the BlockEnd bit here, and forcing ADSR envelope volume to 0, is a bit late. (And I'm not sure if it should be done once
// per decoded block, or more than once, but that's probably not something games would rely on, but we should test it anyway).
//
// Correctish timing can be achieved by moving this block of code up above voice->DecodeAvail >= 11, and sticking it inside an: if(voice->DecodeAvail <= 12),
// though more tests are needed on the ADPCM decoding process as a whole before we should actually make such a change. Additionally, we'd probably
// have to separate the CurAddr = LoopAddr logic, so we don't generate spurious early SPU IRQs.
if(voice->DecodeFlags & 0x1)
{
voice->CurAddr = voice->LoopAddr & ~0x7;
BlockEnd |= 1 << (voice - Voices);
if(!(voice->DecodeFlags & 0x2)) // Force enveloping to 0 if not "looping". TODO: Should we reset the ADSR divider counter too?
{
if(!(Noise_Mode & (1 << (voice - Voices))))
{
voice->ADSR.Phase = ADSR_RELEASE;
voice->ADSR.EnvLevel = 0;
}
}
}
}
//for(int z = 0; z < 4; z++)
{
if(SPUControl & 0x40)
{
unsigned test_addr = voice->CurAddr & 0x3FFFF;
if(IRQAddr == test_addr || IRQAddr == (test_addr & 0x3FFF8))
{
IRQAsserted = true;
IRQ_Assert(IRQ_SPU, IRQAsserted);
}
}
if((voice->CurAddr & 0x7) == 0)
{
const uint16 CV = SPURAM[voice->CurAddr];
voice->DecodeShift = CV & 0xF;
voice->DecodeWeight = (CV >> 4) & 0xF;
voice->DecodeFlags = (CV >> 8) & 0xFF;
if(voice->DecodeFlags & 0x4)
{
if(!voice->IgnoreSampLA)
voice->LoopAddr = voice->CurAddr;
}
voice->CurAddr = (voice->CurAddr + 1) & 0x3FFFF;
}
//
// Don't else this block; we need to ALWAYS decode 4 samples per call to RunDecoder() if DecodeAvail < 11, or else sample playback
// at higher rates will fail horribly.
//
{
const int32 weight_m1 = Weights[voice->DecodeWeight][0];
const int32 weight_m2 = Weights[voice->DecodeWeight][1];
uint16 CV;
unsigned shift;
uint32 coded;
int16 *tb = &voice->DecodeBuffer[voice->DecodeWritePos];
CV = SPURAM[voice->CurAddr];
shift = voice->DecodeShift;
if(MDFN_UNLIKELY(shift > 12))
{
shift = 8;
CV &= 0x8888;
}
coded = (uint32)CV << 12;
for(int i = 0; i < 4; i++)
{
int32 sample = (int16)(coded & 0xF000) >> shift;
sample += ((voice->DecodeM2 * weight_m2) >> 6);
sample += ((voice->DecodeM1 * weight_m1) >> 6);
clamp(&sample, -32768, 32767);
tb[i] = sample;
voice->DecodeM2 = voice->DecodeM1;
voice->DecodeM1 = sample;
coded >>= 4;
}
voice->DecodeWritePos = (voice->DecodeWritePos + 4) & 0x1F;
voice->DecodeAvail += 4;
voice->CurAddr = (voice->CurAddr + 1) & 0x3FFFF;
}
}
}
void PS_SPU::CacheEnvelope(SPU_Voice *voice)
{
uint32_t raw = voice->ADSRControl;
SPU_ADSR *ADSR = &voice->ADSR;
int32_t Sl = (raw >> 0) & 0x0F;
int32_t Dr = (raw >> 4) & 0x0F;
int32_t Ar = (raw >> 8) & 0x7F;
int32_t Rr = (raw >> 16) & 0x1F;
int32_t Sr = (raw >> 22) & 0x7F;
ADSR->AttackExp = (bool)(raw & (1 << 15));
ADSR->ReleaseExp = (bool)(raw & (1 << 21));
ADSR->SustainExp = (bool)(raw & (1 << 31));
ADSR->SustainDec = (bool)(raw & (1 << 30));
ADSR->AttackRate = Ar;
ADSR->DecayRate = Dr << 2;
ADSR->SustainRate = Sr;
ADSR->ReleaseRate = Rr << 2;
ADSR->SustainLevel = (Sl + 1) << 11;
}
void PS_SPU::ResetEnvelope(SPU_Voice *voice)
{
SPU_ADSR *ADSR = &voice->ADSR;
ADSR->EnvLevel = 0;
ADSR->Divider = 0;
ADSR->Phase = ADSR_ATTACK;
}
void PS_SPU::ReleaseEnvelope(SPU_Voice *voice)
{
SPU_ADSR *ADSR = &voice->ADSR;
ADSR->Divider = 0;
ADSR->Phase = ADSR_RELEASE;
}
void PS_SPU::RunEnvelope(SPU_Voice *voice)
{
SPU_ADSR *ADSR = &voice->ADSR;
int increment;
int divinco;
int16 uoflow_reset;
if(ADSR->Phase == ADSR_ATTACK && ADSR->EnvLevel == 0x7FFF)
ADSR->Phase++;
//static INLINE void CalcVCDelta(const uint8 zs, uint8 speed, bool log_mode, bool decrement, bool inv_increment, int16 Current, int &increment, int &divinco)
switch(ADSR->Phase)
{
default: assert(0);
break;
case ADSR_ATTACK:
CalcVCDelta(0x7F, ADSR->AttackRate, ADSR->AttackExp, false, false, (int16)ADSR->EnvLevel, increment, divinco);
uoflow_reset = 0x7FFF;
break;
case ADSR_DECAY:
CalcVCDelta(0x1F << 2, ADSR->DecayRate, true, true, true, (int16)ADSR->EnvLevel, increment, divinco);
uoflow_reset = 0;
break;
case ADSR_SUSTAIN:
CalcVCDelta(0x7F, ADSR->SustainRate, ADSR->SustainExp, ADSR->SustainDec, ADSR->SustainDec, (int16)ADSR->EnvLevel, increment, divinco);
uoflow_reset = ADSR->SustainDec ? 0 : 0x7FFF;
break;
case ADSR_RELEASE:
CalcVCDelta(0x1F << 2, ADSR->ReleaseRate, ADSR->ReleaseExp, true, true, (int16)ADSR->EnvLevel, increment, divinco);
uoflow_reset = 0;
break;
}
ADSR->Divider += divinco;
if(ADSR->Divider & 0x8000)
{
const uint16 prev_level = ADSR->EnvLevel;
ADSR->Divider = 0;
ADSR->EnvLevel += increment;
if(ADSR->Phase == ADSR_ATTACK)
{
// If previous the upper bit was 0, but now it's 1, handle overflow.
if(((prev_level ^ ADSR->EnvLevel) & ADSR->EnvLevel) & 0x8000)
ADSR->EnvLevel = uoflow_reset;
}
else
{
if(ADSR->EnvLevel & 0x8000)
ADSR->EnvLevel = uoflow_reset;
}
if(ADSR->Phase == ADSR_DECAY && (uint16)ADSR->EnvLevel < ADSR->SustainLevel)
ADSR->Phase++;
}
}
INLINE void PS_SPU::CheckIRQAddr(uint32 addr)
{
if(SPUControl & 0x40)
{
if(IRQAddr != addr)
return;
IRQAsserted = true;
IRQ_Assert(IRQ_SPU, IRQAsserted);
}
}
INLINE void PS_SPU::WriteSPURAM(uint32 addr, uint16 value)
{
CheckIRQAddr(addr);
SPURAM[addr] = value;
}
INLINE uint16 PS_SPU::ReadSPURAM(uint32 addr)
{
CheckIRQAddr(addr);
return(SPURAM[addr]);
}
static INLINE int16 ReverbSat(int32 samp)
{
if(samp > 32767)
samp = 32767;
if(samp < -32768)
samp = -32768;
return(samp);
}
#define REVERB_NEG(samp) (((samp) == -32768) ? 0x7FFF : -(samp))
INLINE uint32 PS_SPU::Get_Reverb_Offset(uint32 in_offset)
{
uint32 offset = ReverbCur + (in_offset & 0x3FFFF);
offset += ReverbWA & ((int32)(offset << 13) >> 31);
offset &= 0x3FFFF;
return(offset);
}
int16 NO_INLINE PS_SPU::RD_RVB(uint16 raw_offs, int32 extra_offs)
{
return ReadSPURAM(Get_Reverb_Offset((raw_offs << 2) + extra_offs));
}
void NO_INLINE PS_SPU::WR_RVB(uint16 raw_offs, int16 sample)
{
if(SPUControl & 0x80)
WriteSPURAM(Get_Reverb_Offset(raw_offs << 2), sample);
}
//
// Zeroes optimized out; middle removed too(it's 16384)
static const int16 ResampTable[20] =
{
-1, 2, -10, 35, -103, 266, -616, 1332, -2960, 10246, 10246, -2960, 1332, -616, 266, -103, 35, -10, 2, -1,
};
static INLINE int32 Reverb4422(const int16 *src)
{
int32 out = 0; // 32-bits is adequate(it won't overflow)
for(unsigned i = 0; i < 20; i++)
out += ResampTable[i] * src[i * 2];
// Middle non-zero
out += 0x4000 * src[19];
out >>= 15;
clamp(&out, -32768, 32767);
return(out);
}
static INLINE int32 Reverb2244(const int16 *src)
{
unsigned i;
int32_t out = 0; /* 32bits is adequate (it won't overflow) */
for(i = 0; i < 20; i++)
out += ResampTable[i] * src[i];
out >>= 14;
clamp(&out, -32768, 32767);
return out;
}
static int32 IIASM(const int16 IIR_ALPHA, const int16 insamp)
{
if(MDFN_UNLIKELY(IIR_ALPHA == -32768))
{
if(insamp == -32768)
return 0;
return insamp * -65536;
}
return insamp * (32768 - IIR_ALPHA);
}
//
// Take care to thoroughly test the reverb resampling code when modifying anything that uses RvbResPos.
//
void PS_SPU::RunReverb(const int32* in, int32* out)
{
unsigned lr;
int32 upsampled[2];
upsampled[0] = upsampled[1] = 0;
for(lr = 0; lr < 2; lr++)
{
RDSB[lr][RvbResPos | 0x00] = in[lr];
RDSB[lr][RvbResPos | 0x40] = in[lr]; // So we don't have to &/bounds check in our MAC loop
}
if(RvbResPos & 1)
{
int32 downsampled[2];
for(unsigned lr = 0; lr < 2; lr++)
downsampled[lr] = Reverb4422(&RDSB[lr][(RvbResPos - 38) & 0x3F]);
/* Run algorithm */
for(unsigned lr = 0; lr < 2; lr++)
{
const int16 IIR_INPUT_A = ReverbSat((((RD_RVB(IIR_SRC_A[lr ^ 0]) * IIR_COEF) >> 14) + ((downsampled[lr] * IN_COEF[lr]) >> 14)) >> 1);
const int16 IIR_INPUT_B = ReverbSat((((RD_RVB(IIR_SRC_B[lr ^ 1]) * IIR_COEF) >> 14) + ((downsampled[lr] * IN_COEF[lr]) >> 14)) >> 1);
const int16 IIR_A = ReverbSat((((IIR_INPUT_A * IIR_ALPHA) >> 14) + (IIASM(IIR_ALPHA, RD_RVB(IIR_DEST_A[lr], -1)) >> 14)) >> 1);
const int16 IIR_B = ReverbSat((((IIR_INPUT_B * IIR_ALPHA) >> 14) + (IIASM(IIR_ALPHA, RD_RVB(IIR_DEST_B[lr], -1)) >> 14)) >> 1);
WR_RVB(IIR_DEST_A[lr], IIR_A);
WR_RVB(IIR_DEST_B[lr], IIR_B);
const int32 ACC = ((RD_RVB(ACC_SRC_A[lr]) * ACC_COEF_A) >> 14) +
((RD_RVB(ACC_SRC_B[lr]) * ACC_COEF_B) >> 14) +
((RD_RVB(ACC_SRC_C[lr]) * ACC_COEF_C) >> 14) +
((RD_RVB(ACC_SRC_D[lr]) * ACC_COEF_D) >> 14);
const int16 FB_A = RD_RVB(MIX_DEST_A[lr] - FB_SRC_A);
const int16 FB_B = RD_RVB(MIX_DEST_B[lr] - FB_SRC_B);
const int16 MDA = ReverbSat((ACC + ((FB_A * REVERB_NEG(FB_ALPHA)) >> 14)) >> 1);
const int16 MDB = ReverbSat(FB_A + ((((MDA * FB_ALPHA) >> 14) + ((FB_B * REVERB_NEG(FB_X)) >> 14)) >> 1));
const int16 IVB = ReverbSat(FB_B + ((MDB * FB_X) >> 15));
WR_RVB(MIX_DEST_A[lr], MDA);
WR_RVB(MIX_DEST_B[lr], MDB);
#if 0
{
static uint32 sqcounter;
RUSB[lr][(RvbResPos >> 1) | 0x20] = RUSB[lr][RvbResPos >> 1] = ((sqcounter & 0xFF) == 0) ? 0x8000 : 0x0000; //((sqcounter & 0x80) ? 0x7000 : 0x9000);
sqcounter += lr;
}
#else
RUSB[lr][(RvbResPos >> 1) | 0x20] = RUSB[lr][RvbResPos >> 1] = IVB; // Output sample
#endif
}
ReverbCur = (ReverbCur + 1) & 0x3FFFF;
if(!ReverbCur)
ReverbCur = ReverbWA;
for(unsigned lr = 0; lr < 2; lr++)
{
const int16 *src = &RUSB[lr][((RvbResPos >> 1) - 19) & 0x1F];
upsampled[lr] = Reverb2244(src);
}
}
else
{
for(unsigned lr = 0; lr < 2; lr++)
{
const int16 *src = &RUSB[lr][((RvbResPos >> 1) - 19) & 0x1F];
upsampled[lr] = src[9]; /* Reverb 2244 (Middle non-zero */
}
}
RvbResPos = (RvbResPos + 1) & 0x3F;
for(unsigned lr = 0; lr < 2; lr++)
out[lr] = upsampled[lr];
}
INLINE void PS_SPU::RunNoise(void)
{
const unsigned rf = ((SPUControl >> 8) & 0x3F);
uint32 NoiseDividerInc = (2 << (rf >> 2));
uint32 NoiseCounterInc = 4 + (rf & 0x3);
if(rf >= 0x3C)
{
NoiseDividerInc = 0x8000;
NoiseCounterInc = 8;
}
NoiseDivider += NoiseDividerInc;
if(NoiseDivider & 0x8000)
{
NoiseDivider = 0;
NoiseCounter += NoiseCounterInc;
if(NoiseCounter & 0x8)
{
NoiseCounter &= 0x7;
LFSR = (LFSR << 1) | (((LFSR >> 15) ^ (LFSR >> 12) ^ (LFSR >> 11) ^ (LFSR >> 10) ^ 1) & 1);
}
}
}
int32 PS_SPU::UpdateFromCDC(int32 clocks)
{
//int32 clocks = timestamp - lastts;
int32 sample_clocks = 0;
//lastts = timestamp;
clock_divider -= clocks;
while(clock_divider <= 0)
{
clock_divider += 768;
sample_clocks++;
}
while(sample_clocks > 0)
{
// xxx[0] = left, xxx[1] = right
// Accumulated sound output.
int32 accum[2];
// Accumulated sound output for reverb input
int32 accum_fv[2];
// Output of reverb processing.
int32 reverb[2];
// Final output.
int32 output[2];
accum[0] = accum[1] = 0;
accum_fv[0] = accum_fv[1] = 0;
reverb[0] = reverb[1] = 0;
output[0] = output[1] = 0;
const uint32 PhaseModCache = FM_Mode & ~ 1;
/*
**
** 0x1F801DAE Notes and Conjecture:
** -------------------------------------------------------------------------------------
** | 15 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 4 3 2 1 0 |
** | ? | *13| ? | ba | *10 | wrr|rdr| df | is | c |
** -------------------------------------------------------------------------------------
**
** c - Appears to be delayed copy of lower 6 bits from 0x1F801DAA.
**
** is - Interrupt asserted out status. (apparently not instantaneous status though...)
**
** df - Related to (c & 0x30) == 0x20 or (c & 0x30) == 0x30, at least.
** 0 = DMA busy(FIFO not empty when in DMA write mode?)?
** 1 = DMA ready? Something to do with the FIFO?
**
** rdr - Read(DMA read?) Ready?
**
** wrr - Write(DMA write?) Ready?
**
** *10 - Unknown. Some sort of (FIFO?) busy status?(BIOS tests for this bit in places)
**
** ba - Alternates between 0 and 1, even when SPUControl bit15 is 0; might be related to CD audio and voice 1 and 3 writing to SPU RAM.
**
** *13 - Unknown, was set to 1 when testing with an SPU delay system reg value of 0x200921E1(test result might not be reliable, re-run).
*/
SPUStatus = SPUControl & 0x3F;
SPUStatus |= IRQAsserted ? 0x40 : 0x00;
if(Regs[0xD6] == 0x4) // TODO: Investigate more(case 0x2C in global regs r/w handler)
SPUStatus |= (CWA & 0x100) ? 0x800 : 0x000;
for(int voice_num = 0; voice_num < 24; voice_num++)
{
SPU_Voice *voice = &Voices[voice_num];
int32 voice_pvs;
voice->PreLRSample = 0;
//PSX_WARNING("[SPU] Voice %d CurPhase=%08x, pitch=%04x, CurAddr=%08x", voice_num, voice->CurPhase, voice->Pitch, voice->CurAddr);
if(voice->DecodePlayDelay)
{
voice->IgnoreSampLA = false;
}
//
// Decode new samples if necessary.
//
RunDecoder(voice);
//
//
//
int l, r;
if(Noise_Mode & (1 << voice_num))
voice_pvs = (int16)LFSR;
else
{
const int si = voice->DecodeReadPos;
const int pi = ((voice->CurPhase & 0xFFF) >> 4);
voice_pvs = ((voice->DecodeBuffer[(si + 0) & 0x1F] * FIR_Table[pi][0]) +
(voice->DecodeBuffer[(si + 1) & 0x1F] * FIR_Table[pi][1]) +
(voice->DecodeBuffer[(si + 2) & 0x1F] * FIR_Table[pi][2]) +
(voice->DecodeBuffer[(si + 3) & 0x1F] * FIR_Table[pi][3])) >> 15;
}
voice_pvs = (voice_pvs * (int16)voice->ADSR.EnvLevel) >> 15;
voice->PreLRSample = voice_pvs;
if(voice_num == 1 || voice_num == 3)
{
int index = voice_num >> 1;
WriteSPURAM(0x400 | (index * 0x200) | CWA, voice_pvs);
}
l = (voice_pvs * voice->Sweep[0].ReadVolume()) >> 15;
r = (voice_pvs * voice->Sweep[1].ReadVolume()) >> 15;
accum[0] += l;
accum[1] += r;
if(Reverb_Mode & (1 << voice_num))
{
accum_fv[0] += l;
accum_fv[1] += r;
}
// Run sweep
for(int lr = 0; lr < 2; lr++)
{
if((voice->Sweep[lr].Control & 0x8000))
voice->Sweep[lr].Clock();
else
voice->Sweep[lr].Current = (voice->Sweep[lr].Control & 0x7FFF) << 1;
}
// Increment stuff
if(!voice->DecodePlayDelay)
{
unsigned phase_inc;
// Run enveloping
RunEnvelope(voice);
if(PhaseModCache & (1 << voice_num))
{
// This old formula: phase_inc = (voice->Pitch * ((voice - 1)->PreLRSample + 0x8000)) >> 15;
// is incorrect, as it does not handle carrier pitches >= 0x8000 properly.
phase_inc = voice->Pitch + (((int16)voice->Pitch * ((voice - 1)->PreLRSample)) >> 15);
}
else
phase_inc = voice->Pitch;
if(phase_inc > 0x3FFF)
phase_inc = 0x3FFF;
{
const uint32 tmp_phase = voice->CurPhase + phase_inc;
const unsigned used = tmp_phase >> 12;
voice->CurPhase = tmp_phase & 0xFFF;
voice->DecodeAvail -= used;
voice->DecodeReadPos = (voice->DecodeReadPos + used) & 0x1F;
}
}
else
voice->DecodePlayDelay--;
if(VoiceOff & (1U << voice_num))
{
if(voice->ADSR.Phase != ADSR_RELEASE)
{
// TODO/FIXME:
// To fix all the missing notes in "Dragon Ball GT: Final Bout" music, !voice->DecodePlayDelay instead of
// voice->DecodePlayDelay < 3 is necessary, but that would cause the length of time for which the voice off is
// effectively ignored to be too long by about half a sample(rough test measurement). That, combined with current
// CPU and DMA emulation timing inaccuracies(execution generally too fast), creates a significant risk of regressions
// in other games, so be very conservative for now.
//
// Also, voice on should be ignored during the delay as well, but comprehensive tests are needed before implementing that
// due to some effects that appear to occur repeatedly during the delay on a PS1 but are currently only emulated as
// performed when the voice on is processed(e.g. curaddr = startaddr).
//
if(voice->DecodePlayDelay < 3)
{
ReleaseEnvelope(voice);
}
}
}
if(VoiceOn & (1U << voice_num))
{
ResetEnvelope(voice);
voice->DecodeFlags = 0;
voice->DecodeWritePos = 0;
voice->DecodeReadPos = 0;
voice->DecodeAvail = 0;
voice->DecodePlayDelay = 4;
BlockEnd &= ~(1 << voice_num);
//
// Weight/filter previous value initialization:
//
voice->DecodeM2 = 0;
voice->DecodeM1 = 0;
voice->CurPhase = 0;
voice->CurAddr = voice->StartAddr & ~0x7;
voice->IgnoreSampLA = false;
}
if(!(SPUControl & 0x8000))
{
voice->ADSR.Phase = ADSR_RELEASE;
voice->ADSR.EnvLevel = 0;
}
}
VoiceOff = 0;
VoiceOn = 0;
// "Mute" control doesn't seem to affect CD audio(though CD audio reverb wasn't tested...)
// TODO: If we add sub-sample timing accuracy, see if it's checked for every channel at different times, or just once.
if(!(SPUControl & 0x4000))
{
accum[0] = 0;
accum[1] = 0;
accum_fv[0] = 0;
accum_fv[1] = 0;
}
// Get CD-DA
{
int32 cda_raw[2];
int32 cdav[2];
const unsigned freq = (PSX_CDC->AudioBuffer.ReadPos < PSX_CDC->AudioBuffer.Size) ? PSX_CDC->AudioBuffer.Freq : 0;
cda_raw[0] = cda_raw[1] = 0;
if (freq)
PSX_CDC->GetCDAudio(cda_raw, freq); // PS_CDC::GetCDAudio() guarantees the variables passed by reference will be set to 0,
// and that their range shall be -32768 through 32767.
WriteSPURAM(CWA | 0x000, cda_raw[0]);
WriteSPURAM(CWA | 0x200, cda_raw[1]);
for(unsigned i = 0; i < 2; i++)
cdav[i] = (cda_raw[i] * CDVol[i]) >> 15;
if(SPUControl & 0x0001)
{
accum[0] += cdav[0];
accum[1] += cdav[1];
if(SPUControl & 0x0004) // TODO: Test this bit(and see if it is really dependent on bit0)
{
accum_fv[0] += cdav[0];
accum_fv[1] += cdav[1];
}
}
}
CWA = (CWA + 1) & 0x1FF;
RunNoise();
for (unsigned lr = 0; lr < 2; lr++)
clamp(&accum_fv[lr], -32768, 32767);
RunReverb(accum_fv, reverb);
for(unsigned lr = 0; lr < 2; lr++)
{
accum[lr] += ((reverb[lr] * ReverbVol[lr]) >> 15);
clamp(&accum[lr], -32768, 32767);
output[lr] = (accum[lr] * GlobalSweep[lr].ReadVolume()) >> 15;
clamp(&output[lr], -32768, 32767);
}
if(IntermediateBufferPos < 4096) // Overflow might occur in some debugger use cases.
{
// 75%, for some (resampling) headroom.
for(unsigned lr = 0; lr < 2; lr++)
IntermediateBuffer[IntermediateBufferPos][lr] = (output[lr] * 3 + 2) >> 2;
IntermediateBufferPos++;
}
sample_clocks--;
// Clock global sweep
for(unsigned lr = 0; lr < 2; lr++)
{
if((GlobalSweep[lr].Control & 0x8000))
GlobalSweep[lr].Clock();
else
GlobalSweep[lr].Current = (GlobalSweep[lr].Control & 0x7FFF) << 1;
}
}
//assert(clock_divider < 768);
return clock_divider;
}
void PS_SPU::WriteDMA(uint32 V)
{
WriteSPURAM(RWAddr, V);
RWAddr = (RWAddr + 1) & 0x3FFFF;
WriteSPURAM(RWAddr, V >> 16);
RWAddr = (RWAddr + 1) & 0x3FFFF;
CheckIRQAddr(RWAddr);
}
uint32 PS_SPU::ReadDMA(void)
{
uint32 ret = (uint16)ReadSPURAM(RWAddr);
RWAddr = (RWAddr + 1) & 0x3FFFF;
ret |= (uint32)(uint16)ReadSPURAM(RWAddr) << 16;
RWAddr = (RWAddr + 1) & 0x3FFFF;
CheckIRQAddr(RWAddr);
return(ret);
}
void PS_SPU::Write(int32_t timestamp, uint32 A, uint16 V)
{
//if((A & 0x3FF) < 0x180)
// PSX_WARNING("[SPU] Write: %08x %04x", A, V);
A &= 0x3FF;
if(A >= 0x200)
{
if(A < 0x260)
{
SPU_Voice *voice = &Voices[(A - 0x200) >> 2];
voice->Sweep[(A & 2) >> 1].WriteVolume(V);
}
else if(A < 0x280)
AuxRegs[(A & 0x1F) >> 1] = V;
return;
}
if(A < 0x180)
{
SPU_Voice *voice = &Voices[A >> 4];
switch(A & 0xF)
{
case 0x00:
case 0x02:
voice->Sweep[(A & 2) >> 1].WriteControl(V);
break;
case 0x04:
voice->Pitch = V;
break;
case 0x06:
voice->StartAddr = (V << 2) & 0x3FFFF;
break;
case 0x08:
voice->ADSRControl &= 0xFFFF0000;
voice->ADSRControl |= V;
CacheEnvelope(voice);
break;
case 0x0A:
voice->ADSRControl &= 0x0000FFFF;
voice->ADSRControl |= V << 16;
CacheEnvelope(voice);
break;
case 0x0C:
voice->ADSR.EnvLevel = V;
break;
case 0x0E:
voice->LoopAddr = (V << 2) & 0x3FFFF;
voice->IgnoreSampLA = true;
break;
}
}
else
{
switch(A & 0x7F)
{
case 0x00:
case 0x02: GlobalSweep[(A & 2) >> 1].WriteControl(V);
break;
case 0x04: ReverbVol[0] = (int16)V;
break;
case 0x06: ReverbVol[1] = (int16)V;
break;
// Voice ON:
case 0x08: VoiceOn &= 0xFFFF0000;
VoiceOn |= V << 0;
break;
case 0x0a: VoiceOn &= 0x0000FFFF;
VoiceOn |= (V & 0xFF) << 16;
break;
// Voice OFF:
case 0x0c: VoiceOff &= 0xFFFF0000;
VoiceOff |= V << 0;
break;
case 0x0e: VoiceOff &= 0x0000FFFF;
VoiceOff |= (V & 0xFF) << 16;
break;
case 0x10: FM_Mode &= 0xFFFF0000;
FM_Mode |= V << 0;
break;
case 0x12: FM_Mode &= 0x0000FFFF;
FM_Mode |= (V & 0xFF) << 16;
break;
case 0x14: Noise_Mode &= 0xFFFF0000;
Noise_Mode |= V << 0;
break;
case 0x16: Noise_Mode &= 0x0000FFFF;
Noise_Mode |= (V & 0xFF) << 16;
break;
case 0x18: Reverb_Mode &= 0xFFFF0000;
Reverb_Mode |= V << 0;
break;
case 0x1A: Reverb_Mode &= 0x0000FFFF;
Reverb_Mode |= (V & 0xFF) << 16;
break;
case 0x1C: BlockEnd &= 0xFFFF0000;
BlockEnd |= V << 0;
break;
case 0x1E: BlockEnd &= 0x0000FFFF;
BlockEnd |= V << 16;
break;
case 0x22: ReverbWA = (V << 2) & 0x3FFFF;
ReverbCur = ReverbWA;
//PSX_WARNING("[SPU] Reverb WA set: 0x%04x", V);
break;
case 0x24:
IRQAddr = (V << 2) & 0x3FFFF;
CheckIRQAddr(RWAddr);
break;
case 0x26:
RWAddr = (V << 2) & 0x3FFFF;
CheckIRQAddr(RWAddr);
break;
case 0x28: WriteSPURAM(RWAddr, V);
RWAddr = (RWAddr + 1) & 0x3FFFF;
CheckIRQAddr(RWAddr);
break;
case 0x2A:
SPUControl = V;
if(!(V & 0x40))
{
IRQAsserted = false;
IRQ_Assert(IRQ_SPU, IRQAsserted);
}
CheckIRQAddr(RWAddr);
break;
case 0x2C:
//PSX_WARNING("[SPU] Global reg 0x2c set: 0x%04x", V);
break;
case 0x30: CDVol[0] = (int16_t)V;
break;
case 0x32: CDVol[1] = (int16_t)V;
break;
case 0x34: ExternVol[0] = (int16_t)V;
break;
case 0x36: ExternVol[1] = (int16_t)V;
break;
case 0x38:
case 0x3A: GlobalSweep[(A & 2) >> 1].WriteVolume(V);
break;
}
}
Regs[(A & 0x1FF) >> 1] = V;
}
uint16 PS_SPU::Read(int32_t timestamp, uint32 A)
{
A &= 0x3FF;
if(A >= 0x200)
{
if(A < 0x260)
{
SPU_Voice *voice = &Voices[(A - 0x200) >> 2];
return voice->Sweep[(A & 2) >> 1].ReadVolume();
}
else if(A < 0x280)
return(AuxRegs[(A & 0x1F) >> 1]);
return(0xFFFF);
}
if(A < 0x180)
{
SPU_Voice *voice = &Voices[A >> 4];
switch(A & 0xF)
{
case 0x0C:
return(voice->ADSR.EnvLevel);
case 0x0E:
return(voice->LoopAddr >> 2);
}
}
else
{
switch(A & 0x7F)
{
case 0x1C:
return(BlockEnd);
case 0x1E:
return(BlockEnd >> 16);
case 0x26:
//PSX_WARNING("[SPU] RWADDR Read");
break;
case 0x28:
//PSX_WARNING("[SPU] SPURAM Read port(?) Read");
{
uint16 ret = ReadSPURAM(RWAddr);
RWAddr = (RWAddr + 1) & 0x3FFFF;
CheckIRQAddr(RWAddr);
return (ret);
}
case 0x2a:
return(SPUControl);
/* FIXME: What is this used for? */
case 0x3C:
//PSX_WARNING("[SPU] Read Unknown: %08x", A);
return(0);
case 0x38:
case 0x3A:
return(GlobalSweep[(A & 2) >> 1].ReadVolume());
}
}
return(Regs[(A & 0x1FF) >> 1]);
}
int PS_SPU::StateAction(StateMem *sm, int load, int data_only)
{
SFORMAT StateRegs[] =
{
#define SFSWEEP(r) SFVAR((r).Control), \
SFVAR((r).Current), \
SFVAR((r).Divider)
#define SFVOICE(n) SFARRAY16(&Voices[n].DecodeBuffer[0], sizeof(Voices[n].DecodeBuffer) / sizeof(Voices[n].DecodeBuffer[0])), \
SFVAR(Voices[n].DecodeM2), \
SFVAR(Voices[n].DecodeM1), \
SFVAR(Voices[n].DecodePlayDelay), \
SFVAR(Voices[n].DecodeWritePos), \
SFVAR(Voices[n].DecodeReadPos), \
SFVAR(Voices[n].DecodeAvail), \
SFVAR(Voices[n].DecodeShift), \
SFVAR(Voices[n].DecodeWeight), \
SFVAR(Voices[n].DecodeFlags), \
SFVAR(Voices[n].IgnoreSampLA), \
\
SFSWEEP(Voices[n].Sweep[0]), \
SFSWEEP(Voices[n].Sweep[1]), \
\
SFVAR(Voices[n].Pitch), \
SFVAR(Voices[n].CurPhase), \
\
SFVAR(Voices[n].StartAddr), \
SFVAR(Voices[n].CurAddr), \
SFVAR(Voices[n].ADSRControl), \
SFVAR(Voices[n].LoopAddr), \
SFVAR(Voices[n].PreLRSample), \
\
SFVAR(Voices[n].ADSR.EnvLevel), \
SFVAR(Voices[n].ADSR.Divider), \
SFVAR(Voices[n].ADSR.Phase), \
\
SFVAR(Voices[n].ADSR.AttackExp), \
SFVAR(Voices[n].ADSR.SustainExp), \
SFVAR(Voices[n].ADSR.SustainDec), \
SFVAR(Voices[n].ADSR.ReleaseExp), \
\
SFVAR(Voices[n].ADSR.AttackRate), \
SFVAR(Voices[n].ADSR.DecayRate), \
SFVAR(Voices[n].ADSR.SustainRate), \
SFVAR(Voices[n].ADSR.ReleaseRate), \
\
SFVAR(Voices[n].ADSR.SustainLevel)
SFVOICE(0),
SFVOICE(1),
SFVOICE(2),
SFVOICE(3),
SFVOICE(4),
SFVOICE(5),
SFVOICE(6),
SFVOICE(7),
SFVOICE(8),
SFVOICE(9),
SFVOICE(10),
SFVOICE(11),
SFVOICE(12),
SFVOICE(13),
SFVOICE(14),
SFVOICE(15),
SFVOICE(16),
SFVOICE(17),
SFVOICE(18),
SFVOICE(19),
SFVOICE(20),
SFVOICE(21),
SFVOICE(22),
SFVOICE(23),
#undef SFVOICE
SFVAR(NoiseDivider),
SFVAR(NoiseCounter),
SFVAR(LFSR),
SFVAR(FM_Mode),
SFVAR(Noise_Mode),
SFVAR(Reverb_Mode),
SFVAR(ReverbWA),
SFSWEEP(GlobalSweep[0]),
SFSWEEP(GlobalSweep[1]),
SFARRAY32(ReverbVol, sizeof(ReverbVol) / sizeof(ReverbVol[0])),
SFARRAY32(CDVol, sizeof(CDVol) / sizeof(CDVol[0])),
SFARRAY32(ExternVol, sizeof(ExternVol) / sizeof(ExternVol[0])),
SFVAR(IRQAddr),
SFVAR(RWAddr),
SFVAR(SPUControl),
SFVAR(VoiceOn),
SFVAR(VoiceOff),
SFVAR(BlockEnd),
SFVAR(CWA),
SFARRAY16(Regs, sizeof(Regs) / sizeof(Regs[0])),
SFARRAY16(AuxRegs, sizeof(AuxRegs) / sizeof(AuxRegs[0])),
SFARRAY16(&RDSB[0][0], sizeof(RDSB) / sizeof(RDSB[0][0])),
SFVAR(RvbResPos),
SFARRAY16(&RUSB[0][0], sizeof(RUSB) / sizeof(RUSB[0][0])),
SFVAR(ReverbCur),
SFVAR(IRQAsserted),
SFVAR(clock_divider),
SFARRAY16(SPURAM, 524288 / sizeof(uint16)),
SFEND
};
#undef SFSWEEP
int ret = 1;
ret &= MDFNSS_StateAction(sm, load, data_only, StateRegs, "SPU");
if(load)
{
for(unsigned i = 0; i < 24; i++)
{
Voices[i].DecodeReadPos &= 0x1F;
Voices[i].DecodeWritePos &= 0x1F;
Voices[i].CurAddr &= 0x3FFFF;
Voices[i].StartAddr &= 0x3FFFF;
Voices[i].LoopAddr &= 0x3FFFF;
}
if(clock_divider <= 0 || clock_divider > 768)
clock_divider = 768;
RWAddr &= 0x3FFFF;
CWA &= 0x1FF;
ReverbWA &= 0x3FFFF;
ReverbCur &= 0x3FFFF;
RvbResPos &= 0x3F;
IRQ_Assert(IRQ_SPU, IRQAsserted);
}
return(ret);
}
uint16 PS_SPU::PeekSPURAM(uint32 address)
{
return(SPURAM[address & 0x3FFFF]);
}
void PS_SPU::PokeSPURAM(uint32 address, uint16 value)
{
SPURAM[address & 0x3FFFF] = value;
}
uint32 PS_SPU::GetRegister(unsigned int which, char *special, const uint32 special_len)
{
if(which >= 0x8000)
{
unsigned int v = (which - 0x8000) >> 8;
switch((which & 0xFF) | 0x8000)
{
case GSREG_V0_VOL_CTRL_L:
return Regs[v * 8 + 0x0];
case GSREG_V0_VOL_CTRL_R:
return Regs[v * 8 + 0x1];
case GSREG_V0_VOL_L:
return Voices[v].Sweep[0].ReadVolume() & 0xFFFF;
case GSREG_V0_VOL_R:
return Voices[v].Sweep[1].ReadVolume() & 0xFFFF;
case GSREG_V0_PITCH:
return Voices[v].Pitch;
case GSREG_V0_STARTADDR:
return Voices[v].StartAddr;
case GSREG_V0_ADSR_CTRL:
return Voices[v].ADSRControl;
case GSREG_V0_ADSR_LEVEL:
return Voices[v].ADSR.EnvLevel;
case GSREG_V0_LOOP_ADDR:
return Voices[v].LoopAddr;
case GSREG_V0_READ_ADDR:
return Voices[v].CurAddr;
}
}
else if (which >= 18 && which <= 49)
return ReverbRegs[which - GSREG_FB_SRC_A];
else switch(which)
{
case GSREG_SPUCONTROL:
return SPUControl;
case GSREG_FM_ON:
return FM_Mode;
case GSREG_NOISE_ON:
return Noise_Mode;
case GSREG_REVERB_ON:
return Reverb_Mode;
case GSREG_CDVOL_L:
return (uint16)CDVol[0];
case GSREG_CDVOL_R:
return (uint16)CDVol[1];
case GSREG_MAINVOL_CTRL_L:
return Regs[0xC0];
case GSREG_MAINVOL_CTRL_R:
return Regs[0xC1];
case GSREG_MAINVOL_L:
return GlobalSweep[0].ReadVolume() & 0xFFFF;
case GSREG_MAINVOL_R:
return GlobalSweep[1].ReadVolume() & 0xFFFF;
case GSREG_RVBVOL_L:
return (uint16)ReverbVol[0];
case GSREG_RVBVOL_R:
return (uint16)ReverbVol[1];
case GSREG_RWADDR:
return RWAddr;
case GSREG_IRQADDR:
return IRQAddr;
case GSREG_REVERBWA:
return ReverbWA >> 2;
case GSREG_VOICEON:
return VoiceOn;
case GSREG_VOICEOFF:
return VoiceOff;
case GSREG_BLOCKEND:
return BlockEnd;
}
return 0xDEADBEEF;
}
void PS_SPU::SetRegister(unsigned int which, uint32 value)
{
if(which >= GSREG_FB_SRC_A && which <= GSREG_IN_COEF_R)
ReverbRegs[which - GSREG_FB_SRC_A] = value;
else switch(which)
{
case GSREG_SPUCONTROL:
SPUControl = value;
break;
case GSREG_FM_ON:
FM_Mode = value & 0xFFFFFF;
break;
case GSREG_NOISE_ON:
Noise_Mode = value & 0xFFFFFF;
break;
case GSREG_REVERB_ON:
Reverb_Mode = value & 0xFFFFFF;
break;
case GSREG_CDVOL_L:
CDVol[0] = (int16)value;
break;
case GSREG_CDVOL_R:
CDVol[1] = (int16)value;
break;
case GSREG_MAINVOL_CTRL_L:
Regs[0xC0] = value;
GlobalSweep[0].WriteControl(value);
//GlobalSweep[0].Control = value;
break;
case GSREG_MAINVOL_CTRL_R:
Regs[0xC1] = value;
GlobalSweep[1].WriteControl(value);
//GlobalSweep[1].Control = value;
break;
case GSREG_MAINVOL_L:
GlobalSweep[0].WriteVolume(value);
break;
case GSREG_MAINVOL_R:
GlobalSweep[1].WriteVolume(value);
break;
case GSREG_RVBVOL_L:
ReverbVol[0] = (int16)value;
break;
case GSREG_RVBVOL_R:
ReverbVol[1] = (int16)value;
break;
case GSREG_RWADDR:
RWAddr = value & 0x3FFFF;
break;
case GSREG_IRQADDR:
IRQAddr = value & 0x3FFFC;
break;
//
// REVERB_WA
//
case GSREG_VOICEON:
VoiceOn = value & 0xFFFFFF;
break;
case GSREG_VOICEOFF:
VoiceOff = value & 0xFFFFFF;
break;
case GSREG_BLOCKEND:
BlockEnd = value & 0xFFFFFF;
break;
}
}