@blibliki/engine 0.1.26 → 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.
Files changed (121) hide show
  1. package/README.md +252 -76
  2. package/dist/index.cjs +2 -0
  3. package/dist/index.cjs.map +1 -0
  4. package/dist/index.d.cts +765 -0
  5. package/dist/index.d.ts +765 -0
  6. package/dist/index.js +2 -0
  7. package/dist/index.js.map +1 -0
  8. package/package.json +19 -31
  9. package/src/Engine.ts +158 -177
  10. package/src/core/IO/AudioIO.ts +72 -0
  11. package/src/core/IO/Base.ts +118 -0
  12. package/src/core/IO/Collection.ts +123 -47
  13. package/src/core/IO/MidiIO.ts +43 -0
  14. package/src/core/IO/PolyAudioIO.ts +115 -0
  15. package/src/core/IO/index.ts +7 -61
  16. package/src/core/Note/frequencyTable.ts +144 -144
  17. package/src/core/Note/index.ts +49 -59
  18. package/src/core/Route.ts +79 -0
  19. package/src/core/Timing/Scheduler.ts +37 -0
  20. package/src/core/Timing/Time.ts +103 -0
  21. package/src/core/Timing/Transport.ts +104 -0
  22. package/src/core/Timing/index.ts +16 -0
  23. package/src/core/index.ts +36 -0
  24. package/src/core/midi/{ComputerKeyboardInput.ts → ComputerKeyboardDevice.ts} +31 -11
  25. package/src/core/midi/MidiDevice.ts +38 -31
  26. package/src/core/midi/MidiDeviceManager.ts +54 -55
  27. package/src/core/midi/MidiEvent.ts +36 -60
  28. package/src/core/module/Module.ts +233 -0
  29. package/src/core/module/PolyModule.ts +246 -0
  30. package/src/core/module/VoiceScheduler.ts +121 -0
  31. package/src/core/module/index.ts +3 -0
  32. package/src/core/schema.ts +41 -0
  33. package/src/index.ts +31 -9
  34. package/src/modules/BiquadFilter.ts +162 -0
  35. package/src/modules/Constant.ts +72 -0
  36. package/src/modules/Envelope.ts +178 -0
  37. package/src/modules/Filter.ts +109 -104
  38. package/src/modules/Gain.ts +78 -0
  39. package/src/modules/Inspector.ts +59 -0
  40. package/src/modules/Master.ts +18 -21
  41. package/src/modules/MidiSelector.ts +50 -50
  42. package/src/modules/Oscillator.ts +202 -148
  43. package/src/modules/Scale.ts +79 -0
  44. package/src/modules/StepSequencer.ts +61 -0
  45. package/src/modules/VirtualMidi.ts +33 -49
  46. package/src/modules/index.ts +159 -74
  47. package/src/nodePolyfill.ts +25 -0
  48. package/src/processors/filter-processor.ts +82 -0
  49. package/src/processors/index.ts +28 -0
  50. package/src/processors/scale-processor.ts +81 -0
  51. package/dist/build/Engine.d.ts +0 -82
  52. package/dist/build/core/IO/AudioNode.d.ts +0 -35
  53. package/dist/build/core/IO/Collection.d.ts +0 -13
  54. package/dist/build/core/IO/ForwardNode/Base.d.ts +0 -18
  55. package/dist/build/core/IO/ForwardNode/index.d.ts +0 -27
  56. package/dist/build/core/IO/ForwardNode.d.ts +0 -38
  57. package/dist/build/core/IO/MidiNode.d.ts +0 -30
  58. package/dist/build/core/IO/Node.d.ts +0 -40
  59. package/dist/build/core/IO/index.d.ts +0 -21
  60. package/dist/build/core/Module/MonoModule.d.ts +0 -67
  61. package/dist/build/core/Module/PolyModule.d.ts +0 -61
  62. package/dist/build/core/Module/index.d.ts +0 -6
  63. package/dist/build/core/Note/frequencyTable.d.ts +0 -4
  64. package/dist/build/core/Note/index.d.ts +0 -28
  65. package/dist/build/core/midi/ComputerKeyboardInput.d.ts +0 -11
  66. package/dist/build/core/midi/MidiDevice.d.ts +0 -29
  67. package/dist/build/core/midi/MidiDeviceManager.d.ts +0 -14
  68. package/dist/build/core/midi/MidiEvent.d.ts +0 -21
  69. package/dist/build/core/midi/index.d.ts +0 -5
  70. package/dist/build/index.d.ts +0 -9
  71. package/dist/build/modules/BitCrusher.d.ts +0 -16
  72. package/dist/build/modules/Delay.d.ts +0 -19
  73. package/dist/build/modules/Distortion.d.ts +0 -16
  74. package/dist/build/modules/Effect.d.ts +0 -19
  75. package/dist/build/modules/Envelope/AmpEnvelope.d.ts +0 -18
  76. package/dist/build/modules/Envelope/Base.d.ts +0 -66
  77. package/dist/build/modules/Envelope/FreqEnvelope.d.ts +0 -25
  78. package/dist/build/modules/Envelope/index.d.ts +0 -3
  79. package/dist/build/modules/Filter.d.ts +0 -42
  80. package/dist/build/modules/LFO.d.ts +0 -44
  81. package/dist/build/modules/Master.d.ts +0 -11
  82. package/dist/build/modules/MidiSelector.d.ts +0 -21
  83. package/dist/build/modules/Oscillator.d.ts +0 -55
  84. package/dist/build/modules/Reverb.d.ts +0 -19
  85. package/dist/build/modules/Sequencer.d.ts +0 -42
  86. package/dist/build/modules/VirtualMidi.d.ts +0 -32
  87. package/dist/build/modules/VoiceScheduler.d.ts +0 -44
  88. package/dist/build/modules/Volume.d.ts +0 -26
  89. package/dist/build/modules/index.d.ts +0 -17
  90. package/dist/build/routes.d.ts +0 -18
  91. package/dist/build/types.d.ts +0 -5
  92. package/dist/build/utils.d.ts +0 -1
  93. package/dist/main.cjs.js +0 -25
  94. package/dist/main.cjs.js.map +0 -1
  95. package/dist/main.esm.js +0 -25
  96. package/dist/main.esm.js.map +0 -1
  97. package/src/core/IO/AudioNode.ts +0 -82
  98. package/src/core/IO/ForwardNode/Base.ts +0 -99
  99. package/src/core/IO/ForwardNode/index.ts +0 -60
  100. package/src/core/IO/MidiNode.ts +0 -67
  101. package/src/core/IO/Node.ts +0 -118
  102. package/src/core/Module/MonoModule.ts +0 -219
  103. package/src/core/Module/PolyModule.ts +0 -218
  104. package/src/core/Module/index.ts +0 -15
  105. package/src/core/midi/index.ts +0 -5
  106. package/src/modules/BitCrusher.ts +0 -45
  107. package/src/modules/Delay.ts +0 -53
  108. package/src/modules/Distortion.ts +0 -45
  109. package/src/modules/Effect.ts +0 -46
  110. package/src/modules/Envelope/AmpEnvelope.ts +0 -23
  111. package/src/modules/Envelope/Base.ts +0 -176
  112. package/src/modules/Envelope/FreqEnvelope.ts +0 -64
  113. package/src/modules/Envelope/index.ts +0 -3
  114. package/src/modules/LFO.ts +0 -149
  115. package/src/modules/Reverb.ts +0 -53
  116. package/src/modules/Sequencer.ts +0 -178
  117. package/src/modules/VoiceScheduler.ts +0 -145
  118. package/src/modules/Volume.ts +0 -72
  119. package/src/routes.ts +0 -49
  120. package/src/types.ts +0 -3
  121. package/src/utils.ts +0 -18
