|
|
/*
* 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 <windows.h>
#include <mmsystem.h>
#include <mmddk.h>
#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; }
|