@blibliki/engine 0.1.0
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/README.md +4 -0
- package/build/Engine.d.ts +114 -0
- package/build/Engine.js +81 -0
- package/build/Engine.js.map +1 -0
- package/build/MidiDevice.d.ts +25 -0
- package/build/MidiDevice.js +45 -0
- package/build/MidiDevice.js.map +1 -0
- package/build/MidiDeviceManager.d.ts +12 -0
- package/build/MidiDeviceManager.js +45 -0
- package/build/MidiDeviceManager.js.map +1 -0
- package/build/MidiEvent.d.ts +12 -0
- package/build/MidiEvent.js +37 -0
- package/build/MidiEvent.js.map +1 -0
- package/build/Module/Base.d.ts +72 -0
- package/build/Module/Base.js +139 -0
- package/build/Module/Base.js.map +1 -0
- package/build/Module/Envelope/AmpEnvelope.d.ts +8 -0
- package/build/Module/Envelope/AmpEnvelope.js +15 -0
- package/build/Module/Envelope/AmpEnvelope.js.map +1 -0
- package/build/Module/Envelope/Base.d.ts +45 -0
- package/build/Module/Envelope/Base.js +110 -0
- package/build/Module/Envelope/Base.js.map +1 -0
- package/build/Module/Envelope/FreqEnvelope.d.ts +17 -0
- package/build/Module/Envelope/FreqEnvelope.js +51 -0
- package/build/Module/Envelope/FreqEnvelope.js.map +1 -0
- package/build/Module/Envelope/index.d.ts +3 -0
- package/build/Module/Envelope/index.js +4 -0
- package/build/Module/Envelope/index.js.map +1 -0
- package/build/Module/Filter.d.ts +31 -0
- package/build/Module/Filter.js +83 -0
- package/build/Module/Filter.js.map +1 -0
- package/build/Module/IO.d.ts +39 -0
- package/build/Module/IO.js +59 -0
- package/build/Module/IO.js.map +1 -0
- package/build/Module/Master.d.ts +7 -0
- package/build/Module/Master.js +12 -0
- package/build/Module/Master.js.map +1 -0
- package/build/Module/MidiSelector.d.ts +16 -0
- package/build/Module/MidiSelector.js +44 -0
- package/build/Module/MidiSelector.js.map +1 -0
- package/build/Module/Oscillator.d.ts +42 -0
- package/build/Module/Oscillator.js +123 -0
- package/build/Module/Oscillator.js.map +1 -0
- package/build/Module/PolyModule.d.ts +57 -0
- package/build/Module/PolyModule.js +191 -0
- package/build/Module/PolyModule.js.map +1 -0
- package/build/Module/VoiceScheduler.d.ts +37 -0
- package/build/Module/VoiceScheduler.js +117 -0
- package/build/Module/VoiceScheduler.js.map +1 -0
- package/build/Module/Volume.d.ts +15 -0
- package/build/Module/Volume.js +34 -0
- package/build/Module/Volume.js.map +1 -0
- package/build/Module/index.d.ts +20 -0
- package/build/Module/index.js +60 -0
- package/build/Module/index.js.map +1 -0
- package/build/Note/frequencyTable.d.ts +4 -0
- package/build/Note/frequencyTable.js +146 -0
- package/build/Note/frequencyTable.js.map +1 -0
- package/build/Note/index.d.ts +13 -0
- package/build/Note/index.js +53 -0
- package/build/Note/index.js.map +1 -0
- package/build/index.d.ts +8 -0
- package/build/index.js +6 -0
- package/build/index.js.map +1 -0
- package/build/routes.d.ts +17 -0
- package/build/routes.js +23 -0
- package/build/routes.js.map +1 -0
- package/package.json +37 -0
- package/src/Engine.ts +129 -0
- package/src/MidiDevice.ts +67 -0
- package/src/MidiDeviceManager.ts +61 -0
- package/src/MidiEvent.ts +44 -0
- package/src/Module/Base.ts +200 -0
- package/src/Module/Envelope/AmpEnvelope.ts +17 -0
- package/src/Module/Envelope/Base.ts +159 -0
- package/src/Module/Envelope/FreqEnvelope.ts +69 -0
- package/src/Module/Envelope/index.ts +3 -0
- package/src/Module/Filter.ts +111 -0
- package/src/Module/IO.ts +85 -0
- package/src/Module/Master.ts +18 -0
- package/src/Module/MidiSelector.ts +64 -0
- package/src/Module/Oscillator.ts +166 -0
- package/src/Module/PolyModule.ts +243 -0
- package/src/Module/VoiceScheduler.ts +153 -0
- package/src/Module/Volume.ts +46 -0
- package/src/Module/index.ts +78 -0
- package/src/Note/frequencyTable.ts +146 -0
- package/src/Note/index.ts +65 -0
- package/src/index.ts +10 -0
- package/src/routes.ts +41 -0
- package/tsconfig.json +22 -0
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import MidiDevice from "../MidiDevice";
|
|
2
|
+
import MidiDeviceManager from "../MidiDeviceManager";
|
|
3
|
+
import MidiEvent from "../MidiEvent";
|
|
4
|
+
import Module, { ModuleType, DummnyInternalModule } from "./Base";
|
|
5
|
+
import { Output } from "./IO";
|
|
6
|
+
|
|
7
|
+
export interface MidiSelectorInterface {
|
|
8
|
+
selectedId: string | null;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const InitialProps: MidiSelectorInterface = {
|
|
12
|
+
selectedId: null,
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export default class MidiSelector extends Module<
|
|
16
|
+
DummnyInternalModule,
|
|
17
|
+
MidiSelectorInterface
|
|
18
|
+
> {
|
|
19
|
+
midiOutput: Output;
|
|
20
|
+
|
|
21
|
+
constructor(name: string, props: Partial<MidiSelectorInterface>) {
|
|
22
|
+
super(new DummnyInternalModule(), {
|
|
23
|
+
name,
|
|
24
|
+
props: { ...InitialProps, ...props },
|
|
25
|
+
type: ModuleType.MidiSelector,
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
this.registerOutputs();
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
get selectedId() {
|
|
32
|
+
return this._props["selectedId"];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
set selectedId(value: string | null) {
|
|
36
|
+
if (this.selectedId) {
|
|
37
|
+
MidiDeviceManager.find(this.selectedId).then((midiDevice: MidiDevice) => {
|
|
38
|
+
midiDevice.removeEventListener(this.onMidiEvent);
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
this._props = { ...this.props, selectedId: value };
|
|
43
|
+
|
|
44
|
+
if (!value) return;
|
|
45
|
+
|
|
46
|
+
MidiDeviceManager.find(value).then((midiDevice: MidiDevice) => {
|
|
47
|
+
midiDevice.addEventListener(this.onMidiEvent);
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
onMidiEvent = (midiEvent: MidiEvent) => {
|
|
52
|
+
this.midiOutput.connections.forEach((input) => {
|
|
53
|
+
input.pluggable(midiEvent);
|
|
54
|
+
});
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
async availableDevices(): Promise<MidiDevice[]> {
|
|
58
|
+
return MidiDeviceManager.fetchDevices();
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
private registerOutputs() {
|
|
62
|
+
this.midiOutput = this.registerOutput({ name: "midi out" });
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import MidiEvent from "../MidiEvent";
|
|
2
|
+
import { Oscillator as Osc, ToneOscillatorType } from "tone";
|
|
3
|
+
|
|
4
|
+
import Module, { ModuleType } from "../Module";
|
|
5
|
+
import Note from "../Note";
|
|
6
|
+
import PolyModule, { PolyModuleType } from "./PolyModule";
|
|
7
|
+
|
|
8
|
+
export interface OscillatorInterface {
|
|
9
|
+
noteName: string;
|
|
10
|
+
fine: number;
|
|
11
|
+
coarse: number;
|
|
12
|
+
wave: string;
|
|
13
|
+
volume: number;
|
|
14
|
+
range: number;
|
|
15
|
+
voiceNo?: number;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const InitialProps: OscillatorInterface = {
|
|
19
|
+
noteName: "C3",
|
|
20
|
+
fine: 0,
|
|
21
|
+
coarse: 0,
|
|
22
|
+
wave: "sine",
|
|
23
|
+
range: 0,
|
|
24
|
+
volume: 0,
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export default class Oscillator extends Module<Osc, OscillatorInterface> {
|
|
28
|
+
private _note: Note;
|
|
29
|
+
|
|
30
|
+
constructor(name: string, props: Partial<OscillatorInterface>) {
|
|
31
|
+
super(new Osc(), {
|
|
32
|
+
name,
|
|
33
|
+
props: { ...InitialProps, ...props },
|
|
34
|
+
type: ModuleType.Oscillator,
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
this.note = new Note("C3");
|
|
38
|
+
|
|
39
|
+
this.internalModule.sync();
|
|
40
|
+
this.internalModule.start();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
get note(): Note {
|
|
44
|
+
return this._note;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
setNoteAt(value: Note | string, time: number) {
|
|
48
|
+
this._note = this.getNote(value);
|
|
49
|
+
this.updateFrequency(time);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
set note(value: Note | string) {
|
|
53
|
+
this._note = this.getNote(value);
|
|
54
|
+
this.updateFrequency();
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
get noteName() {
|
|
58
|
+
return this._props["noteName"];
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
set noteName(value: string) {
|
|
62
|
+
this._props = { ...this.props, noteName: value };
|
|
63
|
+
this.note = new Note(this.noteName);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
get fine() {
|
|
67
|
+
return this._props["fine"];
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
set fine(value: number) {
|
|
71
|
+
this._props = { ...this.props, fine: Math.floor(value) };
|
|
72
|
+
this.internalModule.detune.value = this.fine;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
get coarse() {
|
|
76
|
+
return this._props["coarse"];
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
set coarse(value: number) {
|
|
80
|
+
this._props = { ...this.props, coarse: Math.floor(value) };
|
|
81
|
+
|
|
82
|
+
this.updateFrequency();
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
get wave() {
|
|
86
|
+
return this._props["wave"];
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
set wave(value: string) {
|
|
90
|
+
this._props = { ...this.props, wave: value };
|
|
91
|
+
this.internalModule.type = this.wave as ToneOscillatorType;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
get volume() {
|
|
95
|
+
return this._props["volume"];
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
set volume(value: number) {
|
|
99
|
+
this._props = { ...this.props, volume: value };
|
|
100
|
+
|
|
101
|
+
this.internalModule.volume.value = this.volume;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
get range() {
|
|
105
|
+
return this._props["range"];
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
set range(value: number) {
|
|
109
|
+
this._props = { ...this.props, range: value };
|
|
110
|
+
this.updateFrequency();
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
start() {
|
|
114
|
+
this.internalModule.start();
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
triggerAttack(midiEvent: MidiEvent) {
|
|
118
|
+
if (!midiEvent.note) return;
|
|
119
|
+
this.setNoteAt(midiEvent.note, midiEvent.triggeredAt);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
triggerRelease(midiEvent: MidiEvent) {
|
|
123
|
+
// Do nothing
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
private updateFrequency(time?: number) {
|
|
127
|
+
if (!this.note) return;
|
|
128
|
+
|
|
129
|
+
const freq = this.note.frequency(this.range, this.coarse);
|
|
130
|
+
|
|
131
|
+
if (time) {
|
|
132
|
+
this.internalModule.restart(time);
|
|
133
|
+
this.internalModule.frequency.setValueAtTime(freq, time);
|
|
134
|
+
} else {
|
|
135
|
+
this.internalModule.restart();
|
|
136
|
+
this.internalModule.frequency.value = freq;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
private getNote(note: Note | string): Note {
|
|
141
|
+
return note instanceof Note ? note : new Note(note);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export class PolyOscillator extends PolyModule<
|
|
146
|
+
Oscillator,
|
|
147
|
+
OscillatorInterface
|
|
148
|
+
> {
|
|
149
|
+
constructor(name: string, props: Partial<OscillatorInterface>) {
|
|
150
|
+
super(PolyModuleType.Oscillator, {
|
|
151
|
+
name,
|
|
152
|
+
props: { ...InitialProps, ...props },
|
|
153
|
+
type: ModuleType.Oscillator,
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
this.registerBasicOutputs();
|
|
157
|
+
this.registerInputs();
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
private registerInputs() {
|
|
161
|
+
this.registerInput({
|
|
162
|
+
name: "midi in",
|
|
163
|
+
pluggable: this.midiTriggered,
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
}
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
import MidiEvent from "../MidiEvent";
|
|
2
|
+
import { v4 as uuidv4 } from "uuid";
|
|
3
|
+
import { createModule } from ".";
|
|
4
|
+
import Module, { ModuleInterface, ModuleType, Connectable } from "./Base";
|
|
5
|
+
import { Input, Output, IOInterface } from "./IO";
|
|
6
|
+
import { AudioModule } from "../Module";
|
|
7
|
+
|
|
8
|
+
export enum PolyModuleType {
|
|
9
|
+
Oscillator = "oscillator",
|
|
10
|
+
Envelope = "envelope",
|
|
11
|
+
AmpEnvelope = "ampEnvelope",
|
|
12
|
+
FreqEnvelope = "freqEnvelope",
|
|
13
|
+
VoiceScheduler = "voiceScheduler",
|
|
14
|
+
Filter = "filter",
|
|
15
|
+
Volume = "volume",
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
interface PolyModuleInterface extends ModuleInterface {}
|
|
19
|
+
|
|
20
|
+
export default abstract class PolyModule<
|
|
21
|
+
MonoAudioModule extends Module<Connectable, any>,
|
|
22
|
+
PropsInterface
|
|
23
|
+
> {
|
|
24
|
+
readonly id: string;
|
|
25
|
+
_name: string;
|
|
26
|
+
audioModules: MonoAudioModule[];
|
|
27
|
+
inputs: Input[] = [];
|
|
28
|
+
outputs: Output[] = [];
|
|
29
|
+
readonly _type: PolyModuleType;
|
|
30
|
+
readonly childrenType: ModuleType;
|
|
31
|
+
private _numberOfVoices: number;
|
|
32
|
+
|
|
33
|
+
constructor(type: PolyModuleType, props: PolyModuleInterface) {
|
|
34
|
+
this.id = uuidv4();
|
|
35
|
+
this._type = type;
|
|
36
|
+
this.childrenType = props.type;
|
|
37
|
+
this.audioModules = [];
|
|
38
|
+
|
|
39
|
+
const { props: extraProps, ...basicProps } = props;
|
|
40
|
+
Object.assign(this, basicProps);
|
|
41
|
+
|
|
42
|
+
this.numberOfVoices = 1;
|
|
43
|
+
|
|
44
|
+
Object.assign(this, { props: extraProps });
|
|
45
|
+
this.registerNumberOfVoicesInput();
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
get type() {
|
|
49
|
+
return this._type;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
get name() {
|
|
53
|
+
return this._name;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
set name(value: string) {
|
|
57
|
+
this._name = value;
|
|
58
|
+
this.audioModules.forEach((m) => (m.name = value));
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Do nothing
|
|
62
|
+
// This is a little hack to avoid override type by children module
|
|
63
|
+
set type(value: PolyModuleType) {}
|
|
64
|
+
|
|
65
|
+
get props() {
|
|
66
|
+
if (this.audioModules.length === 0) {
|
|
67
|
+
throw Error("There isn't any initialized module");
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return this.audioModules[0].props;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
set props(value: PropsInterface) {
|
|
74
|
+
Object.assign(this, value);
|
|
75
|
+
this.audioModules.forEach((m) => (m.props = value));
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
get numberOfVoices() {
|
|
79
|
+
return this._numberOfVoices;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
set numberOfVoices(value: number) {
|
|
83
|
+
this._numberOfVoices = value;
|
|
84
|
+
this.adjustNumberOfModules();
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
plug(audioModule: AudioModule, from: string, to: string) {
|
|
88
|
+
const output = this.outputs.find((i) => i.name === from);
|
|
89
|
+
if (!output) throw Error(`Output ${from} not exist`);
|
|
90
|
+
|
|
91
|
+
console.log(`${this.name}:${from} => ${audioModule.name}:${to}`);
|
|
92
|
+
|
|
93
|
+
const input = audioModule.inputs.find((i) => i.name === to);
|
|
94
|
+
if (!input)
|
|
95
|
+
throw Error(`Input ${to} in module ${audioModule.name} not exist`);
|
|
96
|
+
|
|
97
|
+
output.plug(input);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
unplugAll() {
|
|
101
|
+
this.outputs.forEach((o) => o.unPlugAll());
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
dispose() {
|
|
105
|
+
this.audioModules.forEach((m) => {
|
|
106
|
+
m.dispose();
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
midiTriggered = (midiEvent: MidiEvent, voiceNo: number = 0) => {
|
|
111
|
+
const audioModule = this.findVoice(voiceNo);
|
|
112
|
+
audioModule?.midiTriggered(midiEvent);
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
serialize() {
|
|
116
|
+
if (this.audioModules.length === 0)
|
|
117
|
+
throw Error("There isn't any initialized module");
|
|
118
|
+
|
|
119
|
+
return {
|
|
120
|
+
...this.audioModules[0].serialize(),
|
|
121
|
+
id: this.id,
|
|
122
|
+
type: this.type,
|
|
123
|
+
inputs: this.inputs.map((i) => i.serialize()),
|
|
124
|
+
outputs: this.outputs.map((i) => i.serialize()),
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
protected connect = (
|
|
129
|
+
inputAudioModule: AudioModule,
|
|
130
|
+
attribute: string = "internalModule"
|
|
131
|
+
) => {
|
|
132
|
+
if (inputAudioModule instanceof PolyModule) {
|
|
133
|
+
inputAudioModule.audioModules.forEach((m) => {
|
|
134
|
+
if (m.voiceNo === undefined) throw Error("Voice error");
|
|
135
|
+
|
|
136
|
+
const audioModule = this.findVoice(m.voiceNo);
|
|
137
|
+
audioModule?.connect(m, attribute);
|
|
138
|
+
});
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
this.audioModules.forEach((m) => m.connect(inputAudioModule, attribute));
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
protected disconnect = (
|
|
146
|
+
inputAudioModule: AudioModule,
|
|
147
|
+
attribute: string = "internalModule"
|
|
148
|
+
) => {
|
|
149
|
+
if (inputAudioModule instanceof PolyModule) {
|
|
150
|
+
inputAudioModule.audioModules.forEach((m) => {
|
|
151
|
+
if (m.voiceNo === undefined) throw Error("Voice error");
|
|
152
|
+
|
|
153
|
+
const audioModule = this.findVoice(m.voiceNo);
|
|
154
|
+
|
|
155
|
+
try {
|
|
156
|
+
audioModule?.disconnect(m, attribute);
|
|
157
|
+
} catch (e) {
|
|
158
|
+
console.log(e);
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
try {
|
|
165
|
+
this.audioModules.forEach((m) =>
|
|
166
|
+
m.disconnect(inputAudioModule, attribute)
|
|
167
|
+
);
|
|
168
|
+
} catch (e) {
|
|
169
|
+
console.log(e);
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
protected registerInput(props: IOInterface): Input {
|
|
174
|
+
const input = new Input(this, props);
|
|
175
|
+
this.inputs.push(input);
|
|
176
|
+
|
|
177
|
+
return input;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
protected registerOutput(props: IOInterface): Output {
|
|
181
|
+
const output = new Output(this, props);
|
|
182
|
+
this.outputs.push(output);
|
|
183
|
+
|
|
184
|
+
return output;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
protected find(callback: (audioModule: MonoAudioModule) => boolean) {
|
|
188
|
+
const audioModule = this.audioModules.find(callback);
|
|
189
|
+
if (!audioModule) throw Error(`Audio module not found`);
|
|
190
|
+
|
|
191
|
+
return audioModule;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
protected findVoice(voiceNo: number) {
|
|
195
|
+
return this.audioModules.find((m) => m.voiceNo === voiceNo);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
protected registerBasicOutputs() {
|
|
199
|
+
this.registerOutput({
|
|
200
|
+
name: "output",
|
|
201
|
+
onPlug: (output: Output) => {
|
|
202
|
+
this.connect(output.audioModule);
|
|
203
|
+
},
|
|
204
|
+
onUnPlug: (output: Output) => {
|
|
205
|
+
this.disconnect(output.audioModule);
|
|
206
|
+
},
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
protected registerBasicInputs() {
|
|
211
|
+
this.registerInput({
|
|
212
|
+
name: "input",
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
private registerNumberOfVoicesInput() {
|
|
217
|
+
this.registerInput({
|
|
218
|
+
name: "number of voices",
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
private adjustNumberOfModules() {
|
|
223
|
+
if (this.audioModules.length === this.numberOfVoices) return;
|
|
224
|
+
|
|
225
|
+
if (this.audioModules.length > this.numberOfVoices) {
|
|
226
|
+
const audioModule = this.audioModules.pop();
|
|
227
|
+
audioModule?.dispose();
|
|
228
|
+
} else {
|
|
229
|
+
const props = this.audioModules.length ? this.props : {};
|
|
230
|
+
const audioModule = createModule(this.name, this.childrenType, {
|
|
231
|
+
...props,
|
|
232
|
+
voiceNo: this.audioModules.length,
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
if (audioModule instanceof PolyModule)
|
|
236
|
+
throw Error("Polymodule not supported");
|
|
237
|
+
|
|
238
|
+
this.audioModules.push(audioModule as MonoAudioModule);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
this.adjustNumberOfModules();
|
|
242
|
+
}
|
|
243
|
+
}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import Module, { ModuleType, DummnyInternalModule } from "./Base";
|
|
2
|
+
import MidiEvent from "../MidiEvent";
|
|
3
|
+
import { Input, Output } from "./IO";
|
|
4
|
+
import PolyModule, { PolyModuleType } from "./PolyModule";
|
|
5
|
+
|
|
6
|
+
export interface VoiceSchedulerInterface {
|
|
7
|
+
polyNumber: number;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export default class VoiceScheduler extends PolyModule<
|
|
11
|
+
Voice,
|
|
12
|
+
VoiceSchedulerInterface
|
|
13
|
+
> {
|
|
14
|
+
midiOutput: Output;
|
|
15
|
+
numberOfVoicesOut: Output;
|
|
16
|
+
|
|
17
|
+
constructor(name: string, props: VoiceSchedulerInterface) {
|
|
18
|
+
super(PolyModuleType.VoiceScheduler, {
|
|
19
|
+
name,
|
|
20
|
+
props,
|
|
21
|
+
type: ModuleType.Voice,
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
this.registerInputs();
|
|
25
|
+
this.registerOutputs();
|
|
26
|
+
this.polyNumber = this.numberOfVoices;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
set polyNumber(value: number) {
|
|
30
|
+
super.numberOfVoices = value;
|
|
31
|
+
if (!this.numberOfVoicesOut) return;
|
|
32
|
+
|
|
33
|
+
this.numberOfVoicesOut.connections.forEach((input) => {
|
|
34
|
+
if (input.audioModule instanceof Module) return;
|
|
35
|
+
|
|
36
|
+
input.audioModule.numberOfVoices = value;
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
get polyNumber() {
|
|
41
|
+
return this.numberOfVoices;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
midiTriggered = (midiEvent: MidiEvent) => {
|
|
45
|
+
let voice: Voice | undefined;
|
|
46
|
+
|
|
47
|
+
switch (midiEvent.type) {
|
|
48
|
+
case "noteOn":
|
|
49
|
+
voice = this.findFreeVoice();
|
|
50
|
+
|
|
51
|
+
break;
|
|
52
|
+
case "noteOff":
|
|
53
|
+
voice = this.audioModules.find(
|
|
54
|
+
(v) => v.activeNote === midiEvent.note?.fullName
|
|
55
|
+
);
|
|
56
|
+
break;
|
|
57
|
+
default:
|
|
58
|
+
throw Error("This type is not a note");
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (voice === undefined) return;
|
|
62
|
+
|
|
63
|
+
voice.midiTriggered(midiEvent);
|
|
64
|
+
this.midiOutput.connections.forEach((input) => {
|
|
65
|
+
input.pluggable(midiEvent, voice?.voiceNo);
|
|
66
|
+
});
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
serialize() {
|
|
70
|
+
const serialize = super.serialize();
|
|
71
|
+
delete serialize.props.voiceNo;
|
|
72
|
+
|
|
73
|
+
return {
|
|
74
|
+
...serialize,
|
|
75
|
+
props: { ...serialize.props, polyNumber: this.polyNumber },
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
private findFreeVoice(): Voice {
|
|
80
|
+
let voice = this.audioModules.find((v) => !v.activeNote);
|
|
81
|
+
|
|
82
|
+
if (!voice) {
|
|
83
|
+
voice = this.audioModules.sort((a, b) => {
|
|
84
|
+
if (!a || !b) return 0;
|
|
85
|
+
|
|
86
|
+
return a.triggeredAt - b.triggeredAt;
|
|
87
|
+
})[0];
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return voice;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
private registerInputs() {
|
|
94
|
+
this.registerInput({
|
|
95
|
+
name: "midi in",
|
|
96
|
+
pluggable: this.midiTriggered,
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
private registerOutputs() {
|
|
101
|
+
this.numberOfVoicesOut = this.registerOutput({
|
|
102
|
+
name: "number of voices",
|
|
103
|
+
onPlug: (input: Input) => {
|
|
104
|
+
if (input.audioModule instanceof Module) return;
|
|
105
|
+
|
|
106
|
+
input.audioModule.numberOfVoices = this.numberOfVoices;
|
|
107
|
+
},
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
this.midiOutput = this.registerOutput({ name: "midi out" });
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export interface VoiceInterface {
|
|
115
|
+
voiceNo?: number;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export class Voice extends Module<DummnyInternalModule, VoiceInterface> {
|
|
119
|
+
midiEvent: MidiEvent | null;
|
|
120
|
+
activeNote: string | null;
|
|
121
|
+
triggeredAt: number;
|
|
122
|
+
midiOutput: Output;
|
|
123
|
+
|
|
124
|
+
constructor(name: string, props: VoiceInterface) {
|
|
125
|
+
super(new DummnyInternalModule(), {
|
|
126
|
+
name,
|
|
127
|
+
type: ModuleType.Voice,
|
|
128
|
+
props,
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
midiTriggered = (midiEvent: MidiEvent) => {
|
|
133
|
+
const { triggeredAt, note, type } = midiEvent;
|
|
134
|
+
|
|
135
|
+
if (!note) throw Error("No valid note on this event");
|
|
136
|
+
const noteName = note.fullName;
|
|
137
|
+
|
|
138
|
+
switch (type) {
|
|
139
|
+
case "noteOn":
|
|
140
|
+
this.activeNote = noteName;
|
|
141
|
+
this.triggeredAt = triggeredAt;
|
|
142
|
+
this.midiEvent = midiEvent;
|
|
143
|
+
|
|
144
|
+
break;
|
|
145
|
+
case "noteOff":
|
|
146
|
+
this.activeNote = null;
|
|
147
|
+
this.midiEvent = null;
|
|
148
|
+
break;
|
|
149
|
+
default:
|
|
150
|
+
throw Error("This type is not a note");
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { Volume as Vol } from "tone";
|
|
2
|
+
|
|
3
|
+
import Module, { ModuleType } from "../Module";
|
|
4
|
+
import PolyModule, { PolyModuleType } from "./PolyModule";
|
|
5
|
+
|
|
6
|
+
export interface VolumeInterface {
|
|
7
|
+
volume: number;
|
|
8
|
+
voiceNo?: number;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const InitialProps: VolumeInterface = {
|
|
12
|
+
volume: -100,
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export default class Volume extends Module<Vol, VolumeInterface> {
|
|
16
|
+
constructor(name: string, props: Partial<VolumeInterface>) {
|
|
17
|
+
super(new Vol(), {
|
|
18
|
+
name,
|
|
19
|
+
props: { ...InitialProps, ...props },
|
|
20
|
+
type: ModuleType.Volume,
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
get volume() {
|
|
25
|
+
return this._props["volume"];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
set volume(value: number) {
|
|
29
|
+
this._props = { ...this.props, volume: value };
|
|
30
|
+
|
|
31
|
+
this.internalModule.volume.value = this.volume;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export class PolyVolume extends PolyModule<Volume, VolumeInterface> {
|
|
36
|
+
constructor(name: string, props: Partial<VolumeInterface>) {
|
|
37
|
+
super(PolyModuleType.Volume, {
|
|
38
|
+
name,
|
|
39
|
+
props: { ...InitialProps, ...props },
|
|
40
|
+
type: ModuleType.Volume,
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
this.registerBasicInputs();
|
|
44
|
+
this.registerBasicOutputs();
|
|
45
|
+
}
|
|
46
|
+
}
|