spessasynth_core 3.26.4 → 3.26.6

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/package.json +1 -1
  2. package/src/midi/midi_tools/used_keys_loaded.js +26 -8
  3. package/src/sequencer/song_control.js +20 -25
  4. package/src/soundfont/basic_soundfont/basic_preset.js +1 -40
  5. package/src/soundfont/basic_soundfont/generator.js +9 -1
  6. package/src/synthetizer/audio_engine/engine_components/controller_tables.js +6 -5
  7. package/src/synthetizer/audio_engine/engine_components/dynamic_modulator_system.js +95 -0
  8. package/src/synthetizer/audio_engine/engine_components/midi_audio_channel.js +11 -11
  9. package/src/synthetizer/audio_engine/engine_components/soundfont_manager.js +45 -17
  10. package/src/synthetizer/audio_engine/engine_components/stereo_panner.js +2 -2
  11. package/src/synthetizer/audio_engine/engine_components/voice.js +51 -51
  12. package/src/synthetizer/audio_engine/engine_methods/controller_control/reset_controllers.js +3 -6
  13. package/src/synthetizer/audio_engine/engine_methods/data_entry/data_entry_coarse.js +20 -21
  14. package/src/synthetizer/audio_engine/engine_methods/note_on.js +28 -8
  15. package/src/synthetizer/audio_engine/engine_methods/program_change.js +4 -39
  16. package/src/synthetizer/audio_engine/engine_methods/render_voice.js +18 -11
  17. package/src/synthetizer/audio_engine/engine_methods/soundfont_management/embedded_sound_bank.js +43 -0
  18. package/src/synthetizer/audio_engine/engine_methods/soundfont_management/get_preset.js +0 -22
  19. package/src/synthetizer/audio_engine/engine_methods/soundfont_management/update_preset_list.js +5 -20
  20. package/src/synthetizer/audio_engine/engine_methods/stopping_notes/kill_note.js +3 -0
  21. package/src/synthetizer/audio_engine/engine_methods/stopping_notes/note_off.js +2 -1
  22. package/src/synthetizer/audio_engine/engine_methods/system_exclusive.js +442 -173
  23. package/src/synthetizer/audio_engine/engine_methods/tuning_control/channel_pressure.js +1 -0
  24. package/src/synthetizer/audio_engine/main_processor.js +27 -20
  25. package/src/synthetizer/synth_constants.js +3 -1
  26. package/src/synthetizer/audio_engine/engine_methods/soundfont_management/clear_sound_font.js +0 -32
  27. package/src/synthetizer/audio_engine/engine_methods/soundfont_management/set_embedded_sound_font.js +0 -33
@@ -6,6 +6,9 @@ import { isSystemXG } from "../../../utils/xg_hacks.js";
6
6
  import { masterParameterType } from "./controller_control/master_parameters.js";
7
7
  import { readBytesAsString } from "../../../utils/byte_functions/string.js";
8
8
  import { synthDisplayTypes } from "../engine_components/enums.js";
9
+ import { customControllers, NON_CC_INDEX_OFFSET } from "../engine_components/controller_tables.js";
10
+ import { modulatorSources } from "../../../soundfont/basic_soundfont/modulator.js";
11
+ import { generatorTypes } from "../../../soundfont/basic_soundfont/generator.js";
9
12
 
10
13
  /**
11
14
  * KeyNum: tuning
@@ -40,29 +43,54 @@ function getTuning(byte1, byte2, byte3)
40
43
  return { midiNote: midiNote, centTuning: fraction * 0.0061 };
41
44
  }
42
45
 
43
-
44
46
  /**
45
47
  * Executes a system exclusive
46
- * @param messageData {number[]|IndexedByteArray} - the message data without f0
48
+ * @param syx {number[]|IndexedByteArray} - the message data without f0
47
49
  * @param channelOffset {number}
48
50
  * @this {SpessaSynthProcessor}
49
51
  */
