eas_midi.c revision a8c89077d78769bf4840fa91609edc51fe2fa02d
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 */
44typedef 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 */
72static EAS_RESULT ProcessMIDIMessage (S_EAS_DATA *pEASData, S_SYNTH *pSynth, S_MIDI_STREAM *pMIDIStream, EAS_INT parserMode);
73static 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*/
90void 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*/
115EAS_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*/
232static 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*/
344static 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