/************************************************************************** VARMINT'S AUDIO TOOLS Source code: SOUND.C Turbo C++ V 3.1 Be careful about the memory model! The definition for SAMPLE will define what model you should use. Written by: Peter Sprenger and Eric Jorgensen (Feb, 1995) smeagol@rt66.com sound.c is an adaptation (by Eric Jorgensen) from Peter Sprenger's SoundX library. Eric's modifications (In part) are as follows: - Removal of all VOC, PLAY, and MIXER functions - reprogramming of DMA functions - Addition of MIDI functions - Addition of an interrupt driven sound handler - Addition of numerous comments (about 98% of all comments are Eric's) - Consolidation of all functions into a single file - Rewriting of some of Peter's original functions to improve readability and performance. ---------------------------------------------------------------------- Peter Sprenger's Original Copywrite is as follows: * Copyright 1993 by Peter Sprenger Pete@amber.dinoco.de * 5014 Kerpen 3 * Germany * * Permission to use, copy, modify, and distribute this * software and its documentation for any purpose and without * fee is hereby granted, provided that the above copyright * notice appear in all copies. The author Peter Sprenger * makes no representations about the suitability of this * software for any purpose. It is provided "as is" without * express or implied warranty. ---------------------------------------------------------------------- In the spirit of Peter's effort, I am offering my modification of his library as FREEWARE. You are free to use it and distribute this library, but you may charge no fee for it. If you construct another sound library based on this one, it must be freeware, too. This restriction does not apply to programs that only use this library to generate audio output. (ie: if you make a game that uses this library for sound generation, you can charge all the money you want for your game.) **** WARNING **** Use Varmint's Audio tools at your own risk. This code has not undergone any sort of rigourous testing and has been found to cause cancer in laboratory rats. **************************************************************************/ #include "sound.h" #define CINTNO 0 #define SLAVEPIC 2 #define RTCINTNO 8 #define WORD unsigned int #define BYTE unsigned char #define TD midi_data->track[i] + trkloc[i] static cardtype CheckHard(void); static int test_int(void); static int scan_int(void); static int FM_Detect(void); BYTE FM_Status(void); int DSP_Reset(void); BYTE DSP_Read(void); void DSP_Write(BYTE output); WORD DSP_GetVersion(void); void SB_SetVect(void); void SB_RemoveVect(void); int get_sb_env(void); int CardCheck(void); cardtype WhichCard(void); BYTE int2vect(BYTE intnr); void enable_int(BYTE nr); void disable_int(BYTE nr); void InitT2(void); void measure(void); void dma_set(BYTE far *sound_address,WORD len,BYTE channel); WORD polldma(BYTE channel); int ReadVarLen(BYTE *data,long int *value); long int ReadLong(FILE *infile); int ReadShort(FILE *infile); void MidiPlayer(void); int getvoice(VOICE v[],int track,int channel, int note); static void far interrupt (*orgint)(void) = NULL; static void far interrupt (*orgtick)(void)= NULL; static void far interrupt (*orgirqint)(void) = NULL; static void far (*call_func)(void); static WORD ioaddr[6]={0x220,0x240,0x210,0x230,0x250,0x260}; static WORD FM_off[9]={0,0x100,0x200,0x800,0x900,0xa00,0x1000,0x1100,0x1200}; static BYTE FM_fnr[12]={0x57,0x6b,0x81,0x98,0xb0,0xca,0xe5,0x02,0x20,0x41,0x63,0x87}; static BYTE FM_key_or[12]={1,1,1,1,1,1,1,2,2,2,2,2}; static BYTE intrx[5]={7,5,2,3,10}; static BYTE FM_key[9],FM_keyscale1[9],FM_keyscale2[9]; static BYTE FM_vol[9] = {0x3f,0x3f,0x3f,0x3f,0x3f,0x3f,0x3f,0x3f,0x3f}; WORD io_addr,intnr,dma_ch,card_id,fm_addr,fm_left,fm_right,fm_both; static WORD mue3,mue23,dsp_vers,wh_card,rythm=0xbd00; volatile BYTE voc_mode,tst_cnt,dma_sb_busy; static WORD dma_adr[8]= {0x00,0x02,0x04,0x06,0xc0,0xc4,0xc8,0xcc}; static WORD dma_len[8]= {0x01,0x03,0x05,0x07,0xc2,0xc6,0xca,0xce}; static WORD dma_page[8]={0x87,0x83,0x81,0x82,0x8f,0x8b,0x89,0x8a}; WORD dma_bufferlen = 60; WORD dma_bytesahead =100; BYTE far *dma_buffer = NULL; SBERROR sberr = 0; char *errname[] = { "Cannot detect FMchip", "Cannot detect DSP", "Cannot find an open IRQ", "Cannot find an open DMA channel", "Cannot allocate memory for DMA buffer"}; static WORD timer_val,timer_hold,timer_diff,mue999; static WORD timadd,timsum; MIDI *midi_data = NULL; int midi_reset = TRUE; int midi_on = FALSE; float midi_callfreq = 1.0; float midi_usertempo = 2.0; float midi_tempoadjust = 2.0; BYTE music_volume = 0x32; DWORD vclock=0; int debugnum=0; WORD DSP_overhead = 0; int sounds_in_queue = 0; int sample_rate = 11000; float interrupt_time_period; SAMPLE *sounddata[MAXSOUNDS]; DWORD soundpos[MAXSOUNDS]; extern unsigned _stklen = 8000; // The stack is usually only 4K, but // I was getting stack overflow problems // with that ammount. /************************************************************************** void Go_Varmint(void) void Dropdead_Varmint(void) DESCRIPTION: Starts/stops the interrupt routine for Varmint's audio tools **************************************************************************/ void Go_Varmint(void) { SB_SetVect(); // Install the looper DSP_Write(0x48); // Set DSP for 8bit DMA DSP_Write((dma_bufferlen) & 0xff); // Write length low byte DSP_Write((dma_bufferlen) >> 8); // Write length high byte dma_set(dma_buffer,dma_bufferlen,dma_ch); DSP_Write(0x1C); // Set DSP for Autoinit 8bit DMA DSP_Write(DSP_INVOKE_INTR); // Ignition! } void Dropdead_Varmint(void) { DSP_Write(0xD0); // Halt DMA SB_RemoveVect(); } /* --------------- FM Stuff ------------ */ /************************************************************************** void FM_Write(WORD data) DESCRIPTION: Writes a byte to the FM chip. The high byte is the register, the low byte is the data. **************************************************************************/ void FM_Write(WORD data) { asm mov dx,fm_addr; // FM ddress asm mov ax,data; // register and data into accumulator asm xchg al,ah; // exchange accumulator bytes asm out dx,al; // write register address to FM chip asm mov cx,mue3; // Wait 3 micro seconds loop1: asm loop loop1; asm inc dx; // inc FM address to data write port asm mov al,ah; // put the data into the low byte of AX asm out dx,al; // write data to the FM chip mdelay(mue23); // wait 23 microseconds } /************************************************************************** void FM_Reset() DESCRIPTION: Resets the FM chip by clearing all the registers then setting a few appropriate bits. **************************************************************************/ void FM_Reset(void) { WORD i; for(i = 0; i <= 0xf500 ; i+= 0x100) FM_Write(i); FM_Write(0x0120); // Turn on Wave form control FM_Write(0xbdc0); // Set AM and Vibrato to high } /************************************************************************** BYTE FM_Status() DESCRIPTION: Reads the status byte of the FM chip **************************************************************************/ BYTE FM_Status(void) { asm mov dx,fm_addr; asm in al,dx; return(_AL); } /************************************************************************** static int FM_Detect() DESCRIPTION: Detects the presence of an FM chip **************************************************************************/ static int FM_Detect(void) { FM_Write(0x0100); /* init Test register */ FM_Write(0x0460); /* reset both timer */ FM_Write(0x0480); /* enable interrupts */ if(FM_Status() & 0xe0) return(FALSE); FM_Write(0x02ff); /* write ffh to timer 1 */ FM_Write(0x0421); /* start timer 1 */ if(fm_addr==0x388) msdelay(21); /* wait 21000 mcs */ else mdelay(mcalc(80)); /* wait at least 80 microsec */ if((FM_Status() & 0xe0)!=0xc0) return(FALSE); FM_Write(0x0460); /* reset both timer */ FM_Write(0x0480); /* enable interrupts */ return(TRUE); } /************************************************************************** void FM_SetVoice(BYTE voice,BYTE *ins) DESCRIPTION: Sets the voice from an 11 byte array BYTE ID 0 Ampmod /vib /envtype /scale rate/ mod freq mult (oper 1) 1 Ampmod /vib /envtype /scale rate/ mod freq mult (oper 2) 2 Key level scaling/ total level (oper 1) 3 Key level scaling/ total level (oper 2) 4 Attack Rate/ Decay rate (oper 1) 5 Attack Rate/ Decay rate (oper 2) 6 Sustain Level/ Release rate (oper 1) 7 Sustain Level/ Release rate (oper 2) 8 Feedback / Algorythm (oper 1&2) 9 Wave Form Select (oper 1) 10 Wave Form Select (oper 2) **************************************************************************/ void FM_SetVoice(BYTE voice,BYTE *ins) { if(voice > 8) return; FM_keyscale1[voice]=ins[2] & 0xc0; // store key scaling for FM_Vol FM_keyscale2[voice]=ins[3] & 0xc0; // Write voice data FM_Write((0x2000 + FM_off[voice]) | ins[0]); FM_Write((0x2300 + FM_off[voice]) | ins[1]); // For the next tow, we want to // make sure current volume is // preserved. FM_Write((0x4000 + FM_off[voice]) | (ins[2] & 0xc0) | FM_vol[voice]); FM_Write((0x4300 + FM_off[voice]) | (ins[3] & 0xc0) | FM_vol[voice]); FM_Write((0x6000 + FM_off[voice]) | ins[4]); FM_Write((0x6300 + FM_off[voice]) | ins[5]); FM_Write((0x8000 + FM_off[voice]) | ins[6]); FM_Write((0x8300 + FM_off[voice]) | ins[7]); FM_Write((0xc000 + voice * 0x100) | ins[8]); FM_Write((0xE000 + FM_off[voice]) | ins[9]); FM_Write((0xE300 + FM_off[voice]) | ins[10]); } /************************************************************************** void FM_SetFreq(BYTE voice,int freq) DESCRIPTION: sets an explicit pseudo frequency (0 - 0xffff) Note: There is no way to really set a direct frequency on an FM chip, so I wrote this routine which is based on octaves, so I imagine it is slightyl non-linear. Still, it is good for special effects. **************************************************************************/ void FM_SetFreq(BYTE voice,WORD freq) { BYTE highbits,lowbits; WORD data,frac; int octave; octave = (freq / 0x2000); // Extract octtave number (0-7) // convert remaining fraction into a // 10 bit value. frac = ((double)(freq - octave * 0x2000)/(double)0x2000) * 0x157 + 0x157; highbits = (frac & 0x300) >> 8; // divide fraction into low and high bits lowbits = frac & 0xff; data=0xa000+(voice<<8)|lowbits; // store low bits for now FM_key[voice]=highbits|(octave<<2); // save high bits for Key_on(); (octave 4) FM_Write(data); // write low bits to FM chip; } /************************************************************************** void FM_SetNote(BYTE voice,BYTE note) DESCRIPTION: sets the frequency for a chromatic note **************************************************************************/ void FM_SetNote(BYTE voice,BYTE note) { BYTE blk,notex; WORD data; // calculate freq number and octave notex=note-24; blk=1; while(notex>=12) { notex-=12; blk++; // octave number } data=0xa000+(voice<<8)|FM_fnr[notex]; FM_key[voice]=FM_key_or[notex]|(blk<<2); // save part of the note for Key_on() FM_Write(data); // write note to the chip } /************************************************************************** void FM_SetVol(BYTE voice,BYTE vol) DESCRIPTION: The the volume (0-63) for a voice. **************************************************************************/ void FM_SetVol(BYTE voice,BYTE vol) { if (voice >8) return; FM_vol[voice] = (0x3f - (vol & 0x3f)); FM_Write((0x4000+FM_off[voice]) |FM_vol[voice] | FM_keyscale1[voice]); FM_Write((0x4300+FM_off[voice]) |FM_vol[voice] | FM_keyscale2[voice]); } /* A NOTE ABOUT RYTHM FUNCTIONS: I've only played around with these functions a little bit. Here are some things that I've learned: - only channels 6,7,and 8 are affected by the rythm mode. - You will need to develop special instrument definitions to get the rythm instruments to sound right. The most important parameters in a rythm instrument definition are attack/decay/sustain rates and the waveform (bytes 9 and 10). - channels 6,7, and 8 each behave differently in rythm mode: 6 - Instrumental. Sounds like a triangle 7 - White noise. Sounds like a snare drum 8 - High white noise. Sounds like a Cymbal. - If you want to add white noise effects to your program (Gun shots engines, etc...) channel 7 in rythm mode is a good source. - ERIC */ /************************************************************************** void FM_RythmMode(BYTE bool) DESCRIPTION: Turns on/off rythm mode based on input. **************************************************************************/ void FM_RythmMode(BYTE bool) { WORD data; if(bool) data=0xbde0; else data=0xbdc0; rythm=data; FM_Write(data); } /************************************************************************** void FM_RythmOn(BYTE inst) DESCRIPTION: Turns on a Specified rythm instrument. You should use these definitions: FM_HIHAT FM_TOPCYM FM_TOMTOM FM_SNARE FM_BASS **************************************************************************/ void FM_RythmOn(BYTE inst) { rythm|=inst; FM_Write(rythm); } /************************************************************************** void FM_RythmOff(BYTE inst) DESCRIPTION: Turns off a Specified rythm instrument. You should use these definitions: FM_HIHAT FM_TOPCYM FM_TOMTOM FM_SNARE FM_BASS **************************************************************************/ void FM_RythmOff(BYTE inst) { rythm&=(~inst); FM_Write(rythm); } /************************************************************************** void FM_KeyOn(BYTE voice) DESCRIPTION: Turn on an FM voice. (actually it initiates it.) **************************************************************************/ void FM_KeyOn(BYTE voice) { WORD data; if(voice > 8) return; data=0xb000+(voice<<8); // set write address data |= FM_key[voice]|0x20; // set key on bit and frequency FM_Write(data); } /************************************************************************** void FM_KeyOff(BYTE voice) DESCRIPTION: Turn off an FM voice **************************************************************************/ void FM_KeyOff(BYTE voice) { WORD data; if(voice > 8) return; data=0xb000+(voice<<8); // set address data |= FM_key[voice]; // preserve frequency data FM_Write(data); // working. } /* --------------- DSP Stuff ------------ */ /************************************************************************** int DSP_Reset() DESCRIPTION: Resets the DSP **************************************************************************/ int DSP_Reset(void) { int i; asm mov dx,io_addr; // load address for dsp reset asm add dx,DSP_RESET; asm mov al,1 asm out dx,al; // Send a 1 to the DSP mdelay(mcalc(mue3)); // Wait three micro seconds asm mov dx,io_addr; // load address again asm add dx,DSP_RESET; asm mov al,0 asm out dx,al; // send a 0 to the DSP for(i=0;i<50;i++) // DSP should send back an 0xaa { mdelay(mcalc(mue3)); if(DSP_Read()==0xaa) return(TRUE); } return(FALSE); } /************************************************************************** BYTE DSP_Read() DESCRIPTION: reads a byte from the dsp **************************************************************************/ BYTE DSP_Read(void) { asm mov dx,io_addr; // load address to read status byte asm add dx,DSP_RSTATUS; loop: asm in al,dx; // read until high bit is set asm test al,0x80; asm jz loop asm mov dx,io_addr; // read byte from output address asm add dx,DSP_READ; asm in al,dx; return(_AL); } /************************************************************************** void DSP_Write(BYTE output) DESCRIPTION: Writes a byte to the DSP **************************************************************************/ void DSP_Write(BYTE output) { asm mov dx,io_addr; // dx holds the port address asm add dx,DSP_WSTATUS; loop: // wait for bit 7 to clear asm in al,dx; asm test al,0x80; asm jnz loop // write our bute! asm mov dx,io_addr; asm add dx,DSP_WRITE; asm mov al,output asm out dx,al; } /************************************************************************** int get_sb_env() DESCRIPTION: Get sound blaster information from the environment variable "BLASTER" **************************************************************************/ int get_sb_env(void) { char *str; int i; str=getenv("BLASTER"); if(!str) return(FALSE); // no blaster variable? go home // Convert string to upper case for(i = 0 ; i < strlen(str); i++) *(str+i) = toupper(*(str+i)); // pick apart variable for info. // Io address for(i = 0; *(str+i) != 0 && *(str + i) != 'A'; i++); if(*(str+i) == 0) return(FALSE); sscanf(str+i+1,"%x",&io_addr); if(io_addr<0x210 || io_addr>0x260) return (FALSE); // Dma channel number for(i = 0; *(str+i) != 0 && *(str + i) != 'D'; i++); if(*(str+i) == 0) return(FALSE); sscanf(str+i+1,"%d",&dma_ch); if(dma_ch > 7) return(FALSE); // only 0-7 allowed // IRQ port number (?) for(i = 0; *(str+i) != 0 && *(str + i) != 'I'; i++); if(*(str+i) == 0) return(FALSE); sscanf(str+i+1,"%d",&intnr); if(intnr < 2 || intnr > 10) return (FALSE); // card_id for(i = 0; *(str+i) != 0 && *(str + i) != 'T'; i++); if(*(str+i) == 0) return(FALSE); sscanf(str+i+1,"%d",&card_id); return(TRUE); } /************************************************************************** WORD DSP_GetVersion() DESCRIPTION: Get the version number of the DSP **************************************************************************/ WORD DSP_GetVersion(void) { DSP_Write(DSP_GET_VERS); return((WORD)DSP_Read()*256+DSP_Read()); } /* --------------- Misc. Stuff ------------ */ /************************************************************************** int CardCheck() DESCRIPTION: Check for both FM chip and DSP **************************************************************************/ int CardCheck(void) { int ret=0; if(FM_Detect()) ret|=FM_DETECT; if(DSP_Reset()) ret|=DSP_DETECT; return(ret); } /************************************************************************** static void far interrupt testn_int() DESCRIPTION: Function stored as an interrupt to test various interrrupt vectors by test_int() **************************************************************************/ static void far interrupt testn_int(void) { tst_cnt++; asm mov dx,io_addr; asm add dx,DSP_RSTATUS; asm in al,dx; /* Ack DSP interrupt */ asm mov al,0x20; asm out 0x20,al; /* set EOI */ asm mov bx,intnr asm cmp bx,7 asm jbe end asm out 0xa0,al; /* set EOI */ end: } /************************************************************************** static int test_int() DESCRIPTION: This function is used by scan_int() to test interrupt stuff. It installs a test interrupt in the requested spot (intnr) then sees if the DSP can use it. **************************************************************************/ static int test_int(void) { int i; BYTE int1,int2; orgint=getvect(int2vect(intnr)); asm in al,0x21; /* save org master intr settings */ asm mov int1,al; asm in al,0xa1; /* save org slave intr settings */ asm mov int2,al; asm mov al,0xfe; asm cli; asm out 0x21,al; /* disable ALL intr (except timer) */ asm mov al,0xff asm out 0xa1,al; asm sti; tst_cnt=0; // reset our test interrupt counter. enable_int(intnr); // put in our test interrupt setvect(int2vect(intnr),testn_int); DSP_Write(DSP_INVOKE_INTR); /* still magic -- make DSP interrupt? */ for(i=0;i<30000;i++) if(tst_cnt) break; asm cli; asm mov al,int1; asm out 0x21,al; /* restore org master intr */ asm mov al,int2; /* restore org slave intr */ asm out 0xa1,al; asm sti; setvect(int2vect(intnr),orgint); if(i==30000) return(FALSE); else return(TRUE); } /************************************************************************** static int scan_int() DESCRIPTION: This makes sure that the interrupt number picked by the IRQ specification is a good choice. **************************************************************************/ static int scan_int(void) { int i; if(test_int()) return(intnr); // Original choice good? for(i=0;i<5;i++) // Try our five best guesses { intnr=intrx[i]; if(test_int()) return(i); } return(0); } /************************************************************************** static cardtype CheckHard() DESCRIPTION: Checks hardware for DSP and FM chip **************************************************************************/ static cardtype CheckHard(void) { int ret; ret=DSP_Reset(); if(ret) { if(!scan_int()) { // Scan IRQ's sberr= irqerr; return(none); } fm_addr=io_addr+FM_BOTH_OFF; if(!FM_Detect()) { sberr = fmerr; return(none); /* no fm? -> damaged! */ } // SBPro checking here. Not too critical /* fm_both=fm_addr; fm_addr=io_addr+FM_RIGHT_OFF; fm_right=fm_addr; ret3=FM_Detect(); fm_addr=fm_both; if(ret3) { wh_card=sbpro; fm_left=io_addr+FM_LEFT_OFF; } else wh_card=sb20; */ wh_card = sb20; return(wh_card); } sberr = nodsperr; return(none); } /************************************************************************** cardtype WhichCard() DESCRIPTION: Calls various functions to make sure you've got a Sound Blaster **************************************************************************/ cardtype WhichCard(void) { cardtype cret; int i; if(get_sb_env()) cret=CheckHard(); // grab environment variable if(cret!=nodsp) return(cret); // If dsp is there, then go home intnr=7; for(i=0;i<6;i++) // scan around for a better io address { io_addr=ioaddr[i]; cret=CheckHard(); if(cret!=nodsp) return(cret); } return(none); // Uh oh. } /************************************************************************** int SB_Setup() DESCRIPTION: Sets up the sound blaster for action. This is the only function a programmer should really use. Most of the nitty gritty is handled internally. **************************************************************************/ int SB_Setup(void) { int i; InitT2(); /* init Timer 2 */ measure(); /* time loop factor */ mue3=mcalc(3) ; /* calc val for 3 micro sec delay */ mue23=mcalc(23) ; /* calc val for 23 micro sec delay */ WhichCard(); if(wh_card==none) return(FALSE); if(wh_card==sb20 || wh_card==sbpro) { // Get DSP ready dsp_vers=DSP_GetVersion(); DSP_Write(DSP_SPKR_ON); } // Allocate space for Mixing buffer dma_buffer = (BYTE far *)farmalloc(dma_bufferlen+5); if(!dma_buffer) { sberr = nomem; return(FALSE); } // Clear the buffer for(i = 0; i < dma_bufferlen+4; i++) { *(dma_buffer+i) = 0; } SetRate(11000); // Set the sample rate return(TRUE); } /************************************************************************** DWORD far2long(char far *adr) DESCRIPTION: This is used by dma_set to convert a regular far address to a 20 bit flat address. **************************************************************************/ DWORD far2long(char far *adr) { return(((DWORD)FP_SEG(adr)<<4)+FP_OFF(adr)); } /************************************************************************** void SetRate(WORD rate) DESCRIPTION: Sets the sample rate (specified in hz) **************************************************************************/ void SetRate(WORD rate) { DWORD val; if(rate<4000) return; // Calculate number for the sound card val=256-1000000L/rate; DSP_Write(DSP_SAMPLE_RATE); DSP_Write((BYTE)val); sample_rate = rate; // FYI } /************************************************************************** WORD dma_set(DWORD adrl,WORD len,int channel) DESCRIPTION: This programs the DMA controller to start a single pass output transfer. (Draeden of VLA has provided some good information for DMA programming in his INTRO to DMA document) **************************************************************************/ void dma_set(BYTE far *sound_address,WORD len,BYTE channel) { WORD adr; DWORD adrl; BYTE page; adrl = far2long(sound_address); // convert address to 20 bit format adr=(WORD)adrl; // extract page address page=(BYTE)(adrl>>16); // extract page number // PREPARE DMA. // (Channels 0-3 have different command // ports than 4-7.) if(channel < 4) { // channels 0-3? asm { // SET THE CHANNEL MASK BIT mov al,04 // set 3rd bit add al,channel out 0x0a,al // write the channel mov al,0 out 0x0c,al // Clear the byte pointer mov al,0x58 // Read mode (was 48) add al,channel out 0x0b,al // set the mode } } else { // channels 4-7? asm { // SET THE CHANNEL MASK BIT mov al,channel // (no need to set third bit) out 0xd4,al // write the channel mov al,0 out 0xd8,al // Clear the byte pointer mov al,0x58 // Read mode add al,channel out 0xd6,al // set the mode } } // OK. Now the transfer info // WRITE THE ADDRESS OF THE DATA _DX = dma_adr[channel]; // Set the address port asm { mov ax,adr out dx,al // Write address low byte mov al,ah out dx,al // write address high byte } _DX = dma_page[channel]; // Set the page port asm { mov al,page out dx,al // Write the page byte } // WRITE THE LENGTH OF THE DATA _DX = dma_len[channel]; // Set the length port asm { mov ax,len out dx,al // Write length low byte mov al,ah out dx,al // write length high byte } // WRITE THE PAGE LOCATION OF THE DATA // CLEAR THE CHANNEL MASK BIT if(channel < 4) { asm { mov al,channel // (mask bit already clear) out 0x0a,al // write the channel } } else { asm { mov al,channel and al,0x03 // Clear the mask bit out 0x0a,al // write the channel } } } /************************************************************************** void polldma(BYTE channel) DESCRIPTION: This function poles the DMA controller to find out how many bytes are left in the current transfer. As of version 0.4, this function is no long used, but I thought it might be useful to someone else, so I've only commented it out. **************************************************************************/ /*WORD polldma(BYTE channel) { BYTE low1,high1,low2,high2; disable_int(intnr); // Turn off the interrupt so we don't get // caught with our pants down. asm { mov dx,0x0c // Flip the master reset switch mov al,0 out dx,al } _DX = dma_len[channel]; // Load in the counter address // read position twice, becasue sometimes // there is a problem asm{ in al,dx // read the low byte first mov low1,al in al,dx // read the high byte next mov high1,al in al,dx // read the low byte first mov low2,al in al,dx // read the high byte next mov high2,al } enable_int(intnr); // Done, so we'll put the interrupt back. // High bytes the same? Use second reading if(high1 == high2)return((WORD)high2*256+low2); // else the First reading is accurate return(high1*256+low1); }*/ /************************************************************************** static void far interrupt sb_int() DESCRIPTION: This is the sound Blaster interrupt that is to be called at the end of DMA transfer. This software is set up for a continual DMA transfer, so all it does is have the DMA transfer start all over again. **************************************************************************/ static void far interrupt sb_int(void) { int d,i,j; static long int midi_count = 100; static int dma_pos=0; if(DSP_overhead) timer_on(); // start timer to measure DSP overhead // SAMPLE MIXER // Write data to the dma buffer for(dma_pos = 0; dma_pos < dma_bufferlen+1; dma_pos++) { d = 128; // reset data holder for(i = 0; i < sounds_in_queue; i++) { d += *(sounddata[i]++); // accumulate data soundpos[i]--; // move to next byte if(!soundpos[i]) { // Sound done? // Clear the slot and scoot sounds left for(j = i; j < sounds_in_queue;j++) { sounddata[j] = sounddata[j+1]; soundpos[j] = soundpos[j+1]; } sounds_in_queue--; } } if(d>255) d = 255; // clip the output byte if(d<0) d = 0; *(dma_buffer + dma_pos) = d; // Write data to the buffer } *(dma_buffer) = d; // This prevents the popping sound // That can happen when the buffer is // suddenly reset // acknowledge DSP interrupt asm mov dx,io_addr; asm add dx,DSP_RSTATUS; asm in al,dx; // reset interrupt asm mov al,0x20; asm out 0x20,al; /* set EOI */ asm mov bx,intnr asm cmp bx,7 asm jbe end asm out 0xa0,al; /* set EOI */ end: // Varmint's system clock vclock ++; // MIDI stuff first midi_count-= 100; if(midi_count < 100) { MidiPlayer(); // Call a midi player midi_count += midi_callfreq * midi_usertempo*100.0; // reset counter debugnum =midi_usertempo * 100; } if(DSP_overhead) DSP_overhead = timer_off();// How long did this take? } /************************************************************************** void SB_SetVect() DESCRIPTION: Installs the DMA interrupt vector. This makes it so that sb_int() is called whenever a DMA transfer is finished **************************************************************************/ void SB_SetVect(void) { orgirqint=getvect(int2vect(intnr)); setvect(int2vect(intnr),sb_int); /* set vector to our routine */ enable_int(intnr); /* enable sb interrupt */ } /************************************************************************** void SB_RemoveVect() DESCRIPTION: Removes the DMA interrupt vector **************************************************************************/ void SB_RemoveVect(void) { disable_int(intnr); /* disable sb interrupt */ setvect(int2vect(intnr),orgirqint); /* restore org intr vector */ } /* --------------------------------------------------- */ /* timerX routines are following These routines are for highly accurate time measurements */ /************************************************************************** void InitT2() DESCRIPTION: Initializes speaker timer for timing operations. **************************************************************************/ void InitT2(void) { asm in al,0x61 /* no signal on speaker! */ asm and al,0xfd asm or al,1 asm out 0x61,al asm mov al,0xb4 /* program timer 2 with modus 2 */ asm out 0x43,al /* and counter value of 0 (2^16)*/ asm mov al,0 asm out 0x42,al asm out 0x42,al } /************************************************************************** void timer_on() DESCRIPTION: Turns on timer counter for a time measurement **************************************************************************/ void timer_on(void) { asm mov al,0x80; /* latch timer 2 */ asm out 0x43,al; /* save value in timer_hold */ asm in al,0x42; asm mov bl,al; asm in al,0x42; asm mov bh,al; asm mov timer_hold,bx; } /************************************************************************** WORD timer_off() DESCRIPTION: Turns off time and reports clicks elapsed. Note that this timer is so quick that it is wraps after only 56 milliseconds. If you want to timer longer stuff, I suggest using the global variable vclock. It's tick frequency is sample_rate / dma_bufferlen. **************************************************************************/ WORD timer_off(void) { asm mov al,0x80; /* latch timer 2 */ asm out 0x43,al; asm in al,0x42; asm mov ah,al; asm in al,0x42; asm xchg ah,al; asm mov bx,timer_hold; asm sub ax,bx; asm neg ax; asm mov timer_diff,ax; /* calc timer_hold - ax to timer_diff */ return(_AX); } /************************************************************************** WORD to_micro(WORD clk) DESCRIPTION: Converts clock ticks number to microsecs **************************************************************************/ WORD to_micro(WORD clk) { return(clk*838/1000); } /************************************************************************** void clkdelay(WORD clicks) DESCRIPTION: Wait specified number of clock ticks **************************************************************************/ void clkdelay(WORD clicks) { asm mov al,0x80; /* latch timer 2 */ asm out 0x43,al; /* save value in bx */ asm in al,0x42; asm mov bl,al; asm in al,0x42; asm mov bh,al; loop: asm mov al,0x80; asm out 0x43,al; asm in al,0x42; asm mov ah,al; asm in al,0x42; asm xchg ah,al; asm sub ax,bx; asm neg ax; asm cmp ax,clicks; /* leave routine after click CLK's */ asm jle loop; } /************************************************************************** void measure() DESCRIPTION: measures a standard delay loop for other delay functions **************************************************************************/ void measure(void) { timer_on(); asm cli asm mov cx,10000 /* internal test loop */ loop1: asm loop loop1 timer_off(); asm sti timer_val=timer_diff; mue999=mcalc(999); /* calc for msdelay */ } /************************************************************************** void mdelay(WORD delay) DESCRIPTION: Very tiny delay **************************************************************************/ void mdelay(WORD delay) { asm mov cx,delay loop1: asm loop loop1 } /************************************************************************** void _saveregs msdelay(WORD delay) DESCRIPTION: Millisec delay. When using this library, you should use this delay for millisecond delays instead of the delay functions that comes with turbo C. **************************************************************************/ void _saveregs msdelay(WORD delay) { WORD i; for(i=0;i7) return(intnr + 0x68); else return(intnr+8); } /************************************************************************** void enable_int(BYTE nr) DESCRIPTION: Enables an IRQ interrupt using the Programmable interrupt controller (PIC) **************************************************************************/ void enable_int(BYTE nr) { if(nr>7) /* use 2nd intr controller? */ { asm in al,0xa1; // Read the PIC status from 0xa1 asm mov cl,nr; // load the interrupt number in the counter asm sub cl,8 // subract 8 to get the bit location right asm mov bl,1; // load a 1 to bl /* calc correct mask */ asm shl bl,cl; // Bitshift left bl by cl asm not bl; // calculate the compliment asm and al,bl; // leave a hole asm out 0xa1,al; // write the result to the port } else { asm in al,0x21; asm mov cl,nr; asm mov bl,1; asm shl bl,cl; /* calc correct mask */ asm not bl; asm and al,bl; asm out 0x21,al; } } /************************************************************************** void disable_int(BYTE nr) DESCRIPTION: Disables an IRQ interrupt using the Programmable interrupt controller. **************************************************************************/ void disable_int(BYTE nr) { if(nr>7) { asm in al,0xa1; /* use 2nd intr controller? */ asm mov cl,nr; asm sub cl,8 asm mov bl,1; asm shl bl,cl; /* calc correct mask */ asm or al,bl; asm out 0xa1,al; } else { asm in al,0x21; asm mov cl,nr; asm mov bl,1; asm shl bl,cl; /* calc correct mask */ asm or al,bl; asm out 0x21,al; } } /************************************************************************** int getvoice(VOICE v[],int track,int channel, int note) DESCRIPTION: Find the first matching voice (or first inactive voice if a match is not found). This function is used by the midi routine as an interface to get FM voices. **************************************************************************/ int getvoice(VOICE v[],int track,int channel, int note) { int i; for(i = 0; i < 9; i++) { // find matching active note if(v[i].active) { if(v[i].owner_track == track && v[i].owner_channel == channel && v[i].note == note) return(i); } } // no note, so find first inactive voice for(i = 0; i < 9; i++) { if(!v[i].active) return(i); } // no available voices... error return -1; } /************************************************************************** MidiPlayer() DESCRIPTION: Routine for playing midi files. THis is designed to be called from a timer interrupt. To use, set these values in this order: midi_data (must point to a filled MIDI structure.) midi_reset = TRUE; midi_on = TRUE; The interrupt should pick up from there. It is easy to add functionality to this routine. I've already included code to flag a wide variety of MIDI events, so all you have to do is add your own code under the point an event is flagged. I've left a bunch of commented print statements in to help make the code more readable and provide cues for accessing the data. *** WARNING *** If you add your own code here, make sure that it doesn't take more than a few milliseconds to execute. If MidiPlayer() is called again by the interrupt before your code is done, your whole program will probably crash. **************************************************************************/ void MidiPlayer(void) { static VOICE v[9]; // Nine FM voices VOICE vh[9]; // waiting list int vhold = 0; static int i,j,live_tracks,vidx; static int divisions = 96,ms_per_div=5000; static BYTE event,ev,ch,b1,b2,etype,track_on[16]; static last_ev[16]; static long int trkloc[16],itmr,length,l2; static float tmr[16]; static char tdata[256]; // static float beat_ratio = 1.0; if(!midi_data) { // must have data to play! midi_on = FALSE; return; } if(midi_reset) { // Reset? zero track pointers and timers for(i = 0; i < 16; i++ ) { trkloc[i] = 1; // no need to read first time offset tmr[i] = 0; // all timers start at zero track_on[i] = TRUE; if(i < 9) v[i].active = 0; // unreserve all voices last_ev[i] = 0x80; // set last event to note off } midi_reset = 0; // clear midi reset flag live_tracks = midi_data->num_tracks;// set number of active tracks so // we know when to stop. divisions = midi_data->divisions; // ticks per quarter note if(divisions < 0) divisions = -divisions; // some midi files have // negative division values } if(!midi_on) return; // logical switch for midi on/off for(i = 0 ; i < midi_data->num_tracks; i++) { // loop over tracks while(tmr[i] <= 0) { // Process while timer is 0; event = *(TD); // get next event (TD is a macro) trkloc[i]++; // advance track location pointer if(event == 0xFF) { // META event? etype = *(TD); trkloc[i] ++; trkloc[i] += ReadVarLen(TD,&length); // read length of meta event // grab any text data for text events for(j = 0; j < length; j++) tdata[j] = *(TD + j); tdata[j] = 0; switch(etype) { case 0x00: j = *(TD)*256 + *(TD+1); //printf("[%d] SEQUENCE NUMBER (%d)\n",i,j); break; case 0x01: //printf("[%d] TEXT EVENT (%s)\n",i,tdata); break; case 0x02: //printf("[%d] COPYWRITE EVENT (%s)\n",i,tdata); break; case 0x03: //printf("[%d] TRACK NAME EVENT (%s)\n",i,tdata); break; case 0x04: //printf("[%d] INSTRUMENT NAME EVENT (%s)\n",i,tdata); break; case 0x05: //printf("[%d] LYRIC EVENT (%s)\n",i,tdata); break; case 0x06: //printf("[%d] MARKER EVENT (%s)\n",i,tdata); break; case 0x07: //printf("[%d] CUE EVENT (%s)\n",i,tdata); break; case 0x2f: // End of track //printf("[%d] END OF TRACK\n",i); tmr[i] = MAXFLOAT; // set timer to highest value track_on[i] = FALSE; // turn off track live_tracks--; // decrement track counter if(live_tracks == 0) { // last track? Turn off midi! midi_on = FALSE; midi_reset = TRUE; // Make sure we start over return; } break; case 0x51: // TEMPO event (microsecs per 1/4 note) l2 = *(TD) * 0x10000L + *(TD+1) * 0x100 + *(TD+2); //printf("[%d] TEMPO EVENT (%ld)\n",i,l2); ms_per_div = (int)(l2/divisions); // Convert number to a counter used // by an 183 Hhz interrupt. midi_callfreq = ms_per_div/5454.0; break; case 0x58: //printf("[%d] TIME SIG EVENT (%X,%X,%X,%X)\n",i, // *(TD),*(TD+1),*(TD+2),*(TD+3)); break; case 0x59: //printf("[%d] KEY SIG EVENT (%X,%X)\n",i,*(TD),*(TD+1)); break; case 0x7F: //printf("[%d] SEQUENCER DATA EVENT\n",i); break; default: //printf("[%d] *** undefined event *** (%X,type: %X,length %ld)\n",i,event,etype,length); break; } trkloc[i] += length; } else if(event == 0xF0 || event == 0xF7) { // sysex event trkloc[i] += ReadVarLen(TD,&length); //printf("Sysex type 1 [length: %ld]\n",length); trkloc[i] += length; } else { // PROCESS MIDI EVENTS if(!(event & 0x80)) { // top bit Not set? Running status! b1 = event; // b1 = note (usually) b2 = *(TD + 1); // b2 = volume? (usually) event = last_ev[i]; // use last event //printf("Running status >>"); //for(j = 0; j < 9; j++) printf("%d",v[j].active); //printf("\n"); trkloc[i] --; // one less byte for running status. } else { // Else it was a regular event last_ev[i] = event; // set to last event b1 = *(TD); // get next two bytes b2 = *(TD+1); } ev = event & 0xF0; // strip lower four bits ch = event & 0x0f; // channel vidx = getvoice(v,i,ch,b1); // Get a voice index switch(ev) { case 0x80: // Note off //printf("[%d] Note off (%d,%d)",i,b1,b2); trkloc[i] += 2; if(vidx > -1) { // If a matching voice was found, // kill it. FM_KeyOff(vidx); FM_SetVol(vidx,0); v[vidx].active = FALSE; } break; case 0x90: // Note On //printf("[%d] Note on (%X,%d,%d)",i,event,b1,b2); trkloc[i] += 2; if(vidx > -1) { // Voice found? if(v[vidx].active) { // already active? Turn it off. v[vidx].active = FALSE; FM_KeyOff(vidx); FM_SetVol(vidx,0); } else { // Wasn't active? Turn it on. v[vidx].owner_track = i; v[vidx].owner_channel = ch; v[vidx].note = b1; v[vidx].volume = b2; v[vidx].active = TRUE; FM_SetNote(vidx,b1); FM_SetVol(vidx,music_volume); FM_KeyOn(vidx); } } else { // There might be space later // store our note vh[vhold].owner_track = i; vh[vhold].owner_channel = ch; vh[vhold].note = b1; vh[vhold].volume = b2; vhold ++; if(vhold >8) vhold = 8; // Only nine hold notes considered } break; case 0xA0: // Key pressure //printf("[%d] Note presure (%d,%d)\n",i,b1,b2); trkloc[i] += 2; break; case 0xB0: // Control CHange //printf("[%d] Control Change (%d,%d)\n",i,b1,b2); trkloc[i] += 2; break; case 0xC0: // Program change //printf("[%d] Program change (%d)\n",i,b1); trkloc[i] += 1; break; case 0xD0: // Channel Pressure //printf("[%d] Channel Pressure (%d,%d)\n",i,b1); trkloc[i] += 1; break; case 0xE0: // Pitch wheel change //printf("[%d] Pitch change (%d,%d)\n",i,b1,b2); trkloc[i] += 2; break; default: // Uh-OH //printf("MIDI ERROR (F0 midi command)\n"); midi_on = FALSE; return; } } // read next time offset if(track_on[i]) { trkloc[i] += ReadVarLen(TD,&itmr); tmr[i] += itmr; //printf(" T: %ld\n",tmr[i]); } } tmr[i]-= 1.0 * midi_tempoadjust; // decrement timer } // Since there is a limited number // of FM voices, some notes do // not get voiced. This next // section takes a list of // unallocated notes and tries // to find a spot for them. while(vhold) { vhold--; // go to next note for(i = 0; i < 9; i++) { // loop through FM voices if(!v[i].active) { // found empty one? set the note! v[i].owner_track = vh[vhold].owner_track; v[i].owner_channel = vh[vhold].owner_channel; v[i].note = vh[vhold].note; v[i].volume = vh[vhold].volume; v[i].active = TRUE; FM_SetNote(i,b1); FM_SetVol(i,music_volume); FM_KeyOn(i); break; } } if(i == 9) vhold = 0; // List full? forget about other notes } } /************************************************************************** ReadMidi(char *filename, MIDI *mdata, char *errstring) DESCRIPTION: Reads a midi file and stores it to a MIDI data structure INPUTS: filename Pointer to full midi filename midipoint Indirect pointer to empty midi pointer errstring Pointer to a pre-allocated string. Outputs: returns 0 if successful. On error, it returns a number and fills errstring with the error message. **************************************************************************/ int ReadMidi(char *filename, MIDI **midipoint, char *errstring) { int i; FILE *input; char sdata[256]; long int lidata; int idata,lread; MIDI *mdata; input = fopen(filename,"rb"); // open a midi file if(!input) { sprintf(errstring,"cannot open %s",filename); return(1); } // Read the header fread(sdata,1,4,input); // midi id there? sdata[4] = 0; if(strcmp(sdata,"MThd")) { sprintf(errstring,"Not a midi file."); fclose(input); return(2); } // printf("Chunk type: %s\n",sdata); lidata = ReadLong(input); // length of remaining header chunk? // printf("Chunk length: %ld\n",lidata); if(lidata > 250) { sprintf(errstring,"Header chunk has a weird length"); exit(0); } mdata = (MIDI *)malloc(sizeof(MIDI)); // make room for music! if(!mdata) { sprintf(errstring,"Out of memory."); fclose(input); return(3); } *midipoint = mdata; // Assign our pointer idata = ReadShort(input); // Format //printf("Format: %d\n",idata); if(idata != 0 && idata != 1) { sprintf(errstring,"Unrecognized MIDI format"); fclose(input); return(4); } mdata->format = idata; idata = ReadShort(input); // number of tracks //printf("# of Tracks: %d\n",idata); if(idata < 1 || idata > 16) { sprintf(errstring,"Bad number of tracks [%d]",idata); fclose(input); return(5); } mdata->num_tracks = idata; idata = ReadShort(input); // division number (tempo) //printf("1/4 note division: %d\n",idata); mdata->divisions = abs(idata); // Read individual track data for(i = 0; i < mdata->num_tracks; i++) { fread(sdata,1,4,input); // midi track id there? sdata[4] = 0; if(strcmp(sdata,"MTrk")) { sprintf(errstring,"Error reading track #%d",i); fclose(input); return(6); } //printf("Chunk type: %s\n",sdata); lidata = ReadLong(input); // length of remaining track chunk? //printf("Chunk length: %ld\n",lidata); // Allocate space for track mdata->track[i] = (BYTE *)malloc(lidata); if(!mdata->track[i]) { sprintf(errstring,"Out of memory."); fclose(input); return(3); } // read in entire track lread = fread(mdata->track[i],1,lidata,input); if(lread < lidata) { sprintf(errstring,"Premature end of midi file [track %d]",i); fclose(input); return(7); } } fclose(input); return 0; } /************************************************************************** int ReadVarLen(char *data,long int *value) DESCRIPTION: Reads a variable length long interger from data string **************************************************************************/ int ReadVarLen(BYTE *data,long int *value) { int i=0; BYTE c; if ((*value = *(data + i)) & 0x80) { *value &= 0x7f; do { i++; *value = (*value << 7) + ((c = *(data +i)) & 0x7f); } while (c & 0x80); } return(i+1); // return number of bytes read } /************************************************************************** long int ReadShort(FILE *inflile) DESCRIPTION: Reads a short interger from a file **************************************************************************/ int ReadShort(FILE *infile) { return (fgetc(infile) << 8) | fgetc(infile); } /************************************************************************** long int ReadLong(FILE *inflile) DESCRIPTION: Reads a long interger from a file **************************************************************************/ long int ReadLong(FILE *infile) { int i; long int num = 0; num = (unsigned char)fgetc(infile); for(i = 0; i < 3; i++) { num = (num << 8) | (unsigned char)fgetc(infile); } return(num); } /************************************************************************** void playsound(SAMPLE *data,length) DESCRIPTION: Adds a sound to the play list. If the playlist is full, all the sounds are scooted over and the new sound is added as the last item; **************************************************************************/ void playsound(SAMPLE *data,DWORD length) { int i; if(sounds_in_queue >= MAXSOUNDS) { for(i= 0; i 240) flen = 240; // Just a precaution so that // We do not overload dummydata fread(&tag,1,2,input); // tag fread(&num_channels,1,2,input); // number of channels fread(&s_per_sec,1,2,input); // sample rate (hz) fread(&b_per_sec,1,2,input); // bytes per seconf rate fread(dummydata,1,flen-8,input); // Skip ahead fread(dataid,1,4,input); // Dataid string dataid[4] = 0; fread(length,1,4,input); // length of data data = (SAMPLE *)farmalloc(*length+1); // allocate memory for data if(!data) { // oops. Not enough mem! fclose(input); return(NULL); } fread(data,1,*length,input); // read the data for(i = 0; i < *length; i++) { // convert to signed format *(data + i) = ((BYTE)*(data + i))-128; } fclose(input); // Wrap it up return(data); } /************************************************************************** void load_instruments(char *filename,BYTE inst[128][11]) DESCRIPTION: Loads instrument defs from a file (128) File format: 11 hex values followed by a name. eg: 30 33 40 00 E1 E2 87 63 06 01 00 "Electric Piano 2" 33 34 00 00 92 C3 C3 B3 02 01 00 "Harpsichord" 32 32 00 00 92 C3 C3 B3 02 01 00 "Clavichord" . . . (The name is not loaded.) The hex values are dumped into an 2-D array. The file can have more or less than 128 defs without harm to this function. **************************************************************************/ int load_instruments(char *filename,BYTE inst[128][11]) { FILE *input; int i=0,j; char string[255]; input = fopen(filename,"r"); // open the file if(!input) return(0); // read it's contents while(fgets(string,255,input) && i < 128) { for(j = 0; j < 11; j++) sscanf(string+j*3,"%X ",&inst[i][j]); i++; } // clean up and go home fclose(input); return(i); } /************************************************************************** void freemidi(MIDI *m) DESCRIPTION: Frees the data allocated for a MIDI structure **************************************************************************/ void freemidi(MIDI *m) { int i; for(i = 0; i < m->num_tracks; i++) free(m->track[i]); free(m); }