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

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?

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.

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 ?

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.