spessasynth_lib 3.25.0 → 3.25.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/midi_parser/midi_editor.js +50 -44
  2. package/midi_parser/rmidi_writer.js +90 -45
  3. package/midi_parser/used_keys_loaded.js +59 -39
  4. package/package.json +1 -1
  5. package/sequencer/sequencer.js +0 -1
  6. package/soundfont/basic_soundfont/basic_preset.js +12 -0
  7. package/soundfont/basic_soundfont/basic_soundfont.js +25 -15
  8. package/soundfont/dls/dls_preset.js +11 -1
  9. package/synthetizer/synthetizer.js +48 -1
  10. package/synthetizer/worklet_processor.min.js +12 -12
  11. package/synthetizer/worklet_system/main_processor.js +46 -4
  12. package/synthetizer/worklet_system/message_protocol/handle_message.js +4 -1
  13. package/synthetizer/worklet_system/message_protocol/worklet_message.js +5 -6
  14. package/synthetizer/worklet_system/snapshot/channel_snapshot.js +18 -3
  15. package/synthetizer/worklet_system/snapshot/synthesizer_snapshot.js +1 -1
  16. package/synthetizer/worklet_system/worklet_methods/controller_control/controller_change.js +64 -135
  17. package/synthetizer/worklet_system/worklet_methods/controller_control/reset_controllers.js +8 -4
  18. package/synthetizer/worklet_system/worklet_methods/program_change.js +10 -5
  19. package/synthetizer/worklet_system/worklet_methods/soundfont_management/clear_sound_font.js +2 -3
  20. package/synthetizer/worklet_system/worklet_methods/soundfont_management/get_preset.js +2 -2
  21. package/synthetizer/worklet_system/worklet_methods/soundfont_management/reload_sound_font.js +1 -2
  22. package/synthetizer/worklet_system/worklet_methods/system_exclusive.js +6 -6
  23. package/synthetizer/worklet_system/worklet_methods/worklet_soundfont_manager/worklet_soundfont_manager.js +20 -5
  24. package/synthetizer/worklet_system/worklet_utilities/worklet_processor_channel.js +70 -14
  25. package/synthetizer/worklet_system/worklet_utilities/worklet_voice.js +2 -3
  26. package/utils/sysex_detector.js +46 -0
  27. package/utils/xg_hacks.js +176 -0
@@ -2,7 +2,11 @@ import { WorkletSequencer } from "../../sequencer/worklet_sequencer/worklet_sequ
2
2
  import { SpessaSynthInfo } from "../../utils/loggin.js";
3
3
  import { consoleColors } from "../../utils/other.js";
4
4
  import { voiceKilling } from "./worklet_methods/stopping_notes/voice_killing.js";
5
- import { ALL_CHANNELS_OR_DIFFERENT_ACTION, returnMessageType } from "./message_protocol/worklet_message.js";
5
+ import {
6
+ ALL_CHANNELS_OR_DIFFERENT_ACTION,
7
+ masterParameterType,
8
+ returnMessageType
9
+ } from "./message_protocol/worklet_message.js";
6
10
  import { stbvorbis } from "../../externals/stbvorbis_sync/stbvorbis_sync.min.js";
7
11
  import { VOLUME_ENVELOPE_SMOOTHING_FACTOR } from "./worklet_utilities/volume_envelope.js";
8
12
  import { handleMessage } from "./message_protocol/handle_message.js";
@@ -181,13 +185,28 @@ class SpessaSynthProcessor extends AudioWorkletProcessor
181
185
  * @type {SynthSystem}
182
186
  */
183
187
  system = DEFAULT_SYNTH_MODE;
184
-
185
188
  /**
186
189
  * Current total voices amount
187
190
  * @type {number}
188
191
  */
189
192
  totalVoicesAmount = 0;
190
193
 
