Ring Buffer Implementation in juce_Sampler

Below is my modifications to juce_Sampler so that it’s access to samples is continuous, a hacked-in ring buffer to effect.

So as you can see the original code … DoInterpolate() works perfectly.
My “ringbuffer” equivalent GetSampleInterpolatedLinear() sounds distorted.

I’ll let you know how i’m doing, but if anyone see’s my problem in the mean time or can help me out great.

[code]float wrap(float f)
{
if(f < 0.f){return f + 1.0f;}
if(f > 1.f){return f - 1.0f;}
return f;
}

UINT32 index_wrap(const int in, const int max)
{
int i = in;
while(i < 0){i += max;}
while(i >= max){i -= max;}
return i;
}

float LinearInterpolate(float a, float b, float i)
{
return (b - a) * i + a;
}

//NEW INTERPOLATION (SOUNDS DISTORTED)
void GetSampleInterpolatedLinear(juce::AudioSampleBuffer& buff, int m_len, float m_reciplen, double& m_phase, float pitch, float* l, float* r)
{
const float index = m_phase * float(m_len);

m_phase += pitch * m_reciplen;
wrap(m_phase);

*l = LinearInterpolate(*buff.getSampleData(0, index_wrap(int(index), m_len)), *buff.getSampleData(0, index_wrap(int(index)+1, m_len)), index - int(index));
*r = LinearInterpolate(*buff.getSampleData(1, index_wrap(int(index), m_len)), *buff.getSampleData(0, index_wrap(int(index)+1, m_len)), index - int(index));

}

//OLD INTERPOLATION (WORKS PERFECTLY)
void DoInterpolate(float* l, float r, const float inL, const float* inR, int m_len, double& sourceSamplePosition, float pitch)
{
const int pos = (int) sourceSamplePosition;
const float alpha = (float) (sourceSamplePosition - pos);
const float invAlpha = 1.0f - alpha;

// just using a very simple linear interpolation here..
*l = (inL [pos] * invAlpha + inL [pos + 1] * alpha);
*r = (inR != nullptr) ? (inR [pos] * invAlpha + inR [pos + 1] * alpha)
                            : *l;

sourceSamplePosition += pitch;
if (sourceSamplePosition > m_len)
	sourceSamplePosition = 0;

}

