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
@@ -27,6 +27,8 @@ import { channelPressure } from "../worklet_methods/tuning_control/channel_press
27
27
  import { pitchWheel } from "../worklet_methods/tuning_control/pitch_wheel.js";
28
28
  import { setOctaveTuning } from "../worklet_methods/tuning_control/set_octave_tuning.js";
29
29
  import { programChange } from "../worklet_methods/program_change.js";
30
+ import { chooseBank, parseBankSelect } from "../../../utils/xg_hacks.js";
31
+ import { DEFAULT_PERCUSSION } from "../../synth_constants.js";
30
32
 
31
33
  /**
32
34
  * This class represents a single MIDI Channel within the synthesizer.
@@ -113,6 +115,12 @@ class WorkletProcessorChannel
113
115
  */
114
116
  bank = 0;
115
117
 
118
+ /**
119
+ * The bank LSB number of the channel (used for patch changes in XG mode).
120
+ * @type {number}
121
+ */
122
+ bankLSB = 0;
123
+
116
124
  /**
117
125
  * The preset currently assigned to the channel.
118
126
  * @type {BasicPreset}
@@ -125,6 +133,12 @@ class WorkletProcessorChannel
125
133
  */
126
134
  lockPreset = false;
127
135
 
136
+ /**
137
+ * Indicates the MIDI system when the preset was locked.
138
+ * @type {SynthSystem}
139
+ */
140
+ lockedSystem = "gs";
141
+
128
142
  /**
129
143
  * Indicates whether the channel uses a preset from the override soundfont.
130
144
  * @type {boolean}
@@ -189,6 +203,11 @@ class WorkletProcessorChannel
189
203
  this.channelNumber = channelNumber;
190
204
  }
191
205
 
206
+ get isXGChannel()
207
+ {
208
+ return this.synth.system === "xg" || (this.lockPreset && this.lockedSystem === "xg");
209
+ }
210
+
192
211
  /**
193
212
  * @param type {customControllers|number}
194
213
  * @param value {number}
@@ -230,14 +249,61 @@ class WorkletProcessorChannel
230
249
  ));
231
250
  }
232
251
 
252
+ /**
253
+ * @param locked {boolean}
254
+ */
255
+ setPresetLock(locked)
256
+ {
257
+ this.lockPreset = locked;
258
+ if (locked)
259
+ {
260
+ this.lockedSystem = this.synth.system;
261
+ }
262
+ }
263
+
233
264
  /**
234
265
  * @param bank {number}
266
+ * @param isLSB {boolean}
235
267
  */
236
- setBankSelect(bank)
268
+ setBankSelect(bank, isLSB = false)
237
269
  {
238
- if (!this.lockPreset)
270
+ if (this.lockPreset)
271
+ {
272
+ return;
273
+ }
274
+ if (isLSB)
275
+ {
276
+ this.bankLSB = bank;
277
+ }
278
+ else
239
279
  {
240
280
  this.bank = bank;
281
+ const bankLogic = parseBankSelect(
282
+ this.getBankSelect(),
283
+ bank,
284
+ this.synth.system,
285
+ false,
286
+ this.drumChannel,
287
+ this.channelNumber
288
+ );
289
+ switch (bankLogic.drumsStatus)
290
+ {
291
+ default:
292
+ case 0:
293
+ break;
294
+
295
+ case 1:
296
+ if (this.channelNumber % 16 === DEFAULT_PERCUSSION)
297
+ {
298
+ // cannot disable drums on channel 9
299
+ this.bank = 127;
300
+ }
301
+ break;
302
+
303
+ case 2:
304
+ this.setDrums(true);
305
+ break;
306
+ }
241
307
  }
242
308
  }
243
309
 
@@ -246,11 +312,7 @@ class WorkletProcessorChannel
246
312
  */
247
313
  getBankSelect()
248
314
  {
249
- if (this.drumChannel)
250
- {
251
- return 128;
252
- }
253
- return this.bank;
315
+ return chooseBank(this.bank, this.bankLSB, this.drumChannel, this.isXGChannel);
254
316
  }
255
317
 
