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
|
@@ -5,6 +5,8 @@ import { consoleColors } from "../utils/other.js";
|
|
|
5
5
|
|
|
6
6
|
import { customControllers } from "../synthetizer/worklet_system/worklet_utilities/controller_tables.js";
|
|
7
7
|
import { DEFAULT_PERCUSSION } from "../synthetizer/synth_constants.js";
|
|
8
|
+
import { isGMOn, isGSOn, isXGOn } from "../utils/sysex_detector.js";
|
|
9
|
+
import { isXGDrums, XG_SFX_VOICE } from "../utils/xg_hacks.js";
|
|
8
10
|
|
|
9
11
|
/**
|
|
10
12
|
* @param ticks {number}
|
|
@@ -126,6 +128,11 @@ export function modifyMIDI(
|
|
|
126
128
|
const midi = this;
|
|
127
129
|
SpessaSynthGroupCollapsed("%cApplying changes to the MIDI file...", consoleColors.info);
|
|
128
130
|
|
|
131
|
+
SpessaSynthInfo("Desired program changes:", desiredProgramChanges);
|
|
132
|
+
SpessaSynthInfo("Desired CC changes:", desiredControllerChanges);
|
|
133
|
+
SpessaSynthInfo("Desired channels to clear:", desiredChannelsToClear);
|
|
134
|
+
SpessaSynthInfo("Desired channels to transpose:", desiredChannelsToTranspose);
|
|
135
|
+
|
|
129
136
|
/**
|
|
130
137
|
* @type {Set<number>}
|
|
131
138
|
*/
|
|
@@ -336,7 +343,7 @@ export function modifyMIDI(
|
|
|
336
343
|
if (channelsToChangeProgram.has(channel))
|
|
337
344
|
{
|
|
338
345
|
const change = desiredProgramChanges.find(c => c.channel === channel);
|
|
339
|
-
let desiredBank = change.bank;
|
|
346
|
+
let desiredBank = Math.max(0, Math.min(change.bank, 127));
|
|
340
347
|
const desiredProgram = change.program;
|
|
341
348
|
SpessaSynthInfo(
|
|
342
349
|
`%cSetting %c${change.channel}%c to %c${desiredBank}:${desiredProgram}%c. Track num: %c${trackNum}`,
|
|
@@ -361,50 +368,61 @@ export function modifyMIDI(
|
|
|
361
368
|
);
|
|
362
369
|
addEventBefore(programChange);
|
|
363
370
|
|
|
364
|
-
|
|
365
|
-
if (!change.isDrum && system === "xg")
|
|
371
|
+
const addBank = (isLSB, v) =>
|
|
366
372
|
{
|
|
367
|
-
const
|
|
373
|
+
const bankChange = getControllerChange(
|
|
368
374
|
midiChannel,
|
|
369
|
-
midiControllers.lsbForControl0BankSelect,
|
|
370
|
-
|
|
375
|
+
isLSB ? midiControllers.lsbForControl0BankSelect : midiControllers.bankSelect,
|
|
376
|
+
v,
|
|
371
377
|
e.ticks
|
|
372
378
|
);
|
|
373
|
-
addEventBefore(
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
// add the bank MSB
|
|
377
|
-
const bankChange = getControllerChange(
|
|
378
|
-
midiChannel,
|
|
379
|
-
midiControllers.bankSelect,
|
|
380
|
-
desiredBank,
|
|
381
|
-
e.ticks
|
|
382
|
-
);
|
|
383
|
-
addEventBefore(bankChange);
|
|
379
|
+
addEventBefore(bankChange);
|
|
380
|
+
};
|
|
384
381
|
|
|
385
|
-
//
|
|
386
|
-
|
|
387
|
-
// do not add gs drum change on the drum channel
|
|
388
|
-
if (change.isDrum)
|
|
382
|
+
// on xg, add lsb
|
|
383
|
+
if (system === "xg")
|
|
389
384
|
{
|
|
390
|
-
|
|
385
|
+
// xg drums: msb can be 120, 126 or 127
|
|
386
|
+
if (change.isDrum)
|
|
391
387
|
{
|
|
392
388
|
SpessaSynthInfo(
|
|
393
|
-
`%cAdding
|
|
389
|
+
`%cAdding XG Drum change on track %c${trackNum}`,
|
|
394
390
|
consoleColors.recognized,
|
|
395
391
|
consoleColors.value
|
|
396
392
|
);
|
|
397
|
-
|
|
393
|
+
addBank(false, isXGDrums(desiredBank) ? desiredBank : 127);
|
|
394
|
+
addBank(true, 0);
|
|
395
|
+
}
|
|
396
|
+
else
|
|
397
|
+
{
|
|
398
|
+
// sfx voice is set via MSB
|
|
399
|
+
if (desiredBank === XG_SFX_VOICE)
|
|
400
|
+
{
|
|
401
|
+
addBank(false, XG_SFX_VOICE);
|
|
402
|
+
addBank(true, 0);
|
|
403
|
+
}
|
|
404
|
+
else
|
|
405
|
+
{
|
|
406
|
+
// add variation as LSB
|
|
407
|
+
addBank(false, 0);
|
|
408
|
+
addBank(true, desiredBank);
|
|
409
|
+
}
|
|
398
410
|
}
|
|
399
|
-
|
|
411
|
+
}
|
|
412
|
+
else
|
|
413
|
+
{
|
|
414
|
+
// add just msb
|
|
415
|
+
addBank(false, desiredBank);
|
|
416
|
+
|
|
417
|
+
if (change.isDrum && midiChannel !== DEFAULT_PERCUSSION)
|
|
400
418
|
{
|
|
419
|
+
// add gs drum change
|
|
401
420
|
SpessaSynthInfo(
|
|
402
|
-
`%cAdding
|
|
421
|
+
`%cAdding GS Drum change on track %c${trackNum}`,
|
|
403
422
|
consoleColors.recognized,
|
|
404
423
|
consoleColors.value
|
|
405
424
|
);
|
|
406
|
-
|
|
407
|
-
desiredBank = 127;
|
|
425
|
+
addEventBefore(getDrumChange(midiChannel, e.ticks));
|
|
408
426
|
}
|
|
409
427
|
}
|
|
410
428
|
}
|
|
@@ -450,12 +468,7 @@ export function modifyMIDI(
|
|
|
450
468
|
|
|
451
469
|
case messageTypes.systemExclusive:
|
|
452
470
|
// check for xg on
|
|
453
|
-
if (
|
|
454
|
-
e.messageData[0] === 0x43 && // Yamaha
|
|
455
|
-
e.messageData[2] === 0x4C && // XG ON
|
|
456
|
-
e.messageData[5] === 0x7E &&
|
|
457
|
-
e.messageData[6] === 0x00
|
|
458
|
-
)
|
|
471
|
+
if (isXGOn(e))
|
|
459
472
|
{
|
|
460
473
|
SpessaSynthInfo("%cXG system on detected", consoleColors.info);
|
|
461
474
|
system = "xg";
|
|
@@ -479,11 +492,7 @@ export function modifyMIDI(
|
|
|
479
492
|
}
|
|
480
493
|
else
|
|
481
494
|
// check for GS on
|
|
482
|
-
if (
|
|
483
|
-
e.messageData[0] === 0x41 // roland
|
|
484
|
-
&& e.messageData[2] === 0x42 // GS
|
|
485
|
-
&& e.messageData[6] === 0x7F // Mode set
|
|
486
|
-
)
|
|
495
|
+
if (isGSOn(e))
|
|
487
496
|
{
|
|
488
497
|
// that's a GS on, we're done here
|
|
489
498
|
addedGs = true;
|
|
@@ -495,10 +504,7 @@ export function modifyMIDI(
|
|
|
495
504
|
}
|
|
496
505
|
else
|
|
497
506
|
// check for GM/2 on
|
|
498
|
-
if (
|
|
499
|
-
e.messageData[0] === 0x7E // non realtime
|
|
500
|
-
&& e.messageData[2] === 0x09 // gm system
|
|
501
|
-
)
|
|
507
|
+
if (isGMOn(e))
|
|
502
508
|
{
|
|
503
509
|
// that's a GM/2 system change, remove it!
|
|
504
510
|
SpessaSynthInfo(
|
|
@@ -511,7 +517,7 @@ export function modifyMIDI(
|
|
|
511
517
|
}
|
|
512
518
|
}
|
|
513
519
|
// check for gs
|
|
514
|
-
if (!addedGs)
|
|
520
|
+
if (!addedGs && desiredProgramChanges.length > 0)
|
|
515
521
|
{
|
|
516
522
|
// gs is not on, add it on the first track at index 0 (or 1 if track name is first)
|
|
517
523
|
let index = 0;
|
|
@@ -7,6 +7,8 @@ import { SpessaSynthGroup, SpessaSynthGroupEnd, SpessaSynthInfo } from "../utils
|
|
|
7
7
|
import { consoleColors } from "../utils/other.js";
|
|
8
8
|
import { writeLittleEndian } from "../utils/byte_functions/little_endian.js";
|
|
9
9
|
import { DEFAULT_PERCUSSION } from "../synthetizer/synth_constants.js";
|
|
10
|
+
import { chooseBank, parseBankSelect } from "../utils/xg_hacks.js";
|
|
11
|
+
import { isGMOn, isGSDrumsOn, isGSOn, isXGOn } from "../utils/sysex_detector.js";
|
|
10
12
|
|
|
11
13
|
/**
|
|
12
14
|
* @enum {string}
|
|
@@ -122,6 +124,7 @@ export function writeRMIDI(
|
|
|
122
124
|
* program: number,
|
|
123
125
|
* drums: boolean,
|
|
124
126
|
* lastBank: MIDIMessage,
|
|
127
|
+
* lastBankLSB: MIDIMessage,
|
|
125
128
|
* hasBankSelect: boolean
|
|
126
129
|
* }[]}
|
|
127
130
|
*/
|
|
@@ -132,6 +135,7 @@ export function writeRMIDI(
|
|
|
132
135
|
program: 0,
|
|
133
136
|
drums: i % 16 === DEFAULT_PERCUSSION, // drums appear on 9 every 16 channels,
|
|
134
137
|
lastBank: undefined,
|
|
138
|
+
lastBankLSB: undefined,
|
|
135
139
|
hasBankSelect: false
|
|
136
140
|
});
|
|
137
141
|
}
|
|
@@ -166,37 +170,18 @@ export function writeRMIDI(
|
|
|
166
170
|
if (status === messageTypes.systemExclusive)
|
|
167
171
|
{
|
|
168
172
|
// check for drum sysex
|
|
169
|
-
if (
|
|
170
|
-
e.messageData[0] !== 0x41 || // roland
|
|
171
|
-
e.messageData[2] !== 0x42 || // GS
|
|
172
|
-
e.messageData[3] !== 0x12 || // GS
|
|
173
|
-
e.messageData[4] !== 0x40 || // system parameter
|
|
174
|
-
(e.messageData[5] & 0x10) === 0 || // part parameter
|
|
175
|
-
e.messageData[6] !== 0x15 // drum part
|
|
176
|
-
)
|
|
173
|
+
if (!isGSDrumsOn(e))
|
|
177
174
|
{
|
|
178
175
|
// check for XG
|
|
179
|
-
if (
|
|
180
|
-
e.messageData[0] === 0x43 && // yamaha
|
|
181
|
-
e.messageData[2] === 0x4C && // sXG ON
|
|
182
|
-
e.messageData[5] === 0x7E &&
|
|
183
|
-
e.messageData[6] === 0x00
|
|
184
|
-
)
|
|
176
|
+
if (isXGOn(e))
|
|
185
177
|
{
|
|
186
178
|
system = "xg";
|
|
187
179
|
}
|
|
188
|
-
else if (
|
|
189
|
-
e.messageData[0] === 0x41 // roland
|
|
190
|
-
&& e.messageData[2] === 0x42 // GS
|
|
191
|
-
&& e.messageData[6] === 0x7F // Mode set
|
|
192
|
-
)
|
|
180
|
+
else if (isGSOn(e))
|
|
193
181
|
{
|
|
194
182
|
system = "gs";
|
|
195
183
|
}
|
|
196
|
-
else if (
|
|
197
|
-
e.messageData[0] === 0x7E // non realtime
|
|
198
|
-
&& e.messageData[2] === 0x09 // gm system
|
|
199
|
-
)
|
|
184
|
+
else if (isGMOn(e))
|
|
200
185
|
{
|
|
201
186
|
system = "gm";
|
|
202
187
|
unwantedSystems.push({
|
|
@@ -213,47 +198,73 @@ export function writeRMIDI(
|
|
|
213
198
|
|
|
214
199
|
// program change
|
|
215
200
|
const chNum = (e.messageStatusByte & 0xF) + portOffset;
|
|
201
|
+
/**
|
|
202
|
+
* @type {{program: number, drums: boolean, lastBank: MIDIMessage, lastBankLSB: MIDIMessage, hasBankSelect: boolean}}
|
|
203
|
+
*/
|
|
216
204
|
const channel = channelsInfo[chNum];
|
|
217
205
|
if (status === messageTypes.programChange)
|
|
218
206
|
{
|
|
207
|
+
const isXG = system === "xg";
|
|
219
208
|
// check if the preset for this program exists
|
|
209
|
+
const initialProgram = e.messageData[0];
|
|
220
210
|
if (channel.drums)
|
|
221
211
|
{
|
|
222
|
-
if (soundfont.presets.findIndex(p => p.program ===
|
|
212
|
+
if (soundfont.presets.findIndex(p => p.program === initialProgram && p.isDrumPreset(
|
|
213
|
+
isXG,
|
|
214
|
+
true
|
|
215
|
+
)) === -1)
|
|
223
216
|
{
|
|
224
217
|
// doesn't exist. pick any preset that has bank 128.
|
|
225
|
-
e.messageData[0] = soundfont.presets.find(p => p.
|
|
218
|
+
e.messageData[0] = soundfont.presets.find(p => p.isDrumPreset(isXG))?.program || 0;
|
|
219
|
+
SpessaSynthInfo(
|
|
220
|
+
`%cNo drum preset %c${initialProgram}%c. Channel %c${chNum}%c. Changing program to ${e.messageData[0]}.`,
|
|
221
|
+
consoleColors.info,
|
|
222
|
+
consoleColors.unrecognized,
|
|
223
|
+
consoleColors.info,
|
|
224
|
+
consoleColors.recognized,
|
|
225
|
+
consoleColors.info
|
|
226
|
+
);
|
|
226
227
|
}
|
|
227
228
|
}
|
|
228
229
|
else
|
|
229
230
|
{
|
|
230
|
-
if (soundfont.presets.findIndex(p => p.program ===
|
|
231
|
+
if (soundfont.presets.findIndex(p => p.program === initialProgram && !p.isDrumPreset(isXG)) === -1)
|
|
231
232
|
{
|
|
232
233
|
// doesn't exist. pick any preset that does not have bank 128.
|
|
233
|
-
e.messageData[0] = soundfont.presets.find(p => p.
|
|
234
|
+
e.messageData[0] = soundfont.presets.find(p => !p.isDrumPreset(isXG))?.program || 0;
|
|
235
|
+
SpessaSynthInfo(
|
|
236
|
+
`%cNo preset %c${initialProgram}%c. Channel %c${chNum}%c. Changing program to ${e.messageData[0]}.`,
|
|
237
|
+
consoleColors.info,
|
|
238
|
+
consoleColors.unrecognized,
|
|
239
|
+
consoleColors.info,
|
|
240
|
+
consoleColors.recognized,
|
|
241
|
+
consoleColors.info
|
|
242
|
+
);
|
|
234
243
|
}
|
|
235
244
|
}
|
|
236
245
|
channel.program = e.messageData[0];
|
|
237
246
|
// check if this preset exists for program and bank
|
|
238
247
|
const realBank = Math.max(0, channel.lastBank?.messageData[1] - mid.bankOffset); // make sure to take the previous bank offset into account
|
|
239
|
-
const
|
|
248
|
+
const bankLSB = (channel?.lastBankLSB?.messageData[1] - mid.bankOffset) || 0;
|
|
240
249
|
if (channel.lastBank === undefined)
|
|
241
250
|
{
|
|
242
251
|
continue;
|
|
243
252
|
}
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
// drums override: set the bank to 127
|
|
247
|
-
channelsInfo[chNum].lastBank.messageData[1] = 127;
|
|
248
|
-
}
|
|
249
|
-
|
|
253
|
+
// adjust bank for XG
|
|
254
|
+
let bank = chooseBank(realBank, bankLSB, channel.drums, isXG);
|
|
250
255
|
if (soundfont.presets.findIndex(p => p.bank === bank && p.program === e.messageData[0]) === -1)
|
|
251
256
|
{
|
|
252
257
|
// no preset with this bank. find this program with any bank
|
|
253
258
|
const targetBank = (soundfont.presets.find(p => p.program === e.messageData[0])?.bank + bankOffset) || bankOffset;
|
|
254
259
|
channel.lastBank.messageData[1] = targetBank;
|
|
260
|
+
if (channel?.lastBankLSB?.messageData)
|
|
261
|
+
{
|
|
262
|
+
channel.lastBankLSB.messageData[1] = targetBank;
|
|
263
|
+
}
|
|
255
264
|
SpessaSynthInfo(
|
|
256
|
-
`%cNo preset %c${bank}:${e.messageData[0]}%c. Changing bank to ${targetBank}.`,
|
|
265
|
+
`%cNo preset %c${bank}:${e.messageData[0]}%c. Channel %c${chNum}%c. Changing bank to ${targetBank}.`,
|
|
266
|
+
consoleColors.info,
|
|
267
|
+
consoleColors.unrecognized,
|
|
257
268
|
consoleColors.info,
|
|
258
269
|
consoleColors.recognized,
|
|
259
270
|
consoleColors.info
|
|
@@ -262,11 +273,21 @@ export function writeRMIDI(
|
|
|
262
273
|
else
|
|
263
274
|
{
|
|
264
275
|
// There is a preset with this bank. Add offset. For drums add the normal offset.
|
|
265
|
-
let drumBank =
|
|
266
|
-
|
|
276
|
+
let drumBank = bank;
|
|
277
|
+
if (system === "xg" && bank === 128)
|
|
278
|
+
{
|
|
279
|
+
bank = 127;
|
|
280
|
+
}
|
|
281
|
+
const newBank = (bank === 128 ? 128 : drumBank) + bankOffset;
|
|
267
282
|
channel.lastBank.messageData[1] = newBank;
|
|
283
|
+
if (channel?.lastBankLSB?.messageData && !channel.drums)
|
|
284
|
+
{
|
|
285
|
+
channel.lastBankLSB.messageData[1] = channel.lastBankLSB.messageData[1] - mid.bankOffset + bankOffset;
|
|
286
|
+
}
|
|
268
287
|
SpessaSynthInfo(
|
|
269
|
-
`%cPreset %c${bank}:${e.messageData[0]}%c exists. Changing bank to ${newBank}.`,
|
|
288
|
+
`%cPreset %c${bank}:${e.messageData[0]}%c exists. Channel %c${chNum}%c. Changing bank to ${newBank}.`,
|
|
289
|
+
consoleColors.info,
|
|
290
|
+
consoleColors.recognized,
|
|
270
291
|
consoleColors.info,
|
|
271
292
|
consoleColors.recognized,
|
|
272
293
|
consoleColors.info
|
|
@@ -274,19 +295,42 @@ export function writeRMIDI(
|
|
|
274
295
|
}
|
|
275
296
|
continue;
|
|
276
297
|
}
|
|
277
|
-
|
|
278
|
-
|
|
298
|
+
|
|
299
|
+
// controller change
|
|
300
|
+
// we only care about bank-selects
|
|
301
|
+
const isLSB = e.messageData[0] === midiControllers.lsbForControl0BankSelect;
|
|
302
|
+
if (e.messageData[0] !== midiControllers.bankSelect && !isLSB)
|
|
279
303
|
{
|
|
280
304
|
continue;
|
|
281
305
|
}
|
|
282
306
|
// bank select
|
|
283
307
|
channel.hasBankSelect = true;
|
|
284
|
-
|
|
308
|
+
const bankNumber = e.messageData[1];
|
|
309
|
+
// interpret
|
|
310
|
+
const intepretation = parseBankSelect(
|
|
311
|
+
channel?.lastBank?.messageData[1] || 0,
|
|
312
|
+
bankNumber,
|
|
313
|
+
system,
|
|
314
|
+
isLSB,
|
|
315
|
+
channel.drums,
|
|
316
|
+
chNum
|
|
317
|
+
);
|
|
318
|
+
if (intepretation.drumsStatus === 2)
|
|
319
|
+
{
|
|
320
|
+
channel.drums = true;
|
|
321
|
+
}
|
|
322
|
+
else if (intepretation.drumsStatus === 1)
|
|
323
|
+
{
|
|
324
|
+
channel.drums = false;
|
|
325
|
+
}
|
|
326
|
+
if (isLSB)
|
|
327
|
+
{
|
|
328
|
+
channel.lastBankLSB = e;
|
|
329
|
+
}
|
|
330
|
+
else
|
|
285
331
|
{
|
|
286
|
-
|
|
287
|
-
channel.drums = e.messageData[1] === 120 || e.messageData[1] === 126 || e.messageData[1] === 127;
|
|
332
|
+
channel.lastBank = e;
|
|
288
333
|
}
|
|
289
|
-
channel.lastBank = e;
|
|
290
334
|
}
|
|
291
335
|
|
|
292
336
|
// add missing bank selects
|
|
@@ -339,7 +383,8 @@ export function writeRMIDI(
|
|
|
339
383
|
const ticks = track[indexToAdd].ticks;
|
|
340
384
|
const targetBank = (soundfont.getPreset(
|
|
341
385
|
0,
|
|
342
|
-
has.program
|
|
386
|
+
has.program,
|
|
387
|
+
system === "xg"
|
|
343
388
|
)?.bank + bankOffset) || bankOffset;
|
|
344
389
|
track.splice(indexToAdd, 0, new MIDIMessage(
|
|
345
390
|
ticks,
|
|
@@ -2,11 +2,13 @@ import { SpessaSynthGroupCollapsed, SpessaSynthGroupEnd, SpessaSynthInfo } from
|
|
|
2
2
|
import { consoleColors } from "../utils/other.js";
|
|
3
3
|
import { messageTypes, midiControllers } from "./midi_message.js";
|
|
4
4
|
import { DEFAULT_PERCUSSION } from "../synthetizer/synth_constants.js";
|
|
5
|
+
import { chooseBank, parseBankSelect } from "../utils/xg_hacks.js";
|
|
6
|
+
import { isGSDrumsOn, isXGOn } from "../utils/sysex_detector.js";
|
|
5
7
|
|
|
6
8
|
/**
|
|
7
9
|
* Gets the used programs and keys for this MIDI file with a given sound bank
|
|
8
10
|
* @this {BasicMIDI}
|
|
9
|
-
* @param soundfont {
|
|
11
|
+
* @param soundfont {BasicSoundBank|WorkletSoundfontManager} - the sound bank
|
|
10
12
|
* @returns {Object<string, Set<string>>}
|
|
11
13
|
*/
|
|
12
14
|
export function getUsedProgramsAndKeys(soundfont)
|
|
@@ -19,7 +21,7 @@ export function getUsedProgramsAndKeys(soundfont)
|
|
|
19
21
|
// Find every bank:program combo and every key:velocity for each. Make sure to care about ports and drums
|
|
20
22
|
const channelsAmount = 16 + mid.midiPortChannelOffsets.reduce((max, cur) => cur > max ? cur : max);
|
|
21
23
|
/**
|
|
22
|
-
* @type {{program: number, bank: number, drums: boolean, string: string}[]}
|
|
24
|
+
* @type {{program: number, bank: number, bankLSB: number, drums: boolean, string: string, actualBank: number}[]}
|
|
23
25
|
*/
|
|
24
26
|
const channelPresets = [];
|
|
25
27
|
for (let i = 0; i < channelsAmount; i++)
|
|
@@ -28,18 +30,24 @@ export function getUsedProgramsAndKeys(soundfont)
|
|
|
28
30
|
channelPresets.push({
|
|
29
31
|
program: 0,
|
|
30
32
|
bank: bank,
|
|
33
|
+
bankLSB: 0,
|
|
34
|
+
actualBank: bank,
|
|
31
35
|
drums: i % 16 === DEFAULT_PERCUSSION, // drums appear on 9 every 16 channels,
|
|
32
36
|
string: `${bank}:0`
|
|
33
37
|
});
|
|
34
38
|
}
|
|
35
39
|
|
|
40
|
+
// check for xg
|
|
41
|
+
let system = "gs";
|
|
42
|
+
|
|
36
43
|
function updateString(ch)
|
|
37
44
|
{
|
|
45
|
+
const bank = chooseBank(ch.bank, ch.bankLSB, ch.drums, system === "xg");
|
|
38
46
|
// check if this exists in the soundfont
|
|
39
|
-
let exists = soundfont.getPreset(
|
|
40
|
-
ch.
|
|
47
|
+
let exists = soundfont.getPreset(bank, ch.program, system === "xg");
|
|
48
|
+
ch.actualBank = exists.bank;
|
|
41
49
|
ch.program = exists.program;
|
|
42
|
-
ch.string = ch.
|
|
50
|
+
ch.string = ch.actualBank + ":" + ch.program;
|
|
43
51
|
if (!usedProgramsAndKeys[ch.string])
|
|
44
52
|
{
|
|
45
53
|
SpessaSynthInfo(
|
|
@@ -85,8 +93,11 @@ export function getUsedProgramsAndKeys(soundfont)
|
|
|
85
93
|
}
|
|
86
94
|
|
|
87
95
|
const ports = mid.midiPorts.slice();
|
|
88
|
-
//
|
|
89
|
-
|
|
96
|
+
// initialize
|
|
97
|
+
channelPresets.forEach(c =>
|
|
98
|
+
{
|
|
99
|
+
updateString(c);
|
|
100
|
+
});
|
|
90
101
|
while (remainingTracks > 0)
|
|
91
102
|
{
|
|
92
103
|
let trackNum = findFirstEventIndex();
|
|
@@ -124,7 +135,8 @@ export function getUsedProgramsAndKeys(soundfont)
|
|
|
124
135
|
break;
|
|
125
136
|
|
|
126
137
|
case messageTypes.controllerChange:
|
|
127
|
-
|
|
138
|
+
const isLSB = event.messageData[0] === midiControllers.lsbForControl0BankSelect;
|
|
139
|
+
if (event.messageData[0] !== midiControllers.bankSelect && !isLSB)
|
|
128
140
|
{
|
|
129
141
|
// we only care about bank select
|
|
130
142
|
continue;
|
|
@@ -136,24 +148,43 @@ export function getUsedProgramsAndKeys(soundfont)
|
|
|
136
148
|
}
|
|
137
149
|
const bank = event.messageData[1];
|
|
138
150
|
const realBank = Math.max(0, bank - mid.bankOffset);
|
|
139
|
-
if (
|
|
151
|
+
if (isLSB)
|
|
140
152
|
{
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
153
|
+
ch.bankLSB = realBank;
|
|
154
|
+
}
|
|
155
|
+
else
|
|
156
|
+
{
|
|
157
|
+
ch.bank = realBank;
|
|
158
|
+
}
|
|
159
|
+
// interpret the bank
|
|
160
|
+
const intepretation = parseBankSelect(
|
|
161
|
+
ch.bank,
|
|
162
|
+
realBank,
|
|
163
|
+
system,
|
|
164
|
+
isLSB,
|
|
165
|
+
ch.drums,
|
|
166
|
+
channel
|
|
167
|
+
);
|
|
168
|
+
switch (intepretation.drumsStatus)
|
|
169
|
+
{
|
|
170
|
+
case 0:
|
|
171
|
+
// no change
|
|
172
|
+
break;
|
|
173
|
+
|
|
174
|
+
case 1:
|
|
175
|
+
// drums changed to off
|
|
145
176
|
// drum change is a program change
|
|
146
|
-
ch.drums =
|
|
147
|
-
ch.bank = ch.drums ? 128 : realBank;
|
|
177
|
+
ch.drums = false;
|
|
148
178
|
updateString(ch);
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
179
|
+
break;
|
|
180
|
+
|
|
181
|
+
case 2:
|
|
182
|
+
// drums changed to on
|
|
183
|
+
// drum change is a program change
|
|
184
|
+
ch.drums = true;
|
|
185
|
+
updateString(ch);
|
|
186
|
+
break;
|
|
155
187
|
}
|
|
156
|
-
channelPresets[channel].bank = realBank;
|
|
157
188
|
// do not update the data, bank change doesn't change the preset
|
|
158
189
|
break;
|
|
159
190
|
|
|
@@ -163,31 +194,21 @@ export function getUsedProgramsAndKeys(soundfont)
|
|
|
163
194
|
// that's a note off
|
|
164
195
|
continue;
|
|
165
196
|
}
|
|
166
|
-
updateString(ch);
|
|
167
197
|
usedProgramsAndKeys[ch.string].add(`${event.messageData[0]}-${event.messageData[1]}`);
|
|
168
198
|
break;
|
|
169
199
|
|
|
170
200
|
case messageTypes.systemExclusive:
|
|
171
201
|
// check for drum sysex
|
|
172
|
-
if (
|
|
173
|
-
event.messageData[0] !== 0x41 || // roland
|
|
174
|
-
event.messageData[2] !== 0x42 || // GS
|
|
175
|
-
event.messageData[3] !== 0x12 || // GS
|
|
176
|
-
event.messageData[4] !== 0x40 || // system parameter
|
|
177
|
-
(event.messageData[5] & 0x10) === 0 || // part parameter
|
|
178
|
-
event.messageData[6] !== 0x15 // drum pars
|
|
179
|
-
|
|
180
|
-
)
|
|
202
|
+
if (!isGSDrumsOn(event))
|
|
181
203
|
{
|
|
182
204
|
// check for XG
|
|
183
|
-
if (
|
|
184
|
-
event.messageData[0] === 0x43 && // yamaha
|
|
185
|
-
event.messageData[2] === 0x4C && // sXG ON
|
|
186
|
-
event.messageData[5] === 0x7E &&
|
|
187
|
-
event.messageData[6] === 0x00
|
|
188
|
-
)
|
|
205
|
+
if (isXGOn(event))
|
|
189
206
|
{
|
|
190
207
|
system = "xg";
|
|
208
|
+
SpessaSynthInfo(
|
|
209
|
+
"%cXG on detected!",
|
|
210
|
+
consoleColors.recognized
|
|
211
|
+
);
|
|
191
212
|
}
|
|
192
213
|
continue;
|
|
193
214
|
}
|
|
@@ -195,7 +216,6 @@ export function getUsedProgramsAndKeys(soundfont)
|
|
|
195
216
|
const isDrum = !!(event.messageData[7] > 0 && event.messageData[5] >> 4);
|
|
196
217
|
ch = channelPresets[sysexChannel];
|
|
197
218
|
ch.drums = isDrum;
|
|
198
|
-
ch.bank = isDrum ? 128 : 0;
|
|
199
219
|
updateString(ch);
|
|
200
220
|
break;
|
|
201
221
|
|
package/package.json
CHANGED
package/sequencer/sequencer.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { MIDI } from "../midi_parser/midi_loader.js";
|
|
2
1
|
import { Synthetizer } from "../synthetizer/synthetizer.js";
|
|
3
2
|
import { messageTypes } from "../midi_parser/midi_message.js";
|
|
4
3
|
import { workletMessageType } from "../synthetizer/worklet_system/message_protocol/worklet_message.js";
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import { generatorTypes } from "./generator.js";
|
|
11
11
|
import { Modulator } from "./modulator.js";
|
|
12
|
+
import { isXGDrums } from "../../utils/xg_hacks.js";
|
|
12
13
|
|
|
13
14
|
export class BasicPreset
|
|
14
15
|
{
|
|
@@ -78,6 +79,17 @@ export class BasicPreset
|
|
|
78
79
|
this.defaultModulators = modulators;
|
|
79
80
|
}
|
|
80
81
|
|
|
82
|
+
/**
|
|
83
|
+
* @param allowXG {boolean}
|
|
84
|
+
* @param allowSFX {boolean}
|
|
85
|
+
* @returns {boolean}
|
|
86
|
+
*/
|
|
87
|
+
isDrumPreset(allowXG, allowSFX = false)
|
|
88
|
+
{
|
|
89
|
+
// sfx is not cool
|
|
90
|
+
return this.bank === 128 || (allowXG && isXGDrums(this.bank) && (this.bank !== 126 || allowSFX));
|
|
91
|
+
}
|
|
92
|
+
|
|
81
93
|
deletePreset()
|
|
82
94
|
{
|
|
83
95
|
this.presetZones.forEach(z => z.deleteZone());
|