tone
Version:
A Web Audio framework for making interactive music in the browser.
227 lines (207 loc) • 6.06 kB
text/typescript
import { Gain } from "../../core/context/Gain.js";
import {
connectSeries,
ToneAudioNode,
} from "../../core/context/ToneAudioNode.js";
import { Frequency } from "../../core/type/Units.js";
import { optionsFromArguments } from "../../core/util/Defaults.js";
import { readOnly, writable } from "../../core/util/Interface.js";
import { isNumber } from "../../core/util/TypeCheck.js";
import { Signal } from "../../signal/Signal.js";
import { assert } from "../../core/util/Debug.js";
import { BiquadFilter, BiquadFilterOptions } from "./BiquadFilter.js";
export type FilterRollOff = -12 | -24 | -48 | -96;
export type FilterOptions = BiquadFilterOptions & {
rolloff: FilterRollOff;
};
/**
* 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.
* @example
* const filter = new Tone.Filter(1500, "highpass").toDestination();
* filter.frequency.rampTo(20000, 10);
* const noise = new Tone.Noise().connect(filter).start();
* @category Component
*/
export class Filter extends ToneAudioNode<FilterOptions> {
readonly name: string = "Filter";
readonly input = new Gain({ context: this.context });
readonly output = new Gain({ context: this.context });
private _filters: BiquadFilter[] = [];
/**
* the rolloff value of the filter
*/
private _rolloff!: FilterRollOff;
private _type: BiquadFilterType;
/**
* The Q or Quality of the filter
*/
readonly Q: Signal<"positive">;
/**
* The cutoff frequency of the filter.
*/
readonly frequency: Signal<"frequency">;
/**
* The detune parameter
*/
readonly detune: Signal<"cents">;
/**
* The gain of the filter, only used in certain filter types
*/
readonly gain: Signal<"decibels">;
/**
* @param frequency The cutoff frequency of the filter.
* @param type The type of filter.
* @param rolloff The drop in decibels per octave after the cutoff frequency
*/
constructor(
frequency?: Frequency,
type?: BiquadFilterType,
rolloff?: FilterRollOff
);
constructor(options?: Partial<FilterOptions>);
constructor() {
const options = optionsFromArguments(Filter.getDefaults(), arguments, [
"frequency",
"type",
"rolloff",
]);
super(options);
this._filters = [];
this.Q = new Signal({
context: this.context,
units: "positive",
value: options.Q,
});
this.frequency = new Signal({
context: this.context,
units: "frequency",
value: options.frequency,
});
this.detune = new Signal({
context: this.context,
units: "cents",
value: options.detune,
});
this.gain = new Signal({
context: this.context,
units: "decibels",
convert: false,
value: options.gain,
});
this._type = options.type;
this.rolloff = options.rolloff;
readOnly(this, ["detune", "frequency", "gain", "Q"]);
}
static getDefaults(): FilterOptions {
return Object.assign(ToneAudioNode.getDefaults(), {
Q: 1,
detune: 0,
frequency: 350,
gain: 0,
rolloff: -12 as FilterRollOff,
type: "lowpass" as BiquadFilterType,
});
}
/**
* The type of the filter. Types: "lowpass", "highpass",
* "bandpass", "lowshelf", "highshelf", "notch", "allpass", or "peaking".
*/
get type(): BiquadFilterType {
return this._type;
}
set type(type: BiquadFilterType) {
const types: BiquadFilterType[] = [
"lowpass",
"highpass",
"bandpass",
"lowshelf",
"highshelf",
"notch",
"allpass",
"peaking",
];
assert(types.indexOf(type) !== -1, `Invalid filter type: ${type}`);
this._type = type;
this._filters.forEach((filter) => (filter.type = type));
}
/**
* The rolloff of the filter which is the drop in db
* per octave. Implemented internally by cascading filters.
* Only accepts the values -12, -24, -48 and -96.
*/
get rolloff(): FilterRollOff {
return this._rolloff;
}
set rolloff(rolloff) {
const rolloffNum = isNumber(rolloff)
? rolloff
: (parseInt(rolloff, 10) as FilterRollOff);
const possibilities = [-12, -24, -48, -96];
let cascadingCount = possibilities.indexOf(rolloffNum);
// check the rolloff is valid
assert(
cascadingCount !== -1,
`rolloff can only be ${possibilities.join(", ")}`
);
cascadingCount += 1;
this._rolloff = rolloffNum;
this.input.disconnect();
this._filters.forEach((filter) => filter.disconnect());
this._filters = new Array(cascadingCount);
for (let count = 0; count < cascadingCount; count++) {
const filter = new BiquadFilter({
context: this.context,
});
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;
}
this._internalChannels = this._filters;
connectSeries(this.input, ...this._internalChannels, this.output);
}
/**
* Get the frequency response curve. This curve represents how the filter
* responses to frequencies between 20hz-20khz.
* @param len The number of values to return
* @return The frequency response curve between 20-20kHz
*/
getFrequencyResponse(len = 128): Float32Array {
const filterClone = new BiquadFilter({
context: this.context,
frequency: this.frequency.value,
gain: this.gain.value,
Q: this.Q.value,
type: this._type,
detune: this.detune.value,
});
// start with all 1s
const totalResponse = new Float32Array(len).map(() => 1);
this._filters.forEach(() => {
const response = filterClone.getFrequencyResponse(len);
response.forEach((val, i) => (totalResponse[i] *= val));
});
filterClone.dispose();
return totalResponse;
}
/**
* Clean up.
*/
dispose(): this {
super.dispose();
this._filters.forEach((filter) => {
filter.dispose();
});
writable(this, ["detune", "frequency", "gain", "Q"]);
this.frequency.dispose();
this.Q.dispose();
this.detune.dispose();
this.gain.dispose();
return this;
}
}