Passed
Push — master ( 90242f...6979db )
by Rafael S.
02:34
created

interpolator.js ➔ window_   A

Complexity

Conditions 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
dl 0
loc 3
rs 10
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 clipping function.
68
     * @type {Function}
69
     */
70
    this.clip_ = clipClamp_;
71
    // The clip function
72
    if (details.clip === 'periodic') {
73
      this.scaleFactor_ = scaleFrom / scaleTo;
74
      this.clip_ = clipPeriodic_;
75
    } else if (details.clip === 'mirror') {
76
      this.clip_ = clipMirror_;
77
    }
78
    /**
79
     * The tanget factor for cubic interpolation.
80
     * @type {number}
81
     */
82
    this.tangentFactor_ = 1 - Math.max(0, Math.min(1, details.tension || 0));
83
    // Configure the kernel for sinc
84
    /**
85
     * The sinc filter size.
86
     * @type {number}
87
     */
88
    this.sincFilterSize_ = details.sincFilterSize || 1;
89
    /**
90
     * The sinc kernel.
91
     * @type {Function}
92
     */
93
    this.kernel_ = sincKernel_(details.sincWindow || window_);
94
  }
95
96
  /**
97
   * @param {number} t The index to interpolate.
98
   * @param {Array|TypedArray} samples the original array.
99
   * @return {number} The interpolated value.
100
   */
101
  point(t, samples) {
102
    return this.getClippedInput_(Math.round(this.scaleFactor_ * t), samples);
103
  }
104
105
  /**
106
   * @param {number} t The index to interpolate.
107
   * @param {Array|TypedArray} samples the original array.
108
   * @return {number} The interpolated value.
109
   */
110
  linear(t, samples) {
111
    t = this.scaleFactor_ * t;
112
    let k = Math.floor(t);
113
    t -= k;
114
    return (1 - t) *
115
    	this.getClippedInput_(k, samples) + t *
116
    	this.getClippedInput_(k + 1, samples);
117
  }
118
119
  /**
120
   * @param {number} t The index to interpolate.
121
   * @param {Array|TypedArray} samples the original array.
122
   * @return {number} The interpolated value.
123
   */
124
  cubic(t, samples) {
125
    t = this.scaleFactor_ * t;
126
    let k = Math.floor(t);
127
    let m = [this.getTangent_(k, samples), this.getTangent_(k + 1, samples)];
128
    let p = [this.getClippedInput_(k, samples),
129
      this.getClippedInput_(k + 1, samples)];
130
    t -= k;
131
    let t2 = t * t;
132
    let t3 = t * t2;
133
    return (2 * t3 - 3 * t2 + 1) *
134
      p[0] + (t3 - 2 * t2 + t) *
135
      m[0] + (-2 * t3 + 3 * t2) *
136
      p[1] + (t3 - t2) * m[1];
137
  }
138
139
  /**
140
   * @param {number} t The index to interpolate.
141
   * @param {Array|TypedArray} samples the original array.
142
   * @return {number} The interpolated value.
143
   */
144
  sinc(t, samples) {
145
    t = this.scaleFactor_ * t;
146
    let k = Math.floor(t);
147
    let ref = k - this.sincFilterSize_ + 1;
148
    let ref1 = k + this.sincFilterSize_;
149
    let sum = 0;
150
    for (let n = ref; n <= ref1; n++) {
151
      sum += this.kernel_(t - n) * this.getClippedInput_(n, samples);
152
    }
153
    return sum;
154
  }
155
156
  /**
157
   * @param {number} k The scaled index to interpolate.
158
   * @param {Array|TypedArray} samples the original array.
159
   * @return {number} The tangent.
160
   * @private
161
   */
162
  getTangent_(k, samples) {
163
    return this.tangentFactor_ *
164
      (this.getClippedInput_(k + 1, samples) -
165
        this.getClippedInput_(k - 1, samples)) / 2;
166
  }
167
168
  /**
169
   * @param {number} t The scaled index to interpolate.
170
   * @param {Array|TypedArray} samples the original array.
171
   * @return {number} The interpolated value.
172
   * @private
173
   */
174
  getClippedInput_(t, samples) {
175
    if ((0 <= t && t < this.length_)) {
176
      return samples[t];
177
    }
178
    return samples[this.clip_(t, this.length_)];
179
  }
180
}
181
182
// Sinc functions
183
184
/**
185
 * The default window function.
186
 * @param {number} x The sinc signal.
187
 * @return {number}
188
 * @private
189
 */
190
function window_(x) {
191
  return Math.exp(-x / 2 * x / 2);
192
}
193
194
/**
195
 * @param {Function} window The window function.
196
 * @return {Function}
197
 * @private
198
 */
199
function sincKernel_(window) {
200
  return function(x) { return sinc_(x) * window(x); };
201
}
202
203
/**
204
 * @param {number} x The sinc signal.
205
 * @return {number}
206
 * @private
207
 */
208
function sinc_(x) {
209
  if (x === 0) {
210
    return 1;
211
  }
212
  return Math.sin(Math.PI * x) / (Math.PI * x);
213
}
214
215
// Clip functions
216
217
/**
218
 * @param {number} t The scaled index
219
 * @param {number} n The size of the original array
220
 * @return {number}
221
 * @private
222
 */
223
function clipClamp_(t, n) {
224
  return Math.max(0, Math.min(t, n - 1));
225
}
226
227
/**
228
 * @param {number} t The scaled index
229
 * @param {number} n The size of the original array
230
 * @return {number}
231
 * @private
232
 */
233
function clipPeriodic_(t, n) {
234
  t = t % n;
235
  if (t < 0) {
236
    t += n;
237
  }
238
  return t;
239
}
240
241
/**
242
 * @param {number} t The scaled index
243
 * @param {number} n The size of the original array
244
 * @return {number}
245
 * @private
246
 */
247
function clipMirror_(t, n) {
248
  let period = 2 * (n - 1);
249
  t = clipPeriodic_(t, period);
250
  if (t > n - 1) {
251
    t = period - t;
252
  }
253
  return t;
254
}
255