@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.
Files changed (43) hide show
  1. package/build/MidiEvent.d.ts +4 -2
  2. package/build/MidiEvent.js +14 -6
  3. package/build/MidiEvent.js.map +1 -1
  4. package/build/Module/Base.d.ts +6 -4
  5. package/build/Module/Base.js +15 -9
  6. package/build/Module/Base.js.map +1 -1
  7. package/build/Module/Envelope/Base.d.ts +4 -3
  8. package/build/Module/Envelope/Base.js +14 -14
  9. package/build/Module/Envelope/Base.js.map +1 -1
  10. package/build/Module/Oscillator.d.ts +2 -3
  11. package/build/Module/Oscillator.js +5 -7
  12. package/build/Module/Oscillator.js.map +1 -1
  13. package/build/Module/PolyModule.d.ts +1 -1
  14. package/build/Module/PolyModule.js +2 -2
  15. package/build/Module/PolyModule.js.map +1 -1
  16. package/build/Module/Sequencer.d.ts +22 -7
  17. package/build/Module/Sequencer.js +96 -29
  18. package/build/Module/Sequencer.js.map +1 -1
  19. package/build/Module/VirtualMidi.d.ts +3 -2
  20. package/build/Module/VirtualMidi.js +6 -11
  21. package/build/Module/VirtualMidi.js.map +1 -1
  22. package/build/Module/VoiceScheduler.d.ts +2 -2
  23. package/build/Module/VoiceScheduler.js +29 -16
  24. package/build/Module/VoiceScheduler.js.map +1 -1
  25. package/build/Module/index.d.ts +2 -0
  26. package/build/Module/index.js +1 -0
  27. package/build/Module/index.js.map +1 -1
  28. package/build/Note/index.d.ts +4 -7
  29. package/build/Note/index.js +6 -11
  30. package/build/Note/index.js.map +1 -1
  31. package/build/index.d.ts +1 -1
  32. package/package.json +1 -1
  33. package/src/MidiEvent.ts +19 -6
  34. package/src/Module/Base.ts +23 -8
  35. package/src/Module/Envelope/Base.ts +14 -14
  36. package/src/Module/Oscillator.ts +6 -7
  37. package/src/Module/PolyModule.ts +6 -2
  38. package/src/Module/Sequencer.ts +113 -33
  39. package/src/Module/VirtualMidi.ts +7 -11
  40. package/src/Module/VoiceScheduler.ts +30 -20
  41. package/src/Module/index.ts +3 -0
  42. package/src/Note/index.ts +9 -21
  43. package/src/index.ts +1 -1
@@ -1,16 +1,25 @@
1
- import { Part } from "tone";
1
+ import { Part, Time } from "tone";
2
2
  import Module, { DummnyInternalModule } from "./Base";
3
- import Note, { INote } from "../Note";
3
+ import { INote } from "../Note";
4
4
  import { Output } from "./IO";
5
5
  import MidiEvent from "../MidiEvent";
6
6
 
7
- interface ISequencer {
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: ISequencer = {
12
- notes: [],
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 _part: Part;
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.registerOutputs();
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 notes() {
34
- return this._props["notes"];
65
+ get sequences() {
66
+ return this._props["sequences"];
35
67
  }
36
68
 
37
- set notes(value: INote[]) {
38
- const notes = value;
39
- this._props = { ...this.props, notes };
40
- this.updateNotesToPart();
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 get part() {
56
- if (this._part) return this._part;
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
- this._part = new Part(this.onEvent, this.notes);
59
- this._part.loop = true;
60
- this._part.loopEnd = this.loopEnd;
97
+ private adjustNumberOfBars() {
98
+ const currentBar = this.sequences.length;
99
+ const num = currentBar - this.bars;
61
100
 
62
- return this._part;
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 updateNotesToPart() {
66
- this.part.clear();
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
- this.notes.forEach((note) => {
69
- this.part.add(note.time, note);
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
- const end =
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 onEvent = (time: number, note: INote) => {
83
- const event = MidiEvent.fromNote(note, "noteOn", time);
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(midiEvent: MidiEvent) {
48
- if (!midiEvent.note) return;
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 !== midiEvent.note!.fullName
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 voice: Voice | undefined;
46
+ let voices: Array<Voice | undefined>;
47
47
 
48
48
  switch (midiEvent.type) {
49
49
  case "noteOn":
50
- voice = this.findFreeVoice();
50
+ voices = this.findFreeVoices(midiEvent.notes.length);
51
51
 
52
52
  break;
53
53
  case "noteOff":
54
- voice = this.audioModules.find(
55
- (v) => v.activeNote === midiEvent.note?.fullName
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 (voice === undefined) return;
62
+ if (voices.length === 0) return;
63
63
 
64
- voice.midiTriggered(midiEvent);
65
- this.midiOutput.connections.forEach((input) => {
66
- input.pluggable(midiEvent, voice?.voiceNo);
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 findFreeVoice(): Voice {
81
- let voice = this.audioModules.find((v) => !v.activeNote);
84
+ private findFreeVoices(num: number = 1): Voice[] {
85
+ let voices = this.audioModules.filter((v) => !v.activeNote).slice(0, num);
82
86
 
83
- if (!voice) {
84
- voice = this.audioModules.sort((a, b) => {
85
- if (!a || !b) return 0;
87
+ if (voices.length === 0) {
88
+ voices = this.audioModules
89
+ .sort((a, b) => {
90
+ if (!a || !b) return 0;
86
91
 
87
- return a.triggeredAt - b.triggeredAt;
88
- })[0];
92
+ return a.triggeredAt - b.triggeredAt;
93
+ })
94
+ .slice(0, num);
89
95
  }
90
96
 
91
- return voice;
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
- const { triggeredAt, note, type } = midiEvent;
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) throw Error("No valid note on this event");
143
+ if (!note) return;
134
144
  const noteName = note.fullName;
135
145
 
136
146
  switch (type) {
@@ -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
- time: string;
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
- time: TimeClass = Time("0:0:0");
21
- velocity?: number = 1;
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
- const { note, time, duration, velocity } = props;
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";