diff --git a/Tone/component/envelope/Envelope.ts b/Tone/component/envelope/Envelope.ts index 64b10227..0ecbe542 100644 --- a/Tone/component/envelope/Envelope.ts +++ b/Tone/component/envelope/Envelope.ts @@ -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 { 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 { 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 { 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 { 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 { } 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); } diff --git a/Tone/core/util/Decorator.ts b/Tone/core/util/Decorator.ts new file mode 100644 index 00000000..cbefcebe --- /dev/null +++ b/Tone/core/util/Decorator.ts @@ -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 = 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 = 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); + } + }, + ); + }; +} diff --git a/Tone/instrument/Sampler.ts b/Tone/instrument/Sampler.ts index af5745b5..711ebe7a 100644 --- a/Tone/instrument/Sampler.ts +++ b/Tone/instrument/Sampler.ts @@ -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 { * @min 0 * @max 1 */ + @timeRange(0) attack: Time; /** @@ -71,6 +74,7 @@ export class Sampler extends Instrument { * @min 0 * @max 1 */ + @timeRange(0) release: Time; /** @@ -101,7 +105,7 @@ export class Sampler extends Instrument { 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 { 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 { * @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(); diff --git a/Tone/signal/Signal.test.ts b/Tone/signal/Signal.test.ts index 1e576b39..3e263efe 100644 --- a/Tone/signal/Signal.test.ts +++ b/Tone/signal/Signal.test.ts @@ -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(); }); diff --git a/tsconfig.json b/tsconfig.json index 4e9e792e..482b8559 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -13,6 +13,7 @@ "moduleResolution": "node", "strictPropertyInitialization" : true, "downlevelIteration" : true, + "experimentalDecorators": true, "lib": ["es6", "dom", "es2015"], "baseUrl": "./" },