Skip to content

Commit a28f1af

Browse files
committed
feat: Chorus extends StereoFeedbackEffect
This makes it possible to do flanger-type effects. fixes #575
1 parent 94ab939 commit a28f1af

File tree

4 files changed

+84
-33
lines changed

4 files changed

+84
-33
lines changed

Tone/effect/Chorus.test.ts

+36-2
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { EffectTests } from "test/helper/EffectTests";
44
import { expect } from "chai";
55
import { CompareToFile } from "test/helper/CompareToFile";
66
import { Oscillator } from "Tone/source";
7+
import { Offline } from "test/helper/Offline";
78

89
describe("Chorus", () => {
910

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

1314
it("matches a file", () => {
1415
return CompareToFile(() => {
15-
const chorus = new Chorus().toDestination();
16+
const chorus = new Chorus().toDestination().start();
1617
const osc = new Oscillator(220, "sawtooth").connect(chorus).start();
17-
}, "chorus.wav", 0.15);
18+
}, "chorus.wav", 0.1);
1819
});
1920

2021
context("API", () => {
@@ -48,6 +49,39 @@ describe("Chorus", () => {
4849
expect(chorus.delayTime).to.equal(3);
4950
chorus.dispose();
5051
});
52+
53+
it("can be started and stopped", () => {
54+
const chorus = new Chorus();
55+
chorus.start().stop("+0.2");
56+
chorus.dispose();
57+
});
58+
59+
it("can sync the frequency to the transport", () => {
60+
return Offline(({ transport }) => {
61+
const chorus = new Chorus(2);
62+
chorus.sync();
63+
chorus.frequency.toDestination();
64+
transport.bpm.setValueAtTime(transport.bpm.value * 2, 0.05);
65+
// transport.start(0)
66+
}, 0.1).then((buffer) => {
67+
expect(buffer.getValueAtTime(0)).to.be.closeTo(2, 0.1);
68+
expect(buffer.getValueAtTime(0.05)).to.be.closeTo(4, 0.1);
69+
});
70+
});
71+
72+
it("can unsync the frequency to the transport", () => {
73+
return Offline(({ transport }) => {
74+
const chorus = new Chorus(2);
75+
chorus.sync();
76+
chorus.frequency.toDestination();
77+
transport.bpm.setValueAtTime(transport.bpm.value * 2, 0.05);
78+
chorus.unsync();
79+
// transport.start(0)
80+
}, 0.1).then((buffer) => {
81+
expect(buffer.getValueAtTime(0)).to.be.closeTo(2, 0.1);
82+
expect(buffer.getValueAtTime(0.05)).to.be.closeTo(2, 0.1);
83+
});
84+
});
5185
});
5286
});
5387

Tone/effect/Chorus.ts

+46-29
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
1-
import { StereoEffect, StereoEffectOptions } from "../effect/StereoEffect";
2-
import { Degrees, Frequency, Milliseconds, NormalRange, Seconds } from "../core/type/Units";
1+
import { StereoFeedbackEffect, StereoFeedbackEffectOptions } from "../effect/StereoFeedbackEffect";
2+
import { Degrees, Frequency, Milliseconds, NormalRange, Seconds, Time } from "../core/type/Units";
33
import { ToneOscillatorType } from "../source/oscillator/OscillatorInterface";
44
import { optionsFromArguments } from "../core/util/Defaults";
55
import { LFO } from "../source/oscillator/LFO";
66
import { Delay } from "../core/context/Delay";
77
import { Signal } from "../signal/Signal";
88
import { readOnly } from "../core/util/Interface";
9-
import { Gain } from "../core/context/Gain";
109

