/**************************************************************************** * * * (C) Copyright Creative Technology Ltd. 1993-96. All rights reserved. * * * * THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY * * KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE * * IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR * * PURPOSE. * * * ****************************************************************************/ /* DELAY.C * * D. Kaden 6/22/94 * * This program records and plays simultaneously using two Sound Blaster * cards. * * In addition to the normal BLASTER environment variable, you should also * have a BLASTER2 environment variable for the second Sound Blaster card. * * For example, your environment should look something like this: * * BLASTER=A220 I5 D1 H5 P330 T6 * BLASTER2=A240 I7 D3 H6 P330 T6 * * The first card (BLASTER) does the recording. The second (BLASTER2) does * the playback. * */ #include #include #include #include #include #include #include #include "mydefs.h" #include "sbdefs.h" #define FILENAME "delay.raw" // Structure for storing data associated with each SB card. typedef struct { int base, // SB card base I/O address MixerAddr, MixerData, // mixer chip addresses Reset, WrBuf, // DSP addresses DataAvail, ReadData, // DSP addresses irq, // IRQ number dma8, // 8-bit DMA channel dma16, // 16-bit DMA channel midi, // midi port cardtype; // card type number from environment void interrupt (*intvecsave)(); // previous interrupt vector int intrnum; // interrupt number int intrmask; // interrupt mask } cardinfo; // Structure for retrieving DMA controller register port addresses. typedef struct { unsigned char addr, count, page, mask, mode, FF; } dmaportstruct; // Constant array that defines port addresses for DMA controller registers. const dmaportstruct dmaportarray[8] = { {0x0, 0x1, 0x87, 0xA, 0xB, 0xC}, // chan 0 {0x2, 0x3, 0x83, 0xA, 0xB, 0xC}, // chan 1 {0x4, 0x5, 0x81, 0xA, 0xB, 0xC}, // chan 2 DON'T USE {0x6, 0x7, 0x82, 0xA, 0xB, 0xC}, // chan 3 {0x0, 0x0, 0x00, 0xD4, 0xD6, 0xD8}, // chan 4 DON'T USE {0xC4, 0xC6, 0x8B, 0xD4, 0xD6, 0xD8}, // chan 5 {0xC8, 0xCA, 0x89, 0xD4, 0xD6, 0xD8}, // chan 6 {0xCC, 0xCE, 0x8A, 0xD4, 0xD6, 0xD8} // chan 7 }; #define BUFSIZE (8*1024) // size of the DMA buffer #define NUMBUFS 2 #define TIMECONST 165 // 165 = approx 11.5 KHz sample rate cardinfo card1, card2; // It would be better to dynamically allocate the DMA buffers // to guarantee that they don't cross a DMA page boundary. // This is quick and dirty, but it usually works. unsigned char recbuf[BUFSIZE], // recording DMA buffer playbuf[BUFSIZE]; // playback DMA buffer unsigned int inpage, outpage, // DMA pages for input and output buffers inoffset, outoffset, // offsets for DMA input and output buffers count = BUFSIZE; // size of the DMA buffers char *inbuffers[NUMBUFS]; // Input (recording) buffers to // transfer data from DMA buffer // to disk. char *outbuffers[NUMBUFS]; // Output (playback) buffers to // transfer data from disk to DMA // buffer. int NumFilledInBufs = 0, // number of filled input buffers NumFilledOutBufs = 0, // number of filled output buffers NumRecBufToFill = 0, // next input buffer to fill from DMA buffer NumRecBufToEmpty = 0, // next input buffer to copy to disk NumPlayBufToFill = 0, // next output buffer to fill from disk NumPlayBufToEmpty = 0, // next output buffer to copy to DMA buffer HalfBufToEmpty = 0, // which half of input DMA buffer to empty HalfBufToFill = 0, // which half of output DMA buffer to fill NumBufsWritten = 0; // number of buffers written to disk int base16(char **str, int *val) /* Takes a double pointer to a string, interprets the characters as a * base-16 number, and advances the pointer. * Returns 0 if successful, 1 if not. */ { char c; int digit; *val = 0; while ((**str != ' ') && (**str != '\0')) { c = toupper(**str); if (c >= '0' && c <= '9') digit = c - '0'; else if (c >= 'A' && c <= 'F') digit = c - 'A' + 10; else return 1; // error in string *val = *val * 16 + digit; (*str)++; } return 0; } int base10(char **str, int *val) /* Takes a double pointer to a string, interprets the characters as a * base-10 number, and advances the pointer. * Returns 0 if successful, 1 if not. */ { char c; int digit; *val = 0; while ((**str != ' ') && (**str != '\0')) { c = toupper(**str); if (c >= '0' && c <= '9') digit = c - '0'; else return 1; // error in string *val = *val * 10 + digit; (*str)++; } return 0; } int ReadBlasterEnv(char *name, int *port, int *irq, int *dma8, int *dma16, int *midi, int *cardtype) /* Gets the Blaster environment statement and stores the values in the * variables whose addresses were passed to it. * * Input: * name - environment name to get, usually "BLASTER" * * Returns: * 0 if successful * 1 if there was an error reading the port address. * 2 if there was an error reading the IRQ number. * 3 if there was an error reading the 8-bit DMA channel. * 4 if there was an error reading the 16-bit DMA channel. * 5 if there was an error reading the MIDI address. * 6 if there was an error reading the card type number. */ { char *env; unsigned val; int digit; env = getenv(name); if (!env) return 7; // if there was no environment entry for name. while (*env) { switch(toupper( *(env++) )) { case 'A': if (base16(&env, port)) return 1; break; case 'I': if (base10(&env, irq)) return 2; break; case 'D': if (base10(&env, dma8)) return 3; break; case 'H': if (base10(&env, dma16)) return 4; break; case 'P': if (base16(&env, midi)) return 5; break; case 'T': if (base10(&env, cardtype)) return 6; break; default: break; } } return 0; } void SegToPhys(unsigned char far *ptr, unsigned long size, unsigned long *physaddr, unsigned long *endaddr) // Converts a segmented address to a physical memory address. { *physaddr=(unsigned long)(((unsigned long)FP_SEG(ptr) << 4) + FP_OFF(ptr)); *endaddr = *physaddr+size-1; } unsigned int PhysToPage(unsigned long physaddr, unsigned long endaddr, unsigned int *page, unsigned int *offset) /* Converts a physical memory address to a DMA-page address. Returns the * number of bytes in this block of memory that aren't in a different DMA * page. * INPUT: * physaddr - a physical (not segmented) address specifying the start * of the DMA buffer. * endaddr - a physical address specifying the last byte of the DMA * buffer. * OUTPUT: * page - the DMA page of the buffer. * offset - the offset of the buffer. * RETURN VALUE: * The minimum of (endaddr-physaddr+1, number of bytes in this page * starting at physaddr). */ { unsigned int remaining; *page=physaddr >> 16; *offset=physaddr & 0xFFFF; remaining=0xFFFF - *offset + 1; /* bytes remaining in page */ if (remaining>endaddr-physaddr) /* if more bytes left in page than in buffer */ return endaddr-physaddr+1; /* return number of bytes in buffer */ else return remaining; /* else return number of bytes left in page */ } void dspout(cardinfo *card, unsigned int val) // Output a byte to the Sound Blaster Digital Sound Processor. { while (inp(card->WrBuf) & 0x80) ; outp(card->WrBuf, val); } boolean dspin(cardinfo *card, unsigned int *val) // Read a byte from the Digital Sound Processor. { while (!(inp(card->DataAvail) & 0x80)) { ; } *val=inp(card->ReadData); return true; // used to be meaningful } void SetupDMA(int dmachan, unsigned short page, unsigned short ofs, unsigned short DMAcount, unsigned char dmacmd) // This programs the DMA controller. // NOTE: This function supports 8-bit DMA channels ONLY (because this pro- // gram only uses 8-bit voice mode. The SetupDMA function in the program // DELAY2.C supports both 8- and 16-bit DMA channels. { dmaportstruct dmaports; /**** There MUST be parens around the values to be output, or the compiler **** will convert them to unsigned chars BEFORE it does the operations **** on them. ****/ dmaports = dmaportarray[dmachan]; outp(dmaports.mask, 4 | dmachan); // mask off dma channel outp(dmaports.FF, 0); // clear flip-flop /* This next value to DMAC is different between auto-init and non-auto-init. * (0x58 vs. 0x48) */ outp(dmaports.mode , dmacmd | dmachan); // set mode outp(dmaports.addr, (ofs & 0xFF)); // low byte base addr outp(dmaports.addr, (ofs >> 8)); // high byte base addr outp(dmaports.page, page); // physical page number outp(dmaports.count,((DMAcount-1) & 0xFF)); // count low byte outp(dmaports.count,((DMAcount-1) >> 8)); // count high byte outp(dmaports.mask, dmachan); // enable dma channel } void SetupDSP(cardinfo *card, unsigned char dspcmd, unsigned short DSPcount, unsigned char tc) // Programs the Sound Blaster card. { // Program the time constant. dspout(card, dspcmdTimeConst); dspout(card, tc); // Program the DMA buffer size. dspout(card, dspcmdBlockSize); dspout(card, (DSPcount-1) & 0xFF); dspout(card, (DSPcount-1) >> 8); // Send the play or record command. dspout(card, dspcmd); } void SetupInputDMA(unsigned int page, unsigned int ofs, int count, unsigned char tc) // Program the DMA Controller and Sound Blaster for DMA input. { SetupDMA(card1.dma8, page, ofs, count, DMAMODEWRITE); SetupDSP(&card1, dspcmdAUTODMAADC, count/2, tc); } void SetupOutputDMA(unsigned int page, unsigned int ofs, int count, unsigned char tc) // Program the DMA Controller and Sound Blaster for DMA output. { SetupDMA(card2.dma8, page, ofs, count, DMAMODEREAD); SetupDSP(&card2, dspcmdAUTODMADAC, count/2, tc); } void SetMixer(cardinfo *card1, cardinfo *card2) // Resets the SB cards and selects the proper input. { unsigned int val; /* reset sb card #1 */ outp(card1->Reset,1); delay(1); outp(card1->Reset,0); if (dspin(card1, &val)) if (val!=dspReady) printf("Sound Blaster card #1 not ready."); switch (card1->cardtype) { case 2: case 4: // SBPRO outp(card1->MixerAddr, ADCSELECT); outp(card1->MixerData, 0); // select microphone dspout(card1, 0xD3); // turn speaker off break; case 6: // SB16 outp(card1->MixerAddr, SB16INPUTL); outp(card1->MixerData, 1); // select microphone outp(card1->MixerAddr, SB16INPUTR); outp(card1->MixerData, 1); // select microphone break; } /* reset sb card #2 */ outp(card2->Reset,1); delay(1); outp(card2->Reset,0); if (dspin(card2,&val)) if (val!=dspReady) printf("Sound Blaster card #2 not ready."); switch (card2->cardtype) { case 2: case 4: // SBPRO dspout(card2,0xD1); // turn speaker on break; case 6: outp(card2->MixerAddr, SB16INPUTL); outp(card2->MixerData, 0); // select nothing outp(card2->MixerAddr, SB16INPUTR); outp(card2->MixerData, 0); // select nothing break; } } void InitScreen(void) { clrscr(); printf("Two Sound Blaster Delay program.\nPress any key to stop.\n"); } void DrainInputBuffer(void) // Copies half the DMA buffer to one of the record buffers. { // Copy data from current half of DMA buffer to next input buffer. memcpy(inbuffers[NumRecBufToFill], recbuf + HalfBufToEmpty*BUFSIZE/2, BUFSIZE/2); // Make next buffer current ++NumRecBufToFill; if (NumRecBufToFill >= NUMBUFS) NumRecBufToFill = 0; // Increment count of filled input buffers. ++NumFilledInBufs; // Next time use other half of DMA buffer. HalfBufToEmpty ^= 1; } void FillOutputBuffer(void) // Copies one of the output buffers to half the DMA buffer. { // Copy data from next output buffer to current half of DMA buffer. memcpy(playbuf + HalfBufToFill*BUFSIZE/2, outbuffers[NumPlayBufToEmpty], BUFSIZE/2); // Make next buffer current ++NumPlayBufToEmpty; if (NumPlayBufToEmpty >= NUMBUFS) NumPlayBufToEmpty = 0; // Decrement count of filled output buffers. --NumFilledOutBufs; // Next time use other half of DMA buffer. HalfBufToFill ^= 1; } void interrupt InputSBISR(void) // Service interrupts from SB doing input. { // Make sure this wasn't a spurious interrupt. if (card1.irq == 7) { outp(PIC1MODE, 0xB); // select In Service Register if ((inp(PIC1MODE) & 0x80) == 0) // if bit 7 == 0, spurious interrupt return; } inp(card1.DataAvail); // acknowledge interrupt DrainInputBuffer(); if (card1.irq > 8) // If irq 10, send EOI to second PIC. outp(PIC2MODE, PICEOI); outp(PIC1MODE,PICEOI); // Send EOI to first PIC. } void interrupt OutputSBISR(void) // Service interrupts from SB doing output. { // Make sure this wasn't a spurious interrupt. if (card2.irq == 7) { outp(PIC1MODE, 0xB); // select In Service Register if ((inp(PIC1MODE) & 0x80) == 0) // if bit 7 == 0, spurious interrupt return; } inp(card2.DataAvail); // acknowledge interrupt FillOutputBuffer(); if (card2.irq > 8) // If irq 10, send EOI to second PIC. outp(PIC2MODE, PICEOI); outp(PIC1MODE,PICEOI); // Send EOI to first PIC. } int GetCardInfo(char *name, cardinfo *card) // Read a blaster environment string. { int result; int dummy; result=ReadBlasterEnv(name, &card->base, &card->irq, &card->dma8, &dummy, &dummy, &card->cardtype); if (result != 0) { switch (result) { case 1: printf("Error in %s port address.\n",name); break; case 2: printf("Error in %s IRQ number.\n",name); break; case 3: printf("Error in %s 8-bit DMA channel.\n",name); break; case 4: printf("Error in %s 16-bit DMA channel.\n",name); break; case 5: printf("Error in %s MIDI address.\n",name); break; case 6: printf("Error in %s card type number.\n",name); break; case 7: printf("Error: %s environment variable not set.\n",name); break; } } card->WrBuf = card->base + dspoffsetWrBuf; card->Reset = card->base + dspoffsetReset; card->ReadData = card->base + dspoffsetReadData; card->DataAvail = card->base + dspoffsetDataAvail; card->MixerAddr = card->base + dspoffsetMixerAddr; card->MixerData = card->base + dspoffsetMixerData; return result; } void EnableCardInterrupt(cardinfo *card, void interrupt (*newvect)()) { int intrmask; // calculate interrupt number for IRQ if (card->irq < 8) card->intrnum = card->irq + 8; // IRQs 0-7 map to interrupts 8-15. else card->intrnum = card->irq - 8 + 0x70; // IRQs 8-15 map to interrupts 70H-78H. // generate 16-bit mask for IRQ card->intrmask = 1 << card->irq; card->intvecsave = getvect(card->intrnum); // save previous interrupt vector setvect(card->intrnum, newvect); // set new interrupt vector // enable interrupts at interrupt controllers intrmask = card->intrmask; outp(PIC1MASK, inp(PIC1MASK) & ~intrmask); intrmask >>= 8; outp(PIC2MASK, inp(PIC2MASK) & ~intrmask); } void DisableCardInterrupt(cardinfo *card) { int intrmask; // disable interrupts at interrupt controllers intrmask = card->intrmask; outp(PIC1MASK, inp(PIC1MASK) | intrmask); intrmask >>= 8; outp(PIC2MASK, inp(PIC2MASK) | intrmask); // Restore previous vector setvect(card->intrnum, card->intvecsave); } int CompareCards(cardinfo *card1, cardinfo *card2) // Make sure the two SB cards don't have the same settings. { int result = 0; if (card1->base == card2->base) { printf("Error: Cards are at the same I/O address: %X\n",card1->base); result |= 1; } if (card1->irq == card2->irq) { printf("Error: Cards are using the same interrupt: %d\n",card1->irq); result |= 2; } if (card1->dma8 == card2->dma8) { printf("Error: Cards are using the same DMA channel: %d\n",card1->dma8); result |= 4; } return result; } void WriteBufsToDisk(int handle) // Writes all the record buffers that have data to the disk. { unsigned nread; // While there are input buffers with data, write them to disk while (NumFilledInBufs > 0) { _dos_write(handle, inbuffers[NumRecBufToEmpty], BUFSIZE/2, &nread); ++NumBufsWritten; --NumFilledInBufs; ++NumRecBufToEmpty; if (NumRecBufToEmpty >= NUMBUFS) NumRecBufToEmpty = 0; } } void ReadBufsFromDisk(int handle) // Fills all empty playback buffers from disk. { unsigned nread; // While some output buffers are empty, fill them from disk while (NumFilledOutBufs < NUMBUFS) { _dos_read(handle, outbuffers[NumPlayBufToFill], BUFSIZE/2, &nread); ++NumFilledOutBufs; ++NumPlayBufToFill; if (NumPlayBufToFill >= NUMBUFS) NumPlayBufToFill = 0; } } void main(void) { int i,b,c; unsigned long physaddr,endaddr; boolean done=false; int result; int rech, playh; // file handles for recording and playing char fname[] = FILENAME; result=GetCardInfo("BLASTER", &card1); if (result != 0) return; result=GetCardInfo("BLASTER2", &card2); if (result != 0) return; if (CompareCards(&card1, &card2)) return; SegToPhys(recbuf,BUFSIZE,&physaddr,&endaddr); if (PhysToPage(physaddr,endaddr,&inpage,&inoffset) < BUFSIZE) { printf("The DMA buffer crossed a page boundary. Sorry, I didn't write the program\n"); printf("to deal with this. See comments in program. Terminating.\n"); return; /* * The program should dynamically allocate buffers until a buffer is * found that doesn't cross a DMA page boundary. I took a short cut * here by just using an array, which usually works if it's much less * than 64K bytes. */ } SegToPhys(playbuf,BUFSIZE,&physaddr,&endaddr); if (PhysToPage(physaddr,endaddr,&outpage,&outoffset) < BUFSIZE) { printf("The DMA buffer crossed a page boundary. Sorry, I didn't write the program\n"); printf("to deal with this. See comments in program. Terminating.\n"); return; } InitScreen(); // allocate buffers to transfer data between DMA buffer and disk for (i=0; i