C++程序  |  570行  |  15.95 KB

/*----------------------------------------------------------------------------
 *
 * File: 
 * eas_midi.c
 *
 * Contents and purpose:
 * This file implements the MIDI stream parser. It is called by eas_smf.c to parse MIDI messages
 * that are streamed out of the file. It can also parse live MIDI streams.
 *
 * Copyright Sonic Network Inc. 2005

 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 *----------------------------------------------------------------------------
 * Revision Control:
 *   $Revision: 794 $
 *   $Date: 2007-08-01 00:08:48 -0700 (Wed, 01 Aug 2007) $
 *----------------------------------------------------------------------------
*/

#include "eas_data.h"
#include "eas_report.h"
#include "eas_miditypes.h"
#include "eas_midi.h"
#include "eas_vm_protos.h"
#include "eas_parser.h"

#ifdef JET_INTERFACE
#include "jet_data.h"
#endif


/* state enumerations for ProcessSysExMessage */
typedef enum
{
	eSysEx,
	eSysExUnivNonRealTime,
	eSysExUnivNrtTargetID,
	eSysExGMControl,
	eSysExUnivRealTime,
	eSysExUnivRtTargetID,
	eSysExDeviceControl,
	eSysExMasterVolume,
	eSysExMasterVolLSB,
	eSysExSPMIDI,
	eSysExSPMIDIchan,
	eSysExSPMIDIMIP,
	eSysExMfgID1,
	eSysExMfgID2,
	eSysExMfgID3,
	eSysExEnhancer,
	eSysExEnhancerSubID,
	eSysExEnhancerFeedback1,
	eSysExEnhancerFeedback2,
	eSysExEnhancerDrive,
	eSysExEnhancerWet,
	eSysExEOX,
	eSysExIgnore
} E_SYSEX_STATES;

/* local prototypes */
static EAS_RESULT ProcessMIDIMessage (S_EAS_DATA *pEASData, S_SYNTH *pSynth, S_MIDI_STREAM *pMIDIStream, EAS_INT parserMode);
static EAS_RESULT ProcessSysExMessage (S_EAS_DATA *pEASData, S_SYNTH *pSynth, S_MIDI_STREAM *pMIDIStream, EAS_U8 c, EAS_INT parserMode);

/*----------------------------------------------------------------------------
 * EAS_InitMIDIStream()
 *----------------------------------------------------------------------------
 * Purpose: 
 * Initializes the MIDI stream state for parsing.
 *
 * Inputs:
 *		
 * Outputs:
 * returns EAS_RESULT (EAS_SUCCESS is OK)
 *
 * Side Effects:
 *
 *----------------------------------------------------------------------------
*/
void EAS_InitMIDIStream (S_MIDI_STREAM *pMIDIStream)
{
	pMIDIStream->byte3 = EAS_FALSE;
	pMIDIStream->pending = EAS_FALSE;
	pMIDIStream->runningStatus = 0;
	pMIDIStream->status = 0;
}

