Can you help me to understand "Interpolation" on reading samples?

DSP, Plugin and Host development discussion.
RELATED
PRODUCTS

Post

ToneCarver wrote:The 'base + 1' could exceed the end of the array when base is at the end of the array.
Of course ;) Already done!
ToneCarver wrote: Setting the index_pointer to 0 would discard any fractional part that has accumulated when wrapping to the front of the buffer (essentially resetting the fraction to 0 when it should not be).
Uhm, I'm lost here. The fractional part (since I am at the end) is already processed.
What do you mean? Can you give to me an example? Thanks dudes! Love you.

Post

Nowhk wrote:
ToneCarver wrote:The 'base + 1' could exceed the end of the array when base is at the end of the array.
Of course ;) Already done!
ToneCarver wrote: Setting the index_pointer to 0 would discard any fractional part that has accumulated when wrapping to the front of the buffer (essentially resetting the fraction to 0 when it should not be).
Uhm, I'm lost here. The fractional part (since I am at the end) is already processed.
What do you mean? Can you give to me an example? Thanks dudes! Love you.
Take a small example and work it out on paper. Go through the steps of interpolating, say, 4 samples at a rate of 0.72 or 1.119 (for example). What is the value of the fraction after the 4th sample is output? Shouldn't that fraction be applied as the weighting between the earlier sample (which happens to be at the last position in the WAV) and the subsequent sample (which happens to be at the first position in the WAV) to keep the pitch translation consistent?

Post

ToneCarver wrote:
Nowhk wrote:
ToneCarver wrote:The 'base + 1' could exceed the end of the array when base is at the end of the array.
Of course ;) Already done!
ToneCarver wrote: Setting the index_pointer to 0 would discard any fractional part that has accumulated when wrapping to the front of the buffer (essentially resetting the fraction to 0 when it should not be).
Uhm, I'm lost here. The fractional part (since I am at the end) is already processed.
What do you mean? Can you give to me an example? Thanks dudes! Love you.
Take a small example and work it out on paper. Go through the steps of interpolating, say, 4 samples at a rate of 0.72 or 1.119 (for example). What is the value of the fraction after the 4th sample is output? Shouldn't that fraction be applied as the weighting between the earlier sample (which happens to be at the last position in the WAV) and the subsequent sample (which happens to be at the first position in the WAV) to keep the pitch translation consistent?
Oh damn you are totally right! Its true. How would you fix it so? The idea I have is to compensate with a sort of index_pointer= index_pointer - 1 and than iterate again. (so the next will be -1, than 0 again). But I'm not sure... it looks too much easy :o

Post

Nowhk wrote:
ToneCarver wrote:
Nowhk wrote:
ToneCarver wrote:The 'base + 1' could exceed the end of the array when base is at the end of the array.
Of course ;) Already done!
ToneCarver wrote: Setting the index_pointer to 0 would discard any fractional part that has accumulated when wrapping to the front of the buffer (essentially resetting the fraction to 0 when it should not be).
Uhm, I'm lost here. The fractional part (since I am at the end) is already processed.
What do you mean? Can you give to me an example? Thanks dudes! Love you.
Take a small example and work it out on paper. Go through the steps of interpolating, say, 4 samples at a rate of 0.72 or 1.119 (for example). What is the value of the fraction after the 4th sample is output? Shouldn't that fraction be applied as the weighting between the earlier sample (which happens to be at the last position in the WAV) and the subsequent sample (which happens to be at the first position in the WAV) to keep the pitch translation consistent?
Oh damn you are totally right! Its true. How would you fix it so? The idea I have is to compensate with a sort of index_pointer= index_pointer - 1 and than iterate again. (so the next will be -1, than 0 again). But I'm not sure... it looks too much easy :o
Actually, once you work out the details of handing the corner cases the algorithm ends up being fairly simple. An approach I use is to have two indexes, one for the location of the earlier sample and one for the location of the later sample.

Once you get the basic algorithm all figured out it is only a little more complicated to extend it to handle playback rates greater than 2 and/or negative playback rates to drive the playback in a reverse direction. :)

Post

ToneCarver wrote:An approach I use is to have two indexes, one for the location of the earlier sample and one for the location of the later sample.
Actually, I think using a single index and fetching the before and after samples independently is a little simpler and more clear.

