breakfastquay / rubberband

Official mirror of Rubber Band Library, an audio time-stretching and pitch-shifting library.
http://breakfastquay.com/rubberband/
GNU General Public License v2.0
566 stars 91 forks source link

`setTimeRatio` does nothing and `setPitchScale` only resamples #46

Closed woolgathering closed 3 years ago

woolgathering commented 3 years ago

I'm not sure if it's a usage issue or what but there are 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.

cannam commented 3 years ago

Is shifter a RubberBandStretcher object pointer?

If you have constructed it in offline mode, then time-stretching is a two-pass procedure - you must go through the input file block by block and pass each block to the study method (setting the final argument to true for the last block) first, before then going through and passing each block to process.

It is study that determines where the transient sync points should go for proper synchronisation in the subsequent process calls - without that, the offline process has no stretch profile to follow.

(If this does turn out to be the cause of the issue, then I'll have to see if I can make it clearer in the documentation!)

woolgathering commented 3 years ago

Ah, that makes sense and it was it.

The documentation in setTimeRatio makes it seem like it is possible to run this without study (since it also runs in realtime). To be clear, one needs to call study in offline mode for the timeScale to do anything but doesn't need to call study in realtime mode?

cannam commented 3 years ago

That's correct - study is for offline mode only.

In offline mode: construct and configure, set time and pitch ratios, study the file in blocks, process the file in blocks (output is produced as you run process). You can't change the ratios after the first call to study.

In realtime mode: construct and configure, then process the stream in blocks, setting time and pitch ratios whenever you like (output is produced as you run process, though as you correctly say, handling latency can be tricky). No study.

cannam commented 3 years ago

I've added some notes on this to the inline docs. Closing the ticket.