spessasynth_lib 3.26.0 → 3.26.1

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.
@@ -0,0 +1,35 @@
1
+ import { reverbBufferBinary } from "./reverb_as_binary.js";
2
+
3
+ /**
4
+ * Creates a reverb processor
5
+ * @param context {BaseAudioContext}
6
+ * @param reverbBuffer {AudioBuffer}
7
+ * @returns {{conv: ConvolverNode, promise: Promise<AudioBuffer>}}
8
+ */
9
+ export function getReverbProcessor(context, reverbBuffer = undefined)
10
+ {
11
+ let solve;
12
+ /**
13
+ * @type {Promise<AudioBuffer>}
14
+ */
15
+ let promise = new Promise(r => solve = r);
16
+ const convolver = context.createConvolver();
17
+ if (reverbBuffer)
18
+ {
19
+ convolver.buffer = reverbBuffer;
20
+ solve();
21
+ }
22
+ else
23
+ {
24
+ // decode
25
+ promise = context.decodeAudioData(reverbBufferBinary.slice(0));
26
+ promise.then(b =>
27
+ {
28
+ convolver.buffer = b;
29
+ });
30
+ }
31
+ return {
32
+ conv: convolver,
33
+ promise: promise
34
+ };
35
+ }
@@ -0,0 +1,18 @@
1
+ import { rbCompressed } from "./rb_compressed.min.js";
2
+ import { SpessaSynthCoreUtils } from "spessasynth_core";
3
+
4
+ // convert the base64 string to array buffer
5
+ const binaryString = atob(rbCompressed);
6
+ const binary = new Uint8Array(binaryString.length);
7
+ for (let i = 0; i < binaryString.length; i++)
8
+ {
9
+ binary[i] = binaryString.charCodeAt(i);
10
+ }
11
+
12
+
13
+ /**
14
+ * the reverb is zlib compressed, decompress here
15
+ * @type {ArrayBuffer}
16
+ */
17
+ const reverbBufferBinary = SpessaSynthCoreUtils.inflateSync(binary).buffer;
18
+ export { reverbBufferBinary };
@@ -0,0 +1,113 @@
1
+ import { workletMessageType } from "./worklet_message.js";
2
+ import { KeyModifier } from "spessasynth_core";
3
+
4
+ /**
5
+ * @enum {number}
6
+ */
7
+ export const workletKeyModifierMessageType = {
8
+ addMapping: 0, // [channel<number>, midiNote<number>, mapping<KeyModifier>]
9
+ deleteMapping: 1, // [channel<number>, midiNote<number>]
10
+ clearMappings: 2 // <no data>
11
+ };
12
+
13
+ export class WorkletKeyModifierManagerWrapper
14
+ {
15
+ /**
16
+ * @param synth {Synthetizer}
17
+ */
18
+ constructor(synth)
19
+ {
20
+ this.synth = synth;
21
+ /**
22
+ * The velocity override mappings for MIDI keys
23
+ * @type {KeyModifier[][]}
24
+ * @private
25
+ */
26
+ this._keyModifiers = [];
27
+ }
28
+
29
+ /**
30
+ * @private
31
+ * @param type {workletKeyModifierMessageType}
32
+ * @param data {any}
33
+ */
34
+ _sendToWorklet(type, data)
35
+ {
36
+ this.synth.post({
37
+ messageType: workletMessageType.keyModifierManager,
38
+ messageData: [
39
+ type,
40
+ data
41
+ ]
42
+ });
43
+ }
44
+
45
+ /**
46
+ * Modifies a single key
47
+ * @param channel {number} the channel affected. Usually 0-15
48
+ * @param midiNote {number} the MIDI note to change. 0-127
49
+ * @param options {{
50
+ * velocity: number|undefined,
51
+ * patch: {
52
+ * bank: number,
53
+ * program: number
54
+ * }|undefined,
55
+ * gain: number|undefined
56
+ * }} the key's modifiers
57
+ */
58
+ addModifier(channel, midiNote, options)
59
+ {
60
+ const velocity = options?.velocity ?? -1;
61
+ const program = options?.patch?.program ?? -1;
62
+ const bank = options?.patch?.bank ?? -1;
63
+ const gain = options?.gain ?? 1;
64
+ const mod = new KeyModifier(velocity, bank, program, gain);
65
+ if (this._keyModifiers[channel] === undefined)
66
+ {
67
+ this._keyModifiers[channel] = [];
68
+ }
69
+ this._keyModifiers[channel][midiNote] = mod;
70
+ this._sendToWorklet(
71
+ workletKeyModifierMessageType.addMapping,
72
+ [channel, midiNote, mod]
73
+ );
74
+ }
75
+
76
+ /**
77
+ * Gets a key modifier
78
+ * @param channel {number} the channel affected. Usually 0-15
79
+ * @param midiNote {number} the MIDI note to change. 0-127
80
+ * @returns {KeyModifier|undefined}
81
+ */
82
+ getModifier(channel, midiNote)
83
+ {
84
+ return this._keyModifiers?.[channel]?.[midiNote];
85
+ }
86
+
87
+ /**
88
+ * Deletes a key modifier
89
+ * @param channel {number} the channel affected. Usually 0-15
90
+ * @param midiNote {number} the MIDI note to change. 0-127
91
+ */
92
+ deleteModifier(channel, midiNote)
93
+ {
94
+ this._sendToWorklet(
95
+ workletKeyModifierMessageType.deleteMapping,
96
+ [channel, midiNote]
97
+ );
98
+ if (this._keyModifiers[channel]?.[midiNote] === undefined)
99
+ {
100
+ return;
101
+ }
102
+ this._keyModifiers[channel][midiNote] = undefined;
103
+ }
104
+
105
+ /**
106
+ * Clears ALL Modifiers
107
+ */
108
+ clearModifiers()
109
+ {
110
+ this._sendToWorklet(workletKeyModifierMessageType.clearMappings, undefined);
111
+ this._keyModifiers = [];
112
+ }
113
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * @enum {number}
3
+ */
4
+ export const WorkletSoundfontManagerMessageType = {
5
+ reloadSoundFont: 0, // buffer<ArrayBuffer>
6
+ addNewSoundFont: 2, // [buffer<ArrayBuffer>, id<string>, bankOffset<number>]
7
+ deleteSoundFont: 3, // id<string>
8
+ rearrangeSoundFonts: 4 // newOrder<string[]> // where string is the id
9
+ };
@@ -0,0 +1,217 @@
1
+ import { synthDisplayTypes } from "spessasynth_core";
2
+
3
+ /**
4
+ * synth_event_handler.js
5
+ * purpose: manages the synthesizer's event system, calling assigned functions when synthesizer requests dispatching the event
6
+ */
7
+
8
+ /**
9
+ * @typedef {Object} NoteOnCallback
10
+ * @property {number} midiNote - The MIDI note number.
11
+ * @property {number} channel - The MIDI channel number.
12
+ * @property {number} velocity - The velocity of the note.
13
+ */
14
+
15
+ /**
16
+ * @typedef {Object} NoteOffCallback
17
+ * @property {number} midiNote - The MIDI note number.
18
+ * @property {number} channel - The MIDI channel number.
19
+ */
20
+
21
+ /**
22
+ * @typedef {Object} DrumChangeCallback
23
+ * @property {number} channel - The MIDI channel number.
24
+ * @property {boolean} isDrumChannel - Indicates if the channel is a drum channel.
25
+ */
26
+
27
+ /**
28
+ * @typedef {Object} ProgramChangeCallback
29
+ * @property {number} channel - The MIDI channel number.
30
+ * @property {number} program - The program number.
31
+ * @property {number} bank - The bank number.
32
+ */
33
+
34
+ /**
35
+ * @typedef {Object} ControllerChangeCallback
36
+ * @property {number} channel - The MIDI channel number.
37
+ * @property {number} controllerNumber - The controller number.
38
+ * @property {number} controllerValue - The value of the controller.
39
+ */
40
+
41
+ /**
42
+ * @typedef {Object} MuteChannelCallback
43
+ * @property {number} channel - The MIDI channel number.
44
+ * @property {boolean} isMuted - Indicates if the channel is muted.
45
+ */
46
+
47
+ /**
48
+ * @typedef {Object} PresetListChangeCallbackSingle
49
+ * @property {string} presetName - The name of the preset.
50
+ * @property {number} bank - The bank number.
51
+ * @property {number} program - The program number.
52
+ */
53
+
54
+ /**
55
+ * @typedef {PresetListChangeCallbackSingle[]} PresetListChangeCallback - A list of preset objects.
56
+ */
57
+
58
+ /**
59
+ * @typedef {Object} SynthDisplayCallback
60
+ * @property {Uint8Array} displayData - The data to display.
61
+ * @property {synthDisplayTypes} displayType - The type of display.
62
+ */
63
+
64
+ /**
65
+ * @typedef {Object} PitchWheelCallback
66
+ * @property {number} channel - The MIDI channel number.
67
+ * @property {number} MSB - The most significant byte of the pitch-wheel value.
68
+ * @property {number} LSB - The least significant byte of the pitch-wheel value.
69
+ */
70
+
71
+ /**
72
+ * @typedef {Object} ChannelPressureCallback
73
+ * @property {number} channel - The MIDI channel number.
74
+ * @property {number} pressure - The pressure value.
75
+ */
76
+
77
+ /**
78
+ * @typedef {Error} SoundfontErrorCallback - The error message for soundfont errors.
79
+ */
80
+
81
+ /**
82
+ * @typedef {
83
+ * NoteOnCallback |
84
+ * NoteOffCallback |
85
+ * DrumChangeCallback |
86
+ * ProgramChangeCallback |
87
+ * ControllerChangeCallback |
88
+ * MuteChannelCallback |
89
+ * PresetListChangeCallback |
90
+ * PitchWheelCallback |
91
+ * SoundfontErrorCallback |
92
+ * ChannelPressureCallback |
93
+ * SynthDisplayCallback |
94
+ * undefined
95
+ * } EventCallbackData
96
+ */
97
+
98
+ /**
99
+ * @typedef {
100
+ * "noteon"|
101
+ * "noteoff"|
102
+ * "pitchwheel"|
103
+ * "controllerchange"|
104
+ * "programchange"|
105
+ * "channelpressure"|
106
+ * "polypressure" |
107
+ * "drumchange"|
108
+ * "stopall"|
109
+ * "newchannel"|
110
+ * "mutechannel"|
111
+ * "presetlistchange"|
112
+ * "allcontrollerreset"|
113
+ * "soundfonterror"|
114
+ * "synthdisplay"} EventTypes
115
+ */
116
+ export class EventHandler
117
+ {
118
+ /**
119
+ * A new synthesizer event handler
120
+ */
121
+ constructor()
122
+ {
123
+ /**
124
+ * The main list of events
125
+ * @type {Object<EventTypes, Object<string, function(EventCallbackData)>>}
126
+ */
127
+ this.events = {
128
+ "noteoff": {}, // called on a note off message
129
+ "noteon": {}, // called on a note on message
130
+ "pitchwheel": {}, // called on a pitch-wheel change
131
+ "controllerchange": {}, // called on a controller change
132
+ "programchange": {}, // called on a program change
133
+ "channelpressure": {}, // called on a channel pressure message
134
+ "polypressure": {}, // called on a poly pressure message
135
+ "drumchange": {}, // called when a channel type changes
136
+ "stopall": {}, // called when the synth receives stop all command
137
+ "newchannel": {}, // called when a new channel is created
138
+ "mutechannel": {}, // called when a channel is muted/unmuted
139
+ "presetlistchange": {}, // called when the preset list changes (soundfont gets reloaded)
140
+ "allcontrollerreset": {}, // called when all controllers are reset
141
+ "soundfonterror": {}, // called when a soundfont parsing error occurs
142
+ "synthdisplay": {} // called when there's a SysEx message to display some text
143
+ };
144
+
145
+ /**
146
+ * Set to 0 to disabled, otherwise in seconds
147
+ * @type {number}
148
+ */
149
+ this.timeDelay = 0;
150
+ }
151
+
152
+ /**
153
+ * Adds a new event listener
154
+ * @param name {EventTypes}
155
+ * @param id {string} the unique identifier for the event (to delete it
156
+ * @param callback {function(EventCallbackData)}
157
+ */
158
+ addEvent(name, id, callback)
159
+ {
160
+ this.events[name][id] = callback;
161
+ }
162
+
163
+ // noinspection JSUnusedGlobalSymbols
164
+ /**
165
+ * Removes an event listener
166
+ * @param name {EventTypes}
167
+ * @param id {string}
168
+ */
169
+ removeEvent(name, id)
170
+ {
171
+ delete this.events[name][id];
172
+ }
173
+
174
+ /**
175
+ * Calls the given event
176
+ * @param name {EventTypes}
177
+ * @param eventData {EventCallbackData}
178
+ */
179
+ callEvent(name, eventData)
180
+ {
181
+ if (this.events[name])
182
+ {
183
+ if (this.timeDelay > 0)
184
+ {
185
+ setTimeout(() =>
186
+ {
187
+ Object.values(this.events[name]).forEach(ev =>
188
+ {
189
+ try
190
+ {
191
+ ev(eventData);
192
+ }
193
+ catch (e)
194
+ {
195
+ console.error(`Error while executing an event callback for ${name}:`, e);
196
+ }
197
+ });
198
+ }, this.timeDelay * 1000);
199
+ }
200
+ else
201
+ {
202
+ Object.values(this.events[name]).forEach(ev =>
203
+ {
204
+ try
205
+ {
206
+ ev(eventData);
207
+ }
208
+ catch (e)
209
+ {
210
+ console.error(`Error while executing an event callback for ${name}:`, e);
211
+ }
212
+ }
213
+ );
214
+ }
215
+ }
216
+ }
217
+ }
@@ -0,0 +1,112 @@
1
+ import { workletMessageType } from "./worklet_message.js";
2
+ import { WorkletSoundfontManagerMessageType } from "./sfman_message.js";
3
+ import { SpessaSynthCoreUtils } from "spessasynth_core";
4
+
5
+ export class SoundfontManager
6
+ {
7
+ /**
8
+ * Creates a new instance of the soundfont manager
9
+ * @param synth {Synthetizer}
10
+ */
11
+ constructor(synth)
12
+ {
13
+ /**
14
+ * The current list of soundfonts, in order from the most important to the least.
15
+ * @type {{
16
+ * id: string,
17
+ * bankOffset: number
18
+ * }[]}
19
+ */
20
+ this.soundfontList = [{
21
+ id: "main",
22
+ bankOffset: 0
23
+ }];
24
+
25
+ /**
26
+ * @type {MessagePort}
27
+ * @private
28
+ */
29
+ this._port = synth.worklet.port;
30
+ this.synth = synth;
31
+ }
32
+
33
+ /**
34
+ * @private
35
+ * @param type {WorkletSoundfontManagerMessageType}
36
+ * @param data {any}
37
+ */
38
+ _sendToWorklet(type, data)
39
+ {
40
+ this._port.postMessage({
41
+ messageType: workletMessageType.soundFontManager,
42
+ messageData: [
43
+ type,
44
+ data
45
+ ]
46
+ });
47
+ }
48
+
49
+ // noinspection JSUnusedGlobalSymbols
50
+ /**
51
+ * Adds a new soundfont buffer with a given ID
52
+ * @param soundfontBuffer {ArrayBuffer} - the soundfont's buffer
53
+ * @param id {string} - the soundfont's unique identifier
54
+ * @param bankOffset {number} - the soundfont's bank offset. Default is 0
55
+ */
56
+ async addNewSoundFont(soundfontBuffer, id, bankOffset = 0)
57
+ {
58
+ if (this.soundfontList.find(s => s.id === id) !== undefined)
59
+ {
60
+ throw new Error("Cannot overwrite the existing soundfont. Use soundfontManager.delete(id) instead.");
61
+ }
62
+ this._sendToWorklet(WorkletSoundfontManagerMessageType.addNewSoundFont, [soundfontBuffer, id, bankOffset]);
63
+ await new Promise(r => this.synth._resolveWhenReady = r);
64
+ this.soundfontList.push({
65
+ id: id,
66
+ bankOffset: bankOffset
67
+ });
68
+ }
69
+
70
+ // noinspection JSUnusedGlobalSymbols
71
+ /**
72
+ * Deletes a soundfont with the given ID
73
+ * @param id {string} - the soundfont to delete
74
+ */
75
+ deleteSoundFont(id)
76
+ {
77
+ if (this.soundfontList.length === 0)
78
+ {
79
+ SpessaSynthCoreUtils.SpessaSynthWarn("1 soundfont left. Aborting!");
80
+ return;
81
+ }
82
+ if (this.soundfontList.findIndex(s => s.id === id) === -1)
83
+ {
84
+ SpessaSynthCoreUtils.SpessaSynthWarn(`No soundfont with id of "${id}" found. Aborting!`);
85
+ return;
86
+ }
87
+ this._sendToWorklet(WorkletSoundfontManagerMessageType.deleteSoundFont, id);
88
+ }
89
+
90
+ // noinspection JSUnusedGlobalSymbols
91
+ /**
92
+ * Rearranges the soundfonts in a given order
93
+ * @param newList {string[]} the order of soundfonts, a list of identifiers, first overwrites second
94
+ */
95
+ rearrangeSoundFonts(newList)
96
+ {
97
+ this._sendToWorklet(WorkletSoundfontManagerMessageType.rearrangeSoundFonts, newList);
98
+ this.soundfontList.sort((a, b) =>
99
+ newList.indexOf(a.id) - newList.indexOf(b.id)
100
+ );
101
+ }
102
+
103
+ /**
104
+ * DELETES ALL SOUNDFONTS! and creates a new one with id "main"
105
+ * @param newBuffer {ArrayBuffer}
106
+ */
107
+ async reloadManager(newBuffer)
108
+ {
109
+ this._sendToWorklet(WorkletSoundfontManagerMessageType.reloadSoundFont, newBuffer);
110
+ await new Promise(r => this.synth._resolveWhenReady = r);
111
+ }
112
+ }