You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
570 lines
18 KiB
570 lines
18 KiB
/*----------------------------------------------------------------------------
|
|
*
|
|
* 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 */
|
|
|