Passed
Push — master ( 13b350...038bd4 )
by Rafael S.
03:46
created

Interpolator.constructor   A

Complexity

Conditions 4

Size

Total Lines 40
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 13
dl 0
loc 40
rs 9.75
c 0
b 0
f 0
1
/*
2
 * Copyright (c) 2019 Rafael da Silva Rocha.
3
 * Copyright 2012 Spencer Cohen
4
 *
5
 * Permission is hereby granted, free of charge, to any person obtaining
6
 * a copy of this software and associated documentation files (the
7
 * "Software"), to deal in the Software without restriction, including
8
 * without limitation the rights to use, copy, modify, merge, publish,
9
 * distribute, sublicense, and/or sell copies of the Software, and to
10
 * permit persons to whom the Software is furnished to do so, subject to
11
 * the following conditions:
12
 *
13
 * The above copyright notice and this permission notice shall be
14
 * included in all copies or substantial portions of the Software.
15
 *
16
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23
 *
24
 */
25
26
/**
27
 * @fileoverview The Interpolator class. Based on Smooth.js by Spencer Cohen.
28
 * @see https://github.com/rochars/wavefile
29
 * @see https://github.com/osuushi/Smooth.js
30
 */
31
32
/**
33
 * A class to get scaled values out of arrays.
34
 * @extends WaveFileReader
35
 */
36
export class Interpolator {
37
  
38
  /**
39
   * @param {number} scaleFrom the length of the original array.
40
   * @param {number} scaleTo The length of the new array.
41
   * @param {!Object} details The extra configuration, if needed.
42
   */
43
  constructor(scaleFrom, scaleTo, details) {
44
    /**
45
     * The length of the original array.
46
     * @type {number}
47
     */
48
    this.length_ = scaleFrom;
49
    /**
50
     * The scaling factor.
51
     * @type {number}
52
     */
53
    this.scaleFactor_ = (scaleFrom - 1) / scaleTo;
54
    /**
55
     * The interpolation function.
56
     * @type {Function}
57
     */
58
    this.interpolate = this.sinc;
59
    if (details.method === 'point') {
60
    	this.interpolate = this.point;
61
    } else if(details.method === 'linear') {
62
    	this.interpolate = this.linear;
63
    } else if(details.method === 'cubic') {
64
    	this.interpolate = this.cubic;
65
    }
66
    /**
67
     * The tanget factor for cubic interpolation.
68
     * @type {number}
69
     */
70
    this.tangentFactor_ = 1 - Math.max(0, Math.min(1, details.tension || 0));
71
    // Configure the kernel for sinc
72
    /**
73
     * The sinc filter size.
74
     * @type {number}
75
     */
76
    this.sincFilterSize_ = details.sincFilterSize || 1;
77
    /**
78
     * The sinc kernel.
79
     * @type {Function}
80
     */
81
    this.kernel_ = sincKernel_(details.sincWindow || window_);
82
  }
83
84
  /**
85
   * @param {number} t The index to interpolate.
86
   * @param {Array<number>|TypedArray} samples the original array.
87
   * @return {number} The interpolated value.
88
   */
89
  point(t, samples) {
90
    return this.getClippedInput_(Math.round(this.scaleFactor_ * t), samples);
91
  }
92
93
  /**
94
   * @param {number} t The index to interpolate.
95
   * @param {Array<number>|TypedArray} samples the original array.
96
   * @return {number} The interpolated value.
97
   */
98
  linear(t, samples) {
99
    t = this.scaleFactor_ * t;
100
    /** @type {number} */
101
    let k = Math.floor(t);
102
    t -= k;
103
    return (1 - t) *
104
    	this.getClippedInput_(k, samples) + t *
105
    	this.getClippedInput_(k + 1, samples);
106
  }
107
108
  /**
109
   * @param {number} t The index to interpolate.
110
   * @param {Array<number>|TypedArray} samples the original array.
111
   * @return {number} The interpolated value.
112
   */
113
  cubic(t, samples) {
114
    t = this.scaleFactor_ * t;
115
    /** @type {number} */
116
    let k = Math.floor(t);
117
    /** @type {Array<number>} */
118
    let m = [this.getTangent_(k, samples), this.getTangent_(k + 1, samples)];
119
    /** @type {Array<number>} */
120
    let p = [this.getClippedInput_(k, samples),
121
      this.getClippedInput_(k + 1, samples)];
122
    t -= k;
123
    /** @type {number} */
124
    let t2 = t * t;
125
    /** @type {number} */
126
    let t3 = t * t2;
127
    return (2 * t3 - 3 * t2 + 1) *
128
      p[0] + (t3 - 2 * t2 + t) *
129
      m[0] + (-2 * t3 + 3 * t2) *
130
      p[1] + (t3 - t2) * m[1];
131
  }
132
133
  /**
134
   * @param {number} t The index to interpolate.
135
   * @param {Array<number>|TypedArray} samples the original array.
136
   * @return {number} The interpolated value.
137
   */
138
  sinc(t, samples) {
139
    t = this.scaleFactor_ * t;
140
    /** @type {number} */
141
    let k = Math.floor(t);
142
    /** @type {number} */
143
    let ref = k - this.sincFilterSize_ + 1;
144
    /** @type {number} */
145
    let ref1 = k + this.sincFilterSize_;
146
    /** @type {number} */
147
    let sum = 0;
148
    for (let n = ref; n <= ref1; n++) {
149
      sum += this.kernel_(t - n) * this.getClippedInput_(n, samples);
150
    }
151
    return sum;
152
  }
153
154
  /**
155
   * @param {number} k The scaled index to interpolate.
156
   * @param {Array<number>|TypedArray} samples the original array.
157
   * @return {number} The tangent.
158
   * @private
159
   */
160
  getTangent_(k, samples) {
161
    return this.tangentFactor_ *
162
      (this.getClippedInput_(k + 1, samples) -
163
        this.getClippedInput_(k - 1, samples)) / 2;
164
  }
165
166
  /**
167
   * @param {number} t The scaled index to interpolate.
168
   * @param {Array<number>|TypedArray} samples the original array.
169
   * @return {number} The interpolated value.
170
   * @private
171
   */
172
  getClippedInput_(t, samples) {
173
    if ((0 <= t && t < this.length_)) {
174
      return samples[t];
175
    }
176
    return 0;
177
  }
178
}
179
180
/**
181
 * The default window function.
182
 * @param {number} x The sinc signal.
183
 * @return {number}
184
 * @private
185
 */
186
function window_(x) {
187
  return Math.exp(-x / 2 * x / 2);
188
}
189
190
/**
191
 * @param {Function} window The window function.
192
 * @return {Function}
193
 * @private
194
 */
195
function sincKernel_(window) {
196
  return function(x) { return sinc_(x) * window(x); };
197
}
198
199
/**
200
 * @param {number} x The sinc signal.
201
 * @return {number}
202
 * @private
203
 */
204
function sinc_(x) {
205
  if (x === 0) {
206
    return 1;
207
  }
208
  return Math.sin(Math.PI * x) / (Math.PI * x);
209
}
210