/** ADLIB.C, low level sound driver V1.03 ===== To be used with OUTCHIP.ASM file. ===== Copyright Ad Lib Inc, 1988, 1989 1988/06/22, Dale Glowinski, Marc Savary, Ad Lib Inc. 1989/04/20, Marc savary. The following routines are public (see each routine for further documentation): SoundColdInit( port) SoundWarmInit() SetMode( mode) Set3812( state) SetPitchRange( pR) SetGParam( amD, vibD, nSel) SetVoiceTimbre( voice, paramArray) SetVoiceVolume( voice, volume) SetVoicePitch( voice, pitchBend) NoteOn( voice, pitch) NoteOff( voice) **/ #include "adlib.h" /* In percussive mode, the last 4 voices ( SD TOM HH CYMB) are created using melodic voices 7 & 8. A noise generator use channels 7 & 8 frequency information for creating rhythm instruments. Best result are obtained by setting TOM two octaves below mid C and SD 7 half-tones above TOM. In this implementation, only the TOM pitch may vary, with the SD always set 7 half-tones above. */ #define TOM_PITCH 24 /* best frequency, in range of 0 to 95 */ #define TOM_TO_SD 7 /* 7 half-tones between voice 7 & 8 */ #define SD_PITCH (TOM_PITCH + TOM_TO_SD) #define NR_STEP_PITCH 25 /* 25 steps within a half-tone for pitch bend */ #define GetLocPrm( slot, prm)( (unsigned)paramSlot[ slot][ prm]) #define HighByte( word) ( ((char *)(&word))[ 1]) /* 80x86-8 only .. */ #define RhythmMode() (percussion) typedef char SLOT_PARAM; /* ----------------------------------------------------------------- */ unsigned genAddr; /* address of sound chip */ static char percBits; /* control bits of percussive voices */ static char percMasks[] = { 0x10, 0x08, 0x04, 0x02, 0x01 }; static char notePitch[ 11]; /* pitch of last note-on of each voice */ static char voiceKeyOn[ 11]; /* state of keyOn bit of each voice */ static char noteDIV12[ 96]; /* table of (0..95) DIV 12 */ static char noteMOD12[ 96]; /* table of (0..95) MOD 12 */ static char slotRelVolume[ 18]; /* relative volume of slots */ /* definition of the ELECTRIC-PIANO voice (opr0 & opr1) */ static char pianoParamsOp0[ nbLocParam] = { 1, 1, 3, 15, 5, 0, 1, 3, 15, 0, 0, 0, 1, 0 }; static char pianoParamsOp1[ nbLocParam] = { 0, 1, 1, 15, 7, 0, 2, 4, 0, 0, 0, 1, 0, 0 }; /* definition of default percussive voices: */ static char bdOpr0[] = { 0, 0, 0, 10, 4, 0, 8, 12, 11, 0, 0, 0, 1, 0 }; static char bdOpr1[] = { 0, 0, 0, 13, 4, 0, 6, 15, 0, 0, 0, 0, 1, 0 }; static char sdOpr[] = { 0, 12, 0, 15, 11, 0, 8, 5, 0, 0, 0, 0, 0, 0 }; static char tomOpr[] = { 0, 4, 0, 15, 11, 0, 7, 5, 0, 0, 0, 0, 0, 0 }; static char cymbOpr[] ={ 0, 1, 0, 15, 11, 0, 5, 5, 0, 0, 0, 0, 0, 0 }; static char hhOpr[] = { 0, 1, 0, 15, 11, 0, 7, 5, 0, 0, 0, 0, 0, 0 }; static SLOT_PARAM paramSlot[ 18][ nbLocParam]; /* all the parameters of slots... */ static char amDepth; /* chip global parameters .. */ static char vibDepth; /* ... */ static char noteSel; /* ... */ static char percussion; /* percussion mode parameter */ /* Slot numbers as a function of the voice and the operator. ( melodic only) */ char slotVoice[] [ 2] = { {0, 3}, /* voix 0 */ {1, 4}, /* 1 */ {2, 5}, /* 2 */ {6, 9}, /* 3 */ {7, 10}, /* 4 */ {8, 11}, /* 5 */ {12, 15}, /* 6 */ {13, 16}, /* 7 */ {14, 17} /* 8 */ }; /* Slot numbers for the percussive voices. 0 indicates that there is only one slot. */ char slotPerc[] [ 2] = { {12, 15}, /* Bass Drum: slot 12 et 15 */ {16, 0}, /* SD: slot 16 */ {14, 0}, /* TOM: slot 14 */ {17, 0}, /* TOP-CYM: slot 17 */ {13, 0} /* HH: slot 13 */ }; /* This table gives the offset of each slot within the chip. offset = fn( slot) */ static char offsetSlot[] = { 0, 1, 2, 3, 4, 5, 8, 9, 10, 11, 12, 13, 16, 17, 18, 19, 20, 21 }; /* This table indicates if the slot is a modulator (0) or a carrier (1). opr = fn( slot) */ static char operSlot[] = { 0, 0, 0, /* 1 2 3 */ 1, 1, 1, /* 4 5 6 */ 0, 0, 0, /* 7 8 9 */ 1, 1, 1, /* 10 11 12 */ 0, 0, 0, /* 13 14 15 */ 1, 1, 1, /* 16 17 18 */ }; /* This table gives the voice number associated with each slot. (melodic mode only) voice = fn( slot) */ static char voiceSlot[] = { 0, 1, 2, 0, 1, 2, 3, 4, 5, 3, 4, 5, 6, 7, 8, 6, 7, 8, }; static unsigned fNumNotes[ NR_STEP_PITCH] [ 12]; static int halfToneOffset[ 11]; static unsigned * fNumFreqPtr[ 11]; static int pitchRange; /* pitch variation, half-tone [+1,+12] */ static int pitchRangeStep; /* == pitchRange * NR_STEP_PITCH */ static int modeWaveSel; /* != 0 if used with the 'wave-select' parameters */ extern SndOutput(); /* in file OUTCHIP.ASM */ /* ---------------------------------------------------------- */ /* Must be called for start-up initialisation. Return 0 if harware not found. */ int SoundColdInit( port) unsigned port; /* io port address of sound board (0x388) */ { int hardware; genAddr = port; hardware = BoardInstalled(); SoundWarmInit(); return hardware; } /* SoundColdInit() */ /* ----------------------------------------------- Initialize the chip in melodic mode (mode == 0), set all 9 voices to electric-piano timbres, set the 3 global parameters to zero, set the pitch bend range to 1 half-tone, set the pitch bend of each voice to 0x2000 (no detune), set the volume of each voice to maximum level, and enable the wave-select parameter. ----------------------------------------------- */ SoundWarmInit() { int i; InitSlotVolume(); InitFNums(); SetMode( 0); /* melodic mode */ SetGParam( 0, 0, 0); /* init global parameters */ for( i = 0 ; i < 9; i++) SoundChut( i); SetPitchRange( 1); /* default pitch range is 1 half-tone */ Set3812( 1); } /* SoundWarmInit() */ /* --------------------------------------------- Put the chip in melodic mode (mode == 0), or in percussive mode ( mode != 0). If the melodic mode is chosen, all voices are set to electric-piano, else the first 5 are set to electric-piano, and the percussion voices to their default timbres. --------------------------------------------- */ SetMode( mode) int mode; { if( mode){ SoundChut( BD); SoundChut( SD); SoundChut( TOM); /* set the frequency for the last 4 percussion voices: */ SetFreq( TOM, TOM_PITCH, 0); SetFreq( SD, SD_PITCH, 0); } percussion = mode; percBits = 0; InitSlotParams(); SndSAmVibRhythm(); } /* SetMode() */ /* Enable (state != 0) / disable (state == 0) the wave-select parameters. If you do not want to use the wave-select parameters, call this function with a value of 0 AFTER calling SoundColdInit() or SoundWarmInit(). */ Set3812( state) { int i; modeWaveSel = state ? 0x20 : 0; for( i = 0; i < 18; i++) SndOutput( 0xE0 + offsetSlot[ i], 0); SndOutput( 1, modeWaveSel); } /* Set3812() */ /* Routine to change the pitch bend range. The value can be from 1 to 12 (in half-tones). For example, the value 12 means that the pitch bend will range from -12 (pitchBend == 0, see function 'SetVoicePitch()') to +12 (pitchBend == 0x3fff) half-tones. The change will be effective as of the next call to 'SetVoicePitch()'. */ SetPitchRange( pR) unsigned pR; { if( pR > 12) pR = 12; if( pR < 1) pR = 1; pitchRange = pR; pitchRangeStep = pitchRange * NR_STEP_PITCH; } /* SetPitchRange() */ /* ---------------------------------------------- Set the 3 global parameters AmDepth, VibDepth & NoteSel The change takes place immediately. ---------------------------------------------- */ SetGParam( amD, vibD, nSel) int amD, vibD, nSel; { amDepth = amD; vibDepth = vibD; noteSel = nSel; SndSAmVibRhythm(); SndSNoteSel(); } /* SetGParam() */ /* ------------------------------------------------- Set the parameters of the voice 'voice'. In melodic mode, 'voice' varies from 0 to 8. In percussive mode, voices 0 to 5 are melodic and 6 to 10 are percussive. A timbre (melodic or percussive) is defined as follows: the 13 first parameters of operator 0 ( ksl, multi, feedBack, attack, sustain, eg-typem decay, release, level, am, vib, ksr, fm) followed by the 13 parameters of operator 1 (if a percussive voice, all the parameters are zero), followed by the wave-select parameter for the operators 0 and 1. 'paramArray' is structured as follows: struct { int opr0Prm[ 13]; first 13 parameters int opr1Prm[ 13]; must be 0 if percussive timbre int opr0WaveSel; last parameter int opr1WaveSel; must be 0 if percussive timbre } TimbreDef; The old timbre files (*.INS) do not contain the parameters 'opr0WaveSel' and 'opr1WaveSel'. Set these two parameters to zero if you are using the old file format. ------------------------------------------------- */ SetVoiceTimbre( voice, paramArray) int voice, * paramArray; { int wave0, wave1; int * prm1, * wavePtr; wavePtr = paramArray + 2 * ( nbLocParam -1); wave0 = * wavePtr++; wave1 = * wavePtr; prm1 = paramArray + nbLocParam -1; if( !RhythmMode() || voice < BD) { /* melodic only */ SetSlotParam( (int)slotVoice[ voice][ 0], paramArray, wave0); SetSlotParam( (int)slotVoice[ voice][ 1], prm1, wave1); } else if( voice == BD) { /* Bass Drum */ SetSlotParam( (int)slotPerc[ 0][ 0], paramArray, wave0); SetSlotParam( (int)slotPerc[ 0][ 1], prm1, wave1); } else /* percussion, 1 slot */ SetSlotParam( (int)slotPerc[ voice -BD][ 0], paramArray, wave0); } /* SetVoiceTimbre() */ /* -------------------------------------------------- Set the volume of the voice 'voice' to 'volume'. The resulting output level is (timbreVolume * volume / 127). The change takes place immediately. 0 <= volume <= 127 -------------------------------------------------- */ SetVoiceVolume( voice, volume) unsigned volume; /* 0 - 0x7f */ { int slot; char * slots; #if 1 if( !RhythmMode() || voice < BD) slot = slotVoice[ voice][ 1]; else slot = slotPerc[ voice -BD][ voice == BD ? 1 : 0]; if( volume > MAX_VOLUME) volume = MAX_VOLUME; slotRelVolume[ slot] = volume; SndSKslLevel( slot); #else /* code that modify the two oper. volume of an additive sound: */ if( volume > MAX_VOLUME) volume = MAX_VOLUME; if( !RhythmMode() || voice <= BD) { slots = slotVoice[ voice]; slotRelVolume[ slots[ 1]] = volume; SndSKslLevel( slots[ 1]); if( !GetLocPrm( slots[ 0], prmFm)) { /* additive syntesis: set volume of first slot too */ slotRelVolume[ slots[ 0]] = volume; SndSKslLevel( slots[ 0]); } } else { slot = slotPerc[ voice -BD][ 0]; slotRelVolume[ slot] = volume; SndSKslLevel( slot); } #endif } /* SetVoiceVolume() */ /* ------------------------------------------------- Change the pitch value of a voice. The variation in pitch is a function of the previous call to 'SetPitchRange()' and the value of 'pitchBend'. A value of 0 means -half-tone * pitchRange, 0x2000 means no variation (exact pitch) and 0x3fff means +half-tone * pitchRange. Does not affect the percussive voices, except for the bass drum. The change takes place immediately. 0 <= pitchBend <= 0x3fff, 0x2000 == exact tuning ------------------------------------------------- */ SetVoicePitch( voice, pitchBend) unsigned voice; unsigned pitchBend; { if( ! RhythmMode() || voice <= BD) { /* melodic + bass-drum */ if( pitchBend > MAX_PITCH) pitchBend = MAX_PITCH; ChangePitch( voice, pitchBend); SetFreq( voice, notePitch[ voice], voiceKeyOn[ voice]); } } /* SetVoicePitch() */ /* ----------------------------------------------------------- Routine to start a note playing. 0 <= voice <= 8 in melodic mode, 0 <= voice <= 10 in percussive mode; 0 <= pitch <= 127, 60 == MID_C ( the card can play between 12 and 107 ) ----------------------------------------------------------- */ NoteOn( voice, pitch) unsigned voice; int pitch; /* 0 - 127 */ { pitch -= ( MID_C - CHIP_MID_C); if( pitch < 0) pitch = 0; if( voice < BD || ! RhythmMode()) /* this is a melodic voice */ SetFreq( voice, pitch, 1); else { /* this is a percussive voice */ if( voice == BD) SetFreq( BD, pitch, 0); else if( voice == TOM) { /* for the last 4 percussions, only the TOM may change in frequency, modifying the three others: */ SetFreq( TOM, pitch, 0); SetFreq( SD, pitch + TOM_TO_SD, 0); /* f7 = 3 * f8 */ } percBits |= percMasks[ voice - BD]; SndSAmVibRhythm(); } } /* NoteOn() */ /* Routine to stop playing the note which was started in 'NoteOn()'. 0 <= voice <= 8 in melodic mode, 0 <= voice <= 10 in percussive mode; */ NoteOff( voice) unsigned voice; { if( !RhythmMode() || voice < BD) SetFreq( voice, notePitch[ voice], 0); /* shut off */ else { percBits &= ~percMasks[ voice - BD]; SndSAmVibRhythm(); } } /* NoteOff() */ /* ------------------------------------------------------------------------ static functions ... ------------------------------------------------------------------------ */ /* In melodic mode, initialize all voices to electric-pianos. In percussive mode, initialize the first 6 voices to electric-pianos and the percussive voices to their default timbres. */ static InitSlotParams() { int i; for( i = 0; i < 18; i++) if( operSlot[ i]) SetCharSlotParam( i, pianoParamsOp1, 0); else SetCharSlotParam( i, pianoParamsOp0, 0); if( RhythmMode()) { SetCharSlotParam( 12, bdOpr0, 0); SetCharSlotParam( 15, bdOpr1, 0); SetCharSlotParam( 16, sdOpr, 0); SetCharSlotParam( 14, tomOpr, 0); SetCharSlotParam( 17, cymbOpr, 0); SetCharSlotParam( 13, hhOpr, 0); } } /* InitSlotParams() */ /* Set the volume of all slots. */ static InitSlotVolume() { int i; for( i = 0; i < 18; i++) slotRelVolume[ i] = MAX_VOLUME; } /* InitSlotVolume() */ /* Return binary value of the frequency 260.44 ( C) shifted by +/- numdeltaDemiTon/denDeltaDemiTon multiplied by 8. If the numerator (numDeltaDemiTon) is positive, the frequency is increased; if negative, it is decreased. Fo = Fb( 1 + 0.06 num /den) Fnum8 = Fo * 65536 * 72 / 3.58e6 -100 <= numDeltaDemiTon <= +100 1 <= denDeltaDemiTon <= 100 */ static long CalcPremFNum( numDeltaDemiTon, denDeltaDemiTon) { long f8, fNum8, d100; d100 = denDeltaDemiTon * 100; f8 = ( d100 + 6 * numDeltaDemiTon) * (26044L * 2L); /* 260.44 * 100 * 2 */ f8 /= d100 * 25; fNum8 = f8 * 16384; /*( 16384L * 9L); */ fNum8 *= 9L; fNum8 /= 179L * 625L; return fNum8; } /* CalcPremFNum() */ /* Initialize a line in the frequency table with shifted frequency values. The values are shifted a fraction (num/den) of a half-tone. See following routine. */ static SetFNum( fNumVec, num, den) unsigned * fNumVec; { int i; long val; *fNumVec++ = (unsigned)(4 +(val = CalcPremFNum( num, den))) >> 3; for ( i = 1; i < 12; i++) { val *= 106; *fNumVec++ = (unsigned)(4 +(val /= 100)) >> 3; } } /* SetFNum() */ /* Initialize all lines of the frequency table. Each line represents 12 half-tones shifted by (n/NR_STEP_PITCH), where 'n' is the line number and ranges from 1 to NR_STEP_PITCH. */ static InitFNums() { unsigned i, j, k, num, numStep, pas; numStep = 100 / NR_STEP_PITCH; for( num = pas = 0; pas < NR_STEP_PITCH; pas++, num += numStep) SetFNum( fNumNotes[ pas], num, 100); for( i = 0; i < 11; i++) { fNumFreqPtr[ i] = (unsigned *) fNumNotes[ 0]; halfToneOffset[ i] = 0; } k = 0; for( i = 0; i < 8; i++) for( j = 0; j < 12; j++, k++) { noteDIV12[ k] = i; noteMOD12[ k] = j; } } /* InitFNums() */ /* Routine to set 'halfToneOffset[]' & 'fNumFreqPtr[]'. These two global variables are used to determine the frequency variation to use when a note is played. 0 <= pitchBend <= 3fffH */ static ChangePitch( voice, pitchBend) int voice; int pitchBend; /* 0 - 3fffH, 2000H == exact tuning */ { int t1, t2, delta; long l; static long oldL = ~0; static int oldHt; static unsigned * oldPtr; l = (long)(pitchBend - MID_PITCH) * pitchRangeStep; if( oldL == l) { /* optimisation ... */ fNumFreqPtr[ voice] = oldPtr; halfToneOffset[ voice] = oldHt; } else { t1 = l / MID_PITCH; if( t1 < 0) { t2 = NR_STEP_PITCH -1 -t1; oldHt = halfToneOffset[ voice] = -(t2 / NR_STEP_PITCH); delta = (t2 - NR_STEP_PITCH +1) % NR_STEP_PITCH; if( delta) delta = NR_STEP_PITCH - delta; } else { oldHt = halfToneOffset[ voice] = t1 / NR_STEP_PITCH; delta = t1 % NR_STEP_PITCH; } oldPtr = fNumFreqPtr[ voice] = (unsigned *) fNumNotes[ delta]; oldL = l; } } /* ChangePitch() */ /* Used to change the parameter 'param' of the slot 'slot' with the value 'val'. The chip registers are updated. */ SetASlotParam( slot, param, val) int slot, val; int param; /* parameter number */ { paramSlot[ slot][ param] = val; SndSetPrm( slot, param); } /* SetASlotParam() */ /* ------------------------------------------------------ Set the 14 parameters ( 13 in 'param', 1 in 'waveSel') of slot 'slot'. Update the parameter array and the chip. ------------------------------------------------------ */ static SetSlotParam( slot, param, waveSel) unsigned slot, * param, waveSel; { int i, k; SLOT_PARAM * ptr; for( i = 0, ptr = ¶mSlot[ slot][ 0]; i < nbLocParam -1; i++) *ptr++ = *param++; *ptr = waveSel &= 0x3; SndSetAllPrm( slot); } /* SetSlotParam() */ SetCharSlotParam( slot, cParam, waveSel) unsigned slot, waveSel; char * cParam; { int param[ nbLocParam]; int i; for( i = 0; i < nbLocParam -1; i++) param[ i] = *cParam++; SetSlotParam( slot, param, waveSel); } /* SetCharSlotParam() */ /* ----------------------------------------------- Update the parameter 'prm' for the slot 'slot'. Update the chip registers. ----------------------------------------------- */ static SndSetPrm( slot, prm) int slot, prm; { switch( prm) { case prmPercussion: case prmAmDepth: case prmVibDepth: SndSAmVibRhythm(); break; case prmNoteSel: SndSNoteSel(); break; case prmKsl: case prmLevel: SndSKslLevel( slot); break; case prmFm: case prmFeedBack: SndSFeedFm( slot); break; case prmAttack: case prmDecay: SndSAttDecay( slot); break; case prmRelease: case prmSustain: SndSSusRelease( slot); break; case prmMulti: case prmVib: case prmStaining: case prmKsr: case prmAm: SndSAVEK( slot); break; case prmWaveSel: SndWaveSelect( slot); break; } } /* SndSetPrm() */ /*------------------------------------------------- Transfer all the parameters from slot 'slot' to the chip. */ static SndSetAllPrm( slot) { SndSAmVibRhythm(); SndSNoteSel(); SndSKslLevel( slot); SndSFeedFm( slot); SndSAttDecay( slot); SndSSusRelease( slot); SndSAVEK( slot); SndWaveSelect( slot); } /* SndSetAllPrm() */ /* KSL, LEVEL */ static SndSKslLevel( slot) { unsigned t1; t1 = 63 - (GetLocPrm( slot, prmLevel) & 0x3f); /* amplitude */ t1 = slotRelVolume[ slot] * t1; t1 += t1 + MAX_VOLUME; /* round off to 0.5 */ t1 = 63 - t1 / ( 2 * MAX_VOLUME); t1 |= GetLocPrm( slot, prmKsl) << 6; SndOutput( 0x40 + (int)offsetSlot[ slot], t1); } /* -------------------------------------------- Note sel */ static SndSNoteSel() { SndOutput( 0x08, noteSel ? 64 : 0); } /* SndSNoteSel() */ /* -------------------------------------------- FEED-BACK and FM (connection). Applicable only to operator 0 in melodic mode. */ static SndSFeedFm( slot) { unsigned t1; if( operSlot[ slot]) return; t1 = GetLocPrm( slot, prmFeedBack) << 1; t1 |= GetLocPrm( slot, prmFm) ? 0 : 1; SndOutput( 0xC0 + (int)voiceSlot[ slot], t1); } /* ATTACK, DECAY */ static SndSAttDecay( slot) { unsigned t1; t1 = GetLocPrm( slot, prmAttack) << 4; t1 |= GetLocPrm( slot, prmDecay) & 0xf; SndOutput( 0x60 + (int)offsetSlot[ slot], t1); } /* SUSTAIN, RELEASE */ static SndSSusRelease( slot) { unsigned t1; t1 = GetLocPrm( slot, prmSustain) << 4; t1 |= GetLocPrm( slot, prmRelease) & 0xf; SndOutput( 0x80 + (int)offsetSlot[ slot], t1); } /* AM, VIB, EG-TYP( Sustaining), KSR, MULTI */ static SndSAVEK( slot) { unsigned t1; t1 = GetLocPrm( slot, prmAm) ? 0x80 : 0; t1 += GetLocPrm( slot, prmVib) ? 0x40 : 0; t1 += GetLocPrm( slot, prmStaining) ? 0x20 : 0; t1 += GetLocPrm( slot, prmKsr) ? 0x10 : 0; t1 += GetLocPrm( slot, prmMulti) & 0xf; SndOutput( 0x20 + (int)offsetSlot[ slot], t1); } /* SndSAVEK() */ /* Set the values: AM Depth, VIB depth & Rhythm */ static SndSAmVibRhythm() { unsigned t1; t1 = amDepth ? 0x80 : 0; t1 |= vibDepth ? 0x40 : 0; t1 |= RhythmMode() ? 0x20 : 0; t1 |= percBits; SndOutput( 0xBD, t1); } /* Set the wave-select parameter. */ static SndWaveSelect( slot) { unsigned wave; if( modeWaveSel) wave = GetLocPrm( slot, prmWaveSel) & 0x03; else wave = 0; SndOutput( 0xE0 + offsetSlot[ slot], wave); } /* SndWaveSelect() */ /* Change pitch of voices 0 to 8, for melodic or percussive mode. */ SetFreq( voice, pitch, keyOn) unsigned voice; /* voice number */ int pitch; /* 0 - 95 */ unsigned keyOn; /* Set key-on/off */ { unsigned int fNbr, t1; voiceKeyOn[ voice] = keyOn; notePitch[ voice] = pitch; pitch += halfToneOffset[ voice]; if( pitch > 95) pitch = 95; if( pitch < 0) pitch = 0; fNbr = * ( fNumFreqPtr[ voice] + noteMOD12[ pitch]); SndOutput( 0xA0 +voice, fNbr); t1 = keyOn ? 32 : 0; t1 += ( (unsigned)noteDIV12[ pitch] << 2) + ( 0x3 & HighByte( fNbr) ); SndOutput( 0xB0 +voice, t1); } /* Set the frequency of voice 'voice' to 0 hertz. */ static SoundChut( voice) int voice; { SndOutput( 0xA0 +voice, 0); SndOutput( 0xB0 +voice, 0); } /* SoundChut() */ /* Return 0 if board is not installed */ int BoardInstalled() { unsigned t1, t2, i; SndOutput( 4, 0x60); /* mask T1 & T2 */ SndOutput( 4, 0x80); /* reset IRQ */ t1 = inp( genAddr); /* read status register */ SndOutput( 2, 0xff); /* set timer-1 latch */ SndOutput( 4, 0x21); /* unmask & start T1 */ for( i = 0; i < 200; i++) /* 100 uSec delay for timer-1 overflow */ inp( genAddr); t2 = inp( genAddr); /* read status register */ SndOutput( 4, 0x60); SndOutput( 4, 0x80); return (t1 & 0xE0) == 0 && (t2 & 0xE0) == 0xC0; }