+/* FCE Ultra - NES/Famicom Emulator\r
+ *\r
+ * Copyright notice for this file:\r
+ * Copyright (C) 2002 Ben Parnell\r
+ *\r
+ * This program is free software; you can redistribute it and/or modify\r
+ * it under the terms of the GNU General Public License as published by\r
+ * the Free Software Foundation; either version 2 of the License, or\r
+ * (at your option) any later version.\r
+ *\r
+ * This program is distributed in the hope that it will be useful,\r
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of\r
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\r
+ * GNU General Public License for more details.\r
+ *\r
+ * You should have received a copy of the GNU General Public License\r
+ * along with this program; if not, write to the Free Software\r
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA\r
+ */\r
+\r
+#include <stdio.h>\r
+#include <stdlib.h>\r
+#include <string.h>\r
+#include <sys/farptr.h>\r
+#include <pc.h>\r
+#include <dos.h>\r
+#include <dpmi.h>\r
+#include <go32.h>\r
+#include <ctype.h>\r
+\r
+#include "dos.h"\r
+#include "dos-sound.h"\r
+#include "dos-joystick.h"\r
+\r
+\r
+static void SBIRQHandler(_go32_dpmi_registers *r);\r
+static uint32 LMBuffer; /* Address of low memory DMA playback buffer. */\r
+static int LMSelector;\r
+\r
+static uint8 *WaveBuffer;\r
+static unsigned int IVector, SBIRQ, SBDMA, SBDMA16, SBPort;\r
+static int DSPV,hsmode;\r
+static int format;\r
+static int frags, fragsize, fragtotal;\r
+static volatile int WritePtr, ReadPtr;\r
+static volatile int hbusy;\r
+static volatile int whichbuf;\r
+\r
+\r
+static uint8 PICMask;\r
+/* Protected mode interrupt vector info. */\r
+static _go32_dpmi_seginfo SBIH,SBIHOld;\r
+\r
+/* Real mode interrupt vector info. */\r
+static _go32_dpmi_seginfo SBIHRM,SBIHRMOld;\r
+static _go32_dpmi_registers SBIHRMRegs;\r
+\r
+static int WriteDSP(uint8 V)\r
+{\r
+ int x;\r
+\r
+ for(x=65536;x;x--)\r
+ {\r
+ if(!(inportb(SBPort+0xC)&0x80))\r
+ {\r
+ outportb(SBPort+0xC,V);\r
+ return(1);\r
+ }\r
+ }\r
+ return(0);\r
+}\r
+\r
+static int ReadDSP(uint8 *V)\r
+{\r
+ int x;\r
+\r
+ for(x=65536;x;x--) /* Should be more than enough time... */\r
+ {\r
+ if(inportb(SBPort+0xE)&0x80)\r
+ {\r
+ *V=inportb(SBPort+0xA);\r
+ return(1);\r
+ }\r
+ }\r
+ return(0);\r
+}\r
+\r
+\r
+static int SetVectors(void)\r
+{\r
+ SBIH.pm_offset=SBIHRM.pm_offset=(int)SBIRQHandler;\r
+ SBIH.pm_selector=SBIHRM.pm_selector=_my_cs();\r
+\r
+ /* Get and set real mode interrupt vector. */\r
+ _go32_dpmi_get_real_mode_interrupt_vector(IVector,&SBIHRMOld);\r
+ _go32_dpmi_allocate_real_mode_callback_iret(&SBIHRM, &SBIHRMRegs);\r
+ _go32_dpmi_set_real_mode_interrupt_vector(IVector,&SBIHRM); \r
+\r
+ /* Get and set protected mode interrupt vector. */\r
+ _go32_dpmi_get_protected_mode_interrupt_vector(IVector,&SBIHOld);\r
+ _go32_dpmi_allocate_iret_wrapper(&SBIH);\r
+ _go32_dpmi_set_protected_mode_interrupt_vector(IVector,&SBIH); \r
+\r
+ return(1);\r
+}\r
+\r
+static void ResetVectors(void)\r
+{\r
+ _go32_dpmi_set_protected_mode_interrupt_vector(IVector,&SBIHOld);\r
+ _go32_dpmi_free_iret_wrapper(&SBIH);\r
+ _go32_dpmi_set_real_mode_interrupt_vector(IVector,&SBIHRMOld);\r
+ _go32_dpmi_free_real_mode_callback(&SBIHRM);\r
+}\r
+\r
+int GetBLASTER(void)\r
+{\r
+ int check=0;\r
+ char *s;\r
+\r
+ if(!(s=getenv("BLASTER")))\r
+ {\r
+ puts(" Error getting BLASTER environment variable.");\r
+ return(0);\r
+ }\r
+\r
+ while(*s)\r
+ {\r
+ switch(toupper(*s))\r
+ {\r
+ case 'A': check|=(sscanf(s+1,"%x",&SBPort)==1)?1:0;break;\r
+ case 'I': check|=(sscanf(s+1,"%d",&SBIRQ)==1)?2:0;break;\r
+ case 'D': check|=(sscanf(s+1,"%d",&SBDMA)==1)?4:0;break;\r
+ case 'H': check|=(sscanf(s+1,"%d",&SBDMA16)==1)?8:0;break;\r
+ }\r
+ s++;\r
+ }\r
+ \r
+ if((check^7)&7 || SBDMA>=4 || (SBDMA16<=4 && check&8) || SBIRQ>15)\r
+ {\r
+ puts(" Invalid or incomplete BLASTER environment variable.");\r
+ return(0);\r
+ }\r
+ if(!(check&8))\r
+ format=0;\r
+ return(1);\r
+}\r
+\r
+static int ResetDSP(void)\r
+{\r
+ uint8 b;\r
+\r
+ outportb(SBPort+0x6,0x1);\r
+ delay(10);\r
+ outportb(SBPort+0x6,0x0);\r
+ delay(10);\r
+\r
+ if(ReadDSP(&b))\r
+ if(b==0xAA)\r
+ return(1); \r
+ return(0);\r
+}\r
+\r
+static int GetDSPVersion(void)\r
+{\r
+ int ret;\r
+ uint8 t;\r
+\r
+ if(!WriteDSP(0xE1))\r
+ return(0);\r
+ if(!ReadDSP(&t))\r
+ return(0);\r
+ ret=t<<8;\r
+ if(!ReadDSP(&t))\r
+ return(0);\r
+ ret|=t;\r
+\r
+ return(ret);\r
+}\r
+\r
+static void KillDMABuffer(void)\r
+{\r
+ __dpmi_free_dos_memory(LMSelector);\r
+}\r
+\r
+static int MakeDMABuffer(void)\r
+{\r
+ uint32 size;\r
+ int32 tmp;\r
+\r
+ size=fragsize*2; /* Two buffers in the DMA buffer. */\r
+ size<<=format; /* Twice the size for 16-bit than for 8-bit. */\r
+\r
+ size<<=1; /* Double the size in case the first 2 buffers\r
+ cross a 64KB or 128KB page boundary.\r
+ */\r
+ size=(size+15)>>4; /* Convert to paragraphs */\r
+\r
+ if((tmp=__dpmi_allocate_dos_memory(size,&LMSelector))<0)\r
+ return(0);\r
+\r
+ LMBuffer=tmp<<=4;\r
+\r
+ if(format) /* Check for and fix 128KB page boundary crossing. */\r
+ {\r
+ if((LMBuffer&0x20000) != ((LMBuffer+fragsize*2*2-1)&0x20000))\r
+ LMBuffer+=fragsize*2*2;\r
+ }\r
+ else /* Check for and fix 64KB page boundary crossing. */\r
+ {\r
+ if((LMBuffer&0x10000) != ((LMBuffer+fragsize*2-1)&0x10000))\r
+ LMBuffer+=fragsize*2;\r
+ }\r
+\r
+ DOSMemSet(LMBuffer, format?0:128, (fragsize*2)<<format);\r
+\r
+ return(1);\r
+}\r
+\r
+static void ProgramDMA(void)\r
+{\r
+ static int PPorts[8]={0x87,0x83,0x81,0x82,0,0x8b,0x89,0x8a};\r
+ uint32 tmp;\r
+\r
+ if(format)\r
+ {\r
+ outportb(0xd4,(SBDMA16&0x3)|0x4);\r
+ outportb(0xd8,0x0);\r
+ outportb(0xd6,(SBDMA16&0x3)|0x58);\r
+ tmp=((SBDMA16&3)<<2)+0xC2;\r
+ }\r
+ else\r
+ {\r
+ outportb(0xA,SBDMA|0x4);\r
+ outportb(0xC,0x0);\r
+ outportb(0xB,SBDMA|0x58);\r
+ tmp=(SBDMA<<1)+1;\r
+ }\r
+\r
+ /* Size of entire buffer. */\r
+ outportb(tmp,(fragsize*2-1));\r
+ outportb(tmp,(fragsize*2-1)>>8);\r
+\r
+ /* Page of buffer. */\r
+ outportb(PPorts[format?SBDMA16:SBDMA],LMBuffer>>16);\r
+\r
+ /* Offset of buffer within page. */\r
+ if(format)\r
+ tmp=((SBDMA16&3)<<2)+0xc0;\r
+ else\r
+ tmp=SBDMA<<1;\r
+\r
+ outportb(tmp,(LMBuffer>>format));\r
+ outportb(tmp,(LMBuffer>>(8+format)));\r
+}\r
+\r
+int InitSB(int Rate, int bittage)\r
+{\r
+ hsmode=hbusy=0;\r
+ whichbuf=1;\r
+ puts("Initializing Sound Blaster...");\r
+\r
+ format=bittage?1:0;\r
+ frags=8;\r
+\r
+ if(Rate<=11025)\r
+ fragsize=1<<5;\r
+ else if(Rate<=22050)\r
+ fragsize=1<<6;\r
+ else\r
+ fragsize=1<<7;\r
+\r
+ fragtotal=frags*fragsize;\r
+ WaveBuffer=malloc(fragtotal<<format);\r
+\r
+ if(format)\r
+ memset(WaveBuffer,0,fragtotal*2);\r
+ else\r
+ memset(WaveBuffer,128,fragtotal);\r
+\r
+ WritePtr=ReadPtr=0;\r
+\r
+ if((Rate<8192) || (Rate>65535))\r
+ {\r
+ printf(" Unsupported playback rate: %d samples per second\n",Rate);\r
+ return(0);\r
+ }\r
+\r
+ if(!GetBLASTER())\r
+ return(0);\r
+ \r
+ /* Disable IRQ line in PIC0 or PIC1 */\r
+ if(SBIRQ>7)\r
+ {\r
+ PICMask=inportb(0xA1);\r
+ outportb(0xA1,PICMask|(1<<(SBIRQ&7)));\r
+ }\r
+ else\r
+ {\r
+ PICMask=inportb(0x21);\r
+ outportb(0x21,PICMask|(1<<SBIRQ));\r
+ }\r
+ if(!ResetDSP())\r
+ {\r
+ puts(" Error resetting the DSP.");\r
+ return(0);\r
+ }\r
+ \r
+ if(!(DSPV=GetDSPVersion()))\r
+ {\r
+ puts(" Error getting the DSP version.");\r
+ return(0);\r
+ }\r
+\r
+ printf(" DSP Version: %d.%d\n",DSPV>>8,DSPV&0xFF);\r
+ if(DSPV<0x201)\r
+ {\r
+ printf(" DSP version number is too low.\n");\r
+ return(0);\r
+ }\r
+\r
+ if(DSPV<0x400)\r
+ format=0;\r
+ if(!MakeDMABuffer())\r
+ {\r
+ puts(" Error creating low-memory DMA buffer.");\r
+ return(0);\r
+ }\r
+\r
+ if(SBIRQ>7) IVector=SBIRQ+0x68;\r
+ else IVector=SBIRQ+0x8;\r
+\r
+ if(!SetVectors())\r
+ {\r
+ puts(" Error setting interrupt vectors.");\r
+ KillDMABuffer();\r
+ return(0);\r
+ }\r
+\r
+ /* Reenable IRQ line. */\r
+ if(SBIRQ>7)\r
+ outportb(0xA1,PICMask&(~(1<<(SBIRQ&7))));\r
+ else\r
+ outportb(0x21,PICMask&(~(1<<SBIRQ)));\r
+\r
+ ProgramDMA();\r
+\r
+ /* Note that the speaker must always be turned on before the mode transfer\r
+ byte is sent to the DSP if we're going into high-speed mode, since\r
+ a real Sound Blaster(at least my SBPro) won't accept DSP commands(except\r
+ for the reset "command") after it goes into high-speed mode.\r
+ */\r
+ WriteDSP(0xD1); // Turn on DAC speaker\r
+\r
+ if(DSPV>=0x400)\r
+ {\r
+ WriteDSP(0x41); // Set sampling rate\r
+ WriteDSP(Rate>>8); // High byte\r
+ WriteDSP(Rate&0xFF); // Low byte\r
+ if(!format)\r
+ {\r
+ WriteDSP(0xC6); // 8-bit output\r
+ WriteDSP(0x00); // 8-bit mono unsigned PCM\r
+ }\r
+ else\r
+ {\r
+ WriteDSP(0xB6); // 16-bit output\r
+ WriteDSP(0x10); // 16-bit mono signed PCM\r
+ }\r
+ WriteDSP((fragsize-1)&0xFF);// Low byte of size\r
+ WriteDSP((fragsize-1)>>8); // High byte of size\r
+ }\r
+ else\r
+ {\r
+ int tc,command;\r
+ if(Rate>22050)\r
+ {\r
+ tc=(65536-(256000000/Rate))>>8;\r
+ Rate=256000000/(65536-(tc<<8));\r
+ command=0x90; // High-speed auto-initialize DMA mode transfer\r
+ hsmode=1;\r
+ }\r
+ else\r
+ {\r
+ tc=256-(1000000/Rate);\r
+ Rate=1000000/(256-tc);\r
+ command=0x1c; // Auto-initialize DMA mode transfer\r
+ }\r
+ WriteDSP(0x40); // Set DSP time constant\r
+ WriteDSP(tc); // time constant\r
+ WriteDSP(0x48); // Set DSP block transfer size\r
+ WriteDSP((fragsize-1)&0xFF);\r
+ WriteDSP((fragsize-1)>>8);\r
+\r
+ WriteDSP(command);\r
+ }\r
+\r
+ /* Enable DMA */\r
+ if(format)\r
+ outportb(0xd4,SBDMA16&3);\r
+ else\r
+ outportb(0xa,SBDMA);\r
+\r
+ printf(" %d hz, %d-bit\n",Rate,8<<format);\r
+ return(Rate);\r
+}\r
+\r
+extern volatile int soundjoyer;\r
+extern volatile int soundjoyeron;\r
+static int ssilence=0;\r
+\r
+static void SBIRQHandler(_go32_dpmi_registers *r)\r
+{\r
+ uint32 *src;\r
+ uint32 dest;\r
+ int32 x;\r
+\r
+\r
+ if(format)\r
+ {\r
+ uint8 status;\r
+\r
+ outportb(SBPort+4,0x82);\r
+ status=inportb(SBPort+5);\r
+ if(status&2)\r
+ inportb(SBPort+0x0F);\r
+ }\r
+ else\r
+ inportb(SBPort+0x0E);\r
+\r
+ #ifdef OLD\r
+ {\r
+ uint8 status;\r
+\r
+ outportb(SBPort+4,0x82);\r
+ status=inportb(SBPort+5);\r
+ if(status&1)\r
+ inportb(SBPort+0x0E);\r
+ else if(status&2)\r
+ inportb(SBPort+0x0F);\r
+ else\r
+ return; // Mysterious interrupt source! *eerie music*\r
+ } \r
+ #endif\r
+\r
+ if(hbusy)\r
+ {\r
+ outportb(0x20,0x20);\r
+ if(SBIRQ>=8)\r
+ outportb(0xA0,0x20);\r
+ whichbuf^=1; \r
+ return;\r
+ }\r
+ hbusy=1;\r
+\r
+ {\r
+ /* This code seems to fail on many SB emulators. Bah.\r
+ SCREW SB EMULATORS. ^_^ */\r
+ uint32 count;\r
+ uint32 block;\r
+ uint32 port;\r
+ \r
+ if(format)\r
+ port=((SBDMA16&3)*4)+0xc2;\r
+ else\r
+ port=(SBDMA*2)+1;\r
+\r
+ count=inportb(port);\r
+ count|=inportb(port)<<8;\r
+\r
+ if(count>=fragsize)\r
+ block=1;\r
+ else\r
+ block=0;\r
+ dest=LMBuffer+((block*fragsize)<<format);\r
+\r
+ #ifdef MOO\r
+ dest=LMBuffer+((whichbuf*fragsize)<<format);\r
+ whichbuf^=1;\r
+ #endif\r
+ }\r
+\r
+ _farsetsel(_dos_ds);\r
+\r
+ src=(uint32 *)(WaveBuffer+(ReadPtr<<format));\r
+\r
+ if(ssilence)\r
+ {\r
+ uint32 sby;\r
+ if(format) sby=0; /* 16-bit silence. */\r
+ else sby=0x80808080; /* 8-bit silence. */\r
+\r
+ for(x=(fragsize<<format)>>2;x;x--,dest+=4)\r
+ {\r
+ _farnspokel(dest,sby);\r
+ }\r
+ }\r
+ else\r
+ {\r
+ for(x=(fragsize<<format)>>2;x;x--,dest+=4,src++)\r
+ {\r
+ _farnspokel(dest,*src);\r
+ }\r
+ ReadPtr=(ReadPtr+fragsize)&(fragtotal-1);\r
+ }\r
+\r
+ if(soundjoyeron)\r
+ {\r
+ static int coot=0;\r
+ if(!coot)\r
+ {\r
+ UpdateJoyData();\r
+ soundjoyer=1;\r
+ }\r
+ coot=(coot+1)&3;\r
+ }\r
+ hbusy=0;\r
+ outportb(0x20,0x20);\r
+ if(SBIRQ>=8) \r
+ outportb(0xA0,0x20);\r
+}\r
+\r
+void SilenceSound(int s)\r
+{\r
+ ssilence=s;\r
+}\r
+\r
+void WriteSBSound(int32 *Buffer, int Count, int NoBlocking)\r
+{\r
+ int x;\r
+\r
+ if(!format)\r
+ {\r
+ for(x=0;x<Count;x++)\r
+ {\r
+ while(WritePtr==ReadPtr)\r
+ if(NoBlocking)\r
+ return;\r
+ WaveBuffer[WritePtr]=(uint8)((Buffer[x])>>8)^128;\r
+ WritePtr=(WritePtr+1)&(fragtotal-1);\r
+ }\r
+ } \r
+ else // 16 bit\r
+ {\r
+ for(x=0;x<Count;x++)\r
+ {\r
+ while(WritePtr==ReadPtr)\r
+ if(NoBlocking)\r
+ return;\r
+ ((int16 *)WaveBuffer)[WritePtr]=Buffer[x];\r
+ WritePtr=(WritePtr+1)&(fragtotal-1);\r
+ }\r
+ }\r
+}\r
+\r
+void KillSB(void)\r
+{\r
+ if(hsmode)\r
+ ResetDSP(); /* High-speed mode requires a DSP reset. */\r
+ else\r
+ WriteDSP(format?0xD9:0xDA); /* Exit auto-init DMA transfer mode. */ \r
+ WriteDSP(0xD3); /* Turn speaker off. */\r
+\r
+ outportb((SBIRQ>7)?0xA1:0x21,PICMask|(1<<(SBIRQ&7)));\r
+ ResetVectors();\r
+ outportb((SBIRQ>7)?0xA1:0x21,PICMask);\r
+ KillDMABuffer();\r
+}\r