50
- export function systemExclusive(messageData, channelOffset = 0)
52
+ export function systemExclusive(syx, channelOffset = 0)
51
53
  {
52
- const type = messageData[0];
53
- if (this.deviceID !== ALL_CHANNELS_OR_DIFFERENT_ACTION && messageData[1] !== 0x7F)
54
+ const type = syx[0];
55
+ if (this.deviceID !== ALL_CHANNELS_OR_DIFFERENT_ACTION && syx[1] !== 0x7F)
54
56
  {
55
- if (this.deviceID !== messageData[1])
57
+ if (this.deviceID !== syx[1])
56
58
  {
57
59
  // not our device ID
58
60
  return;
59
61
  }
60
62
  }
63
+
64
+ function niceLogging(channel, value, what, units)
65
+ {
66
+ SpessaSynthInfo(
67
+ `%cChannel %c${channel}%c ${what}. %c${value} ${units}%c, with %c${arrayToHexString(syx)}`,
68
+ consoleColors.info,
69
+ consoleColors.recognized,
70
+ consoleColors.info,
71
+ consoleColors.value,
72
+ consoleColors.info,
73
+ consoleColors.value
74
+ );
75
+ }
76
+
77
+ function notRecognized()
78
+ {
79
+ // this is some other GS sysex...
80
+ SpessaSynthWarn(
81
+ `%cUnrecognized Roland %cGS %cSysEx: %c${arrayToHexString(syx)}`,
82
+ consoleColors.warn,
83
+ consoleColors.recognized,
84
+ consoleColors.warn,
85
+ consoleColors.unrecognized
86
+ );
87
+ }
88
+
61
89
  switch (type)
62
90
  {
63
91
  default:
64
92
  SpessaSynthWarn(
65
- `%cUnrecognized SysEx: %c${arrayToHexString(messageData)}`,
93
+ `%cUnrecognized SysEx: %c${arrayToHexString(syx)}`,
66
94
  consoleColors.warn,
67
95
  consoleColors.unrecognized
68
96
  );
@@ -71,16 +99,16 @@ export function systemExclusive(messageData, channelOffset = 0)
71
99
  // non realtime
72
100
  case 0x7E:
73
101
  case 0x7F:
74
- switch (messageData[2])
102
+ switch (syx[2])
75
103
  {
76
104
  case 0x04:
77
105
  let cents;
78
106
  // device control
79
- switch (messageData[3])
107
+ switch (syx[3])
80
108
  {
81
109
  case 0x01:
82
110
  // main volume
83
- const vol = messageData[5] << 7 | messageData[4];
111
+ const vol = syx[5] << 7 | syx[4];
84
112
  this.setMIDIVolume(vol / 16384);
85
113
  SpessaSynthInfo(
86
114
  `%cMaster Volume. Volume: %c${vol}`,
@@ -92,7 +120,7 @@ export function systemExclusive(messageData, channelOffset = 0)
92
120
  case 0x02:
93
121
  // main balance
94
122
  // midi spec page 62
95
- const balance = messageData[5] << 7 | messageData[4];
123
+ const balance = syx[5] << 7 | syx[4];
96
124
  const pan = (balance - 8192) / 8192;
97
125
  this.setMasterParameter(masterParameterType.masterPan, pan);
98
126
  SpessaSynthInfo(
@@ -105,7 +133,7 @@ export function systemExclusive(messageData, channelOffset = 0)
105
133
 
106
134
  case 0x03:
107
135
  // fine-tuning
108
- const tuningValue = ((messageData[5] << 7) | messageData[6]) - 8192;
136
+ const tuningValue = ((syx[5] << 7) | syx[6]) - 8192;
109
137
  cents = Math.floor(tuningValue / 81.92); // [-100;+99] cents range
110
138
  this.setMasterTuning(cents);
111
139
  SpessaSynthInfo(
@@ -118,7 +146,7 @@ export function systemExclusive(messageData, channelOffset = 0)
118
146
  case 0x04:
119
147
  // coarse tuning
120
148
  // lsb is ignored
121
- const semitones = messageData[5] - 64;
149
+ const semitones = syx[5] - 64;
122
150
  cents = semitones * 100;
123
151
  this.setMasterTuning(cents);
124
152
  SpessaSynthInfo(
@@ -130,7 +158,7 @@ export function systemExclusive(messageData, channelOffset = 0)
130
158
 
131
159
  default:
132
160
  SpessaSynthWarn(
133
- `%cUnrecognized MIDI Device Control Real-time message: %c${arrayToHexString(messageData)}`,
161
+ `%cUnrecognized MIDI Device Control Real-time message: %c${arrayToHexString(syx)}`,
134
162
  consoleColors.warn,
135
163
  consoleColors.unrecognized
136
164
  );
@@ -139,12 +167,12 @@ export function systemExclusive(messageData, channelOffset = 0)
139
167
 
140
168
  case 0x09:
141
169
  // gm system related
142
- if (messageData[3] === 0x01)
170
+ if (syx[3] === 0x01)
143
171
  {
144
172
  SpessaSynthInfo("%cGM1 system on", consoleColors.info);
145
173
  this.setSystem("gm");
146
174
  }
147
- else if (messageData[3] === 0x03)
175
+ else if (syx[3] === 0x03)
148
176
  {
149
177
  SpessaSynthInfo("%cGM2 system on", consoleColors.info);
150
178
  this.setSystem("gm2");
@@ -160,18 +188,18 @@ export function systemExclusive(messageData, channelOffset = 0)
160
188
  // https://midi.org/midi-tuning-updated-specification
161
189
  case 0x08:
162
190
  let currentMessageIndex = 4;
163
- switch (messageData[3])
191
+ switch (syx[3])
164
192
  {
165
193
  // bulk tuning dump: all 128 notes
166
194
  case 0x01:
167
- const program = messageData[currentMessageIndex++];
195
+ const program = syx[currentMessageIndex++];
168
196
  // read the name
169
- messageData.currentIndex = currentMessageIndex;
170
- const tuningName = readBytesAsString(messageData, 16);
197
+ syx.currentIndex = currentMessageIndex;
198
+ const tuningName = readBytesAsString(syx, 16);
171
199
  currentMessageIndex += 16;
172
- if (messageData.length < 384)
200
+ if (syx.length < 384)
173
201
  {
174
- SpessaSynthWarn(`The Bulk Tuning Dump is too short! (${messageData.length} bytes, at least 384 are expected)`);
202
+ SpessaSynthWarn(`The Bulk Tuning Dump is too short! (${syx.length} bytes, at least 384 are expected)`);
175
203
  return;
176
204
  }
177
205
  // 128 frequencies follow
@@ -179,9 +207,9 @@ export function systemExclusive(messageData, channelOffset = 0)
179
207
  {
180
208
  // set the given tuning to the program
181
209
  this.tunings[program][i] = getTuning(
182
- messageData[currentMessageIndex++],
183
- messageData[currentMessageIndex++],
184
- messageData[currentMessageIndex++]
210
+ syx[currentMessageIndex++],
211
+ syx[currentMessageIndex++],
212
+ syx[currentMessageIndex++]
185
213
  );
186
214
  }
187
215
  SpessaSynthInfo(
@@ -197,21 +225,21 @@ export function systemExclusive(messageData, channelOffset = 0)
197
225
  // single note change bank
198
226
  case 0x02:
199
227
  case 0x07:
200
- if (messageData[3] === 0x07)
228
+ if (syx[3] === 0x07)
201
229
  {
202
230
  // skip the bank
203
231
  currentMessageIndex++;
204
232
  }
205
233
  // get program and number of changes
206
- const tuningProgram = messageData[currentMessageIndex++];
207
- const numberOfChanges = messageData[currentMessageIndex++];
234
+ const tuningProgram = syx[currentMessageIndex++];
235
+ const numberOfChanges = syx[currentMessageIndex++];
208
236
  for (let i = 0; i < numberOfChanges; i++)
209
237
  {
210
238
  // set the given tuning to the program
211
- this.tunings[tuningProgram][messageData[currentMessageIndex++]] = getTuning(
212
- messageData[currentMessageIndex++],
213
- messageData[currentMessageIndex++],
214
- messageData[currentMessageIndex++]
239
+ this.tunings[tuningProgram][syx[currentMessageIndex++]] = getTuning(
240
+ syx[currentMessageIndex++],
241
+ syx[currentMessageIndex++],
242
+ syx[currentMessageIndex++]
215
243
  );
216
244
  }
217
245
  SpessaSynthInfo(
@@ -230,12 +258,12 @@ export function systemExclusive(messageData, channelOffset = 0)
230
258
  // get tuning:
231
259
  const newOctaveTuning = new Int8Array(12);
232
260
  // start from bit 7
233
- if (messageData[3] === 0x08)
261
+ if (syx[3] === 0x08)
234
262
  {
235
263
  // 1 byte tuning: 0 is -64 cents, 64 is 0, 127 is +63
236
264
  for (let i = 0; i < 12; i++)
237
265
  {
238
- newOctaveTuning[i] = messageData[7 + i] - 64;
266
+ newOctaveTuning[i] = syx[7 + i] - 64;
239
267
  }
240
268
  }
241
269
  else
@@ -243,17 +271,17 @@ export function systemExclusive(messageData, channelOffset = 0)
243
271
  // 2 byte tuning. Like fine tune: 0 is -100 cents, 8192 is 0 cents, 16,383 is +100 cents
244
272
  for (let i = 0; i < 24; i += 2)
245
273
  {
246
- const tuning = ((messageData[7 + i] << 7) | messageData[8 + i]) - 8192;
274
+ const tuning = ((syx[7 + i] << 7) | syx[8 + i]) - 8192;
247
275
  newOctaveTuning[i / 2] = Math.floor(tuning / 81.92); // map to [-100;+99] cents
248
276
  }
249
277
  }
250
278
  // apply to channels (ordered from 0)
251
279
  // bit 1: 14 and 15
252
- if ((messageData[4] & 1) === 1)
280
+ if ((syx[4] & 1) === 1)
253
281
  {
254
282
  this.midiAudioChannels[14 + channelOffset].setOctaveTuning(newOctaveTuning);
255
283
  }
256
- if (((messageData[4] >> 1) & 1) === 1)
284
+ if (((syx[4] >> 1) & 1) === 1)
257
285
  {
258
286
  this.midiAudioChannels[15 + channelOffset].setOctaveTuning(newOctaveTuning);
259
287
  }
@@ -261,7 +289,7 @@ export function systemExclusive(messageData, channelOffset = 0)
261
289
  // bit 2: channels 7 to 13
262
290
  for (let i = 0; i < 7; i++)
263
291
  {
264
- const bit = (messageData[5] >> i) & 1;
292
+ const bit = (syx[5] >> i) & 1;
265
293
  if (bit === 1)
266
294
  {
267
295
  this.midiAudioChannels[7 + i + channelOffset].setOctaveTuning(newOctaveTuning);
@@ -271,7 +299,7 @@ export function systemExclusive(messageData, channelOffset = 0)
271
299
  // bit 3: channels 0 to 16
272
300
  for (let i = 0; i < 7; i++)
273
301
  {
274
- const bit = (messageData[6] >> i) & 1;
302
+ const bit = (syx[6] >> i) & 1;
275
303
  if (bit === 1)
276
304
  {
277
305
  this.midiAudioChannels[i + channelOffset].setOctaveTuning(newOctaveTuning);
@@ -280,7 +308,7 @@ export function systemExclusive(messageData, channelOffset = 0)
280
308
 
281
309
  SpessaSynthInfo(
282
310
  `%cMIDI Octave Scale ${
283
- messageData[3] === 0x08 ? "(1 byte)" : "(2 bytes)"
311
+ syx[3] === 0x08 ? "(1 byte)" : "(2 bytes)"
284
312
  } tuning via Tuning: %c${newOctaveTuning.join(" ")}`,
285
313
  consoleColors.info,
286
314
  consoleColors.value
@@ -289,7 +317,7 @@ export function systemExclusive(messageData, channelOffset = 0)
289
317
 
290
318
  default:
291
319
  SpessaSynthWarn(
292
- `%cUnrecognized MIDI Tuning standard message: %c${arrayToHexString(messageData)}`,
320
+ `%cUnrecognized MIDI Tuning standard message: %c${arrayToHexString(syx)}`,
293
321
  consoleColors.warn,
294
322
  consoleColors.unrecognized
295
323
  );
@@ -299,7 +327,7 @@ export function systemExclusive(messageData, channelOffset = 0)
299
327
 
300
328
  default:
301
329
  SpessaSynthWarn(
302
- `%cUnrecognized MIDI Realtime/non realtime message: %c${arrayToHexString(messageData)}`,
330
+ `%cUnrecognized MIDI Realtime/non realtime message: %c${arrayToHexString(syx)}`,
303
331
  consoleColors.warn,
304
332
  consoleColors.unrecognized
305
333
  );
@@ -311,54 +339,24 @@ export function systemExclusive(messageData, channelOffset = 0)
311
339
  // http://www.bandtrax.com.au/sysex.htm
312
340
  // https://cdn.roland.com/assets/media/pdf/AT-20R_30R_MI.pdf
313
341
  case 0x41:
314
-
315
- function notRecognized()
316
- {
317
- // this is some other GS sysex...
318
- SpessaSynthWarn(
319
- `%cUnrecognized Roland %cGS %cSysEx: %c${arrayToHexString(messageData)}`,
320
- consoleColors.warn,
321
- consoleColors.recognized,
322
- consoleColors.warn,
323
- consoleColors.unrecognized
324
- );
325
- }
326
-
327
- if (messageData[2] === 0x42 && messageData[3] === 0x12)
342
+ if (syx[2] === 0x42 && syx[3] === 0x12)
328
343
  {
329
344
  // this is a GS sysex
330
- // messageData[5] and [6] is the system parameter, messageData[7] is the value
331
- const messageValue = messageData[7];
332
- if (messageData[6] === 0x7F)
333
- {
334
- // GS mode set
335
- if (messageValue === 0x00)
336
- {
337
- // this is a GS reset
338
- SpessaSynthInfo("%cGS Reset received!", consoleColors.info);
339
- this.resetAllControllers(false);
340
- this.setSystem("gs");
341
- }
342
- else if (messageValue === 0x7F)
343
- {
344
- // GS mode off
345
- SpessaSynthInfo("%cGS system off, switching to GM2", consoleColors.info);
346
- this.resetAllControllers(false);
347
- this.setSystem("gm2");
348
- }
349
- return;
350
- }
351
- else if (messageData[4] === 0x40)
345
+ const messageValue = syx[7];
346
+ // syx[5] and [6] is the system parameter, syx[7] is the value
347
+ // either patch common or SC-88 mode set
348
+ if (syx[4] === 0x40 || (syx[4] === 0x00 && syx[6] === 0x7F))
352
349
  {
353
- // this is a system parameter
354
- if ((messageData[5] & 0x10) > 0)
350
+ // this is a channel parameter
351
+ if ((syx[5] & 0x10) > 0)
355
352
  {
356
353
  // this is an individual part (channel) parameter
357
354
  // determine the channel 0 means channel 10 (default), 1 means 1 etc.
358
- const channel = [9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 11, 12, 13, 14, 15][messageData[5] & 0x0F] + channelOffset;
355
+ // SC88 manual page 196
356
+ const channel = [9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 11, 12, 13, 14, 15][syx[5] & 0x0F] + channelOffset;
359
357
  // for example, 0x1A means A = 11, which corresponds to channel 12 (counting from 1)
360
358
  const channelObject = this.midiAudioChannels[channel];
361
- switch (messageData[6])
359
+ switch (syx[6])
362
360
  {
363
361
  default:
364
362
  // this is some other GS sysex...
@@ -367,14 +365,14 @@ export function systemExclusive(messageData, channelOffset = 0)
367
365
 
368
366
  case 0x15:
369
367
  // this is the Use for Drum Part sysex (multiple drums)
370
- const isDrums = messageValue > 0 && messageData[5] >> 4; // if set to other than 0, is a drum channel
368
+ const isDrums = messageValue > 0 && syx[5] >> 4; // if set to other than 0, is a drum channel
371
369
  channelObject.setDrums(isDrums);
372
370
  SpessaSynthInfo(
373
371
  `%cChannel %c${channel}%c ${isDrums ?
374
372
  "is now a drum channel"
375
373
  :
376
374
  "now isn't a drum channel"
377
- }%c via: %c${arrayToHexString(messageData)}`,
375
+ }%c via: %c${arrayToHexString(syx)}`,
378
376
  consoleColors.info,
379
377
  consoleColors.value,
380
378
  consoleColors.recognized,
@@ -386,17 +384,8 @@ export function systemExclusive(messageData, channelOffset = 0)
386
384
  case 0x16:
387
385
  // this is the pitch key shift sysex
388
386
  const keyShift = messageValue - 64;
389
- channelObject.transposeChannel(keyShift);
390
- SpessaSynthInfo(
391
- `%cChannel %c${channel}%c pitch shift. Semitones %c${keyShift}%c, with %c${arrayToHexString(
392
- messageData)}`,
393
- consoleColors.info,
394
- consoleColors.recognized,
395
- consoleColors.info,
396
- consoleColors.value,
397
- consoleColors.info,
398
- consoleColors.value
399
- );
387
+ channelObject.setCustomController(customControllers.channelKeyShift, keyShift);
388
+ niceLogging(channel, keyShift, "key shift", "keys");
400
389
  return;
401
390
 
402
391
  // pan position
@@ -444,96 +433,376 @@ export function systemExclusive(messageData, channelOffset = 0)
444
433
  case 0x4A:
445
434
  case 0x4B:
446
435
  // scale tuning: up to 12 bytes
447
- const tuningBytes = messageData.length - 9; // data starts at 7, minus checksum and f7
436
+ const tuningBytes = syx.length - 9; // data starts at 7, minus checksum and f7
448
437
  // read em bytes
449
438
  const newTuning = new Int8Array(12);
450
439
  for (let i = 0; i < tuningBytes; i++)
451
440
  {
452
- newTuning[i] = messageData[i + 7] - 64;
441
+ newTuning[i] = syx[i + 7] - 64;
453
442
  }
454
443
  channelObject.setOctaveTuning(newTuning);
455
444
  const cents = messageValue - 64;
456
- SpessaSynthInfo(
457
- `%cChannel %c${channel}%c octave scale tuning. Cents %c${newTuning.join(
458
- " ")}%c, with %c${arrayToHexString(messageData)}`,
459
- consoleColors.info,
460
- consoleColors.recognized,
461
- consoleColors.info,
462
- consoleColors.value,
463
- consoleColors.info,
464
- consoleColors.value
465
- );
445
+ niceLogging(channel, newTuning.join(" "), "octave scale tuning", "cents");
466
446
  channelObject.setTuning(cents);
467
447
  break;
468
448
  }
469
449
  return;
470
450
  }
471
451
  else
472
- // this is a global system parameter
473
- if (messageData[5] === 0x00 && messageData[6] === 0x06)
452
+ // this is a channel parameter also
453
+ if ((syx[5] & 0x20) > 0)
474
454
  {
475
- // roland master pan
476
- SpessaSynthInfo(
477
- `%cRoland GS Master Pan set to: %c${messageValue}%c with: %c${arrayToHexString(
478
- messageData)}`,
479
- consoleColors.info,
480
- consoleColors.value,
481
- consoleColors.info,
482
- consoleColors.value
483
- );
484
- this.setMasterParameter(masterParameterType.masterPan, (messageValue - 64) / 64);
455
+ // this is an individual part (channel) parameter
456
+ // determine the channel 0 means channel 10 (default), 1 means 1 etc.
457
+ const channel = [9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 11, 12, 13, 14, 15][syx[5] & 0x0F] + channelOffset;
458
+ // for example, 0x1A means A = 11, which corresponds to channel 12 (counting from 1)
459
+ const channelObject = this.midiAudioChannels[channel];
460
+ const centeredValue = (messageValue - 64);
461
+ const normalizedValue = centeredValue / 64;
462
+ const normalizedNotCentered = messageValue / 128;
463
+
464
+ // setup receivers for cc to parameter (sc-88 manual page 198)
465
+ const setupReceivers = (source, sourceName, bipolar = false) =>
466
+ {
467
+ switch (syx[6] & 0x0F)
468
+ {
469
+ case 0x00:
470
+ // see https://github.com/spessasus/SpessaSynth/issues/154
471
+ // pitch control
472
+ channelObject.sysExModulators.setModulator(
473
+ source,
474
+ generatorTypes.fineTune,
475
+ centeredValue * 100,
476
+ bipolar
477
+ );
478
+ niceLogging(channel, centeredValue, `${sourceName} pitch control`, "semitones");
479
+ break;
480
+
481
+ case 0x01:
482
+ // cutoff
483
+ channelObject.sysExModulators.setModulator(
484
+ source,
485
+ generatorTypes.initialFilterFc,
486
+ normalizedValue * 9600,
487
+ bipolar
488
+ );
489
+ niceLogging(
490
+ channel,
491
+ normalizedValue * 9600,
492
+ `${sourceName} pitch control`,
493
+ "cents"
494
+ );
495
+ break;
496
+
497
+ case 0x02:
498
+ // amplitude
499
+ channelObject.sysExModulators.setModulator(
500
+ source,
501
+ generatorTypes.initialAttenuation,
502
+ normalizedValue * 960, // spec says "100%" so 960cB in sf2
503
+ bipolar
504
+ );
505
+ niceLogging(channel, normalizedValue * 960, `${sourceName} amplitude`, "cB");
506
+ break;
507
+
508
+ // rate control is ignored as it is in hertz
509
+
510
+ case 0x04:
511
+ // LFO1 pitch depth
512
+ channelObject.sysExModulators.setModulator(
513
+ source,
514
+ generatorTypes.vibLfoToPitch,
515
+ normalizedNotCentered * 600
516
+ );
517
+ niceLogging(
518
+ channel,
519
+ normalizedNotCentered * 600,
520
+ `${sourceName} LFO1 pitch depth`,
521
+ "cents"
522
+ );
523
+ break;
524
+
525
+ case 0x05:
526
+ // LFO1 filter depth
527
+ channelObject.sysExModulators.setModulator(
528
+ source,
529
+ generatorTypes.vibLfoToFilterFc,
530
+ normalizedNotCentered * 2400
531
+ );
532
+ niceLogging(
533
+ channel,
534
+ normalizedNotCentered * 2400,
535
+ `${sourceName} LFO1 filter depth`,
536
+ "cents"
537
+ );
538
+ break;
539
+
540
+ case 0x06:
541
+ // LFO1 amplitude depth
542
+ channelObject.sysExModulators.setModulator(
543
+ source,
544
+ generatorTypes.vibLfoToVolume,
545
+ normalizedNotCentered * 960
546
+ );
547
+ niceLogging(
548
+ channel,
549
+ normalizedNotCentered * 960,
550
+ `${sourceName} LFO1 amplitude depth`,
551
+ "cB"
552
+ );
553
+ break;
554
+
555
+ // rate control is ignored as it is in hertz
556
+
557
+ case 0x08:
558
+ // LFO2 pitch depth
559
+ channelObject.sysExModulators.setModulator(
560
+ source,
561
+ generatorTypes.modLfoToPitch,
562
+ normalizedNotCentered * 600
563
+ );
564
+ niceLogging(
565
+ channel,
566
+ normalizedNotCentered * 600,
567
+ `${sourceName} LFO2 pitch depth`,
568
+ "cents"
569
+ );
570
+ break;
571
+
572
+ case 0x09:
573
+ // LFO2 filter depth
574
+ channelObject.sysExModulators.setModulator(
575
+ source,
576
+ generatorTypes.modLfoToFilterFc,
577
+ normalizedNotCentered * 2400
578
+ );
579
+ niceLogging(
580
+ channel,
581
+ normalizedNotCentered * 2400,
582
+ `${sourceName} LFO2 filter depth`,
583
+ "cents"
584
+ );
585
+ break;
586
+
587
+ case 0x0A:
588
+ // LFO2 amplitude depth
589
+ channelObject.sysExModulators.setModulator(
590
+ source,
591
+ generatorTypes.modLfoToVolume,
592
+ normalizedNotCentered * 960
593
+ );
594
+ niceLogging(
595
+ channel,
596
+ normalizedNotCentered * 960,
597
+ `${sourceName} LFO2 amplitude depth`,
598
+ "cB"
599
+ );
600
+ break;
601
+ }
602
+ };
603
+
604
+ // SC88 manual page 198
605
+ switch (syx[6] & 0xF0)
606
+ {
607
+ default:
608
+ // this is some other GS sysex...
609
+ notRecognized();
610
+ break;
611
+
612
+ case 0x00:
613
+ // modulation wheel
614
+ setupReceivers(midiControllers.modulationWheel, "mod wheel");
615
+ break;
616
+
617
+ case 0x10:
618
+ // pitch bend
619
+ setupReceivers(NON_CC_INDEX_OFFSET + modulatorSources.pitchWheel, "pitch bend", true);
620
+ break;
621
+
622
+ case 0x20:
623
+ // channel pressure
624
+ setupReceivers(
625
+ NON_CC_INDEX_OFFSET + modulatorSources.channelPressure,
626
+ "channel pressure"
627
+ );
628
+ break;
629
+
630
+ case 0x30:
631
+ // poly pressure
632
+ setupReceivers(
633
+ NON_CC_INDEX_OFFSET + modulatorSources.polyPressure,
634
+ "poly pressure"
635
+ );
636
+ break;
637
+ }
485
638
  return;
486
639
  }
487
- else if (messageData[5] === 0x00 && messageData[6] === 0x05)
640
+ else
641
+ // this is a global system parameter
642
+ if (syx[5] === 0x00)
488
643
  {
489
- // roland master key shift (transpose)
490
- const transpose = messageValue - 64;
491
- SpessaSynthInfo(
492
- `%cRoland GS Master Key-Shift set to: %c${transpose}%c with: %c${arrayToHexString(
493
- messageData)}`,
494
- consoleColors.info,
495
- consoleColors.value,
496
- consoleColors.info,
497
- consoleColors.value
498
- );
499
- this.setMasterTuning(transpose * 100);
644
+ switch (syx[6])
645
+ {
646
+ default:
647
+ notRecognized();
648
+ break;
649
+
650
+ case 0x7F:
651
+ // roland mode set
652
+ // GS mode set
653
+ if (messageValue === 0x00)
654
+ {
655
+ // this is a GS reset
656
+ SpessaSynthInfo("%cGS Reset received!", consoleColors.info);
657
+ this.resetAllControllers(false);
658
+ this.setSystem("gs");
659
+ }
660
+ else if (messageValue === 0x7F)
661
+ {
662
+ // GS mode off
663
+ SpessaSynthInfo("%cGS system off, switching to GM2", consoleColors.info);
664
+ this.resetAllControllers(false);
665
+ this.setSystem("gm2");
666
+ }
667
+ break;
668
+
669
+ case 0x06:
670
+ // roland master pan
671
+ SpessaSynthInfo(
672
+ `%cRoland GS Master Pan set to: %c${messageValue}%c with: %c${arrayToHexString(
673
+ syx)}`,
674
+ consoleColors.info,
675
+ consoleColors.value,
676
+ consoleColors.info,
677
+ consoleColors.value
678
+ );
679
+ this.setMasterParameter(masterParameterType.masterPan, (messageValue - 64) / 64);
680
+ break;
681
+
682
+
683
+ case 0x04:
684
+ // roland GS master volume
685
+ SpessaSynthInfo(
686
+ `%cRoland GS Master Volume set to: %c${messageValue}%c with: %c${arrayToHexString(
687
+ syx)}`,
688
+ consoleColors.info,
689
+ consoleColors.value,
690
+ consoleColors.info,
691
+ consoleColors.value
692
+ );
693
+ this.setMIDIVolume(messageValue / 127);
694
+ break;
695
+
696
+ case 0x05:
697
+ // roland master key shift (transpose)
698
+ const transpose = messageValue - 64;
699
+ SpessaSynthInfo(
700
+ `%cRoland GS Master Key-Shift set to: %c${transpose}%c with: %c${arrayToHexString(
701
+ syx)}`,
702
+ consoleColors.info,
703
+ consoleColors.value,
704
+ consoleColors.info,
705
+ consoleColors.value
706
+ );
707
+ this.setMasterTuning(transpose * 100);
708
+ break;
709
+ }
500
710
  return;
501
711
  }
502
- else if (messageData[5] === 0x00 && messageData[6] === 0x04)
712
+ else
713
+ // this is a global system parameter also
714
+ if (syx[5] === 0x01)
503
715
  {
504
- // roland GS master volume
505
- SpessaSynthInfo(
506
- `%cRoland GS Master Volume set to: %c${messageValue}%c with: %c${arrayToHexString(
507
- messageData)}`,
508
- consoleColors.info,
509
- consoleColors.value,
510
- consoleColors.info,
511
- consoleColors.value
512
- );
513
- this.setMIDIVolume(messageValue / 127);
514
- return;
716
+ switch (syx[6])
717
+ {
718
+ default:
719
+ notRecognized();
720
+ break;
721
+
722
+ case 0x00:
723
+ // patch name. cool!
724
+ syx.currentIndex = 7;
725
+ const patchName = readBytesAsString(syx, 16);
726
+ SpessaSynthInfo(
727
+ `%cGS Patch name: %c${patchName}`,
728
+ consoleColors.info,
729
+ consoleColors.value
730
+ );
731
+ break;
732
+
733
+ case 0x33:
734
+ // reverb level
735
+ SpessaSynthInfo(
736
+ `%cGS Reverb level: %c${messageValue}`,
737
+ consoleColors.info, consoleColors.value
738
+ );
739
+ // 64 is the default
740
+ this.reverbSend = messageValue / 64;
741
+ break;
742
+
743
+ // unsupported reverb params
744
+ case 0x30:
745
+ case 0x31:
746
+ case 0x32:
747
+ case 0x34:
748
+ case 0x35:
749
+ case 0x37:
750
+ SpessaSynthInfo(
751
+ `%cUnsupported GS Reverb Parameter: %c${syx[6].toString(16)}`,
752
+ consoleColors.warn, consoleColors.unrecognized
753
+ );
754
+ break;
755
+
756
+ case 0x3A:
757
+ // chorus level
758
+ SpessaSynthInfo(
759
+ `%cGS Chorus level: %c${messageValue}`,
760
+ consoleColors.info, consoleColors.value
761
+ );
762
+ // 64 is the default
763
+ this.chorusSend = messageValue / 64;
764
+ break;
765
+
766
+ // unsupported chorus params
767
+ case 0x38:
768
+ case 0x39:
769
+ case 0x3B:
770
+ case 0x3C:
771
+ case 0x3D:
772
+ case 0x3E:
773
+ case 0x3F:
774
+ case 0x40:
775
+ SpessaSynthInfo(
776
+ `%cUnsupported GS Chorus Parameter: %c${syx[6].toString(16)}`,
777
+ consoleColors.warn, consoleColors.unrecognized
778
+ );
779
+ break;
780
+ }
515
781
  }
516
782
  }
517
- // this is some other GS sysex...
518
- notRecognized();
783
+ else
784
+ {
785
+ // this is some other GS sysex...
786
+ notRecognized();
787
+ }
519
788
  return;
520
789
  }
521
- else if (messageData[2] === 0x45 && messageData[3] === 0x12)
790
+ else if (syx[2] === 0x45 && syx[3] === 0x12)
522
791
  {
523
792
  // 0x45: GS Display Data, 0x12: DT1 (Device Transmit)
524
793
  // check for embedded copyright
525
794
  // (roland SC display sysex) http://www.bandtrax.com.au/sysex.htm
526
795
 
527
796
  if (
528
- messageData[4] === 0x10 && // Sound Canvas Display
529
- messageData[6] === 0x00 // Data follows
797
+ syx[4] === 0x10 && // Sound Canvas Display
798
+ syx[6] === 0x00 // Data follows
530
799
  )
531
800
  {
532
- if (messageData[5] === 0x00) // Display letters
801
+ if (syx[5] === 0x00) // Display letters
533
802
  {
534
803
  // get the text
535
804
  // and header ends with (checksum) F7
536
- const text = new Uint8Array(messageData.slice(7, messageData.length - 2));
805
+ const text = new Uint8Array(syx.slice(7, syx.length - 2));
537
806
  this.callEvent(
538
807
  "synthdisplay",
539
808
  {
@@ -542,11 +811,11 @@ export function systemExclusive(messageData, channelOffset = 0)
542
811
  }
543
812
  );
544
813
  }
545
- else if (messageData[5] === 0x01) // Matrix display
814
+ else if (syx[5] === 0x01) // Matrix display
546
815
  {
547
816
  // get the data
548
817
  // and header ends with (checksum) F7
549
- const dotMatrixData = new Uint8Array(messageData.slice(7, messageData.length - 3));
818
+ const dotMatrixData = new Uint8Array(syx.slice(7, syx.length - 3));
550
819
  this.callEvent(
551
820
  "synthdisplay",
552
821
  {
@@ -556,7 +825,7 @@ export function systemExclusive(messageData, channelOffset = 0)
556
825
  );
557
826
  SpessaSynthInfo(
558
827
  `%cRoland SC Display Dot Matrix via: %c${arrayToHexString(
559
- messageData)}`,
828
+ syx)}`,
560
829
  consoleColors.info,
561
830
  consoleColors.value
562
831
  );
@@ -568,13 +837,13 @@ export function systemExclusive(messageData, channelOffset = 0)
568
837
  }
569
838
  }
570
839
  }
571
- else if (messageData[2] === 0x16 && messageData[3] === 0x12 && messageData[4] === 0x10)
840
+ else if (syx[2] === 0x16 && syx[3] === 0x12 && syx[4] === 0x10)
572
841
  {
573
842
  // this is a roland master volume message
574
- this.setMIDIVolume(messageData[7] / 100);
843
+ this.setMIDIVolume(syx[7] / 100);
575
844
  SpessaSynthInfo(
576
- `%cRoland Master Volume control set to: %c${messageData[7]}%c via: %c${arrayToHexString(
577
- messageData)}`,
845
+ `%cRoland Master Volume control set to: %c${syx[7]}%c via: %c${arrayToHexString(
846
+ syx)}`,
578
847
  consoleColors.info,
579
848
  consoleColors.value,
580
849
  consoleColors.info,
@@ -586,7 +855,7 @@ export function systemExclusive(messageData, channelOffset = 0)
586
855
  {
587
856
  // this is something else...
588
857
  SpessaSynthWarn(
589
- `%cUnrecognized Roland SysEx: %c${arrayToHexString(messageData)}`,
858
+ `%cUnrecognized Roland SysEx: %c${arrayToHexString(syx)}`,
590
859
  consoleColors.warn,
591
860
  consoleColors.unrecognized
592
861
  );
@@ -598,16 +867,16 @@ export function systemExclusive(messageData, channelOffset = 0)
598
867
  // http://www.studio4all.de/htmle/main91.html
599
868
  case 0x43:
600
869
  // XG sysex
601
- if (messageData[2] === 0x4C)
870
+ if (syx[2] === 0x4C)
602
871
  {
603
872
  // XG system parameter
604
- if (messageData[3] === 0x00 && messageData[4] === 0x00)
873
+ if (syx[3] === 0x00 && syx[4] === 0x00)
605
874
  {
606
- switch (messageData[5])
875
+ switch (syx[5])
607
876
  {
608
877
  // master volume
609
878
  case 0x04:
610
- const vol = messageData[6];
879
+ const vol = syx[6];
611
880
  this.setMIDIVolume(vol / 127);
612
881
  SpessaSynthInfo(
613
882
  `%cXG master volume. Volume: %c${vol}`,
@@ -618,7 +887,7 @@ export function systemExclusive(messageData, channelOffset = 0)
618
887
 
619
888
  // master transpose
620
889
  case 0x06:
621
- const transpose = messageData[6] - 64;
890
+ const transpose = syx[6] - 64;
622
891
  this.transposeAllChannels(transpose);
623
892
  SpessaSynthInfo(
624
893
  `%cXG master transpose. Volume: %c${transpose}`,
@@ -637,21 +906,21 @@ export function systemExclusive(messageData, channelOffset = 0)
637
906
  }
638
907
  else
639
908
  // XG part parameter
640
- if (messageData[3] === 0x08)
909
+ if (syx[3] === 0x08)
641
910
  {
642
911
  if (!isSystemXG(this.system))
643
912
  {
644
913
  return;
645
914
  }
646
- const channel = messageData[4] + channelOffset;
915
+ const channel = syx[4] + channelOffset;
647
916
  if (channel >= this.midiAudioChannels.length)
648
917
  {
649
918
  // invalid channel
650
919
  return;
651
920
  }
652
921
  const channelObject = this.midiAudioChannels[channel];
653
- const value = messageData[6];
654
- switch (messageData[5])
922
+ const value = syx[6];
923
+ switch (syx[5])
655
924
  {
656
925
  // bank-select MSB
657
926
  case 0x01:
@@ -716,7 +985,7 @@ export function systemExclusive(messageData, channelOffset = 0)
716
985
 
717
986
  default:
718
987
  SpessaSynthWarn(
719
- `%cUnrecognized Yamaha XG Part Setup: %c${messageData[5].toString(16)
988
+ `%cUnrecognized Yamaha XG Part Setup: %c${syx[5].toString(16)
720
989
  .toUpperCase()}`,
721
990
  consoleColors.warn,
722
991
  consoleColors.unrecognized
@@ -724,13 +993,13 @@ export function systemExclusive(messageData, channelOffset = 0)
724
993
  }
725
994
  }
726
995
  else if (
727
- messageData[3] === 0x06 && // XG System parameter
728
- messageData[4] === 0x00 // System Byte
996
+ syx[3] === 0x06 && // XG System parameter
997
+ syx[4] === 0x00 // System Byte
729
998
  )
730
999
  {
731
1000
  // displayed letters (remove F7 at the end)
732
1001
  // include byte 5 as it seems to be line information (useful)
733
- const textData = new Uint8Array(messageData.slice(5, messageData.length - 1));
1002
+ const textData = new Uint8Array(syx.slice(5, syx.length - 1));
734
1003
  this.callEvent(
735
1004
  "synthdisplay",
736
1005
  {
@@ -742,7 +1011,7 @@ export function systemExclusive(messageData, channelOffset = 0)
742
1011
  else if (isSystemXG(this.system))
743
1012
  {
744
1013
  SpessaSynthWarn(
745
- `%cUnrecognized Yamaha XG SysEx: %c${arrayToHexString(messageData)}`,
1014
+ `%cUnrecognized Yamaha XG SysEx: %c${arrayToHexString(syx)}`,
746
1015
  consoleColors.warn,
747
1016
  consoleColors.unrecognized
748
1017
  );
@@ -754,7 +1023,7 @@ export function systemExclusive(messageData, channelOffset = 0)
754
1023
  if (isSystemXG(this.system))
755
1024
  {
756
1025
  SpessaSynthWarn(
757
- `%cUnrecognized Yamaha SysEx: %c${arrayToHexString(messageData)}`,
1026
+ `%cUnrecognized Yamaha SysEx: %c${arrayToHexString(syx)}`,
758
1027
  consoleColors.warn,
759
1028
  consoleColors.unrecognized
760
1029
  );