First a side note:
average is probably the worst choice, as it is more or less meaningless for audio samples. It becomes obvious if you remember, that the average in the end will always be 0, unless you have a DC offset.
The measures you want to look at are: max = peak values or RMS = root mean square.
And because they are important and used all over the place, they exist in optimised versions, to be found e.g. float FloatVectorOperations::findMaximum (const float *src, int numValues).
Each time you type a for loop, try to replace that with one of these.
The RMS can be obtained via Type AudioBuffer< Type >::getRMSLevel (int channel, int startSample, int numSamples) const. Currently this is not a SIMD operation, but might be in the future, so it is always worth it not to roll your own.
And some code inspiration, although it is a slightly different thing: I wrote this code for a circular buffer, displaying the buffers rolling in (currently mono), maybe it gives you some inspiration:
// members:
std::vector<float> minBuffer;
std::vector<float> maxBuffer;
std::atomic<int> writePointer;
int fraction = 0;
// in preparToPlay
minBuffer.resize (1024, 0.0f);
maxBuffer.resize (1024, 0.0f);
writePointer = writePointer % 1024;
// create peak values in processBlock
{
int blockSize = 128;
int samples = 0;
Range<float> minMax;
while (samples < numSamples) {
int leftover = numSamples - samples;
if (fraction > 0) {
minMax = FloatVectorOperations::findMinAndMax (buffer.getReadPointer (0), blockSize - fraction);
maxBuffer [writePointer] = std::max (maxBuffer [writePointer], minMax.getEnd());
minBuffer [writePointer] = std::min (minBuffer [writePointer], minMax.getStart());
samples += blockSize - fraction;
fraction = 0;
writePointer = (writePointer + 1) % maxBuffer.size();
}
else if (leftover > blockSize) {
minMax = FloatVectorOperations::findMinAndMax (buffer.getReadPointer (0, samples), blockSize);
maxBuffer [writePointer] = minMax.getEnd();
minBuffer [writePointer] = minMax.getStart();
samples += blockSize;
writePointer = (writePointer + 1) % maxBuffer.size();
}
else {
minMax = FloatVectorOperations::findMinAndMax (buffer.getReadPointer (0, samples), leftover);
maxBuffer [writePointer] = minMax.getEnd();
minBuffer [writePointer] = minMax.getStart();
samples += blockSize - fraction;
fraction = leftover;
}
}
}
and create a path to be painted like this:
Path MyAudioProcessor::getChannelOutline (const Rectangle<float> bounds, const int numSamples) const
{
const int bufferSize = static_cast<int> (maxBuffer.size());
Path outline;
int latest = writePointer > 0 ? writePointer - 1 : bufferSize - 1;
int oldest = (latest >= numSamples) ? latest - numSamples : latest + bufferSize - numSamples;
const float dx = bounds.getWidth() / numSamples;
const float dy = bounds.getHeight() * 0.35f;
const float my = bounds.getCentreY();
float x = bounds.getX();
int s = oldest;
outline.startNewSubPath (x, my);
for (int i=0; i<numSamples; ++i) {
outline.lineTo (x, my + minBuffer [s] * dy);
x += dx;
if (s < minBuffer.size() - 1)
s += 1;
else
s = 0;
}
for (int i=0; i<numSamples; ++i) {
outline.lineTo (x, my + maxBuffer [s] * dy);
x -= dx;
if (s > 1)
s -= 1;
else
s = bufferSize - 1;
}
return outline;
}
HTH
EDIT: just realised, that writePointer needs to be atomic, as it is written in processBlock but also read from paint()
EDIT 2: FYI: I added this now to my publicly available meters module: github:ff_meters