/* * Copyright (c) 1992-1994 Microsoft Corporation */ /* * Interface functions for the OPL3 midi device type. * * These functions are called from midi.c when the kernel driver * has decreed that this is an opl3-compatible device. * * Geraint Davies, Dec 92 */ #include #include #include #include "driver.h" #include "opl3.h" #pragma pack(1) /* --- typedefs ----------------------------------------------- */ /* typedefs for MIDI patches */ #define NUMOPS (4) #define PATCH_1_4OP (0) /* use 4-operator patch */ #define PATCH_2_2OP (1) /* use two 2-operator patches */ #define PATCH_1_2OP (2) /* use one 2-operator patch */ #define RIFF_PATCH (mmioFOURCC('P','t','c','h')) #define RIFF_FM4 (mmioFOURCC('f','m','4',' ')) #define NUM2VOICES (18) /* # 2operator voices */ typedef struct _operStruct { BYTE bAt20; /* flags which are send to 0x20 on fm */ BYTE bAt40; /* flags seet to 0x40 */ /* the note velocity & midi velocity affect total level */ BYTE bAt60; /* flags sent to 0x60 */ BYTE bAt80; /* flags sent to 0x80 */ BYTE bAtE0; /* flags send to 0xe0 */ } operStruct; typedef struct _noteStruct { operStruct op[NUMOPS]; /* operators */ BYTE bAtA0[2]; /* send to 0xA0, A3 */ BYTE bAtB0[2]; /* send to 0xB0, B3 */ /* use in a patch, the block should be 4 to indicate normal pitch, 3 => octave below, etc. */ BYTE bAtC0[2]; /* sent to 0xc0, C3 */ BYTE bOp; /* see PATCH_??? */ BYTE bDummy; /* place holder */ } noteStruct; typedef struct _patchStruct { noteStruct note; /* note. This is all in the structure at the moment */ } patchStruct; #pragma pack() /* MIDI */ typedef struct _voiceStruct { BYTE bNote; /* note played */ BYTE bChannel; /* channel played on */ BYTE bPatch; /* what patch is the note, drums patch = drum note + 128 */ BYTE bOn; /* TRUE if note is on, FALSE if off */ BYTE bVelocity; /* velocity */ BYTE bJunk; /* filler */ DWORD dwTime; /* time that was turned on/off; 0 time indicates that its not in use */ DWORD dwOrigPitch[2]; /* original pitch, for pitch bend */ BYTE bBlock[2]; /* value sent to the block */ } voiceStruct; /* --- module data -------------------------------------------- */ /* a bit of tuning information */ #define FSAMP (50000.0) /* sampling frequency */ #define PITCH(x) ((DWORD)((x) * (double) (1L << 19) / FSAMP)) /* x is the desired frequency, == FNUM at b=1 */ #define EQUAL (1.059463094359) #ifdef EUROPE # define A (442.0) #else # define A (440.0) #endif #define ASHARP (A * EQUAL) #define B (ASHARP * EQUAL) #define C (B * EQUAL / 2.0) #define CSHARP (C * EQUAL) #define D (CSHARP * EQUAL) #define DSHARP (D * EQUAL) #define E (DSHARP * EQUAL) #define F (E * EQUAL) #define FSHARP (F * EQUAL) #define G (FSHARP * EQUAL) #define GSHARP (G * EQUAL) /* volume */ WORD wSynthAttenL = 0; /* in 1.5dB steps */ WORD wSynthAttenR = 0; /* in 1.5dB steps */ /* patch library */ patchStruct FAR * glpPatch = NULL; /* points to the patches */ /* voices being played */ voiceStruct gVoice[NUM2VOICES]; /* info on what voice is where */ static DWORD gdwCurTime = 1; /* for note on/off */ BYTE gbCur4opReg = 0; /* current value to 4-operator connection */ /* channel volumes */ BYTE gbChanAtten[NUMCHANNELS]; /* attenuation of each channel, in .75 db steps */ BYTE gbStereoMask[NUMCHANNELS]; /* mask for left/right for stereo midi files */ extern short giBend[ NUMCHANNELS ] ; // bend for each channel /* operator offset location */ static WORD BCODE gw2OpOffset[ NUM2VOICES ][ 2 ] = { { 0x000,0x003 }, { 0x001,0x004 }, { 0x002,0x005 }, { 0x008,0x00b }, { 0x009,0x00c }, { 0x00a,0x00d }, { 0x010,0x013 }, { 0x011,0x014 }, { 0x012,0x015 }, { 0x100,0x103 }, { 0x101,0x104 }, { 0x102,0x105 }, { 0x108,0x10b }, { 0x109,0x10c }, { 0x10a,0x10d }, { 0x110,0x113 }, { 0x111,0x114 }, { 0x112,0x115 }, } ; /* pitch values, from middle c, to octave above it */ static DWORD BCODE gdwPitch[12] = { PITCH(C), PITCH(CSHARP), PITCH(D), PITCH(DSHARP), PITCH(E), PITCH(F), PITCH(FSHARP), PITCH(G), PITCH(GSHARP), PITCH(A), PITCH(ASHARP), PITCH(B)}; /* --- internal functions -------------------------------------- */ //------------------------------------------------------------------------ // VOID MidiFMNote // // Description: // Turns on an FM-synthesizer note. // // Parameters: // WORD wNote // the note number from 0 to NUMVOICES // // noteStruct FAR *lpSN // structure containing information about what // is to be played. // // Return Value: // Nothing. // // //------------------------------------------------------------------------ VOID NEAR PASCAL Opl3_FMNote ( WORD wNote, noteStruct FAR * lpSN ) { WORD i ; WORD wOffset ; operStruct FAR *lpOS ; // D1( "\nMidiFMNote" ) ; // write out a note off, just to make sure... wOffset = (wNote < (NUM2VOICES / 2)) ? wNote : (wNote + 0x100 - 9) ; MidiSendFM( AD_BLOCK + wOffset, 0 ) ; // writing the operator information // for (i = 0; i < (WORD)((wNote < NUM4VOICES) ? NUMOPS : 2); i++) for (i = 0; i < 2; i++) { lpOS = &lpSN -> op[ i ] ; wOffset = gw2OpOffset[ wNote ][ i ] ; MidiSendFM( 0x20 + wOffset, lpOS -> bAt20) ; MidiSendFM( 0x40 + wOffset, lpOS -> bAt40) ; MidiSendFM( 0x60 + wOffset, lpOS -> bAt60) ; MidiSendFM( 0x80 + wOffset, lpOS -> bAt80) ; MidiSendFM( 0xE0 + wOffset, lpOS -> bAtE0) ; #ifdef DEBUG { char szDebug[ 80 ] ; wsprintf( szDebug, "20=%02x 40=%02x 60=%02x 80=%02x E0=%02x", lpOS -> bAt20, lpOS -> bAt40, lpOS -> bAt60, lpOS -> bAt80, lpOS -> bAtE0 ) ; D1( szDebug ) ; } #endif } // write out the voice information wOffset = (wNote < 9) ? wNote : (wNote + 0x100 - 9) ; MidiSendFM( 0xa0 + wOffset, lpSN -> bAtA0[ 0 ] ) ; MidiSendFM( 0xc0 + wOffset, lpSN -> bAtC0[ 0 ] ) ; // Note on... MidiSendFM( 0xb0 + wOffset, (BYTE)(lpSN -> bAtB0[ 0 ] | 0x20) ) ; #ifdef NOT_USED { char szDebug[ 80 ] ; wsprintf( szDebug, "A0=%02x B0=%02x C0=%02x", lpSN -> bAtA0[ 0 ], lpSN -> bAtB0[ 0 ], lpSN -> bAtC0[ 0 ] ) ; D1( szDebug ) ; } #endif } // end of MidiFMNote() //------------------------------------------------------------------------ // WORD MidiFindEmptySlot // // Description: // This finds an empty note-slot for a MIDI voice. // If there are no empty slots then this looks for the oldest // off note. If this doesn't work then it looks for the oldest // on-note of the same patch. If all notes are still on then // this finds the oldests turned-on-note. // // Parameters: // BYTE bPatch // MIDI patch that will replace it. // // Return Value: // WORD // note slot # // // //------------------------------------------------------------------------ WORD NEAR PASCAL Opl3_FindEmptySlot ( BYTE bPatch ) { WORD i, found ; DWORD dwOldest ; // D1( "\nMidiFindEmptySlot" ) ; // First, look for a slot with a time == 0 for (i = 0; i < NUM2VOICES; i++) if (!gVoice[ i ].dwTime) return ( i ) ; // Now, look for a slot of the oldest off-note dwOldest = 0xffffffff ; found = 0xffff ; for (i = 0; i < NUM2VOICES; i++) if (!gVoice[ i ].bOn && (gVoice[ i ].dwTime < dwOldest)) { dwOldest = gVoice[ i ].dwTime ; found = i ; } if (found != 0xffff) return ( found ) ; // Now, look for a slot of the oldest note with // the same patch dwOldest = 0xffffffff ; found = 0xffff ; for (i = 0; i < NUM2VOICES; i++) if ((gVoice[ i ].bPatch == bPatch) && (gVoice[ i ].dwTime < dwOldest)) { dwOldest = gVoice[ i ].dwTime ; found = i ; } if (found != 0xffff) return ( found ) ; // Now, just look for the oldest voice found = 0 ; dwOldest = gVoice[ found ].dwTime ; for (i = (found + 1); i < NUM2VOICES; i++) if (gVoice[ i ].dwTime < dwOldest) { dwOldest = gVoice[ i ].dwTime ; found = i ; } return ( found ) ; } // end of MidiFindEmptySlot() //------------------------------------------------------------------------ // WORD MidiFindFullSlot // // Description: // This finds a slot with a specific note, and channel. // If it is not found then 0xFFFF is returned. // // Parameters: // BYTE bNote // MIDI note number // // BYTE bChannel // MIDI channel # // // Return Value: // WORD // note slot #, or 0xFFFF if can't find it // // //------------------------------------------------------------------------ WORD NEAR PASCAL Opl3_FindFullSlot ( BYTE bNote, BYTE bChannel ) { WORD i ; // D1( "\nMidiFindFullSlot" ) ; for (i = 0; i < NUM2VOICES; i++) if ((bChannel == gVoice[ i ].bChannel) && (bNote == gVoice[ i ].bNote) && (gVoice[ i ].bOn)) return ( i ) ; // couldn't find it return ( 0xFFFF ) ; } // end of MidiFindFullSlot() /************************************************************** Opl3_CalcBend - This calculates the effects of pitch bend on an original value. inputs DWORD dwOrig - original frequency short iBend - from -32768 to 32768, -2 half steps to +2 returns DWORD - new frequency */ DWORD NEAR PASCAL Opl3_CalcBend (DWORD dwOrig, short iBend) { DWORD dw; // D1 (("Opl3_CalcBend")); /* do different things depending upon positive or negative bend */ if (iBend > 0) { dw = (DWORD)((iBend * (LONG)(256.0 * (EQUAL * EQUAL - 1.0))) >> 8); dwOrig += (DWORD)(AsULMUL(dw, dwOrig) >> 15); } else if (iBend < 0) { dw = (DWORD)(((-iBend) * (LONG)(256.0 * (1.0 - 1.0 / EQUAL / EQUAL))) >> 8); dwOrig -= (DWORD)(AsULMUL(dw, dwOrig) >> 15); } return dwOrig; } /***************************************************************** Opl3_CalcFAndB - Calculates the FNumber and Block given a frequency. inputs DWORD dwPitch - pitch returns WORD - High byte contains the 0xb0 section of the block and fNum, and the low byte contains the 0xa0 section of the fNumber */ WORD NEAR PASCAL Opl3_CalcFAndB (DWORD dwPitch) { BYTE bBlock; // D1(("Opl3_CalcFAndB")); /* bBlock is like an exponential to dwPitch (or FNumber) */ for (bBlock = 1; dwPitch >= 0x400; dwPitch >>= 1, bBlock++) ; if (bBlock > 0x07) bBlock = 0x07; /* we cant do anything about this */ /* put in high two bits of F-num into bBlock */ return ((WORD) bBlock << 10) | (WORD) dwPitch; } /************************************************************** Opl3_CalcVolume - This calculates the attenuation for an operator. inputs BYTE bOrigAtten - original attenuation in 0.75 dB units BYTE bChannel - MIDI channel BYTE bVelocity - velocity of the note BYTE bOper - operator number (from 0 to 3) BYTE bMode - voice mode (from 0 through 7 for modulator/carrier selection) returns BYTE - new attenuation in 0.75 dB units, maxing out at 0x3f. */ BYTE NEAR PASCAL Opl3_CalcVolume (BYTE bOrigAtten, BYTE bChannel, BYTE bVelocity, BYTE bOper, BYTE bMode) { BYTE bVolume; WORD wTemp; WORD wMin; switch (bMode) { case 0: bVolume = (BYTE)(bOper == 3); break; case 1: bVolume = (BYTE)((bOper == 1) || (bOper == 3)); break; case 2: bVolume = (BYTE)((bOper == 0) || (bOper == 3)); break; case 3: bVolume = (BYTE)(bOper != 1); break; case 4: bVolume = (BYTE)((bOper == 1) || (bOper == 3)); break; case 5: bVolume = (BYTE)(bOper >= 1); break; case 6: bVolume = (BYTE)(bOper <= 2); break; case 7: bVolume = TRUE; break; }; if (!bVolume) return bOrigAtten; /* this is a modulator wave */ wMin =(wSynthAttenL < wSynthAttenR) ? wSynthAttenL : wSynthAttenR; wTemp = bOrigAtten + ((wMin << 1) + gbChanAtten[bChannel] + gbVelocityAtten[bVelocity >> 2]); return (wTemp > 0x3f) ? (BYTE) 0x3f : (BYTE) wTemp; } /************************************************************** Opl3_CalcStereoMask - This calculates the stereo mask. inputs BYTE bChannel - MIDI channel returns BYTE - mask (for register 0xc0-c8) for eliminating the left/right/both channels */ BYTE NEAR PASCAL Opl3_CalcStereoMask (BYTE bChannel) { WORD wLeft, wRight; /* figure out the basic levels of the 2 channels */ wLeft = (wSynthAttenL << 1) + gbChanAtten[bChannel]; wRight = (wSynthAttenR << 1) + gbChanAtten[bChannel]; /* if both are too quiet then mask to nothing */ if ((wLeft > 0x3f) && (wRight > 0x3f)) return 0xcf; /* if one channel is significantly quieter than the other than eliminate it */ if ((wLeft + 8) < wRight) return (BYTE)(0xef & gbStereoMask[bChannel]); /* right is too quiet so eliminate */ else if ((wRight + 8) < wLeft) return (BYTE)(0xdf & gbStereoMask[bChannel]); /* left too quiet so eliminate */ else return (BYTE)(gbStereoMask[bChannel]); /* use both channels */ } //------------------------------------------------------------------------ // VOID MidiNewVolume // // Description: // This should be called if a volume level has changed. // This will adjust the levels of all the playing voices. // // Parameters: // BYTE bChannel // channel # of 0xFF for all channels // // Return Value: // Nothing. // // //------------------------------------------------------------------------ VOID FAR PASCAL Opl3_setvolume ( BYTE bChannel ) { WORD i, j, wTemp, wOffset ; noteStruct FAR *lpPS ; BYTE bMode, bStereo ; // Make sure that we are actually open... if (!glpPatch) return ; // Loop through all the notes looking for the right // channel. Anything with the right channel gets // its pitch bent. for (i = 0; i < NUM2VOICES; i++) if ((gVoice[ i ].bChannel == bChannel) || (bChannel == 0xff)) { // Get a pointer to the patch lpPS = &(glpPatch + gVoice[ i ].bPatch) -> note ; // Modify level for each operator, but only if they // are carrier waves... bMode = (BYTE) ( (lpPS->bAtC0[0] & 0x01) * 2 + 4); for (j = 0; j < 2; j++) { wTemp = (BYTE) Opl3_CalcVolume( (BYTE)(lpPS -> op[ j ].bAt40 & (BYTE) 0x3f), gVoice[ i ].bChannel, gVoice[i].bVelocity, (BYTE) j, bMode ) ; // Write the new value out. wOffset = gw2OpOffset[ i ][ j ] ; MidiSendFM( 0x40 + wOffset, (BYTE) ((lpPS -> op[ j ].bAt40 & (BYTE)0xc0) | (BYTE) wTemp) ) ; } // Do stereo panning, but cutting off a left or right // channel if necessary. bStereo = Opl3_CalcStereoMask( gVoice[ i ].bChannel ) ; wOffset = (i < (NUM2VOICES / 2)) ? i : (i + 0x100 - 9) ; MidiSendFM( 0xc0 + wOffset, (BYTE)(lpPS -> bAtC0[ 0 ] & bStereo) ) ; } } // end of MidiNewVolume() //------------------------------------------------------------------------ // WORD MidiNoteOn // // Description: // This turns on a note, including drums with a patch # of the // drum note + 0x80. // // Parameters: // BYTE bPatch // MIDI patch // // BYTE bNote // MIDI note // // BYTE bChannel // MIDI channel // // BYTE bVelocity // velocity value // // short iBend // current pitch bend from -32768 to 32767 // // Return Value: // WORD // note slot #, or 0xFFFF if it is inaudible // // //------------------------------------------------------------------------ VOID NEAR PASCAL Opl3_NoteOn ( BYTE bPatch, BYTE bNote, BYTE bChannel, BYTE bVelocity, short iBend ) { WORD wTemp, i, j ; BYTE b4Op, bTemp, bMode, bStereo ; patchStruct FAR *lpPS ; DWORD dwBasicPitch, dwPitch[ 2 ] ; noteStruct NS ; // D1( "\nMidiNoteOn" ) ; // Get a pointer to the patch lpPS = glpPatch + bPatch ; #ifdef DEBUG { char szDebug[ 80 ] ; wsprintf( szDebug, "bPatch = %d, bNote = %d", bPatch, bNote ) ; D1( szDebug ) ; } #endif // Find out the basic pitch according to our // note value. This may be adjusted because of // pitch bends or special qualities for the note. dwBasicPitch = gdwPitch[ bNote % 12 ] ; bTemp = bNote / (BYTE) 12 ; if (bTemp > (BYTE) (60 / 12)) dwBasicPitch = AsLSHL( dwBasicPitch, (BYTE)(bTemp - (BYTE)(60/12)) ) ; else if (bTemp < (BYTE) (60/12)) dwBasicPitch = AsULSHR( dwBasicPitch, (BYTE)((BYTE) (60/12) - bTemp) ) ; // Copy the note information over and modify // the total level and pitch according to // the velocity, midi volume, and tuning. AsMemCopy( (LPSTR) &NS, (LPSTR) &lpPS -> note, sizeof( noteStruct ) ) ; b4Op = (BYTE)(NS.bOp != PATCH_1_2OP) ; // for (j = 0; j < (WORD)(b4Op ? 2 : 1); j++) for (j = 0; j < 2; j++) { // modify pitch dwPitch[ j ] = dwBasicPitch ; bTemp = (BYTE)((NS.bAtB0[ j ] >> 2) & 0x07) ; if (bTemp > 4) dwPitch[ j ] = AsLSHL( dwPitch[ j ], (BYTE)(bTemp - (BYTE)4) ) ; else if (bTemp < 4) dwPitch[ j ] = AsULSHR( dwPitch[ j ], (BYTE)((BYTE)4 - bTemp) ) ; wTemp = Opl3_CalcFAndB( Opl3_CalcBend( dwPitch[ j ], iBend ) ) ; NS.bAtA0[ j ] = (BYTE) wTemp ; NS.bAtB0[ j ] = (BYTE) 0x20 | (BYTE) (wTemp >> 8) ; } // Modify level for each operator, but only // if they are carrier waves // if (b4Op) // { // bMode = (BYTE)((NS.bAtC0[ 0 ] & 0x01) * 2 | (NS.bAtC0[ 1 ] & 0x01)) ; // if (NS.bOp == PATCH_2_2OP) // bMode += 4 ; // } // else bMode = (BYTE) ((NS.bAtC0[ 0 ] & 0x01) * 2 + 4) ; // for (i = 0; i < (WORD)(b4Op ? NUMOPS : 2); i++) for (i = 0; i < 2; i++) { wTemp = (BYTE) Opl3_CalcVolume ( (BYTE)(NS.op[ i ].bAt40 & (BYTE) 0x3f), bChannel, bVelocity, (BYTE) i, bMode ) ; NS.op[ i ].bAt40 = (NS.op[ i ].bAt40 & (BYTE)0xc0) | (BYTE) wTemp ; } // Do stereo panning, but cutting off a left or // right channel if necessary... bStereo = Opl3_CalcStereoMask( bChannel ) ; NS.bAtC0[ 0 ] &= bStereo ; // if (b4Op) // NS.bAtC0[ 1 ] &= bStereo ; // Find an empty slot, and use it... // wTemp = Opl3_FindEmptySlot( bPatch, b4Op ) ; wTemp = Opl3_FindEmptySlot( bPatch ) ; #ifdef DEBUG { char szDebug[ 80 ] ; wsprintf( szDebug, "Found empty slot: %d", wTemp ) ; D1( szDebug ) ; } #endif Opl3_FMNote( wTemp, &NS ) ; gVoice[ wTemp ].bNote = bNote ; gVoice[ wTemp ].bChannel = bChannel ; gVoice[ wTemp ].bPatch = bPatch ; gVoice[ wTemp ].bVelocity = bVelocity ; gVoice[ wTemp ].bOn = TRUE ; gVoice[ wTemp ].dwTime = gdwCurTime++ ; gVoice[ wTemp ].dwOrigPitch[0] = dwPitch[ 0 ] ; // not including bend gVoice[ wTemp ].dwOrigPitch[1] = dwPitch[ 1 ] ; // not including bend gVoice[ wTemp ].bBlock[0] = NS.bAtB0[ 0 ] ; gVoice[ wTemp ].bBlock[1] = NS.bAtB0[ 1 ] ; } // end of Opl3_NoteOn() //------------------------------------------------------------------------ // VOID Opl3_NoteOff // // Description: // This turns off a note, including drums with a patch // # of the drum note + 128. // // Parameters: // BYTE bPatch // MIDI patch // // BYTE bNote // MIDI note // // BYTE bChannel // MIDI channel // // Return Value: // Nothing. // // //------------------------------------------------------------------------ VOID FAR PASCAL Opl3_NoteOff ( BYTE bPatch, BYTE bNote, BYTE bChannel ) { patchStruct FAR *lpPS ; WORD wOffset, wTemp ; // D1( "\nMidiNoteOff" ) ; // Find the note slot wTemp = Opl3_FindFullSlot( bNote, bChannel ) ; if (wTemp != 0xffff) { // get a pointer to the patch lpPS = glpPatch + (BYTE) gVoice[ wTemp ].bPatch ; // shut off the note portion // we have the note slot, turn it off. // if (wTemp < NUM4VOICES) // { // MidiSendFM( AD_BLOCK + gw4VoiceOffset[ wTemp ], // (BYTE)(gVoice[ wTemp ].bBlock[ 0 ] & 0x1f) ) ; // MidiSendFM( AD_BLOCK + gw4VoiceOffset[ wTemp ] + 3, // (BYTE)(gVoice[ wTemp ].bBlock[ 1 ] & 0x1f) ) ; // } // else wOffset = (wTemp < (NUM2VOICES / 2)) ? wTemp : (wTemp + 0x100 - 9) ; MidiSendFM( AD_BLOCK + wOffset, (BYTE)(gVoice[ wTemp ].bBlock[ 0 ] & 0x1f) ) ; // Note this... gVoice[ wTemp ].bOn = FALSE ; gVoice[ wTemp ].bBlock[ 0 ] &= 0x1f ; gVoice[ wTemp ].bBlock[ 1 ] &= 0x1f ; gVoice[ wTemp ].dwTime = gdwCurTime ; } } // end of Opl3_NoteOff() /* * Opl3_ChannelNotesOff - turn off all notes on a channel */ VOID Opl3_ChannelNotesOff(BYTE bChannel) { int i; for (i = 0; i < NUM2VOICES; i++) { if ((gVoice[ i ].bOn) && (gVoice[ i ].bChannel == bChannel)) { Opl3_NoteOff( gVoice[i].bPatch, gVoice[i].bNote, gVoice[i].bChannel ) ; } } } /* * Opl3_AllNotesOff - turn off all notes * */ VOID Opl3_AllNotesOff(void) { BYTE i; for (i = 0; i < NUM2VOICES; i++) { Opl3_NoteOff (gVoice[i].bPatch, gVoice[i].bNote, gVoice[i].bChannel); } } /* Opl3_NewVolume - This should be called if a volume level * has changed. This will adjust the levels of all the playing * voices. * * inputs * WORD wLeft - left attenuation (1.5 db units) * WORD wRight - right attenuation (ignore if mono) * returns * none */ VOID FAR PASCAL Opl3_NewVolume (WORD wLeft, WORD wRight) { /* make sure that we are actually open */ if (!glpPatch) return; wSynthAttenL = wLeft; wSynthAttenR = wRight; Opl3_setvolume(0xff); } /* Opl3_ChannelVolume - set the volume level for an individual channel. * * inputs * BYTE bChannel - channel number to change * WORD wAtten - attenuation in 1.5 db units * * returns * none */ VOID FAR PASCAL Opl3_ChannelVolume(BYTE bChannel, WORD wAtten) { gbChanAtten[bChannel] = (BYTE)wAtten; Opl3_setvolume(bChannel); } /* Opl3_SetPan - set the left-right pan position. * * inputs * BYTE bChannel - channel number to alter * BYTE bPan - 0 for left, 127 for right or somewhere in the middle. * * returns - none */ VOID FAR PASCAL Opl3_SetPan(BYTE bChannel, BYTE bPan) { /* change the pan level */ if (bPan > (64 + 16)) gbStereoMask[bChannel] = 0xef; /* let only right channel through */ else if (bPan < (64 - 16)) gbStereoMask[bChannel] = 0xdf; /* let only left channel through */ else gbStereoMask[bChannel] = 0xff; /* let both channels */ /* change any curently playing patches */ Opl3_setvolume(bChannel); } //------------------------------------------------------------------------ // VOID MidiPitchBend // // Description: // This pitch bends a channel. // // Parameters: // BYTE bChannel // channel // // short iBend // values from -32768 to 32767, being -2 to +2 half steps // // Return Value: // Nothing. // // //------------------------------------------------------------------------ VOID NEAR PASCAL Opl3_PitchBend ( BYTE bChannel, short iBend ) { WORD i, wTemp[ 2 ], wOffset, j ; DWORD dwNew ; // D1( "\nMidiPitchBend" ) ; // Remember the current bend.. giBend[ bChannel ] = iBend ; // Loop through all the notes looking for the right // channel. Anything with the right channel gets its // pitch bent... for (i = 0; i < NUM2VOICES; i++) if (gVoice[ i ].bChannel == bChannel) { j = 0 ; dwNew = Opl3_CalcBend( gVoice[ i ].dwOrigPitch[ j ], iBend ) ; wTemp[ j ] = Opl3_CalcFAndB( dwNew ) ; gVoice[ i ].bBlock[ j ] = (gVoice[ i ].bBlock[ j ] & (BYTE) 0xe0) | (BYTE) (wTemp[ j ] >> 8) ; wOffset = (i < (NUM2VOICES / 2)) ? i : (i + 0x100 - 9) ; MidiSendFM( AD_BLOCK + wOffset, gVoice[ i ].bBlock[ 0 ] ) ; MidiSendFM( AD_FNUMBER + wOffset, (BYTE) wTemp[ 0 ] ) ; } } // end of MidiPitchBend() #ifdef LOAD_PATCHES static TCHAR BCODE gszDefPatchLib[] = TEXT("SYNTH.PAT"); static TCHAR BCODE gszIniKeyPatchLib[] = INI_STR_PATCHLIB; static TCHAR BCODE gszIniDrvSection[] = INI_DRIVER; static TCHAR BCODE gszIniDrvFile[] = INI_SOUND; static TCHAR BCODE gszSysIniSection[] = TEXT("synth.dll"); static TCHAR BCODE gszSysIniFile[] = TEXT("System.Ini"); /** static DWORD NEAR PASCAL DrvGetProfileString(LPSTR szKeyName, LPSTR szDef, LPSTR szBuf, UINT wBufLen) * * DESCRIPTION: * * * ARGUMENTS: * (LPSTR szKeyName, LPSTR szDef, LPSTR szBuf, WORD wBufLen) * HINT wSystem - if TRUE write/read to system.ini * * RETURN (static DWORD NEAR PASCAL): * * * NOTES: * ** cjp */ static DWORD NEAR PASCAL DrvGetProfileString(LPTSTR szKeyName, LPTSTR szDef, LPTSTR szBuf, UINT wBufLen, UINT wSystem) { return GetPrivateProfileString(wSystem ? gszSysIniSection : gszIniDrvSection, szKeyName, szDef, szBuf, wBufLen, wSystem ? gszSysIniFile : gszIniDrvFile); } /* DrvGetProfileString() */ #endif // LOAD_PATCHES /* Opl3_BoardInit - initialise board and load patches as necessary. * * inputs - none * returns - 0 for success or the error code */ WORD Opl3_BoardInit(void) { HMMIO hmmio; MMCKINFO mmckinfo, mm2; TCHAR szPatchLib[STRINGLEN]; /* patch libarary */ D1 (("Opl3_Init")); /* Check we haven't already initialized */ if (glpPatch != NULL) { return 0; } /* allocate the memory, and fill it up from the patch * library. */ glpPatch = (patchStruct FAR *)GlobalLock(GlobalAlloc(GMEM_MOVEABLE|GMEM_SHARE|GMEM_ZEROINIT, sizeof(patchStruct) * NUMPATCHES)); if (!glpPatch) { D1 (("Opl3_Init: could not allocate patch container memory!")); return ERROR_OUTOFMEMORY; } #ifdef LOAD_PATCHES /* should the load patches be moved to the init section? */ DrvGetProfileString(gszIniKeyPatchLib, TEXT(""), szPatchLib, sizeof(szPatchLib), FALSE); if (lstrcmpi( (LPTSTR) szPatchLib, TEXT("") ) == 0) #endif // LOAD_PATCHES { HRSRC hrsrcPatches ; HGLOBAL hPatches ; LPTSTR lpPatches ; hrsrcPatches = FindResource( ghModule, MAKEINTRESOURCE( DATA_FMPATCHES ), RT_BINARY ) ; if (NULL != hrsrcPatches) { hPatches = LoadResource( ghModule, hrsrcPatches ) ; lpPatches = LockResource( hPatches ) ; AsMemCopy( glpPatch, lpPatches, sizeof( patchStruct ) * NUMPATCHES ) ; UnlockResource( hPatches ) ; FreeResource( hPatches ) ; } else { TCHAR szAlert[ 50 ] ; TCHAR szErrorBuffer[ 255 ] ; LoadString( ghModule, SR_ALERT, szAlert, sizeof( szAlert ) / sizeof(TCHAR)) ; LoadString( ghModule, SR_ALERT_NORESOURCE, szErrorBuffer, sizeof( szErrorBuffer ) / sizeof(TCHAR) ) ; MessageBox( NULL, szErrorBuffer, szAlert, MB_OK|MB_ICONHAND ) ; } } #ifdef LOAD_PATCHES else { hmmio = mmioOpen (szPatchLib, NULL, MMIO_READ); if (hmmio) { mmioDescend (hmmio, &mmckinfo, NULL, 0); if ((mmckinfo.ckid == FOURCC_RIFF) && (mmckinfo.fccType == RIFF_PATCH)) { mm2.ckid = RIFF_FM4; if (!mmioDescend (hmmio, &mm2, &mmckinfo, MMIO_FINDCHUNK)) { /* we have found the synthesis chunk */ if (mm2.cksize > (sizeof(patchStruct)*NUMPATCHES)) mm2.cksize = sizeof(patchStruct)*NUMPATCHES; mmioRead (hmmio, (LPSTR) glpPatch, mm2.cksize); } else { D1(("Bad mmioDescend2")); } } else { D1(("Bad mmioDescend1")); } mmioClose (hmmio, 0); } else { TCHAR szAlert[50]; TCHAR szErrorBuffer[255]; LoadString(ghModule, SR_ALERT, szAlert, sizeof(szAlert)); LoadString(ghModule, SR_ALERT_NOPATCH, szErrorBuffer, sizeof(szErrorBuffer)); MessageBox(NULL, szErrorBuffer, szAlert, MB_OK|MB_ICONHAND); D1 (("Bad mmioOpen")); } } #endif // LOAD_PATCHES return 0; /* done */ } /* * Opl3_BoardReset - silence the board and set all voices off. */ VOID Opl3_BoardReset(void) { BYTE i; /* make sure all notes turned off */ Opl3_AllNotesOff(); /* ---- silence the chip -------- */ /* tell the FM chip to use 4-operator mode, and fill in any other random variables */ MidiSendFM (AD_NEW, 0x01); MidiSendFM (AD_MASK, 0x60); MidiSendFM (AD_CONNECTION, 0x00); MidiSendFM (AD_NTS, 0x00); /* turn off the drums, and use high vibrato/modulation */ MidiSendFM (AD_DRUM, 0xc0); /* turn off all the oscillators */ for (i = 0; i < 0x15; i++) { MidiSendFM (AD_LEVEL + i, 0x3f); MidiSendFM (AD_LEVEL2 + i, 0x3f); }; /* turn off all the voices */ for (i = 0; i < 0x08; i++) { MidiSendFM (AD_BLOCK + i, 0x00); MidiSendFM (AD_BLOCK2 + i, 0x00); }; /* clear all of the voice slots to empty */ for (i = 0; i < NUM2VOICES; i++) gVoice[i].dwTime = 0; /* start attenuations at -3 dB, which is 90 MIDI level */ for (i = 0; i < NUMCHANNELS; i++) { gbChanAtten[i] = 4; gbStereoMask[i] = 0xff; }; /* turn off the pitch bend */ for (i = 0; i < NUMCHANNELS; i++) giBend[i] = 0; }