Skip to content

Commit

Permalink
feat: Chorus extends StereoFeedbackEffect
Browse files Browse the repository at this point in the history
This makes it possible to do flanger-type effects.

fixes #575
  • Loading branch information
tambien committed Nov 13, 2019
1 parent 94ab939 commit a28f1af
Show file tree
Hide file tree
Showing 4 changed files with 84 additions and 33 deletions.
38 changes: 36 additions & 2 deletions Tone/effect/Chorus.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { EffectTests } from "test/helper/EffectTests";
import { expect } from "chai";
import { CompareToFile } from "test/helper/CompareToFile";
import { Oscillator } from "Tone/source";
import { Offline } from "test/helper/Offline";

describe("Chorus", () => {

Expand All @@ -12,9 +13,9 @@ describe("Chorus", () => {

it("matches a file", () => {
return CompareToFile(() => {
const chorus = new Chorus().toDestination();
const chorus = new Chorus().toDestination().start();
const osc = new Oscillator(220, "sawtooth").connect(chorus).start();
}, "chorus.wav", 0.15);
}, "chorus.wav", 0.1);
});

context("API", () => {
Expand Down Expand Up @@ -48,6 +49,39 @@ describe("Chorus", () => {
expect(chorus.delayTime).to.equal(3);
chorus.dispose();
});

it("can be started and stopped", () => {
const chorus = new Chorus();
chorus.start().stop("+0.2");
chorus.dispose();
});

it("can sync the frequency to the transport", () => {
return Offline(({ transport }) => {
const chorus = new Chorus(2);
chorus.sync();
chorus.frequency.toDestination();
transport.bpm.setValueAtTime(transport.bpm.value * 2, 0.05);
// transport.start(0)
}, 0.1).then((buffer) => {
expect(buffer.getValueAtTime(0)).to.be.closeTo(2, 0.1);
expect(buffer.getValueAtTime(0.05)).to.be.closeTo(4, 0.1);
});
});

it("can unsync the frequency to the transport", () => {
return Offline(({ transport }) => {
const chorus = new Chorus(2);
chorus.sync();
chorus.frequency.toDestination();
transport.bpm.setValueAtTime(transport.bpm.value * 2, 0.05);
chorus.unsync();
// transport.start(0)
}, 0.1).then((buffer) => {
expect(buffer.getValueAtTime(0)).to.be.closeTo(2, 0.1);
expect(buffer.getValueAtTime(0.05)).to.be.closeTo(2, 0.1);
});
});
});
});

75 changes: 46 additions & 29 deletions Tone/effect/Chorus.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
import { StereoEffect, StereoEffectOptions } from "../effect/StereoEffect";
import { Degrees, Frequency, Milliseconds, NormalRange, Seconds } from "../core/type/Units";
import { StereoFeedbackEffect, StereoFeedbackEffectOptions } from "../effect/StereoFeedbackEffect";
import { Degrees, Frequency, Milliseconds, NormalRange, Seconds, Time } from "../core/type/Units";
import { ToneOscillatorType } from "../source/oscillator/OscillatorInterface";
import { optionsFromArguments } from "../core/util/Defaults";
import { LFO } from "../source/oscillator/LFO";
import { Delay } from "../core/context/Delay";
import { Signal } from "../signal/Signal";
import { readOnly } from "../core/util/Interface";
import { Gain } from "../core/context/Gain";

