1 | import { ToneAudioNodeOptions } from "../core/context/ToneAudioNode.js";
|
2 | import { optionsFromArguments } from "../core/util/Defaults.js";
|
3 | import { isArray, isFunction } from "../core/util/TypeCheck.js";
|
4 | import { assert } from "../core/util/Debug.js";
|
5 | import { Signal } from "./Signal.js";
|
6 | import { SignalOperator } from "./SignalOperator.js";
|
7 |
|
8 | export type WaveShaperMappingFn = (value: number, index?: number) => number;
|
9 |
|
10 | type WaveShaperMapping = WaveShaperMappingFn | number[] | Float32Array;
|
11 |
|
12 | interface WaveShaperOptions extends ToneAudioNodeOptions {
|
13 | mapping?: WaveShaperMapping;
|
14 | length: number;
|
15 | curve?: number[] | Float32Array;
|
16 | }
|
17 |
|
18 |
|
19 |
|
20 |
|
21 |
|
22 |
|
23 |
|
24 |
|
25 |
|
26 |
|
27 |
|
28 |
|
29 | export class WaveShaper extends SignalOperator<WaveShaperOptions> {
|
30 | readonly name: string = "WaveShaper";
|
31 |
|
32 | |
33 |
|
34 |
|
35 | private _shaper: WaveShaperNode = this.context.createWaveShaper();
|
36 |
|
37 | |
38 |
|
39 |
|
40 | input = this._shaper;
|
41 |
|
42 | |
43 |
|
44 |
|
45 | output = this._shaper;
|
46 |
|
47 | |
48 |
|
49 |
|
50 |
|
51 |
|
52 |
|
53 |
|
54 |
|
55 |
|
56 |
|
57 |
|
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 |
|
87 |
|
88 |
|
89 |
|
90 |
|
91 |
|
92 |
|
93 |
|
94 |
|
95 |
|
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 |
|
109 |
|
110 |
|
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 |
|
122 |
|
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 |
|
141 |
|
142 | dispose(): this {
|
143 | super.dispose();
|
144 | this._shaper.disconnect();
|
145 | return this;
|
146 | }
|
147 | }
|