mirror of https://github.com/PCSX2/pcsx2.git
1316 lines
36 KiB
C++
1316 lines
36 KiB
C++
//GiGaHeRz's SPU2 Driver
|
|
//Copyright (c) 2003-2008, David Quintana <gigaherz@gmail.com>
|
|
//
|
|
//This library 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 Foundation; either
|
|
//version 2.1 of the License, or (at your option) any later version.
|
|
//
|
|
//This library 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 this library; if not, write to the Free Software
|
|
//Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
//
|
|
|
|
// [Air] Notes ----->
|
|
// Adding 'static' to the __forceinline methods hints to the linker that it need not
|
|
// actually include procedural versions of the methods in the DLL. Under normal circumstances
|
|
// the compiler will still generate the procedures even though they are never used (the inline
|
|
// code is used instead). Using static reduced the size of my generated .DLL by a few KB.
|
|
// (doesn't really make anything faster, but eh... whatever :)
|
|
//
|
|
#include "spu2.h"
|
|
|
|
#include <assert.h>
|
|
#include <math.h>
|
|
#include <float.h>
|
|
#include "lowpass.h"
|
|
|
|
extern void spdif_update();
|
|
|
|
void ADMAOutLogWrite(void *lpData, u32 ulSize);
|
|
|
|
extern void VoiceStop(int core,int vc);
|
|
|
|
double pow_2_31 = pow(2.0,31.0);
|
|
|
|
LPF_data L,R;
|
|
|
|
extern u32 core;
|
|
|
|
u32 core, voice;
|
|
|
|
extern u8 callirq;
|
|
|
|
double srate_pv=1.0;
|
|
|
|
extern u32 PsxRates[160];
|
|
|
|
|
|
void InitADSR() // INIT ADSR
|
|
{
|
|
for (int i=0; i<(32+128); i++)
|
|
{
|
|
int shift=(i-32)>>2;
|
|
__int64 rate=(i&3)+4;
|
|
if (shift<0)
|
|
{
|
|
rate>>=-shift;
|
|
} else
|
|
{
|
|
rate<<=shift;
|
|
}
|
|
PsxRates[i]=(int)min(rate,0x3fffffff);
|
|
}
|
|
}
|
|
|
|
#define VOL(x) (((s32)x)) //24.8 volume
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////////////
|
|
/////////////////////////////////////////////////////////////////////////////////////////
|
|
// //
|
|
|
|
const s32 f[5][2] ={{ 0, 0 },
|
|
{ 60, 0 },
|
|
{ 115, -52 },
|
|
{ 98, -55 },
|
|
{ 122, -60 }};
|
|
|
|
static s16 __forceinline XA_decode(s32 pred1, s32 pred2, s32 shift, s32& prev1, s32& prev2, s32 data)
|
|
{
|
|
s32 pcm = data>>shift;
|
|
pcm+=((pred1*prev1)+(pred2*prev2))>>6;
|
|
if(pcm> 32767) pcm= 32767;
|
|
if(pcm<-32768) pcm=-32768;
|
|
prev2=prev1;
|
|
prev1=pcm;
|
|
return (s16)pcm;
|
|
}
|
|
|
|
static s16 __forceinline XA_decode_block(s16* buffer, const s16* block, s32& prev1, s32& prev2)
|
|
{
|
|
s32 data=*block;
|
|
s32 Shift = ((data>> 0)&0xF)+16;
|
|
s32 Predict1 = f[(data>> 4)&0xF][0];
|
|
s32 Predict2 = f[(data>> 4)&0xF][1];
|
|
|
|
for(int i=0;i<7;i++)
|
|
{
|
|
s32 SampleData=block[i+1];
|
|
|
|
*(buffer++) = XA_decode(Predict1, Predict2, Shift, prev1, prev2, (SampleData<<28)&0xF0000000);
|
|
*(buffer++) = XA_decode(Predict1, Predict2, Shift, prev1, prev2, (SampleData<<24)&0xF0000000);
|
|
*(buffer++) = XA_decode(Predict1, Predict2, Shift, prev1, prev2, (SampleData<<20)&0xF0000000);
|
|
*(buffer++) = XA_decode(Predict1, Predict2, Shift, prev1, prev2, (SampleData<<16)&0xF0000000);
|
|
}
|
|
|
|
return data;
|
|
}
|
|
|
|
static s16 __forceinline XA_decode_block_fast(s16* buffer, const s16* block, s32& prev1, s32& prev2)
|
|
{
|
|
s32 header = *block;
|
|
s32 shift = ((header>> 0)&0xF)+16;
|
|
s32 pred1 = f[(header>> 4)&0xF][0];
|
|
s32 pred2 = f[(header>> 4)&0xF][1];
|
|
|
|
const s8* blockbytes = (s8*)&block[1];
|
|
|
|
for(int i=0; i<14; i++, blockbytes++)
|
|
{
|
|
s32 pcm, pcm2;
|
|
{
|
|
s32 data = ((*blockbytes)<<28) & 0xF0000000;
|
|
pcm = data>>shift;
|
|
pcm+=((pred1*prev1)+(pred2*prev2))>>6;
|
|
if(pcm> 32767) pcm= 32767;
|
|
if(pcm<-32768) pcm=-32768;
|
|
*(buffer++) = pcm;
|
|
}
|
|
|
|
//prev2=prev1;
|
|
//prev1=pcm;
|
|
|
|
{
|
|
s32 data = ((*blockbytes)<<24) & 0xF0000000;
|
|
pcm2 = data>>shift;
|
|
pcm2+=((pred1*pcm)+(pred2*prev1))>>6;
|
|
if(pcm2> 32767) pcm2= 32767;
|
|
if(pcm2<-32768) pcm2=-32768;
|
|
*(buffer++) = pcm2;
|
|
}
|
|
|
|
prev2=pcm;
|
|
prev1=pcm2;
|
|
}
|
|
|
|
return header;
|
|
}
|
|
|
|
static s16 __forceinline XA_decode_block_unsaturated(s16* buffer, const s16* block, s32& prev1, s32& prev2)
|
|
{
|
|
s32 header = *block;
|
|
s32 shift = ((header>> 0)&0xF)+16;
|
|
s32 pred1 = f[(header>> 4)&0xF][0];
|
|
s32 pred2 = f[(header>> 4)&0xF][1];
|
|
|
|
const s8* blockbytes = (s8*)&block[1];
|
|
|
|
for(int i=0; i<14; i++, blockbytes++)
|
|
{
|
|
s32 pcm, pcm2;
|
|
{
|
|
s32 data = ((*blockbytes)<<28) & 0xF0000000;
|
|
pcm = data>>shift;
|
|
pcm+=((pred1*prev1)+(pred2*prev2))>>6;
|
|
// [Air] : Fast method, no saturation is performed.
|
|
*(buffer++) = pcm;
|
|
}
|
|
|
|
{
|
|
s32 data = ((*blockbytes)<<24) & 0xF0000000;
|
|
pcm2 = data>>shift;
|
|
pcm2+=((pred1*pcm)+(pred2*prev1))>>6;
|
|
// [Air] : Fast method, no saturation is performed.
|
|
*(buffer++) = pcm2;
|
|
}
|
|
|
|
prev2=pcm;
|
|
prev1=pcm2;
|
|
}
|
|
|
|
return header;
|
|
}
|
|
|
|
static void __forceinline IncrementNextA( const V_Core& thiscore, V_Voice& vc )
|
|
{
|
|
if((vc.NextA==thiscore.IRQA)&&(thiscore.IRQEnable)) {
|
|
ConLog(" * SPU2: IRQ Called (IRQ passed).\n");
|
|
Spdif.Info=4<<core;
|
|
SetIrqCall();
|
|
}
|
|
|
|
vc.NextA++;
|
|
vc.NextA&=0xFFFFF;
|
|
}
|
|
|
|
|
|
static void __fastcall GetNextDataBuffered( V_Core& thiscore, V_Voice& vc, s32& Data)
|
|
{
|
|
//static s32 pcm=0;
|
|
s16 data=0;
|
|
|
|
if (vc.SCurrent>=28)
|
|
{
|
|
if(vc.LoopEnd)
|
|
{
|
|
if(vc.Loop)
|
|
{
|
|
vc.NextA=vc.LoopStartA;
|
|
}
|
|
else
|
|
{
|
|
if(MsgVoiceOff) ConLog(" * SPU2: Voice Off by EndPoint: %d \n", voice);
|
|
VoiceStop(core,voice);
|
|
thiscore.Regs.ENDX|=1<<voice;
|
|
vc.lastStopReason = 1;
|
|
}
|
|
}
|
|
|
|
// [Air]: Original ADPCM decoder.
|
|
//data = XA_decode_block(vc.SBuffer,GetMemPtr(vc.NextA&0xFFFFF), vc.Prev1, vc.Prev2);
|
|
|
|
// [Air]: Testing of a new saturated decoder. (benchmark needed)
|
|
// My gut tells me that this should be faster, but you never can tell with these types
|
|
// of things. Benchmark it against the original and see what you think.
|
|
|
|
//data = XA_decode_block_fast(vc.SBuffer,GetMemPtr(vc.NextA&0xFFFFF), vc.Prev1, vc.Prev2);
|
|
|
|
// [Air]: Testing use of a new unsaturated decoder. (benchmark needed)
|
|
// Chances are the saturation isn't needed, but for a very few exception games.
|
|
// This is definitely faster than either of the above versions, but the question is by how
|
|
// much (biggest impact will be on games like Xenosaga2, which use lots of SPU2 voices).
|
|
// If the speed boost is worth it then maybe it should be added as a speedhack option
|
|
// in the spu2ghz config.
|
|
|
|
data = XA_decode_block_unsaturated(vc.SBuffer,GetMemPtr(vc.NextA&0xFFFFF), vc.Prev1, vc.Prev2);
|
|
|
|
vc.LoopEnd = (data>> 8)&1;
|
|
vc.Loop = (data>> 9)&1;
|
|
vc.LoopStart= (data>>10)&1;
|
|
vc.SCurrent = 0;
|
|
vc.FirstBlock = 0;
|
|
|
|
if( vc.LoopStart && !vc.LoopMode )
|
|
{
|
|
vc.LoopStartA=vc.NextA;
|
|
}
|
|
|
|
IncrementNextA( thiscore, vc );
|
|
}
|
|
|
|
Data=vc.SBuffer[vc.SCurrent];
|
|
|
|
if((vc.SCurrent&3)==3)
|
|
{
|
|
IncrementNextA( thiscore, vc );
|
|
}
|
|
vc.SCurrent++;
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////////////
|
|
/////////////////////////////////////////////////////////////////////////////////////////
|
|
// //
|
|
|
|
const int InvExpOffsets[] = { 0,4,6,8,9,10,11,12 };
|
|
|
|
static void __forceinline CalculateADSR( V_Voice& vc )
|
|
{
|
|
V_ADSR& env(vc.ADSR);
|
|
u32 SLevel=((u32)env.Sl)<<27;
|
|
u32 off=InvExpOffsets[(env.Value>>28)&7];
|
|
|
|
if(env.Releasing)
|
|
{
|
|
if((env.Phase>0)&&(env.Phase<5))
|
|
{
|
|
env.Phase=5;
|
|
}
|
|
}
|
|
|
|
switch (env.Phase)
|
|
{
|
|
case 1: // attack
|
|
if (env.Am) // pseudo exponential
|
|
{
|
|
if (env.Value<0x60000000) // below 75%
|
|
{
|
|
env.Value+=PsxRates[(env.Ar^0x7f)-0x10+32];
|
|
}
|
|
else // above 75%
|
|
{
|
|
env.Value+=PsxRates[(env.Ar^0x7f)-0x18+32];
|
|
}
|
|
}
|
|
else // linear
|
|
{
|
|
env.Value+=PsxRates[(env.Ar^0x7f)-0x10+32];
|
|
}
|
|
|
|
if (env.Value>=0x7fffffff)
|
|
{
|
|
env.Phase++;
|
|
env.Value=0x7fffffff;
|
|
}
|
|
|
|
break;
|
|
|
|
case 2: // decay
|
|
env.Value-=PsxRates[((env.Dr^0x1f)<<2)-0x18+off+32];
|
|
|
|
if ((env.Value<=SLevel) || (env.Value>0x7fffffff))
|
|
{
|
|
if (env.Value>0x7fffffff) env.Value = 0;
|
|
else env.Value = SLevel;
|
|
env.Phase++;
|
|
}
|
|
|
|
break;
|
|
|
|
case 3: // sustain
|
|
if (env.Sm&2) // decreasing
|
|
{
|
|
if (env.Sm&4) // exponential
|
|
{
|
|
env.Value-=PsxRates[(env.Sr^0x7f)-0x1b+off+32];
|
|
}
|
|
else // linear
|
|
{
|
|
env.Value-=PsxRates[(env.Sr^0x7f)-0xf+32];
|
|
}
|
|
}
|
|
else // increasing
|
|
{
|
|
if (env.Sm&4) // pseudo exponential
|
|
{
|
|
if (env.Value<0x60000000) // below 75%
|
|
{
|
|
env.Value+=PsxRates[(env.Sr^0x7f)-0x10+32];
|
|
}
|
|
else // above 75%
|
|
{
|
|
env.Value+=PsxRates[(env.Sr^0x7f)-0x18+32];
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// linear
|
|
|
|
env.Value+=PsxRates[(env.Sr^0x7f)-0x10+32];
|
|
}
|
|
}
|
|
|
|
if ((env.Value>=0x7fffffff) || (env.Value==0))
|
|
{
|
|
env.Value=(env.Sm&2)?0:0x7fffffff;
|
|
env.Phase++;
|
|
}
|
|
|
|
break;
|
|
|
|
case 4: // sustain end
|
|
env.Value=(env.Sm&2)?0:0x7fffffff;
|
|
if(env.Value==0)
|
|
env.Phase=6;
|
|
break;
|
|
|
|
case 5: // release
|
|
{
|
|
if (env.Rm) // exponential
|
|
{
|
|
env.Value-=PsxRates[((env.Rr^0x1f)<<2)-0x18+off+32];
|
|
}
|
|
else // linear
|
|
{
|
|
env.Value-=PsxRates[((env.Rr^0x1f)<<2)-0xc+32];
|
|
}
|
|
}
|
|
|
|
if ((env.Value>0x7fffffff) || (env.Value==0))
|
|
{
|
|
env.Phase++;
|
|
env.Value=0;
|
|
}
|
|
|
|
break;
|
|
|
|
case 6: // release end
|
|
env.Value=0;
|
|
break;
|
|
|
|
//jNO_DEFAULT
|
|
}
|
|
|
|
if (env.Phase==6) {
|
|
if(MsgVoiceOff) ConLog(" * SPU2: Voice Off by ADSR: %d \n", voice);
|
|
VoiceStop(core,voice);
|
|
Cores[core].Regs.ENDX|=(1<<voice);
|
|
env.Phase=0;
|
|
vc.lastStopReason = 2;
|
|
}
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////////////
|
|
/////////////////////////////////////////////////////////////////////////////////////////
|
|
// //
|
|
static void __forceinline GetNoiseValues(s32& VD)
|
|
{
|
|
static s32 Seed = 0x41595321;
|
|
|
|
if(Seed&0x100) VD =(s32)((Seed&0xff)<<8);
|
|
else if(!(Seed&0xffff)) VD = (s32)0x8000;
|
|
else VD = (s32)0x7fff;
|
|
|
|
__asm {
|
|
MOV eax,Seed
|
|
ROR eax,5
|
|
XOR eax,0x9a
|
|
MOV ebx,eax
|
|
ROL eax,2
|
|
ADD eax,ebx
|
|
XOR eax,ebx
|
|
ROR eax,3
|
|
MOV Seed,eax
|
|
}
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////////////
|
|
/////////////////////////////////////////////////////////////////////////////////////////
|
|
// //
|
|
|
|
void LowPassFilterInit()
|
|
{
|
|
LPF_init(&L,11000,48000);
|
|
LPF_init(&R,11000,48000);
|
|
}
|
|
|
|
void LowPass(s32& VL, s32& VR)
|
|
{
|
|
VL = (s32)(LPF(&L,(VL)/pow_2_31)*pow_2_31);
|
|
VR = (s32)(LPF(&R,(VR)/pow_2_31)*pow_2_31);
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////////////
|
|
/////////////////////////////////////////////////////////////////////////////////////////
|
|
// //
|
|
|
|
static void __fastcall GetVoiceValues(V_Core& thiscore, V_Voice& vc, s32& Value)
|
|
{
|
|
s64 Data=0;
|
|
s32 DT=0;
|
|
|
|
// [Air] : Put a scope on the pitch variable, which should help it get optimized to a
|
|
// register.
|
|
{
|
|
s32 pitch;
|
|
|
|
// [Air] : re-ordered comparisons: Modulated is much more likely to be zero than voice,
|
|
// and so the way it was before it's have to check both voice and modulated values
|
|
// most of the time. Now it'll just check Modulated and short-circut past the voice
|
|
// check (not that it amounts to much, but eh every little bit helps).
|
|
if( (vc.Modulated==0) || (voice==0) )
|
|
pitch=vc.Pitch;
|
|
else
|
|
pitch=(vc.Pitch*(32768 + abs(thiscore.Voices[voice-1].OutX)))>>15;
|
|
|
|
vc.SP+=pitch;
|
|
}
|
|
|
|
while(vc.SP>=4096)
|
|
{
|
|
GetNextDataBuffered( thiscore, vc, DT );
|
|
|
|
vc.PV4=vc.PV3;
|
|
vc.PV3=vc.PV2;
|
|
vc.PV2=vc.PV1;
|
|
vc.PV1=DT<<16; //32bit processing
|
|
|
|
vc.SP-=4096;
|
|
}
|
|
|
|
CalculateADSR( vc );
|
|
|
|
if(vc.ADSR.Phase==0)
|
|
{
|
|
Value=0;
|
|
vc.OutX=0;
|
|
}
|
|
else
|
|
{
|
|
// [Air]: if SP is zero then we landed perfectly on a sample source, no
|
|
// interpolation necessary (besides being a little faster this is important
|
|
// too, since the interpolator will pick the wrong sample to mix otherwise).
|
|
|
|
if(Interpolation==0 || vc.SP == 0)
|
|
{
|
|
Data = vc.PV1;
|
|
}
|
|
else if(Interpolation==1) //linear
|
|
{
|
|
// [Air]: Inverted the interpolation delta. The old way was generating
|
|
// inverted waveforms.
|
|
s64 t0 = vc.PV2 - vc.PV1;
|
|
s64 t1 = vc.PV1;
|
|
Data = (((t0*vc.SP)>>12) + t1);
|
|
}
|
|
else // if(Interpolation==2) //must be cubic
|
|
{
|
|
s64 a0 = vc.PV1 - vc.PV2 - vc.PV4 + vc.PV3;
|
|
s64 a1 = vc.PV4 - vc.PV3 - a0;
|
|
s64 a2 = vc.PV1 - vc.PV4;
|
|
s64 a3 = vc.PV2;
|
|
s64 mu = 4096-vc.SP;
|
|
|
|
s64 t0 = ((a0 )*mu)>>18;
|
|
s64 t1 = ((t0+a1)*mu)>>18;
|
|
s64 t2 = ((t1+a2)*mu)>>18;
|
|
s64 t3 = ((t2+a3));
|
|
|
|
Data = t3;
|
|
}
|
|
|
|
Value=(s32)((Data*vc.ADSR.Value)>>48); //32bit ADSR + convert to 16bit
|
|
|
|
// [Air]: Moved abs() to the modulation code above, so that the abs conditionals are
|
|
// only run in select cases where modulation is active.
|
|
vc.OutX=Value;
|
|
}
|
|
}
|
|
|
|
// [Air]: Noise values need to be mixed without going through interpolation, since it
|
|
// can wreak havoc on the noise (causing muffling or popping)
|
|
static void __fastcall GetNoiseValues(V_Core& thiscore, V_Voice& vc, s32& Value)
|
|
{
|
|
s64 Data=0;
|
|
s32 DT=0;
|
|
|
|
{
|
|
s32 pitch;
|
|
|
|
if( (vc.Modulated==0) || (voice==0) )
|
|
pitch=vc.Pitch;
|
|
else
|
|
pitch=(vc.Pitch*(32768 + abs(thiscore.Voices[voice-1].OutX)))>>15;
|
|
|
|
vc.SP+=pitch;
|
|
}
|
|
|
|
while(vc.SP>=4096)
|
|
{
|
|
GetNoiseValues(DT);
|
|
vc.SP-=4096;
|
|
}
|
|
|
|
Data = DT<<16; //32bit processing
|
|
|
|
CalculateADSR( vc );
|
|
|
|
if(vc.ADSR.Phase==0)
|
|
{
|
|
Value=0;
|
|
vc.OutX=0;
|
|
}
|
|
else
|
|
{
|
|
Value=(s32)((Data*vc.ADSR.Value)>>48); //32bit ADSR + convert to 16bit
|
|
vc.OutX=Value;
|
|
}
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////////////
|
|
/////////////////////////////////////////////////////////////////////////////////////////
|
|
// //
|
|
|
|
void __fastcall ReadInput(V_Core& thiscore, s32& PDataL,s32& PDataR)
|
|
{
|
|
if((thiscore.AutoDMACtrl&(core+1))==(core+1))
|
|
{
|
|
s32 tl,tr;
|
|
|
|
if((core==1)&&((PlayMode&8)==8))
|
|
{
|
|
thiscore.InputPos&=~1;
|
|
|
|
//CDDA mode
|
|
#ifdef PCM24_S1_INTERLEAVE
|
|
*PDataL=*(((s32*)(thiscore.ADMATempBuffer+(thiscore.InputPos<<1))));
|
|
*PDataR=*(((s32*)(thiscore.ADMATempBuffer+(thiscore.InputPos<<1)+2)));
|
|
#else
|
|
s32 *pl=(s32*)&(thiscore.ADMATempBuffer[thiscore.InputPos]);
|
|
s32 *pr=(s32*)&(thiscore.ADMATempBuffer[thiscore.InputPos+0x200]);
|
|
PDataL=*pl;
|
|
PDataR=*pr;
|
|
#endif
|
|
|
|
PDataL>>=4; //give 16.8 data
|
|
PDataR>>=4;
|
|
|
|
thiscore.InputPos+=2;
|
|
if((thiscore.InputPos==0x100)||(thiscore.InputPos>=0x200)) {
|
|
thiscore.AdmaInProgress=0;
|
|
if(thiscore.InputDataLeft>=0x200)
|
|
{
|
|
u8 k=thiscore.InputDataLeft>=thiscore.InputDataProgress;
|
|
|
|
#ifdef PCM24_S1_INTERLEAVE
|
|
AutoDMAReadBuffer(core,1);
|
|
#else
|
|
AutoDMAReadBuffer(core,0);
|
|
#endif
|
|
thiscore.AdmaInProgress=1;
|
|
|
|
thiscore.TSA=(core<<10)+thiscore.InputPos;
|
|
|
|
if (thiscore.InputDataLeft<0x200)
|
|
{
|
|
FileLog("[%10d] AutoDMA%c block end.\n",Cycles, (core==0)?'4':'7');
|
|
|
|
if(thiscore.InputDataLeft>0)
|
|
{
|
|
if(MsgAutoDMA) ConLog("WARNING: adma buffer didn't finish with a whole block!!\n");
|
|
}
|
|
thiscore.InputDataLeft=0;
|
|
thiscore.DMAICounter=1;
|
|
}
|
|
}
|
|
thiscore.InputPos&=0x1ff;
|
|
}
|
|
|
|
}
|
|
else if((core==0)&&((PlayMode&4)==4))
|
|
{
|
|
thiscore.InputPos&=~1;
|
|
|
|
s32 *pl=(s32*)&(thiscore.ADMATempBuffer[thiscore.InputPos]);
|
|
s32 *pr=(s32*)&(thiscore.ADMATempBuffer[thiscore.InputPos+0x200]);
|
|
PDataL=*pl;
|
|
PDataR=*pr;
|
|
|
|
thiscore.InputPos+=2;
|
|
if(thiscore.InputPos>=0x200) {
|
|
thiscore.AdmaInProgress=0;
|
|
if(thiscore.InputDataLeft>=0x200)
|
|
{
|
|
u8 k=thiscore.InputDataLeft>=thiscore.InputDataProgress;
|
|
|
|
AutoDMAReadBuffer(core,0);
|
|
|
|
thiscore.AdmaInProgress=1;
|
|
|
|
thiscore.TSA=(core<<10)+thiscore.InputPos;
|
|
|
|
if (thiscore.InputDataLeft<0x200)
|
|
{
|
|
FileLog("[%10d] Spdif AutoDMA%c block end.\n",Cycles, (core==0)?'4':'7');
|
|
|
|
if(thiscore.InputDataLeft>0)
|
|
{
|
|
if(MsgAutoDMA) ConLog("WARNING: adma buffer didn't finish with a whole block!!\n");
|
|
}
|
|
thiscore.InputDataLeft=0;
|
|
thiscore.DMAICounter=1;
|
|
}
|
|
}
|
|
thiscore.InputPos&=0x1ff;
|
|
}
|
|
|
|
}
|
|
else
|
|
{
|
|
if((core==1)&&((PlayMode&2)!=0))
|
|
{
|
|
tl=0;
|
|
tr=0;
|
|
}
|
|
else
|
|
{
|
|
// Using the temporary buffer because this area gets overwritten by some other code.
|
|
//*PDataL=(s32)*(s16*)(spu2mem+0x2000+(core<<10)+thiscore.InputPos);
|
|
//*PDataR=(s32)*(s16*)(spu2mem+0x2200+(core<<10)+thiscore.InputPos);
|
|
|
|
tl=(s32)thiscore.ADMATempBuffer[thiscore.InputPos];
|
|
tr=(s32)thiscore.ADMATempBuffer[thiscore.InputPos+0x200];
|
|
|
|
}
|
|
|
|
PDataL=tl;
|
|
PDataR=tr;
|
|
|
|
thiscore.InputPos++;
|
|
if((thiscore.InputPos==0x100)||(thiscore.InputPos>=0x200)) {
|
|
thiscore.AdmaInProgress=0;
|
|
if(thiscore.InputDataLeft>=0x200)
|
|
{
|
|
u8 k=thiscore.InputDataLeft>=thiscore.InputDataProgress;
|
|
|
|
AutoDMAReadBuffer(core,0);
|
|
|
|
thiscore.AdmaInProgress=1;
|
|
|
|
thiscore.TSA=(core<<10)+thiscore.InputPos;
|
|
|
|
if (thiscore.InputDataLeft<0x200)
|
|
{
|
|
FileLog("[%10d] AutoDMA%c block end.\n",Cycles, (core==0)?'4':'7');
|
|
|
|
thiscore.AutoDMACtrl |=~3;
|
|
|
|
if(thiscore.InputDataLeft>0)
|
|
{
|
|
if(MsgAutoDMA) ConLog("WARNING: adma buffer didn't finish with a whole block!!\n");
|
|
}
|
|
thiscore.InputDataLeft=0;
|
|
thiscore.DMAICounter=1;
|
|
}
|
|
}
|
|
thiscore.InputPos&=0x1ff;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
PDataL=0;
|
|
PDataR=0;
|
|
}
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////////////
|
|
/////////////////////////////////////////////////////////////////////////////////////////
|
|
// //
|
|
|
|
void __fastcall ReadInputPV(V_Core& thiscore, s32& ValL,s32& ValR)
|
|
{
|
|
s32 DL=0, DR=0;
|
|
|
|
u32 pitch=AutoDMAPlayRate[core];
|
|
|
|
if(pitch==0) pitch=48000;
|
|
|
|
thiscore.ADMAPV+=pitch;
|
|
while(thiscore.ADMAPV>=48000)
|
|
{
|
|
ReadInput(thiscore, DL,DR);
|
|
thiscore.ADMAPV-=48000;
|
|
thiscore.ADMAPL=DL;
|
|
thiscore.ADMAPR=DR;
|
|
}
|
|
|
|
ValL=thiscore.ADMAPL;
|
|
ValR=thiscore.ADMAPR;
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////////////
|
|
/////////////////////////////////////////////////////////////////////////////////////////
|
|
// //
|
|
|
|
static void __forceinline UpdateVolume(V_Volume& Vol)
|
|
{
|
|
s32 NVal;
|
|
|
|
// TIMINGS ARE FAKE!!! Need to investigate.
|
|
|
|
int reverse_phase = Vol.Mode&1;
|
|
int exponential = Vol.Mode&4;
|
|
int decrement = Vol.Mode&2;
|
|
int slide_enable = Vol.Mode&8;
|
|
|
|
if (!slide_enable) return;
|
|
|
|
NVal=Vol.Value;
|
|
if(reverse_phase) NVal = -NVal;
|
|
|
|
if (decrement) { // Decrement
|
|
|
|
if(exponential)
|
|
{
|
|
NVal=NVal * Vol.Increment >> 7;
|
|
}
|
|
else
|
|
{
|
|
NVal-=Vol.Increment;
|
|
}
|
|
NVal-=((32768*5)>>(Vol.Increment));
|
|
if (NVal<0) {
|
|
Vol.Value=0;
|
|
Vol.Mode=0;
|
|
}
|
|
else Vol.Value=NVal & 0xffff;
|
|
}
|
|
else { // Increment
|
|
if(exponential)
|
|
{
|
|
int T = Vol.Increment>>(NVal>>12);
|
|
NVal+=T;
|
|
}
|
|
else
|
|
{
|
|
NVal+=Vol.Increment;
|
|
}
|
|
}
|
|
|
|
if((NVal<0)||(NVal>0x7fff))
|
|
{
|
|
NVal=decrement?0:0x7fff;
|
|
Vol.Mode=0; // disable slide
|
|
}
|
|
|
|
if(reverse_phase) NVal = -NVal;
|
|
|
|
Vol.Value=NVal;
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////////////
|
|
/////////////////////////////////////////////////////////////////////////////////////////
|
|
// //
|
|
|
|
static s32 __forceinline clamp(s32 x)
|
|
{
|
|
if (x>0x00ffffff) return 0x00ffffff;
|
|
if (x<0xff000000) return 0xff000000;
|
|
return x;
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////////////
|
|
/////////////////////////////////////////////////////////////////////////////////////////
|
|
// //
|
|
|
|
static void DoReverb( V_Core& thiscore, s32& OutL, s32& OutR, s32 InL, s32 InR)
|
|
{
|
|
static s32 INPUT_SAMPLE_L,INPUT_SAMPLE_R;
|
|
static s32 OUTPUT_SAMPLE_L,OUTPUT_SAMPLE_R;
|
|
|
|
if(!(thiscore.FxEnable&&EffectsEnabled))
|
|
{
|
|
OUTPUT_SAMPLE_L=0;
|
|
OUTPUT_SAMPLE_R=0;
|
|
}
|
|
else if((Cycles&1)==0)
|
|
{
|
|
INPUT_SAMPLE_L=InL;
|
|
INPUT_SAMPLE_R=InR;
|
|
}
|
|
else
|
|
{
|
|
/////////////////////////////////////////////////////////////////////////////////////////
|
|
/////////////////////////////////////////////////////////////////////////////////////////
|
|
/////////////////////////////////////////////////////////////////////////////////////////
|
|
s32 IIR_INPUT_A0,IIR_INPUT_A1,IIR_INPUT_B0,IIR_INPUT_B1;
|
|
s32 ACC0,ACC1;
|
|
s32 FB_A0,FB_A1,FB_B0,FB_B1;
|
|
s32 buffsize=thiscore.EffectsEndA-thiscore.EffectsStartA+1;
|
|
|
|
if(buffsize<0)
|
|
{
|
|
buffsize = thiscore.EffectsEndA;
|
|
thiscore.EffectsEndA=thiscore.EffectsStartA;
|
|
thiscore.EffectsStartA=buffsize;
|
|
buffsize=thiscore.EffectsEndA-thiscore.EffectsStartA+1;
|
|
}
|
|
|
|
//filter the 2 samples (prev then current)
|
|
LowPass(INPUT_SAMPLE_L, INPUT_SAMPLE_R);
|
|
LowPass(InL, InR);
|
|
|
|
INPUT_SAMPLE_L=(INPUT_SAMPLE_L+InL)>>9;
|
|
INPUT_SAMPLE_R=(INPUT_SAMPLE_R+InR)>>9;
|
|
|
|
#define BUFFER(x) ((s32)(*GetMemPtr(thiscore.EffectsStartA + ((thiscore.ReverbX + buffsize-((x)<<2))%buffsize))))
|
|
#define SBUFFER(x) (*GetMemPtr(thiscore.EffectsStartA + ((thiscore.ReverbX + buffsize-((x)<<2))%buffsize)))
|
|
|
|
thiscore.ReverbX=((thiscore.ReverbX + 4)%buffsize);
|
|
|
|
IIR_INPUT_A0 = (BUFFER(thiscore.Revb.IIR_SRC_A0) * thiscore.Revb.IIR_COEF + INPUT_SAMPLE_L * thiscore.Revb.IN_COEF_L)>>16;
|
|
IIR_INPUT_A1 = (BUFFER(thiscore.Revb.IIR_SRC_A1) * thiscore.Revb.IIR_COEF + INPUT_SAMPLE_R * thiscore.Revb.IN_COEF_R)>>16;
|
|
IIR_INPUT_B0 = (BUFFER(thiscore.Revb.IIR_SRC_B0) * thiscore.Revb.IIR_COEF + INPUT_SAMPLE_L * thiscore.Revb.IN_COEF_L)>>16;
|
|
IIR_INPUT_B1 = (BUFFER(thiscore.Revb.IIR_SRC_B1) * thiscore.Revb.IIR_COEF + INPUT_SAMPLE_R * thiscore.Revb.IN_COEF_R)>>16;
|
|
|
|
SBUFFER(thiscore.Revb.IIR_DEST_A0 + 4) = clamp((IIR_INPUT_A0 * thiscore.Revb.IIR_ALPHA + BUFFER(thiscore.Revb.IIR_DEST_A0) * (65535 - thiscore.Revb.IIR_ALPHA))>>16);
|
|
SBUFFER(thiscore.Revb.IIR_DEST_A1 + 4) = clamp((IIR_INPUT_A1 * thiscore.Revb.IIR_ALPHA + BUFFER(thiscore.Revb.IIR_DEST_A1) * (65535 - thiscore.Revb.IIR_ALPHA))>>16);
|
|
SBUFFER(thiscore.Revb.IIR_DEST_B0 + 4) = clamp((IIR_INPUT_B0 * thiscore.Revb.IIR_ALPHA + BUFFER(thiscore.Revb.IIR_DEST_B0) * (65535 - thiscore.Revb.IIR_ALPHA))>>16);
|
|
SBUFFER(thiscore.Revb.IIR_DEST_B1 + 4) = clamp((IIR_INPUT_B1 * thiscore.Revb.IIR_ALPHA + BUFFER(thiscore.Revb.IIR_DEST_B1) * (65535 - thiscore.Revb.IIR_ALPHA))>>16);
|
|
|
|
ACC0 = (s32)(BUFFER(thiscore.Revb.ACC_SRC_A0) * thiscore.Revb.ACC_COEF_A +
|
|
BUFFER(thiscore.Revb.ACC_SRC_B0) * thiscore.Revb.ACC_COEF_B +
|
|
BUFFER(thiscore.Revb.ACC_SRC_C0) * thiscore.Revb.ACC_COEF_C +
|
|
BUFFER(thiscore.Revb.ACC_SRC_D0) * thiscore.Revb.ACC_COEF_D)>>16;
|
|
ACC1 = (s32)(BUFFER(thiscore.Revb.ACC_SRC_A1) * thiscore.Revb.ACC_COEF_A +
|
|
BUFFER(thiscore.Revb.ACC_SRC_B1) * thiscore.Revb.ACC_COEF_B +
|
|
BUFFER(thiscore.Revb.ACC_SRC_C1) * thiscore.Revb.ACC_COEF_C +
|
|
BUFFER(thiscore.Revb.ACC_SRC_D1) * thiscore.Revb.ACC_COEF_D)>>16;
|
|
|
|
FB_A0 = BUFFER(thiscore.Revb.MIX_DEST_A0 - thiscore.Revb.FB_SRC_A);
|
|
FB_A1 = BUFFER(thiscore.Revb.MIX_DEST_A1 - thiscore.Revb.FB_SRC_A);
|
|
FB_B0 = BUFFER(thiscore.Revb.MIX_DEST_B0 - thiscore.Revb.FB_SRC_B);
|
|
FB_B1 = BUFFER(thiscore.Revb.MIX_DEST_B1 - thiscore.Revb.FB_SRC_B);
|
|
|
|
SBUFFER(thiscore.Revb.MIX_DEST_A0) = clamp((ACC0 - FB_A0 * thiscore.Revb.FB_ALPHA)>>16);
|
|
SBUFFER(thiscore.Revb.MIX_DEST_A1) = clamp((ACC1 - FB_A1 * thiscore.Revb.FB_ALPHA)>>16);
|
|
SBUFFER(thiscore.Revb.MIX_DEST_B0) = clamp(((thiscore.Revb.FB_ALPHA * ACC0) - FB_A0 * (65535 - thiscore.Revb.FB_ALPHA) - FB_B0 * thiscore.Revb.FB_X)>>16);
|
|
SBUFFER(thiscore.Revb.MIX_DEST_B1) = clamp(((thiscore.Revb.FB_ALPHA * ACC1) - FB_A1 * (65535 - thiscore.Revb.FB_ALPHA) - FB_B1 * thiscore.Revb.FB_X)>>16);
|
|
|
|
OUTPUT_SAMPLE_L=clamp((BUFFER(thiscore.Revb.MIX_DEST_A0)+BUFFER(thiscore.Revb.MIX_DEST_B0))>>2);
|
|
OUTPUT_SAMPLE_R=clamp((BUFFER(thiscore.Revb.MIX_DEST_B1)+BUFFER(thiscore.Revb.MIX_DEST_B1))>>2);
|
|
}
|
|
OutL=OUTPUT_SAMPLE_L;
|
|
OutR=OUTPUT_SAMPLE_R;
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////////////
|
|
/////////////////////////////////////////////////////////////////////////////////////////
|
|
// //
|
|
bool inited = false;
|
|
s32 count=0;
|
|
s32 maxpeak=0;
|
|
double rfactor=1;
|
|
double cfactor=1;
|
|
double diff=0;
|
|
|
|
static s32 __forceinline ApplyVolume(s32 data, s32 volume)
|
|
{
|
|
return (volume * data);
|
|
}
|
|
|
|
static void __forceinline MixVoice(V_Voice& vc, s32& VValL, s32& VValR)
|
|
{
|
|
s32 Value=0;
|
|
|
|
VValL=0;
|
|
VValR=0;
|
|
|
|
UpdateVolume(vc.VolumeL);
|
|
UpdateVolume(vc.VolumeR);
|
|
|
|
if (vc.ADSR.Phase>0)
|
|
{
|
|
if( vc.Noise )
|
|
GetNoiseValues( Cores[core], vc, Value );
|
|
else
|
|
GetVoiceValues( Cores[core], vc, Value );
|
|
|
|
#ifdef _DEBUG
|
|
vc.displayPeak = max(vc.displayPeak,abs(Value));
|
|
#endif
|
|
|
|
VValL=ApplyVolume(Value,(vc.VolumeL.Value));
|
|
VValR=ApplyVolume(Value,(vc.VolumeR.Value));
|
|
}
|
|
|
|
if (voice==1) spu2Ms16(0x400 + (core<<12) + OutPos)=(s16)((Value));
|
|
else if (voice==3) spu2Ms16(0x600 + (core<<12) + OutPos)=(s16)((Value));
|
|
|
|
}
|
|
|
|
static void __fastcall MixCore(s32& OutL, s32& OutR, s32 ExtL, s32 ExtR)
|
|
{
|
|
s32 InpL=0, InpR=0;
|
|
|
|
s32 RVL,RVR;
|
|
s32 TDL=0,TDR=0,TWL=0,TWR=0;
|
|
s32 SDL=0,SDR=0,SWL=0,SWR=0;
|
|
|
|
TDL=TDR=TWL=TWR=(s32)0;
|
|
|
|
if (core == 1) { //Core 0 doesn't have External input
|
|
spu2Ms16(0x800 + OutPos)=(s16)(ExtL>>16);
|
|
spu2Ms16(0xA00 + OutPos)=(s16)(ExtR>>16);
|
|
}
|
|
|
|
V_Core& thiscore( Cores[core] );
|
|
|
|
if((core==0)&&((PlayMode&4)!=4))
|
|
{
|
|
ReadInputPV(thiscore, InpL,InpR); // get input data from input buffers
|
|
}
|
|
if((core==1)&&((PlayMode&8)!=8))
|
|
{
|
|
ReadInputPV(thiscore, InpL,InpR); // get input data from input buffers
|
|
}
|
|
|
|
s32 InputPeak = max(abs(InpL),abs(InpR));
|
|
if(thiscore.AutoDMAPeak<InputPeak) thiscore.AutoDMAPeak=InputPeak;
|
|
|
|
InpL = MulDiv(InpL,(thiscore.InpL),1<<1);
|
|
InpR = MulDiv(InpR,(thiscore.InpR),1<<1);
|
|
|
|
ExtL = MulDiv(ExtL,(thiscore.ExtL),1<<12);
|
|
ExtR = MulDiv(ExtR,(thiscore.ExtR),1<<12);
|
|
|
|
SDL=SDR=SWL=SWR=(s32)0;
|
|
|
|
for (voice=0;voice<24;voice++)
|
|
{
|
|
s32 VValL,VValR;
|
|
|
|
V_Voice& vc( thiscore.Voices[voice] );
|
|
MixVoice( vc,VValL,VValR );
|
|
|
|
SDL += VValL * vc.DryL;
|
|
SDR += VValR * vc.DryR;
|
|
SWL += VValL * vc.WetL;
|
|
SWR += VValR * vc.WetR;
|
|
}
|
|
|
|
//Write To Output Area
|
|
spu2Ms16(0x1000 + (core<<12) + OutPos)=(s16)(SDL>>16);
|
|
spu2Ms16(0x1200 + (core<<12) + OutPos)=(s16)(SDR>>16);
|
|
spu2Ms16(0x1400 + (core<<12) + OutPos)=(s16)(SWL>>16);
|
|
spu2Ms16(0x1600 + (core<<12) + OutPos)=(s16)(SWR>>16);
|
|
|
|
// Mix in the Voice data
|
|
TDL += SDL * thiscore.SndDryL;
|
|
TDR += SDR * thiscore.SndDryR;
|
|
TWL += SWL * thiscore.SndWetL;
|
|
TWR += SWR * thiscore.SndWetR;
|
|
|
|
// Mix in the Input data
|
|
TDL += InpL * thiscore.InpDryL;
|
|
TDR += InpR * thiscore.InpDryR;
|
|
TWL += InpL * thiscore.InpWetL;
|
|
TWR += InpR * thiscore.InpWetR;
|
|
|
|
// Mix in the External (nothing/core0) data
|
|
TDL += ExtL * thiscore.ExtDryL;
|
|
TDR += ExtR * thiscore.ExtDryR;
|
|
TWL += ExtL * thiscore.ExtWetL;
|
|
TWR += ExtR * thiscore.ExtWetR;
|
|
|
|
if(EffectsEnabled)
|
|
{
|
|
//Apply Effects
|
|
DoReverb( thiscore, RVL,RVR,TWL>>16,TWR>>16);
|
|
|
|
TWL=ApplyVolume(RVL,VOL(thiscore.FxL));
|
|
TWR=ApplyVolume(RVR,VOL(thiscore.FxR));
|
|
}
|
|
else
|
|
{
|
|
TWL=0;
|
|
TWR=0;
|
|
}
|
|
|
|
//Mix Wet,Dry
|
|
OutL=(TDL + TWL);
|
|
OutR=(TDR + TWR);
|
|
|
|
//Apply Master Volume
|
|
UpdateVolume(thiscore.MasterL);
|
|
UpdateVolume(thiscore.MasterR);
|
|
|
|
if (thiscore.Mute==0) {
|
|
OutL=MulDiv(OutL,thiscore.MasterL.Value,1<<16);
|
|
OutR=MulDiv(OutR,thiscore.MasterR.Value,1<<16);
|
|
}
|
|
else
|
|
{
|
|
OutL=0;
|
|
OutR=0;
|
|
}
|
|
|
|
if((core==1)&&(PlayMode&8))
|
|
{
|
|
ReadInput(thiscore, OutL,OutR);
|
|
}
|
|
|
|
if((core==0)&&(PlayMode&4))
|
|
{
|
|
OutL=0;
|
|
OutR=0;
|
|
}
|
|
}
|
|
|
|
void __fastcall Mix()
|
|
{
|
|
s32 ExtL=0, ExtR=0, OutL, OutR;
|
|
|
|
static s32 Peak0,Peak1;
|
|
static s32 PCount;
|
|
|
|
core=0;
|
|
MixCore(ExtL,ExtR,0,0);
|
|
|
|
core=1;
|
|
MixCore(OutL,OutR,ExtL,ExtR);
|
|
|
|
#ifdef _DEBUG
|
|
Peak0 = max(Peak0,max(ExtL,ExtR));
|
|
Peak1 = max(Peak1,max(OutL,OutR));
|
|
#endif
|
|
|
|
ExtL=MulDiv(OutL,VolumeMultiplier,VolumeDivisor<<6);
|
|
ExtR=MulDiv(OutR,VolumeMultiplier,VolumeDivisor<<6);
|
|
|
|
// Update spdif (called each sample)
|
|
if(PlayMode&4)
|
|
{
|
|
spdif_update();
|
|
}
|
|
|
|
// AddToBuffer
|
|
SndWrite(ExtL,ExtR);
|
|
OutPos++;
|
|
if (OutPos>=0x200) OutPos=0;
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////////////
|
|
/////////////////////////////////////////////////////////////////////////////////////////
|
|
// //
|
|
u32 PsxRates[160]={
|
|
|
|
//for +Lin: PsxRates[value+8]
|
|
//for -Lin: PsxRates[value+7]
|
|
|
|
0xFFFFFFFF,0xFFFFFFFF,0xFFFFFFFF,0xFFFFFFFF,0xFFFFFFFF,0xFFFFFFFF,0xFFFFFFFF,0xFFFFFFFF,
|
|
0xD744FCCB,0xB504F334,0x9837F052,0x80000000,0x6BA27E65,0x5A82799A,0x4C1BF829,0x40000000,
|
|
0x35D13F33,0x2D413CCD,0x260DFC14,0x20000000,0x1AE89F99,0x16A09E66,0x1306FE0A,0x10000000,
|
|
0x0D744FCD,0x0B504F33,0x09837F05,0x08000000,0x06BA27E6,0x05A8279A,0x04C1BF83,0x04000000,
|
|
0x035D13F3,0x02D413CD,0x0260DFC1,0x02000000,0x01AE89FA,0x016A09E6,0x01306FE1,0x01000000,
|
|
0x00D744FD,0x00B504F3,0x009837F0,0x00800000,0x006BA27E,0x005A827A,0x004C1BF8,0x00400000,
|
|
0x0035D13F,0x002D413D,0x00260DFC,0x00200000,0x001AE8A0,0x0016A09E,0x001306FE,0x00100000,
|
|
0x000D7450,0x000B504F,0x0009837F,0x00080000,0x0006BA28,0x0005A828,0x0004C1C0,0x00040000,
|
|
0x00035D14,0x0002D414,0x000260E0,0x00020000,0x0001AE8A,0x00016A0A,0x00013070,0x00010000,
|
|
0x0000D745,0x0000B505,0x00009838,0x00008000,0x00006BA2,0x00005A82,0x00004C1C,0x00004000,
|
|
0x000035D1,0x00002D41,0x0000260E,0x00002000,0x00001AE9,0x000016A1,0x00001307,0x00001000,
|
|
0x00000D74,0x00000B50,0x00000983,0x00000800,0x000006BA,0x000005A8,0x000004C2,0x00000400,
|
|
0x0000035D,0x000002D4,0x00000261,0x00000200,0x000001AF,0x0000016A,0x00000130,0x00000100,
|
|
0x000000D7,0x000000B5,0x00000098,0x00000080,0x0000006C,0x0000005B,0x0000004C,0x00000040,
|
|
0x00000036,0x0000002D,0x00000026,0x00000020,0x0000001B,0x00000017,0x00000013,0x00000010,
|
|
0x0000000D,0x0000000B,0x0000000A,0x00000008,0x00000007,0x00000006,0x00000005,0x00000004,
|
|
0x00000003,0x00000003,0x00000002,0x00000002,0x00000002,0x00000001,0x00000001,0x00000000,
|
|
|
|
//128+8
|
|
0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
|
|
};
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////////////
|
|
/////////////////////////////////////////////////////////////////////////////////////////
|
|
// //
|
|
|
|
/*
|
|
-----------------------------------------------------------------------------
|
|
PSX reverb hardware notes
|
|
by Neill Corlett
|
|
-----------------------------------------------------------------------------
|
|
|
|
Yadda yadda disclaimer yadda probably not perfect yadda well it's okay anyway
|
|
yadda yadda.
|
|
|
|
-----------------------------------------------------------------------------
|
|
|
|
Basics
|
|
------
|
|
|
|
- The reverb buffer is 22khz 16-bit mono PCM.
|
|
- It starts at the reverb address given by 1DA2, extends to
|
|
the end of sound RAM, and wraps back to the 1DA2 address.
|
|
|
|
Setting the address at 1DA2 resets the current reverb work address.
|
|
|
|
This work address ALWAYS increments every 1/22050 sec., regardless of
|
|
whether reverb is enabled (bit 7 of 1DAA set).
|
|
|
|
And the contents of the reverb buffer ALWAYS play, scaled by the
|
|
"reverberation depth left/right" volumes (1D84/1D86).
|
|
(which, by the way, appear to be scaled so 3FFF=approx. 1.0, 4000=-1.0)
|
|
|
|
-----------------------------------------------------------------------------
|
|
|
|
Register names
|
|
--------------
|
|
|
|
These are probably not their real names.
|
|
These are probably not even correct names.
|
|
We will use them anyway, because we can.
|
|
|
|
1DC0: FB_SRC_A (offset)
|
|
1DC2: FB_SRC_B (offset)
|
|
1DC4: IIR_ALPHA (coef.)
|
|
1DC6: ACC_COEF_A (coef.)
|
|
1DC8: ACC_COEF_B (coef.)
|
|
1DCA: ACC_COEF_C (coef.)
|
|
1DCC: ACC_COEF_D (coef.)
|
|
1DCE: IIR_COEF (coef.)
|
|
1DD0: FB_ALPHA (coef.)
|
|
1DD2: FB_X (coef.)
|
|
1DD4: IIR_DEST_A0 (offset)
|
|
1DD6: IIR_DEST_A1 (offset)
|
|
1DD8: ACC_SRC_A0 (offset)
|
|
1DDA: ACC_SRC_A1 (offset)
|
|
1DDC: ACC_SRC_B0 (offset)
|
|
1DDE: ACC_SRC_B1 (offset)
|
|
1DE0: IIR_SRC_A0 (offset)
|
|
1DE2: IIR_SRC_A1 (offset)
|
|
1DE4: IIR_DEST_B0 (offset)
|
|
1DE6: IIR_DEST_B1 (offset)
|
|
1DE8: ACC_SRC_C0 (offset)
|
|
1DEA: ACC_SRC_C1 (offset)
|
|
1DEC: ACC_SRC_D0 (offset)
|
|
1DEE: ACC_SRC_D1 (offset)
|
|
1DF0: IIR_SRC_B1 (offset)
|
|
1DF2: IIR_SRC_B0 (offset)
|
|
1DF4: MIX_DEST_A0 (offset)
|
|
1DF6: MIX_DEST_A1 (offset)
|
|
1DF8: MIX_DEST_B0 (offset)
|
|
1DFA: MIX_DEST_B1 (offset)
|
|
1DFC: IN_COEF_L (coef.)
|
|
1DFE: IN_COEF_R (coef.)
|
|
|
|
The coefficients are signed fractional values.
|
|
-32768 would be -1.0
|
|
32768 would be 1.0 (if it were possible... the highest is of course 32767)
|
|
|
|
The offsets are (byte/8) offsets into the reverb buffer.
|
|
i.e. you multiply them by 8, you get byte offsets.
|
|
You can also think of them as (samples/4) offsets.
|
|
They appear to be signed. They can be negative.
|
|
None of the documented presets make them negative, though.
|
|
|
|
Yes, 1DF0 and 1DF2 appear to be backwards. Not a typo.
|
|
|
|
-----------------------------------------------------------------------------
|
|
|
|
What it does
|
|
------------
|
|
|
|
We take all reverb sources:
|
|
- regular channels that have the reverb bit on
|
|
- cd and external sources, if their reverb bits are on
|
|
and mix them into one stereo 44100hz signal.
|
|
|
|
Lowpass/downsample that to 22050hz. The PSX uses a proper bandlimiting
|
|
algorithm here, but I haven't figured out the hysterically exact specifics.
|
|
I use an 8-tap filter with these coefficients, which are nice but probably
|
|
not the real ones:
|
|
|
|
0.037828187894
|
|
0.157538631280
|
|
0.321159685278
|
|
0.449322115345
|
|
0.449322115345
|
|
0.321159685278
|
|
0.157538631280
|
|
0.037828187894
|
|
|
|
So we have two input samples (INPUT_SAMPLE_L, INPUT_SAMPLE_R) every 22050hz.
|
|
|
|
* IN MY EMULATION, I divide these by 2 to make it clip less.
|
|
(and of course the L/R output coefficients are adjusted to compensate)
|
|
The real thing appears to not do this.
|
|
|
|
At every 22050hz tick:
|
|
- If the reverb bit is enabled (bit 7 of 1DAA), execute the reverb
|
|
steady-state algorithm described below
|
|
- AFTERWARDS, retrieve the "wet out" L and R samples from the reverb buffer
|
|
(This part may not be exactly right and I guessed at the coefs. TODO: check later.)
|
|
L is: 0.333 * (buffer[MIX_DEST_A0] + buffer[MIX_DEST_B0])
|
|
R is: 0.333 * (buffer[MIX_DEST_A1] + buffer[MIX_DEST_B1])
|
|
- Advance the current buffer position by 1 sample
|
|
|
|
The wet out L and R are then upsampled to 44100hz and played at the
|
|
"reverberation depth left/right" (1D84/1D86) volume, independent of the main
|
|
volume.
|
|
|
|
-----------------------------------------------------------------------------
|
|
|
|
Reverb steady-state
|
|
-------------------
|
|
|
|
The reverb steady-state algorithm is fairly clever, and of course by
|
|
"clever" I mean "batshit insane".
|
|
|
|
buffer[x] is relative to the current buffer position, not the beginning of
|
|
the buffer. Note that all buffer offsets must wrap around so they're
|
|
contained within the reverb work area.
|
|
|
|
Clipping is performed at the end... maybe also sooner, but definitely at
|
|
the end.
|
|
|
|
IIR_INPUT_A0 = buffer[IIR_SRC_A0] * IIR_COEF + INPUT_SAMPLE_L * IN_COEF_L;
|
|
IIR_INPUT_A1 = buffer[IIR_SRC_A1] * IIR_COEF + INPUT_SAMPLE_R * IN_COEF_R;
|
|
IIR_INPUT_B0 = buffer[IIR_SRC_B0] * IIR_COEF + INPUT_SAMPLE_L * IN_COEF_L;
|
|
IIR_INPUT_B1 = buffer[IIR_SRC_B1] * IIR_COEF + INPUT_SAMPLE_R * IN_COEF_R;
|
|
|
|
IIR_A0 = IIR_INPUT_A0 * IIR_ALPHA + buffer[IIR_DEST_A0] * (1.0 - IIR_ALPHA);
|
|
IIR_A1 = IIR_INPUT_A1 * IIR_ALPHA + buffer[IIR_DEST_A1] * (1.0 - IIR_ALPHA);
|
|
IIR_B0 = IIR_INPUT_B0 * IIR_ALPHA + buffer[IIR_DEST_B0] * (1.0 - IIR_ALPHA);
|
|
IIR_B1 = IIR_INPUT_B1 * IIR_ALPHA + buffer[IIR_DEST_B1] * (1.0 - IIR_ALPHA);
|
|
|
|
buffer[IIR_DEST_A0 + 1sample] = IIR_A0;
|
|
buffer[IIR_DEST_A1 + 1sample] = IIR_A1;
|
|
buffer[IIR_DEST_B0 + 1sample] = IIR_B0;
|
|
buffer[IIR_DEST_B1 + 1sample] = IIR_B1;
|
|
|
|
ACC0 = buffer[ACC_SRC_A0] * ACC_COEF_A +
|
|
buffer[ACC_SRC_B0] * ACC_COEF_B +
|
|
buffer[ACC_SRC_C0] * ACC_COEF_C +
|
|
buffer[ACC_SRC_D0] * ACC_COEF_D;
|
|
ACC1 = buffer[ACC_SRC_A1] * ACC_COEF_A +
|
|
buffer[ACC_SRC_B1] * ACC_COEF_B +
|
|
buffer[ACC_SRC_C1] * ACC_COEF_C +
|
|
buffer[ACC_SRC_D1] * ACC_COEF_D;
|
|
|
|
FB_A0 = buffer[MIX_DEST_A0 - FB_SRC_A];
|
|
FB_A1 = buffer[MIX_DEST_A1 - FB_SRC_A];
|
|
FB_B0 = buffer[MIX_DEST_B0 - FB_SRC_B];
|
|
FB_B1 = buffer[MIX_DEST_B1 - FB_SRC_B];
|
|
|
|
buffer[MIX_DEST_A0] = ACC0 - FB_A0 * FB_ALPHA;
|
|
buffer[MIX_DEST_A1] = ACC1 - FB_A1 * FB_ALPHA;
|
|
buffer[MIX_DEST_B0] = (FB_ALPHA * ACC0) - FB_A0 * (FB_ALPHA^0x8000) - FB_B0 * FB_X;
|
|
buffer[MIX_DEST_B1] = (FB_ALPHA * ACC1) - FB_A1 * (FB_ALPHA^0x8000) - FB_B1 * FB_X;
|
|
|
|
-----------------------------------------------------------------------------
|
|
*/
|