blob: 08aed727940d603ceed97022391e72c4c5169f38 [file] [log] [blame]
/*----------------------------------------------------------------------------
*
* 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 */