/*----------------------------------------------------------------------------
 * EAS_ParseMIDIStream()
 *----------------------------------------------------------------------------
 * Purpose: 
 * Parses a MIDI input stream character by character. Characters are pushed (rather than pulled)
 * so the interface works equally well for both file and stream I/O.
 *
 * Inputs:
 * c			- character from MIDI stream
 *		
 * Outputs:
 * returns EAS_RESULT (EAS_SUCCESS is OK)
 *
 * Side Effects:
 *
 *----------------------------------------------------------------------------
*/
EAS_RESULT EAS_ParseMIDIStream (S_EAS_DATA *pEASData, S_SYNTH *pSynth, S_MIDI_STREAM *pMIDIStream, EAS_U8 c, EAS_INT parserMode)
{

	/* check for new status byte */
	if (c & 0x80)
	{
		/* save new running status */
		if (c < 0xf8)
		{
			pMIDIStream->runningStatus = c;
			pMIDIStream->byte3 = EAS_FALSE;

			/* deal with SysEx */
			if ((c == 0xf7) || (c == 0xf0))
			{
				if (parserMode == eParserModeMetaData)
					return EAS_SUCCESS;
				return ProcessSysExMessage(pEASData, pSynth, pMIDIStream, c, parserMode);
			}

			/* inform the file parser that we're in the middle of a message */
			if ((c < 0xf4) || (c > 0xf6))
				pMIDIStream->pending = EAS_TRUE;
		}

		/* real-time message - ignore it */
		return EAS_SUCCESS;
	}

	/* 3rd byte of a 3-byte message? */
	if (pMIDIStream->byte3)
	{
		pMIDIStream->d2 = c;
		pMIDIStream->byte3 = EAS_FALSE;
		pMIDIStream->pending = EAS_FALSE;
		if (parserMode == eParserModeMetaData)
			return EAS_SUCCESS;
		return ProcessMIDIMessage(pEASData, pSynth, pMIDIStream, parserMode);
	}

	/* check for status received */
	if (pMIDIStream->runningStatus)
	{
		
		/* save new status and data byte */
		pMIDIStream->status = pMIDIStream->runningStatus;
		
		/* check for 3-byte messages */
		if (pMIDIStream->status < 0xc0)
		{
			pMIDIStream->d1 = c;
			pMIDIStream->pending = EAS_TRUE;
			pMIDIStream->byte3 = EAS_TRUE;
			return EAS_SUCCESS;
		}
		
		/* check for 2-byte messages */
		if (pMIDIStream->status < 0xe0)
		{
			pMIDIStream->d1 = c;
			pMIDIStream->pending = EAS_FALSE;
			if (parserMode == eParserModeMetaData)
				return EAS_SUCCESS;
			return ProcessMIDIMessage(pEASData, pSynth, pMIDIStream, parserMode);
		}
		
		/* check for more 3-bytes message */
		if (pMIDIStream->status < 0xf0)
		{
			pMIDIStream->d1 = c;
			pMIDIStream->pending = EAS_TRUE;
			pMIDIStream->byte3 = EAS_TRUE;
			return EAS_SUCCESS;
		}

		/* SysEx message? */
		if (pMIDIStream->status == 0xF0)
		{
			if (parserMode == eParserModeMetaData)
				return EAS_SUCCESS;
			return ProcessSysExMessage(pEASData, pSynth, pMIDIStream, c, parserMode);
		}

		/* remaining messages all clear running status */
		pMIDIStream->runningStatus = 0;			

		/* F2 is 3-byte message */
		if (pMIDIStream->status == 0xf2)
		{
			pMIDIStream->byte3 = EAS_TRUE;
			return EAS_SUCCESS;
		}
	}

	/* no status byte received, provide a warning, but we should be able to recover */
	{ /* dpp: EAS_ReportEx(_EAS_SEVERITY_WARNING, "Received MIDI data without a valid status byte: %d\n",c); */ }
	pMIDIStream->pending = EAS_FALSE;
	return EAS_SUCCESS;
}

