0

I've this code that iterate some samples and build a simple linear interpolation between the points:

foreach sample:
   base = floor(index_pointer)
   frac = index_pointer - base
   out = in[base] * (1 - frac) + in[base + 1] * frac
   index_pointer += speed

   // restart
   if(index_pointer >= sample_length) 
   {
      index_pointer = 0
   }

using "speed" equal to 1, the game is done. But if the index_pointer is different than 1 (i.e. got fractional part) I need to wrap last/first element keeping the translation consistent.

How would you do this? Double indexes?

Here's an example of values I have. Let say in array of 4 values: [8, 12, 16, 20].

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[4]=8 // wrong; here I need to wrapper

the last point is wrong. [4] will be 0 because I don't have [4], but the first part need to take care of 0.4 and the weight of first sample (I think?).

markzzz
  • 47,390
  • 120
  • 299
  • 507
  • You might be able to improve your code example some. Wrapping it into a function would be a good start, as then, we are able to see what the intended inputs and outputs shall be. There seems to be an array ``in``, which contains values. So, if the array is a time series, for example, one would assume from your code, that the time steps are equidistant and your function would look like this: ``interpolate: deltaT : float -> data : float array -> x : float -> float``. Would that be it? – BitTickler Feb 15 '16 at 09:11
  • Yes, `in` is the array of points (equidistant) I have. I've added an example. Is it enough? Or are you asking to me somethigs more? – markzzz Feb 15 '16 at 09:24
  • Is ``out`` a value or an array? If it is a value, why compute it in a loop. If it is an array, speed should also be an array? – BitTickler Feb 15 '16 at 09:50
  • out its the value of the interpolation at that interation :) I compute it in a loop because I need to find each value of interpolation at each step (that have different speed). – markzzz Feb 15 '16 at 09:53
  • ``let succ n x = | x < (n-1) -> x+1 | _ -> x`` would yield `` 0.4 * in[3] + 0.6 * in[succ N 3] = 8`` – BitTickler Feb 15 '16 at 10:07
  • Or do you want to program something like a FIR (finite impulse response) filter after all? – BitTickler Feb 15 '16 at 10:12
  • Uhm? I'm making a sampler reader that will read sample at fixed speed and loop once reach the end of samples. That's all...! With speed =1 I don't have problems (I read one after one). But once frac happens, the interpolation "interpolate" between new points I'm finding. – markzzz Feb 15 '16 at 10:14

2 Answers2

2

Just wrap around the indices:

out = in[base] * (1 - frac) + in[(base + 1) % N] * frac

, where % is the modulo operator and N is the number of input samples.

This procedure generates the following line for your sample data (the dashed lines are the interpolated sample points, the circles are the input values):

Sample Data

Nico Schertler
  • 32,049
  • 4
  • 39
  • 70
  • Is it not arbirtray if he assumes points outside the scope of the array as 0, the same value as the edge value of the array or...as you propose that it is circular? – BitTickler Feb 15 '16 at 09:55
  • But in this way the first "base" index won't wrap. – markzzz Feb 15 '16 at 09:56
  • Check the update example! No I can't have negative index_pointer. I just need to wrap the calculation of interpolated value when I reach the last value. – markzzz Feb 15 '16 at 09:58
  • If `index_pointer` exceeds `N`, just subtract `N`. This will automatically adjust `base` for all subsequent iterations. Alternatively, you could also do `in[base % N]`. But that is not really necessary. – Nico Schertler Feb 15 '16 at 09:59
  • Following your example and my data, I'll have that last operation (before wrap) is `0.4*in[3] + 0.6*in[0]= 8 + 4,8 = 12,8`. I don't think that's correct. It should be a value betwen 19.52 (last value I've found) and 20, isn't it? – markzzz Feb 15 '16 at 10:10
  • I added a sample image of my understanding of your problem. If this is not what you mean, edit your question accordingly. – Nico Schertler Feb 15 '16 at 10:21
1

I think I understand the problem now (answer only applies if I really did...):

You sample values at a nominal speed sn. But actually your sampler samples at a real speed s, where s != sn. Now, you want to create a function which re-samples the series, sampled at speed s, so it yields a series as if it were sampled with speed sn by means of linear interpolation between 2 adjacent samples. Or, your sampler jitters (has variances in time when it actually samples, which is sn + Noise(sn)).

Here is my approach - a function named "re-sample". It takes the sample data and a list of desired re-sample-points. For any re-sample point which would index outside the raw data, it returns the respective border value.

