spessasynth_lib 3.15.1 → 3.16.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.
Files changed (30) hide show
  1. package/@types/midi_parser/used_keys_loaded.d.ts +4 -2
  2. package/@types/soundfont/read/presets.d.ts +1 -0
  3. package/@types/soundfont/soundfont.d.ts +12 -0
  4. package/@types/synthetizer/synth_soundfont_manager.d.ts +52 -0
  5. package/@types/synthetizer/synthetizer.d.ts +13 -1
  6. package/@types/synthetizer/worklet_system/message_protocol/worklet_message.d.ts +2 -2
  7. package/@types/synthetizer/worklet_system/worklet_methods/worklet_soundfont_manager/sfman_message.d.ts +7 -0
  8. package/README.md +1 -0
  9. package/midi_parser/used_keys_loaded.js +1 -1
  10. package/package.json +1 -1
  11. package/sequencer/worklet_sequencer/song_control.js +2 -2
  12. package/soundfont/read/presets.js +2 -0
  13. package/soundfont/soundfont.js +25 -0
  14. package/synthetizer/synth_soundfont_manager.js +111 -0
  15. package/synthetizer/synthetizer.js +19 -12
  16. package/synthetizer/worklet_processor.min.js +6 -6
  17. package/synthetizer/worklet_system/main_processor.js +132 -9
  18. package/synthetizer/worklet_system/message_protocol/handle_message.js +6 -5
  19. package/synthetizer/worklet_system/message_protocol/message_sending.js +0 -13
  20. package/synthetizer/worklet_system/message_protocol/worklet_message.js +4 -3
  21. package/synthetizer/worklet_system/worklet_methods/note_off.js +5 -4
  22. package/synthetizer/worklet_system/worklet_methods/note_on.js +0 -1
  23. package/synthetizer/worklet_system/worklet_methods/program_control.js +11 -16
  24. package/synthetizer/worklet_system/worklet_methods/voice_control.js +20 -12
  25. package/synthetizer/worklet_system/worklet_methods/worklet_soundfont_manager/sfman_message.js +9 -0
  26. package/synthetizer/worklet_system/worklet_methods/worklet_soundfont_manager/worklet_soundfont_manager.js +231 -0
  27. package/synthetizer/worklet_system/worklet_processor.js +1 -1
  28. package/synthetizer/worklet_system/worklet_utilities/worklet_processor_channel.js +5 -5
  29. package/synthetizer/worklet_system/worklet_utilities/worklet_voice.js +1 -3
  30. package/synthetizer/worklet_system/combine_class.js +0 -104
@@ -1,8 +1,10 @@
1
1
  /**
2
2
  * @param mid {MIDI}
3
- * @param soundfont {SoundFont2}
3
+ * @param soundfont {{getPreset: function(number, number): Preset}}
4
4
  * @returns {Object<string, Set<string>>}
5
5
  */
