spessasynth_lib 3.21.14 → 3.22.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.
Files changed (27) hide show
  1. package/@types/soundfont/basic_soundfont/basic_soundfont.d.ts +4 -0
  2. package/@types/synthetizer/key_modifier_manager.d.ts +42 -0
  3. package/@types/synthetizer/synthetizer.d.ts +6 -0
  4. package/@types/synthetizer/worklet_system/message_protocol/worklet_message.d.ts +4 -0
  5. package/@types/synthetizer/worklet_system/worklet_methods/worklet_key_modifier.d.ts +69 -0
  6. package/package.json +1 -1
  7. package/sequencer/sequencer.js +1 -1
  8. package/sequencer/worklet_sequencer/song_control.js +1 -1
  9. package/soundfont/basic_soundfont/basic_soundfont.js +8 -0
  10. package/soundfont/basic_soundfont/write_sf2/write.js +1 -1
  11. package/soundfont/dls/articulator_converter.js +1 -1
  12. package/soundfont/dls/dls_sample.js +1 -1
  13. package/soundfont/dls/dls_soundfont.js +18 -5
  14. package/soundfont/dls/read_instrument.js +1 -1
  15. package/soundfont/dls/read_samples.js +1 -1
  16. package/soundfont/read_sf2/samples.js +12 -5
  17. package/soundfont/read_sf2/soundfont.js +3 -3
  18. package/synthetizer/key_modifier_manager.js +73 -0
  19. package/synthetizer/synthetizer.js +7 -0
  20. package/synthetizer/worklet_processor.min.js +10 -10
  21. package/synthetizer/worklet_system/main_processor.js +8 -1
  22. package/synthetizer/worklet_system/message_protocol/handle_message.js +21 -2
  23. package/synthetizer/worklet_system/message_protocol/worklet_message.js +4 -1
  24. package/synthetizer/worklet_system/worklet_methods/note_on.js +7 -0
  25. package/synthetizer/worklet_system/worklet_methods/worklet_key_modifier.js +141 -0
  26. package/synthetizer/worklet_system/worklet_utilities/lowpass_filter.js +0 -1
  27. package/synthetizer/worklet_system/worklet_utilities/worklet_voice.js +114 -100
@@ -81,6 +81,10 @@ export class BasicSoundFont {
81
81
  * @returns {BasicPreset}
82
82
  */
83
83
  getPresetByName(presetName: string): BasicPreset;
84
+ /**
85
+ * @param error {string}
86
+ */
87
+ parsingError(error: string): void;
84
88
  write: typeof write;
85
89
  }
86
90
  import { Modulator } from "./modulator.js";
