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