spessasynth_lib 3.26.0 → 3.26.2
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/external_midi/README.md +4 -0
- package/external_midi/midi_handler.js +130 -0
- package/external_midi/web_midi_link.js +43 -0
- package/index.js +1 -56
- package/package.json +5 -2
- package/sequencer/README.md +32 -0
- package/sequencer/default_sequencer_options.js +8 -0
- package/sequencer/midi_data.js +63 -0
- package/sequencer/sequencer.js +808 -0
- package/sequencer/sequencer_message.js +53 -0
- package/synthetizer/README.md +38 -0
- package/synthetizer/audio_effects/effects_config.js +25 -0
- package/synthetizer/audio_effects/fancy_chorus.js +162 -0
- package/synthetizer/audio_effects/rb_compressed.min.js +1 -0
- package/synthetizer/audio_effects/reverb.js +35 -0
- package/synthetizer/audio_effects/reverb_as_binary.js +18 -0
- package/synthetizer/key_modifier_manager.js +113 -0
- package/synthetizer/sfman_message.js +9 -0
- package/synthetizer/synth_event_handler.js +217 -0
- package/synthetizer/synth_soundfont_manager.js +112 -0
- package/synthetizer/synthetizer.js +1033 -0
- package/synthetizer/worklet_message.js +120 -0
- package/synthetizer/worklet_processor.js +637 -0
- package/synthetizer/worklet_processor.min.js +22 -0
- package/synthetizer/worklet_processor.min.js.map +7 -0
- package/synthetizer/worklet_url.js +18 -0
- package/utils/buffer_to_wav.js +28 -0
- package/utils/fill_with_defaults.js +21 -0
- package/utils/other.js +11 -0
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { Synthetizer } from "../synthetizer/synthetizer.js";
|
|
2
|
+
import { SpessaSynthCoreUtils as util } from "spessasynth_core";
|
|
3
|
+
import { consoleColors } from "../utils/other.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* midi_handler.js
|
|
7
|
+
* purpose: handles the connection between MIDI devices and synthesizer/sequencer via Web MIDI API
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const NO_INPUT = null;
|
|
11
|
+
|
|
12
|
+
// noinspection JSUnusedGlobalSymbols
|
|
13
|
+
/**
|
|
14
|
+
* A class for handling MIDI devices
|
|
15
|
+
*/
|
|
16
|
+
export class MIDIDeviceHandler
|
|
17
|
+
{
|
|
18
|
+
/**
|
|
19
|
+
* @returns {Promise<boolean>} if succeeded
|
|
20
|
+
*/
|
|
21
|
+
async createMIDIDeviceHandler()
|
|
22
|
+
{
|
|
23
|
+
/**
|
|
24
|
+
* @type {MIDIInput}
|
|
25
|
+
*/
|
|
26
|
+
this.selectedInput = NO_INPUT;
|
|
27
|
+
/**
|
|
28
|
+
* @type {MIDIOutput}
|
|
29
|
+
*/
|
|
30
|
+
this.selectedOutput = NO_INPUT;
|
|
31
|
+
if (navigator.requestMIDIAccess)
|
|
32
|
+
{
|
|
33
|
+
// prepare the midi access
|
|
34
|
+
try
|
|
35
|
+
{
|
|
36
|
+
const response = await navigator.requestMIDIAccess({ sysex: true, software: true });
|
|
37
|
+
this.inputs = response.inputs;
|
|
38
|
+
this.outputs = response.outputs;
|
|
39
|
+
util.SpessaSynthInfo("%cMIDI handler created!", consoleColors.recognized);
|
|
40
|
+
return true;
|
|
41
|
+
}
|
|
42
|
+
catch (e)
|
|
43
|
+
{
|
|
44
|
+
util.SpessaSynthWarn(`Could not get MIDI Devices:`, e);
|
|
45
|
+
this.inputs = [];
|
|
46
|
+
this.outputs = [];
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
else
|
|
51
|
+
{
|
|
52
|
+
util.SpessaSynthWarn("Web MIDI Api not supported!", consoleColors.unrecognized);
|
|
53
|
+
this.inputs = [];
|
|
54
|
+
this.outputs = [];
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Connects the sequencer to a given MIDI output port
|
|
61
|
+
* @param output {MIDIOutput}
|
|
62
|
+
* @param seq {Sequencer}
|
|
63
|
+
*/
|
|
64
|
+
connectMIDIOutputToSeq(output, seq)
|
|
65
|
+
{
|
|
66
|
+
this.selectedOutput = output;
|
|
67
|
+
seq.connectMidiOutput(output);
|
|
68
|
+
util.SpessaSynthInfo(
|
|
69
|
+
`%cPlaying MIDI to %c${output.name}`,
|
|
70
|
+
consoleColors.info,
|
|
71
|
+
consoleColors.recognized
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Disconnects a midi output port from the sequencer
|
|
77
|
+
* @param seq {Sequencer}
|
|
78
|
+
*/
|
|
79
|
+
disconnectSeqFromMIDI(seq)
|
|
80
|
+
{
|
|
81
|
+
this.selectedOutput = NO_INPUT;
|
|
82
|
+
seq.connectMidiOutput(undefined);
|
|
83
|
+
util.SpessaSynthInfo(
|
|
84
|
+
"%cDisconnected from MIDI out.",
|
|
85
|
+
consoleColors.info
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Connects a MIDI input to the synthesizer
|
|
91
|
+
* @param input {MIDIInput}
|
|
92
|
+
* @param synth {Synthetizer}
|
|
93
|
+
*/
|
|
94
|
+
connectDeviceToSynth(input, synth)
|
|
95
|
+
{
|
|
96
|
+
this.selectedInput = input;
|
|
97
|
+
input.onmidimessage = event =>
|
|
98
|
+
{
|
|
99
|
+
synth.sendMessage(event.data);
|
|
100
|
+
};
|
|
101
|
+
util.SpessaSynthInfo(
|
|
102
|
+
`%cListening for messages on %c${input.name}`,
|
|
103
|
+
consoleColors.info,
|
|
104
|
+
consoleColors.recognized
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* @param input {MIDIInput}
|
|
110
|
+
*/
|
|
111
|
+
disconnectDeviceFromSynth(input)
|
|
112
|
+
{
|
|
113
|
+
this.selectedInput = NO_INPUT;
|
|
114
|
+
input.onmidimessage = undefined;
|
|
115
|
+
util.SpessaSynthInfo(
|
|
116
|
+
`%cDisconnected from %c${input.name}`,
|
|
117
|
+
consoleColors.info,
|
|
118
|
+
consoleColors.recognized
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
disconnectAllDevicesFromSynth()
|
|
123
|
+
{
|
|
124
|
+
this.selectedInput = NO_INPUT;
|
|
125
|
+
for (const i of this.inputs)
|
|
126
|
+
{
|
|
127
|
+
i[1].onmidimessage = undefined;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { Synthetizer } from "../synthetizer/synthetizer.js";
|
|
2
|
+
import { consoleColors } from "../utils/other.js";
|
|
3
|
+
import { SpessaSynthCoreUtils } from "spessasynth_core";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* web_midi_link.js
|
|
7
|
+
* purpose: handles the web midi link connection to the synthesizer
|
|
8
|
+
* https://www.g200kg.com/en/docs/webmidilink/
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
export class WebMIDILinkHandler
|
|
12
|
+
{
|
|
13
|
+
/**
|
|
14
|
+
* @param synth {Synthetizer} the synth to play to
|
|
15
|
+
*/
|
|
16
|
+
constructor(synth)
|
|
17
|
+
{
|
|
18
|
+
|
|
19
|
+
window.addEventListener("message", msg =>
|
|
20
|
+
{
|
|
21
|
+
if (typeof msg.data !== "string")
|
|
22
|
+
{
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* @type {string[]}
|
|
27
|
+
*/
|
|
28
|
+
const data = msg.data.split(",");
|
|
29
|
+
if (data[0] !== "midi")
|
|
30
|
+
{
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
data.shift(); // remove MIDI
|
|
35
|
+
|
|
36
|
+
const midiData = data.map(byte => parseInt(byte, 16));
|
|
37
|
+
|
|
38
|
+
synth.sendMessage(midiData);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
SpessaSynthCoreUtils.SpessaSynthInfo("%cWeb MIDI Link handler created!", consoleColors.recognized);
|
|
42
|
+
}
|
|
43
|
+
}
|
package/index.js
CHANGED
|
@@ -1,31 +1,5 @@
|
|
|
1
1
|
// Import modules
|
|
2
|
-
|
|
3
|
-
ALL_CHANNELS_OR_DIFFERENT_ACTION,
|
|
4
|
-
BasicInstrument,
|
|
5
|
-
BasicInstrumentZone,
|
|
6
|
-
BasicMIDI,
|
|
7
|
-
BasicPreset,
|
|
8
|
-
BasicPresetZone,
|
|
9
|
-
BasicSample,
|
|
10
|
-
BasicSoundBank,
|
|
11
|
-
ChannelSnapshot,
|
|
12
|
-
DEFAULT_PERCUSSION,
|
|
13
|
-
Generator,
|
|
14
|
-
IndexedByteArray,
|
|
15
|
-
loadSoundFont,
|
|
16
|
-
messageTypes,
|
|
17
|
-
MIDI,
|
|
18
|
-
MIDIBuilder,
|
|
19
|
-
midiControllers,
|
|
20
|
-
MIDIMessage,
|
|
21
|
-
Modulator,
|
|
22
|
-
modulatorSources,
|
|
23
|
-
NON_CC_INDEX_OFFSET,
|
|
24
|
-
RMIDINFOChunks,
|
|
25
|
-
SpessaSynthLogging,
|
|
26
|
-
SynthesizerSnapshot,
|
|
27
|
-
VOICE_CAP
|
|
28
|
-
} from "spessasynth_core";
|
|
2
|
+
|
|
29
3
|
import { Synthetizer } from "./synthetizer/synthetizer.js";
|
|
30
4
|
import { Sequencer } from "./sequencer/sequencer.js";
|
|
31
5
|
import { audioBufferToWav } from "./utils/buffer_to_wav.js";
|
|
@@ -39,39 +13,10 @@ export {
|
|
|
39
13
|
// Synthesizer and Sequencer
|
|
40
14
|
Sequencer,
|
|
41
15
|
Synthetizer,
|
|
42
|
-
SynthesizerSnapshot,
|
|
43
|
-
ChannelSnapshot,
|
|
44
|
-
DEFAULT_PERCUSSION,
|
|
45
|
-
VOICE_CAP,
|
|
46
16
|
DEFAULT_SYNTH_CONFIG,
|
|
47
|
-
ALL_CHANNELS_OR_DIFFERENT_ACTION,
|
|
48
|
-
NON_CC_INDEX_OFFSET,
|
|
49
|
-
|
|
50
|
-
// SoundFont
|
|
51
|
-
BasicSoundBank,
|
|
52
|
-
BasicSample,
|
|
53
|
-
BasicInstrumentZone,
|
|
54
|
-
BasicInstrument,
|
|
55
|
-
BasicPreset,
|
|
56
|
-
BasicPresetZone,
|
|
57
|
-
Generator,
|
|
58
|
-
Modulator,
|
|
59
|
-
loadSoundFont,
|
|
60
|
-
modulatorSources,
|
|
61
|
-
|
|
62
|
-
// MIDI
|
|
63
|
-
MIDI,
|
|
64
|
-
BasicMIDI,
|
|
65
|
-
MIDIBuilder,
|
|
66
|
-
MIDIMessage,
|
|
67
|
-
RMIDINFOChunks,
|
|
68
17
|
|
|
69
18
|
// Utilities
|
|
70
|
-
IndexedByteArray,
|
|
71
19
|
audioBufferToWav,
|
|
72
|
-
SpessaSynthLogging,
|
|
73
|
-
midiControllers,
|
|
74
|
-
messageTypes,
|
|
75
20
|
MIDIDeviceHandler,
|
|
76
21
|
WebMIDILinkHandler,
|
|
77
22
|
WORKLET_URL_ABSOLUTE
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "spessasynth_lib",
|
|
3
|
-
"version": "3.26.
|
|
3
|
+
"version": "3.26.2",
|
|
4
4
|
"description": "MIDI and SoundFont2/DLS library for the browsers with no compromises",
|
|
5
5
|
"browser": "index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -45,7 +45,10 @@
|
|
|
45
45
|
"index.js",
|
|
46
46
|
"LICENSE",
|
|
47
47
|
"README.md",
|
|
48
|
-
"
|
|
48
|
+
"sequencer",
|
|
49
|
+
"synthetizer",
|
|
50
|
+
"utils",
|
|
51
|
+
"external_midi"
|
|
49
52
|
],
|
|
50
53
|
"dependencies": {
|
|
51
54
|
"spessasynth_core": "latest"
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
## This is the sequencer's folder.
|
|
2
|
+
|
|
3
|
+
The code here is responsible for playing back the parsed MIDI sequence with the synthesizer.
|
|
4
|
+
|
|
5
|
+
- `sequencer_engine` - the core sequencer engine, currently runs in audio worklet
|
|
6
|
+
|
|
7
|
+
### Message protocol:
|
|
8
|
+
|
|
9
|
+
#### Message structure
|
|
10
|
+
|
|
11
|
+
```js
|
|
12
|
+
const message = {
|
|
13
|
+
messageType: number, // WorkletSequencerMessageType
|
|
14
|
+
messageData: any // any
|
|
15
|
+
}
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
#### To worklet
|
|
19
|
+
|
|
20
|
+
Sequencer uses `Synthetizer`'s `post` method to post a message with `messageData` set to
|
|
21
|
+
`workletMessageType.sequencerSpecific`.
|
|
22
|
+
The `messageData` is set to the sequencer's message.
|
|
23
|
+
|
|
24
|
+
#### From worklet
|
|
25
|
+
|
|
26
|
+
`WorkletSequencer` uses `SpessaSynthProcessor`'s post to send a message with `messageData` set to
|
|
27
|
+
`returnMessageType.sequencerSpecific`.
|
|
28
|
+
The `messageData` is set to the sequencer's return message.
|
|
29
|
+
|
|
30
|
+
### Process tick
|
|
31
|
+
|
|
32
|
+
`processTick` is called every time the `process` method is called via `SpessaSynthProcessor.processTickCallback`.
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { BasicMIDI, MIDISequenceData } from "spessasynth_core";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A simplified version of the MIDI, accessible at all times from the Sequencer.
|
|
5
|
+
* Use getMIDI() to get the actual sequence.
|
|
6
|
+
* This class contains all properties that MIDI does, except for tracks and the embedded soundfont.
|
|
7
|
+
*/
|
|
8
|
+
export class MIDIData extends MIDISequenceData
|
|
9
|
+
{
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* A boolean indicating if the MIDI file contains an embedded soundfont.
|
|
13
|
+
* If the embedded soundfont is undefined, this will be false.
|
|
14
|
+
* @type {boolean}
|
|
15
|
+
*/
|
|
16
|
+
isEmbedded = false;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Constructor that copies data from a BasicMIDI instance.
|
|
20
|
+
* @param {BasicMIDI} midi - The BasicMIDI instance to copy data from.
|
|
21
|
+
*/
|
|
22
|
+
constructor(midi)
|
|
23
|
+
{
|
|
24
|
+
super();
|
|
25
|
+
this._copyFromSequence(midi);
|
|
26
|
+
|
|
27
|
+
// Set isEmbedded based on the presence of an embeddedSoundFont
|
|
28
|
+
this.isEmbedded = midi.embeddedSoundFont !== undefined;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Temporary MIDI data used when the MIDI is not loaded.
|
|
35
|
+
* @type {MIDIData}
|
|
36
|
+
*/
|
|
37
|
+
export const DUMMY_MIDI_DATA = {
|
|
38
|
+
duration: 99999,
|
|
39
|
+
firstNoteOn: 0,
|
|
40
|
+
loop: {
|
|
41
|
+
start: 0,
|
|
42
|
+
end: 123456
|
|
43
|
+
},
|
|
44
|
+
|
|
45
|
+
lastVoiceEventTick: 123456,
|
|
46
|
+
lyrics: [],
|
|
47
|
+
copyright: "",
|
|
48
|
+
midiPorts: [],
|
|
49
|
+
midiPortChannelOffsets: [],
|
|
50
|
+
tracksAmount: 0,
|
|
51
|
+
tempoChanges: [{ ticks: 0, tempo: 120 }],
|
|
52
|
+
fileName: "NOT_LOADED.mid",
|
|
53
|
+
midiName: "Loading...",
|
|
54
|
+
rawMidiName: new Uint8Array([76, 111, 97, 100, 105, 110, 103, 46, 46, 46]), // "Loading..."
|
|
55
|
+
usedChannelsOnTrack: [],
|
|
56
|
+
timeDivision: 0,
|
|
57
|
+
keyRange: { min: 0, max: 127 },
|
|
58
|
+
isEmbedded: false,
|
|
59
|
+
RMIDInfo: {},
|
|
60
|
+
bankOffset: 0,
|
|
61
|
+
midiNameUsesFileName: false,
|
|
62
|
+
format: 0
|
|
63
|
+
};
|