2019-10-30 03:14:03 +00:00
|
|
|
import { Effect, EffectOptions } from "./Effect";
|
|
|
|
import { Filter } from "../component/filter/Filter";
|
|
|
|
import { Follower } from "../component/analysis/Follower";
|
|
|
|
import { Decibels, Frequency, GainFactor, Hertz, Positive, Time } from "../core/type/Units";
|
|
|
|
import { optionsFromArguments } from "../core/util/Defaults";
|
|
|
|
import { Gain } from "../core/context/Gain";
|
|
|
|
import { dbToGain, gainToDb } from "../core/type/Conversions";
|
|
|
|
import { ScaleExp } from "../signal/ScaleExp";
|
|
|
|
import { Signal } from "../signal/Signal";
|
|
|
|
import { readOnly } from "../core/util/Interface";
|
|
|
|
|
|
|
|
export interface AutoWahOptions extends EffectOptions {
|
|
|
|
baseFrequency: Frequency;
|
|
|
|
octaves: Positive;
|
|
|
|
sensitivity: Decibels;
|
|
|
|
Q: Positive;
|
|
|
|
gain: GainFactor;
|
|
|
|
follower: Time;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* AutoWah connects a [[Follower]] to a [[Filter]].
|
|
|
|
* The frequency of the filter, follows the input amplitude curve.
|
|
|
|
* Inspiration from [Tuna.js](https://github.com/Dinahmoe/tuna).
|
|
|
|
*
|
|
|
|
* @example
|
2020-04-17 02:24:18 +00:00
|
|
|
* const autoWah = new Tone.AutoWah(50, 6, -30).toDestination();
|
2019-10-30 03:14:03 +00:00
|
|
|
* // initialize the synth and connect to autowah
|
2020-04-17 02:24:18 +00:00
|
|
|
* const synth = new Tone.Synth().connect(autoWah);
|
2019-10-30 03:14:03 +00:00
|
|
|
* // Q value influences the effect of the wah - default is 2
|
|
|
|
* autoWah.Q.value = 6;
|
|
|
|
* // more audible on higher notes
|
|
|
|
* synth.triggerAttackRelease("C4", "8n");
|
|
|
|
* @category Effect
|
|
|
|
*/
|
|
|
|
export class AutoWah extends Effect<AutoWahOptions> {
|
|
|
|
|
|
|
|
readonly name: string = "AutoWah";
|
2020-04-17 02:24:18 +00:00
|
|
|
|
2019-10-30 03:14:03 +00:00
|
|
|
/**
|
|
|
|
* The envelope follower. Set the attack/release
|
|
|
|
* timing to adjust how the envelope is followed.
|
|
|
|
*/
|
|
|
|
private _follower: Follower;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* scales the follower value to the frequency domain
|
|
|
|
*/
|
|
|
|
private _sweepRange: ScaleExp;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Hold the base frequency value
|
|
|
|
*/
|
|
|
|
private _baseFrequency: Hertz;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Private holder for the octave count
|
|
|
|
*/
|
|
|
|
private _octaves: Positive;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* the input gain to adjust the sensitivity
|
|
|
|
*/
|
|
|
|
private _inputBoost: Gain;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Private holder for the filter
|
|
|
|
*/
|
|
|
|
private _bandpass: Filter;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The peaking fitler
|
|
|
|
*/
|
|
|
|
private _peaking: Filter;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The gain of the filter.
|
|
|
|
*/
|
|
|
|
readonly gain: Signal<"decibels">;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The quality of the filter.
|
|
|
|
*/
|
|
|
|
readonly Q: Signal<"positive">;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param baseFrequency The frequency the filter is set to at the low point of the wah
|
|
|
|
* @param octaves The number of octaves above the baseFrequency the filter will sweep to when fully open.
|
|
|
|
* @param sensitivity The decibel threshold sensitivity for the incoming signal. Normal range of -40 to 0.
|
|
|
|
*/
|
|
|
|
constructor(baseFrequency?: Frequency, octaves?: Positive, sensitivity?: Decibels);
|
|
|
|
constructor(options?: Partial<AutoWahOptions>);
|
|
|
|
constructor() {
|
|
|
|
|
|
|
|
super(optionsFromArguments(AutoWah.getDefaults(), arguments, ["baseFrequency", "octaves", "sensitivity"]));
|
|
|
|
const options = optionsFromArguments(AutoWah.getDefaults(), arguments, ["baseFrequency", "octaves", "sensitivity"]);
|
|
|
|
|
|
|
|
this._follower = new Follower({
|
|
|
|
context: this.context,
|
|
|
|
smoothing: options.follower,
|
|
|
|
});
|
|
|
|
this._sweepRange = new ScaleExp({
|
|
|
|
context: this.context,
|
|
|
|
min: 0,
|
|
|
|
max: 1,
|
|
|
|
exponent: 0.5,
|
|
|
|
});
|
|
|
|
this._baseFrequency = this.toFrequency(options.baseFrequency);
|
|
|
|
this._octaves = options.octaves;
|
|
|
|
this._inputBoost = new Gain({ context: this.context });
|
|
|
|
this._bandpass = new Filter({
|
|
|
|
context: this.context,
|
|
|
|
rolloff: -48,
|
|
|
|
frequency: 0,
|
|
|
|
Q: options.Q,
|
|
|
|
});
|
|
|
|
this._peaking = new Filter({
|
|
|
|
context: this.context,
|
|
|
|
type: "peaking"
|
|
|
|
});
|
|
|
|
this._peaking.gain.value = options.gain;
|
|
|
|
this.gain = this._peaking.gain;
|
|
|
|
this.Q = this._bandpass.Q;
|
|
|
|
|
|
|
|
// the control signal path
|
|
|
|
this.effectSend.chain(this._inputBoost, this._follower, this._sweepRange);
|
|
|
|
this._sweepRange.connect(this._bandpass.frequency);
|
|
|
|
this._sweepRange.connect(this._peaking.frequency);
|
|
|
|
// the filtered path
|
|
|
|
this.effectSend.chain(this._bandpass, this._peaking, this.effectReturn);
|
|
|
|
// set the initial value
|
|
|
|
this._setSweepRange();
|
|
|
|
this.sensitivity = options.sensitivity;
|
|
|
|
|
|
|
|
readOnly(this, ["gain", "Q"]);
|
|
|
|
}
|
|
|
|
|
|
|
|
static getDefaults(): AutoWahOptions {
|
|
|
|
return Object.assign(Effect.getDefaults(), {
|
|
|
|
baseFrequency: 100,
|
|
|
|
octaves: 6,
|
|
|
|
sensitivity: 0,
|
|
|
|
Q: 2,
|
|
|
|
gain: 2,
|
|
|
|
follower: 0.2,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The number of octaves that the filter will sweep above the baseFrequency.
|
|
|
|
*/
|
|
|
|
get octaves() {
|
|
|
|
return this._octaves;
|
|
|
|
}
|
|
|
|
set octaves(octaves) {
|
|
|
|
this._octaves = octaves;
|
|
|
|
this._setSweepRange();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The follower's smoothing time
|
|
|
|
*/
|
|
|
|
get follower(): Time {
|
|
|
|
return this._follower.smoothing;
|
|
|
|
}
|
|
|
|
set follower(follower) {
|
|
|
|
this._follower.smoothing = follower;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The base frequency from which the sweep will start from.
|
|
|
|
*/
|
|
|
|
get baseFrequency(): Frequency {
|
|
|
|
return this._baseFrequency;
|
|
|
|
}
|
|
|
|
set baseFrequency(baseFreq) {
|
|
|
|
this._baseFrequency = this.toFrequency(baseFreq);
|
|
|
|
this._setSweepRange();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The sensitivity to control how responsive to the input signal the filter is.
|
|
|
|
*/
|
|
|
|
get sensitivity(): Decibels {
|
|
|
|
return gainToDb(1 / this._inputBoost.gain.value);
|
|
|
|
}
|
|
|
|
set sensitivity(sensitivity) {
|
|
|
|
this._inputBoost.gain.value = 1 / dbToGain(sensitivity);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* sets the sweep range of the scaler
|
|
|
|
*/
|
|
|
|
private _setSweepRange() {
|
|
|
|
this._sweepRange.min = this._baseFrequency;
|
|
|
|
this._sweepRange.max = Math.min(this._baseFrequency * Math.pow(2, this._octaves), this.context.sampleRate / 2);
|
|
|
|
}
|
|
|
|
|
|
|
|
dispose(): this {
|
|
|
|
super.dispose();
|
|
|
|
this._follower.dispose();
|
|
|
|
this._sweepRange.dispose();
|
|
|
|
this._bandpass.dispose();
|
|
|
|
this._peaking.dispose();
|
|
|
|
this._inputBoost.dispose();
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
}
|