/* * Play and record digitized sound sample on soundblaster DAC/ADC using DMA. * This source code is in the public domain. * * Modification History * * 9-Nov-93 David Baggett Wrote it based on Sound Blaster * Freedom project and Linux code. * * 24-Jun-94 Gerhard Kordmann - modified for recording facilities * - added keyboard safety-routines to * allow stopping of playing/recording * - removed click while buffer switch * - added bugfixes by Grzegorz Jablonski * and several safety checks * - added free dosmem at end of program * 08-Jul-94 Gerhard Kordmann - click also removed in recording * - changes from dosmem... to memcpy * * */ #include #include #include #include #include #include #include "sb.h" /* GO32 DPMI structs for accessing DOS memory. */ static _go32_dpmi_seginfo dosmem; /* DOS (conventional) memory buffer */ static _go32_dpmi_seginfo oldirq_rm; /* original real mode IRQ */ static _go32_dpmi_registers rm_regs; static _go32_dpmi_seginfo rm_si; /* real mode interrupt segment info */ static _go32_dpmi_seginfo oldirq_pm; /* original prot-mode IRQ */ static _go32_dpmi_seginfo pm_si; /* prot-mode interrupt segment info */ /* Card parameters */ unsigned int sb_ioaddr; unsigned int sb_irq; unsigned int sb_dmachan; /* Is a sound currently playing or recorded ? */ volatile int sb_dma_active = 0; /* Conventional memory buffers for DMA. */ static volatile int sb_bufnum = 0; static char *sb_buf[2]; static unsigned int sb_buflen[2]; /* Info about current sample */ static unsigned char *sb_curdata; /* pointer to next bit of data */ static unsigned long sb_curlength; /* total length length left to play */ /* DMA chunk size, in bytes. * This parameter determines how big our DMA buffers are. We play * the sample by piecing together chunks that are this big. This * means that we don't have to copy the entire sample down into * conventional memory before playing it. (A nice side effect of * this is that we can play samples that are longer than 64K.) * * Setting this is tricky. If it's too small, we'll get lots * of interrupts, and slower machines might not be able to keep * up. Furthermore, the smaller this is, the more grainy the * sound will come out. * * On the other hand, if we make it too big there will be a noticeable * delay between a call to sb_play and when the sound actually starts * playing, which is unacceptable for things like games where sound * effects should be "instantaneous". * */ #define DMA_CHUNK (16000) /* * Define replacements for DOS enable and disable. * Be careful about inlining these -- GCC has a tendency to move * them around even if you declare them volatile. (This is definitely * true before 2.5.2; may be fixed in 2.5.2.) */ void disable() { __asm__ __volatile__ ("cli"); } void enable() { __asm__ __volatile__ ("sti"); } /* Interrupt handler for recording * * This is called in both protected mode and in real mode -- this means * we don't have to switch modes when we service the interrupt. */ void sb_intr_rec(_go32_dpmi_registers *reg) { register unsigned n = sb_bufnum; /* buffer we just recorded */ inportb(sb_ioaddr + SB_DSP_DATA_AVAIL); /* Acknowledge soundblaster */ sb_rec_buffer(1 - n); /* Start next buffer recording */ sb_empty_buffer(n); /* Save this buffer */ outportb(0x20, 0x20); /* Acknowledge the interrupt */ enable(); } /* Save buffer n in sb_curdata and calculate size for next time */ void sb_empty_buffer(register unsigned n) { if(sb_buflen[n] > 0) { memcpy(sb_curdata, (char *)(0xE0000000+(unsigned long) sb_buf[n]), sb_curlength); /* * the original version, sometimes spot of misterious crashes * dosmemget((unsigned long) sb_buf[n], sb_buflen[n], sb_curdata); */ sb_curdata += sb_buflen[n]; sb_curlength -= sb_buflen[n]; /* for determination of buffersize keep in mind, that right now */ /* the other buffer is recording up to DMA_CHUNK bytes. Only if */ /* - after saving this other buffer - there is still work to do,*/ /* then this buffer can take the rest in the next round */ if (sb_curlength > DMA_CHUNK) { if (sb_curlength > 2*DMA_CHUNK) sb_buflen[n] = DMA_CHUNK; else sb_buflen[n] = sb_curlength - DMA_CHUNK; } else sb_buflen[n] = 0; } } void sb_rec_buffer(register unsigned n) { int t; unsigned char im, tm; if (sb_buflen[n] == 0) { /* See if we're already done */ sb_dma_active = 0; return; } disable(); im = inportb(0x21); /* Enable interrupts on PIC */ tm = ~(1 << sb_irq); outportb(0x21,im & tm); outportb(SB_DMA_MASK, 5); /* Set DMA mode to 'record' */ outportb(SB_DMA_FF, 0); outportb(SB_DMA_MODE, 0x45); sb_bufnum = n; /* Set transfer address */ t = (int) ((unsigned long) sb_buf[n] >> 16) ; outportb(SB_DMAPAGE + 3, t & 0xFF); t = (int) ((unsigned long) sb_buf[n] & 0xFFFF); outportb(SB_DMA + 2 * sb_dmachan, t & 0xFF); outportb(SB_DMA + 2 * sb_dmachan, t >> 8); /* Set transfer length byte count */ outportb(SB_DMA + 2 * sb_dmachan + 1, (sb_buflen[n]-1) & 0xFF); outportb(SB_DMA + 2 * sb_dmachan + 1, (sb_buflen[n]-1) >> 8); outportb(SB_DMA_MASK, sb_dmachan); /* Unmask DMA channel */ enable(); sb_writedac(SB_DMA_ADC); /* command byte for DMA ADC transfer */ sb_writedac((sb_buflen[n]-1) & 0xFF); /* sb_write length */ sb_writedac((sb_buflen[n]-1) >> 8); /* A sound is recorded now. */ sb_dma_active = 1; /* A sound is recorded now. */ } /* Record a sample through the ADC using DMA. */ /* return number of bytes actually recorded */ unsigned long sb_rec(unsigned char *data, unsigned long length) { sb_install_interrupts(sb_intr_rec); /* Install our interrupt handlers */ sb_curdata = data; /* Prime the buffers */ sb_curlength = length; if(length > DMA_CHUNK) sb_buflen[0] = DMA_CHUNK; else sb_buflen[0] = length; if(length <= DMA_CHUNK) sb_buflen[1] = 0; else if(length > 2*DMA_CHUNK) sb_buflen[1] = DMA_CHUNK; else sb_buflen[1] = length-DMA_CHUNK; sb_rec_buffer(0); /* Start the first buffer recording. */ while (sb_dma_active) if(mykbhit()) { /* kbhit crashed sometimes */ int rest; rest = sb_read_counter(); /* samples still to record */ sb_writedac(SB_HALT_DMA); /* stop playing */ rest = sb_buflen[sb_bufnum] - rest;/* samples already recorded */ length -= (sb_curlength - rest);/* total samples recorded */ sb_buflen[sb_bufnum] = rest; /* save those samples */ sb_empty_buffer(sb_bufnum); sb_dma_active = 0; /* and exit the loop */ sb_buflen[0] = sb_buflen[1] = sb_curlength = 0; /* clean up */ } sb_cleanup_ints(); /* remove interrupts */ return length; } void sb_intr_play(_go32_dpmi_registers *reg) { register unsigned n = sb_bufnum; /* buffer we just played */ inportb(sb_ioaddr + SB_DSP_DATA_AVAIL); /* Acknowledge soundblaster */ sb_play_buffer(1 - n); /* Start next buffer player */ sb_fill_buffer(n); /* Fill this buffer for next time around */ outportb(0x20, 0x20); /* Acknowledge the interrupt */ enable(); } /* Fill buffer n with the next data. */ void sb_fill_buffer(register unsigned n) { if (sb_curlength > DMA_CHUNK) { sb_buflen[n] = DMA_CHUNK; memcpy((char *)(0xE0000000+(unsigned long) sb_buf[n]), sb_curdata, DMA_CHUNK); /* * dosmemput(sb_curdata, DMA_CHUNK, (unsigned long) sb_buf[n]); */ sb_curlength -= DMA_CHUNK; sb_curdata += DMA_CHUNK; } else if (sb_curlength <= 0) { sb_buflen[n] = 0; sb_curlength = 0; } else { sb_buflen[n] = sb_curlength; /* * the original version, sometimes spot of misterious crashes * dosmemput(sb_curdata, sb_curlength, (unsigned long) sb_buf[n]); */ memcpy((char *)(0xE0000000+(unsigned long) sb_buf[n]), sb_curdata, sb_curlength); sb_curdata += sb_curlength; sb_curlength = 0; } } void sb_play_buffer(register unsigned n) { int t; unsigned char im, tm; if (sb_buflen[n] <= 0) { /* See if we're already done */ sb_dma_active = 0; return; } disable(); im = inportb(0x21); /* Enable interrupts on PIC */ tm = ~(1 << sb_irq); outportb(0x21,im & tm); outportb(SB_DMA_MASK, 5); /* Set DMA mode 'play' */ outportb(SB_DMA_FF, 0); outportb(SB_DMA_MODE, 0x49); sb_bufnum = n; /* Set transfer address */ t = (int) ((unsigned long) sb_buf[n] >> 16); outportb(SB_DMAPAGE + 3, t); t = (int) ((unsigned long) sb_buf[n] & 0xFFFF); outportb(SB_DMA + 2 * sb_dmachan, t & 0xFF); outportb(SB_DMA + 2 * sb_dmachan, t >> 8); /* Set transfer length byte count */ outportb(SB_DMA + 2 * sb_dmachan + 1, (sb_buflen[n]-1) & 0xFF); outportb(SB_DMA + 2 * sb_dmachan + 1, (sb_buflen[n]-1) >> 8); outportb(SB_DMA_MASK, sb_dmachan); /* Unmask DMA channel */ enable(); sb_writedac(SB_DMA_8_BIT_DAC); /* command byte for DMA DAC transfer */ sb_writedac((sb_buflen[n]-1) & 0xFF); /* sb_write length */ sb_writedac((sb_buflen[n]-1) >> 8); sb_dma_active = 1; /* A sound is playing now. */ } /* Play a sample through the DAC using DMA. */ void sb_play(unsigned char *data, unsigned long length) { sb_install_interrupts(sb_intr_play); /* Install our interrupt handlers */ sb_curdata = data; /* Prime the buffers */ sb_curlength = length; sb_fill_buffer(0); sb_fill_buffer(1); sb_play_buffer(0); /* Start the first buffer playing. */ while (sb_dma_active) { /* keyboard overflow crashes */ if(mykbhit()) { /* so does kbhit() */ if(mygetch() == 27) { sb_writedac(SB_HALT_DMA); sb_dma_active = 0; sb_buflen[0] = sb_buflen[1] = sb_curlength = 0; } else kbclear(); } } sb_cleanup_ints(); /* remove interrupts */ } /* Set sampling/playback rate. * Parameter is rate in Hz (samples per second). */ void sb_set_sample_rate(unsigned int rate) { unsigned char tc = (unsigned char) (256 - 1000000/rate); sb_writedac(SB_TIME_CONSTANT); /* Command byte for sample rate */ sb_writedac(tc); /* Sample rate time constant */ printf("\nRate %d = tc %d\n",rate,tc); } void sb_voice(int state) { sb_writedac(state ? SB_SPEAKER_ON : SB_SPEAKER_OFF); } /* Read soundblaster card parameters from BLASTER enivronment variable .*/ void sb_getparams() { char *t, *blaster; sb_ioaddr = 0x220; /* Set arguments to Soundblaster defaults */ sb_irq = 7; sb_dmachan = 1; t = getenv("BLASTER"); if (!t) return; blaster = strdup(t); /* Get a copy */ t = strtok(blaster, " \t"); /* Parse the BLASTER variable */ while (t) { switch (t[0]) { case 'A': case 'a': /* I/O address */ sscanf(t + 1, "%x", &sb_ioaddr); break; case 'I': case 'i': /* IRQ */ sb_irq = atoi(t + 1); break; case 'D': case 'd': /* DMA channel */ sb_dmachan = atoi(t + 1); break; case 'T': case 't': /* what is this? */ break; default: printf("Unknown BLASTER option %c\n",t[0]); break; } t = strtok(NULL," \t"); } free(blaster); return; } /* Init the soundblaster card. */ /* gk : modified for autodetection */ int sb_initcard(void) { int i,j,error=0; int NrOfBases = 6; int Bases[] = {0x210,0x220,0x230,0x240,0x250,0x260}; int Base; for(i=0;i