static int _testFillEventsFromMiddleOfRange(void) { MidiSequence m = newMidiSequence(); MidiEvent e = newMidiEvent(); MidiEvent e2 = newMidiEvent(); LinkedList l = newLinkedList(); e->status = 0xf7; e->timestamp = 100; e2->status = 0xf7; e2->timestamp = 300; appendMidiEventToSequence(m, e); appendMidiEventToSequence(m, e2); _assert(fillMidiEventsFromRange(m, 100, 256, l)); _assertIntEquals(numItemsInList(l), 2); return 0; }
static int _testAppendMidiEventToSequence(void) { MidiSequence m = newMidiSequence(); MidiEvent e = newMidiEvent(); appendMidiEventToSequence(m, e); _assertIntEquals(numItemsInList(m->midiEvents), 1); return 0; }
static int _testFillEventsFromEmptyRange(void) { MidiSequence m = newMidiSequence(); MidiEvent e = newMidiEvent(); LinkedList l = newLinkedList(); e->status = 0xf7; e->timestamp = 100; appendMidiEventToSequence(m, e); _assert(fillMidiEventsFromRange(m, 0, 0, l)); _assertIntEquals(numItemsInList(l), 0); return 0; }
static int _testFillMidiEventsFromRangeStart(void) { MidiSequence m = newMidiSequence(); MidiEvent e = newMidiEvent(); LinkedList l = newLinkedList(); e->status = 0xf7; e->timestamp = 100; appendMidiEventToSequence(m, e); _assertFalse(fillMidiEventsFromRange(m, 0, 256, l)); _assertIntEquals(numItemsInList(l), 1); _assertIntEquals(((MidiEvent)l->item)->status, 0xf7) return 0; }
static boolByte _readMidiFileTrack(FILE *midiFile, const int trackNumber, const int timeDivision, const MidiFileTimeDivisionType divisionType, MidiSequence midiSequence) { unsigned int numBytesBuffer; byte *trackData, *currentByte, *endByte; size_t itemsRead, numBytes; unsigned long currentTimeInSampleFrames = 0; unsigned long unpackedVariableLength; MidiEvent midiEvent = NULL; unsigned int i; if (!_readMidiFileChunkHeader(midiFile, "MTrk")) { return false; } itemsRead = fread(&numBytesBuffer, sizeof(unsigned int), 1, midiFile); if (itemsRead < 1) { logError("Short read of MIDI file (at track %d header, num items)", trackNumber); return false; } // Read in the entire track in one pass and parse the events from the buffer data. Much easier // than having to call fread() for each event. numBytes = (size_t)convertBigEndianIntToPlatform(numBytesBuffer); trackData = (byte *)malloc(numBytes); itemsRead = fread(trackData, 1, numBytes, midiFile); if (itemsRead != numBytes) { logError("Short read of MIDI file (at track %d)", trackNumber); free(trackData); return false; } currentByte = trackData; endByte = trackData + numBytes; while (currentByte < endByte) { // Unpack variable length timestamp unpackedVariableLength = *currentByte; if (unpackedVariableLength & 0x80) { unpackedVariableLength &= 0x7f; do { unpackedVariableLength = (unpackedVariableLength << 7) + (*(++currentByte) & 0x7f); } while (*currentByte & 0x80); } currentByte++; freeMidiEvent(midiEvent); midiEvent = newMidiEvent(); switch (*currentByte) { case 0xff: midiEvent->eventType = MIDI_TYPE_META; currentByte++; midiEvent->status = *(currentByte++); numBytes = *(currentByte++); midiEvent->extraData = (byte *)malloc(numBytes); for (i = 0; i < numBytes; i++) { midiEvent->extraData[i] = *(currentByte++); } break; case 0x7f: logUnsupportedFeature("MIDI files containing sysex events"); free(trackData); freeMidiEvent(midiEvent); return false; default: midiEvent->eventType = MIDI_TYPE_REGULAR; midiEvent->status = *currentByte++; midiEvent->data1 = *currentByte++; // All regular MIDI events have 3 bytes except for program change and channel aftertouch if (!((midiEvent->status & 0xf0) == 0xc0 || (midiEvent->status & 0xf0) == 0xd0)) { midiEvent->data2 = *currentByte++; } break; } switch (divisionType) { case TIME_DIVISION_TYPE_TICKS_PER_BEAT: { double ticksPerSecond = (double)timeDivision * getTempo() / 60.0; double sampleFramesPerTick = getSampleRate() / ticksPerSecond; currentTimeInSampleFrames += (long)(unpackedVariableLength * sampleFramesPerTick); } break; case TIME_DIVISION_TYPE_FRAMES_PER_SECOND: // Actually, this should be caught when parsing the file type logUnsupportedFeature("Time division frames/sec"); free(trackData); freeMidiEvent(midiEvent); return false; case TIME_DIVISION_TYPE_INVALID: default: logInternalError("Invalid time division type"); free(trackData); freeMidiEvent(midiEvent); return false; } midiEvent->timestamp = currentTimeInSampleFrames; if (midiEvent->eventType == MIDI_TYPE_META) { switch (midiEvent->status) { case MIDI_META_TYPE_TEXT: case MIDI_META_TYPE_COPYRIGHT: case MIDI_META_TYPE_SEQUENCE_NAME: case MIDI_META_TYPE_INSTRUMENT: case MIDI_META_TYPE_LYRIC: case MIDI_META_TYPE_MARKER: case MIDI_META_TYPE_CUE_POINT: // This event type could theoretically be supported, as long as the // plugin supports it case MIDI_META_TYPE_PROGRAM_NAME: case MIDI_META_TYPE_DEVICE_NAME: case MIDI_META_TYPE_KEY_SIGNATURE: case MIDI_META_TYPE_PROPRIETARY: logDebug("Ignoring MIDI meta event of type 0x%x at %ld", midiEvent->status, midiEvent->timestamp); break; case MIDI_META_TYPE_TEMPO: case MIDI_META_TYPE_TIME_SIGNATURE: case MIDI_META_TYPE_TRACK_END: logDebug("Parsed MIDI meta event of type 0x%02x at %ld", midiEvent->status, midiEvent->timestamp); appendMidiEventToSequence(midiSequence, midiEvent); midiEvent = NULL; break; default: logWarn("Ignoring MIDI meta event of type 0x%x at %ld", midiEvent->status, midiEvent->timestamp); break; } } else { logDebug("MIDI event of type 0x%02x parsed at %ld", midiEvent->status, midiEvent->timestamp); appendMidiEventToSequence(midiSequence, midiEvent); midiEvent = NULL; } } free(trackData); freeMidiEvent(midiEvent); return true; }
static int _testAppendEventToNullSequence(void) { // Test is not crashing MidiEvent e = newMidiEvent(); appendMidiEventToSequence(NULL, e); return 0; }