194
+ /**
195
+ * Synth's default (reset) preset
196
+ * @type {BasicPreset}
197
+ */
198
+ defaultPreset;
199
+
200
+ defaultPresetUsesOverride = false;
201
+
202
+ /**
203
+ * Synth's default (reset) drum preset
204
+ * @type {BasicPreset}
205
+ */
206
+ drumPreset;
207
+
208
+ defaultDrumsUsesOverride = false;
209
+
191
210
  /**
192
211
  * Creates a new worklet synthesis system. contains all channels
193
212
  * @param options {{
@@ -232,8 +251,7 @@ class SpessaSynthProcessor extends AudioWorkletProcessor
232
251
  }
233
252
  this.sendPresetList();
234
253
 
235
- this.defaultPreset = this.getPreset(0, 0);
236
- this.drumPreset = this.getPreset(128, 0);
254
+ this.getDefaultPresets();
237
255
 
238
256
 
239
257
  for (let i = 0; i < options.processorOptions.midiChannels; i++)
@@ -300,6 +318,30 @@ class SpessaSynthProcessor extends AudioWorkletProcessor
300
318
  return this.masterGain * this.midiVolume;
301
319
  }
302
320
 
321
+ getDefaultPresets()
322
+ {
323
+ // override this to XG, to set the default preset to NOT be XG drums!
324
+ const sys = this.system;
325
+ this.system = "xg";
326
+ this.defaultPreset = this.getPreset(0, 0);
327
+ this.defaultPresetUsesOverride = this.overrideSoundfont?.presets?.indexOf(this.defaultPreset) >= 0;
328
+ this.system = sys;
329
+ this.drumPreset = this.getPreset(128, 0);
330
+ this.defaultDrumsUsesOverride = this.overrideSoundfont?.presets?.indexOf(this.drumPreset) >= 0;
331
+ }
332
+
333
+ /**
334
+ * @param value {SynthSystem}
335
+ */
336
+ setSystem(value)
337
+ {
338
+ this.system = value;
339
+ this.post({
340
+ messageType: returnMessageType.masterParameterChange,
341
+ messageData: [masterParameterType.midiSystem, this.system]
342
+ });
343
+ }
344
+
303
345
  /**
304
346
  * @param bank {number}
305
347
  * @param program {number}
@@ -158,6 +158,9 @@ export function handleMessage(message)
158
158
  case masterParameterType.interpolationType:
159
159
  this.interpolationType = value;
160
160
  break;
161
+
162
+ case masterParameterType.midiSystem:
163
+ this.setSystem(value);
161
164
  }
162
165
  break;
163
166
 
@@ -183,7 +186,7 @@ export function handleMessage(message)
183
186
  case workletMessageType.lockController:
184
187
  if (data[0] === ALL_CHANNELS_OR_DIFFERENT_ACTION)
185
188
  {
186
- channelObject.lockPreset = data[1];
189
+ channelObject.setPresetLock(data[1]);
187
190
  }
188
191
  else
189
192
  {
@@ -70,7 +70,8 @@ export const masterParameterType = {
70
70
  mainVolume: 0,
71
71
  masterPan: 1,
72
72
  voicesCap: 2,
73
- interpolationType: 3
73
+ interpolationType: 3,
74
+ midiSystem: 4
74
75
  };
75
76
 
76
77
 
@@ -110,12 +111,11 @@ export const ALL_CHANNELS_OR_DIFFERENT_ACTION = -1;
110
111
  *
111
112
  * 0 - channel properties -> [...<ChannelProperty>] see message_sending.js line 29
112
113
  * 1 - event call -> {eventName<string>, eventData:<the event's data>}
113
- * 2 - reported current time -> currentTime<number>
114
+ * 2 - master parameter change -> [parameter<masterParameterType>, value<string|number>]
114
115
  * 3 - sequencer specific -> [messageType<WorkletSequencerReturnMessageType> messageData<any>] note: refer to sequencer_message.js
115
116
  * 4 - synthesizer snapshot -> snapshot<SynthesizerSnapshot> note: refer to synthesizer_snapshot.js
116
117
  * 5 - ready -> (no data)
117
118
  * 6 - soundfontError -> errorMessage<string>
118
- * 7 - idenfity -> version<string>
119
119
  */
120
120
 
121
121
  /**
@@ -124,10 +124,9 @@ export const ALL_CHANNELS_OR_DIFFERENT_ACTION = -1;
124
124
  export const returnMessageType = {
125
125
  channelProperties: 0,
126
126
  eventCall: 1,
127
- reportedCurrentTime: 2,
127
+ masterParameterChange: 2,
128
128
  sequencerSpecific: 3,
129
129
  synthesizerSnapshot: 4,
130
130
  ready: 5,
131
- soundfontError: 6,
132
- identify: 7
131
+ soundfontError: 6
133
132
  };
@@ -15,6 +15,12 @@ export class ChannelSnapshot
15
15
  */
