Skip to content

Commit 8f04c73

Browse files
committed
Use IIR or IFR LPFs on resample
1 parent 4a2e645 commit 8f04c73

39 files changed

+705
-139
lines changed

CHANGELOG.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,16 @@
11
# CHANGELOG
22

3+
## version 10.4.0 - 2020-01-20
4+
- Default LPF for resample is IIR
5+
- Use FIR or IIR LPFs to resample:
6+
```javascript
7+
// Will use 'linear' method with a FIR LPF
8+
wav.toSampleRate(44100, {method: "linear", LPF: true, LPFType: 'FIR'});
9+
10+
// Will use 'linear' method with a IIR LPF, the default
11+
wav.toSampleRate(44100, {method: "linear", LPF: true});
12+
```
13+
314
## version 10.3.0 - 2020-01-20
415

516
### Features

README.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -421,6 +421,15 @@ wav.toSampleRate(44100, {method: "sinc", LPF: false});
421421
wav.toSampleRate(44100, {method: "linear", LPF: true});
422422
```
423423

424+
The default LPF is a IIR LPF. You may define what type of LPF will be used by changing the LPFType attribute on the *toSampleRate()* param. You can use **IIR** or **FIR**:
425+
```javascript
426+
// Will use 'linear' method with a FIR LPF
427+
wav.toSampleRate(44100, {method: "linear", LPF: true, LPFType: 'FIR'});
428+
429+
// Will use 'linear' method with a IIR LPF, the default
430+
wav.toSampleRate(44100, {method: "linear", LPF: true});
431+
```
432+
424433
#### Changing the sample rate of ADPCM, mu-Law or A-Law
425434
You need to convert compressed files to standard PCM before resampling:
426435

dist/wavefile.js

Lines changed: 86 additions & 84 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/index.html

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -405,6 +405,13 @@ <h4>Resampling methods</h4>
405405
// Will use 'linear' method with LPF
406406
wav.toSampleRate(44100, {method: &quot;linear&quot;, LPF: true});
407407
</code></pre>
408+
<p>The default LPF is a IIR LPF. You may define what type of LPF will be used by changing the LPFType attribute on the <em>toSampleRate()</em> param. You can use <strong>IIR</strong> or <strong>FIR</strong>:</p>
409+
<pre class="prettyprint source lang-javascript"><code>// Will use 'linear' method with a FIR LPF
410+
wav.toSampleRate(44100, {method: &quot;linear&quot;, LPF: true, LPFType: 'FIR'});
411+
412+
// Will use 'linear' method with a IIR LPF, the default
413+
wav.toSampleRate(44100, {method: &quot;linear&quot;, LPF: true});
414+
</code></pre>
408415
<h4>Changing the sample rate of ADPCM, mu-Law or A-Law</h4>
409416
<p>You need to convert compressed files to standard PCM before resampling:</p>
410417
<p>To resample a mu-Law file:</p>

externs/wavefile.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -441,8 +441,9 @@ WaveFile.prototype.toSampleRate = function(
441441
sincFilterSize: 32,
442442
lanczosFilterSize: 24,
443443
sincWindow: function(x){},
444-
LPForder: 1,
445-
LPF: true}) {};
444+
LPF: true,
445+
LPFType: 'IIR',
446+
LPForder: 1}) {};
446447

447448
/**
448449
* Encode a 16-bit wave file as 4-bit IMA ADPCM.

lib/resampler/butterworth-lpf.js

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
/*
2+
* Copyright (c) 2019 Rafael da Silva Rocha.
3+
* Copyright (c) 2014 Florian Markert
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 Butterworth LPF. Based on the Butterworth LPF from Fili.js.
28+
* @see https://github.com/rochars/wavefile
29+
* @see https://github.com/markert/fili.js
30+
*/
31+
32+
/**
33+
* Butterworth LPF.
34+
*/
35+
export class ButterworthLPF {
36+
37+
/**
38+
* @param {number} order The order of the filter.
39+
* @param {number} sampleRate The sample rate.
40+
* @param {number} cutOff The cut off frequency.
41+
*/
42+
constructor(order, sampleRate, cutOff) {
43+
let filters = [];
44+
for (let i = 0; i < order; i++) {
45+
filters.push(this.getCoeffs_({
46+
Fs: sampleRate,
47+
Fc: cutOff,
48+
Q: 0.5 / (Math.sin((Math.PI / (order * 2)) * (i + 0.5)))
49+
}));
50+
}
51+
this.stages = [];
52+
for (let i = 0; i < filters.length; i++) {
53+
this.stages[i] = {
54+
b0 : filters[i].b[0],
55+
b1 : filters[i].b[1],
56+
b2 : filters[i].b[2],
57+
a1 : filters[i].a[0],
58+
a2 : filters[i].a[1],
59+
k : filters[i].k,
60+
z : [0, 0]
61+
};
62+
}
63+
}
64+
65+
/**
66+
* @param {number} sample A sample of a sequence.
67+
* @return {number}
68+
*/
69+
filter(sample) {
70+
let out = sample;
71+
for (let i = 0, len = this.stages.length; i < len; i++) {
72+
out = this.runStage_(i, out);
73+
}
74+
return out;
75+
}
76+
77+
getCoeffs_(params) {
78+
let coeffs = {};
79+
coeffs.z = [0, 0];
80+
coeffs.a = [];
81+
coeffs.b = [];
82+
let p = this.preCalc_(params, coeffs);
83+
coeffs.k = 1;
84+
coeffs.b.push((1 - p.cw) / (2 * p.a0));
85+
coeffs.b.push(2 * coeffs.b[0]);
86+
coeffs.b.push(coeffs.b[0]);
87+
return coeffs;
88+
}
89+
90+
preCalc_(params, coeffs) {
91+
let pre = {};
92+
let w = 2 * Math.PI * params.Fc / params.Fs;
93+
pre.alpha = Math.sin(w) / (2 * params.Q);
94+
pre.cw = Math.cos(w);
95+
pre.a0 = 1 + pre.alpha;
96+
coeffs.a0 = pre.a0;
97+
coeffs.a.push((-2 * pre.cw) / pre.a0);
98+
coeffs.k = 1;
99+
coeffs.a.push((1 - pre.alpha) / pre.a0);
100+
return pre;
101+
}
102+
103+
runStage_(i, input) {
104+
let temp =
105+
input * this.stages[i].k - this.stages[i].a1 * this.stages[i].z[0] -
106+
this.stages[i].a2 * this.stages[i].z[1];
107+
let out =
108+
this.stages[i].b0 * temp + this.stages[i].b1 * this.stages[i].z[0] +
109+
this.stages[i].b2 * this.stages[i].z[1];
110+
this.stages[i].z[1] = this.stages[i].z[0];
111+
this.stages[i].z[0] = temp;
112+
return out;
113+
}
114+
115+
/**
116+
* Reset the filter.
117+
*/
118+
reset() {
119+
for (let i = 0; i < this.stages.length; i++) {
120+
this.stages[i].z = [0, 0];
121+
}
122+
}
123+
}

lib/resampler/index.js

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929

3030
import { Interpolator } from './interpolator';
3131
import { FIRLPF } from './fir-lpf';
32+
import { ButterworthLPF } from './butterworth-lpf';
3233

3334
const DEFAULT_LPF_USE = {
3435
'point': false,
@@ -38,10 +39,13 @@ const DEFAULT_LPF_USE = {
3839
};
3940

4041
const DEFAULT_LPF_ORDER = {
41-
'point': 71,
42-
'linear': 71,
43-
'cubic': 71,
44-
'sinc': 71
42+
'IIR': 16,
43+
'FIR': 71
44+
};
45+
46+
const DEFAULT_LPF = {
47+
'IIR': ButterworthLPF,
48+
'FIR': FIRLPF
4549
};
4650

4751
/**
@@ -73,18 +77,20 @@ export function resample(samples, oldSampleRate, sampleRate, details={}) {
7377
details.LPF = DEFAULT_LPF_USE[details.method];
7478
}
7579
if (details.LPF) {
80+
details.LPFType = details.LPFType || 'IIR';
81+
const LPF = DEFAULT_LPF[details.LPFType];
7682
// Upsampling
7783
if (sampleRate > oldSampleRate) {
78-
let filter = new FIRLPF(
79-
details.LPForder || DEFAULT_LPF_ORDER[details.method],
84+
let filter = new LPF(
85+
details.LPForder || DEFAULT_LPF_ORDER[details.LPFType],
8086
sampleRate,
8187
(oldSampleRate / 2));
8288
upsample_(
8389
samples, newSamples, interpolator, filter);
8490
// Downsampling
8591
} else {
86-
let filter = new FIRLPF(
87-
details.LPForder || DEFAULT_LPF_ORDER[details.method],
92+
let filter = new LPF(
93+
details.LPForder || DEFAULT_LPF_ORDER[details.LPFType],
8894
oldSampleRate,
8995
sampleRate / 2);
9096
downsample_(
Binary file not shown.
Binary file not shown.
Binary file not shown.

0 commit comments

Comments
 (0)