@@ -0,0 +1,42 @@
1
+ export class KeyModifierManager {
2
+ /**
3
+ * @param synth {Synthetizer}
4
+ */
5
+ constructor(synth: Synthetizer);
6
+ synth: Synthetizer;
7
+ /**
8
+ * @private
9
+ * @param type {workletKeyModifierMessageType}
10
+ * @param data {any}
11
+ */
12
+ private _sendToWorklet;
13
+ /**
14
+ * Modifies a single key
15
+ * @param channel {number} the channel affected. Usually 0-15
16
+ * @param midiNote {number} the MIDI note to change. 0-127
17
+ * @param options {{
18
+ * velocity: number|undefined,
19
+ * patch: {
20
+ * bank: number,
21
+ * program: number
22
+ * }|undefined
23
+ * }} the key's modifiers
24
+ */
25
+ addModifier(channel: number, midiNote: number, options: {
26
+ velocity: number | undefined;
27
+ patch: {
28
+ bank: number;
29
+ program: number;
30
+ } | undefined;
31
+ }): void;
32
+ /**
33
+ * Deletes a key modifier
34
+ * @param channel {number} the channel affected. Usually 0-15
35
+ * @param midiNote {number} the MIDI note to change. 0-127
36
+ */
37
+ deleteModifier(channel: number, midiNote: number): void;
38
+ /**
39
+ * Clears ALL Modifiers
40
+ */
41
+ clearModifiers(): void;
42
+ }
@@ -82,6 +82,11 @@ export class Synthetizer {
82
82
  * @type {SoundfontManager}
83
83
  */
84
84
  soundfontManager: SoundfontManager;
85
+ /**
86
+ * The synth's key modifier manager
87
+ * @type {KeyModifierManager}
88
+ */
89
+ keyModifierManager: KeyModifierManager;
85
90
  /**
86
91
  * @type {function(SynthesizerSnapshot)}
87
92
  * @private
@@ -343,4 +348,5 @@ export type StartRenderingDataConfig = {
343
348
  };
344
349
  import { EventHandler } from "./synth_event_handler.js";
345
350
  import { SoundfontManager } from "./synth_soundfont_manager.js";
351
+ import { KeyModifierManager } from "./key_modifier_manager.js";
346
352
  import { FancyChorus } from "./audio_effects/fancy_chorus.js";
@@ -29,6 +29,7 @@ export namespace workletMessageType {
29
29
  let sequencerSpecific: number;
30
30
  let requestSynthesizerSnapshot: number;
31
31
  let setLogLevel: number;
32
+ let keyModifierManager: number;
32
33
  }
33
34
  export type masterParameterType = number;
34
35
  export namespace masterParameterType {
@@ -63,6 +64,9 @@ export type WorkletMessage = {
63
64
  } | boolean | ArrayBuffer | {
64
65
  messageType: WorkletSequencerMessageType;
65
66
  messageData: any;
67
+ } | {
68
+ messageType: workletKeyModifierMessageType;
69
+ messageData: any;
66
70
  });
67
71
  };
68
72
  export type WorkletReturnMessage = {
@@ -0,0 +1,69 @@
1
+ export class KeyModifier {
2
+ /**
3
+ * @param velocity {number}
4
+ * @param bank {number}
5
+ * @param program {number}
6
+ */
7
+ constructor(velocity?: number, bank?: number, program?: number);
8
+ /**
9
+ * The new override velocity. -1 means unchanged
10
+ * @type {number}
11
+ */
12
+ velocity: number;
13
+ /**
14
+ * The patch this key uses. -1 on either means default
15
+ * @type {{bank: number, program: number}}
16
+ */
17
+ patch: {
18
+ bank: number;
19
+ program: number;
20
+ };
21
+ }
22
+ export type workletKeyModifierMessageType = number;
23
+ export namespace workletKeyModifierMessageType {
24
+ let addMapping: number;
25
+ let deleteMapping: number;
26
+ let clearMappings: number;
27
+ }
28
+ export class WorkletKeyModifierManager {
29
+ /**
30
+ * The velocity override mappings for MIDI keys
31
+ * @type {KeyModifier[][]}
32
+ * @private
33
+ */
34
+ private _keyMappings;
35
+ /**
36
+ * @param type {workletKeyModifierMessageType}
37
+ * @param data {any}
38
+ */
39
+ handleMessage(type: workletKeyModifierMessageType, data: any): void;
40
+ /**
41
+ * @param channel {number}
42
+ * @param midiNote {number}
43
+ * @param mapping {KeyModifier}
44
+ */
45
+ addMapping(channel: number, midiNote: number, mapping: KeyModifier): void;
46
+ deleteMapping(channel: any, midiNote: any): void;
47
+ clearMappings(): void;
48
+ /**
49
+ * @param channel {number}
50
+ * @param midiNote {number}
51
+ * @returns {number} velocity, -1 if unchanged
52
+ */
53
+ getVelocity(channel: number, midiNote: number): number;
54
+ /**
55
+ * @param channel {number}
56
+ * @param midiNote {number}
57
+ * @returns {boolean}
58
+ */
59
+ hasOverridePatch(channel: number, midiNote: number): boolean;
60
+ /**
61
+ * @param channel {number}
62
+ * @param midiNote {number}
63
+ * @returns {{bank: number, program: number}} -1 if unchanged
64
+ */
65
+ getPatch(channel: number, midiNote: number): {
66
+ bank: number;
67
+ program: number;
68
+ };
69
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "spessasynth_lib",
3
- "version": "3.21.14",
3
+ "version": "3.22.2",
4
4
  "description": "MIDI and SoundFont2/DLS library with no compromises",
5
5
  "browser": "index.js",
6
6
  "types": "@types/index.d.ts",
@@ -459,7 +459,7 @@ export class Sequencer
459
459
  }
