/* SBIO.C */
/* Soundblaster 16 basic audio I/O functions */
/* Copyright 1995 by Ethan Brodsky. All rights reserved */
/* Modified extensively by Philip VanBaren to suit my purposes */
/* Ethan Brodsky's original SB16 sampling code may be found at: */
/*
ftp://oak.oakland.edu/simtel/msdos/sound/sb16snd.zip */
/* Interface variables that can be changed in the background */
volatile int sb16dmarunning;
volatile char curblock;
#include <alloc.h>
#include <stdio.h>
#include <dos.h>
#include <mem.h>
#include <stdlib.h>
#include "sbio.h"
#include "freq.h" /* Needed for DOUT() definition only */
#define lo(value) (unsigned char)((value) & 0x00FF)
#define hi(value) (unsigned char)((value) >> 8)
int mixerport;
int mixdataport;
int resetport;
int readport;
int writeport;
int pollport;
int poll16port;
int pic_rotateport;
int pic_maskport;
int dma_maskport;
int dma_clrptrport;
int dma_modeport;
int dma_baseaddrport;
int dma_countport;
int dma_pageport;
char irq_startmask;
char irq_stopmask;
char irq_intvector;
char int_controller;
char dma_startmask;
char dma_stopmask;
char dma_mode;
/* This function is defined in sc_sb16.c */
extern void interrupt sb16_callback(void);
void interrupt (*oldintvector)() = NULL;
int handlerinstalled;
void far *dmabuffer = NULL; /* Twice the size of the output buffer */
int far *dmaptr = NULL; /* Pointer to the used portion */
unsigned long buf_addr; /* 16-bit addressing */
unsigned char buf_page;
unsigned int buf_ofs;
mode iomode; /* Flags input or output mode */
/* Low level sound card I/O */
void write_dsp(unsigned char value)
{
while (inp(writeport) & 0x80); /* Wait for bit 7 to be cleared */
outp(writeport, value);
}
unsigned char read_dsp(void)
{
long timeout=1000000L;
/* Wait for bit 7 to be set, or for a timeout */
while((!(inp(pollport) & 0x80)) && (--timeout));
return inp(readport);
}
int reset_dsp(void)
{
int i;
sb16dmarunning=0;
outp(resetport, 1);
outp(resetport, 0);
i = 100;
while ((read_dsp() != 0xAA) && i--);
return i;
}
/* Convert a far pointer to a linear address, for DMA addressing */
#define getlinearaddr(p) ((unsigned long)FP_SEG(p)*16 + (unsigned long)FP_OFF(p))
/* Initialization and shutdown */
void installhandler(void)
{
/* Install the interrupt handler */
disable(); /* Disable interrupts */
outp(pic_maskport, (inp(pic_maskport)|irq_stopmask)); /* Mask IRQ */
oldintvector = getvect(irq_intvector); /* Save old vector */
setvect(irq_intvector, sb16_callback); /* Install new handler */
outp(pic_maskport, (inp(pic_maskport)&irq_startmask)); /* Unmask IRQ */
enable(); /* Reenable interupts */
handlerinstalled = 1;
}
void uninstallhandler(void)
{
sb16dmarunning=0;
disable(); /* Disable interrupts */
outp(pic_maskport, (inp(pic_maskport)|irq_stopmask)); /* Mask IRQ */
setvect(irq_intvector, oldintvector); /* Restore old vector */
enable(); /* Enable interrupts */
handlerinstalled = 0;
}
/* This function is run at program exit to ensure cleanup */
void sb_exitproc(void)
{
sb16dmarunning=0;
outp(0x20, 0x20); outp(0xA0, 0x20); /* Acknowledge any hanging ints */
write_dsp(0xD5); /* Stop digitized sound xfer */
outp(dma_maskport, dma_stopmask); /* Mask DMA channel */
if (handlerinstalled) uninstallhandler(); /* Uninstall int handler */
reset_dsp(); /* Reset SB DSP */
}
/*
* Initialize the Soundblaster-16 card with the settings:
* baseio: base IO address for the card
* irq: IRQ channel used by the card
* dma16: 16-bit DMA channel used by the card
* io: sampling mode, either "input" or "output"
* length: maximum block size to be used, in samples
* (this is used for allocating the DMA buffer)
*/
int init_sb(int baseio, char irq, char dma16, mode io, unsigned int length)
{
int val,onboard;
/* Sound card IO ports */
mixerport = baseio + 0x004;
mixdataport = baseio + 0x005;
resetport = baseio + 0x006;
readport = baseio + 0x00A;
writeport = baseio + 0x00C;
pollport = baseio + 0x00E;
poll16port = baseio + 0x00F;
/* Reset DSP */
sb16dmarunning=0;
if (!reset_dsp()) return 0;
/* Verify that we have a SB-16 at this address */
write_dsp(0xE1);
val=read_dsp(); /* Get the major version number */
read_dsp(); /* Grab the minor version number also */
if(val<4) return 0;
/* Check the current IRQ and DMA settings, and compare with
* the values passed to this routine */
outportb(mixerport,0x80); /* IRQ settings */
val=inportb(mixdataport);
onboard=-1;
if(val&0x01) { onboard=2; DOUT("SB16: IRQ2 enabled"); }
if(val&0x02) { onboard=5; DOUT("SB16: IRQ5 enabled"); }
if(val&0x04) { onboard=7; DOUT("SB16: IRQ7 enabled"); }
if(val&0x08) { onboard=10; DOUT("SB16: IRQ10 enabled"); }
if(onboard==-1)
{
puts("Error: Soundblaster has no IRQ enabled, aborting ...");
return(0);
}
switch(irq)
{
case 2: if(!(val&0x01)) { irq=-1; } break;
case 5: if(!(val&0x02)) { irq=-1; } break;
case 7: if(!(val&0x04)) { irq=-1; } break;
case 10: if(!(val&0x08)) { irq=-1; } break;
default: irq=-1;
}
if(irq==-1)
{
printf("Warning: Soundblaster has IRQ%d selected, using that value instead\n",onboard);
irq=onboard;
}
outportb(mixerport,0x81); /* DMA settings */
val=inportb(mixdataport);
onboard=-1;
if(val&0x01) { /*onboard=0;*/ DOUT("SB16: DMA0 enabled"); }
if(val&0x02) { /*onboard=1;*/ DOUT("SB16: DMA1 enabled"); }
if(val&0x08) { /*onboard=3;*/ DOUT("SB16: DMA3 enabled"); }
if(val&0x20) { onboard=5; DOUT("SB16: DMA5 enabled"); }
if(val&0x40) { onboard=6; DOUT("SB16: DMA6 enabled"); }
if(val&0x80) { onboard=7; DOUT("SB16: DMA7 enabled"); }
if(onboard==-1)
{
puts("Error: Soundblaster has no 16-bit DMA enabled, aborting ...");
return(0);
}
switch(dma16)
{
// case 0: if(!(val&0x01)) { dma16=-1; } break;
// case 1: if(!(val&0x02)) { dma16=-1; } break;
// case 3: if(!(val&0x08)) { dma16=-1; } break;
case 5: if(!(val&0x20)) { dma16=-1; } break;
case 6: if(!(val&0x40)) { dma16=-1; } break;
case 7: if(!(val&0x80)) { dma16=-1; } break;
default: dma16=-1;
}
if(dma16==-1)
{
printf("Warning: Soundblaster has DMA%d selected, using that value instead\n",onboard);
dma16=onboard;
}
#ifdef DEBUG_OUTPUT
/* Check if the IRQs are enabled */
outportb(mixerport,0x82); /* IRQ status */
val=inportb(mixdataport);
if(val&0x01) { DOUT("SB16: 8-bit IRQ active"); }
if(val&0x02) { DOUT("SB16: 16-bit IRQ active"); }
if(val&0x04) { DOUT("SB16: MPU-401 IRQ active"); }
#endif
/* These two lines are strictly a kludge for freq.exe */
outportb(mixerport,0x3d); /* Input control */
outportb(mixdataport,0x7f); /* Use all channels for input */
/* Compute interrupt ports and parameters */
if (irq < 8)
{
int_controller = 1;
pic_rotateport = 0x20;
pic_maskport = 0x21;
irq_intvector = 0x08 + irq;
}
else
{
int_controller = 2;
pic_rotateport = 0xA0;
pic_maskport = 0x21;
irq_intvector = 0x70 + irq-8;
}
irq_stopmask = 1 << (irq % 8);
irq_startmask = ~irq_stopmask;
/* Compute DMA ports and parameters */
dma_maskport = 0xD4;
dma_clrptrport = 0xD8;
dma_modeport = 0xD6;
dma_baseaddrport = 0xC0 + 4*(dma16-4);
dma_countport = 0xC2 + 4*(dma16-4);
switch(dma16)
{
case 5: dma_pageport = 0x8B; break;
case 6: dma_pageport = 0x89; break;
case 7: dma_pageport = 0x8A; break;
}
dma_stopmask = dma16-4 + 0x04; /* 000001xx */
dma_startmask = dma16-4 + 0x00; /* 000000xx */
/* Allocate a buffer for DMA transfer */
/* (need a block of memory that does not cross a page boundary) */
if ((dmabuffer = farmalloc(8*length)) == NULL)
{
puts("Unable to allocate DMA buffer.");
exit(1);
}
dmaptr = (int far *)dmabuffer;
if(((getlinearaddr(dmabuffer) >> 1) % 65536L) + length*2 > 65536L)
dmaptr += 2*length; /* Pick second half to avoid crossing boundary */
/* Compute DMA parameters */
buf_addr = getlinearaddr(dmaptr);
buf_page = (unsigned int)(buf_addr >> 16);
buf_ofs = (unsigned int)((buf_addr >> 1) % 65536L);
/* Other initialization */
iomode = io;
switch (iomode)
{
case input: dma_mode = dma16-4 + 0x54; break; /* 010101xx */
case output: dma_mode = dma16-4 + 0x58; break; /* 010110xx */
}
installhandler(); /* Install interrupt handler */
atexit(sb_exitproc); /* Install exit procedure */
return 1;
}
/*
* Shut down the sampling process, clean up the interrupts,
* and free up the memory allocation
*/
void shutdown_sb(void)
{
sb16dmarunning=0;
reset_dsp();
if(handlerinstalled) uninstallhandler();
farfree(dmabuffer);
}
/*
* Start continuous I/O at a rate of {rate} Hz, with a call to the
* callback_sb16 procedure after every block of length {length} has
* been finished. The sampling input or output is done on alternating
* blocks pointed to by (dmaptr+curblock*length).
* i.e. When curblock is 0, the current block is (dmaptr).
* When curblock is 1, the current block is (dmaptr+length)
* The variable curblock is maintained in the callback_sb16 routine.
*/
void startio(unsigned int rate, unsigned long length)
{
sb16dmarunning = 1;
curblock = 0;
/* Program DMA controller */
outp(dma_maskport, dma_stopmask);
outp(dma_clrptrport, 0x00);
outp(dma_modeport, dma_mode);
outp(dma_baseaddrport, lo(buf_ofs)); /* Low byte of offset */
outp(dma_baseaddrport, hi(buf_ofs)); /* High word of offset */
outp(dma_countport, lo(length*2-1)); /* Low byte of count */
outp(dma_countport, hi(length*2-1)); /* High byte of count */
outp(dma_pageport, buf_page);
outp(dma_maskport, dma_startmask);
/* Program sound card */
switch (iomode)
{
case input: write_dsp(0x42); break; /* Set input sampling rate */
case output: write_dsp(0x41); break; /* Set output sampling rate */
}
write_dsp(hi(rate)); /* High byte of sampling rate */
write_dsp(lo(rate)); /* Low byte of sampling rate */
switch (iomode)
{
case output: write_dsp(0xB6); break; /* 16-bit D->A, A/I, FIFO */
case input: write_dsp(0xBE); break; /* 16-bit A->D, A/I, FIFO */
}
write_dsp(0x10); /* DMA Mode: 16-bit signed mono */
write_dsp(lo(length-1)); /* Low byte of block length */
write_dsp(hi(length-1)); /* High byte of block length */
}
/*
* Stops the current sampling process.
*/
void stopio(void)
{
outp(0x20, 0x20); outp(0xA0, 0x20); /* Acknowledge any hanging ints */
write_dsp(0xD9); /* Stop digitized sound xfer */
outp(dma_maskport, dma_stopmask); /* Mask DMA channel */
sb16dmarunning=0;
reset_dsp(); /* Reset SB DSP */
}
/* Mixer setting and reading functions */
void set_cd_level(unsigned int level)
{
level=level*256/100;
if(level>255) level=255;
outportb(mixerport,0x36); /* CD Audio left */
outportb(mixdataport,level);
outportb(mixerport,0x37); /* CD Audio right */
outportb(mixdataport,level);
}
void set_mic_level(unsigned int level)
{
level=level*256/100;
if(level>255) level=255;
outportb(mixerport,0x3A); /* Microphone */
outportb(mixdataport,level);
}
void set_line_level(unsigned int level)
{
level=level*256/100;
if(level>255) level=255;
outportb(mixerport,0x38); /* Line In left */
outportb(mixdataport,level);
outportb(mixerport,0x39); /* Line In right */
outportb(mixdataport,level);
}
unsigned int get_cd_level(void)
{
unsigned int level;
outportb(mixerport,0x36); /* CD Audio left */
level=inportb(mixdataport);
outportb(mixerport,0x37); /* CD Audio right */
level+=inportb(mixdataport);
level=level*50/256;
if(level>100) level=100;
return(level);
}
unsigned int get_mic_level(void)
{
unsigned int level;
outportb(mixerport,0x3A); /* Microphone */
level=inportb(mixdataport);
level=level*100/256;
if(level>100) level=100;
return(level);
}
unsigned int get_line_level(void)
{
unsigned int level;
outportb(mixerport,0x38); /* Line In left */
level=inportb(mixdataport);
outportb(mixerport,0x39); /* Line In right */
level+=inportb(mixdataport);
level=level*50/256;
if(level>100) level=100;
return(level);
}