AudioSampleBuffer assignment reallocation default


#1

When assigning AudioSampleBuffers (using overloaded =) it will reallocate by default.
I think its best practice to avoid reallocating (as this is mostly for the audio thread) if the buffer already has enough bytes allocated, and so I’m forced to write my own AudioSampleBuffer assignment procedure.

I think most are interested in avoiding reallocation by default, and so this is an inquiry to change the default AudioSampleBuffer operator = to avoid reallocation.


#2

What would you expect to happen? Do you want to move the object, have the copy reference the original or create a copy?


#3

I’d except it to do exactly what it already does: makes a copy of the audio buffer in the destination.

I just don’t like the memory semantics of reallocating if there is already enough bytes allocated in the destination buffer. In fact, it use the AudioSampleBuffer.setSize(…) which defaults to not-reallocating, but it doesnt use that default.


#4

You’re right, that’s not a bad idea. In fact, almost all STL containers do not maintain the capacity of a container when assigning. For example:

std::vector<double> small (10, 0.0);
small.reserve (10000);

std::cout << small.capacity() << std::endl;

std::vector<double> big (50, 0.0);
big = small;

std::cout << small.capacity() << std::endl;

Will print:

10000
50

So the assignment operator does not preserve the capacity - it only copies the elements.

However, changing this would be a breaking change which could mess up a lot of JUCE user’s projects in a subtle way. Imagine a plug-in setting up a bunch of buffers in the prepareToPlay method like this:

void prepareToPlay (double /*sampleRate*/, int actualBufferSize) override
{
     // Make sure the buffer has the capacity for maxBufferSize
    AudioSampleBufferf myBuffer (2, kMaxBufferSize);
    myBuffer.setSize (2, actualBufferSize, false, false, true);
    
    // We need several buffers with this capacity and size
    aBuffer = myBuffer; bBuffer = myBuffer; cBuffer = myBuffer;
}

The user then wants to be able to call setSize on all of the above buffers in the processBlock method without re-allocating

void processBlock (AudioSampleBuffer& buffer, MidiBuffer&)
{
    int n = buffer.getNumSamples();
    jassert (n < kMaxBufferSize);

    // this should never allocate even if n is larger than actualBufferSize
    cBuffer.setSize (2, n, false, false, true);
}

Your suggested change would break such projects.


#5

Yes, i understand.
Can we have a new method then?
AudioSampleBuffer::assign(AudioSampleBuffer& source, bool avoidReallocation=true)


#6

I’ve amended the makeCopyOf method to add an option to avoid re-allocation. Does this work for you?


#7

Not really - that method is slow because of the bit-conversion.


#8

No, as the types are known at compile-time the compiler will do no bit-conversion if the types are the same. When compiling for release, VS replaces the loop with a memcpy and XCode produces this tight loop:

0x100002200 <+544>: movl   (%rsi,%rdi,4), %ebx
0x100002203 <+547>: movl   %ebx, (%rdx,%rdi,4)
0x100002206 <+550>: incq   %rdi
0x100002209 <+553>: cmpq   %rax, %rdi
0x10000220c <+556>: jl     0x100002200               ; <+544>
0x10000220e <+558>: incq   %rcx
0x100002211 <+561>: cmpq   %r12, %rcx
0x100002214 <+564>: jl     0x1000020f0 

That’s probably as fast as it can get.


#9

Ah thanks, Fabian. Great insights and smart compilers!
makeCopyOf will do for me then.