460
460
  else
461
461
  {
462
- throw new Error(messageData);
462
+ throw new Error("Sequencer error: " + messageData);
463
463
  }
464
464
  return;
465
465
 
@@ -49,7 +49,7 @@ export function loadNewSequence(parsedMidi, autoPlay = true)
49
49
  this.stop();
50
50
  if (!parsedMidi.tracks)
51
51
  {
52
- throw "No tracks supplied!";
52
+ throw new Error("This MIDI has no tracks!");
53
53
  }
54
54
 
55
55
  this.oneTickToSeconds = 60 / (120 * parsedMidi.timeDivision);
@@ -225,6 +225,14 @@ class BasicSoundFont
225
225
  }
226
226
  return preset;
227
227
  }
228
+
229
+ /**
230
+ * @param error {string}
231
+ */
232
+ parsingError(error)
233
+ {
234
+ throw new Error(`SF parsing error: ${error} The file may be corrupted.`);
235
+ }
228
236
  }
229
237
 
230
238
  BasicSoundFont.prototype.write = write;
@@ -123,7 +123,7 @@ export function write(options = DEFAULT_WRITE_OPTIONS)
123
123
  smplStartOffsets,
124
124
  smplEndOffsets,
125
125
  options?.compress,
126
- options?.compressionQuality || 0.5,
126
+ options?.compressionQuality ?? 0.5,
127
127
  options.compressionFunction
128
128
  );
129
129
 
@@ -71,7 +71,7 @@ function getSF2SourceFromDLS(source)
71
71
  }
72
72
  if (sourceEnum === undefined)
73
73
  {
74
- throw `not known?? ${source}`;
74
+ throw new Error(`Unknown DLS Source: ${source}`);
75
75
  }
76
76
  return { enum: sourceEnum, isCC: isCC };
77
77
  }
@@ -58,7 +58,7 @@ export class DLSSample extends BasicSample
58
58
  {
59
59
  if (!this.compressedData)
60
60
  {
61
- throw new Error("Compressed but no data??");
61
+ throw new Error("Compressed but no data?? This shouldn't happen!!");
62
62
  }
63
63
  return this.compressedData;
64
64
  }
@@ -25,7 +25,7 @@ class DLSSoundFont extends BasicSoundFont
25
25
  if (!this.dataArray)
26
26
  {
27
27
  SpessaSynthGroupEnd();
28
- throw new TypeError("No data!");
28
+ this.parsingError("No data provided!");
29
29
  }
30
30
 
31
31
  // read the main chunk
@@ -85,7 +85,7 @@ class DLSSoundFont extends BasicSoundFont
85
85
  if (!colhChunk)
86
86
  {
87
87
  SpessaSynthGroupEnd();
88
- throw new Error("No colh chunk!");
88
+ this.parsingError("No colh chunk!");
89
89
  }
90
90
  this.instrumentAmount = readLittleEndian(colhChunk.chunkData, 4);
91
91
  SpessaSynthInfo(
@@ -96,6 +96,11 @@ class DLSSoundFont extends BasicSoundFont
96
96
 
97
97
  // read wave list
98
98
  let waveListChunk = findRIFFListType(chunks, "wvpl");
99
+ if (!waveListChunk)
100
+ {
101
+ SpessaSynthGroupEnd();
102
+ this.parsingError("No wvpl chunk!");
103
+ }
99
104
  this.readDLSSamples(waveListChunk);
100
105
 
101
106
  // read instrument list
@@ -103,7 +108,7 @@ class DLSSoundFont extends BasicSoundFont
103
108
  if (!instrumentListChunk)
104
109
  {
105
110
  SpessaSynthGroupEnd();
106
- throw new Error("No lins chunk!");
111
+ this.parsingError("No lins chunk!");
107
112
  }
108
113
  this.readDLSInstrumentList(instrumentListChunk);
109
114
 
@@ -135,7 +140,7 @@ class DLSSoundFont extends BasicSoundFont
135
140
  if (chunk.header.toLowerCase() !== expected.toLowerCase())
136
141
  {
137
142
  SpessaSynthGroupEnd();
138
- throw new SyntaxError(`Invalid DLS chunk header! Expected "${expected.toLowerCase()}" got "${chunk.header.toLowerCase()}"`);
143
+ this.parsingError(`Invalid DLS chunk header! Expected "${expected.toLowerCase()}" got "${chunk.header.toLowerCase()}"`);
139
144
  }
140
145
  }