16
16
  bank;
17
17
 
18
+ /**
19
+ * If the bank is LSB. For restoring.
20
+ * @type {boolean}
21
+ */
22
+ isBankLSB;
23
+
18
24
  /**
19
25
  * The name of the patch currently loaded in the channel.
20
26
  * @type {string}
@@ -27,6 +33,12 @@ export class ChannelSnapshot
27
33
  */
28
34
  lockPreset;
29
35
 
36
+ /**
37
+ * Indicates the MIDI system when the preset was locked
38
+ * @type {SynthSystem}
39
+ */
40
+ lockedSystem;
41
+
30
42
  /**
31
43
  * The array of all MIDI controllers (in 14-bit values) with the modulator sources at the end.
32
44
  * @type {Int16Array}
@@ -103,7 +115,9 @@ export class ChannelSnapshot
103
115
  // program data
104
116
  channelSnapshot.program = channelObject.preset.program;
105
117
  channelSnapshot.bank = channelObject.getBankSelect();
118
+ channelSnapshot.isBankLSB = channelSnapshot.bank !== channelObject.bank;
106
119
  channelSnapshot.lockPreset = channelObject.lockPreset;
120
+ channelSnapshot.lockedSystem = channelObject.lockedSystem;
107
121
  channelSnapshot.patchName = channelObject.preset.presetName;
108
122
 
109
123
  // controller data
@@ -152,9 +166,10 @@ export class ChannelSnapshot
152
166
  channelObject.velocityOverride = channelSnapshot.velocityOverride;
153
167
 
154
168
  // restore preset and lock
155
- channelObject.lockPreset = false;
156
- channelObject.setBankSelect(channelSnapshot.bank);
169
+ channelObject.setPresetLock(false);
170
+ channelObject.setBankSelect(channelSnapshot.bank, channelSnapshot.isBankLSB);
157
171
  channelObject.programChange(channelSnapshot.program);
158
- channelObject.lockPreset = channelSnapshot.lockPreset;
172
+ channelObject.setPresetLock(channelSnapshot.lockPreset);
173
+ channelObject.lockedSystem = channelSnapshot.lockedSystem;
159
174
  }
160
175
  }
@@ -94,7 +94,7 @@ export class SynthesizerSnapshot
94
94
  static applySnapshot(workletProcessor, snapshot)
95
95
  {
96
96
  // restore system
97
- workletProcessor.system = snapshot.system;
97
+ workletProcessor.setSystem(snapshot.system);
98
98
 
99
99
  // restore pan and volume
100
100
  workletProcessor.setMasterGain(snapshot.mainVolume);
@@ -1,9 +1,6 @@
1
- import { SpessaSynthInfo } from "../../../../utils/loggin.js";
2
1
  import { midiControllers } from "../../../../midi_parser/midi_message.js";
3
2
  import { computeModulators } from "../../worklet_utilities/worklet_modulator.js";
4
- import { consoleColors } from "../../../../utils/other.js";
5
3
  import { channelConfiguration, dataEntryStates } from "../../worklet_utilities/controller_tables.js";
6
- import { DEFAULT_PERCUSSION } from "../../../synth_constants.js";
7
4
 
8
5
  /**
9
6
  * @param controllerNumber {number}
@@ -56,144 +53,76 @@ export function controllerChange(controllerNumber, controllerValue, force = fals
56
53
  this.midiControllers[controllerNumber] = controllerValue << 7;
57
54
 
58
55
  // interpret special CCs
59
- switch (controllerNumber)
60
56
  {
61
- case midiControllers.allNotesOff:
62
- this.stopAllNotes();
63
- break;
64
-
65
- case midiControllers.allSoundOff:
66
- this.stopAllNotes(true);
67
- break;
68
-
69
- // special case: bank select
70
- case midiControllers.bankSelect:
71
- let bankNr = controllerValue;
72
- if (!force)
73
- {
74
- switch (this.synth.system)
75
- {
76
- case "gm":
77
- // gm ignores bank select
78
- SpessaSynthInfo(
79
- `%cIgnoring the Bank Select (${controllerValue}), as the synth is in GM mode.`,
80
- consoleColors.info
81
- );
82
- return;
83
-
84
- case "xg":
85
- // for xg, if msb is 120, 126 or 127, then it's drums
86
- if (bankNr === 120 || bankNr === 126 || bankNr === 127)
87
- {
88
- this.setDrums(true);
89
- }
90
- else
91
- {
92
- // drums shall not be disabled on channel 9
93
- if (this.channelNumber % 16 !== DEFAULT_PERCUSSION)
94
- {
95
- this.setDrums(false);
96
- }
97
- }
98
- break;
99
-
100
- case "gm2":
101
- if (bankNr === 120)
102
- {
103
- this.setDrums(true);
104
- }
105
- else
106
- {
107
- if (this.channelNumber % 16 !== DEFAULT_PERCUSSION)
108
- {
109
- this.setDrums(false);
110
- }
111
- }
112
- }
113
-
114
- if (this.drumChannel)
115
- {
116
- // 128 for percussion channel
117
- bankNr = 128;
118
- }
119
- if (bankNr === 128 && !this.drumChannel)
57
+ switch (controllerNumber)
58
+ {
59
+ case midiControllers.allNotesOff:
60
+ this.stopAllNotes();
61
+ break;
62
+
63
+ case midiControllers.allSoundOff:
64
+ this.stopAllNotes(true);
65
+ break;
66
+
67
+ // special case: bank select
68
+ case midiControllers.bankSelect:
69
+ this.setBankSelect(controllerValue);
70
+ break;
71
+
72
+ case midiControllers.lsbForControl0BankSelect:
73
+ this.setBankSelect(controllerValue, true);
74
+ break;
75
+
76
+ // check for RPN and NPRN and data entry
77
+ case midiControllers.RPNLsb:
78
+ this.dataEntryState = dataEntryStates.RPFine;
79
+ break;
80
+
81
+ case midiControllers.RPNMsb:
82
+ this.dataEntryState = dataEntryStates.RPCoarse;
83
+ break;
84
+
85
+ case midiControllers.NRPNMsb:
86
+ this.dataEntryState = dataEntryStates.NRPCoarse;
87
+ break;
88
+
89
+ case midiControllers.NRPNLsb:
90
+ this.dataEntryState = dataEntryStates.NRPFine;
91
+ break;
92
+
93
+ case midiControllers.dataEntryMsb:
94
+ this.dataEntryCoarse(controllerValue);
95
+ break;
96
+
97
+ case midiControllers.lsbForControl6DataEntry:
98
+ this.dataEntryFine(controllerValue);
99
+ break;
100
+
101
+ case midiControllers.resetAllControllers:
102
+ this.resetControllersRP15Compliant();
103
+ break;
104
+
105
+ case midiControllers.sustainPedal:
106
+ if (controllerValue >= 64)
120
107
  {
121
- // if a channel is not for percussion, default to bank current
122
- bankNr = this.getBankSelect();
108
+ this.holdPedal = true;
123
109
  }
124
- }
125
-
126
- this.setBankSelect(bankNr);
127
- break;
128
-
129
- case midiControllers.lsbForControl0BankSelect:
130
- if (this.synth.system === "xg")
131
- {
132
- if (!this.drumChannel)
110
+ else
133
111
  {
134
- // some soundfonts use 127 as drums and
135
- // if it's not marked as drums by bank MSB (line 47), then we DO NOT want the drums!
136
- if (controllerValue !== 127)
112
+ this.holdPedal = false;
113
+ this.sustainedVoices.forEach(v =>
137
114
  {
138
- this.setBankSelect(controllerValue);
139
- }
115
+ v.release();
116
+ });
117
+ this.sustainedVoices = [];
140
118
  }
141
- }
142
- else if (this.synth.system === "gm2")
143
- {
144
- this.setBankSelect(controllerValue);
145
- }
146
- break;
147
-
148
- // check for RPN and NPRN and data entry
149
- case midiControllers.RPNLsb:
150
- this.dataEntryState = dataEntryStates.RPFine;
151
- break;
152
-
153
- case midiControllers.RPNMsb:
154
- this.dataEntryState = dataEntryStates.RPCoarse;
155
- break;
156
-
157
- case midiControllers.NRPNMsb:
158
- this.dataEntryState = dataEntryStates.NRPCoarse;
159
- break;
160
-
161
- case midiControllers.NRPNLsb:
162
- this.dataEntryState = dataEntryStates.NRPFine;
163
- break;
164
-
165
- case midiControllers.dataEntryMsb:
166
- this.dataEntryCoarse(controllerValue);
167
- break;
168
-
169
- case midiControllers.lsbForControl6DataEntry:
170
- this.dataEntryFine(controllerValue);
171
- break;
172
-
173
- case midiControllers.resetAllControllers:
174
- this.resetControllersRP15Compliant();
175
- break;
176
-
177
- case midiControllers.sustainPedal:
178
- if (controllerValue >= 64)
179
- {
180
- this.holdPedal = true;
181
- }
182
- else
183
- {
184
- this.holdPedal = false;
185
- this.sustainedVoices.forEach(v =>
186
- {
187
- v.release();
188
- });
189
- this.sustainedVoices = [];
190
- }
191
- break;
192
-
193
- // default: just compute modulators
194
- default:
195
- this.voices.forEach(v => computeModulators(v, this.midiControllers, 1, controllerNumber));
196
- break;
119
+ break;
120
+
121
+ // default: just compute modulators
122
+ default:
123
+ this.voices.forEach(v => computeModulators(v, this.midiControllers, 1, controllerNumber));
124
+ break;
125
+ }
197
126
  }
198
127
  this.synth.callEvent("controllerchange", {
199
128
  channel: this.channelNumber,
@@ -25,6 +25,7 @@ export function resetAllControllers(log = true)
25
25
  SpessaSynthInfo("%cResetting all controllers!", consoleColors.info);
26
26
  }
27
27
  this.callEvent("allcontrollerreset", undefined);
28
+ this.setSystem(DEFAULT_SYNTH_MODE);
28
29
  for (let channelNumber = 0; channelNumber < this.workletProcessorChannels.length; channelNumber++)
29
30
  {
30
31
  this.workletProcessorChannels[channelNumber].resetControllers();
@@ -37,11 +38,11 @@ export function resetAllControllers(log = true)
37
38
  // if preset is unlocked, switch to non-drums and call event
38
39
  if (!ch.lockPreset)
39
40
  {
40
- ch.presetUsesOverride = true;
41
41
  ch.setBankSelect(0);
42
42
  if (channelNumber % 16 === DEFAULT_PERCUSSION)
43
43
  {
44
- this.workletProcessorChannels[channelNumber].setPreset(this.drumPreset);
44
+ ch.setPreset(this.drumPreset);
45
+ ch.presetUsesOverride = this.defaultDrumsUsesOverride;
45
46
  ch.drumChannel = true;
46
47
  this.callEvent("drumchange", {
47
48
  channel: channelNumber,
@@ -51,6 +52,7 @@ export function resetAllControllers(log = true)
51
52
  else
52
53
  {
53
54
  ch.drumChannel = false;
55
+ ch.presetUsesOverride = this.defaultDrumsUsesOverride;
54
56
  ch.setPreset(this.defaultPreset);
55
57
  this.callEvent("drumchange", {
56
58
  channel: channelNumber,
@@ -66,11 +68,14 @@ export function resetAllControllers(log = true)
66
68
  });
67
69
  }
68
70
 
71
+ const presetBank = ch.preset.bank;
72
+ const sentBank = presetBank === 128 ? 128 : (ch.presetUsesOverride ? presetBank + this.soundfontBankOffset : presetBank);
73
+
69
74
  // call program change
70
75
  this.callEvent("programchange", {
71
76
  channel: channelNumber,
72
77
  program: ch.preset.program,
73
- bank: ch.getBankSelect(),
78
+ bank: sentBank,
74
79
  userCalled: false
75
80
  });
76
81
 
@@ -110,7 +115,6 @@ export function resetAllControllers(log = true)
110
115
  }
111
116
 
112
117
  this.setMIDIVolume(1);
113
- this.system = DEFAULT_SYNTH_MODE;
114
118
  }
115
119
 
116
120
  /**
@@ -11,31 +11,36 @@ export function programChange(programNumber, userChange = false)
11
11
  return;
12
12
  }
13
13
  // always 128 for percussion
14
- const bank = this.getBankSelect();
14
+ let bank = this.getBankSelect();
15
15
  let sentBank;
16
16
  let preset;
17
17
 
18
+ const isXG = this.isXGChannel;
18
19
  // check if override
19
20
  if (this.synth.overrideSoundfont)
20
21
  {
21
22
  const bankWithOffset = bank === 128 ? 128 : bank - this.synth.soundfontBankOffset;
22
- const p = this.synth.overrideSoundfont.getPresetNoFallback(bankWithOffset, programNumber);
23
+ const p = this.synth.overrideSoundfont.getPresetNoFallback(
24
+ bankWithOffset,
25
+ programNumber,
26
+ isXG
27
+ );
23
28
  if (p)
24
29
  {
25
- sentBank = bank;
30
+ sentBank = p.bank === 128 ? 128 : p.bank + this.synth.soundfontBankOffset;
26
31
  preset = p;
27
32
  this.presetUsesOverride = true;
28
33
  }
29
34
  else
30
35
  {
31
- preset = this.synth.soundfontManager.getPreset(bank, programNumber);
36
+ preset = this.synth.soundfontManager.getPreset(bank, programNumber, isXG);
32
37
  sentBank = preset.bank;
33
38
  this.presetUsesOverride = false;
34
39
  }
35
40
  }
36
41
  else
37
42
  {
38
- preset = this.synth.soundfontManager.getPreset(bank, programNumber);
43
+ preset = this.synth.soundfontManager.getPreset(bank, programNumber, isXG);
39
44
  sentBank = preset.bank;
40
45
  this.presetUsesOverride = false;
41
46
  }
@@ -11,8 +11,7 @@ export function clearSoundFont(sendPresets = true, clearOverride = true)
11
11
  delete this.overrideSoundfont;
12
12
  this.overrideSoundfont = undefined;
13
13
  }
14
- this.defaultPreset = this.getPreset(0, 0);
15
- this.drumPreset = this.getPreset(128, 0);
14
+ this.getDefaultPresets();
16
15
  this.cachedVoices = [];
17
16
 
18
17
  for (let i = 0; i < this.workletProcessorChannels.length; i++)
@@ -20,7 +19,7 @@ export function clearSoundFont(sendPresets = true, clearOverride = true)
20
19
  const channelObject = this.workletProcessorChannels[i];
21
20
  if (!clearOverride || (clearOverride && channelObject.presetUsesOverride))
22
21
  {
23
- channelObject.lockPreset = false;
22
+ channelObject.setPresetLock(false);
24
23
  }
25
24
  channelObject.programChange(channelObject.preset.program);
26
25
  }
@@ -10,11 +10,11 @@ export function getPreset(bank, program)
10
10
  {
11
11
  // if override soundfont
12
12
  const bankWithOffset = bank === 128 ? 128 : bank - this.soundfontBankOffset;
13
- const preset = this.overrideSoundfont.getPresetNoFallback(bankWithOffset, program);
13
+ const preset = this.overrideSoundfont.getPresetNoFallback(bankWithOffset, program, this.system === "xg");
14
14
  if (preset)
15
15
  {
16
16
  return preset;
17
17
  }
18
18
  }
19
- return this.soundfontManager.getPreset(bank, program);
19
+ return this.soundfontManager.getPreset(bank, program, this.system === "xg");
20
20
  }
@@ -32,8 +32,7 @@ export function reloadSoundFont(buffer, isOverride = false)
32
32
  });
33
33
  return;
34
34
  }
35
- this.defaultPreset = this.getPreset(0, 0);
36
- this.drumPreset = this.getPreset(128, 0);
35
+ this.getDefaultPresets();
37
36
  this.workletProcessorChannels.forEach(c =>
38
37
  c.programChange(c.preset.program)
39
38
  );
@@ -148,17 +148,17 @@ export function systemExclusive(messageData, channelOffset = 0)
148
148
  if (messageData[3] === 0x01)
149
149
  {
150
150
  SpessaSynthInfo("%cGM system on", consoleColors.info);
151
- this.system = "gm";
151
+ this.setSystem("gm");
152
152
  }
153
153
  else if (messageData[3] === 0x03)
154
154
  {
155
155
  SpessaSynthInfo("%cGM2 system on", consoleColors.info);
156
- this.system = "gm2";
156
+ this.setSystem("gm2");
157
157
  }
158
158
  else
159
159
  {
160
160
  SpessaSynthInfo("%cGM system off, defaulting to GS", consoleColors.info);
161
- this.system = "gs";
161
+ this.setSystem("gs");
162
162
  }
163
163
  break;
164
164
 
@@ -312,14 +312,14 @@ export function systemExclusive(messageData, channelOffset = 0)
312
312
  // this is a GS reset
313
313
  SpessaSynthInfo("%cGS Reset received!", consoleColors.info);
314
314
  this.resetAllControllers(false);
315
- this.system = "gs";
315
+ this.setSystem("gs");
316
316
  }
317
317
  else if (messageValue === 0x7F)
318
318
  {
319
319
  // GS mode off
320
320
  SpessaSynthInfo("%cGS system off, switching to GM2", consoleColors.info);
321
321
  this.resetAllControllers(false);
322
- this.system = "gm2";
322
+ this.setSystem("gm2");
323
323
  }
324
324
  return;
325
325
  }
@@ -606,7 +606,7 @@ export function systemExclusive(messageData, channelOffset = 0)
606
606
  case 0x7E:
607
607
  SpessaSynthInfo("%cXG system on", consoleColors.info);
608
608
  this.resetAllControllers(false);
609
- this.system = "xg";
609
+ this.setSystem("xg");
610
610
  break;
611
611
  }
612
612
  }
@@ -1,6 +1,7 @@
1
1
  import { SpessaSynthWarn } from "../../../../utils/loggin.js";
2
2
  import { WorkletSoundfontManagerMessageType } from "./sfman_message.js";
3
3
  import { loadSoundFont } from "../../../../soundfont/load_soundfont.js";
4
+ import { isXGDrums } from "../../../../utils/xg_hacks.js";
4
5
 
5
6
  /**
6
7
  * @typedef {Object} SoundFontType
@@ -194,9 +195,10 @@ export class WorkletSoundfontManager
194
195
  * Gets a given preset from the soundfont stack
195
196
  * @param bankNumber {number}
196
197
  * @param programNumber {number}
198
+ * @param allowXGDrums {boolean} if true, allows XG drum banks (120, 126 and 127) as drum preset
197
199
  * @returns {BasicPreset} the preset
198
200
  */
