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