copyright : (C) 2002 by Pete Bernert
email : BlackDove@addcom.de
- Portions (C) Gražvydas "notaz" Ignotas, 2010-2011
+ Portions (C) Gražvydas "notaz" Ignotas, 2010-2012
***************************************************************************/
/***************************************************************************
#include "externals.h"
#include "registers.h"
-#include "dsoundoss.h"
+#include "out.h"
+#include "arm_features.h"
#ifdef ENABLE_NLS
#include <libintl.h>
} while (0)
#endif
+#define PSXCLK 33868800 /* 33.8688 MHz */
+
/*
#if defined (USEMACOSX)
static char * libraryName = N_("Mac OS X Sound");
unsigned char * spuMemC;
unsigned char * pSpuIrq=0;
unsigned char * pSpuBuffer;
-unsigned char * pMixIrq=0;
// user settings
int iVolume=768; // 1024 is 1.0
int iXAPitch=1;
-int iSPUIRQWait=1;
-int iDebugMode=0;
-int iRecordMode=0;
int iUseReverb=2;
int iUseInterpolation=2;
unsigned int dwNoiseVal; // global noise generator
unsigned int dwNoiseCount;
-int iSpuAsyncWait=0;
unsigned short spuCtrl=0; // some vars to store psx reg infos
unsigned short spuStat=0;
int iCycle = 0;
short * pS;
+static int decode_dirty_ch;
+int decode_pos;
+int had_dma;
int lastch=-1; // last channel processed on spu irq in timer mode
static int lastns=0; // last ns pos
+static int cycles_since_update;
#define CDDA_BUFFER_SIZE (16384 * sizeof(uint32_t)) // must be power of 2
// helpers for gauss interpolation
#define gval0 (((short*)(&s_chan[ch].SB[29]))[gpos])
-#define gval(x) (((short*)(&s_chan[ch].SB[29]))[(gpos+x)&3])
+#define gval(x) ((int)((short*)(&s_chan[ch].SB[29]))[(gpos+x)&3])
#include "gauss_i.h"
#include "xa.c"
+static void do_irq(void)
+{
+ //if(!(spuStat & STAT_IRQ))
+ {
+ spuStat |= STAT_IRQ; // asserted status?
+ if(irqCallback) irqCallback();
+ }
+}
+
+static int check_irq(int ch, unsigned char *pos)
+{
+ if((spuCtrl & CTRL_IRQ) && pos == pSpuIrq)
+ {
+ //printf("ch%d irq %04x\n", ch, pos - spuMemC);
+ do_irq();
+ return 1;
+ }
+ return 0;
+}
+
////////////////////////////////////////////////////////////////////////
// START SOUND... called by main thread to setup a new sound on a channel
////////////////////////////////////////////////////////////////////////
int vl, vr;int gpos;
vl = (spos >> 6) & ~3;
gpos = s_chan[ch].SB[28];
- vr=(gauss[vl]*gval0)&~2047;
+ vr=(gauss[vl]*(int)gval0)&~2047;
vr+=(gauss[vl+1]*gval(1))&~2047;
vr+=(gauss[vl+2]*gval(2))&~2047;
vr+=(gauss[vl+3]*gval(3))&~2047;
return fa;
}
-static void do_irq(void)
-{
- //if(!(spuStat & STAT_IRQ))
- {
- spuStat |= STAT_IRQ; // asserted status?
- if(irqCallback) irqCallback();
- }
-}
-
static void decode_block_data(int *dest, const unsigned char *src, int predict_nr, int shift_factor)
{
int nSample;
{
unsigned char *start;
int predict_nr,shift_factor,flags;
+ int stop = 0;
int ret = 0;
- start=s_chan[ch].pCurr; // set up the current pos
- if(dwPendingChanOff&(1<<ch))
+ start = s_chan[ch].pCurr; // set up the current pos
+ if(start == spuMemC) // ?
+ stop = 1;
+
+ if(s_chan[ch].prevflags&1) // 1: stop/loop
{
- dwChannelOn&=~(1<<ch); // -> turn everything off
- dwPendingChanOff&=~(1<<ch);
- s_chan[ch].bStop=1;
- s_chan[ch].ADSRX.EnvelopeVol=0;
- }
+ if(!(s_chan[ch].prevflags&2))
+ stop = 1;
- //////////////////////////////////////////// irq check
+ start = s_chan[ch].pLoop;
+ }
+ else
+ ret = check_irq(ch, start); // hack, see check_irq below..
- if(spuCtrl&CTRL_IRQ)
+ if(stop)
{
- if(pSpuIrq == start) // irq address reached?
- {
- do_irq(); // -> call main emu
- ret = 1;
- }
+ dwChannelOn &= ~(1<<ch); // -> turn everything off
+ s_chan[ch].bStop = 1;
+ s_chan[ch].ADSRX.EnvelopeVol = 0;
}
predict_nr=(int)start[0];
decode_block_data(s_chan[ch].SB, start + 2, predict_nr, shift_factor);
- //////////////////////////////////////////// flag handler
-
flags=(int)start[1];
if(flags&4)
s_chan[ch].pLoop=start; // loop adress
start+=16;
- if(flags&1) // 1: stop/loop
- {
- if(!(flags&2))
- dwPendingChanOff|=1<<ch;
+ if(flags&1) { // 1: stop/loop
start = s_chan[ch].pLoop;
+ ret |= check_irq(ch, start); // hack.. :(
}
- if (start - spuMemC >= 0x80000) {
- // most likely wrong
+ if (start - spuMemC >= 0x80000)
start = spuMemC;
- printf("ch%d oflow\n", ch);
- }
s_chan[ch].pCurr = start; // store values for next cycle
- s_chan[ch].bJump = flags & 1;
+ s_chan[ch].prevflags = flags;
return ret;
}
{
unsigned char *start = s_chan[ch].pCurr;
int flags = start[1];
- int ret = 0;
+ int ret = check_irq(ch, start);
- if(start == pSpuIrq)
- {
- do_irq();
- ret = 1;
- }
+ if(s_chan[ch].prevflags & 1)
+ start = s_chan[ch].pLoop;
if(flags & 4)
s_chan[ch].pLoop = start;
- s_chan[ch].pCurr += 16;
+ start += 16;
if(flags & 1)
- s_chan[ch].pCurr = s_chan[ch].pLoop;
+ start = s_chan[ch].pLoop;
+
+ s_chan[ch].pCurr = start;
+ s_chan[ch].prevflags = flags;
- s_chan[ch].bJump = flags & 1;
return ret;
}
{ \
sbpos = 0; \
d = decode_block(ch); \
- if(d && iSPUIRQWait) \
- { \
- ret = ns; \
- goto out; \
- } \
+ if(d) \
+ ret = ns_to = ns + 1; \
} \
\
fa = SB[sbpos++]; \
spos += sinc; \
} \
\
-out: \
s_chan[ch].sinc = sinc; \
s_chan[ch].spos = spos; \
s_chan[ch].iSBPos = sbpos; \
static int do_samples_noise(int ch, int ns, int ns_to)
{
int level, shift, bit;
+ int ret = -1, d;
s_chan[ch].spos += s_chan[ch].sinc * (ns_to - ns);
while (s_chan[ch].spos >= 28*0x10000)
{
- skip_block(ch);
+ d = skip_block(ch);
+ if (d)
+ ret = ns_to;
s_chan[ch].spos -= 28*0x10000;
}
ChanBuf[ns] = (signed short)dwNoiseVal;
}
- return -1;
+ return ret;
}
-#ifdef __arm__
+#ifdef HAVE_ARMV5
// asm code; lv and rv must be 0-3fff
extern void mix_chan(int start, int count, int lv, int rv);
extern void mix_chan_rvb(int start, int count, int lv, int rv);
}
#endif
+// 0x0800-0x0bff Voice 1
+// 0x0c00-0x0fff Voice 3
+static void noinline do_decode_bufs(int which, int start, int count)
+{
+ const int *src = ChanBuf + start;
+ unsigned short *dst = &spuMem[0x800/2 + which*0x400/2];
+ int cursor = decode_pos + start;
+
+ while (count-- > 0)
+ {
+ cursor &= 0x1ff;
+ dst[cursor] = *src++;
+ cursor++;
+ }
+
+ // decode_pos is updated and irqs are checked later, after voice loop
+}
+
////////////////////////////////////////////////////////////////////////
// MAIN SPU FUNCTION
// here is the main job handler...
// basically the whole sound processing is done in this fat func!
////////////////////////////////////////////////////////////////////////
-static int do_samples(void)
+static int do_samples(int forced_updates)
{
int volmult = iVolume;
int ns,ns_from,ns_to;
int ch,d,silentch;
int bIRQReturn=0;
- while(1)
- {
- // ok, at the beginning we are looking if there is
- // enuff free place in the dsound/oss buffer to
- // fill in new data, or if there is a new channel to start.
- // if not, we wait (thread) or return (timer/spuasync)
- // until enuff free place is available/a new channel gets
- // started
-
- if(!dwNewChannel && SoundGetBytesBuffered()) // still enuff data in sound buffer?
- {
- return 0;
- }
+ // ok, at the beginning we are looking if there is
+ // enuff free place in the dsound/oss buffer to
+ // fill in new data, or if there is a new channel to start.
+ // if not, we return until enuff free place is available
+ // /a new channel gets started
- //--------------------------------------------------// continue from irq handling in timer mode?
+ if(!forced_updates && out_current->busy()) // still enuff data in sound buffer?
+ return 0;
+ while(!bIRQReturn)
+ {
ns_from=0;
ns_to=NSSIZE;
ch=0;
bIRQReturn=1;
lastch=ch;
lastns=ns_to=d;
- if(d==0)
- break;
}
MixADSR(ch, ns_from, ns_to);
+ if(ch==1 || ch==3)
+ {
+ do_decode_bufs(ch/2, ns_from, ns_to-ns_from);
+ decode_dirty_ch |= 1<<ch;
+ }
+
if(s_chan[ch].bFMod==2) // fmod freq channel
memcpy(iFMod, ChanBuf, sizeof(iFMod));
else if(s_chan[ch].bRVBActive)
// advance "stopped" channels that can cause irqs
// (all chans are always playing on the real thing..)
- if(!bIRQReturn && (spuCtrl&CTRL_IRQ))
+ if(spuCtrl&CTRL_IRQ)
for(ch=0;ch<MAXCHAN;ch++)
{
if(!(silentch&(1<<ch))) continue; // already handled
if(s_chan[ch].pCurr > pSpuIrq && s_chan[ch].pLoop > pSpuIrq)
continue;
- s_chan[ch].spos += s_chan[ch].sinc * NSSIZE;
+ s_chan[ch].spos += s_chan[ch].sinc * (ns_to - ns_from);
while(s_chan[ch].spos >= 28 * 0x10000)
{
unsigned char *start = s_chan[ch].pCurr;
// no need for bIRQReturn since the channel is silent
- iSpuAsyncWait |= skip_block(ch);
- if(start == s_chan[ch].pCurr)
+ skip_block(ch);
+ if(start == s_chan[ch].pCurr || start - spuMemC < 0x1000)
{
- // looping on self
+ // looping on self or stopped(?)
dwChannelDead |= 1<<ch;
s_chan[ch].spos = 0;
break;
}
}
- if(bIRQReturn && iSPUIRQWait) // special return for "spu irq - wait for cpu action"
- {
- iSpuAsyncWait=1;
- bIRQReturn=0;
+ if(bIRQReturn) // special return for "spu irq - wait for cpu action"
return 0;
- }
+ if(unlikely(silentch & decode_dirty_ch & (1<<1))) // must clear silent channel decode buffers
+ {
+ memset(&spuMem[0x800/2], 0, 0x400);
+ decode_dirty_ch &= ~(1<<1);
+ }
+ if(unlikely(silentch & decode_dirty_ch & (1<<3)))
+ {
+ memset(&spuMem[0xc00/2], 0, 0x400);
+ decode_dirty_ch &= ~(1<<3);
+ }
//---------------------------------------------------//
//- here we have another 1 ms of sound data
ns++;
}
+ cycles_since_update -= PSXCLK / 44100 * NSSIZE;
+
//////////////////////////////////////////////////////
// special irq handling in the decode buffers (0x0000-0x1000)
// we know:
// an IRQ. Only problem: the "wait for cpu" option is kinda hard to do here
// in some of Peops timer modes. So: we ignore this option here (for now).
- if(pMixIrq)
+ if(unlikely((spuCtrl&CTRL_IRQ) && pSpuIrq && pSpuIrq<spuMemC+0x1000))
{
- for(ns=0;ns<NSSIZE;ns++)
+ int irq_pos=(pSpuIrq-spuMemC)/2 & 0x1ff;
+ if((decode_pos <= irq_pos && irq_pos < decode_pos+NSSIZE)
+ || (decode_pos+NSSIZE > 0x200 && irq_pos < ((decode_pos+NSSIZE) & 0x1ff)))
{
- if((spuCtrl&0x40) && pSpuIrq && pSpuIrq<spuMemC+0x1000)
- {
- for(ch=0;ch<4;ch++)
- {
- if(pSpuIrq>=pMixIrq+(ch*0x400) && pSpuIrq<pMixIrq+(ch*0x400)+2)
- do_irq();
- }
- }
- pMixIrq+=2;if(pMixIrq>spuMemC+0x3ff) pMixIrq=spuMemC;
+ //printf("decoder irq %x\n", decode_pos);
+ do_irq();
+ bIRQReturn = 1;
}
}
+ decode_pos = (decode_pos + NSSIZE) & 0x1ff;
InitREVERB();
// feed the sound
// wanna have around 1/60 sec (16.666 ms) updates
- if (iCycle++ > 16/FRAG_MSECS)
+ if (iCycle++ >= 16/FRAG_MSECS)
{
- SoundFeedStreamData((unsigned char *)pSpuBuffer,
- ((unsigned char *)pS) - ((unsigned char *)pSpuBuffer));
+ out_current->feed(pSpuBuffer, (unsigned char *)pS - pSpuBuffer);
pS = (short *)pSpuBuffer;
iCycle = 0;
+
+ if(!forced_updates && out_current->busy())
+ break;
}
+
+ if(forced_updates > 0)
+ {
+ forced_updates--;
+ if(forced_updates == 0 && out_current->busy())
+ break;
+ }
+
+ if(cycles_since_update <= -PSXCLK/60 / 4)
+ break;
}
+ // this may cause desync, but help audio when the emu can't keep up..
+ if(cycles_since_update < 0)
+ cycles_since_update = 0;
+
return 0;
}
void CALLBACK SPUasync(unsigned long cycle)
{
+ int forced_updates = 0;
+ int do_update = 0;
+
if(!bSpuInit) return; // -> no init, no call
- if(iSpuAsyncWait)
+ cycles_since_update += cycle;
+
+ if(dwNewChannel || had_dma)
{
- iSpuAsyncWait++;
- if(iSpuAsyncWait<=16/FRAG_MSECS) return;
- iSpuAsyncWait=0;
+ forced_updates = 1;
+ do_update = 1;
+ had_dma = 0;
}
- do_samples();
+ if(cycles_since_update > PSXCLK/60 * 5/4)
+ do_update = 1;
+
+ if(do_update)
+ do_samples(forced_updates);
}
// SPU UPDATE... new epsxe func
s_chan[i].pCurr=spuMemC;
}
- pMixIrq=spuMemC; // enable decoded buffer irqs by setting the address
-
ClearWorkingState();
bSpuInit=1; // flag: we are inited
spuIrq = 0;
spuAddr = 0xffffffff;
spuMemC = (unsigned char *)spuMem;
- pMixIrq = 0;
+ decode_pos = 0;
memset((void *)s_chan, 0, (MAXCHAN + 1) * sizeof(SPUCHAN));
pSpuIrq = 0;
- //iSPUIRQWait = 0;
lastch = -1;
SetupStreams(); // prepare streaming
bSPUIsOpen = 0; // no more open
- RemoveSound(); // no more sound handling
+ out_current->finish(); // no more sound handling
return 0;
}