mirror of https://github.com/tongzx/nt5src
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.
911 lines
26 KiB
911 lines
26 KiB
/*
|
|
*
|
|
* Copyright (c) 1992-1995 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 };
|
|
|
|
BYTE BCODE gbPercMap[53][2] =
|
|
{
|
|
{ 0, 35 },
|
|
{ 0, 35 },
|
|
{ 2, 52 },
|
|
{ 3, 48 },
|
|
{ 4, 58 },
|
|
{ 5, 60 },
|
|
{ 6, 47 },
|
|
{ 7, 43 },
|
|
{ 6, 49 },
|
|
{ 9, 43 },
|
|
{ 6, 51 },
|
|
{ 11, 43 },
|
|
{ 6, 54 },
|
|
{ 6, 57 },
|
|
{ 14, 72 },
|
|
{ 6, 60 },
|
|
{ 16, 76 },
|
|
{ 17, 84 },
|
|
{ 18, 36 },
|
|
{ 19, 76 },
|
|
{ 20, 84 },
|
|
{ 21, 83 },
|
|
{ 22, 84 },
|
|
{ 23, 24 },
|
|
{ 16, 77 },
|
|
{ 25, 60 },
|
|
{ 26, 65 },
|
|
{ 27, 59 },
|
|
{ 28, 51 },
|
|
{ 29, 45 },
|
|
{ 30, 71 },
|
|
{ 31, 60 },
|
|
{ 32, 58 },
|
|
{ 33, 53 },
|
|
{ 34, 64 },
|
|
{ 35, 71 },
|
|
{ 36, 61 },
|
|
{ 37, 61 },
|
|
{ 38, 48 },
|
|
{ 39, 48 },
|
|
{ 40, 69 },
|
|
{ 41, 68 },
|
|
{ 42, 63 },
|
|
{ 43, 74 },
|
|
{ 44, 60 },
|
|
{ 45, 80 },
|
|
{ 46, 64 },
|
|
{ 47, 69 },
|
|
{ 48, 73 },
|
|
{ 49, 75 },
|
|
{ 50, 68 },
|
|
{ 51, 48 },
|
|
{ 52, 53 }
|
|
} ;
|
|
|
|
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.
|
|
*/
|
|
|
|
|
|
/**************************************************************
|
|
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) {
|
|
switch(gMidiType) {
|
|
case TYPE_OPL3:
|
|
if (bChannel == DRUMCHANNEL)
|
|
{
|
|
if (bNote >= 35 && bNote <= 87)
|
|
{
|
|
#ifdef DEBUG
|
|
char szDebug[ 80 ] ;
|
|
|
|
wsprintf( szDebug, "bChannel = %d, bNote = %d",
|
|
bChannel, bNote ) ;
|
|
D1( szDebug ) ;
|
|
#endif
|
|
|
|
Opl3_NoteOn( (BYTE) (gbPercMap[ bNote - 35 ][ 0 ] + 0x80),
|
|
gbPercMap[ bNote - 35 ][ 1 ],
|
|
bChannel, bVelocity,
|
|
(short) giBend[ bChannel ] ) ;
|
|
}
|
|
}
|
|
else
|
|
Opl3_NoteOn( (BYTE) gbPatch[ bChannel ], bNote, bChannel,
|
|
bVelocity, (short) giBend[ bChannel ] ) ;
|
|
break;
|
|
|
|
case TYPE_ADLIB:
|
|
Adlib_NoteOn (
|
|
(BYTE) ((bChannel == DRUMCHANNEL) ?
|
|
(BYTE) (bNote + 128) : (BYTE) gbPatch[bChannel]),
|
|
bNote, bChannel, bVelocity, (short) giBend[bChannel]);
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
|
|
/* else, continue through and turn key off */
|
|
case 0x80:
|
|
/* turn key off */
|
|
switch (gMidiType) {
|
|
case TYPE_OPL3:
|
|
if (bChannel == DRUMCHANNEL)
|
|
{
|
|
if (bNote >= 35 && bNote <= 87)
|
|
Opl3_NoteOff( (BYTE) (gbPercMap[ bNote - 35 ][ 0 ] + 0x80),
|
|
gbPercMap[ bNote - 35 ][ 1 ], bChannel ) ;
|
|
}
|
|
else
|
|
Opl3_NoteOff( (BYTE) gbPatch[bChannel], bNote, bChannel ) ;
|
|
break;
|
|
|
|
case TYPE_ADLIB:
|
|
Adlib_NoteOff (
|
|
(BYTE) ((bChannel == DRUMCHANNEL) ?
|
|
(BYTE) (bNote + 128) : (BYTE) gbPatch[bChannel]),
|
|
bNote, bChannel);
|
|
break;
|
|
}
|
|
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:
|
|
if (bChannel != DRUMCHANNEL)
|
|
{
|
|
int i ;
|
|
|
|
// Turn off all active notes for this channel...
|
|
|
|
if (gMidiType == TYPE_OPL3) {
|
|
Opl3_ChannelNotesOff(bChannel);
|
|
}
|
|
|
|
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 | midiSynthCallback | 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 midiSynthCallback(NPPORTALLOC pPort, WORD msg, DWORD_PTR dw1, DWORD_PTR 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;
|
|
midiSynthCallback(&gMidiInClient, msg, (DWORD_PTR)lpmh, dwMsgTime);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
|
|
/****************************************************************************
|
|
|
|
This function conforms to the standard MIDI output driver message proc
|
|
modMessage, which is documented in mmddk.d.
|
|
|
|
***************************************************************************/
|
|
DWORD APIENTRY modSynthMessage(UINT id,
|
|
UINT msg, DWORD_PTR dwUser, DWORD_PTR dwParam1, DWORD_PTR dwParam2)
|
|
{
|
|
LPMIDIHDR lpHdr;
|
|
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 */
|
|
|
|
|
|
// 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"));
|
|
//
|
|
// Check if the kernel driver got loaded OK
|
|
//
|
|
mRc = sndGetNumDevs(SYNTH_DEVICE);
|
|
break;
|
|
|
|
case MODM_GETDEVCAPS:
|
|
D1(("MODM_GETDEVCAPS"));
|
|
mRc = midiGetDevCaps(0, SYNTH_DEVICE, (LPBYTE)dwParam1, (WORD)dwParam2);
|
|
break;
|
|
|
|
case MODM_OPEN:
|
|
D1(("MODM_OPEN"));
|
|
|
|
/* open the midi */
|
|
if (MidiOpen())
|
|
return MMSYSERR_ALLOCATED;
|
|
|
|
// save client information
|
|
gMidiOutClient.dwCallback = ((LPMIDIOPENDESC)dwParam1)->dwCallback;
|
|
gMidiOutClient.dwInstance = ((LPMIDIOPENDESC)dwParam1)->dwInstance;
|
|
gMidiOutClient.hMidi = (HMIDIOUT)((LPMIDIOPENDESC)dwParam1)->hMidi;
|
|
gMidiOutClient.dwFlags = (DWORD)dwParam2;
|
|
|
|
// notify client
|
|
midiSynthCallback(&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();
|
|
|
|
// notify client
|
|
midiSynthCallback(&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 = (DWORD)dwParam1;
|
|
break;
|
|
|
|
case MODM_DATA: // message is in dwParam1
|
|
MidiCheckVolume(); // See if the volume has changed
|
|
|
|
// 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 ((DWORD)dwParam1);
|
|
|
|
wMidiOutEntered--;
|
|
mRc = 0L;
|
|
break;
|
|
|
|
case MODM_LONGDATA: // far pointer to header in dwParam1
|
|
|
|
MidiCheckVolume(); // See if the volume has changed
|
|
|
|
// 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;
|
|
midiSynthCallback (&gMidiOutClient, MOM_DONE, dwParam1, 0L);
|
|
|
|
wMidiOutEntered--;
|
|
mRc = 0L;
|
|
break;
|
|
|
|
case MODM_SETVOLUME:
|
|
mRc = MidiSetVolume(LOWORD(dwParam1) << 16, HIWORD(dwParam1) << 16);
|
|
break;
|
|
|
|
case MODM_GETVOLUME:
|
|
mRc = MidiGetVolume((LPDWORD)dwParam1);
|
|
break;
|
|
|
|
default:
|
|
return MMSYSERR_NOTSUPPORTED;
|
|
}
|
|
MidiFlush();
|
|
|
|
return mRc;
|
|
|
|
|
|
// should never get here...
|
|
return MMSYSERR_NOTSUPPORTED;
|
|
}
|
|
|
|
/****************************************************************
|
|
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(("MidiOpen"));
|
|
|
|
|
|
//
|
|
// 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(("MidiClose"));
|
|
|
|
if (MidiDeviceHandle == NULL) {
|
|
return;
|
|
}
|
|
|
|
/* make sure all notes turned off */
|
|
MidiAllNotesOff();
|
|
|
|
MidiCloseDevice(MidiDeviceHandle);
|
|
|
|
MidiDeviceHandle = NULL;
|
|
}
|
|
|
|
/** void FAR PASCAL MidiReset(void)
|
|
*
|
|
* DESCRIPTION:
|
|
*
|
|
*
|
|
* ARGUMENTS:
|
|
* (void)
|
|
*
|
|
* RETURN (void FAR PASCAL):
|
|
*
|
|
*
|
|
* NOTES:
|
|
*
|
|
** cjp */
|
|
|
|
void FAR PASCAL MidiReset(void)
|
|
{
|
|
|
|
D1(("MidiReset"));
|
|
|
|
/* make sure all notes turned off */
|
|
MidiAllNotesOff();
|
|
|
|
/* silence the board and reset board-specific variables */
|
|
MidiBoardReset();
|
|
|
|
|
|
} /* MidiReset() */
|
|
|
|
|
|
|