@blibliki/engine 0.5.1 → 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.
Files changed (47) hide show
  1. package/README.md +22 -2
  2. package/dist/index.d.ts +501 -107
  3. package/dist/index.js +1 -1
  4. package/dist/index.js.map +1 -1
  5. package/package.json +7 -7
  6. package/src/Engine.ts +46 -29
  7. package/src/core/index.ts +11 -2
  8. package/src/core/midi/BaseMidiDevice.ts +47 -0
  9. package/src/core/midi/ComputerKeyboardDevice.ts +2 -1
  10. package/src/core/midi/MidiDeviceManager.ts +125 -31
  11. package/src/core/midi/{MidiDevice.ts → MidiInputDevice.ts} +6 -30
  12. package/src/core/midi/MidiOutputDevice.ts +23 -0
  13. package/src/core/midi/adapters/NodeMidiAdapter.ts +99 -13
  14. package/src/core/midi/adapters/WebMidiAdapter.ts +68 -10
  15. package/src/core/midi/adapters/types.ts +13 -4
  16. package/src/core/midi/controllers/BaseController.ts +14 -0
  17. package/src/core/module/Module.ts +121 -13
  18. package/src/core/module/PolyModule.ts +36 -0
  19. package/src/core/module/VoiceScheduler.ts +150 -10
  20. package/src/core/module/index.ts +9 -4
  21. package/src/index.ts +27 -3
  22. package/src/modules/Chorus.ts +222 -0
  23. package/src/modules/Constant.ts +2 -2
  24. package/src/modules/Delay.ts +347 -0
  25. package/src/modules/Distortion.ts +182 -0
  26. package/src/modules/Envelope.ts +158 -92
  27. package/src/modules/Filter.ts +7 -7
  28. package/src/modules/Gain.ts +2 -2
  29. package/src/modules/LFO.ts +287 -0
  30. package/src/modules/LegacyEnvelope.ts +146 -0
  31. package/src/modules/{MidiSelector.ts → MidiInput.ts} +26 -19
  32. package/src/modules/MidiMapper.ts +59 -4
  33. package/src/modules/MidiOutput.ts +121 -0
  34. package/src/modules/Noise.ts +259 -0
  35. package/src/modules/Oscillator.ts +9 -3
  36. package/src/modules/Reverb.ts +379 -0
  37. package/src/modules/Scale.ts +49 -4
  38. package/src/modules/StepSequencer.ts +410 -22
  39. package/src/modules/StereoPanner.ts +1 -1
  40. package/src/modules/index.ts +142 -29
  41. package/src/processors/custom-envelope-processor.ts +125 -0
  42. package/src/processors/index.ts +10 -0
  43. package/src/processors/lfo-processor.ts +123 -0
  44. package/src/processors/scale-processor.ts +42 -5
  45. package/src/utils/WetDryMixer.ts +123 -0
  46. package/src/utils/expandPatternSequence.ts +18 -0
  47. package/src/utils/index.ts +2 -0
@@ -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 Envelope, { envelopePropSchema, IEnvelopeProps } from "./Envelope";
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 MidiSelector, {
19
- IMidiSelectorProps,
20
- midiSelectorPropSchema,
21
- } from "./MidiSelector";
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
- MidiSelector = "MidiSelector",
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.MidiSelector]: IMidiSelectorProps;
62
- [ModuleType.Envelope]: IEnvelopeProps;
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.MidiSelector]: MidiSelector;
79
- [ModuleType.Envelope]: Envelope;
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.MidiSelector]: midiSelectorPropSchema,
96
- [ModuleType.Envelope]: envelopePropSchema,
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 { IMidiSelector } from "./MidiSelector";
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
- ISequence,
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 new Oscillator(engineId, params);
246
+ return Oscillator.create(Oscillator, engineId, params);
151
247
  case ModuleType.Gain:
152
- return new Gain(engineId, params);
248
+ return Gain.create(Gain, engineId, params);
153
249
  case ModuleType.Master:
154
- return new Master(engineId, params);
155
- case ModuleType.MidiSelector:
156
- return new MidiSelector(engineId, params);
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 new Envelope(engineId, params);
259
+ return CustomEnvelope.create(CustomEnvelope, engineId, params);
159
260
  case ModuleType.Filter:
160
- return new Filter(engineId, params);
261
+ return Filter.create(Filter, engineId, params);
161
262
  case ModuleType.Scale:
162
- return new Scale(engineId, params);
263
+ return Scale.create(Scale, engineId, params);
163
264
  case ModuleType.StereoPanner:
164
- return new StereoPanner(engineId, params);
265
+ return StereoPanner.create(StereoPanner, engineId, params);
165
266
  case ModuleType.Inspector:
166
- return new Inspector(engineId, params);
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 new Constant(engineId, params);
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 new MidiMapper(engineId, params);
277
+ return MidiMapper.create(MidiMapper, engineId, params);
171
278
  case ModuleType.VirtualMidi:
172
- return new VirtualMidi(engineId, params);
279
+ return VirtualMidi.create(VirtualMidi, engineId, params);
173
280
  case ModuleType.StepSequencer:
174
- return new StepSequencer(engineId, params);
281
+ return StepSequencer.create(StepSequencer, engineId, params);
175
282
  case ModuleType.VoiceScheduler:
176
- return new VoiceScheduler(engineId, params);
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
+ );
@@ -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
- if (!minValues || !maxValues || !currentValues) return true;
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
- if (x < 0) {
82
- outputChannel[i] = current * Math.pow(min / current, -x);
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
- outputChannel[i] = current * Math.pow(max / current, x);
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
  }