@blibliki/engine 0.3.5 → 0.3.7
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 +205 -70
- package/dist/index.d.ts +205 -70
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +6 -6
- package/src/Engine.ts +10 -4
- package/src/core/IO/Collection.ts +4 -1
- package/src/core/index.ts +7 -2
- package/src/core/midi/MidiDevice.ts +1 -0
- package/src/core/midi/MidiEvent.ts +17 -1
- package/src/core/module/Module.ts +114 -23
- package/src/core/module/PolyModule.ts +27 -2
- package/src/core/module/VoiceScheduler.ts +3 -2
- package/src/core/module/index.ts +1 -1
- package/src/core/schema.ts +53 -8
- package/src/index.ts +10 -2
- package/src/modules/Constant.ts +10 -6
- package/src/modules/Envelope.ts +21 -38
- package/src/modules/Filter.ts +58 -39
- package/src/modules/Gain.ts +8 -6
- package/src/modules/Inspector.ts +16 -6
- package/src/modules/Master.ts +2 -3
- package/src/modules/MidiMapper.ts +292 -0
- package/src/modules/MidiSelector.ts +13 -8
- package/src/modules/Oscillator.ts +38 -21
- package/src/modules/Scale.ts +19 -10
- package/src/modules/StepSequencer.ts +2 -2
- package/src/modules/StereoPanner.ts +86 -0
- package/src/modules/VirtualMidi.ts +2 -2
- package/src/modules/index.ts +26 -15
- package/src/modules/BiquadFilter.ts +0 -162
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@blibliki/engine",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.7",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"source": "src/index.ts",
|
|
6
6
|
"main": "dist/index.cjs",
|
|
@@ -12,16 +12,16 @@
|
|
|
12
12
|
"dist"
|
|
13
13
|
],
|
|
14
14
|
"devDependencies": {
|
|
15
|
-
"@types/audioworklet": "^0.0.
|
|
15
|
+
"@types/audioworklet": "^0.0.91",
|
|
16
16
|
"vite-tsconfig-paths": "^5.1.4",
|
|
17
|
-
"vitest": "^
|
|
17
|
+
"vitest": "^4.0.6"
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
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.7",
|
|
24
|
+
"@blibliki/utils": "^0.3.7"
|
|
25
25
|
},
|
|
26
26
|
"scripts": {
|
|
27
27
|
"build": "tsup",
|
package/src/Engine.ts
CHANGED
|
@@ -20,7 +20,7 @@ import {
|
|
|
20
20
|
ModuleTypeToModuleMapping,
|
|
21
21
|
createModule,
|
|
22
22
|
} from "@/modules";
|
|
23
|
-
import { PolyModule } from "./core/module/PolyModule";
|
|
23
|
+
import { IPolyModule, PolyModule } from "./core/module/PolyModule";
|
|
24
24
|
import { loadProcessors } from "./processors";
|
|
25
25
|
|
|
26
26
|
export type IUpdateModule<T extends ModuleType> = {
|
|
@@ -37,7 +37,7 @@ export class Engine {
|
|
|
37
37
|
private static _engines = new Map<string, Engine>();
|
|
38
38
|
private static _currentId: string | undefined;
|
|
39
39
|
private propsUpdateCallbacks: (<T extends ModuleType>(
|
|
40
|
-
params: IModule<T>,
|
|
40
|
+
params: IModule<T> | IPolyModule<T>,
|
|
41
41
|
) => void)[] = [];
|
|
42
42
|
|
|
43
43
|
readonly id: string;
|
|
@@ -219,11 +219,17 @@ export class Engine {
|
|
|
219
219
|
return this.midiDeviceManager.find(id);
|
|
220
220
|
}
|
|
221
221
|
|
|
222
|
-
onPropsUpdate(
|
|
222
|
+
onPropsUpdate(
|
|
223
|
+
callback: <T extends ModuleType>(
|
|
224
|
+
params: IModule<T> | IPolyModule<T>,
|
|
225
|
+
) => void,
|
|
226
|
+
) {
|
|
223
227
|
this.propsUpdateCallbacks.push(callback);
|
|
224
228
|
}
|
|
225
229
|
|
|
226
|
-
_triggerPropsUpdate<T extends ModuleType>(
|
|
230
|
+
_triggerPropsUpdate<T extends ModuleType>(
|
|
231
|
+
params: IModule<T> | IPolyModule<T>,
|
|
232
|
+
) {
|
|
227
233
|
this.propsUpdateCallbacks.forEach((callback) => {
|
|
228
234
|
callback(params);
|
|
229
235
|
});
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { assertNever } from "@blibliki/utils";
|
|
2
|
+
import { sortBy } from "es-toolkit";
|
|
2
3
|
import { ModuleType } from "@/modules";
|
|
3
4
|
import { Module } from "../module";
|
|
4
5
|
import { PolyModule } from "../module/PolyModule";
|
|
@@ -129,7 +130,9 @@ export default abstract class IOCollection<T extends CollectionType> {
|
|
|
129
130
|
}
|
|
130
131
|
|
|
131
132
|
serialize() {
|
|
132
|
-
return this.collection
|
|
133
|
+
return sortBy(this.collection, [(io) => (io.isMidi() ? -1 : 1)]).map((io) =>
|
|
134
|
+
io.serialize(),
|
|
135
|
+
);
|
|
133
136
|
}
|
|
134
137
|
|
|
135
138
|
private validateUniqName(name: string) {
|
package/src/core/index.ts
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
export { Module } from "./module";
|
|
2
|
-
export type {
|
|
2
|
+
export type {
|
|
3
|
+
IModule,
|
|
4
|
+
IModuleSerialize,
|
|
5
|
+
IPolyModuleSerialize,
|
|
6
|
+
SetterHooks,
|
|
7
|
+
} from "./module";
|
|
3
8
|
|
|
4
9
|
export type IAnyAudioContext = AudioContext | OfflineAudioContext;
|
|
5
10
|
|
|
@@ -20,7 +25,7 @@ export type {
|
|
|
20
25
|
} from "./IO";
|
|
21
26
|
|
|
22
27
|
export type {
|
|
23
|
-
|
|
28
|
+
ModulePropSchema,
|
|
24
29
|
PropSchema,
|
|
25
30
|
NumberProp,
|
|
26
31
|
StringProp,
|
|
@@ -5,7 +5,7 @@ import Note, { INote } from "../Note";
|
|
|
5
5
|
export enum MidiEventType {
|
|
6
6
|
noteOn = "noteon",
|
|
7
7
|
noteOff = "noteoff",
|
|
8
|
-
cc = "
|
|
8
|
+
cc = "controlchange",
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
export default class MidiEvent {
|
|
@@ -51,6 +51,22 @@ export default class MidiEvent {
|
|
|
51
51
|
);
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
+
get isCC() {
|
|
55
|
+
return this.type === MidiEventType.cc;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
get cc(): number | undefined {
|
|
59
|
+
if (!this.isCC) return;
|
|
60
|
+
|
|
61
|
+
return this.message.dataBytes[0];
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
get ccValue(): number | undefined {
|
|
65
|
+
if (!this.isCC) return;
|
|
66
|
+
|
|
67
|
+
return this.message.dataBytes[1];
|
|
68
|
+
}
|
|
69
|
+
|
|
54
70
|
defineNotes() {
|
|
55
71
|
if (!this.isNote) return;
|
|
56
72
|
if (this.note) return;
|
|
@@ -35,6 +35,49 @@ 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
|
+
export type SetterHooks<P> = {
|
|
74
|
+
[K in keyof P as `onSet${Capitalize<string & K>}`]: (value: P[K]) => P[K];
|
|
75
|
+
} & {
|
|
76
|
+
[K in keyof P as `onAfterSet${Capitalize<string & K>}`]: (
|
|
77
|
+
value: P[K],
|
|
78
|
+
) => void;
|
|
79
|
+
};
|
|
80
|
+
|
|
38
81
|
export abstract class Module<T extends ModuleType> implements IModule<T> {
|
|
39
82
|
id: string;
|
|
40
83
|
engineId: string;
|
|
@@ -47,6 +90,7 @@ export abstract class Module<T extends ModuleType> implements IModule<T> {
|
|
|
47
90
|
protected _props!: ModuleTypeToPropsMapping[T];
|
|
48
91
|
protected superInitialized = false;
|
|
49
92
|
protected activeNotes: Note[];
|
|
93
|
+
private pendingUIUpdates = false;
|
|
50
94
|
|
|
51
95
|
constructor(engineId: string, params: IModuleConstructor<T>) {
|
|
52
96
|
const { id, name, moduleType, voiceNo, audioNodeConstructor, props } =
|
|
@@ -59,13 +103,17 @@ export abstract class Module<T extends ModuleType> implements IModule<T> {
|
|
|
59
103
|
this.voiceNo = voiceNo ?? 0;
|
|
60
104
|
this.activeNotes = [];
|
|
61
105
|
this.audioNode = audioNodeConstructor?.(this.context);
|
|
62
|
-
this._props =
|
|
63
|
-
this.props = props;
|
|
106
|
+
this._props = props;
|
|
64
107
|
|
|
65
108
|
this.inputs = new InputCollection(this);
|
|
66
109
|
this.outputs = new OutputCollection(this);
|
|
67
110
|
|
|
68
111
|
this.superInitialized = true;
|
|
112
|
+
|
|
113
|
+
// Defer hook calls until after subclass is fully initialized
|
|
114
|
+
queueMicrotask(() => {
|
|
115
|
+
this.props = props;
|
|
116
|
+
});
|
|
69
117
|
}
|
|
70
118
|
|
|
71
119
|
get props(): ModuleTypeToPropsMapping[T] {
|
|
@@ -73,25 +121,51 @@ export abstract class Module<T extends ModuleType> implements IModule<T> {
|
|
|
73
121
|
}
|
|
74
122
|
|
|
75
123
|
set props(value: Partial<ModuleTypeToPropsMapping[T]>) {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
124
|
+
const updatedValue = { ...value };
|
|
125
|
+
|
|
126
|
+
(Object.keys(value) as (keyof ModuleTypeToPropsMapping[T])[]).forEach(
|
|
127
|
+
(key) => {
|
|
128
|
+
const propValue = value[key];
|
|
129
|
+
if (propValue !== undefined) {
|
|
130
|
+
const result = this.callPropHook("onSet", key, propValue);
|
|
131
|
+
if (result !== undefined) {
|
|
132
|
+
updatedValue[key] = result;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
},
|
|
136
|
+
);
|
|
85
137
|
|
|
86
|
-
|
|
87
|
-
const onSetAttr = `onAfterSet${upperFirst(key)}`;
|
|
138
|
+
this._props = { ...this._props, ...updatedValue };
|
|
88
139
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
140
|
+
(
|
|
141
|
+
Object.keys(updatedValue) as (keyof ModuleTypeToPropsMapping[T])[]
|
|
142
|
+
).forEach((key) => {
|
|
143
|
+
const propValue = updatedValue[key];
|
|
144
|
+
if (propValue !== undefined) {
|
|
145
|
+
this.callPropHook("onAfterSet", key, propValue);
|
|
146
|
+
}
|
|
92
147
|
});
|
|
93
148
|
}
|
|
94
149
|
|
|
150
|
+
private callPropHook<K extends keyof ModuleTypeToPropsMapping[T]>(
|
|
151
|
+
hookType: "onSet" | "onAfterSet",
|
|
152
|
+
key: K,
|
|
153
|
+
value: ModuleTypeToPropsMapping[T][K],
|
|
154
|
+
): ModuleTypeToPropsMapping[T][K] | undefined {
|
|
155
|
+
const hookName = `${hookType}${upperFirst(key as string)}`;
|
|
156
|
+
const hook = this[hookName as keyof this];
|
|
157
|
+
|
|
158
|
+
if (typeof hook === "function") {
|
|
159
|
+
const result = (
|
|
160
|
+
hook as (
|
|
161
|
+
value: ModuleTypeToPropsMapping[T][K],
|
|
162
|
+
) => ModuleTypeToPropsMapping[T][K] | undefined
|
|
163
|
+
).call(this, value);
|
|
164
|
+
return result;
|
|
165
|
+
}
|
|
166
|
+
return undefined;
|
|
167
|
+
}
|
|
168
|
+
|
|
95
169
|
serialize(): IModuleSerialize<T> {
|
|
96
170
|
return {
|
|
97
171
|
id: this.id,
|
|
@@ -149,6 +223,10 @@ export abstract class Module<T extends ModuleType> implements IModule<T> {
|
|
|
149
223
|
);
|
|
150
224
|
}
|
|
151
225
|
|
|
226
|
+
handleCC(_event: MidiEvent, _triggeredAt: ContextTime): void {
|
|
227
|
+
// Optional implementation in modules
|
|
228
|
+
}
|
|
229
|
+
|
|
152
230
|
onMidiEvent = (midiEvent: MidiEvent) => {
|
|
153
231
|
const { note, triggeredAt } = midiEvent;
|
|
154
232
|
|
|
@@ -160,18 +238,31 @@ export abstract class Module<T extends ModuleType> implements IModule<T> {
|
|
|
160
238
|
case MidiEventType.noteOff:
|
|
161
239
|
this.triggerRelease(note!, triggeredAt);
|
|
162
240
|
break;
|
|
241
|
+
case MidiEventType.cc:
|
|
242
|
+
this.handleCC(midiEvent, triggeredAt);
|
|
243
|
+
break;
|
|
163
244
|
default:
|
|
164
245
|
throw Error("This type is not a note");
|
|
165
246
|
}
|
|
166
247
|
};
|
|
167
248
|
|
|
168
|
-
|
|
169
|
-
this.
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
249
|
+
triggerPropsUpdate = () => {
|
|
250
|
+
if (this.pendingUIUpdates) return;
|
|
251
|
+
|
|
252
|
+
this.pendingUIUpdates = true;
|
|
253
|
+
this.sheduleTriggerUpdate();
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
private sheduleTriggerUpdate() {
|
|
257
|
+
requestAnimationFrame(() => {
|
|
258
|
+
this.engine._triggerPropsUpdate({
|
|
259
|
+
id: this.id,
|
|
260
|
+
moduleType: this.moduleType,
|
|
261
|
+
voiceNo: this.voiceNo,
|
|
262
|
+
name: this.name,
|
|
263
|
+
props: this.props,
|
|
264
|
+
});
|
|
265
|
+
this.pendingUIUpdates = false;
|
|
175
266
|
});
|
|
176
267
|
}
|
|
177
268
|
|
|
@@ -47,6 +47,7 @@ export abstract class PolyModule<T extends ModuleType>
|
|
|
47
47
|
protected superInitialized = false;
|
|
48
48
|
private _voices!: number;
|
|
49
49
|
private _name!: string;
|
|
50
|
+
private pendingUIUpdates = false;
|
|
50
51
|
|
|
51
52
|
constructor(engineId: string, params: IPolyModuleConstructor<T>) {
|
|
52
53
|
const { id, name, moduleType, voices, monoModuleConstructor, props } =
|
|
@@ -60,8 +61,7 @@ export abstract class PolyModule<T extends ModuleType>
|
|
|
60
61
|
this.name = name;
|
|
61
62
|
this.moduleType = moduleType;
|
|
62
63
|
this.voices = voices || 1;
|
|
63
|
-
this._props =
|
|
64
|
-
this.props = props;
|
|
64
|
+
this._props = props;
|
|
65
65
|
|
|
66
66
|
this.inputs = new InputCollection(
|
|
67
67
|
this as unknown as PolyModule<ModuleType>,
|
|
@@ -71,6 +71,11 @@ export abstract class PolyModule<T extends ModuleType>
|
|
|
71
71
|
);
|
|
72
72
|
|
|
73
73
|
this.superInitialized = true;
|
|
74
|
+
|
|
75
|
+
// Defer hook calls until after subclass is fully initialized
|
|
76
|
+
queueMicrotask(() => {
|
|
77
|
+
this.props = props;
|
|
78
|
+
});
|
|
74
79
|
}
|
|
75
80
|
|
|
76
81
|
get name() {
|
|
@@ -166,6 +171,26 @@ export abstract class PolyModule<T extends ModuleType>
|
|
|
166
171
|
audioModule.onMidiEvent(midiEvent);
|
|
167
172
|
};
|
|
168
173
|
|
|
174
|
+
triggerPropsUpdate = () => {
|
|
175
|
+
if (this.pendingUIUpdates) return;
|
|
176
|
+
|
|
177
|
+
this.pendingUIUpdates = true;
|
|
178
|
+
this.sheduleTriggerUpdate();
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
private sheduleTriggerUpdate() {
|
|
182
|
+
requestAnimationFrame(() => {
|
|
183
|
+
this.engine._triggerPropsUpdate({
|
|
184
|
+
id: this.id,
|
|
185
|
+
moduleType: this.moduleType,
|
|
186
|
+
voices: this.voices,
|
|
187
|
+
name: this.name,
|
|
188
|
+
props: this.props,
|
|
189
|
+
});
|
|
190
|
+
this.pendingUIUpdates = false;
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
|
|
169
194
|
findVoice(voiceNo: number) {
|
|
170
195
|
const moduleByVoice = this.audioModules.find((m) => m.voiceNo === voiceNo);
|
|
171
196
|
if (!moduleByVoice)
|
|
@@ -3,12 +3,13 @@ import { EmptyObject } from "@blibliki/utils";
|
|
|
3
3
|
import { ICreateModule, ModuleType } from "@/modules";
|
|
4
4
|
import { MidiOutput } from "../IO";
|
|
5
5
|
import MidiEvent, { MidiEventType } from "../midi/MidiEvent";
|
|
6
|
-
import {
|
|
6
|
+
import { ModulePropSchema } from "../schema";
|
|
7
7
|
import { IModuleConstructor, Module } from "./Module";
|
|
8
8
|
import { IPolyModuleConstructor, PolyModule } from "./PolyModule";
|
|
9
9
|
|
|
10
10
|
export type IVoiceSchedulerProps = EmptyObject;
|
|
11
|
-
export const voiceSchedulerPropSchema:
|
|
11
|
+
export const voiceSchedulerPropSchema: ModulePropSchema<IVoiceSchedulerProps> =
|
|
12
|
+
{};
|
|
12
13
|
const DEFAULT_PROPS = {};
|
|
13
14
|
|
|
14
15
|
class Voice extends Module<ModuleType.VoiceScheduler> {
|
package/src/core/module/index.ts
CHANGED
package/src/core/schema.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { EmptyObject } from "@blibliki/utils";
|
|
2
|
+
|
|
1
3
|
type BasePropType = {
|
|
2
4
|
label?: string;
|
|
3
5
|
description?: string;
|
|
@@ -8,6 +10,7 @@ export type NumberProp = BasePropType & {
|
|
|
8
10
|
min?: number;
|
|
9
11
|
max?: number;
|
|
10
12
|
step?: number;
|
|
13
|
+
exp?: number;
|
|
11
14
|
};
|
|
12
15
|
|
|
13
16
|
export type EnumProp<T extends string | number> = BasePropType & {
|
|
@@ -28,14 +31,56 @@ export type ArrayProp = BasePropType & {
|
|
|
28
31
|
kind: "array";
|
|
29
32
|
};
|
|
30
33
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
34
|
+
// Union of all possible prop schema types
|
|
35
|
+
export type PropSchema =
|
|
36
|
+
| NumberProp
|
|
37
|
+
| EnumProp<string>
|
|
38
|
+
| EnumProp<number>
|
|
39
|
+
| StringProp
|
|
40
|
+
| BooleanProp
|
|
41
|
+
| ArrayProp;
|
|
42
|
+
|
|
43
|
+
// Utility type to map TypeScript types to their primary schema types
|
|
44
|
+
type PrimarySchemaForType<T> = T extends boolean
|
|
45
|
+
? BooleanProp
|
|
46
|
+
: T extends string
|
|
47
|
+
? StringProp
|
|
48
|
+
: T extends number
|
|
49
|
+
? NumberProp
|
|
50
|
+
: T extends unknown[]
|
|
38
51
|
? ArrayProp
|
|
39
52
|
: never;
|
|
40
53
|
|
|
41
|
-
|
|
54
|
+
/**
|
|
55
|
+
* Schema type that maps each property to its primary schema type, with optional overrides.
|
|
56
|
+
* This provides excellent IntelliSense for both simple and complex cases.
|
|
57
|
+
*
|
|
58
|
+
* Basic usage:
|
|
59
|
+
* ```typescript
|
|
60
|
+
* type MyProps = { count: number; name: string; enabled: boolean };
|
|
61
|
+
* const mySchema: ModulePropSchema<MyProps> = {
|
|
62
|
+
* count: { kind: "number", min: 0, max: 100 },
|
|
63
|
+
* name: { kind: "string" },
|
|
64
|
+
* enabled: { kind: "boolean" }
|
|
65
|
+
* };
|
|
66
|
+
* ```
|
|
67
|
+
*
|
|
68
|
+
* With overrides for custom schema types:
|
|
69
|
+
* ```typescript
|
|
70
|
+
* type MyProps = { wave: OscillatorWave; frequency: number };
|
|
71
|
+
* const mySchema: ModulePropSchema<MyProps, {
|
|
72
|
+
* wave: EnumProp<OscillatorWave>
|
|
73
|
+
* }> = {
|
|
74
|
+
* wave: { kind: "enum", options: Object.values(OscillatorWave) },
|
|
75
|
+
* frequency: { kind: "number", min: 0, max: 1000 }
|
|
76
|
+
* };
|
|
77
|
+
* ```
|
|
78
|
+
*/
|
|
79
|
+
export type ModulePropSchema<
|
|
80
|
+
T,
|
|
81
|
+
TOverrides extends Partial<Record<keyof T, PropSchema>> = EmptyObject,
|
|
82
|
+
> = {
|
|
83
|
+
[K in keyof T]: K extends keyof TOverrides
|
|
84
|
+
? TOverrides[K]
|
|
85
|
+
: PrimarySchemaForType<T[K]>;
|
|
86
|
+
};
|
package/src/index.ts
CHANGED
|
@@ -8,7 +8,7 @@ export type {
|
|
|
8
8
|
IModuleSerialize,
|
|
9
9
|
IPolyModuleSerialize,
|
|
10
10
|
IMidiDevice,
|
|
11
|
-
|
|
11
|
+
ModulePropSchema,
|
|
12
12
|
PropSchema,
|
|
13
13
|
StringProp,
|
|
14
14
|
NumberProp,
|
|
@@ -24,7 +24,12 @@ export type { TimeSignature, Position } from "@blibliki/transport";
|
|
|
24
24
|
|
|
25
25
|
export { Context } from "@blibliki/utils";
|
|
26
26
|
|
|
27
|
-
export {
|
|
27
|
+
export {
|
|
28
|
+
ModuleType,
|
|
29
|
+
moduleSchemas,
|
|
30
|
+
OscillatorWave,
|
|
31
|
+
MidiMappingMode,
|
|
32
|
+
} from "./modules";
|
|
28
33
|
export type {
|
|
29
34
|
IOscillator,
|
|
30
35
|
IGain,
|
|
@@ -35,4 +40,7 @@ export type {
|
|
|
35
40
|
ModuleTypeToPropsMapping,
|
|
36
41
|
ICreateModule,
|
|
37
42
|
ModuleParams,
|
|
43
|
+
IMidiMapper,
|
|
44
|
+
IMidiMapperProps,
|
|
45
|
+
MidiMapping,
|
|
38
46
|
} from "./modules";
|
package/src/modules/Constant.ts
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { ContextTime } from "@blibliki/transport";
|
|
2
2
|
import { Context } from "@blibliki/utils";
|
|
3
|
-
import { IModule, Module } from "@/core";
|
|
3
|
+
import { IModule, Module, ModulePropSchema, SetterHooks } from "@/core";
|
|
4
4
|
import Note from "@/core/Note";
|
|
5
|
-
import { PropSchema } from "@/core/schema";
|
|
6
5
|
import { ICreateModule, ModuleType } from ".";
|
|
7
6
|
|
|
8
7
|
export type IConstant = IModule<ModuleType.Constant>;
|
|
@@ -10,7 +9,7 @@ export type IConstantProps = {
|
|
|
10
9
|
value: number;
|
|
11
10
|
};
|
|
12
11
|
|
|
13
|
-
export const constantPropSchema:
|
|
12
|
+
export const constantPropSchema: ModulePropSchema<IConstantProps> = {
|
|
14
13
|
value: {
|
|
15
14
|
kind: "number",
|
|
16
15
|
min: -Infinity,
|
|
@@ -22,7 +21,10 @@ export const constantPropSchema: PropSchema<IConstantProps> = {
|
|
|
22
21
|
|
|
23
22
|
const DEFAULT_PROPS: IConstantProps = { value: 1 };
|
|
24
23
|
|
|
25
|
-
export default class Constant
|
|
24
|
+
export default class Constant
|
|
25
|
+
extends Module<ModuleType.Constant>
|
|
26
|
+
implements Pick<SetterHooks<IConstantProps>, "onAfterSetValue">
|
|
27
|
+
{
|
|
26
28
|
declare audioNode: ConstantSourceNode;
|
|
27
29
|
isStated = false;
|
|
28
30
|
|
|
@@ -40,9 +42,9 @@ export default class Constant extends Module<ModuleType.Constant> {
|
|
|
40
42
|
this.registerDefaultIOs("out");
|
|
41
43
|
}
|
|
42
44
|
|
|
43
|
-
|
|
45
|
+
onAfterSetValue: SetterHooks<IConstantProps>["onAfterSetValue"] = (value) => {
|
|
44
46
|
this.audioNode.offset.value = value;
|
|
45
|
-
}
|
|
47
|
+
};
|
|
46
48
|
|
|
47
49
|
start(time: ContextTime) {
|
|
48
50
|
if (this.isStated) return;
|
|
@@ -52,6 +54,8 @@ export default class Constant extends Module<ModuleType.Constant> {
|
|
|
52
54
|
}
|
|
53
55
|
|
|
54
56
|
stop(time: ContextTime) {
|
|
57
|
+
if (!this.isStated) return;
|
|
58
|
+
|
|
55
59
|
this.audioNode.stop(time);
|
|
56
60
|
this.rePlugAll(() => {
|
|
57
61
|
this.audioNode = new ConstantSourceNode(this.context.audioContext, {
|