Home > Mobile >  Reverse interpolate from [-60, -30, -10, 0, 3, 6, 10] scale to 0-1 decimal range?
Reverse interpolate from [-60, -30, -10, 0, 3, 6, 10] scale to 0-1 decimal range?

Time:08-04

thanks for solving numerous of my problems. <3

I'm sure someone already solved this, but I don't know how the math needed is called (I've tried reverse interpolation etc., but no success) so I'm posting it like this...

I've interpolated x (a knob's decimal value between 0 and 1) to the scale of [-60, -30, -10, 0, 3, 6, 10].

function interpolate (scale: number[], value: number) {
   const count = scale.length - 1
   const low = Math.max(Math.floor(count * value), 0) | 0
   const high = Math.min(Math.ceil(count * value), count) | 0
   return lerp(scale[low], scale[high], rescale(value, [low / count, high / count], [0, 1]))
}


function lerp(start: number, end: number, value: number) {
   return (1 - value) * start   value * end
}


function rescale(value: number, srcRange: [number, number], dstRange: [number, number]) {
   const [dstMin, dstMax] = dstRange;
   const [srcMin, srcMax] = srcRange;

   if (srcMin == srcMax) {
       return dstMin
   }

   const scale = (value - srcMin) / (srcMax - srcMin);
   return scale * (dstMax - dstMin)   dstMin;
}

When the value is recalled I need to recalculate the 0-1 range decimal value. How?

I've tried the following, but is not correct...

function deinterpolate(scale: number[], value: number): number {
   const scale = values.sort((a, b) => a-b)

   for (let i = 0; i < scale.length - 1; i  ) {
       const min = scale[i]
       const max = scale[i   1]
    
       if (min <= value && value <= max) {
           const result = lerp(min, max, value)
           const offset = i / (scale.length - 1)
           return result   offset
       }
   }
}

CodePudding user response:

One approach is:

function deinterpolate(scale: number[], value: number): number {
    const count = scale.length - 1;
    let high = scale.findIndex(n => n > value);
    if (high < 0) return 1;
    if (high === 0) return 0;
    const low = high - 1;
    return lerp(low / count, high / count, 
      rescale(value, [scale[low], scale[high]], [0, 1]));
}

The main idea here is to use the findIndex() array method to identify the smallest index of the scale array whose value is greater than the value parameter. This is then the high index, and the one before it is the low index. Of course there are possible edge cases where the value that comes out of deinterpolate() isn't the same one that went into interpolate(): if the scale array has repeated elements; if the scale array ever decreases; if the value is outside of the scale range. I assume it's okay to ignore those.

Once we identify low and high we can essentially run your lerp()/rescale() in reverse, by switching the roles of scale[j] and j / count.

We can test it:

console.log("start test")
const scale = [-60, -30, -10, 0, 3, 6, 10];
for (let value = 0; value <= 1; value  = (1 / 256)) {
    const interped = interpolate(scale, value);
    const deinterped = deinterpolate(scale, interped);
    if (Math.abs(value - deinterped) > 0.00001) {
        console.log("OOPS", value, interped, deinterped)
    }
}
console.log("end test")
// "start test"
// "end test"

Since this doesn't print "OOPS", it's a good indication that it works, at least for the example in your question.

Playground link to code

  • Related