Storing a copy of channel data from the buffer


#1

I would like to find the best practices for making a copy of channel data during audio processing.

I'd like to do some audio processing in parallel, kind of like a splitter. I think success would be to have my own copy of the data of getWritePointer and getArrayOfWritePointers, that I could use in the same way as those functions.  The copies should be deleted after each block is processed.

I'm working in Visual Studio 2010, which doesn't have some of the options of the latest C++ version.

To dimension an array in processBlock on the stack (to keep it lockless), I tried making a const float of numSamples and dimensioning the array with it, I still get an error that it is non-constant:

const float numsamples = buffer.getNumSamples();
float sampleHold[numsamples];

I have also tried memcpy and memmove without success.

Does anyone know of best practices for this?


#2

First of all, it's generally a bad idea to do any dynamic allocation on the audio thread (in your processBlock method). See, for example, Timur's talk on why this is a bad idea. The solution is to allocate any buffers you may need in the prepareToPlay method. You can use the estimatedSamplesPerBlock as a maximum buffer size that your processBlock method will receive. Your code would typically look something like this (not tested, so there may be typos):

class MyAudioProcessor : public AudioProcessor
{
private:
    AudioSampleBuffer scratchBuffer;
public:
.
.
.
     void prepareToPlay (double sampleRate, int maxSize) overflow
     {
          ...
          scratchBuffer.setSize (getNumInputChannels(), maxSize);
          ...
     }

     void processBlock (AudioSampleBuffer &buffer, MidiBuffer &midiMessages)
     {
           AudioSampleBuffer tmpBuffer (scratchBuffer.getArrayOfWritePointers(), buffer.getNumChannels(), buffer.getNumSamples());
           for (int ch = 0; ch < buffer.getNumChannels(); ++ch)
               tmpBuffer.copyFrom (ch, 0, buffer, ch, 0, buffer.getNumSamples());
           // now use tmpBuffer
     }
}

#3

Have a look at AudioSampleBuffer: http://learn.juce.com/doc/classAudioSampleBuffer.php

There you find several copy, multiplying and adding functions. The benefit is, that these functions are vectorized, i.e. faster than iterating yourself.

If you want to throw the data away (as you wrote) after the block processing is done, you should not need to worry about locking. It becomes an issue if another thread accesses the same data.

You can do for your example:

AudioSampleBuffer sampleHold (1, buffer.getNumSamples);

sampleHold.copyFrom (0, 0, buffer.getReadPointer(0), buffer.getNumSamples());

const float* ptr = sampleHold.getReadPointer(0);

But consider:

1) better allocate the AudioSampleBuffer outside the processBlock callback, as allocating is timeconsuming and could even fail or take longer than expected because of reshuffeling of memory. Best place is allocate in the constructor and resize it in prepareToPlay

void MyAudioProcessor::prepareToPlay (double sampleRate, int samplesPerBlock)
{
    sampleHold.resize (1, samplesPerBlock);
}
    

void MyAudioProcessor::processBlock (AudioSampleBuffer& buffer, MidiBuffer& midiMessages)
{

    sampleHold.copyFrom (0, 0, buffer.getReadPointer(0), buffer.getNumSamples());

    [...]

}

 

2) do you need the copy? you can read your source as often as you want

For the snippet you posted, I wonder why the compiler did not complain about creating the array with float number of samples? Would be the more obvious error to me. An implicit type cast to integer could make this compile, but this is probably not what you want.

Hope that helps...


#4

Oh, Fabian was faster, so take his reply, he's the Pro ;-)


#5

Good to see that our answers are nearly identical :-)


#6

The API documentation says about estimatedSamplesPerBlock: "The actual block sizes that the host uses may be different each time the callback happens, and may be more or less than this value".

Is that still true ?

If so, your code will fail if it receives a buffer bigger than the estimatedSamplesPerBlock.


#7

I'll need to ask Jules about this comment but I think that it indicates that your plugin should do something sensible when the buffer size is larger than estimatedSamplesPerBlock, i.e. not crash. I've had an effect plug-in on the market for years now which will simply zeros out any samples if the buffer size is larger than estimatedSamplesPerBlock. We've never had any complaints from users about clicking or something like that, so I assume that DAWs only send in larger buffers in rare occasions - maybe when the audio is already muted after playback.

I'd be really interested in peoples comments on this as I am unsure myself.


#8

Yeah, I couldn't say for 100% sure, but I've never known of a DAW to ask for more samples than it asked for in the prepareToPlay call. (They will often ask for fewer though).

Doing something like Fabian suggests would be a sensible fallback if it's not possible for your plugin to actually process the number of samples that were asked for.


#9

Can someone update the documentation with this? It’d have been really handy years ago to have this in the docs :slight_smile:

How about:

The estimatedSamplesPerBlock value is a HINT about the typical number of samples that will be processed for each callback, but isn’t any kind of guarantee. The actual block sizes that the host uses may be different each time the callback happens, and may be more or less than this value.

Is changed to:

The maximumExpectedSamplesPerBlock value is a strong hint about the maximum number of samples that will be provided in each block. You may want to use this value to resize internal buffers. You should program defensively in case a buggy host exceeds this value. The actual block sizes that the host uses may be different each time the callback happens: completely variable block sizes can be expected from some hosts.


#10

Thank you. See latest commit!