spessasynth_lib 3.25.0 → 3.25.1

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.
@@ -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,7 +185,6 @@ 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}
@@ -300,6 +303,18 @@ class SpessaSynthProcessor extends AudioWorkletProcessor
300
303
  return this.masterGain * this.midiVolume;
301
304
  }
302
305
 
306
+ /**
307
+ * @param value {SynthSystem}
308
+ */
309
+ setSystem(value)
310
+ {
311
+ this.system = value;
312
+ this.post({
313
+ messageType: returnMessageType.masterParameterChange,
314
+ messageData: [masterParameterType.midiSystem, this.system]
315
+ });
316
+ }
317
+
303
318
  /**
304
319
  * @param bank {number}
305
320
  * @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
 
@@ -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
  };
@@ -153,7 +153,7 @@ export class ChannelSnapshot
153
153
 
154
154
  // restore preset and lock
155
155
  channelObject.lockPreset = false;
156
- channelObject.setBankSelect(channelSnapshot.bank);
156
+ channelObject.setBankSelect(channelSnapshot.bank, true);
157
157
  channelObject.programChange(channelSnapshot.program);
158
158
  channelObject.lockPreset = channelSnapshot.lockPreset;
159
159
  }
@@ -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,83 @@ 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)
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
+ if (!force)
75
70
  {
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
- }
71
+ this.setBankSelect(controllerValue);
112
72
  }
113
-
114
- if (this.drumChannel)
73
+ else
115
74
  {
116
- // 128 for percussion channel
117
- bankNr = 128;
75
+ this.bank = controllerValue;
118
76
  }
119
- if (bankNr === 128 && !this.drumChannel)
77
+ break;
78
+
79
+ case midiControllers.lsbForControl0BankSelect:
80
+ this.setBankSelect(controllerValue, false, true);
81
+ break;
82
+
83
+ // check for RPN and NPRN and data entry
84
+ case midiControllers.RPNLsb:
85
+ this.dataEntryState = dataEntryStates.RPFine;
86
+ break;
87
+
88
+ case midiControllers.RPNMsb:
89
+ this.dataEntryState = dataEntryStates.RPCoarse;
90
+ break;
91
+
92
+ case midiControllers.NRPNMsb:
93
+ this.dataEntryState = dataEntryStates.NRPCoarse;
94
+ break;
95
+
96
+ case midiControllers.NRPNLsb:
97
+ this.dataEntryState = dataEntryStates.NRPFine;
98
+ break;
99
+
100
+ case midiControllers.dataEntryMsb:
101
+ this.dataEntryCoarse(controllerValue);
102
+ break;
103
+
104
+ case midiControllers.lsbForControl6DataEntry:
105
+ this.dataEntryFine(controllerValue);
106
+ break;
107
+
108
+ case midiControllers.resetAllControllers:
109
+ this.resetControllersRP15Compliant();
110
+ break;
111
+
112
+ case midiControllers.sustainPedal:
113
+ if (controllerValue >= 64)
120
114
  {
121
- // if a channel is not for percussion, default to bank current
122
- bankNr = this.getBankSelect();
115
+ this.holdPedal = true;
123
116
  }
124
- }
125
-
126
- this.setBankSelect(bankNr);
127
- break;
128
-
129
- case midiControllers.lsbForControl0BankSelect:
130
- if (this.synth.system === "xg")
131
- {
132
- if (!this.drumChannel)
117
+ else
133
118
  {
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)
119
+ this.holdPedal = false;
120
+ this.sustainedVoices.forEach(v =>
137
121
  {
138
- this.setBankSelect(controllerValue);
139
- }
122
+ v.release();
123
+ });
124
+ this.sustainedVoices = [];
140
125
  }
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;
126
+ break;
127
+
128
+ // default: just compute modulators
129
+ default:
130
+ this.voices.forEach(v => computeModulators(v, this.midiControllers, 1, controllerNumber));
131
+ break;
132
+ }
197
133
  }
198
134
  this.synth.callEvent("controllerchange", {
199
135
  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();
@@ -38,7 +39,7 @@ export function resetAllControllers(log = true)
38
39
  if (!ch.lockPreset)
39
40
  {
40
41
  ch.presetUsesOverride = true;
41
- ch.setBankSelect(0);
42
+ ch.setBankSelect(0, true);
42
43
  if (channelNumber % 16 === DEFAULT_PERCUSSION)
43
44
  {
44
45
  this.workletProcessorChannels[channelNumber].setPreset(this.drumPreset);
@@ -110,7 +111,6 @@ export function resetAllControllers(log = true)
110
111
  }
111
112
 
112
113
  this.setMIDIVolume(1);
113
- this.system = DEFAULT_SYNTH_MODE;
114
114
  }
115
115
 
116
116
  /**
@@ -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
  }
@@ -27,6 +27,7 @@ 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 { parseBankSelect } from "../../../utils/xg_hacks.js";
30
31
 
31
32
  /**
32
33
  * This class represents a single MIDI Channel within the synthesizer.
@@ -232,13 +233,45 @@ class WorkletProcessorChannel
232
233
 
233
234
  /**
234
235
  * @param bank {number}
236
+ * @param force {boolean}
237
+ * @param isLSB {boolean}
235
238
  */