/*----------------------------------------------------------------------------
 * ProcessMIDIMessage()
 *----------------------------------------------------------------------------
 * Purpose: 
 * This function processes a typical MIDI message. All of the data has been received, just need
 * to take appropriate action.
 *
 * Inputs:
 * 
 *		
 * Outputs:
 * 
 *
 * Side Effects:
 *
 *----------------------------------------------------------------------------
*/
static EAS_RESULT ProcessMIDIMessage (S_EAS_DATA *pEASData, S_SYNTH *pSynth, S_MIDI_STREAM *pMIDIStream, EAS_INT parserMode)
{
	EAS_U8 channel;

	channel = pMIDIStream->status & 0x0f;
	switch (pMIDIStream->status & 0xf0)
	{
	case 0x80:
		{ /* dpp: EAS_ReportEx(_EAS_SEVERITY_DETAIL,"NoteOff: %02x %02x %02x\n",
			pMIDIStream->status, pMIDIStream->d1, pMIDIStream->d2); */ }
		if (parserMode <= eParserModeMute)
			VMStopNote(pEASData->pVoiceMgr, pSynth, channel, pMIDIStream->d1, pMIDIStream->d2); 
		break;
		
	case 0x90:
		if (pMIDIStream->d2)
		{
			{ /* dpp: EAS_ReportEx(_EAS_SEVERITY_DETAIL,"NoteOn: %02x %02x %02x\n",
				pMIDIStream->status, pMIDIStream->d1, pMIDIStream->d2); */ }
			pMIDIStream->flags |= MIDI_FLAG_FIRST_NOTE;
			if (parserMode == eParserModePlay)
				VMStartNote(pEASData->pVoiceMgr, pSynth, channel, pMIDIStream->d1, pMIDIStream->d2); 
		}
		else
		{
			{ /* dpp: EAS_ReportEx(_EAS_SEVERITY_DETAIL,"NoteOff: %02x %02x %02x\n",
				pMIDIStream->status, pMIDIStream->d1, pMIDIStream->d2); */ }
			if (parserMode <= eParserModeMute)
				VMStopNote(pEASData->pVoiceMgr, pSynth, channel, pMIDIStream->d1, pMIDIStream->d2); 
		}
		break;

	case 0xa0:
		{ /* dpp: EAS_ReportEx(_EAS_SEVERITY_DETAIL,"PolyPres: %02x %02x %02x\n",
			pMIDIStream->status, pMIDIStream->d1, pMIDIStream->d2); */ }
		break;
		
	case 0xb0:
		{ /* dpp: EAS_ReportEx(_EAS_SEVERITY_DETAIL,"Control: %02x %02x %02x\n",
			pMIDIStream->status, pMIDIStream->d1, pMIDIStream->d2); */ }
		if (parserMode <= eParserModeMute)
			VMControlChange(pEASData->pVoiceMgr, pSynth, channel, pMIDIStream->d1, pMIDIStream->d2);
#ifdef JET_INTERFACE
		if (pMIDIStream->jetData & MIDI_FLAGS_JET_CB)
		{
			JET_Event(pEASData, pMIDIStream->jetData & (JET_EVENT_SEG_MASK | JET_EVENT_TRACK_MASK),
				channel, pMIDIStream->d1, pMIDIStream->d2);
		}
#endif
		break;
		
	case 0xc0:
		{ /* dpp: EAS_ReportEx(_EAS_SEVERITY_DETAIL,"Program: %02x %02x\n",
			pMIDIStream->status, pMIDIStream->d1); */ }
		if (parserMode <= eParserModeMute)
			VMProgramChange(pEASData->pVoiceMgr, pSynth, channel, pMIDIStream->d1);
		break;
		
	case 0xd0:
		{ /* dpp: EAS_ReportEx(_EAS_SEVERITY_DETAIL,"ChanPres: %02x %02x\n",
			pMIDIStream->status, pMIDIStream->d1); */ }
		if (parserMode <= eParserModeMute)
			VMChannelPressure(pSynth, channel, pMIDIStream->d1);
		break;
		
	case 0xe0:
		{ /* dpp: EAS_ReportEx(_EAS_SEVERITY_DETAIL,"PBend: %02x %02x %02x\n",
			pMIDIStream->status, pMIDIStream->d1, pMIDIStream->d2); */ }
		if (parserMode <= eParserModeMute)
			VMPitchBend(pSynth, channel, pMIDIStream->d1, pMIDIStream->d2);
		break;
		
	default:
		{ /* dpp: EAS_ReportEx(_EAS_SEVERITY_DETAIL,"Unknown: %02x %02x %02x\n",
			pMIDIStream->status, pMIDIStream->d1, pMIDIStream->d2); */ }
	}
	return EAS_SUCCESS;
}

