spessasynth_lib 3.24.16 → 3.24.22

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 (41) hide show
  1. package/midi_parser/basic_midi.js +2 -0
  2. package/midi_parser/midi_message.js +1 -1
  3. package/package.json +1 -1
  4. package/sequencer/sequencer.js +35 -2
  5. package/sequencer/worklet_sequencer/events.js +20 -7
  6. package/sequencer/worklet_sequencer/process_event.js +2 -6
  7. package/sequencer/worklet_sequencer/sequencer_message.js +8 -1
  8. package/sequencer/worklet_sequencer/song_control.js +5 -4
  9. package/sequencer/worklet_sequencer/worklet_sequencer.js +147 -86
  10. package/soundfont/basic_soundfont/basic_preset.js +4 -2
  11. package/soundfont/basic_soundfont/basic_sample.js +2 -2
  12. package/soundfont/basic_soundfont/modulator.js +19 -4
  13. package/soundfont/dls/articulator_converter.js +14 -5
  14. package/soundfont/dls/dls_zone.js +1 -1
  15. package/soundfont/dls/read_articulation.js +3 -3
  16. package/soundfont/dls/read_region.js +21 -9
  17. package/soundfont/read_sf2/samples.js +9 -5
  18. package/synthetizer/synth_soundfont_manager.js +2 -2
  19. package/synthetizer/synthetizer.js +86 -55
  20. package/synthetizer/worklet_processor.min.js +11 -11
  21. package/synthetizer/worklet_system/main_processor.js +189 -96
  22. package/synthetizer/worklet_system/message_protocol/handle_message.js +2 -1
  23. package/synthetizer/worklet_system/message_protocol/message_sending.js +5 -4
  24. package/synthetizer/worklet_system/snapshot/channel_snapshot.js +1 -0
  25. package/synthetizer/worklet_system/worklet_methods/controller_control/controller_change.js +4 -4
  26. package/synthetizer/worklet_system/worklet_methods/controller_control/reset_controllers.js +2 -2
  27. package/synthetizer/worklet_system/worklet_methods/note_on.js +9 -4
  28. package/synthetizer/worklet_system/worklet_methods/render_voice.js +26 -22
  29. package/synthetizer/worklet_system/worklet_methods/soundfont_management/clear_sound_font.js +1 -5
  30. package/synthetizer/worklet_system/worklet_methods/tuning_control/set_master_tuning.js +1 -1
  31. package/synthetizer/worklet_system/worklet_methods/tuning_control/set_modulation_depth.js +1 -1
  32. package/synthetizer/worklet_system/worklet_methods/tuning_control/set_octave_tuning.js +5 -1
  33. package/synthetizer/worklet_system/worklet_methods/tuning_control/set_tuning.js +1 -1
  34. package/synthetizer/worklet_system/worklet_methods/tuning_control/set_tuning_semitones.js +1 -1
  35. package/synthetizer/worklet_system/worklet_methods/tuning_control/transpose_channel.js +5 -2
  36. package/synthetizer/worklet_system/worklet_utilities/controller_tables.js +11 -2
  37. package/synthetizer/worklet_system/worklet_utilities/lowpass_filter.js +90 -59
  38. package/synthetizer/worklet_system/worklet_utilities/stereo_panner.js +13 -13
  39. package/synthetizer/worklet_system/worklet_utilities/worklet_modulator.js +2 -2
  40. package/synthetizer/worklet_system/worklet_utilities/worklet_processor_channel.js +35 -20
  41. package/synthetizer/worklet_system/worklet_utilities/worklet_voice.js +20 -16
@@ -96,6 +96,8 @@ export class BasicMIDI extends MIDISequenceData
96
96
  this.midiPorts = [];
97
97
  this.midiPortChannelOffsets = [];
98
98
 
