native_midi_mac.c
上传用户:nini_0081
上传日期:2022-07-21
资源大小:2628k
文件大小:18k
源码类别:

多媒体编程

开发平台:

DOS

  1. /*
  2.     native_midi_mac:  Native Midi support on MacOS for the SDL_mixer library
  3.     Copyright (C) 2001  Max Horn
  4.     This library is free software; you can redistribute it and/or
  5.     modify it under the terms of the GNU Library General Public
  6.     License as published by the Free Software Foundation; either
  7.     version 2 of the License, or (at your option) any later version.
  8.     This library is distributed in the hope that it will be useful,
  9.     but WITHOUT ANY WARRANTY; without even the implied warranty of
  10.     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  11.     Library General Public License for more details.
  12.     You should have received a copy of the GNU Library General Public
  13.     License along with this library; if not, write to the Free
  14.     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  15.     Max Horn
  16.     max@quendi.de
  17. */
  18. #include "SDL_config.h"
  19. #include "SDL_endian.h"
  20. #if __MACOS__ /*|| __MACOSX__ */
  21. #include "native_midi.h"
  22. #include "native_midi_common.h"
  23. #if __MACOSX__
  24. #include <QuickTime/QuickTimeMusic.h>
  25. #else
  26. #include <QuickTimeMusic.h>
  27. #endif
  28. #include <assert.h>
  29. #include <stdlib.h>
  30. #include <string.h>
  31. /* Native Midi song */
  32. struct _NativeMidiSong
  33. {
  34. Uint32 *tuneSequence;
  35. Uint32 *tuneHeader;
  36. };
  37. enum
  38. {
  39. /* number of (32-bit) long words in a note request event */
  40. kNoteRequestEventLength = ((sizeof(NoteRequest)/sizeof(long)) + 2),
  41. /* number of (32-bit) long words in a marker event */
  42. kMarkerEventLength = 1,
  43. /* number of (32-bit) long words in a general event, minus its data */
  44. kGeneralEventLength = 2
  45. };
  46. #define ERROR_BUF_SIZE 256
  47. #define BUFFER_INCREMENT 5000
  48. #define REST_IF_NECESSARY() do {
  49. int timeDiff = eventPos->time - lastEventTime;
  50. if(timeDiff)
  51. {
  52. timeDiff = (int)(timeDiff*tick);
  53. qtma_StuffRestEvent(*tunePos, timeDiff);
  54. tunePos++;
  55. lastEventTime = eventPos->time;
  56. }
  57. } while(0)
  58. static Uint32 *BuildTuneSequence(MIDIEvent *evntlist, int ppqn, int part_poly_max[32], int part_to_inst[32], int *numParts);
  59. static Uint32 *BuildTuneHeader(int part_poly_max[32], int part_to_inst[32], int numParts);
  60. /* The global TunePlayer instance */
  61. static TunePlayer gTunePlayer = NULL;
  62. static int gInstaceCount = 0;
  63. static Uint32 *gCurrentTuneSequence = NULL;
  64. static char gErrorBuffer[ERROR_BUF_SIZE] = "";
  65. /* Check whether QuickTime is available */
  66. int native_midi_detect()
  67. {
  68. /* TODO */
  69. return 1;
  70. }
  71. NativeMidiSong *native_midi_loadsong(const char *midifile)
  72. {
  73. NativeMidiSong *song = NULL;
  74. MIDIEvent *evntlist = NULL;
  75. int part_to_inst[32];
  76. int part_poly_max[32];
  77. int numParts = 0;
  78. Uint16 ppqn;
  79. SDL_RWops *rw;
  80. /* Init the arrays */
  81. memset(part_poly_max,0,sizeof(part_poly_max));
  82. memset(part_to_inst,-1,sizeof(part_to_inst));
  83. /* Attempt to load the midi file */
  84. rw = SDL_RWFromFile(midifile, "rb");
  85. if (rw) {
  86. evntlist = CreateMIDIEventList(rw, &ppqn);
  87. SDL_RWclose(rw);
  88. if (!evntlist)
  89. goto bail;
  90. }
  91. /* Allocate memory for the song struct */
  92. song = malloc(sizeof(NativeMidiSong));
  93. if (!song)
  94. goto bail;
  95. /* Build a tune sequence from the event list */
  96. song->tuneSequence = BuildTuneSequence(evntlist, ppqn, part_poly_max, part_to_inst, &numParts);
  97. if(!song->tuneSequence)
  98. goto bail;
  99. /* Now build a tune header from the data we collect above, create
  100.    all parts as needed and assign them the correct instrument.
  101. */
  102. song->tuneHeader = BuildTuneHeader(part_poly_max, part_to_inst, numParts);
  103. if(!song->tuneHeader)
  104. goto bail;
  105. /* Increment the instance count */
  106. gInstaceCount++;
  107. if (gTunePlayer == NULL)
  108. gTunePlayer = OpenDefaultComponent(kTunePlayerComponentType, 0);
  109. /* Finally, free the event list */
  110. FreeMIDIEventList(evntlist);
  111. return song;
  112. bail:
  113. if (evntlist)
  114. FreeMIDIEventList(evntlist);
  115. if (song)
  116. {
  117. if(song->tuneSequence)
  118. free(song->tuneSequence);
  119. if(song->tuneHeader)
  120. DisposePtr((Ptr)song->tuneHeader);
  121. free(song);
  122. }
  123. return NULL;
  124. }
  125. NativeMidiSong *native_midi_loadsong_RW(SDL_RWops *rw)
  126. {
  127. NativeMidiSong *song = NULL;
  128. MIDIEvent *evntlist = NULL;
  129. int part_to_inst[32];
  130. int part_poly_max[32];
  131. int numParts = 0;
  132. Uint16 ppqn;
  133. /* Init the arrays */
  134. memset(part_poly_max,0,sizeof(part_poly_max));
  135. memset(part_to_inst,-1,sizeof(part_to_inst));
  136. /* Attempt to load the midi file */
  137. evntlist = CreateMIDIEventList(rw, &ppqn);
  138. if (!evntlist)
  139. goto bail;
  140. /* Allocate memory for the song struct */
  141. song = malloc(sizeof(NativeMidiSong));
  142. if (!song)
  143. goto bail;
  144. /* Build a tune sequence from the event list */
  145. song->tuneSequence = BuildTuneSequence(evntlist, ppqn, part_poly_max, part_to_inst, &numParts);
  146. if(!song->tuneSequence)
  147. goto bail;
  148. /* Now build a tune header from the data we collect above, create
  149.    all parts as needed and assign them the correct instrument.
  150. */
  151. song->tuneHeader = BuildTuneHeader(part_poly_max, part_to_inst, numParts);
  152. if(!song->tuneHeader)
  153. goto bail;
  154. /* Increment the instance count */
  155. gInstaceCount++;
  156. if (gTunePlayer == NULL)
  157. gTunePlayer = OpenDefaultComponent(kTunePlayerComponentType, 0);
  158. /* Finally, free the event list */
  159. FreeMIDIEventList(evntlist);
  160. return song;
  161. bail:
  162. if (evntlist)
  163. FreeMIDIEventList(evntlist);
  164. if (song)
  165. {
  166. if(song->tuneSequence)
  167. free(song->tuneSequence);
  168. if(song->tuneHeader)
  169. DisposePtr((Ptr)song->tuneHeader);
  170. free(song);
  171. }
  172. return NULL;
  173. }
  174. void native_midi_freesong(NativeMidiSong *song)
  175. {
  176. if(!song || !song->tuneSequence)
  177. return;
  178. /* If this is the currently playing song, stop it now */
  179. if (song->tuneSequence == gCurrentTuneSequence)
  180. native_midi_stop();
  181. /* Finally, free the data storage */
  182. free(song->tuneSequence);
  183. DisposePtr((Ptr)song->tuneHeader);
  184. free(song);
  185. /* Increment the instance count */
  186. gInstaceCount--;
  187. if ((gTunePlayer != NULL) && (gInstaceCount == 0))
  188. {
  189. CloseComponent(gTunePlayer);
  190. gTunePlayer = NULL;
  191. }
  192. }
  193. void native_midi_start(NativeMidiSong *song)
  194. {
  195. UInt32 queueFlags = 0;
  196. ComponentResult tpError;
  197. assert (gTunePlayer != NULL);
  198. SDL_PauseAudio(1);
  199. SDL_UnlockAudio();
  200.     
  201. /* First, stop the currently playing music */
  202. native_midi_stop();
  203. /* Set up the queue flags */
  204. queueFlags = kTuneStartNow;
  205. /* Set the time scale (units per second), we want milliseconds */
  206. tpError = TuneSetTimeScale(gTunePlayer, 1000);
  207. if (tpError != noErr)
  208. {
  209. strncpy (gErrorBuffer, "MIDI error during TuneSetTimeScale", ERROR_BUF_SIZE);
  210. goto done;
  211. }
  212. /* Set the header, to tell what instruments are used */
  213. tpError = TuneSetHeader(gTunePlayer, (UInt32 *)song->tuneHeader);
  214. if (tpError != noErr)
  215. {
  216. strncpy (gErrorBuffer, "MIDI error during TuneSetHeader", ERROR_BUF_SIZE);
  217. goto done;
  218. }
  219. /* Have it allocate whatever resources are needed */
  220. tpError = TunePreroll(gTunePlayer);
  221. if (tpError != noErr)
  222. {
  223. strncpy (gErrorBuffer, "MIDI error during TunePreroll", ERROR_BUF_SIZE);
  224. goto done;
  225. }
  226. /* We want to play at normal volume */
  227. tpError = TuneSetVolume(gTunePlayer, 0x00010000);
  228. if (tpError != noErr)
  229. {
  230. strncpy (gErrorBuffer, "MIDI error during TuneSetVolume", ERROR_BUF_SIZE);
  231. goto done;
  232. }
  233. /* Finally, start playing the full song */
  234. gCurrentTuneSequence = song->tuneSequence;
  235. tpError = TuneQueue(gTunePlayer, (UInt32 *)song->tuneSequence, 0x00010000, 0, 0xFFFFFFFF, queueFlags, NULL, 0);
  236. if (tpError != noErr)
  237. {
  238. strncpy (gErrorBuffer, "MIDI error during TuneQueue", ERROR_BUF_SIZE);
  239. goto done;
  240. }
  241.     
  242. done:
  243. SDL_LockAudio();
  244. SDL_PauseAudio(0);
  245. }
  246. void native_midi_stop()
  247. {
  248. if (gTunePlayer == NULL)
  249. return;
  250. /* Stop music */
  251. TuneStop(gTunePlayer, 0);
  252. /* Deallocate all instruments */
  253. TuneUnroll(gTunePlayer);
  254. }
  255. int native_midi_active()
  256. {
  257. if (gTunePlayer != NULL)
  258. {
  259. TuneStatus ts;
  260. TuneGetStatus(gTunePlayer,&ts);
  261. return ts.queueTime != 0;
  262. }
  263. else
  264. return 0;
  265. }
  266. void native_midi_setvolume(int volume)
  267. {
  268. if (gTunePlayer == NULL)
  269. return;
  270. /* QTMA olume may range from 0.0 to 1.0 (in 16.16 fixed point encoding) */
  271. TuneSetVolume(gTunePlayer, (0x00010000 * volume)/SDL_MIX_MAXVOLUME);
  272. }
  273. const char *native_midi_error(void)
  274. {
  275. return gErrorBuffer;
  276. }
  277. Uint32 *BuildTuneSequence(MIDIEvent *evntlist, int ppqn, int part_poly_max[32], int part_to_inst[32], int *numParts)
  278. {
  279. int part_poly[32];
  280. int channel_to_part[16];
  281. int channel_pan[16];
  282. int channel_vol[16];
  283. int channel_pitch_bend[16];
  284. int lastEventTime = 0;
  285. int tempo = 500000;
  286. double Ippqn = 1.0 / (1000*ppqn);
  287. double tick = tempo * Ippqn;
  288. MIDIEvent *eventPos = evntlist;
  289. MIDIEvent *noteOffPos;
  290. Uint32  *tunePos, *endPos;
  291. Uint32 *tuneSequence;
  292. size_t tuneSize;
  293. /* allocate space for the tune header */
  294. tuneSize = 5000;
  295. tuneSequence = (Uint32 *)malloc(tuneSize * sizeof(Uint32));
  296. if (tuneSequence == NULL)
  297. return NULL;
  298. /* Set starting position in our tune memory */
  299. tunePos = tuneSequence;
  300. endPos = tuneSequence + tuneSize;
  301. /* Initialise the arrays */
  302. memset(part_poly,0,sizeof(part_poly));
  303. memset(channel_to_part,-1,sizeof(channel_to_part));
  304. memset(channel_pan,-1,sizeof(channel_pan));
  305. memset(channel_vol,-1,sizeof(channel_vol));
  306. memset(channel_pitch_bend,-1,sizeof(channel_pitch_bend));
  307. *numParts = 0;
  308. /*
  309.  * Now the major work - iterate over all GM events,
  310.  * and turn them into QuickTime Music format.
  311.  * At the same time, calculate the max. polyphony for each part,
  312.  * and also the part->instrument mapping.
  313.  */
  314. while(eventPos)
  315. {
  316. int status = (eventPos->status&0xF0)>>4;
  317. int channel = eventPos->status&0x0F;
  318. int part = channel_to_part[channel];
  319.         int velocity, pitch;
  320.         int value, controller;
  321.         int bend;
  322.         int newInst;
  323. /* Check if we are running low on space... */
  324. if((tunePos+16) > endPos)
  325. {
  326. /* Resize our data storage. */
  327. Uint32  *oldTuneSequence = tuneSequence;
  328. tuneSize += BUFFER_INCREMENT;
  329. tuneSequence = (Uint32 *)realloc(tuneSequence, tuneSize * sizeof(Uint32));
  330. if(oldTuneSequence != tuneSequence)
  331. tunePos += tuneSequence - oldTuneSequence;
  332. endPos = tuneSequence + tuneSize;
  333. }
  334. switch (status)
  335. {
  336. case MIDI_STATUS_NOTE_OFF:
  337. assert(part>=0 && part<=31);
  338. /* Keep track of the polyphony of the current part */
  339. part_poly[part]--;
  340. break;
  341. case MIDI_STATUS_NOTE_ON:
  342. if (part < 0)
  343. {
  344. /* If no part is specified yet, we default to the first instrument, which
  345.    is piano (or the first drum kit if we are on the drum channel)
  346. */
  347. int newInst;
  348. if (channel == 9)
  349. newInst = kFirstDrumkit + 1; /* the first drum kit is the "no drum" kit! */
  350. else
  351. newInst = kFirstGMInstrument;
  352. part = channel_to_part[channel] = *numParts;
  353. part_to_inst[(*numParts)++] = newInst;
  354. }
  355. /* TODO - add support for more than 32 parts using eXtended QTMA events */
  356. assert(part<=31);
  357. /* Decode pitch & velocity */
  358. pitch = eventPos->data[0];
  359. velocity = eventPos->data[1];
  360. if (velocity == 0)
  361. {
  362. /* was a NOTE OFF in disguise, so we decrement the polyphony */
  363. part_poly[part]--;
  364. }
  365. else
  366. {
  367. /* Keep track of the polyphony of the current part */
  368. int foo = ++part_poly[part];
  369. if (part_poly_max[part] < foo)
  370. part_poly_max[part] = foo;
  371. /* Now scan forward to find the matching NOTE OFF event */
  372. for(noteOffPos = eventPos; noteOffPos; noteOffPos = noteOffPos->next)
  373. {
  374. if ((noteOffPos->status&0xF0)>>4 == MIDI_STATUS_NOTE_OFF
  375. && channel == (eventPos->status&0x0F)
  376. && pitch == noteOffPos->data[0])
  377. break;
  378. /* NOTE ON with velocity == 0 is the same as a NOTE OFF */
  379. if ((noteOffPos->status&0xF0)>>4 == MIDI_STATUS_NOTE_ON
  380. && channel == (eventPos->status&0x0F)
  381. && pitch == noteOffPos->data[0]
  382. && 0 == noteOffPos->data[1])
  383. break;
  384. }
  385. /* Did we find a note off? Should always be the case, but who knows... */
  386. if (noteOffPos)
  387. {
  388. /* We found a NOTE OFF, now calculate the note duration */
  389. int duration = (int)((noteOffPos->time - eventPos->time)*tick);
  390. REST_IF_NECESSARY();
  391. /* Now we need to check if we get along with a normal Note Event, or if we need an extended one... */
  392. if (duration < 2048 && pitch>=32 && pitch<=95 && velocity>=0 && velocity<=127)
  393. {
  394. qtma_StuffNoteEvent(*tunePos, part, pitch, velocity, duration);
  395. tunePos++;
  396. }
  397. else
  398. {
  399. qtma_StuffXNoteEvent(*tunePos, *(tunePos+1), part, pitch, velocity, duration);
  400. tunePos+=2;
  401. }
  402. }
  403. }
  404. break;
  405. case MIDI_STATUS_AFTERTOUCH:
  406. /* NYI - use kControllerAfterTouch. But how are the parameters to be mapped? */
  407. break;
  408. case MIDI_STATUS_CONTROLLER:
  409. controller = eventPos->data[0];
  410. value = eventPos->data[1];
  411. switch(controller)
  412. {
  413. case 0: /* bank change - igore for now */
  414. break;
  415. case kControllerVolume:
  416. if(channel_vol[channel] != value<<8)
  417. {
  418. channel_vol[channel] = value<<8;
  419. if(part>=0 && part<=31)
  420. {
  421. REST_IF_NECESSARY();
  422. qtma_StuffControlEvent(*tunePos, part, kControllerVolume, channel_vol[channel]);
  423. tunePos++;
  424. }
  425. }
  426. break;
  427. case kControllerPan:
  428. if(channel_pan[channel] != (value << 1) + 256)
  429. {
  430. channel_pan[channel] = (value << 1) + 256;
  431. if(part>=0 && part<=31)
  432. {
  433. REST_IF_NECESSARY();
  434. qtma_StuffControlEvent(*tunePos, part, kControllerPan, channel_pan[channel]);
  435. tunePos++;
  436. }
  437. }
  438. break;
  439. default:
  440. /* No other controllers implemented yet */;
  441. break;
  442. }
  443. break;
  444. case MIDI_STATUS_PROG_CHANGE:
  445. /* Instrument changed */
  446. newInst = eventPos->data[0];
  447. /* Channel 9 (the 10th channel) is different, it indicates a drum kit */
  448. if (channel == 9)
  449. newInst += kFirstDrumkit;
  450. else
  451. newInst += kFirstGMInstrument;
  452. /* Only if the instrument for this channel *really* changed, add a new part. */
  453. if(newInst != part_to_inst[part])
  454. {
  455. /* TODO maybe make use of kGeneralEventPartChange here,
  456.    to help QT reuse note channels?
  457. */
  458. part = channel_to_part[channel] = *numParts;
  459. part_to_inst[(*numParts)++] = newInst;
  460. if(channel_vol[channel] >= 0)
  461. {
  462. REST_IF_NECESSARY();
  463. qtma_StuffControlEvent(*tunePos, part, kControllerVolume, channel_vol[channel]);
  464. tunePos++;
  465. }
  466. if(channel_pan[channel] >= 0)
  467. {
  468. REST_IF_NECESSARY();
  469. qtma_StuffControlEvent(*tunePos, part, kControllerPan, channel_pan[channel]);
  470. tunePos++;
  471. }
  472. if(channel_pitch_bend[channel] >= 0)
  473. {
  474. REST_IF_NECESSARY();
  475. qtma_StuffControlEvent(*tunePos, part, kControllerPitchBend, channel_pitch_bend[channel]);
  476. tunePos++;
  477. }
  478. }
  479. break;
  480. case MIDI_STATUS_PRESSURE:
  481. /* NYI */
  482. break;
  483. case MIDI_STATUS_PITCH_WHEEL:
  484. /* In the midi spec, 0x2000 = center, 0x0000 = - 2 semitones, 0x3FFF = +2 semitones
  485.    but for QTMA, we specify it as a 8.8 fixed point of semitones
  486.    TODO: detect "pitch bend range changes" & honor them!
  487. */
  488. bend = (eventPos->data[0] & 0x7f) | ((eventPos->data[1] & 0x7f) << 7);
  489. /* "Center" the bend */
  490. bend -= 0x2000;
  491. /* Move it to our format: */
  492. bend <<= 4;
  493. /* If it turns out the pitch bend didn't change, stop here */
  494. if(channel_pitch_bend[channel] == bend)
  495. break;
  496. channel_pitch_bend[channel] = bend;
  497. if(part>=0 && part<=31)
  498. {
  499. /* Stuff a control event */
  500. REST_IF_NECESSARY();
  501. qtma_StuffControlEvent(*tunePos, part, kControllerPitchBend, bend);
  502. tunePos++;
  503. }
  504. break;
  505. case MIDI_STATUS_SYSEX:
  506. if (eventPos->status == 0xFF && eventPos->data[0] == 0x51) /* Tempo change */
  507. {
  508. tempo = (eventPos->extraData[0] << 16) +
  509. (eventPos->extraData[1] << 8) +
  510. eventPos->extraData[2];
  511. tick = tempo * Ippqn;
  512. }
  513. break;
  514. }
  515. /* on to the next event */
  516. eventPos = eventPos->next;
  517. /* Finally, place an end marker */
  518. *tunePos = kEndMarkerValue;
  519. return tuneSequence;
  520. }
  521. Uint32 *BuildTuneHeader(int part_poly_max[32], int part_to_inst[32], int numParts)
  522. {
  523. Uint32 *myHeader;
  524. Uint32 *myPos1, *myPos2; /* pointers to the head and tail long words of a music event */
  525. NoteRequest *myNoteRequest;
  526. NoteAllocator myNoteAllocator; /* for the NAStuffToneDescription call */
  527. ComponentResult myErr = noErr;
  528. int part;
  529. myHeader = NULL;
  530. myNoteAllocator = NULL;
  531. /*
  532.  * Open up the Note Allocator
  533.  */
  534. myNoteAllocator = OpenDefaultComponent(kNoteAllocatorComponentType,0);
  535. if (myNoteAllocator == NULL)
  536. goto bail;
  537. /*
  538.  * Allocate space for the tune header
  539.  */
  540. myHeader = (Uint32 *)
  541. NewPtrClear((numParts * kNoteRequestEventLength + kMarkerEventLength) * sizeof(Uint32));
  542. if (myHeader == NULL)
  543. goto bail;
  544. myPos1 = myHeader;
  545. /*
  546.  * Loop over all parts
  547.  */
  548. for(part = 0; part < numParts; ++part)
  549. {
  550. /*
  551.  * Stuff request for the instrument with the given polyphony
  552.  */
  553. myPos2 = myPos1 + (kNoteRequestEventLength - 1); /* last longword of general event */
  554. qtma_StuffGeneralEvent(*myPos1, *myPos2, part, kGeneralEventNoteRequest, kNoteRequestEventLength);
  555. myNoteRequest = (NoteRequest *)(myPos1 + 1);
  556. myNoteRequest->info.flags = 0;
  557. /* I'm told by the Apple people that the Quicktime types were poorly designed and it was 
  558.  * too late to change them. On little endian, the BigEndian(Short|Fixed) types are structs
  559.  * while on big endian they are primitive types. Furthermore, Quicktime failed to 
  560.  * provide setter and getter functions. To get this to work, we need to case the 
  561.  * code for the two possible situations.
  562.  * My assumption is that the right-side value was always expected to be BigEndian
  563.  * as it was written way before the Universal Binary transition. So in the little endian
  564.  * case, OSSwap is used.
  565.  */
  566. #if SDL_BYTEORDER == SDL_LIL_ENDIAN
  567. myNoteRequest->info.polyphony.bigEndianValue = OSSwapHostToBigInt16(part_poly_max[part]);
  568. myNoteRequest->info.typicalPolyphony.bigEndianValue = OSSwapHostToBigInt32(0x00010000);
  569. #else
  570. myNoteRequest->info.polyphony = part_poly_max[part];
  571. myNoteRequest->info.typicalPolyphony = 0x00010000;
  572. #endif
  573. myErr = NAStuffToneDescription(myNoteAllocator,part_to_inst[part],&myNoteRequest->tone);
  574. if (myErr != noErr)
  575. goto bail;
  576. /* move pointer to beginning of next event */
  577. myPos1 += kNoteRequestEventLength;
  578. }
  579. *myPos1 = kEndMarkerValue; /* end of sequence marker */
  580. bail:
  581. if(myNoteAllocator)
  582. CloseComponent(myNoteAllocator);
  583. /* if we encountered an error, dispose of the storage we allocated and return NULL */
  584. if (myErr != noErr) {
  585. DisposePtr((Ptr)myHeader);
  586. myHeader = NULL;
  587. }
  588. return myHeader;
  589. }
  590. #endif /* MacOS native MIDI support */