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.
- package/midi_parser/midi_editor.js +50 -44
- package/midi_parser/rmidi_writer.js +90 -45
- package/midi_parser/used_keys_loaded.js +59 -39
- package/package.json +1 -1
- package/sequencer/sequencer.js +0 -1
- package/soundfont/basic_soundfont/basic_preset.js +12 -0
- package/soundfont/basic_soundfont/basic_soundfont.js +25 -15
- package/soundfont/dls/dls_preset.js +11 -1
- package/synthetizer/synthetizer.js +48 -1
- package/synthetizer/worklet_processor.min.js +12 -12
- package/synthetizer/worklet_system/main_processor.js +46 -4
- package/synthetizer/worklet_system/message_protocol/handle_message.js +4 -1
- package/synthetizer/worklet_system/message_protocol/worklet_message.js +5 -6
- package/synthetizer/worklet_system/snapshot/channel_snapshot.js +18 -3
- package/synthetizer/worklet_system/snapshot/synthesizer_snapshot.js +1 -1
- package/synthetizer/worklet_system/worklet_methods/controller_control/controller_change.js +64 -135
- package/synthetizer/worklet_system/worklet_methods/controller_control/reset_controllers.js +8 -4
- package/synthetizer/worklet_system/worklet_methods/program_change.js +10 -5
- package/synthetizer/worklet_system/worklet_methods/soundfont_management/clear_sound_font.js +2 -3
- package/synthetizer/worklet_system/worklet_methods/soundfont_management/get_preset.js +2 -2
- package/synthetizer/worklet_system/worklet_methods/soundfont_management/reload_sound_font.js +1 -2
- package/synthetizer/worklet_system/worklet_methods/system_exclusive.js +6 -6
- package/synthetizer/worklet_system/worklet_methods/worklet_soundfont_manager/worklet_soundfont_manager.js +20 -5
- package/synthetizer/worklet_system/worklet_utilities/worklet_processor_channel.js +70 -14
- package/synthetizer/worklet_system/worklet_utilities/worklet_voice.js +2 -3
- package/utils/sysex_detector.js +46 -0
- 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 (
|
|
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
|
-
|
|
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
|
+
}
|