@blibliki/engine 0.1.27 → 0.3.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 +252 -76
- package/dist/index.cjs +2 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +765 -0
- package/dist/index.d.ts +765 -0
- package/dist/index.js +2 -19484
- package/dist/index.js.map +1 -0
- package/package.json +19 -28
- package/src/Engine.ts +158 -177
- package/src/core/IO/AudioIO.ts +72 -0
- package/src/core/IO/Base.ts +118 -0
- package/src/core/IO/Collection.ts +123 -47
- package/src/core/IO/MidiIO.ts +43 -0
- package/src/core/IO/PolyAudioIO.ts +115 -0
- package/src/core/IO/index.ts +7 -61
- package/src/core/Note/frequencyTable.ts +144 -144
- package/src/core/Note/index.ts +49 -59
- package/src/core/Route.ts +79 -0
- package/src/core/Timing/Scheduler.ts +37 -0
- package/src/core/Timing/Time.ts +103 -0
- package/src/core/Timing/Transport.ts +104 -0
- package/src/core/Timing/index.ts +16 -0
- package/src/core/index.ts +36 -0
- package/src/core/midi/{ComputerKeyboardInput.ts → ComputerKeyboardDevice.ts} +31 -11
- package/src/core/midi/MidiDevice.ts +38 -31
- package/src/core/midi/MidiDeviceManager.ts +54 -55
- package/src/core/midi/MidiEvent.ts +36 -60
- package/src/core/module/Module.ts +233 -0
- package/src/core/module/PolyModule.ts +246 -0
- package/src/core/module/VoiceScheduler.ts +121 -0
- package/src/core/module/index.ts +3 -0
- package/src/core/schema.ts +41 -0
- package/src/index.ts +31 -9
- package/src/modules/BiquadFilter.ts +162 -0
- package/src/modules/Constant.ts +72 -0
- package/src/modules/Envelope.ts +178 -0
- package/src/modules/Filter.ts +109 -104
- package/src/modules/Gain.ts +78 -0
- package/src/modules/Inspector.ts +59 -0
- package/src/modules/Master.ts +18 -21
- package/src/modules/MidiSelector.ts +50 -50
- package/src/modules/Oscillator.ts +203 -158
- package/src/modules/Scale.ts +79 -0
- package/src/modules/StepSequencer.ts +61 -0
- package/src/modules/VirtualMidi.ts +33 -49
- package/src/modules/index.ts +159 -74
- package/src/nodePolyfill.ts +25 -0
- package/src/processors/filter-processor.ts +82 -0
- package/src/processors/index.ts +28 -0
- package/src/processors/scale-processor.ts +81 -0
- package/dist/index.umd.cjs +0 -227
- package/dist/src/Engine.d.ts +0 -83
- package/dist/src/core/IO/AudioNode.d.ts +0 -36
- package/dist/src/core/IO/Collection.d.ts +0 -14
- package/dist/src/core/IO/ForwardNode/Base.d.ts +0 -19
- package/dist/src/core/IO/ForwardNode/index.d.ts +0 -28
- package/dist/src/core/IO/MidiNode.d.ts +0 -31
- package/dist/src/core/IO/Node.d.ts +0 -41
- package/dist/src/core/IO/index.d.ts +0 -22
- package/dist/src/core/Module/MonoModule.d.ts +0 -68
- package/dist/src/core/Module/PolyModule.d.ts +0 -62
- package/dist/src/core/Module/index.d.ts +0 -7
- package/dist/src/core/Note/frequencyTable.d.ts +0 -4
- package/dist/src/core/Note/index.d.ts +0 -28
- package/dist/src/core/midi/ComputerKeyboardInput.d.ts +0 -12
- package/dist/src/core/midi/MidiDevice.d.ts +0 -30
- package/dist/src/core/midi/MidiDeviceManager.d.ts +0 -15
- package/dist/src/core/midi/MidiEvent.d.ts +0 -22
- package/dist/src/core/midi/index.d.ts +0 -5
- package/dist/src/index.d.ts +0 -9
- package/dist/src/main.d.ts +0 -0
- package/dist/src/modules/BitCrusher.d.ts +0 -17
- package/dist/src/modules/Delay.d.ts +0 -20
- package/dist/src/modules/Distortion.d.ts +0 -17
- package/dist/src/modules/Effect.d.ts +0 -20
- package/dist/src/modules/Envelope/AmpEnvelope.d.ts +0 -19
- package/dist/src/modules/Envelope/Base.d.ts +0 -67
- package/dist/src/modules/Envelope/FreqEnvelope.d.ts +0 -26
- package/dist/src/modules/Envelope/index.d.ts +0 -3
- package/dist/src/modules/Filter.d.ts +0 -43
- package/dist/src/modules/LFO.d.ts +0 -45
- package/dist/src/modules/Master.d.ts +0 -12
- package/dist/src/modules/MidiSelector.d.ts +0 -22
- package/dist/src/modules/Oscillator.d.ts +0 -56
- package/dist/src/modules/Reverb.d.ts +0 -20
- package/dist/src/modules/Sequencer.d.ts +0 -43
- package/dist/src/modules/VirtualMidi.d.ts +0 -33
- package/dist/src/modules/VoiceScheduler.d.ts +0 -45
- package/dist/src/modules/Volume.d.ts +0 -27
- package/dist/src/modules/index.d.ts +0 -18
- package/dist/src/routes.d.ts +0 -19
- package/dist/src/types.d.ts +0 -5
- package/dist/src/utils.d.ts +0 -1
- package/dist/test/MockingModules.d.ts +0 -22
- package/dist/test/Module/Oscillator.test.d.ts +0 -1
- package/dist/test/core/IO.test.d.ts +0 -1
- package/src/core/IO/AudioNode.ts +0 -82
- package/src/core/IO/ForwardNode/Base.ts +0 -99
- package/src/core/IO/ForwardNode/index.ts +0 -60
- package/src/core/IO/MidiNode.ts +0 -67
- package/src/core/IO/Node.ts +0 -118
- package/src/core/Module/MonoModule.ts +0 -219
- package/src/core/Module/PolyModule.ts +0 -218
- package/src/core/Module/index.ts +0 -15
- package/src/core/midi/index.ts +0 -5
- package/src/main.ts +0 -1
- package/src/modules/BitCrusher.ts +0 -45
- package/src/modules/Delay.ts +0 -53
- package/src/modules/Distortion.ts +0 -45
- package/src/modules/Effect.ts +0 -46
- package/src/modules/Envelope/AmpEnvelope.ts +0 -23
- package/src/modules/Envelope/Base.ts +0 -176
- package/src/modules/Envelope/FreqEnvelope.ts +0 -64
- package/src/modules/Envelope/index.ts +0 -3
- package/src/modules/LFO.ts +0 -149
- package/src/modules/Reverb.ts +0 -53
- package/src/modules/Sequencer.ts +0 -178
- package/src/modules/VoiceScheduler.ts +0 -145
- package/src/modules/Volume.ts +0 -72
- package/src/routes.ts +0 -49
- package/src/types.ts +0 -3
- package/src/utils.ts +0 -18
- package/src/vite-env.d.ts +0 -1
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { IAnyAudioContext, Module } from "@/core";
|
|
2
|
+
import { IModuleConstructor } from "@/core/module/Module";
|
|
3
|
+
import { IPolyModuleConstructor, PolyModule } from "@/core/module/PolyModule";
|
|
4
|
+
import { PropSchema } from "@/core/schema";
|
|
5
|
+
import { createModule, ICreateModule, ModuleType } from ".";
|
|
6
|
+
import { MonoGain } from "./Gain";
|
|
7
|
+
import Scale from "./Scale";
|
|
8
|
+
|
|
9
|
+
export type IBiquadFilterProps = {
|
|
10
|
+
cutoff: number;
|
|
11
|
+
envelopeAmount: number;
|
|
12
|
+
type: BiquadFilterType;
|
|
13
|
+
Q: number;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const MIN_FREQ = 20;
|
|
17
|
+
const MAX_FREQ = 20000;
|
|
18
|
+
|
|
19
|
+
const DEFAULT_PROPS: IBiquadFilterProps = {
|
|
20
|
+
cutoff: MAX_FREQ,
|
|
21
|
+
envelopeAmount: 0,
|
|
22
|
+
type: "lowpass",
|
|
23
|
+
Q: 1,
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export const biquadFilterPropSchema: PropSchema<IBiquadFilterProps> = {
|
|
27
|
+
cutoff: {
|
|
28
|
+
kind: "number",
|
|
29
|
+
min: MIN_FREQ,
|
|
30
|
+
max: MAX_FREQ,
|
|
31
|
+
step: 1,
|
|
32
|
+
label: "Cutoff",
|
|
33
|
+
},
|
|
34
|
+
envelopeAmount: {
|
|
35
|
+
kind: "number",
|
|
36
|
+
min: -1,
|
|
37
|
+
max: 1,
|
|
38
|
+
step: 0.01,
|
|
39
|
+
label: "Envelope Amount",
|
|
40
|
+
},
|
|
41
|
+
type: {
|
|
42
|
+
kind: "enum",
|
|
43
|
+
options: ["lowpass", "highpass", "bandpass"] satisfies BiquadFilterType[],
|
|
44
|
+
label: "Type",
|
|
45
|
+
},
|
|
46
|
+
Q: {
|
|
47
|
+
kind: "number",
|
|
48
|
+
min: -100,
|
|
49
|
+
max: 100,
|
|
50
|
+
step: 0.1,
|
|
51
|
+
label: "Q",
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
class MonoBiquadFilter extends Module<ModuleType.BiquadFilter> {
|
|
56
|
+
declare audioNode: BiquadFilterNode;
|
|
57
|
+
private scale: Scale;
|
|
58
|
+
private amount: MonoGain;
|
|
59
|
+
|
|
60
|
+
constructor(
|
|
61
|
+
engineId: string,
|
|
62
|
+
params: ICreateModule<ModuleType.BiquadFilter>,
|
|
63
|
+
) {
|
|
64
|
+
const props = { ...DEFAULT_PROPS, ...params.props };
|
|
65
|
+
|
|
66
|
+
const audioNodeConstructor = (context: IAnyAudioContext) =>
|
|
67
|
+
new BiquadFilterNode(context, {
|
|
68
|
+
type: props.type,
|
|
69
|
+
frequency: props.cutoff,
|
|
70
|
+
Q: props.Q,
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
super(engineId, {
|
|
74
|
+
...params,
|
|
75
|
+
props,
|
|
76
|
+
audioNodeConstructor,
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
this.amount = new MonoGain(engineId, {
|
|
80
|
+
name: "amount",
|
|
81
|
+
moduleType: ModuleType.Gain,
|
|
82
|
+
props: { gain: props.envelopeAmount },
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
this.scale = createModule(engineId, {
|
|
86
|
+
name: "scale",
|
|
87
|
+
moduleType: ModuleType.Scale,
|
|
88
|
+
props: { min: MIN_FREQ, max: MAX_FREQ, current: this.props.cutoff },
|
|
89
|
+
}) as Scale;
|
|
90
|
+
|
|
91
|
+
this.amount.plug({ audioModule: this.scale, from: "out", to: "in" });
|
|
92
|
+
this.scale.audioNode.connect(this.audioNode.frequency);
|
|
93
|
+
|
|
94
|
+
this.registerDefaultIOs();
|
|
95
|
+
this.registerInputs();
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
protected onSetType(value: IBiquadFilterProps["type"]) {
|
|
99
|
+
this.audioNode.type = value;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
protected onSetCutoff(value: IBiquadFilterProps["cutoff"]) {
|
|
103
|
+
if (!this.superInitialized) return;
|
|
104
|
+
|
|
105
|
+
this.scale.props = { current: value };
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
protected onSetQ(value: IBiquadFilterProps["Q"]) {
|
|
109
|
+
this.audioNode.Q.value = value;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
protected onSetEnvelopeAmount(value: IBiquadFilterProps["envelopeAmount"]) {
|
|
113
|
+
if (!this.superInitialized) return;
|
|
114
|
+
|
|
115
|
+
this.amount.props = { gain: value };
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
private registerInputs() {
|
|
119
|
+
this.registerAudioInput({
|
|
120
|
+
name: "cutoff",
|
|
121
|
+
getAudioNode: () => this.audioNode.frequency,
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
this.registerAudioInput({
|
|
125
|
+
name: "cutoffMod",
|
|
126
|
+
getAudioNode: () => this.amount.audioNode,
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
this.registerAudioInput({
|
|
130
|
+
name: "Q",
|
|
131
|
+
getAudioNode: () => this.audioNode.Q,
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export default class BiquadFilter extends PolyModule<ModuleType.BiquadFilter> {
|
|
137
|
+
constructor(
|
|
138
|
+
engineId: string,
|
|
139
|
+
params: IPolyModuleConstructor<ModuleType.BiquadFilter>,
|
|
140
|
+
) {
|
|
141
|
+
const props = { ...DEFAULT_PROPS, ...params.props };
|
|
142
|
+
const monoModuleConstructor = (
|
|
143
|
+
engineId: string,
|
|
144
|
+
params: IModuleConstructor<ModuleType.BiquadFilter>,
|
|
145
|
+
) => new MonoBiquadFilter(engineId, params);
|
|
146
|
+
|
|
147
|
+
super(engineId, {
|
|
148
|
+
...params,
|
|
149
|
+
props,
|
|
150
|
+
monoModuleConstructor,
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
this.registerInputs();
|
|
154
|
+
this.registerDefaultIOs();
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
private registerInputs() {
|
|
158
|
+
this.registerAudioInput({ name: "cutoff" });
|
|
159
|
+
this.registerAudioInput({ name: "cutoffMod" });
|
|
160
|
+
this.registerAudioInput({ name: "Q" });
|
|
161
|
+
}
|
|
162
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { IAnyAudioContext, IModule, Module } from "@/core";
|
|
2
|
+
import Note from "@/core/Note";
|
|
3
|
+
import { nt, TTime } from "@/core/Timing/Time";
|
|
4
|
+
import { PropSchema } from "@/core/schema";
|
|
5
|
+
import { ICreateModule, ModuleType } from ".";
|
|
6
|
+
|
|
7
|
+
export type IConstant = IModule<ModuleType.Constant>;
|
|
8
|
+
export type IConstantProps = {
|
|
9
|
+
value: number;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export const constantPropSchema: PropSchema<IConstantProps> = {
|
|
13
|
+
value: {
|
|
14
|
+
kind: "number",
|
|
15
|
+
min: -Infinity,
|
|
16
|
+
max: Infinity,
|
|
17
|
+
step: 0.01,
|
|
18
|
+
label: "Value",
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const DEFAULT_PROPS: IConstantProps = { value: 1 };
|
|
23
|
+
|
|
24
|
+
export default class Constant extends Module<ModuleType.Constant> {
|
|
25
|
+
declare audioNode: ConstantSourceNode;
|
|
26
|
+
isStated = false;
|
|
27
|
+
|
|
28
|
+
constructor(engineId: string, params: ICreateModule<ModuleType.Constant>) {
|
|
29
|
+
const props = { ...DEFAULT_PROPS, ...params.props };
|
|
30
|
+
const audioNodeConstructor = (context: IAnyAudioContext) =>
|
|
31
|
+
new ConstantSourceNode(context);
|
|
32
|
+
|
|
33
|
+
super(engineId, {
|
|
34
|
+
...params,
|
|
35
|
+
props,
|
|
36
|
+
audioNodeConstructor,
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
this.registerDefaultIOs("out");
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
protected onSetValue(value: IConstantProps["value"]) {
|
|
43
|
+
this.audioNode.offset.value = value;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
start(time: TTime) {
|
|
47
|
+
if (this.isStated) return;
|
|
48
|
+
|
|
49
|
+
this.isStated = true;
|
|
50
|
+
this.audioNode.start(nt(time));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
stop(time: TTime) {
|
|
54
|
+
this.audioNode.stop(nt(time));
|
|
55
|
+
this.rePlugAll(() => {
|
|
56
|
+
this.audioNode = new ConstantSourceNode(this.context, {
|
|
57
|
+
offset: this.props.value,
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
this.isStated = false;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
triggerAttack = (note: Note, triggeredAt: TTime) => {
|
|
65
|
+
this.audioNode.offset.setValueAtTime(note.frequency, nt(triggeredAt));
|
|
66
|
+
this.start(triggeredAt);
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
triggerRelease = () => {
|
|
70
|
+
// Do nothing
|
|
71
|
+
};
|
|
72
|
+
}
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import { createScaleNormalized } from "@blibliki/utils";
|
|
2
|
+
import { IAnyAudioContext, Module } from "@/core";
|
|
3
|
+
import Note from "@/core/Note";
|
|
4
|
+
import { nt, TTime } from "@/core/Timing/Time";
|
|
5
|
+
import { IModuleConstructor } from "@/core/module/Module";
|
|
6
|
+
import { IPolyModuleConstructor, PolyModule } from "@/core/module/PolyModule";
|
|
7
|
+
import { PropSchema } from "@/core/schema";
|
|
8
|
+
import { ICreateModule, ModuleType } from ".";
|
|
9
|
+
|
|
10
|
+
export type IEnvelopeProps = {
|
|
11
|
+
attack: number;
|
|
12
|
+
decay: number;
|
|
13
|
+
sustain: number;
|
|
14
|
+
release: number;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const DEFAULT_PROPS: IEnvelopeProps = {
|
|
18
|
+
attack: 0.1,
|
|
19
|
+
decay: 0.2,
|
|
20
|
+
sustain: 0,
|
|
21
|
+
release: 0.3,
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export const envelopePropSchema: PropSchema<IEnvelopeProps> = {
|
|
25
|
+
attack: {
|
|
26
|
+
kind: "number",
|
|
27
|
+
min: 0.0001,
|
|
28
|
+
max: 1,
|
|
29
|
+
step: 0.01,
|
|
30
|
+
label: "Attack",
|
|
31
|
+
},
|
|
32
|
+
decay: {
|
|
33
|
+
kind: "number",
|
|
34
|
+
min: 0,
|
|
35
|
+
max: 1,
|
|
36
|
+
step: 0.01,
|
|
37
|
+
label: "Decay",
|
|
38
|
+
},
|
|
39
|
+
sustain: {
|
|
40
|
+
kind: "number",
|
|
41
|
+
min: 0,
|
|
42
|
+
max: 1,
|
|
43
|
+
step: 0.01,
|
|
44
|
+
label: "Sustain",
|
|
45
|
+
},
|
|
46
|
+
release: {
|
|
47
|
+
kind: "number",
|
|
48
|
+
min: 0,
|
|
49
|
+
max: 1,
|
|
50
|
+
step: 0.01,
|
|
51
|
+
label: "Release",
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const scaleToTen = createScaleNormalized({
|
|
56
|
+
min: 0.001,
|
|
57
|
+
max: 10,
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
const scaleToFive = createScaleNormalized({
|
|
61
|
+
min: 0.001,
|
|
62
|
+
max: 5,
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
class MonoEnvelope extends Module<ModuleType.Envelope> {
|
|
66
|
+
declare audioNode: GainNode;
|
|
67
|
+
|
|
68
|
+
constructor(engineId: string, params: ICreateModule<ModuleType.Envelope>) {
|
|
69
|
+
const props = { ...DEFAULT_PROPS, ...params.props };
|
|
70
|
+
const audioNodeConstructor = (context: IAnyAudioContext) => {
|
|
71
|
+
const audioNode = new GainNode(context);
|
|
72
|
+
audioNode.gain.value = 0;
|
|
73
|
+
return audioNode;
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
super(engineId, {
|
|
77
|
+
...params,
|
|
78
|
+
props,
|
|
79
|
+
audioNodeConstructor,
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
this.registerDefaultIOs();
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
triggerAttack(note: Note, triggeredAt: TTime) {
|
|
86
|
+
super.triggerAttack(note, triggeredAt);
|
|
87
|
+
|
|
88
|
+
const attack = this.scaledAttack();
|
|
89
|
+
const decay = this.scaledDecay();
|
|
90
|
+
const sustain = this.props.sustain;
|
|
91
|
+
const triggeredAtNum = nt(triggeredAt);
|
|
92
|
+
|
|
93
|
+
this.audioNode.gain.cancelAndHoldAtTime(triggeredAtNum);
|
|
94
|
+
|
|
95
|
+
// Always start from a tiny value, can't ramp from 0
|
|
96
|
+
if (this.audioNode.gain.value === 0) {
|
|
97
|
+
this.audioNode.gain.setValueAtTime(0.001, triggeredAtNum);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Attack
|
|
101
|
+
this.audioNode.gain.exponentialRampToValueAtTime(
|
|
102
|
+
1.0,
|
|
103
|
+
triggeredAtNum + attack,
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
// Decay
|
|
107
|
+
if (sustain > 0) {
|
|
108
|
+
this.audioNode.gain.exponentialRampToValueAtTime(
|
|
109
|
+
sustain,
|
|
110
|
+
triggeredAtNum + attack + decay,
|
|
111
|
+
);
|
|
112
|
+
// Do not set to zero or anything else!
|
|
113
|
+
} else {
|
|
114
|
+
this.audioNode.gain.exponentialRampToValueAtTime(
|
|
115
|
+
0.001,
|
|
116
|
+
triggeredAtNum + attack + decay,
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
triggerRelease(note: Note, triggeredAt: TTime) {
|
|
122
|
+
super.triggerRelease(note, triggeredAt);
|
|
123
|
+
if (this.activeNotes.length > 0) return;
|
|
124
|
+
|
|
125
|
+
const release = this.scaledRelease();
|
|
126
|
+
const triggeredAtNum = nt(triggeredAt);
|
|
127
|
+
|
|
128
|
+
// Cancel scheduled automations and set gain to the ACTUAL value at this moment
|
|
129
|
+
this.audioNode.gain.cancelAndHoldAtTime(triggeredAtNum);
|
|
130
|
+
const currentGainValue = this.audioNode.gain.value;
|
|
131
|
+
|
|
132
|
+
if (currentGainValue >= 0.0001) {
|
|
133
|
+
// Always set the value at the release time to ensure a smooth ramp from here
|
|
134
|
+
this.audioNode.gain.setValueAtTime(currentGainValue, triggeredAtNum);
|
|
135
|
+
// Exponential ramp to a tiny value
|
|
136
|
+
this.audioNode.gain.exponentialRampToValueAtTime(
|
|
137
|
+
0.0001,
|
|
138
|
+
triggeredAtNum + release - 0.0001,
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Set to zero at the very end
|
|
143
|
+
this.audioNode.gain.setValueAtTime(0, triggeredAtNum + release);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
private scaledAttack() {
|
|
147
|
+
return scaleToTen(this.props.attack);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
private scaledDecay() {
|
|
151
|
+
return scaleToFive(this.props.decay);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
private scaledRelease() {
|
|
155
|
+
return scaleToTen(this.props.release);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export default class Envelope extends PolyModule<ModuleType.Envelope> {
|
|
160
|
+
constructor(
|
|
161
|
+
engineId: string,
|
|
162
|
+
params: IPolyModuleConstructor<ModuleType.Envelope>,
|
|
163
|
+
) {
|
|
164
|
+
const props = { ...DEFAULT_PROPS, ...params.props };
|
|
165
|
+
const monoModuleConstructor = (
|
|
166
|
+
engineId: string,
|
|
167
|
+
params: IModuleConstructor<ModuleType.Envelope>,
|
|
168
|
+
) => new MonoEnvelope(engineId, params);
|
|
169
|
+
|
|
170
|
+
super(engineId, {
|
|
171
|
+
...params,
|
|
172
|
+
props,
|
|
173
|
+
monoModuleConstructor,
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
this.registerDefaultIOs();
|
|
177
|
+
}
|
|
178
|
+
}
|
package/src/modules/Filter.ts
CHANGED
|
@@ -1,148 +1,153 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
import { IAnyAudioContext, Module } from "@/core";
|
|
2
|
+
import { IModuleConstructor } from "@/core/module/Module";
|
|
3
|
+
import { IPolyModuleConstructor, PolyModule } from "@/core/module/PolyModule";
|
|
4
|
+
import { PropSchema } from "@/core/schema";
|
|
5
|
+
import { CustomWorklet, newAudioWorklet } from "@/processors";
|
|
6
|
+
import { createModule, ICreateModule, ModuleType } from ".";
|
|
7
|
+
import { MonoGain } from "./Gain";
|
|
8
|
+
import Scale from "./Scale";
|
|
9
|
+
|
|
10
|
+
export type IFilterProps = {
|
|
5
11
|
cutoff: number;
|
|
6
|
-
filterType: BiquadFilterType;
|
|
7
|
-
resonance: number;
|
|
8
|
-
slope: FilterRollOff;
|
|
9
12
|
envelopeAmount: number;
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
type FilterProps = Partial<FilterInterface>;
|
|
13
|
+
resonance: number;
|
|
14
|
+
};
|
|
13
15
|
|
|
14
|
-
const
|
|
16
|
+
const MIN_FREQ = 20;
|
|
17
|
+
const MAX_FREQ = 22050;
|
|
15
18
|
|
|
16
|
-
const
|
|
19
|
+
const DEFAULT_PROPS: IFilterProps = {
|
|
17
20
|
cutoff: MAX_FREQ,
|
|
18
|
-
resonance: 0,
|
|
19
21
|
envelopeAmount: 0,
|
|
20
|
-
|
|
21
|
-
filterType: "lowpass",
|
|
22
|
+
resonance: 0,
|
|
22
23
|
};
|
|
23
24
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
25
|
+
export const filterPropSchema: PropSchema<IFilterProps> = {
|
|
26
|
+
cutoff: {
|
|
27
|
+
kind: "number",
|
|
28
|
+
min: MIN_FREQ,
|
|
29
|
+
max: MAX_FREQ,
|
|
30
|
+
step: 0.0001,
|
|
31
|
+
label: "Cutoff",
|
|
32
|
+
},
|
|
33
|
+
envelopeAmount: {
|
|
34
|
+
kind: "number",
|
|
35
|
+
min: -1,
|
|
36
|
+
max: 1,
|
|
37
|
+
step: 0.01,
|
|
38
|
+
label: "Envelope Amount",
|
|
39
|
+
},
|
|
40
|
+
resonance: {
|
|
41
|
+
kind: "number",
|
|
42
|
+
min: 0,
|
|
43
|
+
max: 4,
|
|
44
|
+
step: 0.01,
|
|
45
|
+
label: "resonance",
|
|
46
|
+
},
|
|
47
|
+
};
|
|
43
48
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
49
|
+
class MonoFilter extends Module<ModuleType.Filter> {
|
|
50
|
+
declare audioNode: AudioWorkletNode;
|
|
51
|
+
private scale: Scale;
|
|
52
|
+
private amount: MonoGain;
|
|
47
53
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
}
|
|
54
|
+
constructor(engineId: string, params: ICreateModule<ModuleType.Filter>) {
|
|
55
|
+
const props = { ...DEFAULT_PROPS, ...params.props };
|
|
51
56
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
this._cutoff.addend.value = value;
|
|
55
|
-
}
|
|
57
|
+
const audioNodeConstructor = (context: IAnyAudioContext) =>
|
|
58
|
+
newAudioWorklet(context, CustomWorklet.FilterProcessor);
|
|
56
59
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
+
super(engineId, {
|
|
61
|
+
...params,
|
|
62
|
+
props,
|
|
63
|
+
audioNodeConstructor,
|
|
64
|
+
});
|
|
60
65
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
66
|
+
this.amount = new MonoGain(engineId, {
|
|
67
|
+
name: "amount",
|
|
68
|
+
moduleType: ModuleType.Gain,
|
|
69
|
+
props: { gain: props.envelopeAmount },
|
|
70
|
+
});
|
|
64
71
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
72
|
+
this.scale = createModule(engineId, {
|
|
73
|
+
name: "scale",
|
|
74
|
+
moduleType: ModuleType.Scale,
|
|
75
|
+
props: { min: MIN_FREQ, max: MAX_FREQ, current: this.props.cutoff },
|
|
76
|
+
}) as Scale;
|
|
69
77
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
}
|
|
78
|
+
this.amount.plug({ audioModule: this.scale, from: "out", to: "in" });
|
|
79
|
+
this.scale.audioNode.connect(this.cutoff);
|
|
73
80
|
|
|
74
|
-
|
|
75
|
-
this.
|
|
76
|
-
this.internalModule.rolloff = value;
|
|
81
|
+
this.registerDefaultIOs();
|
|
82
|
+
this.registerInputs();
|
|
77
83
|
}
|
|
78
84
|
|
|
79
|
-
get
|
|
80
|
-
return this.
|
|
85
|
+
get cutoff() {
|
|
86
|
+
return this.audioNode.parameters.get("cutoff")!;
|
|
81
87
|
}
|
|
82
88
|
|
|
83
89
|
get resonance() {
|
|
84
|
-
return this.
|
|
90
|
+
return this.audioNode.parameters.get("resonance")!;
|
|
85
91
|
}
|
|
86
92
|
|
|
87
|
-
|
|
88
|
-
|
|
93
|
+
protected onSetCutoff(value: IFilterProps["cutoff"]) {
|
|
94
|
+
if (!this.superInitialized) return;
|
|
89
95
|
|
|
90
|
-
this.
|
|
96
|
+
this.scale.props = { current: value };
|
|
91
97
|
}
|
|
92
98
|
|
|
93
|
-
|
|
94
|
-
|
|
99
|
+
protected onSetResonance(value: IFilterProps["resonance"]) {
|
|
100
|
+
this.resonance.value = value;
|
|
95
101
|
}
|
|
96
102
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
this.updateAmountFactor();
|
|
100
|
-
}
|
|
103
|
+
protected onSetEnvelopeAmount(value: IFilterProps["envelopeAmount"]) {
|
|
104
|
+
if (!this.superInitialized) return;
|
|
101
105
|
|
|
102
|
-
|
|
103
|
-
|
|
106
|
+
this.amount.props = { gain: value };
|
|
107
|
+
}
|
|
104
108
|
|
|
109
|
+
private registerInputs() {
|
|
105
110
|
this.registerAudioInput({
|
|
106
111
|
name: "cutoff",
|
|
107
|
-
|
|
112
|
+
getAudioNode: () => this.scale.audioNode,
|
|
108
113
|
});
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
private updateAmountFactor() {
|
|
112
|
-
if (!this._amount) return;
|
|
113
114
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
115
|
+
this.registerAudioInput({
|
|
116
|
+
name: "cutoffMod",
|
|
117
|
+
getAudioNode: () => this.amount.audioNode,
|
|
118
|
+
});
|
|
118
119
|
|
|
119
|
-
this.
|
|
120
|
+
this.registerAudioInput({
|
|
121
|
+
name: "Q",
|
|
122
|
+
getAudioNode: () => this.resonance,
|
|
123
|
+
});
|
|
120
124
|
}
|
|
121
125
|
}
|
|
122
126
|
|
|
123
|
-
export default class Filter extends PolyModule<
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
127
|
+
export default class Filter extends PolyModule<ModuleType.Filter> {
|
|
128
|
+
constructor(
|
|
129
|
+
engineId: string,
|
|
130
|
+
params: IPolyModuleConstructor<ModuleType.Filter>,
|
|
131
|
+
) {
|
|
132
|
+
const props = { ...DEFAULT_PROPS, ...params.props };
|
|
133
|
+
const monoModuleConstructor = (
|
|
134
|
+
engineId: string,
|
|
135
|
+
params: IModuleConstructor<ModuleType.Filter>,
|
|
136
|
+
) => new MonoFilter(engineId, params);
|
|
137
|
+
|
|
138
|
+
super(engineId, {
|
|
139
|
+
...params,
|
|
140
|
+
props,
|
|
141
|
+
monoModuleConstructor,
|
|
138
142
|
});
|
|
139
143
|
|
|
140
|
-
this.
|
|
141
|
-
this.
|
|
144
|
+
this.registerInputs();
|
|
145
|
+
this.registerDefaultIOs();
|
|
142
146
|
}
|
|
143
147
|
|
|
144
|
-
private
|
|
145
|
-
this.
|
|
146
|
-
this.
|
|
148
|
+
private registerInputs() {
|
|
149
|
+
this.registerAudioInput({ name: "cutoff" });
|
|
150
|
+
this.registerAudioInput({ name: "cutoffMod" });
|
|
151
|
+
this.registerAudioInput({ name: "Q" });
|
|
147
152
|
}
|
|
148
153
|
}
|