1 /***************************************************************************
\r
2 registers.c - description
\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
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
16 ***************************************************************************/
\r
20 #define _IN_REGISTERS
\r
22 #include "externals.h"
\r
23 #include "registers.h"
\r
24 #include "spu_config.h"
\r
27 static void SoundOn(int start,int end,unsigned short val);
\r
28 static void SoundOff(int start,int end,unsigned short val);
\r
29 static void FModOn(int start,int end,unsigned short val);
\r
30 static void NoiseOn(int start,int end,unsigned short val);
\r
31 static void SetVolumeL(unsigned char ch,short vol);
\r
32 static void SetVolumeR(unsigned char ch,short vol);
\r
33 static void SetPitch(int ch,unsigned short val);
\r
34 static void ReverbOn(int start,int end,unsigned short val);
\r
36 ////////////////////////////////////////////////////////////////////////
\r
37 // WRITE REGISTERS: called by main emu
\r
38 ////////////////////////////////////////////////////////////////////////
\r
40 static const uint32_t ignore_dupe[16] = {
\r
41 // ch 0-15 c40 c80 cc0
\r
42 0x7f7f7f7f, 0x7f7f7f7f, 0x7f7f7f7f, 0x7f7f7f7f,
\r
43 // ch 16-24 d40 control reverb
\r
44 0x7f7f7f7f, 0x7f7f7f7f, 0xff05ff0f, 0xffffffff,
\r
45 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
\r
46 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff
\r
49 void CALLBACK SPUwriteRegister(unsigned long reg, unsigned short val,
\r
50 unsigned int cycles)
\r
52 int r = reg & 0xffe;
\r
53 int rofs = (r - 0xc00) >> 1;
\r
54 int changed = spu.regArea[rofs] != val;
\r
55 spu.regArea[rofs] = val;
\r
57 if (!changed && (ignore_dupe[rofs >> 5] & (1u << (rofs & 0x1f))))
\r
59 // zero keyon/keyoff?
\r
60 if (val == 0 && (r & 0xff8) == 0xd88)
\r
63 do_samples_if_needed(cycles, 0, 16);
\r
65 if(r>=0x0c00 && r<0x0d80) // some channel info?
\r
67 int ch=(r>>4)-0xc0; // calc channel
\r
70 //------------------------------------------------// r volume
\r
72 SetVolumeL((unsigned char)ch,val);
\r
74 //------------------------------------------------// l volume
\r
76 SetVolumeR((unsigned char)ch,val);
\r
78 //------------------------------------------------// pitch
\r
82 //------------------------------------------------// start
\r
84 // taken from regArea later
\r
86 //------------------------------------------------// level with pre-calcs
\r
89 const unsigned long lval=val;
\r
90 //---------------------------------------------//
\r
91 spu.s_chan[ch].ADSRX.AttackModeExp=(lval&0x8000)?1:0;
\r
92 spu.s_chan[ch].ADSRX.AttackRate=(lval>>8) & 0x007f;
\r
93 spu.s_chan[ch].ADSRX.DecayRate=(lval>>4) & 0x000f;
\r
94 spu.s_chan[ch].ADSRX.SustainLevel=lval & 0x000f;
\r
95 //---------------------------------------------//
\r
98 //------------------------------------------------// adsr times with pre-calcs
\r
101 const unsigned long lval=val;
\r
103 //----------------------------------------------//
\r
104 spu.s_chan[ch].ADSRX.SustainModeExp = (lval&0x8000)?1:0;
\r
105 spu.s_chan[ch].ADSRX.SustainIncrease= (lval&0x4000)?0:1;
\r
106 spu.s_chan[ch].ADSRX.SustainRate = (lval>>6) & 0x007f;
\r
107 spu.s_chan[ch].ADSRX.ReleaseModeExp = (lval&0x0020)?1:0;
\r
108 spu.s_chan[ch].ADSRX.ReleaseRate = lval & 0x001f;
\r
109 //----------------------------------------------//
\r
112 //------------------------------------------------// adsr volume... mmm have to investigate this
\r
115 //------------------------------------------------//
\r
117 spu.s_chan[ch].pLoop=spu.spuMemC+((val&~1)<<3);
\r
118 spu.s_chan[ch].bIgnoreLoop = 1;
\r
120 //------------------------------------------------//
\r
124 else if (0x0e00 <= r && r < 0x0e60)
\r
126 int ch = (r >> 2) & 0x1f;
\r
127 log_unhandled("c%02d w %cvol %04x\n", ch, (r & 2) ? 'r' : 'l', val);
\r
128 spu.s_chan[ch].iVolume[(r >> 1) & 1] = (signed short)val >> 1;
\r
133 //-------------------------------------------------//
\r
135 spu.spuAddr = (unsigned int)val << 3;
\r
136 //check_irq_io(spu.spuAddr);
\r
138 //-------------------------------------------------//
\r
140 *(unsigned short *)(spu.spuMemC + spu.spuAddr) = HTOLE16(val);
\r
142 spu.spuAddr &= 0x7fffe;
\r
143 check_irq_io(spu.spuAddr);
\r
145 //-------------------------------------------------//
\r
147 spu.spuStat = (spu.spuStat & ~0xbf) | (val & 0x3f) | ((val << 2) & 0x80);
\r
148 spu.spuStat &= ~STAT_IRQ | val;
\r
149 if (!(spu.spuCtrl & CTRL_IRQ)) {
\r
150 if (val & CTRL_IRQ)
\r
151 schedule_next_irq();
\r
155 //-------------------------------------------------//
\r
157 //spu.spuStat=val&0xf800;
\r
159 //-------------------------------------------------//
\r
160 case H_SPUReverbAddr:
\r
162 //-------------------------------------------------//
\r
165 // log_unhandled("w irq with lsb: %08lx %04x\n", reg, val);
\r
166 spu.pSpuIrq = spu.spuMemC + (((int)val << 3) & ~0xf);
\r
167 //check_irq_io(spu.spuAddr);
\r
169 //-------------------------------------------------//
\r
171 spu.rvb->VolLeft = (int16_t)val;
\r
173 //-------------------------------------------------//
\r
175 spu.rvb->VolRight = (int16_t)val;
\r
177 //-------------------------------------------------//
\r
181 int ofs = H_SPUcmvolL - H_SPUmvolL;
\r
182 unsigned short *cur = ®AreaGet(r + ofs);
\r
183 if (val & 0x8000) {
\r
184 // this (for now?) lacks an update mechanism, so is instant
\r
185 log_unhandled("w master sweep: %08lx %04x\n", reg, val);
\r
186 int was_neg = (*cur >> 14) & 1;
\r
187 int dec = (val >> 13) & 1;
\r
188 int inv = (val >> 12) & 1;
\r
189 *cur = (was_neg ^ dec ^ inv) ? 0x7fff : 0;
\r
198 log_unhandled("1f801dac %04x\n", val);
\r
203 //auxprintf("EL %d\n",val);
\r
205 //-------------------------------------------------//
\r
207 //auxprintf("ER %d\n",val);
\r
209 //-------------------------------------------------//
\r
211 //auxprintf("M0 %04x\n",val);
\r
213 //-------------------------------------------------//
\r
215 //auxprintf("M1 %04x\n",val);
\r
218 //-------------------------------------------------//
\r
220 spu.last_keyon_cycles = cycles;
\r
221 do_samples_if_needed(cycles, 0, 2);
\r
224 //-------------------------------------------------//
\r
226 spu.last_keyon_cycles = cycles;
\r
227 do_samples_if_needed(cycles, 0, 2);
\r
228 SoundOn(16,24,val);
\r
230 //-------------------------------------------------//
\r
232 if (cycles - spu.last_keyon_cycles < 786u) {
\r
233 if (val & regAreaGet(H_SPUon1))
\r
234 log_unhandled("koff1 %04x %d\n", val, cycles - spu.last_keyon_cycles);
\r
235 val &= ~regAreaGet(H_SPUon1);
\r
237 do_samples_if_needed(cycles, 0, 2);
\r
238 SoundOff(0,16,val);
\r
240 //-------------------------------------------------//
\r
242 if (cycles - spu.last_keyon_cycles < 786u) {
\r
243 if (val & regAreaGet(H_SPUon1))
\r
244 log_unhandled("koff2 %04x %d\n", val, cycles - spu.last_keyon_cycles);
\r
245 val &= ~regAreaGet(H_SPUon2);
\r
247 do_samples_if_needed(cycles, 0, 2);
\r
248 SoundOff(16,24,val);
\r
250 //-------------------------------------------------//
\r
252 spu.iLeftXAVol=(int16_t)val;
\r
253 //if(spu.cddavCallback) spu.cddavCallback(0,(int16_t)val);
\r
256 spu.iRightXAVol=(int16_t)val;
\r
257 //if(spu.cddavCallback) spu.cddavCallback(1,(int16_t)val);
\r
259 //-------------------------------------------------//
\r
263 //-------------------------------------------------//
\r
267 //-------------------------------------------------//
\r
271 //-------------------------------------------------//
\r
273 NoiseOn(16,24,val);
\r
275 //-------------------------------------------------//
\r
277 ReverbOn(0,16,val);
\r
279 //-------------------------------------------------//
\r
281 ReverbOn(16,24,val);
\r
283 //-------------------------------------------------//
\r
284 case H_Reverb + 0x00 : goto rvbd;
\r
285 case H_Reverb + 0x02 : goto rvbd;
\r
286 case H_Reverb + 0x04 : spu.rvb->vIIR = (signed short)val; break;
\r
287 case H_Reverb + 0x06 : spu.rvb->vCOMB1 = (signed short)val; break;
\r
288 case H_Reverb + 0x08 : spu.rvb->vCOMB2 = (signed short)val; break;
\r
289 case H_Reverb + 0x0a : spu.rvb->vCOMB3 = (signed short)val; break;
\r
290 case H_Reverb + 0x0c : spu.rvb->vCOMB4 = (signed short)val; break;
\r
291 case H_Reverb + 0x0e : spu.rvb->vWALL = (signed short)val; break;
\r
292 case H_Reverb + 0x10 : spu.rvb->vAPF1 = (signed short)val; break;
\r
293 case H_Reverb + 0x12 : spu.rvb->vAPF2 = (signed short)val; break;
\r
294 case H_Reverb + 0x14 : goto rvbd;
\r
295 case H_Reverb + 0x16 : goto rvbd;
\r
296 case H_Reverb + 0x18 : goto rvbd;
\r
297 case H_Reverb + 0x1a : goto rvbd;
\r
298 case H_Reverb + 0x1c : goto rvbd;
\r
299 case H_Reverb + 0x1e : goto rvbd;
\r
300 case H_Reverb + 0x20 : goto rvbd;
\r
301 case H_Reverb + 0x22 : goto rvbd;
\r
302 case H_Reverb + 0x24 : goto rvbd;
\r
303 case H_Reverb + 0x26 : goto rvbd;
\r
304 case H_Reverb + 0x28 : goto rvbd;
\r
305 case H_Reverb + 0x2a : goto rvbd;
\r
306 case H_Reverb + 0x2c : goto rvbd;
\r
307 case H_Reverb + 0x2e : goto rvbd;
\r
308 case H_Reverb + 0x30 : goto rvbd;
\r
309 case H_Reverb + 0x32 : goto rvbd;
\r
310 case H_Reverb + 0x34 : goto rvbd;
\r
311 case H_Reverb + 0x36 : goto rvbd;
\r
312 case H_Reverb + 0x38 : goto rvbd;
\r
313 case H_Reverb + 0x3a : goto rvbd;
\r
314 case H_Reverb + 0x3c : spu.rvb->vLIN = (signed short)val; break;
\r
315 case H_Reverb + 0x3e : spu.rvb->vRIN = (signed short)val; break;
\r
320 if (spu.spuCtrl & CTRL_IRQ)
\r
321 schedule_next_irq();
\r
325 spu.rvb->dirty = 1; // recalculate on next update
\r
328 ////////////////////////////////////////////////////////////////////////
\r
329 // READ REGISTER: called by main emu
\r
330 ////////////////////////////////////////////////////////////////////////
\r
332 unsigned short CALLBACK SPUreadRegister(unsigned long reg, unsigned int cycles)
\r
334 const unsigned long r = reg & 0xffe;
\r
336 if(r>=0x0c00 && r<0x0d80)
\r
340 case 12: // get adsr vol
\r
342 // this used to return 1 immediately after keyon to deal with
\r
343 // some poor timing, but that causes Rayman 2 to lose track of
\r
344 // it's channels on busy scenes and start looping some of them forever
\r
345 const int ch = (r>>4) - 0xc0;
\r
346 if (spu.s_chan[ch].bStarting)
\r
347 do_samples_if_needed(cycles, 0, 2);
\r
348 return (unsigned short)(spu.s_chan[ch].ADSRX.EnvelopeVol >> 16);
\r
351 case 14: // get loop address
\r
353 const int ch=(r>>4)-0xc0;
\r
354 return (unsigned short)((spu.s_chan[ch].pLoop-spu.spuMemC)>>3);
\r
358 else if (0x0e00 <= r && r < 0x0e60)
\r
360 int ch = (r >> 2) & 0x1f;
\r
361 int v = spu.s_chan[ch].iVolume[(r >> 1) & 1] << 1;
\r
362 log_unhandled("c%02d r %cvol %04x\n", ch, (r & 2) ? 'r' : 'l', v);
\r
369 return spu.spuCtrl;
\r
372 return spu.spuStat;
\r
375 return (unsigned short)(spu.spuAddr>>3);
\r
377 // this reportedly doesn't work on real hw
\r
380 unsigned short s = LE16TOH(*(unsigned short *)(spu.spuMemC + spu.spuAddr));
\r
382 spu.spuAddr &= 0x7fffe;
\r
383 //check_irq_io(spu.spuAddr);
\r
388 // return IsSoundOn(0,16);
\r
391 // return IsSoundOn(16,24);
\r
395 log_unhandled("spu r isOn: %08lx %04x\n", reg, regAreaGet(r));
\r
400 log_unhandled("spu r mvol: %08lx %04x\n", reg, regAreaGet(r));
\r
413 log_unhandled("spu r %08lx %04x\n", reg, regAreaGet(r));
\r
417 return spu.regArea[(r-0xc00)>>1];
\r
420 ////////////////////////////////////////////////////////////////////////
\r
421 // SOUND ON register write
\r
422 ////////////////////////////////////////////////////////////////////////
\r
424 static void SoundOn(int start,int end,unsigned short val)
\r
428 for(ch=start;ch<end;ch++,val>>=1) // loop channels
\r
430 if((val&1) && regAreaGetCh(ch, 6)) // mmm... start has to be set before key on !?!
\r
432 spu.s_chan[ch].bIgnoreLoop = 0;
\r
433 spu.s_chan[ch].bStarting = 1;
\r
434 spu.dwNewChannel|=(1<<ch);
\r
439 ////////////////////////////////////////////////////////////////////////
\r
440 // SOUND OFF register write
\r
441 ////////////////////////////////////////////////////////////////////////
\r
443 static void SoundOff(int start,int end,unsigned short val)
\r
446 for (ch = start; val && ch < end; ch++, val >>= 1) // loop channels
\r
450 spu.s_chan[ch].ADSRX.State = ADSR_RELEASE;
\r
452 // Jungle Book - Rhythm 'n Groove
\r
453 // - turns off buzzing sound (loop hangs)
\r
454 spu.dwNewChannel &= ~(1<<ch);
\r
459 ////////////////////////////////////////////////////////////////////////
\r
460 // FMOD register write
\r
461 ////////////////////////////////////////////////////////////////////////
\r
463 static void FModOn(int start,int end,unsigned short val)
\r
467 for(ch=start;ch<end;ch++,val>>=1) // loop channels
\r
469 if(val&1) // -> fmod on/off
\r
473 spu.s_chan[ch].bFMod=1; // --> sound channel
\r
474 spu.s_chan[ch-1].bFMod=2; // --> freq channel
\r
479 spu.s_chan[ch].bFMod=0; // --> turn off fmod
\r
480 if(ch>0&&spu.s_chan[ch-1].bFMod==2)
\r
481 spu.s_chan[ch-1].bFMod=0;
\r
486 ////////////////////////////////////////////////////////////////////////
\r
487 // NOISE register write
\r
488 ////////////////////////////////////////////////////////////////////////
\r
490 static void NoiseOn(int start,int end,unsigned short val)
\r
494 for(ch=start;ch<end;ch++,val>>=1) // loop channels
\r
496 spu.s_chan[ch].bNoise=val&1; // -> noise on/off
\r
500 ////////////////////////////////////////////////////////////////////////
\r
501 // LEFT VOLUME register write
\r
502 ////////////////////////////////////////////////////////////////////////
\r
504 // please note: sweep and phase invert are wrong... but I've never seen
\r
507 static void SetVolumeL(unsigned char ch,short vol) // LEFT VOLUME
\r
509 if(vol&0x8000) // sweep?
\r
511 short sInc=1; // -> sweep up?
\r
512 log_unhandled("ch%d sweepl %04x\n", ch, vol);
\r
513 if(vol&0x2000) sInc=-1; // -> or down?
\r
514 if(vol&0x1000) vol^=0xffff; // -> mmm... phase inverted? have to investigate this
\r
515 vol=((vol&0x7f)+1)/2; // -> sweep: 0..127 -> 0..64
\r
516 vol+=vol/(2*sInc); // -> HACK: we don't sweep right now, so we just raise/lower the volume by the half!
\r
521 if(vol&0x4000) // -> mmm... phase inverted? have to investigate this
\r
523 vol=0x3fff-(vol&0x3fff);
\r
527 spu.s_chan[ch].iLeftVolume=vol; // store volume
\r
528 //spu.regArea[(0xe00-0xc00)/2 + ch*2 + 0] = vol << 1;
\r
531 ////////////////////////////////////////////////////////////////////////
\r
532 // RIGHT VOLUME register write
\r
533 ////////////////////////////////////////////////////////////////////////
\r
535 static void SetVolumeR(unsigned char ch,short vol) // RIGHT VOLUME
\r
537 if(vol&0x8000) // comments... see above :)
\r
540 log_unhandled("ch%d sweepr %04x\n", ch, vol);
\r
541 if(vol&0x2000) sInc=-1;
\r
542 if(vol&0x1000) vol^=0xffff;
\r
543 vol=((vol&0x7f)+1)/2;
\r
549 if(vol&0x4000) //vol=vol^=0xffff;
\r
550 vol=0x3fff-(vol&0x3fff);
\r
555 spu.s_chan[ch].iRightVolume=vol;
\r
556 //spu.regArea[(0xe00-0xc00)/2 + ch*2 + 1] = vol << 1;
\r
559 ////////////////////////////////////////////////////////////////////////
\r
560 // PITCH register write
\r
561 ////////////////////////////////////////////////////////////////////////
\r
563 static void SetPitch(int ch,unsigned short val) // SET PITCH
\r
566 if(val>0x3fff) NP=0x3fff; // get pitch val
\r
569 spu.s_chan[ch].iRawPitch = NP;
\r
570 spu.s_chan[ch].sinc = NP << 4;
\r
571 spu.s_chan[ch].sinc_inv = 0;
\r
573 // don't mess spu.dwChannelsAudible as adsr runs independently
\r
576 ////////////////////////////////////////////////////////////////////////
\r
577 // REVERB register write
\r
578 ////////////////////////////////////////////////////////////////////////
\r
580 static void ReverbOn(int start,int end,unsigned short val)
\r
584 for(ch=start;ch<end;ch++,val>>=1) // loop channels
\r
586 spu.s_chan[ch].bReverb=val&1; // -> reverb on/off
\r
590 // vim:shiftwidth=1:expandtab
\r