[wip] convert Filter to .ts (#503)

* Convert filter.js to typescript

* remove unneeded imports in tests
This commit is contained in:
Yotam Mann 2019-06-21 14:09:49 -04:00 committed by GitHub
commit 5f6f6f9e0a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 326 additions and 0 deletions

View file

@ -0,0 +1,126 @@
import { expect } from "chai";
import { BasicTests } from "test/helper/Basic";
import { connectFrom, connectTo } from "test/helper/Connect";
import { Filter } from "./Filter";
import { Offline } from "test/helper/Offline";
import { PassAudio } from "test/helper/PassAudio";
import { Oscillator } from '../../source/oscillator/Oscillator'
describe("Filter", () => {
BasicTests(Filter);
context("Filtering", () => {
it("handles input and output connections", () => {
var filter = new Filter();
connectFrom().connect(filter);
filter.dispose();
});
it("can be constructed with a arguments", () => {
var filter = new Filter(200, "highpass");
expect(filter.frequency.value).to.be.closeTo(200, 0.001);
expect(filter.type).to.equal("highpass");
filter.dispose();
});
it("can be constructed with an object", () => {
var filter = new Filter({
"frequency" : 340,
"type" : "bandpass"
});
expect(filter.frequency.value).to.be.closeTo(340, 0.001);
expect(filter.type).to.equal("bandpass");
filter.dispose();
});
it("can set/get values as an Object", () => {
var filter = new Filter();
var values = {
type : "highpass",
frequency : 440,
rolloff : -24,
Q : 2,
gain : -6,
};
filter.set(values);
expect(filter.get()).to.have.keys(["type", "frequency", "rolloff", "Q", "gain"]);
expect(filter.type).to.equal(values.type);
expect(filter.frequency.value).to.equal(values.frequency);
expect(filter.rolloff).to.equal(values.rolloff);
expect(filter.Q.value).to.equal(values.Q);
expect(filter.gain.value).to.be.closeTo(values.gain, 0.04);
filter.dispose();
});
it("can get the frequency response curve", () => {
var filter = new Filter();
var curve = filter.getFrequencyResponse(32);
expect(curve.length).to.equal(32);
expect(curve[0]).be.closeTo(1, 0.01);
expect(curve[5]).be.closeTo(0.5, 0.1);
expect(curve[15]).be.closeTo(0, 0.01);
expect(curve[31]).be.closeTo(0, 0.01);
filter.dispose();
});
it("passes the incoming signal through", () => {
return PassAudio(function(input){
var filter = new Filter().toMaster();
input.connect(filter);
});
});
it.skip("passes the incoming stereo signal through", () => {
// return PassAudioStereo(function(input){
// var filter = new Filter().toMaster();
// input.connect(filter);
// });
});
it("only accepts filter values -12, -24, -48 and -96", () => {
var filter = new Filter();
filter.rolloff = -12;
expect(filter.rolloff).to.equal(-12);
filter.rolloff = "-24";
expect(filter.rolloff).to.equal(-24);
filter.rolloff = -48;
expect(filter.rolloff).to.equal(-48);
filter.rolloff = -96;
expect(filter.rolloff).to.equal(-96);
expect(() =>{
// @ts-ignore
filter.rolloff = -95;
}).to.throw(Error);
filter.dispose();
});
it("can set the basic filter types", () => {
var filter = new Filter();
var types = ["lowpass", "highpass", "bandpass", "lowshelf", "highshelf", "notch", "allpass", "peaking"];
for (var i = 0; i < types.length; i++){
filter.type = types[i];
expect(filter.type).to.equal(types[i]);
}
expect(() => {
filter.type = "nontype";
}).to.throw(Error);
filter.dispose();
});
it("attenuates the incoming signal", () => {
return Offline(() => {
var filter = new Filter(700, "lowpass").toMaster();
filter.Q.value = 0;
var osc = new Oscillator(880).connect(filter);
osc.start(0);
}, 0.2).then(function(buffer){
expect(buffer.getRmsAtTime(0.05)).to.be.within(0.37, 0.53);
expect(buffer.getRmsAtTime(0.1)).to.be.within(0.37, 0.53);
});
});
});
});

View file

@ -0,0 +1,200 @@
import {ToneAudioNode, ToneAudioNodeOptions, InputNode, OutputNode} from '../../core/context/ToneAudioNode';
import { optionsFromArguments } from "Tone/core/util/Defaults";
import { Signal } from 'Tone/signal/Signal';
import { readOnly, writable } from 'Tone/core/util/Interface';
type FilterType = "lowpass" | "highpass" | "bandpass" | "lowshelf" | "highshelf" | "peaking" | "notch" | "allpass";
type RolloffType = "-12" | "-24" | "-48" | "-96";
interface FilterOptions extends ToneAudioNodeOptions {
type: FilterType;
frequency: number;
rolloff: RolloffType;
Q: number;
gain: number
}
/**
* @class Tone.Filter is a filter which allows for all of the same native methods
* as the [BiquadFilterNode](http://webaudio.github.io/web-audio-api/#the-biquadfilternode-interface).
* Tone.Filter has the added ability to set the filter rolloff at -12
* (default), -24 and -48.
*
* @constructor
* @extends {Tone.AudioNode}
* @param {Frequency|Object} [frequency] The cutoff frequency of the filter.
* @param {string=} type The type of filter.
* @param {number=} rolloff The drop in decibels per octave after the cutoff frequency.
* 3 choices: -12, -24, and -48
* @example
* var filter = new Tone.Filter(200, "highpass");
*/
export class Filter extends ToneAudioNode<FilterOptions> {
readonly name = "Filter";
readonly input = this.context.createBiquadFilter();
readonly output = this.context.createBiquadFilter();
private _filters: BiquadFilterNode[];
/**
* The internal channels for channel routing changes
*/
protected _internalChannels = [this.output];
/**
* the rolloff value of the filter
* @type {number}
* @private
*/
private _rolloff: number;
private _type: FilterType;
readonly Q: Signal;
/**
* The cutoff frequency of the filter.
* @type {Frequency}
* @signal
*/
readonly frequency: Signal;
/**
* The detune parameter
* @type {Cents}
* @signal
*/
readonly detune = new Signal(0, "cents");
/**
* The gain of the filter, only used in certain filter types
* @type {Number}
* @signal
*/
readonly gain: Signal;
constructor(frequency?: number, type?: FilterType, rolloff?: RolloffType);
constructor(Q?: number, gain?: number, frequency?: number, type?: FilterType, rolloff?: RolloffType);
constructor(options?: Partial<FilterOptions>);
constructor() {
super(optionsFromArguments(Filter.getDefaults(), arguments, ["frequency", "type", "rolloff", "Q", "gain"]));
const options = optionsFromArguments(Filter.getDefaults(), arguments, ["frequency", "type", "rolloff", "Q", "gain"]);
this._filters = [];
this._rolloff = parseInt(options.rolloff);
this.rolloff = options.rolloff;
this.Q = new Signal(options.Q);
this.frequency = new Signal(options.frequency, "frequency");
this.gain = new Signal(options.gain, "decibels");
this._type = options.type;
readOnly(this, ["detune", "frequency", "gain", "Q"]);
}
static getDefaults(): FilterOptions {
return Object.assign(ToneAudioNode.getDefaults(), {
type: "lowpass" as FilterType,
frequency: 200,
rolloff: "-12" as RolloffType,
Q: 1,
gain: 0
});
}
get type(): FilterType {
return this.type;
}
set type(type: FilterType) {
var types: FilterType[] = ["lowpass", "highpass", "bandpass", "lowshelf", "highshelf", "notch", "allpass", "peaking"];
if (types.indexOf(type)=== -1){
throw new TypeError("Tone.Filter: invalid type "+type);
}
this._filters.forEach(filter => filter.type = type);
}
get rolloff(): RolloffType {
return this.rolloff;
}
set rolloff(rolloff: RolloffType) {
const rolloffNum = parseInt(rolloff, 10);
const possibilities = [-12, -24, -48, -96];
const cascadingCount = possibilities.indexOf(rolloffNum);
//check the rolloff is valid
if (cascadingCount === -1){
throw new RangeError("Tone.Filter: rolloff can only be -12, -24, -48 or -96");
}
this._rolloff = rolloffNum;
this.input.disconnect();
this._filters.forEach(filter => {
filter.disconnect();
});
this._filters = new Array(cascadingCount);
for (let count = 0; count < cascadingCount; count++){
let filter = this.context.createBiquadFilter();
filter.type = this._type;
this.frequency.connect(filter.frequency);
this.detune.connect(filter.detune);
this.Q.connect(filter.Q);
this.gain.connect(filter.gain);
this._filters[count] = filter;
}
}
/**
* Get the frequency response curve. This curve represets how the filter
* responses to frequencies between 20hz-20khz.
* @param {Number} [len=128] The number of values to return
* @return {Float32Array} The frequency response curve between 20-20k
*/
getFrequencyResponse(len: number): Float32Array {
// len = Tone.defaultArg(len, 128); Is this still needed?
//start with all 1s
const totalResponse = new Float32Array(len).map(() => 1);
const freqValues = new Float32Array(len);
for (let i = 0; i < len; i++){
const norm = Math.pow(i / len, 2);
const freq = norm * (20000 - 20) + 20;
freqValues[i] = freq;
}
const magValues = new Float32Array(len);
const phaseValues = new Float32Array(len);
this._filters.forEach(() => {
const filterClone = this.context.createBiquadFilter();
filterClone.type = this._type;
filterClone.Q.value = this.Q.value;
filterClone.frequency.value = this.frequency.value;
filterClone.gain.value = this.gain.value;
filterClone.getFrequencyResponse(freqValues, magValues, phaseValues);
magValues.forEach((val, i) =>{
totalResponse[i] *= val;
});
});
return totalResponse;
}
/**
* Clean up.
* @return {Tone.Filter} this
*/
dispose(): this {
super.dispose();
this._filters.forEach(filter => {
filter.disconnect();
});
writable(this, ["detune", "frequency", "gain", "Q"]);
this.frequency.dispose();
this.Q.dispose();
this.detune.dispose();
this.gain.dispose();
return this;
}
}