Post

I vaguely recall, in such instances, using a zero-based interpolation sample buffer.

If the buffer will hold N samples, then buffer[0] holds the last sample from the last-processed buffer.
The current buffer samples are stored in buffer[1] thru buffer[N]
The var BufferTop, used to avoid over-running the top of the buffer, also equals N.

First setting up before processing the first buffer, buffer[0] is set to zero, and FractionalIndex is set to 1 so it begins on the first valid sample in the first audio buffer received.

For instance if the FractionalIndexIncrement might be 0.67 or whatever. Do a loop calculating each interpolated sample then incrementing FractionalIndex by FractionalIndexIncrement. Continue the loop as long as FractionalIndex < BufferTop.

When FractionalIndex >= BufferTop--
* stop the loop
* copy the last sample to buffer[0]: buffer[0] = buffer[BufferTop];
* decrement FractionalIndex by N: FractionalIndex = FractionalIndex - BufferTop;

For instance if the buffer is 1024 samples long. If at the end of a loop, FractionalIndex == 1024.33, then after subtracting 1024 FractionalIndex == 0.33. We copy buffer[1024] to buffer[0].

So then when the next interpolation buffer arrives, we fill the new samples into buffer[1] thru buffer [1024]. Because the remembered, adjusted FractionalIndex == 0.33, when we start the next interpolation loop on the new samples, the first interpolation in the new loop will be between buffer[0] and buffer[1]. Buffer[0] being the last sample from the last set of samples, and buffer[1] being the first sample in the new set of samples. So the interpolation continues across buffer bounds.

This is only good for simple interpolations needing two samples. Using fancier interpolations requiring more samples, the same pointer scheme might be adapted to stop the loop earlier and copy-backwards more samples. Or maybe if the interpolation method requires many samples, then some other buffering method would be better.

Post

ToneCarver wrote:Go through the steps of interpolating, say, 4 samples at a rate of 0.72 or 1.119 (for example). What is the value of the fraction after the 4th sample is output?
Maybe I've done it. Let say I've 4 samples, [8, 12, 16, 20], speed 0.72.
It will be:

1.0*in[0] + 0.0*in[1]=8
0.28*in[0] + 0.72*in[1]=10.88
0.56*in[1] + 0.44*in[2]=13.76
0.84*in[2] + 0.14*in[3]=16.64
0.12*in[2] + 0.88*in[3]=19.52
0.4*in[3] + 0.6*in[0]=12.8

Code: Select all

foreach sample:
   base = floor(index_pointer)
   frac = index_pointer - base
   out = in[base] * (1 - frac) + in[(base + 1) % N] * frac
   index_pointer += speed
   if(index_pointer>=sample_length) 
   {
      index_pointer = 0
   }
i.e. (base + 1) % N

What do you think? It should also work with speed > 2... Am I correct?

Post

Nowhk wrote:i.e. (base + 1) % N

What do you think? It should also work with speed > 2... Am I correct?
Yes, that will work for wrapping the index. My personal preference is to avoid divides so I typically increment and wrap indexes using explicit conditionals, but using a modulo operation is logically equivalent.
Nowhk wrote:

Code: Select all

1.0*in[0] + 0.0*in[1]=8
0.28*in[0] + 0.72*in[1]=10.88
0.56*in[1] + 0.44*in[2]=13.76
0.84*in[2] + 0.14*in[3]=16.64
0.12*in[2] + 0.88*in[3]=19.52
0.4*in[3] + 0.6*in[0]=12.8
So ... what is the value of the index_pointer at the start of the next step (after the in[3] + in[0] step)? i.e, what are the x.x. and y.y values in 'x.x * in[0] + y.y * in[1]'?

Post

ToneCarver wrote: So ... what is the value of the index_pointer at the start of the next step (after the in[3] + in[0] step)? i.e, what are the x.x. and y.y values in 'x.x * in[0] + y.y * in[1]'?
Well. Since after that there is this check:

Code: Select all

   index_pointer += speed
   if(index_pointer>=sample_length) 
   {
      index_pointer = 0
   }
it will back (again) to the first iteration (because index_pointer reset): 1.0*in[0] + 0.0*in[1]=8.

I'll got somethings like this (dashed lines are the interpolated sample points, the circles are the input values):

