From f5321a3438af892d4bd51c6a2256a683ccb137d1 Mon Sep 17 00:00:00 2001 From: Tomohiro IKEDA Date: Sun, 29 Sep 2024 17:02:28 +0900 Subject: [PATCH] feat: Add `GainNode` that controls Dry and Wet parameters to Phaser --- src/SoundModule/Effectors/Phaser.ts | 62 ++++++++++++++++++----- test/SoundModule/Effectors/Phaser.test.ts | 49 +++++++++++++----- 2 files changed, 84 insertions(+), 27 deletions(-) diff --git a/src/SoundModule/Effectors/Phaser.ts b/src/SoundModule/Effectors/Phaser.ts index 1706dfea..25ba1061 100644 --- a/src/SoundModule/Effectors/Phaser.ts +++ b/src/SoundModule/Effectors/Phaser.ts @@ -9,7 +9,9 @@ export type PhaserParams = { resonance?: number, depth?: number, rate?: number, - mix?: number + mix?: number, + dry?: number, + wet?: number }; /** @@ -20,7 +22,8 @@ export class Phaser extends Effector { private numberOfStages: PhaserNumberOfStages = 12; // The default number of All-pass Filters private filters: BiquadFilterNode[] = []; - private mix: GainNode; + private dry: GainNode; + private wet: GainNode; private depthRate = 0; /** @@ -40,12 +43,14 @@ export class Phaser extends Effector { this.filters.push(filter); } - this.mix = context.createGain(); + this.dry = context.createGain(); + this.wet = context.createGain(); // Initialize parameters this.depth.gain.value = 0; this.rate.value = 0; - this.mix.gain.value = 0; + this.dry.gain.value = 1; + this.wet.gain.value = 0; // `Phaser` is not connected by default this.deactivate(); @@ -82,22 +87,24 @@ export class Phaser extends Effector { this.filters[i].disconnect(0); } - this.mix.disconnect(0); + this.dry.disconnect(0); + this.wet.disconnect(0); - // GainNode (Input) -> GainNode (Output) - this.input.connect(this.output); + // GainNode (Input) -> GainNode (Dry) -> GainNode (Output) + this.input.connect(this.dry); + this.dry.connect(this.output); // Effect ON if (this.isActive && (this.numberOfStages > 0)) { - // GainNode (Input) -> BiquadFilterNode (All-pass Filter x N) -> GainNode (Mix) -> GainNode (Output) + // GainNode (Input) -> BiquadFilterNode (All-pass Filter x N) -> GainNode (Wet) -> GainNode (Output) this.input.connect(this.filters[0]); for (let i = 0; i < this.numberOfStages; i++) { if (i < (this.numberOfStages - 1)) { this.filters[i].connect(this.filters[i + 1]); } else { - this.filters[i].connect(this.mix); - this.mix.connect(this.output); + this.filters[i].connect(this.wet); + this.wet.connect(this.output); } } @@ -122,6 +129,8 @@ export class Phaser extends Effector { public param(params: 'depth'): number; public param(params: 'rate'): number; public param(params: 'mix'): number; + public param(params: 'dry'): number; + public param(params: 'wet'): number; public param(params: PhaserParams): Phaser; public param(params: keyof PhaserParams | PhaserParams): PhaserParams[keyof PhaserParams] | Phaser { if (typeof params === 'string') { @@ -151,7 +160,15 @@ export class Phaser extends Effector { } case 'mix': { - return this.mix.gain.value; + return this.wet.gain.value; + } + + case 'dry': { + return this.dry.gain.value; + } + + case 'wet': { + return this.wet.gain.value; } } } @@ -226,7 +243,24 @@ export class Phaser extends Effector { case 'mix': { if (typeof value === 'number') { - this.mix.gain.value = value; + this.wet.gain.value = value; + this.dry.gain.value = 1 - this.wet.gain.value; + } + + break; + } + + case 'dry': { + if (typeof value === 'number') { + this.dry.gain.value = value; + } + + break; + } + + case 'wet': { + if (typeof value === 'number') { + this.wet.gain.value = value; } break; @@ -246,7 +280,9 @@ export class Phaser extends Effector { resonance: this.filters[0].Q.value, depth : this.depthRate, rate : this.rate.value, - mix : this.mix.gain.value + mix : this.wet.gain.value, + dry : this.dry.gain.value, + wet : this.wet.gain.value }; } diff --git a/test/SoundModule/Effectors/Phaser.test.ts b/test/SoundModule/Effectors/Phaser.test.ts index 76d067e1..f15c0e22 100644 --- a/test/SoundModule/Effectors/Phaser.test.ts +++ b/test/SoundModule/Effectors/Phaser.test.ts @@ -59,7 +59,8 @@ describe(Phaser.name, () => { describe(phaser.connect.name, () => { /* eslint-disable dot-notation */ const originalInput = phaser['input']; - const originalMix = phaser['mix']; + const originalDry = phaser['dry']; + const originalWet = phaser['wet']; const originalConnect = BiquadFilterNode.prototype.connect; const originalDisconnect = BiquadFilterNode.prototype.disconnect; /* eslint-enable dot-notation */ @@ -67,7 +68,8 @@ describe(Phaser.name, () => { afterAll(() => { /* eslint-disable dot-notation */ phaser['input'] = originalInput; - phaser['mix'] = originalMix; + phaser['dry'] = originalDry; + phaser['wet'] = originalWet; BiquadFilterNode.prototype.connect = originalConnect; BiquadFilterNode.prototype.disconnect = originalDisconnect; /* eslint-enable dot-notation */ @@ -80,14 +82,18 @@ describe(Phaser.name, () => { const inputDisconnectMock = jest.fn(); const filterConnectMock = jest.fn(); const filterDisconnectMock = jest.fn(); - const mixConnectMock = jest.fn(); - const mixDisconnectMock = jest.fn(); + const dryConnectMock = jest.fn(); + const dryDisconnectMock = jest.fn(); + const wetConnectMock = jest.fn(); + const wetDisconnectMock = jest.fn(); /* eslint-disable dot-notation */ phaser['input'].connect = inputConnectMock; phaser['input'].disconnect = inputDisconnectMock; - phaser['mix'].connect = mixConnectMock; - phaser['mix'].disconnect = mixDisconnectMock; + phaser['dry'].connect = dryConnectMock; + phaser['dry'].disconnect = dryDisconnectMock; + phaser['wet'].connect = wetConnectMock; + phaser['wet'].disconnect = wetDisconnectMock; BiquadFilterNode.prototype.connect = filterConnectMock; BiquadFilterNode.prototype.disconnect = filterDisconnectMock; /* eslint-enable dot-notation */ @@ -96,19 +102,20 @@ describe(Phaser.name, () => { expect(inputConnectMock).toHaveBeenCalledTimes(1); expect(filterConnectMock).toHaveBeenCalledTimes(0); - expect(mixConnectMock).toHaveBeenCalledTimes(0); - expect(mixConnectMock).toHaveBeenCalledTimes(0); + expect(dryConnectMock).toHaveBeenCalledTimes(1); + expect(wetConnectMock).toHaveBeenCalledTimes(0); expect(inputDisconnectMock).toHaveBeenCalledTimes(1); expect(filterDisconnectMock).toHaveBeenCalledTimes(24); - expect(mixDisconnectMock).toHaveBeenCalledTimes(1); + expect(dryDisconnectMock).toHaveBeenCalledTimes(1); + expect(wetDisconnectMock).toHaveBeenCalledTimes(1); phaser.activate(); expect(inputConnectMock).toHaveBeenCalledTimes(3); expect(filterConnectMock).toHaveBeenCalledTimes(12); - expect(mixConnectMock).toHaveBeenCalledTimes(1); + expect(dryConnectMock).toHaveBeenCalledTimes(2); expect(filterDisconnectMock).toHaveBeenCalledTimes(48); - expect(mixDisconnectMock).toHaveBeenCalledTimes(2); + expect(wetDisconnectMock).toHaveBeenCalledTimes(2); }); }); @@ -119,7 +126,9 @@ describe(Phaser.name, () => { resonance: 1, depth : 0, rate : 0, - mix : 0 + mix : 0, + dry : 1, + wet : 0, }; const params: PhaserParams = { @@ -128,7 +137,9 @@ describe(Phaser.name, () => { resonance: 10, depth : 0.5, rate : 0.5, - mix : 0.5 + mix : 0.5, + dry : 0.5, + wet : 0.5 }; beforeAll(() => { @@ -168,6 +179,14 @@ describe(Phaser.name, () => { test('should return `mix`', () => { expect(phaser.param('mix')).toBeCloseTo(0.5, 1); }); + + test('should return `dry`', () => { + expect(phaser.param('dry')).toBeCloseTo(0.5, 1); + }); + + test('should return `wet`', () => { + expect(phaser.param('wet')).toBeCloseTo(0.5, 1); + }); }); describe(phaser.params.name, () => { @@ -179,7 +198,9 @@ describe(Phaser.name, () => { resonance: 1, depth : 0, rate : 0, - mix : 0 + mix : 0, + dry : 1, + wet : 0 }); }); });