Skip to content

Commit

Permalink
Use IIR or IFR LPFs on resample
Browse files Browse the repository at this point in the history
  • Loading branch information
rochars committed Jan 21, 2020
1 parent 4a2e645 commit 8f04c73
Show file tree
Hide file tree
Showing 39 changed files with 705 additions and 139 deletions.
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
# CHANGELOG

## version 10.4.0 - 2020-01-20
- Default LPF for resample is IIR
- Use FIR or IIR LPFs to resample:
```javascript
// Will use 'linear' method with a FIR LPF
wav.toSampleRate(44100, {method: "linear", LPF: true, LPFType: 'FIR'});

// Will use 'linear' method with a IIR LPF, the default
wav.toSampleRate(44100, {method: "linear", LPF: true});
```

## version 10.3.0 - 2020-01-20

### Features
Expand Down
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -421,6 +421,15 @@ wav.toSampleRate(44100, {method: "sinc", LPF: false});
wav.toSampleRate(44100, {method: "linear", LPF: true});
```

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**:
```javascript
// Will use 'linear' method with a FIR LPF
wav.toSampleRate(44100, {method: "linear", LPF: true, LPFType: 'FIR'});

// Will use 'linear' method with a IIR LPF, the default
wav.toSampleRate(44100, {method: "linear", LPF: true});
```

#### Changing the sample rate of ADPCM, mu-Law or A-Law
You need to convert compressed files to standard PCM before resampling:

Expand Down
170 changes: 86 additions & 84 deletions dist/wavefile.js

Large diffs are not rendered by default.

7 changes: 7 additions & 0 deletions docs/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -405,6 +405,13 @@ <h4>Resampling methods</h4>
// Will use 'linear' method with LPF
wav.toSampleRate(44100, {method: &quot;linear&quot;, LPF: true});
</code></pre>
<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>
<pre class="prettyprint source lang-javascript"><code>// Will use 'linear' method with a FIR LPF
wav.toSampleRate(44100, {method: &quot;linear&quot;, LPF: true, LPFType: 'FIR'});

// Will use 'linear' method with a IIR LPF, the default
wav.toSampleRate(44100, {method: &quot;linear&quot;, LPF: true});
</code></pre>
<h4>Changing the sample rate of ADPCM, mu-Law or A-Law</h4>
<p>You need to convert compressed files to standard PCM before resampling:</p>
<p>To resample a mu-Law file:</p>
Expand Down
5 changes: 3 additions & 2 deletions externs/wavefile.js
Original file line number Diff line number Diff line change
Expand Up @@ -441,8 +441,9 @@ WaveFile.prototype.toSampleRate = function(
sincFilterSize: 32,
lanczosFilterSize: 24,
sincWindow: function(x){},
LPForder: 1,
LPF: true}) {};
LPF: true,
LPFType: 'IIR',
LPForder: 1}) {};

/**
* Encode a 16-bit wave file as 4-bit IMA ADPCM.
Expand Down
123 changes: 123 additions & 0 deletions lib/resampler/butterworth-lpf.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
/*
* Copyright (c) 2019 Rafael da Silva Rocha.
* Copyright (c) 2014 Florian Markert
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
*/

/**
* @fileoverview Butterworth LPF. Based on the Butterworth LPF from Fili.js.
* @see https://github.com/rochars/wavefile
* @see https://github.com/markert/fili.js
*/

/**
* Butterworth LPF.
*/
export class ButterworthLPF {

/**
* @param {number} order The order of the filter.
* @param {number} sampleRate The sample rate.
* @param {number} cutOff The cut off frequency.
*/
constructor(order, sampleRate, cutOff) {
let filters = [];
for (let i = 0; i < order; i++) {
filters.push(this.getCoeffs_({
Fs: sampleRate,
Fc: cutOff,
Q: 0.5 / (Math.sin((Math.PI / (order * 2)) * (i + 0.5)))
}));
}
this.stages = [];
for (let i = 0; i < filters.length; i++) {
this.stages[i] = {
b0 : filters[i].b[0],
b1 : filters[i].b[1],
b2 : filters[i].b[2],
a1 : filters[i].a[0],
a2 : filters[i].a[1],
k : filters[i].k,
z : [0, 0]
};
}
}

/**
* @param {number} sample A sample of a sequence.
* @return {number}
*/
filter(sample) {
let out = sample;
for (let i = 0, len = this.stages.length; i < len; i++) {
out = this.runStage_(i, out);
}
return out;
}

getCoeffs_(params) {
let coeffs = {};
coeffs.z = [0, 0];
coeffs.a = [];
coeffs.b = [];
let p = this.preCalc_(params, coeffs);
coeffs.k = 1;
coeffs.b.push((1 - p.cw) / (2 * p.a0));
coeffs.b.push(2 * coeffs.b[0]);
coeffs.b.push(coeffs.b[0]);
return coeffs;
}

preCalc_(params, coeffs) {
let pre = {};
let w = 2 * Math.PI * params.Fc / params.Fs;
pre.alpha = Math.sin(w) / (2 * params.Q);
pre.cw = Math.cos(w);
pre.a0 = 1 + pre.alpha;
coeffs.a0 = pre.a0;
coeffs.a.push((-2 * pre.cw) / pre.a0);
coeffs.k = 1;
coeffs.a.push((1 - pre.alpha) / pre.a0);
return pre;
}

runStage_(i, input) {
let temp =
input * this.stages[i].k - this.stages[i].a1 * this.stages[i].z[0] -
this.stages[i].a2 * this.stages[i].z[1];
let out =
this.stages[i].b0 * temp + this.stages[i].b1 * this.stages[i].z[0] +
this.stages[i].b2 * this.stages[i].z[1];
this.stages[i].z[1] = this.stages[i].z[0];
this.stages[i].z[0] = temp;
return out;
}

/**
* Reset the filter.
*/
reset() {
for (let i = 0; i < this.stages.length; i++) {
this.stages[i].z = [0, 0];
}
}
}
22 changes: 14 additions & 8 deletions lib/resampler/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@

import { Interpolator } from './interpolator';
import { FIRLPF } from './fir-lpf';
import { ButterworthLPF } from './butterworth-lpf';

const DEFAULT_LPF_USE = {
'point': false,
Expand All @@ -38,10 +39,13 @@ const DEFAULT_LPF_USE = {
};

const DEFAULT_LPF_ORDER = {
'point': 71,
'linear': 71,
'cubic': 71,
'sinc': 71
'IIR': 16,
'FIR': 71
};

const DEFAULT_LPF = {
'IIR': ButterworthLPF,
'FIR': FIRLPF
};

/**
Expand Down Expand Up @@ -73,18 +77,20 @@ export function resample(samples, oldSampleRate, sampleRate, details={}) {
details.LPF = DEFAULT_LPF_USE[details.method];
}
if (details.LPF) {
details.LPFType = details.LPFType || 'IIR';
const LPF = DEFAULT_LPF[details.LPFType];
// Upsampling
if (sampleRate > oldSampleRate) {
let filter = new FIRLPF(
details.LPForder || DEFAULT_LPF_ORDER[details.method],
let filter = new LPF(
details.LPForder || DEFAULT_LPF_ORDER[details.LPFType],
sampleRate,
(oldSampleRate / 2));
upsample_(
samples, newSamples, interpolator, filter);
// Downsampling
} else {
let filter = new FIRLPF(
details.LPForder || DEFAULT_LPF_ORDER[details.method],
let filter = new LPF(
details.LPForder || DEFAULT_LPF_ORDER[details.LPFType],
oldSampleRate,
sampleRate / 2);
downsample_(
Expand Down
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
165 changes: 165 additions & 0 deletions test/resampler-full/cubic-IIR.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
/**
* WaveFile: https://github.com/rochars/wavefile
* Copyright (c) 2017-2018 Rafael da Silva Rocha. MIT License.
*
* Tests for sample rate conversion, cubic method.
*
*/

const assert = require('assert');
const fs = require("fs");
const WaveFile = require("../../test/loader.js");
const path = "./test/files/";

console.log('cubic + IIR');

let hrstart = process.hrtime();

// Chirps, FP

describe('Downsample a 32-bit 44.1kHz log sine sweep', function() {
// Read a 8kHz wav
let wav = new WaveFile(
fs.readFileSync(path + "32fp-44100Hz-mono-chirp-1-22050-log.wav"));

// Convert to another sample rate
wav.toSampleRate(16000, {method: 'cubic', LPFType: 'IIR'});

// Write the file
fs.writeFileSync(
path + "/out/to-sample-rate/cubic-IIR-32fp-16000Hz-mono-chirp-1-22050-log.wav",
wav.toBuffer());
});

describe('Upsample a 32-bit 44.1kHz log sine sweep', function() {
// Read a 8kHz wav
let wav = new WaveFile(
fs.readFileSync(path + "32fp-44100Hz-mono-chirp-1-22050-log.wav"));

// Convert to another sample rate
wav.toSampleRate(96000, {method: 'cubic', LPFType: 'IIR'});

// Write the file
fs.writeFileSync(
path + "/out/to-sample-rate/cubic-IIR-32fp-96000Hz-mono-chirp-1-22050-log.wav",
wav.toBuffer());
});

describe('Downsample a 32-bit 44.1kHz linear sine sweep', function() {
// Read a 8kHz wav
let wav = new WaveFile(
fs.readFileSync(path + "32fp-44100Hz-mono-chirp-1-22050-linear.wav"));

// Convert to another sample rate
wav.toSampleRate(16000, {method: 'cubic', LPFType: 'IIR'});

// Write the file
fs.writeFileSync(
path + "/out/to-sample-rate/cubic-IIR-32fp-16000Hz-mono-chirp-1-22050-linear.wav",
wav.toBuffer());
});

describe('Upsample a 32-bit 44.1kHz linear sine sweep', function() {
// Read a 8kHz wav
let wav = new WaveFile(
fs.readFileSync(path + "32fp-44100Hz-mono-chirp-1-22050-linear.wav"));

// Convert to another sample rate
wav.toSampleRate(96000, {method: 'cubic', LPFType: 'IIR'});

// Write the file
fs.writeFileSync(
path + "/out/to-sample-rate/cubic-IIR-32fp-96000Hz-mono-chirp-1-22050-linear.wav",
wav.toBuffer());
});

// Chirps, int

describe('Downsample a 16-bit 44.1kHz log sine sweep', function() {
// Read a 8kHz wav
let wav = new WaveFile(
fs.readFileSync(path + "16bit-44100Hz-mono-chirp-1-22050-log.wav"));

// Convert to another sample rate
wav.toSampleRate(16000, {method: 'cubic', LPFType: 'IIR'});

// Write the file
fs.writeFileSync(
path + "/out/to-sample-rate/cubic-IIR-16bit-16000Hz-mono-chirp-1-22050-log.wav",
wav.toBuffer());
});

describe('Upsample a 16-bit 44.1kHz log sine sweep', function() {
// Read a 8kHz wav
let wav = new WaveFile(
fs.readFileSync(path + "16bit-44100Hz-mono-chirp-1-22050-log.wav"));

// Convert to another sample rate
wav.toSampleRate(96000, {method: 'cubic', LPFType: 'IIR'});

// Write the file
fs.writeFileSync(
path + "/out/to-sample-rate/cubic-IIR-16bit-96000Hz-mono-chirp-1-22050-log.wav",
wav.toBuffer());
});

describe('Downsample a 16-bit 44.1kHz linear sine sweep', function() {
// Read a 8kHz wav
let wav = new WaveFile(
fs.readFileSync(path + "16bit-44100Hz-mono-chirp-1-22050-linear.wav"));

// Convert to another sample rate
wav.toSampleRate(16000, {method: 'cubic', LPFType: 'IIR'});

// Write the file
fs.writeFileSync(
path + "/out/to-sample-rate/cubic-IIR-16bit-16000Hz-mono-chirp-1-22050-linear.wav",
wav.toBuffer());
});

describe('Upsample a 16-bit 44.1kHz linear sine sweep', function() {
// Read a 8kHz wav
let wav = new WaveFile(
fs.readFileSync(path + "16bit-44100Hz-mono-chirp-1-22050-linear.wav"));

// Convert to another sample rate
wav.toSampleRate(96000, {method: 'cubic', LPFType: 'IIR'});

// Write the file
fs.writeFileSync(
path + "/out/to-sample-rate/cubic-IIR-16bit-96000Hz-mono-chirp-1-22050-linear.wav",
wav.toBuffer());
});

// Songs

describe('Downsample a 16bit 44.1kHz file', function() {
// Read a 8kHz wav
let wav = new WaveFile(
fs.readFileSync(path + "song1.wav"));

// Convert to another sample rate
wav.toSampleRate(16000, {method: 'cubic', LPFType: 'IIR'});

// Write the file
fs.writeFileSync(
path + "/out/to-sample-rate/cubic-IIR-song1-16kHz.wav",
wav.toBuffer());
});

describe('Upsample a 16bit 44.1kHz file', function() {
// Read a 8kHz wav
let wav = new WaveFile(
fs.readFileSync(path + "song1.wav"));

// Convert to another sample rate
wav.toSampleRate(96000, {method: 'cubic', LPFType: 'IIR'});

// Write the file
fs.writeFileSync(
path + "/out/to-sample-rate/cubic-IIR-song1-96kHz.wav",
wav.toBuffer());
});

hrend = process.hrtime(hrstart);
console.info('%ds %dms', hrend[0], hrend[1] / 1000000);
Loading

0 comments on commit 8f04c73

Please sign in to comment.