@blibliki/engine 0.1.26 → 0.3.1

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 (121) hide show
  1. package/README.md +252 -76
  2. package/dist/index.cjs +2 -0
  3. package/dist/index.cjs.map +1 -0
  4. package/dist/index.d.cts +765 -0
  5. package/dist/index.d.ts +765 -0
  6. package/dist/index.js +2 -0
  7. package/dist/index.js.map +1 -0
  8. package/package.json +19 -31
  9. package/src/Engine.ts +158 -177
  10. package/src/core/IO/AudioIO.ts +72 -0
  11. package/src/core/IO/Base.ts +118 -0
  12. package/src/core/IO/Collection.ts +123 -47
  13. package/src/core/IO/MidiIO.ts +43 -0
  14. package/src/core/IO/PolyAudioIO.ts +115 -0
  15. package/src/core/IO/index.ts +7 -61
  16. package/src/core/Note/frequencyTable.ts +144 -144
  17. package/src/core/Note/index.ts +49 -59
  18. package/src/core/Route.ts +79 -0
  19. package/src/core/Timing/Scheduler.ts +37 -0
  20. package/src/core/Timing/Time.ts +103 -0
  21. package/src/core/Timing/Transport.ts +104 -0
  22. package/src/core/Timing/index.ts +16 -0
  23. package/src/core/index.ts +36 -0
  24. package/src/core/midi/{ComputerKeyboardInput.ts → ComputerKeyboardDevice.ts} +31 -11
  25. package/src/core/midi/MidiDevice.ts +38 -31
  26. package/src/core/midi/MidiDeviceManager.ts +54 -55
  27. package/src/core/midi/MidiEvent.ts +36 -60
  28. package/src/core/module/Module.ts +233 -0
  29. package/src/core/module/PolyModule.ts +246 -0
  30. package/src/core/module/VoiceScheduler.ts +121 -0
  31. package/src/core/module/index.ts +3 -0
  32. package/src/core/schema.ts +41 -0
  33. package/src/index.ts +31 -9
  34. package/src/modules/BiquadFilter.ts +162 -0
  35. package/src/modules/Constant.ts +72 -0
  36. package/src/modules/Envelope.ts +178 -0
  37. package/src/modules/Filter.ts +109 -104
  38. package/src/modules/Gain.ts +78 -0
  39. package/src/modules/Inspector.ts +59 -0
  40. package/src/modules/Master.ts +18 -21
  41. package/src/modules/MidiSelector.ts +50 -50
  42. package/src/modules/Oscillator.ts +202 -148
  43. package/src/modules/Scale.ts +79 -0
  44. package/src/modules/StepSequencer.ts +61 -0
  45. package/src/modules/VirtualMidi.ts +33 -49
  46. package/src/modules/index.ts +159 -74
  47. package/src/nodePolyfill.ts +25 -0
  48. package/src/processors/filter-processor.ts +82 -0
  49. package/src/processors/index.ts +28 -0
  50. package/src/processors/scale-processor.ts +81 -0
  51. package/dist/build/Engine.d.ts +0 -82
  52. package/dist/build/core/IO/AudioNode.d.ts +0 -35
  53. package/dist/build/core/IO/Collection.d.ts +0 -13
  54. package/dist/build/core/IO/ForwardNode/Base.d.ts +0 -18
  55. package/dist/build/core/IO/ForwardNode/index.d.ts +0 -27
  56. package/dist/build/core/IO/ForwardNode.d.ts +0 -38
  57. package/dist/build/core/IO/MidiNode.d.ts +0 -30
  58. package/dist/build/core/IO/Node.d.ts +0 -40
  59. package/dist/build/core/IO/index.d.ts +0 -21
  60. package/dist/build/core/Module/MonoModule.d.ts +0 -67
  61. package/dist/build/core/Module/PolyModule.d.ts +0 -61
  62. package/dist/build/core/Module/index.d.ts +0 -6
  63. package/dist/build/core/Note/frequencyTable.d.ts +0 -4
  64. package/dist/build/core/Note/index.d.ts +0 -28
  65. package/dist/build/core/midi/ComputerKeyboardInput.d.ts +0 -11
  66. package/dist/build/core/midi/MidiDevice.d.ts +0 -29
  67. package/dist/build/core/midi/MidiDeviceManager.d.ts +0 -14
  68. package/dist/build/core/midi/MidiEvent.d.ts +0 -21
  69. package/dist/build/core/midi/index.d.ts +0 -5
  70. package/dist/build/index.d.ts +0 -9
  71. package/dist/build/modules/BitCrusher.d.ts +0 -16
  72. package/dist/build/modules/Delay.d.ts +0 -19
  73. package/dist/build/modules/Distortion.d.ts +0 -16
  74. package/dist/build/modules/Effect.d.ts +0 -19
  75. package/dist/build/modules/Envelope/AmpEnvelope.d.ts +0 -18
  76. package/dist/build/modules/Envelope/Base.d.ts +0 -66
  77. package/dist/build/modules/Envelope/FreqEnvelope.d.ts +0 -25
  78. package/dist/build/modules/Envelope/index.d.ts +0 -3
  79. package/dist/build/modules/Filter.d.ts +0 -42
  80. package/dist/build/modules/LFO.d.ts +0 -44
  81. package/dist/build/modules/Master.d.ts +0 -11
  82. package/dist/build/modules/MidiSelector.d.ts +0 -21
  83. package/dist/build/modules/Oscillator.d.ts +0 -55
  84. package/dist/build/modules/Reverb.d.ts +0 -19
  85. package/dist/build/modules/Sequencer.d.ts +0 -42
  86. package/dist/build/modules/VirtualMidi.d.ts +0 -32
  87. package/dist/build/modules/VoiceScheduler.d.ts +0 -44
  88. package/dist/build/modules/Volume.d.ts +0 -26
  89. package/dist/build/modules/index.d.ts +0 -17
  90. package/dist/build/routes.d.ts +0 -18
  91. package/dist/build/types.d.ts +0 -5
  92. package/dist/build/utils.d.ts +0 -1
  93. package/dist/main.cjs.js +0 -25
  94. package/dist/main.cjs.js.map +0 -1
  95. package/dist/main.esm.js +0 -25
  96. package/dist/main.esm.js.map +0 -1
  97. package/src/core/IO/AudioNode.ts +0 -82
  98. package/src/core/IO/ForwardNode/Base.ts +0 -99
  99. package/src/core/IO/ForwardNode/index.ts +0 -60
  100. package/src/core/IO/MidiNode.ts +0 -67
  101. package/src/core/IO/Node.ts +0 -118
  102. package/src/core/Module/MonoModule.ts +0 -219
  103. package/src/core/Module/PolyModule.ts +0 -218
  104. package/src/core/Module/index.ts +0 -15
  105. package/src/core/midi/index.ts +0 -5
  106. package/src/modules/BitCrusher.ts +0 -45
  107. package/src/modules/Delay.ts +0 -53
  108. package/src/modules/Distortion.ts +0 -45
  109. package/src/modules/Effect.ts +0 -46
  110. package/src/modules/Envelope/AmpEnvelope.ts +0 -23
  111. package/src/modules/Envelope/Base.ts +0 -176
  112. package/src/modules/Envelope/FreqEnvelope.ts +0 -64
  113. package/src/modules/Envelope/index.ts +0 -3
  114. package/src/modules/LFO.ts +0 -149
  115. package/src/modules/Reverb.ts +0 -53
  116. package/src/modules/Sequencer.ts +0 -178
  117. package/src/modules/VoiceScheduler.ts +0 -145
  118. package/src/modules/Volume.ts +0 -72
  119. package/src/routes.ts +0 -49
  120. package/src/types.ts +0 -3
  121. package/src/utils.ts +0 -18