99
+ this.keyRange = { max: 0, min: 127 };
100
+
99
101
  /**
100
102
  * Will be joined with "\n" to form the final string
101
103
  * @type {string[]}
@@ -204,7 +204,7 @@ export const midiControllers = {
204
204
  legatoFootswitch: 68,
205
205
  hold2Pedal: 69,
206
206
  soundVariation: 70,
207
- timbreHarmonicContent: 71,
207
+ filterResonance: 71,
208
208
  releaseTime: 72,
209
209
  attackTime: 73,
210
210
  brightness: 74,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "spessasynth_lib",
3
- "version": "3.24.16",
3
+ "version": "3.24.22",
4
4
  "description": "MIDI and SoundFont2/DLS library with no compromises",
5
5
  "browser": "index.js",
6
6
  "type": "module",
@@ -3,6 +3,7 @@ import { Synthetizer } from "../synthetizer/synthetizer.js";
3
3
  import { messageTypes } from "../midi_parser/midi_message.js";
4
4
  import { workletMessageType } from "../synthetizer/worklet_system/message_protocol/worklet_message.js";
5
5
  import {
6
+ SongChangeType,
6
7
  WorkletSequencerMessageType,
7
8
  WorkletSequencerReturnMessageType
8
9
  } from "./worklet_sequencer/sequencer_message.js";
@@ -261,6 +262,38 @@ export class Sequencer
261
262
  this._playbackRate = value;
262
263
  }
263
264
 
265
+ /**
266
+ * @type {boolean}
267
+ * @private
268
+ */
269
+ _shuffleSongs = false;
270
+
271
+ /**
272
+ * Indicates if the song order is random
273
+ * @returns {boolean}
274
+ */
275
+ get shuffleSongs()
276
+ {
277
+ return this._shuffleSongs;
278
+ }
279
+
280
+ /**
281
+ * Indicates if the song order is random
282
+ * @param value {boolean}
283
+ */
284
+ set shuffleSongs(value)
285
+ {
286
+ this._shuffleSongs = value;
287
+ if (value)
288
+ {
289
+ this._sendMessage(WorkletSequencerMessageType.changeSong, SongChangeType.shuffleOn);
290
+ }
291
+ else
292
+ {
293
+ this._sendMessage(WorkletSequencerMessageType.changeSong, SongChangeType.shuffleOff);
294
+ }
295
+ }
296
+
264
297
  /**
265
298
  * Indicates if the sequencer should skip to first note on
266
299
  * @return {boolean}
@@ -446,12 +479,12 @@ export class Sequencer
446
479
 
447
480
  nextSong()
448
481
  {
449
- this._sendMessage(WorkletSequencerMessageType.changeSong, true);
482
+ this._sendMessage(WorkletSequencerMessageType.changeSong, SongChangeType.forwards);
450
483
  }
451
484
 
452
485
  previousSong()
453
486
  {
454
- this._sendMessage(WorkletSequencerMessageType.changeSong, false);
487
+ this._sendMessage(WorkletSequencerMessageType.changeSong, SongChangeType.backwards);
455
488
  }
456
489
 
457
490
  /**
@@ -2,7 +2,7 @@ import {
2
2
  ALL_CHANNELS_OR_DIFFERENT_ACTION,
3
3
  returnMessageType
4
4
  } from "../../synthetizer/worklet_system/message_protocol/worklet_message.js";
5
- import { WorkletSequencerMessageType, WorkletSequencerReturnMessageType } from "./sequencer_message.js";
5
+ import { SongChangeType, WorkletSequencerMessageType, WorkletSequencerReturnMessageType } from "./sequencer_message.js";
6
6
  import { messageTypes, midiControllers } from "../../midi_parser/midi_message.js";
7
7
  import { MIDI_CHANNEL_COUNT } from "../../synthetizer/synthetizer.js";
8
8
 
@@ -60,13 +60,26 @@ export function processMessage(messageType, messageData)
60
60
  break;
61
61
 
62
62
  case WorkletSequencerMessageType.changeSong:
63
- if (messageData)
63
+ switch (messageData)
64
64
  {
65
- this.nextSong();
66
- }
67
- else
68
- {
69
- this.previousSong();
65
+ case SongChangeType.forwards:
66
+ this.nextSong();
67
+ break;
68
+
69
+ case SongChangeType.backwards:
70
+ this.previousSong();
71
+ break;
72
+
73
+ case SongChangeType.shuffleOff:
74
+ this.shuffleMode = false;
75
+ this.songIndex = this.shuffledSongIndexes[this.songIndex];
76
+ break;
77
+
78
+ case SongChangeType.shuffleOn:
79
+ this.shuffleMode = true;
80
+ this.shuffleSongIndexes();
81
+ this.songIndex = 0;
82
+ this.loadCurrentSong();
70
83
  }
71
84
  break;
72
85
 
@@ -13,10 +13,6 @@ import { readBytesAsUintBigEndian } from "../../utils/byte_functions/big_endian.
13
13
  */
