@blibliki/engine 0.1.17 → 0.1.18

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 (144) hide show
  1. package/.eslintrc.js +14 -0
  2. package/.parcerc +6 -0
  3. package/README.md +13 -1
  4. package/dist/main.cjs.js +33 -0
  5. package/package.json +21 -22
  6. package/rollup.config.mjs +21 -0
  7. package/src/Engine.ts +27 -36
  8. package/src/core/IO/AudioNode.ts +77 -0
  9. package/src/core/IO/Collection.ts +76 -0
  10. package/src/core/IO/ForwardNode.ts +192 -0
  11. package/src/core/IO/MidiNode.ts +67 -0
  12. package/src/core/IO/Node.ts +117 -0
  13. package/src/core/IO/index.ts +47 -0
  14. package/src/core/Module/MonoModule.ts +219 -0
  15. package/src/core/Module/PolyModule.ts +206 -0
  16. package/src/core/Module/index.ts +15 -0
  17. package/src/{Note → core/Note}/index.ts +19 -4
  18. package/src/{MidiDeviceManager.ts → core/midi/MidiDeviceManager.ts} +20 -15
  19. package/src/core/midi/MidiEvent.ts +91 -0
  20. package/src/core/midi/index.ts +5 -0
  21. package/src/index.ts +8 -12
  22. package/src/{Module → modules}/BitCrusher.ts +15 -4
  23. package/src/{Module → modules}/Delay.ts +15 -4
  24. package/src/{Module → modules}/Distortion.ts +15 -4
  25. package/src/{Module → modules}/Effect.ts +10 -6
  26. package/src/modules/Envelope/AmpEnvelope.ts +23 -0
  27. package/src/{Module → modules}/Envelope/Base.ts +50 -31
  28. package/src/{Module → modules}/Envelope/FreqEnvelope.ts +18 -23
  29. package/src/{Module → modules}/Filter.ts +18 -46
  30. package/src/{Module → modules}/Master.ts +8 -2
  31. package/src/{Module → modules}/MidiSelector.ts +18 -14
  32. package/src/{Module → modules}/Oscillator.ts +27 -16
  33. package/src/{Module → modules}/Reverb.ts +15 -4
  34. package/src/{Module → modules}/Sequencer.ts +17 -13
  35. package/src/{Module → modules}/VirtualMidi.ts +16 -12
  36. package/src/modules/VoiceScheduler.ts +145 -0
  37. package/src/{Module → modules}/Volume.ts +23 -15
  38. package/src/{Module → modules}/index.ts +14 -21
  39. package/src/routes.ts +19 -18
  40. package/src/types.ts +3 -0
  41. package/src/utils.ts +18 -0
  42. package/test/MockingModules.ts +27 -0
  43. package/test/Module/Oscillator.test.ts +1 -1
  44. package/test/core/IO.test.ts +172 -0
  45. package/build/Engine.d.ts +0 -89
  46. package/build/Engine.js +0 -165
  47. package/build/Engine.js.map +0 -1
  48. package/build/MidiDevice.d.ts +0 -25
  49. package/build/MidiDevice.js +0 -45
  50. package/build/MidiDevice.js.map +0 -1
  51. package/build/MidiDeviceManager.d.ts +0 -13
  52. package/build/MidiDeviceManager.js +0 -59
  53. package/build/MidiDeviceManager.js.map +0 -1
  54. package/build/MidiEvent.d.ts +0 -18
  55. package/build/MidiEvent.js +0 -64
  56. package/build/MidiEvent.js.map +0 -1
  57. package/build/Module/Base.d.ts +0 -63
  58. package/build/Module/Base.js +0 -138
  59. package/build/Module/Base.js.map +0 -1
  60. package/build/Module/BitCrusher.d.ts +0 -12
  61. package/build/Module/BitCrusher.js +0 -22
  62. package/build/Module/BitCrusher.js.map +0 -1
  63. package/build/Module/DataSequencer.d.ts +0 -26
  64. package/build/Module/DataSequencer.js +0 -91
  65. package/build/Module/DataSequencer.js.map +0 -1
  66. package/build/Module/Delay.d.ts +0 -15
  67. package/build/Module/Delay.js +0 -30
  68. package/build/Module/Delay.js.map +0 -1
  69. package/build/Module/Distortion.d.ts +0 -12
  70. package/build/Module/Distortion.js +0 -22
  71. package/build/Module/Distortion.js.map +0 -1
  72. package/build/Module/Effect.d.ts +0 -14
  73. package/build/Module/Effect.js +0 -22
  74. package/build/Module/Effect.js.map +0 -1
  75. package/build/Module/Envelope/AmpEnvelope.d.ts +0 -10
  76. package/build/Module/Envelope/AmpEnvelope.js +0 -14
  77. package/build/Module/Envelope/AmpEnvelope.js.map +0 -1
  78. package/build/Module/Envelope/Base.d.ts +0 -47
  79. package/build/Module/Envelope/Base.js +0 -106
  80. package/build/Module/Envelope/Base.js.map +0 -1
  81. package/build/Module/Envelope/FreqEnvelope.d.ts +0 -18
  82. package/build/Module/Envelope/FreqEnvelope.js +0 -50
  83. package/build/Module/Envelope/FreqEnvelope.js.map +0 -1
  84. package/build/Module/Envelope/index.d.ts +0 -3
  85. package/build/Module/Envelope/index.js +0 -4
  86. package/build/Module/Envelope/index.js.map +0 -1
  87. package/build/Module/Filter.d.ts +0 -37
  88. package/build/Module/Filter.js +0 -100
  89. package/build/Module/Filter.js.map +0 -1
  90. package/build/Module/IO.d.ts +0 -39
  91. package/build/Module/IO.js +0 -59
  92. package/build/Module/IO.js.map +0 -1
  93. package/build/Module/Master.d.ts +0 -8
  94. package/build/Module/Master.js +0 -12
  95. package/build/Module/Master.js.map +0 -1
  96. package/build/Module/MidiSelector.d.ts +0 -17
  97. package/build/Module/MidiSelector.js +0 -50
  98. package/build/Module/MidiSelector.js.map +0 -1
  99. package/build/Module/Oscillator.d.ts +0 -45
  100. package/build/Module/Oscillator.js +0 -136
  101. package/build/Module/Oscillator.js.map +0 -1
  102. package/build/Module/PolyModule.d.ts +0 -49
  103. package/build/Module/PolyModule.js +0 -175
  104. package/build/Module/PolyModule.js.map +0 -1
  105. package/build/Module/Reverb.d.ts +0 -15
  106. package/build/Module/Reverb.js +0 -30
  107. package/build/Module/Reverb.js.map +0 -1
  108. package/build/Module/Sequencer.d.ts +0 -38
  109. package/build/Module/Sequencer.js +0 -131
  110. package/build/Module/Sequencer.js.map +0 -1
  111. package/build/Module/VirtualMidi.d.ts +0 -28
  112. package/build/Module/VirtualMidi.js +0 -53
  113. package/build/Module/VirtualMidi.js.map +0 -1
  114. package/build/Module/VoiceScheduler.d.ts +0 -38
  115. package/build/Module/VoiceScheduler.js +0 -130
  116. package/build/Module/VoiceScheduler.js.map +0 -1
  117. package/build/Module/Volume.d.ts +0 -20
  118. package/build/Module/Volume.js +0 -48
  119. package/build/Module/Volume.js.map +0 -1
  120. package/build/Module/index.d.ts +0 -14
  121. package/build/Module/index.js +0 -66
  122. package/build/Module/index.js.map +0 -1
  123. package/build/Note/frequencyTable.d.ts +0 -4
  124. package/build/Note/frequencyTable.js +0 -146
  125. package/build/Note/frequencyTable.js.map +0 -1
  126. package/build/Note/index.d.ts +0 -25
  127. package/build/Note/index.js +0 -81
  128. package/build/Note/index.js.map +0 -1
  129. package/build/index.d.ts +0 -8
  130. package/build/index.js +0 -5
  131. package/build/index.js.map +0 -1
  132. package/build/routes.d.ts +0 -17
  133. package/build/routes.js +0 -31
  134. package/build/routes.js.map +0 -1
  135. package/src/MidiEvent.ts +0 -93
  136. package/src/Module/Base.ts +0 -223
  137. package/src/Module/DataSequencer.ts +0 -121
  138. package/src/Module/Envelope/AmpEnvelope.ts +0 -17
  139. package/src/Module/IO.ts +0 -85
  140. package/src/Module/PolyModule.ts +0 -234
  141. package/src/Module/VoiceScheduler.ts +0 -161
  142. /package/src/{Note → core/Note}/frequencyTable.ts +0 -0
  143. /package/src/{MidiDevice.ts → core/midi/MidiDevice.ts} +0 -0
  144. /package/src/{Module → modules}/Envelope/index.ts +0 -0
