@blibliki/engine 0.5.2 → 0.9.0
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 +22 -2
- package/dist/index.d.ts +501 -107
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +7 -7
- package/src/Engine.ts +46 -29
- package/src/core/index.ts +11 -2
- package/src/core/midi/BaseMidiDevice.ts +47 -0
- package/src/core/midi/ComputerKeyboardDevice.ts +2 -1
- package/src/core/midi/MidiDeviceManager.ts +125 -31
- package/src/core/midi/{MidiDevice.ts → MidiInputDevice.ts} +6 -30
- package/src/core/midi/MidiOutputDevice.ts +23 -0
- package/src/core/midi/adapters/NodeMidiAdapter.ts +99 -13
- package/src/core/midi/adapters/WebMidiAdapter.ts +68 -10
- package/src/core/midi/adapters/types.ts +13 -4
- package/src/core/midi/controllers/BaseController.ts +14 -0
- package/src/core/module/Module.ts +121 -13
- package/src/core/module/PolyModule.ts +36 -0
- package/src/core/module/VoiceScheduler.ts +150 -10
- package/src/core/module/index.ts +9 -4
- package/src/index.ts +27 -3
- package/src/modules/Chorus.ts +222 -0
- package/src/modules/Constant.ts +2 -2
- package/src/modules/Delay.ts +347 -0
- package/src/modules/Distortion.ts +182 -0
- package/src/modules/Envelope.ts +158 -92
- package/src/modules/Filter.ts +7 -7
- package/src/modules/Gain.ts +2 -2
- package/src/modules/LFO.ts +287 -0
- package/src/modules/LegacyEnvelope.ts +146 -0
- package/src/modules/{MidiSelector.ts → MidiInput.ts} +26 -19
- package/src/modules/MidiMapper.ts +59 -4
- package/src/modules/MidiOutput.ts +121 -0
- package/src/modules/Noise.ts +259 -0
- package/src/modules/Oscillator.ts +9 -3
- package/src/modules/Reverb.ts +379 -0
- package/src/modules/Scale.ts +49 -4
- package/src/modules/StepSequencer.ts +410 -22
- package/src/modules/StereoPanner.ts +1 -1
- package/src/modules/index.ts +142 -29
- package/src/processors/custom-envelope-processor.ts +125 -0
- package/src/processors/index.ts +10 -0
- package/src/processors/lfo-processor.ts +123 -0
- package/src/processors/scale-processor.ts +42 -5
- package/src/utils/WetDryMixer.ts +123 -0
- package/src/utils/expandPatternSequence.ts +18 -0
- package/src/utils/index.ts +2 -0
package/src/modules/index.ts
CHANGED
|
@@ -5,27 +5,45 @@ import VoiceScheduler, {
|
|
|
5
5
|
IVoiceSchedulerProps,
|
|
6
6
|
voiceSchedulerPropSchema,
|
|
7
7
|
} from "@/core/module/VoiceScheduler";
|
|
8
|
+
import Chorus, { chorusPropSchema, IChorusProps } from "./Chorus";
|
|
8
9
|
import Constant, { constantPropSchema, IConstantProps } from "./Constant";
|
|
9
|
-
import
|
|
10
|
+
import Delay, { delayPropSchema, IDelayProps } from "./Delay";
|
|
11
|
+
import Distortion, {
|
|
12
|
+
distortionPropSchema,
|
|
13
|
+
IDistortionProps,
|
|
14
|
+
} from "./Distortion";
|
|
15
|
+
import CustomEnvelope, {
|
|
16
|
+
customEnvelopePropSchema,
|
|
17
|
+
ICustomEnvelopeProps,
|
|
18
|
+
} from "./Envelope";
|
|
10
19
|
import Filter, { filterPropSchema, IFilterProps } from "./Filter";
|
|
11
20
|
import Gain, { gainPropSchema, IGainProps } from "./Gain";
|
|
12
21
|
import Inspector, { IInspectorProps, inspectorPropSchema } from "./Inspector";
|
|
22
|
+
import LFO, { ILFOProps, lfoPropSchema } from "./LFO";
|
|
23
|
+
import LegacyEnvelope, {
|
|
24
|
+
envelopePropSchema as legacyEnvelopePropSchema,
|
|
25
|
+
IEnvelopeProps as ILegacyEnvelopeProps,
|
|
26
|
+
} from "./LegacyEnvelope";
|
|
13
27
|
import Master, { IMasterProps, masterPropSchema } from "./Master";
|
|
28
|
+
import MidiInput, { IMidiInputProps, midiInputPropSchema } from "./MidiInput";
|
|
14
29
|
import MidiMapper, {
|
|
15
30
|
IMidiMapperProps,
|
|
16
31
|
midiMapperPropSchema,
|
|
17
32
|
} from "./MidiMapper";
|
|
18
|
-
import
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
} from "./
|
|
33
|
+
import MidiOutput, {
|
|
34
|
+
IMidiOutputProps,
|
|
35
|
+
midiOutputPropSchema,
|
|
36
|
+
} from "./MidiOutput";
|
|
37
|
+
import Noise, { INoiseProps, noisePropSchema } from "./Noise";
|
|
22
38
|
import Oscillator, {
|
|
23
39
|
IOscillatorProps,
|
|
24
40
|
oscillatorPropSchema,
|
|
25
41
|
} from "./Oscillator";
|
|
42
|
+
import Reverb, { IReverbProps, reverbPropSchema } from "./Reverb";
|
|
26
43
|
import Scale, { IScaleProps, scalePropSchema } from "./Scale";
|
|
27
44
|
import StepSequencer, {
|
|
28
45
|
IStepSequencerProps,
|
|
46
|
+
IStepSequencerState,
|
|
29
47
|
stepSequencerPropSchema,
|
|
30
48
|
} from "./StepSequencer";
|
|
31
49
|
import StereoPanner, {
|
|
@@ -41,83 +59,157 @@ export enum ModuleType {
|
|
|
41
59
|
Master = "Master",
|
|
42
60
|
Oscillator = "Oscillator",
|
|
43
61
|
Gain = "Gain",
|
|
44
|
-
|
|
62
|
+
MidiInput = "MidiInput",
|
|
63
|
+
MidiOutput = "MidiOutput",
|
|
64
|
+
LegacyEnvelope = "LegacyEnvelope", // BACKWARD_COMPAT: Legacy envelope for old patches
|
|
45
65
|
Envelope = "Envelope",
|
|
46
66
|
Filter = "Filter",
|
|
47
67
|
Scale = "Scale",
|
|
48
68
|
StereoPanner = "StereoPanner",
|
|
49
69
|
Inspector = "Inspector",
|
|
70
|
+
Chorus = "Chorus",
|
|
50
71
|
Constant = "Constant",
|
|
72
|
+
Delay = "Delay",
|
|
73
|
+
Distortion = "Distortion",
|
|
51
74
|
MidiMapper = "MidiMapper",
|
|
52
75
|
VirtualMidi = "VirtualMidi",
|
|
53
76
|
StepSequencer = "StepSequencer",
|
|
54
77
|
VoiceScheduler = "VoiceScheduler",
|
|
78
|
+
LFO = "LFO",
|
|
79
|
+
Noise = "Noise",
|
|
80
|
+
Reverb = "Reverb",
|
|
55
81
|
}
|
|
56
82
|
|
|
57
83
|
export type ModuleTypeToPropsMapping = {
|
|
58
84
|
[ModuleType.Oscillator]: IOscillatorProps;
|
|
59
85
|
[ModuleType.Gain]: IGainProps;
|
|
60
86
|
[ModuleType.Master]: IMasterProps;
|
|
61
|
-
[ModuleType.
|
|
62
|
-
[ModuleType.
|
|
87
|
+
[ModuleType.MidiInput]: IMidiInputProps;
|
|
88
|
+
[ModuleType.MidiOutput]: IMidiOutputProps;
|
|
89
|
+
[ModuleType.LegacyEnvelope]: ILegacyEnvelopeProps; // BACKWARD_COMPAT: Legacy envelope for old patches
|
|
90
|
+
[ModuleType.Envelope]: ICustomEnvelopeProps;
|
|
63
91
|
[ModuleType.Filter]: IFilterProps;
|
|
64
92
|
[ModuleType.Scale]: IScaleProps;
|
|
65
93
|
[ModuleType.StereoPanner]: IStereoPannerProps;
|
|
66
94
|
[ModuleType.Inspector]: IInspectorProps;
|
|
95
|
+
[ModuleType.Chorus]: IChorusProps;
|
|
67
96
|
[ModuleType.Constant]: IConstantProps;
|
|
97
|
+
[ModuleType.Delay]: IDelayProps;
|
|
98
|
+
[ModuleType.Distortion]: IDistortionProps;
|
|
68
99
|
[ModuleType.MidiMapper]: IMidiMapperProps;
|
|
69
100
|
[ModuleType.VirtualMidi]: IVirtualMidiProps;
|
|
70
101
|
[ModuleType.StepSequencer]: IStepSequencerProps;
|
|
71
102
|
[ModuleType.VoiceScheduler]: IVoiceSchedulerProps;
|
|
103
|
+
[ModuleType.LFO]: ILFOProps;
|
|
104
|
+
[ModuleType.Noise]: INoiseProps;
|
|
105
|
+
[ModuleType.Reverb]: IReverbProps;
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
export type ModuleTypeToStateMapping = {
|
|
109
|
+
[ModuleType.Oscillator]: never;
|
|
110
|
+
[ModuleType.Gain]: never;
|
|
111
|
+
[ModuleType.Master]: never;
|
|
112
|
+
[ModuleType.MidiInput]: never;
|
|
113
|
+
[ModuleType.MidiOutput]: never;
|
|
114
|
+
[ModuleType.LegacyEnvelope]: never;
|
|
115
|
+
[ModuleType.Envelope]: never;
|
|
116
|
+
[ModuleType.Filter]: never;
|
|
117
|
+
[ModuleType.Scale]: never;
|
|
118
|
+
[ModuleType.StereoPanner]: never;
|
|
119
|
+
[ModuleType.Inspector]: never;
|
|
120
|
+
[ModuleType.Chorus]: never;
|
|
121
|
+
[ModuleType.Constant]: never;
|
|
122
|
+
[ModuleType.Delay]: never;
|
|
123
|
+
[ModuleType.Distortion]: never;
|
|
124
|
+
[ModuleType.MidiMapper]: never;
|
|
125
|
+
[ModuleType.VirtualMidi]: never;
|
|
126
|
+
[ModuleType.StepSequencer]: IStepSequencerState;
|
|
127
|
+
[ModuleType.VoiceScheduler]: never;
|
|
128
|
+
[ModuleType.LFO]: never;
|
|
129
|
+
[ModuleType.Noise]: never;
|
|
130
|
+
[ModuleType.Reverb]: never;
|
|
72
131
|
};
|
|
73
132
|
|
|
74
133
|
export type ModuleTypeToModuleMapping = {
|
|
75
134
|
[ModuleType.Oscillator]: Oscillator;
|
|
76
135
|
[ModuleType.Gain]: Gain;
|
|
77
136
|
[ModuleType.Master]: Master;
|
|
78
|
-
[ModuleType.
|
|
79
|
-
[ModuleType.
|
|
137
|
+
[ModuleType.MidiInput]: MidiInput;
|
|
138
|
+
[ModuleType.MidiOutput]: MidiOutput;
|
|
139
|
+
[ModuleType.LegacyEnvelope]: LegacyEnvelope; // BACKWARD_COMPAT: Legacy envelope for old patches
|
|
140
|
+
[ModuleType.Envelope]: CustomEnvelope;
|
|
80
141
|
[ModuleType.Filter]: Filter;
|
|
81
142
|
[ModuleType.Scale]: Scale;
|
|
82
143
|
[ModuleType.StereoPanner]: StereoPanner;
|
|
83
144
|
[ModuleType.Inspector]: Inspector;
|
|
145
|
+
[ModuleType.Chorus]: Chorus;
|
|
84
146
|
[ModuleType.Constant]: Constant;
|
|
147
|
+
[ModuleType.Delay]: Delay;
|
|
148
|
+
[ModuleType.Distortion]: Distortion;
|
|
85
149
|
[ModuleType.MidiMapper]: MidiMapper;
|
|
86
150
|
[ModuleType.VirtualMidi]: VirtualMidi;
|
|
87
151
|
[ModuleType.StepSequencer]: StepSequencer;
|
|
88
152
|
[ModuleType.VoiceScheduler]: VoiceScheduler;
|
|
153
|
+
[ModuleType.LFO]: LFO;
|
|
154
|
+
[ModuleType.Noise]: Noise;
|
|
155
|
+
[ModuleType.Reverb]: Reverb;
|
|
89
156
|
};
|
|
90
157
|
|
|
91
158
|
export const moduleSchemas = {
|
|
92
159
|
[ModuleType.Oscillator]: oscillatorPropSchema,
|
|
93
160
|
[ModuleType.Gain]: gainPropSchema,
|
|
94
161
|
[ModuleType.Master]: masterPropSchema,
|
|
95
|
-
[ModuleType.
|
|
96
|
-
[ModuleType.
|
|
162
|
+
[ModuleType.MidiInput]: midiInputPropSchema,
|
|
163
|
+
[ModuleType.MidiOutput]: midiOutputPropSchema,
|
|
164
|
+
[ModuleType.LegacyEnvelope]: legacyEnvelopePropSchema, // BACKWARD_COMPAT: Legacy envelope for old patches
|
|
165
|
+
[ModuleType.Envelope]: customEnvelopePropSchema,
|
|
97
166
|
[ModuleType.Filter]: filterPropSchema,
|
|
98
167
|
[ModuleType.Scale]: scalePropSchema,
|
|
99
168
|
[ModuleType.StereoPanner]: stereoPannerPropSchema,
|
|
100
169
|
[ModuleType.Inspector]: inspectorPropSchema,
|
|
170
|
+
[ModuleType.Chorus]: chorusPropSchema,
|
|
101
171
|
[ModuleType.Constant]: constantPropSchema,
|
|
172
|
+
[ModuleType.Delay]: delayPropSchema,
|
|
173
|
+
[ModuleType.Distortion]: distortionPropSchema,
|
|
102
174
|
[ModuleType.MidiMapper]: midiMapperPropSchema,
|
|
103
175
|
[ModuleType.VirtualMidi]: virtualMidiPropSchema,
|
|
104
176
|
[ModuleType.StepSequencer]: stepSequencerPropSchema,
|
|
105
177
|
[ModuleType.VoiceScheduler]: voiceSchedulerPropSchema,
|
|
178
|
+
[ModuleType.LFO]: lfoPropSchema,
|
|
179
|
+
[ModuleType.Noise]: noisePropSchema,
|
|
180
|
+
[ModuleType.Reverb]: reverbPropSchema,
|
|
106
181
|
};
|
|
107
182
|
|
|
108
183
|
export type { IOscillator } from "./Oscillator";
|
|
109
184
|
export { OscillatorWave } from "./Oscillator";
|
|
110
185
|
export type { IGain } from "./Gain";
|
|
111
186
|
export type { IMaster } from "./Master";
|
|
112
|
-
export type {
|
|
187
|
+
export type { IMidiInput, IMidiInputProps } from "./MidiInput";
|
|
188
|
+
export type { IMidiOutput, IMidiOutputProps } from "./MidiOutput";
|
|
113
189
|
export type { IStereoPanner } from "./StereoPanner";
|
|
114
190
|
export type {
|
|
115
191
|
IStepSequencer,
|
|
116
192
|
IStepSequencerProps,
|
|
117
|
-
|
|
193
|
+
IStepSequencerState,
|
|
194
|
+
IStep,
|
|
195
|
+
IPage,
|
|
196
|
+
IPattern,
|
|
197
|
+
IStepNote,
|
|
198
|
+
IStepCC,
|
|
118
199
|
} from "./StepSequencer";
|
|
200
|
+
export { Resolution, PlaybackMode, stepPropSchema } from "./StepSequencer";
|
|
119
201
|
export type { IMidiMapper, IMidiMapperProps, MidiMapping } from "./MidiMapper";
|
|
120
202
|
export { MidiMappingMode } from "./MidiMapper";
|
|
203
|
+
export type { ILFO, ILFOProps } from "./LFO";
|
|
204
|
+
export { LFOWaveform } from "./LFO";
|
|
205
|
+
export type { INoise } from "./Noise";
|
|
206
|
+
export { NoiseType } from "./Noise";
|
|
207
|
+
export type { IReverb, IReverbProps } from "./Reverb";
|
|
208
|
+
export { ReverbType } from "./Reverb";
|
|
209
|
+
export { DelayTimeMode } from "./Delay";
|
|
210
|
+
export type { IDelay, IDelayProps } from "./Delay";
|
|
211
|
+
export type { IDistortion, IDistortionProps } from "./Distortion";
|
|
212
|
+
export type { IChorus, IChorusProps } from "./Chorus";
|
|
121
213
|
|
|
122
214
|
export type AnyModule = Module<ModuleType>;
|
|
123
215
|
export type IAnyModule = IModule<ModuleType>;
|
|
@@ -133,10 +225,14 @@ export type ModuleParams = {
|
|
|
133
225
|
[K in ModuleType]: K extends
|
|
134
226
|
| ModuleType.Oscillator
|
|
135
227
|
| ModuleType.Gain
|
|
228
|
+
| ModuleType.LegacyEnvelope
|
|
136
229
|
| ModuleType.Envelope
|
|
137
230
|
| ModuleType.Filter
|
|
138
231
|
| ModuleType.StereoPanner
|
|
139
232
|
| ModuleType.VoiceScheduler
|
|
233
|
+
| ModuleType.Scale
|
|
234
|
+
| ModuleType.LFO
|
|
235
|
+
| ModuleType.Distortion
|
|
140
236
|
? IPolyModuleConstructor<K>
|
|
141
237
|
: ICreateModule<K>;
|
|
142
238
|
}[ModuleType];
|
|
@@ -147,33 +243,50 @@ export function createModule(
|
|
|
147
243
|
): ModuleTypeToModuleMapping[keyof ModuleTypeToModuleMapping] {
|
|
148
244
|
switch (params.moduleType) {
|
|
149
245
|
case ModuleType.Oscillator:
|
|
150
|
-
return
|
|
246
|
+
return Oscillator.create(Oscillator, engineId, params);
|
|
151
247
|
case ModuleType.Gain:
|
|
152
|
-
return
|
|
248
|
+
return Gain.create(Gain, engineId, params);
|
|
153
249
|
case ModuleType.Master:
|
|
154
|
-
return
|
|
155
|
-
case ModuleType.
|
|
156
|
-
return
|
|
250
|
+
return Master.create(Master, engineId, params);
|
|
251
|
+
case ModuleType.MidiInput:
|
|
252
|
+
return MidiInput.create(MidiInput, engineId, params);
|
|
253
|
+
case ModuleType.MidiOutput:
|
|
254
|
+
return MidiOutput.create(MidiOutput, engineId, params);
|
|
255
|
+
// BACKWARD_COMPAT: Legacy envelope for old patches
|
|
256
|
+
case ModuleType.LegacyEnvelope:
|
|
257
|
+
return LegacyEnvelope.create(LegacyEnvelope, engineId, params);
|
|
157
258
|
case ModuleType.Envelope:
|
|
158
|
-
return
|
|
259
|
+
return CustomEnvelope.create(CustomEnvelope, engineId, params);
|
|
159
260
|
case ModuleType.Filter:
|
|
160
|
-
return
|
|
261
|
+
return Filter.create(Filter, engineId, params);
|
|
161
262
|
case ModuleType.Scale:
|
|
162
|
-
return
|
|
263
|
+
return Scale.create(Scale, engineId, params);
|
|
163
264
|
case ModuleType.StereoPanner:
|
|
164
|
-
return
|
|
265
|
+
return StereoPanner.create(StereoPanner, engineId, params);
|
|
165
266
|
case ModuleType.Inspector:
|
|
166
|
-
return
|
|
267
|
+
return Inspector.create(Inspector, engineId, params);
|
|
268
|
+
case ModuleType.Chorus:
|
|
269
|
+
return Chorus.create(Chorus, engineId, params);
|
|
167
270
|
case ModuleType.Constant:
|
|
168
|
-
return
|
|
271
|
+
return Constant.create(Constant, engineId, params);
|
|
272
|
+
case ModuleType.Delay:
|
|
273
|
+
return Delay.create(Delay, engineId, params);
|
|
274
|
+
case ModuleType.Distortion:
|
|
275
|
+
return Distortion.create(Distortion, engineId, params);
|
|
169
276
|
case ModuleType.MidiMapper:
|
|
170
|
-
return
|
|
277
|
+
return MidiMapper.create(MidiMapper, engineId, params);
|
|
171
278
|
case ModuleType.VirtualMidi:
|
|
172
|
-
return
|
|
279
|
+
return VirtualMidi.create(VirtualMidi, engineId, params);
|
|
173
280
|
case ModuleType.StepSequencer:
|
|
174
|
-
return
|
|
281
|
+
return StepSequencer.create(StepSequencer, engineId, params);
|
|
175
282
|
case ModuleType.VoiceScheduler:
|
|
176
|
-
return
|
|
283
|
+
return VoiceScheduler.create(VoiceScheduler, engineId, params);
|
|
284
|
+
case ModuleType.LFO:
|
|
285
|
+
return LFO.create(LFO, engineId, params);
|
|
286
|
+
case ModuleType.Noise:
|
|
287
|
+
return Noise.create(Noise, engineId, params);
|
|
288
|
+
case ModuleType.Reverb:
|
|
289
|
+
return Reverb.create(Reverb, engineId, params);
|
|
177
290
|
default:
|
|
178
291
|
assertNever(params);
|
|
179
292
|
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
export const customEnvelopeProcessorURL = URL.createObjectURL(
|
|
2
|
+
new Blob(
|
|
3
|
+
[
|
|
4
|
+
"(",
|
|
5
|
+
(() => {
|
|
6
|
+
class CustomEnvelopeProcessor extends AudioWorkletProcessor {
|
|
7
|
+
private _lasttrig = 0;
|
|
8
|
+
private _trig = 0;
|
|
9
|
+
private _phase = 0;
|
|
10
|
+
private _value = 0;
|
|
11
|
+
|
|
12
|
+
static get parameterDescriptors() {
|
|
13
|
+
return [
|
|
14
|
+
{
|
|
15
|
+
name: "attack",
|
|
16
|
+
defaultValue: 0.1,
|
|
17
|
+
minValue: 0,
|
|
18
|
+
maxValue: 60,
|
|
19
|
+
automationRate: "k-rate",
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
name: "attackcurve",
|
|
23
|
+
defaultValue: 0.5,
|
|
24
|
+
minValue: 0,
|
|
25
|
+
maxValue: 1,
|
|
26
|
+
automationRate: "k-rate",
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
name: "decay",
|
|
30
|
+
defaultValue: 0.1,
|
|
31
|
+
minValue: 0,
|
|
32
|
+
maxValue: 60,
|
|
33
|
+
automationRate: "k-rate",
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
name: "sustain",
|
|
37
|
+
defaultValue: 1,
|
|
38
|
+
minValue: 0,
|
|
39
|
+
maxValue: 1,
|
|
40
|
+
automationRate: "k-rate",
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
name: "release",
|
|
44
|
+
defaultValue: 0.1,
|
|
45
|
+
minValue: 0,
|
|
46
|
+
maxValue: 60,
|
|
47
|
+
automationRate: "k-rate",
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
name: "trigger",
|
|
51
|
+
defaultValue: 0,
|
|
52
|
+
minValue: 0,
|
|
53
|
+
maxValue: 1,
|
|
54
|
+
automationRate: "a-rate",
|
|
55
|
+
},
|
|
56
|
+
];
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
process(
|
|
60
|
+
_inputs: Float32Array[][],
|
|
61
|
+
outputs: Float32Array[][],
|
|
62
|
+
parameters: Record<string, Float32Array>,
|
|
63
|
+
) {
|
|
64
|
+
const output = outputs[0];
|
|
65
|
+
if (!output?.[0]) return true;
|
|
66
|
+
|
|
67
|
+
const trigs = parameters.trigger;
|
|
68
|
+
const atk = parameters.attack![0]!;
|
|
69
|
+
const dec = parameters.decay![0]!;
|
|
70
|
+
const sus = parameters.sustain![0]!;
|
|
71
|
+
const rel = parameters.release![0]!;
|
|
72
|
+
const atkmax = 1.01 / Math.max(0.01, parameters.attackcurve![0]!);
|
|
73
|
+
const atkRatio =
|
|
74
|
+
1 - Math.pow(1 - 1 / atkmax, 1 / (sampleRate * atk));
|
|
75
|
+
const decRatio = 1 - Math.pow(0.36787944, 1 / (sampleRate * dec));
|
|
76
|
+
const relRatio = 1 - Math.pow(0.36787944, 1 / (sampleRate * rel));
|
|
77
|
+
|
|
78
|
+
if (trigs?.length === 1) this._trig = trigs[0]!;
|
|
79
|
+
|
|
80
|
+
for (let i = 0; i < output[0].length; ++i) {
|
|
81
|
+
if (trigs && trigs.length > 1) this._trig = trigs[i]!;
|
|
82
|
+
|
|
83
|
+
// Trigger detection
|
|
84
|
+
if (this._trig >= 0.5) {
|
|
85
|
+
if (this._lasttrig < 0.5) this._phase = 1;
|
|
86
|
+
} else {
|
|
87
|
+
this._phase = 0;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Attack phase
|
|
91
|
+
if (this._phase === 1) {
|
|
92
|
+
this._value += (atkmax - this._value) * atkRatio;
|
|
93
|
+
if (this._value >= 1.0) {
|
|
94
|
+
this._value = 1.0;
|
|
95
|
+
this._phase = 0;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
// Decay phase (only while trigger is held)
|
|
99
|
+
else if (this._trig >= 0.5 && this._value > sus) {
|
|
100
|
+
this._value += (sus - this._value) * decRatio;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Release phase
|
|
104
|
+
if (this._trig < 0.5) {
|
|
105
|
+
this._value += -this._value * relRatio;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
for (const channel of output) {
|
|
109
|
+
channel[i] = this._value;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
this._lasttrig = this._trig;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return true;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
registerProcessor("custom-envelope-processor", CustomEnvelopeProcessor);
|
|
120
|
+
}).toString(),
|
|
121
|
+
")()",
|
|
122
|
+
],
|
|
123
|
+
{ type: "application/javascript" },
|
|
124
|
+
),
|
|
125
|
+
);
|
package/src/processors/index.ts
CHANGED
|
@@ -1,15 +1,21 @@
|
|
|
1
1
|
import { assertNever, Context } from "@blibliki/utils";
|
|
2
|
+
import { customEnvelopeProcessorURL } from "./custom-envelope-processor";
|
|
2
3
|
import { filterProcessorURL } from "./filter-processor";
|
|
4
|
+
import { lfoProcessorURL } from "./lfo-processor";
|
|
3
5
|
import { scaleProcessorURL } from "./scale-processor";
|
|
4
6
|
|
|
5
7
|
export enum CustomWorklet {
|
|
6
8
|
ScaleProcessor = "ScaleProcessor",
|
|
7
9
|
FilterProcessor = "FilterProcessor",
|
|
10
|
+
LFOProcessor = "LFOProcessor",
|
|
11
|
+
CustomEnvelopeProcessor = "CustomEnvelopeProcessor",
|
|
8
12
|
}
|
|
9
13
|
|
|
10
14
|
export async function loadProcessors(context: Context) {
|
|
11
15
|
await context.addModule(scaleProcessorURL);
|
|
12
16
|
await context.addModule(filterProcessorURL);
|
|
17
|
+
await context.addModule(lfoProcessorURL);
|
|
18
|
+
await context.addModule(customEnvelopeProcessorURL);
|
|
13
19
|
}
|
|
14
20
|
|
|
15
21
|
export function newAudioWorklet(context: Context, worklet: CustomWorklet) {
|
|
@@ -18,6 +24,10 @@ export function newAudioWorklet(context: Context, worklet: CustomWorklet) {
|
|
|
18
24
|
return context.newAudioWorklet("scale-processor");
|
|
19
25
|
case CustomWorklet.FilterProcessor:
|
|
20
26
|
return context.newAudioWorklet("filter-processor");
|
|
27
|
+
case CustomWorklet.LFOProcessor:
|
|
28
|
+
return context.newAudioWorklet("lfo-processor");
|
|
29
|
+
case CustomWorklet.CustomEnvelopeProcessor:
|
|
30
|
+
return context.newAudioWorklet("custom-envelope-processor");
|
|
21
31
|
default:
|
|
22
32
|
assertNever(worklet);
|
|
23
33
|
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
export const lfoProcessorURL = URL.createObjectURL(
|
|
2
|
+
new Blob(
|
|
3
|
+
[
|
|
4
|
+
"(",
|
|
5
|
+
(() => {
|
|
6
|
+
class LFOProcessor extends AudioWorkletProcessor {
|
|
7
|
+
private phase = 0;
|
|
8
|
+
private randomValue = Math.random() * 2 - 1;
|
|
9
|
+
|
|
10
|
+
static get parameterDescriptors() {
|
|
11
|
+
return [
|
|
12
|
+
{
|
|
13
|
+
name: "frequency",
|
|
14
|
+
defaultValue: 1.0,
|
|
15
|
+
minValue: 0.01,
|
|
16
|
+
maxValue: 100,
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
name: "waveform",
|
|
20
|
+
defaultValue: 0, // 0=sine, 1=triangle, 2=square, 3=sawtooth, 4=rampDown, 5=random
|
|
21
|
+
minValue: 0,
|
|
22
|
+
maxValue: 5,
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
name: "phase",
|
|
26
|
+
defaultValue: 0.0,
|
|
27
|
+
minValue: 0.0,
|
|
28
|
+
maxValue: 1.0,
|
|
29
|
+
},
|
|
30
|
+
];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
process(
|
|
34
|
+
_inputs: Float32Array[][],
|
|
35
|
+
outputs: Float32Array[][],
|
|
36
|
+
parameters: Record<string, Float32Array>,
|
|
37
|
+
) {
|
|
38
|
+
const output = outputs[0];
|
|
39
|
+
if (!output) return true;
|
|
40
|
+
|
|
41
|
+
const frequencyValues = parameters.frequency;
|
|
42
|
+
const waveformValues = parameters.waveform;
|
|
43
|
+
const phaseValues = parameters.phase;
|
|
44
|
+
|
|
45
|
+
if (!frequencyValues || !waveformValues || !phaseValues)
|
|
46
|
+
return true;
|
|
47
|
+
|
|
48
|
+
const blockSize = output[0]?.length ?? 128;
|
|
49
|
+
|
|
50
|
+
for (let i = 0; i < blockSize; i++) {
|
|
51
|
+
// Get parameter values (can be per-sample or per-block)
|
|
52
|
+
const frequency =
|
|
53
|
+
frequencyValues.length > 1
|
|
54
|
+
? (frequencyValues[i] ?? 1.0)
|
|
55
|
+
: (frequencyValues[0] ?? 1.0);
|
|
56
|
+
const waveformIdx = Math.round(
|
|
57
|
+
waveformValues.length > 1
|
|
58
|
+
? (waveformValues[i] ?? 0)
|
|
59
|
+
: (waveformValues[0] ?? 0),
|
|
60
|
+
);
|
|
61
|
+
const phaseOffset =
|
|
62
|
+
phaseValues.length > 1
|
|
63
|
+
? (phaseValues[i] ?? 0)
|
|
64
|
+
: (phaseValues[0] ?? 0);
|
|
65
|
+
|
|
66
|
+
// Calculate current phase with offset
|
|
67
|
+
const currentPhase = (this.phase + phaseOffset) % 1.0;
|
|
68
|
+
|
|
69
|
+
// Generate sample based on waveform type
|
|
70
|
+
let sample = 0;
|
|
71
|
+
switch (waveformIdx) {
|
|
72
|
+
case 0: // Sine
|
|
73
|
+
sample = Math.sin(2 * Math.PI * currentPhase);
|
|
74
|
+
break;
|
|
75
|
+
case 1: // Triangle
|
|
76
|
+
sample = 2 * Math.abs(2 * currentPhase - 1) - 1;
|
|
77
|
+
break;
|
|
78
|
+
case 2: // Square
|
|
79
|
+
sample = currentPhase < 0.5 ? 1 : -1;
|
|
80
|
+
break;
|
|
81
|
+
case 3: // Sawtooth
|
|
82
|
+
sample = 2 * currentPhase - 1;
|
|
83
|
+
break;
|
|
84
|
+
case 4: // Ramp Down
|
|
85
|
+
sample = 1 - 2 * currentPhase;
|
|
86
|
+
break;
|
|
87
|
+
case 5: // Random (sample & hold)
|
|
88
|
+
sample = this.randomValue;
|
|
89
|
+
break;
|
|
90
|
+
default:
|
|
91
|
+
sample = Math.sin(2 * Math.PI * currentPhase);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Write to all output channels
|
|
95
|
+
for (const channel of output) {
|
|
96
|
+
channel[i] = sample;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Update phase
|
|
100
|
+
const phaseIncrement = frequency / sampleRate;
|
|
101
|
+
this.phase += phaseIncrement;
|
|
102
|
+
|
|
103
|
+
// Wrap phase and update random value on wrap
|
|
104
|
+
if (this.phase >= 1.0) {
|
|
105
|
+
this.phase -= 1.0;
|
|
106
|
+
// Update random value only for random waveform
|
|
107
|
+
if (waveformIdx === 5) {
|
|
108
|
+
this.randomValue = Math.random() * 2 - 1;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return true;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
registerProcessor("lfo-processor", LFOProcessor);
|
|
118
|
+
}).toString(),
|
|
119
|
+
")()",
|
|
120
|
+
],
|
|
121
|
+
{ type: "application/javascript" },
|
|
122
|
+
),
|
|
123
|
+
);
|
|
@@ -18,6 +18,10 @@ export const scaleProcessorURL = URL.createObjectURL(
|
|
|
18
18
|
name: "current",
|
|
19
19
|
defaultValue: 0.5,
|
|
20
20
|
},
|
|
21
|
+
{
|
|
22
|
+
name: "mode",
|
|
23
|
+
defaultValue: 0, // 0 = exponential, 1 = linear
|
|
24
|
+
},
|
|
21
25
|
];
|
|
22
26
|
}
|
|
23
27
|
|
|
@@ -33,7 +37,9 @@ export const scaleProcessorURL = URL.createObjectURL(
|
|
|
33
37
|
const minValues = parameters.min;
|
|
34
38
|
const maxValues = parameters.max;
|
|
35
39
|
const currentValues = parameters.current;
|
|
36
|
-
|
|
40
|
+
const modeValues = parameters.mode;
|
|
41
|
+
if (!minValues || !maxValues || !currentValues || !modeValues)
|
|
42
|
+
return true;
|
|
37
43
|
|
|
38
44
|
const firstInput = input[0];
|
|
39
45
|
if (!firstInput || firstInput.length === 0) {
|
|
@@ -70,18 +76,49 @@ export const scaleProcessorURL = URL.createObjectURL(
|
|
|
70
76
|
currentValues.length > 1
|
|
71
77
|
? (currentValues[i] ?? currentValues[0])
|
|
72
78
|
: currentValues[0];
|
|
79
|
+
const mode =
|
|
80
|
+
modeValues.length > 1
|
|
81
|
+
? (modeValues[i] ?? modeValues[0])
|
|
82
|
+
: modeValues[0];
|
|
73
83
|
|
|
74
84
|
if (
|
|
75
85
|
min === undefined ||
|
|
76
86
|
max === undefined ||
|
|
77
|
-
current === undefined
|
|
87
|
+
current === undefined ||
|
|
88
|
+
mode === undefined
|
|
78
89
|
)
|
|
79
90
|
continue;
|
|
80
91
|
|
|
81
|
-
|
|
82
|
-
|
|
92
|
+
const isLinearMode = mode >= 0.5; // 0 = exponential, 1 = linear
|
|
93
|
+
|
|
94
|
+
if (isLinearMode) {
|
|
95
|
+
// Linear interpolation
|
|
96
|
+
if (x < 0) {
|
|
97
|
+
outputChannel[i] = current + -x * (min - current);
|
|
98
|
+
} else {
|
|
99
|
+
outputChannel[i] = current + x * (max - current);
|
|
100
|
+
}
|
|
83
101
|
} else {
|
|
84
|
-
|
|
102
|
+
// Exponential scaling
|
|
103
|
+
// Handle edge cases where exponential would fail
|
|
104
|
+
if (
|
|
105
|
+
current === 0 ||
|
|
106
|
+
(x < 0 && min === 0) ||
|
|
107
|
+
(x > 0 && max === 0)
|
|
108
|
+
) {
|
|
109
|
+
// Fallback to linear for invalid exponential cases
|
|
110
|
+
if (x < 0) {
|
|
111
|
+
outputChannel[i] = current + -x * (min - current);
|
|
112
|
+
} else {
|
|
113
|
+
outputChannel[i] = current + x * (max - current);
|
|
114
|
+
}
|
|
115
|
+
} else {
|
|
116
|
+
if (x < 0) {
|
|
117
|
+
outputChannel[i] = current * Math.pow(min / current, -x);
|
|
118
|
+
} else {
|
|
119
|
+
outputChannel[i] = current * Math.pow(max / current, x);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
85
122
|
}
|
|
86
123
|
}
|
|
87
124
|
}
|