Windows NT 4.0 source code leak
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

1009 lines
23 KiB

/*
*
* Copyright (c) 1992 Microsoft Corporation
*
*/
/*
* midi.c
*
* Midi FM Synthesis routines. converts midi messages into calls to
* FM Synthesis functions - currently supports base adlib (in adlib.c)
* and opl3 synthesisers (in opl3.c).
*
* 15 Dec 92 Geraint Davies - based on a combination of the adlib
* and WSS midi drivers.
*/
#include <windows.h>
#include <mmsystem.h>
#include "mmddk.h"
#include "driver.h"
#include "adlib.h"
#include "opl3.h"
/***********************************************************
global memory */
PORTALLOC gMidiInClient; // input client information structure
DWORD dwRefTime; // time when midi input was opened
DWORD dwMsgTime; // timestamp (in ms) of current msg
DWORD dwMsg = 0L; // short midi message
BYTE bBytesLeft = 0; // number of bytes needed to complete message
BYTE bBytePos = 0; // position in short message buffer
DWORD dwCurData = 0L; // position in long message buffer
BOOL fSysex = FALSE; // are we in sysex mode?
BYTE status = 0;
BYTE fMidiInStarted = 0; /* has the midi been started */
LPMIDIHDR lpMIQueue = NULL;
BYTE gbMidiInUse = 0; /* if MIDI is in use */
static WORD wMidiOutEntered = 0; // reentrancy check
static PORTALLOC gMidiOutClient; // client information
/* transformation of linear velocity value to
logarithmic attenuation */
BYTE gbVelocityAtten[32] = {
40, 36, 32, 28, 23, 21, 19, 17,
15, 14, 13, 12, 11, 10, 9, 8,
7, 6, 5, 5, 4, 4, 3, 3,
2, 2, 1, 1, 1, 0, 0, 0 };
short giBend[NUMCHANNELS]; /* bend for each channel */
BYTE gbPatch[NUMCHANNELS]; /* patch number mapped to */
/* --- interface functions ---------------------------------- */
/*
* the functions in this section call out to adlib.c or opl3.c
* depending on which device we have installed.
*/
/***************************************************************
MidiNoteOn - This turns a note on. (Including drums, with
a patch # of the drum Note + 128)
inputs
BYTE bPatch - MIDI patch number
BYTE bNote - MIDI note number
BYTE bChannel - MIDI channel #
BYTE bVelocity - Velocity #
short iBend - current pitch bend from -32768, to 32767
returns
WORD - note slot #, or 0xffff if its inaudible
*/
VOID NEAR PASCAL MidiNoteOn (BYTE bPatch,
BYTE bNote, BYTE bChannel, BYTE bVelocity,
short iBend)
{
switch(gMidiType) {
case TYPE_OPL3:
Opl3_NoteOn(bPatch, bNote, bChannel, bVelocity, iBend);
break;
case TYPE_ADLIB:
Adlib_NoteOn(bPatch, bNote, bChannel, bVelocity, iBend);
break;
}
return;
}
/**************************************************************
MidiNoteOff - This turns a note off. (Including drums,
with a patch # of the drum note + 128)
inputs
BYTE bPatch - MIDI patch #
BYTE bNote - MIDI note number
BYTE bChannel - MIDI channel #
returns
none
*/
VOID FAR PASCAL MidiNoteOff (BYTE bPatch,
BYTE bNote, BYTE bChannel)
{
switch (gMidiType) {
case TYPE_OPL3:
Opl3_NoteOff(bPatch, bNote, bChannel);
break;
case TYPE_ADLIB:
Adlib_NoteOff(bPatch, bNote, bChannel);
break;
}
}
/**************************************************************
MidiAllNotesOff - switch off all active voices.
inputs - none
returns - none
*/
VOID MidiAllNotesOff(void)
{
switch (gMidiType) {
case TYPE_OPL3:
Opl3_AllNotesOff();
break;
case TYPE_ADLIB:
Adlib_AllNotesOff();
break;
}
}
/**************************************************************
MidiNewVolume - 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 MidiNewVolume (WORD wLeft, WORD wRight)
{
switch (gMidiType) {
case TYPE_OPL3:
Opl3_NewVolume(wLeft, wRight);
break;
case TYPE_ADLIB:
Adlib_NewVolume(wLeft, wRight);
break;
}
}
/***************************************************************
MidiChannelVolume - 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 MidiChannelVolume(BYTE bChannel, WORD wAtten)
{
switch (gMidiType) {
case TYPE_OPL3:
Opl3_ChannelVolume(bChannel, wAtten);
break;
case TYPE_ADLIB:
Adlib_ChannelVolume(bChannel, wAtten);
break;
}
}
/***************************************************************
MidiSetPan - 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 MidiSetPan(BYTE bChannel, BYTE bPan)
{
switch (gMidiType) {
case TYPE_OPL3:
Opl3_SetPan(bChannel, bPan);
break;
case TYPE_ADLIB:
Adlib_SetPan(bChannel, bPan);
break;
}
}
/***************************************************************
MidiPitchBend - This pitch bends a channel.
inputs
BYTE bChannel - channel
short iBend - Values from -32768 to 32767, being
-2 to +2 half steps
returns
none
*/
VOID NEAR PASCAL MidiPitchBend (BYTE bChannel,
short iBend)
{
switch (gMidiType) {
case TYPE_OPL3:
Opl3_PitchBend(bChannel, iBend);
break;
case TYPE_ADLIB:
Adlib_PitchBend(bChannel, iBend);
break;
}
}
/***************************************************************
MidiBoardInit - initialise board and load patches as necessary.
* inputs - none
* returns - 0 for success or the error code
*/
WORD MidiBoardInit(void)
{
/*
* load patch tables and reset board
*/
switch (gMidiType) {
case TYPE_OPL3:
return( Opl3_BoardInit());
break;
case TYPE_ADLIB:
return (Adlib_BoardInit());
break;
}
return(MMSYSERR_ERROR);
}
/*
* MidiBoardReset - silence the board and set all voices off.
*/
VOID MidiBoardReset(void)
{
BYTE i;
/*
* switch off pitch bend (we own this, not the opl3/adlib code)
*/
for (i = 0; i < NUMCHANNELS; i++)
giBend[i] = 0;
/*
* set all voices off, set channel atten to default,
* & silence board.
*/
switch (gMidiType) {
case TYPE_OPL3:
Opl3_BoardReset();
break;
case TYPE_ADLIB:
Adlib_BoardReset();
break;
}
}
/* --- midi interpretation -------------------------------------*/
/***************************************************************
MidiMessage - This handles a MIDI message. This
does not do running status.
inputs
DWORD dwData - up to 4 bytes of MIDI data
depending upon the message.
returns
none
*/
VOID NEAR PASCAL MidiMessage (DWORD dwData)
{
BYTE bChannel, bVelocity, bNote;
WORD wTemp;
// D1("\nMidiMessage");
bChannel = (BYTE) dwData & (BYTE)0x0f;
bVelocity = (BYTE) (dwData >> 16) & (BYTE)0x7f;
bNote = (BYTE) ((WORD) dwData >> 8) & (BYTE)0x7f;
switch ((BYTE)dwData & 0xf0) {
case 0x90:
#ifdef DEBUG
{
char szTemp[4];
szTemp[0] = "0123456789abcdef"[bNote >> 4];
szTemp[1] = "0123456789abcdef"[bNote & 0x0f];
szTemp[2] = ' ';
szTemp[3] = 0;
if ((bChannel == 9) && bVelocity) D1(szTemp);
}
#endif
/* turn key on, or key off if volume == 0 */
if (bVelocity) {
MidiNoteOn (
(BYTE) ((bChannel == DRUMCHANNEL) ?
(BYTE) (bNote + 128) : (BYTE) gbPatch[bChannel]),
bNote, bChannel, bVelocity, (short) giBend[bChannel]);
break;
};
/* else, continue through and turn key off */
case 0x80:
/* turn key off */
MidiNoteOff (
(BYTE) ((bChannel == DRUMCHANNEL) ?
(BYTE) (bNote + 128) : (BYTE) gbPatch[bChannel]),
bNote, bChannel);
break;
case 0xb0:
// D1("\nChangeControl");
/* change control */
switch (bNote) {
case 7:
/* change channel volume */
MidiChannelVolume(
bChannel,
gbVelocityAtten[(bVelocity & 0x7f) >> 2]);
break;
case 8:
case 10:
/* change the pan level */
MidiSetPan(bChannel, bVelocity);
break;
};
break;
case 0xc0:
/* program change */
#if DBG
if (wDebugLevel > 1) {
char szTemp[64];
wsprintfA(szTemp, "0x%x: patch chan %d>%d\n",dwData, bChannel, bNote);
OutputDebugStringA(szTemp);
}
#endif
/* change patch */
gbPatch[bChannel] = bNote;
break;
case 0xe0:
// D1("\nBend");
/* pitch bend */
wTemp = ((WORD) bVelocity << 9) | ((WORD) bNote << 2);
giBend[bChannel] = (short) (WORD) (wTemp + 0x7FFF);
MidiPitchBend (bChannel, giBend[bChannel]);
break;
};
return;
}
/****************************************************************************
* @doc INTERNAL
*
* @api void | midiCallback | This calls DriverCallback for a midi device.
*
* @parm NPPORTALLOC| pPort | Pointer to the PORTALLOC.
*
* @parm WORD | msg | The message to send.
*
* @parm DWORD | dw1 | Message-dependent parameter.
*
* @parm DWORD | dw2 | Message-dependent parameter.
*
* @rdesc There is no return value.
***************************************************************************/
void NEAR PASCAL midiCallback(NPPORTALLOC pPort, WORD msg, DWORD dw1, DWORD dw2)
{
// invoke the callback function, if it exists. dwFlags contains driver-
// specific flags in the LOWORD and generic driver flags in the HIWORD
if (pPort->dwCallback)
DriverCallback(pPort->dwCallback, // client's callback DWORD
HIWORD(pPort->dwFlags) | DCB_NOSWITCH, // callback flags
(HDRVR)pPort->hMidi, // handle to the wave device
msg, // the message
pPort->dwInstance, // client's instance data
dw1, // first DWORD
dw2); // second DWORD
}
/****************************************************************************
* @doc INTERNAL
*
* @api void | midBufferWrite | This function writes a byte into the long
* message buffer. If the buffer is full or a SYSEX_ERROR or
* end-of-sysex byte is received, the buffer is marked as 'done' and
* it's owner is called back.
*
* @parm BYTE | byte | The byte received.
*
* @rdesc There is no return value
***************************************************************************/
static void NEAR PASCAL midBufferWrite(BYTE byte)
{
LPMIDIHDR lpmh;
WORD msg;
// if no buffers, nothing happens
if (lpMIQueue == NULL)
return;
lpmh = lpMIQueue;
if (byte == SYSEX_ERROR) {
D2("sysexerror");
msg = MIM_LONGERROR;
}
else {
D2("bufferwrite");
msg = MIM_LONGDATA;
*((HPSTR)(lpmh->lpData) + dwCurData++) = byte;
}
// if end of sysex, buffer full or error, send them back the buffer
if ((byte == SYSEX_ERROR) || (byte == 0xF7) || (dwCurData >= lpmh->dwBufferLength)) {
D2("bufferdone");
lpMIQueue = lpMIQueue->lpNext;
lpmh->dwBytesRecorded = dwCurData;
dwCurData = 0L;
lpmh->dwFlags |= MHDR_DONE;
lpmh->dwFlags &= ~MHDR_INQUEUE;
midiCallback(&gMidiInClient, msg, (DWORD)lpmh, dwMsgTime);
}
return;
}
/****************************************************************************
This function conforms to the standard MIDI output driver message proc
modMessage, which is documented in mmddk.d.
***************************************************************************/
DWORD FAR PASCAL _loadds modMessage(UINT id,
UINT msg, DWORD dwUser, DWORD dwParam1, DWORD dwParam2)
{
LPMIDIHDR lpHdr;
#ifdef FOGHORN
WORD wTemp1, wTemp2;
#endif
LPSTR lpBuf; /* current spot in the long msg buf */
DWORD dwBytesRead; /* how far are we in the buffer */
DWORD dwMsg = 0; /* short midi message sent to synth */
BYTE bBytePos=0; /* shift current byte by dwBytePos*s */
BYTE bBytesLeft = 0; /* how many dat bytes needed */
BYTE curByte; /* current byte in long buffer */
UINT mRc; /* Return code */
#ifdef WIN16
if (!fEnabled || gfMidiSuspended) {
D1("modMessage called while disabled");
} else {
if (msg == MODM_GETNUMDEVS)
else
return MMSYSERR_NOTENABLED;
}
#endif // WIN16
// this driver only supports one device
if (id != 0) {
D1("invalid midi device id");
return MMSYSERR_BADDEVICEID;
}
switch (msg) {
case MODM_GETNUMDEVS:
D1("MODM_GETNUMDEVS");
#ifdef WIN16
mRc = 1L;
#else
//
// Check if the kernel driver got loaded OK
//
{
HANDLE hDevice;
if (MidiOpenDevice(&hDevice, FALSE) == MMSYSERR_NOERROR) {
CloseHandle(hDevice);
mRc = 1L;
} else {
mRc = 0L;
}
}
#endif // WIN16
break;
case MODM_GETDEVCAPS:
D1("MODM_GETDEVCAPS");
modGetDevCaps((LPBYTE)dwParam1, (WORD)dwParam2);
mRc = 0L;
break;
case MODM_OPEN:
D1("MODM_OPEN");
/* open the midi */
if (MidiOpen())
return MMSYSERR_ALLOCATED;
/* call up the volume control */
#ifdef WIN16
VolSendMsg (FH_MIDI_BEGIN);
#endif // WIN16
// save client information
gMidiOutClient.dwCallback = ((LPMIDIOPENDESC)dwParam1)->dwCallback;
gMidiOutClient.dwInstance = ((LPMIDIOPENDESC)dwParam1)->dwInstance;
gMidiOutClient.hMidi = (HMIDIOUT)((LPMIDIOPENDESC)dwParam1)->hMidi;
gMidiOutClient.dwFlags = dwParam2;
// notify client
midiCallback(&gMidiOutClient, MOM_OPEN, 0L, 0L);
/* were in use */
gbMidiInUse = TRUE;
mRc = 0L;
break;
case MODM_CLOSE:
D1("MODM_CLOSE");
/* shut up the FM synthesizer */
MidiClose();
/* tell volume about this */
#ifdef WIN16
VolSendMsg (FH_MIDI_END);
#endif // WIN16
// notify client
midiCallback(&gMidiOutClient, MOM_CLOSE, 0L, 0L);
/* were not used any more */
gbMidiInUse = FALSE;
mRc = 0L;
break;
case MODM_RESET:
D1("MODM_RESET");
//
// turn off FM synthesis
//
// note that we increment our 're-entered' counter so that a
// background interrupt handler doesn't mess up our resetting
// of the synth by calling midiOut[Short|Long]Msg.. just
// practicing safe midi. NOTE: this should never be necessary
// if a midi app is PROPERLY written!
//
wMidiOutEntered++;
{
if (wMidiOutEntered == 1)
{
MidiReset();
dwParam1 = 0L;
}
else
{
D1("MODM_RESET reentered!");
dwParam1 = MIDIERR_NOTREADY;
}
}
wMidiOutEntered--;
mRc = (dwParam1);
break;
case MODM_DATA: // message is in dwParam1
#ifndef WIN16
MidiCheckVolume(); // See if the volume has changed
#endif // WIN16
// make sure we're not being reentered
wMidiOutEntered++;
if (wMidiOutEntered > 1) {
D1("MODM_DATA reentered!");
wMidiOutEntered--;
return MIDIERR_NOTREADY;
}
/* if have repeated messages */
if (dwParam1 & 0x00000080) /* status byte */
status = LOBYTE(LOWORD(dwParam1));
else
dwParam1 = (dwParam1 << 8) | ((DWORD) status);
/* if not, have an FM synthesis message */
MidiMessage (dwParam1);
wMidiOutEntered--;
mRc = 0L;
break;
case MODM_LONGDATA: // far pointer to header in dwParam1
#ifndef WIN16
MidiCheckVolume(); // See if the volume has changed
#endif // WIN16
// make sure we're not being reentered
wMidiOutEntered++;
if (wMidiOutEntered > 1) {
D1("MODM_LONGDATA reentered!");
wMidiOutEntered--;
return MIDIERR_NOTREADY;
}
// check if it's been prepared
lpHdr = (LPMIDIHDR)dwParam1;
if (!(lpHdr->dwFlags & MHDR_PREPARED)) {
wMidiOutEntered--;
return MIDIERR_UNPREPARED;
}
lpBuf = lpHdr->lpData;
dwBytesRead = 0;
curByte = *lpBuf;
while (TRUE) {
/* if its a system realtime message send it and continue
this does not affect the running status */
if (curByte >= 0xf8)
MidiMessage (0x000000ff & curByte);
else if (curByte >= 0xf0) {
status = 0; /* kill running status */
dwMsg = 0L; /* throw away any incomplete data */
bBytePos = 0; /* start at beginning of message */
switch (curByte) {
case 0xf0: /* sysex - ignore */
case 0xf7:
break;
case 0xf4: /* system common, no data */
case 0xf5:
case 0xf6:
MidiMessage (0x000000ff & curByte);
break;
case 0xf1: /* system common, one data byte */
case 0xf3:
dwMsg |= curByte;
bBytesLeft = 1;
bBytePos = 1;
break;
case 0xf2: /* system common, 2 data bytes */
dwMsg |= curByte;
bBytesLeft = 2;
bBytePos = 1;
break;
};
}
/* else its a channel message */
else if (curByte >= 0x80) {
status = curByte;
dwMsg = 0L;
switch (curByte & 0xf0) {
case 0xc0: /* channel message, one data */
case 0xd0:
dwMsg |= curByte;
bBytesLeft = 1;
bBytePos = 1;
break;
case 0x80: /* two bytes */
case 0x90:
case 0xa0:
case 0xb0:
case 0xe0:
dwMsg |= curByte;
bBytesLeft = 2;
bBytePos = 1;
break;
};
}
/* else if its an expected data byte */
else if (bBytePos != 0) {
dwMsg |= ((DWORD)curByte) << (bBytePos++ * 8);
if (--bBytesLeft == 0) {
MidiMessage (dwMsg);
if (status) {
dwMsg = status;
bBytesLeft = bBytePos - (BYTE)1;
bBytePos = 1;
}
else {
dwMsg = 0L;
bBytePos = 0;
};
};
};
/* read the next byte if there is one */
/* remember we have already read and processed one byte that
* we have not yet counted- so we need to pre-inc, not post-inc
*/
if (++dwBytesRead >= lpHdr->dwBufferLength) break;
curByte = *++lpBuf;
}; /* while TRUE */
/* return buffer to client */
lpHdr->dwFlags |= MHDR_DONE;
midiCallback (&gMidiOutClient, MOM_DONE, dwParam1, 0L);
wMidiOutEntered--;
mRc = 0L;
break;
#ifdef WIN16
case MODM_SETVOLUME:
gwLinVol[VOL_MIDI][VOL_LEFT] = LOWORD(dwParam1);
gwLinVol[VOL_MIDI][VOL_RIGHT] = HIWORD(dwParam1);
VolSetMidiVolume (VolLinearToLog ((WORD)dwParam1),
VolLinearToLog ((WORD) (dwParam1 >> 16) ));
mRc = 0L;
mRc = MidiSetVolume(LOWORD(dwParam1) << 16, HIWORD(dwParam1) << 16);
break;
case MODM_GETVOLUME:
*((DWORD FAR *)dwParam1) =
MAKELONG (
gwLinVol[VOL_MIDI][VOL_LEFT],
gwLinVol[VOL_MIDI][VOL_RIGHT]);
mRc = 0L;
break;
#else
case MODM_SETVOLUME:
mRc = MidiSetVolume(LOWORD(dwParam1) << 16, HIWORD(dwParam1) << 16);
break;
case MODM_GETVOLUME:
mRc = MidiGetVolume((LPDWORD)dwParam1);
break;
#endif // WIN16
#ifdef FOGHORN
case MODM_VUMETER:
/* there is a duplicate in auxil.c which must be
kept up to date */
/* if we're out of range then 0 volume */
if (dwParam1 >= 16) {
*((DWORD FAR *)dwParam2) = 0;
return 0;
};
if (!gbMidiInUse) {
*((DWORD FAR *) dwParam2) = 0;
return MMSYSERR_ALLOCATED;
};
/* find out what the values are */
wTemp1 = (WORD) gbLeftLevel[dwParam1] << 9;
wTemp2 = (WORD) gbRightLevel[dwParam1] << 9;
gbLeftLevel[dwParam1] = gbRightLevel[dwParam1] = 0;
*((DWORD FAR *) dwParam2) = ((DWORD) wTemp2 << 16) | wTemp1;
mRc = 0L;
break;
case MODM_SETPATCH:
/* there is a duplicate in auxil.c which must be
kept up to date */
wTemp1 = (LOWORD(dwParam2));
if (wTemp1 >= 256)
wTemp1 = 0;
MidiSetPatch ((BYTE) wTemp1, (LPSTR) dwParam1, HIWORD(dwParam2));
mRc = 0L;
break;
case MODM_GETPATCH:
/* there is a duplicate in auxil.c which must be
kept up to date */
wTemp1 = (LOWORD(dwParam2));
if (wTemp1 >= 256)
wTemp1 = 0;
MidiGetPatch ((BYTE) wTemp1, (LPSTR) dwParam1, HIWORD(dwParam2));
mRc = 0L;
break;
#endif
default:
return MMSYSERR_NOTSUPPORTED;
}
#ifndef WIN16
MidiFlush();
#endif // WIN16
return mRc;
// should never get here...
return MMSYSERR_NOTSUPPORTED;
}
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");
/****************************************************************
MidiInit - Initializes the FM synthesis chip and internal
variables. This assumes that HwInit() has been called
and that a card location has been found. This loads in
the patch information.
inputs
none
returns
WORD - 0 if successful, else error
*/
WORD FAR PASCAL MidiInit (VOID)
{
// WORD i;
D1 ("\nMidiInit");
// don't reset the patch map - it will be initialised at loadtime to 0
// (because its static data) and we should not change it after that
// since the mci sequencer will not re-send patch change messages.
//
//
// /* reset all channels to patch 0 */
// for (i = 0; i < NUMCHANNELS; i++) {
// gbPatch[i] = 0;
// }
/* initialise the h/w specific patch tables */
return MidiBoardInit();
}
/*****************************************************************
MidiOpen - This should be called when a midi file is opened.
It initializes some variables and locks the patch global
memories.
inputs
none
returns
UINT - 0 if succedes, else error
*/
UINT FAR PASCAL MidiOpen (VOID)
{
MMRESULT mRc;
D1("\nMidiOpen");
//
// For 32-bit we must open our kernel device
//
mRc = MidiOpenDevice(&MidiDeviceHandle, TRUE);
if (mRc != MMSYSERR_NOERROR) {
return mRc;
}
/*
* reset the device (set default channel attenuation etc)
*/
MidiBoardReset();
return 0;
}
/***************************************************************
MidiClose - This kills the playing midi voices and closes the kernel driver
inputs
none
returns
none
*/
VOID FAR PASCAL MidiClose (VOID)
{
D1("\nMidiClose");
/* make sure all notes turned off */
MidiAllNotesOff();
MidiCloseDevice(MidiDeviceHandle);
}
/** void FAR PASCAL MidiReset(void)
*
* DESCRIPTION:
*
*
* ARGUMENTS:
* (void)
*
* RETURN (void FAR PASCAL):
*
*
* NOTES:
*
** cjp */
void FAR PASCAL MidiReset(void)
{
D1("\nMidiReset");
/* make sure all notes turned off */
MidiAllNotesOff();
/* silence the board and reset board-specific variables */
MidiBoardReset();
} /* MidiReset() */
/*****************************************************************************
* @doc INTERNAL
*
* @api void | modGetDevCaps | Get the capabilities of the port.
*
* @parm LPBYTE | lpCaps | Far pointer to a MIDIOUTCAPS structure.
*
* @parm UINT | wSize | Size of the MIDIOUTCAPS structure.
*
* @rdesc There is no return value.
****************************************************************************/
void FAR PASCAL modGetDevCaps(LPBYTE lpCaps, UINT wSize)
{
MIDIOUTCAPS mc;
mc.wMid = MM_MICROSOFT;
mc.wPid = MM_ADLIB;
mc.wTechnology = MOD_FMSYNTH;
mc.wVoices = 128;
mc.wChannelMask = 0xffff; // all channels
mc.vDriverVersion = 0x100;
if (gMidiType == TYPE_OPL3) {
mc.wNotes = 12;
mc.dwSupport = MIDICAPS_VOLUME | MIDICAPS_LRVOLUME;
} else {
mc.wNotes = 11;
mc.dwSupport = MIDICAPS_VOLUME;
}
LoadString(ghModule, SR_STR_DRIVERMIDIOUT, mc.szPname, sizeof(mc.szPname)/sizeof(TCHAR));
AsMemCopy(lpCaps, &mc, min(wSize,sizeof(mc)));
}