Home | History | Annotate | Download | only in lib_src
      1 /*----------------------------------------------------------------------------
      2  *
      3  * File:
      4  * eas_midi.c
      5  *
      6  * Contents and purpose:
      7  * This file implements the MIDI stream parser. It is called by eas_smf.c to parse MIDI messages
      8  * that are streamed out of the file. It can also parse live MIDI streams.
      9  *
     10  * Copyright Sonic Network Inc. 2005
     11 
     12  * Licensed under the Apache License, Version 2.0 (the "License");
     13  * you may not use this file except in compliance with the License.
     14  * You may obtain a copy of the License at
     15  *
     16  *      http://www.apache.org/licenses/LICENSE-2.0
     17  *
     18  * Unless required by applicable law or agreed to in writing, software
     19  * distributed under the License is distributed on an "AS IS" BASIS,
     20  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     21  * See the License for the specific language governing permissions and
     22  * limitations under the License.
     23  *
     24  *----------------------------------------------------------------------------
     25  * Revision Control:
     26  *   $Revision: 794 $
     27  *   $Date: 2007-08-01 00:08:48 -0700 (Wed, 01 Aug 2007) $
     28  *----------------------------------------------------------------------------
     29 */
     30 
     31 #include "eas_data.h"
     32 #include "eas_report.h"
     33 #include "eas_miditypes.h"
     34 #include "eas_midi.h"
     35 #include "eas_vm_protos.h"
     36 #include "eas_parser.h"
     37 
     38 #ifdef JET_INTERFACE
     39 #include "jet_data.h"
     40 #endif
     41 
     42 
     43 /* state enumerations for ProcessSysExMessage */
     44 typedef enum
     45 {
     46     eSysEx,
     47     eSysExUnivNonRealTime,
     48     eSysExUnivNrtTargetID,
     49     eSysExGMControl,
     50     eSysExUnivRealTime,
     51     eSysExUnivRtTargetID,
     52     eSysExDeviceControl,
     53     eSysExMasterVolume,
     54     eSysExMasterVolLSB,
     55     eSysExSPMIDI,
     56     eSysExSPMIDIchan,
     57     eSysExSPMIDIMIP,
     58     eSysExMfgID1,
     59     eSysExMfgID2,
     60     eSysExMfgID3,
     61     eSysExEnhancer,
     62     eSysExEnhancerSubID,
     63     eSysExEnhancerFeedback1,
     64     eSysExEnhancerFeedback2,
     65     eSysExEnhancerDrive,
     66     eSysExEnhancerWet,
     67     eSysExEOX,
     68     eSysExIgnore
     69 } E_SYSEX_STATES;
     70 
     71 /* local prototypes */
     72 static EAS_RESULT ProcessMIDIMessage (S_EAS_DATA *pEASData, S_SYNTH *pSynth, S_MIDI_STREAM *pMIDIStream, EAS_INT parserMode);
     73 static EAS_RESULT ProcessSysExMessage (S_EAS_DATA *pEASData, S_SYNTH *pSynth, S_MIDI_STREAM *pMIDIStream, EAS_U8 c, EAS_INT parserMode);
     74 
     75 /*----------------------------------------------------------------------------
     76  * EAS_InitMIDIStream()
     77  *----------------------------------------------------------------------------
     78  * Purpose:
     79  * Initializes the MIDI stream state for parsing.
     80  *
     81  * Inputs:
     82  *
     83  * Outputs:
     84  * returns EAS_RESULT (EAS_SUCCESS is OK)
     85  *
     86  * Side Effects:
     87  *
     88  *----------------------------------------------------------------------------
     89 */
     90 void EAS_InitMIDIStream (S_MIDI_STREAM *pMIDIStream)
     91 {
     92     pMIDIStream->byte3 = EAS_FALSE;
     93     pMIDIStream->pending = EAS_FALSE;
     94     pMIDIStream->runningStatus = 0;
     95     pMIDIStream->status = 0;
     96 }
     97 
     98 /*----------------------------------------------------------------------------
     99  * EAS_ParseMIDIStream()
    100  *----------------------------------------------------------------------------
    101  * Purpose:
    102  * Parses a MIDI input stream character by character. Characters are pushed (rather than pulled)
    103  * so the interface works equally well for both file and stream I/O.
    104  *
    105  * Inputs:
    106  * c            - character from MIDI stream
    107  *
    108  * Outputs:
    109  * returns EAS_RESULT (EAS_SUCCESS is OK)
    110  *
    111  * Side Effects:
    112  *
    113  *----------------------------------------------------------------------------
    114 */
    115 EAS_RESULT EAS_ParseMIDIStream (S_EAS_DATA *pEASData, S_SYNTH *pSynth, S_MIDI_STREAM *pMIDIStream, EAS_U8 c, EAS_INT parserMode)
    116 {
    117 
    118     /* check for new status byte */
    119     if (c & 0x80)
    120     {
    121         /* save new running status */
    122         if (c < 0xf8)
    123         {
    124             pMIDIStream->runningStatus = c;
    125             pMIDIStream->byte3 = EAS_FALSE;
    126 
    127             /* deal with SysEx */
    128             if ((c == 0xf7) || (c == 0xf0))
    129             {
    130                 if (parserMode == eParserModeMetaData)
    131                     return EAS_SUCCESS;
    132                 return ProcessSysExMessage(pEASData, pSynth, pMIDIStream, c, parserMode);
    133             }
    134 
    135             /* inform the file parser that we're in the middle of a message */
    136             if ((c < 0xf4) || (c > 0xf6))
    137                 pMIDIStream->pending = EAS_TRUE;
    138         }
    139 
    140         /* real-time message - ignore it */
    141         return EAS_SUCCESS;
    142     }
    143 
    144     /* 3rd byte of a 3-byte message? */
    145     if (pMIDIStream->byte3)
    146     {
    147         pMIDIStream->d2 = c;
    148         pMIDIStream->byte3 = EAS_FALSE;
    149         pMIDIStream->pending = EAS_FALSE;
    150         if (parserMode == eParserModeMetaData)
    151             return EAS_SUCCESS;
    152         return ProcessMIDIMessage(pEASData, pSynth, pMIDIStream, parserMode);
    153     }
    154 
    155     /* check for status received */
    156     if (pMIDIStream->runningStatus)
    157     {
    158 
    159         /* save new status and data byte */
    160         pMIDIStream->status = pMIDIStream->runningStatus;
    161 
    162         /* check for 3-byte messages */
    163         if (pMIDIStream->status < 0xc0)
    164         {
    165             pMIDIStream->d1 = c;
    166             pMIDIStream->pending = EAS_TRUE;
    167             pMIDIStream->byte3 = EAS_TRUE;
    168             return EAS_SUCCESS;
    169         }
    170 
    171         /* check for 2-byte messages */
    172         if (pMIDIStream->status < 0xe0)
    173         {
    174             pMIDIStream->d1 = c;
    175             pMIDIStream->pending = EAS_FALSE;
    176             if (parserMode == eParserModeMetaData)
    177                 return EAS_SUCCESS;
    178             return ProcessMIDIMessage(pEASData, pSynth, pMIDIStream, parserMode);
    179         }
    180 
    181         /* check for more 3-bytes message */
    182         if (pMIDIStream->status < 0xf0)
    183         {
    184             pMIDIStream->d1 = c;
    185             pMIDIStream->pending = EAS_TRUE;
    186             pMIDIStream->byte3 = EAS_TRUE;
    187             return EAS_SUCCESS;
    188         }
    189 
    190         /* SysEx message? */
    191         if (pMIDIStream->status == 0xF0)
    192         {
    193             if (parserMode == eParserModeMetaData)
    194                 return EAS_SUCCESS;
    195             return ProcessSysExMessage(pEASData, pSynth, pMIDIStream, c, parserMode);
    196         }
    197 
    198         /* remaining messages all clear running status */
    199         pMIDIStream->runningStatus = 0;
    200 
    201         /* F2 is 3-byte message */
    202         if (pMIDIStream->status == 0xf2)
    203         {
    204             pMIDIStream->byte3 = EAS_TRUE;
    205             return EAS_SUCCESS;
    206         }
    207     }
    208 
    209     /* no status byte received, provide a warning, but we should be able to recover */
    210     { /* dpp: EAS_ReportEx(_EAS_SEVERITY_WARNING, "Received MIDI data without a valid status byte: %d\n",c); */ }
    211     pMIDIStream->pending = EAS_FALSE;
    212     return EAS_SUCCESS;
    213 }
    214 
    215 /*----------------------------------------------------------------------------
    216  * ProcessMIDIMessage()
    217  *----------------------------------------------------------------------------
    218  * Purpose:
    219  * This function processes a typical MIDI message. All of the data has been received, just need
    220  * to take appropriate action.
    221  *
    222  * Inputs:
    223  *
    224  *
    225  * Outputs:
    226  *
    227  *
    228  * Side Effects:
    229  *
    230  *----------------------------------------------------------------------------
    231 */
    232 static EAS_RESULT ProcessMIDIMessage (S_EAS_DATA *pEASData, S_SYNTH *pSynth, S_MIDI_STREAM *pMIDIStream, EAS_INT parserMode)
    233 {
    234     EAS_U8 channel;
    235 
    236     channel = pMIDIStream->status & 0x0f;
    237     switch (pMIDIStream->status & 0xf0)
    238     {
    239     case 0x80:
    240         { /* dpp: EAS_ReportEx(_EAS_SEVERITY_DETAIL,"NoteOff: %02x %02x %02x\n",
    241             pMIDIStream->status, pMIDIStream->d1, pMIDIStream->d2); */ }
    242         if (parserMode <= eParserModeMute)
    243             VMStopNote(pEASData->pVoiceMgr, pSynth, channel, pMIDIStream->d1, pMIDIStream->d2);
    244         break;
    245 
    246     case 0x90:
    247         if (pMIDIStream->d2)
    248         {
    249             { /* dpp: EAS_ReportEx(_EAS_SEVERITY_DETAIL,"NoteOn: %02x %02x %02x\n",
    250                 pMIDIStream->status, pMIDIStream->d1, pMIDIStream->d2); */ }
    251             pMIDIStream->flags |= MIDI_FLAG_FIRST_NOTE;
    252             if (parserMode == eParserModePlay)
    253                 VMStartNote(pEASData->pVoiceMgr, pSynth, channel, pMIDIStream->d1, pMIDIStream->d2);
    254         }
    255         else
    256         {
    257             { /* dpp: EAS_ReportEx(_EAS_SEVERITY_DETAIL,"NoteOff: %02x %02x %02x\n",
    258                 pMIDIStream->status, pMIDIStream->d1, pMIDIStream->d2); */ }
    259             if (parserMode <= eParserModeMute)
    260                 VMStopNote(pEASData->pVoiceMgr, pSynth, channel, pMIDIStream->d1, pMIDIStream->d2);
    261         }
    262         break;
    263 
    264     case 0xa0:
    265         { /* dpp: EAS_ReportEx(_EAS_SEVERITY_DETAIL,"PolyPres: %02x %02x %02x\n",
    266             pMIDIStream->status, pMIDIStream->d1, pMIDIStream->d2); */ }
    267         break;
    268 
    269     case 0xb0:
    270         { /* dpp: EAS_ReportEx(_EAS_SEVERITY_DETAIL,"Control: %02x %02x %02x\n",
    271             pMIDIStream->status, pMIDIStream->d1, pMIDIStream->d2); */ }
    272         if (parserMode <= eParserModeMute)
    273             VMControlChange(pEASData->pVoiceMgr, pSynth, channel, pMIDIStream->d1, pMIDIStream->d2);
    274 #ifdef JET_INTERFACE
    275         if (pMIDIStream->jetData & MIDI_FLAGS_JET_CB)
    276         {
    277             JET_Event(pEASData, pMIDIStream->jetData & (JET_EVENT_SEG_MASK | JET_EVENT_TRACK_MASK),
    278                 channel, pMIDIStream->d1, pMIDIStream->d2);
    279         }
    280 #endif
    281         break;
    282 
    283     case 0xc0:
    284         { /* dpp: EAS_ReportEx(_EAS_SEVERITY_DETAIL,"Program: %02x %02x\n",
    285             pMIDIStream->status, pMIDIStream->d1); */ }
    286         if (parserMode <= eParserModeMute)
    287             VMProgramChange(pEASData->pVoiceMgr, pSynth, channel, pMIDIStream->d1);
    288         break;
    289 
    290     case 0xd0:
    291         { /* dpp: EAS_ReportEx(_EAS_SEVERITY_DETAIL,"ChanPres: %02x %02x\n",
    292             pMIDIStream->status, pMIDIStream->d1); */ }
    293         if (parserMode <= eParserModeMute)
    294             VMChannelPressure(pSynth, channel, pMIDIStream->d1);
    295         break;
    296 
    297     case 0xe0:
    298         { /* dpp: EAS_ReportEx(_EAS_SEVERITY_DETAIL,"PBend: %02x %02x %02x\n",
    299             pMIDIStream->status, pMIDIStream->d1, pMIDIStream->d2); */ }
    300         if (parserMode <= eParserModeMute)
    301             VMPitchBend(pSynth, channel, pMIDIStream->d1, pMIDIStream->d2);
    302         break;
    303 
    304     default:
    305         { /* dpp: EAS_ReportEx(_EAS_SEVERITY_DETAIL,"Unknown: %02x %02x %02x\n",
    306             pMIDIStream->status, pMIDIStream->d1, pMIDIStream->d2); */ }
    307     }
    308     return EAS_SUCCESS;
    309 }
    310 
    311 /*----------------------------------------------------------------------------
    312  * ProcessSysExMessage()
    313  *----------------------------------------------------------------------------
    314  * Purpose:
    315  * Process a SysEx character byte from the MIDI stream. Since we cannot
    316  * simply wait for the next character to arrive, we are forced to save
    317  * state after each character. It would be easier to parse at the file
    318  * level, but then we lose the nice feature of being able to support
    319  * these messages in a real-time MIDI stream.
    320  *
    321  * Inputs:
    322  * pEASData         - pointer to synthesizer instance data
    323  * c                - character to be processed
    324  * locating         - if true, the sequencer is relocating to a new position
    325  *
    326  * Outputs:
    327  *
    328  *
    329  * Side Effects:
    330  *
    331  * Notes:
    332  * These are the SysEx messages we can receive:
    333  *
    334  * SysEx messages
    335  * { f0 7e 7f 09 01 f7 } GM 1 On
    336  * { f0 7e 7f 09 02 f7 } GM 1/2 Off
    337  * { f0 7e 7f 09 03 f7 } GM 2 On
    338  * { f0 7f 7f 04 01 lsb msb } Master Volume
    339  * { f0 7f 7f 0b 01 ch mip [ch mip ...] f7 } SP-MIDI
    340  * { f0 00 01 3a 04 01 fdbk1 fdbk2 drive wet dry f7 } Enhancer
    341  *
    342  *----------------------------------------------------------------------------
    343 */
    344 static EAS_RESULT ProcessSysExMessage (S_EAS_DATA *pEASData, S_SYNTH *pSynth, S_MIDI_STREAM *pMIDIStream, EAS_U8 c, EAS_INT parserMode)
    345 {
    346 
    347     /* check for start byte */
    348     if (c == 0xf0)
    349     {
    350         pMIDIStream->sysExState = eSysEx;
    351     }
    352     /* check for end byte */
    353     else if (c == 0xf7)
    354     {
    355         /* if this was a MIP message, update the MIP table */
    356         if ((pMIDIStream->sysExState == eSysExSPMIDIchan) && (parserMode != eParserModeMetaData))
    357             VMUpdateMIPTable(pEASData->pVoiceMgr, pSynth);
    358         pMIDIStream->sysExState = eSysExIgnore;
    359     }
    360 
    361     /* process SysEx message */
    362     else
    363     {
    364         switch (pMIDIStream->sysExState)
    365         {
    366         case eSysEx:
    367 
    368             /* first byte, determine message class */
    369             switch (c)
    370             {
    371             case 0x7e:
    372                 pMIDIStream->sysExState = eSysExUnivNonRealTime;
    373                 break;
    374             case 0x7f:
    375                 pMIDIStream->sysExState = eSysExUnivRealTime;
    376                 break;
    377             case 0x00:
    378                 pMIDIStream->sysExState = eSysExMfgID1;
    379                 break;
    380             default:
    381                 pMIDIStream->sysExState = eSysExIgnore;
    382                 break;
    383             }
    384             break;
    385 
    386         /* process GM message */
    387         case eSysExUnivNonRealTime:
    388             if (c == 0x7f)
    389                 pMIDIStream->sysExState = eSysExUnivNrtTargetID;
    390             else
    391                 pMIDIStream->sysExState = eSysExIgnore;
    392             break;
    393 
    394         case eSysExUnivNrtTargetID:
    395             if (c == 0x09)
    396                 pMIDIStream->sysExState = eSysExGMControl;
    397             else
    398                 pMIDIStream->sysExState = eSysExIgnore;
    399             break;
    400 
    401         case eSysExGMControl:
    402             if ((c == 1) || (c == 3))
    403             {
    404                 /* GM 1 or GM2 On, reset synth */
    405                 if (parserMode != eParserModeMetaData)
    406                 {
    407                     pMIDIStream->flags |= MIDI_FLAG_GM_ON;
    408                     VMReset(pEASData->pVoiceMgr, pSynth, EAS_FALSE);
    409                     VMInitMIPTable(pSynth);
    410                 }
    411                 pMIDIStream->sysExState = eSysExEOX;
    412             }
    413             else
    414                 pMIDIStream->sysExState = eSysExIgnore;
    415             break;
    416 
    417         /* Process Master Volume and SP-MIDI */
    418         case eSysExUnivRealTime:
    419             if (c == 0x7f)
    420                 pMIDIStream->sysExState = eSysExUnivRtTargetID;
    421             else
    422                 pMIDIStream->sysExState = eSysExIgnore;
    423             break;
    424 
    425         case eSysExUnivRtTargetID:
    426             if (c == 0x04)
    427                 pMIDIStream->sysExState = eSysExDeviceControl;
    428             else if (c == 0x0b)
    429                 pMIDIStream->sysExState = eSysExSPMIDI;
    430             else
    431                 pMIDIStream->sysExState = eSysExIgnore;
    432             break;
    433 
    434         /* process master volume */
    435         case eSysExDeviceControl:
    436             if (c == 0x01)
    437                 pMIDIStream->sysExState = eSysExMasterVolume;
    438             else
    439                 pMIDIStream->sysExState = eSysExIgnore;
    440             break;
    441 
    442         case eSysExMasterVolume:
    443             /* save LSB */
    444             pMIDIStream->d1 = c;
    445             pMIDIStream->sysExState = eSysExMasterVolLSB;
    446             break;
    447 
    448         case eSysExMasterVolLSB:
    449             if (parserMode != eParserModeMetaData)
    450             {
    451                 EAS_I32 gain = ((EAS_I32) c << 8) | ((EAS_I32) pMIDIStream->d1 << 1);
    452                 gain = (gain * gain) >> 15;
    453                 VMSetVolume(pSynth, (EAS_U16) gain);
    454             }
    455             pMIDIStream->sysExState = eSysExEOX;
    456             break;
    457 
    458         /* process SP-MIDI MIP message */
    459         case eSysExSPMIDI:
    460             if (c == 0x01)
    461             {
    462                 /* assume all channels are muted */
    463                 if (parserMode != eParserModeMetaData)
    464                     VMInitMIPTable(pSynth);
    465                 pMIDIStream->d1 = 0;
    466                 pMIDIStream->sysExState = eSysExSPMIDIchan;
    467             }
    468             else
    469                 pMIDIStream->sysExState = eSysExIgnore;
    470             break;
    471 
    472         case eSysExSPMIDIchan:
    473             if (c < NUM_SYNTH_CHANNELS)
    474             {
    475                 pMIDIStream->d2 = c;
    476                 pMIDIStream->sysExState = eSysExSPMIDIMIP;
    477             }
    478             else
    479             {
    480                 /* bad MIP message - unmute channels */
    481                 if (parserMode != eParserModeMetaData)
    482                     VMInitMIPTable(pSynth);
    483                 pMIDIStream->sysExState = eSysExIgnore;
    484             }
    485             break;
    486 
    487         case eSysExSPMIDIMIP:
    488             /* process MIP entry here */
    489             if (parserMode != eParserModeMetaData)
    490                 VMSetMIPEntry(pEASData->pVoiceMgr, pSynth, pMIDIStream->d2, pMIDIStream->d1, c);
    491             pMIDIStream->sysExState = eSysExSPMIDIchan;
    492 
    493             /* if 16 channels received, update MIP table */
    494             if (++pMIDIStream->d1 == NUM_SYNTH_CHANNELS)
    495             {
    496                 if (parserMode != eParserModeMetaData)
    497                     VMUpdateMIPTable(pEASData->pVoiceMgr, pSynth);
    498                 pMIDIStream->sysExState = eSysExEOX;
    499             }
    500             break;
    501 
    502         /* process Enhancer */
    503         case eSysExMfgID1:
    504             if (c == 0x01)
    505                 pMIDIStream->sysExState = eSysExMfgID1;
    506             else
    507                 pMIDIStream->sysExState = eSysExIgnore;
    508             break;
    509 
    510         case eSysExMfgID2:
    511             if (c == 0x3a)
    512                 pMIDIStream->sysExState = eSysExMfgID1;
    513             else
    514                 pMIDIStream->sysExState = eSysExIgnore;
    515             break;
    516 
    517         case eSysExMfgID3:
    518             if (c == 0x04)
    519                 pMIDIStream->sysExState = eSysExEnhancer;
    520             else
    521                 pMIDIStream->sysExState = eSysExIgnore;
    522             break;
    523 
    524         case eSysExEnhancer:
    525             if (c == 0x01)
    526                 pMIDIStream->sysExState = eSysExEnhancerSubID;
    527             else
    528                 pMIDIStream->sysExState = eSysExIgnore;
    529             break;
    530 
    531         case eSysExEnhancerSubID:
    532             pMIDIStream->sysExState = eSysExEnhancerFeedback1;
    533             break;
    534 
    535         case eSysExEnhancerFeedback1:
    536             pMIDIStream->sysExState = eSysExEnhancerFeedback2;
    537             break;
    538 
    539         case eSysExEnhancerFeedback2:
    540             pMIDIStream->sysExState = eSysExEnhancerDrive;
    541             break;
    542 
    543         case eSysExEnhancerDrive:
    544             pMIDIStream->sysExState = eSysExEnhancerWet;
    545             break;
    546 
    547         case eSysExEnhancerWet:
    548             pMIDIStream->sysExState = eSysExEOX;
    549             break;
    550 
    551         case eSysExEOX:
    552             { /* dpp: EAS_ReportEx(_EAS_SEVERITY_DETAIL, "Expected F7, received %02x\n", c); */ }
    553             pMIDIStream->sysExState = eSysExIgnore;
    554             break;
    555 
    556         case eSysExIgnore:
    557             break;
    558 
    559         default:
    560             pMIDIStream->sysExState = eSysExIgnore;
    561             break;
    562         }
    563     }
    564 
    565     if (pMIDIStream->sysExState == eSysExIgnore)
    566         { /* dpp: EAS_ReportEx(_EAS_SEVERITY_DETAIL, "Ignoring SysEx byte %02x\n", c); */ }
    567     return EAS_SUCCESS;
    568 } /* end ProcessSysExMessage */
    569 
    570