gpfce patch
[fceu.git] / drivers / cli / dos-sound.c
CommitLineData
c62d2810 1/* FCE Ultra - NES/Famicom Emulator\r
2 *\r
3 * Copyright notice for this file:\r
4 * Copyright (C) 2002 Ben Parnell\r
5 *\r
6 * This program is free software; you can redistribute it and/or modify\r
7 * it under the terms of the GNU General Public License as published by\r
8 * the Free Software Foundation; either version 2 of the License, or\r
9 * (at your option) any later version.\r
10 *\r
11 * This program is distributed in the hope that it will be useful,\r
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of\r
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\r
14 * GNU General Public License for more details.\r
15 *\r
16 * You should have received a copy of the GNU General Public License\r
17 * along with this program; if not, write to the Free Software\r
18 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA\r
19 */\r
20\r
21#include <stdio.h>\r
22#include <stdlib.h>\r
23#include <string.h>\r
24#include <sys/farptr.h>\r
25#include <pc.h>\r
26#include <dos.h>\r
27#include <dpmi.h>\r
28#include <go32.h>\r
29#include <ctype.h>\r
30\r
31#include "dos.h"\r
32#include "dos-sound.h"\r
33#include "dos-joystick.h"\r
34\r
35\r
36static void SBIRQHandler(_go32_dpmi_registers *r);\r
37static uint32 LMBuffer; /* Address of low memory DMA playback buffer. */\r
38static int LMSelector;\r
39\r
40static uint8 *WaveBuffer;\r
41static unsigned int IVector, SBIRQ, SBDMA, SBDMA16, SBPort;\r
42static int DSPV,hsmode;\r
43static int format;\r
44static int frags, fragsize, fragtotal;\r
45static volatile int WritePtr, ReadPtr;\r
46static volatile int hbusy;\r
47static volatile int whichbuf;\r
48\r
49\r
50static uint8 PICMask;\r
51/* Protected mode interrupt vector info. */\r
52static _go32_dpmi_seginfo SBIH,SBIHOld;\r
53\r
54/* Real mode interrupt vector info. */\r
55static _go32_dpmi_seginfo SBIHRM,SBIHRMOld;\r
56static _go32_dpmi_registers SBIHRMRegs;\r
57\r
58static int WriteDSP(uint8 V)\r
59{\r
60 int x;\r
61\r
62 for(x=65536;x;x--)\r
63 {\r
64 if(!(inportb(SBPort+0xC)&0x80))\r
65 {\r
66 outportb(SBPort+0xC,V);\r
67 return(1);\r
68 }\r
69 }\r
70 return(0);\r
71}\r
72\r
73static int ReadDSP(uint8 *V)\r
74{\r
75 int x;\r
76\r
77 for(x=65536;x;x--) /* Should be more than enough time... */\r
78 {\r
79 if(inportb(SBPort+0xE)&0x80)\r
80 {\r
81 *V=inportb(SBPort+0xA);\r
82 return(1);\r
83 }\r
84 }\r
85 return(0);\r
86}\r
87\r
88\r
89static int SetVectors(void)\r
90{\r
91 SBIH.pm_offset=SBIHRM.pm_offset=(int)SBIRQHandler;\r
92 SBIH.pm_selector=SBIHRM.pm_selector=_my_cs();\r
93\r
94 /* Get and set real mode interrupt vector. */\r
95 _go32_dpmi_get_real_mode_interrupt_vector(IVector,&SBIHRMOld);\r
96 _go32_dpmi_allocate_real_mode_callback_iret(&SBIHRM, &SBIHRMRegs);\r
97 _go32_dpmi_set_real_mode_interrupt_vector(IVector,&SBIHRM); \r
98\r
99 /* Get and set protected mode interrupt vector. */\r
100 _go32_dpmi_get_protected_mode_interrupt_vector(IVector,&SBIHOld);\r
101 _go32_dpmi_allocate_iret_wrapper(&SBIH);\r
102 _go32_dpmi_set_protected_mode_interrupt_vector(IVector,&SBIH); \r
103\r
104 return(1);\r
105}\r
106\r
107static void ResetVectors(void)\r
108{\r
109 _go32_dpmi_set_protected_mode_interrupt_vector(IVector,&SBIHOld);\r
110 _go32_dpmi_free_iret_wrapper(&SBIH);\r
111 _go32_dpmi_set_real_mode_interrupt_vector(IVector,&SBIHRMOld);\r
112 _go32_dpmi_free_real_mode_callback(&SBIHRM);\r
113}\r
114\r
115int GetBLASTER(void)\r
116{\r
117 int check=0;\r
118 char *s;\r
119\r
120 if(!(s=getenv("BLASTER")))\r
121 {\r
122 puts(" Error getting BLASTER environment variable.");\r
123 return(0);\r
124 }\r
125\r
126 while(*s)\r
127 {\r
128 switch(toupper(*s))\r
129 {\r
130 case 'A': check|=(sscanf(s+1,"%x",&SBPort)==1)?1:0;break;\r
131 case 'I': check|=(sscanf(s+1,"%d",&SBIRQ)==1)?2:0;break;\r
132 case 'D': check|=(sscanf(s+1,"%d",&SBDMA)==1)?4:0;break;\r
133 case 'H': check|=(sscanf(s+1,"%d",&SBDMA16)==1)?8:0;break;\r
134 }\r
135 s++;\r
136 }\r
137 \r
138 if((check^7)&7 || SBDMA>=4 || (SBDMA16<=4 && check&8) || SBIRQ>15)\r
139 {\r
140 puts(" Invalid or incomplete BLASTER environment variable.");\r
141 return(0);\r
142 }\r
143 if(!(check&8))\r
144 format=0;\r
145 return(1);\r
146}\r
147\r
148static int ResetDSP(void)\r
149{\r
150 uint8 b;\r
151\r
152 outportb(SBPort+0x6,0x1);\r
153 delay(10);\r
154 outportb(SBPort+0x6,0x0);\r
155 delay(10);\r
156\r
157 if(ReadDSP(&b))\r
158 if(b==0xAA)\r
159 return(1); \r
160 return(0);\r
161}\r
162\r
163static int GetDSPVersion(void)\r
164{\r
165 int ret;\r
166 uint8 t;\r
167\r
168 if(!WriteDSP(0xE1))\r
169 return(0);\r
170 if(!ReadDSP(&t))\r
171 return(0);\r
172 ret=t<<8;\r
173 if(!ReadDSP(&t))\r
174 return(0);\r
175 ret|=t;\r
176\r
177 return(ret);\r
178}\r
179\r
180static void KillDMABuffer(void)\r
181{\r
182 __dpmi_free_dos_memory(LMSelector);\r
183}\r
184\r
185static int MakeDMABuffer(void)\r
186{\r
187 uint32 size;\r
188 int32 tmp;\r
189\r
190 size=fragsize*2; /* Two buffers in the DMA buffer. */\r
191 size<<=format; /* Twice the size for 16-bit than for 8-bit. */\r
192\r
193 size<<=1; /* Double the size in case the first 2 buffers\r
194 cross a 64KB or 128KB page boundary.\r
195 */\r
196 size=(size+15)>>4; /* Convert to paragraphs */\r
197\r
198 if((tmp=__dpmi_allocate_dos_memory(size,&LMSelector))<0)\r
199 return(0);\r
200\r
201 LMBuffer=tmp<<=4;\r
202\r
203 if(format) /* Check for and fix 128KB page boundary crossing. */\r
204 {\r
205 if((LMBuffer&0x20000) != ((LMBuffer+fragsize*2*2-1)&0x20000))\r
206 LMBuffer+=fragsize*2*2;\r
207 }\r
208 else /* Check for and fix 64KB page boundary crossing. */\r
209 {\r
210 if((LMBuffer&0x10000) != ((LMBuffer+fragsize*2-1)&0x10000))\r
211 LMBuffer+=fragsize*2;\r
212 }\r
213\r
214 DOSMemSet(LMBuffer, format?0:128, (fragsize*2)<<format);\r
215\r
216 return(1);\r
217}\r
218\r
219static void ProgramDMA(void)\r
220{\r
221 static int PPorts[8]={0x87,0x83,0x81,0x82,0,0x8b,0x89,0x8a};\r
222 uint32 tmp;\r
223\r
224 if(format)\r
225 {\r
226 outportb(0xd4,(SBDMA16&0x3)|0x4);\r
227 outportb(0xd8,0x0);\r
228 outportb(0xd6,(SBDMA16&0x3)|0x58);\r
229 tmp=((SBDMA16&3)<<2)+0xC2;\r
230 }\r
231 else\r
232 {\r
233 outportb(0xA,SBDMA|0x4);\r
234 outportb(0xC,0x0);\r
235 outportb(0xB,SBDMA|0x58);\r
236 tmp=(SBDMA<<1)+1;\r
237 }\r
238\r
239 /* Size of entire buffer. */\r
240 outportb(tmp,(fragsize*2-1));\r
241 outportb(tmp,(fragsize*2-1)>>8);\r
242\r
243 /* Page of buffer. */\r
244 outportb(PPorts[format?SBDMA16:SBDMA],LMBuffer>>16);\r
245\r
246 /* Offset of buffer within page. */\r
247 if(format)\r
248 tmp=((SBDMA16&3)<<2)+0xc0;\r
249 else\r
250 tmp=SBDMA<<1;\r
251\r
252 outportb(tmp,(LMBuffer>>format));\r
253 outportb(tmp,(LMBuffer>>(8+format)));\r
254}\r
255\r
256int InitSB(int Rate, int bittage)\r
257{\r
258 hsmode=hbusy=0;\r
259 whichbuf=1;\r
260 puts("Initializing Sound Blaster...");\r
261\r
262 format=bittage?1:0;\r
263 frags=8;\r
264\r
265 if(Rate<=11025)\r
266 fragsize=1<<5;\r
267 else if(Rate<=22050)\r
268 fragsize=1<<6;\r
269 else\r
270 fragsize=1<<7;\r
271\r
272 fragtotal=frags*fragsize;\r
273 WaveBuffer=malloc(fragtotal<<format);\r
274\r
275 if(format)\r
276 memset(WaveBuffer,0,fragtotal*2);\r
277 else\r
278 memset(WaveBuffer,128,fragtotal);\r
279\r
280 WritePtr=ReadPtr=0;\r
281\r
282 if((Rate<8192) || (Rate>65535))\r
283 {\r
284 printf(" Unsupported playback rate: %d samples per second\n",Rate);\r
285 return(0);\r
286 }\r
287\r
288 if(!GetBLASTER())\r
289 return(0);\r
290 \r
291 /* Disable IRQ line in PIC0 or PIC1 */\r
292 if(SBIRQ>7)\r
293 {\r
294 PICMask=inportb(0xA1);\r
295 outportb(0xA1,PICMask|(1<<(SBIRQ&7)));\r
296 }\r
297 else\r
298 {\r
299 PICMask=inportb(0x21);\r
300 outportb(0x21,PICMask|(1<<SBIRQ));\r
301 }\r
302 if(!ResetDSP())\r
303 {\r
304 puts(" Error resetting the DSP.");\r
305 return(0);\r
306 }\r
307 \r
308 if(!(DSPV=GetDSPVersion()))\r
309 {\r
310 puts(" Error getting the DSP version.");\r
311 return(0);\r
312 }\r
313\r
314 printf(" DSP Version: %d.%d\n",DSPV>>8,DSPV&0xFF);\r
315 if(DSPV<0x201)\r
316 {\r
317 printf(" DSP version number is too low.\n");\r
318 return(0);\r
319 }\r
320\r
321 if(DSPV<0x400)\r
322 format=0;\r
323 if(!MakeDMABuffer())\r
324 {\r
325 puts(" Error creating low-memory DMA buffer.");\r
326 return(0);\r
327 }\r
328\r
329 if(SBIRQ>7) IVector=SBIRQ+0x68;\r
330 else IVector=SBIRQ+0x8;\r
331\r
332 if(!SetVectors())\r
333 {\r
334 puts(" Error setting interrupt vectors.");\r
335 KillDMABuffer();\r
336 return(0);\r
337 }\r
338\r
339 /* Reenable IRQ line. */\r
340 if(SBIRQ>7)\r
341 outportb(0xA1,PICMask&(~(1<<(SBIRQ&7))));\r
342 else\r
343 outportb(0x21,PICMask&(~(1<<SBIRQ)));\r
344\r
345 ProgramDMA();\r
346\r
347 /* Note that the speaker must always be turned on before the mode transfer\r
348 byte is sent to the DSP if we're going into high-speed mode, since\r
349 a real Sound Blaster(at least my SBPro) won't accept DSP commands(except\r
350 for the reset "command") after it goes into high-speed mode.\r
351 */\r
352 WriteDSP(0xD1); // Turn on DAC speaker\r
353\r
354 if(DSPV>=0x400)\r
355 {\r
356 WriteDSP(0x41); // Set sampling rate\r
357 WriteDSP(Rate>>8); // High byte\r
358 WriteDSP(Rate&0xFF); // Low byte\r
359 if(!format)\r
360 {\r
361 WriteDSP(0xC6); // 8-bit output\r
362 WriteDSP(0x00); // 8-bit mono unsigned PCM\r
363 }\r
364 else\r
365 {\r
366 WriteDSP(0xB6); // 16-bit output\r
367 WriteDSP(0x10); // 16-bit mono signed PCM\r
368 }\r
369 WriteDSP((fragsize-1)&0xFF);// Low byte of size\r
370 WriteDSP((fragsize-1)>>8); // High byte of size\r
371 }\r
372 else\r
373 {\r
374 int tc,command;\r
375 if(Rate>22050)\r
376 {\r
377 tc=(65536-(256000000/Rate))>>8;\r
378 Rate=256000000/(65536-(tc<<8));\r
379 command=0x90; // High-speed auto-initialize DMA mode transfer\r
380 hsmode=1;\r
381 }\r
382 else\r
383 {\r
384 tc=256-(1000000/Rate);\r
385 Rate=1000000/(256-tc);\r
386 command=0x1c; // Auto-initialize DMA mode transfer\r
387 }\r
388 WriteDSP(0x40); // Set DSP time constant\r
389 WriteDSP(tc); // time constant\r
390 WriteDSP(0x48); // Set DSP block transfer size\r
391 WriteDSP((fragsize-1)&0xFF);\r
392 WriteDSP((fragsize-1)>>8);\r
393\r
394 WriteDSP(command);\r
395 }\r
396\r
397 /* Enable DMA */\r
398 if(format)\r
399 outportb(0xd4,SBDMA16&3);\r
400 else\r
401 outportb(0xa,SBDMA);\r
402\r
403 printf(" %d hz, %d-bit\n",Rate,8<<format);\r
404 return(Rate);\r
405}\r
406\r
407extern volatile int soundjoyer;\r
408extern volatile int soundjoyeron;\r
409static int ssilence=0;\r
410\r
411static void SBIRQHandler(_go32_dpmi_registers *r)\r
412{\r
413 uint32 *src;\r
414 uint32 dest;\r
415 int32 x;\r
416\r
417\r
418 if(format)\r
419 {\r
420 uint8 status;\r
421\r
422 outportb(SBPort+4,0x82);\r
423 status=inportb(SBPort+5);\r
424 if(status&2)\r
425 inportb(SBPort+0x0F);\r
426 }\r
427 else\r
428 inportb(SBPort+0x0E);\r
429\r
430 #ifdef OLD\r
431 {\r
432 uint8 status;\r
433\r
434 outportb(SBPort+4,0x82);\r
435 status=inportb(SBPort+5);\r
436 if(status&1)\r
437 inportb(SBPort+0x0E);\r
438 else if(status&2)\r
439 inportb(SBPort+0x0F);\r
440 else\r
441 return; // Mysterious interrupt source! *eerie music*\r
442 } \r
443 #endif\r
444\r
445 if(hbusy)\r
446 {\r
447 outportb(0x20,0x20);\r
448 if(SBIRQ>=8)\r
449 outportb(0xA0,0x20);\r
450 whichbuf^=1; \r
451 return;\r
452 }\r
453 hbusy=1;\r
454\r
455 {\r
456 /* This code seems to fail on many SB emulators. Bah.\r
457 SCREW SB EMULATORS. ^_^ */\r
458 uint32 count;\r
459 uint32 block;\r
460 uint32 port;\r
461 \r
462 if(format)\r
463 port=((SBDMA16&3)*4)+0xc2;\r
464 else\r
465 port=(SBDMA*2)+1;\r
466\r
467 count=inportb(port);\r
468 count|=inportb(port)<<8;\r
469\r
470 if(count>=fragsize)\r
471 block=1;\r
472 else\r
473 block=0;\r
474 dest=LMBuffer+((block*fragsize)<<format);\r
475\r
476 #ifdef MOO\r
477 dest=LMBuffer+((whichbuf*fragsize)<<format);\r
478 whichbuf^=1;\r
479 #endif\r
480 }\r
481\r
482 _farsetsel(_dos_ds);\r
483\r
484 src=(uint32 *)(WaveBuffer+(ReadPtr<<format));\r
485\r
486 if(ssilence)\r
487 {\r
488 uint32 sby;\r
489 if(format) sby=0; /* 16-bit silence. */\r
490 else sby=0x80808080; /* 8-bit silence. */\r
491\r
492 for(x=(fragsize<<format)>>2;x;x--,dest+=4)\r
493 {\r
494 _farnspokel(dest,sby);\r
495 }\r
496 }\r
497 else\r
498 {\r
499 for(x=(fragsize<<format)>>2;x;x--,dest+=4,src++)\r
500 {\r
501 _farnspokel(dest,*src);\r
502 }\r
503 ReadPtr=(ReadPtr+fragsize)&(fragtotal-1);\r
504 }\r
505\r
506 if(soundjoyeron)\r
507 {\r
508 static int coot=0;\r
509 if(!coot)\r
510 {\r
511 UpdateJoyData();\r
512 soundjoyer=1;\r
513 }\r
514 coot=(coot+1)&3;\r
515 }\r
516 hbusy=0;\r
517 outportb(0x20,0x20);\r
518 if(SBIRQ>=8) \r
519 outportb(0xA0,0x20);\r
520}\r
521\r
522void SilenceSound(int s)\r
523{\r
524 ssilence=s;\r
525}\r
526\r
527void WriteSBSound(int32 *Buffer, int Count, int NoBlocking)\r
528{\r
529 int x;\r
530\r
531 if(!format)\r
532 {\r
533 for(x=0;x<Count;x++)\r
534 {\r
535 while(WritePtr==ReadPtr)\r
536 if(NoBlocking)\r
537 return;\r
538 WaveBuffer[WritePtr]=(uint8)((Buffer[x])>>8)^128;\r
539 WritePtr=(WritePtr+1)&(fragtotal-1);\r
540 }\r
541 } \r
542 else // 16 bit\r
543 {\r
544 for(x=0;x<Count;x++)\r
545 {\r
546 while(WritePtr==ReadPtr)\r
547 if(NoBlocking)\r
548 return;\r
549 ((int16 *)WaveBuffer)[WritePtr]=Buffer[x];\r
550 WritePtr=(WritePtr+1)&(fragtotal-1);\r
551 }\r
552 }\r
553}\r
554\r
555void KillSB(void)\r
556{\r
557 if(hsmode)\r
558 ResetDSP(); /* High-speed mode requires a DSP reset. */\r
559 else\r
560 WriteDSP(format?0xD9:0xDA); /* Exit auto-init DMA transfer mode. */ \r
561 WriteDSP(0xD3); /* Turn speaker off. */\r
562\r
563 outportb((SBIRQ>7)?0xA1:0x21,PICMask|(1<<(SBIRQ&7)));\r
564 ResetVectors();\r
565 outportb((SBIRQ>7)?0xA1:0x21,PICMask);\r
566 KillDMABuffer();\r
567}\r