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 */
EAS_InitMIDIStream(S_MIDI_STREAM * pMIDIStream)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 */
EAS_ParseMIDIStream(S_EAS_DATA * pEASData,S_SYNTH * pSynth,S_MIDI_STREAM * pMIDIStream,EAS_U8 c,EAS_INT parserMode)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 */
ProcessMIDIMessage(S_EAS_DATA * pEASData,S_SYNTH * pSynth,S_MIDI_STREAM * pMIDIStream,EAS_INT parserMode)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 */
ProcessSysExMessage(S_EAS_DATA * pEASData,S_SYNTH * pSynth,S_MIDI_STREAM * pMIDIStream,EAS_U8 c,EAS_INT parserMode)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