home *** CD-ROM | disk | FTP | other *** search
- /*
- * Play digitized sound sample on soundblaster DAC using DMA.
- * This source code is in the public domain.
- *
- * Modification History
- *
- * 9-Nov-93 David Baggett Wrote it based on Sound Blaster
- * <dmb@ai.mit.edu> Freedom project and Linux code.
- *
- */
- #include <sys/types.h>
- #include <sys/stat.h>
- #include <stdio.h>
- #include <go32.h>
- #include <dos.h>
- #include <dpmi.h>
- #include <string.h>
- #include <pc.h>
-
- #include "sb.h"
-
- #define _PROTO_(x) x
-
- #include "proto.h"
-
- /*
- * Define TEST to make an executable (i.e., compile main).
- */
- #define TEST
-
- /*
- * 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
- */
- static unsigned int sb_ioaddr;
- static unsigned int sb_irq;
- static unsigned int sb_dmachan;
-
- /*
- * Is a sound currently playing?
- */
- static volatile int sb_sound_playing = 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 (2048)
-
- /*
- * 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
- *
- * 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(_go32_dpmi_registers *reg)
- {
- register unsigned n = sb_bufnum; /* buffer we just played */
-
- /*
- * Acknowledge soundblaster
- */
- inportb(sb_ioaddr + SB_DSP_DATA_AVAIL);
-
- /*
- * Start next buffer player
- */
- sb_play_buffer(1 - n);
-
- /*
- * Fill this buffer for next time around
- */
- sb_fill_buffer(n);
-
- /*
- * Acknowledge the interrupt
- */
- outportb(0x20, 0x20);
-
- 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;
- 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;
- dosmemput(sb_curdata, sb_curlength, (unsigned long) sb_buf[n]);
- sb_curdata += sb_curlength;
- sb_curlength = 0;
- }
- }
-
- void
- sb_play_buffer(register unsigned n)
- {
- int t;
- unsigned char im, tm;
-
- /*
- * See if we're already done
- */
- if (sb_buflen[n] == 0) {
- sb_sound_playing = 0;
- return;
- }
-
- disable();
-
- /*
- * Enable interrupts on PIC
- */
- im = inportb(0x21);
- tm = ~(1 << sb_irq);
- outportb(0x21,im & tm);
-
- /*
- * Set DMA mode
- */
- outportb(SB_DMA_MASK, 5);
- outportb(SB_DMA_FF, 0);
- outportb(SB_DMA_MODE, 0x49);
-
- /*
- * Set transfer address
- */
- sb_bufnum = n;
- 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] & 0xFF);
- outportb(SB_DMA + 2 * sb_dmachan + 1, sb_buflen[n] >> 8);
-
- /*
- * Unmask DMA channel
- */
- outportb(SB_DMA_MASK, sb_dmachan);
-
- enable();
-
- sb_writedac(SB_DMA_8_BIT_DAC); /* command byte for DMA DAC transfer */
-
- /* sb_write length */
- sb_writedac(sb_buflen[n] & 0xFF);
- sb_writedac(sb_buflen[n] >> 8);
-
- /*
- * A sound is playing now.
- */
- sb_sound_playing = 1;
- }
-
- /*
- * 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 */
- }
-
- 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;
-
- /*
- * Set arguments to Soundblaster defaults
- */
- sb_ioaddr = 0x220;
- sb_irq = 7;
- sb_dmachan = 1;
-
- t = getenv("BLASTER");
- if (!t)
- return;
-
- /*
- * Get a copy
- */
- blaster = strdup(t);
-
- /*
- * Parse the BLASTER variable
- */
- t = strtok(blaster, " \t");
- 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.
- */
- void
- sb_initcard()
- {
- outportb(sb_ioaddr + SB_DSP_RESET, 1);
-
- /*
- * Kill some time
- */
- inportb(sb_ioaddr + SB_DSP_RESET);
- inportb(sb_ioaddr + SB_DSP_RESET);
- inportb(sb_ioaddr + SB_DSP_RESET);
- inportb(sb_ioaddr + SB_DSP_RESET);
-
- outportb(sb_ioaddr + SB_DSP_RESET, 0);
-
- /*
- * Need to add a timeout here!
- */
- while (inportb(sb_ioaddr + SB_DSP_READ_DATA) != 0xAA)
- ;
- }
-
- /*
- * Install our interrupt as the real mode interrupt handler for
- * the IRQ the soundblaster is on.
- *
- * We accomplish this by have GO32 allocate a real mode callback for us.
- * The callback packages our protected mode code up in a real mode wrapper.
- */
- void
- sb_install_rm_interrupt()
- {
- int ret;
-
- rm_si.pm_offset = (int) sb_intr;
- ret = _go32_dpmi_allocate_real_mode_callback_iret(&rm_si, &rm_regs);
- if (ret != 0) {
- printf("cannot allocate real mode callback, error=%04x\n",ret);
- exit(1);
- }
-
- #ifdef TEST
- printf("real mode callback is at %04x:%04x\n",
- rm_si.rm_segment, rm_si.rm_offset);
- #endif
-
- /*
- * Install our real mode interrupt handler
- */
- disable();
- _go32_dpmi_get_real_mode_interrupt_vector(8 + sb_irq, &oldirq_rm);
- _go32_dpmi_set_real_mode_interrupt_vector(8 + sb_irq, &rm_si);
- enable();
- }
-
- /*
- * Remove our real mode interrupt handler.
- */
- void
- sb_cleanup_rm_interrupt()
- {
- disable();
- _go32_dpmi_set_real_mode_interrupt_vector(8 + sb_irq, &oldirq_rm);
- _go32_dpmi_free_real_mode_callback(&rm_si);
- enable();
- }
-
- /*
- * Install our interrupt as the protected mode interrupt handler for
- * the IRQ the soundblaster is on.
- */
- void
- sb_install_pm_interrupt()
- {
- disable();
- _go32_dpmi_get_protected_mode_interrupt_vector(8 + sb_irq, &oldirq_pm);
- pm_si.pm_offset = (int) sb_intr;
- pm_si.pm_selector = _go32_my_cs();
- _go32_dpmi_chain_protected_mode_interrupt_vector(8 + sb_irq, &pm_si);
- enable();
- }
-
- /*
- * Remove our protected mode interrupt handler.
- */
- void
- sb_cleanup_pm_interrupt()
- {
- disable();
- _go32_dpmi_set_protected_mode_interrupt_vector(8 + sb_irq, &oldirq_pm);
- enable();
- }
-
- /*
- * Allocate conventional memory for our DMA buffers.
- * Each DMA buffer must be aligned on a 64K boundary in physical memory.
- */
- void
- sb_init_buffers()
- {
- dosmem.size = 65536*3/16;
- if (_go32_dpmi_allocate_dos_memory(&dosmem)) {
- printf("Unable to allocate dos memory - max size is %d\n", dosmem.size);
- exit(1);
- }
-
- #ifdef TEST
- printf("dos buffer at 0x%04x:0\n", dosmem.rm_segment);
- #endif
-
- (unsigned long) sb_buf[0] = dosmem.rm_segment * 16;
- (unsigned long) sb_buf[0] += 0x0FFFFL;
- (unsigned long) sb_buf[0] &= 0xFFFF0000L;
- (unsigned long) sb_buf[1] = (unsigned long) sb_buf[0] + 0x10000;
-
- #ifdef TEST
- printf("DMA buffers at physical 0x%0x and 0x%0x\n",
- (unsigned int) sb_buf[0], (unsigned int) sb_buf[1]);
- #endif
- }
-
- /*
- * Initliaze our internal buffers and the card itself to prepare
- * for sample playing.
- *
- * Call this once per program, not once per sample.
- */
- void
- sb_init()
- {
- /*
- * Card card params and initialize card.
- */
- sb_getparams();
- sb_initcard();
-
- /*
- * Install our interrupt handlers
- */
- sb_install_rm_interrupt();
- sb_install_pm_interrupt();
-
- /*
- * Allocate buffers in conventional memory for double-buffering
- */
- sb_init_buffers();
- }
-
- /*
- * Restore card and system to sane state before exiting.
- */
- void
- sb_cleanup()
- {
- /*
- * Remove our interrupt handlers
- */
- sb_cleanup_rm_interrupt();
- sb_cleanup_pm_interrupt();
- }
-
- /*
- * Play a sample through the DAC using DMA.
- */
- void
- sb_play(unsigned char *data, unsigned long length)
- {
- /*
- * Prime the buffers
- */
- sb_curdata = data;
- sb_curlength = length;
- sb_fill_buffer(0);
- sb_fill_buffer(1);
-
- /*
- * Start the first buffer playing.
- */
- sb_play_buffer(0);
- }
-
- #ifdef TEST
- void
- main(argc, argv)
- int argc;
- char **argv;
- {
- unsigned long length;
- unsigned char *data;
- FILE *fp;
- struct stat statbuf;
-
- if (argc < 3) {
- printf("usage: sb sample.sam sample-rate\n");
- printf("sample-rate is in hertz (e.g., 11000)\n");
- exit(0);
- }
-
- if (stat(argv[1], &statbuf) < 0) {
- printf("%s: can't stat %s\n", argv[0], argv[1]);
- exit(1);
- }
-
- length = statbuf.st_size;
-
- data = calloc(length, 1);
- if (!data) {
- printf("%s: out of memory\n", argv[0]);
- exit(1);
- }
-
- fp = fopen(argv[1], "rb");
- if (!fp) {
- printf("%s: can't open %s\n", argv[0], argv[1]);
- exit(1);
- }
-
- if (fread(data, 1, length, fp) < length) {
- printf("%s: error reading %s\n", argv[0], argv[1]);
- exit(1);
- }
-
- sb_init();
-
- printf("I/O addr = %x, IRQ = %d, DMA channel = %d\n",
- sb_ioaddr, sb_irq, sb_dmachan);
-
- sb_voice(1);
- sb_set_sample_rate(atoi(argv[2]));
- sb_play(data, length);
-
- while (sb_sound_playing)
- ;
-
- sb_cleanup();
-
- exit(0);
- }
- #endif
-