@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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blibliki/engine",
3
- "version": "0.1.20",
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
- "ts:check": "tsc --noEmit",
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, VoiceScheduler } from "./modules";
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({ id, name, type, props: {} });
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
- updateNameModule(id: string, name: string) {
111
+ updateModule(params: UpdateModuleProps) {
112
+ const {
113
+ id,
114
+ changes: { name, numberOfVoices, props = {} },
115
+ } = params;
94
116
  const audioModule = this.findById(id);
95
- audioModule.name = name;
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: string;
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: string;
20
+ state: TMidiPortState;
15
21
  eventListerCallbacks: EventListerCallback[] = [];
16
22
 
17
- private _midi: MIDIInput;
23
+ private _midi: IMidiInput;
18
24
 
19
- constructor(midi: MIDIInput) {
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 = e instanceof MIDIMessageEvent;
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 MidiDevice from "./MidiDevice";
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 MIDIInput;
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));
@@ -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: 20000,
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.registerBasicOutputs();
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.internalModule.frequency.value = value;
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
  }
@@ -15,7 +15,10 @@ export default class Master extends Module<
15
15
  name: "Master",
16
16
  });
17
17
 
18
- this.registerBasicInputs();
18
+ this.registerAudioInput({
19
+ name: "input",
20
+ internalModule: this.internalModule,
21
+ });
19
22
  }
20
23
 
21
24
  dispose(): void {
@@ -32,6 +32,7 @@ export {
32
32
  export interface ICreateModule {
33
33
  id?: string;
34
34
  name: string;
35
+ numberOfVoices?: number;
35
36
  type: string;
36
37
  props: any;
37
38
  }