+static void do_silent_chans(int ns_to, int silentch)
+{
+ unsigned int mask;
+ SPUCHAN *s_chan;
+ int ch;
+
+ mask = silentch & 0xffffff;
+ for (ch = 0; mask != 0; ch++, mask >>= 1)
+ {
+ if (!(mask & 1)) continue;
+ if (spu.dwChannelDead & (1<<ch)) continue;
+
+ s_chan = &spu.s_chan[ch];
+ if (s_chan->pCurr > spu.pSpuIrq && s_chan->pLoop > spu.pSpuIrq)
+ continue;
+
+ s_chan->spos += s_chan->iSBPos << 16;
+ s_chan->iSBPos = 0;
+
+ s_chan->spos += s_chan->sinc * ns_to;
+ while (s_chan->spos >= 28 * 0x10000)
+ {
+ unsigned char *start = s_chan->pCurr;
+
+ skip_block(ch);
+ if (start == s_chan->pCurr || start - spu.spuMemC < 0x1000)
+ {
+ // looping on self or stopped(?)
+ spu.dwChannelDead |= 1<<ch;
+ s_chan->spos = 0;
+ break;
+ }
+
+ s_chan->spos -= 28 * 0x10000;
+ }
+ }
+}
+
+static void do_channels(int ns_to)
+{
+ unsigned int mask;
+ SPUCHAN *s_chan;
+ int *SB, sinc;
+ int ch, d;
+
+ InitREVERB(ns_to);
+
+ mask = spu.dwChannelOn & 0xffffff;
+ for (ch = 0; mask != 0; ch++, mask >>= 1) // loop em all...
+ {
+ if (!(mask & 1)) continue; // channel not playing? next
+
+ s_chan = &spu.s_chan[ch];
+ SB = spu.SB + ch * SB_SIZE;
+ sinc = s_chan->sinc;
+
+ if (s_chan->bNoise)
+ d = do_samples_noise(ch, ns_to);
+ else if (s_chan->bFMod == 2
+ || (s_chan->bFMod == 0 && spu_config.iUseInterpolation == 0))
+ d = do_samples_noint(decode_block, ch, ns_to,
+ SB, sinc, &s_chan->spos, &s_chan->iSBPos);
+ else if (s_chan->bFMod == 0 && spu_config.iUseInterpolation == 1)
+ d = do_samples_simple(decode_block, ch, ns_to,
+ SB, sinc, &s_chan->spos, &s_chan->iSBPos);
+ else
+ d = do_samples_default(decode_block, ch, ns_to,
+ SB, sinc, &s_chan->spos, &s_chan->iSBPos);
+
+ d = MixADSR(&s_chan->ADSRX, d);
+ if (d < ns_to) {
+ spu.dwChannelOn &= ~(1 << ch);
+ s_chan->ADSRX.EnvelopeVol = 0;
+ memset(&ChanBuf[d], 0, (ns_to - d) * sizeof(ChanBuf[0]));
+ }
+
+ if (ch == 1 || ch == 3)
+ {
+ do_decode_bufs(spu.spuMem, ch/2, ns_to, spu.decode_pos);
+ spu.decode_dirty_ch |= 1 << ch;
+ }
+
+ if (s_chan->bFMod == 2) // fmod freq channel
+ memcpy(iFMod, &ChanBuf, ns_to * sizeof(iFMod[0]));
+ if (s_chan->bRVBActive)
+ mix_chan_rvb(0, ns_to, s_chan->iLeftVolume, s_chan->iRightVolume, spu.sRVBStart);
+ else
+ mix_chan(0, ns_to, s_chan->iLeftVolume, s_chan->iRightVolume);
+ }
+}
+
+static void do_samples_finish(int ns_to, int silentch, int decode_pos);
+
+// optional worker thread handling
+
+#if defined(THREAD_ENABLED) || defined(WANT_THREAD_CODE)
+
+static void thread_work_start(void);
+static void thread_work_wait_sync(void);
+static void thread_sync_caches(void);
+
+static void queue_channel_work(int ns_to, int silentch)
+{
+ const SPUCHAN *s_chan;
+ unsigned int mask;
+ int ch;
+
+ worker->ns_to = ns_to;
+ worker->ctrl = spu.spuCtrl;
+ worker->decode_pos = spu.decode_pos;
+ worker->silentch = silentch;
+
+ mask = worker->chmask = spu.dwChannelOn & 0xffffff;
+ for (ch = 0; mask != 0; ch++, mask >>= 1)
+ {
+ if (!(mask & 1)) continue;
+
+ s_chan = &spu.s_chan[ch];
+ worker->ch[ch].spos = s_chan->spos;
+ worker->ch[ch].sbpos = s_chan->iSBPos;
+ worker->ch[ch].sinc = s_chan->sinc;
+ worker->ch[ch].adsr = s_chan->ADSRX;
+ worker->ch[ch].start = s_chan->pCurr - spu.spuMemC;
+ worker->ch[ch].loop = s_chan->pLoop - spu.spuMemC;
+ if (s_chan->prevflags & 1)
+ worker->ch[ch].start = worker->ch[ch].loop;
+
+ worker->ch[ch].ns_to = do_samples_skip(ch, ns_to);
+ }
+
+ worker->pending = 1;
+ thread_work_start();
+}
+
+static void do_channel_work(void)
+{
+ unsigned int mask, endmask = 0;
+ unsigned int decode_dirty_ch = 0;
+ int *SB, sinc, spos, sbpos;
+ int d, ch, ns_to;
+ SPUCHAN *s_chan;
+
+ ns_to = worker->ns_to;
+ memset(spu.sRVBStart, 0, ns_to * sizeof(spu.sRVBStart[0]) * 2);
+
+ mask = worker->chmask;
+ for (ch = 0; mask != 0; ch++, mask >>= 1)
+ {
+ if (!(mask & 1)) continue;
+
+ d = worker->ch[ch].ns_to;
+ spos = worker->ch[ch].spos;
+ sbpos = worker->ch[ch].sbpos;
+ sinc = worker->ch[ch].sinc;
+
+ s_chan = &spu.s_chan[ch];
+ SB = spu.SB + ch * SB_SIZE;
+
+ if (s_chan->bNoise)
+ do_lsfr_samples(d, worker->ctrl, &spu.dwNoiseCount, &spu.dwNoiseVal);
+ else if (s_chan->bFMod == 2
+ || (s_chan->bFMod == 0 && spu_config.iUseInterpolation == 0))
+ do_samples_noint(decode_block_work, ch, d, SB, sinc, &spos, &sbpos);
+ else if (s_chan->bFMod == 0 && spu_config.iUseInterpolation == 1)
+ do_samples_simple(decode_block_work, ch, d, SB, sinc, &spos, &sbpos);
+ else
+ do_samples_default(decode_block_work, ch, d, SB, sinc, &spos, &sbpos);
+
+ d = MixADSR(&worker->ch[ch].adsr, d);
+ if (d < ns_to) {
+ endmask |= 1 << ch;
+ worker->ch[ch].adsr.EnvelopeVol = 0;
+ memset(&ChanBuf[d], 0, (ns_to - d) * sizeof(ChanBuf[0]));
+ }
+ worker->r.ch[ch].adsrState = worker->ch[ch].adsr.State;
+ worker->r.ch[ch].adsrEnvelopeVol = worker->ch[ch].adsr.EnvelopeVol;
+
+ if (ch == 1 || ch == 3)
+ {
+ do_decode_bufs(spu.spuMem, ch/2, ns_to, worker->decode_pos);
+ decode_dirty_ch |= 1 << ch;
+ }
+
+ if (s_chan->bFMod == 2) // fmod freq channel
+ memcpy(iFMod, &ChanBuf, ns_to * sizeof(iFMod[0]));
+ if (s_chan->bRVBActive)
+ mix_chan_rvb(0, ns_to, s_chan->iLeftVolume, s_chan->iRightVolume, spu.sRVBStart);
+ else
+ mix_chan(0, ns_to, s_chan->iLeftVolume, s_chan->iRightVolume);
+ }
+
+ worker->r.chan_end = endmask;
+ worker->r.decode_dirty = decode_dirty_ch;
+}
+
+static void sync_worker_thread(int do_direct)
+{
+ unsigned int mask;
+ int ch;
+
+ if (do_direct)
+ thread_sync_caches();
+ if (!worker->pending)
+ return;
+
+ thread_work_wait_sync();
+ worker->pending = 0;
+
+ mask = worker->chmask;
+ for (ch = 0; mask != 0; ch++, mask >>= 1) {
+ if (!(mask & 1)) continue;
+
+ // be sure there was no keyoff while thread was working
+ if (spu.s_chan[ch].ADSRX.State != ADSR_RELEASE)
+ spu.s_chan[ch].ADSRX.State = worker->r.ch[ch].adsrState;
+ spu.s_chan[ch].ADSRX.EnvelopeVol = worker->r.ch[ch].adsrEnvelopeVol;
+ }
+
+ spu.dwChannelOn &= ~worker->r.chan_end;
+ spu.decode_dirty_ch |= worker->r.decode_dirty;
+
+ do_samples_finish(worker->ns_to, worker->silentch,
+ worker->decode_pos);
+}
+
+#else
+
+static void queue_channel_work(int ns_to, int silentch) {}
+static void sync_worker_thread(int do_direct) {}
+
+#endif // THREAD_ENABLED
+