/[pcsx2_0.9.7]/trunk/plugins/SPU2null/SPU2.cpp
ViewVC logotype

Contents of /trunk/plugins/SPU2null/SPU2.cpp

Parent Directory Parent Directory | Revision Log Revision Log


Revision 31 - (show annotations) (download)
Tue Sep 7 03:24:11 2010 UTC (10 years, 10 months ago) by william
File size: 31658 byte(s)
committing r3113 initial commit again...
1 /* SPU2null
2 * Copyright (C) 2002-2005 SPU2null Team
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17 */
18
19 #include "SPU2.h"
20
21 #include <assert.h>
22 #include <stdlib.h>
23 #include <string>
24 using namespace std;
25
26 const u8 version = PS2E_SPU2_VERSION;
27 const u8 revision = 0;
28 const u8 build = 8; // increase that with each version
29 const u32 minor = 0; // increase that with each version
30
31 // ADSR constants
32 #define ATTACK_MS 494L
33 #define DECAYHALF_MS 286L
34 #define DECAY_MS 572L
35 #define SUSTAIN_MS 441L
36 #define RELEASE_MS 437L
37
38 #ifdef PCSX2_DEBUG
39 char *libraryName = "SPU2null (Debug)";
40 #else
41 char *libraryName = "SPU2null ";
42 #endif
43 string s_strIniPath="inis/SPU2null.ini";
44
45 FILE *spu2Log;
46 Config conf;
47
48 ADMA Adma4;
49 ADMA Adma7;
50
51 u32 MemAddr[2];
52 u32 g_nSpuInit = 0;
53 u16 interrupt = 0;
54 s8 *spu2regs = NULL;
55 u16* spu2mem = NULL;
56 u16* pSpuIrq[2] = {NULL};
57 u32 dwEndChannel2[2] = {0}; // keeps track of what channels have ended
58 u32 dwNoiseVal = 1; // global noise generator
59
60 s32 SPUCycles = 0, SPUWorkerCycles = 0;
61 s32 SPUStartCycle[2];
62 s32 SPUTargetCycle[2];
63
64 int ADMAS4Write();
65 int ADMAS7Write();
66
67 void InitADSR();
68
69 void (*irqCallbackSPU2)(); // func of main emu, called on spu irq
70 void (*irqCallbackDMA4)() = 0; // func of main emu, called on spu irq
71 void (*irqCallbackDMA7)() = 0; // func of main emu, called on spu irq
72
73 const s32 f[5][2] = {
74 { 0, 0 },
75 { 60, 0 },
76 { 115, -52 },
77 { 98, -55 },
78 { 122, -60 }
79 };
80
81 u32 RateTable[160];
82
83 // channels and voices
84 VOICE_PROCESSED voices[SPU_NUMBER_VOICES+1]; // +1 for modulation
85
86 EXPORT_C_(u32) PS2EgetLibType()
87 {
88 return PS2E_LT_SPU2;
89 }
90
91 EXPORT_C_(char*) PS2EgetLibName()
92 {
93 return libraryName;
94 }
95
96 EXPORT_C_(u32) PS2EgetLibVersion2(u32 type)
97 {
98 return (version << 16) | (revision << 8) | build | (minor << 24);
99 }
100
101 void __Log(char *fmt, ...)
102 {
103 va_list list;
104
105 if (!conf.Log || spu2Log == NULL) return;
106
107 va_start(list, fmt);
108 vfprintf(spu2Log, fmt, list);
109 va_end(list);
110 }
111
112 EXPORT_C_(s32) SPU2init()
113 {
114 #ifdef SPU2_LOG
115 spu2Log = fopen("logs/spu2.txt", "w");
116 if (spu2Log) setvbuf(spu2Log, NULL, _IONBF, 0);
117 SPU2_LOG("Spu2 null version %d,%d\n", revision, build);
118 SPU2_LOG("SPU2init\n");
119 #endif
120 spu2regs = (s8*)malloc(0x10000);
121 if (spu2regs == NULL)
122 {
123 SysMessage("Error allocating Memory\n");
124 return -1;
125 }
126 memset(spu2regs, 0, 0x10000);
127
128 spu2mem = (u16*)malloc(0x200000); // 2Mb
129 if (spu2mem == NULL)
130 {
131 SysMessage("Error allocating Memory\n");
132 return -1;
133 }
134 memset(spu2mem, 0, 0x200000);
135 memset(dwEndChannel2, 0, sizeof(dwEndChannel2));
136
137 InitADSR();
138
139 memset(voices, 0, sizeof(voices));
140 // last 24 channels have higher mem offset
141 for (int i = 0; i < 24; ++i)
142 voices[i+24].memoffset = 0x400;
143
144 // init each channel
145 for (u32 i = 0; i < ArraySize(voices); ++i)
146 {
147
148 voices[i].pLoop = voices[i].pStart = voices[i].pCurr = (u8*)spu2mem;
149
150 voices[i].pvoice = (_SPU_VOICE*)((u8*)spu2regs + voices[i].memoffset) + (i % 24);
151 voices[i].ADSRX.SustainLevel = 1024; // -> init sustain
152 }
153
154 return 0;
155 }
156
157 EXPORT_C_(s32) SPU2open(void *pDsp)
158 {
159 LoadConfig();
160 SPUCycles = SPUWorkerCycles = 0;
161 interrupt = 0;
162 SPUStartCycle[0] = SPUStartCycle[1] = 0;
163 SPUTargetCycle[0] = SPUTargetCycle[1] = 0;
164 g_nSpuInit = 1;
165 return 0;
166 }
167
168 EXPORT_C_(void) SPU2close()
169 {
170 g_nSpuInit = 0;
171 }
172
173 EXPORT_C_(void) SPU2shutdown()
174 {
175 free(spu2regs);
176 spu2regs = NULL;
177 free(spu2mem);
178 spu2mem = NULL;
179 #ifdef SPU2_LOG
180 if (spu2Log) fclose(spu2Log);
181 #endif
182 }
183
184 // simulate SPU2 for 1ms
185 void SPU2Worker();
186
187 #define CYCLES_PER_MS (36864000/1000)
188
189 EXPORT_C_(void) SPU2async(u32 cycle)
190 {
191 SPUCycles += cycle;
192 if (interrupt & (1 << 2))
193 {
194 if (SPUCycles - SPUStartCycle[1] >= SPUTargetCycle[1])
195 {
196 interrupt &= ~(1 << 2);
197 irqCallbackDMA7();
198 }
199
200 }
201
202 if (interrupt & (1 << 1))
203 {
204 if (SPUCycles - SPUStartCycle[0] >= SPUTargetCycle[0])
205 {
206 interrupt &= ~(1 << 1);
207 irqCallbackDMA4();
208 }
209 }
210
211 if (g_nSpuInit)
212 {
213
214 while (SPUCycles - SPUWorkerCycles > 0 && CYCLES_PER_MS < SPUCycles - SPUWorkerCycles)
215 {
216 SPU2Worker();
217 SPUWorkerCycles += CYCLES_PER_MS;
218 }
219 }
220 }
221
222 void InitADSR() // INIT ADSR
223 {
224 u32 r, rs, rd;
225 s32 i;
226 memset(RateTable, 0, sizeof(u32)*160); // build the rate table according to Neill's rules (see at bottom of file)
227
228 r = 3;
229 rs = 1;
230 rd = 0;
231
232 for (i = 32;i < 160;i++) // we start at pos 32 with the real values... everything before is 0
233 {
234 if (r < 0x3FFFFFFF)
235 {
236 r += rs;
237 rd++;
238 if (rd == 5)
239 {
240 rd = 1;
241 rs *= 2;
242 }
243 }
244 if (r > 0x3FFFFFFF) r = 0x3FFFFFFF;
245
246 RateTable[i] = r;
247 }
248 }
249
250 int MixADSR(VOICE_PROCESSED* pvoice) // MIX ADSR
251 {
252 if (pvoice->bStop) // should be stopped:
253 {
254 if (pvoice->bIgnoreLoop == 0)
255 {
256 pvoice->ADSRX.EnvelopeVol = 0;
257 pvoice->bOn = false;
258 pvoice->pStart = (u8*)(spu2mem + pvoice->iStartAddr);
259 pvoice->pLoop = (u8*)(spu2mem + pvoice->iStartAddr);
260 pvoice->pCurr = (u8*)(spu2mem + pvoice->iStartAddr);
261 pvoice->bStop = true;
262 pvoice->bIgnoreLoop = false;
263 return 0;
264 }
265 if (pvoice->ADSRX.ReleaseModeExp)// do release
266 {
267 switch ((pvoice->ADSRX.EnvelopeVol >> 28)&0x7)
268 {
269 case 0:
270 pvoice->ADSRX.EnvelopeVol -= RateTable[(4*(pvoice->ADSRX.ReleaseRate^0x1F))-0x18 +0 + 32];
271 break;
272 case 1:
273 pvoice->ADSRX.EnvelopeVol -= RateTable[(4*(pvoice->ADSRX.ReleaseRate^0x1F))-0x18 +4 + 32];
274 break;
275 case 2:
276 pvoice->ADSRX.EnvelopeVol -= RateTable[(4*(pvoice->ADSRX.ReleaseRate^0x1F))-0x18 +6 + 32];
277 break;
278 case 3:
279 pvoice->ADSRX.EnvelopeVol -= RateTable[(4*(pvoice->ADSRX.ReleaseRate^0x1F))-0x18 +8 + 32];
280 break;
281 case 4:
282 pvoice->ADSRX.EnvelopeVol -= RateTable[(4*(pvoice->ADSRX.ReleaseRate^0x1F))-0x18 +9 + 32];
283 break;
284 case 5:
285 pvoice->ADSRX.EnvelopeVol -= RateTable[(4*(pvoice->ADSRX.ReleaseRate^0x1F))-0x18 +10+ 32];
286 break;
287 case 6:
288 pvoice->ADSRX.EnvelopeVol -= RateTable[(4*(pvoice->ADSRX.ReleaseRate^0x1F))-0x18 +11+ 32];
289 break;
290 case 7:
291 pvoice->ADSRX.EnvelopeVol -= RateTable[(4*(pvoice->ADSRX.ReleaseRate^0x1F))-0x18 +12+ 32];
292 break;
293 }
294 }
295 else
296 {
297 pvoice->ADSRX.EnvelopeVol -= RateTable[(4*(pvoice->ADSRX.ReleaseRate^0x1F))-0x0C + 32];
298 }
299
300 if (pvoice->ADSRX.EnvelopeVol < 0)
301 {
302 pvoice->ADSRX.EnvelopeVol = 0;
303 pvoice->bOn = false;
304 pvoice->pStart = (u8*)(spu2mem + pvoice->iStartAddr);
305 pvoice->pLoop = (u8*)(spu2mem + pvoice->iStartAddr);
306 pvoice->pCurr = (u8*)(spu2mem + pvoice->iStartAddr);
307 pvoice->bStop = true;
308 pvoice->bIgnoreLoop = false;
309 //pvoice->bReverb=0;
310 //pvoice->bNoise=0;
311 }
312
313 pvoice->ADSRX.lVolume = pvoice->ADSRX.EnvelopeVol >> 21;
314 pvoice->ADSRX.lVolume = pvoice->ADSRX.EnvelopeVol >> 21;
315 return pvoice->ADSRX.lVolume;
316 }
317 else // not stopped yet?
318 {
319 if (pvoice->ADSRX.State == 0) // -> attack
320 {
321 if (pvoice->ADSRX.AttackModeExp)
322 {
323 if (pvoice->ADSRX.EnvelopeVol < 0x60000000)
324 pvoice->ADSRX.EnvelopeVol += RateTable[(pvoice->ADSRX.AttackRate^0x7F)-0x10 + 32];
325 else
326 pvoice->ADSRX.EnvelopeVol += RateTable[(pvoice->ADSRX.AttackRate^0x7F)-0x18 + 32];
327 }
328 else
329 {
330 pvoice->ADSRX.EnvelopeVol += RateTable[(pvoice->ADSRX.AttackRate^0x7F)-0x10 + 32];
331 }
332
333 if (pvoice->ADSRX.EnvelopeVol < 0)
334 {
335 pvoice->ADSRX.EnvelopeVol = 0x7FFFFFFF;
336 pvoice->ADSRX.State = 1;
337 }
338
339 pvoice->ADSRX.lVolume = pvoice->ADSRX.EnvelopeVol >> 21;
340 return pvoice->ADSRX.lVolume;
341 }
342 //--------------------------------------------------//
343 if (pvoice->ADSRX.State == 1) // -> decay
344 {
345 switch ((pvoice->ADSRX.EnvelopeVol >> 28)&0x7)
346 {
347 case 0:
348 pvoice->ADSRX.EnvelopeVol -= RateTable[(4*(pvoice->ADSRX.DecayRate^0x1F))-0x18+0 + 32];
349 break;
350 case 1:
351 pvoice->ADSRX.EnvelopeVol -= RateTable[(4*(pvoice->ADSRX.DecayRate^0x1F))-0x18+4 + 32];
352 break;
353 case 2:
354 pvoice->ADSRX.EnvelopeVol -= RateTable[(4*(pvoice->ADSRX.DecayRate^0x1F))-0x18+6 + 32];
355 break;
356 case 3:
357 pvoice->ADSRX.EnvelopeVol -= RateTable[(4*(pvoice->ADSRX.DecayRate^0x1F))-0x18+8 + 32];
358 break;
359 case 4:
360 pvoice->ADSRX.EnvelopeVol -= RateTable[(4*(pvoice->ADSRX.DecayRate^0x1F))-0x18+9 + 32];
361 break;
362 case 5:
363 pvoice->ADSRX.EnvelopeVol -= RateTable[(4*(pvoice->ADSRX.DecayRate^0x1F))-0x18+10+ 32];
364 break;
365 case 6:
366 pvoice->ADSRX.EnvelopeVol -= RateTable[(4*(pvoice->ADSRX.DecayRate^0x1F))-0x18+11+ 32];
367 break;
368 case 7:
369 pvoice->ADSRX.EnvelopeVol -= RateTable[(4*(pvoice->ADSRX.DecayRate^0x1F))-0x18+12+ 32];
370 break;
371 }
372
373 if (pvoice->ADSRX.EnvelopeVol < 0) pvoice->ADSRX.EnvelopeVol = 0;
374 if (((pvoice->ADSRX.EnvelopeVol >> 27)&0xF) <= pvoice->ADSRX.SustainLevel)
375 {
376 pvoice->ADSRX.State = 2;
377 }
378
379 pvoice->ADSRX.lVolume = pvoice->ADSRX.EnvelopeVol >> 21;
380 return pvoice->ADSRX.lVolume;
381 }
382 //--------------------------------------------------//
383 if (pvoice->ADSRX.State == 2) // -> sustain
384 {
385 if (pvoice->ADSRX.SustainIncrease)
386 {
387 if (pvoice->ADSRX.SustainModeExp)
388 {
389 if (pvoice->ADSRX.EnvelopeVol < 0x60000000)
390 pvoice->ADSRX.EnvelopeVol += RateTable[(pvoice->ADSRX.SustainRate^0x7F)-0x10 + 32];
391 else
392 pvoice->ADSRX.EnvelopeVol += RateTable[(pvoice->ADSRX.SustainRate^0x7F)-0x18 + 32];
393 }
394 else
395 {
396 pvoice->ADSRX.EnvelopeVol += RateTable[(pvoice->ADSRX.SustainRate^0x7F)-0x10 + 32];
397 }
398
399 if (pvoice->ADSRX.EnvelopeVol < 0)
400 {
401 pvoice->ADSRX.EnvelopeVol = 0x7FFFFFFF;
402 }
403 }
404 else
405 {
406 if (pvoice->ADSRX.SustainModeExp)
407 {
408 switch ((pvoice->ADSRX.EnvelopeVol >> 28)&0x7)
409 {
410 case 0:
411 pvoice->ADSRX.EnvelopeVol -= RateTable[((pvoice->ADSRX.SustainRate^0x7F))-0x1B +0 + 32];
412 break;
413 case 1:
414 pvoice->ADSRX.EnvelopeVol -= RateTable[((pvoice->ADSRX.SustainRate^0x7F))-0x1B +4 + 32];
415 break;
416 case 2:
417 pvoice->ADSRX.EnvelopeVol -= RateTable[((pvoice->ADSRX.SustainRate^0x7F))-0x1B +6 + 32];
418 break;
419 case 3:
420 pvoice->ADSRX.EnvelopeVol -= RateTable[((pvoice->ADSRX.SustainRate^0x7F))-0x1B +8 + 32];
421 break;
422 case 4:
423 pvoice->ADSRX.EnvelopeVol -= RateTable[((pvoice->ADSRX.SustainRate^0x7F))-0x1B +9 + 32];
424 break;
425 case 5:
426 pvoice->ADSRX.EnvelopeVol -= RateTable[((pvoice->ADSRX.SustainRate^0x7F))-0x1B +10+ 32];
427 break;
428 case 6:
429 pvoice->ADSRX.EnvelopeVol -= RateTable[((pvoice->ADSRX.SustainRate^0x7F))-0x1B +11+ 32];
430 break;
431 case 7:
432 pvoice->ADSRX.EnvelopeVol -= RateTable[((pvoice->ADSRX.SustainRate^0x7F))-0x1B +12+ 32];
433 break;
434 }
435 }
436 else
437 {
438 pvoice->ADSRX.EnvelopeVol -= RateTable[((pvoice->ADSRX.SustainRate^0x7F))-0x0F + 32];
439 }
440
441 if (pvoice->ADSRX.EnvelopeVol < 0)
442 {
443 pvoice->ADSRX.EnvelopeVol = 0;
444 }
445 }
446 pvoice->ADSRX.lVolume = pvoice->ADSRX.EnvelopeVol >> 21;
447 return pvoice->ADSRX.lVolume;
448 }
449 }
450 return 0;
451 }
452
453 // simulate SPU2 for 1ms
454 void SPU2Worker()
455 {
456 u8* start;
457 int ch, flags;
458
459 VOICE_PROCESSED* pChannel = voices;
460 for (ch = 0;ch < SPU_NUMBER_VOICES;ch++, pChannel++) // loop em all... we will collect 1 ms of sound of each playing channel
461 {
462 if (pChannel->bNew)
463 {
464 pChannel->StartSound(); // start new sound
465 dwEndChannel2[ch/24] &= ~(1 << (ch % 24)); // clear end channel bit
466 }
467
468 if (!pChannel->bOn)
469 {
470 // fill buffer with empty data
471 continue;
472 }
473
474 if (pChannel->iActFreq != pChannel->iUsedFreq) // new psx frequency?
475 pChannel->VoiceChangeFrequency();
476
477 // loop until 1 ms of data is reached
478 int ns = 0;
479 while (ns < NSSIZE)
480 {
481 while (pChannel->spos >= 0x10000)
482 {
483 if (pChannel->iSBPos == 28) // 28 reached?
484 {
485 start = pChannel->pCurr; // set up the current pos
486
487 // special "stop" sign
488 if (start == (u8*) - 1) //!pChannel->bOn
489 {
490 pChannel->bOn = false; // -> turn everything off
491 pChannel->ADSRX.lVolume = 0;
492 pChannel->ADSRX.EnvelopeVol = 0;
493 goto ENDX; // -> and done for this channel
494 }
495
496 pChannel->iSBPos = 0;
497
498 // decode the 16 byte packet
499
500 flags = (int)start[1];
501 start += 16;
502
503 // some callback and irq active?
504 if (pChannel->GetCtrl()->irq)
505 {
506 // if irq address reached or irq on looping addr, when stop/loop flag is set
507 u8* pirq = (u8*)pSpuIrq[ch>=24];
508 if ((pirq > start - 16 && pirq <= start)
509 || ((flags&1) && (pirq > pChannel->pLoop - 16 && pirq <= pChannel->pLoop)))
510 {
511 IRQINFO |= 4 << (int)(ch >= 24);
512 irqCallbackSPU2();
513 }
514 }
515
516 // flag handler
517 if ((flags&4) && (!pChannel->bIgnoreLoop))
518 pChannel->pLoop = start - 16; // loop adress
519
520 if (flags&1) // 1: stop/loop
521 {
522 // We play this block out first...
523 dwEndChannel2[ch/24] |= (1 << (ch % 24));
524 //if(!(flags&2)) // 1+2: do loop... otherwise: stop
525 if (flags != 3 || pChannel->pLoop == NULL) // PETE: if we don't check exactly for 3, loop hang ups will happen (DQ4, for example)
526 { // and checking if pLoop is set avoids crashes, yeah
527 start = (u8*) - 1;
528 pChannel->bStop = true;
529 pChannel->bIgnoreLoop = false;
530 }
531 else
532 {
533 start = pChannel->pLoop;
534 }
535 }
536
537 pChannel->pCurr = start; // store values for next cycle
538 }
539
540 pChannel->iSBPos++; // get sample data
541 pChannel->spos -= 0x10000;
542 }
543
544 MixADSR(pChannel);
545
546 // go to the next packet
547 ns++;
548 pChannel->spos += pChannel->sinc;
549 }
550 ENDX:
551 ;
552 }
553
554 // mix all channels
555 if ((spu2Ru16(REG_C0_MMIX) & 0xC0) && (spu2Ru16(REG_C0_ADMAS) & 0x1) && !(spu2Ru16(REG_C0_CTRL) & 0x30))
556 {
557 for (int ns = 0;ns < NSSIZE;ns++)
558 {
559 Adma4.Index += 1;
560
561 if (Adma4.Index == 128 || Adma4.Index == 384)
562 {
563 if (ADMAS4Write())
564 {
565 spu2Ru16(REG_C0_SPUSTAT) &= ~0x80;
566 irqCallbackDMA4();
567 }
568 else MemAddr[0] += 1024;
569 }
570
571 if (Adma4.Index == 512)
572 {
573 Adma4.Index = 0;
574 }
575 }
576 }
577
578
579 if ((spu2Ru16(REG_C1_MMIX) & 0xC0) && (spu2Ru16(REG_C1_ADMAS) & 0x2) && !(spu2Ru16(REG_C1_CTRL) & 0x30))
580 {
581 for (int ns = 0;ns < NSSIZE;ns++)
582 {
583 Adma7.Index += 1;
584
585 if (Adma7.Index == 128 || Adma7.Index == 384)
586 {
587 if (ADMAS7Write())
588 {
589 spu2Ru16(REG_C1_SPUSTAT) &= ~0x80;
590 irqCallbackDMA7();
591 }
592 else MemAddr[1] += 1024;
593 }
594
595 if (Adma7.Index == 512) Adma7.Index = 0;
596 }
597 }
598 }
599
600 EXPORT_C_(void) SPU2readDMA4Mem(u16 *pMem, int size)
601 {
602 u32 spuaddr = C0_SPUADDR;
603 int i;
604
605 SPU2_LOG("SPU2 readDMA4Mem size %x, addr: %x\n", size, pMem);
606
607 for (i = 0;i < size;i++)
608 {
609 *pMem++ = *(u16*)(spu2mem + spuaddr);
610 if ((spu2Rs16(REG_C0_CTRL)&0x40) && C0_IRQA == spuaddr)
611 {
612 spu2Ru16(SPDIF_OUT) |= 0x4;
613 C0_SPUADDR_SET(spuaddr);
614 IRQINFO |= 4;
615 irqCallbackSPU2();
616 }
617
618 spuaddr++; // inc spu addr
619 if (spuaddr > 0x0fffff) // wrap at 2Mb
620 spuaddr = 0; // wrap
621 }
622
623 spuaddr += 19; //Transfer Local To Host TSAH/L + Data Size + 20 (already +1'd)
624 C0_SPUADDR_SET(spuaddr);
625
626 // got from J.F. and Kanodin... is it needed?
627 spu2Ru16(REG_C0_SPUSTAT) &= ~0x80; // DMA complete
628 SPUStartCycle[0] = SPUCycles;
629 SPUTargetCycle[0] = size;
630 interrupt |= (1 << 1);
631 }
632
633 EXPORT_C_(void) SPU2readDMA7Mem(u16* pMem, int size)
634 {
635 u32 spuaddr = C1_SPUADDR;
636 int i;
637
638 SPU2_LOG("SPU2 readDMA7Mem size %x, addr: %x\n", size, pMem);
639
640 for (i = 0;i < size;i++)
641 {
642 *pMem++ = *(u16*)(spu2mem + spuaddr);
643 if ((spu2Rs16(REG_C1_CTRL)&0x40) && C1_IRQA == spuaddr)
644 {
645 spu2Ru16(SPDIF_OUT) |= 0x8;
646 C1_SPUADDR_SET(spuaddr);
647 IRQINFO |= 8;
648 irqCallbackSPU2();
649 }
650 spuaddr++; // inc spu addr
651 if (spuaddr > 0x0fffff) // wrap at 2Mb
652 spuaddr = 0; // wrap
653 }
654
655 spuaddr += 19; //Transfer Local To Host TSAH/L + Data Size + 20 (already +1'd)
656 C1_SPUADDR_SET(spuaddr);
657
658 // got from J.F. and Kanodin... is it needed?
659 spu2Ru16(REG_C1_SPUSTAT) &= ~0x80; // DMA complete
660 SPUStartCycle[1] = SPUCycles;
661 SPUTargetCycle[1] = size;
662 interrupt |= (1 << 2);
663 }
664
665 // WRITE
666
667 // AutoDMA's are used to transfer to the DIRECT INPUT area of the spu2 memory
668 // Left and Right channels are always interleaved together in the transfer so
669 // the AutoDMA's deinterleaves them and transfers them. An interrupt is
670 // generated when half of the buffer (256 short-words for left and 256
671 // short-words for right ) has been transferred. Another interrupt occurs at
672 // the end of the transfer.
673 int ADMAS4Write()
674 {
675 u32 spuaddr;
676 if (interrupt & 0x2) return 0;
677 if (Adma4.AmountLeft <= 0) return 1;
678
679 spuaddr = C0_SPUADDR;
680 // SPU2 Deinterleaves the Left and Right Channels
681 memcpy((s16*)(spu2mem + spuaddr + 0x2000), (s16*)Adma4.MemAddr, 512);
682 Adma4.MemAddr += 256;
683 memcpy((s16*)(spu2mem + spuaddr + 0x2200), (s16*)Adma4.MemAddr, 512);
684 Adma4.MemAddr += 256;
685 spuaddr = (spuaddr + 256) & 511;
686 C0_SPUADDR_SET(spuaddr);
687
688 Adma4.AmountLeft -= 512;
689 if (Adma4.AmountLeft == 0)
690 {
691 SPUStartCycle[0] = SPUCycles;
692 SPUTargetCycle[0] = 1;//512*48000;
693 spu2Ru16(REG_C0_SPUSTAT) &= ~0x80;
694 interrupt |= (1 << 1);
695 }
696 return 0;
697 }
698
699 int ADMAS7Write()
700 {
701 u32 spuaddr;
702 if (interrupt & 0x4) return 0;
703 if (Adma7.AmountLeft <= 0) return 1;
704
705 spuaddr = C1_SPUADDR;
706 // SPU2 Deinterleaves the Left and Right Channels
707 memcpy((s16*)(spu2mem + spuaddr + 0x2400), (s16*)Adma7.MemAddr, 512);
708 Adma7.MemAddr += 256;
709 memcpy((s16*)(spu2mem + spuaddr + 0x2600), (s16*)Adma7.MemAddr, 512);
710 Adma7.MemAddr += 256;
711 spuaddr = (spuaddr + 256) & 511;
712 C1_SPUADDR_SET(spuaddr);
713
714 Adma7.AmountLeft -= 512;
715 if (Adma7.AmountLeft == 0)
716 {
717 SPUStartCycle[1] = SPUCycles;
718 SPUTargetCycle[1] = 1;//512*48000;
719 spu2Ru16(REG_C1_SPUSTAT) &= ~0x80;
720 interrupt |= (1 << 2);
721 }
722 return 0;
723 }
724
725 EXPORT_C_(void) SPU2writeDMA4Mem(u16* pMem, int size)
726 {
727 u32 spuaddr;
728
729 SPU2_LOG("SPU2 writeDMA4Mem size %x, addr: %x\n", size, pMem);
730
731 if ((spu2Ru16(REG_C0_ADMAS) & 0x1) && (spu2Ru16(REG_C0_CTRL) & 0x30) == 0 && size)
732 {
733 //fwrite(pMem,iSize<<1,1,LogFile);
734 memset(&Adma4, 0, sizeof(ADMA));
735 C0_SPUADDR_SET(0);
736 Adma4.MemAddr = pMem;
737 Adma4.AmountLeft = size;
738 ADMAS4Write();
739 return;
740 }
741
742 spuaddr = C0_SPUADDR;
743 memcpy((u8*)(spu2mem + spuaddr), (u8*)pMem, size << 1);
744 spuaddr += size;
745 C0_SPUADDR_SET(spuaddr);
746
747 if ((spu2Ru16(REG_C0_CTRL)&0x40) && C0_IRQA == spuaddr)
748 {
749 spu2Ru16(SPDIF_OUT) |= 0x4;
750 IRQINFO |= 4;
751 irqCallbackSPU2();
752 }
753 if (spuaddr > 0xFFFFE)
754 spuaddr = 0x2800;
755 C0_SPUADDR_SET(spuaddr);
756
757 MemAddr[0] += size << 1;
758 spu2Ru16(REG_C0_SPUSTAT) &= ~0x80;
759 SPUStartCycle[0] = SPUCycles;
760 SPUTargetCycle[0] = 1;//iSize;
761 interrupt |= (1 << 1);
762 }
763
764 EXPORT_C_(void) SPU2writeDMA7Mem(u16* pMem, int size)
765 {
766 u32 spuaddr;
767
768 SPU2_LOG("SPU2 writeDMA7Mem size %x, addr: %x\n", size, pMem);
769
770 if ((spu2Ru16(REG_C1_ADMAS) & 0x2) && (spu2Ru16(REG_C1_CTRL) & 0x30) == 0 && size)
771 {
772 //fwrite(pMem,iSize<<1,1,LogFile);
773 memset(&Adma7, 0, sizeof(ADMA));
774 C1_SPUADDR_SET(0);
775 Adma7.MemAddr = pMem;
776 Adma7.AmountLeft = size;
777 ADMAS7Write();
778 return;
779 }
780
781 spuaddr = C1_SPUADDR;
782 memcpy((u8*)(spu2mem + spuaddr), (u8*)pMem, size << 1);
783 spuaddr += size;
784 C1_SPUADDR_SET(spuaddr);
785
786 if ((spu2Ru16(REG_C1_CTRL)&0x40) && C1_IRQA == spuaddr)
787 {
788 spu2Ru16(SPDIF_OUT) |= 0x8;
789 IRQINFO |= 8;
790 irqCallbackSPU2();
791 }
792 if (spuaddr > 0xFFFFE)
793 spuaddr = 0x2800;
794 C1_SPUADDR_SET(spuaddr);
795
796 MemAddr[1] += size << 1;
797 spu2Ru16(REG_C1_SPUSTAT) &= ~0x80;
798 SPUStartCycle[1] = SPUCycles;
799 SPUTargetCycle[1] = 1;//iSize;
800 interrupt |= (1 << 2);
801 }
802
803 EXPORT_C_(void) SPU2interruptDMA4()
804 {
805 SPU2_LOG("SPU2 interruptDMA4\n");
806
807 spu2Rs16(REG_C0_CTRL) &= ~0x30;
808 spu2Ru16(REG_C0_SPUSTAT) |= 0x80;
809 }
810
811 EXPORT_C_(void) SPU2interruptDMA7()
812 {
813 SPU2_LOG("SPU2 interruptDMA7\n");
814
815 // spu2Rs16(REG_C1_CTRL)&= ~0x30;
816 // //spu2Rs16(REG__5B0) = 0;
817 // spu2Rs16(SPU2_STATX_DREQ)|= 0x80;
818 spu2Rs16(REG_C1_CTRL) &= ~0x30;
819 spu2Ru16(REG_C1_SPUSTAT) |= 0x80;
820 }
821
822 // turn channels on
823 void SoundOn(s32 start, s32 end, u16 val) // SOUND ON PSX COMAND
824 {
825 for (s32 ch = start;ch < end;ch++, val >>= 1) // loop channels
826 {
827 if ((val&1) && voices[ch].pStart) // mmm... start has to be set before key on !?!
828 {
829 voices[ch].bNew = true;
830 voices[ch].bIgnoreLoop = false;
831 }
832 }
833 }
834
835 // turn channels off
836 void SoundOff(s32 start, s32 end, u16 val) // SOUND OFF PSX COMMAND
837 {
838 for (s32 ch = start;ch < end;ch++, val >>= 1) // loop channels
839 {
840 if (val&1) // && s_chan[i].bOn) mmm...
841 voices[ch].bStop = true;
842 }
843 }
844
845 void FModOn(s32 start, s32 end, u16 val) // FMOD ON PSX COMMAND
846 {
847 int ch;
848
849 for (ch = start;ch < end;ch++, val >>= 1) // loop channels
850 {
851 if (val&1) // -> fmod on/off
852 {
853 if (ch > 0)
854 {
855 }
856 }
857 else
858 {
859 // turn fmod off
860 }
861 }
862 }
863
864 EXPORT_C_(void) SPU2write(u32 mem, u16 value)
865 {
866 u32 spuaddr;
867
868 SPU2_LOG("SPU2 write mem %x value %x\n", mem, value);
869
870 assert(C0_SPUADDR < 0x100000);
871 assert(C1_SPUADDR < 0x100000);
872
873 spu2Ru16(mem) = value;
874 u32 r = mem & 0xffff;
875
876 // channel info
877 if ((r >= 0x0000 && r < 0x0180) || (r >= 0x0400 && r < 0x0580)) // some channel info?
878 {
879 int ch = 0;
880 if (r >= 0x400) ch = ((r - 0x400) >> 4) + 24;
881 else ch = (r >> 4);
882
883 VOICE_PROCESSED* pvoice = &voices[ch];
884
885 switch (r&0x0f)
886 {
887 case 0:
888 case 2:
889 pvoice->SetVolume(mem&0x2);
890 break;
891 case 4:
892 {
893 int NP;
894 if (value > 0x3fff)
895 NP = 0x3fff; // get pitch val
896 else
897 NP = value;
898
899 pvoice->pvoice->pitch = NP;
900
901 NP = (44100L * NP) / 4096L; // calc frequency
902 if (NP < 1) NP = 1; // some security
903 pvoice->iActFreq = NP; // store frequency
904 break;
905 }
906 case 6:
907 {
908 pvoice->ADSRX.AttackModeExp = (value & 0x8000) ? 1 : 0;
909 pvoice->ADSRX.AttackRate = ((value >> 8) & 0x007f);
910 pvoice->ADSRX.DecayRate = (((value >> 4) & 0x000f));
911 pvoice->ADSRX.SustainLevel = (value & 0x000f);
912 break;
913 }
914 case 8:
915 pvoice->ADSRX.SustainModeExp = (value & 0x8000) ? 1 : 0;
916 pvoice->ADSRX.SustainIncrease = (value & 0x4000) ? 0 : 1;
917 pvoice->ADSRX.SustainRate = ((value >> 6) & 0x007f);
918 pvoice->ADSRX.ReleaseModeExp = (value & 0x0020) ? 1 : 0;
919 pvoice->ADSRX.ReleaseRate = ((value & 0x001f));
920 break;
921 }
922
923 return;
924 }
925
926 // more channel info
927 if ((r >= 0x01c0 && r <= 0x02E0) || (r >= 0x05c0 && r <= 0x06E0))
928 {
929 s32 ch = 0;
930 u32 rx = r;
931 if (rx >= 0x400)
932 {
933 ch = 24;
934 rx -= 0x400;
935 }
936
937 ch += ((rx - 0x1c0) / 12);
938 rx -= (ch % 24) * 12;
939 VOICE_PROCESSED* pvoice = &voices[ch];
940
941 switch (rx)
942 {
943 case 0x1C0:
944 pvoice->iStartAddr = (((u32)value & 0x3f) << 16) | (pvoice->iStartAddr & 0xFFFF);
945 pvoice->pStart = (u8*)(spu2mem + pvoice->iStartAddr);
946 break;
947 case 0x1C2:
948 pvoice->iStartAddr = (pvoice->iStartAddr & 0x3f0000) | (value & 0xFFFF);
949 pvoice->pStart = (u8*)(spu2mem + pvoice->iStartAddr);
950 break;
951 case 0x1C4:
952 pvoice->iLoopAddr = (((u32)value & 0x3f) << 16) | (pvoice->iLoopAddr & 0xFFFF);
953 pvoice->pLoop = (u8*)(spu2mem + pvoice->iLoopAddr);
954 pvoice->bIgnoreLoop = pvoice->iLoopAddr > 0;
955 break;
956 case 0x1C6:
957 pvoice->iLoopAddr = (pvoice->iLoopAddr & 0x3f0000) | (value & 0xFFFF);
958 pvoice->pLoop = (u8*)(spu2mem + pvoice->iLoopAddr);
959 pvoice->bIgnoreLoop = pvoice->iLoopAddr > 0;
960 break;
961 case 0x1C8:
962 // unused... check if it gets written as well
963 pvoice->iNextAddr = (((u32)value & 0x3f) << 16) | (pvoice->iNextAddr & 0xFFFF);
964 break;
965 case 0x1CA:
966 // unused... check if it gets written as well
967 pvoice->iNextAddr = (pvoice->iNextAddr & 0x3f0000) | (value & 0xFFFF);
968 break;
969 }
970
971 return;
972 }
973
974 // process non-channel data
975 switch (mem&0xffff)
976 {
977 case REG_C0_SPUDATA:
978 spuaddr = C0_SPUADDR;
979 spu2mem[spuaddr] = value;
980 spuaddr++;
981 if ((spu2Ru16(REG_C0_CTRL)&0x40) && C0_IRQA == spuaddr)
982 {
983 spu2Ru16(SPDIF_OUT) |= 0x4;
984 IRQINFO |= 4;
985 irqCallbackSPU2();
986 }
987 if (spuaddr > 0xFFFFE) spuaddr = 0x2800;
988
989 C0_SPUADDR_SET(spuaddr);
990 spu2Ru16(REG_C0_SPUSTAT) &= ~0x80;
991 spu2Ru16(REG_C0_CTRL) &= ~0x30;
992 break;
993 case REG_C1_SPUDATA:
994 spuaddr = C1_SPUADDR;
995 spu2mem[spuaddr] = value;
996 spuaddr++;
997 if ((spu2Ru16(REG_C1_CTRL)&0x40) && C1_IRQA == spuaddr)
998 {
999 spu2Ru16(SPDIF_OUT) |= 0x8;
1000 IRQINFO |= 8;
1001 irqCallbackSPU2();
1002 }
1003 if (spuaddr > 0xFFFFE) spuaddr = 0x2800;
1004
1005 C1_SPUADDR_SET(spuaddr);
1006 spu2Ru16(REG_C1_SPUSTAT) &= ~0x80;
1007 spu2Ru16(REG_C1_CTRL) &= ~0x30;
1008 break;
1009 case REG_C0_IRQA_HI:
1010 case REG_C0_IRQA_LO:
1011 pSpuIrq[0] = spu2mem + (C0_IRQA << 1);
1012 break;
1013 case REG_C1_IRQA_HI:
1014 case REG_C1_IRQA_LO:
1015 pSpuIrq[1] = spu2mem + (C1_IRQA << 1);
1016 break;
1017
1018 case REG_C0_SPUADDR_HI:
1019 case REG_C1_SPUADDR_HI:
1020 spu2Ru16(mem) = value & 0xf;
1021 break;
1022
1023 case REG_C0_SPUON1:
1024 SoundOn(0, 16, value);
1025 break;
1026 case REG_C0_SPUON2:
1027 SoundOn(16, 24, value);
1028 break;
1029 case REG_C1_SPUON1:
1030 SoundOn(24, 40, value);
1031 break;
1032 case REG_C1_SPUON2:
1033 SoundOn(40, 48, value);
1034 break;
1035 case REG_C0_SPUOFF1:
1036 SoundOff(0, 16, value);
1037 break;
1038 case REG_C0_SPUOFF2:
1039 SoundOff(16, 24, value);
1040 break;
1041 case REG_C1_SPUOFF1:
1042 SoundOff(24, 40, value);
1043 break;
1044 case REG_C1_SPUOFF2:
1045 SoundOff(40, 48, value);
1046 break;
1047
1048 // According to manual all bits are cleared by writing an arbitary value
1049 case REG_C0_END1:
1050 dwEndChannel2[0] = 0;
1051 break;
1052 case REG_C0_END2:
1053 dwEndChannel2[0] = 0;
1054 break;
1055 case REG_C1_END1:
1056 dwEndChannel2[1] = 0;
1057 break;
1058 case REG_C1_END2:
1059 dwEndChannel2[1] = 0;
1060 break;
1061 case REG_C0_FMOD1:
1062 FModOn(0, 16, value);
1063 break;
1064 case REG_C0_FMOD2:
1065 FModOn(16, 24, value);
1066 break;
1067 case REG_C1_FMOD1:
1068 FModOn(24, 40, value);
1069 break;
1070 case REG_C1_FMOD2:
1071 FModOn(40, 48, value);
1072 break;
1073 }
1074
1075 assert(C0_SPUADDR < 0x100000);
1076 assert(C1_SPUADDR < 0x100000);
1077 }
1078
1079 EXPORT_C_(u16) SPU2read(u32 mem)
1080 {
1081 u32 spuaddr;
1082 u16 ret;
1083 u32 r = mem & 0xffff;
1084
1085 if ((r >= 0x0000 && r <= 0x0180) || (r >= 0x0400 && r <= 0x0580)) // some channel info?
1086 {
1087 s32 ch = 0;
1088
1089 if (r >= 0x400)
1090 ch = ((r - 0x400) >> 4) + 24;
1091 else
1092 ch = (r >> 4);
1093
1094 VOICE_PROCESSED* pvoice = &voices[ch];
1095
1096 switch (r&0x0f)
1097 {
1098 case 10:
1099 return (u16)(pvoice->ADSRX.EnvelopeVol >> 16);
1100 }
1101 }
1102
1103 if ((r > 0x01c0 && r <= 0x02E0) || (r > 0x05c0 && r <= 0x06E0)) // some channel info?
1104 {
1105 s32 ch = 0;
1106 u32 rx = r;
1107
1108 if (rx >= 0x400)
1109 {
1110 ch = 24;
1111 rx -= 0x400;
1112 }
1113
1114 ch += ((rx - 0x1c0) / 12);
1115 rx -= (ch % 24) * 12;
1116 VOICE_PROCESSED* pvoice = &voices[ch];
1117
1118 switch (rx)
1119 {
1120 case 0x1C0:
1121 return (u16)(((pvoice->pStart - (u8*)spu2mem) >> 17)&0x3F);
1122 case 0x1C2:
1123 return (u16)(((pvoice->pStart - (u8*)spu2mem) >> 1)&0xFFFF);
1124 case 0x1C4:
1125 return (u16)(((pvoice->pLoop - (u8*)spu2mem) >> 17)&0x3F);
1126 case 0x1C6:
1127 return (u16)(((pvoice->pLoop - (u8*)spu2mem) >> 1)&0xFFFF);
1128 case 0x1C8:
1129 return (u16)(((pvoice->pCurr - (u8*)spu2mem) >> 17)&0x3F);
1130 case 0x1CA:
1131 return (u16)(((pvoice->pCurr - (u8*)spu2mem) >> 1)&0xFFFF);
1132 }
1133 }
1134
1135 switch (mem&0xffff)
1136 {
1137 case REG_C0_SPUDATA:
1138 spuaddr = C0_SPUADDR;
1139 ret = spu2mem[spuaddr];
1140 spuaddr++;
1141 if (spuaddr > 0xfffff) spuaddr = 0;
1142 C0_SPUADDR_SET(spuaddr);
1143 break;
1144
1145 case REG_C1_SPUDATA:
1146 spuaddr = C1_SPUADDR;
1147 ret = spu2mem[spuaddr];
1148 spuaddr++;
1149 if (spuaddr > 0xfffff) spuaddr = 0;
1150 C1_SPUADDR_SET(spuaddr);
1151 break;
1152
1153 case REG_C0_END1:
1154 return (dwEndChannel2[0]&0xffff);
1155 case REG_C0_END2:
1156 return (dwEndChannel2[0] >> 16);
1157 case REG_C1_END1:
1158 return (dwEndChannel2[1]&0xffff);
1159 case REG_C1_END2:
1160 return (dwEndChannel2[1] >> 16);
1161
1162 case REG_IRQINFO:
1163 ret = IRQINFO;
1164 IRQINFO = 0;
1165 break;
1166 default:
1167 ret = spu2Ru16(mem);
1168 }
1169
1170 SPU2_LOG("SPU2 read mem %x: %x\n", mem, ret);
1171
1172 return ret;
1173 }
1174
1175 EXPORT_C_(void) SPU2WriteMemAddr(int core, u32 value)
1176 {
1177 MemAddr[core] = value;
1178 }
1179
1180 EXPORT_C_(u32) SPU2ReadMemAddr(int core)
1181 {
1182 return MemAddr[core];
1183 }
1184
1185 EXPORT_C_(void) SPU2irqCallback(void (*SPU2callback)(), void (*DMA4callback)(), void (*DMA7callback)())
1186 {
1187 irqCallbackSPU2 = SPU2callback;
1188 irqCallbackDMA4 = DMA4callback;
1189 irqCallbackDMA7 = DMA7callback;
1190 }
1191
1192 // VOICE_PROCESSED definitions
1193 SPU_CONTROL_* VOICE_PROCESSED::GetCtrl()
1194 {
1195 return ((SPU_CONTROL_*)(spu2regs + memoffset + REG_C0_CTRL));
1196 }
1197
1198 void VOICE_PROCESSED::SetVolume(int iProcessRight)
1199 {
1200 u16 vol = iProcessRight ? pvoice->right.word : pvoice->left.word;
1201
1202 if (vol&0x8000) // sweep not working
1203 {
1204 s16 sInc = 1; // -> sweep up?
1205 if (vol&0x2000) sInc = -1; // -> or down?
1206 if (vol&0x1000) vol ^= 0xffff; // -> mmm... phase inverted? have to investigate this
1207 vol = ((vol & 0x7f) + 1) / 2; // -> sweep: 0..127 -> 0..64
1208 vol += vol / (2 * sInc); // -> HACK: we don't sweep right now, so we just raise/lower the volume by the half!
1209 vol *= 128;
1210 }
1211 else // no sweep:
1212 {
1213 if (vol&0x4000) // -> mmm... phase inverted? have to investigate this
1214 vol = 0x3fff - (vol & 0x3fff);
1215 }
1216
1217 vol &= 0x3fff;
1218 // set volume
1219 //if( iProcessRight ) right = vol;
1220 //else left = vol;
1221 }
1222
1223 void VOICE_PROCESSED::StartSound()
1224 {
1225 ADSRX.lVolume = 1; // and init some adsr vars
1226 ADSRX.State = 0;
1227 ADSRX.EnvelopeVol = 0;
1228
1229 if (bReverb && GetCtrl()->reverb)
1230 {
1231 // setup the reverb effects
1232 }
1233
1234 pCurr = pStart; // set sample start
1235 iSBPos = 28;
1236
1237 bNew = false; // init channel flags
1238 bStop = false;
1239 bOn = true;
1240 spos = 0x10000L;
1241 }
1242
1243 void VOICE_PROCESSED::VoiceChangeFrequency()
1244 {
1245 iUsedFreq = iActFreq; // -> take it and calc steps
1246 sinc = (u32)pvoice->pitch << 4;
1247 if (!sinc)
1248 sinc = 1;
1249 }
1250
1251 void VOICE_PROCESSED::Stop()
1252 {
1253 }
1254
1255 // GUI Routines
1256 EXPORT_C_(s32) SPU2test()
1257 {
1258 return 0;
1259 }
1260
1261 typedef struct
1262 {
1263 u32 version;
1264 u8 spu2regs[0x10000];
1265 } SPU2freezeData;
1266
1267 EXPORT_C_(s32) SPU2freeze(int mode, freezeData *data)
1268 {
1269 SPU2freezeData *spud;
1270
1271 if (mode == FREEZE_LOAD)
1272 {
1273 spud = (SPU2freezeData*)data->data;
1274 if (spud->version == 0x11223344)
1275 {
1276 memcpy(spu2regs, spud->spu2regs, 0x10000);
1277 }
1278 else
1279 {
1280 printf("SPU2null wrong format\n");
1281 }
1282 }
1283 else
1284 if (mode == FREEZE_SAVE)
1285 {
1286 spud = (SPU2freezeData*)data->data;
1287 spud->version = 0x11223344;
1288 memcpy(spud->spu2regs, spu2regs, 0x10000);
1289 }
1290 else
1291 if (mode == FREEZE_SIZE)
1292 {
1293 data->size = sizeof(SPU2freezeData);
1294 }
1295
1296 return 0;
1297 }
1298

  ViewVC Help
Powered by ViewVC 1.1.22