6
- export function getUsedProgramsAndKeys(mid: MIDI, soundfont: SoundFont2): {
6
+ export function getUsedProgramsAndKeys(mid: MIDI, soundfont: {
7
+ getPreset: (arg0: number, arg1: number) => Preset;
8
+ }): {
7
9
  [x: string]: Set<string>;
8
10
  };
@@ -23,6 +23,7 @@ export class Preset {
23
23
  * @type {PresetZone[]}
24
24
  */
25
25
  presetZones: PresetZone[];
26
+ sampleIDOffset: number;
26
27
  /**
27
28
  * Stores already found getSamplesAndGenerators for reuse
28
29
  * @type {SampleAndGenerators[][][]}
@@ -54,6 +54,18 @@ export class SoundFont2 {
54
54
  * @param expected {string}
55
55
  */
56
56
  verifyText(text: string, expected: string): void;
57
+ /**
58
+ * Get the appropriate preset, undefined if not foun d
59
+ * @param bankNr {number}
60
+ * @param presetNr {number}
61
+ * @return {Preset}
62
+ */
63
+ getPresetNoFallback(bankNr: number, presetNr: number): Preset;
64
+ /**
65
+ * To avoid overlapping on multiple desfonts
66
+ * @param offset {number}
67
+ */
68
+ setSampleIDOffset(offset: number): void;
57
69
  /**
58
70
  * Get the appropriate preset
59
71
  * @param bankNr {number}
@@ -0,0 +1,52 @@
1
+ export class SoundfontManager {
2
+ /**
3
+ * Creates a new instance of the soundfont manager
4
+ * @param synth {Synthetizer}
5
+ */
6
+ constructor(synth: Synthetizer);
7
+ /**
8
+ * The current list of soundfonts, in order from the most important to the least.
9
+ * @type {{
10
+ * id: string,
11
+ * bankOffset: number
12
+ * }[]}
13
+ */
14
+ soundfontList: {
15
+ id: string;
16
+ bankOffset: number;
17
+ }[];
18
+ /**
19
+ * @type {MessagePort}
20
+ * @private
21
+ */
22
+ private _port;
23
+ synth: Synthetizer;
24
+ /**
25
+ * @private
26
+ * @param type {WorkletSoundfontManagerMessageType}
27
+ * @param data {any}
28
+ */
29
+ private _sendToWorklet;
30
+ /**
31
+ * Adds a new soundfont buffer with a given ID
32
+ * @param soundfontBuffer {ArrayBuffer} - the soundfont's buffer
33
+ * @param id {string} - the soundfont's unique identifier
34
+ * @param bankOffset {number} - the soundfont's bank offset. Default is 0
35
+ */
36
+ addNewSoundFont(soundfontBuffer: ArrayBuffer, id: string, bankOffset?: number): Promise<void>;
37
+ /**
38
+ * Deletes a soundfont with the given ID
39
+ * @param id {string} - the soundfont to delete
40
+ */
41
+ deleteSoundFont(id: string): void;
42
+ /**
43
+ * Rearranges the soundfonts in a given order
44
+ * @param newList {string[]} the order of soundfonts, a list of identifiers, first overwrites second
45
+ */
46
+ rearrangeSoundFonts(newList: string[]): void;
47
+ /**
48
+ * DELETES ALL SOUNDFONTS!! and creates a new one with id "main"
49
+ * @param newBuffer {ArrayBuffer}
50
+ */
51
+ reloadManager(newBuffer: ArrayBuffer): Promise<void>;
52
+ }
@@ -50,12 +50,15 @@ export class Synthetizer {
50
50
  * @type {number}
51
51
  */
52
52
  channelsAmount: number;
53
+ /**
54
+ * @type {function}
55
+ */
56
+ resolveWhenReady: Function;
53
57
  /**
54
58
  * Indicates if the synth is fully ready
55
59
  * @type {Promise<void>}
56
60
  */
57
61
  isReady: Promise<void>;
58
- _resolveReady: (value: void | PromiseLike<void>) => void;
59
62
  /**
60
63
  * individual channel voices amount
61
64
  * @type {ChannelProperty[]}
@@ -68,6 +71,11 @@ export class Synthetizer {
68
71
  */
69
72
  _highPerformanceMode: boolean;
70
73
  worklet: AudioWorkletNode;
74
+ /**
75
+ * The synth's soundfont manager
76
+ * @type {SoundfontManager}
77
+ */
78
+ soundfontManager: SoundfontManager;
71
79
  /**
72
80
  * @type {function(SynthesizerSnapshot)}
73
81
  * @private
@@ -245,8 +253,11 @@ export class Synthetizer {
245
253
  muteChannel(channel: number, isMuted: boolean): void;
246
254
  /**
247
255
  * Reloads the sounfont.
256
+ * THIS IS DEPRECATED!
257
+ * USE soundfontManager INSTEAD
248
258
  * @param soundFontBuffer {ArrayBuffer} the new soundfont file array buffer
249
259
  * @return {Promise<void>}
260
+ * @deprecated Use the soundfontManager property
250
261
  */
251
262
  reloadSoundFont(soundFontBuffer: ArrayBuffer): Promise<void>;
252
263
  /**
@@ -290,5 +301,6 @@ export type StartRenderingDataConfig = {
290
301
  oneOutput: boolean | undefined;
291
302
  };
292
303
  import { EventHandler } from './synth_event_handler.js';
304
+ import { SoundfontManager } from './synth_soundfont_manager.js';
293
305
  import { FancyChorus } from './audio_effects/fancy_chorus.js';
294
306
  import { IndexedByteArray } from '../utils/indexed_array.js';
@@ -12,7 +12,7 @@ export namespace workletMessageType {
12
12
  let killNote: number;
13
13
  let ccReset: number;
14
14
  let setChannelVibrato: number;
15
- let reloadSoundFont: number;
15
+ let soundFontManager: number;
16
16
  let stopAll: number;
17
17
  let killNotes: number;
18
18
  let muteChannel: number;
@@ -85,5 +85,5 @@ export type WorkletReturnMessage = {
85
85
  } | ChannelProperty[] | PresetListElement[] | string | {
86
86
  messageType: WorkletSequencerReturnMessageType;
87
87
  messageData: any;
88
- } | SynthesizerSnapshot;
88
+ } | SynthesizerSnapshot | [WorkletSoundfontManagerMessageType, any];
89
89
  };
@@ -0,0 +1,7 @@
1
+ export type WorkletSoundfontManagerMessageType = number;
2
+ export namespace WorkletSoundfontManagerMessageType {
3
+ let reloadSoundFont: number;
4
+ let addNewSoundFont: number;
5
+ let deleteSoundFont: number;
6
+ let rearrangeSoundFonts: number;
7
+ }
package/README.md CHANGED
@@ -40,6 +40,7 @@ document.getElementById("button").onclick = async () => {
40
40
  - **Modulator Support:** _First (to my knowledge) JavaScript SoundFont synth with that feature!_
41
41
  - **SoundFont3 Support:** Play compressed SoundFonts!
42
42
  - **Can load very large SoundFonts:** up to 4GB! _Note: Only Firefox handles this well; Chromium has a hard-coded memory limit_
43
+ - **Soundfont manager:** Stack multiple soundfonts!
43
44
  - **Reverb and chorus support:** [customizable!](https://github.com/spessasus/SpessaSynth/wiki/Synthetizer-Class#effects-configuration-object)
44
45
  - **Export audio files** using [OfflineAudioContext](https://developer.mozilla.org/en-US/docs/Web/API/OfflineAudioContext)
45
46
  - **[Custom modulators for additional controllers](https://github.com/spessasus/SpessaSynth/wiki/Modulator-Class#default-modulators):** Why not?
@@ -5,7 +5,7 @@ import { messageTypes, midiControllers } from './midi_message.js'
5
5
 
6
6
  /**
7
7
  * @param mid {MIDI}
8
- * @param soundfont {SoundFont2}
8
+ * @param soundfont {{getPreset: function(number, number): Preset}}
9
9
  * @returns {Object<string, Set<string>>}
10
10
  */
11
11
  export function getUsedProgramsAndKeys(mid, soundfont)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "spessasynth_lib",
3
- "version": "3.15.1",
3
+ "version": "3.16.0",
4
4
  "description": "No compromise MIDI and SoundFont2 Synthesizer library",
5
5
  "browser": "index.js",
6
6
  "types": "@types/index.d.ts",
@@ -67,12 +67,12 @@ export function loadNewSequence(parsedMidi)
67
67
  {
68
68
  if(this.synth.overrideSoundfont)
69
69
  {
70
- // clean up the emdeeded soundfont
70
+ // clean up the embedded soundfont
71
71
  this.synth.clearSoundFont();
72
72
  }
73
73
  SpessaSynthGroupCollapsed("%cPreloading samples...", consoleColors.info);
74
74
  // smart preloading: load only samples used in the midi!
75
- const used = getUsedProgramsAndKeys(this.midiData, this.synth.soundfont);
75
+ const used = getUsedProgramsAndKeys(this.midiData, this.synth.soundfontManager);
76
76
  for (const [programBank, combos] of Object.entries(used))
77
77
  {
78
78
  const bank = parseInt(programBank.split(":")[0]);
@@ -30,6 +30,8 @@ export class Preset {
30
30
  */
31
31
  this.presetZones = [];
32
32
 
33
+ this.sampleIDOffset = 0;
34
+
33
35
  /**
34
36
  * Stores already found getSamplesAndGenerators for reuse
35
37
  * @type {SampleAndGenerators[][][]}
@@ -49,6 +49,11 @@ class SoundFont2
49
49
  SpessaSynthGroupEnd();
50
50
  throw new SyntaxError(`Invalid soundFont! Expected "sfbk" or "sfpk" got "${type}"`);
51
51
  }
52
+ /*
53
+ Some SF2Pack description:
54
+ this is essentially sf2, but the entire smpl chunk is compressed (we only support ogg vorbis here)
55
+ and the only other difference is that the main chunk isn't "sfbk" but rather "sfpk"
56
+ */
52
57
  const isSF2Pack = type === "sfpk";
53
58
 
54
59
  // INFO
@@ -316,6 +321,26 @@ class SoundFont2
316
321
  }
317
322
  }
318
323
 
324
+ /**
325
+ * Get the appropriate preset, undefined if not foun d
326
+ * @param bankNr {number}
327
+ * @param presetNr {number}
328
+ * @return {Preset}
329
+ */
330
+ getPresetNoFallback(bankNr, presetNr)
331
+ {
332
+ return this.presets.find(p => p.bank === bankNr && p.program === presetNr);
333
+ }
334
+
335
+ /**
336
+ * To avoid overlapping on multiple desfonts
337
+ * @param offset {number}
338
+ */
339
+ setSampleIDOffset(offset)
340
+ {
341
+ this.presets.forEach(p => p.sampleIDOffset = offset);
342
+ }
343
+
319
344
  /**
320
345
  * Get the appropriate preset
321
346
  * @param bankNr {number}
@@ -0,0 +1,111 @@
1
+ import { workletMessageType } from './worklet_system/message_protocol/worklet_message.js'
2
+ import {
3
+ WorkletSoundfontManagerMessageType
4
+ } from './worklet_system/worklet_methods/worklet_soundfont_manager/sfman_message.js'
5
+ import { SpessaSynthWarn } from '../utils/loggin.js'
6
+
7
+ export class SoundfontManager
8
+ {
9
+ /**
10
+ * Creates a new instance of the soundfont manager
11
+ * @param synth {Synthetizer}
12
+ */
13
+ constructor(synth)
14
+ {
15
+ /**
16
+ * The current list of soundfonts, in order from the most important to the least.
17
+ * @type {{
18
+ * id: string,
19
+ * bankOffset: number
20
+ * }[]}
21
+ */
22
+ this.soundfontList = [{
23
+ id: "main",
24
+ bankOffset: 0
25
+ }];
26
+
27
+ /**
28
+ * @type {MessagePort}
29
+ * @private
30
+ */
31
+ this._port = synth.worklet.port;
32
+ this.synth = synth;
33
+ }
34
+
35
+ /**
36
+ * @private
37
+ * @param type {WorkletSoundfontManagerMessageType}
38
+ * @param data {any}
39
+ */
40
+ _sendToWorklet(type, data)
41
+ {
42
+ this._port.postMessage({
43
+ messageType: workletMessageType.soundFontManager,
44
+ messageData: [
45
+ type,
46
+ data
47
+ ]
48
+ });
49
+ }
50
+
51
+ /**
52
+ * Adds a new soundfont buffer with a given ID
53
+ * @param soundfontBuffer {ArrayBuffer} - the soundfont's buffer
54
+ * @param id {string} - the soundfont's unique identifier
55
+ * @param bankOffset {number} - the soundfont's bank offset. Default is 0
56
+ */
57
+ async addNewSoundFont(soundfontBuffer, id, bankOffset = 0)
58
+ {
59
+ if(this.soundfontList.find(s => s.id === id) !== undefined)
60
+ {
61
+ throw new Error("Cannot overwrite the existing soundfont. Use soundfontManager.delete(id) instead.");
62
+ }
63
+ this._sendToWorklet(WorkletSoundfontManagerMessageType.addNewSoundFont, [soundfontBuffer, id, bankOffset]);
64
+ await new Promise(r => this.synth.resolveWhenReady = r);
65
+ this.soundfontList.push({
66
+ id: id,
67
+ bankOffset: bankOffset
68
+ });
69
+ }
70
+
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
+ SpessaSynthWarn("1 soundfont left. Aborting!");
80
+ return;
81
+ }
82
+ if(this.soundfontList.findIndex(s => s.id === id) === -1)
83
+ {
84
+ SpessaSynthWarn(`No soundfont with id of "${id}" found. Aborting!`);
85
+ return;
86
+ }
87
+ this._sendToWorklet(WorkletSoundfontManagerMessageType.deleteSoundFont, id);
88
+ }
89
+
90
+ /**
91
+ * Rearranges the soundfonts in a given order
92
+ * @param newList {string[]} the order of soundfonts, a list of identifiers, first overwrites second
93
+ */
94
+ rearrangeSoundFonts(newList)
95
+ {
96
+ this._sendToWorklet(WorkletSoundfontManagerMessageType.rearrangeSoundFonts, newList);
97
+ this.soundfontList.sort((a, b) =>
98
+ newList.indexOf(a.id) - newList.indexOf(b.id)
99
+ );
100
+ }
101
+
102
+ /**
103
+ * DELETES ALL SOUNDFONTS!! and creates a new one with id "main"
104
+ * @param newBuffer {ArrayBuffer}
105
+ */
106
+ async reloadManager(newBuffer)
107
+ {
108
+ this._sendToWorklet(WorkletSoundfontManagerMessageType.reloadSoundFont, newBuffer);
109
+ await new Promise(r => this.synth.resolveWhenReady = r);
110
+ }
111
+ }
@@ -11,6 +11,7 @@ import {
11
11
  } from './worklet_system/message_protocol/worklet_message.js'
