/* rdmid.c version 0.21, december 30, 2019. C implementation sourcefile. Include this file in your project or makefile. RDMID, a portable, thread-safe MIDI file parser. Latest version available at: https://ecomaan.nl/c/rdmid Copyright (c) 2004, 2008, 2019 - Pieter Suurmond Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. Any person wishing to distribute modifications to the Software is requested to send the modifications to the original developer so that they can be incorporated into the canonical version. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. To read from the open stream, we only use 'getc()', nothing else. No global variables. No static data. */ #include /* For malloc() and free(). */ #include /* For getc() and FILE*. */ #include "rdmid.h" /* Prototypes and return codes. */ /* TEMPLATES FOR THE 21 EVENT HANDLERS THAT YOU MAY PROVIDE FOR: ============================================================= Below follow the 21 silent (i.e. dummy) handlers that are used by default. They actually reside here as templates, for you to copy them and alter them. Please make no edits in the file below. Instead, copy the event handlers that you are interested in to your source. Take care, however, NOT to prepend the word 'static' to the event handler in YOUR source (since it must be 'called back' from code within this file). */ /* 3 event handlers for headers and tracks: */ static int rdmid_header(void* userdata, int format, int tracks, int division) { (void)userdata; /* Prevent warnings for unused parameters. */ (void)format; (void)tracks; (void)division; /* printf("Header: format=%d, tracks=%d, div=%d.\n", format, tracks, division); */ return 0; } static int rdmid_trackstart(void* userdata, int track) { (void)userdata; (void)track; /* printf("Track #%d start.\n", track); */ return 0; } static int rdmid_trackend(void* userdata, int track) { (void)userdata; (void)track; /* printf("Track #%d end.\n", track); */ return 0; } /* 7 for channel messages: */ static int rdmid_note_off(void* userdata, long time, int channel, int note, int velocity) { (void)userdata; (void)time; (void)channel; (void)note; (void)velocity; /* printf("%8ld: NOTE OFF: ch=%d, note=%d, vel=%d.\n", time, channel, note, velocity); */ return 0; } static int rdmid_note_on(void* userdata, long time, int channel, int note, int velocity) { (void)userdata; (void)time; (void)channel; (void)note; (void)velocity; /* printf("%8ld: NOTE ON: ch=%d, note=%d, vel=%d.\n", time, channel, note, velocity); */ return 0; } static int rdmid_poly_press(void* userdata, long time, int channel, int note, int pressure) { (void)userdata; (void)time; (void)channel; (void)note; (void)pressure; /* printf("%8ld: POLY PRESS: ch=%d, note=%d, press=%d.\n", time, channel, note, pressure); */ return 0; } static int rdmid_controller(void* userdata, long time, int channel, int number, int value) { (void)userdata; (void)time; (void)channel; (void)number; (void)value; /* printf("%8ld: CONTROL: ch=%d, num=%d, val=%d (7 bit unsigned).\n", time, channel, number, value); */ return 0; } static int rdmid_program(void* userdata, long time, int channel, int number) { (void)userdata; (void)time; (void)channel; (void)number; /* printf("%8ld: PROGRAM: ch=%d, num=%d.\n", time, channel, number); */ return 0; } static int rdmid_chan_press(void* userdata, long time, int channel, int pressure) { (void)userdata; (void)time; (void)channel; (void)pressure; /* printf("%8ld: CHAN PRESS: ch=%d, press=%d.\n", time, channel, pressure); */ return 0; } static int rdmid_pitchbend (void* userdata, long time, int channel, int bend) { (void)userdata; (void)time; (void)channel; (void)bend; /* printf("%8ld: PITCH BEND: ch=%d, val=%d (14 bit signed).\n", time, channel, bend); */ return 0; } /* 1 event handler for system exclusive messages: */ static int rdmid_sysex(void* userdata, long time, long length, char *data) { (void)userdata; (void)time; (void)length; (void)data; /* printf("%8ld: SYSEX (%ld bytes): ", time, length); while (length--) printf("%02X", (int)(*data++)); printf(".\n"); */ return 0; } /* And 10 handlers for meta events: */ static int rdmid_seqnum(void* userdata, long time, int number) { (void)userdata; (void)time; (void)number; /* Prevent warnings for unused arguments. */ /* printf("%8ld: META seqnum: num=%d (16 bit).\n", time, number); */ return 0; } static int rdmid_text(void* userdata, long time, int type, long length, char *data) { (void)userdata; (void)time; (void)type; (void)length; (void)data; /* printf("%8ld: META text: type=%d, ", time, type); while (length--) // Although data is null-terminated by now, printf("%c", *data++); // we don't ignore the length-parameter. printf(".\n"); */ return 0; } static int rdmid_eot(void* userdata, long time) { (void)userdata; (void)time; /* printf("%8ld: META end of track.\n", time); */ return 0; } static int rdmid_tempo(void* userdata, long time, long tempo) { (void)userdata; (void)time; (void)tempo; /* printf("%8ld: META tempo: tempo=%ld (24 bit unsigned).\n", time, tempo); */ return 0; } static int rdmid_smpte(void* userdata, long time, int hour, int min, int sec, int frame, int frac) { (void)userdata; (void)time; (void)hour; (void)min; (void)sec; (void)frame; (void)frac; /* printf("%8ld: META smtpe: %02d:%02d:%02d frame=%d, frac=%d.\n", time, hour, min, sec, frame, frac); */ return 0; } static int rdmid_timesig(void* userdata, long time, int numer, int denom, int mclck, int m32) { (void)userdata; (void)time; (void)numer; (void)denom; (void)mclck; (void)m32; /* printf("%8ld: META timesig: %d/%d, mclck=%d, m32=%d.\n", time, numer, denom, mclck, m32); */ return 0; } static int rdmid_keysig(void* userdata, long time, int sharps, int minor) { (void)userdata; (void)time; (void)sharps; (void)minor; /* printf("%8ld: META keysig: sharps/flats=%d, minor=%d.\n", time, sharps, minor); */ return 0; } static int rdmid_seq_spec(void* userdata, long time, long length, char *data) { (void)userdata; (void)time; (void)length; (void)data; /* printf("%8ld: META seq spec (%ld bytes): ", time, length); while (length--) printf("%02X", (int)(*data++)); printf(".\n"); */ return 0; } static int rdmid_metamisc(void* userdata, long time, int type, long length, char *data) { (void)userdata; (void)time; (void)type; (void)length; (void)data; /* printf("%8ld: META misc (%ld bytes): type=%d", time, length, type); while (length--) printf("%02X", (int)(*data++)); printf(".\n"); */ return 0; } static int rdmid_arbitrary(void* userdata, long time, long length, char *data) { (void)userdata; (void)time; (void)length; (void)data; /* printf("%8ld: META arbitrary (%ld bytes): ", time, length); while (length--) printf("%02X", (int)(*data++)); printf(".\n"); */ return 0; } /* END OF 21 DUMMY / TEMPLATE HANDLERS =================================== */ typedef struct /* Private use in this file only. */ { void* userdata; /* Here for convenience. */ FILE* fp; /* Open stream, also here for convenience. */ char* buffer; /* Pointer to buffer for sysex and meta, or NULL. */ unsigned long size; /* Maximum size. Notice buffer may NOT be null-terminated! */ unsigned long index; /* Current size. */ short no_merge; /* 0 is the default: continued sysex messages are merged. */ /* 21 event handlers. They are initialised to the dummy- */ int (*eh_header)(); /* functions above but can be overruled by the user. */ int (*eh_trackstart)(); int (*eh_trackend)(); int (*eh_note_off)(); int (*eh_note_on)(); int (*eh_poly_press)(); int (*eh_controller)(); int (*eh_program)(); int (*eh_chan_press)(); int (*eh_pitchbend)(); int (*eh_sysex)(); int (*eh_seqnum)(); int (*eh_text)(); int (*eh_eot)(); int (*eh_tempo)(); int (*eh_smpte)(); int (*eh_timesig)(); int (*eh_keysig)(); int (*eh_seq_spec)(); int (*eh_metamisc)(); int (*eh_arbitrary)(); } RDMID_PRIV; /* Memory management for sysex and meta messages. */ static void msg_init(RDMID_PRIV* priv) { priv->index = (unsigned long)0L; } static short msg_add(RDMID_PRIV* priv, char c) { if (priv->index >= priv->size) { /* If necessary, allocate a larger buffer. */ char* larger; unsigned long i; priv->size += 1024L; /* Grow by 1 kilobyte each time. */ if (priv->size > 0x7FFFFFFF) /* Our API uses a SIGNED long for length! */ return RDMID_TOO_BIG; larger = malloc(priv->size); if (larger != NULL) { for (i = 0; i < priv->index; i++) /* Copy. */ larger[i] = priv->buffer[i]; free(priv->buffer); priv->buffer = larger; } else return RDMID_MEM_FAIL; } priv->buffer[priv->index++] = c; return RDMID_OK; } static int meta_event(RDMID_PRIV* priv, long time, short type) { short e = RDMID_OK; unsigned char* b = (unsigned char*)priv->buffer; switch (type) { case 0x00: if (priv->eh_seqnum(priv->userdata, time, ((int)b[0] << 8) | (int)b[1])) e = RDMID_USER_TERM; /* 0..65535. */ break; case 0x01: /* Text event */ case 0x02: /* Copyright notice */ case 0x03: /* Sequence/Track name */ case 0x04: /* Instrument name */ case 0x05: /* Lyric */ case 0x06: /* Marker */ case 0x07: /* Cue point */ case 0x08: case 0x09: case 0x0a: case 0x0b: case 0x0c: case 0x0d: case 0x0e: case 0x0f: /* These are all text events. Make them valid C-strings: null-terminate. */ if (!(e = msg_add(priv, (char)0))) /* May fail with RDMID_MEM_FAIL. */ { if (priv->eh_text(priv->userdata, time, type, (long)priv->index - 1, priv->buffer)) e = RDMID_USER_TERM; } break; case 0x2f: if (priv->eh_eot(priv->userdata, time)) /* End of Track */ e = RDMID_USER_TERM; break; case 0x51: if (priv->eh_tempo(priv->userdata, time, ((((long)b[0] << 8) | (long)b[1]) << 8) | (long)b[2])) e = RDMID_USER_TERM; break; case 0x54: if (priv->eh_smpte(priv->userdata, time, (int)b[0], (int)b[1], (int)b[2], (int)b[3], (int)b[4])) e = RDMID_USER_TERM; break; case 0x58: /* Calc real denominator from negative power of 2. */ if (priv->eh_timesig(priv->userdata, time, (int)b[0], (int)1 << b[1], (int)b[2], (int)b[3])) e = RDMID_USER_TERM; break; case 0x59: /* -8 < sharps < +8; minor = 0 or 1. */ if (priv->eh_keysig(priv->userdata, time, (int)((signed char)b[0]), (int)b[1])) e = RDMID_USER_TERM; break; case 0x7f: /* char*. */ if (priv->eh_seq_spec(priv->userdata, time, (long)priv->index, priv->buffer)) e = RDMID_USER_TERM; break; default: /* char*. */ if (priv->eh_metamisc(priv->userdata, time, type, (long)priv->index, priv->buffer)) e = RDMID_USER_TERM; break; } return e; } static int channel_message(RDMID_PRIV* priv, long time, int status, int databyte1, int databyte2) { int channel = status & 0x0F; switch (status & 0xF0) { case 0x80: return priv->eh_note_off (priv->userdata, time, channel, databyte1, databyte2); case 0x90: return priv->eh_note_on (priv->userdata, time, channel, databyte1, databyte2); case 0xA0: return priv->eh_poly_press(priv->userdata, time, channel, databyte1, databyte2); case 0xB0: return priv->eh_controller(priv->userdata, time, channel, databyte1, databyte2); case 0xC0: return priv->eh_program (priv->userdata, time, channel, databyte1); case 0xD0: return priv->eh_chan_press(priv->userdata, time, channel, databyte1); case 0xE0: return priv->eh_pitchbend (priv->userdata, time, channel, /* Bipolar 14 bit. */ ((databyte2 << 7) | databyte1) - 8192); } return RDMID_OK; /* Should never occur! */ } static int check_string(FILE* fp, const char* s) { int i; while (*s) { if ((i = getc(fp)) < 0) return RDMID_EOF; /* Premature EOF. */ if (*s++ != (char)i) return RDMID_BAD_MT; /* Different MT-string. */ } return RDMID_OK; } static int read_u32(FILE* fp, unsigned long* u32) { short n; int i; *u32 = (unsigned long)0; for (n = 0; n < 4; n++) { if ((i = getc(fp)) < 0) return RDMID_EOF; /* Premature EOF. */ *u32 <<= 8; *u32 |= (unsigned long)i; } return RDMID_OK; /* Ok. */ } static int read_u16(FILE* fp, unsigned long* available, unsigned short* u16) { int i; if (*available < 2L) return RDMID_CHUNK_END; if ((i = getc(fp)) < 0) return RDMID_EOF; /* Premature EOF. */ (*available)--; *u16 = (unsigned short)(i << 8); if ((i = getc(fp)) < 0) return RDMID_EOF; /* Premature EOF. */ (*available)--; *u16 |= (unsigned short)i; return RDMID_OK; /* Ok. */ } static int read_header(RDMID_PRIV* priv, unsigned short* tracks) { unsigned long togo; unsigned short format, division; int e; if ((e = check_string(priv->fp, "MThd"))) return e; if ((e = read_u32(priv->fp, &togo))) return e; if ((e = read_u16(priv->fp, &togo, &format))) return e; if ((e = read_u16(priv->fp, &togo, tracks))) return e; if ((e = read_u16(priv->fp, &togo, &division))) return e; if (priv->eh_header(priv->userdata, (int)format, (int)(*tracks), (int)division)) return RDMID_USER_TERM; /* User terminated the parser. */ /* flush any extra stuff, in case the length of header is not 6. */ while (togo--) { if (getc(priv->fp) < 0) return RDMID_EOF; /* Premature end of file. */ } return RDMID_OK; /* Ok. */ } /* Read a varying-length number. But never more than *available, and never more than 4 bytes. Also decrements *available. Returns nonzero in case of EOF and other errors. Called by read_track() a number of times. */ static int read_varlen(FILE* fp, unsigned long* available, unsigned long* u32) { int c; short max_read = 4; *u32 = (unsigned long)0L; while (max_read--) { if (!(*available)--) return RDMID_CHUNK_END; /* More was promised. */ if ((c = getc(fp)) < 0) return RDMID_EOF; /* Premature end of file. */ if (c & 0x80) { (*u32) |= (unsigned long)(c & 0x7F); (*u32) <<= 7; } else { (*u32) |= (unsigned long)c; return RDMID_OK; /* Ok. */ } } return RDMID_TOO_BIG; /* More than 4 bytes. */ } static int read_track(RDMID_PRIV* priv, int track) /* Read a track chunk. */ { int e, c; unsigned long v, togo, curr_time = 0L; /* Current time in delta-time units. */ int status = 0; int databyte1, databyte2 = 0; short sysex_cont = 0; /* 1 if last msg was unfinished sysex. */ if ((e = check_string(priv->fp, "MTrk"))) return e; /* Wrong track or EOF. */ if ((e = read_u32(priv->fp, &togo))) return e; /* Premature EOF. */ if (priv->eh_trackstart(priv->userdata, track)) return RDMID_USER_TERM; /* User terminate. */ while (togo) { /* Read 32-bit delta time: */ if ((e = read_varlen(priv->fp, &togo, &v))) /* Receive 32 unsigned bits in v, */ return e; /* but also decrement (-4) togo. */ curr_time += v; /* Increment absolute time. */ if (curr_time > 0x7FFFFFFF) /* API uses a SIGNED long for time! */ return RDMID_TOO_BIG; /* Never return times < 0. */ /* Read 1 byte: */ if (!(togo--)) return RDMID_CHUNK_END; if ((c = getc(priv->fp)) < 0) return RDMID_EOF; if (c & 0x80) /* Statusbyte (msbit set)? */ { if (c < 0xF0) /* Channel message? */ { status = c; /* Remember for running status (which may be interrupted by non-channel messages (like sysex, meta events, etc.), many thanks to John Schwartz for poining that out). */ if (!(togo--)) return RDMID_CHUNK_END; if ((databyte1 = getc(priv->fp)) < 0) return RDMID_EOF; if ((status < 0xC0) || (status >= 0xE0)) /* Only program change (C) and */ { /* channel pressure (D) messa- */ if (!(togo--)) return RDMID_CHUNK_END; /* ges use only one databyte. */ if ((databyte2 = getc(priv->fp)) < 0) return RDMID_EOF; } if (channel_message(priv, (long)curr_time, status, databyte1, databyte2)) return RDMID_USER_TERM; } else /* Non-channel message: */ { unsigned long lookfor; if (sysex_cont && (c != 0xF7)) /* Sysex-continuation (F7) must directly */ return RDMID_SYSEX_CONT; /* follow unfinished sysex (F0). */ if (c == 0xFF) /* Meta event? */ { int type; if (!(togo--)) return RDMID_CHUNK_END; if ((type = getc(priv->fp)) < 0) return RDMID_EOF; if ((e = read_varlen(priv->fp, &togo, &v))) /* Receive 32 unsigned bits in v. */ return e; lookfor = togo - v; msg_init(priv); /* Can never fail. */ while (togo > lookfor) { if (!(togo--)) return RDMID_CHUNK_END; if ((c = getc(priv->fp)) < 0) return RDMID_EOF; if ((e = msg_add(priv, c))) return e; /* May fail with RDMID_MEM_FAIL. */ } if ((e = meta_event(priv, (long)curr_time, type))) return e; /* 0 <= t <= 0x7FFFFFFF! */ } /* Function meta_event() may also return memory-failure. */ else if (c == 0xF0) /* Start of system exclusive? */ { if ((e = read_varlen(priv->fp, &togo, &v))) return e; lookfor = togo - v; msg_init(priv); if ((e = msg_add(priv, 0xF0))) return e; while (togo > lookfor) { if (!(togo--)) return RDMID_CHUNK_END; if ((c = getc(priv->fp)) < 0) return RDMID_EOF; if ((e = msg_add(priv, c))) return e; } if ((c == 0xF7) || priv->no_merge) /* Fixed: was (!priv->no_merge) in v0.10. */ { if (priv->eh_sysex(priv->userdata, (long)curr_time, (long)priv->index, priv->buffer)) return RDMID_USER_TERM; } else sysex_cont = 1; /* Merge into the next message. */ } else if (c == 0xF7) /* Sysex continuation or arbitrary stuff? */ { if ((e = read_varlen(priv->fp, &togo, &v))) /* Receive 32 bits in v. */ return e; lookfor = togo - v; if (!sysex_cont) msg_init(priv); while (togo > lookfor) { if (!(togo--)) return RDMID_CHUNK_END; if ((c = getc(priv->fp)) < 0) return RDMID_EOF; if ((e = msg_add(priv, c))) return e; } if (!sysex_cont) { if (priv->eh_arbitrary(priv->userdata, (long)curr_time, (long)priv->index, priv->buffer)) return RDMID_USER_TERM; } else if (c == 0xF7) { if (priv->eh_sysex(priv->userdata, (long)curr_time, (long)priv->index, priv->buffer)) return RDMID_USER_TERM; sysex_cont = 0; } } else if (c == 0xFE) /* Active sensing, thanks to John Schwartz. */ { if (!(togo--)) return RDMID_CHUNK_END; if ((c = getc(priv->fp)) < 0) return RDMID_EOF; if (!(togo--)) return RDMID_CHUNK_END; if ((c = getc(priv->fp)) < 0) return RDMID_EOF; } else if (c == 0xF8) /* Midi clock, thanks to John Schwartz. */ { } else return RDMID_BAD_BYTE; } /* End of non-channel message. */ } /* End of statusbyte. */ else /* Thus a databyte (msbit not set): running status apparently: */ { if (!status) /* Did we read a statusbyte prior to this databyte? */ return RDMID_RUN_STATUS; databyte1 = c; /* We've already read the first databyte. */ if ((status < 0xC0) || (status >= 0xE0)) { if (!(togo--)) return RDMID_CHUNK_END; if ((databyte2 = getc(priv->fp)) < 0) return RDMID_EOF; } if (channel_message(priv, (long)curr_time, status, databyte1, databyte2)) return RDMID_USER_TERM; } } /* End of togo. */ if (priv->eh_trackend(priv->userdata, track)) return RDMID_USER_TERM; return RDMID_OK; /* Ok. */ } /* The only non-static (i.e. public) function in this file: */ int rdmid(void* userdata, FILE* fp, int no_sysex_merge, /* The following 21 arguments are function pointers to your event handlers (or NULL). */ /* 3 handlers for header and tracks: */ int (*eh_header) (void* userdata, int format, /* 0, 1 or 2. */ int tracks, int division), int (*eh_trackstart)(void* userdata, int track), int (*eh_trackend) (void* userdata, int track), /* 7 channel messages: */ /* 0 <= time < 2^31. */ int (*eh_note_off) (void* userdata, long time, int channel, /* 0 <= channel <= 15. */ int note, /* 0 <= note <= 127. */ int velocity), /* 0 <= velocity <= 127. */ int (*eh_note_on) (void* userdata, long time, int channel, int note, int velocity), int (*eh_poly_press)(void* userdata, long time, int channel, int note, int pressure), /* 0 <= pressure <= 127. */ int (*eh_controller)(void* userdata, long time, int channel, int number, /* 0 <= number <= 127. */ int value), /* 0 <= value <= 127. */ int (*eh_program) (void* userdata, long time, int channel, int number), int (*eh_chan_press)(void* userdata, long time, int channel, int pressure), int (*eh_pitchbend) (void* userdata, long time, int channel, int bend), /* -8192 <= bend <= +8191. */ /* 1 system exclusive message: */ int (*eh_sysex) (void* userdata, long time, long length, char* data), /* 10 meta events: */ int (*eh_seqnum) (void* userdata, long time, int number), /* 0 <= number <= 65535. */ int (*eh_text) (void* userdata, long time, int type, long length, char* data), int (*eh_eot) (void* userdata, long time), /* End of track. */ int (*eh_tempo) (void* userdata, long time, long tempo), /* 0 <= tempo <= 1677715. */ /* microsecs/quarter note. */ int (*eh_smpte) (void* userdata, long time, int hour, int min, int sec, int frame, int frac), int (*eh_timesig) (void* userdata, long time, int numer, int denom, /* 1, 2, 4, 8, etc. */ int mclck, int m32), /* Often 8. */ int (*eh_keysig) (void* userdata, long time, int sharps, /* -7 <= sharps <= +7. */ int minor), /* 0=major; 1=minor. */ int (*eh_seq_spec) (void* userdata, long time, long length, char* data), int (*eh_metamisc) (void* userdata, long time, int type, long length, char* data), int (*eh_arbitrary) (void* userdata, long time, long length, char* data) ) { int e; unsigned short tracks, t; RDMID_PRIV priv; static const short c2=2, c3=3, c4=4; /* To surpress warning. */ if ((sizeof(short) < c2) || (sizeof(int) < c3) || (sizeof(long) < c4)) return RDMID_BAD_INTS; /* Better check this at compile-time. */ priv.userdata = userdata; priv.fp = fp; priv.buffer = (char*)NULL; /* Nothing allocated yet. */ priv.size = (unsigned long)0L; priv.index = (unsigned long)0L; priv.no_merge = no_sysex_merge; /* 1 -> continued sysex msgs are not collapsed. */ /* Install user's event handlers or take the built-in dummies: */ if (eh_header != NULL) priv.eh_header = eh_header; else priv.eh_header = rdmid_header; if (eh_trackstart != NULL) priv.eh_trackstart = eh_trackstart; else priv.eh_trackstart = rdmid_trackstart; if (eh_trackend != NULL) priv.eh_trackend = eh_trackend; else priv.eh_trackend = rdmid_trackend; if (eh_note_off != NULL) priv.eh_note_off = eh_note_off; else priv.eh_note_off = rdmid_note_off; if (eh_note_on != NULL) priv.eh_note_on = eh_note_on; else priv.eh_note_on = rdmid_note_on; if (eh_poly_press != NULL) priv.eh_poly_press = eh_poly_press; else priv.eh_poly_press = rdmid_poly_press; if (eh_controller != NULL) priv.eh_controller = eh_controller; else priv.eh_controller = rdmid_controller; if (eh_program != NULL) priv.eh_program = eh_program; else priv.eh_program = rdmid_program; if (eh_chan_press != NULL) priv.eh_chan_press = eh_chan_press; else priv.eh_chan_press = rdmid_chan_press; if (eh_pitchbend != NULL) priv.eh_pitchbend = eh_pitchbend; else priv.eh_pitchbend = rdmid_pitchbend; if (eh_sysex != NULL) priv.eh_sysex = eh_sysex; else priv.eh_sysex = rdmid_sysex; if (eh_seqnum != NULL) priv.eh_seqnum = eh_seqnum; else priv.eh_seqnum = rdmid_seqnum; if (eh_text != NULL) priv.eh_text = eh_text; else priv.eh_text = rdmid_text; if (eh_eot != NULL) priv.eh_eot = eh_eot; else priv.eh_eot = rdmid_eot; if (eh_tempo != NULL) priv.eh_tempo = eh_tempo; else priv.eh_tempo = rdmid_tempo; if (eh_smpte != NULL) priv.eh_smpte = eh_smpte; else priv.eh_smpte = rdmid_smpte; if (eh_timesig != NULL) priv.eh_timesig = eh_timesig; else priv.eh_timesig = rdmid_timesig; if (eh_keysig != NULL) priv.eh_keysig = eh_keysig; else priv.eh_keysig = rdmid_keysig; if (eh_seq_spec != NULL) priv.eh_seq_spec = eh_seq_spec; else priv.eh_seq_spec = rdmid_seq_spec; if (eh_metamisc != NULL) priv.eh_metamisc = eh_metamisc; else priv.eh_metamisc = rdmid_metamisc; if (eh_arbitrary != NULL) priv.eh_arbitrary = eh_arbitrary; else priv.eh_arbitrary = rdmid_arbitrary; e = read_header(&priv, &tracks); /* Receive the number of tracks. */ for (t = 0; (t < tracks) && (!e); t++) /* Read all tracks, until error. */ e = read_track(&priv, t); if (priv.buffer != NULL) /* Release if allocated by read_track(). */ free(priv.buffer); return e; }