let resample (data : float array) times =
    let N = Array.length data
    let maxIndex = N-1
    let weight (t : float) =
        t - (floor t)
    let interpolate x1 x2 w = x1 * (1.0 - w) + x2 * w
    let interp t1 t2 w =
        //printfn "t1 = %d t2 = %d w = %f" t1 t2 w
        interpolate (data.[t1]) (data.[t2]) w
    let inter t =
        let t1 = int (floor t)
        match t1 with
        | x when x >= 0 && x < maxIndex -> 
            let t2 = t1 + 1
            interp t1 t2 (weight t)
        | x when x >= maxIndex -> data.[maxIndex]
        | _ -> data.[0]

    times 
    |> List.map (fun t -> t, inter t)
    |> Array.ofList

let raw_data = [8; 12; 16; 20] |> List.map float |> Array.ofList
let resampled = resample raw_data [0.0..0.2..4.0]

And yields:

val resample : data:float array -> times:float list -> (float * float) []

val raw_data : float [] = [|8.0; 12.0; 16.0; 20.0|]

val resampled : (float * float) [] =

[|(0.0, 8.0); (0.2, 8.8); (0.4, 9.6); (0.6, 10.4); (0.8, 11.2); (1.0, 12.0);

(1.2, 12.8); (1.4, 13.6); (1.6, 14.4); (1.8, 15.2); (2.0, 16.0);

(2.2, 16.8); (2.4, 17.6); (2.6, 18.4); (2.8, 19.2); (3.0, 20.0);

(3.2, 20.0); (3.4, 20.0); (3.6, 20.0); (3.8, 20.0); (4.0, 20.0)|]

Now, I still fail to understand the "wrap around" part of your question. In the end, interpolation - in contrast to extrapolation is only defined for values in [0..N-1]. So it is up to you to decide if the function should produce a run time error or simply use the edge values (or 0) for time values out of bounds of your raw data array.

EDIT

As it turned out, it is about how to use a cyclic (ring) buffer for this as well.

Here, a version of the resample function, using a cyclic buffer. Along with some operations.

  • update adds a new sample value to the ring buffer
  • read reads the content a ring buffer element as if it were a normal array, indexed from [0..N-1].
  • initXXX functions which create the ring buffer in various forms.
  • length which returns the length or capacity of the ring buffer.

The ring buffer logics is factored into a module to keep it all clean.

module Cyclic =
    let wrap n x = x % n // % is modulo operator, just like in C/C++

    type Series = { A : float array; WritePosition : int  }

    let init (n : int) = 
            { A = Array.init n (fun i -> 0.);
            WritePosition = 0 
            }

    let initFromArray a =
        let n = Array.length a
        { A = Array.copy a;
        WritePosition = 0
        }

    let initUseArray a =
        let n = Array.length a
        { A = a;
        WritePosition = 0
        }


    let update (sample : float ) (series : Series)  =
        let wrapper = wrap (Array.length series.A)
        series.A.[series.WritePosition] <- sample
        { series with 
                WritePosition = wrapper (series.WritePosition + 1) }

    let read i series = 
        let n = Array.length series.A
        let wrapper = wrap (Array.length series.A)
        series.A.[wrapper (series.WritePosition + i)] 

    let length (series : Series) = Array.length (series.A)
let resampleSeries (data : Cyclic.Series) times =
    let N = Cyclic.length data
    let maxIndex = N-1
    let weight (t : float) =
        t - (floor t)
    let interpolate x1 x2 w = x1 * (1.0 - w) + x2 * w
    let interp t1 t2 w =
        interpolate (Cyclic.read t1 data) (Cyclic.read t2 data) w
    let inter t =
        let t1 = int (floor t)
        match t1 with
        | x when x >= 0 && x < maxIndex -> 
            let t2 = t1 + 1
            interp t1 t2 (weight t)
        | x when x >= maxIndex -> Cyclic.read maxIndex data
        | _ -> Cyclic.read 0 data

    times 
    |> List.map (fun t -> t, inter t)
    |> Array.ofList    


let input = raw_data
let rawSeries0 = Cyclic.initFromArray input
(resampleSeries rawSeries0 [0.0..0.2..4.0]) = resampled
Community
  • 1
  • 1
BitTickler
  • 10,905
  • 5
  • 32
  • 53
  • With "wrap" I meant that samples loop! Once I reach the ends, it need to restart. It seems that Nico Schertler should works, I need to investigate better. – markzzz Feb 15 '16 at 13:26
  • @markzzz Ah - that is easy to do: You take an array of length N, a readPosition and a writePosition and use it like a cyclic buffer. Each time you sample a new value, you append it at write position and increase read position and write position by one, modulo N. Then you do your steps afterwards in the "modulo range" [readPosition...writePosition]. As the array can wrap around, you write an index function: ``let index n readPosition i = (readPosition + i) % n`` with i being allowed to be in range [0..N-1]. – BitTickler Feb 15 '16 at 14:54
  • Yes, as Nico Schertler said I think. It's already done it right? – markzzz Feb 15 '16 at 14:57