Image
Is it correct boss?

Post

Nowhk wrote:
ToneCarver wrote: So ... what is the value of the index_pointer at the start of the next step (after the in[3] + in[0] step)? i.e, what are the x.x. and y.y values in 'x.x * in[0] + y.y * in[1]'?
Well. Since after that there is this check:

Code: Select all

   index_pointer += speed
   if(index_pointer>=sample_length) 
   {
      index_pointer = 0
   }
it will back (again) to the first iteration (because index_pointer reset): 1.0*in[0] + 0.0*in[1]=8.

I'll got somethings like this (dashed lines are the interpolated sample points, the circles are the input values):

Image
Is it correct boss?
I did not phrase my question correctly. :ud: I see that the algorithm sets the index_pointer to 0, but should it? Resetting to 0 discards the fractional part of the index_pointer which means the distance traveled between the 5th and 6th outputs is different than the distance between the 1st, 2nd, 3rd, and 4th outputs, so the speed is not constant. The algorithm needs to account for the fractional overshoot in the index_pointer to keep the speed constant. Make sense?

Post

Damn you are right! I hoped I got it. I can't figure it out how to keep it constant so, keeping a correct interpolation base and base+1 later. Please, can you help me? Maybe I am not so clever :(

Post

Nowhk wrote:... I can't figure it out how to keep it constant so, keeping a correct interpolation base and base+1 later.

Code: Select all

   index_pointer += speed
   if (index_pointer > (sample_length - 1)) 
   {
      index_pointer -= sample_length
   }
?

Post

Max M. wrote:
Nowhk wrote:... I can't figure it out how to keep it constant so, keeping a correct interpolation base and base+1 later.

Code: Select all

   index_pointer += speed
   if (index_pointer > (sample_length - 1)) 
   {
      index_pointer -= sample_length
   }
?
Yes of course! But I'm not sure if this will works with "loop" that will start N (integer) samples later. For example: if I play from 0 the first time, than from the 3° samples (Loop Start) will this keep the "speed" even if the starting base it is not 0 anymore?

Also: shouldn't be:

Code: Select all

index_pointer = index_pointer - (sample_length -1)
Else the iteration will end with a negative index_pointer (index_pointer of -0.51 will be -1, not 0; so the next "base" is -1, not 0, which is wrong).

Post

Nowhk wrote:For example: if I play from 0 the first time, than from the 3° samples (Loop Start) will this keep the "speed" even if the starting base it is not 0 anymore?
To be honest I'm not quite sure I follow what is "than from the 3° samples (Loop Start)", but it's pretty much standard building block in general (think flanger/chorus fractional delay line for example). `index_pointer -= sample_length` just wraps the pointer around buffer size but preserves the accumulated fractional part.

P.S.
Nowhk wrote: Also: shouldn't be:

Code: Select all

index_pointer = index_pointer - (sample_length -1)
Else the iteration will end with a negative index_pointer...
You're right, my mistake. But correct code should then be:

Code: Select all

   base = floor(index_pointer)
   frac = index_pointer - base
   out = in[base] * (1 - frac) + in[(base + 1) % sample_length] * frac
   index_pointer += speed
   if(index_pointer >= sample_length) 
   {
      index_pointer -= sample_length
   }
Last edited by Max M. on Tue Feb 16, 2016 3:21 pm, edited 1 time in total.

Post

Max M. wrote:To be honest I'm not quite sure I follow what is "than from the 3° samples (Loop Start
I'm looping a sample. After I read it from the beginning (index 0), the loop will start forward than position 0 (let say, from 3° sample, in the above example with 4 samples).

For the second part of the question, I meant (if index_pointer is 6.49 and sample_length 7, for example):

Code: Select all

if (index_pointer > (sample_length - 1)) 
   {
      index_pointer -= sample_length
   }
will be 6.48-7=-0,52, which floor to -1 the next iteration.
So:

index_pointer=-0.52
base=-1
frac=0.48.

Shouldn't be

index_pointer=0.48 (6.48-6)
base=0
frac=0.52

keeping base 0 as starting/relooping point? Honestly base -1 or base 0 change nothing (since I check if read from base>=0; else base=0), but the weight (0.48 instead of 0.52) change. What's the correct value?

Post Reply

Return to “DSP and Plugin Development”