1 /*
2 * midiFile.c - A general purpose midi file handling library. This code
3 * can read and write MIDI files in formats 0 and 1.
4 * Version 1.4
5 *
6 * AUTHOR: Steven Goodwin (StevenGoodwin@gmail.com)
7 * Copyright 1998-2010, Steven Goodwin
8 *
9 * This program is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU General Public License as
11 * published by the Free Software Foundation; either version 2 of
12 * the License,or (at your option) any later version.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, write to the Free Software
21 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
22 */
23
24 #pragma GCC diagnostic push
25 #pragma GCC diagnostic ignored "-Wsign-compare"
26
27 #include <stdio.h>
28 #include <stdlib.h>
29 #include <string.h>
30 #include "midifile.h"
31
32 /*
33 ** Internal Data Structures
34 */
35 typedef struct {
36 BYTE note, chn;
37 BYTE valid, p2;
38 _DWORD end_pos;
39 } MIDI_LAST_NOTE;
40
41 typedef struct {
42 BYTE *ptr;
43 BYTE *pBase;
44 BYTE *pEnd;
45
46 _DWORD pos;
47 _DWORD dt;
48 /* For Reading MIDI Files */
49 _DWORD sz; /* size of whole iTrack */
50 /* For Writing MIDI Files */
51 _DWORD iBlockSize; /* max size of track */
52 BYTE iDefaultChannel; /* use for write only */
53 BYTE last_status; /* used for running status */
54
55 MIDI_LAST_NOTE LastNote[MAX_TRACK_POLYPHONY];
56 } MIDI_FILE_TRACK;
57
58 typedef struct {
59 _DWORD iHeaderSize;
60 /**/
61 WORD iVersion; /* 0, 1 or 2 */
62 WORD iNumTracks; /* number of tracks... (will be 1 for MIDI type 0) */
63 WORD PPQN; /* pulses per quarter note */
64 } MIDI_HEADER;
65
66 typedef struct {
67 FILE *pFile;
68 BOOL bOpenForWriting;
69
70 MIDI_HEADER Header;
71 BYTE *ptr; /* to whole data block */
72 _DWORD file_sz;
73
74 MIDI_FILE_TRACK Track[MAX_MIDI_TRACKS];
75 } _MIDI_FILE;
76
77
78 /*
79 ** Internal Functions
80 */
81
82 #define DT_DEF 32 /* assume maximum delta-time + msg is no more than 32 bytes */
83
84 #ifdef HAVE_BIG_ENDIAN
85 #define SWAP_WORD(w) (w)
86 #define SWAP__DWORD(d) (d)
87 #else
88 #define SWAP_WORD(w) (WORD)(((w)>>8)|((w)<<8))
89 #define SWAP__DWORD(d) (_DWORD)((d)>>24)|(((d)>>8)&0xff00)|(((d)<<8)&0xff0000)|(((d)<<24))
90 #endif
91
92 #define _VAR_CAST _MIDI_FILE *pMF = (_MIDI_FILE *)_pMF
93 #define IsFilePtrValid(pMF) (pMF)
94 #define IsTrackValid(_x) (_midiValidateTrack(pMF, _x))
95 #define IsChannelValid(_x) ((_x)>=1 && (_x)<=16)
96 #define IsNoteValid(_x) ((_x)>=0 && (_x)<128)
97 #define IsMessageValid(_x) ((_x)>=msgNoteOff && (_x)<=msgMetaEvent)
98
99
_midiValidateTrack(const _MIDI_FILE * pMF,int iTrack)100 static BOOL _midiValidateTrack(const _MIDI_FILE *pMF, int iTrack)
101 {
102 if (!IsFilePtrValid(pMF)) return FALSE;
103
104 if (pMF->bOpenForWriting)
105 {
106 if (iTrack < 0 || iTrack >= MAX_MIDI_TRACKS)
107 return FALSE;
108 }
109 else /* open for reading */
110 {
111 if (!pMF->ptr)
112 return FALSE;
113
114 if (iTrack < 0 || iTrack>=pMF->Header.iNumTracks)
115 return FALSE;
116 }
117
118 return TRUE;
119 }
120
_midiWriteVarLen(BYTE * ptr,int n)121 static BYTE *_midiWriteVarLen(BYTE *ptr, int n)
122 {
123 register long buffer;
124 register long value=n;
125
126 buffer = value & 0x7f;
127 while ((value >>= 7) > 0)
128 {
129 buffer <<= 8;
130 buffer |= 0x80;
131 buffer += (value & 0x7f);
132 }
133
134 while (TRUE)
135 {
136 *ptr++ = (BYTE)buffer;
137 if (buffer & 0x80)
138 buffer >>= 8;
139 else
140 break;
141 }
142
143 return(ptr);
144 }
145
146 /* Return a ptr to valid block of memory to store a message
147 ** of up to sz_reqd bytes
148 */
_midiGetPtr(_MIDI_FILE * pMF,int iTrack,int sz_reqd)149 static BYTE *_midiGetPtr(_MIDI_FILE *pMF, int iTrack, int sz_reqd)
150 {
151 const _DWORD mem_sz_inc = 8092; /* arbitary */
152 BYTE *ptr;
153 int curr_offset;
154 MIDI_FILE_TRACK *pTrack = &pMF->Track[iTrack];
155
156 ptr = pTrack->ptr;
157 if (ptr == NULL || ptr+sz_reqd > pTrack->pEnd) /* need more RAM! */
158 {
159 curr_offset = ptr-pTrack->pBase;
160 if ((ptr = (BYTE *)realloc(pTrack->pBase, mem_sz_inc+pTrack->iBlockSize)))
161 {
162 pTrack->pBase = ptr;
163 pTrack->iBlockSize += mem_sz_inc;
164 pTrack->pEnd = ptr+pTrack->iBlockSize;
165 /* Move new ptr to continue data entry: */
166 pTrack->ptr = ptr+curr_offset;
167 ptr += curr_offset;
168 }
169 else
170 {
171 /* NO MEMORY LEFT */
172 return NULL;
173 }
174 }
175
176 return ptr;
177 }
178
179
_midiGetLength(int ppqn,int iNoteLen,BOOL bOverride)180 static int _midiGetLength(int ppqn, int iNoteLen, BOOL bOverride)
181 {
182 int length = ppqn;
183
184 if (bOverride)
185 {
186 length = iNoteLen;
187 }
188 else
189 {
190 switch(iNoteLen)
191 {
192 case MIDI_NOTE_DOTTED_MINIM:
193 length *= 3;
194 break;
195
196 case MIDI_NOTE_DOTTED_CROCHET:
197 length *= 3;
198 length /= 2;
199 break;
200
201 case MIDI_NOTE_DOTTED_QUAVER:
202 length *= 3;
203 length /= 4;
204 break;
205
206 case MIDI_NOTE_DOTTED_SEMIQUAVER:
207 length *= 3;
208 length /= 8;
209 break;
210
211 case MIDI_NOTE_DOTTED_SEMIDEMIQUAVER:
212 length *= 3;
213 length /= 16;
214 break;
215
216 case MIDI_NOTE_BREVE:
217 length *= 4;
218 break;
219
220 case MIDI_NOTE_MINIM:
221 length *= 2;
222 break;
223
224 case MIDI_NOTE_QUAVER:
225 length /= 2;
226 break;
227
228 case MIDI_NOTE_SEMIQUAVER:
229 length /= 4;
230 break;
231
232 case MIDI_NOTE_SEMIDEMIQUAVER:
233 length /= 8;
234 break;
235
236 case MIDI_NOTE_TRIPLE_CROCHET:
237 length *= 2;
238 length /= 3;
239 break;
240 }
241 }
242
243 return length;
244 }
245
246 /*
247 ** midiFile* Functions
248 */
midiFileCreate(const char * pFilename,BOOL bOverwriteIfExists)249 MIDI_FILE *midiFileCreate(const char *pFilename, BOOL bOverwriteIfExists)
250 {
251 _MIDI_FILE *pMF = (_MIDI_FILE *)malloc(sizeof(_MIDI_FILE));
252 int i;
253
254 if (!pMF) return NULL;
255
256 if (!bOverwriteIfExists)
257 {
258 if ((pMF->pFile = fopen(pFilename, "r")))
259 {
260 fclose(pMF->pFile);
261 free(pMF);
262 return NULL;
263 }
264 }
265
266 if ((pMF->pFile = fopen(pFilename, "wb+")))
267 {/*empty*/}
268 else
269 {
270 free((void *)pMF);
271 return NULL;
272 }
273
274 pMF->bOpenForWriting = TRUE;
275 pMF->Header.PPQN = MIDI_PPQN_DEFAULT;
276 pMF->Header.iVersion = MIDI_VERSION_DEFAULT;
277
278 for(i=0;i<MAX_MIDI_TRACKS;++i)
279 {
280 pMF->Track[i].pos = 0;
281 pMF->Track[i].ptr = NULL;
282 pMF->Track[i].pBase = NULL;
283 pMF->Track[i].pEnd = NULL;
284 pMF->Track[i].iBlockSize = 0;
285 pMF->Track[i].dt = 0;
286 pMF->Track[i].iDefaultChannel = (BYTE)(i & 0xf);
287
288 memset(pMF->Track[i].LastNote, '\0', sizeof(pMF->Track[i].LastNote));
289 }
290
291 return (MIDI_FILE *)pMF;
292 }
293
midiFileSetTracksDefaultChannel(MIDI_FILE * _pMF,int iTrack,int iChannel)294 int midiFileSetTracksDefaultChannel(MIDI_FILE *_pMF, int iTrack, int iChannel)
295 {
296 int prev;
297
298 _VAR_CAST;
299 if (!IsFilePtrValid(pMF)) return 0;
300 if (!IsTrackValid(iTrack)) return 0;
301 if (!IsChannelValid(iChannel)) return 0;
302
303 /* For programmer each, iChannel is between 1 & 16 - but MIDI uses
304 ** 0-15. Thus, the fudge factor of 1 :)
305 */
306 prev = pMF->Track[iTrack].iDefaultChannel+1;
307 pMF->Track[iTrack].iDefaultChannel = (BYTE)(iChannel-1);
308 return prev;
309 }
310
midiFileGetTracksDefaultChannel(const MIDI_FILE * _pMF,int iTrack)311 int midiFileGetTracksDefaultChannel(const MIDI_FILE *_pMF, int iTrack)
312 {
313 _VAR_CAST;
314 if (!IsFilePtrValid(pMF)) return 0;
315 if (!IsTrackValid(iTrack)) return 0;
316
317 return pMF->Track[iTrack].iDefaultChannel+1;
318 }
319
midiFileSetPPQN(MIDI_FILE * _pMF,int PPQN)320 int midiFileSetPPQN(MIDI_FILE *_pMF, int PPQN)
321 {
322 int prev;
323
324 _VAR_CAST;
325 if (!IsFilePtrValid(pMF)) return MIDI_PPQN_DEFAULT;
326 prev = pMF->Header.PPQN;
327 pMF->Header.PPQN = (WORD)PPQN;
328 return prev;
329 }
330
midiFileGetPPQN(const MIDI_FILE * _pMF)331 int midiFileGetPPQN(const MIDI_FILE *_pMF)
332 {
333 _VAR_CAST;
334 if (!IsFilePtrValid(pMF)) return MIDI_PPQN_DEFAULT;
335 return (int)pMF->Header.PPQN;
336 }
337
midiFileSetVersion(MIDI_FILE * _pMF,int iVersion)338 int midiFileSetVersion(MIDI_FILE *_pMF, int iVersion)
339 {
340 int prev;
341
342 _VAR_CAST;
343 if (!IsFilePtrValid(pMF)) return MIDI_VERSION_DEFAULT;
344 if (iVersion<0 || iVersion>2) return MIDI_VERSION_DEFAULT;
345 prev = pMF->Header.iVersion;
346 pMF->Header.iVersion = (WORD)iVersion;
347 return prev;
348 }
349
midiFileGetVersion(const MIDI_FILE * _pMF)350 int midiFileGetVersion(const MIDI_FILE *_pMF)
351 {
352 _VAR_CAST;
353 if (!IsFilePtrValid(pMF)) return MIDI_VERSION_DEFAULT;
354 return pMF->Header.iVersion;
355 }
356
midiFileOpen(const char * pFilename)357 MIDI_FILE *midiFileOpen(const char *pFilename)
358 {
359 FILE *fp = fopen(pFilename, "rb");
360 _MIDI_FILE *pMF = NULL;
361 BYTE *ptr;
362 BOOL bValidFile=FALSE;
363 long size;
364
365 if (fp)
366 {
367 if ((pMF = (_MIDI_FILE *)malloc(sizeof(_MIDI_FILE))))
368 {
369 fseek(fp, 0L, SEEK_END);
370 size = ftell(fp);
371 if ((pMF->ptr = (BYTE *)malloc(size)))
372 {
373 fseek(fp, 0L, SEEK_SET);
374 (void)fread(pMF->ptr, sizeof(BYTE), size, fp);
375 /* Is this a valid MIDI file ? */
376 ptr = pMF->ptr;
377 if (*(ptr+0) == 'M' && *(ptr+1) == 'T' &&
378 *(ptr+2) == 'h' && *(ptr+3) == 'd')
379 {
380 _DWORD dwData;
381 WORD wData;
382 int i;
383
384 dwData = *((_DWORD *)(ptr+4));
385 pMF->Header.iHeaderSize = SWAP__DWORD(dwData);
386
387 wData = *((WORD *)(ptr+8));
388 pMF->Header.iVersion = (WORD)SWAP_WORD(wData);
389
390 wData = *((WORD *)(ptr+10));
391 pMF->Header.iNumTracks = (WORD)SWAP_WORD(wData);
392
393 wData = *((WORD *)(ptr+12));
394 pMF->Header.PPQN = (WORD)SWAP_WORD(wData);
395
396 ptr += pMF->Header.iHeaderSize+8;
397 /*
398 ** Get all tracks
399 */
400 for(i=0;i<MAX_MIDI_TRACKS;++i)
401 {
402 pMF->Track[i].pos = 0;
403 pMF->Track[i].last_status = 0;
404 }
405
406 for(i=0;i<pMF->Header.iNumTracks;++i)
407 {
408 pMF->Track[i].pBase = ptr;
409 pMF->Track[i].ptr = ptr+8;
410 dwData = *((_DWORD *)(ptr+4));
411 pMF->Track[i].sz = SWAP__DWORD(dwData);
412 pMF->Track[i].pEnd = ptr+pMF->Track[i].sz+8;
413 ptr += pMF->Track[i].sz+8;
414 }
415
416 pMF->bOpenForWriting = FALSE;
417 pMF->pFile = NULL;
418 bValidFile = TRUE;
419 }
420 }
421 }
422
423 fclose(fp);
424 }
425
426 if (!bValidFile)
427 {
428 if (pMF) free((void *)pMF);
429 return NULL;
430 }
431
432 return (MIDI_FILE *)pMF;
433 }
434
435 typedef struct {
436 int iIdx;
437 int iEndPos;
438 } MIDI_END_POINT;
439
qs_cmp_pEndPoints(const void * e1,const void * e2)440 static int qs_cmp_pEndPoints(const void *e1, const void *e2)
441 {
442 MIDI_END_POINT *p1 = (MIDI_END_POINT *)e1;
443 MIDI_END_POINT *p2 = (MIDI_END_POINT *)e2;
444
445 return p1->iEndPos-p2->iEndPos;
446 }
447
midiFileFlushTrack(MIDI_FILE * _pMF,int iTrack,BOOL bFlushToEnd,_DWORD dwEndTimePos)448 BOOL midiFileFlushTrack(MIDI_FILE *_pMF, int iTrack, BOOL bFlushToEnd, _DWORD dwEndTimePos)
449 {
450 int sz;
451 BYTE *ptr;
452 MIDI_END_POINT *pEndPoints;
453 int num, i, mx_pts;
454
455 _VAR_CAST;
456 if (!IsFilePtrValid(pMF)) return FALSE;
457 if (!_midiValidateTrack(pMF, iTrack)) return FALSE;
458 sz = sizeof(pMF->Track[0].LastNote)/sizeof(pMF->Track[0].LastNote[0]);
459
460 /*
461 ** Flush all
462 */
463 pEndPoints = (MIDI_END_POINT *)malloc(sz * sizeof(MIDI_END_POINT));
464 mx_pts = 0;
465 for(i=0;i<sz;++i)
466 if (pMF->Track[iTrack].LastNote[i].valid)
467 {
468 pEndPoints[mx_pts].iIdx = i;
469 pEndPoints[mx_pts].iEndPos = pMF->Track[iTrack].LastNote[i].end_pos;
470 mx_pts++;
471 }
472
473 if (bFlushToEnd)
474 {
475 if (mx_pts)
476 dwEndTimePos = pEndPoints[mx_pts-1].iEndPos;
477 else
478 dwEndTimePos = pMF->Track[iTrack].pos;
479 }
480
481 if (mx_pts)
482 {
483 /* Sort, smallest first, and add the note off msgs */
484 qsort(pEndPoints, mx_pts, sizeof(MIDI_END_POINT), qs_cmp_pEndPoints);
485
486 i = 0;
487 while ((dwEndTimePos >= (_DWORD)pEndPoints[i].iEndPos || bFlushToEnd) && i<mx_pts)
488 {
489 ptr = _midiGetPtr(pMF, iTrack, DT_DEF);
490 if (!ptr)
491 return FALSE;
492
493 num = pEndPoints[i].iIdx; /* get 'LastNote' index */
494
495 ptr = _midiWriteVarLen(ptr, pMF->Track[iTrack].LastNote[num].end_pos - pMF->Track[iTrack].pos);
496 /* msgNoteOn msgNoteOff */
497 *ptr++ = (BYTE)(msgNoteOff | pMF->Track[iTrack].LastNote[num].chn);
498 *ptr++ = pMF->Track[iTrack].LastNote[num].note;
499 *ptr++ = 0;
500
501 pMF->Track[iTrack].LastNote[num].valid = FALSE;
502 pMF->Track[iTrack].pos = pMF->Track[iTrack].LastNote[num].end_pos;
503
504 pMF->Track[iTrack].ptr = ptr;
505
506 ++i;
507 }
508 }
509
510 free((void *)pEndPoints);
511 /*
512 ** Re-calc current position
513 */
514 pMF->Track[iTrack].dt = dwEndTimePos - pMF->Track[iTrack].pos;
515
516 return TRUE;
517 }
518
midiFileSyncTracks(MIDI_FILE * _pMF,int iTrack1,int iTrack2)519 BOOL midiFileSyncTracks(MIDI_FILE *_pMF, int iTrack1, int iTrack2)
520 {
521 int p1, p2;
522
523 _VAR_CAST;
524 if (!IsFilePtrValid(pMF)) return FALSE;
525 if (!IsTrackValid(iTrack1)) return FALSE;
526 if (!IsTrackValid(iTrack2)) return FALSE;
527
528 p1 = pMF->Track[iTrack1].pos + pMF->Track[iTrack1].dt;
529 p2 = pMF->Track[iTrack2].pos + pMF->Track[iTrack2].dt;
530
531 if (p1 < p2) midiTrackIncTime(pMF, iTrack1, p2-p1, TRUE);
532 else if (p2 < p1) midiTrackIncTime(pMF, iTrack2, p1-p2, TRUE);
533
534 return TRUE;
535 }
536
537
midiFileClose(MIDI_FILE * _pMF)538 BOOL midiFileClose(MIDI_FILE *_pMF)
539 {
540 _VAR_CAST;
541 if (!IsFilePtrValid(pMF)) return FALSE;
542
543 if (pMF->bOpenForWriting)
544 {
545 WORD iNumTracks = 0;
546 WORD wTest = 256;
547 BOOL bSwap = FALSE;
548 int i;
549
550 /* Intel processor style-endians need byte swap :( */
551 if (*((BYTE *)&wTest) == 0)
552 bSwap = TRUE;
553
554 /* Flush our buffers */
555 for(i=0;i<MAX_MIDI_TRACKS;++i)
556 {
557 if (pMF->Track[i].ptr)
558 {
559 midiSongAddEndSequence(pMF, i);
560 midiFileFlushTrack(pMF, i, TRUE, 0);
561 iNumTracks++;
562 }
563 }
564 /*
565 ** Header
566 */
567 {
568 const BYTE mthd[4] = {'M', 'T', 'h', 'd'};
569 _DWORD dwData;
570 WORD wData;
571 WORD version, PPQN;
572
573 fwrite(mthd, sizeof(BYTE), 4, pMF->pFile);
574 dwData = 6;
575 if (bSwap) dwData = SWAP__DWORD(dwData);
576 fwrite(&dwData, sizeof(_DWORD), 1, pMF->pFile);
577
578 wData = (WORD)(iNumTracks==1?pMF->Header.iVersion:1);
579 if (bSwap) version = SWAP_WORD(wData); else version = (WORD)wData;
580 if (bSwap) iNumTracks = SWAP_WORD(iNumTracks);
581 wData = pMF->Header.PPQN;
582 if (bSwap) PPQN = SWAP_WORD(wData); else PPQN = wData;
583 fwrite(&version, sizeof(WORD), 1, pMF->pFile);
584 fwrite(&iNumTracks, sizeof(WORD), 1, pMF->pFile);
585 fwrite(&PPQN, sizeof(WORD), 1, pMF->pFile);
586 }
587 /*
588 ** Track data
589 */
590 for(i=0;i<MAX_MIDI_TRACKS;++i)
591 if (pMF->Track[i].ptr)
592 {
593 const BYTE mtrk[4] = {'M', 'T', 'r', 'k'};
594 _DWORD sz, dwData;
595
596 /* Write track header */
597 fwrite(&mtrk, sizeof(BYTE), 4, pMF->pFile);
598
599 /* Write data size */
600 sz = dwData = (int)(pMF->Track[i].ptr - pMF->Track[i].pBase);
601 if (bSwap) sz = SWAP__DWORD(sz);
602 fwrite(&sz, sizeof(_DWORD), 1, pMF->pFile);
603
604 /* Write data */
605 fwrite(pMF->Track[i].pBase, sizeof(BYTE), dwData, pMF->pFile);
606
607 /* Free memory */
608 free((void *)pMF->Track[i].pBase);
609 }
610
611 }
612
613 if (pMF->pFile)
614 return fclose(pMF->pFile)?FALSE:TRUE;
615 free((void *)pMF);
616 return TRUE;
617 }
618
619
620 /*
621 ** midiSong* Functions
622 */
midiSongAddSMPTEOffset(MIDI_FILE * _pMF,int iTrack,int iHours,int iMins,int iSecs,int iFrames,int iFFrames)623 BOOL midiSongAddSMPTEOffset(MIDI_FILE *_pMF, int iTrack, int iHours, int iMins, int iSecs, int iFrames, int iFFrames)
624 {
625 static BYTE tmp[] = {msgMetaEvent, metaSMPTEOffset, 0x05, 0,0,0,0,0};
626
627 _VAR_CAST;
628 if (!IsFilePtrValid(pMF)) return FALSE;
629 if (!IsTrackValid(iTrack)) return FALSE;
630
631 if (iMins<0 || iMins>59) iMins=0;
632 if (iSecs<0 || iSecs>59) iSecs=0;
633 if (iFrames<0 || iFrames>24) iFrames=0;
634
635 tmp[3] = (BYTE)iHours;
636 tmp[4] = (BYTE)iMins;
637 tmp[5] = (BYTE)iSecs;
638 tmp[6] = (BYTE)iFrames;
639 tmp[7] = (BYTE)iFFrames;
640 return midiTrackAddRaw(pMF, iTrack, sizeof(tmp), tmp, FALSE, 0);
641 }
642
643
midiSongAddSimpleTimeSig(MIDI_FILE * _pMF,int iTrack,int iNom,int iDenom)644 BOOL midiSongAddSimpleTimeSig(MIDI_FILE *_pMF, int iTrack, int iNom, int iDenom)
645 {
646 return midiSongAddTimeSig(_pMF, iTrack, iNom, iDenom, 24, 8);
647 }
648
midiSongAddTimeSig(MIDI_FILE * _pMF,int iTrack,int iNom,int iDenom,int iClockInMetroTick,int iNotated32nds)649 BOOL midiSongAddTimeSig(MIDI_FILE *_pMF, int iTrack, int iNom, int iDenom, int iClockInMetroTick, int iNotated32nds)
650 {
651 static BYTE tmp[] = {msgMetaEvent, metaTimeSig, 0x04, 0,0,0,0};
652
653 _VAR_CAST;
654 if (!IsFilePtrValid(pMF)) return FALSE;
655 if (!IsTrackValid(iTrack)) return FALSE;
656
657 tmp[3] = (BYTE)iNom;
658 tmp[4] = (BYTE)(MIDI_NOTE_MINIM/iDenom);
659 tmp[5] = (BYTE)iClockInMetroTick;
660 tmp[6] = (BYTE)iNotated32nds;
661 return midiTrackAddRaw(pMF, iTrack, sizeof(tmp), tmp, FALSE, 0);
662 }
663
midiSongAddKeySig(MIDI_FILE * _pMF,int iTrack,tMIDI_KEYSIG iKey)664 BOOL midiSongAddKeySig(MIDI_FILE *_pMF, int iTrack, tMIDI_KEYSIG iKey)
665 {
666 static BYTE tmp[] = {msgMetaEvent, metaKeySig, 0x02, 0, 0};
667
668 _VAR_CAST;
669 if (!IsFilePtrValid(pMF)) return FALSE;
670 if (!IsTrackValid(iTrack)) return FALSE;
671
672 tmp[3] = (BYTE)((iKey&keyMaskKey)*((iKey&keyMaskNeg)?-1:1));
673 tmp[4] = (BYTE)((iKey&keyMaskMin)?1:0);
674 return midiTrackAddRaw(pMF, iTrack, sizeof(tmp), tmp, FALSE, 0);
675 }
676
midiSongAddTempo(MIDI_FILE * _pMF,int iTrack,int iTempo)677 BOOL midiSongAddTempo(MIDI_FILE *_pMF, int iTrack, int iTempo)
678 {
679 static BYTE tmp[] = {msgMetaEvent, metaSetTempo, 0x03, 0,0,0};
680 int us; /* micro-seconds per qn */
681
682 _VAR_CAST;
683 if (!IsFilePtrValid(pMF)) return FALSE;
684 if (!IsTrackValid(iTrack)) return FALSE;
685
686 us = 60000000L/iTempo;
687 tmp[3] = (BYTE)((us>>16)&0xff);
688 tmp[4] = (BYTE)((us>>8)&0xff);
689 tmp[5] = (BYTE)((us>>0)&0xff);
690 return midiTrackAddRaw(pMF, iTrack, sizeof(tmp), tmp, FALSE, 0);
691 }
692
midiSongAddMIDIPort(MIDI_FILE * _pMF,int iTrack,int iPort)693 BOOL midiSongAddMIDIPort(MIDI_FILE *_pMF, int iTrack, int iPort)
694 {
695 static BYTE tmp[] = {msgMetaEvent, metaMIDIPort, 1, 0};
696
697 _VAR_CAST;
698 if (!IsFilePtrValid(pMF)) return FALSE;
699 if (!IsTrackValid(iTrack)) return FALSE;
700 tmp[3] = (BYTE)iPort;
701 return midiTrackAddRaw(pMF, iTrack, sizeof(tmp), tmp, FALSE, 0);
702 }
703
midiSongAddEndSequence(MIDI_FILE * _pMF,int iTrack)704 BOOL midiSongAddEndSequence(MIDI_FILE *_pMF, int iTrack)
705 {
706 static BYTE tmp[] = {msgMetaEvent, metaEndSequence, 0};
707
708 _VAR_CAST;
709 if (!IsFilePtrValid(pMF)) return FALSE;
710 if (!IsTrackValid(iTrack)) return FALSE;
711
712 return midiTrackAddRaw(pMF, iTrack, sizeof(tmp), tmp, FALSE, 0);
713 }
714
715
716 /*
717 ** midiTrack* Functions
718 */
midiTrackAddRaw(MIDI_FILE * _pMF,int iTrack,int data_sz,const BYTE * pData,BOOL bMovePtr,int dt)719 BOOL midiTrackAddRaw(MIDI_FILE *_pMF, int iTrack, int data_sz, const BYTE *pData, BOOL bMovePtr, int dt)
720 {
721 MIDI_FILE_TRACK *pTrk;
722 BYTE *ptr;
723 int dtime;
724
725 _VAR_CAST;
726 if (!IsFilePtrValid(pMF)) return FALSE;
727 if (!IsTrackValid(iTrack)) return FALSE;
728
729 pTrk = &pMF->Track[iTrack];
730 ptr = _midiGetPtr(pMF, iTrack, data_sz+DT_DEF);
731 if (!ptr)
732 return FALSE;
733
734 dtime = pTrk->dt;
735 if (bMovePtr)
736 dtime += dt;
737
738 ptr = _midiWriteVarLen(ptr, dtime);
739 memcpy(ptr, pData, data_sz);
740
741 pTrk->pos += dtime;
742 pTrk->dt = 0;
743 pTrk->ptr = ptr+data_sz;
744
745 return TRUE;
746 }
747
748
midiTrackIncTime(MIDI_FILE * _pMF,int iTrack,int iDeltaTime,BOOL bOverridePPQN)749 BOOL midiTrackIncTime(MIDI_FILE *_pMF, int iTrack, int iDeltaTime, BOOL bOverridePPQN)
750 {
751 _DWORD will_end_at;
752
753 _VAR_CAST;
754 if (!IsFilePtrValid(pMF)) return FALSE;
755 if (!IsTrackValid(iTrack)) return FALSE;
756
757 will_end_at = _midiGetLength(pMF->Header.PPQN, iDeltaTime, bOverridePPQN);
758 will_end_at += pMF->Track[iTrack].pos + pMF->Track[iTrack].dt;
759
760 midiFileFlushTrack(pMF, iTrack, FALSE, will_end_at);
761
762 return TRUE;
763 }
764
midiTrackAddText(MIDI_FILE * _pMF,int iTrack,tMIDI_TEXT iType,const char * pTxt)765 BOOL midiTrackAddText(MIDI_FILE *_pMF, int iTrack, tMIDI_TEXT iType, const char *pTxt)
766 {
767 BYTE *ptr;
768 int sz;
769
770 _VAR_CAST;
771 if (!IsFilePtrValid(pMF)) return FALSE;
772 if (!IsTrackValid(iTrack)) return FALSE;
773
774 sz = strlen(pTxt);
775 if ((ptr = _midiGetPtr(pMF, iTrack, sz+DT_DEF)))
776 {
777 *ptr++ = 0; /* delta-time=0 */
778 *ptr++ = msgMetaEvent;
779 *ptr++ = (BYTE)iType;
780 ptr = _midiWriteVarLen((BYTE *)ptr, sz);
781 strcpy((char *)ptr, pTxt);
782 pMF->Track[iTrack].ptr = ptr+sz;
783 return TRUE;
784 }
785 else
786 {
787 return FALSE;
788 }
789 }
790
midiTrackSetKeyPressure(MIDI_FILE * pMF,int iTrack,int iNote,int iAftertouch)791 BOOL midiTrackSetKeyPressure(MIDI_FILE *pMF, int iTrack, int iNote, int iAftertouch)
792 {
793 return midiTrackAddMsg(pMF, iTrack, msgNoteKeyPressure, iNote, iAftertouch);
794 }
795
midiTrackAddControlChange(MIDI_FILE * pMF,int iTrack,tMIDI_CC iCCType,int iParam)796 BOOL midiTrackAddControlChange(MIDI_FILE *pMF, int iTrack, tMIDI_CC iCCType, int iParam)
797 {
798 return midiTrackAddMsg(pMF, iTrack, msgControlChange, iCCType, iParam);
799 }
800
midiTrackAddProgramChange(MIDI_FILE * pMF,int iTrack,int iInstrPatch)801 BOOL midiTrackAddProgramChange(MIDI_FILE *pMF, int iTrack, int iInstrPatch)
802 {
803 return midiTrackAddMsg(pMF, iTrack, msgSetProgram, iInstrPatch, 0);
804 }
805
midiTrackChangeKeyPressure(MIDI_FILE * pMF,int iTrack,int iDeltaPressure)806 BOOL midiTrackChangeKeyPressure(MIDI_FILE *pMF, int iTrack, int iDeltaPressure)
807 {
808 return midiTrackAddMsg(pMF, iTrack, msgChangePressure, iDeltaPressure&0x7f, 0);
809 }
810
midiTrackSetPitchWheel(MIDI_FILE * pMF,int iTrack,int iWheelPos)811 BOOL midiTrackSetPitchWheel(MIDI_FILE *pMF, int iTrack, int iWheelPos)
812 {
813 WORD wheel = (WORD)iWheelPos;
814
815 /* bitshift 7 instead of eight because we're dealing with 7 bit numbers */
816 wheel += MIDI_WHEEL_CENTRE;
817 return midiTrackAddMsg(pMF, iTrack, msgSetPitchWheel, wheel&0x7f, (wheel>>7)&0x7f);
818 }
819
midiTrackAddMsg(MIDI_FILE * _pMF,int iTrack,tMIDI_MSG iMsg,int iParam1,int iParam2)820 BOOL midiTrackAddMsg(MIDI_FILE *_pMF, int iTrack, tMIDI_MSG iMsg, int iParam1, int iParam2)
821 {
822 BYTE *ptr;
823 BYTE data[3];
824 int sz;
825
826 _VAR_CAST;
827 if (!IsFilePtrValid(pMF)) return FALSE;
828 if (!IsTrackValid(iTrack)) return FALSE;
829 if (!IsMessageValid(iMsg)) return FALSE;
830
831 ptr = _midiGetPtr(pMF, iTrack, DT_DEF);
832 if (!ptr)
833 return FALSE;
834
835 data[0] = (BYTE)(iMsg | pMF->Track[iTrack].iDefaultChannel);
836 data[1] = (BYTE)(iParam1 & 0x7f);
837 data[2] = (BYTE)(iParam2 & 0x7f);
838 /*
839 ** Is this msg a single, or double BYTE, prm?
840 */
841 switch(iMsg)
842 {
843 case msgSetProgram: /* only one byte required for these msgs */
844 case msgChangePressure:
845 sz = 2;
846 break;
847
848 default: /* double byte messages */
849 sz = 3;
850 break;
851 }
852
853 return midiTrackAddRaw(pMF, iTrack, sz, data, FALSE, 0);
854
855 }
856
midiTrackAddNote(MIDI_FILE * _pMF,int iTrack,int iNote,int iLength,int iVol,BOOL bAutoInc,BOOL bOverrideLength)857 BOOL midiTrackAddNote(MIDI_FILE *_pMF, int iTrack, int iNote, int iLength, int iVol, BOOL bAutoInc, BOOL bOverrideLength)
858 {
859 MIDI_FILE_TRACK *pTrk;
860 BYTE *ptr;
861 BOOL bSuccess = FALSE;
862 int i, chn;
863
864 _VAR_CAST;
865 if (!IsFilePtrValid(pMF)) return FALSE;
866 if (!IsTrackValid(iTrack)) return FALSE;
867 if (!IsNoteValid(iNote)) return FALSE;
868
869 pTrk = &pMF->Track[iTrack];
870 ptr = _midiGetPtr(pMF, iTrack, DT_DEF);
871 if (!ptr)
872 return FALSE;
873
874 chn = pTrk->iDefaultChannel;
875 iLength = _midiGetLength(pMF->Header.PPQN, iLength, bOverrideLength);
876
877 for(i=0;i<sizeof(pTrk->LastNote)/sizeof(pTrk->LastNote[0]);++i)
878 if (pTrk->LastNote[i].valid == FALSE)
879 {
880 pTrk->LastNote[i].note = (BYTE)iNote;
881 pTrk->LastNote[i].chn = (BYTE)chn;
882 pTrk->LastNote[i].end_pos = pTrk->pos+pTrk->dt+iLength;
883 pTrk->LastNote[i].valid = TRUE;
884 bSuccess = TRUE;
885
886 ptr = _midiWriteVarLen(ptr, pTrk->dt); /* delta-time */
887 *ptr++ = (BYTE)(msgNoteOn | chn);
888 *ptr++ = (BYTE)iNote;
889 *ptr++ = (BYTE)iVol;
890 break;
891 }
892
893 if (!bSuccess)
894 return FALSE;
895
896 pTrk->ptr = ptr;
897
898 pTrk->pos += pTrk->dt;
899 pTrk->dt = 0;
900
901 if (bAutoInc)
902 return midiTrackIncTime(pMF, iTrack, iLength, bOverrideLength);
903
904 return TRUE;
905 }
906
midiTrackAddRest(MIDI_FILE * _pMF,int iTrack,int iLength,BOOL bOverridePPQN)907 BOOL midiTrackAddRest(MIDI_FILE *_pMF, int iTrack, int iLength, BOOL bOverridePPQN)
908 {
909 _VAR_CAST;
910 if (!IsFilePtrValid(pMF)) return FALSE;
911 if (!IsTrackValid(iTrack)) return FALSE;
912
913 iLength = _midiGetLength(pMF->Header.PPQN, iLength, bOverridePPQN);
914 return midiTrackIncTime(pMF, iTrack, iLength, bOverridePPQN);
915 }
916
midiTrackGetEndPos(MIDI_FILE * _pMF,int iTrack)917 int midiTrackGetEndPos(MIDI_FILE *_pMF, int iTrack)
918 {
919 _VAR_CAST;
920 if (!IsFilePtrValid(pMF)) return FALSE;
921 if (!IsTrackValid(iTrack)) return FALSE;
922
923 return pMF->Track[iTrack].pos;
924 }
925
926 /*
927 ** midiRead* Functions
928 */
_midiReadVarLen(BYTE * ptr,_DWORD * num)929 static BYTE *_midiReadVarLen(BYTE *ptr, _DWORD *num)
930 {
931 register _DWORD value;
932 register BYTE c;
933
934 if ((value = *ptr++) & 0x80)
935 {
936 value &= 0x7f;
937 do
938 {
939 value = (value << 7) + ((c = *ptr++) & 0x7f);
940 } while (c & 0x80);
941 }
942 *num = value;
943 return(ptr);
944 }
945
946
_midiReadTrackCopyData(MIDI_MSG * pMsg,BYTE * ptr,_DWORD sz,BOOL bCopyPtrData)947 static BOOL _midiReadTrackCopyData(MIDI_MSG *pMsg, BYTE *ptr, _DWORD sz, BOOL bCopyPtrData)
948 {
949 if (sz > pMsg->data_sz)
950 {
951 pMsg->data = (BYTE *)realloc(pMsg->data, sz);
952 pMsg->data_sz = sz;
953 }
954
955 if (!pMsg->data)
956 return FALSE;
957
958 if (bCopyPtrData && ptr)
959 memcpy(pMsg->data, ptr, sz);
960
961 return TRUE;
962 }
963
midiReadGetNumTracks(const MIDI_FILE * _pMF)964 int midiReadGetNumTracks(const MIDI_FILE *_pMF)
965 {
966 _VAR_CAST;
967 return pMF->Header.iNumTracks;
968 }
969
midiReadGetNextMessage(const MIDI_FILE * _pMF,int iTrack,MIDI_MSG * pMsg)970 BOOL midiReadGetNextMessage(const MIDI_FILE *_pMF, int iTrack, MIDI_MSG *pMsg)
971 {
972 MIDI_FILE_TRACK *pTrack;
973 BYTE *bptr, *pMsgDataPtr;
974 int sz;
975
976 _VAR_CAST;
977 if (!IsTrackValid(iTrack)) return FALSE;
978
979 pTrack = &pMF->Track[iTrack];
980 /* FIXME: Check if there is data on this track first!!! */
981 if (pTrack->ptr >= pTrack->pEnd)
982 return FALSE;
983
984 pTrack->ptr = _midiReadVarLen(pTrack->ptr, &pMsg->dt);
985 pTrack->pos += pMsg->dt;
986
987 pMsg->dwAbsPos = pTrack->pos;
988
989 if (*pTrack->ptr & 0x80) /* Is this is sys message */
990 {
991 pMsg->iType = (tMIDI_MSG)((*pTrack->ptr) & 0xf0);
992 pMsgDataPtr = pTrack->ptr+1;
993
994 /* SysEx & Meta events don't carry channel info, but something
995 ** important in their lower bits that we must keep */
996 if (pMsg->iType == 0xf0)
997 pMsg->iType = (tMIDI_MSG)(*pTrack->ptr);
998 }
999 else /* just data - so use the last msg type */
1000 {
1001 pMsg->iType = pMsg->iLastMsgType;
1002 pMsgDataPtr = pTrack->ptr;
1003 }
1004
1005 pMsg->iLastMsgType = (tMIDI_MSG)pMsg->iType;
1006 pMsg->iLastMsgChnl = (BYTE)((*pTrack->ptr) & 0x0f)+1;
1007
1008 switch(pMsg->iType)
1009 {
1010 case msgNoteOn:
1011 pMsg->MsgData.NoteOn.iChannel = pMsg->iLastMsgChnl;
1012 pMsg->MsgData.NoteOn.iNote = *(pMsgDataPtr);
1013 pMsg->MsgData.NoteOn.iVolume = *(pMsgDataPtr+1);
1014 pMsg->iMsgSize = 3;
1015 break;
1016
1017 case msgNoteOff:
1018 pMsg->MsgData.NoteOff.iChannel = pMsg->iLastMsgChnl;
1019 pMsg->MsgData.NoteOff.iNote = *(pMsgDataPtr);
1020 pMsg->iMsgSize = 3;
1021 break;
1022
1023 case msgNoteKeyPressure:
1024 pMsg->MsgData.NoteKeyPressure.iChannel = pMsg->iLastMsgChnl;
1025 pMsg->MsgData.NoteKeyPressure.iNote = *(pMsgDataPtr);
1026 pMsg->MsgData.NoteKeyPressure.iPressure = *(pMsgDataPtr+1);
1027 pMsg->iMsgSize = 3;
1028 break;
1029
1030 case msgSetParameter:
1031 pMsg->MsgData.NoteParameter.iChannel = pMsg->iLastMsgChnl;
1032 pMsg->MsgData.NoteParameter.iControl = (tMIDI_CC)*(pMsgDataPtr);
1033 pMsg->MsgData.NoteParameter.iParam = *(pMsgDataPtr+1);
1034 pMsg->iMsgSize = 3;
1035 break;
1036
1037 case msgSetProgram:
1038 pMsg->MsgData.ChangeProgram.iChannel = pMsg->iLastMsgChnl;
1039 pMsg->MsgData.ChangeProgram.iProgram = *(pMsgDataPtr);
1040 pMsg->iMsgSize = 2;
1041 break;
1042
1043 case msgChangePressure:
1044 pMsg->MsgData.ChangePressure.iChannel = pMsg->iLastMsgChnl;
1045 pMsg->MsgData.ChangePressure.iPressure = *(pMsgDataPtr);
1046 pMsg->iMsgSize = 2;
1047 break;
1048
1049 case msgSetPitchWheel:
1050 pMsg->MsgData.PitchWheel.iChannel = pMsg->iLastMsgChnl;
1051 pMsg->MsgData.PitchWheel.iPitch = *(pMsgDataPtr) | (*(pMsgDataPtr+1) << 7);
1052 pMsg->MsgData.PitchWheel.iPitch -= MIDI_WHEEL_CENTRE;
1053 pMsg->iMsgSize = 3;
1054 break;
1055
1056 case msgMetaEvent:
1057 /* We can use 'pTrack->ptr' from now on, since meta events
1058 ** always have bit 7 set */
1059 bptr = pTrack->ptr;
1060 pMsg->MsgData.MetaEvent.iType = (tMIDI_META)*(pTrack->ptr+1);
1061 pTrack->ptr = _midiReadVarLen(pTrack->ptr+2, &pMsg->iMsgSize);
1062 sz = (pTrack->ptr-bptr)+pMsg->iMsgSize;
1063
1064 if (_midiReadTrackCopyData(pMsg, pTrack->ptr, sz, FALSE) == FALSE)
1065 return FALSE;
1066
1067 /* Now copy the data...*/
1068 memcpy(pMsg->data, bptr, sz);
1069
1070 /* Now place it in a neat structure */
1071 switch(pMsg->MsgData.MetaEvent.iType)
1072 {
1073 case metaMIDIPort:
1074 pMsg->MsgData.MetaEvent.Data.iMIDIPort = *(pTrack->ptr+0);
1075 break;
1076 case metaSequenceNumber:
1077 pMsg->MsgData.MetaEvent.Data.iSequenceNumber = *(pTrack->ptr+0);
1078 break;
1079 case metaTextEvent:
1080 case metaCopyright:
1081 case metaTrackName:
1082 case metaInstrument:
1083 case metaLyric:
1084 case metaMarker:
1085 case metaCuePoint:
1086 /* TODO - Add NULL terminator ??? */
1087 pMsg->MsgData.MetaEvent.Data.Text.pData = pTrack->ptr;
1088 break;
1089 case metaEndSequence:
1090 /* NO DATA */
1091 break;
1092 case metaSetTempo:
1093 {
1094 _DWORD us = ((*(pTrack->ptr+0))<<16)|((*(pTrack->ptr+1))<<8)|(*(pTrack->ptr+2));
1095 pMsg->MsgData.MetaEvent.Data.Tempo.iBPM = 60000000L/us;
1096 }
1097 break;
1098 case metaSMPTEOffset:
1099 pMsg->MsgData.MetaEvent.Data.SMPTE.iHours = *(pTrack->ptr+0);
1100 pMsg->MsgData.MetaEvent.Data.SMPTE.iMins= *(pTrack->ptr+1);
1101 pMsg->MsgData.MetaEvent.Data.SMPTE.iSecs = *(pTrack->ptr+2);
1102 pMsg->MsgData.MetaEvent.Data.SMPTE.iFrames = *(pTrack->ptr+3);
1103 pMsg->MsgData.MetaEvent.Data.SMPTE.iFF = *(pTrack->ptr+4);
1104 break;
1105 case metaTimeSig:
1106 pMsg->MsgData.MetaEvent.Data.TimeSig.iNom = *(pTrack->ptr+0);
1107 pMsg->MsgData.MetaEvent.Data.TimeSig.iDenom = *(pTrack->ptr+1) * MIDI_NOTE_MINIM;
1108 /* TODO: Variations without 24 & 8 */
1109 break;
1110 case metaKeySig:
1111 if (*pTrack->ptr & 0x80)
1112 {
1113 /* Do some trendy sign extending in reverse :) */
1114 pMsg->MsgData.MetaEvent.Data.KeySig.iKey = ((256-*pTrack->ptr)&keyMaskKey);
1115 pMsg->MsgData.MetaEvent.Data.KeySig.iKey |= keyMaskNeg;
1116 }
1117 else
1118 {
1119 pMsg->MsgData.MetaEvent.Data.KeySig.iKey = (tMIDI_KEYSIG)(*pTrack->ptr&keyMaskKey);
1120 }
1121 if (*(pTrack->ptr+1))
1122 pMsg->MsgData.MetaEvent.Data.KeySig.iKey |= keyMaskMin;
1123 break;
1124 case metaSequencerSpecific:
1125 pMsg->MsgData.MetaEvent.Data.Sequencer.iSize = pMsg->iMsgSize;
1126 pMsg->MsgData.MetaEvent.Data.Sequencer.pData = pTrack->ptr;
1127 break;
1128 }
1129
1130 pTrack->ptr += pMsg->iMsgSize;
1131 pMsg->iMsgSize = sz;
1132 break;
1133
1134 case msgSysEx1:
1135 case msgSysEx2:
1136 bptr = pTrack->ptr;
1137 pTrack->ptr = _midiReadVarLen(pTrack->ptr+1, &pMsg->iMsgSize);
1138 sz = (pTrack->ptr-bptr)+pMsg->iMsgSize;
1139
1140 if (_midiReadTrackCopyData(pMsg, pTrack->ptr, sz, FALSE) == FALSE)
1141 return FALSE;
1142
1143 /* Now copy the data... */
1144 memcpy(pMsg->data, bptr, sz);
1145 pTrack->ptr += pMsg->iMsgSize;
1146 pMsg->iMsgSize = sz;
1147 pMsg->MsgData.SysEx.pData = pMsg->data;
1148 pMsg->MsgData.SysEx.iSize = sz;
1149 break;
1150 }
1151 /*
1152 ** Standard MIDI messages use a common copy routine
1153 */
1154 pMsg->bImpliedMsg = FALSE;
1155 if ((pMsg->iType&0xf0) != 0xf0)
1156 {
1157 if (*pTrack->ptr & 0x80)
1158 {
1159 }
1160 else
1161 {
1162 pMsg->bImpliedMsg = TRUE;
1163 pMsg->iImpliedMsg = pMsg->iLastMsgType;
1164 pMsg->iMsgSize--;
1165 }
1166 _midiReadTrackCopyData(pMsg, pTrack->ptr, pMsg->iMsgSize, TRUE);
1167 pTrack->ptr+=pMsg->iMsgSize;
1168 }
1169 return TRUE;
1170 }
1171
midiReadInitMessage(MIDI_MSG * pMsg)1172 void midiReadInitMessage(MIDI_MSG *pMsg)
1173 {
1174 pMsg->data = NULL;
1175 pMsg->data_sz = 0;
1176 pMsg->bImpliedMsg = FALSE;
1177 }
1178
midiReadFreeMessage(MIDI_MSG * pMsg)1179 void midiReadFreeMessage(MIDI_MSG *pMsg)
1180 {
1181 if (pMsg->data) free((void *)pMsg->data);
1182 pMsg->data = NULL;
1183 }
1184