@fluex/fluexgl-dsp 0.4.0 → 0.4.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/README.md +16 -38
- package/lib/dist/core/classes/AudioClip.d.ts +3 -1
- package/lib/dist/core/classes/AudioClip.d.ts.map +1 -1
- package/lib/dist/core/classes/AudioClip.js +18 -5
- package/lib/dist/core/classes/AudioClipPlayer.d.ts +8 -2
- package/lib/dist/core/classes/AudioClipPlayer.d.ts.map +1 -1
- package/lib/dist/core/classes/AudioClipPlayer.js +48 -1
- package/lib/dist/core/classes/AudioDevice.d.ts +2 -0
- package/lib/dist/core/classes/AudioDevice.d.ts.map +1 -1
- package/lib/dist/core/classes/AudioDevice.js +4 -0
- package/lib/dist/core/classes/Channel.d.ts +10 -2
- package/lib/dist/core/classes/Channel.d.ts.map +1 -1
- package/lib/dist/core/classes/Channel.js +67 -0
- package/lib/dist/core/classes/DpsPipeline.d.ts +1 -1
- package/lib/dist/core/classes/DpsPipeline.d.ts.map +1 -1
- package/lib/dist/core/classes/DpsPipeline.js +7 -3
- package/lib/dist/core/classes/Effector.d.ts +2 -2
- package/lib/dist/core/classes/Effector.d.ts.map +1 -1
- package/lib/dist/core/classes/Effector.js +1 -1
- package/lib/dist/core/classes/Master.d.ts +12 -0
- package/lib/dist/core/classes/Master.d.ts.map +1 -1
- package/lib/dist/core/classes/Master.js +77 -7
- package/lib/dist/effects/classes/Analyser.d.ts +16 -0
- package/lib/dist/effects/classes/Analyser.d.ts.map +1 -0
- package/lib/dist/effects/classes/Analyser.js +62 -0
- package/lib/dist/effects/classes/Chorus.d.ts +4 -3
- package/lib/dist/effects/classes/Chorus.d.ts.map +1 -1
- package/lib/dist/effects/classes/Chorus.js +21 -12
- package/lib/dist/effects/classes/clips/HardClip.d.ts +4 -3
- package/lib/dist/effects/classes/clips/HardClip.d.ts.map +1 -1
- package/lib/dist/effects/classes/clips/HardClip.js +15 -9
- package/lib/dist/effects/classes/clips/SoftClip.d.ts +4 -3
- package/lib/dist/effects/classes/clips/SoftClip.d.ts.map +1 -1
- package/lib/dist/effects/classes/clips/SoftClip.js +15 -9
- package/lib/dist/effects/classes/filters/LowPassFilter.d.ts +4 -3
- package/lib/dist/effects/classes/filters/LowPassFilter.d.ts.map +1 -1
- package/lib/dist/effects/classes/filters/LowPassFilter.js +23 -14
- package/lib/dist/effects/exports.d.ts +1 -0
- package/lib/dist/effects/exports.d.ts.map +1 -1
- package/lib/dist/effects/exports.js +1 -0
- package/lib/dist/index.d.ts +5 -4
- package/lib/dist/index.d.ts.map +1 -1
- package/lib/dist/index.js +13 -18
- package/lib/dist/typings.d.ts +13 -11
- package/lib/dist/typings.d.ts.map +1 -1
- package/lib/dist/typings.js +5 -0
- package/lib/dist/utilities/debugger.js +6 -6
- package/lib/dist/utilities/helpers.d.ts +2 -0
- package/lib/dist/utilities/helpers.d.ts.map +1 -1
- package/lib/dist/utilities/helpers.js +35 -12
- package/lib/src/core/classes/AudioClip.ts +23 -7
- package/lib/src/core/classes/AudioClipPlayer.ts +67 -7
- package/lib/src/core/classes/AudioDevice.ts +5 -0
- package/lib/src/core/classes/Channel.ts +97 -4
- package/lib/src/core/classes/DpsPipeline.ts +7 -3
- package/lib/src/core/classes/Effector.ts +3 -2
- package/lib/src/core/classes/Master.ts +98 -11
- package/lib/src/effects/classes/Analyser.ts +84 -0
- package/lib/src/effects/classes/Chorus.ts +22 -13
- package/lib/src/effects/classes/clips/HardClip.ts +17 -10
- package/lib/src/effects/classes/clips/SoftClip.ts +17 -10
- package/lib/src/effects/classes/filters/LowPassFilter.ts +28 -15
- package/lib/src/effects/exports.ts +2 -1
- package/lib/src/index.ts +27 -29
- package/lib/src/typings.ts +14 -14
- package/lib/src/utilities/debugger.ts +6 -6
- package/lib/src/utilities/helpers.ts +38 -14
- package/package.json +1 -1
- package/lib/dist/effects/classes/Filter.d.ts +0 -3
- package/lib/dist/effects/classes/Filter.d.ts.map +0 -1
- package/lib/dist/effects/classes/Filter.js +0 -2
- package/lib/dist/effects/classes/SoftClip.d.ts +0 -10
- package/lib/dist/effects/classes/SoftClip.d.ts.map +0 -1
- package/lib/dist/effects/classes/SoftClip.js +0 -39
- package/lib/dist/test/linkable-channels.d.ts +0 -2
- package/lib/dist/test/linkable-channels.d.ts.map +0 -1
- package/lib/dist/test/linkable-channels.js +0 -16
- package/lib/src/core/functions/rebuild-effect-chain.ts +0 -5
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { Effector } from "../../core/exports";
|
|
2
|
+
|
|
3
|
+
type AnalyserOptionKeys = keyof Omit<AnalyserOptions, "channelCount" | "channelCountMode" | "channelInterpretation">;
|
|
4
|
+
|
|
5
|
+
export class Analyser extends Effector {
|
|
6
|
+
|
|
7
|
+
public label: string | null = "Analyser";
|
|
8
|
+
public name: string = "Analyser";
|
|
9
|
+
|
|
10
|
+
public analyserNode: AnalyserNode | null = null;
|
|
11
|
+
|
|
12
|
+
public float32ArrayBuffer = new Float32Array();
|
|
13
|
+
public uint8ArrayBuffer = new Uint8Array();
|
|
14
|
+
|
|
15
|
+
private options: AnalyserOptions = {
|
|
16
|
+
fftSize: 32,
|
|
17
|
+
smoothingTimeConstant: 0.8,
|
|
18
|
+
minDecibels: -90,
|
|
19
|
+
maxDecibels: -10,
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
constructor(options?: Partial<AnalyserOptions>) {
|
|
23
|
+
super();
|
|
24
|
+
|
|
25
|
+
if (options) this.SetOptions(options);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
public async InitializeOnAttachment(context: AudioContext): Promise<void> {
|
|
29
|
+
|
|
30
|
+
this.context = context;
|
|
31
|
+
this.analyserNode = new AnalyserNode(context, this.options);
|
|
32
|
+
|
|
33
|
+
this.audioWorkletNode = this.analyserNode as unknown as AudioWorkletNode;
|
|
34
|
+
this.allocateBuffers();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
public SetOptions(options: Partial<AnalyserOptions>): void {
|
|
38
|
+
|
|
39
|
+
(Object.keys(options) as AnalyserOptionKeys[]).forEach((key) => {
|
|
40
|
+
const value = options[key];
|
|
41
|
+
|
|
42
|
+
if (typeof value === "undefined") return;
|
|
43
|
+
|
|
44
|
+
if (key === "fftSize" && value) {
|
|
45
|
+
const allowed = [32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768];
|
|
46
|
+
if (!allowed.includes(value)) return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
(this.options as any)[key] = value;
|
|
50
|
+
|
|
51
|
+
if (this.analyserNode) {
|
|
52
|
+
(this.analyserNode as any)[key] = value;
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
if (options.fftSize && this.analyserNode) {
|
|
57
|
+
this.allocateBuffers();
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
public GetWaveformFloatData(): Float32Array | null {
|
|
62
|
+
|
|
63
|
+
if (!this.analyserNode) return null;
|
|
64
|
+
|
|
65
|
+
this.analyserNode.getFloatTimeDomainData(this.float32ArrayBuffer);
|
|
66
|
+
return this.float32ArrayBuffer;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
public GetWaveformByteData(): Uint8Array | null {
|
|
70
|
+
|
|
71
|
+
if (!this.analyserNode) return null;
|
|
72
|
+
|
|
73
|
+
this.analyserNode.getByteTimeDomainData(this.uint8ArrayBuffer);
|
|
74
|
+
return this.uint8ArrayBuffer;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
private allocateBuffers(): void {
|
|
78
|
+
|
|
79
|
+
if (!this.analyserNode) return;
|
|
80
|
+
|
|
81
|
+
this.float32ArrayBuffer = new Float32Array(this.analyserNode.fftSize);
|
|
82
|
+
this.uint8ArrayBuffer = new Uint8Array(this.analyserNode.fftSize);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { SendMessageToWorklet, CreateAudioWorkletNode } from "../../utilities/helpers";
|
|
1
|
+
import { CoerceFiniteNumber, SendMessageToWorklet, CreateAudioWorkletNode } from "../../utilities/helpers";
|
|
2
2
|
import { Effector } from "../../core/classes/Effector";
|
|
3
|
-
import { ChorusEffectOptions, ChorusMessageCommandId, AudioWorkletProcessorNames } from "../../typings";
|
|
3
|
+
import { ChorusEffectOptions, ChorusMessageCommandId, AudioWorkletProcessorNames, StrictMode } from "../../typings";
|
|
4
4
|
|
|
5
5
|
export class Chorus extends Effector {
|
|
6
6
|
|
|
@@ -12,21 +12,24 @@ export class Chorus extends Effector {
|
|
|
12
12
|
public rateHz: number = 1.5;
|
|
13
13
|
public mix: number = 0.5;
|
|
14
14
|
public feedback: number = 0.2;
|
|
15
|
+
public strictMode: StrictMode = StrictMode.Disabled;
|
|
15
16
|
|
|
16
|
-
constructor({ baseDelayMs, depthMs, rateHz, mix, feedback }: Partial<ChorusEffectOptions>) {
|
|
17
|
+
constructor({ baseDelayMs, depthMs, rateHz, mix, feedback, strictMode }: Partial<ChorusEffectOptions>) {
|
|
17
18
|
super();
|
|
18
19
|
|
|
19
|
-
this.baseDelayMs = baseDelayMs
|
|
20
|
-
this.depthMs = depthMs
|
|
21
|
-
this.rateHz = rateHz
|
|
22
|
-
this.mix = mix
|
|
23
|
-
this.feedback = feedback
|
|
24
|
-
}
|
|
20
|
+
this.baseDelayMs = Math.max(0, CoerceFiniteNumber(baseDelayMs, this.baseDelayMs));
|
|
21
|
+
this.depthMs = Math.max(0, CoerceFiniteNumber(depthMs, this.depthMs));
|
|
22
|
+
this.rateHz = Math.max(0, CoerceFiniteNumber(rateHz, this.rateHz));
|
|
23
|
+
this.mix = Math.max(0, Math.min(1, CoerceFiniteNumber(mix, this.mix)));
|
|
24
|
+
this.feedback = Math.max(0, Math.min(1, CoerceFiniteNumber(feedback, this.feedback)));
|
|
25
25
|
|
|
26
|
-
|
|
26
|
+
const mode = CoerceFiniteNumber(strictMode, this.strictMode);
|
|
27
|
+
this.strictMode = mode === StrictMode.Enabled ? StrictMode.Enabled : StrictMode.Disabled;
|
|
28
|
+
}
|
|
27
29
|
|
|
28
|
-
|
|
29
|
-
this.
|
|
30
|
+
public async InitializeOnAttachment(context: AudioContext): Promise<void> {
|
|
31
|
+
this.context = context;
|
|
32
|
+
this.audioWorkletNode = CreateAudioWorkletNode<ChorusEffectOptions>(context, AudioWorkletProcessorNames.Chorus, this.ReturnOptionsAsObject());
|
|
30
33
|
}
|
|
31
34
|
|
|
32
35
|
public ReturnOptionsAsObject(): ChorusEffectOptions {
|
|
@@ -36,31 +39,37 @@ export class Chorus extends Effector {
|
|
|
36
39
|
rateHz: this.rateHz,
|
|
37
40
|
mix: this.mix,
|
|
38
41
|
feedback: this.feedback,
|
|
42
|
+
strictMode: this.strictMode
|
|
39
43
|
}
|
|
40
44
|
}
|
|
41
45
|
|
|
42
46
|
public SetBaseDelayMs(value: number): boolean {
|
|
47
|
+
value = Math.max(0, CoerceFiniteNumber(value, this.baseDelayMs));
|
|
43
48
|
this.baseDelayMs = value;
|
|
44
49
|
return SendMessageToWorklet<ChorusMessageCommandId, number>(this.audioWorkletNode, ChorusMessageCommandId.SetBaseDelayMs, value);
|
|
45
50
|
}
|
|
46
51
|
|
|
47
52
|
public SetDepthMs(value: number) {
|
|
53
|
+
value = Math.max(0, CoerceFiniteNumber(value, this.depthMs));
|
|
48
54
|
this.depthMs = value;
|
|
49
55
|
return SendMessageToWorklet<ChorusMessageCommandId, number>(this.audioWorkletNode, ChorusMessageCommandId.SetDepthMs, value);
|
|
50
56
|
}
|
|
51
57
|
|
|
52
58
|
public SetRateHz(value: number) {
|
|
59
|
+
value = Math.max(0, CoerceFiniteNumber(value, this.rateHz));
|
|
53
60
|
this.rateHz = value;
|
|
54
61
|
return SendMessageToWorklet<ChorusMessageCommandId, number>(this.audioWorkletNode, ChorusMessageCommandId.SetRateHz, value);
|
|
55
62
|
}
|
|
56
63
|
|
|
57
64
|
public SetMix(value: number) {
|
|
65
|
+
value = Math.max(0, Math.min(1, CoerceFiniteNumber(value, this.mix)));
|
|
58
66
|
this.mix = value;
|
|
59
67
|
return SendMessageToWorklet<ChorusMessageCommandId, number>(this.audioWorkletNode, ChorusMessageCommandId.SetMix, value);
|
|
60
68
|
}
|
|
61
69
|
|
|
62
70
|
public SetFeedback(value: number) {
|
|
71
|
+
value = Math.max(0, Math.min(1, CoerceFiniteNumber(value, this.feedback)));
|
|
63
72
|
this.feedback = value;
|
|
64
73
|
return SendMessageToWorklet<ChorusMessageCommandId, number>(this.audioWorkletNode, ChorusMessageCommandId.SetFeedback, value);
|
|
65
74
|
}
|
|
66
|
-
}
|
|
75
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Effector } from "../../../core/classes/Effector";
|
|
2
|
-
import { CreateAudioWorkletNode, SendMessageToWorklet } from "../../../utilities/helpers";
|
|
3
|
-
import { HardClipMessageCommandId, AudioWorkletProcessorNames, HardClipOptions } from "../../../typings";
|
|
2
|
+
import { CoerceFiniteNumber, CreateAudioWorkletNode, SendMessageToWorklet } from "../../../utilities/helpers";
|
|
3
|
+
import { HardClipMessageCommandId, AudioWorkletProcessorNames, HardClipOptions, StrictMode } from "../../../typings";
|
|
4
4
|
|
|
5
5
|
export class HardClip extends Effector {
|
|
6
6
|
|
|
@@ -9,33 +9,40 @@ export class HardClip extends Effector {
|
|
|
9
9
|
|
|
10
10
|
public drive: number = 1;
|
|
11
11
|
public gain: number = 1;
|
|
12
|
+
public strictMode: StrictMode = StrictMode.Disabled;
|
|
12
13
|
|
|
13
|
-
constructor({ drive, gain }: Partial<HardClipOptions>) {
|
|
14
|
+
constructor({ drive, gain, strictMode }: Partial<HardClipOptions>) {
|
|
14
15
|
super();
|
|
15
16
|
|
|
16
|
-
this.drive = drive
|
|
17
|
-
this.gain = gain
|
|
17
|
+
this.drive = Math.max(0, CoerceFiniteNumber(drive, this.drive));
|
|
18
|
+
this.gain = Math.max(0, CoerceFiniteNumber(gain, this.gain));
|
|
19
|
+
|
|
20
|
+
const mode = CoerceFiniteNumber(strictMode, this.strictMode);
|
|
21
|
+
this.strictMode = mode === StrictMode.Enabled ? StrictMode.Enabled : StrictMode.Disabled;
|
|
18
22
|
}
|
|
19
23
|
|
|
20
|
-
public async InitializeOnAttachment(
|
|
21
|
-
this.
|
|
22
|
-
this.audioWorkletNode = CreateAudioWorkletNode<HardClipOptions>(
|
|
24
|
+
public async InitializeOnAttachment(context: AudioContext): Promise<void> {
|
|
25
|
+
this.context = context;
|
|
26
|
+
this.audioWorkletNode = CreateAudioWorkletNode<HardClipOptions>(context, AudioWorkletProcessorNames.HardClip, this.ReturnOptionsAsObject());
|
|
23
27
|
}
|
|
24
28
|
|
|
25
29
|
public ReturnOptionsAsObject(): HardClipOptions {
|
|
26
30
|
return {
|
|
27
31
|
drive: this.drive,
|
|
28
|
-
gain: this.gain
|
|
32
|
+
gain: this.gain,
|
|
33
|
+
strictMode: this.strictMode
|
|
29
34
|
}
|
|
30
35
|
}
|
|
31
36
|
|
|
32
37
|
public SetDrive(drive: number): boolean {
|
|
38
|
+
drive = Math.max(0, CoerceFiniteNumber(drive, this.drive));
|
|
33
39
|
this.drive = drive;
|
|
34
40
|
return SendMessageToWorklet<HardClipMessageCommandId, number>(this.audioWorkletNode, HardClipMessageCommandId.SetDrive, drive);
|
|
35
41
|
}
|
|
36
42
|
|
|
37
43
|
public SetGain(gain: number): boolean{
|
|
44
|
+
gain = Math.max(0, CoerceFiniteNumber(gain, this.gain));
|
|
38
45
|
this.gain = gain;
|
|
39
46
|
return SendMessageToWorklet<HardClipMessageCommandId, number>(this.audioWorkletNode, HardClipMessageCommandId.SetGain, gain);
|
|
40
47
|
}
|
|
41
|
-
}
|
|
48
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { CreateAudioWorkletNode, SendMessageToWorklet } from "../../../utilities/helpers";
|
|
1
|
+
import { CoerceFiniteNumber, CreateAudioWorkletNode, SendMessageToWorklet } from "../../../utilities/helpers";
|
|
2
2
|
import { Effector } from "../../../core/classes/Effector";
|
|
3
|
-
import { SoftClipOptions, AudioWorkletProcessorNames, SoftClipMessageCommandId } from "../../../typings";
|
|
3
|
+
import { SoftClipOptions, AudioWorkletProcessorNames, SoftClipMessageCommandId, StrictMode } from "../../../typings";
|
|
4
4
|
|
|
5
5
|
export class SoftClip extends Effector {
|
|
6
6
|
|
|
@@ -9,33 +9,40 @@ export class SoftClip extends Effector {
|
|
|
9
9
|
|
|
10
10
|
public drive: number = 1;
|
|
11
11
|
public gain: number = 1;
|
|
12
|
+
public strictMode: StrictMode = StrictMode.Disabled;
|
|
12
13
|
|
|
13
|
-
constructor({ drive, gain }: Partial<SoftClipOptions>) {
|
|
14
|
+
constructor({ drive, gain, strictMode }: Partial<SoftClipOptions>) {
|
|
14
15
|
super();
|
|
15
16
|
|
|
16
|
-
this.drive = drive
|
|
17
|
-
this.gain = gain
|
|
17
|
+
this.drive = Math.max(0, CoerceFiniteNumber(drive, this.drive));
|
|
18
|
+
this.gain = Math.max(0, CoerceFiniteNumber(gain, this.gain));
|
|
19
|
+
|
|
20
|
+
const mode = CoerceFiniteNumber(strictMode, this.strictMode);
|
|
21
|
+
this.strictMode = mode === StrictMode.Enabled ? StrictMode.Enabled : StrictMode.Disabled;
|
|
18
22
|
}
|
|
19
23
|
|
|
20
|
-
public async InitializeOnAttachment(
|
|
21
|
-
this.
|
|
22
|
-
this.audioWorkletNode = CreateAudioWorkletNode<SoftClipOptions>(
|
|
24
|
+
public async InitializeOnAttachment(context: AudioContext): Promise<void> {
|
|
25
|
+
this.context = context;
|
|
26
|
+
this.audioWorkletNode = CreateAudioWorkletNode<SoftClipOptions>(context, AudioWorkletProcessorNames.SoftClip, this.ReturnOptionsAsObject());
|
|
23
27
|
}
|
|
24
28
|
|
|
25
29
|
public ReturnOptionsAsObject(): SoftClipOptions {
|
|
26
30
|
return {
|
|
27
31
|
drive: this.drive,
|
|
28
|
-
gain: this.gain
|
|
32
|
+
gain: this.gain,
|
|
33
|
+
strictMode: this.strictMode
|
|
29
34
|
}
|
|
30
35
|
}
|
|
31
36
|
|
|
32
37
|
public SetDrive(drive: number): boolean {
|
|
38
|
+
drive = Math.max(0, CoerceFiniteNumber(drive, this.drive));
|
|
33
39
|
this.drive = drive;
|
|
34
40
|
return SendMessageToWorklet<SoftClipMessageCommandId, number>(this.audioWorkletNode, SoftClipMessageCommandId.SetDrive, drive);
|
|
35
41
|
}
|
|
36
42
|
|
|
37
43
|
public SetGain(gain: number): boolean{
|
|
44
|
+
gain = Math.max(0, CoerceFiniteNumber(gain, this.gain));
|
|
38
45
|
this.gain = gain;
|
|
39
46
|
return SendMessageToWorklet<SoftClipMessageCommandId, number>(this.audioWorkletNode, SoftClipMessageCommandId.SetGain, gain);
|
|
40
47
|
}
|
|
41
|
-
}
|
|
48
|
+
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { Effector } from "../../../core/classes/Effector";
|
|
2
2
|
|
|
3
|
-
import { SendMessageToWorklet, CreateAudioWorkletNode } from "../../../utilities/helpers";
|
|
3
|
+
import { CoerceFiniteNumber, SendMessageToWorklet, CreateAudioWorkletNode } from "../../../utilities/helpers";
|
|
4
4
|
|
|
5
|
-
import { LowPassFilterOptions, LowPassFilterMessageCommandId, AudioWorkletProcessorNames } from "../../../typings";
|
|
5
|
+
import { LowPassFilterOptions, LowPassFilterMessageCommandId, AudioWorkletProcessorNames, StrictMode } from "../../../typings";
|
|
6
6
|
|
|
7
7
|
export class LowPassFilter extends Effector {
|
|
8
8
|
|
|
@@ -12,36 +12,44 @@ export class LowPassFilter extends Effector {
|
|
|
12
12
|
public cutoff: number = 1000;
|
|
13
13
|
public q: number = 0.7;
|
|
14
14
|
public minFrequency: number = 10;
|
|
15
|
+
public strictMode: StrictMode = StrictMode.Disabled;
|
|
15
16
|
|
|
16
|
-
constructor({ cutoff, minFrequency, q }: Partial<LowPassFilterOptions>) {
|
|
17
|
+
constructor({ cutoff, minFrequency, q, strictMode }: Partial<LowPassFilterOptions>) {
|
|
17
18
|
super();
|
|
18
19
|
|
|
19
|
-
this.cutoff = cutoff
|
|
20
|
-
this.minFrequency = minFrequency
|
|
21
|
-
this.q = q
|
|
20
|
+
this.cutoff = CoerceFiniteNumber(cutoff, this.cutoff);
|
|
21
|
+
this.minFrequency = CoerceFiniteNumber(minFrequency, this.minFrequency);
|
|
22
|
+
this.q = CoerceFiniteNumber(q, this.q);
|
|
23
|
+
|
|
24
|
+
const mode = CoerceFiniteNumber(strictMode, this.strictMode);
|
|
25
|
+
this.strictMode = mode === StrictMode.Enabled ? StrictMode.Enabled : StrictMode.Disabled;
|
|
22
26
|
}
|
|
23
27
|
|
|
24
|
-
public async InitializeOnAttachment(
|
|
28
|
+
public async InitializeOnAttachment(context: AudioContext): Promise<void> {
|
|
25
29
|
|
|
26
|
-
this.
|
|
27
|
-
this.audioWorkletNode = CreateAudioWorkletNode(
|
|
28
|
-
this.registerMessageEventListener(this.audioWorkletNode
|
|
30
|
+
this.context = context;
|
|
31
|
+
this.audioWorkletNode = CreateAudioWorkletNode(context, AudioWorkletProcessorNames.LowPassFilter, this.ReturnOptionsAsObject());
|
|
32
|
+
this.audioWorkletNode && this.registerMessageEventListener(this.audioWorkletNode);
|
|
29
33
|
}
|
|
30
34
|
|
|
31
35
|
public ReturnOptionsAsObject(): LowPassFilterOptions {
|
|
32
36
|
return {
|
|
33
37
|
cutoff: this.cutoff,
|
|
34
38
|
minFrequency: this.minFrequency,
|
|
35
|
-
q: this.q
|
|
39
|
+
q: this.q,
|
|
40
|
+
strictMode: this.strictMode
|
|
36
41
|
}
|
|
37
42
|
}
|
|
38
43
|
|
|
39
44
|
public SetCutoff(cutoff: number = 1000): boolean {
|
|
40
45
|
|
|
41
|
-
if (!this.
|
|
46
|
+
if (!this.context) return false;
|
|
47
|
+
|
|
48
|
+
cutoff = CoerceFiniteNumber(cutoff, this.cutoff);
|
|
49
|
+
cutoff = Math.max(this.minFrequency, cutoff);
|
|
42
50
|
|
|
43
|
-
if (cutoff >= this.
|
|
44
|
-
cutoff = this.
|
|
51
|
+
if (cutoff >= this.context.sampleRate)
|
|
52
|
+
cutoff = this.context.sampleRate;
|
|
45
53
|
|
|
46
54
|
this.cutoff = cutoff;
|
|
47
55
|
return SendMessageToWorklet<LowPassFilterMessageCommandId, number>(this.audioWorkletNode, LowPassFilterMessageCommandId.SetCutoff, cutoff);
|
|
@@ -49,6 +57,8 @@ export class LowPassFilter extends Effector {
|
|
|
49
57
|
|
|
50
58
|
public SetMinFrequency(minFrequency: number = 10): boolean {
|
|
51
59
|
|
|
60
|
+
minFrequency = CoerceFiniteNumber(minFrequency, this.minFrequency);
|
|
61
|
+
|
|
52
62
|
if (minFrequency < 10)
|
|
53
63
|
minFrequency = 10;
|
|
54
64
|
|
|
@@ -58,10 +68,13 @@ export class LowPassFilter extends Effector {
|
|
|
58
68
|
|
|
59
69
|
public SetQ(q: number = 0.7): boolean {
|
|
60
70
|
|
|
71
|
+
q = CoerceFiniteNumber(q, this.q);
|
|
72
|
+
q = Math.max(0.0001, q);
|
|
73
|
+
|
|
61
74
|
if(q > 4)
|
|
62
75
|
q = 4;
|
|
63
76
|
|
|
64
77
|
this.q = q;
|
|
65
78
|
return SendMessageToWorklet<LowPassFilterMessageCommandId, number>(this.audioWorkletNode, LowPassFilterMessageCommandId.SetQ, q);
|
|
66
79
|
}
|
|
67
|
-
}
|
|
80
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { Chorus } from "./classes/Chorus";
|
|
2
|
+
export { Analyser } from "./classes/Analyser";
|
|
2
3
|
export { Distortion } from "./classes/Distortion";
|
|
3
4
|
export { Equalizer } from "./classes/Equalizer";
|
|
4
5
|
export { Limiter } from "./classes/Limiter";
|
|
@@ -21,4 +22,4 @@ export { GenericReverb } from "./classes/reverbs/GenericReverb";
|
|
|
21
22
|
export { HallReverb } from "./classes/reverbs/HallReverb";
|
|
22
23
|
export { RoomReverb } from "./classes/reverbs/RoomReverb";
|
|
23
24
|
|
|
24
|
-
export { LowPassFilter } from "./classes/filters/LowPassFilter";
|
|
25
|
+
export { LowPassFilter } from "./classes/filters/LowPassFilter";
|
package/lib/src/index.ts
CHANGED
|
@@ -3,30 +3,25 @@
|
|
|
3
3
|
* A javascript digital audio processor library.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import {
|
|
6
|
+
import { DspOptions } from "./typings";
|
|
7
7
|
|
|
8
|
-
export const
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
debugger: {
|
|
20
|
-
showErrors: true,
|
|
21
|
-
showInfo: true,
|
|
22
|
-
showWarnings: true,
|
|
23
|
-
breakOnError: true
|
|
24
|
-
}
|
|
8
|
+
export const DSP: DspOptions = {
|
|
9
|
+
maxMasterChannels: 8,
|
|
10
|
+
maxTotalChannels: 128,
|
|
11
|
+
sampleRate: 44000,
|
|
12
|
+
spatialization: "stereo",
|
|
13
|
+
overrideMaxAudioBufferNodes: false,
|
|
14
|
+
debugger: {
|
|
15
|
+
showErrors: true,
|
|
16
|
+
showInfo: true,
|
|
17
|
+
showWarnings: true,
|
|
18
|
+
breakOnError: true
|
|
25
19
|
}
|
|
26
20
|
}
|
|
27
21
|
|
|
28
22
|
export {
|
|
29
23
|
Chorus,
|
|
24
|
+
Analyser,
|
|
30
25
|
Distortion,
|
|
31
26
|
Equalizer,
|
|
32
27
|
Limiter,
|
|
@@ -78,10 +73,19 @@ export {
|
|
|
78
73
|
hasInitializedWasm,
|
|
79
74
|
} from "./utilities/web-assembly";
|
|
80
75
|
|
|
76
|
+
export {
|
|
77
|
+
StrictMode,
|
|
78
|
+
ProcessorIdentificationCodes,
|
|
79
|
+
HardClipMessageCommandId,
|
|
80
|
+
SoftClipMessageCommandId,
|
|
81
|
+
AudioWorkletProcessorNames,
|
|
82
|
+
ChorusMessageCommandId,
|
|
83
|
+
LowPassFilterMessageCommandId
|
|
84
|
+
} from "./typings";
|
|
85
|
+
|
|
81
86
|
export type {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
FluexGLAudioOptions,
|
|
87
|
+
DspDebuggerOptions,
|
|
88
|
+
DspOptions,
|
|
85
89
|
LoadAudioSourceOptions,
|
|
86
90
|
AudioSourceData,
|
|
87
91
|
ChannelOptions,
|
|
@@ -95,17 +99,11 @@ export type {
|
|
|
95
99
|
DspPipelineInitializationState,
|
|
96
100
|
ChorusEffectOptions,
|
|
97
101
|
LowPassFilterOptions,
|
|
98
|
-
LowPassFilterMessageCommandId,
|
|
99
|
-
ChorusMessageCommandId,
|
|
100
|
-
AudioWorkletProcessorNames,
|
|
101
|
-
SoftClipMessageCommandId,
|
|
102
102
|
SoftClipOptions,
|
|
103
|
-
HardClipMessageCommandId,
|
|
104
103
|
HardClipOptions,
|
|
105
104
|
EffectorEventMap,
|
|
106
105
|
IncomingMessageType,
|
|
107
106
|
IncomingProcessorMessage,
|
|
108
107
|
ProcessorData,
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
} from "./typings";
|
|
108
|
+
EffectorEvents,
|
|
109
|
+
} from "./typings";
|
package/lib/src/typings.ts
CHANGED
|
@@ -60,28 +60,25 @@ export enum ProcessorIdentificationCodes {
|
|
|
60
60
|
UnknownProcessorCreationDate = "UNKNOWN_PROCESSOR_CREATION_DATE"
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
-
export
|
|
63
|
+
export enum StrictMode {
|
|
64
|
+
Disabled = 0x00,
|
|
65
|
+
Enabled = 0x01
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export interface DspDebuggerOptions {
|
|
64
69
|
breakOnError: boolean;
|
|
65
70
|
showInfo: boolean;
|
|
66
71
|
showErrors: boolean;
|
|
67
72
|
showWarnings: boolean;
|
|
68
73
|
}
|
|
69
74
|
|
|
70
|
-
export interface
|
|
75
|
+
export interface DspOptions {
|
|
71
76
|
maxMasterChannels: number;
|
|
72
77
|
maxTotalChannels: number;
|
|
73
78
|
sampleRate: number;
|
|
74
79
|
spatialization: ChannelSpatialization;
|
|
75
|
-
debugger:
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
export interface FluexGLAudioDescriptor {
|
|
79
|
-
name: string;
|
|
80
|
-
author: string;
|
|
81
|
-
version: string;
|
|
82
|
-
license: string;
|
|
83
|
-
repository: string;
|
|
84
|
-
options: FluexGLAudioOptions;
|
|
80
|
+
debugger: DspDebuggerOptions;
|
|
81
|
+
overrideMaxAudioBufferNodes: boolean;
|
|
85
82
|
}
|
|
86
83
|
|
|
87
84
|
export interface LoadAudioSourceOptions {
|
|
@@ -116,6 +113,7 @@ export interface AudioClipEventMap {
|
|
|
116
113
|
export interface DspPipelineInitializationOptions {
|
|
117
114
|
pathToWasm: string;
|
|
118
115
|
pathToWorklet: string;
|
|
116
|
+
options?: Partial<DspOptions>;
|
|
119
117
|
}
|
|
120
118
|
|
|
121
119
|
export interface DspPipelineInitializationState {
|
|
@@ -123,30 +121,32 @@ export interface DspPipelineInitializationState {
|
|
|
123
121
|
workletBlobUrl: string;
|
|
124
122
|
}
|
|
125
123
|
|
|
126
|
-
// ======== Effect options ========
|
|
127
|
-
|
|
128
124
|
export interface ChorusEffectOptions {
|
|
129
125
|
baseDelayMs: number;
|
|
130
126
|
depthMs: number;
|
|
131
127
|
rateHz: number;
|
|
132
128
|
mix: number;
|
|
133
129
|
feedback: number;
|
|
130
|
+
strictMode: StrictMode;
|
|
134
131
|
}
|
|
135
132
|
|
|
136
133
|
export interface LowPassFilterOptions {
|
|
137
134
|
cutoff: number;
|
|
138
135
|
minFrequency: number;
|
|
139
136
|
q: number;
|
|
137
|
+
strictMode: StrictMode;
|
|
140
138
|
}
|
|
141
139
|
|
|
142
140
|
export interface SoftClipOptions {
|
|
143
141
|
drive: number;
|
|
144
142
|
gain: number;
|
|
143
|
+
strictMode: StrictMode;
|
|
145
144
|
}
|
|
146
145
|
|
|
147
146
|
export interface HardClipOptions {
|
|
148
147
|
drive: number;
|
|
149
148
|
gain: number;
|
|
149
|
+
strictMode: StrictMode;
|
|
150
150
|
}
|
|
151
151
|
|
|
152
152
|
export interface EffectorEventMap {
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { ErrorCodes, WarningCodes } from "../console-codes";
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { DSP } from "../index";
|
|
4
4
|
|
|
5
5
|
export namespace Debug {
|
|
6
6
|
|
|
7
7
|
export function Log(message: string, additionalDetails?: string[]) {
|
|
8
8
|
|
|
9
|
-
if (!
|
|
9
|
+
if (!DSP.debugger.showInfo) return;
|
|
10
10
|
|
|
11
11
|
let outputString: string = `%c[INFO]: %c${message} %c`;
|
|
12
12
|
|
|
@@ -19,7 +19,7 @@ export namespace Debug {
|
|
|
19
19
|
|
|
20
20
|
export function Success(message: string, additionalDetails?: string[]) {
|
|
21
21
|
|
|
22
|
-
if (!
|
|
22
|
+
if (!DSP.debugger.showInfo) return;
|
|
23
23
|
|
|
24
24
|
let outputString: string = `%c[SUCCESS]: %c${message} %c`;
|
|
25
25
|
|
|
@@ -32,7 +32,7 @@ export namespace Debug {
|
|
|
32
32
|
|
|
33
33
|
export function Warn(message: string, additionalDetails?: string[], warningCode?: WarningCodes) {
|
|
34
34
|
|
|
35
|
-
if (!
|
|
35
|
+
if (!DSP.debugger.showWarnings) return;
|
|
36
36
|
|
|
37
37
|
let outputString: string = `%c(${warningCode ? warningCode : "Unknown".toUpperCase()}) %c[WARNING]: %c${message} %c`;
|
|
38
38
|
|
|
@@ -45,7 +45,7 @@ export namespace Debug {
|
|
|
45
45
|
|
|
46
46
|
export function Error(message: string, additionalDetails?: string[], errorCode?: ErrorCodes) {
|
|
47
47
|
|
|
48
|
-
if (!
|
|
48
|
+
if (!DSP.debugger.showErrors) return;
|
|
49
49
|
|
|
50
50
|
let outputString: string = `%c(${errorCode ? errorCode : "Unknown".toUpperCase()}) %c[ERROR]: %c${message} %c`;
|
|
51
51
|
|
|
@@ -55,7 +55,7 @@ export namespace Debug {
|
|
|
55
55
|
|
|
56
56
|
console.log(outputString, "color: red", "color: red;", "color: white", "color: white; font-style: italic;");
|
|
57
57
|
|
|
58
|
-
if (
|
|
58
|
+
if (DSP.debugger.breakOnError) {
|
|
59
59
|
|
|
60
60
|
for (let char of message)
|
|
61
61
|
outputString = outputString.replace("%c", "");
|
|
@@ -235,31 +235,55 @@ export function SendMessageToWorklet<T, K = any>(node: AudioWorkletNode | null,
|
|
|
235
235
|
|
|
236
236
|
if (!node) return false;
|
|
237
237
|
|
|
238
|
+
if (typeof data === "number" && !Number.isFinite(data)) {
|
|
239
|
+
Debug.Warn("Refusing to send non-finite numeric value to AudioWorkletNode.", [
|
|
240
|
+
`Command: ${String(commandId)}`,
|
|
241
|
+
`Value: ${String(data)}`
|
|
242
|
+
]);
|
|
243
|
+
return false;
|
|
244
|
+
}
|
|
245
|
+
|
|
238
246
|
node.port.postMessage({ commandId, data });
|
|
239
247
|
|
|
240
248
|
return true;
|
|
241
249
|
}
|
|
242
250
|
|
|
251
|
+
export function IsFiniteNumber(value: unknown): value is number {
|
|
252
|
+
return typeof value === "number" && Number.isFinite(value);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
export function CoerceFiniteNumber(value: unknown, fallback: number): number {
|
|
256
|
+
return IsFiniteNumber(value) ? value : fallback;
|
|
257
|
+
}
|
|
258
|
+
|
|
243
259
|
export function CreateAudioWorkletNode<T = any>(context: AudioContext, name: AudioWorkletProcessorNames | string, data: T) {
|
|
244
260
|
|
|
245
261
|
if (!compiledWebAssemblyModule) {
|
|
246
|
-
|
|
247
262
|
Debug.Error("Failed to create audio worklet node, because the web assembly module has not been compiled properly.", [
|
|
248
263
|
`Request audio worklet node: ${name}.`
|
|
249
264
|
]);
|
|
250
265
|
return null;
|
|
251
266
|
}
|
|
252
267
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
}
|
|
268
|
+
try {
|
|
269
|
+
return new AudioWorkletNode(context, name, {
|
|
270
|
+
numberOfInputs: 1,
|
|
271
|
+
numberOfOutputs: 1,
|
|
272
|
+
outputChannelCount: [2],
|
|
273
|
+
parameterData: {
|
|
274
|
+
...data,
|
|
275
|
+
sampleRate: context.sampleRate
|
|
276
|
+
},
|
|
277
|
+
processorOptions: {
|
|
278
|
+
module: compiledWebAssemblyModule
|
|
279
|
+
}
|
|
280
|
+
});
|
|
281
|
+
} catch (err) {
|
|
282
|
+
Debug.Error("Failed to create audio worklet node.", [
|
|
283
|
+
`Request audio worklet node: ${name}.`,
|
|
284
|
+
"Make sure you called InitializeDspPipeline/InitializeDpsPipeline and loaded the worklet module on the same AudioContext.",
|
|
285
|
+
String(err)
|
|
286
|
+
]);
|
|
287
|
+
return null;
|
|
288
|
+
}
|
|
289
|
+
}
|