11-
export interface ChorusOptions extends StereoEffectOptions {
10+
export interface ChorusOptions extends StereoFeedbackEffectOptions {
1211
frequency: Frequency;
1312
delayTime: Milliseconds;
1413
depth: NormalRange;
@@ -30,7 +29,7 @@ export interface ChorusOptions extends StereoEffectOptions {
3029
*
3130
* @category Effect
3231
*/
33-
export class Chorus extends StereoEffect<ChorusOptions> {
32+
export class Chorus extends StereoFeedbackEffect<ChorusOptions> {
3433

3534
readonly name: string = "Chorus";
3635

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

72-
/**
73-
* Pass the left signal through
74-
*/
75-
private _passThroughL: Gain;
76-
77-
/**
78-
* Pass the right signal through
79-
*/
80-
private _passThroughR: Gain;
81-
8271
/**
8372
* @param frequency The frequency of the LFO.
8473
* @param delayTime The delay of the chorus effect in ms.
@@ -88,8 +77,8 @@ export class Chorus extends StereoEffect<ChorusOptions> {
8877
constructor(options?: Partial<ChorusOptions>);
8978
constructor() {
9079

91-
super(optionsFromArguments(Chorus.getDefaults(), arguments, ["order"]));
92-
const options = optionsFromArguments(Chorus.getDefaults(), arguments, ["order"]);
80+
super(optionsFromArguments(Chorus.getDefaults(), arguments, ["frequency", "delayTime", "depth"]));
81+
const options = optionsFromArguments(Chorus.getDefaults(), arguments, ["frequency", "delayTime", "depth"]);
9382

9483
this._depth = options.depth;
9584
this._delayTime = options.delayTime / 1000;
@@ -108,8 +97,6 @@ export class Chorus extends StereoEffect<ChorusOptions> {
10897
});
10998
this._delayNodeL = new Delay({ context: this.context });
11099
this._delayNodeR = new Delay({ context: this.context });
111-
this._passThroughL = new Gain({ context: this.context });
112-
this._passThroughR = new Gain({ context: this.context });
113100
this.frequency = this._lfoL.frequency;
114101
readOnly(this, ["frequency"]);
115102
// have one LFO frequency control the other
@@ -118,28 +105,24 @@ export class Chorus extends StereoEffect<ChorusOptions> {
118105
// connections
119106
this.connectEffectLeft(this._delayNodeL);
120107
this.connectEffectRight(this._delayNodeR);
121-
// and pass through to make the detune apparent
122-
this.connectEffectLeft(this._passThroughL);
123-
this.connectEffectRight(this._passThroughR);
124108
// lfo setup
125109
this._lfoL.connect(this._delayNodeL.delayTime);
126110
this._lfoR.connect(this._delayNodeR.delayTime);
127-
// start the lfo
128-
this._lfoL.start();
129-
this._lfoR.start();
130111
// set the initial values
131112
this.depth = this._depth;
132113
this.type = options.type;
133114
this.spread = options.spread;
134115
}
135116

136117
static getDefaults(): ChorusOptions {
137-
return Object.assign(StereoEffect.getDefaults(), {
118+
return Object.assign(StereoFeedbackEffect.getDefaults(), {
138119
frequency: 1.5,
139120
delayTime: 3.5,
140121
depth: 0.7,
141122
type: "sine" as "sine",
142-
spread: 180
123+
spread: 180,
124+
feedback: 0,
125+
wet: 0.5,
143126
});
144127
}
145128

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

181+
/**
182+
* Start the effect.
183+
*/
184+
start(time?: Time): this {
185+
this._lfoL.start(time);
186+
this._lfoR.start(time);
187+
return this;
188+
}
189+
190+
/**
191+
* Stop the lfo
192+
*/
193+
stop(time?: Time): this {
194+
this._lfoL.stop(time);
195+
this._lfoR.stop(time);
196+
return this;
197+
}
198+
199+
/**
200+
* Sync the filter to the transport. See [[LFO.sync]]
201+
*/
202+
sync(): this {
203+
this._lfoL.sync();
204+
this._lfoR.sync();
205+
return this;
206+
}
207+
208+
/**
209+
* Unsync the filter from the transport.
210+
*/
211+
unsync(): this {
212+
this._lfoL.unsync();
213+
this._lfoR.unsync();
214+
return this;
215+
}
216+
198217
dispose(): this {
199218
super.dispose();
200219
this._lfoL.dispose();
201220
this._lfoR.dispose();
202221
this._delayNodeL.dispose();
203222
this._delayNodeR.dispose();
204-
this._passThroughL.dispose();
205-
this._passThroughR.dispose();
206223
this.frequency.dispose();
207224
return this;
208225
}

Tone/effect/StereoFeedbackEffect.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -70,11 +70,11 @@ export class StereoFeedbackEffect<Options extends StereoFeedbackEffectOptions> e
7070
this._merge.connect(this._feedbackSplit);
7171
this._feedbackMerge.connect(this._split);
7272

73-
// the left output connected to the right input
73+
// the left output connected to the left input
7474
this._feedbackSplit.connect(this._feedbackL, 0, 0);
7575
this._feedbackL.connect(this._feedbackMerge, 0, 0);
7676

77-
// the left output connected to the right input
77+
// the right output connected to the right input
7878
this._feedbackSplit.connect(this._feedbackR, 1, 0);
7979
this._feedbackR.connect(this._feedbackMerge, 0, 1);
8080

test/audio/compare/chorus.wav

0 Bytes
Binary file not shown.

0 commit comments

Comments
 (0)