I use a circular buffer, you can only read blocks that have already been written. this only uses memcpy so it’s very fast. Only the cursor is shared.
This is the class I use, I asked chat gpt to comment on it, and he seems to have got it right.
/**
This class represents a circular buffer for audio data.
The CircularBuffer allows storing and retrieving audio samples in a circular
fashion. It provides functions for preparing the buffer, pushing new
blocks of samples, and retrieving previously written samples.
The size of the buffer is determined by the duration and sample rate
specified in the `prepareToPlay` function. The `pushNextBlock` function
is used to add new audio samples to the buffer, and the `getData` function
retrieves previously written samples.
The buffer can handle multi-channel audio data.
Usage:
- Call `prepareToPlay` before using the buffer to set the size based on
the desired duration and sample rate.
- Use `pushNextBlock` to add new audio samples to the buffer.
- Use `getData` to retrieve previously written samples.
Note: Make sure to call `prepareToPlay` before using the buffer.
*/
class CircularBuffer
{
std::unique_ptr<juce::AudioBuffer<float>> data;
int size = 0;
std::atomic<int> cursor;
double sampleRate;
public:
/**
Prepare the buffer for playback.
@param sampleRate The sample rate of the audio.
@param duration The duration (in seconds) of the buffer.
@param numChannels The number of audio channels.
*/
void prepareToPlay(double _sampleRate, double duration, int numChannels)
{
sampleRate = _sampleRate;
size = static_cast<int>(sampleRate * juce::jmax(1.0, duration));
data.reset(new juce::AudioBuffer<float>(numChannels, size));
cursor = 0;
}
/**
Push a new block of audio samples into the buffer.
@param buffer The audio buffer containing the samples to push.
*/
void pushNextBlock(juce::AudioBuffer<float>& buffer)
{
jassert(size > 0); // Size must be greater than 0. Make sure to call prepareToPlay() before updating the buffer.
jassert(buffer.getNumChannels() == data->getNumChannels()); // Number of channels must match between buffers.
int numSamples = buffer.getNumSamples();
int numChannels = buffer.getNumChannels();
for (int channel = 0; channel < numChannels; ++channel)
{
const float* channelData = buffer.getReadPointer(channel);
float* bufferData = data->getWritePointer(channel);
int end = cursor + numSamples;
if (end < size) {
// Copy the entire block in one go
std::memcpy(bufferData + cursor, channelData, numSamples * sizeof(float));
}
else {
// Wrap around to the beginning and copy in two blocks
int firstBlockSize = size - cursor;
int secondBlockSize = end - size;
std::memcpy(bufferData + cursor, channelData, firstBlockSize * sizeof(float));
std::memcpy(bufferData, channelData + firstBlockSize, secondBlockSize * sizeof(float));
}
}
cursor = (cursor + numSamples) % size;
}
/**
Retrieve previously written audio samples from the buffer.
@param channel The channel index to retrieve samples from.
@param destBuffer The destination buffer to copy the samples to.
@param numSamples The number of samples to retrieve.
*/
void getData(int channel, float* destBuffer, int numSamples)
{
jassert(destBuffer != nullptr); // destBuffer must be non-null
if (numSamples > size) numSamples = size;
int firstSample = cursor - numSamples;
if (firstSample >= 0) {
const float* source = data->getReadPointer(channel) + firstSample;
std::memcpy(destBuffer, source, numSamples * sizeof(float));
}
else {
firstSample += size;
int subBlockSize = size - firstSample;
const float* source1 = data->getReadPointer(channel) + firstSample;
std::memcpy(destBuffer, source1, subBlockSize * sizeof(float));
const float* source2 = data->getReadPointer(channel);
std::memcpy(destBuffer + subBlockSize, source2, (numSamples - subBlockSize) * sizeof(float));
}
}
};
to use it, in prepareToPlay:
circularBuffer.prepareToPlay(sampleRate, 4, getMainBusNumOutputChannels());
in processBlock
circularBuffer.pushNextBlock(buffer);
in editor
audioProc->circularBuffer.getData(0, sampleData, sampleSize);
This can be freely used anytime, anywhere, for any representation of audio data such as an oscilloscope or meter. getData would receive any size of the last available samples