package/src/Engine.ts CHANGED
@@ -1,245 +1,226 @@
1
- import { Context, now, setContext } from "tone";
2
- import { MidiEvent, MidiDeviceManager } from "./core/midi";
3
-
4
- import { AudioModule, PolyModule, Startable } from "./core/Module";
5
- import { createModule, VirtualMidi } from "./modules";
6
- import { applyRoutes, createRoute, RouteInterface } from "./routes";
7
- import { AnyObject, Optional } from "./types";
8
-
9
- type LatencyHint = "interactive" | "playback" | "balanced";
10
-
11
- interface ContextInterface {
12
- latencyHint: LatencyHint;
13
- lookAhead: number;
14
- }
15
-
16
- interface InitializeInterface {
17
- context?: Partial<ContextInterface>;
18
- }
19
-
20
- export interface UpdateModuleProps {
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
- changes: {
23
- name?: string;
24
- numberOfVoices?: number;
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
- modules: {
37
- [Identifier: string]: AudioModule;
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
- private constructor() {
45
- this.modules = {};
46
- this.routes = {};
47
- this.propsUpdateCallbacks = [];
55
+ return engine;
48
56
  }
49
57
 
50
- public static getInstance(): Engine {
51
- if (!Engine.instance) {
52
- Engine.instance = new Engine();
53
- }
58
+ static get current(): Engine {
59
+ assertDefined(this._currentId);
54
60
 
55
- return Engine.instance;
61
+ return this.getById(this._currentId);
56
62
  }
57
63
 
58
- initialize(props: InitializeInterface) {
59
- return new Promise((resolve) => {
60
- if (this.context) return resolve({});
64
+ constructor(context: IAnyAudioContext) {
65
+ this.id = uuidv4();
61
66
 
62
- this.context = new Context(props.context);
63
- setContext(this.context);
64
- this.context.transport.start();
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
- addModule(params: {
75
- id?: string;
76
- name: string;
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
- this.updateRoutes();
84
+ async initialize() {
85
+ if (this.isInitialized) return;
96
86
 
97
- return audioModule.serialize();
87
+ await loadProcessors(this.context);
88
+ await this.midiDeviceManager.initialize();
89
+ this.isInitialized = true;
98
90
  }
99
91
 
100
- removeModule(id: string) {
101
- this.modules[id].dispose();
102
- const moduleRouteIds = this.moduleRouteIds(id);
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
- moduleRouteIds.forEach((routeId) => delete this.routes[routeId]);
105
- this.updateRoutes();
106
- delete this.modules[id];
107
-
108
- return moduleRouteIds;
96
+ return module.serialize();
109
97
  }
110
98
 
111
- updateModule(params: UpdateModuleProps) {
112
- const {
113
- id,
114
- changes: { name, numberOfVoices, props = {} },
115
- } = params;
116
- const audioModule = this.findById(id);
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
- audioModule.props = props;
119
- if (name) audioModule.name = name;
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 (numberOfVoices) this.updateRoutes();
110
+ if (module instanceof PolyModule && params.changes.voices !== undefined) {
111
+ module.voices = params.changes.voices;
112
+ }
124
113
 
125
- return audioModule.serialize();
114
+ return module.serialize();
126
115
  }
127
116
 
128
- onPropsUpdate(callback: (id: string, props: AnyObject) => void) {
129
- this.propsUpdateCallbacks.push(callback);
117
+ removeModule(id: string) {
118
+ this.modules.delete(id);
130
119
  }
131
120
 
132
- _triggerPropsUpdate(id: string, props: AnyObject) {
133
- this.propsUpdateCallbacks.forEach((callback) => callback(id, props));
121
+ addRoute(props: ICreateRoute): IRoute {
122
+ return this.routes.addRoute(props);
134
123
  }
135
124
 
136
- addRoute(props: Optional<RouteInterface, "id">) {
137
- if (!this.validRoute(props)) throw Error("Invalid route, incompatible IOs");
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<RouteInterface, "id">): boolean {
148
- const { sourceId, sourceIOId, destinationId, destinationIOId } = props;
129
+ validRoute(props: Optional<IRoute, "id">): boolean {
130
+ const { source, destination } = props;
149
131
 
150
- const output = this.findById(sourceId).outputs.find(sourceIOId);
151
- const input = this.findById(destinationId).inputs.find(destinationIOId);
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
- !!(output?.isMidi && input?.isMidi) ||
155
- !!(output?.isAudio && input?.isAudio)
140
+ (output.isMidi() && input.isMidi()) ||
141
+ (output.isAudio() && input.isAudio())
156
142
  );
157
143
  }
158
144
 
159
- removeRoute(id: string) {
160
- delete this.routes[id];
161
- this.updateRoutes();
145
+ start(props: { offset?: TTime; actionAt?: TTime } = {}) {
146
+ this.transport.start(props);
162
147
  }
163
148
 
164
- triggerVirtualMidi(id: string, noteName: string, type: "noteOn" | "noteOff") {
165
- const virtualMidi = this.findById(id) as VirtualMidi;
166
-
167
- virtualMidi.sendMidi(MidiEvent.fromNote(noteName, type === "noteOn"));
149
+ stop(props: { actionAt?: TTime } = {}) {
150
+ this.transport.stop(props);
168
151
  }
169
152
 
170
- dispose() {
171
- Object.values(this.modules).forEach((m) => {
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
- findById(id: string): AudioModule {
180
- const audioModule = this.modules[id];
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
- get isStarted() {
188
- return (
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
- start() {
196
- const startTime = now();
197
- this._isStarted = true;
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
- am.start(startTime);
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
- stop() {
209
- const startTime = now();
210
- Object.values(this.modules).forEach((audioModule) => {
211
- const am = audioModule as unknown as Startable;
212
- if (!am.stop) return;
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
- am.stop(startTime);
215
- });
184
+ return module;
185
+ }
216
186
 
217
- this._isStarted = false;
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
- get bpm() {
221
- return this.context.transport.bpm.value;
192
+ findMidiDevice(id: string) {
193
+ return this.midiDeviceManager.find(id);
222
194
  }
223
195
 
224
- set bpm(value: number) {
225
- this.context.transport.bpm.value = value;
196
+ onPropsUpdate(callback: <T extends ModuleType>(params: IModule<T>) => void) {
197
+ this.propsUpdateCallbacks.push(callback);
226
198
  }
227
199
 
228
- updateRoutes() {
229
- applyRoutes(Object.values(this.routes));
200
+ _triggerPropsUpdate<T extends ModuleType>(params: IModule<T>) {
201
+ this.propsUpdateCallbacks.forEach((callback) => {
202
+ callback(params);
203
+ });
230
204
  }
231
205
 
232
- private moduleRouteIds(id: string) {
233
- const cloneRoutes = { ...this.routes };
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
- const routeIds = Object.keys(cloneRoutes).filter((routeId) => {
236
- const { sourceId, destinationId } = cloneRoutes[routeId];
212
+ virtualMidi.sendMidi(MidiEvent.fromNote(noteName, type === "noteOn"));
213
+ }
237
214
 
238
- return sourceId === id || destinationId === id;
215
+ private onStart = (actionAt: TTime) => {
216
+ this.modules.forEach((module) => {
217
+ module.start(actionAt);
239
218
  });
219
+ };
240
220
 
241
- return routeIds;
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
+ }