Getting a MidiBuffer onto the audio thread

I’m doing something like this on the audio thread:

void processBlock(AudioBuffer<float>&, MidiBuffer& midiBufferA)
{
    auto midiBufferB = popIncomingMidiBuffer();   // edit: not auto&
    midiBufferA.swapWith(midiBufferB);
}

I’m using a lock-free fifo for getIncomingMidiBuffer(), so this part should be fine. What I’m worried about is that midiBufferB now contains the old midiBufferA, and when it goes out of scope it will deallocate. I guess I could push midiBufferB onto another lock-free fifo and then free it up periodically on another thread, but this seems like it might a bit fragile.

Has anyone run into this problem before? Are there any other approaches for copying MidiMessages onto the audio thread?

Hm, I think at this very moment there might be something off with your memory management in general. Looking at the code, your are using two references. midiBufferA supplied by the caller that is handling with the memory deallocation (if even happens) and midiBufferB which is also a reference. So when you go out of scope in processBlock, you are not deallocating anything at the moment. The lock free queue doesn’t seem to give away the ownership (e.g. my moving).

Just guessing here without having seen the lock free queue, but I think what is happening is that the lock free queue is carrying a constant amount of MidiBuffers. When pushing a new MidiBuffer in the queue you are also using the swapWith method? In that case, the caller that LockFreeQueue::push(MidiBuffer&) method would be owner of a MidiBuffer that eventually goes out of scope at some point having to deallocate the array of MidiMessages containing some data that was active e.g. 64 pulls ago (the size of the queue) and is now just rubbish from a 64 audio blocks ago.

Just to make sure you are not running into issues with the lock free queue: At the moment, the lock free queue is not being told that a buffer finished being read. E.g. the lock free queue doesn’t know that your midiBufferB data is done being processed by the audio thread. That means, that the lock free queue has to make sure not to alter that MidiBuffer reference in it’s queue, until the next getIncomingMidiBuffer is called.

Ah, this was my bad typing. I simplified my code for the example, and the reference was not intentional. My queue does move the buffer onto the audio thread. It’s based on the AbstractFifo stuff, and it’s pretty similar to the example given in the docs.

Actually my mistake was fortuitous, because @Rincewind’s response got me thinking, and it seems that popping might be the wrong approach here. Reworking my fifo container a bit, I now have the following member function:

void applyToFirst(const auto& call) noexcept
{
    int start1, size1, start2, size2;
    fifo.prepareToRead(1, start1, size1, start2, size2);

    jassert(size1 + size2 <= 1);

    if (size1 > 0 && std::invoke(call, data[start1]))
        fifo.finishedRead(size1 + size2);

    else if (size2 > 0 && std::invoke(call, data[start2]))
        fifo.finishedRead(size1 + size2);
}

Now I can do something like this:

void processBlock(AudioBuffer<float>&, MidiBuffer& buffA)
{
    fifo.applyToFirst([&buffA](MidiBuffer& buffB)
        { buffA.swapWith(buffB); return true; });
}

…and nothing gets destructed. Seems like it should work, right?

I’m not experienced enough to say you are doing everything wright with the AbstractFifo, I usually use different implementations. But you shouldn’t capture a variable for your lambda, that might or might not allocate depending on the compiler (I guess - not sure if that was standardised at some point). I think you should pass buffA also into applyFirst either as a MidiBuffer reference or as a template parameter pack with std::forward.

I think you might be wrong about lambda capture allocation (see here for example). I’m no expert either though.

Well the problem I see with that, is that the article says “typically” a bit to much for my tasting :smile:. There is no way for you to test, that either type erasure, inlining or the buffer optimisation has been applied vs knowing that everything is kept on the steck when using templated arguments.