Resample 44.1kHz WAV to 48kHz

My output sample rate is 48kHz (on my iMac/Windows), but may well probably be 44.1kHz on another platform.

My input files are 44.1kHz, although I could recreate them at 48kHz.

I want to avoid duplicating resources @44.1 and 48.

My WAVs are short; < 10s, and I read them into a buffer upon load.

So I think what I'm after is some third party resampler algo.

I'm not sure if I trust linear interpolation...

Can anyone recommend something?

EDIT: found https://ccrma.stanford.edu/~jos/resample/

π

PS I was surprised I can't set the output sample rate:

        pOutputDeviceManager = new AudioDeviceManager();

        auto deviceSetup = AudioDeviceManager::AudioDeviceSetup();
        deviceSetup.sampleRate = 44100;

        pOutputDeviceManager->initialise(
            0,                // numInputChannelsNeeded
            2,                // numOutputChannelsNeeded
            nullptr,        // XML
            true,            // selectDefaultDeviceOnFailure
            String(),        // preferredDefaultDeviceName
            &deviceSetup    // preferredSetupOptions
            );
        
        DBG(pOutputDeviceManager->getCurrentAudioDevice()->getCurrentSampleRate()); // 48000

        for (auto s : pOutputDeviceManager->getCurrentAudioDevice()->getAvailableSampleRates())
            DBG(s); // one item: 48000

See LagrangeInterpolator and CatmullRomInterpolator

ooo goody!

http://www.juce.com/forum/topic/catmull-rom-interpolator-based-lagrange-interpolator

π

I’ve used the Voxengo r8brain free library.

2 Likes

Have you used that in real time with no latency?

Cheers,

Rail

No. Only for converting files. I don't know about its real time performance.

Thanks – from the reading I’ve done online I think it’s latency is too high for real-time use. I was hoping I was wrong :slight_smile:

Cheers,

Rail

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.

Hi Samuel,

That’s great!! PM sent.

Thanks,

Rail

Here we go, this is how I use r8brain.

  • 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();
    }
}

 

1 Like

Oh thanks Samuel for the example here ! this is really nice from you 

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();
        }
}

Rail

3 Likes

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?