@blibliki/engine 0.1.9 → 0.1.10
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/build/MidiEvent.d.ts +4 -2
- package/build/MidiEvent.js +14 -6
- package/build/MidiEvent.js.map +1 -1
- package/build/Module/Base.d.ts +6 -4
- package/build/Module/Base.js +15 -9
- package/build/Module/Base.js.map +1 -1
- package/build/Module/Envelope/Base.d.ts +4 -3
- package/build/Module/Envelope/Base.js +14 -14
- package/build/Module/Envelope/Base.js.map +1 -1
- package/build/Module/Oscillator.d.ts +2 -3
- package/build/Module/Oscillator.js +5 -7
- package/build/Module/Oscillator.js.map +1 -1
- package/build/Module/PolyModule.d.ts +1 -1
- package/build/Module/PolyModule.js +2 -2
- package/build/Module/PolyModule.js.map +1 -1
- package/build/Module/Sequencer.d.ts +22 -7
- package/build/Module/Sequencer.js +96 -29
- package/build/Module/Sequencer.js.map +1 -1
- package/build/Module/VirtualMidi.d.ts +3 -2
- package/build/Module/VirtualMidi.js +6 -11
- package/build/Module/VirtualMidi.js.map +1 -1
- package/build/Module/VoiceScheduler.d.ts +2 -2
- package/build/Module/VoiceScheduler.js +29 -16
- package/build/Module/VoiceScheduler.js.map +1 -1
- package/build/Module/index.d.ts +2 -0
- package/build/Module/index.js +1 -0
- package/build/Module/index.js.map +1 -1
- package/build/Note/index.d.ts +4 -7
- package/build/Note/index.js +6 -11
- package/build/Note/index.js.map +1 -1
- package/build/index.d.ts +1 -1
- package/package.json +1 -1
- package/src/MidiEvent.ts +19 -6
- package/src/Module/Base.ts +23 -8
- package/src/Module/Envelope/Base.ts +14 -14
- package/src/Module/Oscillator.ts +6 -7
- package/src/Module/PolyModule.ts +6 -2
- package/src/Module/Sequencer.ts +113 -33
- package/src/Module/VirtualMidi.ts +7 -11
- package/src/Module/VoiceScheduler.ts +30 -20
- package/src/Module/index.ts +3 -0
- package/src/Note/index.ts +9 -21
- package/src/index.ts +1 -1
package/src/Module/Sequencer.ts
CHANGED
|
@@ -1,16 +1,25 @@
|
|
|
1
|
-
import { Part } from "tone";
|
|
1
|
+
import { Part, Time } from "tone";
|
|
2
2
|
import Module, { DummnyInternalModule } from "./Base";
|
|
3
|
-
import
|
|
3
|
+
import { INote } from "../Note";
|
|
4
4
|
import { Output } from "./IO";
|
|
5
5
|
import MidiEvent from "../MidiEvent";
|
|
6
6
|
|
|
7
|
-
interface
|
|
7
|
+
export interface ISequence {
|
|
8
|
+
active: boolean;
|
|
9
|
+
time: string;
|
|
8
10
|
notes: INote[];
|
|
9
11
|
}
|
|
12
|
+
interface ISequencer {
|
|
13
|
+
sequences: ISequence[][];
|
|
14
|
+
length: number;
|
|
15
|
+
bars: number;
|
|
16
|
+
}
|
|
10
17
|
|
|
11
|
-
const InitialProps
|
|
12
|
-
|
|
13
|
-
|
|
18
|
+
const InitialProps = () => ({
|
|
19
|
+
sequences: [],
|
|
20
|
+
length: 16,
|
|
21
|
+
bars: 1,
|
|
22
|
+
});
|
|
14
23
|
|
|
15
24
|
export default class Sequencer extends Module<
|
|
16
25
|
DummnyInternalModule,
|
|
@@ -18,26 +27,49 @@ export default class Sequencer extends Module<
|
|
|
18
27
|
> {
|
|
19
28
|
static moduleName = "Sequencer";
|
|
20
29
|
midiOutput: Output;
|
|
21
|
-
private
|
|
30
|
+
private part: Part<number>;
|
|
31
|
+
private barParts: Part<ISequence>[] = [];
|
|
22
32
|
|
|
23
33
|
constructor(name: string, props: Partial<ISequencer>) {
|
|
24
34
|
super(new DummnyInternalModule(), {
|
|
25
35
|
name,
|
|
26
|
-
props: { ...InitialProps, ...props },
|
|
36
|
+
props: { ...InitialProps(), ...props },
|
|
27
37
|
});
|
|
28
38
|
|
|
29
|
-
this.
|
|
39
|
+
this.initializePart();
|
|
30
40
|
this.start();
|
|
41
|
+
this.registerOutputs();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
get length() {
|
|
45
|
+
return this._props["length"];
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
set length(value: number) {
|
|
49
|
+
this._props["length"] = value;
|
|
50
|
+
this.adjustNumberOfSequences();
|
|
51
|
+
this.updateBarParts();
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
get bars() {
|
|
55
|
+
return this.props["bars"];
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
set bars(value: number) {
|
|
59
|
+
this._props["bars"] = value;
|
|
60
|
+
this.adjustNumberOfBars();
|
|
61
|
+
this.adjustNumberOfSequences();
|
|
62
|
+
this.updateBarParts();
|
|
31
63
|
}
|
|
32
64
|
|
|
33
|
-
get
|
|
34
|
-
return this._props["
|
|
65
|
+
get sequences() {
|
|
66
|
+
return this._props["sequences"];
|
|
35
67
|
}
|
|
36
68
|
|
|
37
|
-
set
|
|
38
|
-
const
|
|
39
|
-
this._props = { ...this.props,
|
|
40
|
-
this.
|
|
69
|
+
set sequences(value: ISequence[][]) {
|
|
70
|
+
const sequences = value;
|
|
71
|
+
this._props = { ...this.props, sequences };
|
|
72
|
+
this.updateBarParts();
|
|
41
73
|
}
|
|
42
74
|
|
|
43
75
|
start() {
|
|
@@ -52,35 +84,83 @@ export default class Sequencer extends Module<
|
|
|
52
84
|
this.midiOutput = this.registerOutput({ name: "midi out" });
|
|
53
85
|
}
|
|
54
86
|
|
|
55
|
-
private
|
|
56
|
-
|
|
87
|
+
private initializePart() {
|
|
88
|
+
this.part = new Part(this.onPartEvent, [] as number[]);
|
|
89
|
+
this.part.loop = true;
|
|
90
|
+
this.part.loopEnd = this.loopEnd;
|
|
91
|
+
|
|
92
|
+
this.sequences.forEach((_, i) => {
|
|
93
|
+
this.part.add(`${i}:0:0`, i);
|
|
94
|
+
});
|
|
95
|
+
}
|
|
57
96
|
|
|
58
|
-
|
|
59
|
-
this.
|
|
60
|
-
|
|
97
|
+
private adjustNumberOfBars() {
|
|
98
|
+
const currentBar = this.sequences.length;
|
|
99
|
+
const num = currentBar - this.bars;
|
|
61
100
|
|
|
62
|
-
return
|
|
101
|
+
if (num === 0) return;
|
|
102
|
+
|
|
103
|
+
if (num > 0) {
|
|
104
|
+
this.part?.remove(`${currentBar}:0:0`);
|
|
105
|
+
this.sequences.pop();
|
|
106
|
+
} else {
|
|
107
|
+
this.part?.add(`${currentBar}:0:0`, currentBar);
|
|
108
|
+
this.sequences.push([]);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
this.adjustNumberOfBars();
|
|
63
112
|
}
|
|
64
113
|
|
|
65
|
-
private
|
|
66
|
-
this.
|
|
114
|
+
private adjustNumberOfSequences(bar = 0) {
|
|
115
|
+
if (!this.bars) return;
|
|
116
|
+
|
|
117
|
+
const sequences = this.sequences[bar];
|
|
118
|
+
const num = sequences.length - this.length;
|
|
119
|
+
|
|
120
|
+
if (num === 0) {
|
|
121
|
+
if (bar === this.bars - 1) return;
|
|
122
|
+
|
|
123
|
+
this.adjustNumberOfSequences(bar + 1);
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (num > 0) {
|
|
128
|
+
sequences.pop();
|
|
129
|
+
} else {
|
|
130
|
+
const index = sequences.length;
|
|
131
|
+
sequences.push({ active: false, time: `${bar}:0:${index}`, notes: [] });
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
this.adjustNumberOfSequences(bar);
|
|
135
|
+
}
|
|
67
136
|
|
|
68
|
-
|
|
69
|
-
|
|
137
|
+
private updateBarParts() {
|
|
138
|
+
this.barParts = this.sequences.map((barSeqs, bar) => {
|
|
139
|
+
const part = new Part(this.onSequenceEvent, [] as Array<ISequence>);
|
|
140
|
+
barSeqs.forEach((seq) => part.add(seq.time, seq));
|
|
141
|
+
|
|
142
|
+
return part;
|
|
70
143
|
});
|
|
71
144
|
}
|
|
72
145
|
|
|
73
146
|
private get loopEnd() {
|
|
74
|
-
|
|
75
|
-
Math.max(
|
|
76
|
-
...this.notes.map((note) => parseInt(note.time.split(":")[0], 10))
|
|
77
|
-
) + 1;
|
|
78
|
-
|
|
79
|
-
return `${end}:0:0`;
|
|
147
|
+
return `${this.bars}:0:0`;
|
|
80
148
|
}
|
|
81
149
|
|
|
82
|
-
private
|
|
83
|
-
|
|
150
|
+
private onPartEvent = (time: number, bar: number | null) => {
|
|
151
|
+
if (bar === null) return;
|
|
152
|
+
|
|
153
|
+
const part = this.barParts[bar];
|
|
154
|
+
if (!part) return;
|
|
155
|
+
|
|
156
|
+
part.start(time);
|
|
157
|
+
part.stop(time + Time("1m").toSeconds());
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
private onSequenceEvent = (time: number, sequence: ISequence) => {
|
|
161
|
+
if (!sequence.active) return;
|
|
162
|
+
|
|
163
|
+
const event = MidiEvent.fromSequence(sequence, time);
|
|
84
164
|
|
|
85
165
|
this.midiOutput.connections.forEach((input) => {
|
|
86
166
|
input.pluggable(event);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import Engine from "../Engine";
|
|
2
2
|
import MidiEvent from "../MidiEvent";
|
|
3
|
+
import Note from "../Note";
|
|
3
4
|
import Module, { DummnyInternalModule } from "./Base";
|
|
4
5
|
import { Output } from "./IO";
|
|
5
6
|
|
|
@@ -38,27 +39,22 @@ export default class VirtualMidi extends Module<
|
|
|
38
39
|
}
|
|
39
40
|
|
|
40
41
|
sendMidi(midiEvent: MidiEvent) {
|
|
41
|
-
this.midiTriggered(midiEvent);
|
|
42
42
|
this.midiOutput.connections.forEach((input) => {
|
|
43
43
|
input.pluggable(midiEvent);
|
|
44
44
|
});
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
triggerAttack(
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
this.activeNotes = [...this.activeNotes, midiEvent.note.fullName];
|
|
47
|
+
triggerAttack = (note: Note) => {
|
|
48
|
+
this.activeNotes = [...this.activeNotes, note.fullName];
|
|
51
49
|
Engine._triggerPropsUpdate(this.id, this.props);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
triggerRelease(midiEvent: MidiEvent) {
|
|
55
|
-
if (!midiEvent.note) return;
|
|
50
|
+
};
|
|
56
51
|
|
|
52
|
+
triggerRelease = (note: Note) => {
|
|
57
53
|
this.activeNotes = this.activeNotes.filter(
|
|
58
|
-
(name) => name !==
|
|
54
|
+
(name) => name !== note.fullName
|
|
59
55
|
);
|
|
60
56
|
Engine._triggerPropsUpdate(this.id, this.props);
|
|
61
|
-
}
|
|
57
|
+
};
|
|
62
58
|
|
|
63
59
|
serialize() {
|
|
64
60
|
return {
|
|
@@ -43,27 +43,31 @@ export default class VoiceScheduler extends PolyModule<
|
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
midiTriggered = (midiEvent: MidiEvent) => {
|
|
46
|
-
let
|
|
46
|
+
let voices: Array<Voice | undefined>;
|
|
47
47
|
|
|
48
48
|
switch (midiEvent.type) {
|
|
49
49
|
case "noteOn":
|
|
50
|
-
|
|
50
|
+
voices = this.findFreeVoices(midiEvent.notes.length);
|
|
51
51
|
|
|
52
52
|
break;
|
|
53
53
|
case "noteOff":
|
|
54
|
-
|
|
55
|
-
(v) => v.activeNote ===
|
|
56
|
-
);
|
|
54
|
+
voices = midiEvent.notes.map((note) => {
|
|
55
|
+
return this.audioModules.find((v) => v.activeNote === note.fullName);
|
|
56
|
+
});
|
|
57
57
|
break;
|
|
58
58
|
default:
|
|
59
59
|
throw Error("This type is not a note");
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
-
if (
|
|
62
|
+
if (voices.length === 0) return;
|
|
63
63
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
64
|
+
voices.forEach((voice, i) => {
|
|
65
|
+
if (!voice) return;
|
|
66
|
+
|
|
67
|
+
voice.midiTriggered(midiEvent, i);
|
|
68
|
+
this.midiOutput.connections.forEach((input) => {
|
|
69
|
+
input.pluggable(midiEvent, voice.voiceNo, i);
|
|
70
|
+
});
|
|
67
71
|
});
|
|
68
72
|
};
|
|
69
73
|
|
|
@@ -77,18 +81,20 @@ export default class VoiceScheduler extends PolyModule<
|
|
|
77
81
|
};
|
|
78
82
|
}
|
|
79
83
|
|
|
80
|
-
private
|
|
81
|
-
let
|
|
84
|
+
private findFreeVoices(num: number = 1): Voice[] {
|
|
85
|
+
let voices = this.audioModules.filter((v) => !v.activeNote).slice(0, num);
|
|
82
86
|
|
|
83
|
-
if (
|
|
84
|
-
|
|
85
|
-
|
|
87
|
+
if (voices.length === 0) {
|
|
88
|
+
voices = this.audioModules
|
|
89
|
+
.sort((a, b) => {
|
|
90
|
+
if (!a || !b) return 0;
|
|
86
91
|
|
|
87
|
-
|
|
88
|
-
|
|
92
|
+
return a.triggeredAt - b.triggeredAt;
|
|
93
|
+
})
|
|
94
|
+
.slice(0, num);
|
|
89
95
|
}
|
|
90
96
|
|
|
91
|
-
return
|
|
97
|
+
return voices;
|
|
92
98
|
}
|
|
93
99
|
|
|
94
100
|
private registerInputs() {
|
|
@@ -127,10 +133,14 @@ class Voice extends Module<DummnyInternalModule, VoiceInterface> {
|
|
|
127
133
|
});
|
|
128
134
|
}
|
|
129
135
|
|
|
130
|
-
midiTriggered = (midiEvent: MidiEvent) => {
|
|
131
|
-
|
|
136
|
+
midiTriggered = (midiEvent: MidiEvent, noteIndex?: number) => {
|
|
137
|
+
if (this.voiceNo === undefined) throw Error("Voice without voiceNo");
|
|
138
|
+
if (noteIndex === undefined) return;
|
|
139
|
+
|
|
140
|
+
const { triggeredAt, notes, type } = midiEvent;
|
|
141
|
+
const note = notes[noteIndex];
|
|
132
142
|
|
|
133
|
-
if (!note)
|
|
143
|
+
if (!note) return;
|
|
134
144
|
const noteName = note.fullName;
|
|
135
145
|
|
|
136
146
|
switch (type) {
|
package/src/Module/index.ts
CHANGED
|
@@ -21,6 +21,9 @@ export type { ModuleInterface, Connectable, Triggerable } from "./Base";
|
|
|
21
21
|
|
|
22
22
|
export { default as Filter } from "./Filter";
|
|
23
23
|
export { default as Oscillator } from "./Oscillator";
|
|
24
|
+
export { default as Sequencer } from "./Sequencer";
|
|
25
|
+
export type { ISequence } from "./Sequencer";
|
|
26
|
+
|
|
24
27
|
export {
|
|
25
28
|
Envelope,
|
|
26
29
|
AmpEnvelope,
|
package/src/Note/index.ts
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import { TimeClass, Time } from "tone";
|
|
2
|
-
import MidiEvent from "../MidiEvent";
|
|
3
1
|
import frequencyTable from "./frequencyTable";
|
|
4
2
|
|
|
5
3
|
const Notes = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"];
|
|
@@ -8,25 +6,18 @@ const NotesLength = Notes.length;
|
|
|
8
6
|
|
|
9
7
|
export interface INote {
|
|
10
8
|
note: string;
|
|
11
|
-
|
|
9
|
+
duration: string;
|
|
12
10
|
velocity?: number;
|
|
13
|
-
duration?: string;
|
|
14
11
|
}
|
|
15
12
|
|
|
16
13
|
export default class Note {
|
|
17
14
|
static _notes: Note[];
|
|
18
15
|
name: string;
|
|
19
16
|
octave: number;
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
duration?: string;
|
|
23
|
-
|
|
24
|
-
constructor(
|
|
25
|
-
eventOrString: INote | MIDIMessageEvent | string,
|
|
26
|
-
duration?: string
|
|
27
|
-
) {
|
|
28
|
-
this.duration = duration;
|
|
17
|
+
velocity: number = 1;
|
|
18
|
+
duration: string;
|
|
29
19
|
|
|
20
|
+
constructor(eventOrString: Partial<INote> | MIDIMessageEvent | string) {
|
|
30
21
|
if (typeof eventOrString === "string") {
|
|
31
22
|
this.fromString(eventOrString);
|
|
32
23
|
} else if (eventOrString instanceof MIDIMessageEvent) {
|
|
@@ -72,10 +63,9 @@ export default class Note {
|
|
|
72
63
|
|
|
73
64
|
serialize(): INote {
|
|
74
65
|
return {
|
|
75
|
-
time: this.time.toBarsBeatsSixteenths(),
|
|
76
66
|
note: this.fullName,
|
|
67
|
+
velocity: this.velocity,
|
|
77
68
|
duration: this.duration,
|
|
78
|
-
velocity: 1,
|
|
79
69
|
};
|
|
80
70
|
}
|
|
81
71
|
|
|
@@ -91,12 +81,10 @@ export default class Note {
|
|
|
91
81
|
this.octave = Math.floor(event.data[1] / 12) - 2;
|
|
92
82
|
}
|
|
93
83
|
|
|
94
|
-
private fromProps(props: INote) {
|
|
95
|
-
|
|
84
|
+
private fromProps(props: Partial<INote>) {
|
|
85
|
+
Object.assign(this, props);
|
|
86
|
+
if (!props.note) throw Error("note props is mandatory");
|
|
96
87
|
|
|
97
|
-
this.fromString(note);
|
|
98
|
-
this.time = Time(time);
|
|
99
|
-
this.duration = duration;
|
|
100
|
-
this.velocity = velocity;
|
|
88
|
+
this.fromString(props.note);
|
|
101
89
|
}
|
|
102
90
|
}
|
package/src/index.ts
CHANGED
|
@@ -5,6 +5,6 @@ export { default as Note } from "./Note";
|
|
|
5
5
|
|
|
6
6
|
export type { INote } from "./Note";
|
|
7
7
|
export type { MidiDeviceInterface } from "./MidiDevice";
|
|
8
|
-
export type { ModuleInterface, AudioModule } from "./Module";
|
|
8
|
+
export type { ModuleInterface, AudioModule, ISequence } from "./Module";
|
|
9
9
|
|
|
10
10
|
export type { SerializeInterface as IOProps } from "./Module/IO";
|