14
14
  export function _processEvent(event, trackIndex)
15
15
  {
16
- if (this.ignoreEvents)
17
- {
18
- return;
19
- }
20
16
  if (this.sendMIDIMessages)
21
17
  {
22
18
  if (event.messageStatusByte >= 0x80)
@@ -100,7 +96,7 @@ export function _processEvent(event, trackIndex)
100
96
  }
101
97
  break;
102
98
 
103
- // recongized but ignored
99
+ // recognized but ignored
104
100
  case messageTypes.timeSignature:
105
101
  case messageTypes.endOfTrack:
106
102
  case messageTypes.midiChannelPrefix:
@@ -123,7 +119,7 @@ export function _processEvent(event, trackIndex)
123
119
  if (statusByteData.status === messageTypes.lyric)
124
120
  {
125
121
  lyricsIndex = Math.min(
126
- this.midiData.lyricsTicks.indexOf(event.ticks) + 1,
122
+ this.midiData.lyricsTicks.indexOf(event.ticks),
127
123
  this.midiData.lyrics.length - 1
128
124
  );
129
125
  }
@@ -1,3 +1,10 @@
1
+ export const SongChangeType = {
2
+ backwards: 0,
3
+ forwards: 1,
4
+ shuffleOn: 2,
5
+ shuffleOff: 3
6
+ };
7
+
1
8
  /**
2
9
  * @enum {number}
3
10
  * @property {number} loadNewSongList - 0 -> [...song<MIDI>]
@@ -8,7 +15,7 @@
8
15
  * @property {number} changeMIDIMessageSending - 5 -> sendMIDIMessages<boolean>
9
16
  * @property {number} setPlaybackRate - 6 -> playbackRate<number>
10
17
  * @property {number} setLoop - 7 -> [loop<boolean>, count<number]
11
- * @property {number} changeSong - 8 -> goForwards<boolean> if true, next song, if false, previous
18
+ * @property {number} changeSong - 8 -> changeType<number> 0 - back, 1 - forward, 2 - shuffle ON, 3 - shuffle OFF
12
19
  * @property {number} getMIDI - 9 -> (no data)
13
20
  * @property {number} setSkipToFirstNote -10 -> skipToFirstNoteOn<boolean>
14
21
  * @property {number} setPreservePlaybackState -11 -> preservePlaybackState<boolean>
@@ -120,7 +120,7 @@ export function loadNewSequence(parsedMidi, autoPlay = true)
120
120
  });
121
121
 
122
122
  /**
123
- * Same as Audio.duration (seconds)
123
+ * Same as "audio.duration" property (seconds)
124
124
  * @type {number}
125
125
  */
126
126
  this.duration = this.midiData.duration;
@@ -188,7 +188,8 @@ export function loadNewSongList(midiBuffers, autoPlay = true)
188
188
  {
189
189
  this.loop = false;
190
190
  }
191
- this.loadNewSequence(this.songs[this.songIndex], autoPlay);
191
+ this.shuffleSongIndexes();
192
+ this.loadCurrentSong(autoPlay);
192
193
  }
193
194
 
