@blibliki/engine 0.3.6 → 0.3.8
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/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +189 -213
- package/dist/index.d.ts +189 -213
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/src/Engine.ts +52 -2
- package/src/core/Route.ts +14 -2
- package/src/core/midi/MidiDeviceManager.ts +4 -0
- package/src/core/module/Module.ts +0 -38
- package/src/core/module/PolyModule.ts +1 -6
- package/src/index.ts +1 -1
- package/src/modules/MidiSelector.ts +29 -9
- package/src/modules/Oscillator.ts +6 -7
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@blibliki/engine",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.8",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"source": "src/index.ts",
|
|
6
6
|
"main": "dist/index.cjs",
|
|
@@ -20,8 +20,8 @@
|
|
|
20
20
|
"es-toolkit": "^1.41.0",
|
|
21
21
|
"node-web-audio-api": "^1.0.3",
|
|
22
22
|
"webmidi": "^3.1.14",
|
|
23
|
-
"@blibliki/transport": "^0.3.
|
|
24
|
-
"@blibliki/utils": "^0.3.
|
|
23
|
+
"@blibliki/transport": "^0.3.8",
|
|
24
|
+
"@blibliki/utils": "^0.3.8"
|
|
25
25
|
},
|
|
26
26
|
"scripts": {
|
|
27
27
|
"build": "tsup",
|
package/src/Engine.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import {
|
|
2
|
+
BPM,
|
|
2
3
|
ContextTime,
|
|
3
4
|
Ticks,
|
|
4
5
|
TimeSignature,
|
|
@@ -12,7 +13,14 @@ import {
|
|
|
12
13
|
pick,
|
|
13
14
|
uuidv4,
|
|
14
15
|
} from "@blibliki/utils";
|
|
15
|
-
import {
|
|
16
|
+
import {
|
|
17
|
+
IRoute,
|
|
18
|
+
Routes,
|
|
19
|
+
MidiDeviceManager,
|
|
20
|
+
IModule,
|
|
21
|
+
MidiEvent,
|
|
22
|
+
IModuleSerialize,
|
|
23
|
+
} from "@/core";
|
|
16
24
|
import {
|
|
17
25
|
ICreateModule,
|
|
18
26
|
ModuleParams,
|
|
@@ -20,7 +28,11 @@ import {
|
|
|
20
28
|
ModuleTypeToModuleMapping,
|
|
21
29
|
createModule,
|
|
22
30
|
} from "@/modules";
|
|
23
|
-
import {
|
|
31
|
+
import {
|
|
32
|
+
IPolyModule,
|
|
33
|
+
IPolyModuleSerialize,
|
|
34
|
+
PolyModule,
|
|
35
|
+
} from "./core/module/PolyModule";
|
|
24
36
|
import { loadProcessors } from "./processors";
|
|
25
37
|
|
|
26
38
|
export type IUpdateModule<T extends ModuleType> = {
|
|
@@ -33,6 +45,13 @@ export type IUpdateModule<T extends ModuleType> = {
|
|
|
33
45
|
|
|
34
46
|
export type ICreateRoute = Optional<IRoute, "id">;
|
|
35
47
|
|
|
48
|
+
export interface IEngineSerialize {
|
|
49
|
+
bpm: BPM;
|
|
50
|
+
timeSignature: TimeSignature;
|
|
51
|
+
modules: (IModuleSerialize<ModuleType> | IPolyModuleSerialize<ModuleType>)[];
|
|
52
|
+
routes: IRoute[];
|
|
53
|
+
}
|
|
54
|
+
|
|
36
55
|
export class Engine {
|
|
37
56
|
private static _engines = new Map<string, Engine>();
|
|
38
57
|
private static _currentId: string | undefined;
|
|
@@ -65,6 +84,24 @@ export class Engine {
|
|
|
65
84
|
return this.getById(this._currentId);
|
|
66
85
|
}
|
|
67
86
|
|
|
87
|
+
static async load(data: IEngineSerialize): Promise<Engine> {
|
|
88
|
+
const { bpm, timeSignature, modules, routes } = data;
|
|
89
|
+
const context = new Context();
|
|
90
|
+
const engine = new Engine(context);
|
|
91
|
+
await engine.initialize();
|
|
92
|
+
|
|
93
|
+
engine.timeSignature = timeSignature;
|
|
94
|
+
engine.bpm = bpm;
|
|
95
|
+
modules.forEach((m) => {
|
|
96
|
+
engine.addModule(m);
|
|
97
|
+
});
|
|
98
|
+
routes.forEach((r) => {
|
|
99
|
+
engine.addRoute(r);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
return engine;
|
|
103
|
+
}
|
|
104
|
+
|
|
68
105
|
constructor(context: Context) {
|
|
69
106
|
this.id = uuidv4();
|
|
70
107
|
|
|
@@ -201,6 +238,15 @@ export class Engine {
|
|
|
201
238
|
this.modules.clear();
|
|
202
239
|
}
|
|
203
240
|
|
|
241
|
+
serialize(): IEngineSerialize {
|
|
242
|
+
return {
|
|
243
|
+
bpm: this.bpm,
|
|
244
|
+
timeSignature: this.timeSignature,
|
|
245
|
+
modules: Array.from(this.modules.values()).map((m) => m.serialize()),
|
|
246
|
+
routes: this.routes.serialize(),
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
|
|
204
250
|
findModule(
|
|
205
251
|
id: string,
|
|
206
252
|
): ModuleTypeToModuleMapping[keyof ModuleTypeToModuleMapping] {
|
|
@@ -219,6 +265,10 @@ export class Engine {
|
|
|
219
265
|
return this.midiDeviceManager.find(id);
|
|
220
266
|
}
|
|
221
267
|
|
|
268
|
+
findMidiDeviceByName(name: string) {
|
|
269
|
+
return this.midiDeviceManager.findByName(name);
|
|
270
|
+
}
|
|
271
|
+
|
|
222
272
|
onPropsUpdate(
|
|
223
273
|
callback: <T extends ModuleType>(
|
|
224
274
|
params: IModule<T> | IPolyModule<T>,
|
package/src/core/Route.ts
CHANGED
|
@@ -37,9 +37,21 @@ export class Routes {
|
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
clear() {
|
|
40
|
-
|
|
40
|
+
this.routes.forEach((_, id) => {
|
|
41
41
|
this.removeRoute(id);
|
|
42
|
-
}
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
replug() {
|
|
46
|
+
this.routes.forEach((_, id) => {
|
|
47
|
+
const { sourceIO, destinationIO } = this.getIOs(id);
|
|
48
|
+
sourceIO.rePlugAll();
|
|
49
|
+
destinationIO.rePlugAll();
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
serialize(): IRoute[] {
|
|
54
|
+
return Array.from(this.routes.values());
|
|
43
55
|
}
|
|
44
56
|
|
|
45
57
|
private plug(id: string) {
|
|
@@ -27,6 +27,10 @@ export default class MidiDeviceManager {
|
|
|
27
27
|
return this.devices.get(id);
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
+
findByName(name: string): MidiDevice | ComputerKeyboardDevice | undefined {
|
|
31
|
+
return Array.from(this.devices.values()).find((d) => d.name === name);
|
|
32
|
+
}
|
|
33
|
+
|
|
30
34
|
addListener(callback: ListenerCallback) {
|
|
31
35
|
this.listeners.push(callback);
|
|
32
36
|
}
|
|
@@ -35,41 +35,6 @@ export type IModuleConstructor<T extends ModuleType> = Optional<
|
|
|
35
35
|
audioNodeConstructor?: (context: Context) => AudioNode;
|
|
36
36
|
};
|
|
37
37
|
|
|
38
|
-
/**
|
|
39
|
-
* Helper type for type-safe property lifecycle hooks.
|
|
40
|
-
*
|
|
41
|
-
* Hooks are completely optional - only define the ones you need.
|
|
42
|
-
* Use explicit type annotation for automatic type inference.
|
|
43
|
-
*
|
|
44
|
-
* @example
|
|
45
|
-
* ```typescript
|
|
46
|
-
* export type IGainProps = {
|
|
47
|
-
* gain: number;
|
|
48
|
-
* muted: boolean;
|
|
49
|
-
* };
|
|
50
|
-
*
|
|
51
|
-
* export class MonoGain extends Module<ModuleType.Gain> {
|
|
52
|
-
* // ✅ Define only the hooks you need with type annotation
|
|
53
|
-
* // value type is automatically inferred as number!
|
|
54
|
-
* onSetGain: SetterHooks<IGainProps>["onSetGain"] = (value) => {
|
|
55
|
-
* this.audioNode.gain.value = value;
|
|
56
|
-
* return value; // optional: return modified value
|
|
57
|
-
* };
|
|
58
|
-
*
|
|
59
|
-
* // ✅ onAfterSet is called after prop is set
|
|
60
|
-
* onAfterSetMuted: SetterHooks<IGainProps>["onAfterSetMuted"] = (value) => {
|
|
61
|
-
* if (value) this.audioNode.gain.value = 0;
|
|
62
|
-
* };
|
|
63
|
-
*
|
|
64
|
-
* // ✅ You can omit hooks you don't need - they're optional!
|
|
65
|
-
* // No need to define onSetMuted if you don't need it
|
|
66
|
-
*
|
|
67
|
-
* // ❌ This would cause a type error:
|
|
68
|
-
* // onSetGain: SetterHooks<IGainProps>["onSetGain"] = (value: string) => value;
|
|
69
|
-
* // ^^^^^^ Error!
|
|
70
|
-
* }
|
|
71
|
-
* ```
|
|
72
|
-
*/
|
|
73
38
|
export type SetterHooks<P> = {
|
|
74
39
|
[K in keyof P as `onSet${Capitalize<string & K>}`]: (value: P[K]) => P[K];
|
|
75
40
|
} & {
|
|
@@ -88,7 +53,6 @@ export abstract class Module<T extends ModuleType> implements IModule<T> {
|
|
|
88
53
|
inputs: InputCollection;
|
|
89
54
|
outputs: OutputCollection;
|
|
90
55
|
protected _props!: ModuleTypeToPropsMapping[T];
|
|
91
|
-
protected superInitialized = false;
|
|
92
56
|
protected activeNotes: Note[];
|
|
93
57
|
private pendingUIUpdates = false;
|
|
94
58
|
|
|
@@ -108,8 +72,6 @@ export abstract class Module<T extends ModuleType> implements IModule<T> {
|
|
|
108
72
|
this.inputs = new InputCollection(this);
|
|
109
73
|
this.outputs = new OutputCollection(this);
|
|
110
74
|
|
|
111
|
-
this.superInitialized = true;
|
|
112
|
-
|
|
113
75
|
// Defer hook calls until after subclass is fully initialized
|
|
114
76
|
queueMicrotask(() => {
|
|
115
77
|
this.props = props;
|
|
@@ -44,7 +44,6 @@ export abstract class PolyModule<T extends ModuleType>
|
|
|
44
44
|
outputs: OutputCollection;
|
|
45
45
|
protected monoModuleConstructor: IPolyModuleConstructor<T>["monoModuleConstructor"];
|
|
46
46
|
protected _props!: ModuleTypeToPropsMapping[T];
|
|
47
|
-
protected superInitialized = false;
|
|
48
47
|
private _voices!: number;
|
|
49
48
|
private _name!: string;
|
|
50
49
|
private pendingUIUpdates = false;
|
|
@@ -60,7 +59,6 @@ export abstract class PolyModule<T extends ModuleType>
|
|
|
60
59
|
this.engineId = engineId;
|
|
61
60
|
this.name = name;
|
|
62
61
|
this.moduleType = moduleType;
|
|
63
|
-
this.voices = voices || 1;
|
|
64
62
|
this._props = props;
|
|
65
63
|
|
|
66
64
|
this.inputs = new InputCollection(
|
|
@@ -70,10 +68,9 @@ export abstract class PolyModule<T extends ModuleType>
|
|
|
70
68
|
this as unknown as PolyModule<ModuleType>,
|
|
71
69
|
);
|
|
72
70
|
|
|
73
|
-
this.superInitialized = true;
|
|
74
|
-
|
|
75
71
|
// Defer hook calls until after subclass is fully initialized
|
|
76
72
|
queueMicrotask(() => {
|
|
73
|
+
this.voices = voices || 1;
|
|
77
74
|
this.props = props;
|
|
78
75
|
});
|
|
79
76
|
}
|
|
@@ -146,8 +143,6 @@ export abstract class PolyModule<T extends ModuleType>
|
|
|
146
143
|
}
|
|
147
144
|
|
|
148
145
|
rePlugAll(callback?: () => void) {
|
|
149
|
-
if (!this.superInitialized) return;
|
|
150
|
-
|
|
151
146
|
this.inputs.rePlugAll(callback);
|
|
152
147
|
this.outputs.rePlugAll(callback);
|
|
153
148
|
}
|
package/src/index.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { IModule, Module, MidiOutput, SetterHooks } from "@/core";
|
|
1
|
+
import { IModule, Module, MidiOutput, SetterHooks, MidiDevice } from "@/core";
|
|
2
|
+
import ComputerKeyboardInput from "@/core/midi/ComputerKeyboardDevice";
|
|
2
3
|
import MidiEvent from "@/core/midi/MidiEvent";
|
|
3
4
|
import { ModulePropSchema } from "@/core/schema";
|
|
4
5
|
import { ICreateModule, ModuleType } from ".";
|
|
@@ -6,6 +7,7 @@ import { ICreateModule, ModuleType } from ".";
|
|
|
6
7
|
export type IMidiSelector = IModule<ModuleType.MidiSelector>;
|
|
7
8
|
export type IMidiSelectorProps = {
|
|
8
9
|
selectedId: string | undefined | null;
|
|
10
|
+
selectedName: string | undefined | null;
|
|
9
11
|
};
|
|
10
12
|
|
|
11
13
|
export const midiSelectorPropSchema: ModulePropSchema<IMidiSelectorProps> = {
|
|
@@ -13,9 +15,16 @@ export const midiSelectorPropSchema: ModulePropSchema<IMidiSelectorProps> = {
|
|
|
13
15
|
kind: "string",
|
|
14
16
|
label: "Midi device ID",
|
|
15
17
|
},
|
|
18
|
+
selectedName: {
|
|
19
|
+
kind: "string",
|
|
20
|
+
label: "Midi device name",
|
|
21
|
+
},
|
|
16
22
|
};
|
|
17
23
|
|
|
18
|
-
const DEFAULT_PROPS: IMidiSelectorProps = {
|
|
24
|
+
const DEFAULT_PROPS: IMidiSelectorProps = {
|
|
25
|
+
selectedId: undefined,
|
|
26
|
+
selectedName: undefined,
|
|
27
|
+
};
|
|
19
28
|
|
|
20
29
|
export default class MidiSelector
|
|
21
30
|
extends Module<ModuleType.MidiSelector>
|
|
@@ -36,7 +45,15 @@ export default class MidiSelector
|
|
|
36
45
|
props,
|
|
37
46
|
});
|
|
38
47
|
|
|
39
|
-
|
|
48
|
+
const midiDevice =
|
|
49
|
+
(this.props.selectedId &&
|
|
50
|
+
this.engine.findMidiDevice(this.props.selectedId)) ??
|
|
51
|
+
(this.props.selectedName &&
|
|
52
|
+
this.engine.findMidiDeviceByName(this.props.selectedName));
|
|
53
|
+
|
|
54
|
+
if (midiDevice) {
|
|
55
|
+
this.addEventListener(midiDevice);
|
|
56
|
+
}
|
|
40
57
|
|
|
41
58
|
this.registerOutputs();
|
|
42
59
|
}
|
|
@@ -45,7 +62,13 @@ export default class MidiSelector
|
|
|
45
62
|
value,
|
|
46
63
|
) => {
|
|
47
64
|
this.removeEventListener();
|
|
48
|
-
|
|
65
|
+
if (!value) return value;
|
|
66
|
+
|
|
67
|
+
const midiDevice = this.engine.findMidiDevice(value);
|
|
68
|
+
if (!midiDevice) return value;
|
|
69
|
+
|
|
70
|
+
this.props = { selectedName: midiDevice.name };
|
|
71
|
+
this.addEventListener(midiDevice);
|
|
49
72
|
|
|
50
73
|
return value;
|
|
51
74
|
};
|
|
@@ -60,11 +83,8 @@ export default class MidiSelector
|
|
|
60
83
|
return this._forwardMidiEvent;
|
|
61
84
|
}
|
|
62
85
|
|
|
63
|
-
private addEventListener(
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
const midiDevice = this.engine.findMidiDevice(midiId);
|
|
67
|
-
midiDevice?.addEventListener(this.forwardMidiEvent);
|
|
86
|
+
private addEventListener(midiDevice: MidiDevice | ComputerKeyboardInput) {
|
|
87
|
+
midiDevice.addEventListener(this.forwardMidiEvent);
|
|
68
88
|
}
|
|
69
89
|
|
|
70
90
|
private removeEventListener() {
|
|
@@ -108,7 +108,7 @@ export class MonoOscillator
|
|
|
108
108
|
{
|
|
109
109
|
declare audioNode: OscillatorNode;
|
|
110
110
|
isStated = false;
|
|
111
|
-
|
|
111
|
+
outputGain: GainNode;
|
|
112
112
|
detuneGain!: GainNode;
|
|
113
113
|
|
|
114
114
|
constructor(engineId: string, params: ICreateModule<ModuleType.Oscillator>) {
|
|
@@ -122,7 +122,7 @@ export class MonoOscillator
|
|
|
122
122
|
audioNodeConstructor,
|
|
123
123
|
});
|
|
124
124
|
|
|
125
|
-
this.
|
|
125
|
+
this.outputGain = new GainNode(this.context.audioContext, {
|
|
126
126
|
gain: dbToGain(LOW_GAIN),
|
|
127
127
|
});
|
|
128
128
|
|
|
@@ -152,8 +152,8 @@ export class MonoOscillator
|
|
|
152
152
|
this.updateFrequency();
|
|
153
153
|
};
|
|
154
154
|
|
|
155
|
-
onAfterSetLowGain: OscillatorSetterHooks["onAfterSetLowGain"] = () => {
|
|
156
|
-
this.
|
|
155
|
+
onAfterSetLowGain: OscillatorSetterHooks["onAfterSetLowGain"] = (lowGain) => {
|
|
156
|
+
this.outputGain.gain.value = lowGain ? dbToGain(LOW_GAIN) : 1;
|
|
157
157
|
};
|
|
158
158
|
|
|
159
159
|
start(time: ContextTime) {
|
|
@@ -218,7 +218,7 @@ export class MonoOscillator
|
|
|
218
218
|
}
|
|
219
219
|
|
|
220
220
|
private applyOutputGain() {
|
|
221
|
-
this.audioNode.connect(this.
|
|
221
|
+
this.audioNode.connect(this.outputGain);
|
|
222
222
|
}
|
|
223
223
|
|
|
224
224
|
private initializeGainDetune() {
|
|
@@ -236,8 +236,7 @@ export class MonoOscillator
|
|
|
236
236
|
private registerOutputs() {
|
|
237
237
|
this.registerAudioOutput({
|
|
238
238
|
name: "out",
|
|
239
|
-
getAudioNode: () =>
|
|
240
|
-
this.props.lowGain ? this.lowOutputGain : this.audioNode,
|
|
239
|
+
getAudioNode: () => this.outputGain,
|
|
241
240
|
});
|
|
242
241
|
}
|
|
243
242
|
}
|