mirror of
https://github.com/Tonejs/Tone.js
synced 2025-01-12 20:08:48 +00:00
adding decorators which validate the input value range
This commit is contained in:
parent
2b0c2a64f1
commit
48284d78fb
5 changed files with 146 additions and 122 deletions
|
@ -5,7 +5,8 @@ import { optionsFromArguments } from "../../core/util/Defaults";
|
|||
import { isArray, isObject, isString } from "../../core/util/TypeCheck";
|
||||
import { connectSignal, Signal } from "../../signal/Signal";
|
||||
import { OfflineContext } from "../../core/context/OfflineContext";
|
||||
import { assertRange } from "../../core/util/Debug";
|
||||
import { assert } from "../../core/util/Debug";
|
||||
import { range, timeRange } from "../../core/util/Decorator";
|
||||
|
||||
type BasicEnvelopeCurve = "linear" | "exponential";
|
||||
type InternalEnvelopeCurve = BasicEnvelopeCurve | number[];
|
||||
|
@ -55,24 +56,84 @@ export class Envelope extends ToneAudioNode<EnvelopeOptions> {
|
|||
readonly name: string = "Envelope";
|
||||
|
||||
/**
|
||||
* Private container for the attack value
|
||||
* When triggerAttack is called, the attack time is the amount of
|
||||
* time it takes for the envelope to reach it's maximum value.
|
||||
* ```
|
||||
* /\
|
||||
* /X \
|
||||
* /XX \
|
||||
* /XXX \
|
||||
* /XXXX \___________
|
||||
* /XXXXX \
|
||||
* /XXXXXX \
|
||||
* /XXXXXXX \
|
||||
* /XXXXXXXX \
|
||||
* ```
|
||||
* @min 0
|
||||
* @max 2
|
||||
*/
|
||||
private _attack!: Time;
|
||||
@timeRange(0)
|
||||
attack: Time;
|
||||
|
||||
/**
|
||||
* Private holder of the decay time
|
||||
* After the attack portion of the envelope, the value will fall
|
||||
* over the duration of the decay time to it's sustain value.
|
||||
* ```
|
||||
* /\
|
||||
* / X\
|
||||
* / XX\
|
||||
* / XXX\
|
||||
* / XXXX\___________
|
||||
* / XXXXX \
|
||||
* / XXXXX \
|
||||
* / XXXXX \
|
||||
* / XXXXX \
|
||||
* ```
|
||||
* @min 0
|
||||
* @max 2
|
||||
*/
|
||||
private _decay!: Time;
|
||||
@timeRange(0)
|
||||
decay: Time;
|
||||
|
||||
/**
|
||||
* private holder for the sustain value
|
||||
* The sustain value is the value
|
||||
* which the envelope rests at after triggerAttack is
|
||||
* called, but before triggerRelease is invoked.
|
||||
* ```
|
||||
* /\
|
||||
* / \
|
||||
* / \
|
||||
* / \
|
||||
* / \___________
|
||||
* / XXXXXXXXXXX\
|
||||
* / XXXXXXXXXXX \
|
||||
* / XXXXXXXXXXX \
|
||||
* / XXXXXXXXXXX \
|
||||
* ```
|
||||
*/
|
||||
private _sustain!: NormalRange;
|
||||
@range(0, 1)
|
||||
sustain: NormalRange;
|
||||
|
||||
/**
|
||||
* private holder for the release value
|
||||
* After triggerRelease is called, the envelope's
|
||||
* value will fall to it's miminum value over the
|
||||
* duration of the release time.
|
||||
* ```
|
||||
* /\
|
||||
* / \
|
||||
* / \
|
||||
* / \
|
||||
* / \___________
|
||||
* / X\
|
||||
* / XX\
|
||||
* / XXX\
|
||||
* / XXXX\
|
||||
* ```
|
||||
* @min 0
|
||||
* @max 5
|
||||
*/
|
||||
private _release!: Time;
|
||||
@timeRange(0)
|
||||
release: Time;
|
||||
|
||||
/**
|
||||
* The automation curve type for the attack
|
||||
|
@ -153,106 +214,6 @@ export class Envelope extends ToneAudioNode<EnvelopeOptions> {
|
|||
return this.getValueAtTime(this.now());
|
||||
}
|
||||
|
||||
/**
|
||||
* When triggerAttack is called, the attack time is the amount of
|
||||
* time it takes for the envelope to reach it's maximum value.
|
||||
* ```
|
||||
* /\
|
||||
* /X \
|
||||
* /XX \
|
||||
* /XXX \
|
||||
* /XXXX \___________
|
||||
* /XXXXX \
|
||||
* /XXXXXX \
|
||||
* /XXXXXXX \
|
||||
* /XXXXXXXX \
|
||||
* ```
|
||||
* @min 0
|
||||
* @max 2
|
||||
*/
|
||||
get attack(): Time {
|
||||
return this._attack;
|
||||
}
|
||||
set attack(time) {
|
||||
assertRange(this.toSeconds(time), 0);
|
||||
this._attack = time;
|
||||
}
|
||||
|
||||
/**
|
||||
* After the attack portion of the envelope, the value will fall
|
||||
* over the duration of the decay time to it's sustain value.
|
||||
* ```
|
||||
* /\
|
||||
* / X\
|
||||
* / XX\
|
||||
* / XXX\
|
||||
* / XXXX\___________
|
||||
* / XXXXX \
|
||||
* / XXXXX \
|
||||
* / XXXXX \
|
||||
* / XXXXX \
|
||||
* ```
|
||||
* @min 0
|
||||
* @max 2
|
||||
*/
|
||||
get decay(): Time {
|
||||
return this._decay;
|
||||
}
|
||||
set decay(time) {
|
||||
assertRange(this.toSeconds(time), 0);
|
||||
this._decay = time;
|
||||
}
|
||||
|
||||
/**
|
||||
* The sustain value is the value
|
||||
* which the envelope rests at after triggerAttack is
|
||||
* called, but before triggerRelease is invoked.
|
||||
* ```
|
||||
* /\
|
||||
* / \
|
||||
* / \
|
||||
* / \
|
||||
* / \___________
|
||||
* / XXXXXXXXXXX\
|
||||
* / XXXXXXXXXXX \
|
||||
* / XXXXXXXXXXX \
|
||||
* / XXXXXXXXXXX \
|
||||
* ```
|
||||
*/
|
||||
get sustain(): NormalRange {
|
||||
return this._sustain;
|
||||
}
|
||||
set sustain(val) {
|
||||
assertRange(this.toSeconds(val), 0, 1);
|
||||
this._sustain = val;
|
||||
}
|
||||
|
||||
/**
|
||||
* After triggerRelease is called, the envelope's
|
||||
* value will fall to it's miminum value over the
|
||||
* duration of the release time.
|
||||
* ```
|
||||
* /\
|
||||
* / \
|
||||
* / \
|
||||
* / \
|
||||
* / \___________
|
||||
* / X\
|
||||
* / XX\
|
||||
* / XXX\
|
||||
* / XXXX\
|
||||
* ```
|
||||
* @min 0
|
||||
* @max 5
|
||||
*/
|
||||
get release(): Time {
|
||||
return this._release;
|
||||
}
|
||||
set release(time) {
|
||||
assertRange(this.toSeconds(time), 0);
|
||||
this._release = time;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the curve
|
||||
* @param curve
|
||||
|
@ -359,7 +320,7 @@ export class Envelope extends ToneAudioNode<EnvelopeOptions> {
|
|||
return this._decayCurve;
|
||||
}
|
||||
set decayCurve(curve) {
|
||||
this.assert(["linear", "exponential"].some(c => c === curve), `Invalid envelope curve: ${curve}`);
|
||||
assert(["linear", "exponential"].some(c => c === curve), `Invalid envelope curve: ${curve}`);
|
||||
this._decayCurve = curve;
|
||||
}
|
||||
|
||||
|
@ -421,8 +382,6 @@ export class Envelope extends ToneAudioNode<EnvelopeOptions> {
|
|||
if (this._decayCurve === "linear") {
|
||||
this._sig.linearRampToValueAtTime(decayValue, decay + decayStart);
|
||||
} else {
|
||||
this.assert(this._decayCurve === "exponential",
|
||||
`decayCurve can only be "linear" or "exponential", got ${this._decayCurve}`);
|
||||
this._sig.exponentialApproachValueAtTime(decayValue, decayStart, decay);
|
||||
}
|
||||
}
|
||||
|
@ -453,7 +412,7 @@ export class Envelope extends ToneAudioNode<EnvelopeOptions> {
|
|||
} else if (this._releaseCurve === "exponential") {
|
||||
this._sig.targetRampTo(0, release, time);
|
||||
} else {
|
||||
this.assert(isArray(this._releaseCurve), "releaseCurve must be either 'linear', 'exponential' or an array");
|
||||
assert(isArray(this._releaseCurve), "releaseCurve must be either 'linear', 'exponential' or an array");
|
||||
this._sig.cancelAndHoldAtTime(time);
|
||||
this._sig.setValueCurveAtTime(this._releaseCurve, time, release, currentValue);
|
||||
}
|
||||
|
|
51
Tone/core/util/Decorator.ts
Normal file
51
Tone/core/util/Decorator.ts
Normal file
|
@ -0,0 +1,51 @@
|
|||
import { assertRange } from "./Debug";
|
||||
import { Time } from "../type/Units";
|
||||
|
||||
/**
|
||||
* Assert that the time is in the given range
|
||||
*/
|
||||
export function range(min: number, max = Infinity) {
|
||||
const valueMap: WeakMap<any, number> = new WeakMap();
|
||||
return function(target: any, propertyKey: string | symbol) {
|
||||
Reflect.defineProperty(
|
||||
target,
|
||||
propertyKey,
|
||||
{
|
||||
configurable: true,
|
||||
enumerable: true,
|
||||
get: function() {
|
||||
return valueMap.get(this);
|
||||
},
|
||||
set: function(newValue: number) {
|
||||
assertRange(newValue, min, max);
|
||||
valueMap.set(this, newValue);
|
||||
}
|
||||
},
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the time to seconds and assert that the time is in between the two
|
||||
* values when being set.
|
||||
*/
|
||||
export function timeRange(min: number, max = Infinity) {
|
||||
const valueMap: WeakMap<any, Time> = new WeakMap();
|
||||
return function(target: any, propertyKey: string) {
|
||||
Reflect.defineProperty(
|
||||
target,
|
||||
propertyKey,
|
||||
{
|
||||
configurable: true,
|
||||
enumerable: true,
|
||||
get: function() {
|
||||
return valueMap.get(this);
|
||||
},
|
||||
set: function(newValue: Time) {
|
||||
assertRange(this.toSeconds(newValue), min, max);
|
||||
valueMap.set(this, newValue);
|
||||
}
|
||||
},
|
||||
);
|
||||
};
|
||||
}
|
|
@ -8,6 +8,8 @@ import { noOp } from "../core/util/Interface";
|
|||
import { isArray, isNote, isNumber } from "../core/util/TypeCheck";
|
||||
import { Instrument, InstrumentOptions } from "../instrument/Instrument";
|
||||
import { ToneBufferSource, ToneBufferSourceCurve } from "../source/buffer/ToneBufferSource";
|
||||
import { timeRange } from "../core/util/Decorator";
|
||||
import { assert } from "../core/util/Debug";
|
||||
|
||||
interface SamplesMap {
|
||||
[note: string]: ToneAudioBuffer | AudioBuffer | string;
|
||||
|
@ -64,6 +66,7 @@ export class Sampler extends Instrument<SamplerOptions> {
|
|||
* @min 0
|
||||
* @max 1
|
||||
*/
|
||||
@timeRange(0)
|
||||
attack: Time;
|
||||
|
||||
/**
|
||||
|
@ -71,6 +74,7 @@ export class Sampler extends Instrument<SamplerOptions> {
|
|||
* @min 0
|
||||
* @max 1
|
||||
*/
|
||||
@timeRange(0)
|
||||
release: Time;
|
||||
|
||||
/**
|
||||
|
@ -101,7 +105,7 @@ export class Sampler extends Instrument<SamplerOptions> {
|
|||
const urlMap = {};
|
||||
Object.keys(options.urls).forEach((note) => {
|
||||
const noteNumber = parseInt(note, 10);
|
||||
this.assert(isNote(note)
|
||||
assert(isNote(note)
|
||||
|| (isNumber(noteNumber) && isFinite(noteNumber)), `url key is neither a note or midi pitch: ${note}`);
|
||||
if (isNote(note)) {
|
||||
// convert the note name to MIDI
|
||||
|
@ -263,7 +267,7 @@ export class Sampler extends Instrument<SamplerOptions> {
|
|||
const computedTime = this.toSeconds(time);
|
||||
this.triggerAttack(notes, computedTime, velocity);
|
||||
if (isArray(duration)) {
|
||||
this.assert(isArray(notes), "notes must be an array when duration is array");
|
||||
assert(isArray(notes), "notes must be an array when duration is array");
|
||||
(notes as Frequency[]).forEach((note, index) => {
|
||||
const d = duration[Math.min(index, duration.length - 1)];
|
||||
this.triggerRelease(note, computedTime + this.toSeconds(d));
|
||||
|
@ -281,7 +285,7 @@ export class Sampler extends Instrument<SamplerOptions> {
|
|||
* @param callback The callback to invoke when the url is loaded.
|
||||
*/
|
||||
add(note: Note | MidiNote, url: string | ToneAudioBuffer | AudioBuffer, callback?: () => void): this {
|
||||
this.assert(isNote(note) || isFinite(note), `note must be a pitch or midi: ${note}`);
|
||||
assert(isNote(note) || isFinite(note), `note must be a pitch or midi: ${note}`);
|
||||
if (isNote(note)) {
|
||||
// convert the note name to MIDI
|
||||
const mid = new FrequencyClass(this.context, note).toMidi();
|
||||
|
|
|
@ -434,20 +434,29 @@ describe("Signal", () => {
|
|||
});
|
||||
|
||||
it("converts NormalRange units", () => {
|
||||
const signal = new Signal(2, "normalRange");
|
||||
expect(() => {
|
||||
new Signal(2, "normalRange");
|
||||
}).to.throw(RangeError);
|
||||
const signal = new Signal(1, "normalRange");
|
||||
expect(signal.value).to.be.closeTo(1, 0.01);
|
||||
signal.dispose();
|
||||
});
|
||||
|
||||
|
||||
it("converts AudioRange units", () => {
|
||||
const signal = new Signal(-2, "audioRange");
|
||||
expect(() => {
|
||||
new Signal(-2, "audioRange");
|
||||
}).to.throw(RangeError);
|
||||
const signal = new Signal(-1, "audioRange");
|
||||
expect(signal.value).to.be.closeTo(-1, 0.01);
|
||||
signal.dispose();
|
||||
});
|
||||
|
||||
|
||||
it("converts Positive units", () => {
|
||||
const signal = new Signal(-2, "positive");
|
||||
expect(signal.value).to.be.closeTo(0, 0.01);
|
||||
expect(() => {
|
||||
new Signal(-2, "positive");
|
||||
}).to.throw(RangeError);
|
||||
const signal = new Signal(100, "positive");
|
||||
expect(signal.value).to.be.closeTo(100, 0.01);
|
||||
signal.dispose();
|
||||
});
|
||||
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
"moduleResolution": "node",
|
||||
"strictPropertyInitialization" : true,
|
||||
"downlevelIteration" : true,
|
||||
"experimentalDecorators": true,
|
||||
"lib": ["es6", "dom", "es2015"],
|
||||
"baseUrl": "./"
|
||||
},
|
||||
|
|
Loading…
Reference in a new issue