Sampler Voice Interpolation

Hello!

I am making a sampler instrument and utilizing juce::SamplerVoice to make it. I realized the linear interpolation it uses is not the quality I want. I am trying to modify SamplerVoice::renderNextBlock to use Lagrange Interpolation and I have it mostly working but its making small clicking noises. The waveform is slightly distorted every buffer length. I think it has to do with me converting the sourceSamplePosition to int but not sure how else to do it.

Here’s what I’m currently doing:

    auto& data = *playingSound->data;
    const float* const inL = data.getReadPointer (0);
    const float* const inR = data.getNumChannels() > 1 ? data.getReadPointer (1) : nullptr;
    
    voiceBuffer = AudioBuffer<float>(2, numSamples);
    
    float* voiceWriteL = voiceBuffer.getWritePointer (0, 0);
    float* voiceWriteR = voiceBuffer.getWritePointer (1, 0);

    float* outL = outputBuffer.getWritePointer (0, startSample);
    float* outR = outputBuffer.getNumChannels() > 1 ? outputBuffer.getWritePointer (1, startSample) : nullptr;
    
    if (outL != nullptr)  {   lagrangeResamplerL.process(pitchRatio, inL + (int) sourceSamplePosition, voiceWriteL, numSamples);  }
    if (outR != nullptr)  {   lagrangeResamplerR.process(pitchRatio, inR + (int) sourceSamplePosition, voiceWriteR, numSamples);  }
    
    for (int i = 0; i < numSamples; ++i)
    {
        
        float l = voiceBuffer.getSample(0, i);
        float r = voiceBuffer.getSample(1, i);

        auto envelopeValue = adsr.getNextSample();

        l *= lgain * envelopeValue;
        r *= rgain * envelopeValue;

        if (outR != nullptr)
        {
            *outL++ += l;
            *outR++ += r;
        }
        else
        {
            *outL++ += (l + r) * 0.5f;
        }
        
        sourceSamplePosition += pitchRatio;

        if (sourceSamplePosition > playingSound->length)
        {
            stopNote (0.0f, false);
            break;
        }
    }

Any help is super appreciated! I am very new to this!

I think you are on the right track. Keep in mind why the interpolation is necessary in the first place. In case you are playing the sample data faster or slower (for pitching) you’re precision sample position (double) could end up between integers.

Therefore, ‘sourceSamplePosition’ is the “x-Value” (time position) you want to interpolate the “y-Value” (sample value) for. I might not see the whole picture but I can’t figure out how the method lagrangeResamplerL::process could perform this interpolation without receiving the actual x-Value you want to interpolate the y-Value for.
Could you share your interpolation algorithm?

Regards,
Rincewind

Thank you Ricewind!!

I’m just using JUCE’s built in Lagrange Interpolation. Yeah I think you are right that im trying to make something work that’s really possible. I should probably be resampling the entire audio at once, not just 1 buffer at a time thanks!

Really appreciate your thoughtful response!

I’m not so sure that the JUCE interpolator really does what you what. As far as I can see it resamples a hole stream of values. So the interpolator should keep track of the “samplePosition” itself. I find the docs a bit misleading as the state that the interpolators have some kind of state (that could readPos or in other words the already used input values) or some other state, that we are not interested in. I took a look in the code, but I couldn’t quite figure it out.
Try just skipping + (int) samplePosition for the input array (and call the reset method for interpolator only in noteStarted)
If it still doesn’t work, you may want to write your ohne interpolation method

TSampleValue iterpolateLagrange(const TSampleValue* inData, double position) // Check Wikipedia for the correct algorithm. Should be pretty straight forward. Make sure to cover the edge cases correctly (aka. start of sound and end of sound)

You can call this method for each sample inside your for loop (you already have the for loop, where you increment the sample position).

Regards,
Rincewind

Little side note on “sample the whole audio at once” That could work, but you’ve got some pretty serious problems already: The pitch could change any second (by the pitch wheel). To avoid clipping noises you should use a SmoothedValue for managing your pitch. The SmoothedValue is with your current approach (sample a whole buffer at once) already out of scope, as the pitchRatio can’t change between two samples inside the same buffer.

If you pitchRatio is fixed the moment the note starts, you could resample the whole input buffer but may experience some real performance issues when handling larger samples. The audio block starting the note will always be more busy than all other blocks. I’m trying to avoid such performance spikes and have a constant performance during the whole runtime no matter what the sampler has to do. (Unless of course there are some obvious calculation differences. E.g. not playing a sound at all will always be way faster or avoiding to pitching entirely)

Thank You Ricewind!

I ended up making my own Interpolation method based on spline and I got it working well!
I appreciate your responses they helped me understand I was going down the wrong path

Best,
Skyler

Hey there, could you please explain how your interpolation method works?

Yeah, instead of using a function to process them, I simply found the equation for the formula and put it directly into where I needed to process each individual sample.

1 Like