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