Pitch shifter plugin (fix output noise)

Hi all,

I’m a newbie JUCE user trying to implement a pitch-shifter plugin. (By pitch-shifter, I mean retain original playback speed, but change the pitch). I’m using the Soundtouch library, and roughly following the usage in this pehrtree github project.

The pitch shifting sort of works, but the output has a lot of noise in it, crackles, pops, and it also seems to retain the original signal. I’d like only the pitch shifted signal. I suspect I’m not using the library properly at all, and would appreciate advice or comments. The relevant code is included below.

Thanks in advance!

In PluginProcessor.h:

int semitone_shift = 0;
soundtouch::SoundTouch shifter;

In PluginEditor.cpp, in the callback for slider value changed:

audioProcessor.semitone_shift = (int) slider->getValue();
audioProcessor.shifter.setPitchSemiTones(audioProcessor.semitone_shift);

Setup the SoundTouch object, in PluginProcessor.cpp:

void Pitch_shiftAudioProcessor::prepareToPlay (double sampleRate, int samplesPerBlock)
{
    // Use this method as the place to do any pre-playback
    // initialisation that you need..
    shifter.setSampleRate((int)sampleRate);
    shifter.setChannels(2);
}

And the main processing, in PluginProcessor.cpp:

void Pitch_shiftAudioProcessor::processBlock (juce::AudioBuffer<float>& buffer, juce::MidiBuffer& midiMessages)
{
    juce::ScopedNoDenormals noDenormals;
    auto totalNumInputChannels  = getTotalNumInputChannels();
    auto totalNumOutputChannels = getTotalNumOutputChannels();

// In case we have more outputs than inputs, this code clears any output
// channels that didn't contain input data, (because these aren't
// guaranteed to be empty - they may contain garbage).
// This is here to avoid people getting screaming feedback
// when they first compile a plugin, but obviously you don't need to keep
// this code if your algorithm always overwrites all the output channels.
    for (auto i = totalNumInputChannels; i < totalNumOutputChannels; ++i)
        buffer.clear (i, 0, buffer.getNumSamples());

// This is the place where you'd normally do the guts of your plugin's
// audio processing...
// Make sure to reset the state if your inner loop is processing
// the samples and the outer loop is handling the channels.
// Alternatively, you can process the samples with the channels
// interleaved by keeping the same state.

    int numSamples = buffer.getNumSamples();
    for (int channel = 0; channel < totalNumInputChannels; ++channel)
    {
        auto* channelData = buffer.getWritePointer (channel);
    
        // ..do something to the data...
    }
    shifter.putSamples(buffer.getReadPointer(0), numSamples);
    shifter.receiveSamples(buffer.getWritePointer(0), numSamples);


}

Here is old thread that I found useful when I started out:

Pops and clicks are often discontinuities at the beginning or end of a buffer. Jules’ write-up addresses the mistakes most of make in the beginning.

ADDED: lots of good comments in the linked thread.

It’s not about that issue, SoundTouch internally supports multichannel processing. (And the original poster already has set it up to process 2 channels in the prepareToPlay code.)

SoundTouch is just a very finicky library to work with, you have to provide it a lot of input samples before it can start outputting audio. The original poster’s code is wrong, the putSamples and receiveSample calls can’t be made like in that code. One has to call putSamples with new samples so long that there’s actually enough audio to get with the receiveSamples call.

Ah, thanks, @xenakios . That’s what I was doing wrong, then. I felt I was doing something wrong but didn’t know what. Your comments are very helpful. I’ll modify it to do what you suggested, and report on the results.

If you didn’t already notice, the numSamples() method in SoundTouch tells how many samples you can currently get out from receiveSamples.

Here is my next attempt. It still sounds very noisy, and I think I’m still missing a couple of fundamental ideas. For instance, if there aren’t enough processed samples available to fill the buffer, I’m filling in zeros. This is probably what’s causing the noise, but I don’t know how to tell processBlock to not output a block. I guess, ideally the samples going to the output should be a contiguous stream of samples from soundtouch, but not sure how to achieve that.

Also, I’m assuming that each block of samples are to be laid out in the audio buffer channel by channel, i.e first all the channel 1 samples, then all the channel 2 samples, in a contiguous block of memory. That is what soundtouch appears to be doing, so I’m hoping that my putSamples call with getReadPointer(0) is the correct thing to do.

int numSamples = buffer.getNumSamples();
shifter.putSamples(buffer.getReadPointer(0), numSamples);  // Is this correct?

int samplesAvailable = shifter.numSamples();
if (samplesAvailable >= numSamples)
{
    shifter.receiveSamples(buffer.getWritePointer(0), numSamples);
}
else
{
    for (int channel = 0; channel < totalNumInputChannels; ++channel)
    {
        auto* channelData = buffer.getWritePointer (channel);
        for (int idx = 0; idx < numSamples; idx++)
        {
              // Spurious data - how to make this a continuous stream from soundtouch?
              channelData[idx] = 0;  
        }
        
    }
}

I wouldn’t assume or hope anything when programming - make sure instead!