Circular Midi Buffer: Array() or MidiBuffer()?


#1

I get that the MidiBuffer class and MidiBuffer::Iterator class are designed to work with the low-level packed data, but it just seems like it’s designed to not be easy or intuitive to use. I’m trying to figure out how to build a circular FIFO using it (because an Array wouldn’t work as the midiMessage sizes vary) and ran the API past the ol ##c++ people and they all brought out so much hate against it, you’d think I was at a trump rally holding up a Clinton sign!

I’m looking at this method in juce_Array.h:

void removeInternal (const int indexToRemove)
{
    --numUsed;
    ElementType* const e = data.elements + indexToRemove;
    e->~ElementType();
    const int numberToShift = numUsed - indexToRemove;
    if (numberToShift > 0)
        memmove (e, e + 1, ((size_t) numberToShift) * sizeof (ElementType));
    minimiseStorageAfterRemoval();
}

since the MidiMessages can potentially all have unique sizes, could you even remove elements from an Array of MidiMessages? would you have a bunch of memory collisions?


#2

You’re misunderstanding how MidiMessage works. It doesn’t vary in size, and it’s totally OK to have an Array if you want to.


#3

uh, what about this part?

private:
    //==============================================================================
   #ifndef DOXYGEN
    union PackedData
    {
        uint8* allocatedData;
        uint8 asBytes[sizeof (uint8*)];
    };

    PackedData packedData;
    double timeStamp;
    int size;
   #endif

    inline bool isHeapAllocated() const noexcept  { return size > (int) sizeof (packedData); }
    inline uint8* getData() const noexcept        { return isHeapAllocated() ? packedData.allocatedData : (uint8*) packedData.asBytes; }
    
uint8* allocateSpace (int){
    if (bytes > (int) sizeof (packedData))
    {
        uint8* d = static_cast<uint8*> (std::malloc ((size_t) bytes));
        packedData.allocatedData = d;
        return d;
    }

    return packedData.asBytes;
    }
};

I guess the actual data pointed to by packedData.allocatedData wouldn’t be stored within the Array ?


#4

I managed to piece together something that seems to work well, tho I haven’t tested it extensively.

AbstractFifo abstractFifo(31200 * 60 * 3);
Array<MidiMessage> midiBuffer;
midiBuffer.ensureStorageAllocated(31200*60*3); ////midi rate is 31.2kbps. that's bytes, not messages. so, plan for 31,200 messages per second * 60 seconds * 3.

void MidiRecorder::addToFifo(const juce::MidiMessage &oneMessage, int numItems) {
    int start1, size1, start2, size2;
    abstractFifo.prepareToWrite(numItems, start1, size1, start2, size2);
    if( size1 > 0 ) {

            midiBuffer[start1] = oneMessage; //we're only storing one midiMessage at a time, but just in case.

    }
    if( size2 > 0 ) {
        //copySomeData (myBuffer + start2, someData + size1, size2); //where, what, how much
    }
    abstractFifo.finishedWrite(size1+size2);
}
void MidiRecorder::readFromFifo(std::vector<MidiMessage> &messages, int numItems) {
    int start1, size1, start2, size2;
    abstractFifo.prepareToRead (numItems, start1, size1, start2, size2);
    DBG( "\nreadFromFifo(" + String(numItems) + " items)" );
    DBG( "1st read position: " + String(start1)
        + " num you can read: " + String(size1)
        + " 2nd read position: " + String(start2)
        + " num you can read: " + String(size2) );
    DBG( "messages.size() pre-read1: " + String(messages.size() ) );
    int s = messages.size();
    if (size1 > 0) {
        //copySomeData (someData, myBuffer + start1, size1); //to where, from what, how much
        for( int i = 0; i < size1; ++i) {
            messages.push_back( midiBuffer[start1+i] );
        }
    }
    DBG( "messages.size() post-read1: " + String(messages.size() ) );
    if (size2 > 0) {
        for( int i = 0; i < size2; ++i) {
            messages.push_back( midiBuffer[start2+i] );
        }
        //copySomeData (someData + size1, myBuffer + start2, size2); //to where, from what, how much
    }
    DBG( "messages.size() post-read2: " + String(messages.size() ) );
    abstractFifo.finishedRead (size1 + size2);
    DBG( "size1+size2: " + String( size1+size2) );
    DBG( "messages.size() grew by: " + String( messages.size() - s ));
}
void MidiRecorder::handleIncomingMidiMessage(juce::MidiInput *source, const juce::MidiMessage &message) {
    if( !saveTimerIsRunning ) {
        startTime = Time::getMillisecondCounterHiRes();
        startTimer(10000);//10 seconds for testing.  more like 2 minutes in practice. 
        saveTimerIsRunning = true;
    }

    MidiMessage m = message;
    m.setTimeStamp(Time::getMillisecondCounterHiRes() - startTime);
    addToFifo(m, 1);

    numEventsReceived++;
}
void MidiRecorder::timerCallback() {
    //stopTimer();
    DBG("numEvents received: " + String(numEventsReceived));
    if( abstractFifo.getNumReady() < 1 ) { return; }

    MidiFile mf;
    mf.setTicksPerQuarterNote(960 * 0.65f);

    int microsecondsPerQuarter = (60000.f / tempo) * 1000.f;
    MidiMessage tempoEvent = MidiMessage::tempoMetaEvent(microsecondsPerQuarter);
    tempoEvent.setTimeStamp( startTime );

    MidiMessageSequence mms;

    mms.addEvent(tempoEvent); //tempo is 96bpm
    /*
     read numEventsWritten into our mms using readFromFifo(
     */
    std::vector<MidiMessage> newMessages;

    //newMessages.resize(abstractFifo.getNumReady());
    readFromFifo(newMessages, abstractFifo.getNumReady());

    DBG("MidiRecorder::timerCallback() number of new messages: " + String(newMessages.size() ) );
    for( auto msg : newMessages ) {
        mms.addEvent(msg);
    }
    DBG("MidiRecorder::timerCallback() number of events added to MMS: " + String(mms.getNumEvents()) );
    mms.updateMatchedPairs();
    mms.sort();
    mf.addTrack(mms);


    DBG( "MidiRecorder::timerCallback() number of tracks in midi File: " + String(mf.getNumTracks() ) ); //should be 1
    DBG( "MidiRecorder::timerCallback() number of events in track 0: " + String(mf.getTrack(0)->getNumEvents()) );
    DBG( " ");
//do whatever you need to do with this midi file
}

I didn’t realize //newMessages.resize(abstractFifo.getNumReady()); actually created a bunch of empty MidiMessages (http://en.cppreference.com/w/cpp/container/vector/resize see DefaultInsertable) so my midiFiles were twice the size as I was expecting.