@@ -0,0 +1,104 @@
1
+ import { now } from ".";
2
+ import Scheduler from "./Scheduler";
3
+ import Time, { t, TTime } from "./Time";
4
+
5
+ export enum TransportState {
6
+ playing = "playing",
7
+ stopped = "stopped",
8
+ paused = "paused",
9
+ }
10
+
11
+ export type TransportEvents = {
12
+ start: { actionAt: TTime; offset: TTime };
13
+ stop: { actionAt: TTime };
14
+ pause: { actionAt: TTime };
15
+ };
16
+
17
+ type TPlaybackCallback = (actionAt: TTime) => void;
18
+
19
+ type TransportProps = {
20
+ onStart?: TPlaybackCallback;
21
+ onStop?: TPlaybackCallback;
22
+ };
23
+
24
+ export default class Transport {
25
+ bpm = 120;
26
+ timeSignature: [number, number] = [4, 4];
27
+ loopStart: TTime;
28
+ loopEnd?: TTime;
29
+
30
+ state: TransportState = TransportState.stopped;
31
+ offset: TTime = 0;
32
+
33
+ private startTime: TTime = 0;
34
+ private onStart: TransportProps["onStart"];
35
+ private onStop: TransportProps["onStop"];
36
+ private scheduler: Scheduler;
37
+
38
+ constructor(props: TransportProps) {
39
+ this.onStart = props.onStart;
40
+ this.onStop = props.onStop;
41
+ this.loopStart = t("0:0:0");
42
+ this.scheduler = new Scheduler(this);
43
+ }
44
+
45
+ start({
46
+ offset = this.offset,
47
+ actionAt = now(),
48
+ }: {
49
+ offset?: TTime;
50
+ actionAt?: TTime;
51
+ }) {
52
+ if (this.state === TransportState.playing) return;
53
+
54
+ this.validateFutureTime(actionAt);
55
+
56
+ this.scheduler.start(actionAt, () => {
57
+ this.state = TransportState.playing;
58
+ this.offset = offset;
59
+ this.startTime = t(actionAt).subtrack(this.offset);
60
+ });
61
+ this.onStart?.(actionAt);
62
+
63
+ return actionAt;
64
+ }
65
+
66
+ stop({ actionAt: actionAt = now() }: { actionAt?: TTime }) {
67
+ if (this.state === TransportState.stopped) return;
68
+
69
+ this.validateFutureTime(actionAt);
70
+
71
+ this.scheduler.stop(actionAt, () => {
72
+ this.state = TransportState.stopped;
73
+ this.offset = 0;
74
+ });
75
+ this.onStop?.(actionAt);
76
+
77
+ return actionAt;
78
+ }
79
+
80
+ pause({ actionAt: actionAt = now() }: { actionAt?: TTime }) {
81
+ if (this.state === TransportState.paused) return;
82
+
83
+ this.validateFutureTime(actionAt);
84
+
85
+ this.scheduler.stop(actionAt, () => {
86
+ this.state = TransportState.paused;
87
+ this.offset = t(actionAt).subtrack(this.startTime);
88
+ });
89
+ this.onStop?.(actionAt);
90
+
91
+ return actionAt;
92
+ }
93
+
94
+ get playhead(): Time {
95
+ if (this.state === TransportState.stopped) return t(0);
96
+ if (this.state === TransportState.paused) return t(this.offset);
97
+
98
+ return t(now()).subtrack(this.startTime);
99
+ }
100
+
101
+ private validateFutureTime(time: TTime) {
102
+ if (t(time).isBefore(now())) throw Error("Past time not allowed");
103
+ }
104
+ }
@@ -0,0 +1,16 @@
1
+ import { Engine } from "@/Engine";
2
+
3
+ export { default as Transport, TransportState } from "./Transport";
4
+ export { default as Time } from "./Time";
5
+
6
+ export type { TransportEvents } from "./Transport";
7
+ export type { TTime } from "./Time";
8
+
9
+ export function now() {
10
+ return Engine.current.context.currentTime;
11
+ }
12
+
13
+ export function browserToContextTime(time: number): number {
14
+ const differenceBetweenClocks = performance.now() / 1000 - now();
15
+ return time / 1000 - differenceBetweenClocks;
16
+ }
@@ -0,0 +1,36 @@
1
+ export { Module } from "./module";
2
+ export type { IModule, IModuleSerialize, IPolyModuleSerialize } from "./module";
3
+
4
+ export type IAnyAudioContext = AudioContext | OfflineAudioContext;
5
+
6
+ export { Routes } from "./Route";
7
+ export type { IRoute } from "./Route";
8
+
9
+ export { default as MidiDeviceManager } from "./midi/MidiDeviceManager";
10
+ export { default as MidiDevice, MidiPortState } from "./midi/MidiDevice";
11
+ export type { IMidiDevice } from "./midi/MidiDevice";
12
+ export { default as MidiEvent, MidiEventType } from "./midi/MidiEvent";
13
+
14
+ export type {
15
+ MidiOutput,
16
+ MidiInput,
17
+ AudioInput,
18
+ AudioOutput,
19
+ IIOSerialize,
20
+ } from "./IO";
21
+
22
+ export { Time, TransportState, Transport } from "./Timing";
23
+ export type { TTime } from "./Timing";
24
+
25
+ export type {
26
+ PropDefinition,
27
+ PropSchema,
28
+ NumberProp,
29
+ StringProp,
30
+ EnumProp,
31
+ BooleanProp,
32
+ ArrayProp,
33
+ } from "./schema";
34
+
35
+ export { default as Note } from "./Note";
36
+ export type { INote } from "./Note";
@@ -1,8 +1,8 @@
1
1
  import Note from "../Note";
