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