@blibliki/engine 0.1.19 → 0.1.21
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/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/MidiSelector.d.ts +1 -1
- package/dist/build/modules/VirtualMidi.d.ts +2 -2
- package/dist/main.cjs.js +5 -5
- package/dist/main.cjs.js.map +1 -1
- package/dist/main.esm.js +5 -5
- package/dist/main.esm.js.map +1 -1
- package/package.json +10 -10
- 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/Envelope/Base.ts +3 -0
- package/src/modules/Filter.ts +46 -4
- package/src/modules/Master.ts +4 -1
- package/src/modules/MidiSelector.ts +3 -3
- package/src/modules/VirtualMidi.ts +4 -2
package/package.json
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@blibliki/engine",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.21",
|
|
4
|
+
"source": "src/index.ts",
|
|
4
5
|
"main": "dist/main.cjs.js",
|
|
5
6
|
"module": "dist/main.esm.js",
|
|
6
7
|
"types": "dist/build/index.d.ts",
|
|
@@ -16,13 +17,6 @@
|
|
|
16
17
|
"tone": "^14.7.77",
|
|
17
18
|
"uuid": "^8.3.2"
|
|
18
19
|
},
|
|
19
|
-
"scripts": {
|
|
20
|
-
"start": "tsc -w -p tsconfig.json",
|
|
21
|
-
"build": "rollup -c",
|
|
22
|
-
"lint": "eslint src",
|
|
23
|
-
"ts:check": "tsc --noEmit",
|
|
24
|
-
"test": "jest"
|
|
25
|
-
},
|
|
26
20
|
"devDependencies": {
|
|
27
21
|
"@rollup/plugin-commonjs": "^25.0.7",
|
|
28
22
|
"@rollup/plugin-node-resolve": "^15.2.3",
|
|
@@ -38,9 +32,15 @@
|
|
|
38
32
|
"jest": "^29.2.2",
|
|
39
33
|
"prettier": "^2.7.1",
|
|
40
34
|
"rollup": "^4.10.0",
|
|
41
|
-
"rollup-plugin-dts": "^6.1.0",
|
|
42
35
|
"ts-jest": "^29.0.3",
|
|
43
36
|
"typescript": "^5.2.2",
|
|
44
37
|
"typescript-language-server": "^4.0.0"
|
|
38
|
+
},
|
|
39
|
+
"scripts": {
|
|
40
|
+
"start": "tsc -w -p tsconfig.json",
|
|
41
|
+
"build": "rollup -c",
|
|
42
|
+
"lint": "eslint src",
|
|
43
|
+
"ts:check": "tsc --noEmit",
|
|
44
|
+
"test": "jest"
|
|
45
45
|
}
|
|
46
|
-
}
|
|
46
|
+
}
|
|
@@ -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));
|
|
@@ -105,7 +105,10 @@ export default abstract class EnvelopeModule<EnvelopeLike extends Env>
|
|
|
105
105
|
};
|
|
106
106
|
|
|
107
107
|
triggerRelease = (note: Note, triggeredAt: number) => {
|
|
108
|
+
if (this.activeNote && this.activeNote !== note.fullName) return;
|
|
109
|
+
|
|
108
110
|
this.internalModule.triggerRelease(triggeredAt);
|
|
111
|
+
this.activeNote = undefined;
|
|
109
112
|
};
|
|
110
113
|
|
|
111
114
|
private maxTime(stage: EnvelopeStages): number {
|
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
|
@@ -59,10 +59,10 @@ export default class MidiSelector extends Module<
|
|
|
59
59
|
if (!this.onMidiEvent || !midiId) return; // Ugly hack because of weird super bug
|
|
60
60
|
|
|
61
61
|
const midiDevice = Engine.midiDeviceManager.find(midiId);
|
|
62
|
-
midiDevice?.addEventListener(this.
|
|
62
|
+
midiDevice?.addEventListener(this.forwardMidiEvent);
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
-
private
|
|
65
|
+
private forwardMidiEvent = (midiEvent: MidiEvent) => {
|
|
66
66
|
this.midiOutput.onMidiEvent(midiEvent);
|
|
67
67
|
};
|
|
68
68
|
|
|
@@ -70,6 +70,6 @@ export default class MidiSelector extends Module<
|
|
|
70
70
|
if (!this.selectedId) return;
|
|
71
71
|
|
|
72
72
|
const midiDevice = Engine.midiDeviceManager.find(this.selectedId);
|
|
73
|
-
midiDevice?.removeEventListener(this.
|
|
73
|
+
midiDevice?.removeEventListener(this.forwardMidiEvent);
|
|
74
74
|
}
|
|
75
75
|
}
|
|
@@ -48,16 +48,18 @@ export default class VirtualMidi extends Module<
|
|
|
48
48
|
this.midiOutput.onMidiEvent(midiEvent);
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
-
triggerAttack = (note: Note) => {
|
|
51
|
+
triggerAttack = (note: Note, triggerAttack: number) => {
|
|
52
52
|
this.activeNotes = [...this.activeNotes, note.fullName];
|
|
53
53
|
Engine._triggerPropsUpdate(this.id, this.props);
|
|
54
|
+
this.sendMidi(MidiEvent.fromNote(note, true, triggerAttack));
|
|
54
55
|
};
|
|
55
56
|
|
|
56
|
-
triggerRelease = (note: Note) => {
|
|
57
|
+
triggerRelease = (note: Note, triggerAttack: number) => {
|
|
57
58
|
this.activeNotes = this.activeNotes.filter(
|
|
58
59
|
(name) => name !== note.fullName
|
|
59
60
|
);
|
|
60
61
|
Engine._triggerPropsUpdate(this.id, this.props);
|
|
62
|
+
this.sendMidi(MidiEvent.fromNote(note, false, triggerAttack));
|
|
61
63
|
};
|
|
62
64
|
|
|
63
65
|
serialize() {
|