I'm using r8brain to determine the true peak in a plugin. True peak = peak of an oversampled signal with at least 192kHz.
@Rail: I haven't encountered any issue with latency, r8brain does all the upsampling within the given time in processBlock(). If you are interested in some code snippets on how to use r8brain let me know.
I have used juce::LagrangeInterpolator before for this task, but sadly it wasn't accurate enough: By feeding it a 12kHz sine wave with a known peak of 0 dBFS sampled at 48kHz, the oversampled 192kHz signal had a peak of as low as -0.49 dBFS, depending on the phase of the original.
Doing the same with r8brain, the peak of the oversampled signal was at -0.13 dBFS at its lowest.
Get r8brain from https://github.com/avaneev/r8brain-free-src .
Add the headers and the r8bbase.cpp to your Introjucer project. Don't add the example.cpp or anything from the folders 'DLL' or 'others'.
Parts of my TruePeakMeter.h:
#include "../libraries/r8brain-free-src/CDSPResampler.h"
#define R8BASSERT(e) jassert(e) // To catch the assertions in r8brain.
class TruePeakMeter
{
public:
void prepareToPlay (double sampleRate,
int numberOfInputChannels,
int estimatedSamplesPerBlock);
void processBlock (const AudioSampleBuffer& buffer);
void reset();
private:
/** The buffer given to processBlock() contains floats, but the
* r8b resampler needs doubles. Therefore they can't work on
* the same memory block.
*/
HeapBlock<double> bufferOfDoubles;
int maxSamplesPerBlock;
// A resampler is needed for every channel.
OwnedArray <r8b::CDSPResampler24> r8bResampler;
};
Parts of my TruePeakMeter.cpp
void TruePeakMeter::prepareToPlay (double sampleRate,
int numberOfChannels,
int estimatedSamplesPerBlock)
{
inputSampleRate = sampleRate;
outputSampleRate = ...
if (inputSampleRate != 0)
{
maxSamplesPerBlock = 4 * estimatedSamplesPerBlock;
const bool initialiseToZero = false;
bufferOfDoubles.allocate (maxSamplesPerBlock, initialiseToZero);
// Create the resamplers. One for each channel.
r8bResampler.clear();
for (int k = 0; k < numberOfChannels; ++k)
{
r8bResampler.add (new r8b::CDSPResampler24 (sampleRate, outputSampleRate, maxSamplesPerBlock));
}
}
}
void TruePeakMeter::processBlock (const juce::AudioSampleBuffer& buffer)
{
// Enlarge the bufferOfDoubles if needed.
if (maxSamplesPerBlock < buffer.getNumSamples())
{
jassertfalse;
maxSamplesPerBlock = buffer.getNumSamples() * 2;
const bool initialiseToZero = false;
bufferOfDoubles.allocate (maxSamplesPerBlock, initialiseToZero);
r8bResampler.clear();
for (int k = 0; k < buffer.getNumChannels(); ++k)
r8bResampler.add (new r8b::CDSPResampler24 (inputSampleRate, outputSampleRate, maxSamplesPerBlock));
DEB ("More samples are given than 4 times the expected nr of samples declared in prepareToPlay().")
}
const int numberOfChannels = jmin (buffer.getNumChannels(), r8bResampler.size());
// The r8bResampler should have been resized via prepareToPlay().
jassert (buffer.getNumChannels() == r8bResampler.size());
for (int k = 0; k < numberOfChannels; ++k)
{
// Copy the samples from float to double.
const float *sampleFloat = buffer.getReadPointer (k);
double *sampleDouble = bufferOfDoubles.getData();
for (int i = 0; i < buffer.getNumSamples(); ++i)
{
*sampleDouble = *sampleFloat;
++sampleDouble;
++sampleFloat;
}
// Get the oversampled signal.
double *outputSample;
const int numberOfOutputSamples = r8bResampler[k]->process (bufferOfDoubles.getData(), buffer.getNumSamples(), outputSample);
// Use the oversampled signal... e.g.
for (int i = 0; i < numberOfOutputSamples; ++i)
{
// Use *outputSample
...
++outputSample;
}
}
}
void TruePeakMeter::reset()
{
...
for (int k = 0; k < r8bResampler.size(); ++k)
{
r8bResampler[k]->clear();
}
}
So I’m revisiting this and here’s Samuel’s example reformatted for the new forum:
Parts of my TruePeakMeter.h:
#include "../libraries/r8brain-free-src/CDSPResampler.h"
#define R8BASSERT(e) jassert(e) // To catch the assertions in r8brain.
class TruePeakMeter
{
public:
void prepareToPlay (double sampleRate,
int numberOfInputChannels,
int estimatedSamplesPerBlock);
void processBlock (const AudioSampleBuffer& buffer);
void reset();
private:
/** The buffer given to processBlock() contains floats, but the
* r8b resampler needs doubles. Therefore they can't work on
* the same memory block.
*/
HeapBlock<double> bufferOfDoubles;
int maxSamplesPerBlock;
// A resampler is needed for every channel.
OwnedArray <r8b::CDSPResampler24> r8bResampler;
};
Parts of my TruePeakMeter.cpp
void TruePeakMeter::prepareToPlay (double sampleRate,
int numberOfChannels,
int estimatedSamplesPerBlock)
{
inputSampleRate = sampleRate;
outputSampleRate = ...
if (inputSampleRate != 0)
{
maxSamplesPerBlock = 4 * estimatedSamplesPerBlock;
const bool initialiseToZero = false;
bufferOfDoubles.allocate (maxSamplesPerBlock, initialiseToZero);
// Create the resamplers. One for each channel.
r8bResampler.clear();
for (int k = 0; k < numberOfChannels; ++k)
{
r8bResampler.add (new r8b::CDSPResampler24 (sampleRate, outputSampleRate, maxSamplesPerBlock));
}
}
}
void TruePeakMeter::processBlock (const juce::AudioSampleBuffer& buffer)
{
// Enlarge the bufferOfDoubles if needed.
if (maxSamplesPerBlock < buffer.getNumSamples())
{
jassertfalse;
maxSamplesPerBlock = buffer.getNumSamples() * 2;
const bool initialiseToZero = false;
bufferOfDoubles.allocate (maxSamplesPerBlock, initialiseToZero);
r8bResampler.clear();
for (int k = 0; k < buffer.getNumChannels(); ++k)
r8bResampler.add (new r8b::CDSPResampler24 (inputSampleRate, outputSampleRate, maxSamplesPerBlock));
DEB ("More samples are given than 4 times the expected nr of samples declared in prepareToPlay().")
}
const int numberOfChannels = jmin (buffer.getNumChannels(), r8bResampler.size());
// The r8bResampler should have been resized via prepareToPlay().
jassert (buffer.getNumChannels() == r8bResampler.size());
for (int k = 0; k < numberOfChannels; ++k)
{
// Copy the samples from float to double.
const float *sampleFloat = buffer.getReadPointer (k);
double *sampleDouble = bufferOfDoubles.getData();
for (int i = 0; i < buffer.getNumSamples(); ++i)
{
*sampleDouble = *sampleFloat;
++sampleDouble;
++sampleFloat;
}
// Get the oversampled signal.
double* outputSample;
const int numberOfOutputSamples = r8bResampler[k]->process (bufferOfDoubles.getData(), buffer.getNumSamples(), outputSample);
// Use the oversampled signal... e.g.
for (int i = 0; i < numberOfOutputSamples; ++i)
{
// Use *outputSample
...
++outputSample;
}
}
}
void TruePeakMeter::reset()
{
...
for (int k = 0; k < r8bResampler.size(); ++k)
{
r8bResampler[k]->clear();
}
}
Interesting, I was trying this re-sampler yesterday, and the returned number of samples varies when I use a fractional sampling ratio. i.e. From 88200Hz to 48000Hz, how have you got past that problem?