141
146
 
@@ -148,9 +153,17 @@ class DLSSoundFont extends BasicSoundFont
148
153
  if (text.toLowerCase() !== expected.toLowerCase())
149
154
  {
150
155
  SpessaSynthGroupEnd();
151
- throw new SyntaxError(`Invalid DLS soundfont! Expected "${expected.toLowerCase()}" got "${text.toLowerCase()}"`);
156
+ this.parsingError(`FourCC error: Expected "${expected.toLowerCase()}" got "${text.toLowerCase()}"`);
152
157
  }
153
158
  }
159
+
160
+ /**
161
+ * @param error {string}
162
+ */
163
+ parsingError(error)
164
+ {
165
+ throw new Error(`DLS parse error: ${error} The file may be corrupted.`);
166
+ }
154
167
  }
155
168
 
156
169
  DLSSoundFont.prototype.readDLSInstrumentList = readDLSInstrumentList;
@@ -85,7 +85,7 @@ export function readDLSInstrument(chunk)
85
85
  if (type !== "rgn " && type !== "rgn2")
86
86
  {
87
87
  SpessaSynthGroupEnd();
88
- throw new SyntaxError(`Invalid DLS region! Expected "rgn " or "rgn2" got "${type}"`);
88
+ this.parsingError(`Invalid DLS region! Expected "rgn " or "rgn2" got "${type}"`);
89
89
  }
90
90
 
91
91
 
@@ -80,7 +80,7 @@ export function readDLSSamples(waveListChunk)
80
80
  const dataChunk = waveChunks.find(c => c.header === "data");
81
81
  if (!dataChunk)
82
82
  {
83
- throw new Error("No data chunk in the wave chunk!");
83
+ this.parsingError("No data chunk in the WAVE chunk!");
84
84
  }
85
85
  const sampleLength = dataChunk.size / bytesPerSample;
86
86
  const sampleData = new Float32Array(sampleLength);
@@ -113,11 +113,18 @@ export class LoadedSample extends BasicSample
113
113
  const buff = smplArr.slice(this.sampleStartIndex / 2 + smplStart, this.sampleEndIndex / 2 + smplStart);
114
114
  // reset array and being decoding
115
115
  this.sampleData = new Float32Array(0);
116
- /**
117
- * @type {{data: Float32Array[], error: (string|null), sampleRate: number, eof: boolean}}
118
- */
119
- const vorbis = stbvorbis.decode(buff.buffer);
120
- this.sampleData = vorbis.data[0];
116
+ try
117
+ {
118
+ /**
119
+ * @type {{data: Float32Array[], error: (string|null), sampleRate: number, eof: boolean}}
120
+ */
121
+ const vorbis = stbvorbis.decode(buff.buffer);
122
+ this.sampleData = vorbis.data[0];
123
+ }
124
+ catch (e)
125
+ {
126
+ throw new Error(`Ogg Vorbis decode error: ${e}`);
127
+ }
121
128
  }
122
129
 
