tone 15.5.1 → 15.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Tone/core/context/OnRunning.test.ts +63 -0
- package/Tone/core/context/OnRunning.ts +26 -0
- package/Tone/core/context/ToneWithContext.test.ts +82 -0
- package/Tone/core/context/ToneWithContext.ts +24 -0
- package/Tone/core/util/Interface.ts +8 -2
- package/Tone/effect/FrequencyShifter.ts +5 -3
- package/Tone/effect/LFOStereoEffect.test.ts +85 -0
- package/Tone/effect/LFOStereoEffect.ts +136 -0
- package/Tone/effect/Phaser.ts +9 -45
- package/Tone/effect/PitchShift.ts +8 -5
- package/Tone/effect/StereoWidener.ts +12 -2
- package/Tone/effect/Tremolo.test.ts +0 -24
- package/Tone/effect/Tremolo.ts +13 -80
- package/Tone/effect/Vibrato.ts +5 -3
- package/Tone/signal/ToneConstantSource.ts +7 -19
- package/Tone/signal/WaveShaper.ts +15 -8
- package/Tone/version.ts +1 -1
- package/build/Tone.js +1 -1
- package/build/Tone.js.map +1 -1
- package/build/esm/core/context/OnRunning.d.ts +8 -0
- package/build/esm/core/context/OnRunning.js +21 -0
- package/build/esm/core/context/OnRunning.js.map +1 -0
- package/build/esm/core/context/ToneWithContext.d.ts +14 -0
- package/build/esm/core/context/ToneWithContext.js +18 -0
- package/build/esm/core/context/ToneWithContext.js.map +1 -1
- package/build/esm/core/util/Interface.d.ts +2 -2
- package/build/esm/core/util/Interface.js.map +1 -1
- package/build/esm/effect/FrequencyShifter.js +5 -3
- package/build/esm/effect/FrequencyShifter.js.map +1 -1
- package/build/esm/effect/LFOStereoEffect.d.ts +53 -0
- package/build/esm/effect/LFOStereoEffect.js +96 -0
- package/build/esm/effect/LFOStereoEffect.js.map +1 -0
- package/build/esm/effect/Phaser.d.ts +3 -16
- package/build/esm/effect/Phaser.js +6 -28
- package/build/esm/effect/Phaser.js.map +1 -1
- package/build/esm/effect/PitchShift.js +7 -5
- package/build/esm/effect/PitchShift.js.map +1 -1
- package/build/esm/effect/StereoWidener.d.ts +4 -0
- package/build/esm/effect/StereoWidener.js +7 -2
- package/build/esm/effect/StereoWidener.js.map +1 -1
- package/build/esm/effect/Tremolo.d.ts +4 -32
- package/build/esm/effect/Tremolo.js +9 -59
- package/build/esm/effect/Tremolo.js.map +1 -1
- package/build/esm/effect/Vibrato.js +4 -3
- package/build/esm/effect/Vibrato.js.map +1 -1
- package/build/esm/signal/ToneConstantSource.d.ts +1 -1
- package/build/esm/signal/ToneConstantSource.js +16 -27
- package/build/esm/signal/ToneConstantSource.js.map +1 -1
- package/build/esm/signal/WaveShaper.d.ts +5 -0
- package/build/esm/signal/WaveShaper.js +15 -7
- package/build/esm/signal/WaveShaper.js.map +1 -1
- package/build/esm/version.js +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { expect } from "chai";
|
|
2
|
+
|
|
3
|
+
import { Context } from "./Context.js";
|
|
4
|
+
import { OfflineContext } from "./OfflineContext.js";
|
|
5
|
+
import { onContextRunning } from "./OnRunning.js";
|
|
6
|
+
|
|
7
|
+
context("onContextRunning", () => {
|
|
8
|
+
it("callback is invoked immediately when offline context is used", () => {
|
|
9
|
+
const ctx = new OfflineContext(1, 0.1, 44100);
|
|
10
|
+
let wasInvoked = false;
|
|
11
|
+
|
|
12
|
+
onContextRunning(ctx, () => {
|
|
13
|
+
wasInvoked = true;
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
expect(wasInvoked).to.be.true;
|
|
17
|
+
ctx.dispose();
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it("callback is invoked immediately when context is already running", async () => {
|
|
21
|
+
const ctx = new Context();
|
|
22
|
+
await ctx.resume();
|
|
23
|
+
let wasInvoked = false;
|
|
24
|
+
|
|
25
|
+
onContextRunning(ctx, () => {
|
|
26
|
+
wasInvoked = true;
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
expect(wasInvoked).to.be.true;
|
|
30
|
+
ctx.dispose();
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it("callback is invoked when the context starts running", async () => {
|
|
34
|
+
const ctx = new Context();
|
|
35
|
+
let wasInvoked = false;
|
|
36
|
+
|
|
37
|
+
onContextRunning(ctx, () => {
|
|
38
|
+
wasInvoked = true;
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
expect(wasInvoked).to.be.false;
|
|
42
|
+
|
|
43
|
+
await ctx.resume();
|
|
44
|
+
expect(wasInvoked).to.be.true;
|
|
45
|
+
|
|
46
|
+
ctx.dispose();
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it("can remove the callback with the returned function", async () => {
|
|
50
|
+
const ctx = new Context();
|
|
51
|
+
let wasInvoked = false;
|
|
52
|
+
const remove = onContextRunning(ctx, () => {
|
|
53
|
+
wasInvoked = true;
|
|
54
|
+
});
|
|
55
|
+
expect(wasInvoked).to.be.false;
|
|
56
|
+
remove();
|
|
57
|
+
|
|
58
|
+
await ctx.resume();
|
|
59
|
+
|
|
60
|
+
expect(wasInvoked).to.be.false;
|
|
61
|
+
ctx.dispose();
|
|
62
|
+
});
|
|
63
|
+
});
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { BaseContext } from "./BaseContext.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Invoked when the context is started. The callback is invoked immediately
|
|
5
|
+
* if the context is already started or when the context is an OfflineContext.
|
|
6
|
+
* Returns a function which can be called to remove the listener.
|
|
7
|
+
* @internal Used to trigger certain events when the context has been started.
|
|
8
|
+
*/
|
|
9
|
+
export function onContextRunning(
|
|
10
|
+
context: BaseContext,
|
|
11
|
+
callback: () => void
|
|
12
|
+
): () => void {
|
|
13
|
+
const listener = (state: AudioContextState) => {
|
|
14
|
+
if (state === "running") {
|
|
15
|
+
callback();
|
|
16
|
+
context.off("statechange", listener);
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
if (context.isOffline || context.state === "running") {
|
|
20
|
+
callback();
|
|
21
|
+
return () => {};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
context.on("statechange", listener);
|
|
25
|
+
return () => context.off("statechange", listener);
|
|
26
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { expect } from "chai";
|
|
2
|
+
|
|
3
|
+
import { Offline } from "../../../test/helper/Offline.js";
|
|
4
|
+
import { getContext } from "../Global.js";
|
|
5
|
+
import { Gain } from "./Gain.js";
|
|
6
|
+
import { ToneWithContext } from "./ToneWithContext.js";
|
|
7
|
+
|
|
8
|
+
describe("ToneWithContext", () => {
|
|
9
|
+
context("get", () => {
|
|
10
|
+
it("returns an object with all the default properties", () => {
|
|
11
|
+
const gain = new Gain();
|
|
12
|
+
const values = gain.get();
|
|
13
|
+
expect(values).to.be.an("object");
|
|
14
|
+
expect(values).to.have.property("gain");
|
|
15
|
+
gain.dispose();
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it("reflects the current value of properties", () => {
|
|
19
|
+
const gain = new Gain(0.3);
|
|
20
|
+
const values = gain.get();
|
|
21
|
+
expect(values.gain).to.be.closeTo(0.3, 0.001);
|
|
22
|
+
gain.dispose();
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
context("set", () => {
|
|
27
|
+
it("sets properties with an object", () => {
|
|
28
|
+
const gain = new Gain();
|
|
29
|
+
gain.set({ gain: 0.5 });
|
|
30
|
+
expect(gain.gain.value).to.be.closeTo(0.5, 0.001);
|
|
31
|
+
gain.dispose();
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("returns the instance for chaining", () => {
|
|
35
|
+
const gain = new Gain();
|
|
36
|
+
const returned = gain.set({ gain: 0.5 });
|
|
37
|
+
expect(returned).to.equal(gain);
|
|
38
|
+
gain.dispose();
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it("does not set a value if it is the same", () => {
|
|
42
|
+
const gain = new Gain(0.7);
|
|
43
|
+
gain.set({ gain: 0.7 });
|
|
44
|
+
expect(gain.gain.value).to.be.closeTo(0.7, 0.001);
|
|
45
|
+
gain.dispose();
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
context("getDefaults", () => {
|
|
50
|
+
it("returns defaults with a context", () => {
|
|
51
|
+
const defaults = ToneWithContext.getDefaults();
|
|
52
|
+
expect(defaults).to.have.property("context");
|
|
53
|
+
expect(defaults.context).to.equal(getContext());
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
context("_onContextRunning", () => {
|
|
58
|
+
it("invokes the callback immediately for an offline context", () => {
|
|
59
|
+
return Offline(() => {
|
|
60
|
+
let callbackInvoked = false;
|
|
61
|
+
const gain = new Gain();
|
|
62
|
+
// Access protected method via subclass trick
|
|
63
|
+
(gain as any)._onContextRunning(() => {
|
|
64
|
+
callbackInvoked = true;
|
|
65
|
+
});
|
|
66
|
+
expect(callbackInvoked).to.equal(true);
|
|
67
|
+
gain.dispose();
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it("cleans up the listener on dispose", () => {
|
|
72
|
+
return Offline(() => {
|
|
73
|
+
const gain = new Gain();
|
|
74
|
+
(gain as any)._onContextRunning(() => {
|
|
75
|
+
// no-op
|
|
76
|
+
});
|
|
77
|
+
// Should not throw when disposing
|
|
78
|
+
gain.dispose();
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
});
|
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
isUndef,
|
|
20
20
|
} from "../util/TypeCheck.js";
|
|
21
21
|
import { BaseContext } from "./BaseContext.js";
|
|
22
|
+
import { onContextRunning } from "./OnRunning.js";
|
|
22
23
|
|
|
23
24
|
/**
|
|
24
25
|
* A unit which process audio
|
|
@@ -232,4 +233,27 @@ export abstract class ToneWithContext<
|
|
|
232
233
|
});
|
|
233
234
|
return this;
|
|
234
235
|
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Internal method which removes the onContextRunning callback.
|
|
239
|
+
*/
|
|
240
|
+
private _removeOnContextRunning?: () => void;
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Internal method which is called the first time the context is resumed.
|
|
244
|
+
* Useful for setting up AudioNodes which should be running as soon
|
|
245
|
+
* as the context is running, but should not be started until then.
|
|
246
|
+
*/
|
|
247
|
+
protected _onContextRunning(callback: () => void): void {
|
|
248
|
+
this._removeOnContextRunning = onContextRunning(this.context, callback);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Dispose and disconnect
|
|
253
|
+
*/
|
|
254
|
+
dispose(): this {
|
|
255
|
+
super.dispose();
|
|
256
|
+
this._removeOnContextRunning?.();
|
|
257
|
+
return this;
|
|
258
|
+
}
|
|
235
259
|
}
|
|
@@ -6,7 +6,10 @@ export type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
|
|
|
6
6
|
/**
|
|
7
7
|
* Make the property not writable using `defineProperty`. Internal use only.
|
|
8
8
|
*/
|
|
9
|
-
export function readOnly
|
|
9
|
+
export function readOnly<T extends object>(
|
|
10
|
+
target: T,
|
|
11
|
+
property: keyof T | (keyof T)[]
|
|
12
|
+
): void {
|
|
10
13
|
if (isArray(property)) {
|
|
11
14
|
property.forEach((str) => readOnly(target, str));
|
|
12
15
|
} else {
|
|
@@ -20,7 +23,10 @@ export function readOnly(target: object, property: string | string[]): void {
|
|
|
20
23
|
/**
|
|
21
24
|
* Make an attribute writeable. Internal use only.
|
|
22
25
|
*/
|
|
23
|
-
export function writable
|
|
26
|
+
export function writable<T extends object>(
|
|
27
|
+
target: T,
|
|
28
|
+
property: keyof T | (keyof T)[]
|
|
29
|
+
): void {
|
|
24
30
|
if (isArray(property)) {
|
|
25
31
|
property.forEach((str) => writable(target, str));
|
|
26
32
|
} else {
|
|
@@ -131,9 +131,11 @@ export class FrequencyShifter extends Effect<FrequencyShifterOptions> {
|
|
|
131
131
|
this._add.connect(this.effectReturn);
|
|
132
132
|
|
|
133
133
|
// start the oscillators at the same time
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
134
|
+
this._onContextRunning(() => {
|
|
135
|
+
const now = this.immediate();
|
|
136
|
+
this._sine.start(now);
|
|
137
|
+
this._cosine.start(now);
|
|
138
|
+
});
|
|
137
139
|
}
|
|
138
140
|
|
|
139
141
|
static getDefaults(): FrequencyShifterOptions {
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { expect } from "chai";
|
|
2
|
+
|
|
3
|
+
import { BasicTests } from "../../test/helper/Basic.js";
|
|
4
|
+
import { Offline } from "../../test/helper/Offline.js";
|
|
5
|
+
import { LFOStereoEffect, LFOStereoEffectOptions } from "./LFOStereoEffect.js";
|
|
6
|
+
|
|
7
|
+
describe("LFOStereoEffect", () => {
|
|
8
|
+
class LFOStereoEffectTest extends LFOStereoEffect<LFOStereoEffectOptions> {
|
|
9
|
+
getLfoState() {
|
|
10
|
+
return this._lfoL.state;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
BasicTests(LFOStereoEffectTest);
|
|
15
|
+
|
|
16
|
+
context("API", () => {
|
|
17
|
+
it("can pass in options in the constructor", () => {
|
|
18
|
+
const stereoEffect = new LFOStereoEffectTest({
|
|
19
|
+
frequency: 0.2,
|
|
20
|
+
});
|
|
21
|
+
expect(stereoEffect.frequency.value).to.be.closeTo(0.2, 0.01);
|
|
22
|
+
stereoEffect.dispose();
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it("can be started and stopped", () => {
|
|
26
|
+
const stereoEffect = new LFOStereoEffectTest();
|
|
27
|
+
stereoEffect.start().stop("+0.2");
|
|
28
|
+
stereoEffect.dispose();
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("can get/set the options", () => {
|
|
32
|
+
const stereoEffect = new LFOStereoEffectTest();
|
|
33
|
+
stereoEffect.set({
|
|
34
|
+
frequency: 2.4,
|
|
35
|
+
});
|
|
36
|
+
expect(stereoEffect.get().frequency).to.be.closeTo(2.4, 0.01);
|
|
37
|
+
stereoEffect.dispose();
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("can set the frequency and depth", () => {
|
|
41
|
+
const stereoEffect = new LFOStereoEffectTest();
|
|
42
|
+
stereoEffect.frequency.value = 0.4;
|
|
43
|
+
expect(stereoEffect.frequency.value).to.be.closeTo(0.4, 0.01);
|
|
44
|
+
stereoEffect.dispose();
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it("can sync the frequency to the transport", async () => {
|
|
48
|
+
const buffer = await Offline(({ transport }) => {
|
|
49
|
+
const stereoEffect = new LFOStereoEffectTest({
|
|
50
|
+
frequency: 2,
|
|
51
|
+
});
|
|
52
|
+
stereoEffect.sync();
|
|
53
|
+
stereoEffect.frequency.toDestination();
|
|
54
|
+
transport.bpm.setValueAtTime(transport.bpm.value * 2, 0.05);
|
|
55
|
+
}, 0.1);
|
|
56
|
+
expect(buffer.getValueAtTime(0)).to.be.closeTo(2, 0.1);
|
|
57
|
+
expect(buffer.getValueAtTime(0.05)).to.be.closeTo(4, 0.1);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it("can unsync the frequency to the transport", async () => {
|
|
61
|
+
const buffer = await Offline(({ transport }) => {
|
|
62
|
+
const stereoEffect = new LFOStereoEffectTest({
|
|
63
|
+
frequency: 2,
|
|
64
|
+
});
|
|
65
|
+
stereoEffect.sync();
|
|
66
|
+
stereoEffect.frequency.toDestination();
|
|
67
|
+
transport.bpm.setValueAtTime(transport.bpm.value * 2, 0.05);
|
|
68
|
+
stereoEffect.unsync();
|
|
69
|
+
}, 0.1);
|
|
70
|
+
expect(buffer.getValueAtTime(0)).to.be.closeTo(2, 0.1);
|
|
71
|
+
expect(buffer.getValueAtTime(0.05)).to.be.closeTo(2, 0.1);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it("autostart the LFO", () => {
|
|
75
|
+
const stereoEffect = new LFOStereoEffectTest({
|
|
76
|
+
frequency: 0.2,
|
|
77
|
+
autostart: true,
|
|
78
|
+
});
|
|
79
|
+
expect(stereoEffect.frequency.value).to.be.closeTo(0.2, 0.01);
|
|
80
|
+
|
|
81
|
+
expect(stereoEffect.getLfoState()).to.equal("started");
|
|
82
|
+
stereoEffect.dispose();
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
});
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { Frequency, Time } from "../core/type/Units.js";
|
|
2
|
+
import { optionsFromArguments } from "../core/util/Defaults.js";
|
|
3
|
+
import { readOnly } from "../core/util/Interface.js";
|
|
4
|
+
import { Signal } from "../signal/Signal.js";
|
|
5
|
+
import { LFO } from "../source/oscillator/LFO.js";
|
|
6
|
+
import { ToneOscillatorType } from "../source/oscillator/OscillatorInterface.js";
|
|
7
|
+
import { StereoEffect, StereoEffectOptions } from "./StereoEffect.js";
|
|
8
|
+
|
|
9
|
+
export interface LFOStereoEffectOptions extends StereoEffectOptions {
|
|
10
|
+
frequency: Frequency;
|
|
11
|
+
autostart: boolean;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Base class for stereo effects which modulate the incoming signal with an LFO.
|
|
16
|
+
*
|
|
17
|
+
* @category Effect
|
|
18
|
+
*/
|
|
19
|
+
export abstract class LFOStereoEffect<
|
|
20
|
+
Options extends LFOStereoEffectOptions,
|
|
21
|
+
> extends StereoEffect<Options> {
|
|
22
|
+
readonly name: string = "LFOStereoEffect";
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* The LFO in the left channel
|
|
26
|
+
*/
|
|
27
|
+
protected _lfoL: LFO;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* The LFO in the right channel
|
|
31
|
+
*/
|
|
32
|
+
protected _lfoR: LFO;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* The frequency of the tremolo.
|
|
36
|
+
*/
|
|
37
|
+
readonly frequency: Signal<"frequency">;
|
|
38
|
+
|
|
39
|
+
constructor(options?: Partial<LFOStereoEffectOptions>);
|
|
40
|
+
constructor() {
|
|
41
|
+
const options = optionsFromArguments(
|
|
42
|
+
LFOStereoEffect.getDefaults(),
|
|
43
|
+
arguments
|
|
44
|
+
);
|
|
45
|
+
super(options);
|
|
46
|
+
|
|
47
|
+
this._lfoL = new LFO({
|
|
48
|
+
context: this.context,
|
|
49
|
+
min: 0,
|
|
50
|
+
max: 1,
|
|
51
|
+
});
|
|
52
|
+
this._lfoR = new LFO({
|
|
53
|
+
context: this.context,
|
|
54
|
+
min: 0,
|
|
55
|
+
max: 1,
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
// control the frequency with a single signal
|
|
59
|
+
this.frequency = new Signal({
|
|
60
|
+
context: this.context,
|
|
61
|
+
value: options.frequency,
|
|
62
|
+
units: "frequency",
|
|
63
|
+
});
|
|
64
|
+
this.frequency.fan(this._lfoL.frequency, this._lfoR.frequency);
|
|
65
|
+
|
|
66
|
+
readOnly(this, ["frequency"]);
|
|
67
|
+
|
|
68
|
+
if (options.autostart) {
|
|
69
|
+
this._onContextRunning(() => this.start(this.immediate()));
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
static getDefaults(): LFOStereoEffectOptions {
|
|
74
|
+
return Object.assign(StereoEffect.getDefaults(), {
|
|
75
|
+
frequency: 10,
|
|
76
|
+
autostart: false,
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Start the tremolo.
|
|
82
|
+
*/
|
|
83
|
+
start(time?: Time): this {
|
|
84
|
+
this._lfoL.start(time);
|
|
85
|
+
this._lfoR.start(time);
|
|
86
|
+
return this;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Stop the tremolo.
|
|
91
|
+
*/
|
|
92
|
+
stop(time?: Time): this {
|
|
93
|
+
this._lfoL.stop(time);
|
|
94
|
+
this._lfoR.stop(time);
|
|
95
|
+
return this;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Sync the effect to the transport.
|
|
100
|
+
*/
|
|
101
|
+
sync(): this {
|
|
102
|
+
this._lfoL.sync();
|
|
103
|
+
this._lfoR.sync();
|
|
104
|
+
this.context.transport.syncSignal(this.frequency);
|
|
105
|
+
return this;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Unsync the filter from the transport
|
|
110
|
+
*/
|
|
111
|
+
unsync(): this {
|
|
112
|
+
this._lfoL.unsync();
|
|
113
|
+
this._lfoR.unsync();
|
|
114
|
+
this.context.transport.unsyncSignal(this.frequency);
|
|
115
|
+
return this;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* The oscillator type.
|
|
120
|
+
*/
|
|
121
|
+
get type(): ToneOscillatorType {
|
|
122
|
+
return this._lfoL.type;
|
|
123
|
+
}
|
|
124
|
+
set type(type) {
|
|
125
|
+
this._lfoL.type = type;
|
|
126
|
+
this._lfoR.type = type;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
dispose(): this {
|
|
130
|
+
super.dispose();
|
|
131
|
+
this._lfoL.dispose();
|
|
132
|
+
this._lfoR.dispose();
|
|
133
|
+
this.frequency.dispose();
|
|
134
|
+
return this;
|
|
135
|
+
}
|
|
136
|
+
}
|
package/Tone/effect/Phaser.ts
CHANGED
|
@@ -3,10 +3,9 @@ import { optionsFromArguments } from "../core/util/Defaults.js";
|
|
|
3
3
|
import { readOnly } from "../core/util/Interface.js";
|
|
4
4
|
import { Signal } from "../signal/Signal.js";
|
|
5
5
|
import { LFO } from "../source/oscillator/LFO.js";
|
|
6
|
-
import {
|
|
6
|
+
import { LFOStereoEffect, LFOStereoEffectOptions } from "./LFOStereoEffect.js";
|
|
7
7
|
|
|
8
|
-
export interface PhaserOptions extends
|
|
9
|
-
frequency: Frequency;
|
|
8
|
+
export interface PhaserOptions extends LFOStereoEffectOptions {
|
|
10
9
|
octaves: Positive;
|
|
11
10
|
stages: Positive;
|
|
12
11
|
Q: Positive;
|
|
@@ -28,19 +27,9 @@ export interface PhaserOptions extends StereoEffectOptions {
|
|
|
28
27
|
* synth.triggerAttackRelease("E3", "2n");
|
|
29
28
|
* @category Effect
|
|
30
29
|
*/
|
|
31
|
-
export class Phaser extends
|
|
30
|
+
export class Phaser extends LFOStereoEffect<PhaserOptions> {
|
|
32
31
|
readonly name: string = "Phaser";
|
|
33
32
|
|
|
34
|
-
/**
|
|
35
|
-
* the lfo which controls the frequency on the left side
|
|
36
|
-
*/
|
|
37
|
-
private _lfoL: LFO;
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* the lfo which controls the frequency on the right side
|
|
41
|
-
*/
|
|
42
|
-
private _lfoR: LFO;
|
|
43
|
-
|
|
44
33
|
/**
|
|
45
34
|
* the base modulation frequency
|
|
46
35
|
*/
|
|
@@ -66,11 +55,6 @@ export class Phaser extends StereoEffect<PhaserOptions> {
|
|
|
66
55
|
*/
|
|
67
56
|
private _filtersR: BiquadFilterNode[];
|
|
68
57
|
|
|
69
|
-
/**
|
|
70
|
-
* the frequency of the effect
|
|
71
|
-
*/
|
|
72
|
-
readonly frequency: Signal<"frequency">;
|
|
73
|
-
|
|
74
58
|
/**
|
|
75
59
|
* @param frequency The speed of the phasing.
|
|
76
60
|
* @param octaves The octaves of the effect.
|
|
@@ -90,19 +74,8 @@ export class Phaser extends StereoEffect<PhaserOptions> {
|
|
|
90
74
|
]);
|
|
91
75
|
super(options);
|
|
92
76
|
|
|
93
|
-
this.
|
|
94
|
-
|
|
95
|
-
frequency: options.frequency,
|
|
96
|
-
min: 0,
|
|
97
|
-
max: 1,
|
|
98
|
-
});
|
|
99
|
-
this._lfoR = new LFO({
|
|
100
|
-
context: this.context,
|
|
101
|
-
frequency: options.frequency,
|
|
102
|
-
min: 0,
|
|
103
|
-
max: 1,
|
|
104
|
-
phase: 180,
|
|
105
|
-
});
|
|
77
|
+
this._lfoR.phase = 180;
|
|
78
|
+
|
|
106
79
|
this._baseFrequency = this.toFrequency(options.baseFrequency);
|
|
107
80
|
this._octaves = options.octaves;
|
|
108
81
|
this.Q = new Signal({
|
|
@@ -113,30 +86,24 @@ export class Phaser extends StereoEffect<PhaserOptions> {
|
|
|
113
86
|
this._filtersL = this._makeFilters(options.stages, this._lfoL);
|
|
114
87
|
this._filtersR = this._makeFilters(options.stages, this._lfoR);
|
|
115
88
|
|
|
116
|
-
this.frequency = this._lfoL.frequency;
|
|
117
|
-
this.frequency.value = options.frequency;
|
|
118
|
-
|
|
119
89
|
// connect them up
|
|
120
90
|
this.connectEffectLeft(...this._filtersL);
|
|
121
91
|
this.connectEffectRight(...this._filtersR);
|
|
122
|
-
|
|
123
|
-
this._lfoL.frequency.connect(this._lfoR.frequency);
|
|
92
|
+
|
|
124
93
|
// set the options
|
|
125
94
|
this.baseFrequency = options.baseFrequency;
|
|
126
95
|
this.octaves = options.octaves;
|
|
127
|
-
|
|
128
|
-
this._lfoL.start();
|
|
129
|
-
this._lfoR.start();
|
|
130
|
-
readOnly(this, ["frequency", "Q"]);
|
|
96
|
+
readOnly(this, ["Q"]);
|
|
131
97
|
}
|
|
132
98
|
|
|
133
99
|
static getDefaults(): PhaserOptions {
|
|
134
|
-
return Object.assign(
|
|
100
|
+
return Object.assign(LFOStereoEffect.getDefaults(), {
|
|
135
101
|
frequency: 0.5,
|
|
136
102
|
octaves: 3,
|
|
137
103
|
stages: 10,
|
|
138
104
|
Q: 10,
|
|
139
105
|
baseFrequency: 350,
|
|
106
|
+
autostart: true,
|
|
140
107
|
});
|
|
141
108
|
}
|
|
142
109
|
|
|
@@ -185,11 +152,8 @@ export class Phaser extends StereoEffect<PhaserOptions> {
|
|
|
185
152
|
dispose(): this {
|
|
186
153
|
super.dispose();
|
|
187
154
|
this.Q.dispose();
|
|
188
|
-
this._lfoL.dispose();
|
|
189
|
-
this._lfoR.dispose();
|
|
190
155
|
this._filtersL.forEach((f) => f.disconnect());
|
|
191
156
|
this._filtersR.forEach((f) => f.disconnect());
|
|
192
|
-
this.frequency.dispose();
|
|
193
157
|
return this;
|
|
194
158
|
}
|
|
195
159
|
}
|
|
@@ -147,13 +147,16 @@ export class PitchShift extends FeedbackEffect<PitchShiftOptions> {
|
|
|
147
147
|
// route the input
|
|
148
148
|
this.effectSend.fan(this._delayA, this._delayB);
|
|
149
149
|
this._crossFade.chain(this._feedbackDelay, this.effectReturn);
|
|
150
|
-
// start the LFOs at the same time
|
|
151
|
-
const now = this.now();
|
|
152
|
-
this._lfoA.start(now);
|
|
153
|
-
this._lfoB.start(now);
|
|
154
|
-
this._crossFadeLFO.start(now);
|
|
155
150
|
// set the initial value
|
|
156
151
|
this.windowSize = this._windowSize;
|
|
152
|
+
|
|
153
|
+
// start the LFOs at the same time
|
|
154
|
+
this._onContextRunning(() => {
|
|
155
|
+
const now = this.immediate();
|
|
156
|
+
this._lfoA.start(now);
|
|
157
|
+
this._lfoB.start(now);
|
|
158
|
+
this._crossFadeLFO.start(now);
|
|
159
|
+
});
|
|
157
160
|
}
|
|
158
161
|
|
|
159
162
|
static getDefaults(): PitchShiftOptions {
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { connect } from "../core/context/ToneAudioNode.js";
|
|
2
1
|
import { NormalRange } from "../core/type/Units.js";
|
|
3
2
|
import { optionsFromArguments } from "../core/util/Defaults.js";
|
|
4
3
|
import { readOnly } from "../core/util/Interface.js";
|
|
@@ -9,6 +8,7 @@ import {
|
|
|
9
8
|
import { Multiply } from "../signal/Multiply.js";
|
|
10
9
|
import { Signal } from "../signal/Signal.js";
|
|
11
10
|
import { Subtract } from "../signal/Subtract.js";
|
|
11
|
+
import { ToneConstantSource } from "../signal/ToneConstantSource.js";
|
|
12
12
|
|
|
13
13
|
export interface StereoWidenerOptions extends MidSideEffectOptions {
|
|
14
14
|
width: NormalRange;
|
|
@@ -57,6 +57,11 @@ export class StereoWidener extends MidSideEffect<StereoWidenerOptions> {
|
|
|
57
57
|
*/
|
|
58
58
|
private _sideMult: Multiply;
|
|
59
59
|
|
|
60
|
+
/**
|
|
61
|
+
* A constant source to get the value of 1 for the subtract node
|
|
62
|
+
*/
|
|
63
|
+
private _constant: ToneConstantSource;
|
|
64
|
+
|
|
60
65
|
/**
|
|
61
66
|
* @param width The stereo width. A width of 0 is mono and 1 is stereo. 0.5 is no change.
|
|
62
67
|
*/
|
|
@@ -90,7 +95,11 @@ export class StereoWidener extends MidSideEffect<StereoWidenerOptions> {
|
|
|
90
95
|
|
|
91
96
|
this._oneMinusWidth = new Subtract({ context: this.context });
|
|
92
97
|
this._oneMinusWidth.connect(this._twoTimesWidthMid);
|
|
93
|
-
|
|
98
|
+
this._constant = new ToneConstantSource({
|
|
99
|
+
context: this.context,
|
|
100
|
+
offset: 1,
|
|
101
|
+
}).start();
|
|
102
|
+
this._constant.connect(this._oneMinusWidth);
|
|
94
103
|
this.width.connect(this._oneMinusWidth.subtrahend);
|
|
95
104
|
|
|
96
105
|
this._sideMult = new Multiply({ context: this.context });
|
|
@@ -113,6 +122,7 @@ export class StereoWidener extends MidSideEffect<StereoWidenerOptions> {
|
|
|
113
122
|
this._twoTimesWidthMid.dispose();
|
|
114
123
|
this._twoTimesWidthSide.dispose();
|
|
115
124
|
this._oneMinusWidth.dispose();
|
|
125
|
+
this._constant.dispose();
|
|
116
126
|
return this;
|
|
117
127
|
}
|
|
118
128
|
}
|
|
@@ -3,7 +3,6 @@ import { expect } from "chai";
|
|
|
3
3
|
import { BasicTests } from "../../test/helper/Basic.js";
|
|
4
4
|
import { CompareToFile } from "../../test/helper/CompareToFile.js";
|
|
5
5
|
import { EffectTests } from "../../test/helper/EffectTests.js";
|
|
6
|
-
import { Offline } from "../../test/helper/Offline.js";
|
|
7
6
|
import { Oscillator } from "../source/index.js";
|
|
8
7
|
import { Tremolo } from "./Tremolo.js";
|
|
9
8
|
|
|
@@ -60,28 +59,5 @@ describe("Tremolo", () => {
|
|
|
60
59
|
expect(tremolo.frequency.value).to.be.closeTo(0.4, 0.01);
|
|
61
60
|
tremolo.dispose();
|
|
62
61
|
});
|
|
63
|
-
|
|
64
|
-
it("can sync the frequency to the transport", async () => {
|
|
65
|
-
const buffer = await Offline(({ transport }) => {
|
|
66
|
-
const tremolo = new Tremolo(2);
|
|
67
|
-
tremolo.sync();
|
|
68
|
-
tremolo.frequency.toDestination();
|
|
69
|
-
transport.bpm.setValueAtTime(transport.bpm.value * 2, 0.05);
|
|
70
|
-
}, 0.1);
|
|
71
|
-
expect(buffer.getValueAtTime(0)).to.be.closeTo(2, 0.1);
|
|
72
|
-
expect(buffer.getValueAtTime(0.05)).to.be.closeTo(4, 0.1);
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
it("can unsync the frequency to the transport", async () => {
|
|
76
|
-
const buffer = await Offline(({ transport }) => {
|
|
77
|
-
const tremolo = new Tremolo(2);
|
|
78
|
-
tremolo.sync();
|
|
79
|
-
tremolo.frequency.toDestination();
|
|
80
|
-
transport.bpm.setValueAtTime(transport.bpm.value * 2, 0.05);
|
|
81
|
-
tremolo.unsync();
|
|
82
|
-
}, 0.1);
|
|
83
|
-
expect(buffer.getValueAtTime(0)).to.be.closeTo(2, 0.1);
|
|
84
|
-
expect(buffer.getValueAtTime(0.05)).to.be.closeTo(2, 0.1);
|
|
85
|
-
});
|
|
86
62
|
});
|
|
87
63
|
});
|