Date: Fri, 6 May 1994 10:18:55 --100 From: grzegorz AT kmm-lx DOT p DOT lod DOT edu DOT pl (Grzegorz Jablonski) To: djgpp AT sun DOT soe DOT clarkson DOT edu, dmb AT ai DOT mit DOT edu Subject: Sound Blaster program - working version Dear Dave I've modified your Sound Blaster program so it doesn't hang now. But it is still not perfect - all the pages have to be in memory during interrupt handling - paging causes exception. In this version this is enforced by initializing blaster after loading the sound - if the sample is too big, there's no place for callback. But if we reverse the order - we are back in DOS after exception. So happens even with 64k DMA buffer. The callback/wrapper procedures are not reentrant and - I think - the delay before paging-in is to big. Decreasing the sampling rate helps - I can see the disk LED blinking and the hear the sound. Maybe some day go32 will have 32 bit disk access in protected mode sharing the permanent swap file with windows... I was able to playback 6 MB sample in 2MB of memory at 7kHz after allocating the rest of memory and disabling smartdrive (without smartdrive loading time was about 5 minutes). Another problem is, that go32 incorrectly frees base memory and after return to Dos system hangs. Compile this code with djgpp111maint5. In previous version the routine setting real mode interrupt doesn't know about interrupt controller base relocation, while protected mode interrupt setting routine does, so instead of fixed number 8 you have to use _go32_info_block. master_interrupt_controller_base. In maint5 your code works incorrectly, because after servicing interrupt in protected mode you service it again- you chain interrupt instead of setting, so your handler passes control to real mode. However, the real mode handler is never called - you can check it if you #define indicator. So I don't know why it didn't work before and works now. /* * 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 * Freedom project and Linux code. * * 6-May-94 Grzegorz W. Jablonski Fixed bugs: play buffer len; * Set interrupt instead of * chain interrupt; * modified reset code. */ #include #include #include #include #include #include #include #include #include "sb.h" #define _PROTO_(x) x #include "proto.h" #ifdef indicator char* adr=0xe00b8000; #endif /* * 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 (512) /* * 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 */ outportb(0x20,0x20); enable(); /* * Acknowledge soundblaster */ inportb(sb_ioaddr + SB_DSP_DATA_AVAIL); #ifdef indicator *(adr++)='0'+n; *(adr++)=7; #endif /* * Start next buffer player */ sb_play_buffer(1 - n); /* * Fill this buffer for next time around */ sb_fill_buffer(n); /* * Acknowledge the interrupt */ } #ifdef indicator void sb_intr2(_go32_dpmi_registers *reg) { register unsigned n = sb_bufnum; /* buffer we just played */ outportb(0x20,0x20); enable(); /* * Acknowledge soundblaster */ inportb(sb_ioaddr + SB_DSP_DATA_AVAIL); *(adr++)='2'+n; *(adr++)=7; /* * Start next buffer player */ sb_play_buffer(1 - n); /* * Fill this buffer for next time around */ sb_fill_buffer(n); /* * Acknowledge the interrupt */ } #endif /* * 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; static int initted=0; /* * See if we're already done */ if (sb_buflen[n] == 0) { sb_sound_playing = 0; return; } disable(); if(!initted) { initted=0; /* * 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]-1) & 0xFF); outportb(SB_DMA + 2 * sb_dmachan + 1, (sb_buflen[n]-1) >> 8); /* * Unmask DMA channel */ outportb(SB_DMA_MASK, sb_dmachan); sb_writedac(SB_DMA_8_BIT_DAC); /* command byte for DMA DAC transfer */ /* sb_write length */ sb_writedac((sb_buflen[n]-1) & 0xFF); sb_writedac((sb_buflen[n]-1) >> 8); enable(); /* * 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); while(!(inportb(sb_ioaddr+SB_DSP_DATA_AVAIL)&0x80)) ; /* * 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; #ifdef indicator rm_si.pm_offset = (int) sb_intr2; #else rm_si.pm_offset = (int) sb_intr; #endif 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() { int ret; disable(); pm_si.pm_offset = (int) sb_intr; ret = _go32_dpmi_allocate_iret_wrapper(&pm_si); if (ret != 0) { printf("cannot allocate protected mode wrapper, error=%04x\n",ret); exit(1); } pm_si.pm_selector=_go32_my_cs(); _go32_dpmi_get_protected_mode_interrupt_vector(8 + sb_irq, &oldirq_pm); _go32_dpmi_set_protected_mode_interrupt_vector(8 + sb_irq, &pm_si); enable(); } /* * Remove our protected mode interrupt handler. */ void sb_cleanup_pm_interrupt() { disable(); _go32_dpmi_free_iret_wrapper(&pm_si); _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 && (!kbhit())) ; if(kbhit()) getxkey(); sb_cleanup(); exit(0); } #endif Grzegorz W. Jablonski grzegorz AT kmm-lx DOT p DOT lodz DOT pl