123
130
  /**
@@ -39,7 +39,7 @@ export class SoundFont2 extends BasicSoundFont
39
39
  if (!this.dataArray)
40
40
  {
41
41
  SpessaSynthGroupEnd();
42
- throw new TypeError("No data!");
42
+ this.parsingError("No data provided!");
43
43
  }
44
44
 
45
45
  // read the main read
@@ -280,7 +280,7 @@ export class SoundFont2 extends BasicSoundFont
280
280
  if (chunk.header.toLowerCase() !== expected.toLowerCase())
281
281
  {
282
282
  SpessaSynthGroupEnd();
283
- throw new SyntaxError(`Invalid chunk header! Expected "${expected.toLowerCase()}" got "${chunk.header.toLowerCase()}"`);
283
+ this.parsingError(`Invalid chunk header! Expected "${expected.toLowerCase()}" got "${chunk.header.toLowerCase()}"`);
284
284
  }
285
285
  }
286
286
 
@@ -293,7 +293,7 @@ export class SoundFont2 extends BasicSoundFont
293
293
  if (text.toLowerCase() !== expected.toLowerCase())
294
294
  {
295
295
  SpessaSynthGroupEnd();
296
- throw new SyntaxError(`Invalid soundFont! Expected "${expected.toLowerCase()}" got "${text.toLowerCase()}"`);
296
+ this.parsingError(`Invalid FourCC: Expected "${expected.toLowerCase()}" got "${text.toLowerCase()}"\``);
297
297
  }
298
298
  }
299
299
  }
@@ -0,0 +1,73 @@
1
+ import { workletMessageType } from "./worklet_system/message_protocol/worklet_message.js";
2
+ import { KeyModifier, workletKeyModifierMessageType } from "./worklet_system/worklet_methods/worklet_key_modifier.js";
3
+
4
+ export class KeyModifierManager
5
+ {
6
+ /**
7
+ * @param synth {Synthetizer}
8
+ */
9
+ constructor(synth)
10
+ {
11
+ this.synth = synth;
12
+ }
13
+
14
+ /**
15
+ * @private
16
+ * @param type {workletKeyModifierMessageType}
17
+ * @param data {any}
18
+ */
19
+ _sendToWorklet(type, data)
20
+ {
21
+ this.synth.post({
22
+ messageType: workletMessageType.keyModifierManager,
23
+ messageData: [
24
+ type,
25
+ data
26
+ ]
27
+ });
28
+ }
29
+
30
+ /**
31
+ * Modifies a single key
32
+ * @param channel {number} the channel affected. Usually 0-15
33
+ * @param midiNote {number} the MIDI note to change. 0-127
34
+ * @param options {{
35
+ * velocity: number|undefined,
36
+ * patch: {
37
+ * bank: number,
38
+ * program: number
39
+ * }|undefined
40
+ * }} the key's modifiers
41
+ */
42
+ addModifier(channel, midiNote, options)
43
+ {
44
+ const velocity = options?.velocity || -1;
45
+ const program = options?.patch?.program ?? -1;
46
+ const bank = options?.patch?.bank ?? -1;
47
+ this._sendToWorklet(
48
+ workletKeyModifierMessageType.addMapping,
49
+ [channel, midiNote, new KeyModifier(velocity, bank, program)]
50
+ );
51
+ }
52
+
53
+ /**
54
+ * Deletes a key modifier
55
+ * @param channel {number} the channel affected. Usually 0-15
56
+ * @param midiNote {number} the MIDI note to change. 0-127
57
+ */
58
+ deleteModifier(channel, midiNote)
59
+ {
60
+ this._sendToWorklet(
61
+ workletKeyModifierMessageType.deleteMapping,
62
+ [channel, midiNote]
63
+ );
64
+ }
65
+
66
+ /**
67
+ * Clears ALL Modifiers
68
+ */
69
+ clearModifiers()
70
+ {
71
+ this._sendToWorklet(workletKeyModifierMessageType.clearMappings, undefined);
72
+ }
73
+ }
@@ -14,6 +14,7 @@ import { SpessaSynthInfo, SpessaSynthWarn } from "../utils/loggin.js";
14
14
  import { DEFAULT_EFFECTS_CONFIG } from "./audio_effects/effects_config.js";
15
15
  import { SoundfontManager } from "./synth_soundfont_manager.js";
16
16
  import { channelConfiguration } from "./worklet_system/worklet_utilities/worklet_processor_channel.js";
17
+ import { KeyModifierManager } from "./key_modifier_manager.js";
17
18
 
18
19
 
19
20
  /**
@@ -183,6 +184,12 @@ export class Synthetizer
183
184
  */
184
185
  this.soundfontManager = new SoundfontManager(this);
185
186
 
187
+ /**
188
+ * The synth's key modifier manager
189
+ * @type {KeyModifierManager}
190
+ */
191
+ this.keyModifierManager = new KeyModifierManager(this);
192
+
186
193
  /**
187
194
  * @type {function(SynthesizerSnapshot)}
188
195
  * @private