194
195
  /**
@@ -203,7 +204,7 @@ export function nextSong()
203
204
  }
204
205
  this.songIndex++;
205
206
  this.songIndex %= this.songs.length;
206
- this.loadNewSequence(this.songs[this.songIndex]);
207
+ this.loadCurrentSong();
207
208
  }
208
209
 
209
210
  /**
@@ -221,5 +222,5 @@ export function previousSong()
221
222
  {
222
223
  this.songIndex = this.songs.length - 1;
223
224
  }
224
- this.loadNewSequence(this.songs[this.songIndex]);
225
+ this.loadCurrentSong();
225
226
  }
@@ -18,100 +18,139 @@ import { MIDI_CHANNEL_COUNT } from "../../synthetizer/synthetizer.js";
18
18
 
19
19
  class WorkletSequencer
20
20
  {
21
+ /**
22
+ * All the sequencer's songs
23
+ * @type {BasicMIDI[]}
24
+ */
25
+ songs = [];
26
+
27
+ /**
28
+ * Current song index
29
+ * @type {number}
30
+ */
31
+ songIndex = 0;
32
+
33
+ /**
34
+ * shuffled song indexes
35
+ * @type {number[]}
36
+ */
37
+ shuffledSongIndexes = [];
38
+
39
+ /**
40
+ * the synth to use
41
+ * @type {SpessaSynthProcessor}
42
+ */
43
+ synth;
44
+
45
+ /**
46
+ * if the sequencer is active
47
+ * @type {boolean}
48
+ */
49
+ isActive = false;
50
+
51
+ /**
52
+ * If the event should instead be sent back to the main thread instead of synth
53
+ * @type {boolean}
54
+ */
55
+ sendMIDIMessages = false;
56
+
57
+ /**
58
+ * sequencer's loop count
59
+ * @type {number}
60
+ */
61
+ loopCount = Infinity;
62
+
63
+ /**
64
+ * event's number in this.events
65
+ * @type {number[]}
66
+ */
67
+ eventIndex = [];
68
+
69
+ /**
70
+ * tracks the time that has already been played
71
+ * @type {number}
72
+ */
73
+ playedTime = 0;
74
+
75
+ /**
76
+ * The (relative) time when the sequencer was paused. If it's not paused, then it's undefined.
77
+ * @type {number}
78
+ */
79
+ pausedTime = undefined;
80
+
81
+ /**
82
+ * Absolute playback startTime, bases on the synth's time
83
+ * @type {number}
84
+ */
85
+ absoluteStartTime = currentTime;
86
+ /**
87
+ * Currently playing notes (for pausing and resuming)
88
+ * @type {{
89
+ * midiNote: number,
90
+ * channel: number,
91
+ * velocity: number
92
+ * }[]}
93
+ */
94
+ playingNotes = [];
95
+
96
+ /**
97
+ * controls if the sequencer loops (defaults to true)
98
+ * @type {boolean}
99
+ */
100
+ loop = true;
101
+
102
+ /**
103
+ * controls if the songs are ordered randomly
104
+ * @type {boolean}
105
+ */
106
+ shuffleMode = false;
107
+
108
+ /**
109
+ * the current track data
110
+ * @type {BasicMIDI}
111
+ */
112
+ midiData = undefined;
113
+
114
+ /**
115
+ * midi port number for the corresponding track
116
+ * @type {number[]}
117
+ */
118
+ midiPorts = [];
119
+ midiPortChannelOffset = 0;
120
+ /**
121
+ * stored as:
122
+ * Object<midi port, channel offset>
123
+ * @type {Object<number, number>}
124
+ */
125
+ midiPortChannelOffsets = {};
126
+
127
+ /**
128
+ * @type {boolean}
129
+ * @private
130
+ */
131
+ _skipToFirstNoteOn = true;
132
+
133
+ /**
134
+ * If true, seq will stay paused when seeking or changing the playback rate
135
+ * @type {boolean}
136
+ */
137
+ preservePlaybackState = false;
138
+
21
139
  /**
22
140
  * @param spessasynthProcessor {SpessaSynthProcessor}
23
141
  */
24
142
  constructor(spessasynthProcessor)