@@ -0,0 +1,117 @@
1
+ import { AudioModule } from "../Module";
2
+ import {
3
+ AnyInput,
4
+ AnyOuput,
5
+ AudioInput,
6
+ AudioOutput,
7
+ ForwardInput,
8
+ ForwardOutput,
9
+ MidiInput,
10
+ MidiOutput,
11
+ } from ".";
12
+ import { deterministicId } from "../../utils";
13
+
14
+ export interface IIONode {
15
+ name: string;
16
+ ioType: IOType;
17
+ }
18
+
19
+ export interface IIOSerialize {
20
+ id: string;
21
+ name: string;
22
+ ioType: IOType;
23
+ moduleId: string;
24
+ moduleName: string;
25
+ }
26
+
27
+ export enum IOType {
28
+ AudioInput = "audioInput",
29
+ AudioOutput = "audioOutput",
30
+ MidiInput = "midiInput",
31
+ MidiOutput = "midiOutput",
32
+ ForwardInput = "forwardInput",
33
+ ForwardOutput = "forwardOutput",
34
+ }
35
+
36
+ export function plugCompatibleIO(input: AnyInput, output: AnyOuput) {
37
+ if (input instanceof AudioInput && output instanceof AudioOutput) {
38
+ input.plug(output);
39
+ } else if (input instanceof MidiInput && output instanceof MidiOutput) {
40
+ input.plug(output);
41
+ } else if (input instanceof ForwardInput && output instanceof ForwardOutput) {
42
+ input.plug(output);
43
+ } else if (input instanceof ForwardInput && output instanceof MidiOutput) {
44
+ input.plug(output);
45
+ } else if (input instanceof ForwardInput && output instanceof AudioOutput) {
46
+ input.plug(output);
47
+ } else if (input instanceof AudioInput && output instanceof ForwardOutput) {
48
+ input.plug(output);
49
+ } else if (input instanceof MidiInput && output instanceof ForwardOutput) {
50
+ input.plug(output);
51
+ } else {
52
+ throw Error("Input and output type is not compatible");
53
+ }
54
+ }
55
+
56
+ export function unPlugCompatibleIO(input: AnyInput, output: AnyOuput) {
57
+ if (input instanceof AudioInput && output instanceof AudioOutput) {
58
+ input.unPlug(output);
59
+ } else if (input instanceof MidiInput && output instanceof MidiOutput) {
60
+ input.unPlug(output);
61
+ } else if (input instanceof ForwardInput && output instanceof ForwardOutput) {
62
+ input.unPlug(output);
63
+ } else if (input instanceof ForwardInput && output instanceof MidiOutput) {
64
+ input.unPlug(output);
65
+ } else if (input instanceof ForwardInput && output instanceof AudioOutput) {
66
+ input.unPlug(output);
67
+ } else if (input instanceof AudioInput && output instanceof ForwardOutput) {
68
+ input.unPlug(output);
69
+ } else if (input instanceof MidiInput && output instanceof ForwardOutput) {
70
+ input.unPlug(output);
71
+ } else {
72
+ throw Error("Input and output type is not compatible");
73
+ }
74
+ }
75
+
76
+ export default abstract class IONode implements IIONode {
77
+ id: string;
78
+ ioType!: IOType;
79
+ name!: string;
80
+ plugableModule: AudioModule;
81
+ connections: IONode[] = [];
82
+
83
+ static unPlugAll(io: IONode): void {
84
+ io.connections.forEach((otherIO) => io.unPlug(otherIO));
85
+ }
86
+
87
+ constructor(plugableModule: AudioModule, props: IIONode) {
88
+ this.plugableModule = plugableModule;
89
+ this.id = deterministicId(plugableModule.id, props.name);
90
+
91
+ Object.assign(this, props);
92
+ }
93
+
94
+ plug(io: IONode, plugOther: boolean = true) {
95
+ this.connections.push(io);
96
+ if (plugOther) io.plug(this, false);
97
+ }
98
+
99
+ unPlug(io: IONode, plugOther: boolean = true) {
100
+ this.connections = this.connections.filter(
101
+ (currentIO) => currentIO.id !== io.id
102
+ );
103
+ if (plugOther) io.unPlug(this, false);
104
+ }
105
+
106
+ abstract unPlugAll(): void;
107
+
108
+ serialize(): IIOSerialize {
109
+ return {
110
+ id: this.id,
111
+ name: this.name,
112
+ moduleId: this.plugableModule.id,
113
+ moduleName: this.plugableModule.name,
114
+ ioType: this.ioType,
115
+ };
116
+ }
117
+ }
@@ -0,0 +1,47 @@
1
+ import { default as IOCollection } from "./Collection";
2
+ import { IIONode, IOType, IIOSerialize } from "./Node";
3
+ import {
4
+ AudioInput,
5
+ AudioOutput,
6
+ IAudioInput,
7
+ IAudioOutput,
8
+ } from "./AudioNode";
9
+ import { IMidiInput, IMidiOutput, MidiInput, MidiOutput } from "./MidiNode";
10
+ import {
11
+ ForwardInput,
12
+ ForwardOutput,
13
+ IForwardInput,
14
+ IForwardOutput,
15
+ } from "./ForwardNode";
16
+
17
+ type AnyInput = AudioInput | MidiInput | ForwardInput;
18
+ type AnyOuput = AudioOutput | MidiOutput | ForwardOutput;
19
+ type IAnyInput = IAudioInput | IMidiInput | IForwardInput;
20
+ type IAnyOutput = IAudioOutput | IMidiOutput | IForwardOutput;
21
+ type IAnyIO = IAnyInput | IAnyOutput | IForwardOutput;
22
+
23
+ export {
24
+ IOCollection,
25
+ IOType,
26
+ AudioInput,
27
+ AudioOutput,
28
+ MidiInput,
29
+ MidiOutput,
30
+ ForwardInput,
31
+ ForwardOutput,
32
+ };
33
+ export type {
34
+ IAnyIO,
35
+ IIONode,
36
+ IAudioInput,
37
+ IAudioOutput,
38
+ IMidiInput,
39
+ IMidiOutput,
40
+ IForwardInput,
41
+ IForwardOutput,
42
+ IAnyInput,
43
+ IAnyOutput,
44
+ IIOSerialize,
45
+ AnyInput,
46
+ AnyOuput,
47
+ };
@@ -0,0 +1,219 @@
1
+ import { v4 as uuidv4 } from "uuid";
2
+ import { InputNode, Time } from "tone";
3
+
4
+ import {
5
+ IOCollection,
6
+ AudioInput,
7
+ AudioOutput,
8
+ MidiInput,
9
+ IMidiInput,
10
+ IAudioInput,
11
+ IAudioOutput,
12
+ IOType,
13
+ MidiOutput,
14
+ IMidiOutput,
15
+ } from "../IO";
16
+ import { MidiEvent } from "../../core/midi";
17
+ import { AudioModule } from "./index";
18
+ import Note from "../../core/Note";
19
+ import { plugCompatibleIO } from "../IO/Node";
20
+ import { AtLeast } from "../../types";
21
+
22
+ export interface Startable {
23
+ start(time: number): void;
24
+ stop(time: number): void;
25
+ }
26
+
27
+ export interface Connectable {
28
+ connect: (inputNode: InputNode) => void;
29
+ disconnect: (inputNode?: InputNode) => void;
30
+ dispose: () => void;
31
+ }
32
+
33
+ export interface Triggerable {
34
+ triggerAttack: (note: Note, triggeredAt: number) => void;
35
+ triggerRelease: (note: Note, triggeredAt: number) => void;
36
+ }
37
+
38
+ export interface ModuleInterface<PropsInterface> {
39
+ id: string;
40
+ name: string;
41
+ props: PropsInterface;
42
+ voiceNo?: number;
43
+ }
44
+
45
+ export class DummnyInternalModule implements Connectable {
46
+ connect() {
47
+ throw Error("This module is not connectable");
48
+ }
49
+
50
+ disconnect() {
51
+ throw Error("This module is not connectable");
52
+ }
53
+
54
+ dispose() {
55
+ // do nothing
56
+ }
57
+ }
58
+
59
+ abstract class Module<InternalModule extends Connectable, PropsInterface>
60
+ implements ModuleInterface<PropsInterface>
61
+ {
62
+ static readonly moduleName: string;
63
+
64
+ readonly id: string;
65
+ name: string;
66
+ internalModule: InternalModule;
67
+ inputs: IOCollection<AudioInput | MidiInput>;
68
+ outputs: IOCollection<AudioOutput | MidiOutput>;
69
+ readonly voiceNo?: number;
70
+ updatedAt: Date;
71
+ _props: PropsInterface = {} as PropsInterface;
72
+
73
+ constructor(
74
+ internalModule: InternalModule,
75
+ props: AtLeast<ModuleInterface<PropsInterface>, "name">
76
+ ) {
77
+ this.internalModule = internalModule;
78
+ this.id = props.id || uuidv4();
79
+ delete props.id;
80
+
81
+ this.inputs = new IOCollection<AudioInput | MidiInput>(this);
82
+ this.outputs = new IOCollection<AudioOutput | MidiOutput>(this);
83
+
84
+ Object.assign(this, props);
85
+ }
86
+
87
+ set props(value: PropsInterface) {
88
+ if (!value) return;
89
+
90
+ this.updatedAt = new Date();
91
+
92
+ Object.assign(this, value);
93
+ }
94
+
95
+ get props() {
96
+ return this._props;
97
+ }
98
+
99
+ plug(audioModule: AudioModule, from: string, to: string) {
100
+ const output = this.outputs.findByName(from);
101
+ if (!output) throw Error(`Output ${from} not exist`);
102
+
103
+ const input = audioModule.inputs.findByName(to);
104
+ if (!input) throw Error(`Input ${to} not exist`);
105
+
106
+ plugCompatibleIO(input, output);
107
+ }
108
+
109
+ unPlugAll() {
110
+ this.outputs.unPlugAll();
111
+ }
112
+
113
+ dispose() {
114
+ this.inputs.unPlugAll();
115
+ this.outputs.unPlugAll();
116
+ this.internalModule.dispose();
117
+ }
118
+
119
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
120
+ triggerAttack = (note: Note, triggeredAt: number): void => {
121
+ throw Error("triggerAttack not implemented");
122
+ };
123
+
124
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
125
+ triggerRelease = (note: Note, triggeredAt: number): void => {
126
+ throw Error("triggerRelease not implemented");
127
+ };
128
+
129
+ onMidiEvent = (midiEvent: MidiEvent) => {
130
+ if (midiEvent.voiceNo !== undefined && midiEvent.voiceNo !== this.voiceNo)
131
+ return;
132
+
133
+ const { note, triggeredAt } = midiEvent;
134
+
135
+ switch (midiEvent.type) {
136
+ case "noteOn": {
137
+ const { duration } = note;
138
+
139
+ this.triggerer(this.triggerAttack, note, triggeredAt);
140
+
141
+ if (duration) {
142
+ const releaseTriggeredAt = triggeredAt + Time(duration).toSeconds();
143
+
144
+ this.triggerer(this.triggerRelease, note, releaseTriggeredAt);
145
+ }
146
+ break;
147
+ }
148
+ case "noteOff":
149
+ this.triggerer(this.triggerRelease, note, triggeredAt);
150
+ break;
151
+ default:
152
+ throw Error("This type is not a note");
153
+ }
154
+ };
155
+
156
+ private triggerer(
157
+ trigger: (note: Note, triggeredAt: number) => void,
158
+ note: Note,
159
+ triggeredAt: number
160
+ ) {
161
+ trigger(note, triggeredAt);
162
+ }
163
+
164
+ serialize() {
165
+ const klass = this.constructor as typeof Module;
166
+
167
+ return {
168
+ id: this.id,
169
+ name: this.name,
170
+ type: klass.moduleName,
171
+ props: this.props,
172
+ inputs: this.inputs.serialize(),
173
+ outputs: this.outputs.serialize(),
174
+ };
175
+ }
176
+
177
+ protected registerMidiInput(props: Omit<IMidiInput, "ioType">): MidiInput {
178
+ return this.inputs.add({ ...props, ioType: IOType.MidiInput });
179
+ }
180
+
181
+ protected registerAudioInput(props: Omit<IAudioInput, "ioType">): AudioInput {
182
+ return this.inputs.add({ ...props, ioType: IOType.AudioInput });
183
+ }
184
+
185
+ protected registerMidiOutput(props: Omit<IMidiOutput, "ioType">): MidiOutput {
186
+ return this.outputs.add({ ...props, ioType: IOType.MidiOutput });
187
+ }
188
+
189
+ protected registerAudioOutput(
190
+ props: Omit<IAudioOutput, "ioType">
191
+ ): AudioOutput {
192
+ return this.outputs.add({ ...props, ioType: IOType.AudioOutput });
193
+ }
194
+
195
+ protected registerBasicOutputs() {
196
+ this.registerAudioOutput({
197
+ name: "output",
198
+ internalModule: this.internalModule,
199
+ });
200
+ }
201
+
202
+ protected registerBasicInputs() {
203
+ this.registerAudioInput({
204
+ name: "input",
205
+ internalModule: this.internalModule as unknown as InputNode,
206
+ });
207
+
208
+ this.registerDefaultMidiInput();
209
+ }
210
+
211
+ protected registerDefaultMidiInput() {
212
+ this.registerMidiInput({
213
+ name: "midi in",
214
+ onMidiEvent: this.onMidiEvent,
215
+ });
216
+ }
217
+ }
218
+
219
+ export default Module;
@@ -0,0 +1,206 @@
1
+ import { MidiEvent } from "../midi";
2
+ import { v4 as uuidv4 } from "uuid";
3
+ import Module, { Connectable } from "./MonoModule";
4
+ import {
5
+ IOCollection,
6
+ ForwardInput,
7
+ ForwardOutput,
8
+ IOType,
9
+ IMidiInput,
10
+ IMidiOutput,
11
+ IForwardInput,
12
+ IForwardOutput,
13
+ MidiOutput,
14
+ MidiInput,
15
+ } from "../IO";
16
+ import { AudioModule } from "./index";
17
+ import { plugCompatibleIO } from "../IO/Node";
18
+ import { deterministicId } from "../../utils";
19
+
20
+ interface PolyModuleInterface<MonoAudioModule, PropsInterface> {
21
+ id?: string;
22
+ name: string;
23
+ child: new (params: {
24
+ id?: string;
25
+ name: string;
26
+ props: PropsInterface;
27
+ }) => MonoAudioModule;
28
+ props: PropsInterface;
29
+ }
30
+
31
+ export default abstract class PolyModule<
32
+ MonoAudioModule extends Module<Connectable, PropsInterface>,
33
+ PropsInterface
34
+ > {
35
+ static readonly moduleName: string;
36
+
37
+ readonly id: string;
38
+ readonly child: new (params: {
39
+ id?: string;
40
+ name: string;
41
+ props: PropsInterface;
42
+ }) => MonoAudioModule;
43
+ _name: string;
44
+ audioModules: MonoAudioModule[];
45
+ inputs: IOCollection<ForwardInput | MidiInput>;
46
+ outputs: IOCollection<ForwardOutput | MidiOutput>;
47
+ private _numberOfVoices: number;
48
+
49
+ constructor(params: PolyModuleInterface<MonoAudioModule, PropsInterface>) {
50
+ this.id = params.id || uuidv4();
51
+ delete params.id;
52
+
53
+ this.audioModules = [];
54
+
55
+ const { child, props: extraProps, ...basicProps } = params;
56
+ this.child = child;
57
+ Object.assign(this, basicProps);
58
+
59
+ this.numberOfVoices = 1;
60
+ this.inputs = new IOCollection<ForwardInput>(this);
61
+ this.outputs = new IOCollection<ForwardOutput>(this);
62
+
63
+ Object.assign(this, { props: extraProps });
64
+ }
65
+
66
+ get name() {
67
+ return this._name;
68
+ }
69
+
70
+ set name(value: string) {
71
+ this._name = value;
72
+ this.audioModules.forEach((m) => (m.name = value));
73
+ }
74
+
75
+ get props() {
76
+ if (this.audioModules.length === 0) {
77
+ throw Error("There isn't any initialized module");
78
+ }
79
+
80
+ return this.audioModules[0].props;
81
+ }
82
+
83
+ set props(value: PropsInterface) {
84
+ Object.assign(this, value);
85
+ this.audioModules.forEach((m) => (m.props = value));
86
+ }
87
+
88
+ get numberOfVoices() {
89
+ return this._numberOfVoices;
90
+ }
91
+
92
+ set numberOfVoices(value: number) {
93
+ this._numberOfVoices = value;
94
+ this.adjustNumberOfModules();
95
+ }
96
+
97
+ plug(audioModule: AudioModule, from: string, to: string) {
98
+ const output = this.outputs.findByName(from);
99
+ if (!output) throw Error(`Output ${from} not exist`);
100
+
101
+ const input = audioModule.inputs.findByName(to);
102
+ if (!input) throw Error(`Input ${to} not exist`);
103
+
104
+ plugCompatibleIO(input, output);
105
+ }
106
+
107
+ unPlugAll() {
108
+ this.outputs.unPlugAll();
109
+ }
110
+
111
+ dispose() {
112
+ this.unPlugAll();
113
+ this.audioModules.forEach((m) => m.dispose());
114
+ }
115
+
116
+ onMidiEvent = (midiEvent: MidiEvent) => {
117
+ const voiceNo = midiEvent.voiceNo || 0;
118
+ const audioModule = this.findVoice(voiceNo);
119
+ audioModule?.onMidiEvent(midiEvent);
120
+ };
121
+
122
+ serialize() {
123
+ if (this.audioModules.length === 0)
124
+ throw Error("There isn't any initialized module");
125
+
126
+ const klass = this.constructor as typeof PolyModule;
127
+
128
+ return {
129
+ ...this.audioModules[0].serialize(),
130
+ id: this.id,
131
+ type: klass.moduleName,
132
+ inputs: this.inputs.serialize(),
133
+ outputs: this.outputs.serialize(),
134
+ };
135
+ }
136
+
137
+ protected find(callback: (audioModule: MonoAudioModule) => boolean) {
138
+ const audioModule = this.audioModules.find(callback);
139
+ if (!audioModule) throw Error(`Audio module not found`);
140
+
141
+ return audioModule;
142
+ }
143
+
144
+ protected findVoice(voiceNo: number) {
145
+ return this.audioModules.find((m) => m.voiceNo === voiceNo);
146
+ }
147
+
148
+ protected registerInput(props: Omit<IForwardInput, "ioType">): ForwardInput {
149
+ return this.inputs.add({ ...props, ioType: IOType.ForwardInput });
150
+ }
151
+
152
+ protected registerOutput(
153
+ props: Omit<IForwardOutput, "ioType">
154
+ ): ForwardOutput {
155
+ return this.outputs.add({ ...props, ioType: IOType.ForwardOutput });
156
+ }
157
+
158
+ protected registerMidiInput(props: Omit<IMidiInput, "ioType">): MidiInput {
159
+ return this.inputs.add({ ...props, ioType: IOType.MidiInput });
160
+ }
161
+
162
+ protected registerMidiOutput(props: Omit<IMidiOutput, "ioType">): MidiOutput {
163
+ return this.outputs.add({ ...props, ioType: IOType.MidiOutput });
164
+ }
165
+
166
+ protected registerBasicOutputs() {
167
+ this.registerOutput({ name: "output" });
168
+ }
169
+
170
+ protected registerBasicInputs() {
171
+ this.registerInput({ name: "input" });
172
+ this.registerMidiInput({
173
+ name: "midi in",
174
+ onMidiEvent: this.onMidiEvent,
175
+ });
176
+ }
177
+
178
+ private adjustNumberOfModules() {
179
+ if (this.audioModules.length === this.numberOfVoices) return;
180
+
181
+ if (this.audioModules.length > this.numberOfVoices) {
182
+ const audioModule = this.audioModules.pop();
183
+ audioModule?.dispose();
184
+ } else {
185
+ const voiceNo = this.audioModules.length;
186
+ const id = deterministicId(this.id, voiceNo.toString());
187
+ const props = voiceNo === 0 ? ({} as PropsInterface) : this.props;
188
+ const audioModule = new this.child({
189
+ id,
190
+ name: this.name,
191
+ props: {
192
+ ...props,
193
+ voiceNo,
194
+ },
195
+ });
196
+
197
+ if (audioModule instanceof PolyModule) {
198
+ throw Error("Polymodule not supported");
199
+ }
200
+
201
+ this.audioModules.push(audioModule);
202
+ }
203
+
204
+ this.adjustNumberOfModules();
205
+ }
206
+ }
@@ -0,0 +1,15 @@
1
+ import Module, { Connectable } from "./MonoModule";
2
+ import PolyModule from "./PolyModule";
3
+
4
+ export { default, DummnyInternalModule } from "./MonoModule";
5
+ export { default as PolyModule } from "./PolyModule";
6
+ export type {
7
+ ModuleInterface,
8
+ Connectable,
9
+ Triggerable,
10
+ Startable,
11
+ } from "./MonoModule";
12
+
13
+ export type AudioModule =
14
+ | Module<Connectable, any>
15
+ | PolyModule<Module<Connectable, any>, any>;
@@ -4,6 +4,8 @@ const Notes = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"];
4
4
 
