@blibliki/engine 0.1.17 → 0.1.19
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 +13 -1
- package/{build → dist/build}/Engine.d.ts +14 -22
- package/dist/build/core/IO/AudioNode.d.ts +35 -0
- package/dist/build/core/IO/Collection.d.ts +13 -0
- package/dist/build/core/IO/ForwardNode.d.ts +38 -0
- package/dist/build/core/IO/MidiNode.d.ts +30 -0
- package/dist/build/core/IO/Node.d.ts +36 -0
- package/dist/build/core/IO/index.d.ts +12 -0
- package/dist/build/core/Module/MonoModule.d.ts +67 -0
- package/dist/build/core/Module/PolyModule.d.ts +57 -0
- package/dist/build/core/Module/index.d.ts +6 -0
- package/{build → dist/build/core}/Note/index.d.ts +3 -0
- package/{build → dist/build/core/midi}/MidiDevice.d.ts +1 -1
- package/dist/build/core/midi/MidiEvent.d.ts +21 -0
- package/dist/build/core/midi/index.d.ts +5 -0
- package/dist/build/index.d.ts +9 -0
- package/{build/Module → dist/build/modules}/BitCrusher.d.ts +5 -1
- package/{build/Module → dist/build/modules}/Delay.d.ts +5 -1
- package/{build/Module → dist/build/modules}/Distortion.d.ts +5 -1
- package/{build/Module → dist/build/modules}/Effect.d.ts +7 -2
- package/{build/Module → dist/build/modules}/Envelope/AmpEnvelope.d.ts +10 -2
- package/{build/Module → dist/build/modules}/Envelope/Base.d.ts +30 -11
- package/{build/Module → dist/build/modules}/Envelope/FreqEnvelope.d.ts +10 -3
- package/{build/Module → dist/build/modules}/Filter.d.ts +13 -13
- package/{build/Module → dist/build/modules}/Master.d.ts +5 -2
- package/{build/Module → dist/build/modules}/MidiSelector.d.ts +9 -5
- package/{build/Module → dist/build/modules}/Oscillator.d.ts +15 -9
- package/{build/Module → dist/build/modules}/Reverb.d.ts +5 -1
- package/{build/Module → dist/build/modules}/Sequencer.d.ts +9 -5
- package/{build/Module → dist/build/modules}/VirtualMidi.d.ts +12 -8
- package/dist/build/modules/VoiceScheduler.d.ts +43 -0
- package/dist/build/modules/Volume.d.ts +26 -0
- package/dist/build/modules/index.d.ts +16 -0
- package/{build → dist/build}/routes.d.ts +6 -5
- package/dist/build/types.d.ts +5 -0
- package/dist/build/utils.d.ts +1 -0
- package/dist/main.cjs.js +33 -0
- package/dist/main.cjs.js.map +1 -0
- package/dist/main.esm.js +33 -0
- package/dist/main.esm.js.map +1 -0
- package/package.json +26 -22
- package/src/Engine.ts +27 -36
- package/src/core/IO/AudioNode.ts +77 -0
- package/src/core/IO/Collection.ts +76 -0
- package/src/core/IO/ForwardNode.ts +192 -0
- package/src/core/IO/MidiNode.ts +67 -0
- package/src/core/IO/Node.ts +117 -0
- package/src/core/IO/index.ts +47 -0
- package/src/core/Module/MonoModule.ts +219 -0
- package/src/core/Module/PolyModule.ts +206 -0
- package/src/core/Module/index.ts +15 -0
- package/src/{Note → core/Note}/index.ts +19 -4
- package/src/{MidiDeviceManager.ts → core/midi/MidiDeviceManager.ts} +20 -15
- package/src/core/midi/MidiEvent.ts +91 -0
- package/src/core/midi/index.ts +5 -0
- package/src/index.ts +8 -12
- package/src/{Module → modules}/BitCrusher.ts +15 -4
- package/src/{Module → modules}/Delay.ts +15 -4
- package/src/{Module → modules}/Distortion.ts +15 -4
- package/src/{Module → modules}/Effect.ts +10 -6
- package/src/modules/Envelope/AmpEnvelope.ts +23 -0
- package/src/{Module → modules}/Envelope/Base.ts +50 -31
- package/src/{Module → modules}/Envelope/FreqEnvelope.ts +18 -23
- package/src/{Module → modules}/Filter.ts +18 -46
- package/src/{Module → modules}/Master.ts +8 -2
- package/src/{Module → modules}/MidiSelector.ts +18 -14
- package/src/{Module → modules}/Oscillator.ts +27 -16
- package/src/{Module → modules}/Reverb.ts +15 -4
- package/src/{Module → modules}/Sequencer.ts +17 -13
- package/src/{Module → modules}/VirtualMidi.ts +16 -12
- package/src/modules/VoiceScheduler.ts +145 -0
- package/src/{Module → modules}/Volume.ts +23 -15
- package/src/{Module → modules}/index.ts +14 -21
- package/src/routes.ts +19 -18
- package/src/types.ts +3 -0
- package/src/utils.ts +18 -0
- package/build/Engine.js +0 -165
- package/build/Engine.js.map +0 -1
- package/build/MidiDevice.js +0 -45
- package/build/MidiDevice.js.map +0 -1
- package/build/MidiDeviceManager.js +0 -59
- package/build/MidiDeviceManager.js.map +0 -1
- package/build/MidiEvent.d.ts +0 -18
- package/build/MidiEvent.js +0 -64
- package/build/MidiEvent.js.map +0 -1
- package/build/Module/Base.d.ts +0 -63
- package/build/Module/Base.js +0 -138
- package/build/Module/Base.js.map +0 -1
- package/build/Module/BitCrusher.js +0 -22
- package/build/Module/BitCrusher.js.map +0 -1
- package/build/Module/DataSequencer.d.ts +0 -26
- package/build/Module/DataSequencer.js +0 -91
- package/build/Module/DataSequencer.js.map +0 -1
- package/build/Module/Delay.js +0 -30
- package/build/Module/Delay.js.map +0 -1
- package/build/Module/Distortion.js +0 -22
- package/build/Module/Distortion.js.map +0 -1
- package/build/Module/Effect.js +0 -22
- package/build/Module/Effect.js.map +0 -1
- package/build/Module/Envelope/AmpEnvelope.js +0 -14
- package/build/Module/Envelope/AmpEnvelope.js.map +0 -1
- package/build/Module/Envelope/Base.js +0 -106
- package/build/Module/Envelope/Base.js.map +0 -1
- package/build/Module/Envelope/FreqEnvelope.js +0 -50
- package/build/Module/Envelope/FreqEnvelope.js.map +0 -1
- package/build/Module/Envelope/index.js +0 -4
- package/build/Module/Envelope/index.js.map +0 -1
- package/build/Module/Filter.js +0 -100
- package/build/Module/Filter.js.map +0 -1
- package/build/Module/IO.d.ts +0 -39
- package/build/Module/IO.js +0 -59
- package/build/Module/IO.js.map +0 -1
- package/build/Module/Master.js +0 -12
- package/build/Module/Master.js.map +0 -1
- package/build/Module/MidiSelector.js +0 -50
- package/build/Module/MidiSelector.js.map +0 -1
- package/build/Module/Oscillator.js +0 -136
- package/build/Module/Oscillator.js.map +0 -1
- package/build/Module/PolyModule.d.ts +0 -49
- package/build/Module/PolyModule.js +0 -175
- package/build/Module/PolyModule.js.map +0 -1
- package/build/Module/Reverb.js +0 -30
- package/build/Module/Reverb.js.map +0 -1
- package/build/Module/Sequencer.js +0 -131
- package/build/Module/Sequencer.js.map +0 -1
- package/build/Module/VirtualMidi.js +0 -53
- package/build/Module/VirtualMidi.js.map +0 -1
- package/build/Module/VoiceScheduler.d.ts +0 -38
- package/build/Module/VoiceScheduler.js +0 -130
- package/build/Module/VoiceScheduler.js.map +0 -1
- package/build/Module/Volume.d.ts +0 -20
- package/build/Module/Volume.js +0 -48
- package/build/Module/Volume.js.map +0 -1
- package/build/Module/index.d.ts +0 -14
- package/build/Module/index.js +0 -66
- package/build/Module/index.js.map +0 -1
- package/build/Note/frequencyTable.js +0 -146
- package/build/Note/frequencyTable.js.map +0 -1
- package/build/Note/index.js +0 -81
- package/build/Note/index.js.map +0 -1
- package/build/index.d.ts +0 -8
- package/build/index.js +0 -5
- package/build/index.js.map +0 -1
- package/build/routes.js +0 -31
- package/build/routes.js.map +0 -1
- package/jest.config.js +0 -19
- package/src/MidiEvent.ts +0 -93
- package/src/Module/Base.ts +0 -223
- package/src/Module/DataSequencer.ts +0 -121
- package/src/Module/Envelope/AmpEnvelope.ts +0 -17
- package/src/Module/IO.ts +0 -85
- package/src/Module/PolyModule.ts +0 -234
- package/src/Module/VoiceScheduler.ts +0 -161
- package/test/Module/Oscillator.test.ts +0 -9
- package/tsconfig.json +0 -22
- /package/{build → dist/build/core}/Note/frequencyTable.d.ts +0 -0
- /package/{build → dist/build/core/midi}/MidiDeviceManager.d.ts +0 -0
- /package/{build/Module → dist/build/modules}/Envelope/index.d.ts +0 -0
- /package/src/{Note → core/Note}/frequencyTable.ts +0 -0
- /package/src/{MidiDevice.ts → core/midi/MidiDevice.ts} +0 -0
- /package/src/{Module → modules}/Envelope/index.ts +0 -0
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { AudioModule } from "../Module";
|
|
2
|
+
import {
|
|
3
|
+
AnyInput,
|
|
4
|
+
AnyOuput,
|
|
5
|
+
AudioInput,
|
|
6
|
+
AudioOutput,
|
|
7
|
+
ForwardInput,
|
|
8
|
+
ForwardOutput,
|
|
9
|
+
MidiInput,
|
|
10
|
+
MidiOutput,
|
|
11
|
+
} from ".";
|
|
12
|
+
import { deterministicId } from "../../utils";
|
|
13
|
+
|
|
14
|
+
export interface IIONode {
|
|
15
|
+
name: string;
|
|
16
|
+
ioType: IOType;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface IIOSerialize {
|
|
20
|
+
id: string;
|
|
21
|
+
name: string;
|
|
22
|
+
ioType: IOType;
|
|
23
|
+
moduleId: string;
|
|
24
|
+
moduleName: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export enum IOType {
|
|
28
|
+
AudioInput = "audioInput",
|
|
29
|
+
AudioOutput = "audioOutput",
|
|
30
|
+
MidiInput = "midiInput",
|
|
31
|
+
MidiOutput = "midiOutput",
|
|
32
|
+
ForwardInput = "forwardInput",
|
|
33
|
+
ForwardOutput = "forwardOutput",
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function plugCompatibleIO(input: AnyInput, output: AnyOuput) {
|
|
37
|
+
if (input instanceof AudioInput && output instanceof AudioOutput) {
|
|
38
|
+
input.plug(output);
|
|
39
|
+
} else if (input instanceof MidiInput && output instanceof MidiOutput) {
|
|
40
|
+
input.plug(output);
|
|
41
|
+
} else if (input instanceof ForwardInput && output instanceof ForwardOutput) {
|
|
42
|
+
input.plug(output);
|
|
43
|
+
} else if (input instanceof ForwardInput && output instanceof MidiOutput) {
|
|
44
|
+
input.plug(output);
|
|
45
|
+
} else if (input instanceof ForwardInput && output instanceof AudioOutput) {
|
|
46
|
+
input.plug(output);
|
|
47
|
+
} else if (input instanceof AudioInput && output instanceof ForwardOutput) {
|
|
48
|
+
input.plug(output);
|
|
49
|
+
} else if (input instanceof MidiInput && output instanceof ForwardOutput) {
|
|
50
|
+
input.plug(output);
|
|
51
|
+
} else {
|
|
52
|
+
throw Error("Input and output type is not compatible");
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function unPlugCompatibleIO(input: AnyInput, output: AnyOuput) {
|
|
57
|
+
if (input instanceof AudioInput && output instanceof AudioOutput) {
|
|
58
|
+
input.unPlug(output);
|
|
59
|
+
} else if (input instanceof MidiInput && output instanceof MidiOutput) {
|
|
60
|
+
input.unPlug(output);
|
|
61
|
+
} else if (input instanceof ForwardInput && output instanceof ForwardOutput) {
|
|
62
|
+
input.unPlug(output);
|
|
63
|
+
} else if (input instanceof ForwardInput && output instanceof MidiOutput) {
|
|
64
|
+
input.unPlug(output);
|
|
65
|
+
} else if (input instanceof ForwardInput && output instanceof AudioOutput) {
|
|
66
|
+
input.unPlug(output);
|
|
67
|
+
} else if (input instanceof AudioInput && output instanceof ForwardOutput) {
|
|
68
|
+
input.unPlug(output);
|
|
69
|
+
} else if (input instanceof MidiInput && output instanceof ForwardOutput) {
|
|
70
|
+
input.unPlug(output);
|
|
71
|
+
} else {
|
|
72
|
+
throw Error("Input and output type is not compatible");
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export default abstract class IONode implements IIONode {
|
|
77
|
+
id: string;
|
|
78
|
+
ioType!: IOType;
|
|
79
|
+
name!: string;
|
|
80
|
+
plugableModule: AudioModule;
|
|
81
|
+
connections: IONode[] = [];
|
|
82
|
+
|
|
83
|
+
static unPlugAll(io: IONode): void {
|
|
84
|
+
io.connections.forEach((otherIO) => io.unPlug(otherIO));
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
constructor(plugableModule: AudioModule, props: IIONode) {
|
|
88
|
+
this.plugableModule = plugableModule;
|
|
89
|
+
this.id = deterministicId(plugableModule.id, props.name);
|
|
90
|
+
|
|
91
|
+
Object.assign(this, props);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
plug(io: IONode, plugOther: boolean = true) {
|
|
95
|
+
this.connections.push(io);
|
|
96
|
+
if (plugOther) io.plug(this, false);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
unPlug(io: IONode, plugOther: boolean = true) {
|
|
100
|
+
this.connections = this.connections.filter(
|
|
101
|
+
(currentIO) => currentIO.id !== io.id
|
|
102
|
+
);
|
|
103
|
+
if (plugOther) io.unPlug(this, false);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
abstract unPlugAll(): void;
|
|
107
|
+
|
|
108
|
+
serialize(): IIOSerialize {
|
|
109
|
+
return {
|
|
110
|
+
id: this.id,
|
|
111
|
+
name: this.name,
|
|
112
|
+
moduleId: this.plugableModule.id,
|
|
113
|
+
moduleName: this.plugableModule.name,
|
|
114
|
+
ioType: this.ioType,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { default as IOCollection } from "./Collection";
|
|
2
|
+
import { IIONode, IOType, IIOSerialize } from "./Node";
|
|
3
|
+
import {
|
|
4
|
+
AudioInput,
|
|
5
|
+
AudioOutput,
|
|
6
|
+
IAudioInput,
|
|
7
|
+
IAudioOutput,
|
|
8
|
+
} from "./AudioNode";
|
|
9
|
+
import { IMidiInput, IMidiOutput, MidiInput, MidiOutput } from "./MidiNode";
|
|
10
|
+
import {
|
|
11
|
+
ForwardInput,
|
|
12
|
+
ForwardOutput,
|
|
13
|
+
IForwardInput,
|
|
14
|
+
IForwardOutput,
|
|
15
|
+
} from "./ForwardNode";
|
|
16
|
+
|
|
17
|
+
type AnyInput = AudioInput | MidiInput | ForwardInput;
|
|
18
|
+
type AnyOuput = AudioOutput | MidiOutput | ForwardOutput;
|
|
19
|
+
type IAnyInput = IAudioInput | IMidiInput | IForwardInput;
|
|
20
|
+
type IAnyOutput = IAudioOutput | IMidiOutput | IForwardOutput;
|
|
21
|
+
type IAnyIO = IAnyInput | IAnyOutput | IForwardOutput;
|
|
22
|
+
|
|
23
|
+
export {
|
|
24
|
+
IOCollection,
|
|
25
|
+
IOType,
|
|
26
|
+
AudioInput,
|
|
27
|
+
AudioOutput,
|
|
28
|
+
MidiInput,
|
|
29
|
+
MidiOutput,
|
|
30
|
+
ForwardInput,
|
|
31
|
+
ForwardOutput,
|
|
32
|
+
};
|
|
33
|
+
export type {
|
|
34
|
+
IAnyIO,
|
|
35
|
+
IIONode,
|
|
36
|
+
IAudioInput,
|
|
37
|
+
IAudioOutput,
|
|
38
|
+
IMidiInput,
|
|
39
|
+
IMidiOutput,
|
|
40
|
+
IForwardInput,
|
|
41
|
+
IForwardOutput,
|
|
42
|
+
IAnyInput,
|
|
43
|
+
IAnyOutput,
|
|
44
|
+
IIOSerialize,
|
|
45
|
+
AnyInput,
|
|
46
|
+
AnyOuput,
|
|
47
|
+
};
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import { v4 as uuidv4 } from "uuid";
|
|
2
|
+
import { InputNode, Time } from "tone";
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
IOCollection,
|
|
6
|
+
AudioInput,
|
|
7
|
+
AudioOutput,
|
|
8
|
+
MidiInput,
|
|
9
|
+
IMidiInput,
|
|
10
|
+
IAudioInput,
|
|
11
|
+
IAudioOutput,
|
|
12
|
+
IOType,
|
|
13
|
+
MidiOutput,
|
|
14
|
+
IMidiOutput,
|
|
15
|
+
} from "../IO";
|
|
16
|
+
import { MidiEvent } from "../../core/midi";
|
|
17
|
+
import { AudioModule } from "./index";
|
|
18
|
+
import Note from "../../core/Note";
|
|
19
|
+
import { plugCompatibleIO } from "../IO/Node";
|
|
20
|
+
import { AtLeast } from "../../types";
|
|
21
|
+
|
|
22
|
+
export interface Startable {
|
|
23
|
+
start(time: number): void;
|
|
24
|
+
stop(time: number): void;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface Connectable {
|
|
28
|
+
connect: (inputNode: InputNode) => void;
|
|
29
|
+
disconnect: (inputNode?: InputNode) => void;
|
|
30
|
+
dispose: () => void;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface Triggerable {
|
|
34
|
+
triggerAttack: (note: Note, triggeredAt: number) => void;
|
|
35
|
+
triggerRelease: (note: Note, triggeredAt: number) => void;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface ModuleInterface<PropsInterface> {
|
|
39
|
+
id: string;
|
|
40
|
+
name: string;
|
|
41
|
+
props: PropsInterface;
|
|
42
|
+
voiceNo?: number;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export class DummnyInternalModule implements Connectable {
|
|
46
|
+
connect() {
|
|
47
|
+
throw Error("This module is not connectable");
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
disconnect() {
|
|
51
|
+
throw Error("This module is not connectable");
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
dispose() {
|
|
55
|
+
// do nothing
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
abstract class Module<InternalModule extends Connectable, PropsInterface>
|
|
60
|
+
implements ModuleInterface<PropsInterface>
|
|
61
|
+
{
|
|
62
|
+
static readonly moduleName: string;
|
|
63
|
+
|
|
64
|
+
readonly id: string;
|
|
65
|
+
name: string;
|
|
66
|
+
internalModule: InternalModule;
|
|
67
|
+
inputs: IOCollection<AudioInput | MidiInput>;
|
|
68
|
+
outputs: IOCollection<AudioOutput | MidiOutput>;
|
|
69
|
+
readonly voiceNo?: number;
|
|
70
|
+
updatedAt: Date;
|
|
71
|
+
_props: PropsInterface = {} as PropsInterface;
|
|
72
|
+
|
|
73
|
+
constructor(
|
|
74
|
+
internalModule: InternalModule,
|
|
75
|
+
props: AtLeast<ModuleInterface<PropsInterface>, "name">
|
|
76
|
+
) {
|
|
77
|
+
this.internalModule = internalModule;
|
|
78
|
+
this.id = props.id || uuidv4();
|
|
79
|
+
delete props.id;
|
|
80
|
+
|
|
81
|
+
this.inputs = new IOCollection<AudioInput | MidiInput>(this);
|
|
82
|
+
this.outputs = new IOCollection<AudioOutput | MidiOutput>(this);
|
|
83
|
+
|
|
84
|
+
Object.assign(this, props);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
set props(value: PropsInterface) {
|
|
88
|
+
if (!value) return;
|
|
89
|
+
|
|
90
|
+
this.updatedAt = new Date();
|
|
91
|
+
|
|
92
|
+
Object.assign(this, value);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
get props() {
|
|
96
|
+
return this._props;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
plug(audioModule: AudioModule, from: string, to: string) {
|
|
100
|
+
const output = this.outputs.findByName(from);
|
|
101
|
+
if (!output) throw Error(`Output ${from} not exist`);
|
|
102
|
+
|
|
103
|
+
const input = audioModule.inputs.findByName(to);
|
|
104
|
+
if (!input) throw Error(`Input ${to} not exist`);
|
|
105
|
+
|
|
106
|
+
plugCompatibleIO(input, output);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
unPlugAll() {
|
|
110
|
+
this.outputs.unPlugAll();
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
dispose() {
|
|
114
|
+
this.inputs.unPlugAll();
|
|
115
|
+
this.outputs.unPlugAll();
|
|
116
|
+
this.internalModule.dispose();
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
120
|
+
triggerAttack = (note: Note, triggeredAt: number): void => {
|
|
121
|
+
throw Error("triggerAttack not implemented");
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
125
|
+
triggerRelease = (note: Note, triggeredAt: number): void => {
|
|
126
|
+
throw Error("triggerRelease not implemented");
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
onMidiEvent = (midiEvent: MidiEvent) => {
|
|
130
|
+
if (midiEvent.voiceNo !== undefined && midiEvent.voiceNo !== this.voiceNo)
|
|
131
|
+
return;
|
|
132
|
+
|
|
133
|
+
const { note, triggeredAt } = midiEvent;
|
|
134
|
+
|
|
135
|
+
switch (midiEvent.type) {
|
|
136
|
+
case "noteOn": {
|
|
137
|
+
const { duration } = note;
|
|
138
|
+
|
|
139
|
+
this.triggerer(this.triggerAttack, note, triggeredAt);
|
|
140
|
+
|
|
141
|
+
if (duration) {
|
|
142
|
+
const releaseTriggeredAt = triggeredAt + Time(duration).toSeconds();
|
|
143
|
+
|
|
144
|
+
this.triggerer(this.triggerRelease, note, releaseTriggeredAt);
|
|
145
|
+
}
|
|
146
|
+
break;
|
|
147
|
+
}
|
|
148
|
+
case "noteOff":
|
|
149
|
+
this.triggerer(this.triggerRelease, note, triggeredAt);
|
|
150
|
+
break;
|
|
151
|
+
default:
|
|
152
|
+
throw Error("This type is not a note");
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
private triggerer(
|
|
157
|
+
trigger: (note: Note, triggeredAt: number) => void,
|
|
158
|
+
note: Note,
|
|
159
|
+
triggeredAt: number
|
|
160
|
+
) {
|
|
161
|
+
trigger(note, triggeredAt);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
serialize() {
|
|
165
|
+
const klass = this.constructor as typeof Module;
|
|
166
|
+
|
|
167
|
+
return {
|
|
168
|
+
id: this.id,
|
|
169
|
+
name: this.name,
|
|
170
|
+
type: klass.moduleName,
|
|
171
|
+
props: this.props,
|
|
172
|
+
inputs: this.inputs.serialize(),
|
|
173
|
+
outputs: this.outputs.serialize(),
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
protected registerMidiInput(props: Omit<IMidiInput, "ioType">): MidiInput {
|
|
178
|
+
return this.inputs.add({ ...props, ioType: IOType.MidiInput });
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
protected registerAudioInput(props: Omit<IAudioInput, "ioType">): AudioInput {
|
|
182
|
+
return this.inputs.add({ ...props, ioType: IOType.AudioInput });
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
protected registerMidiOutput(props: Omit<IMidiOutput, "ioType">): MidiOutput {
|
|
186
|
+
return this.outputs.add({ ...props, ioType: IOType.MidiOutput });
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
protected registerAudioOutput(
|
|
190
|
+
props: Omit<IAudioOutput, "ioType">
|
|
191
|
+
): AudioOutput {
|
|
192
|
+
return this.outputs.add({ ...props, ioType: IOType.AudioOutput });
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
protected registerBasicOutputs() {
|
|
196
|
+
this.registerAudioOutput({
|
|
197
|
+
name: "output",
|
|
198
|
+
internalModule: this.internalModule,
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
protected registerBasicInputs() {
|
|
203
|
+
this.registerAudioInput({
|
|
204
|
+
name: "input",
|
|
205
|
+
internalModule: this.internalModule as unknown as InputNode,
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
this.registerDefaultMidiInput();
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
protected registerDefaultMidiInput() {
|
|
212
|
+
this.registerMidiInput({
|
|
213
|
+
name: "midi in",
|
|
214
|
+
onMidiEvent: this.onMidiEvent,
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
export default Module;
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import { MidiEvent } from "../midi";
|
|
2
|
+
import { v4 as uuidv4 } from "uuid";
|
|
3
|
+
import Module, { Connectable } from "./MonoModule";
|
|
4
|
+
import {
|
|
5
|
+
IOCollection,
|
|
6
|
+
ForwardInput,
|
|
7
|
+
ForwardOutput,
|
|
8
|
+
IOType,
|
|
9
|
+
IMidiInput,
|
|
10
|
+
IMidiOutput,
|
|
11
|
+
IForwardInput,
|
|
12
|
+
IForwardOutput,
|
|
13
|
+
MidiOutput,
|
|
14
|
+
MidiInput,
|
|
15
|
+
} from "../IO";
|
|
16
|
+
import { AudioModule } from "./index";
|
|
17
|
+
import { plugCompatibleIO } from "../IO/Node";
|
|
18
|
+
import { deterministicId } from "../../utils";
|
|
19
|
+
|
|
20
|
+
interface PolyModuleInterface<MonoAudioModule, PropsInterface> {
|
|
21
|
+
id?: string;
|
|
22
|
+
name: string;
|
|
23
|
+
child: new (params: {
|
|
24
|
+
id?: string;
|
|
25
|
+
name: string;
|
|
26
|
+
props: PropsInterface;
|
|
27
|
+
}) => MonoAudioModule;
|
|
28
|
+
props: PropsInterface;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export default abstract class PolyModule<
|
|
32
|
+
MonoAudioModule extends Module<Connectable, PropsInterface>,
|
|
33
|
+
PropsInterface
|
|
34
|
+
> {
|
|
35
|
+
static readonly moduleName: string;
|
|
36
|
+
|
|
37
|
+
readonly id: string;
|
|
38
|
+
readonly child: new (params: {
|
|
39
|
+
id?: string;
|
|
40
|
+
name: string;
|
|
41
|
+
props: PropsInterface;
|
|
42
|
+
}) => MonoAudioModule;
|
|
43
|
+
_name: string;
|
|
44
|
+
audioModules: MonoAudioModule[];
|
|
45
|
+
inputs: IOCollection<ForwardInput | MidiInput>;
|
|
46
|
+
outputs: IOCollection<ForwardOutput | MidiOutput>;
|
|
47
|
+
private _numberOfVoices: number;
|
|
48
|
+
|
|
49
|
+
constructor(params: PolyModuleInterface<MonoAudioModule, PropsInterface>) {
|
|
50
|
+
this.id = params.id || uuidv4();
|
|
51
|
+
delete params.id;
|
|
52
|
+
|
|
53
|
+
this.audioModules = [];
|
|
54
|
+
|
|
55
|
+
const { child, props: extraProps, ...basicProps } = params;
|
|
56
|
+
this.child = child;
|
|
57
|
+
Object.assign(this, basicProps);
|
|
58
|
+
|
|
59
|
+
this.numberOfVoices = 1;
|
|
60
|
+
this.inputs = new IOCollection<ForwardInput>(this);
|
|
61
|
+
this.outputs = new IOCollection<ForwardOutput>(this);
|
|
62
|
+
|
|
63
|
+
Object.assign(this, { props: extraProps });
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
get name() {
|
|
67
|
+
return this._name;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
set name(value: string) {
|
|
71
|
+
this._name = value;
|
|
72
|
+
this.audioModules.forEach((m) => (m.name = value));
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
get props() {
|
|
76
|
+
if (this.audioModules.length === 0) {
|
|
77
|
+
throw Error("There isn't any initialized module");
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return this.audioModules[0].props;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
set props(value: PropsInterface) {
|
|
84
|
+
Object.assign(this, value);
|
|
85
|
+
this.audioModules.forEach((m) => (m.props = value));
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
get numberOfVoices() {
|
|
89
|
+
return this._numberOfVoices;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
set numberOfVoices(value: number) {
|
|
93
|
+
this._numberOfVoices = value;
|
|
94
|
+
this.adjustNumberOfModules();
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
plug(audioModule: AudioModule, from: string, to: string) {
|
|
98
|
+
const output = this.outputs.findByName(from);
|
|
99
|
+
if (!output) throw Error(`Output ${from} not exist`);
|
|
100
|
+
|
|
101
|
+
const input = audioModule.inputs.findByName(to);
|
|
102
|
+
if (!input) throw Error(`Input ${to} not exist`);
|
|
103
|
+
|
|
104
|
+
plugCompatibleIO(input, output);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
unPlugAll() {
|
|
108
|
+
this.outputs.unPlugAll();
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
dispose() {
|
|
112
|
+
this.unPlugAll();
|
|
113
|
+
this.audioModules.forEach((m) => m.dispose());
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
onMidiEvent = (midiEvent: MidiEvent) => {
|
|
117
|
+
const voiceNo = midiEvent.voiceNo || 0;
|
|
118
|
+
const audioModule = this.findVoice(voiceNo);
|
|
119
|
+
audioModule?.onMidiEvent(midiEvent);
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
serialize() {
|
|
123
|
+
if (this.audioModules.length === 0)
|
|
124
|
+
throw Error("There isn't any initialized module");
|
|
125
|
+
|
|
126
|
+
const klass = this.constructor as typeof PolyModule;
|
|
127
|
+
|
|
128
|
+
return {
|
|
129
|
+
...this.audioModules[0].serialize(),
|
|
130
|
+
id: this.id,
|
|
131
|
+
type: klass.moduleName,
|
|
132
|
+
inputs: this.inputs.serialize(),
|
|
133
|
+
outputs: this.outputs.serialize(),
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
protected find(callback: (audioModule: MonoAudioModule) => boolean) {
|
|
138
|
+
const audioModule = this.audioModules.find(callback);
|
|
139
|
+
if (!audioModule) throw Error(`Audio module not found`);
|
|
140
|
+
|
|
141
|
+
return audioModule;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
protected findVoice(voiceNo: number) {
|
|
145
|
+
return this.audioModules.find((m) => m.voiceNo === voiceNo);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
protected registerInput(props: Omit<IForwardInput, "ioType">): ForwardInput {
|
|
149
|
+
return this.inputs.add({ ...props, ioType: IOType.ForwardInput });
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
protected registerOutput(
|
|
153
|
+
props: Omit<IForwardOutput, "ioType">
|
|
154
|
+
): ForwardOutput {
|
|
155
|
+
return this.outputs.add({ ...props, ioType: IOType.ForwardOutput });
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
protected registerMidiInput(props: Omit<IMidiInput, "ioType">): MidiInput {
|
|
159
|
+
return this.inputs.add({ ...props, ioType: IOType.MidiInput });
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
protected registerMidiOutput(props: Omit<IMidiOutput, "ioType">): MidiOutput {
|
|
163
|
+
return this.outputs.add({ ...props, ioType: IOType.MidiOutput });
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
protected registerBasicOutputs() {
|
|
167
|
+
this.registerOutput({ name: "output" });
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
protected registerBasicInputs() {
|
|
171
|
+
this.registerInput({ name: "input" });
|
|
172
|
+
this.registerMidiInput({
|
|
173
|
+
name: "midi in",
|
|
174
|
+
onMidiEvent: this.onMidiEvent,
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
private adjustNumberOfModules() {
|
|
179
|
+
if (this.audioModules.length === this.numberOfVoices) return;
|
|
180
|
+
|
|
181
|
+
if (this.audioModules.length > this.numberOfVoices) {
|
|
182
|
+
const audioModule = this.audioModules.pop();
|
|
183
|
+
audioModule?.dispose();
|
|
184
|
+
} else {
|
|
185
|
+
const voiceNo = this.audioModules.length;
|
|
186
|
+
const id = deterministicId(this.id, voiceNo.toString());
|
|
187
|
+
const props = voiceNo === 0 ? ({} as PropsInterface) : this.props;
|
|
188
|
+
const audioModule = new this.child({
|
|
189
|
+
id,
|
|
190
|
+
name: this.name,
|
|
191
|
+
props: {
|
|
192
|
+
...props,
|
|
193
|
+
voiceNo,
|
|
194
|
+
},
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
if (audioModule instanceof PolyModule) {
|
|
198
|
+
throw Error("Polymodule not supported");
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
this.audioModules.push(audioModule);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
this.adjustNumberOfModules();
|
|
205
|
+
}
|
|
206
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import Module, { Connectable } from "./MonoModule";
|
|
2
|
+
import PolyModule from "./PolyModule";
|
|
3
|
+
|
|
4
|
+
export { default, DummnyInternalModule } from "./MonoModule";
|
|
5
|
+
export { default as PolyModule } from "./PolyModule";
|
|
6
|
+
export type {
|
|
7
|
+
ModuleInterface,
|
|
8
|
+
Connectable,
|
|
9
|
+
Triggerable,
|
|
10
|
+
Startable,
|
|
11
|
+
} from "./MonoModule";
|
|
12
|
+
|
|
13
|
+
export type AudioModule =
|
|
14
|
+
| Module<Connectable, any>
|
|
15
|
+
| PolyModule<Module<Connectable, any>, any>;
|
|
@@ -4,6 +4,8 @@ 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
|
+
const MIDI_OCTAVE_SYTSTEM = 2;
|
|
8
|
+
|
|
7
9
|
export interface INote {
|
|
8
10
|
note: string;
|
|
9
11
|
frequency: number;
|
|
@@ -15,7 +17,7 @@ export default class Note {
|
|
|
15
17
|
static _notes: Note[];
|
|
16
18
|
name: string;
|
|
17
19
|
octave: number;
|
|
18
|
-
velocity
|
|
20
|
+
velocity = 1;
|
|
19
21
|
duration: string;
|
|
20
22
|
frequency: number;
|
|
21
23
|
|
|
@@ -33,7 +35,7 @@ export default class Note {
|
|
|
33
35
|
}
|
|
34
36
|
}
|
|
35
37
|
|
|
36
|
-
static notes(octave
|
|
38
|
+
static notes(octave = 3) {
|
|
37
39
|
return Notes.map((note: string) => new Note(`${note}${octave}`));
|
|
38
40
|
}
|
|
39
41
|
|
|
@@ -45,11 +47,24 @@ export default class Note {
|
|
|
45
47
|
return `${this.name}${this.octave}`;
|
|
46
48
|
}
|
|
47
49
|
|
|
48
|
-
|
|
50
|
+
midiData(noteOn: boolean = true): Uint8Array {
|
|
51
|
+
const statusByte = noteOn ? 0x90 : 0x80;
|
|
52
|
+
return new Uint8Array([statusByte, 0, this.velocity * 100]);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
get midiNumber(): number {
|
|
56
|
+
return (this.octave + MIDI_OCTAVE_SYTSTEM) * 12 + this.noteIndex;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
get noteIndex(): number {
|
|
60
|
+
return Notes.indexOf(this.name);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
adjustFrequency(range = 0, coarse = 0) {
|
|
49
64
|
if (this.frequency) return this.frequency;
|
|
50
65
|
|
|
51
66
|
let newOctave = this.octave + range;
|
|
52
|
-
|
|
67
|
+
const coarseIndex = Notes.indexOf(this.name) + coarse;
|
|
53
68
|
let nameIndex = coarseIndex;
|
|
54
69
|
|
|
55
70
|
if (coarseIndex >= NotesLength) {
|
|
@@ -2,13 +2,15 @@ import MidiDevice from "./MidiDevice";
|
|
|
2
2
|
|
|
3
3
|
export default class MidiDeviceManager {
|
|
4
4
|
devices: { [Key: string]: MidiDevice } = {};
|
|
5
|
-
private initialized
|
|
5
|
+
private initialized = false;
|
|
6
6
|
|
|
7
7
|
constructor() {
|
|
8
|
-
this.initializeDevices()
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
8
|
+
this.initializeDevices()
|
|
9
|
+
.then(() => {
|
|
10
|
+
this.listenChanges();
|
|
11
|
+
this.initialized = true;
|
|
12
|
+
})
|
|
13
|
+
.catch(() => {});
|
|
12
14
|
}
|
|
13
15
|
|
|
14
16
|
find(id: string): MidiDevice | null {
|
|
@@ -20,20 +22,23 @@ export default class MidiDeviceManager {
|
|
|
20
22
|
}
|
|
21
23
|
|
|
22
24
|
onStateChange(callback: (device: MidiDevice) => void) {
|
|
23
|
-
navigator
|
|
24
|
-
|
|
25
|
-
|
|
25
|
+
navigator
|
|
26
|
+
.requestMIDIAccess()
|
|
27
|
+
.then((access: MIDIAccess) => {
|
|
28
|
+
access.onstatechange = (e) => {
|
|
29
|
+
const isMidiEvent = e instanceof MIDIConnectionEvent;
|
|
26
30
|
|
|
27
|
-
|
|
28
|
-
|
|
31
|
+
if (!isMidiEvent) return;
|
|
32
|
+
if (e.port instanceof MIDIOutput) return;
|
|
29
33
|
|
|
30
|
-
|
|
34
|
+
const input = e.port as MIDIInput;
|
|
31
35
|
|
|
32
|
-
|
|
36
|
+
const midi = new MidiDevice(input);
|
|
33
37
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
38
|
+
callback(midi);
|
|
39
|
+
};
|
|
40
|
+
})
|
|
41
|
+
.catch(() => {});
|
|
37
42
|
}
|
|
38
43
|
|
|
39
44
|
private listenChanges() {
|