@blibliki/engine 0.1.8 → 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/Engine.d.ts +4 -1
- package/build/Engine.js +16 -0
- package/build/Engine.js.map +1 -1
- package/build/MidiEvent.d.ts +10 -7
- package/build/MidiEvent.js +22 -9
- package/build/MidiEvent.js.map +1 -1
- package/build/Module/Base.d.ts +7 -3
- package/build/Module/Base.js +17 -8
- package/build/Module/Base.js.map +1 -1
- package/build/Module/Envelope/Base.d.ts +4 -3
- package/build/Module/Envelope/Base.js +13 -8
- package/build/Module/Envelope/Base.js.map +1 -1
- package/build/Module/IO.js +1 -1
- package/build/Module/IO.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 +37 -0
- package/build/Module/Sequencer.js +128 -0
- package/build/Module/Sequencer.js.map +1 -0
- 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 +4 -0
- package/build/Module/index.js.map +1 -1
- package/build/Note/index.d.ts +10 -1
- package/build/Note/index.js +19 -1
- package/build/Note/index.js.map +1 -1
- package/build/index.d.ts +2 -1
- package/package.json +1 -1
- package/src/Engine.ts +25 -2
- package/src/MidiEvent.ts +37 -12
- package/src/Module/Base.ts +27 -7
- package/src/Module/Envelope/Base.ts +13 -8
- package/src/Module/IO.ts +2 -2
- package/src/Module/Oscillator.ts +6 -7
- package/src/Module/PolyModule.ts +6 -2
- package/src/Module/Sequencer.ts +169 -0
- package/src/Module/VirtualMidi.ts +7 -11
- package/src/Module/VoiceScheduler.ts +30 -20
- package/src/Module/index.ts +6 -0
- package/src/Note/index.ts +27 -2
- package/src/index.ts +2 -1
package/src/Module/Base.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { InputNode } from "tone";
|
|
|
4
4
|
import { Input, Output, IOInterface } from "./IO";
|
|
5
5
|
import MidiEvent from "../MidiEvent";
|
|
6
6
|
import { AudioModule, PolyModule } from "../Module";
|
|
7
|
+
import Note from "../Note";
|
|
7
8
|
|
|
8
9
|
export interface Connectable {
|
|
9
10
|
connect: (inputNode: InputNode) => void;
|
|
@@ -14,6 +15,7 @@ export interface Connectable {
|
|
|
14
15
|
export interface Triggerable {
|
|
15
16
|
triggerAttack: Function;
|
|
16
17
|
triggerRelease: Function;
|
|
18
|
+
triggerAttackRelease: Function;
|
|
17
19
|
}
|
|
18
20
|
|
|
19
21
|
export interface Voicable {
|
|
@@ -48,7 +50,7 @@ class Module<InternalModule extends Connectable, PropsInterface>
|
|
|
48
50
|
outputs: Output[] = [];
|
|
49
51
|
readonly voiceNo?: number;
|
|
50
52
|
updatedAt: Date;
|
|
51
|
-
_props: PropsInterface;
|
|
53
|
+
_props: PropsInterface = {} as PropsInterface;
|
|
52
54
|
|
|
53
55
|
constructor(internalModule: InternalModule, props: Partial<ModuleInterface>) {
|
|
54
56
|
this.internalModule = internalModule;
|
|
@@ -59,7 +61,6 @@ class Module<InternalModule extends Connectable, PropsInterface>
|
|
|
59
61
|
|
|
60
62
|
set props(value: PropsInterface) {
|
|
61
63
|
if (!value) return;
|
|
62
|
-
if (!this._props) this._props = value;
|
|
63
64
|
|
|
64
65
|
this.updatedAt = new Date();
|
|
65
66
|
|
|
@@ -121,27 +122,46 @@ class Module<InternalModule extends Connectable, PropsInterface>
|
|
|
121
122
|
this.internalModule.dispose();
|
|
122
123
|
}
|
|
123
124
|
|
|
124
|
-
triggerAttack(
|
|
125
|
+
triggerAttack(note: Note, triggeredAt: number) {
|
|
125
126
|
throw Error("triggerAttack not implemented");
|
|
126
127
|
}
|
|
127
128
|
|
|
128
|
-
triggerRelease(
|
|
129
|
+
triggerRelease(note: Note, triggeredAt: number) {
|
|
129
130
|
throw Error("triggerRelease not implemented");
|
|
130
131
|
}
|
|
131
132
|
|
|
132
|
-
|
|
133
|
+
triggerAttackRelease(note: Note, triggeredAt: number) {
|
|
134
|
+
throw Error("triggerAttackRelease not implemented");
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
midiTriggered = (midiEvent: MidiEvent, noteIndex?: number) => {
|
|
133
138
|
switch (midiEvent.type) {
|
|
134
139
|
case "noteOn":
|
|
135
|
-
this.triggerAttack
|
|
140
|
+
this.triggerer(this.triggerAttack, midiEvent, noteIndex);
|
|
136
141
|
break;
|
|
137
142
|
case "noteOff":
|
|
138
|
-
this.triggerRelease
|
|
143
|
+
this.triggerer(this.triggerRelease, midiEvent, noteIndex);
|
|
139
144
|
break;
|
|
140
145
|
default:
|
|
141
146
|
throw Error("This type is not a note");
|
|
142
147
|
}
|
|
143
148
|
};
|
|
144
149
|
|
|
150
|
+
private triggerer(
|
|
151
|
+
trigger: Function,
|
|
152
|
+
midiEvent: MidiEvent,
|
|
153
|
+
noteIndex?: number
|
|
154
|
+
) {
|
|
155
|
+
const { notes, triggeredAt } = midiEvent;
|
|
156
|
+
|
|
157
|
+
if (noteIndex !== undefined && this.voiceNo !== undefined) {
|
|
158
|
+
trigger(notes[noteIndex], triggeredAt);
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
notes.forEach((note) => trigger(note, triggeredAt));
|
|
163
|
+
}
|
|
164
|
+
|
|
145
165
|
serialize() {
|
|
146
166
|
const klass = this.constructor as typeof Module;
|
|
147
167
|
|
|
@@ -3,6 +3,7 @@ import { Envelope as Env } from "tone";
|
|
|
3
3
|
import Module, { Connectable, Triggerable, Voicable } from "../Base";
|
|
4
4
|
import PolyModule from "../PolyModule";
|
|
5
5
|
import MidiEvent from "../../MidiEvent";
|
|
6
|
+
import Note from "../../Note";
|
|
6
7
|
|
|
7
8
|
export const enum EnvelopeStages {
|
|
8
9
|
Attack = "attack",
|
|
@@ -88,21 +89,25 @@ export default abstract class EnvelopeModule<EnvelopeLike extends Env>
|
|
|
88
89
|
this.internalModule[stage] = calculatedValue;
|
|
89
90
|
}
|
|
90
91
|
|
|
91
|
-
triggerAttack(
|
|
92
|
-
|
|
92
|
+
triggerAttack = (note: Note, triggeredAt: number) => {
|
|
93
|
+
if (note.duration) return this.triggerAttackRelease(note, triggeredAt);
|
|
93
94
|
|
|
94
|
-
this.activeNote = note
|
|
95
|
+
this.activeNote = note.fullName;
|
|
95
96
|
this.triggeredAt = triggeredAt;
|
|
96
97
|
this.internalModule.triggerAttack(triggeredAt);
|
|
97
|
-
}
|
|
98
|
+
};
|
|
98
99
|
|
|
99
|
-
|
|
100
|
-
|
|
100
|
+
triggerAttackRelease = (note: Note, triggeredAt: number) => {
|
|
101
|
+
this.activeNote = note.fullName;
|
|
102
|
+
this.triggeredAt = triggeredAt;
|
|
103
|
+
this.internalModule.triggerAttackRelease(note.duration, triggeredAt);
|
|
104
|
+
};
|
|
101
105
|
|
|
102
|
-
|
|
106
|
+
triggerRelease = (note: Note, triggeredAt: number) => {
|
|
107
|
+
if (this.activeNote && this.activeNote !== note.fullName) return;
|
|
103
108
|
|
|
104
109
|
this.internalModule.triggerRelease(triggeredAt);
|
|
105
|
-
}
|
|
110
|
+
};
|
|
106
111
|
|
|
107
112
|
private maxTime(stage: EnvelopeStages): number {
|
|
108
113
|
return stage === EnvelopeStages.Sustain ? SUSTAIN_MAX_VALUE : MAX_TIME;
|
package/src/Module/IO.ts
CHANGED
package/src/Module/Oscillator.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import MidiEvent from "../MidiEvent";
|
|
2
|
-
import { Oscillator as Osc, ToneOscillatorType } from "tone";
|
|
2
|
+
import { now, Oscillator as Osc, ToneOscillatorType } from "tone";
|
|
3
3
|
|
|
4
4
|
import Note from "../Note";
|
|
5
5
|
import Module, { Voicable } from "./Base";
|
|
@@ -112,14 +112,13 @@ class MonoOscillator extends Module<Osc, OscillatorInterface> {
|
|
|
112
112
|
this.internalModule.start();
|
|
113
113
|
}
|
|
114
114
|
|
|
115
|
-
triggerAttack(
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
}
|
|
115
|
+
triggerAttack = (note: Note, triggeredAt: number) => {
|
|
116
|
+
this.setNoteAt(note, triggeredAt);
|
|
117
|
+
};
|
|
119
118
|
|
|
120
|
-
triggerRelease(
|
|
119
|
+
triggerRelease = (note: Note) => {
|
|
121
120
|
// Do nothing
|
|
122
|
-
}
|
|
121
|
+
};
|
|
123
122
|
|
|
124
123
|
private updateFrequency(time?: number) {
|
|
125
124
|
if (!this.note) return;
|
package/src/Module/PolyModule.ts
CHANGED
|
@@ -90,9 +90,13 @@ export default abstract class PolyModule<
|
|
|
90
90
|
});
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
-
midiTriggered = (
|
|
93
|
+
midiTriggered = (
|
|
94
|
+
midiEvent: MidiEvent,
|
|
95
|
+
voiceNo: number,
|
|
96
|
+
noteIndex?: number
|
|
97
|
+
) => {
|
|
94
98
|
const audioModule = this.findVoice(voiceNo);
|
|
95
|
-
audioModule?.midiTriggered(midiEvent);
|
|
99
|
+
audioModule?.midiTriggered(midiEvent, noteIndex);
|
|
96
100
|
};
|
|
97
101
|
|
|
98
102
|
serialize() {
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import { Part, Time } from "tone";
|
|
2
|
+
import Module, { DummnyInternalModule } from "./Base";
|
|
3
|
+
import { INote } from "../Note";
|
|
4
|
+
import { Output } from "./IO";
|
|
5
|
+
import MidiEvent from "../MidiEvent";
|
|
6
|
+
|
|
7
|
+
export interface ISequence {
|
|
8
|
+
active: boolean;
|
|
9
|
+
time: string;
|
|
10
|
+
notes: INote[];
|
|
11
|
+
}
|
|
12
|
+
interface ISequencer {
|
|
13
|
+
sequences: ISequence[][];
|
|
14
|
+
length: number;
|
|
15
|
+
bars: number;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const InitialProps = () => ({
|
|
19
|
+
sequences: [],
|
|
20
|
+
length: 16,
|
|
21
|
+
bars: 1,
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
export default class Sequencer extends Module<
|
|
25
|
+
DummnyInternalModule,
|
|
26
|
+
ISequencer
|
|
27
|
+
> {
|
|
28
|
+
static moduleName = "Sequencer";
|
|
29
|
+
midiOutput: Output;
|
|
30
|
+
private part: Part<number>;
|
|
31
|
+
private barParts: Part<ISequence>[] = [];
|
|
32
|
+
|
|
33
|
+
constructor(name: string, props: Partial<ISequencer>) {
|
|
34
|
+
super(new DummnyInternalModule(), {
|
|
35
|
+
name,
|
|
36
|
+
props: { ...InitialProps(), ...props },
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
this.initializePart();
|
|
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();
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
get sequences() {
|
|
66
|
+
return this._props["sequences"];
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
set sequences(value: ISequence[][]) {
|
|
70
|
+
const sequences = value;
|
|
71
|
+
this._props = { ...this.props, sequences };
|
|
72
|
+
this.updateBarParts();
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
start() {
|
|
76
|
+
this.part.start();
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
stop() {
|
|
80
|
+
this.part.stop();
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
private registerOutputs() {
|
|
84
|
+
this.midiOutput = this.registerOutput({ name: "midi out" });
|
|
85
|
+
}
|
|
86
|
+
|
|
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
|
+
}
|
|
96
|
+
|
|
97
|
+
private adjustNumberOfBars() {
|
|
98
|
+
const currentBar = this.sequences.length;
|
|
99
|
+
const num = currentBar - this.bars;
|
|
100
|
+
|
|
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();
|
|
112
|
+
}
|
|
113
|
+
|
|
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
|
+
}
|
|
136
|
+
|
|
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;
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
private get loopEnd() {
|
|
147
|
+
return `${this.bars}:0:0`;
|
|
148
|
+
}
|
|
149
|
+
|
|
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);
|
|
164
|
+
|
|
165
|
+
this.midiOutput.connections.forEach((input) => {
|
|
166
|
+
input.pluggable(event);
|
|
167
|
+
});
|
|
168
|
+
};
|
|
169
|
+
}
|
|
@@ -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
|
@@ -13,6 +13,7 @@ import Reverb from "./Reverb";
|
|
|
13
13
|
import Delay from "./Delay";
|
|
14
14
|
import Distortion from "./Distortion";
|
|
15
15
|
import BitCrusher from "./BitCrusher";
|
|
16
|
+
import Sequencer from "./Sequencer";
|
|
16
17
|
|
|
17
18
|
export { default } from "./Base";
|
|
18
19
|
export { default as PolyModule } from "./PolyModule";
|
|
@@ -20,6 +21,9 @@ export type { ModuleInterface, Connectable, Triggerable } from "./Base";
|
|
|
20
21
|
|
|
21
22
|
export { default as Filter } from "./Filter";
|
|
22
23
|
export { default as Oscillator } from "./Oscillator";
|
|
24
|
+
export { default as Sequencer } from "./Sequencer";
|
|
25
|
+
export type { ISequence } from "./Sequencer";
|
|
26
|
+
|
|
23
27
|
export {
|
|
24
28
|
Envelope,
|
|
25
29
|
AmpEnvelope,
|
|
@@ -73,6 +77,8 @@ function moduleClassFromType(type: string) {
|
|
|
73
77
|
return Distortion;
|
|
74
78
|
case BitCrusher.moduleName:
|
|
75
79
|
return BitCrusher;
|
|
80
|
+
case Sequencer.moduleName:
|
|
81
|
+
return Sequencer;
|
|
76
82
|
default:
|
|
77
83
|
throw Error(`Unknown module type ${type}`);
|
|
78
84
|
}
|
package/src/Note/index.ts
CHANGED
|
@@ -4,16 +4,26 @@ 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
|
+
export interface INote {
|
|
8
|
+
note: string;
|
|
9
|
+
duration: string;
|
|
10
|
+
velocity?: number;
|
|
11
|
+
}
|
|
12
|
+
|
|
7
13
|
export default class Note {
|
|
8
14
|
static _notes: Note[];
|
|
9
15
|
name: string;
|
|
10
16
|
octave: number;
|
|
17
|
+
velocity: number = 1;
|
|
18
|
+
duration: string;
|
|
11
19
|
|
|
12
|
-
constructor(eventOrString: MIDIMessageEvent | string) {
|
|
20
|
+
constructor(eventOrString: Partial<INote> | MIDIMessageEvent | string) {
|
|
13
21
|
if (typeof eventOrString === "string") {
|
|
14
22
|
this.fromString(eventOrString);
|
|
15
|
-
} else {
|
|
23
|
+
} else if (eventOrString instanceof MIDIMessageEvent) {
|
|
16
24
|
this.fromEvent(eventOrString);
|
|
25
|
+
} else {
|
|
26
|
+
this.fromProps(eventOrString);
|
|
17
27
|
}
|
|
18
28
|
}
|
|
19
29
|
|
|
@@ -51,6 +61,14 @@ export default class Note {
|
|
|
51
61
|
return this.fullName;
|
|
52
62
|
}
|
|
53
63
|
|
|
64
|
+
serialize(): INote {
|
|
65
|
+
return {
|
|
66
|
+
note: this.fullName,
|
|
67
|
+
velocity: this.velocity,
|
|
68
|
+
duration: this.duration,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
54
72
|
private fromString(string: string) {
|
|
55
73
|
const matches = string.match(/(\w#?)(\d)?/) || [];
|
|
56
74
|
|
|
@@ -62,4 +80,11 @@ export default class Note {
|
|
|
62
80
|
this.name = Notes[event.data[1] % 12];
|
|
63
81
|
this.octave = Math.floor(event.data[1] / 12) - 2;
|
|
64
82
|
}
|
|
83
|
+
|
|
84
|
+
private fromProps(props: Partial<INote>) {
|
|
85
|
+
Object.assign(this, props);
|
|
86
|
+
if (!props.note) throw Error("note props is mandatory");
|
|
87
|
+
|
|
88
|
+
this.fromString(props.note);
|
|
89
|
+
}
|
|
65
90
|
}
|
package/src/index.ts
CHANGED
|
@@ -3,7 +3,8 @@ export { default as MidiDevice } from "./MidiDevice";
|
|
|
3
3
|
export { default as MidiDeviceManager } from "./MidiDeviceManager";
|
|
4
4
|
export { default as Note } from "./Note";
|
|
5
5
|
|
|
6
|
+
export type { INote } from "./Note";
|
|
6
7
|
export type { MidiDeviceInterface } from "./MidiDevice";
|
|
7
|
-
export type { ModuleInterface, AudioModule } from "./Module";
|
|
8
|
+
export type { ModuleInterface, AudioModule, ISequence } from "./Module";
|
|
8
9
|
|
|
9
10
|
export type { SerializeInterface as IOProps } from "./Module/IO";
|