@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
package/src/Engine.ts
CHANGED
|
@@ -1,245 +1,226 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
|
|
1
|
+
import { assertDefined, Optional, pick, uuidv4 } from "@blibliki/utils";
|
|
2
|
+
import {
|
|
3
|
+
IAnyAudioContext,
|
|
4
|
+
IRoute,
|
|
5
|
+
Routes,
|
|
6
|
+
MidiDeviceManager,
|
|
7
|
+
IModule,
|
|
8
|
+
TTime,
|
|
9
|
+
Transport,
|
|
10
|
+
MidiEvent,
|
|
11
|
+
} from "@/core";
|
|
12
|
+
import {
|
|
13
|
+
ICreateModule,
|
|
14
|
+
ModuleParams,
|
|
15
|
+
ModuleType,
|
|
16
|
+
ModuleTypeToModuleMapping,
|
|
17
|
+
createModule,
|
|
18
|
+
} from "@/modules";
|
|
19
|
+
import { PolyModule } from "./core/module/PolyModule";
|
|
20
|
+
import { loadProcessors } from "./processors";
|
|
21
|
+
|
|
22
|
+
export type IUpdateModule<T extends ModuleType> = {
|
|
21
23
|
id: string;
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
props?: AnyObject;
|
|
24
|
+
moduleType: T;
|
|
25
|
+
changes: Partial<Omit<ICreateModule<T>, "id" | "moduleType" | "voice">> & {
|
|
26
|
+
voices?: number;
|
|
26
27
|
};
|
|
27
|
-
}
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export type ICreateRoute = Optional<IRoute, "id">;
|
|
31
|
+
|
|
32
|
+
export class Engine {
|
|
33
|
+
private static _engines = new Map<string, Engine>();
|
|
34
|
+
private static _currentId: string;
|
|
35
|
+
private propsUpdateCallbacks: (<T extends ModuleType>(
|
|
36
|
+
params: IModule<T>,
|
|
37
|
+
) => void)[] = [];
|
|
38
|
+
|
|
39
|
+
readonly id: string;
|
|
40
|
+
context: IAnyAudioContext;
|
|
41
|
+
isInitialized = false;
|
|
42
|
+
routes: Routes;
|
|
43
|
+
transport: Transport;
|
|
44
|
+
modules: Map<
|
|
45
|
+
string,
|
|
46
|
+
ModuleTypeToModuleMapping[keyof ModuleTypeToModuleMapping]
|
|
47
|
+
>;
|
|
28
48
|
|
|
29
|
-
class Engine {
|
|
30
49
|
midiDeviceManager: MidiDeviceManager;
|
|
31
|
-
private static instance: Engine;
|
|
32
|
-
private context: Context;
|
|
33
|
-
private propsUpdateCallbacks: { (id: string, props: AnyObject): void }[];
|
|
34
|
-
private _isStarted = false;
|
|
35
50
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
routes: {
|
|
41
|
-
[Identifier: string]: RouteInterface;
|
|
42
|
-
};
|
|
51
|
+
static getById(id: string): Engine {
|
|
52
|
+
const engine = Engine._engines.get(id);
|
|
53
|
+
assertDefined(engine);
|
|
43
54
|
|
|
44
|
-
|
|
45
|
-
this.modules = {};
|
|
46
|
-
this.routes = {};
|
|
47
|
-
this.propsUpdateCallbacks = [];
|
|
55
|
+
return engine;
|
|
48
56
|
}
|
|
49
57
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
Engine.instance = new Engine();
|
|
53
|
-
}
|
|
58
|
+
static get current(): Engine {
|
|
59
|
+
assertDefined(this._currentId);
|
|
54
60
|
|
|
55
|
-
return
|
|
61
|
+
return this.getById(this._currentId);
|
|
56
62
|
}
|
|
57
63
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
if (this.context) return resolve({});
|
|
64
|
+
constructor(context: IAnyAudioContext) {
|
|
65
|
+
this.id = uuidv4();
|
|
61
66
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
this.
|
|
65
|
-
|
|
66
|
-
this.midiDeviceManager = new MidiDeviceManager();
|
|
67
|
-
|
|
68
|
-
setTimeout(() => {
|
|
69
|
-
resolve({});
|
|
70
|
-
}, 0);
|
|
67
|
+
this.context = context;
|
|
68
|
+
this.transport = new Transport({
|
|
69
|
+
onStart: this.onStart,
|
|
70
|
+
onStop: this.onStop,
|
|
71
71
|
});
|
|
72
|
+
this.routes = new Routes(this);
|
|
73
|
+
this.modules = new Map();
|
|
74
|
+
this.midiDeviceManager = new MidiDeviceManager();
|
|
75
|
+
|
|
76
|
+
Engine._engines.set(this.id, this);
|
|
77
|
+
Engine._currentId = this.id;
|
|
72
78
|
}
|
|
73
79
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
numberOfVoices?: number;
|
|
78
|
-
type: string;
|
|
79
|
-
props?: AnyObject;
|
|
80
|
-
}) {
|
|
81
|
-
const { id, name, numberOfVoices, type, props = {} } = params;
|
|
82
|
-
|
|
83
|
-
const audioModule = createModule({
|
|
84
|
-
id,
|
|
85
|
-
name,
|
|
86
|
-
type,
|
|
87
|
-
props: {},
|
|
88
|
-
});
|
|
89
|
-
if (audioModule instanceof PolyModule && numberOfVoices) {
|
|
90
|
-
audioModule.numberOfVoices = numberOfVoices;
|
|
91
|
-
}
|
|
92
|
-
audioModule.props = props;
|
|
93
|
-
this.modules[audioModule.id] = audioModule;
|
|
80
|
+
get state() {
|
|
81
|
+
return this.transport.state;
|
|
82
|
+
}
|
|
94
83
|
|
|
95
|
-
|
|
84
|
+
async initialize() {
|
|
85
|
+
if (this.isInitialized) return;
|
|
96
86
|
|
|
97
|
-
|
|
87
|
+
await loadProcessors(this.context);
|
|
88
|
+
await this.midiDeviceManager.initialize();
|
|
89
|
+
this.isInitialized = true;
|
|
98
90
|
}
|
|
99
91
|
|
|
100
|
-
|
|
101
|
-
this.
|
|
102
|
-
|
|
92
|
+
addModule<T extends ModuleType>(params: ICreateModule<T>) {
|
|
93
|
+
const module = createModule(this.id, params as ModuleParams);
|
|
94
|
+
this.modules.set(module.id, module);
|
|
103
95
|
|
|
104
|
-
|
|
105
|
-
this.updateRoutes();
|
|
106
|
-
delete this.modules[id];
|
|
107
|
-
|
|
108
|
-
return moduleRouteIds;
|
|
96
|
+
return module.serialize();
|
|
109
97
|
}
|
|
110
98
|
|
|
111
|
-
updateModule(params:
|
|
112
|
-
const
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
99
|
+
updateModule<T extends ModuleType>(params: IUpdateModule<T>) {
|
|
100
|
+
const module = this.findModule(params.id);
|
|
101
|
+
if (module.moduleType !== params.moduleType) {
|
|
102
|
+
throw Error(
|
|
103
|
+
`The module id ${params.id} isn't moduleType ${params.moduleType}`,
|
|
104
|
+
);
|
|
105
|
+
}
|
|
117
106
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
if (audioModule instanceof PolyModule && numberOfVoices)
|
|
121
|
-
audioModule.numberOfVoices = numberOfVoices;
|
|
107
|
+
const updates = pick(params.changes, ["name", "props"]);
|
|
108
|
+
Object.assign(module, updates);
|
|
122
109
|
|
|
123
|
-
if (
|
|
110
|
+
if (module instanceof PolyModule && params.changes.voices !== undefined) {
|
|
111
|
+
module.voices = params.changes.voices;
|
|
112
|
+
}
|
|
124
113
|
|
|
125
|
-
return
|
|
114
|
+
return module.serialize();
|
|
126
115
|
}
|
|
127
116
|
|
|
128
|
-
|
|
129
|
-
this.
|
|
117
|
+
removeModule(id: string) {
|
|
118
|
+
this.modules.delete(id);
|
|
130
119
|
}
|
|
131
120
|
|
|
132
|
-
|
|
133
|
-
this.
|
|
121
|
+
addRoute(props: ICreateRoute): IRoute {
|
|
122
|
+
return this.routes.addRoute(props);
|
|
134
123
|
}
|
|
135
124
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
const route = createRoute(props);
|
|
139
|
-
const newRoutes = { ...this.routes, [route.id]: route };
|
|
140
|
-
|
|
141
|
-
this.routes = newRoutes;
|
|
142
|
-
this.updateRoutes();
|
|
143
|
-
|
|
144
|
-
return route;
|
|
125
|
+
removeRoute(id: string) {
|
|
126
|
+
this.routes.removeRoute(id);
|
|
145
127
|
}
|
|
146
128
|
|
|
147
|
-
validRoute(props: Optional<
|
|
148
|
-
const {
|
|
129
|
+
validRoute(props: Optional<IRoute, "id">): boolean {
|
|
130
|
+
const { source, destination } = props;
|
|
149
131
|
|
|
150
|
-
const output = this.
|
|
151
|
-
const input = this.
|
|
132
|
+
const output = this.findIO(source.moduleId, source.ioName, "output");
|
|
133
|
+
const input = this.findIO(
|
|
134
|
+
destination.moduleId,
|
|
135
|
+
destination.ioName,
|
|
136
|
+
"input",
|
|
137
|
+
);
|
|
152
138
|
|
|
153
139
|
return (
|
|
154
|
-
|
|
155
|
-
|
|
140
|
+
(output.isMidi() && input.isMidi()) ||
|
|
141
|
+
(output.isAudio() && input.isAudio())
|
|
156
142
|
);
|
|
157
143
|
}
|
|
158
144
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
this.updateRoutes();
|
|
145
|
+
start(props: { offset?: TTime; actionAt?: TTime } = {}) {
|
|
146
|
+
this.transport.start(props);
|
|
162
147
|
}
|
|
163
148
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
virtualMidi.sendMidi(MidiEvent.fromNote(noteName, type === "noteOn"));
|
|
149
|
+
stop(props: { actionAt?: TTime } = {}) {
|
|
150
|
+
this.transport.stop(props);
|
|
168
151
|
}
|
|
169
152
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
m.dispose();
|
|
173
|
-
});
|
|
174
|
-
|
|
175
|
-
this.modules = {};
|
|
176
|
-
this.routes = {};
|
|
153
|
+
pause(props: { actionAt?: TTime } = {}) {
|
|
154
|
+
this.transport.pause(props);
|
|
177
155
|
}
|
|
178
156
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
if (!audioModule) throw Error(`Audio module with id ${id} not exists`);
|
|
183
|
-
|
|
184
|
-
return audioModule;
|
|
157
|
+
get bpm() {
|
|
158
|
+
return this.transport.bpm;
|
|
185
159
|
}
|
|
186
160
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
this.context !== undefined &&
|
|
190
|
-
this.context.transport.state === "started" &&
|
|
191
|
-
this._isStarted
|
|
192
|
-
);
|
|
161
|
+
set bpm(value: number) {
|
|
162
|
+
this.transport.bpm = value;
|
|
193
163
|
}
|
|
194
164
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
this.updateRoutes();
|
|
199
|
-
|
|
200
|
-
Object.values(this.modules).forEach((audioModule) => {
|
|
201
|
-
const am = audioModule as unknown as Startable;
|
|
202
|
-
if (!am.start) return;
|
|
165
|
+
async resume() {
|
|
166
|
+
await this.context.resume();
|
|
167
|
+
}
|
|
203
168
|
|
|
204
|
-
|
|
169
|
+
dispose() {
|
|
170
|
+
this.stop();
|
|
171
|
+
this.routes.clear();
|
|
172
|
+
this.modules.forEach((module) => {
|
|
173
|
+
module.dispose();
|
|
205
174
|
});
|
|
175
|
+
this.modules.clear();
|
|
206
176
|
}
|
|
207
177
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
178
|
+
findModule(
|
|
179
|
+
id: string,
|
|
180
|
+
): ModuleTypeToModuleMapping[keyof ModuleTypeToModuleMapping] {
|
|
181
|
+
const module = this.modules.get(id);
|
|
182
|
+
if (!module) throw Error(`The module with id ${id} is not exists`);
|
|
213
183
|
|
|
214
|
-
|
|
215
|
-
|
|
184
|
+
return module;
|
|
185
|
+
}
|
|
216
186
|
|
|
217
|
-
|
|
187
|
+
findIO(moduleId: string, ioName: string, type: "input" | "output") {
|
|
188
|
+
const module = this.findModule(moduleId);
|
|
189
|
+
return module[`${type}s`].findByName(ioName);
|
|
218
190
|
}
|
|
219
191
|
|
|
220
|
-
|
|
221
|
-
return this.
|
|
192
|
+
findMidiDevice(id: string) {
|
|
193
|
+
return this.midiDeviceManager.find(id);
|
|
222
194
|
}
|
|
223
195
|
|
|
224
|
-
|
|
225
|
-
this.
|
|
196
|
+
onPropsUpdate(callback: <T extends ModuleType>(params: IModule<T>) => void) {
|
|
197
|
+
this.propsUpdateCallbacks.push(callback);
|
|
226
198
|
}
|
|
227
199
|
|
|
228
|
-
|
|
229
|
-
|
|
200
|
+
_triggerPropsUpdate<T extends ModuleType>(params: IModule<T>) {
|
|
201
|
+
this.propsUpdateCallbacks.forEach((callback) => {
|
|
202
|
+
callback(params);
|
|
203
|
+
});
|
|
230
204
|
}
|
|
231
205
|
|
|
232
|
-
|
|
233
|
-
|
|
206
|
+
// TODO: Find better way to support this
|
|
207
|
+
triggerVirtualMidi(id: string, noteName: string, type: "noteOn" | "noteOff") {
|
|
208
|
+
const virtualMidi = this.findModule(id);
|
|
209
|
+
if (virtualMidi.moduleType !== ModuleType.VirtualMidi)
|
|
210
|
+
throw Error("This is not a virtual mid");
|
|
234
211
|
|
|
235
|
-
|
|
236
|
-
|
|
212
|
+
virtualMidi.sendMidi(MidiEvent.fromNote(noteName, type === "noteOn"));
|
|
213
|
+
}
|
|
237
214
|
|
|
238
|
-
|
|
215
|
+
private onStart = (actionAt: TTime) => {
|
|
216
|
+
this.modules.forEach((module) => {
|
|
217
|
+
module.start(actionAt);
|
|
239
218
|
});
|
|
219
|
+
};
|
|
240
220
|
|
|
241
|
-
|
|
242
|
-
|
|
221
|
+
private onStop = (actionAt: TTime) => {
|
|
222
|
+
this.modules.forEach((module) => {
|
|
223
|
+
module.stop(actionAt);
|
|
224
|
+
});
|
|
225
|
+
};
|
|
243
226
|
}
|
|
244
|
-
|
|
245
|
-
export default Engine.getInstance();
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { ModuleType } from "@/modules";
|
|
2
|
+
import { Module } from "../module";
|
|
3
|
+
import IO, { IOProps, IOType } from "./Base";
|
|
4
|
+
import { PolyAudioInput, PolyAudioOutput } from "./PolyAudioIO";
|
|
5
|
+
|
|
6
|
+
export type AudioIO = AudioInput | AudioOutput;
|
|
7
|
+
|
|
8
|
+
export type AudioInputProps = IOProps & {
|
|
9
|
+
ioType: IOType.AudioInput;
|
|
10
|
+
getAudioNode: () => AudioNode | AudioParam | AudioDestinationNode;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export type AudioOutputProps = IOProps & {
|
|
14
|
+
ioType: IOType.AudioOutput;
|
|
15
|
+
getAudioNode: () => AudioNode;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export class AudioInput
|
|
19
|
+
extends IO<AudioOutput | PolyAudioOutput>
|
|
20
|
+
implements AudioInputProps
|
|
21
|
+
{
|
|
22
|
+
declare ioType: IOType.AudioInput;
|
|
23
|
+
getAudioNode: AudioInputProps["getAudioNode"];
|
|
24
|
+
|
|
25
|
+
constructor(module: Module<ModuleType>, props: AudioInputProps) {
|
|
26
|
+
super(module, props);
|
|
27
|
+
this.getAudioNode = props.getAudioNode;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export class AudioOutput
|
|
32
|
+
extends IO<AudioInput | PolyAudioInput>
|
|
33
|
+
implements AudioOutputProps
|
|
34
|
+
{
|
|
35
|
+
declare ioType: IOType.AudioOutput;
|
|
36
|
+
getAudioNode!: AudioOutputProps["getAudioNode"];
|
|
37
|
+
|
|
38
|
+
constructor(module: Module<ModuleType>, props: AudioOutputProps) {
|
|
39
|
+
super(module, props);
|
|
40
|
+
this.getAudioNode = props.getAudioNode;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
plug(io: AudioInput | PolyAudioInput, plugOther = true) {
|
|
44
|
+
super.plug(io, plugOther);
|
|
45
|
+
if (io instanceof PolyAudioInput) return;
|
|
46
|
+
|
|
47
|
+
const input = io.getAudioNode();
|
|
48
|
+
|
|
49
|
+
if (input instanceof AudioParam) {
|
|
50
|
+
this.getAudioNode().connect(input);
|
|
51
|
+
} else {
|
|
52
|
+
this.getAudioNode().connect(input);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
unPlug(io: AudioInput | PolyAudioInput, plugOther = true) {
|
|
57
|
+
super.unPlug(io, plugOther);
|
|
58
|
+
if (io instanceof PolyAudioInput) return;
|
|
59
|
+
|
|
60
|
+
const input = io.getAudioNode();
|
|
61
|
+
|
|
62
|
+
try {
|
|
63
|
+
if (input instanceof AudioParam) {
|
|
64
|
+
this.getAudioNode().disconnect(input);
|
|
65
|
+
} else {
|
|
66
|
+
this.getAudioNode().disconnect(input);
|
|
67
|
+
}
|
|
68
|
+
} catch {
|
|
69
|
+
// Ignore disconnect errors
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { deterministicId } from "@blibliki/utils";
|
|
2
|
+
import { ModuleType } from "@/modules";
|
|
3
|
+
import { Module } from "../module";
|
|
4
|
+
import { PolyModule } from "../module/PolyModule";
|
|
5
|
+
import { AudioInput, AudioOutput } from "./AudioIO";
|
|
6
|
+
import { MidiInput, MidiOutput } from "./MidiIO";
|
|
7
|
+
import { PolyAudioInput, PolyAudioOutput } from "./PolyAudioIO";
|
|
8
|
+
|
|
9
|
+
export type IOProps = {
|
|
10
|
+
name: string;
|
|
11
|
+
ioType: IOType;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export type IIOSerialize = IOProps & {
|
|
15
|
+
id: string;
|
|
16
|
+
moduleId: string;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export enum IOType {
|
|
20
|
+
AudioInput = "audioInput",
|
|
21
|
+
AudioOutput = "audioOutput",
|
|
22
|
+
PolyAudioInput = "polyAudioInput",
|
|
23
|
+
PolyAudioOutput = "polyAudioOutput",
|
|
24
|
+
MidiOutput = "midiOutput",
|
|
25
|
+
MidiInput = "midiInput",
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export type IIO = {
|
|
29
|
+
id: string;
|
|
30
|
+
module: Module<ModuleType> | PolyModule<ModuleType>;
|
|
31
|
+
} & IOProps;
|
|
32
|
+
|
|
33
|
+
export abstract class Base implements IIO {
|
|
34
|
+
id: string;
|
|
35
|
+
ioType: IOType;
|
|
36
|
+
name: string;
|
|
37
|
+
module: Module<ModuleType> | PolyModule<ModuleType>;
|
|
38
|
+
connections: Base[];
|
|
39
|
+
|
|
40
|
+
constructor(
|
|
41
|
+
module: Module<ModuleType> | PolyModule<ModuleType>,
|
|
42
|
+
props: IOProps,
|
|
43
|
+
) {
|
|
44
|
+
this.module = module;
|
|
45
|
+
this.name = props.name;
|
|
46
|
+
this.ioType = props.ioType;
|
|
47
|
+
this.id = deterministicId(this.module.id, this.name);
|
|
48
|
+
this.connections = [];
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
plug(io: Base, plugOther = true) {
|
|
52
|
+
this.connections.push(io);
|
|
53
|
+
if (plugOther) io.plug(this, false);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
unPlug(io: Base, plugOther = true) {
|
|
57
|
+
this.connections = this.connections.filter(
|
|
58
|
+
(currentIO) => currentIO.id !== io.id,
|
|
59
|
+
);
|
|
60
|
+
if (plugOther) io.unPlug(this, false);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
rePlugAll(callback?: () => void) {
|
|
64
|
+
const connections = this.connections;
|
|
65
|
+
this.unPlugAll();
|
|
66
|
+
if (callback) callback();
|
|
67
|
+
|
|
68
|
+
connections.forEach((otherIO) => {
|
|
69
|
+
this.plug(otherIO);
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
unPlugAll() {
|
|
74
|
+
this.connections.forEach((otherIO) => {
|
|
75
|
+
this.unPlug(otherIO);
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
isAudio(): this is
|
|
80
|
+
| AudioInput
|
|
81
|
+
| AudioOutput
|
|
82
|
+
| PolyAudioInput
|
|
83
|
+
| PolyAudioOutput {
|
|
84
|
+
return (
|
|
85
|
+
this.ioType === IOType.AudioInput ||
|
|
86
|
+
this.ioType === IOType.AudioOutput ||
|
|
87
|
+
this.ioType === IOType.PolyAudioInput ||
|
|
88
|
+
this.ioType === IOType.PolyAudioOutput
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
isMidi(): this is MidiInput | MidiOutput {
|
|
93
|
+
return (
|
|
94
|
+
this.ioType === IOType.MidiInput || this.ioType === IOType.MidiOutput
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
serialize(): IIOSerialize {
|
|
99
|
+
return {
|
|
100
|
+
id: this.id,
|
|
101
|
+
name: this.name,
|
|
102
|
+
ioType: this.ioType,
|
|
103
|
+
moduleId: this.module.id,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export default abstract class IO<Connection extends Base> extends Base {
|
|
109
|
+
declare connections: Connection[];
|
|
110
|
+
|
|
111
|
+
plug(io: Connection, plugOther?: boolean): void {
|
|
112
|
+
super.plug(io, plugOther);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
unPlug(io: Connection, plugOther?: boolean): void {
|
|
116
|
+
super.unPlug(io, plugOther);
|
|
117
|
+
}
|
|
118
|
+
}
|