2
- import { IMidiInput, TMidiPortState } from "./MidiDevice";
2
+ import { EventListerCallback, IMidiInput, MidiPortState } from "./MidiDevice";
3
3
  import MidiEvent from "./MidiEvent";
4
4
 
5
- const MAP_KEYS: { [key: string]: Note } = {
5
+ const MAP_KEYS: Record<string, Note> = {
6
6
  a: new Note("C3"),
7
7
  s: new Note("D3"),
8
8
  d: new Note("E3"),
@@ -21,32 +21,52 @@ const MAP_KEYS: { [key: string]: Note } = {
21
21
  p: new Note("D#4"),
22
22
  };
23
23
 
24
- const COMPUTER_KEYBOARD_DATA = {
24
+ const computerKeyboardData = () => ({
25
25
  id: "computer_keyboard",
26
26
  name: "Computer Keyboard",
27
- state: "connected",
28
- };
27
+ state: MidiPortState.connected,
28
+ });
29
29
 
30
30
  export default class ComputerKeyboardInput implements IMidiInput {
31
31
  id: string;
32
32
  name: string;
33
- state: TMidiPortState;
34
- onmidimessage: ((e: MidiEvent) => void) | null;
33
+ state: MidiPortState;
34
+ eventListerCallbacks: EventListerCallback[] = [];
35
35
 
36
36
  constructor() {
37
- Object.assign(this, COMPUTER_KEYBOARD_DATA);
37
+ const { id, name, state } = computerKeyboardData();
38
+ this.id = id;
39
+ this.name = name;
40
+ this.state = state;
41
+
38
42
  document.addEventListener("keydown", this.onKeyTrigger(true));
39
43
  document.addEventListener("keyup", this.onKeyTrigger(false));
40
44
  }
41
45
 
42
- onKeyTrigger = (noteOn: boolean) => (event: KeyboardEvent) => {
43
- if (!this.onmidimessage) return;
46
+ addEventListener(callback: EventListerCallback) {
47
+ this.eventListerCallbacks.push(callback);
48
+ }
49
+
50
+ removeEventListener(callback: EventListerCallback) {
51
+ this.eventListerCallbacks = this.eventListerCallbacks.filter(
52
+ (c) => c !== callback,
53
+ );
54
+ }
55
+
56
+ serialize() {
57
+ const { id, name, state } = this;
44
58
 
59
+ return { id, name, state };
60
+ }
61
+
62
+ onKeyTrigger = (noteOn: boolean) => (event: KeyboardEvent) => {
45
63
  const note = this.extractNote(event);
46
64
  if (!note) return;
47
65
 
48
66
  const midiEvent = MidiEvent.fromNote(note, noteOn);
49
- this.onmidimessage(midiEvent);
67
+ this.eventListerCallbacks.forEach((callback) => {
68
+ callback(midiEvent);
69
+ });
50
70
  };
51
71
 
52
72
  private extractNote(event: KeyboardEvent): Note | undefined {
@@ -1,49 +1,51 @@
1
- import MidiEvent from "./MidiEvent";
1
+ import { MessageEvent, Input } from "webmidi";
2
+ import { browserToContextTime } from "../Timing";
3
+ import MidiEvent, { MidiEventType } from "./MidiEvent";
2
4
 
3
- export type TMidiPortState = "connected" | "disconnected";
5
+ export enum MidiPortState {
6
+ connected = "connected",
7
+ disconnected = "disconnected",
8
+ }
4
9
 
5
- export interface MidiDeviceInterface {
10
+ export type IMidiDevice = {
6
11
  id: string;
7
12
  name: string;
8
- state: TMidiPortState;
9
- }
13
+ state: MidiPortState;
14
+ };
10
15
 
11
- export interface IMidiInput extends MidiDeviceInterface {
12
- onmidimessage: ((e: MidiEvent) => void) | null;
13
- }
16
+ export type IMidiInput = IMidiDevice & {
17
+ eventListerCallbacks: EventListerCallback[];
18
+ };
14
19
 
15
20
  export type EventListerCallback = (event: MidiEvent) => void;
16
21
 
17
- export default class MidiDevice implements MidiDeviceInterface {
22
+ export default class MidiDevice implements IMidiDevice {
18
23
  id: string;
19
24
  name: string;
20
- state: TMidiPortState;
21
25
  eventListerCallbacks: EventListerCallback[] = [];
22
26
 
23
- private _midi: IMidiInput;
27
+ private input: Input;
24
28
 
25
- constructor(midi: IMidiInput) {
26
- this.id = midi.id;
27
- this.name = midi.name || `Device ${midi.id}`;
28
- this.state = midi.state;
29
- this._midi = midi;
29
+ constructor(input: Input) {
30
+ this.id = input.id;
31
+ this.name = input.name || `Device ${input.id}`;
32
+ this.input = input;
30
33
 
31
34
  this.connect();
32
35
  }
33
36
 
34
- connect() {
35
- this._midi.onmidimessage = (e: Event | MidiEvent) => {
36
- const isMidiEvent =
37
- e instanceof MIDIMessageEvent || e instanceof MidiEvent;
38
-
39
- if (!isMidiEvent) return;
37
+ get state() {
38
+ return this.input.state as MidiPortState;
39
+ }
40
40
 
41
+ connect() {
42
+ this.input.addListener("midimessage", (e: MessageEvent) => {
41
43
  this.processEvent(e);
42
- };
44
+ });
43
45
  }
44
46
 
45
47
  disconnect() {
46
- this._midi.onmidimessage = null;
48
+ this.input.removeListener();
47
49
  }
48
50
 
49
51
  serialize() {
@@ -58,17 +60,22 @@ export default class MidiDevice implements MidiDeviceInterface {
58
60
 
59
61
  removeEventListener(callback: EventListerCallback) {
60
62
  this.eventListerCallbacks = this.eventListerCallbacks.filter(
61
- (c) => c !== callback
63
+ (c) => c !== callback,
62
64
  );
63
65
  }
64
66
 
65
- private processEvent(e: MIDIMessageEvent | MidiEvent) {
66
- const event: MidiEvent = e instanceof MidiEvent ? e : new MidiEvent(e);
67
+ private processEvent(event: MessageEvent) {
68
+ const midiEvent = new MidiEvent(
69
+ event.message,
70
+ browserToContextTime(event.timestamp),
71
+ );
67
72
 
68
- switch (event.type) {
69
- case "noteOn":
70
- case "noteOff":
71
- this.eventListerCallbacks.forEach((callback) => callback(event));
73
+ switch (midiEvent.type) {
74
+ case MidiEventType.noteOn:
75
+ case MidiEventType.noteOff:
76
+ this.eventListerCallbacks.forEach((callback) => {
77
+ callback(midiEvent);
78
+ });
72
79
  }
73
80
  }
74
81
  }
@@ -1,80 +1,79 @@
1
- import ComputerKeyboardInput from "./ComputerKeyboardInput";
2
- import MidiDevice, { IMidiInput } from "./MidiDevice";
1
+ import { Input, Output, WebMidi } from "webmidi";
2
+ import ComputerKeyboardDevice from "./ComputerKeyboardDevice";
3
+ import MidiDevice from "./MidiDevice";
4
+
5
+ type ListenerCallback = (device: MidiDevice) => void;
3
6
 
4
7
  export default class MidiDeviceManager {
5
- devices: { [Key: string]: MidiDevice } = {};
8
+ devices = new Map<string, MidiDevice | ComputerKeyboardDevice>();
6
9
  private initialized = false;
7
- private computerKeyboardInput: ComputerKeyboardInput;
10
+ private listeners: ListenerCallback[] = [];
8
11
 
9
12
  constructor() {
10
- this.computerKeyboardInput = new ComputerKeyboardInput();
11
- this.initializeDevices()
12
- .then(() => {
13
- this.listenChanges();
14
- this.initialized = true;
15
- })
16
- .catch(() => {});
13
+ const computerKeyboardDevice = new ComputerKeyboardDevice();
14
+ this.devices.set(computerKeyboardDevice.id, computerKeyboardDevice);
17
15
  }
18
16
 
19
- find(id: string): MidiDevice | null {
20
- const device = this.devices[id];
21
-
22
- if (!device) return null;
17
+ async initialize() {
18
+ await this.initializeDevices();
23
19
 
24
- return device;
20
+ this.listenChanges();
21
+ this.initialized = true;
25
22
  }
26
23
 
27
- onStateChange(callback: (device: MidiDevice) => void) {
28
- navigator
29
- .requestMIDIAccess()
30
- .then((access: MIDIAccess) => {
31
- access.onstatechange = (e) => {
32
- const isMidiEvent = e instanceof MIDIConnectionEvent;
33
-
34
- if (!isMidiEvent) return;
35
- if (e.port instanceof MIDIOutput) return;
36
-
37
- const input = e.port as unknown as IMidiInput;
24
+ find(id: string): MidiDevice | ComputerKeyboardDevice | undefined {
25
+ return this.devices.get(id);
26
+ }
38
27
 
39
- const midi = new MidiDevice(input);
28
+ addListener(callback: ListenerCallback) {
29
+ this.listeners.push(callback);
30
+ }
40
31
 
41
- callback(midi);
42
- };
43
- })
44
- .catch(() => {});
32
+ private async initializeDevices() {
33
+ if (this.initialized) return;
34
+
35
+ try {
36
+ await WebMidi.enable();
37
+
38
+ WebMidi.inputs.forEach((input) => {
39
+ if (!this.devices.has(input.id)) {
40
+ this.devices.set(input.id, new MidiDevice(input));
41
+ }
42
+ });
43
+ } catch (err) {
44
+ console.error("Error enabling WebMidi:", err);
45
+ }
45
46
  }
46
47
 
47
48
  private listenChanges() {
48
- this.onStateChange((device) => {
49
- if (device.state === "disconnected") {
50
- device.disconnect();
51
- delete this.devices[device.id];
52
- } else {
53
- this.devices[device.id] = device;
54
- }
55
- });
56
- }
49
+ WebMidi.addListener("connected", (event) => {
50
+ const port = event.port as Input | Output;
51
+ if (port instanceof Output) return;
57
52
 
58
- private async initializeDevices() {
59
- if (this.initialized) return Object.values(this.devices);
53
+ if (this.devices.has(port.id)) return;
60
54
 
61
- (await this.inputs()).forEach((input) => {
62
- if (this.devices[input.id]) return;
55
+ const device = new MidiDevice(port);
56
+ this.devices.set(device.id, device);
63
57
 
64
- this.devices[input.id] = new MidiDevice(input as IMidiInput);
58
+ this.listeners.forEach((listener) => {
59
+ listener(device);
60
+ });
65
61
  });
66
62
 
67
- return Object.values(this.devices);
68
- }
63
+ WebMidi.addListener("disconnected", (event) => {
64
+ const port = event.port as Input | Output;
65
+ if (port instanceof Output) return;
69
66
 
70
- private async inputs() {
71
- const inputs: Array<MIDIInput | ComputerKeyboardInput> = [
72
- this.computerKeyboardInput,
73
- ];
67
+ const device = this.devices.get(port.id);
68
+ if (!device) return;
69
+ if (device instanceof ComputerKeyboardDevice) return;
74
70
 
75
- const access = await navigator.requestMIDIAccess();
76
- access.inputs.forEach((input) => inputs.push(input));
71
+ device.disconnect();
72
+ this.devices.delete(device.id);
77
73
 
78
- return inputs;
74
+ this.listeners.forEach((listener) => {
75
+ listener(device);
76
+ });
77
+ });
79
78
  }
80
79
  }
@@ -1,91 +1,67 @@
1
- import { now } from "tone";
2
- import { ISequence } from "../../modules";
1
+ import { Message } from "webmidi";
3
2
  import Note, { INote } from "../Note";
3
+ import { t, TTime } from "../Timing/Time";
4
4
 
5
- const EventType: { [key: number]: MidiEventType } = {
6
- 0x80: "noteOff",
7
- 0x90: "noteOn",
8
- 0xb0: "cc",
9
- };
10
-
11
- export type MidiEventType = "noteOn" | "noteOff" | "cc";
5
+ export enum MidiEventType {
6
+ noteOn = "noteon",
7
+ noteOff = "noteoff",
8
+ cc = "cc",
9
+ }
12
10
 
13
11
  export default class MidiEvent {
14
- note: Note;
12
+ note?: Note;
15
13
  voiceNo?: number;
16
- readonly triggeredAt: number;
17
- _type: MidiEventType;
18
- private data: Uint8Array;
19
- private event: MIDIMessageEvent;
20
-
21
- static fromSequence(sequence: ISequence, triggeredAt: number): MidiEvent[] {
22
- return sequence.notes.map((noteName) =>
23
- this.fromNote(noteName, true, triggeredAt)
24
- );
25
- }
14
+ readonly triggeredAt: TTime;
15
+ private message: Message;
26
16
 
27
17
  static fromNote(
28
- noteName: string | Note | INote,
29
- noteOn: boolean = true,
30
- triggeredAt?: number
18
+ noteName: string | Note | Omit<INote, "frequency">,
19
+ noteOn = true,
20
+ triggeredAt?: TTime,
31
21
  ): MidiEvent {
32
22
  const note = noteName instanceof Note ? noteName : new Note(noteName);
33
23
 
34
- const event = new MidiEvent(
35
- new MIDIMessageEvent("", { data: note.midiData(noteOn) }),
36
- triggeredAt
37
- );
38
- event.note = note;
39
-
40
- return event;
24
+ return new MidiEvent(new Message(note.midiData(noteOn)), triggeredAt);
41
25
  }
42
26
 
43
- static fromCC(cc: number, value: number, triggeredAt?: number): MidiEvent {
27
+ static fromCC(cc: number, value: number, triggeredAt?: TTime): MidiEvent {
44
28
  return new MidiEvent(
45
- new MIDIMessageEvent("", { data: new Uint8Array([0xb0, cc, value]) }),
46
- triggeredAt
29
+ new Message(new Uint8Array([0xb0, cc, value])),
30
+ triggeredAt,
47
31
  );
48
32
  }
49
33
 
50
- constructor(event: MIDIMessageEvent, triggeredAt?: number) {
51
- this.event = event;
52
- this.triggeredAt = triggeredAt || now();
53
- this.data = event.data;
34
+ constructor(message: Message, triggeredAt?: TTime) {
35
+ this.message = message;
36
+ this.triggeredAt = triggeredAt ?? t();
54
37
  this.defineNotes();
55
38
  }
56
39
 
57
- get statusByte(): number {
58
- return this.data[0];
59
- }
60
-
61
- get firstData(): number {
62
- return this.data[1];
63
- }
64
-
65
- get secondData(): number {
66
- return this.data[2];
67
- }
68
-
69
40
  get type() {
70
- if (this._type) return this._type;
71
-
72
- let type = EventType[this.statusByte & 0xf0];
73
-
74
- if (type === "noteOn" && this.secondData === 0) {
75
- type = "noteOff";
76
- }
77
-
78
- return (this._type = type);
41
+ return this.message.type as MidiEventType;
79
42
  }
80
43
 
81
44
  get isNote() {
82
- return this.type === "noteOn" || this.type === "noteOff";
45
+ return (
46
+ this.type === MidiEventType.noteOn || this.type === MidiEventType.noteOff
47
+ );
83
48
  }
84
49
 
85
50
  defineNotes() {
86
51
  if (!this.isNote) return;
87
52
  if (this.note) return;
88
53
 
89
- this.note = new Note(this.event);
54
+ this.note = Note.fromEvent(this.message);
55
+ }
56
+
57
+ get rawMessage() {
58
+ return this.message;
59
+ }
60
+
61
+ clone(voiceNo?: number) {
62
+ const newEvent = new MidiEvent(this.message, this.triggeredAt);
63
+ newEvent.voiceNo = voiceNo;
64
+
65
+ return newEvent;
90
66
  }
91
67
  }