256
318
  /**
@@ -286,23 +348,17 @@ class WorkletProcessorChannel
286
348
  // clear transpose
287
349
  this.channelTransposeKeyShift = 0;
288
350
  this.drumChannel = true;
289
- this.setPreset(this.synth.getPreset(this.getBankSelect(), this.preset.program));
290
351
  }
291
352
  else
292
353
  {
293
354
  this.drumChannel = false;
294
- this.setPreset(
295
- this.synth.getPreset(
296
- this.getBankSelect(),
297
- this.preset.program
298
- )
299
- );
300
355
  }
301
356
  this.presetUsesOverride = false;
302
357
  this.synth.callEvent("drumchange", {
303
358
  channel: this.channelNumber,
304
359
  isDrumChannel: this.drumChannel
305
360
  });
361
+ this.programChange(this.preset.program);
306
362
  this.synth.sendChannelProperties();
307
363
  }
308
364
 
@@ -408,7 +408,7 @@ export function getWorkletVoices(channel,
408
408
  let preset = channelObject.preset;
409
409
  if (overridePatch)
410
410
  {
411
- preset = this.soundfontManager.getPreset(bank, program);
411
+ preset = this.soundfontManager.getPreset(bank, program, this.system === "xg");
412
412
  }
413
413
  /**
414
414
  * @returns {WorkletVoice[]}
@@ -503,8 +503,7 @@ export function getWorkletVoices(channel,
503
503
  targetKey,
504
504
  realKey,
505
505
  generators,
506
- sampleAndGenerators.modulators.map(m => Modulator.copy(m)),
507
- this.filterSmoothingFactor
506
+ sampleAndGenerators.modulators.map(m => Modulator.copy(m))
508
507
  )
509
508
  );
510
509
  return voices;
@@ -0,0 +1,46 @@
1
+ /**
2
+ * @param e {MIDIMessage}
3
+ * @returns boolean
4
+ */
5
+ export function isXGOn(e)
6
+ {
7
+ return e.messageData[0] === 0x43 && // Yamaha
8
+ e.messageData[2] === 0x4C && // XG ON
9
+ e.messageData[5] === 0x7E &&
10
+ e.messageData[6] === 0x00;
11
+ }
12
+
13
+ /**
14
+ * @param e {MIDIMessage}
15
+ * @returns boolean
16
+ */
17
+ export function isGSDrumsOn(e)
18
+ {
19
+ return e.messageData[0] === 0x41 && // roland
20
+ e.messageData[2] === 0x42 && // GS
21
+ e.messageData[3] === 0x12 && // GS
22
+ e.messageData[4] === 0x40 && // system parameter
23
+ (e.messageData[5] & 0x10) !== 0 && // part parameter
24
+ e.messageData[6] === 0x15; // drum pars
25
+ }
26
+
27
+ /**
28
+ * @param e {MIDIMessage}
29
+ * @returns boolean
30
+ */
31
+ export function isGSOn(e)
32
+ {
33
+ return e.messageData[0] === 0x41 // roland
34
+ && e.messageData[2] === 0x42 // GS
35
+ && e.messageData[6] === 0x7F; // Mode set
36
+ }
37
+
38
+ /**
39
+ * @param e {MIDIMessage}
40
+ * @returns boolean
41
+ */
42
+ export function isGMOn(e)
43
+ {
44
+ return e.messageData[0] === 0x7E // non realtime
45
+ && e.messageData[2] === 0x09; // gm system
46
+ }
@@ -0,0 +1,176 @@
1
+ import { SpessaSynthInfo } from "./loggin.js";
2
+ import { consoleColors } from "./other.js";
3
+ import { DEFAULT_PERCUSSION } from "../synthetizer/synth_constants.js";
4
+
5
+ export const XG_SFX_VOICE = 64;
6
+
7
+ /**
8
+ * @param bankNr {number}
9
+ * @returns {boolean}
10
+ */
11
+ export function isXGDrums(bankNr)
12
+ {
13
+ return bankNr === 120 || bankNr === 126 || bankNr === 127;
14
+ }
15
+
16
+ /**
17
+ * @param bank {number}
18
+ * @returns {boolean}
19
+ */
20
+ export function isValidXGMSB(bank)
21
+ {
22
+ return isXGDrums(bank) || bank === XG_SFX_VOICE;
23
+ }
24
+
25
+ /**
26
+ * Bank select hacks abstracted here
27
+ * @param bankBefore {number} the current bank number
28
+ * @param bank {number} the cc change bank number
29
+ * @param system {SynthSystem} MIDI system
30
+ * @param isLSB {boolean} is bank LSB?
31
+ * @param isDrums {boolean} is drum channel?
32
+ * @param channelNumber {number} channel number
33
+ * @returns {{
34
+ * newBank: number,
35
+ * drumsStatus: 0|1|2
36
+ * }} 0 - unchanged, 1 - OFF, 2 - ON
37
+ */
38
+ export function parseBankSelect(bankBefore, bank, system, isLSB, isDrums, channelNumber)
39
+ {
40
+ // 64 means SFX in MSB, so it is allowed
41
+ let out = bankBefore;
42
+ let drumsStatus = 0;
43
+ if (isLSB)
44
+ {
45
+ if (system === "xg")
46
+ {
47
+ if (!isValidXGMSB(bank))
48
+ {
49
+ out = bank;
50
+ }
51
+ }
52
+ else if (system === "gm2")
53
+ {
54
+ out = bank;
55
+ }
56
+ }
57
+ else
58
+ {
59
+ let canSetBankSelect = true;
60
+ switch (system)
61
+ {
62
+ case "gm":
63
+ // gm ignores bank select
64
+ SpessaSynthInfo(
65
+ `%cIgnoring the Bank Select (${bank}), as the synth is in GM mode.`,
66
+ consoleColors.info
67
+ );
68
+ canSetBankSelect = false;
69
+ break;
70
+
71
+ case "xg":
72
+ canSetBankSelect = isValidXGMSB(bank);
73
+ // for xg, if msb is 120, 126 or 127, then it's drums
74
+ if (isXGDrums(bank))
75
+ {
76
+ drumsStatus = 2;
77
+ }
78
+ else
79
+ {
80
+ // drums shall not be disabled on channel 9
81
+ if (channelNumber % 16 !== DEFAULT_PERCUSSION)
82
+ {
83
+ drumsStatus = 1;
84
+ }
85
+ }
86
+ break;
87
+
88
+ case "gm2":
89
+ if (bank === 120)
90
+ {
91
+ drumsStatus = 2;
92
+ }
93
+ else
94
+ {
95
+ if (channelNumber % 16 !== DEFAULT_PERCUSSION)
96
+ {
97
+ drumsStatus = 1;
98
+ }
99
+ }
100
+ }
101
+
102
+ if (isDrums)
103
+ {
104
+ // 128 for percussion channel
105
+ bank = 128;
106
+ }
107
+ if (bank === 128 && !isDrums)
108
+ {
109
+ // if a channel is not for percussion, default to bank current
110
+ bank = bankBefore;
111
+ }
112
+ if (canSetBankSelect)
113
+ {
114
+ out = bank;
115
+ }
116
+ }
117
+ return {
118
+ newBank: out,
119
+ drumsStatus: drumsStatus
120
+ };
121
+ }
122
+
123
+
124
+ /**
125
+ * Chooses a bank number according to spessasynth logic
126
+ * That is:
127
+ * for GS, bank MSB if not drum, otherwise 128
128
+ * for XG: bank MSB if drum and MSB is valid, 128 othewise, bank MSB if it is SFX voice, LSB otherwise
129
+ * @param msb {number}
130
+ * @param lsb {number}
131
+ * @param isDrums {boolean}
132
+ * @param isXG {boolean}
133
+ * @returns {number}
134
+ */
135
+ export function chooseBank(msb, lsb, isDrums, isXG)
136
+ {
137
+ if (isXG)
138
+ {
139
+ if (isDrums)
140
+ {
141
+ if (isXGDrums(msb))
142
+ {
143
+ return msb;
144
+ }
145
+ else
146
+ {
147
+ return 128;
148
+ }
149
+ }
150
+ else
151
+ {
152
+ // check for SFX
153
+ if (msb !== XG_SFX_VOICE)
154
+ {
155
+ // if lsb is 0 and msb is not, use that
156
+ if (lsb === 0 && msb !== 0)
157
+ {
158
+ return msb;
159
+ }
160
+ if (!isValidXGMSB(lsb))
161
+ {
162
+ return lsb;
163
+ }
164
+ return 0;
165
+ }
166
+ else
167
+ {
168
+ return XG_SFX_VOICE;
169
+ }
170
+ }
171
+ }
172
+ else
173
+ {
174
+ return isDrums ? 128 : msb;
175
+ }
176
+ }