converting WaveShaper to ts

This commit is contained in:
Yotam Mann 2019-07-11 17:13:43 -04:00
parent 117b4667d4
commit 182d1040db
2 changed files with 234 additions and 0 deletions

View file

@ -0,0 +1,96 @@
import { expect } from "chai";
import { BasicTests } from "test/helper/Basic";
import { ConstantOutput } from "test/helper/ConstantOutput";
import { Offline } from "test/helper/Offline";
import { Signal } from "./Signal";
import { WaveShaper } from "./WaveShaper";
describe("WaveShaper", () => {
BasicTests(WaveShaper);
describe("Construction Options", () => {
it("can be constructed with an array", () => {
const waveshaper = new WaveShaper([1, 2, 3, 4, 5, 6]);
expect(waveshaper.curve && waveshaper.curve[0]).to.equal(1);
expect(waveshaper.curve && waveshaper.curve[2]).to.equal(3);
});
it("can be constructed with a mapping function", () => {
const waveshaper = new WaveShaper(() => {
return -2;
});
expect(waveshaper.curve && waveshaper.curve[0]).to.equal(-2);
expect(waveshaper.curve && waveshaper.curve[1]).to.equal(-2);
});
it("can be constructed with a length and then set with a map", () => {
const waveshaper = new WaveShaper(() => 10, 2048);
expect(waveshaper.curve && waveshaper.curve.length).to.equal(2048);
expect(waveshaper.curve && waveshaper.curve[0]).to.equal(10);
expect(waveshaper.curve && waveshaper.curve[1]).to.equal(10);
});
it("can be set to oversample", () => {
const waveshaper = new WaveShaper();
expect(waveshaper.oversample).to.equal("none");
waveshaper.oversample = "2x";
expect(waveshaper.oversample).to.equal("2x");
expect(() => {
// @ts-ignore
waveshaper.oversample = "3x";
}).to.throw(Error);
});
});
describe("Logic", () => {
it("shapes the output of the incoming signal", () => {
return ConstantOutput(() => {
const signal = new Signal(1);
const waveshaper = new WaveShaper([-10, -10, -10]);
signal.connect(waveshaper);
waveshaper.toMaster();
}, -10);
});
it("outputs the last curve value when the input is above 1", () => {
return ConstantOutput(() => {
const signal = new Signal(10);
const waveshaper = new WaveShaper([-20, 20]);
signal.connect(waveshaper);
waveshaper.toMaster();
}, 20);
});
it("outputs the first curve value when the input is below -1", () => {
return ConstantOutput(() => {
const signal = new Signal(-1);
const waveshaper = new WaveShaper([-20, 20]);
signal.connect(waveshaper);
waveshaper.toMaster();
}, -20);
});
it("maps the input through the waveshaping curve", () => {
return Offline(() => {
const signal = new Signal(-1);
const waveshaper = new WaveShaper((input) => {
return input * 2;
});
signal.connect(waveshaper);
waveshaper.toMaster();
signal.setValueAtTime(-1, 0);
signal.linearRampToValueAtTime(1, 1);
}, 1).then((buffer) => {
buffer.forEach((sample, time) => {
expect(sample).to.be.closeTo(2 * ((time * 2) - 1), 0.005);
});
});
});
});
});

138
Tone/signal/WaveShaper.ts Normal file
View file

@ -0,0 +1,138 @@
// import Tone from "../core/Tone";
// import "../signal/SignalBase";
import { optionsFromArguments } from "../core/util/Defaults";
import { isArray, isFunction, isUndef } from "../core/util/TypeCheck";
import { Signal, SignalOptions } from "./Signal";
import { SignalOperator } from "./SignalOperator";
type WaveShaperMappingFn = (value: number, index?: number) => number;
type WaveShaperMapping = WaveShaperMappingFn | number[] | Float32Array;
interface WaveShaperOptions extends SignalOptions {
mapping?: WaveShaperMapping;
length: number;
curve?: number[] | Float32Array;
}
/**
* Wraps the native Web Audio API
* [WaveShaperNode](http://webaudio.github.io/web-audio-api/#the-waveshapernode-interface).
*
* @param mapping The function used to define the values.
* 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.
*
* @param bufferLen The length of the WaveShaperNode buffer.
* @example
* var timesTwo = new WaveShaper(function(val){
* return val * 2;
* }, 2048);
* @example
* //a waveshaper can also be constructed with an array of values
* var invert = new WaveShaper([1, -1]);
*/
export class WaveShaper extends SignalOperator<WaveShaperOptions> {
readonly name = "WaveShaper";
/**
* the waveshaper node
*/
private _shaper: WaveShaperNode = this.context.createWaveShaper();
/**
* The input to the waveshaper node.
*/
input = this._shaper;
/**
* The output from the waveshaper node
*/
output = this._shaper;
protected _internalChannels = [this._shaper];
constructor(options?: Partial<WaveShaperOptions>);
constructor(mapping?: WaveShaperMapping , length?: number);
constructor() {
super(Object.assign(optionsFromArguments(WaveShaper.getDefaults(), arguments, ["mapping", "length"])));
const options = optionsFromArguments(WaveShaper.getDefaults(), arguments, ["mapping", "length"]);
if (isArray(options.mapping) || options.mapping instanceof Float32Array) {
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,
});
}
/**
* Uses a mapping function to set the value of the curve.
* @param mapping The function used to define the values.
* 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.
* @example
* //map the input signal from [-1, 1] to [0, 10]
* shaper.setMap(function(val, index){
* return (val + 1) * 5;
* })
*/
setMap(mapping: WaveShaperMappingFn, length: number = 1024): this {
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) {
const isOverSampleType = ["none", "2x", "4x"].some(str => str.includes(oversampling));
this.assert(isOverSampleType, "oversampling must be either 'none', '2x', or '4x'");
this._shaper.oversample = oversampling;
}
/**
* Clean up.
*/
dispose(): this {
super.dispose();
this._shaper.disconnect();
return this;
}
}