/*----------------------------------------------------------------------------
 * ProcessSysExMessage()
 *----------------------------------------------------------------------------
 * Purpose: 
 * Process a SysEx character byte from the MIDI stream. Since we cannot
 * simply wait for the next character to arrive, we are forced to save
 * state after each character. It would be easier to parse at the file
 * level, but then we lose the nice feature of being able to support
 * these messages in a real-time MIDI stream.
 *
 * Inputs:
 * pEASData			- pointer to synthesizer instance data
 * c				- character to be processed
 * locating			- if true, the sequencer is relocating to a new position
 *		
 * Outputs:
 * 
 *
 * Side Effects:
 *
 * Notes:
 * These are the SysEx messages we can receive:
 * 
 * SysEx messages
 * { f0 7e 7f 09 01 f7 } GM 1 On
 * { f0 7e 7f 09 02 f7 } GM 1/2 Off
 * { f0 7e 7f 09 03 f7 } GM 2 On
 * { f0 7f 7f 04 01 lsb msb } Master Volume
 * { f0 7f 7f 0b 01 ch mip [ch mip ...] f7 } SP-MIDI
 * { f0 00 01 3a 04 01 fdbk1 fdbk2 drive wet dry f7 } Enhancer
 *
 *----------------------------------------------------------------------------
*/
static EAS_RESULT ProcessSysExMessage (S_EAS_DATA *pEASData, S_SYNTH *pSynth, S_MIDI_STREAM *pMIDIStream, EAS_U8 c, EAS_INT parserMode)
{

	/* check for start byte */
	if (c == 0xf0)
	{
		pMIDIStream->sysExState = eSysEx;
	}
	/* check for end byte */
	else if (c == 0xf7)
	{
		/* if this was a MIP message, update the MIP table */
		if ((pMIDIStream->sysExState == eSysExSPMIDIchan) && (parserMode != eParserModeMetaData))
			VMUpdateMIPTable(pEASData->pVoiceMgr, pSynth);
		pMIDIStream->sysExState = eSysExIgnore;
	}

	/* process SysEx message */
	else
	{
		switch (pMIDIStream->sysExState)
		{
		case eSysEx:

			/* first byte, determine message class */
			switch (c)
			{
			case 0x7e:
				pMIDIStream->sysExState = eSysExUnivNonRealTime;
				break;
			case 0x7f:
				pMIDIStream->sysExState = eSysExUnivRealTime;
				break;
			case 0x00:
				pMIDIStream->sysExState = eSysExMfgID1;
				break;
			default:
				pMIDIStream->sysExState = eSysExIgnore;
				break;
			}
			break;

		/* process GM message */
		case eSysExUnivNonRealTime:
			if (c == 0x7f)
				pMIDIStream->sysExState = eSysExUnivNrtTargetID;
			else
				pMIDIStream->sysExState = eSysExIgnore;
			break;
			
		case eSysExUnivNrtTargetID:
			if (c == 0x09)
				pMIDIStream->sysExState = eSysExGMControl;
			else
				pMIDIStream->sysExState = eSysExIgnore;
			break;
				
		case eSysExGMControl:
			if ((c == 1) || (c == 3))
			{
				/* GM 1 or GM2 On, reset synth */
				if (parserMode != eParserModeMetaData)
				{
					pMIDIStream->flags |= MIDI_FLAG_GM_ON;
					VMReset(pEASData->pVoiceMgr, pSynth, EAS_FALSE);
					VMInitMIPTable(pSynth);
				}
				pMIDIStream->sysExState = eSysExEOX;
			}
			else
				pMIDIStream->sysExState = eSysExIgnore;
			break;

		/* Process Master Volume and SP-MIDI */
		case eSysExUnivRealTime:
			if (c == 0x7f)
				pMIDIStream->sysExState = eSysExUnivRtTargetID;
			else
				pMIDIStream->sysExState = eSysExIgnore;
			break;

		case eSysExUnivRtTargetID:
			if (c == 0x04)
				pMIDIStream->sysExState = eSysExDeviceControl;
			else if (c == 0x0b)
				pMIDIStream->sysExState = eSysExSPMIDI;
			else
				pMIDIStream->sysExState = eSysExIgnore;
			break;
			
		/* process master volume */
		case eSysExDeviceControl:
			if (c == 0x01)
				pMIDIStream->sysExState = eSysExMasterVolume;
			else
				pMIDIStream->sysExState = eSysExIgnore;
			break;
			
		case eSysExMasterVolume:
			/* save LSB */
			pMIDIStream->d1 = c;
			pMIDIStream->sysExState = eSysExMasterVolLSB;
			break;
			
		case eSysExMasterVolLSB:
			if (parserMode != eParserModeMetaData)
			{
				EAS_I32 gain = ((EAS_I32) c << 8) | ((EAS_I32) pMIDIStream->d1 << 1);
				gain = (gain * gain) >> 15;
				VMSetVolume(pSynth, (EAS_U16) gain);
			}
			pMIDIStream->sysExState = eSysExEOX;
			break;

		/* process SP-MIDI MIP message */
		case eSysExSPMIDI:
			if (c == 0x01)
			{
				/* assume all channels are muted */
				if (parserMode != eParserModeMetaData)
					VMInitMIPTable(pSynth);
				pMIDIStream->d1 = 0;
				pMIDIStream->sysExState = eSysExSPMIDIchan;
			}
			else
				pMIDIStream->sysExState = eSysExIgnore;
			break;

		case eSysExSPMIDIchan:
			if (c < NUM_SYNTH_CHANNELS)
			{
				pMIDIStream->d2 = c;
				pMIDIStream->sysExState = eSysExSPMIDIMIP;
			}
			else
			{
				/* bad MIP message - unmute channels */
				if (parserMode != eParserModeMetaData)
					VMInitMIPTable(pSynth);
				pMIDIStream->sysExState = eSysExIgnore;
			}
			break;

		case eSysExSPMIDIMIP:
			/* process MIP entry here */
			if (parserMode != eParserModeMetaData)
				VMSetMIPEntry(pEASData->pVoiceMgr, pSynth, pMIDIStream->d2, pMIDIStream->d1, c);
			pMIDIStream->sysExState = eSysExSPMIDIchan;

			/* if 16 channels received, update MIP table */
			if (++pMIDIStream->d1 == NUM_SYNTH_CHANNELS)
			{
				if (parserMode != eParserModeMetaData)
					VMUpdateMIPTable(pEASData->pVoiceMgr, pSynth);
				pMIDIStream->sysExState = eSysExEOX;
			}
			break;

		/* process Enhancer */
		case eSysExMfgID1:
			if (c == 0x01)
				pMIDIStream->sysExState = eSysExMfgID1;
			else
				pMIDIStream->sysExState = eSysExIgnore;
			break;

		case eSysExMfgID2:
			if (c == 0x3a)
				pMIDIStream->sysExState = eSysExMfgID1;
			else
				pMIDIStream->sysExState = eSysExIgnore;
			break;
			
		case eSysExMfgID3:
			if (c == 0x04)
				pMIDIStream->sysExState = eSysExEnhancer;
			else
				pMIDIStream->sysExState = eSysExIgnore;
			break;

		case eSysExEnhancer:
			if (c == 0x01)
				pMIDIStream->sysExState = eSysExEnhancerSubID;
			else
				pMIDIStream->sysExState = eSysExIgnore;
			break;
			
		case eSysExEnhancerSubID:
			pMIDIStream->sysExState = eSysExEnhancerFeedback1;
			break;
			
		case eSysExEnhancerFeedback1:
			pMIDIStream->sysExState = eSysExEnhancerFeedback2;
			break;
			
		case eSysExEnhancerFeedback2:
			pMIDIStream->sysExState = eSysExEnhancerDrive;
			break;
			
		case eSysExEnhancerDrive:
			pMIDIStream->sysExState = eSysExEnhancerWet;
			break;
			
		case eSysExEnhancerWet:
			pMIDIStream->sysExState = eSysExEOX;
			break;
			
		case eSysExEOX:
			{ /* dpp: EAS_ReportEx(_EAS_SEVERITY_DETAIL, "Expected F7, received %02x\n", c); */ }
			pMIDIStream->sysExState = eSysExIgnore;
			break;
			
		case eSysExIgnore:
			break;
			
		default:
			pMIDIStream->sysExState = eSysExIgnore;
			break;
		}
	}

	if (pMIDIStream->sysExState == eSysExIgnore)
		{ /* dpp: EAS_ReportEx(_EAS_SEVERITY_DETAIL, "Ignoring SysEx byte %02x\n", c); */ }
	return EAS_SUCCESS;	
} /* end ProcessSysExMessage */