@blibliki/engine 0.1.27 → 0.3.1
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 +16 -27
- 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,233 @@
|
|
|
1
|
+
import { Optional, upperFirst, uuidv4 } from "@blibliki/utils";
|
|
2
|
+
import { Engine } from "@/Engine";
|
|
3
|
+
import { AnyModule, ModuleType, ModuleTypeToPropsMapping } from "@/modules";
|
|
4
|
+
import { IAnyAudioContext } from "../";
|
|
5
|
+
import {
|
|
6
|
+
AudioInputProps,
|
|
7
|
+
AudioOutputProps,
|
|
8
|
+
IIOSerialize,
|
|
9
|
+
IOType,
|
|
10
|
+
InputCollection,
|
|
11
|
+
OutputCollection,
|
|
12
|
+
MidiInputProps,
|
|
13
|
+
MidiOutputProps,
|
|
14
|
+
} from "../IO";
|
|
15
|
+
import Note from "../Note";
|
|
16
|
+
import { TTime } from "../Timing/Time";
|
|
17
|
+
import MidiEvent, { MidiEventType } from "../midi/MidiEvent";
|
|
18
|
+
|
|
19
|
+
export type IModule<T extends ModuleType> = {
|
|
20
|
+
id: string;
|
|
21
|
+
name: string;
|
|
22
|
+
voiceNo: number;
|
|
23
|
+
moduleType: T;
|
|
24
|
+
props: ModuleTypeToPropsMapping[T];
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export type IModuleSerialize<T extends ModuleType> = IModule<T> & {
|
|
28
|
+
inputs: IIOSerialize[];
|
|
29
|
+
outputs: IIOSerialize[];
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export type IModuleConstructor<T extends ModuleType> = Optional<
|
|
33
|
+
IModule<T>,
|
|
34
|
+
"id" | "voiceNo"
|
|
35
|
+
> & {
|
|
36
|
+
audioNodeConstructor?: (context: IAnyAudioContext) => AudioNode;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export abstract class Module<T extends ModuleType> implements IModule<T> {
|
|
40
|
+
id: string;
|
|
41
|
+
engineId: string;
|
|
42
|
+
name: string;
|
|
43
|
+
moduleType: T;
|
|
44
|
+
voiceNo: number;
|
|
45
|
+
audioNode: AudioNode | undefined;
|
|
46
|
+
inputs: InputCollection;
|
|
47
|
+
outputs: OutputCollection;
|
|
48
|
+
protected _props!: ModuleTypeToPropsMapping[T];
|
|
49
|
+
protected superInitialized = false;
|
|
50
|
+
protected activeNotes: Note[];
|
|
51
|
+
|
|
52
|
+
constructor(engineId: string, params: IModuleConstructor<T>) {
|
|
53
|
+
const { id, name, moduleType, voiceNo, audioNodeConstructor, props } =
|
|
54
|
+
params;
|
|
55
|
+
|
|
56
|
+
this.id = id ?? uuidv4();
|
|
57
|
+
this.engineId = engineId;
|
|
58
|
+
this.name = name;
|
|
59
|
+
this.moduleType = moduleType;
|
|
60
|
+
this.voiceNo = voiceNo ?? 0;
|
|
61
|
+
this.activeNotes = [];
|
|
62
|
+
this.audioNode = audioNodeConstructor?.(this.context);
|
|
63
|
+
this._props = {} as ModuleTypeToPropsMapping[T];
|
|
64
|
+
this.props = props;
|
|
65
|
+
|
|
66
|
+
this.inputs = new InputCollection(this);
|
|
67
|
+
this.outputs = new OutputCollection(this);
|
|
68
|
+
|
|
69
|
+
this.superInitialized = true;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
get props(): ModuleTypeToPropsMapping[T] {
|
|
73
|
+
return this._props;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
set props(value: Partial<ModuleTypeToPropsMapping[T]>) {
|
|
77
|
+
Object.keys(value).forEach((key) => {
|
|
78
|
+
const onSetAttr = `onSet${upperFirst(key)}`;
|
|
79
|
+
|
|
80
|
+
// @ts-expect-error TS7053 ignore this error
|
|
81
|
+
// eslint-disable-next-line
|
|
82
|
+
this[onSetAttr]?.(value[key]);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
this._props = { ...this._props, ...value };
|
|
86
|
+
|
|
87
|
+
Object.keys(value).forEach((key) => {
|
|
88
|
+
const onSetAttr = `onAfterSet${upperFirst(key)}`;
|
|
89
|
+
|
|
90
|
+
// @ts-expect-error TS7053 ignore this error
|
|
91
|
+
// eslint-disable-next-line
|
|
92
|
+
this[onSetAttr]?.(value[key]);
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
serialize(): IModuleSerialize<T> {
|
|
97
|
+
return {
|
|
98
|
+
id: this.id,
|
|
99
|
+
name: this.name,
|
|
100
|
+
moduleType: this.moduleType,
|
|
101
|
+
voiceNo: this.voiceNo,
|
|
102
|
+
props: this.props,
|
|
103
|
+
inputs: this.inputs.serialize(),
|
|
104
|
+
outputs: this.outputs.serialize(),
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
plug({
|
|
109
|
+
audioModule,
|
|
110
|
+
from,
|
|
111
|
+
to,
|
|
112
|
+
}: {
|
|
113
|
+
audioModule: AnyModule;
|
|
114
|
+
from: string;
|
|
115
|
+
to: string;
|
|
116
|
+
}) {
|
|
117
|
+
const output = this.outputs.findByName(from);
|
|
118
|
+
const input = audioModule.inputs.findByName(to);
|
|
119
|
+
|
|
120
|
+
output.plug(input);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
protected rePlugAll(callback?: () => void) {
|
|
124
|
+
this.inputs.rePlugAll(callback);
|
|
125
|
+
this.outputs.rePlugAll(callback);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
protected unPlugAll() {
|
|
129
|
+
this.inputs.unPlugAll();
|
|
130
|
+
this.outputs.unPlugAll();
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
start(_time: TTime): void {
|
|
134
|
+
// Optional implementation in modules
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
stop(_time: TTime): void {
|
|
138
|
+
// Optional implementation in modules
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
triggerAttack(note: Note, _triggeredAt: TTime): void {
|
|
142
|
+
if (this.activeNotes.some((n) => n.fullName === note.fullName)) return;
|
|
143
|
+
|
|
144
|
+
this.activeNotes.push(note);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
triggerRelease(note: Note, _triggeredAt: TTime): void {
|
|
148
|
+
this.activeNotes = this.activeNotes.filter(
|
|
149
|
+
(n) => n.fullName !== note.fullName,
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
onMidiEvent = (midiEvent: MidiEvent) => {
|
|
154
|
+
const { note, triggeredAt } = midiEvent;
|
|
155
|
+
|
|
156
|
+
switch (midiEvent.type) {
|
|
157
|
+
case MidiEventType.noteOn: {
|
|
158
|
+
this.triggerAttack(note!, triggeredAt);
|
|
159
|
+
break;
|
|
160
|
+
}
|
|
161
|
+
case MidiEventType.noteOff:
|
|
162
|
+
this.triggerRelease(note!, triggeredAt);
|
|
163
|
+
break;
|
|
164
|
+
default:
|
|
165
|
+
throw Error("This type is not a note");
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
protected triggerPropsUpdate() {
|
|
170
|
+
this.engine._triggerPropsUpdate({
|
|
171
|
+
id: this.id,
|
|
172
|
+
moduleType: this.moduleType,
|
|
173
|
+
voiceNo: this.voiceNo,
|
|
174
|
+
name: this.name,
|
|
175
|
+
props: this.props,
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
dispose() {
|
|
180
|
+
this.inputs.unPlugAll();
|
|
181
|
+
this.outputs.unPlugAll();
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
protected registerDefaultIOs(value: "both" | "in" | "out" = "both") {
|
|
185
|
+
this.registerMidiInput({
|
|
186
|
+
name: "midi in",
|
|
187
|
+
onMidiEvent: this.onMidiEvent,
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
if (!this.audioNode) return;
|
|
191
|
+
|
|
192
|
+
if (value === "in" || value === "both") {
|
|
193
|
+
this.registerAudioInput({
|
|
194
|
+
name: "in",
|
|
195
|
+
getAudioNode: () => this.audioNode!,
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (value === "out" || value === "both") {
|
|
200
|
+
this.registerAudioOutput({
|
|
201
|
+
name: "out",
|
|
202
|
+
getAudioNode: () => this.audioNode!,
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
protected registerAudioInput(props: Omit<AudioInputProps, "ioType">) {
|
|
208
|
+
return this.inputs.add({ ...props, ioType: IOType.AudioInput });
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
protected registerAudioOutput(props: Omit<AudioOutputProps, "ioType">) {
|
|
212
|
+
return this.outputs.add({ ...props, ioType: IOType.AudioOutput });
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
protected registerMidiInput(props: Omit<MidiInputProps, "ioType">) {
|
|
216
|
+
return this.inputs.add({ ...props, ioType: IOType.MidiInput });
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
protected registerMidiOutput(props: Omit<MidiOutputProps, "ioType">) {
|
|
220
|
+
return this.outputs.add({
|
|
221
|
+
...props,
|
|
222
|
+
ioType: IOType.MidiOutput,
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
protected get engine() {
|
|
227
|
+
return Engine.getById(this.engineId);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
protected get context() {
|
|
231
|
+
return this.engine.context;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
import { deterministicId, Optional, uuidv4 } from "@blibliki/utils";
|
|
2
|
+
import { Engine } from "@/Engine";
|
|
3
|
+
import { ModuleType, ModuleTypeToPropsMapping } from "@/modules";
|
|
4
|
+
import {
|
|
5
|
+
IIOSerialize,
|
|
6
|
+
InputCollection,
|
|
7
|
+
IOType,
|
|
8
|
+
MidiInputProps,
|
|
9
|
+
MidiOutputProps,
|
|
10
|
+
OutputCollection,
|
|
11
|
+
} from "../IO";
|
|
12
|
+
import { PolyAudioInputProps, PolyAudioOutputProps } from "../IO/PolyAudioIO";
|
|
13
|
+
import { TTime } from "../Timing";
|
|
14
|
+
import MidiEvent from "../midi/MidiEvent";
|
|
15
|
+
import { IModule, IModuleConstructor, Module } from "./Module";
|
|
16
|
+
|
|
17
|
+
export type IPolyModule<T extends ModuleType> = Omit<IModule<T>, "voiceNo"> & {
|
|
18
|
+
voices: number;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export type IPolyModuleSerialize<T extends ModuleType> = IPolyModule<T> & {
|
|
22
|
+
inputs: IIOSerialize[];
|
|
23
|
+
outputs: IIOSerialize[];
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export type IPolyModuleConstructor<T extends ModuleType> = Optional<
|
|
27
|
+
IPolyModule<T>,
|
|
28
|
+
"id"
|
|
29
|
+
> & {
|
|
30
|
+
monoModuleConstructor: (
|
|
31
|
+
engineId: string,
|
|
32
|
+
params: IModuleConstructor<T>,
|
|
33
|
+
) => Module<T>;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export abstract class PolyModule<T extends ModuleType>
|
|
37
|
+
implements IPolyModule<T>
|
|
38
|
+
{
|
|
39
|
+
id: string;
|
|
40
|
+
engineId: string;
|
|
41
|
+
moduleType: T;
|
|
42
|
+
audioModules!: Module<T>[];
|
|
43
|
+
inputs: InputCollection;
|
|
44
|
+
outputs: OutputCollection;
|
|
45
|
+
protected monoModuleConstructor: IPolyModuleConstructor<T>["monoModuleConstructor"];
|
|
46
|
+
protected _props!: ModuleTypeToPropsMapping[T];
|
|
47
|
+
protected superInitialized = false;
|
|
48
|
+
private _voices!: number;
|
|
49
|
+
private _name!: string;
|
|
50
|
+
|
|
51
|
+
constructor(engineId: string, params: IPolyModuleConstructor<T>) {
|
|
52
|
+
const { id, name, moduleType, voices, monoModuleConstructor, props } =
|
|
53
|
+
params;
|
|
54
|
+
|
|
55
|
+
this.audioModules = [];
|
|
56
|
+
|
|
57
|
+
this.monoModuleConstructor = monoModuleConstructor;
|
|
58
|
+
this.id = id ?? uuidv4();
|
|
59
|
+
this.engineId = engineId;
|
|
60
|
+
this.name = name;
|
|
61
|
+
this.moduleType = moduleType;
|
|
62
|
+
this.voices = voices || 1;
|
|
63
|
+
this._props = {} as ModuleTypeToPropsMapping[T];
|
|
64
|
+
this.props = props;
|
|
65
|
+
|
|
66
|
+
this.inputs = new InputCollection(
|
|
67
|
+
this as unknown as PolyModule<ModuleType>,
|
|
68
|
+
);
|
|
69
|
+
this.outputs = new OutputCollection(
|
|
70
|
+
this as unknown as PolyModule<ModuleType>,
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
this.superInitialized = true;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
get name() {
|
|
77
|
+
return this._name;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
set name(value: string) {
|
|
81
|
+
this._name = value;
|
|
82
|
+
this.audioModules.forEach((m) => (m.name = value));
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
get props(): ModuleTypeToPropsMapping[T] {
|
|
86
|
+
return this._props;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
set props(value: Partial<ModuleTypeToPropsMapping[T]>) {
|
|
90
|
+
this._props = { ...this._props, ...value };
|
|
91
|
+
this.audioModules.forEach((m) => (m.props = value));
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
get voices() {
|
|
95
|
+
return this._voices;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
set voices(value: number) {
|
|
99
|
+
this._voices = value;
|
|
100
|
+
this.adjustNumberOfModules();
|
|
101
|
+
this.rePlugAll();
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
start(time: TTime): void {
|
|
105
|
+
this.audioModules.forEach((m) => {
|
|
106
|
+
m.start(time);
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
stop(time: TTime): void {
|
|
111
|
+
this.audioModules.forEach((m) => {
|
|
112
|
+
m.stop(time);
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
serialize(): IPolyModuleSerialize<T> {
|
|
117
|
+
return {
|
|
118
|
+
id: this.id,
|
|
119
|
+
name: this.name,
|
|
120
|
+
moduleType: this.moduleType,
|
|
121
|
+
voices: this.voices,
|
|
122
|
+
props: this.props,
|
|
123
|
+
inputs: this.inputs.serialize(),
|
|
124
|
+
outputs: this.outputs.serialize(),
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
plug({
|
|
129
|
+
audioModule,
|
|
130
|
+
from,
|
|
131
|
+
to,
|
|
132
|
+
}: {
|
|
133
|
+
audioModule: Module<ModuleType> | PolyModule<ModuleType>;
|
|
134
|
+
from: string;
|
|
135
|
+
to: string;
|
|
136
|
+
}) {
|
|
137
|
+
const output = this.outputs.findByName(from);
|
|
138
|
+
const input = audioModule.inputs.findByName(to);
|
|
139
|
+
|
|
140
|
+
output.plug(input);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
rePlugAll(callback?: () => void) {
|
|
144
|
+
if (!this.superInitialized) return;
|
|
145
|
+
|
|
146
|
+
this.inputs.rePlugAll(callback);
|
|
147
|
+
this.outputs.rePlugAll(callback);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
protected unPlugAll() {
|
|
151
|
+
this.inputs.unPlugAll();
|
|
152
|
+
this.outputs.unPlugAll();
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
dispose() {
|
|
156
|
+
this.inputs.unPlugAll();
|
|
157
|
+
this.outputs.unPlugAll();
|
|
158
|
+
this.audioModules.forEach((m) => {
|
|
159
|
+
m.dispose();
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
onMidiEvent = (midiEvent: MidiEvent) => {
|
|
164
|
+
const voiceNo = midiEvent.voiceNo ?? 0;
|
|
165
|
+
const audioModule = this.findVoice(voiceNo);
|
|
166
|
+
audioModule.onMidiEvent(midiEvent);
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
findVoice(voiceNo: number) {
|
|
170
|
+
const moduleByVoice = this.audioModules.find((m) => m.voiceNo === voiceNo);
|
|
171
|
+
if (!moduleByVoice)
|
|
172
|
+
throw Error(`Voice ${voiceNo} on module ${this.name} not found`);
|
|
173
|
+
|
|
174
|
+
return moduleByVoice;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
protected registerDefaultIOs(value: "both" | "in" | "out" = "both") {
|
|
178
|
+
this.registerMidiInput({
|
|
179
|
+
name: "midi in",
|
|
180
|
+
onMidiEvent: this.onMidiEvent,
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
if (value === "in" || value === "both") {
|
|
184
|
+
this.registerAudioInput({
|
|
185
|
+
name: "in",
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (value === "out" || value === "both") {
|
|
190
|
+
this.registerAudioOutput({
|
|
191
|
+
name: "out",
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
protected registerAudioInput(props: Omit<PolyAudioInputProps, "ioType">) {
|
|
197
|
+
return this.inputs.add({ ...props, ioType: IOType.PolyAudioInput });
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
protected registerAudioOutput(props: Omit<PolyAudioOutputProps, "ioType">) {
|
|
201
|
+
return this.outputs.add({ ...props, ioType: IOType.PolyAudioOutput });
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
protected registerMidiInput(props: Omit<MidiInputProps, "ioType">) {
|
|
205
|
+
return this.inputs.add({ ...props, ioType: IOType.MidiInput });
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
protected registerMidiOutput(props: Omit<MidiOutputProps, "ioType">) {
|
|
209
|
+
return this.outputs.add({
|
|
210
|
+
...props,
|
|
211
|
+
ioType: IOType.MidiOutput,
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
private adjustNumberOfModules() {
|
|
216
|
+
if (this.audioModules.length === this.voices) return;
|
|
217
|
+
|
|
218
|
+
if (this.audioModules.length > this.voices) {
|
|
219
|
+
const audioModule = this.audioModules.pop();
|
|
220
|
+
audioModule?.dispose();
|
|
221
|
+
} else {
|
|
222
|
+
const voiceNo = this.audioModules.length;
|
|
223
|
+
const id = deterministicId(this.id, voiceNo.toString());
|
|
224
|
+
|
|
225
|
+
const audioModule = this.monoModuleConstructor(this.engineId, {
|
|
226
|
+
id,
|
|
227
|
+
name: this.name,
|
|
228
|
+
moduleType: this.moduleType,
|
|
229
|
+
voiceNo,
|
|
230
|
+
props: { ...this.props },
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
this.audioModules.push(audioModule);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
this.adjustNumberOfModules();
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
protected get engine() {
|
|
240
|
+
return Engine.getById(this.engineId);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
protected get context() {
|
|
244
|
+
return this.engine.context;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { EmptyObject } from "@blibliki/utils";
|
|
2
|
+
import { ICreateModule, ModuleType } from "@/modules";
|
|
3
|
+
import { MidiOutput } from "../IO";
|
|
4
|
+
import { nt, t, TTime } from "../Timing/Time";
|
|
5
|
+
import MidiEvent, { MidiEventType } from "../midi/MidiEvent";
|
|
6
|
+
import { PropSchema } from "../schema";
|
|
7
|
+
import { IModuleConstructor, Module } from "./Module";
|
|
8
|
+
import { IPolyModuleConstructor, PolyModule } from "./PolyModule";
|
|
9
|
+
|
|
10
|
+
export type IVoiceSchedulerProps = EmptyObject;
|
|
11
|
+
export const voiceSchedulerPropSchema: PropSchema<IVoiceSchedulerProps> = {};
|
|
12
|
+
const DEFAULT_PROPS = {};
|
|
13
|
+
|
|
14
|
+
class Voice extends Module<ModuleType.VoiceScheduler> {
|
|
15
|
+
declare audioNode: undefined;
|
|
16
|
+
activeNote: string | null = null;
|
|
17
|
+
triggeredAt: TTime = t(0);
|
|
18
|
+
|
|
19
|
+
constructor(
|
|
20
|
+
engineId: string,
|
|
21
|
+
params: ICreateModule<ModuleType.VoiceScheduler>,
|
|
22
|
+
) {
|
|
23
|
+
const props = { ...DEFAULT_PROPS, ...params.props };
|
|
24
|
+
|
|
25
|
+
super(engineId, {
|
|
26
|
+
...params,
|
|
27
|
+
props,
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
midiTriggered = (midiEvent: MidiEvent) => {
|
|
32
|
+
const { triggeredAt, note, type } = midiEvent;
|
|
33
|
+
|
|
34
|
+
if (!note) return;
|
|
35
|
+
const noteName = note.fullName;
|
|
36
|
+
|
|
37
|
+
switch (type) {
|
|
38
|
+
case MidiEventType.noteOn:
|
|
39
|
+
this.activeNote = noteName;
|
|
40
|
+
this.triggeredAt = triggeredAt;
|
|
41
|
+
|
|
42
|
+
break;
|
|
43
|
+
case MidiEventType.noteOff:
|
|
44
|
+
this.activeNote = null;
|
|
45
|
+
break;
|
|
46
|
+
default:
|
|
47
|
+
throw Error("This type is not a note");
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export default class VoiceScheduler extends PolyModule<ModuleType.VoiceScheduler> {
|
|
53
|
+
declare audioModules: Voice[];
|
|
54
|
+
midiOutput!: MidiOutput;
|
|
55
|
+
|
|
56
|
+
constructor(
|
|
57
|
+
engineId: string,
|
|
58
|
+
params: IPolyModuleConstructor<ModuleType.VoiceScheduler>,
|
|
59
|
+
) {
|
|
60
|
+
const props = { ...DEFAULT_PROPS, ...params.props };
|
|
61
|
+
const monoModuleConstructor = (
|
|
62
|
+
engineId: string,
|
|
63
|
+
params: IModuleConstructor<ModuleType.VoiceScheduler>,
|
|
64
|
+
) => new Voice(engineId, params);
|
|
65
|
+
|
|
66
|
+
super(engineId, {
|
|
67
|
+
...params,
|
|
68
|
+
props,
|
|
69
|
+
monoModuleConstructor,
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
this.registerInputs();
|
|
73
|
+
this.registerOutputs();
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
onMidiEvent = (midiEvent: MidiEvent) => {
|
|
77
|
+
let voice: Voice | undefined;
|
|
78
|
+
|
|
79
|
+
switch (midiEvent.type) {
|
|
80
|
+
case MidiEventType.noteOn:
|
|
81
|
+
voice = this.findFreeVoice();
|
|
82
|
+
|
|
83
|
+
break;
|
|
84
|
+
case MidiEventType.noteOff:
|
|
85
|
+
voice = this.audioModules.find(
|
|
86
|
+
(v) => v.activeNote === midiEvent.note!.fullName,
|
|
87
|
+
);
|
|
88
|
+
break;
|
|
89
|
+
default:
|
|
90
|
+
throw Error("This type is not a note");
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (!voice) return;
|
|
94
|
+
|
|
95
|
+
voice.midiTriggered(midiEvent);
|
|
96
|
+
midiEvent.voiceNo = voice.voiceNo;
|
|
97
|
+
this.midiOutput.onMidiEvent(midiEvent);
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
private findFreeVoice(): Voice {
|
|
101
|
+
let voice = this.audioModules.find((v) => !v.activeNote);
|
|
102
|
+
|
|
103
|
+
// If no available voice, get the one with the lowest triggeredAt
|
|
104
|
+
voice ??= this.audioModules.sort((a, b) => {
|
|
105
|
+
return nt(a.triggeredAt) - nt(b.triggeredAt);
|
|
106
|
+
})[0];
|
|
107
|
+
|
|
108
|
+
return voice;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
private registerInputs() {
|
|
112
|
+
this.registerMidiInput({
|
|
113
|
+
name: "midi in",
|
|
114
|
+
onMidiEvent: this.onMidiEvent,
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
private registerOutputs() {
|
|
119
|
+
this.midiOutput = this.registerMidiOutput({ name: "midi out" });
|
|
120
|
+
}
|
|
121
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
type BasePropType = {
|
|
2
|
+
label?: string;
|
|
3
|
+
description?: string;
|
|
4
|
+
};
|
|
5
|
+
|
|
6
|
+
export type NumberProp = BasePropType & {
|
|
7
|
+
kind: "number";
|
|
8
|
+
min?: number;
|
|
9
|
+
max?: number;
|
|
10
|
+
step?: number;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export type EnumProp<T extends string | number> = BasePropType & {
|
|
14
|
+
kind: "enum";
|
|
15
|
+
options: T[];
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export type StringProp = BasePropType & {
|
|
19
|
+
kind: "string";
|
|
20
|
+
pattern?: RegExp;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export type BooleanProp = BasePropType & {
|
|
24
|
+
kind: "boolean";
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export type ArrayProp = BasePropType & {
|
|
28
|
+
kind: "array";
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export type PropDefinition<T> = T extends number
|
|
32
|
+
? NumberProp | EnumProp<number>
|
|
33
|
+
: T extends boolean
|
|
34
|
+
? BooleanProp
|
|
35
|
+
: T extends string
|
|
36
|
+
? StringProp | EnumProp<string>
|
|
37
|
+
: T extends (string | number)[]
|
|
38
|
+
? ArrayProp
|
|
39
|
+
: never;
|
|
40
|
+
|
|
41
|
+
export type PropSchema<T> = { [K in keyof T]: PropDefinition<T[K]> };
|
package/src/index.ts
CHANGED
|
@@ -1,11 +1,33 @@
|
|
|
1
|
-
export {
|
|
2
|
-
export {
|
|
3
|
-
export { default as Note } from "./core/Note";
|
|
1
|
+
export { Engine } from "./Engine";
|
|
2
|
+
export type { ICreateRoute, IUpdateModule } from "./Engine";
|
|
4
3
|
|
|
5
|
-
export type {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
4
|
+
export type {
|
|
5
|
+
IRoute,
|
|
6
|
+
IIOSerialize,
|
|
7
|
+
IModule,
|
|
8
|
+
IModuleSerialize,
|
|
9
|
+
IPolyModuleSerialize,
|
|
10
|
+
IMidiDevice,
|
|
11
|
+
PropDefinition,
|
|
12
|
+
PropSchema,
|
|
13
|
+
StringProp,
|
|
14
|
+
NumberProp,
|
|
15
|
+
EnumProp,
|
|
16
|
+
BooleanProp,
|
|
17
|
+
ArrayProp,
|
|
18
|
+
INote,
|
|
19
|
+
} from "./core";
|
|
20
|
+
export { TransportState, MidiDevice, MidiPortState, Note } from "./core";
|
|
9
21
|
|
|
10
|
-
export
|
|
11
|
-
export type {
|
|
22
|
+
export { ModuleType, moduleSchemas, OscillatorWave } from "./modules";
|
|
23
|
+
export type {
|
|
24
|
+
IOscillator,
|
|
25
|
+
IGain,
|
|
26
|
+
IMaster,
|
|
27
|
+
ISequence,
|
|
28
|
+
IStepSequencerProps,
|
|
29
|
+
IStepSequencer,
|
|
30
|
+
ModuleTypeToPropsMapping,
|
|
31
|
+
ICreateModule,
|
|
32
|
+
ModuleParams,
|
|
33
|
+
} from "./modules";
|