//==============================================================================
void SamplerVoice::renderNextBlock (AudioSampleBuffer& outputBuffer, int startSample, int numSamples)
{
const SamplerSound* const playingSound = static_cast <SamplerSound*> (getCurrentlyPlayingSound().getObject());

if (playingSound != nullptr)
{
	const float reciplen = 1.0f/playingSound->length;

    const float* const inL = playingSound->data->getSampleData (0, 0);
    const float* const inR = playingSound->data->getNumChannels() > 1
                                ? playingSound->data->getSampleData (1, 0) : nullptr;

    float* outL = outputBuffer.getSampleData (0, startSample);
    float* outR = outputBuffer.getNumChannels() > 1 ? outputBuffer.getSampleData (1, startSample) : nullptr;

    while (--numSamples >= 0)
    {
        float l = 0.f;
		float r = 0.f;

		//DoInterpolate(&l, &r, inL, inR, playingSound->length, sourceSamplePosition, pitchRatio);
		GetSampleInterpolatedLinear(*playingSound->data, playingSound->length, reciplen, sourceSamplePosition, pitchRatio, &l, &r);

        l *= lgain;
        r *= rgain;

        if (isInAttack)
        {
            l *= attackReleaseLevel;
            r *= attackReleaseLevel;

            attackReleaseLevel += attackDelta;

            if (attackReleaseLevel >= 1.0f)
            {
                attackReleaseLevel = 1.0f;
                isInAttack = false;
            }
        }
        else if (isInRelease)
        {
            l *= attackReleaseLevel;
            r *= attackReleaseLevel;

            attackReleaseLevel += releaseDelta;

            if (attackReleaseLevel <= 0.0f)
            {
                stopNote (false);
                break;
            }
        }

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

}[/code]

Okay so if we ignore my silly code for a second here’s the original interpolate function that sounds good:

BUT I have added:
while(sourceSamplePosition < 0){sourceSamplePosition += m_len;}
while(sourceSamplePosition >= m_len){sourceSamplePosition -= m_len;}

Which should loop the sample position like a ring buffer.

I have a seamless audio clip in ogg format, and although it should now loop seamlessly (as in my SDL demo) … it still clicks, surly the solution is correct?

Is it because im using the ogg format? I hear that empty bytes are added to the beginning of OGG streams, does JUCE not clip these off?

[code]void DoInterpolate(float* l, float r, const float inL, const float* inR, int m_len, double& sourceSamplePosition, float pitch)
{
const int pos = (int) sourceSamplePosition;
const float alpha = (float) (sourceSamplePosition - pos);
const float invAlpha = 1.0f - alpha;

// just using a very simple linear interpolation here..
*l = (inL [pos] * invAlpha + inL [pos + 1] * alpha);
*r = (inR != nullptr) ? (inR [pos] * invAlpha + inR [pos + 1] * alpha)
                            : *l;

sourceSamplePosition += pitch;
while(sourceSamplePosition < 0){sourceSamplePosition += m_len;}
while(sourceSamplePosition >= m_len){sourceSamplePosition -= m_len;}

}[/code]

well if your loop is of the size of the whole sample then inL [pos + 1] can read bogus value when pos = m_len

I wrote a simple linearInterpolate function a while ago that works pretty well, you can see the code in this file.

But basically like otristan says, you’re not taking into account the buffer size for pos + 1 cases.

This correction sounds better, but still clicks :frowning:

while(sourceSamplePosition < 0){sourceSamplePosition += m_len-1;}
while(sourceSamplePosition >= m_len-1){sourceSamplePosition -= m_len-1;}

It’s these lines that are causing the problem:

*l = (inL [pos] * invAlpha + inL [pos + 1] * alpha); *r = (inR != nullptr) ? (inR [pos] * invAlpha + inR [pos + 1] * alpha) : *l;

The bit you just changed should be how it originally was otherwise you will not be shifting your sourceSamplePosition up correctly. Personally I would separate out the interpolation and the buffer position changing into two different functions so its clearer what is going on.

[code]int wrapd(const int d, const int m_len)
{
int r = d;
while(r < 0){r += m_len;}
while(r >= m_len){r -= m_len;}
return r;
}

void DoInterpolate(float* l, float r, const float inL, const float* inR, int m_len, double& sourceSamplePosition, float pitch)
{
const int pos = (int) sourceSamplePosition;
const float alpha = (float) (sourceSamplePosition - pos);
const float invAlpha = 1.0f - alpha;

// just using a very simple linear interpolation here..
*l = (inL [wrapd(pos, m_len)] * invAlpha + inL [wrapd(pos + 1, m_len)] * alpha);
*r = (inR != nullptr) ? (inR [pos] * invAlpha + inR [pos + 1] * alpha)
                            : *l;

sourceSamplePosition += pitch;
while(sourceSamplePosition < 0){sourceSamplePosition += m_len;}
while(sourceSamplePosition >= m_len){sourceSamplePosition -= m_len;}

}[/code]

Still a click, but if I pass 0.f on attack and release time it seems to loop perfectly, however if I comment out the effect on l & r buffers of the envelope in renderBlock() it still clicks?!

synth.addSound (new SamplerSound ("ogg sound", *audioReader, note, i, // root midi note 0.f, // attack time 0.f, // release time 10.0 // maximum sample length ));

I'm not sure if this is still relevant to you, or what your final solution was, but I just changed:

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

to:

if (sourceSamplePosition > playingSound->length)
            {
                sourceSamplePosition = sourceSamplePosition - playingSound->length;
            }

I haven't tested this too extensively, but for a basic drumloop it seems to work just fine

 

edit: remove the 'break' from the position reset  and works great with even a sinewave

The old link doesn’t work. Here’s the updated (Nov 2023) linearInterpolate link that @dave96 referenced.