/*---------------------------------------------------------------------------- | |
* | |
* 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 */ | |