199
- getPreset(bankNumber, programNumber)
201
+ getPreset(bankNumber, programNumber, allowXGDrums = false)
200
202
  {
201
203
  if (this.soundfontList.length < 1)
202
204
  {
@@ -205,19 +207,25 @@ export class WorkletSoundfontManager
205
207
  for (const sf of this.soundfontList)
206
208
  {
207
209
  // check for the preset (with given offset)
208
- const preset = sf.soundfont.getPresetNoFallback(bankNumber - sf.bankOffset, programNumber);
210
+ const preset = sf.soundfont.getPresetNoFallback(
211
+ bankNumber - sf.bankOffset,
212
+ programNumber,
213
+ allowXGDrums
214
+ );
209
215
  if (preset !== undefined)
210
216
  {
211
217
  return preset;
212
218
  }
213
219
  // if not found, advance to the next soundfont
214
220
  }
221
+ const isDrum = bankNumber === 128 || (allowXGDrums && isXGDrums(bankNumber));
215
222
  // if none found, return the first correct preset found
216
- if (bankNumber !== 128)
223
+ if (!isDrum)
217
224
  {
218
225
  for (const sf of this.soundfontList)
219
226
  {
220
- const preset = sf.soundfont.presets.find(p => p.program === programNumber);
227
+ const preset = sf.soundfont.presets.find(p => p.program === programNumber && !p.isDrumPreset(
228
+ allowXGDrums));
221
229
  if (preset)
222
230
  {
223
231
  return preset;
@@ -230,7 +238,14 @@ export class WorkletSoundfontManager
230
238
  {
231
239
  for (const sf of this.soundfontList)
232
240
  {
233
- const preset = sf.soundfont.presets.find(p => p.bank === 128);
241
+ // check for any drum type (127/128) and matching program
242
+ const p = sf.soundfont.presets.find(p => p.isDrumPreset(allowXGDrums) && p.program === programNumber);
243
+ if (p)
244
+ {
245
+ return p;
246
+ }
247
+ // check for any drum preset
248
+ const preset = sf.soundfont.presets.find(p => p.isDrumPreset(allowXGDrums));
234
249
  if (preset)
235
250
  {
236
251
  return preset;