如何包装 last/first 元素进行建筑插值?

How to wrap last/first element making building interpolation?

我有这段代码可以迭代一些样本并在点之间建立一个简单的线性插值:

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
   }

使用"speed"等于1,游戏结束。但是如果 index_pointer 不同于 1(即有小数部分),我需要包装 last/first 元素以保持翻译一致。

你会怎么做?双索引?

这是我的价值观的一个例子。假设 in 4 个值的数组:[8, 12, 16, 20].

它将是:

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

最后一点是错误的。 [4] 将是 0,因为我没有 [4],但第一部分需要处理 0.4 和第一个样本的权重(我认为?)。

只需环绕索引:

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

,其中 % 是模运算符,N 是输入样本的数量。

此过程为您的样本数据生成以下行(虚线是插值样本点,圆圈是输入值):

我想我现在明白了这个问题(答案只有在我真的明白的情况下才适用...):

您以标称速度 sn 对值进行采样。但实际上您的采样器以实际速度 s 进行采样,其中 s != sn。现在,您想要创建一个函数来对系列进行重新采样,以速度 s 进行采样,因此它会产生一个系列,就好像它是通过 2 个相邻样本之间的线性插值以速度 sn 进行采样一样。或者,您的采样器抖动(在实际采样时有时间差异,即 sn + Noise(sn))。

这是我的方法 - 一个名为“re-sample”的函数。它获取样本数据和所需的重新采样点列表。 对于任何会在原始数据之外建立索引的重新采样点,它 returns 各自的边界值。

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]

并产生:

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)|]

现在,我仍然无法理解您问题的“环绕”部分。最后,插值 - 与外推相反,仅针对 [0..N-1] 中的值定义。因此,由您决定函数是否应该产生 运行 时间错误,或者简单地使用边缘值(或 0)作为超出原始数据数组范围的时间值。

编辑

事实证明,它也是关于如何为此使用循环(环形)缓冲区的。

这里是 resample 函数的一个版本,使用循环缓冲区。以及一些操作。

  • update 将新样本值添加到环形缓冲区
  • read 读取环形缓冲区元素的内容,就好像它是一个普通数组,从 [0..N-1] 索引。
  • initXXX 以各种形式创建环形缓冲区的函数。
  • length 其中 returns 环形缓冲区的长度或容量。

环形缓冲区逻辑被分解到一个模块中,以保持一切清洁。

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