eas_midi.c revision e442bb7cd6a085b33a4dd52c0e20a157ada7feb1
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