UNPKG

4.46 kBPlain TextView Raw
1import { ToneAudioNodeOptions } from "../core/context/ToneAudioNode.js";
2import { optionsFromArguments } from "../core/util/Defaults.js";
3import { isArray, isFunction } from "../core/util/TypeCheck.js";
4import { assert } from "../core/util/Debug.js";
5import { Signal } from "./Signal.js";
6import { SignalOperator } from "./SignalOperator.js";
7
8export type WaveShaperMappingFn = (value: number, index?: number) => number;
9
10type WaveShaperMapping = WaveShaperMappingFn | number[] | Float32Array;
11
12interface WaveShaperOptions extends ToneAudioNodeOptions {
13 mapping?: WaveShaperMapping;
14 length: number;
15 curve?: number[] | Float32Array;
16}
17
18/**
19 * Wraps the native Web Audio API
20 * [WaveShaperNode](http://webaudio.github.io/web-audio-api/#the-waveshapernode-interface).
21 *
22 * @example
23 * const osc = new Tone.Oscillator().toDestination().start();
24 * // multiply the output of the signal by 2 using the waveshaper's function
25 * const timesTwo = new Tone.WaveShaper((val) => val * 2, 2048).connect(osc.frequency);
26 * const signal = new Tone.Signal(440).connect(timesTwo);
27 * @category Signal
28 */
29export class WaveShaper extends SignalOperator<WaveShaperOptions> {
30 readonly name: string = "WaveShaper";
31
32 /**
33 * the waveshaper node
34 */
35 private _shaper: WaveShaperNode = this.context.createWaveShaper();
36
37 /**
38 * The input to the waveshaper node.
39 */
40 input = this._shaper;
41
42 /**
43 * The output from the waveshaper node
44 */
45 output = this._shaper;
46
47 /**
48 * @param mapping The function used to define the values.
49 * The mapping function should take two arguments:
50 * the first is the value at the current position
51 * and the second is the array position.
52 * If the argument is an array, that array will be
53 * set as the wave shaping function. The input
54 * signal is an AudioRange [-1, 1] value and the output
55 * signal can take on any numerical values.
56 *
57 * @param length The length of the WaveShaperNode buffer.
58 */
59 constructor(mapping?: WaveShaperMapping, length?: number);
60 constructor(options?: Partial<WaveShaperOptions>);
61 constructor() {
62 const options = optionsFromArguments(
63 WaveShaper.getDefaults(),
64 arguments,
65 ["mapping", "length"]
66 );
67 super(options);
68
69 if (
70 isArray(options.mapping) ||
71 options.mapping instanceof Float32Array
72 ) {
73 this.curve = Float32Array.from(options.mapping);
74 } else if (isFunction(options.mapping)) {
75 this.setMap(options.mapping, options.length);
76 }
77 }
78
79 static getDefaults(): WaveShaperOptions {
80 return Object.assign(Signal.getDefaults(), {
81 length: 1024,
82 });
83 }
84
85 /**
86 * Uses a mapping function to set the value of the curve.
87 * @param mapping The function used to define the values.
88 * The mapping function take two arguments:
89 * the first is the value at the current position
90 * which goes from -1 to 1 over the number of elements
91 * in the curve array. The second argument is the array position.
92 * @example
93 * const shaper = new Tone.WaveShaper();
94 * // map the input signal from [-1, 1] to [0, 10]
95 * shaper.setMap((val, index) => (val + 1) * 5);
96 */
97 setMap(mapping: WaveShaperMappingFn, length = 1024): this {
98 const array = new Float32Array(length);
99 for (let i = 0, len = length; i < len; i++) {
100 const normalized = (i / (len - 1)) * 2 - 1;
101 array[i] = mapping(normalized, i);
102 }
103 this.curve = array;
104 return this;
105 }
106
107 /**
108 * The array to set as the waveshaper curve. For linear curves
109 * array length does not make much difference, but for complex curves
110 * longer arrays will provide smoother interpolation.
111 */
112 get curve(): Float32Array | null {
113 return this._shaper.curve;
114 }
115
116 set curve(mapping: Float32Array | null) {
117 this._shaper.curve = mapping;
118 }
119
120 /**
121 * Specifies what type of oversampling (if any) should be used when
122 * applying the shaping curve. Can either be "none", "2x" or "4x".
123 */
124 get oversample(): OverSampleType {
125 return this._shaper.oversample;
126 }
127
128 set oversample(oversampling: OverSampleType) {
129 const isOverSampleType = ["none", "2x", "4x"].some((str) =>
130 str.includes(oversampling)
131 );
132 assert(
133 isOverSampleType,
134 "oversampling must be either 'none', '2x', or '4x'"
135 );
136 this._shaper.oversample = oversampling;
137 }
138
139 /**
140 * Clean up.
141 */
142 dispose(): this {
143 super.dispose();
144 this._shaper.disconnect();
145 return this;
146 }
147}