* *
***************************************************************************/
+#include <assert.h>
#include "stdafx.h"
#define _IN_SPU
#include "registers.h"
#include "out.h"
#include "spu_config.h"
+#include "spu.h"
#ifdef __arm__
#include "arm_features.h"
#include "gauss_i.h"
#include "xa.c"
-static void do_irq(void)
+static void do_irq(int cycles_after)
{
- //if(!(spu.spuStat & STAT_IRQ))
+ if (spu.spuStat & STAT_IRQ)
+ log_unhandled("spu: missed irq?\n");
+ else
{
spu.spuStat |= STAT_IRQ; // asserted status?
- if(spu.irqCallback) spu.irqCallback();
+ if (spu.irqCallback)
+ spu.irqCallback(cycles_after);
}
}
{
if((spu.spuCtrl & (CTRL_ON|CTRL_IRQ)) == (CTRL_ON|CTRL_IRQ) && pos == spu.pSpuIrq)
{
- //printf("ch%d irq %04x\n", ch, pos - spu.spuMemC);
- do_irq();
+ //printf("ch%d irq %04zx\n", ch, pos - spu.spuMemC);
+ do_irq(0);
return 1;
}
return 0;
if((spu.spuCtrl & (CTRL_ON|CTRL_IRQ)) == (CTRL_ON|CTRL_IRQ) && addr == irq_addr)
{
//printf("io irq %04x\n", irq_addr);
- do_irq();
+ do_irq(0);
+ }
+}
+
+void do_irq_io(int cycles_after)
+{
+ if ((spu.spuCtrl & (CTRL_ON|CTRL_IRQ)) == (CTRL_ON|CTRL_IRQ))
+ {
+ do_irq(cycles_after);
}
}
// ALL KIND OF HELPERS
////////////////////////////////////////////////////////////////////////
-INLINE int FModChangeFrequency(int pitch, int ns)
+INLINE int FModChangeFrequency(int pitch, int ns, int *fmod_buf)
{
pitch = (signed short)pitch;
- pitch = ((32768 + iFMod[ns]) * pitch) >> 15;
+ pitch = ((32768 + fmod_buf[ns]) * pitch) >> 15;
pitch &= 0xffff;
if (pitch > 0x3fff)
pitch = 0x3fff;
- iFMod[ns] = 0;
+ fmod_buf[ns] = 0;
return pitch << 4;
}
}
#define make_do_samples(name, fmod_code, interp_start, interp_store, interp_get, interp_end) \
-static noinline int name( \
+static noinline int name(int *dst, \
int (*decode_f)(void *context, int ch, int *SB), void *ctx, \
int ch, int ns_to, sample_buf *sb, int sinc, int *spos, int *sbpos) \
{ \
if(sinc<0x10000) /* -> upsampling? */ \
InterpolateUp(sb, sinc); /* --> interpolate up */ \
else InterpolateDown(sb, sinc); /* --> else down */ \
- ChanBuf[ns] = sb->SB[29]
+ dst[ns] = sb->SB[29]
make_do_samples(do_samples_nointerp, , fa = sb->SB[29],
- , ChanBuf[ns] = fa, sb->SB[29] = fa)
+ , dst[ns] = fa, sb->SB[29] = fa)
make_do_samples(do_samples_simple, , ,
simple_interp_store, simple_interp_get, )
make_do_samples(do_samples_gauss, , ,
StoreInterpolationGaussCubic(sb, fa),
- ChanBuf[ns] = GetInterpolationGauss(sb, *spos), )
+ dst[ns] = GetInterpolationGauss(sb, *spos), )
make_do_samples(do_samples_cubic, , ,
StoreInterpolationGaussCubic(sb, fa),
- ChanBuf[ns] = GetInterpolationCubic(sb, *spos), )
+ dst[ns] = GetInterpolationCubic(sb, *spos), )
make_do_samples(do_samples_fmod,
- sinc = FModChangeFrequency(spu.s_chan[ch].iRawPitch, ns), ,
+ sinc = FModChangeFrequency(spu.s_chan[ch].iRawPitch, ns, iFMod), ,
StoreInterpolationGaussCubic(sb, fa),
- ChanBuf[ns] = GetInterpolationGauss(sb, *spos), )
+ dst[ns] = GetInterpolationGauss(sb, *spos), )
-INLINE int do_samples_adpcm(
+INLINE int do_samples_adpcm(int *dst,
int (*decode_f)(void *context, int ch, int *SB), void *ctx,
int ch, int ns_to, int fmod, sample_buf *sb, int sinc, int *spos, int *sbpos)
{
int interp = spu.interpolation;
if (fmod == 1)
- return do_samples_fmod(decode_f, ctx, ch, ns_to, sb, sinc, spos, sbpos);
+ return do_samples_fmod(dst, decode_f, ctx, ch, ns_to, sb, sinc, spos, sbpos);
if (fmod)
interp = 2;
switch (interp) {
case 0:
- return do_samples_nointerp(decode_f, ctx, ch, ns_to, sb, sinc, spos, sbpos);
+ return do_samples_nointerp(dst, decode_f, ctx, ch, ns_to, sb, sinc, spos, sbpos);
case 1:
- return do_samples_simple (decode_f, ctx, ch, ns_to, sb, sinc, spos, sbpos);
+ return do_samples_simple (dst, decode_f, ctx, ch, ns_to, sb, sinc, spos, sbpos);
default:
- return do_samples_gauss (decode_f, ctx, ch, ns_to, sb, sinc, spos, sbpos);
+ return do_samples_gauss (dst, decode_f, ctx, ch, ns_to, sb, sinc, spos, sbpos);
case 3:
- return do_samples_cubic (decode_f, ctx, ch, ns_to, sb, sinc, spos, sbpos);
+ return do_samples_cubic (dst, decode_f, ctx, ch, ns_to, sb, sinc, spos, sbpos);
}
}
return ret;
}
-static void do_lsfr_samples(int ns_to, int ctrl,
+static int do_samples_skip_fmod(int ch, int ns_to, int *fmod_buf)
+{
+ SPUCHAN *s_chan = &spu.s_chan[ch];
+ int spos = s_chan->spos;
+ int ret = ns_to, ns, d;
+
+ spos += s_chan->iSBPos << 16;
+
+ for (ns = 0; ns < ns_to; ns++)
+ {
+ spos += FModChangeFrequency(s_chan->iRawPitch, ns, fmod_buf);
+ while (spos >= 28*0x10000)
+ {
+ d = skip_block(ch);
+ if (d && ns < ret)
+ ret = ns;
+ spos -= 28*0x10000;
+ }
+ }
+
+ s_chan->iSBPos = spos >> 16;
+ s_chan->spos = spos & 0xffff;
+
+ return ret;
+}
+
+static void do_lsfr_samples(int *dst, int ns_to, int ctrl,
unsigned int *dwNoiseCount, unsigned int *dwNoiseVal)
{
unsigned int counter = *dwNoiseCount;
val = (val << 1) | bit;
}
- ChanBuf[ns] = (signed short)val;
+ dst[ns] = (signed short)val;
}
*dwNoiseCount = counter;
*dwNoiseVal = val;
}
-static int do_samples_noise(int ch, int ns_to)
+static int do_samples_noise(int *dst, int ch, int ns_to)
{
int ret;
ret = do_samples_skip(ch, ns_to);
- do_lsfr_samples(ns_to, spu.spuCtrl, &spu.dwNoiseCount, &spu.dwNoiseVal);
+ do_lsfr_samples(dst, ns_to, spu.spuCtrl, &spu.dwNoiseCount, &spu.dwNoiseVal);
return ret;
}
s_chan = &spu.s_chan[ch];
if (s_chan->bNoise)
- d = do_samples_noise(ch, ns_to);
+ d = do_samples_noise(ChanBuf, ch, ns_to);
else
- d = do_samples_adpcm(decode_block, NULL, ch, ns_to, s_chan->bFMod,
+ d = do_samples_adpcm(ChanBuf, decode_block, NULL, ch, ns_to, s_chan->bFMod,
&spu.sb[ch], s_chan->sinc, &s_chan->spos, &s_chan->iSBPos);
if (!s_chan->bStarting) {
- d = MixADSR(&s_chan->ADSRX, d);
+ d = MixADSR(ChanBuf, &s_chan->ADSRX, d);
if (d < ns_to) {
spu.dwChannelsAudible &= ~(1 << ch);
s_chan->ADSRX.State = ADSR_RELEASE;
if (s_chan->bFMod == 2) // fmod freq channel
memcpy(iFMod, &ChanBuf, ns_to * sizeof(iFMod[0]));
- if (s_chan->bRVBActive && do_rvb)
+ if (!(spu.spuCtrl & CTRL_MUTE))
+ ;
+ else if (s_chan->bRVBActive && do_rvb)
mix_chan_rvb(spu.SSumLR, ns_to, s_chan->iLeftVolume, s_chan->iRightVolume, RVB);
else
mix_chan(spu.SSumLR, ns_to, s_chan->iLeftVolume, s_chan->iRightVolume);
}
- MixXA(spu.SSumLR, RVB, ns_to, spu.decode_pos);
+ MixCD(spu.SSumLR, RVB, ns_to, spu.decode_pos);
if (spu.rvb->StartAddr) {
if (do_rvb)
static struct spu_worker {
union {
struct {
- unsigned int exit_thread;
+ unsigned char exit_thread;
+ unsigned char prev_work_in_thread;
+ unsigned char pad[2];
unsigned int i_ready;
unsigned int i_reaped;
unsigned int last_boot_cnt; // dsp
unsigned int ram_dirty;
+ unsigned int channels_last;
};
// aligning for C64X_DSP
unsigned int _pad0[128/4];
unsigned short bNoise:1;
unsigned short bFMod:2;
unsigned short bRVBActive:1;
+ unsigned short bStarting:1;
ADSRInfoEx adsr;
} ch[24];
int SSumLR[NSSIZE * 2];
static void queue_channel_work(int ns_to, unsigned int silentch)
{
+ int tmpFMod[NSSIZE];
struct work_item *work;
SPUCHAN *s_chan;
unsigned int mask;
work->decode_pos = spu.decode_pos;
work->channels_silent = silentch;
+ if (!worker->prev_work_in_thread) {
+ // copy adpcm and interpolation state to sb_thread
+ worker->prev_work_in_thread = 1;
+ mask = spu.dwChannelsAudible & ~spu.dwNewChannel & 0xffffff;
+ for (ch = 0; mask != 0; ch++, mask >>= 1) {
+ if (mask & 1)
+ memcpy(spu.sb_thread[ch].SB, spu.sb[ch].SB, sizeof(spu.sb_thread[ch].SB));
+ }
+ }
+
mask = work->channels_new = spu.dwNewChannel & 0xffffff;
for (ch = 0; mask != 0; ch++, mask >>= 1) {
if (mask & 1)
- StartSoundMain(ch);
+ StartSound(ch);
}
mask = work->channels_on = spu.dwChannelsAudible & 0xffffff;
+ worker->channels_last = mask;
spu.decode_dirty_ch |= mask & 0x0a;
for (ch = 0; mask != 0; ch++, mask >>= 1)
work->ch[ch].bNoise = s_chan->bNoise;
work->ch[ch].bFMod = s_chan->bFMod;
work->ch[ch].bRVBActive = s_chan->bRVBActive;
+ work->ch[ch].bStarting = s_chan->bStarting;
if (s_chan->prevflags & 1)
work->ch[ch].start = work->ch[ch].loop;
- d = do_samples_skip(ch, ns_to);
+ if (unlikely(s_chan->bFMod == 2))
+ {
+ // sucks, have to do double work
+ assert(!s_chan->bNoise);
+ d = do_samples_gauss(tmpFMod, decode_block, NULL, ch, ns_to,
+ &spu.sb[ch], s_chan->sinc, &s_chan->spos, &s_chan->iSBPos);
+ if (!s_chan->bStarting) {
+ d = MixADSR(tmpFMod, &s_chan->ADSRX, d);
+ if (d < ns_to) {
+ spu.dwChannelsAudible &= ~(1 << ch);
+ s_chan->ADSRX.State = ADSR_RELEASE;
+ s_chan->ADSRX.EnvelopeVol = 0;
+ }
+ }
+ memset(&tmpFMod[d], 0, (ns_to - d) * sizeof(tmpFMod[d]));
+ work->ch[ch].ns_to = d;
+ continue;
+ }
+ if (unlikely(s_chan->bFMod))
+ d = do_samples_skip_fmod(ch, ns_to, tmpFMod);
+ else
+ d = do_samples_skip(ch, ns_to);
work->ch[ch].ns_to = d;
if (!s_chan->bStarting) {
s_chan->ADSRX.EnvelopeVol = 0;
}
}
- }
+ } // for (ch;;)
work->rvb_addr = 0;
if (spu.rvb->StartAddr) {
if (unlikely(spu.interpolation != spu_config.iUseInterpolation))
{
spu.interpolation = spu_config.iUseInterpolation;
- mask = spu.dwChannelsAudible & 0xffffff;
+ mask = work->channels_on;
for (ch = 0; mask != 0; ch++, mask >>= 1)
if (mask & 1)
- ResetInterpolation(&spu.sb[ch]);
+ ResetInterpolation(&spu.sb_thread[ch]);
}
if (work->rvb_addr)
mask = work->channels_new;
for (ch = 0; mask != 0; ch++, mask >>= 1) {
if (mask & 1)
- StartSoundSB(&spu.sb[ch]);
+ StartSoundSB(&spu.sb_thread[ch]);
}
mask = work->channels_on;
sbpos = work->ch[ch].sbpos;
if (work->ch[ch].bNoise)
- do_lsfr_samples(d, work->ctrl, &spu.dwNoiseCount, &spu.dwNoiseVal);
+ do_lsfr_samples(ChanBuf, d, work->ctrl, &spu.dwNoiseCount, &spu.dwNoiseVal);
else
- do_samples_adpcm(decode_block_work, work, ch, d, work->ch[ch].bFMod,
- &spu.sb[ch], work->ch[ch].sinc, &spos, &sbpos);
+ do_samples_adpcm(ChanBuf, decode_block_work, work, ch, d, work->ch[ch].bFMod,
+ &spu.sb_thread[ch], work->ch[ch].sinc, &spos, &sbpos);
- d = MixADSR(&work->ch[ch].adsr, d);
+ d = MixADSR(ChanBuf, &work->ch[ch].adsr, d);
if (d < ns_to) {
work->ch[ch].adsr.EnvelopeVol = 0;
memset(&ChanBuf[d], 0, (ns_to - d) * sizeof(ChanBuf[0]));
REVERBDo(work->SSumLR, RVB, ns_to, work->rvb_addr);
}
-static void sync_worker_thread(int force)
+static void sync_worker_thread(int force_no_thread)
{
+ int force = force_no_thread;
struct work_item *work;
int done, used_space;
work = &worker->i[worker->i_reaped & WORK_I_MASK];
thread_work_wait_sync(work, force);
- MixXA(work->SSumLR, RVB, work->ns_to, work->decode_pos);
+ MixCD(work->SSumLR, RVB, work->ns_to, work->decode_pos);
do_samples_finish(work->SSumLR, work->ns_to,
work->channels_silent, work->decode_pos);
done = thread_get_i_done() - worker->i_reaped;
used_space = worker->i_ready - worker->i_reaped;
}
- if (force)
+ if (force_no_thread && worker->prev_work_in_thread) {
+ unsigned int ch, mask = worker->channels_last;
+ worker->prev_work_in_thread = 0;
thread_sync_caches();
+ for (ch = 0; mask != 0; ch++, mask >>= 1) {
+ if (mask & 1)
+ memcpy(spu.sb[ch].SB, spu.sb_thread[ch].SB, sizeof(spu.sb_thread[ch].SB));
+ }
+ }
}
#else
static void queue_channel_work(int ns_to, int silentch) {}
-static void sync_worker_thread(int force) {}
+static void sync_worker_thread(int force_no_thread) {}
static const void * const worker = NULL;
// here is the main job handler...
////////////////////////////////////////////////////////////////////////
-void do_samples(unsigned int cycles_to, int do_direct)
+void do_samples(unsigned int cycles_to, int force_no_thread)
{
unsigned int silentch;
int cycle_diff;
cycle_diff = cycles_to - spu.cycles_played;
if (cycle_diff < -2*1048576 || cycle_diff > 2*1048576)
{
- //xprintf("desync %u %d\n", cycles_to, cycle_diff);
+ log_unhandled("desync %u %d\n", cycles_to, cycle_diff);
spu.cycles_played = cycles_to;
return;
}
silentch = ~(spu.dwChannelsAudible | spu.dwNewChannel) & 0xffffff;
- do_direct |= (silentch == 0xffffff);
+ force_no_thread |= (silentch == 0xffffff);
if (worker != NULL)
- sync_worker_thread(do_direct);
+ sync_worker_thread(force_no_thread);
if (cycle_diff < 2 * 768)
return;
ns_to = (cycle_diff / 768 + 1) & ~1;
if (ns_to > NSSIZE) {
// should never happen
- //xprintf("ns_to oflow %d %d\n", ns_to, NSSIZE);
+ log_unhandled("ns_to oflow %d %d\n", ns_to, NSSIZE);
ns_to = NSSIZE;
}
if (0 < left && left <= ns_to)
{
//xprintf("decoder irq %x\n", spu.decode_pos);
- do_irq();
+ do_irq(0);
}
}
if (!spu.cycles_dma_end || (int)(spu.cycles_dma_end - cycles_to) < 0) {
if (unlikely(spu.rvb->dirty))
REVERBPrep();
- if (do_direct || worker == NULL || !spu_config.iUseThread) {
+ if (force_no_thread || worker == NULL || !spu_config.iUseThread) {
do_channels(ns_to);
do_samples_finish(spu.SSumLR, ns_to, silentch, spu.decode_pos);
}
vol_l = vol_l * spu_config.iVolume >> 10;
vol_r = vol_r * spu_config.iVolume >> 10;
- if (!(spu.spuCtrl & CTRL_MUTE) || !(vol_l | vol_r))
+ if (!(vol_l | vol_r))
{
// muted? (rare)
memset(spu.pS, 0, ns_to * 2 * sizeof(spu.pS[0]));
if(!xap->freq) return; // no xa freq ? bye
if (is_start)
+ spu.XAPlay = spu.XAFeed = spu.XAStart;
+ if (spu.XAPlay == spu.XAFeed)
do_samples(cycle, 1); // catch up to prevent source underflows later
FeedXA(xap); // call main XA feeder
}
// CDDA AUDIO
-int CALLBACK SPUplayCDDAchannel(short *pcm, int nbytes, unsigned int cycle, int is_start)
+int CALLBACK SPUplayCDDAchannel(short *pcm, int nbytes, unsigned int cycle, int unused)
{
if (!pcm) return -1;
if (nbytes<=0) return -1;
- if (is_start)
+ if (spu.CDDAPlay == spu.CDDAFeed)
do_samples(cycle, 1); // catch up to prevent source underflows later
FeedCDDA((unsigned char *)pcm, nbytes);
return 0;
}
+void CALLBACK SPUsetCDvol(unsigned char ll, unsigned char lr,
+ unsigned char rl, unsigned char rr, unsigned int cycle)
+{
+ if (spu.XAPlay != spu.XAFeed || spu.CDDAPlay != spu.CDDAFeed)
+ do_samples(cycle, 1);
+ spu.cdv.ll = ll;
+ spu.cdv.lr = lr;
+ spu.cdv.rl = rl;
+ spu.cdv.rr = rr;
+}
+
// to be called after state load
void ClearWorkingState(void)
{
{
int ret;
+ spu.sb_thread = spu.sb_thread_;
+
if (sysconf(_SC_NPROCESSORS_ONLN) <= 1)
return;
int i;
memset(&spu, 0, sizeof(spu));
- spu.spuMemC = calloc(1, 512 * 1024);
+ spu.spuMemC = calloc(1, 512 * 1024 + 16);
+ // a guard for runaway channels - End+Mute
+ spu.spuMemC[512 * 1024 + 1] = 1;
+
InitADSR();
spu.s_chan = calloc(MAXCHAN+1, sizeof(spu.s_chan[0])); // channel + 1 infos (1 is security for fmod handling)
// SETUP CALLBACKS
// this functions will be called once,
// passes a callback that should be called on SPU-IRQ/cdda volume change
-void CALLBACK SPUregisterCallback(void (CALLBACK *callback)(void))
+void CALLBACK SPUregisterCallback(void (CALLBACK *callback)(int))
{
spu.irqCallback = callback;
}
void CALLBACK SPUregisterCDDAVolume(void (CALLBACK *CDDAVcallback)(short, short))
{
- spu.cddavCallback = CDDAVcallback;
+ //spu.cddavCallback = CDDAVcallback;
}
void CALLBACK SPUregisterScheduleCb(void (CALLBACK *callback)(unsigned int))