2024-05-03 18:31:14 +00:00
|
|
|
import { ToneAudioNodeOptions } from "../core/context/ToneAudioNode.js";
|
|
|
|
import { optionsFromArguments } from "../core/util/Defaults.js";
|
|
|
|
import { isArray, isFunction } from "../core/util/TypeCheck.js";
|
|
|
|
import { assert } from "../core/util/Debug.js";
|
|
|
|
import { Signal } from "./Signal.js";
|
|
|
|
import { SignalOperator } from "./SignalOperator.js";
|
2019-07-11 21:13:43 +00:00
|
|
|
|
2019-09-16 22:18:03 +00:00
|
|
|
export type WaveShaperMappingFn = (value: number, index?: number) => number;
|
2019-07-11 21:13:43 +00:00
|
|
|
|
2019-09-14 22:12:44 +00:00
|
|
|
type WaveShaperMapping = WaveShaperMappingFn | number[] | Float32Array;
|
2019-07-11 21:13:43 +00:00
|
|
|
|
2019-07-15 19:10:11 +00:00
|
|
|
interface WaveShaperOptions extends ToneAudioNodeOptions {
|
2019-07-11 21:13:43 +00:00
|
|
|
mapping?: WaveShaperMapping;
|
|
|
|
length: number;
|
|
|
|
curve?: number[] | Float32Array;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Wraps the native Web Audio API
|
|
|
|
* [WaveShaperNode](http://webaudio.github.io/web-audio-api/#the-waveshapernode-interface).
|
|
|
|
*
|
2019-08-27 15:53:14 +00:00
|
|
|
* @example
|
2020-04-17 02:24:18 +00:00
|
|
|
* const osc = new Tone.Oscillator().toDestination().start();
|
2019-10-25 20:54:33 +00:00
|
|
|
* // multiply the output of the signal by 2 using the waveshaper's function
|
2020-04-17 02:24:18 +00:00
|
|
|
* const timesTwo = new Tone.WaveShaper((val) => val * 2, 2048).connect(osc.frequency);
|
|
|
|
* const signal = new Tone.Signal(440).connect(timesTwo);
|
2019-09-16 14:15:23 +00:00
|
|
|
* @category Signal
|
2019-07-11 21:13:43 +00:00
|
|
|
*/
|
|
|
|
export class WaveShaper extends SignalOperator<WaveShaperOptions> {
|
2019-09-04 23:18:44 +00:00
|
|
|
readonly name: string = "WaveShaper";
|
2019-07-11 21:13:43 +00:00
|
|
|
|
|
|
|
/**
|
2019-09-14 20:39:18 +00:00
|
|
|
* the waveshaper node
|
2019-07-11 21:13:43 +00:00
|
|
|
*/
|
|
|
|
private _shaper: WaveShaperNode = this.context.createWaveShaper();
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The input to the waveshaper node.
|
|
|
|
*/
|
|
|
|
input = this._shaper;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The output from the waveshaper node
|
|
|
|
*/
|
|
|
|
output = this._shaper;
|
|
|
|
|
2019-08-27 15:53:14 +00:00
|
|
|
/**
|
2019-08-30 16:06:38 +00:00
|
|
|
* @param mapping The function used to define the values.
|
2019-09-14 20:39:18 +00:00
|
|
|
* The mapping function should take two arguments:
|
|
|
|
* the first is the value at the current position
|
|
|
|
* and the second is the array position.
|
|
|
|
* If the argument is an array, that array will be
|
|
|
|
* set as the wave shaping function. The input
|
|
|
|
* signal is an AudioRange [-1, 1] value and the output
|
|
|
|
* signal can take on any numerical values.
|
2019-08-27 15:53:14 +00:00
|
|
|
*
|
2024-04-30 13:44:57 +00:00
|
|
|
* @param length The length of the WaveShaperNode buffer.
|
2019-08-27 15:53:14 +00:00
|
|
|
*/
|
2019-09-14 22:12:44 +00:00
|
|
|
constructor(mapping?: WaveShaperMapping, length?: number);
|
2019-08-27 15:53:14 +00:00
|
|
|
constructor(options?: Partial<WaveShaperOptions>);
|
2019-07-11 21:13:43 +00:00
|
|
|
constructor() {
|
2024-05-03 18:31:14 +00:00
|
|
|
const options = optionsFromArguments(
|
|
|
|
WaveShaper.getDefaults(),
|
|
|
|
arguments,
|
|
|
|
["mapping", "length"]
|
|
|
|
);
|
2024-05-06 14:55:55 +00:00
|
|
|
super(options);
|
2024-05-03 18:31:14 +00:00
|
|
|
|
|
|
|
if (
|
|
|
|
isArray(options.mapping) ||
|
|
|
|
options.mapping instanceof Float32Array
|
|
|
|
) {
|
2019-07-11 21:13:43 +00:00
|
|
|
this.curve = Float32Array.from(options.mapping);
|
|
|
|
} else if (isFunction(options.mapping)) {
|
|
|
|
this.setMap(options.mapping, options.length);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static getDefaults(): WaveShaperOptions {
|
|
|
|
return Object.assign(Signal.getDefaults(), {
|
|
|
|
length: 1024,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2019-09-14 20:39:18 +00:00
|
|
|
* Uses a mapping function to set the value of the curve.
|
2019-08-30 16:06:38 +00:00
|
|
|
* @param mapping The function used to define the values.
|
2019-09-14 20:39:18 +00:00
|
|
|
* The mapping function take two arguments:
|
|
|
|
* the first is the value at the current position
|
|
|
|
* which goes from -1 to 1 over the number of elements
|
|
|
|
* in the curve array. The second argument is the array position.
|
2019-07-11 21:13:43 +00:00
|
|
|
* @example
|
2020-04-17 02:24:18 +00:00
|
|
|
* const shaper = new Tone.WaveShaper();
|
2019-10-25 20:54:33 +00:00
|
|
|
* // map the input signal from [-1, 1] to [0, 10]
|
|
|
|
* shaper.setMap((val, index) => (val + 1) * 5);
|
2019-07-11 21:13:43 +00:00
|
|
|
*/
|
2019-11-17 18:09:19 +00:00
|
|
|
setMap(mapping: WaveShaperMappingFn, length = 1024): this {
|
2019-07-11 21:13:43 +00:00
|
|
|
const array = new Float32Array(length);
|
|
|
|
for (let i = 0, len = length; i < len; i++) {
|
|
|
|
const normalized = (i / (len - 1)) * 2 - 1;
|
|
|
|
array[i] = mapping(normalized, i);
|
|
|
|
}
|
|
|
|
this.curve = array;
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The array to set as the waveshaper curve. For linear curves
|
|
|
|
* array length does not make much difference, but for complex curves
|
|
|
|
* longer arrays will provide smoother interpolation.
|
|
|
|
*/
|
|
|
|
get curve(): Float32Array | null {
|
|
|
|
return this._shaper.curve;
|
|
|
|
}
|
|
|
|
|
|
|
|
set curve(mapping: Float32Array | null) {
|
|
|
|
this._shaper.curve = mapping;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Specifies what type of oversampling (if any) should be used when
|
|
|
|
* applying the shaping curve. Can either be "none", "2x" or "4x".
|
|
|
|
*/
|
|
|
|
get oversample(): OverSampleType {
|
|
|
|
return this._shaper.oversample;
|
|
|
|
}
|
|
|
|
|
|
|
|
set oversample(oversampling: OverSampleType) {
|
2024-05-03 18:31:14 +00:00
|
|
|
const isOverSampleType = ["none", "2x", "4x"].some((str) =>
|
|
|
|
str.includes(oversampling)
|
|
|
|
);
|
|
|
|
assert(
|
|
|
|
isOverSampleType,
|
|
|
|
"oversampling must be either 'none', '2x', or '4x'"
|
|
|
|
);
|
2019-07-11 21:13:43 +00:00
|
|
|
this._shaper.oversample = oversampling;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2019-09-14 20:39:18 +00:00
|
|
|
* Clean up.
|
2019-07-11 21:13:43 +00:00
|
|
|
*/
|
|
|
|
dispose(): this {
|
|
|
|
super.dispose();
|
|
|
|
this._shaper.disconnect();
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
}
|