MidiBuffer::addEvent in processBlock


#1

Hi,

given the fact that doing memory allocations in an audio callback is not considered good practice, I was wondering if calling addEvent on the incoming MidiBuffer is the right way to generate MIDI notes from a processBlock call?
I looked into the underlying code a bit, and it looks like an interal buffer resize is being done (ensureSize), so I guess some memory allocations are going on in there.
In my own old VST code, I pre-allocated an array of VstMidiEvents (granted, with a maximum size, but it was quite big…) so that memory allocation was not needed.
Should I worry about this?

Koen


#2

Hmm, good point. I guess that if you’re worried about this, you could preallocate by putting a load of midimessages into the buffer and then clearing it - the buffers only grow, they don’t shrink their storage.


#3

Yes, that sounds like a good solution. Will try that.
(a MidiBuffer::ensureSize function might also be handy)


#4

[quote=“KoenTanghe”]
(a MidiBuffer::ensureSize function might also be handy)[/quote]

Proposal;
MidiBuffer (int initialCapacity=MidiBuffer::DefaultCapacity) throw ()
Creates a MidiBuffer with the given allocated space

int getCapacity () const throw ()
Returns the allocated capacity. This is not the actual number of events in the buffer.

void setCapacity (int newCapacity ) const throw ()
Resizes the buffer. Will preserve existing events if the new capacity is greater.
Will remove existing events if new capacity is smaller than current number of events.


#5

+1 for Frank’s proposal

But doing it that way is actually not possible after all…
We can only access the MidiBuffer when processBlock is called. There is no way to obtain that buffer and making sure it has a big enough size in advance before the first processBlock is called. Of course, we could do it in the very first call to processBlock, but that feels more like a workaround.
For my old VST plugin, the “MIDI event buffer” was a pre-allocated array, contained in (well, pointed to by) a VST event struct, so there was no memory allocation done at all in processReplacing.

I heard a few developers say that it’s not that bad to do some memory allocation in the audio callback, and if the midi buffer needs to grow, it will only do so a few times until it’s big enough because the allocated memory is doubled when there is no space left any more. However, in ensureSize I saw that it only grows to the wanted size, so memory allocation will need to be done on each occasion where there is not enough space. When using a “double the size when full” policy this would occur less often.

Still a bit reluctant to unnecessary memory allocations in the audio callback… But I’ll use what is there now and see how that goes :wink:


#6

Ok, how about this:

I just noticed MidiBuffer has a swapWith() function that allows it to swap its contents with another MidiBuffer instance.
So could this work:

  • create a MidiBuffer in advance (processor constructor or prepareToPlay), fill it up with enough events, and then clear it again
  • in the first call to processBlock, add the incoming MIDI events (if any) to our own buffer, and then swap the incoming MidiBuffer out with our own buffer (never use our own buffer any more from that point on…)
  • add all new events to the incoming midiMessages buffer (which now uses the memory we allocated with our own buffer before the first call to processBlock)

Question: are we guaranteed that the incoming midiMessages buffer will always be the same object in all processBlock calls? If that’s not true, this still won’t work.


#7

I’ll certainly add an ensureSize() method for you, and will tweak the wrapper code to give their buffers an initial size before they start processing.

I think that’s true at the moment, but it’s not a guarantee.


#8

OK, thanks! That would be helpful!

For now, I’m using this as the first thing in my processBlock call:

[code] // Special stuff on first pass
if (m_IsFirstProcessBlockCall)
{
// Add existing messages to our own (big enough) buffer
m_MIDIBuffer.addEvents(midiMessages,0,-1,0);

    // Then swap out incoming buffer with ours
    midiMessages.swapWith(m_MIDIBuffer);

    // From now on, the midiMessages buffer should be big enough to avoid memory allocation 
    // in the processBlock call. We won't use m_MIDIBuffer any longer.
    m_IsFirstProcessBlockCall = false;
}[/code]

And this in my constructor:

[code] // In order to be able to do special things on the first processBlock call
m_IsFirstProcessBlockCall = true;

// Pre-allocate MIDI event buffer
for (int i = 0; i < kDefaultNumOfEventMessagesPerBlock; i++)
    m_MIDIBuffer.addEvent(MidiMessage::noteOff(1,0),0);
m_MIDIBuffer.clear();[/code]

with:

bool m_IsFirstProcessBlockCall; MidiBuffer m_MIDIBuffer;

It works fine. But as you say there is no guarantee that the incoming midiMessages buffer will always be the same, it would be best to do this on the wrapper level, indeed.

By the way: I noticed that in juce_VST_Wrapper.cpp (line 789), you are doing:

so at least 64 native VstMidiEvents are pre-allocated.
Probably there (in resume()) is also the place to do the ensureSize() for the midiEvents member variable?

When you add this, could you make it big enough? 64 is ok, but a plugin that sends out 16 calculated values every 128 samples (= 2.9 ms @ 44100 Hz) as MIDI CC’s already needs 128 events for a buffer of 1024 samples. A number like 512 or 1024 might be a better default.
Or perhaps make it so that the developer can change it to whatever he thinks suits the plugin best?


#9

I already checked something in this morning to make the wrappers use 2048 as the default size - have a go and see if it works for you!


#10

OK, just checked it out. Thanks!

Looks good, but I would also change line 791 in juce_VST_Wrapper.cpp to:
outgoingEvents.ensureSize (2048); // was still 64
See my last remark in my last post: 64 is not enough.
You used 2048 for the midiEvents object, but not for the outgoingEvents object.


#11

Ah yes, sorry, I missed that. Thanks!