25
143
  {
26
144
  this.synth = spessasynthProcessor;
27
- this.ignoreEvents = false;
28
- this.isActive = false;
29
-
30
- /**
31
- * If the event should instead be sent back to the main thread instead of synth
32
- * @type {boolean}
33
- */
34
- this.sendMIDIMessages = false;
35
-
36
- this.loopCount = Infinity;
37
-
38
- // event's number in this.events
39
- /**
40
- * @type {number[]}
41
- */
42
- this.eventIndex = [];
43
- this.songIndex = 0;
44
-
45
- // tracks the time that we have already played
46
- /**
47
- * @type {number}
48
- */
49
- this.playedTime = 0;
50
-
51
- /**
52
- * The (relative) time when the sequencer was paused. If it's not paused then it's undefined.
53
- * @type {number}
54
- */
55
- this.pausedTime = undefined;
56
-
57
- /**
58
- * Absolute playback startTime, bases on the synth's time
59
- * @type {number}
60
- */
61
- this.absoluteStartTime = currentTime;
62
-
63
- /**
64
- * Controls the playback's rate
65
- * @type {number}
66
- */
67
- this._playbackRate = 1;
68
-
69
- /**
70
- * Currently playing notes (for pausing and resuming)
71
- * @type {{
72
- * midiNote: number,
73
- * channel: number,
74
- * velocity: number
75
- * }[]}
76
- */
77
- this.playingNotes = [];
78
-
79
- // controls if the sequencer loops (defaults to true)
80
- this.loop = true;
81
-
82
- /**
83
- * the current track data
84
- * @type {BasicMIDI}
85
- */
86
- this.midiData = undefined;
87
-
88
- /**
89
- * midi port number for the corresponding track
90
- * @type {number[]}
91
- */
92
- this.midiPorts = [];
93
-
94
- this.midiPortChannelOffset = 0;
95
-
96
- /**
97
- * midi port: channel offset
98
- * @type {Object<number, number>}
99
- */
100
- this.midiPortChannelOffsets = {};
101
-
102
- /**
103
- * @type {boolean}
104
- * @private
105
- */
106
- this._skipToFirstNoteOn = true;
107
-
108
- /**
109
- * If true, seq will stay paused when seeking or changing the playback rate
110
- * @type {boolean}
111
- */
112
- this.preservePlaybackState = false;
113
145
  }
114
146
 
147
+ /**
148
+ * Controls the playback's rate
149
+ * @type {number}
150
+ * @private
151
+ */
152
+ _playbackRate = 1;
153
+
115
154
  /**
116
155
  * @param value {number}
117
156
  */
@@ -229,6 +268,16 @@ class WorkletSequencer
229
268
  }
230
269
  }
231
270
 
271
+ loadCurrentSong(autoPlay = true)
272
+ {
273
+ let index = this.songIndex;
274
+ if (this.shuffleMode)
275
+ {
276
+ index = this.shuffledSongIndexes[this.songIndex];
277
+ }
278
+ this.loadNewSequence(this.songs[index], autoPlay);
279
+ }
280
+
232
281
  _resetTimers()
233
282
  {
234
283
  this.playedTime = 0;
@@ -244,6 +293,18 @@ class WorkletSequencer
244
293
  {
245
294
  this.isActive = false;
246
295
  }
296
+
297
+ shuffleSongIndexes()
298
+ {
299
+ const indexes = this.songs.map((_, i) => i);
300
+ this.shuffledSongIndexes = [];
301
+ while (indexes.length > 0)
302
+ {
303
+ const index = indexes[Math.floor(Math.random() * indexes.length)];
304
+ this.shuffledSongIndexes.push(index);
305
+ indexes.splice(indexes.indexOf(index), 1);
306
+ }
307
+ }
247
308
  }
248
309
 
249
310
  // Web MIDI sending
@@ -93,6 +93,7 @@ export class BasicPreset
93
93
  this.presetZones.splice(index, 1);
94
94
  }
95
95
 
96
+ // noinspection JSUnusedGlobalSymbols
96
97
  /**
97
98
  * Preloads all samples (async)
98
99
  */
@@ -210,7 +211,7 @@ export class BasicPreset
210
211
 
