+ 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