@blibliki/engine 0.1.20 → 0.1.22
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/build/Engine.d.ts +11 -11
- package/dist/build/core/Module/PolyModule.d.ts +2 -0
- package/dist/build/core/midi/ComputerKeyboardInput.d.ts +11 -0
- package/dist/build/core/midi/MidiDevice.d.ts +8 -4
- package/dist/build/core/midi/MidiDeviceManager.d.ts +1 -0
- package/dist/build/modules/Filter.d.ts +5 -0
- package/dist/build/modules/VoiceScheduler.d.ts +1 -0
- package/dist/build/modules/index.d.ts +1 -0
- package/dist/build/routes.d.ts +1 -1
- package/dist/main.cjs.js +12 -12
- package/dist/main.cjs.js.map +1 -1
- package/dist/main.esm.js +12 -12
- package/dist/main.esm.js.map +1 -1
- package/package.json +2 -2
- package/src/Engine.ts +34 -23
- package/src/core/Module/PolyModule.ts +3 -1
- package/src/core/midi/ComputerKeyboardInput.ts +57 -0
- package/src/core/midi/MidiDevice.ts +15 -8
- package/src/core/midi/MidiDeviceManager.ts +9 -4
- package/src/modules/Filter.ts +46 -4
- package/src/modules/Master.ts +4 -1
- package/src/modules/index.ts +1 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@blibliki/engine",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.22",
|
|
4
4
|
"source": "src/index.ts",
|
|
5
5
|
"main": "dist/main.cjs.js",
|
|
6
6
|
"module": "dist/main.esm.js",
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
"start": "tsc -w -p tsconfig.json",
|
|
41
41
|
"build": "rollup -c",
|
|
42
42
|
"lint": "eslint src",
|
|
43
|
-
"
|
|
43
|
+
"tsc": "tsc --noEmit",
|
|
44
44
|
"test": "jest"
|
|
45
45
|
}
|
|
46
46
|
}
|
package/src/Engine.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { Context, now, setContext } from "tone";
|
|
2
2
|
import { MidiEvent, MidiDeviceManager } from "./core/midi";
|
|
3
3
|
|
|
4
|
-
import { AudioModule, Startable } from "./core/Module";
|
|
5
|
-
import { createModule, VirtualMidi
|
|
4
|
+
import { AudioModule, PolyModule, Startable } from "./core/Module";
|
|
5
|
+
import { createModule, VirtualMidi } from "./modules";
|
|
6
6
|
import { applyRoutes, createRoute, RouteInterface } from "./routes";
|
|
7
7
|
import { AnyObject, Optional } from "./types";
|
|
8
8
|
|
|
@@ -17,6 +17,15 @@ interface InitializeInterface {
|
|
|
17
17
|
context?: Partial<ContextInterface>;
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
+
export interface UpdateModuleProps {
|
|
21
|
+
id: string;
|
|
22
|
+
changes: {
|
|
23
|
+
name?: string;
|
|
24
|
+
numberOfVoices?: number;
|
|
25
|
+
props?: AnyObject;
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
20
29
|
class Engine {
|
|
21
30
|
midiDeviceManager: MidiDeviceManager;
|
|
22
31
|
private static instance: Engine;
|
|
@@ -65,12 +74,21 @@ class Engine {
|
|
|
65
74
|
addModule(params: {
|
|
66
75
|
id?: string;
|
|
67
76
|
name: string;
|
|
77
|
+
numberOfVoices?: number;
|
|
68
78
|
type: string;
|
|
69
79
|
props?: AnyObject;
|
|
70
80
|
}) {
|
|
71
|
-
const { id, name, type, props = {} } = params;
|
|
81
|
+
const { id, name, numberOfVoices, type, props = {} } = params;
|
|
72
82
|
|
|
73
|
-
const audioModule = createModule({
|
|
83
|
+
const audioModule = createModule({
|
|
84
|
+
id,
|
|
85
|
+
name,
|
|
86
|
+
type,
|
|
87
|
+
props: {},
|
|
88
|
+
});
|
|
89
|
+
if (audioModule instanceof PolyModule && numberOfVoices) {
|
|
90
|
+
audioModule.numberOfVoices = numberOfVoices;
|
|
91
|
+
}
|
|
74
92
|
audioModule.props = props;
|
|
75
93
|
this.modules[audioModule.id] = audioModule;
|
|
76
94
|
|
|
@@ -90,9 +108,19 @@ class Engine {
|
|
|
90
108
|
return moduleRouteIds;
|
|
91
109
|
}
|
|
92
110
|
|
|
93
|
-
|
|
111
|
+
updateModule(params: UpdateModuleProps) {
|
|
112
|
+
const {
|
|
113
|
+
id,
|
|
114
|
+
changes: { name, numberOfVoices, props = {} },
|
|
115
|
+
} = params;
|
|
94
116
|
const audioModule = this.findById(id);
|
|
95
|
-
|
|
117
|
+
|
|
118
|
+
audioModule.props = props;
|
|
119
|
+
if (name) audioModule.name = name;
|
|
120
|
+
if (audioModule instanceof PolyModule && numberOfVoices)
|
|
121
|
+
audioModule.numberOfVoices = numberOfVoices;
|
|
122
|
+
|
|
123
|
+
//if (numberOfVoices) this.updateRoutes();
|
|
96
124
|
|
|
97
125
|
return audioModule.serialize();
|
|
98
126
|
}
|
|
@@ -105,16 +133,6 @@ class Engine {
|
|
|
105
133
|
this.propsUpdateCallbacks.forEach((callback) => callback(id, props));
|
|
106
134
|
}
|
|
107
135
|
|
|
108
|
-
updatePropsModule(id: string, props: AnyObject) {
|
|
109
|
-
const audioModule = this.findById(id);
|
|
110
|
-
|
|
111
|
-
const applyRoutesRequired = this.applyRoutesRequired(audioModule, props);
|
|
112
|
-
audioModule.props = props;
|
|
113
|
-
if (applyRoutesRequired) this.updateRoutes();
|
|
114
|
-
|
|
115
|
-
return audioModule.serialize();
|
|
116
|
-
}
|
|
117
|
-
|
|
118
136
|
addRoute(props: Optional<RouteInterface, "id">) {
|
|
119
137
|
const route = createRoute(props);
|
|
120
138
|
const newRoutes = { ...this.routes, [route.id]: route };
|
|
@@ -198,13 +216,6 @@ class Engine {
|
|
|
198
216
|
applyRoutes(Object.values(this.routes));
|
|
199
217
|
}
|
|
200
218
|
|
|
201
|
-
private applyRoutesRequired(audioModule: AudioModule, props: AnyObject) {
|
|
202
|
-
if (!props.polyNumber) return false;
|
|
203
|
-
if (!(audioModule instanceof VoiceScheduler)) return false;
|
|
204
|
-
|
|
205
|
-
return props.polyNumber !== audioModule.polyNumber;
|
|
206
|
-
}
|
|
207
|
-
|
|
208
219
|
private moduleRouteIds(id: string) {
|
|
209
220
|
const cloneRoutes = { ...this.routes };
|
|
210
221
|
|
|
@@ -20,6 +20,7 @@ import { deterministicId } from "../../utils";
|
|
|
20
20
|
interface PolyModuleInterface<MonoAudioModule, PropsInterface> {
|
|
21
21
|
id?: string;
|
|
22
22
|
name: string;
|
|
23
|
+
numberOfVoices?: number;
|
|
23
24
|
child: new (params: {
|
|
24
25
|
id?: string;
|
|
25
26
|
name: string;
|
|
@@ -56,7 +57,7 @@ export default abstract class PolyModule<
|
|
|
56
57
|
this.child = child;
|
|
57
58
|
Object.assign(this, basicProps);
|
|
58
59
|
|
|
59
|
-
this.numberOfVoices = 1;
|
|
60
|
+
this.numberOfVoices = params.numberOfVoices || 1;
|
|
60
61
|
this.inputs = new IOCollection<ForwardInput>(this);
|
|
61
62
|
this.outputs = new IOCollection<ForwardOutput>(this);
|
|
62
63
|
|
|
@@ -129,6 +130,7 @@ export default abstract class PolyModule<
|
|
|
129
130
|
...this.audioModules[0].serialize(),
|
|
130
131
|
id: this.id,
|
|
131
132
|
type: klass.moduleName,
|
|
133
|
+
numberOfVoices: this.numberOfVoices,
|
|
132
134
|
inputs: this.inputs.serialize(),
|
|
133
135
|
outputs: this.outputs.serialize(),
|
|
134
136
|
};
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import Note from "../Note";
|
|
2
|
+
import { IMidiInput, TMidiPortState } from "./MidiDevice";
|
|
3
|
+
import MidiEvent from "./MidiEvent";
|
|
4
|
+
|
|
5
|
+
const MAP_KEYS: { [key: string]: Note } = {
|
|
6
|
+
a: new Note("C3"),
|
|
7
|
+
s: new Note("D3"),
|
|
8
|
+
d: new Note("E3"),
|
|
9
|
+
f: new Note("F3"),
|
|
10
|
+
g: new Note("G3"),
|
|
11
|
+
h: new Note("A3"),
|
|
12
|
+
j: new Note("B3"),
|
|
13
|
+
k: new Note("C4"),
|
|
14
|
+
l: new Note("D4"),
|
|
15
|
+
w: new Note("C#3"),
|
|
16
|
+
e: new Note("D#3"),
|
|
17
|
+
t: new Note("F#3"),
|
|
18
|
+
y: new Note("G#3"),
|
|
19
|
+
u: new Note("A#3"),
|
|
20
|
+
o: new Note("C#4"),
|
|
21
|
+
p: new Note("D#4"),
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const COMPUTER_KEYBOARD_DATA = {
|
|
25
|
+
id: "computer_keyboard",
|
|
26
|
+
name: "Computer Keyboard",
|
|
27
|
+
state: "connected",
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export default class ComputerKeyboardInput implements IMidiInput {
|
|
31
|
+
id: string;
|
|
32
|
+
name: string;
|
|
33
|
+
state: TMidiPortState;
|
|
34
|
+
onmidimessage: ((e: MidiEvent) => void) | null;
|
|
35
|
+
|
|
36
|
+
constructor() {
|
|
37
|
+
Object.assign(this, COMPUTER_KEYBOARD_DATA);
|
|
38
|
+
document.addEventListener("keydown", this.onKeyTrigger(true));
|
|
39
|
+
document.addEventListener("keyup", this.onKeyTrigger(false));
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
onKeyTrigger = (noteOn: boolean) => (event: KeyboardEvent) => {
|
|
43
|
+
if (!this.onmidimessage) return;
|
|
44
|
+
|
|
45
|
+
const note = this.extractNote(event);
|
|
46
|
+
if (!note) return;
|
|
47
|
+
|
|
48
|
+
const midiEvent = MidiEvent.fromNote(note, noteOn);
|
|
49
|
+
this.onmidimessage(midiEvent);
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
private extractNote(event: KeyboardEvent): Note | undefined {
|
|
53
|
+
if (event.repeat) return;
|
|
54
|
+
|
|
55
|
+
return MAP_KEYS[event.key];
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
import MidiEvent from "./MidiEvent";
|
|
2
2
|
|
|
3
|
+
export type TMidiPortState = "connected" | "disconnected";
|
|
4
|
+
|
|
3
5
|
export interface MidiDeviceInterface {
|
|
4
6
|
id: string;
|
|
5
7
|
name: string;
|
|
6
|
-
state:
|
|
8
|
+
state: TMidiPortState;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface IMidiInput extends MidiDeviceInterface {
|
|
12
|
+
onmidimessage: ((e: MidiEvent) => void) | null;
|
|
7
13
|
}
|
|
8
14
|
|
|
9
15
|
export type EventListerCallback = (event: MidiEvent) => void;
|
|
@@ -11,12 +17,12 @@ export type EventListerCallback = (event: MidiEvent) => void;
|
|
|
11
17
|
export default class MidiDevice implements MidiDeviceInterface {
|
|
12
18
|
id: string;
|
|
13
19
|
name: string;
|
|
14
|
-
state:
|
|
20
|
+
state: TMidiPortState;
|
|
15
21
|
eventListerCallbacks: EventListerCallback[] = [];
|
|
16
22
|
|
|
17
|
-
private _midi:
|
|
23
|
+
private _midi: IMidiInput;
|
|
18
24
|
|
|
19
|
-
constructor(midi:
|
|
25
|
+
constructor(midi: IMidiInput) {
|
|
20
26
|
this.id = midi.id;
|
|
21
27
|
this.name = midi.name || `Device ${midi.id}`;
|
|
22
28
|
this.state = midi.state;
|
|
@@ -26,8 +32,9 @@ export default class MidiDevice implements MidiDeviceInterface {
|
|
|
26
32
|
}
|
|
27
33
|
|
|
28
34
|
connect() {
|
|
29
|
-
this._midi.onmidimessage = (e) => {
|
|
30
|
-
const isMidiEvent =
|
|
35
|
+
this._midi.onmidimessage = (e: Event | MidiEvent) => {
|
|
36
|
+
const isMidiEvent =
|
|
37
|
+
e instanceof MIDIMessageEvent || e instanceof MidiEvent;
|
|
31
38
|
|
|
32
39
|
if (!isMidiEvent) return;
|
|
33
40
|
|
|
@@ -55,8 +62,8 @@ export default class MidiDevice implements MidiDeviceInterface {
|
|
|
55
62
|
);
|
|
56
63
|
}
|
|
57
64
|
|
|
58
|
-
private processEvent(e: MIDIMessageEvent) {
|
|
59
|
-
const event = new MidiEvent(e);
|
|
65
|
+
private processEvent(e: MIDIMessageEvent | MidiEvent) {
|
|
66
|
+
const event: MidiEvent = e instanceof MidiEvent ? e : new MidiEvent(e);
|
|
60
67
|
|
|
61
68
|
switch (event.type) {
|
|
62
69
|
case "noteOn":
|
|
@@ -1,10 +1,13 @@
|
|
|
1
|
-
import
|
|
1
|
+
import ComputerKeyboardInput from "./ComputerKeyboardInput";
|
|
2
|
+
import MidiDevice, { IMidiInput } from "./MidiDevice";
|
|
2
3
|
|
|
3
4
|
export default class MidiDeviceManager {
|
|
4
5
|
devices: { [Key: string]: MidiDevice } = {};
|
|
5
6
|
private initialized = false;
|
|
7
|
+
private computerKeyboardInput: ComputerKeyboardInput;
|
|
6
8
|
|
|
7
9
|
constructor() {
|
|
10
|
+
this.computerKeyboardInput = new ComputerKeyboardInput();
|
|
8
11
|
this.initializeDevices()
|
|
9
12
|
.then(() => {
|
|
10
13
|
this.listenChanges();
|
|
@@ -31,7 +34,7 @@ export default class MidiDeviceManager {
|
|
|
31
34
|
if (!isMidiEvent) return;
|
|
32
35
|
if (e.port instanceof MIDIOutput) return;
|
|
33
36
|
|
|
34
|
-
const input = e.port as
|
|
37
|
+
const input = e.port as unknown as IMidiInput;
|
|
35
38
|
|
|
36
39
|
const midi = new MidiDevice(input);
|
|
37
40
|
|
|
@@ -58,14 +61,16 @@ export default class MidiDeviceManager {
|
|
|
58
61
|
(await this.inputs()).forEach((input) => {
|
|
59
62
|
if (this.devices[input.id]) return;
|
|
60
63
|
|
|
61
|
-
this.devices[input.id] = new MidiDevice(input);
|
|
64
|
+
this.devices[input.id] = new MidiDevice(input as IMidiInput);
|
|
62
65
|
});
|
|
63
66
|
|
|
64
67
|
return Object.values(this.devices);
|
|
65
68
|
}
|
|
66
69
|
|
|
67
70
|
private async inputs() {
|
|
68
|
-
const inputs: Array<MIDIInput> = [
|
|
71
|
+
const inputs: Array<MIDIInput | ComputerKeyboardInput> = [
|
|
72
|
+
this.computerKeyboardInput,
|
|
73
|
+
];
|
|
69
74
|
|
|
70
75
|
const access = await navigator.requestMIDIAccess();
|
|
71
76
|
access.inputs.forEach((input) => inputs.push(input));
|
package/src/modules/Filter.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Filter as InternalFilter, FilterRollOff } from "tone";
|
|
1
|
+
import { Filter as InternalFilter, FilterRollOff, Add, Multiply } from "tone";
|
|
2
2
|
import Module, { PolyModule } from "../core/Module";
|
|
3
3
|
|
|
4
4
|
interface FilterInterface {
|
|
@@ -11,8 +11,10 @@ interface FilterInterface {
|
|
|
11
11
|
|
|
12
12
|
type FilterProps = Partial<FilterInterface>;
|
|
13
13
|
|
|
14
|
+
const MAX_FREQ = 20000;
|
|
15
|
+
|
|
14
16
|
const InitialProps: FilterInterface = {
|
|
15
|
-
cutoff:
|
|
17
|
+
cutoff: MAX_FREQ,
|
|
16
18
|
resonance: 0,
|
|
17
19
|
envelopeAmount: 0,
|
|
18
20
|
slope: -24,
|
|
@@ -20,6 +22,9 @@ const InitialProps: FilterInterface = {
|
|
|
20
22
|
};
|
|
21
23
|
|
|
22
24
|
class MonoFilter extends Module<InternalFilter, FilterInterface> {
|
|
25
|
+
private _cutoff: Add;
|
|
26
|
+
private _amount: Multiply;
|
|
27
|
+
|
|
23
28
|
constructor(params: { id?: string; name: string; props: FilterProps }) {
|
|
24
29
|
const { id, name, props } = params;
|
|
25
30
|
|
|
@@ -29,8 +34,15 @@ class MonoFilter extends Module<InternalFilter, FilterInterface> {
|
|
|
29
34
|
props: { ...InitialProps, ...props },
|
|
30
35
|
});
|
|
31
36
|
|
|
37
|
+
this._cutoff = new Add(this.cutoff);
|
|
38
|
+
this._cutoff.connect(this.internalModule.frequency);
|
|
39
|
+
|
|
40
|
+
this._amount = new Multiply();
|
|
41
|
+
this._amount.connect(this._cutoff);
|
|
42
|
+
this.updateAmountFactor();
|
|
43
|
+
|
|
32
44
|
this.registerBasicInputs();
|
|
33
|
-
this.
|
|
45
|
+
this.registerOutputs();
|
|
34
46
|
}
|
|
35
47
|
|
|
36
48
|
get cutoff() {
|
|
@@ -38,8 +50,12 @@ class MonoFilter extends Module<InternalFilter, FilterInterface> {
|
|
|
38
50
|
}
|
|
39
51
|
|
|
40
52
|
set cutoff(value: number) {
|
|
53
|
+
if (this._cutoff) {
|
|
54
|
+
this._cutoff.addend.value = value;
|
|
55
|
+
}
|
|
56
|
+
|
|
41
57
|
this._props = { ...this.props, cutoff: value };
|
|
42
|
-
this.
|
|
58
|
+
this.updateAmountFactor();
|
|
43
59
|
}
|
|
44
60
|
|
|
45
61
|
get filterType() {
|
|
@@ -80,6 +96,27 @@ class MonoFilter extends Module<InternalFilter, FilterInterface> {
|
|
|
80
96
|
|
|
81
97
|
set envelopeAmount(value: number) {
|
|
82
98
|
this._props = { ...this.props, envelopeAmount: value };
|
|
99
|
+
this.updateAmountFactor();
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
private registerOutputs() {
|
|
103
|
+
this.registerBasicOutputs();
|
|
104
|
+
|
|
105
|
+
this.registerAudioInput({
|
|
106
|
+
name: "cutoff",
|
|
107
|
+
internalModule: this._amount,
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
private updateAmountFactor() {
|
|
112
|
+
if (!this._amount) return;
|
|
113
|
+
|
|
114
|
+
const value =
|
|
115
|
+
this.envelopeAmount > 0
|
|
116
|
+
? this.envelopeAmount * Math.abs(MAX_FREQ - this.cutoff)
|
|
117
|
+
: this.envelopeAmount * Math.abs(1 - this.cutoff);
|
|
118
|
+
|
|
119
|
+
this._amount.factor.value = value;
|
|
83
120
|
}
|
|
84
121
|
}
|
|
85
122
|
|
|
@@ -101,6 +138,11 @@ export default class Filter extends PolyModule<MonoFilter, FilterInterface> {
|
|
|
101
138
|
});
|
|
102
139
|
|
|
103
140
|
this.registerBasicInputs();
|
|
141
|
+
this.registerOutputs();
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
private registerOutputs() {
|
|
104
145
|
this.registerBasicOutputs();
|
|
146
|
+
this.registerInput({ name: "cutoff" });
|
|
105
147
|
}
|
|
106
148
|
}
|
package/src/modules/Master.ts
CHANGED