211
212
  presetZonesInRange.forEach(zone =>
212
213
  {
213
- // global zone is already taken into account earlier
214
+ // the global zone is already taken into account earlier
214
215
  if (zone.instrument.instrumentZones.length < 1)
215
216
  {
216
217
  return;
@@ -288,7 +289,8 @@ export class BasicPreset
288
289
  m => Modulator.isIdentical(mod, m));
289
290
  if (identicalInstrumentModulator !== -1)
290
291
  {
291
- // sum the amounts (this makes a new modulator because otherwise it would overwrite the one in the soundfont!!!
292
+ // sum the amounts
293
+ // (this makes a new modulator because otherwise it would overwrite the one in the soundfont!
292
294
  finalModulatorList[identicalInstrumentModulator] = finalModulatorList[identicalInstrumentModulator].sumTransform(
293
295
  mod);
294
296
  }
@@ -63,12 +63,12 @@ export class BasicSample
63
63
  */
64
64
  this.sampleType = sampleType;
65
65
  /**
66
- * Relative to start of the sample in sample points
66
+ * Relative to the start of the sample in sample points
67
67
  * @type {number}
68
68
  */
69
69
  this.sampleLoopStartIndex = loopStart;
70
70
  /**
71
- * Relative to start of the sample in sample points
71
+ * Relative to the start of the sample in sample points
72
72
  * @type {number}
73
73
  */
74
74
  this.sampleLoopEndIndex = loopEnd;
@@ -121,7 +121,7 @@ export class Modulator
121
121
  }
122
122
 
123
123
  /**
124
- * Sums transform and creates a NEW modulator
124
+ * Sum transform and create a NEW modulator
125
125
  * @param modulator {Modulator}
126
126
  * @returns {Modulator}
127
127
  */
@@ -279,6 +279,21 @@ const customModulators = [
279
279
  transform: 0
280
280
  }),
281
281
 
282
+ // cc 73 (attack time) to volEnv attack
283
+ new Modulator({
284
+ srcEnum: getModSourceEnum(
285
+ modulatorCurveTypes.convex,
286
+ 1,
287
+ 0,
288
+ 1,
289
+ midiControllers.attackTime
290
+ ), // linear forward bipolar cc 72
291
+ dest: generatorTypes.attackVolEnv,
292
+ amt: 6000,
293
+ secSrcEnum: 0x0, // no controller
294
+ transform: 0
295
+ }),
296
+
282
297
  // cc 72 (release time) to volEnv release
283
298
  new Modulator({
284
299
  srcEnum: getModSourceEnum(
@@ -289,7 +304,7 @@ const customModulators = [
289
304
  midiControllers.releaseTime
290
305
  ), // linear forward bipolar cc 72
291
306
  dest: generatorTypes.releaseVolEnv,
292
- amt: 1200,
307
+ amt: 3600,
293
308
  secSrcEnum: 0x0, // no controller
294
309
  transform: 0
295
310
  }),
@@ -309,14 +324,14 @@ const customModulators = [
309
324
  transform: 0
310
325
  }),
311
326
 