236
- setBankSelect(bank)
239
+ setBankSelect(bank, force = false, isLSB = false)
237
240
  {
238
- if (!this.lockPreset)
241
+ if (this.lockPreset)
242
+ {
243
+ return;
244
+ }
245
+ if (force)
239
246
  {
240
247
  this.bank = bank;
241
248
  }
249
+ else
250
+ {
251
+ const bankLogic = parseBankSelect(
252
+ this.getBankSelect(),
253
+ bank,
254
+ this.synth.system,
255
+ isLSB,
256
+ this.drumChannel,
257
+ this.channelNumber
258
+ );
259
+ this.bank = bankLogic.newBank;
260
+ switch (bankLogic.drumsStatus)
261
+ {
262
+ default:
263
+ case 0:
264
+ break;
265
+
266
+ case 1:
267
+ this.setDrums(false);
268
+ break;
269
+
270
+ case 2:
271
+ this.setDrums(true);
272
+ break;
273
+ }
274
+ }
242
275
  }
243
276
 
244
277
  /**
@@ -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,128 @@
1
+ import { SpessaSynthInfo } from "./loggin.js";
2
+ import { consoleColors } from "./other.js";
3
+ import { DEFAULT_PERCUSSION } from "../synthetizer/synth_constants.js";
4
+
5
+ /**
6
+ * @param bankNr {number}
7
+ * @returns {boolean}
8
+ */
9
+ export function isXGDrums(bankNr)
10
+ {
11
+ return bankNr === 120 || bankNr === 126 || bankNr === 127;
12
+ }
13
+
14
+ /**
15
+ * Bank select hacks abstracted here
16
+ * @param bankBefore {number} the current bank number
17
+ * @param bank {number} the cc change bank number
18
+ * @param system {SynthSystem} MIDI system
19
+ * @param isLSB {boolean} is bank LSB?
20
+ * @param isDrums {boolean} is drum channel?
21
+ * @param channelNumber {number} channel number
22
+ * @returns {{
23
+ * newBank: number,
24
+ * drumsStatus: 0|1|2
25
+ * }} 0 - unchanged, 1 - OFF, 2 - ON
26
+ */
27
+ export function parseBankSelect(bankBefore, bank, system, isLSB, isDrums, channelNumber)
28
+ {
29
+ // 64 means SFX in MSB, so it is allowed
30
+ let out = bankBefore;
31
+ let drumsStatus = 0;
32
+ const isValidMSB = b => isXGDrums(b) || b === 64;
33
+ if (isLSB)
34
+ {
35
+ if (system === "xg")
36
+ {
37
+ if (!isValidMSB(bank))
38
+ {
39
+ out = bank;
40
+ }
41
+ }
42
+ else if (system === "gm2")
43
+ {
44
+ out = bank;
45
+ }
46
+ }
47
+ else
48
+ {
49
+ let canSetBankSelect = true;
50
+ switch (system)
51
+ {
52
+ case "gm":
53
+ // gm ignores bank select
54
+ SpessaSynthInfo(
55
+ `%cIgnoring the Bank Select (${bank}), as the synth is in GM mode.`,
56
+ consoleColors.info
57
+ );
58
+ canSetBankSelect = false;
59
+ break;
60
+
61
+ case "xg":
62
+ canSetBankSelect = isValidMSB(bank);
63
+ // for xg, if msb is 120, 126 or 127, then it's drums
64
+ if (isXGDrums(bank))
65
+ {
66
+ drumsStatus = 2;
67
+ }
68
+ else
69
+ {
70
+ // drums shall not be disabled on channel 9
71
+ if (channelNumber % 16 !== DEFAULT_PERCUSSION)
72
+ {
73
+ drumsStatus = 1;
74
+ }
75
+ }
76
+ break;
77
+
78
+ case "gm2":
79
+ if (bank === 120)
80
+ {
81
+ drumsStatus = 2;
82
+ }
83
+ else
84
+ {
85
+ if (channelNumber % 16 !== DEFAULT_PERCUSSION)
86
+ {
87
+ drumsStatus = 1;
88
+ }
89
+ }
90
+ }
91
+
92
+ if (isDrums)
93
+ {
94
+ // 128 for percussion channel
95
+ bank = 128;
96
+ }
97
+ if (bank === 128 && !isDrums)
98
+ {
99
+ // if a channel is not for percussion, default to bank current
100
+ bank = bankBefore;
101
+ }
102
+ if (canSetBankSelect)
103
+ {
104
+ out = bank;
105
+ }
106
+ }
107
+ return {
108
+ newBank: out,
109
+ drumsStatus: drumsStatus
110
+ };
111
+ }
112
+
113
+
114
+ /**
115
+ * @param msb {number}
116
+ * @param lsb {number}
117
+ */
118
+ export function chooseBank(msb, lsb)
119
+ {
120
+ if (lsb > 0)
121
+ {
122
+ if (!isXGDrums(msb) && msb !== 64)
123
+ {
124
+ return lsb;
125
+ }
126
+ }
127
+ return msb;
128
+ }