12
12
  import { SpessaSynthInfo, SpessaSynthWarn } from '../utils/loggin.js'
13
13
  import { DEFAULT_EFFECTS_CONFIG } from './audio_effects/effects_config.js'
14
+ import { SoundfontManager } from './synth_soundfont_manager.js'
14
15
 
15
16
 
16
17
  /**
@@ -81,11 +82,16 @@ export class Synthetizer {
81
82
  */
82
83
  this.channelsAmount = this._outputsAmount;
83
84
 
85
+ /**
86
+ * @type {function}
87
+ */
88
+ this.resolveWhenReady = undefined;
89
+
84
90
  /**
85
91
  * Indicates if the synth is fully ready
86
92
  * @type {Promise<void>}
87
93
  */
88
- this.isReady = new Promise(resolve => this._resolveReady = resolve);
94
+ this.isReady = new Promise(resolve => this.resolveWhenReady = resolve);
89
95
 
90
96
 
91
97
  /**
@@ -145,6 +151,12 @@ export class Synthetizer {
145
151
  // worklet sends us some data back
146
152
  this.worklet.port.onmessage = e => this.handleMessage(e.data);
147
153
 
154
+ /**
155
+ * The synth's soundfont manager
156
+ * @type {SoundfontManager}
157
+ */
158
+ this.soundfontManager = new SoundfontManager(this);
159
+
148
160
  /**
149
161
  * @type {function(SynthesizerSnapshot)}
150
162
  * @private
@@ -282,7 +294,7 @@ export class Synthetizer {
282
294
  break;
283
295
 
284
296
  case returnMessageType.ready:
285
- this._resolveReady();
297
+ this.resolveWhenReady();
286
298
  break;
287
299
 
288
300
  case returnMessageType.soundfontError:
@@ -634,21 +646,16 @@ export class Synthetizer {
634
646
 
635
647
  /**
636
648
  * Reloads the sounfont.
649
+ * THIS IS DEPRECATED!
650
+ * USE soundfontManager INSTEAD
637
651
  * @param soundFontBuffer {ArrayBuffer} the new soundfont file array buffer
638
652
  * @return {Promise<void>}
653
+ * @deprecated Use the soundfontManager property
639
654
  */
640
655
  async reloadSoundFont(soundFontBuffer)
641
656
  {
642
- // copy and use transferable
643
- const bufferCopy = soundFontBuffer.slice(0);
644
- await new Promise(resolve => {
645
- this._resolveReady = resolve;
646
- this.worklet.port.postMessage({
647
- channelNumber: 0,
648
- messageType: workletMessageType.reloadSoundFont,
649
- messageData: bufferCopy
650
- }, [bufferCopy]);
651
- });
657
+ SpessaSynthWarn("reloadSoundFont is deprecated. Please use the soundfontManager property instead.")
658
+ await this.soundfontManager.reloadManager(soundFontBuffer);
652
659
  }
653
660
 
654
661
  /**