312
- // cc 71 (filter q) to filterq
327
+ // cc 71 (filter Q) to filter Q
313
328
  new Modulator({
314
329
  srcEnum: getModSourceEnum(
315
330
  modulatorCurveTypes.linear,
316
331
  1,
317
332
  0,
318
333
  1,
319
- midiControllers.timbreHarmonicContent
334
+ midiControllers.filterResonance
320
335
  ), // linear forwards bipolar cc 74
321
336
  dest: generatorTypes.initialFilterQ,
322
337
  amt: 250,
@@ -136,7 +136,7 @@ function getSF2GeneratorFromDLS(destination, amount)
136
136
  case DLSDestinations.reverbSend:
137
137
  return generatorTypes.reverbEffectsSend;
138
138
 
139
- // lfos
139
+ // lfo
140
140
  case DLSDestinations.modLfoFreq:
141
141
  return generatorTypes.freqModLFO;
142
142
  case DLSDestinations.modLfoDelay:
@@ -319,8 +319,9 @@ export function getSF2ModulatorFromArticulator(
319
319
  if (isSourceNoController)
320
320
  {
321
321
  // we force it into this state because before it was some strange value,
322
- // like vibrato lfo bipolar for example
323
- // since we turn it into NoController -> vibLfoToPitch the result is the same and bipolar concontroller is technically 0
322
+ // like vibrato lfo bipolar, for example,
323
+ // since we turn it into NoController -> vibLfoToPitch,
324
+ // the result is the same and bipolar controller is technically 0
324
325
  sourceEnumFinal = 0x0;
325
326
  }
326
327
  else
@@ -328,7 +329,7 @@ export function getSF2ModulatorFromArticulator(
328
329
  // output transform is ignored as it's not a thing in sfont format
329
330
  // unless the curve type of source is linear, then output is copied
330
331
  const outputTransform = transform & 0b1111;
331
- // source curve type maps to desfont curve type in section 2.10, table 9
332
+ // source curve type maps to a desfont curve type in section 2.10, table 9
332
333
  let sourceTransform = (transform >> 10) & 0b1111;
333
334
  if (sourceTransform === modulatorCurveTypes.linear && outputTransform !== modulatorCurveTypes.linear)
334
335
  {
@@ -341,7 +342,7 @@ export function getSF2ModulatorFromArticulator(
341
342
  {
342
343
  // if the value is negative, the source shall be negative!
343
344
  // why?
344
- // Idk, it makes it work with ROCK.RMI and NOKIA_S30.dls
345
+ // IDK, it makes it work with ROCK.RMI and NOKIA_S30.dls
345
346
  if (value < 0)
346
347
  {
347
348
  sourceIsNegative = 1;
@@ -356,6 +357,14 @@ export function getSF2ModulatorFromArticulator(
356
357
  );
357
358
  }
358
359
 
360
+ // a corrupted rendition of gm.dls was found under
361
+ // https://sembiance.com/fileFormatSamples/audio/downloadableSoundBank/
362
+ // which specifies a whopping -32,768 decibels of attenuation
363
+ if (destinationGenerator === generatorTypes.initialAttenuation)
364
+ {
365
+ newValue = Math.max(960, Math.min(0, newValue));
366
+ }
367
+
359
368
  const secSourceTransform = (transform >> 4) & 0b1111;
360
369
  const secSourceIsBipolar = (transform >> 8) & 1;
361
370
  const secSourceIsNegative = transform >> 9 & 1;
@@ -82,7 +82,7 @@ export class DLSZone extends BasicInstrumentZone
82
82
  }
83
83
  }
84
84
  }
85
- // correct key if needed
85
+ // correct the key if needed
86
86
  if (sampleKey !== sample.samplePitch)
87
87
  {
88
88
  this.generators.push(new Generator(generatorTypes.overridingRootKey, sampleKey));
@@ -76,7 +76,7 @@ export function readArticulation(chunk, disableVibrato)
76
76
  generator = new Generator(generatorTypes.delayVibLFO, value);
77
77
  break;
78
78
 
79
- // vol env: all times are timecents like sf2
79
+ // vol. env: all times are timecents like sf2
80
80
  case DLSDestinations.volEnvDelay:
81
81
  generator = new Generator(generatorTypes.delayVolEnv, value);
82
82
  break;
@@ -144,7 +144,7 @@ export function readArticulation(chunk, disableVibrato)
144
144
  }
145
145
  }
146
146
  else
147
- // if not, modulator??
147
+ // if not, modulator?
148
148
  {
149
149
  let isGenerator = true;
150
150
  // a few special cases which are generators:
@@ -190,7 +190,7 @@ export function readArticulation(chunk, disableVibrato)
190
190
  if (source === DLSSources.keyNum && destination === DLSDestinations.pitch)
191
191
  {
192
192
  // this is just a soundfont generator, but the amount must be changed
193
- // 12800 means the regular scale (100)
193
+ // 12,800 means the regular scale (100)
194
194
  generators.push(new Generator(generatorTypes.scaleTuning, value / 128));
195
195
  }
196
196
  else