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;
}
Beispiel #5
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;
}