5
5
  const NotesLength = Notes.length;
6
6
 
7
+ const MIDI_OCTAVE_SYTSTEM = 2;
8
+
7
9
  export interface INote {
8
10
  note: string;
9
11
  frequency: number;
@@ -15,7 +17,7 @@ export default class Note {
15
17
  static _notes: Note[];
16
18
  name: string;
17
19
  octave: number;
18
- velocity: number = 1;
20
+ velocity = 1;
19
21
  duration: string;
20
22
  frequency: number;
21
23
 
@@ -33,7 +35,7 @@ export default class Note {
33
35
  }
34
36
  }
35
37
 
36
- static notes(octave: number = 3) {
38
+ static notes(octave = 3) {
37
39
  return Notes.map((note: string) => new Note(`${note}${octave}`));
38
40
  }
39
41
 
@@ -45,11 +47,24 @@ export default class Note {
45
47
  return `${this.name}${this.octave}`;
46
48
  }
47
49
 
48
- adjustFrequency(range: number = 0, coarse: number = 0) {
50
+ midiData(noteOn: boolean = true): Uint8Array {
51
+ const statusByte = noteOn ? 0x90 : 0x80;
52
+ return new Uint8Array([statusByte, 0, this.velocity * 100]);
53
+ }
54
+
55
+ get midiNumber(): number {
56
+ return (this.octave + MIDI_OCTAVE_SYTSTEM) * 12 + this.noteIndex;
57
+ }
58
+
59
+ get noteIndex(): number {
60
+ return Notes.indexOf(this.name);
61
+ }
62
+
63
+ adjustFrequency(range = 0, coarse = 0) {
49
64
  if (this.frequency) return this.frequency;
50
65
 
51
66
  let newOctave = this.octave + range;
52
- let coarseIndex = Notes.indexOf(this.name) + coarse;
67
+ const coarseIndex = Notes.indexOf(this.name) + coarse;
53
68
  let nameIndex = coarseIndex;
54
69
 
55
70
  if (coarseIndex >= NotesLength) {
@@ -2,13 +2,15 @@ import MidiDevice from "./MidiDevice";
2
2
 
3
3
  export default class MidiDeviceManager {
4
4
  devices: { [Key: string]: MidiDevice } = {};
5
- private initialized: boolean = false;
5
+ private initialized = false;
6
6
 
7
7
  constructor() {
8
- this.initializeDevices().then(() => {
9
- this.listenChanges();
10
- this.initialized = true;
11
- });
8
+ this.initializeDevices()
9
+ .then(() => {
10
+ this.listenChanges();
11
+ this.initialized = true;
12
+ })
13
+ .catch(() => {});
12
14
  }
13
15
 
14
16
  find(id: string): MidiDevice | null {
@@ -20,20 +22,23 @@ export default class MidiDeviceManager {
20
22
  }
21
23
 
22
24
  onStateChange(callback: (device: MidiDevice) => void) {
23
- navigator.requestMIDIAccess().then((access: MIDIAccess) => {
24
- access.onstatechange = (e) => {
25
- const isMidiEvent = e instanceof MIDIConnectionEvent;
25
+ navigator
26
+ .requestMIDIAccess()
27
+ .then((access: MIDIAccess) => {
28
+ access.onstatechange = (e) => {
29
+ const isMidiEvent = e instanceof MIDIConnectionEvent;
26
30
 
27
- if (!isMidiEvent) return;
28
- if (e.port instanceof MIDIOutput) return;
31
+ if (!isMidiEvent) return;
32
+ if (e.port instanceof MIDIOutput) return;
29
33
 
30
- const input = e.port as MIDIInput;
34
+ const input = e.port as MIDIInput;
31
35
 
32
- const midi = new MidiDevice(input);
36
+ const midi = new MidiDevice(input);
33
37
 
34
- callback(midi);
35
- };
36
- });
38
+ callback(midi);
39
+ };
40
+ })
41
+ .catch(() => {});
37
42
  }
38
43
 
39
44
  private listenChanges() {