export interface ChorusOptions extends StereoEffectOptions {
export interface ChorusOptions extends StereoFeedbackEffectOptions {
frequency: Frequency;
delayTime: Milliseconds;
depth: NormalRange;
Expand All @@ -30,7 +29,7 @@ export interface ChorusOptions extends StereoEffectOptions {
*
* @category Effect
*/
export class Chorus extends StereoEffect<ChorusOptions> {
export class Chorus extends StereoFeedbackEffect<ChorusOptions> {

readonly name: string = "Chorus";

Expand Down Expand Up @@ -69,16 +68,6 @@ export class Chorus extends StereoEffect<ChorusOptions> {
*/
readonly frequency: Signal<"frequency">

/**
* Pass the left signal through
*/
private _passThroughL: Gain;

/**
* Pass the right signal through
*/
private _passThroughR: Gain;

/**
* @param frequency The frequency of the LFO.
* @param delayTime The delay of the chorus effect in ms.
Expand All @@ -88,8 +77,8 @@ export class Chorus extends StereoEffect<ChorusOptions> {
constructor(options?: Partial<ChorusOptions>);
constructor() {

super(optionsFromArguments(Chorus.getDefaults(), arguments, ["order"]));
const options = optionsFromArguments(Chorus.getDefaults(), arguments, ["order"]);
super(optionsFromArguments(Chorus.getDefaults(), arguments, ["frequency", "delayTime", "depth"]));
const options = optionsFromArguments(Chorus.getDefaults(), arguments, ["frequency", "delayTime", "depth"]);

this._depth = options.depth;
this._delayTime = options.delayTime / 1000;
Expand All @@ -108,8 +97,6 @@ export class Chorus extends StereoEffect<ChorusOptions> {
});
this._delayNodeL = new Delay({ context: this.context });
this._delayNodeR = new Delay({ context: this.context });
this._passThroughL = new Gain({ context: this.context });
this._passThroughR = new Gain({ context: this.context });
this.frequency = this._lfoL.frequency;
readOnly(this, ["frequency"]);
// have one LFO frequency control the other
Expand All @@ -118,28 +105,24 @@ export class Chorus extends StereoEffect<ChorusOptions> {
// connections
this.connectEffectLeft(this._delayNodeL);
this.connectEffectRight(this._delayNodeR);
// and pass through to make the detune apparent
this.connectEffectLeft(this._passThroughL);
this.connectEffectRight(this._passThroughR);
// lfo setup
this._lfoL.connect(this._delayNodeL.delayTime);
this._lfoR.connect(this._delayNodeR.delayTime);
// start the lfo
this._lfoL.start();
this._lfoR.start();
// set the initial values
this.depth = this._depth;
this.type = options.type;
this.spread = options.spread;
}

static getDefaults(): ChorusOptions {
return Object.assign(StereoEffect.getDefaults(), {
return Object.assign(StereoFeedbackEffect.getDefaults(), {
frequency: 1.5,
delayTime: 3.5,
depth: 0.7,
type: "sine" as "sine",
spread: 180
spread: 180,
feedback: 0,
wet: 0.5,
});
}

Expand Down Expand Up @@ -195,14 +178,48 @@ export class Chorus extends StereoEffect<ChorusOptions> {
this._lfoR.phase = (spread/2) + 90;
}

/**
* Start the effect.
*/
start(time?: Time): this {
this._lfoL.start(time);
this._lfoR.start(time);
return this;
}

/**
* Stop the lfo
*/
stop(time?: Time): this {
this._lfoL.stop(time);
this._lfoR.stop(time);
return this;
}

/**
* Sync the filter to the transport. See [[LFO.sync]]
*/
sync(): this {
this._lfoL.sync();
this._lfoR.sync();
return this;
}

/**
* Unsync the filter from the transport.
*/
unsync(): this {
this._lfoL.unsync();
this._lfoR.unsync();
return this;
}

dispose(): this {
super.dispose();
this._lfoL.dispose();
this._lfoR.dispose();
this._delayNodeL.dispose();
this._delayNodeR.dispose();
this._passThroughL.dispose();
this._passThroughR.dispose();
this.frequency.dispose();
return this;
}
Expand Down
4 changes: 2 additions & 2 deletions Tone/effect/StereoFeedbackEffect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,11 +70,11 @@ export class StereoFeedbackEffect<Options extends StereoFeedbackEffectOptions> e
this._merge.connect(this._feedbackSplit);
this._feedbackMerge.connect(this._split);

// the left output connected to the right input
// the left output connected to the left input
this._feedbackSplit.connect(this._feedbackL, 0, 0);
this._feedbackL.connect(this._feedbackMerge, 0, 0);

// the left output connected to the right input
// the right output connected to the right input
this._feedbackSplit.connect(this._feedbackR, 1, 0);
this._feedbackR.connect(this._feedbackMerge, 0, 1);

Expand Down
Binary file modified test/audio/compare/chorus.wav
Binary file not shown.

0 comments on commit a28f1af

Please sign in to comment.