| /* | |
| * A simple MIDI parsing library | |
| * | |
| * Copyright (C) 2014-2022 Mateusz Viste | |
| * All rights reserved. | |
| * | |
| * Redistribution and use in source and binary forms, with or without | |
| * modification, are permitted provided that the following conditions are met: | |
| * | |
| * 1. Redistributions of source code must retain the above copyright notice, | |
| * this list of conditions and the following disclaimer. | |
| * | |
| * 2. Redistributions in binary form must reproduce the above copyright | |
| * notice, this list of conditions and the following disclaimer in the | |
| * documentation and/or other materials provided with the distribution. | |
| * | |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |
| * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
| * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |
| * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE | |
| * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR | |
| * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF | |
| * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS | |
| * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN | |
| * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) | |
| * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | |
| * POSSIBILITY OF SUCH DAMAGE. | |
| */ | |
| #include "defines.h" | |
| #include <stddef.h> /* NULL */ | |
| #include <stdint.h> | |
| #include <string.h> /* memcpy() */ | |
| #include "bitfield.h" | |
| #include "fio.h" | |
| #include "mem.h" | |
| #include "midi.h" /* include self for control */ | |
| //#define BSWAP32(x) ((uint32_t)((((uint32_t)(x) & 0x000000FFul) << 24) | (((uint32_t)(x) & 0x0000FF00ul) << 8) | (((uint32_t)(x) & 0x00FF0000ul) >> 8) | (((uint32_t)(x) & 0xFF000000ul) >> 24))) | |
| extern unsigned char wbuff[]; | |
| static uint32_t riff_ident, rmid_ident, mthd_ident, mtrk_ident; | |
| void midi_init_static_ident() { | |
| memcpy(&riff_ident, "RIFF", 4); | |
| memcpy(&rmid_ident, "RMID", 4); | |
| memcpy(&mthd_ident, "MThd", 4); | |
| memcpy(&mtrk_ident, "MTrk", 4); | |
| } | |
| /* PRIVATE ROUTINES USED FOR INTERNAL PROCESSING ONLY */ | |
| /* fetch a variable length quantity value from a given offset. returns number of bytes read */ | |
| static int midi_fetch_variablelen_fromfile(struct fiofile *f, unsigned long int *result) { | |
| unsigned char bytebuff; | |
| int offset = 0; | |
| *result = 0; | |
| for (;;) { | |
| fio_read(f, &bytebuff, 1); | |
| *result <<= 7; | |
| *result |= (bytebuff & 127); | |
| if ((bytebuff & 128) == 0) break; | |
| } | |
| return(offset); | |
| } | |
| /* reads a MIDI file and computes a map of chunks (ie a list of offsets) */ | |
| static int midi_gettrackmap(struct fiofile *f, unsigned long int *tracklist, int maxchunks) { | |
| int i; | |
| union { | |
| uint32_t ui32; | |
| uint8_t ba[4]; | |
| } buffer; | |
| long int offset; | |
| for (i = 0; i < maxchunks; i++) { | |
| /* read and validate chunk's id */ | |
| if (fio_read(f, &buffer, 4) != 4) break; | |
| if (buffer.ui32 != mtrk_ident) return(-1); /* if header != "MTrk" */ | |
| /* compute the track's byte length */ | |
| if (fio_read(f, &buffer, 4) != 4) break; | |
| offset = | |
| ((long int)buffer.ba[0] << 24) | ((long int)buffer.ba[1] << 16) | | |
| ((long int)buffer.ba[2] << 8) | buffer.ba[3]; | |
| /* remember chunk data offset */ | |
| tracklist[i] = fio_seek(f, FIO_SEEK_CUR, 0); | |
| /* skip to next chunk */ | |
| fio_seek(f, FIO_SEEK_CUR, offset); | |
| } | |
| return(i); | |
| } | |
| /* PUBLIC INTERFACE */ | |
| int midi_readhdr(struct fiofile *f, int *format, unsigned short int *timeunitdiv, unsigned long int *tracklist, int maxtracks) { | |
| const uint32_t *uint32_p = (const uint32_t *)wbuff; | |
| unsigned short tracks; | |
| /* | |
| * Here's an example of a complete MThd chunk: | |
| * 4D 54 68 64 MThd ID | |
| * 00 00 00 06 Length of the MThd chunk is always 6 | |
| * 00 01 The Format type is 1 | |
| * 00 02 There are 2 MTrk chunks in this file | |
| * E7 28 Each increment of delta-time represents a millisecond | |
| */ | |
| /* test for RMID header */ | |
| /* a RMID file starts with RIFFxxxxRMID (xxxx being the data size) followed | |
| * by the word 'data' followed by a 32-bit data size. */ | |
| /* read first 14 bytes - if unable, return an error */ | |
| if (fio_read(f, wbuff, 14) != 14) return(-8); | |
| /* if no RMID header, then assume it's normal MIDI */ | |
| if (uint32_p[0] == riff_ident && uint32_p[1] == rmid_ident) { | |
| /* skip 6 bytes and there we should have our MThd MIDI header */ | |
| fio_seek(f, FIO_SEEK_CUR, 6); | |
| if (fio_read(f, wbuff, 14) != 14) return(-7); | |
| } | |
| /* check id (MThd) and len (must be exactly 6 bytes) */ | |
| if (uint32_p[0] != mthd_ident || wbuff[4] || wbuff[5] || wbuff[6] || wbuff[7] != 6) { | |
| return(-6); | |
| } | |
| if (wbuff[8] != 0) return(-5); /* format is 1 or 2 so 1st digit must be 0 */ | |
| *format = wbuff[9]; | |
| tracks = wbuff[10]; | |
| tracks <<= 8; | |
| tracks |= wbuff[11]; | |
| /* midi_gettrackmap() should not try reading more tracks than declared in | |
| * the header - some MIDI/RMI files may be trailed with some extra stuff */ | |
| if (tracks < maxtracks) maxtracks = tracks; | |
| *timeunitdiv = wbuff[12]; | |
| *timeunitdiv <<= 8; | |
| *timeunitdiv |= wbuff[13]; | |
| /* timeunitdiv must be a positive number */ | |
| if (*timeunitdiv < 1) return(-3); | |
| /* default tempo -> quarter note (1 beat) == 500'000 microseconds (0.5s), ie 120 bpm. | |
| a delta time unit is therefore (0.5s / DIV) long. */ | |
| if (*format > 2) return(-2); | |
| /* read the tracks map and return number of tracks */ | |
| return(midi_gettrackmap(f, tracklist, maxtracks)); | |
| } | |
| /* returns a negative value on error, 0 on success, 1 on end of track */ | |
| #ifdef DBGFILE | |
| static int ld_meta(struct midi_event *event, struct fiofile *f, FILE *logf, unsigned long int *tracklen, char *title, int titlemaxlen, char *copyright, int copyrightmaxlen, char *text, int textmaxlen) { | |
| #else | |
| static int ld_meta(struct midi_event *event, struct fiofile *f, unsigned long int *tracklen, char *title, int titlemaxlen, char *copyright, int copyrightmaxlen, char *text, int textmaxlen) { | |
| #endif | |
| unsigned long metalen; | |
| unsigned long i; | |
| int result = 0; | |
| unsigned char subtype; | |
| fio_read(f, &subtype, 1); /* fetch the META subtype fields */ | |
| midi_fetch_variablelen_fromfile(f, &metalen); | |
| /* printf("got META[0x%02X] of %ld bytes\n", subtype, metalen); */ | |
| switch (subtype) { | |
| case 1: /* text or marker - often used to describe the file... */ | |
| case 6: /* marker */ | |
| i = 0; | |
| if ((text != NULL) && (text[0] == 0) && (textmaxlen > 3)) { /* title might be NULL */ | |
| for (; i < metalen; i++) { | |
| if (i+1 >= textmaxlen) break; /* avoid overflow */ | |
| fio_read(f, text + i, 1); | |
| } | |
| text[i] = 0; | |
| /* recompute the available maxlen */ | |
| text += i; | |
| textmaxlen -= i; | |
| /* add a LF trailer, just in case we'd like to append more data */ | |
| if (textmaxlen > 2) { | |
| *text = '\n'; | |
| text++; | |
| *text = 0; | |
| textmaxlen--; | |
| } | |
| } | |
| /* skip the rest, if we had to truncate the string */ | |
| fio_seek(f, FIO_SEEK_CUR, metalen - i); | |
| #ifdef DBGFILE | |
| if (logf) fprintf(logf, "%lu: TEXT OR MARKER EVENT\n", *tracklen); | |
| #endif | |
| break; | |
| case 2: /* copyright notice */ | |
| i = 0; | |
| if ((copyright != NULL) && (copyright[0] == 0)) { /* take care, copyright might be NULL */ | |
| for (; i < metalen; i++) { | |
| if (i+1 >= copyrightmaxlen) break; /* avoid overflow */ | |
| fio_read(f, copyright + i, 1); | |
| } | |
| copyright[i] = 0; | |
| } | |
| fio_seek(f, FIO_SEEK_CUR, metalen - i); /* skip the rest, if we had to truncate the string */ | |
| break; | |
| case 3: /* track name */ | |
| i = 0; | |
| if (title != NULL) { /* title might be NULL */ | |
| for (; i < metalen; i++) { | |
| if (i+1 >= titlemaxlen) break; /* avoid overflow */ | |
| fio_read(f, title + i, 1); | |
| } | |
| title[i] = 0; | |
| } | |
| fio_seek(f, FIO_SEEK_CUR, metalen - i); /* skip the rest, if we had to truncate the string */ | |
| break; | |
| case 4: /* instrument name */ | |
| fio_seek(f, FIO_SEEK_CUR, metalen); | |
| #ifdef DBGFILE | |
| if (logf) fprintf(logf, "%lu: INSTRUMENT EVENT (ignored)\n", *tracklen); | |
| #endif | |
| break; | |
| case 5: /* lyric */ | |
| fio_seek(f, FIO_SEEK_CUR, metalen); | |
| #ifdef DBGFILE | |
| if (logf) fprintf(logf, "%lu: LYRIC EVENT (ignored)\n", *tracklen); | |
| #endif | |
| break; | |
| case 0x21: /* MIDI port -- no support for multi-MIDI files, I just ignore it */ | |
| fio_seek(f, FIO_SEEK_CUR, metalen); | |
| #ifdef DBGFILE | |
| if (logf) fprintf(logf, "%lu: MIDI PORT EVENT (ignored)\n", *tracklen); | |
| #endif | |
| break; | |
| case 0x2F: /* end of track */ | |
| #ifdef DBGFILE | |
| if (logf) fprintf(logf, "%lu: END OF TRACK\n", *tracklen); | |
| #endif | |
| result = 1; | |
| break; | |
| case 0x51: /* set tempo */ | |
| if (metalen != 3) { | |
| #ifdef DBGFILE | |
| if (logf) fprintf(logf, "%lu: TEMPO ERROR\n", *tracklen); | |
| #endif | |
| return(-1); | |
| } else { | |
| unsigned char b[3]; | |
| event->type = EVENT_TEMPO; | |
| fio_read(f, b, 3); | |
| event->data.tempoval = b[0]; | |
| event->data.tempoval <<= 8; | |
| event->data.tempoval |= b[1]; | |
| event->data.tempoval <<= 8; | |
| event->data.tempoval |= b[2]; | |
| #ifdef DBGFILE | |
| if (logf) fprintf(logf, "%lu: TEMPO -> %lu\n", *tracklen, event->data.tempoval); | |
| #endif | |
| } | |
| break; | |
| case 0x54: /* SMPTE offset -> since I expect only format 0/1 files, I ignore this because I want to start playing asap anyway */ | |
| fio_seek(f, FIO_SEEK_CUR, metalen); | |
| #ifdef DBGFILE | |
| if (logf) fprintf(logf, "%lu: SMPTE OFFSET (ignored)\n", *tracklen); | |
| #endif | |
| break; | |
| case 0x58: /* Time signature */ | |
| if (metalen != 4) { | |
| #ifdef DBGFILE | |
| if (logf) fprintf(logf, "%lu: INVALID TIME SIGNATURE!\n", *tracklen); | |
| #endif | |
| return(-1); | |
| } else { | |
| fio_seek(f, FIO_SEEK_CUR, metalen); | |
| #ifdef DBGFILE | |
| if (logf) fprintf(logf, "%lu: TIME SIGNATURE (ignored)\n", *tracklen); | |
| #endif | |
| } | |
| break; | |
| case 0x59: /* key signature */ | |
| if (metalen != 2) { | |
| #ifdef DBGFILE | |
| if (logf) fprintf(logf, "%lu: INVALID KEY SIGNATURE!\n", *tracklen); | |
| #endif | |
| return(-1); | |
| } else { | |
| fio_seek(f, FIO_SEEK_CUR, metalen); | |
| #ifdef DBGFILE | |
| if (logf) fprintf(logf, "%lu: KEY SIGNATURE (ignored)\n", *tracklen); | |
| #endif | |
| } | |
| break; | |
| case 0x7F: /* proprietary event -> this is non-standard stuff, I ignore it */ | |
| fio_seek(f, FIO_SEEK_CUR, metalen); | |
| #ifdef DBGFILE | |
| if (logf) fprintf(logf, "%lu: PROPRIETARY EVENT (ignored)\n", *tracklen); | |
| #endif | |
| break; | |
| default: | |
| fio_seek(f, FIO_SEEK_CUR, metalen); /* skip the meta data */ | |
| #ifdef DBGFILE | |
| if (logf) fprintf(logf, "%lu: UNHANDLED META EVENT [0x%02Xh] (ignored)\n", *tracklen, subtype); | |
| #endif | |
| break; | |
| } | |
| return(result); | |
| } | |
| /* returns a negative value on error, 0 on success, 1 on end of track */ | |
| #ifdef DBGFILE | |
| static int ld_sysex(struct midi_event *event, struct fiofile *f, FILE *logf, unsigned char statusbyte, unsigned long int *tracklen) { | |
| #else | |
| static int ld_sysex(struct midi_event *event, struct fiofile *f, unsigned char statusbyte, unsigned long int *tracklen) { | |
| #endif | |
| unsigned long sysexlen; | |
| int sysexleneven; /* can be int, guaranteed to be less than 4K */ | |
| unsigned char *sysexbuff; | |
| midi_fetch_variablelen_fromfile(f, &sysexlen); /* get length */ | |
| sysexlen += 1; /* add one byte for the status byte that is not counted, but that we will add to the top of the buffer later */ | |
| #ifdef DBGFILE | |
| if (logf) fprintf(logf, "%lu: SYSEX EVENT OF %ld BYTES ON CHAN #%d\n", *tracklen, sysexlen, statusbyte & 0x0F); | |
| #endif | |
| if (sysexlen > 4096) { /* skip SYSEX events that are more than 4K big */ | |
| fio_seek(f, FIO_SEEK_CUR, sysexlen); | |
| return(0); | |
| } | |
| /* read the sysex string */ | |
| sysexleneven = sysexlen + 2; /* add two bytes for the sysex length that I will add in front of the actual sysex string */ | |
| if ((sysexleneven & 1) != 0) sysexleneven++; /* make sysexleneven an even number (XMS moves MUST occur on even numbers of bytes) */ | |
| sysexbuff = wbuff; | |
| event->type = EVENT_SYSEX; | |
| ((uint16_t *)sysexbuff)[0] = sysexlen; | |
| sysexbuff[2] = statusbyte; /* I store the entire sysex string in memory */ | |
| fio_read(f, sysexbuff + 3, sysexlen - 1); /* read sysexlen-1 because I have already read the status byte */ | |
| event->data.sysex.sysexptr = mem_alloc(sysexleneven); | |
| if (event->data.sysex.sysexptr >= 0) { | |
| mem_push(sysexbuff, event->data.sysex.sysexptr, sysexleneven); | |
| } else { | |
| event->type = EVENT_NONE; | |
| #ifdef DBGFILE | |
| if (logf) fprintf(logf, "%lu: SYSEX MEM_ALLOC FAILED FOR %ld BYTES\n", *tracklen, sysexlen); | |
| #endif | |
| return(MIDI_OUTOFMEM); | |
| } | |
| return(0); | |
| } | |
| #ifdef DBGFILE | |
| static int ld_note(struct midi_event *event, struct fiofile *f, FILE *logf, unsigned char statusbyte, unsigned long int *tracklen, unsigned short int *channelsusage, void *reqpatches) { | |
| #else | |
| static int ld_note(struct midi_event *event, struct fiofile *f, unsigned char statusbyte, unsigned long int *tracklen, unsigned short int *channelsusage, void *reqpatches) { | |
| #endif | |
| unsigned char ubuff[2]; /* micro buffer for loading data */ | |
| switch (statusbyte & 0xF0) { /* I care only about NoteOn/NoteOff events */ | |
| case 0x80: /* Note OFF */ | |
| fio_read(f, ubuff, 2); | |
| event->type = EVENT_NOTEOFF; | |
| event->data.note.chan = statusbyte & 0x0F; | |
| event->data.note.note = ubuff[0] & 127; /* a note must be in range 0..127 */ | |
| event->data.note.velocity = ubuff[1]; | |
| break; | |
| case 0x90: /* Note ON */ | |
| fio_read(f, ubuff, 2); | |
| event->type = EVENT_NOTEON; | |
| event->data.note.chan = statusbyte & 0x0F; | |
| event->data.note.note = ubuff[0] & 127; | |
| event->data.note.velocity = ubuff[1]; | |
| if (event->data.note.velocity == 0) { | |
| event->type = EVENT_NOTEOFF; /* if no velocity, it's in fact a note OFF */ | |
| } else { | |
| *channelsusage |= (1 << event->data.note.chan); /* update the channel usage flags */ | |
| } | |
| /* if it's percussion, mark the required patch */ | |
| if (event->data.note.chan == 9) BIT_SET(reqpatches, event->data.note.note | 128); | |
| break; | |
| case 0xA0: /* key after-touch */ | |
| fio_read(f, ubuff, 2); | |
| event->type = EVENT_KEYPRESSURE; | |
| event->data.keypressure.chan = statusbyte & 0x0F; | |
| event->data.keypressure.note = ubuff[0]; | |
| event->data.keypressure.pressure = ubuff[1]; | |
| break; | |
| case 0xB0: /* control change */ | |
| fio_read(f, ubuff, 2); | |
| event->type = EVENT_CONTROL; | |
| event->data.control.chan = statusbyte & 0x0F; | |
| event->data.control.id = ubuff[0]; | |
| event->data.control.val = ubuff[1]; | |
| break; | |
| case 0xC0: /* program (patch) change */ | |
| fio_read(f, ubuff, 1); | |
| event->type = EVENT_PROGCHAN; | |
| event->data.prog.chan = statusbyte & 0x0F; | |
| event->data.prog.prog = ubuff[0] & 127; | |
| BIT_SET(reqpatches, event->data.prog.prog); | |
| break; | |
| case 0xD0: /* channel after-touch (aka "channel pressure") */ | |
| fio_read(f, ubuff, 1); | |
| event->type = EVENT_CHANPRESSURE; | |
| event->data.chanpressure.chan = statusbyte & 0x0F; | |
| event->data.chanpressure.pressure = ubuff[0]; | |
| break; | |
| case 0xE0: /* pitch wheel change */ | |
| fio_read(f, ubuff, 2); | |
| event->type = EVENT_PITCH; | |
| event->data.pitch.chan = statusbyte & 0x0F; | |
| event->data.pitch.wheel = ubuff[1]; | |
| event->data.pitch.wheel <<= 7; | |
| event->data.pitch.wheel |= ubuff[0]; | |
| break; | |
| default: | |
| #ifdef DBGFILE | |
| if (logf) fprintf(logf, "%lu: Unknown note data\n", *tracklen); | |
| #endif | |
| return(-1); | |
| break; | |
| } | |
| return(0); | |
| } | |
| /* parse a track object and returns the id of the first events in the linked | |
| * list. channelsusage contains 16 flags indicating what channels are used. | |
| * titlemaxlen and copyrightmaxlen are the maximum lengths of the strings, | |
| * including the NULL terminator. | |
| * returns MIDI_EMPTYTRACK if no event found in the track | |
| * returns MIDI_TRACKERROR if the track is corrupted | |
| * returns MIDI_OUTOFMEM if failed to store events in memory */ | |
| #ifdef DBGFILE | |
| long int midi_track2events(struct fiofile *f, char *title, int titlemaxlen, char *copyright, int copyrightmaxlen, char *text, int textmaxlen, unsigned short int *channelsusage, FILE *logf, unsigned long int *tracklen, void *reqpatches) { | |
| #else | |
| long int midi_track2events(struct fiofile *f, char *title, int titlemaxlen, char *copyright, int copyrightmaxlen, char *text, int textmaxlen, unsigned short int *channelsusage, unsigned long int *tracklen, void *reqpatches) { | |
| #endif | |
| unsigned long deltatime; | |
| unsigned char statusbyte = 0; | |
| struct midi_event event; | |
| long result = MIDI_EMPTYTRACK; | |
| unsigned long ignoreddeltas = 0; | |
| /* zero out title and copyright strings, if provided */ | |
| if (titlemaxlen > 0) title[0] = 0; | |
| if (copyrightmaxlen > 0) copyright[0] = 0; | |
| if (textmaxlen > 0) text[0] = 0; | |
| *tracklen = 0; | |
| for (;;) { | |
| int r; | |
| unsigned char bytebuff; | |
| /* read the delta time first - variable length */ | |
| midi_fetch_variablelen_fromfile(f, &deltatime); | |
| *tracklen += deltatime; | |
| /* check the type of the event */ | |
| /* if it's a byte with MSB set, we are dealing with running status (so it's same status as last time */ | |
| if (fio_read(f, &bytebuff, 1) == 0) return(MIDI_TRACKERROR); | |
| if ((bytebuff & 128) != 0) { | |
| statusbyte = bytebuff; | |
| } else { /* get back one byte */ | |
| fio_seek(f, FIO_SEEK_CUR, -1); | |
| } | |
| event.type = EVENT_NONE; | |
| event.deltatime = deltatime; | |
| event.next = -1; | |
| if (statusbyte == 0xFF) { /* META event */ | |
| #ifdef DBGFILE | |
| r = ld_meta(&event, f, logf, tracklen, title, titlemaxlen, copyright, copyrightmaxlen, text, textmaxlen); | |
| #else | |
| r = ld_meta(&event, f, tracklen, title, titlemaxlen, copyright, copyrightmaxlen, text, textmaxlen); | |
| #endif | |
| if (r < 0) return(MIDI_TRACKERROR); | |
| if (r == 1) break; /* end of track */ | |
| } else if ((statusbyte >= 0xF0) && (statusbyte <= 0xF7)) { /* SYSEX event */ | |
| #ifdef DBGFILE | |
| r = ld_sysex(&event, f, logf, statusbyte, tracklen); | |
| #else | |
| r = ld_sysex(&event, f, statusbyte, tracklen); | |
| #endif | |
| if (r != 0) return(MIDI_TRACKERROR); | |
| } else if ((statusbyte >= 0x80) && (statusbyte <= 0xEF)) { /* else it's a note-related command */ | |
| #ifdef DBGFILE | |
| r = ld_note(&event, f, logf, statusbyte, tracklen, channelsusage, reqpatches); | |
| #else | |
| r = ld_note(&event, f, statusbyte, tracklen, channelsusage, reqpatches); | |
| #endif | |
| if (r != 0) return(MIDI_TRACKERROR); | |
| } else { /* else it's an error - free memory we allocated and return NULL */ | |
| #ifdef DBGFILE | |
| if (logf) fprintf(logf, "Err. at offset %04lX (bytebuff = 0x%02X)\n", fio_seek(f, FIO_SEEK_CUR, 0), statusbyte); | |
| #endif | |
| return(MIDI_TRACKERROR); | |
| } | |
| /* add the event to the queue */ | |
| if (event.type == EVENT_NONE) { | |
| ignoreddeltas += event.deltatime; | |
| } else { | |
| int pusheventres; | |
| event.deltatime += ignoreddeltas; /* add any previously ignored delta times */ | |
| ignoreddeltas = 0; | |
| /* add the event to the queue */ | |
| if (result == MIDI_EMPTYTRACK) { /* this is the first event in the queue */ | |
| pusheventres = pusheventqueue(&event, &result); | |
| } else { | |
| pusheventres = pusheventqueue(&event, NULL); | |
| } | |
| if (pusheventres != 0) { | |
| return(MIDI_OUTOFMEM); | |
| } | |
| } | |
| } | |
| if (result >= 0) { | |
| if (pusheventqueue(NULL, NULL) != 0) return(MIDI_OUTOFMEM); /* flush last event in buffer to memory */ | |
| } | |
| return(result); | |
| } | |
| /* merge two MIDI tracks into a single (serialized) one. returns a "pointer" | |
| * to the unique track. I take care not to allocate/free memory here. | |
| * All notes are already in RAM after all. totlen is filled with the total | |
| * time of the merged tracks (in seconds). */ | |
| long midi_mergetrack(long t0, long t1, unsigned long *totlen, unsigned short timeunitdiv) { | |
| long res = -1, lasteventid = -1, selectedid; | |
| int selected; | |
| unsigned long curtempo = 500000l, utotlen = 0; | |
| struct midi_event event[2], lastevent; | |
| if (totlen != NULL) *totlen = 0; | |
| /* fetch first events for both tracks */ | |
| if (t0 >= 0) mem_pull(t0, event, sizeof(struct midi_event)); | |
| if (t1 >= 0) mem_pull(t1, event + 1, sizeof(struct midi_event)); | |
| /* start looping */ | |
| while ((t0 >= 0) || (t1 >= 0)) { | |
| /* compare both tracks, and select the soonest one */ | |
| if (t0 >= 0) { | |
| if ((t1 >= 0) && (event[1].deltatime < event[0].deltatime)) { | |
| selected = 1; | |
| selectedid = t1; | |
| } else { | |
| selected = 0; | |
| selectedid = t0; | |
| } | |
| } else { | |
| selected = 1; | |
| selectedid = t1; | |
| } | |
| /* on first iteration, make sure to assign a result */ | |
| if (lasteventid < 0) { | |
| res = selectedid; | |
| } else if (lastevent.next != selectedid) { /* otherwise attach selected */ | |
| lastevent.next = selectedid; /* track to last note, and */ | |
| mem_push(&lastevent, lasteventid, sizeof(struct midi_event)); /* update last pointer (if */ | |
| } /* not good already) */ | |
| /* save the last event into buffer for later, and remember its id */ | |
| lasteventid = selectedid; | |
| memcpy(&lastevent, event + selected, sizeof(struct midi_event)); | |
| /* increment timer */ | |
| if ((totlen != NULL) && (event[selected].deltatime != 0)) { | |
| utotlen += DELTATIME2US(event[selected].deltatime, curtempo, timeunitdiv); | |
| while (utotlen >= 1000000lu) { | |
| utotlen -= 1000000lu; | |
| *totlen += 1; | |
| } | |
| } | |
| if (event[selected].type == EVENT_TEMPO) curtempo = event[selected].data.tempoval; | |
| /* decrement timer on the non-selected track, and synch it to xms if needed */ | |
| /* then move along on the selected track, and fetch it from xms */ | |
| if (selected == 0) { | |
| if ((t1 >= 0) && (event[0].deltatime != 0)) { | |
| event[1].deltatime -= event[0].deltatime; | |
| mem_push(event + 1, t1, sizeof(struct midi_event)); | |
| } | |
| t0 = event[0].next; | |
| if (t0 >= 0) mem_pull(t0, event, sizeof(struct midi_event)); | |
| } else { /* selected == 1 */ | |
| if ((t0 >= 0) && (event[1].deltatime != 0)) { | |
| event[0].deltatime -= event[1].deltatime; | |
| mem_push(event, t0, sizeof(struct midi_event)); | |
| } | |
| t1 = event[1].next; | |
| if (t1 >= 0) mem_pull(t1, event + 1, sizeof(struct midi_event)); | |
| } | |
| } | |
| return(res); | |
| } |