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