(Another) Rubberband Usage Help

Ok, I’m at a complete loss as to how to get Rubberband working. Two problems:

  1. setTimeRatio does nothing to the output. Setting this to any value has no effect.
  2. setPitchScale only resamples. I.e. if set to 2, there is an octave shift upwards but the duration of halved.

I’ve tried using a circular buffer for input/output, reading/writing directly to and from vectors, arrays, juce::AudioBuffer, etc. The sample values are not exactly the same but are extremely close which makes me think it is a function of the input being processed by whatever FFT process and being resynthesized.

I’m attempting to do this all offline so I don’t have to deal with latency since time-stretching is inherently non-realtime.

A toned-down example with lots of experiments removed:

void PitchShifter::processOffline (AudioBuffer<float>* buffer, const String& writePath)
{
    int                      samples        = buffer->getNumSamples();
    size_t                   sampsToProcess = 0;
    size_t                   numProcessed   = 0;
    int                      numAvailable   = 0;
    size_t                   writeOffset    = 0;
    size_t                   processedInBlock     = 0;
    bool                     saved          = false;
    size_t                   blockSize      = 512;
    juce::AudioBuffer<float> output (buffer->getNumChannels(), buffer->getNumSamples()*2);
    float*                   inPtrs[buffer->getNumChannels()]; // <- these are in/outs of RubberBand
    float*                   outPtrs[output.getNumChannels()]; // <- these are in/outs of RubberBand

    shifter->reset();
    shifter->setMaxProcessSize(blockSize);
    output.clear(); // make sure things are clear

    /*
        I don't know why the time scaling isn't working and why the pitch is not
        independent of time! Note that when you set pitchscale to 2, the duration
        also halves. WTF.
    */
    shifter->setPitchScale (pitchScale);
    shifter->setTimeRatio (timeRatio);

    samplesRequired = static_cast<int> (shifter->getSamplesRequired());

    // do the whole file
    while (numProcessed < samples)
    {

        // do each block of blocksize samples (512)
        while (processedInBlock < blockSize && numProcessed < samples)
        {
            // update the pointers
            for (size_t c = 0; c < buffer->getNumChannels(); ++c)
            {
                inPtrs[c]  = (float* const) buffer->getReadPointer (static_cast<int> (c), numProcessed);
                outPtrs[c] = (float* const) output.getWritePointer (static_cast<int> (c), writeOffset);
            }

            sampsToProcess = std::min (blockSize, std::min (shifter->getSamplesRequired(), samples - numProcessed)); // pass the minumum required
            // sampsToProcess = std::min (blockSize, samples - numProcessed);

            if (numProcessed + sampsToProcess <= samples)
            {
                shifter->process (inPtrs, sampsToProcess, false);
            }
            else
            {
                shifter->process (inPtrs, sampsToProcess, true);
            }

            // return as many samples as we can
            numAvailable = shifter->available();
            if (numAvailable > 0)
            {
                writeOffset += shifter->retrieve (outPtrs, numAvailable);;

            }

            numProcessed += sampsToProcess;
            processedInBlock += sampsToProcess;
            numAvailable = 0; // just in case
        }


        processedInBlock = 0;
        while (shifter->available() > 0)
        {
            numAvailable = shifter->available();
            writeOffset += shifter->retrieve (outPtrs, numAvailable);
        }
    }

    saved = saveBufferToFile (writePath, output, fs); // function that lives elsewhere
}

This simply ends up writing the input buffer to the output buffer when shifter->setPitchRatio(1) and shifter->setTimeScale(x) where x is literally any number.

Any help or pointing out glaring errors is appreciated.

Hi,

Haven’t used rubberband for a while, don’t know what your exact problem is but, off the top of my head:

  • It seems you’re reading from and writing to the same buffer, i guess this might be an issue if you’re slowing down since for N samples fed you should have more than N samples output, so you may be overriding your input before you ever read it.
  • There are quite a few processing options that can be set, maybe you don’t use the correct ones?
  • The bottom of this page seems to reference a few examples, probably worth you have a look at it: https://breakfastquay.com/rubberband/integration.html

Hope this helps.

Figured it out (at least this part). I wasn’t calling study and that is required when running in offline mode. Will be writing up a “realtime” one, so I’ll see how it behaves.

For future reference (in pseudo-code):

void PitchShifter::processOffline (AudioBuffer<float>* buffer, const String& writePath)
{
    int                      samples        = buffer->getNumSamples();
    size_t                   sampsToProcess = 0;
    size_t                   numProcessed   = 0;
    int                      numAvailable   = 0;
    size_t                   writeOffset    = 0;
    size_t                   processedInBlock     = 0;
    bool                     saved          = false;
    size_t                   blockSize      = 512;
    juce::AudioBuffer<float> output (buffer->getNumChannels(), buffer->getNumSamples()*2);
    float*                   inPtrs[buffer->getNumChannels()]; // <- these are in/outs of RubberBand
    float*                   outPtrs[output.getNumChannels()]; // <- these are in/outs of RubberBand

    shifter->reset();
    shifter->setMaxProcessSize(blockSize);
    output.clear(); // make sure things are clear

    shifter->setPitchScale (pitchScale);
    shifter->setTimeRatio (timeRatio);

    // set the pointers
    for (size_t c = 0; c < buffer->getNumChannels(); ++c)
    {
        inPtrs[c]  = (float* const) buffer->getReadPointer (static_cast<int> (c), numProcessed);
        outPtrs[c] = output.getWritePointer (static_cast<int> (c), writeOffset);
    }
    shifter->study(inPtrs, samples, true);

    // do the whole file
    while (numProcessed < samples)
    {

        // do each block of blocksize samples (512)
        while (processedInBlock < blockSize && numProcessed < samples)
        {
            // update the pointers
            for (size_t c = 0; c < buffer->getNumChannels(); ++c)
            {
                inPtrs[c]  = (float* const) buffer->getReadPointer (static_cast<int> (c), numProcessed);
                outPtrs[c] = (float* const) output.getWritePointer (static_cast<int> (c), writeOffset);
            }

            sampsToProcess = std::min (blockSize, std::min (shifter->getSamplesRequired(), samples - numProcessed)); // pass the minumum required
            // sampsToProcess = std::min (blockSize, samples - numProcessed);

            if (numProcessed + sampsToProcess <= samples)
            {
                shifter->process (inPtrs, sampsToProcess, false);
            }
            else
            {
                shifter->process (inPtrs, sampsToProcess, true);
            }

            // return as many samples as we can
            numAvailable = shifter->available();
            if (numAvailable > 0)
            {
                writeOffset += shifter->retrieve (outPtrs, numAvailable);;

            }

            numProcessed += sampsToProcess;
            processedInBlock += sampsToProcess;
            numAvailable = 0; // just in case
        }


        processedInBlock = 0;
        while (shifter->available() > 0)
        {
            numAvailable = shifter->available();
            writeOffset += shifter->retrieve (outPtrs, numAvailable);
        }
    }

    saved = saveBufferToFile (writePath, output, fs); // function that lives elsewhere
}