gte: backport fixes from pcsxr
[pcsx_rearmed.git] / plugins / dfsound / registers.c
1 /***************************************************************************\r
2                          registers.c  -  description\r
3                              -------------------\r
4     begin                : Wed May 15 2002\r
5     copyright            : (C) 2002 by Pete Bernert\r
6     email                : BlackDove@addcom.de\r
7  ***************************************************************************/\r
8 /***************************************************************************\r
9  *                                                                         *\r
10  *   This program is free software; you can redistribute it and/or modify  *\r
11  *   it under the terms of the GNU General Public License as published by  *\r
12  *   the Free Software Foundation; either version 2 of the License, or     *\r
13  *   (at your option) any later version. See also the license.txt file for *\r
14  *   additional informations.                                              *\r
15  *                                                                         *\r
16  ***************************************************************************/\r
17 \r
18 #include "stdafx.h"\r
19 \r
20 #define _IN_REGISTERS\r
21 \r
22 #include "externals.h"\r
23 #include "registers.h"\r
24 #include "regs.h"\r
25 #include "reverb.h"\r
26 \r
27 /*\r
28 // adsr time values (in ms) by James Higgs ... see the end of\r
29 // the adsr.c source for details\r
30 \r
31 #define ATTACK_MS     514L\r
32 #define DECAYHALF_MS  292L\r
33 #define DECAY_MS      584L\r
34 #define SUSTAIN_MS    450L\r
35 #define RELEASE_MS    446L\r
36 */\r
37 \r
38 // we have a timebase of 1.020408f ms, not 1 ms... so adjust adsr defines\r
39 #define ATTACK_MS      494L\r
40 #define DECAYHALF_MS   286L\r
41 #define DECAY_MS       572L\r
42 #define SUSTAIN_MS     441L\r
43 #define RELEASE_MS     437L\r
44 \r
45 ////////////////////////////////////////////////////////////////////////\r
46 // WRITE REGISTERS: called by main emu\r
47 ////////////////////////////////////////////////////////////////////////\r
48 \r
49 void CALLBACK SPUwriteRegister(unsigned long reg, unsigned short val)\r
50 {\r
51  const unsigned long r=reg&0xfff;\r
52  regArea[(r-0xc00)>>1] = val;\r
53 \r
54  if(r>=0x0c00 && r<0x0d80)                             // some channel info?\r
55   {\r
56    int ch=(r>>4)-0xc0;                                 // calc channel\r
57    switch(r&0x0f)\r
58     {\r
59      //------------------------------------------------// r volume\r
60      case 0:                                           \r
61        SetVolumeL((unsigned char)ch,val);\r
62        break;\r
63      //------------------------------------------------// l volume\r
64      case 2:                                           \r
65        SetVolumeR((unsigned char)ch,val);\r
66        break;\r
67      //------------------------------------------------// pitch\r
68      case 4:                                           \r
69        SetPitch(ch,val);\r
70        break;\r
71      //------------------------------------------------// start\r
72      case 6:      \r
73        s_chan[ch].pStart=spuMemC+((unsigned long) val<<3);\r
74        break;\r
75      //------------------------------------------------// level with pre-calcs\r
76      case 8:\r
77        {\r
78         const unsigned long lval=val;unsigned long lx;\r
79         //---------------------------------------------//\r
80         s_chan[ch].ADSRX.AttackModeExp=(lval&0x8000)?1:0; \r
81         s_chan[ch].ADSRX.AttackRate=(lval>>8) & 0x007f;\r
82         s_chan[ch].ADSRX.DecayRate=(lval>>4) & 0x000f;\r
83         s_chan[ch].ADSRX.SustainLevel=lval & 0x000f;\r
84         //---------------------------------------------//\r
85         if(!iDebugMode) break;\r
86         //---------------------------------------------// stuff below is only for debug mode\r
87 \r
88         s_chan[ch].ADSR.AttackModeExp=(lval&0x8000)?1:0;        //0x007f\r
89 \r
90         lx=(((lval>>8) & 0x007f)>>2);                  // attack time to run from 0 to 100% volume\r
91         lx=min(31,lx);                                 // no overflow on shift!\r
92         if(lx) \r
93          { \r
94           lx = (1<<lx);\r
95           if(lx<2147483) lx=(lx*ATTACK_MS)/10000L;     // another overflow check\r
96           else           lx=(lx/10000L)*ATTACK_MS;\r
97           if(!lx) lx=1;\r
98          }\r
99         s_chan[ch].ADSR.AttackTime=lx;                \r
100 \r
101         s_chan[ch].ADSR.SustainLevel=                 // our adsr vol runs from 0 to 1024, so scale the sustain level\r
102          (1024*((lval) & 0x000f))/15;\r
103 \r
104         lx=(lval>>4) & 0x000f;                         // decay:\r
105         if(lx)                                         // our const decay value is time it takes from 100% to 0% of volume\r
106          {\r
107           lx = ((1<<(lx))*DECAY_MS)/10000L;\r
108           if(!lx) lx=1;\r
109          }\r
110         s_chan[ch].ADSR.DecayTime =                   // so calc how long does it take to run from 100% to the wanted sus level\r
111          (lx*(1024-s_chan[ch].ADSR.SustainLevel))/1024;\r
112        }\r
113       break;\r
114      //------------------------------------------------// adsr times with pre-calcs\r
115      case 10:\r
116       {\r
117        const unsigned long lval=val;unsigned long lx;\r
118 \r
119        //----------------------------------------------//\r
120        s_chan[ch].ADSRX.SustainModeExp = (lval&0x8000)?1:0;\r
121        s_chan[ch].ADSRX.SustainIncrease= (lval&0x4000)?0:1;\r
122        s_chan[ch].ADSRX.SustainRate = (lval>>6) & 0x007f;\r
123        s_chan[ch].ADSRX.ReleaseModeExp = (lval&0x0020)?1:0;\r
124        s_chan[ch].ADSRX.ReleaseRate = lval & 0x001f;\r
125        //----------------------------------------------//\r
126        if(!iDebugMode) break;\r
127        //----------------------------------------------// stuff below is only for debug mode\r
128 \r
129        s_chan[ch].ADSR.SustainModeExp = (lval&0x8000)?1:0;\r
130        s_chan[ch].ADSR.ReleaseModeExp = (lval&0x0020)?1:0;\r
131                    \r
132        lx=((((lval>>6) & 0x007f)>>2));                 // sustain time... often very high\r
133        lx=min(31,lx);                                  // values are used to hold the volume\r
134        if(lx)                                          // until a sound stop occurs\r
135         {                                              // the highest value we reach (due to \r
136          lx = (1<<lx);                                 // overflow checking) is: \r
137          if(lx<2147483) lx=(lx*SUSTAIN_MS)/10000L;     // 94704 seconds = 1578 minutes = 26 hours... \r
138          else           lx=(lx/10000L)*SUSTAIN_MS;     // should be enuff... if the stop doesn't \r
139          if(!lx) lx=1;                                 // come in this time span, I don't care :)\r
140         }\r
141        s_chan[ch].ADSR.SustainTime = lx;\r
142 \r
143        lx=(lval & 0x001f);\r
144        s_chan[ch].ADSR.ReleaseVal     =lx;\r
145        if(lx)                                          // release time from 100% to 0%\r
146         {                                              // note: the release time will be\r
147          lx = (1<<lx);                                 // adjusted when a stop is coming,\r
148          if(lx<2147483) lx=(lx*RELEASE_MS)/10000L;     // so at this time the adsr vol will \r
149          else           lx=(lx/10000L)*RELEASE_MS;     // run from (current volume) to 0%\r
150          if(!lx) lx=1;\r
151         }\r
152        s_chan[ch].ADSR.ReleaseTime=lx;\r
153 \r
154        if(lval & 0x4000)                               // add/dec flag\r
155             s_chan[ch].ADSR.SustainModeDec=-1;\r
156        else s_chan[ch].ADSR.SustainModeDec=1;\r
157       }\r
158      break;\r
159      //------------------------------------------------// adsr volume... mmm have to investigate this\r
160      case 12:\r
161        break;\r
162      //------------------------------------------------//\r
163      case 14:                                          // loop?\r
164        //WaitForSingleObject(s_chan[ch].hMutex,2000);        // -> no multithread fuckups\r
165        s_chan[ch].pLoop=spuMemC+((unsigned long) val<<3);\r
166        s_chan[ch].bIgnoreLoop=1;\r
167        //ReleaseMutex(s_chan[ch].hMutex);                    // -> oki, on with the thread\r
168        break;\r
169      //------------------------------------------------//\r
170     }\r
171    iSpuAsyncWait=0;\r
172    return;\r
173   }\r
174 \r
175  switch(r)\r
176    {\r
177     //-------------------------------------------------//\r
178     case H_SPUaddr:\r
179       spuAddr = (unsigned long) val<<3;\r
180       break;\r
181     //-------------------------------------------------//\r
182     case H_SPUdata:\r
183       spuMem[spuAddr>>1] = val;\r
184       spuAddr+=2;\r
185       if(spuAddr>0x7ffff) spuAddr=0;\r
186       break;\r
187     //-------------------------------------------------//\r
188     case H_SPUctrl:\r
189       spuCtrl=val;\r
190       break;\r
191     //-------------------------------------------------//\r
192     case H_SPUstat:\r
193       spuStat=val & 0xf800;\r
194       break;\r
195     //-------------------------------------------------//\r
196     case H_SPUReverbAddr:\r
197       if(val==0xFFFF || val<=0x200)\r
198        {rvb.StartAddr=rvb.CurrAddr=0;}\r
199       else\r
200        {\r
201         const long iv=(unsigned long)val<<2;\r
202         if(rvb.StartAddr!=iv)\r
203          {\r
204           rvb.StartAddr=(unsigned long)val<<2;\r
205           rvb.CurrAddr=rvb.StartAddr;\r
206          }\r
207        }\r
208       break;\r
209     //-------------------------------------------------//\r
210     case H_SPUirqAddr:\r
211       spuIrq = val;\r
212       pSpuIrq=spuMemC+((unsigned long) val<<3);\r
213       break;\r
214     //-------------------------------------------------//\r
215     case H_SPUrvolL:\r
216       rvb.VolLeft=val;\r
217       break;\r
218     //-------------------------------------------------//\r
219     case H_SPUrvolR:\r
220       rvb.VolRight=val;\r
221       break;\r
222     //-------------------------------------------------//\r
223 \r
224 /*\r
225     case H_ExtLeft:\r
226      //auxprintf("EL %d\n",val);\r
227       break;\r
228     //-------------------------------------------------//\r
229     case H_ExtRight:\r
230      //auxprintf("ER %d\n",val);\r
231       break;\r
232     //-------------------------------------------------//\r
233     case H_SPUmvolL:\r
234      //auxprintf("ML %d\n",val);\r
235       break;\r
236     //-------------------------------------------------//\r
237     case H_SPUmvolR:\r
238      //auxprintf("MR %d\n",val);\r
239       break;\r
240     //-------------------------------------------------//\r
241     case H_SPUMute1:\r
242      //auxprintf("M0 %04x\n",val);\r
243       break;\r
244     //-------------------------------------------------//\r
245     case H_SPUMute2:\r
246      //auxprintf("M1 %04x\n",val);\r
247       break;\r
248 */\r
249     //-------------------------------------------------//\r
250     case H_SPUon1:\r
251       SoundOn(0,16,val);\r
252       break;\r
253     //-------------------------------------------------//\r
254      case H_SPUon2:\r
255       SoundOn(16,24,val);\r
256       break;\r
257     //-------------------------------------------------//\r
258     case H_SPUoff1:\r
259       SoundOff(0,16,val);\r
260       break;\r
261     //-------------------------------------------------//\r
262     case H_SPUoff2:\r
263       SoundOff(16,24,val);\r
264       break;\r
265     //-------------------------------------------------//\r
266     case H_CDLeft:\r
267       iLeftXAVol=val  & 0x7fff;\r
268       if(cddavCallback) cddavCallback(0,val);\r
269       break;\r
270     case H_CDRight:\r
271       iRightXAVol=val & 0x7fff;\r
272       if(cddavCallback) cddavCallback(1,val);\r
273       break;\r
274     //-------------------------------------------------//\r
275     case H_FMod1:\r
276       FModOn(0,16,val);\r
277       break;\r
278     //-------------------------------------------------//\r
279     case H_FMod2:\r
280       FModOn(16,24,val);\r
281       break;\r
282     //-------------------------------------------------//\r
283     case H_Noise1:\r
284       NoiseOn(0,16,val);\r
285       break;\r
286     //-------------------------------------------------//\r
287     case H_Noise2:\r
288       NoiseOn(16,24,val);\r
289       break;\r
290     //-------------------------------------------------//\r
291     case H_RVBon1:\r
292       ReverbOn(0,16,val);\r
293       break;\r
294     //-------------------------------------------------//\r
295     case H_RVBon2:\r
296       ReverbOn(16,24,val);\r
297       break;\r
298     //-------------------------------------------------//\r
299     case H_Reverb+0:\r
300 \r
301       rvb.FB_SRC_A=val;\r
302 \r
303       // OK, here's the fake REVERB stuff...\r
304       // depending on effect we do more or less delay and repeats... bah\r
305       // still... better than nothing :)\r
306 \r
307       SetREVERB(val);\r
308       break;\r
309 \r
310 \r
311     case H_Reverb+2   : rvb.FB_SRC_B=(short)val;       break;\r
312     case H_Reverb+4   : rvb.IIR_ALPHA=(short)val;      break;\r
313     case H_Reverb+6   : rvb.ACC_COEF_A=(short)val;     break;\r
314     case H_Reverb+8   : rvb.ACC_COEF_B=(short)val;     break;\r
315     case H_Reverb+10  : rvb.ACC_COEF_C=(short)val;     break;\r
316     case H_Reverb+12  : rvb.ACC_COEF_D=(short)val;     break;\r
317     case H_Reverb+14  : rvb.IIR_COEF=(short)val;       break;\r
318     case H_Reverb+16  : rvb.FB_ALPHA=(short)val;       break;\r
319     case H_Reverb+18  : rvb.FB_X=(short)val;           break;\r
320     case H_Reverb+20  : rvb.IIR_DEST_A0=(short)val;    break;\r
321     case H_Reverb+22  : rvb.IIR_DEST_A1=(short)val;    break;\r
322     case H_Reverb+24  : rvb.ACC_SRC_A0=(short)val;     break;\r
323     case H_Reverb+26  : rvb.ACC_SRC_A1=(short)val;     break;\r
324     case H_Reverb+28  : rvb.ACC_SRC_B0=(short)val;     break;\r
325     case H_Reverb+30  : rvb.ACC_SRC_B1=(short)val;     break;\r
326     case H_Reverb+32  : rvb.IIR_SRC_A0=(short)val;     break;\r
327     case H_Reverb+34  : rvb.IIR_SRC_A1=(short)val;     break;\r
328     case H_Reverb+36  : rvb.IIR_DEST_B0=(short)val;    break;\r
329     case H_Reverb+38  : rvb.IIR_DEST_B1=(short)val;    break;\r
330     case H_Reverb+40  : rvb.ACC_SRC_C0=(short)val;     break;\r
331     case H_Reverb+42  : rvb.ACC_SRC_C1=(short)val;     break;\r
332     case H_Reverb+44  : rvb.ACC_SRC_D0=(short)val;     break;\r
333     case H_Reverb+46  : rvb.ACC_SRC_D1=(short)val;     break;\r
334     case H_Reverb+48  : rvb.IIR_SRC_B1=(short)val;     break;\r
335     case H_Reverb+50  : rvb.IIR_SRC_B0=(short)val;     break;\r
336     case H_Reverb+52  : rvb.MIX_DEST_A0=(short)val;    break;\r
337     case H_Reverb+54  : rvb.MIX_DEST_A1=(short)val;    break;\r
338     case H_Reverb+56  : rvb.MIX_DEST_B0=(short)val;    break;\r
339     case H_Reverb+58  : rvb.MIX_DEST_B1=(short)val;    break;\r
340     case H_Reverb+60  : rvb.IN_COEF_L=(short)val;      break;\r
341     case H_Reverb+62  : rvb.IN_COEF_R=(short)val;      break;\r
342    }\r
343 \r
344  iSpuAsyncWait=0;\r
345 }\r
346 \r
347 ////////////////////////////////////////////////////////////////////////\r
348 // READ REGISTER: called by main emu\r
349 ////////////////////////////////////////////////////////////////////////\r
350 \r
351 unsigned short CALLBACK SPUreadRegister(unsigned long reg)\r
352 {\r
353  const unsigned long r=reg&0xfff;\r
354         \r
355  iSpuAsyncWait=0;\r
356 \r
357  if(r>=0x0c00 && r<0x0d80)\r
358   {\r
359    switch(r&0x0f)\r
360     {\r
361      case 12:                                          // get adsr vol\r
362       {\r
363        const int ch=(r>>4)-0xc0;\r
364        if(s_chan[ch].bNew) return 1;                   // we are started, but not processed? return 1\r
365        if(s_chan[ch].ADSRX.lVolume &&                  // same here... we haven't decoded one sample yet, so no envelope yet. return 1 as well\r
366           !s_chan[ch].ADSRX.EnvelopeVol)                   \r
367         return 1;\r
368        return (unsigned short)(s_chan[ch].ADSRX.EnvelopeVol>>16);\r
369       }\r
370 \r
371      case 14:                                          // get loop address\r
372       {\r
373        const int ch=(r>>4)-0xc0;\r
374        if(s_chan[ch].pLoop==NULL) return 0;\r
375        return (unsigned short)((s_chan[ch].pLoop-spuMemC)>>3);\r
376       }\r
377     }\r
378   }\r
379 \r
380  switch(r)\r
381   {\r
382     case H_SPUctrl:\r
383      return spuCtrl;\r
384 \r
385     case H_SPUstat:\r
386      return spuStat;\r
387         \r
388     case H_SPUaddr:\r
389      return (unsigned short)(spuAddr>>3);\r
390 \r
391     case H_SPUdata:\r
392      {\r
393       unsigned short s=spuMem[spuAddr>>1];\r
394       spuAddr+=2;\r
395       if(spuAddr>0x7ffff) spuAddr=0;\r
396       return s;\r
397      }\r
398 \r
399     case H_SPUirqAddr:\r
400      return spuIrq;\r
401 \r
402     //case H_SPUIsOn1:\r
403     // return IsSoundOn(0,16);\r
404 \r
405     //case H_SPUIsOn2:\r
406     // return IsSoundOn(16,24);\r
407  \r
408   }\r
409 \r
410  return regArea[(r-0xc00)>>1];\r
411 }\r
412  \r
413 ////////////////////////////////////////////////////////////////////////\r
414 // SOUND ON register write\r
415 ////////////////////////////////////////////////////////////////////////\r
416 \r
417 void SoundOn(int start,int end,unsigned short val)     // SOUND ON PSX COMAND\r
418 {\r
419  int ch;\r
420 \r
421  for(ch=start;ch<end;ch++,val>>=1)                     // loop channels\r
422   {\r
423    if((val&1) && s_chan[ch].pStart)                    // mmm... start has to be set before key on !?!\r
424     {\r
425      s_chan[ch].bIgnoreLoop=0;\r
426      s_chan[ch].bNew=1;\r
427      dwNewChannel|=(1<<ch);                            // bitfield for faster testing\r
428     }\r
429   }\r
430 }\r
431 \r
432 ////////////////////////////////////////////////////////////////////////\r
433 // SOUND OFF register write\r
434 ////////////////////////////////////////////////////////////////////////\r
435 \r
436 void SoundOff(int start,int end,unsigned short val)    // SOUND OFF PSX COMMAND\r
437 {\r
438  int ch;\r
439  for(ch=start;ch<end;ch++,val>>=1)                     // loop channels\r
440   {\r
441    if(val&1)                                           // && s_chan[i].bOn)  mmm...\r
442     {\r
443      s_chan[ch].bStop=1;\r
444     }                                                  \r
445   }\r
446 }\r
447 \r
448 ////////////////////////////////////////////////////////////////////////\r
449 // FMOD register write\r
450 ////////////////////////////////////////////////////////////////////////\r
451 \r
452 void FModOn(int start,int end,unsigned short val)      // FMOD ON PSX COMMAND\r
453 {\r
454  int ch;\r
455 \r
456  for(ch=start;ch<end;ch++,val>>=1)                     // loop channels\r
457   {\r
458    if(val&1)                                           // -> fmod on/off\r
459     {\r
460      if(ch>0) \r
461       {\r
462        s_chan[ch].bFMod=1;                             // --> sound channel\r
463        s_chan[ch-1].bFMod=2;                           // --> freq channel\r
464       }\r
465     }\r
466    else\r
467     {\r
468      s_chan[ch].bFMod=0;                               // --> turn off fmod\r
469     }\r
470   }\r
471 }\r
472 \r
473 ////////////////////////////////////////////////////////////////////////\r
474 // NOISE register write\r
475 ////////////////////////////////////////////////////////////////////////\r
476 \r
477 void NoiseOn(int start,int end,unsigned short val)     // NOISE ON PSX COMMAND\r
478 {\r
479  int ch;\r
480 \r
481  for(ch=start;ch<end;ch++,val>>=1)                     // loop channels\r
482   {\r
483    if(val&1)                                           // -> noise on/off\r
484     {\r
485      s_chan[ch].bNoise=1;\r
486     }\r
487    else \r
488     {\r
489      s_chan[ch].bNoise=0;\r
490     }\r
491   }\r
492 }\r
493 \r
494 ////////////////////////////////////////////////////////////////////////\r
495 // LEFT VOLUME register write\r
496 ////////////////////////////////////////////////////////////////////////\r
497 \r
498 // please note: sweep and phase invert are wrong... but I've never seen\r
499 // them used\r
500 \r
501 void SetVolumeL(unsigned char ch,short vol)            // LEFT VOLUME\r
502 {\r
503  s_chan[ch].iLeftVolRaw=vol;\r
504 \r
505  if(vol&0x8000)                                        // sweep?\r
506   {\r
507    short sInc=1;                                       // -> sweep up?\r
508    if(vol&0x2000) sInc=-1;                             // -> or down?\r
509    if(vol&0x1000) vol^=0xffff;                         // -> mmm... phase inverted? have to investigate this\r
510    vol=((vol&0x7f)+1)/2;                               // -> sweep: 0..127 -> 0..64\r
511    vol+=vol/(2*sInc);                                  // -> HACK: we don't sweep right now, so we just raise/lower the volume by the half!\r
512    vol*=128;\r
513   }\r
514  else                                                  // no sweep:\r
515   {\r
516    if(vol&0x4000)                                      // -> mmm... phase inverted? have to investigate this\r
517     //vol^=0xffff;\r
518     vol=0x3fff-(vol&0x3fff);\r
519   }\r
520 \r
521  vol&=0x3fff;\r
522  s_chan[ch].iLeftVolume=vol;                           // store volume\r
523 }\r
524 \r
525 ////////////////////////////////////////////////////////////////////////\r
526 // RIGHT VOLUME register write\r
527 ////////////////////////////////////////////////////////////////////////\r
528 \r
529 void SetVolumeR(unsigned char ch,short vol)            // RIGHT VOLUME\r
530 {\r
531  s_chan[ch].iRightVolRaw=vol;\r
532 \r
533  if(vol&0x8000)                                        // comments... see above :)\r
534   {\r
535    short sInc=1;\r
536    if(vol&0x2000) sInc=-1;\r
537    if(vol&0x1000) vol^=0xffff;\r
538    vol=((vol&0x7f)+1)/2;        \r
539    vol+=vol/(2*sInc);\r
540    vol*=128;\r
541   }\r
542  else            \r
543   {\r
544    if(vol&0x4000) //vol=vol^=0xffff;\r
545     vol=0x3fff-(vol&0x3fff);\r
546   }\r
547 \r
548  vol&=0x3fff;\r
549 \r
550  s_chan[ch].iRightVolume=vol;\r
551 }\r
552 \r
553 ////////////////////////////////////////////////////////////////////////\r
554 // PITCH register write\r
555 ////////////////////////////////////////////////////////////////////////\r
556 \r
557 void SetPitch(int ch,unsigned short val)               // SET PITCH\r
558 {\r
559  int NP;\r
560  if(val>0x3fff) NP=0x3fff;                             // get pitch val\r
561  else           NP=val;\r
562 \r
563  s_chan[ch].iRawPitch=NP;\r
564 \r
565  NP=(44100L*NP)/4096L;                                 // calc frequency\r
566  if(NP<1) NP=1;                                        // some security\r
567  s_chan[ch].iActFreq=NP;                               // store frequency\r
568 }\r
569 \r
570 ////////////////////////////////////////////////////////////////////////\r
571 // REVERB register write\r
572 ////////////////////////////////////////////////////////////////////////\r
573 \r
574 void ReverbOn(int start,int end,unsigned short val)    // REVERB ON PSX COMMAND\r
575 {\r
576  int ch;\r
577 \r
578  for(ch=start;ch<end;ch++,val>>=1)                     // loop channels\r
579   {\r
580    if(val&1)                                           // -> reverb on/off\r
581     {\r
582      s_chan[ch].bReverb=1;\r
583     }\r
584    else \r
585     {\r
586      s_chan[ch].bReverb=0;\r
587     }\r
588   }\r
589 }\r