Merge branch 'dev' of https://github.com/Tonejs/Tone.js into dev

This commit is contained in:
Yotam Mann 2020-09-07 09:11:32 -04:00
commit ca47d0f719
23 changed files with 91 additions and 22 deletions

View file

@ -9,11 +9,12 @@ import { optionsFromArguments } from "../../core/util/Defaults";
export type MidSideMergeOptions = ToneAudioNodeOptions;
/**
* MidSideMerge merges the mid and side signal after they've been separated by [[MidSideMerge]]
* MidSideMerge merges the mid and side signal after they've been separated by [[MidSideSplit]]
* ```
* Mid = (Left+Right)/sqrt(2); // obtain mid-signal from left and right
* Side = (Left-Right)/sqrt(2); // obtain side-signal from left and righ
* Side = (Left-Right)/sqrt(2); // obtain side-signal from left and right
* ```
* @category Component
*/
export class MidSideMerge extends ToneAudioNode<MidSideMergeOptions> {

View file

@ -14,6 +14,7 @@ export type MidSideSplitOptions = ToneAudioNodeOptions;
* Mid = (Left+Right)/sqrt(2); // obtain mid-signal from left and right
* Side = (Left-Right)/sqrt(2); // obtain side-signal from left and right
* ```
* @category Component
*/
export class MidSideSplit extends ToneAudioNode<MidSideSplitOptions> {

View file

@ -34,6 +34,7 @@ export interface RecorderOptions extends ToneAudioNodeOptions {
* anchor.href = url;
* anchor.click();
* }, 4000);
* @category Component
*/
export class Recorder extends ToneAudioNode<RecorderOptions> {

View file

@ -21,6 +21,7 @@ export interface GateOptions extends ToneAudioNodeOptions {
* const mic = new Tone.UserMedia().connect(gate);
* // the gate will only pass through the incoming
* // signal when it's louder than -30db
* @category Component
*/
export class Gate extends ToneAudioNode<GateOptions> {

View file

@ -21,6 +21,7 @@ export interface LimiterOptions extends ToneAudioNodeOptions {
* const limiter = new Tone.Limiter(-20).toDestination();
* const oscillator = new Tone.Oscillator().connect(limiter);
* oscillator.start();
* @category Component
*/
export class Limiter extends ToneAudioNode<LimiterOptions> {

View file

@ -13,6 +13,7 @@ export interface MidSideCompressorOptions extends ToneAudioNodeOptions {
/**
* MidSideCompressor applies two different compressors to the [[mid]]
* and [[side]] signal components of the input. See [[MidSideSplit]] and [[MidSideMerge]].
* @category Component
*/
export class MidSideCompressor extends ToneAudioNode<MidSideCompressorOptions> {

View file

@ -26,6 +26,7 @@ export interface MultibandCompressorOptions extends ToneAudioNodeOptions {
* threshold: -12
* }
* });
* @category Component
*/
export class MultibandCompressor extends ToneAudioNode<MultibandCompressorOptions> {

View file

@ -23,6 +23,7 @@ export interface FrequencyEnvelopeOptions extends EnvelopeOptions {
* });
* freqEnv.connect(oscillator.frequency);
* freqEnv.triggerAttack();
* @category Component
*/
export class FrequencyEnvelope extends Envelope {

View file

@ -17,6 +17,7 @@ export interface BaseToneOptions { }
/**
* @class Tone is the base class of all other classes.
* @category Core
* @constructor
*/
export abstract class Tone {

View file

@ -46,6 +46,7 @@ export type AutomationEvent = NormalAutomationEvent | TargetAutomationEvent;
* additional unit conversion functionality. It also
* serves as a base-class for classes which have a single,
* automatable parameter.
* @category Core
*/
export class Param<TypeName extends UnitName = "number">
extends ToneWithContext<ParamOptions<TypeName>>

View file

@ -29,6 +29,19 @@ describe("Emitter", () => {
emitter.dispose();
});
it("can unbind duplicate events", () => {
const emitter = new Emitter();
const callback = () => {
throw new Error("should call this");
};
emitter.on("something", callback);
emitter.on("something", callback);
emitter.on("something", callback);
emitter.off("something", callback);
emitter.emit("something");
emitter.dispose();
});
it("'off' does nothing if there is no event scheduled", () => {
const emitter = new Emitter();
const callback = () => {

View file

@ -10,6 +10,7 @@ export interface EmitterEventObject {
* the ability to listen for and emit events.
* Inspiration and reference from Jerome Etienne's [MicroEvent](https://github.com/jeromeetienne/microevent.js).
* MIT (c) 2011 Jerome Etienne.
* @category Core
*/
export class Emitter<EventType extends string = string> extends Tone {
@ -73,7 +74,7 @@ export class Emitter<EventType extends string = string> extends Tone {
this._events[event] = [];
} else {
const eventList = this._events[event];
for (let i = 0; i < eventList.length; i++) {
for (let i = eventList.length - 1; i >= 0; i--) {
if (eventList[i] === callback) {
eventList.splice(i, 1);
}

View file

@ -13,6 +13,7 @@ export type MidSideEffectOptions = EffectOptions;
* Applies a Mid/Side seperation and recombination.
* Algorithm found in [kvraudio forums](http://www.kvraudio.com/forum/viewtopic.php?t=212587).
* This is a base-class for Mid/Side Effects.
* @category Effect
*/
export abstract class MidSideEffect<Options extends MidSideEffectOptions> extends Effect<Options> {

View file

@ -11,17 +11,7 @@ export interface StereoFeedbackEffectOptions extends StereoEffectOptions {
}
/**
* Just like a stereo feedback effect, but the feedback is routed from left to right
* and right to left instead of on the same channel.
* ```
* +--------------------------------+ feedbackL <-----------------------------------+
* | |
* +--> +-----> +----> +---+
* feedbackMerge +--> split (EFFECT) merge +--> feedbackSplit
* +--> +-----> +----> +---+
* | |
* +--------------------------------+ feedbackR <-----------------------------------+
* ```
* Base class for stereo feedback effects where the effectReturn is fed back into the same channel.
*/
export class StereoFeedbackEffect<Options extends StereoFeedbackEffectOptions> extends StereoEffect<Options> {

View file

@ -18,6 +18,7 @@ export interface StereoXFeedbackEffectOptions extends StereoFeedbackEffectOption
* | |
* +--------------------------------+ feedbackR <-------------------------------------+
* ```
* @category Effect
*/
export class StereoXFeedbackEffect<Options extends StereoXFeedbackEffectOptions> extends StereoFeedbackEffect<Options> {

View file

@ -80,14 +80,25 @@ export abstract class Instrument<Options extends InstrumentOptions> extends Tone
* Tone.Transport.start();
*/
sync(): this {
if (!this._synced) {
this._synced = true;
if (this._syncState()) {
this._syncMethod("triggerAttack", 1);
this._syncMethod("triggerRelease", 0);
}
return this;
}
/**
* set _sync
*/
protected _syncState(): boolean {
let changed = false;
if (!this._synced) {
this._synced = true;
changed = true;
}
return changed;
}
/**
* Wrap the given method so that it can be synchronized
* @param method Which method to wrap and sync

View file

@ -103,8 +103,10 @@ export class NoiseSynth extends Instrument<NoiseSynthOptions> {
}
sync(): this {
this._syncMethod("triggerAttack", 0);
this._syncMethod("triggerRelease", 0);
if (this._syncState()) {
this._syncMethod("triggerAttack", 0);
this._syncMethod("triggerRelease", 0);
}
return this;
}

View file

@ -334,8 +334,10 @@ export class PolySynth<Voice extends Monophonic<any> = Synth> extends Instrument
}
sync(): this {
this._syncMethod("triggerAttack", 1);
this._syncMethod("triggerRelease", 1);
if (this._syncState()) {
this._syncMethod("triggerAttack", 1);
this._syncMethod("triggerRelease", 1);
}
return this;
}

View file

@ -254,8 +254,10 @@ export class Sampler extends Instrument<SamplerOptions> {
}
sync(): this {
this._syncMethod("triggerAttack", 1);
this._syncMethod("triggerRelease", 1);
if (this._syncState()) {
this._syncMethod("triggerAttack", 1);
this._syncMethod("triggerRelease", 1);
}
return this;
}

View file

@ -17,6 +17,7 @@ export type GreaterThanOptions = SignalOptions<"number">;
* const gt = new Tone.GreaterThan(2).toDestination();
* const sig = new Tone.Signal(4).connect(gt);
* }, 0.1, 1);
* @category Signal
*/
export class GreaterThan extends Signal<"number"> {

View file

@ -14,6 +14,7 @@ export type GreaterThanZeroOptions = SignalOperatorOptions
* const sig = new Tone.Signal(0.5).connect(gt0);
* sig.setValueAtTime(-1, 0.05);
* }, 0.1, 1);
* @category Signal
*/
export class GreaterThanZero extends SignalOperator<GreaterThanZeroOptions> {

View file

@ -14,6 +14,7 @@ export interface ScaleExpOptions extends ScaleOptions {
* @example
* const scaleExp = new Tone.ScaleExp(0, 100, 2);
* const signal = new Tone.Signal(0.5).connect(scaleExp);
* @category Signal
*/
export class ScaleExp extends Scale<ScaleExpOptions> {

View file

@ -5,6 +5,10 @@ import { Offline } from "./Offline";
import { OutputAudio } from "./OutputAudio";
import { Monophonic } from "Tone/instrument/Monophonic";
function wait(time) {
return new Promise(done => setTimeout(done, time));
}
export function InstrumentTest(Constr, note, constrArg?, optionsIndex?): void {
context("Instrument Tests", () => {
@ -167,6 +171,35 @@ export function InstrumentTest(Constr, note, constrArg?, optionsIndex?): void {
});
});
it("can unsync and re-sync triggerAttack to the Transport", () => {
return Offline(async ({ transport }) => {
const instance = new Constr(constrArg);
instance.toDestination();
instance.sync();
if (note) {
instance.triggerAttack(note, 0.1);
} else {
instance.triggerAttack(0.1);
}
transport.start(0.1);
await wait(100);
instance.unsync();
transport.stop();
instance.sync();
if (note) {
instance.triggerAttack(note, 0.1);
} else {
instance.triggerAttack(0.1);
}
transport.start(0.1);
}, 1).then((buffer) => {
expect(buffer.getTimeOfFirstSound()).to.be.within(0.19, 0.25);
});
});
it("calling sync and unsync multiple times has no effect", () => {
return Offline(({ transport }) => {
const instance = new Constr(constrArg);