spessasynth_lib 3.24.13 → 3.24.15

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 (61) hide show
  1. package/midi_parser/basic_midi.js +457 -68
  2. package/midi_parser/midi_loader.js +18 -503
  3. package/midi_parser/midi_message.js +18 -5
  4. package/midi_parser/midi_sequence.js +2 -2
  5. package/package.json +1 -1
  6. package/sequencer/worklet_sequencer/process_event.js +1 -6
  7. package/synthetizer/synthetizer.js +9 -6
  8. package/synthetizer/worklet_processor.min.js +12 -12
  9. package/synthetizer/worklet_system/README.md +2 -2
  10. package/synthetizer/worklet_system/main_processor.js +106 -95
  11. package/synthetizer/worklet_system/message_protocol/handle_message.js +22 -17
  12. package/synthetizer/worklet_system/message_protocol/worklet_message.js +2 -1
  13. package/synthetizer/worklet_system/snapshot/apply_synthesizer_snapshot.js +14 -0
  14. package/synthetizer/worklet_system/snapshot/channel_snapshot.js +166 -0
  15. package/synthetizer/worklet_system/snapshot/send_synthesizer_snapshot.js +14 -0
  16. package/synthetizer/worklet_system/snapshot/synthesizer_snapshot.js +121 -0
  17. package/synthetizer/worklet_system/worklet_methods/controller_control/controller_change.js +196 -0
  18. package/synthetizer/worklet_system/worklet_methods/controller_control/master_parameters.js +34 -0
  19. package/synthetizer/worklet_system/worklet_methods/{reset_controllers.js → controller_control/reset_controllers.js} +33 -39
  20. package/synthetizer/worklet_system/worklet_methods/create_worklet_channel.js +26 -0
  21. package/synthetizer/worklet_system/worklet_methods/{data_entry.js → data_entry/data_entry_coarse.js} +38 -105
  22. package/synthetizer/worklet_system/worklet_methods/data_entry/data_entry_fine.js +64 -0
  23. package/synthetizer/worklet_system/worklet_methods/mute_channel.js +17 -0
  24. package/synthetizer/worklet_system/worklet_methods/note_on.js +36 -34
  25. package/synthetizer/worklet_system/worklet_methods/program_change.js +49 -0
  26. package/synthetizer/worklet_system/worklet_methods/{voice_control.js → render_voice.js} +37 -120
  27. package/synthetizer/worklet_system/worklet_methods/soundfont_management/clear_sound_font.js +35 -0
  28. package/synthetizer/worklet_system/worklet_methods/soundfont_management/get_preset.js +20 -0
  29. package/synthetizer/worklet_system/worklet_methods/soundfont_management/reload_sound_font.js +43 -0
  30. package/synthetizer/worklet_system/worklet_methods/soundfont_management/send_preset_list.js +31 -0
  31. package/synthetizer/worklet_system/worklet_methods/soundfont_management/set_embedded_sound_font.js +21 -0
  32. package/synthetizer/worklet_system/worklet_methods/stopping_notes/kill_note.js +19 -0
  33. package/synthetizer/worklet_system/worklet_methods/stopping_notes/note_off.js +51 -0
  34. package/synthetizer/worklet_system/worklet_methods/stopping_notes/stop_all_channels.js +16 -0
  35. package/synthetizer/worklet_system/worklet_methods/stopping_notes/stop_all_notes.js +30 -0
  36. package/synthetizer/worklet_system/worklet_methods/stopping_notes/voice_killing.js +63 -0
  37. package/synthetizer/worklet_system/worklet_methods/system_exclusive.js +31 -30
  38. package/synthetizer/worklet_system/worklet_methods/tuning_control/channel_pressure.js +24 -0
  39. package/synthetizer/worklet_system/worklet_methods/tuning_control/pitch_wheel.js +33 -0
  40. package/synthetizer/worklet_system/worklet_methods/tuning_control/poly_pressure.js +31 -0
  41. package/synthetizer/worklet_system/worklet_methods/tuning_control/set_master_tuning.js +15 -0
  42. package/synthetizer/worklet_system/worklet_methods/tuning_control/set_modulation_depth.js +27 -0
  43. package/synthetizer/worklet_system/worklet_methods/tuning_control/set_octave_tuning.js +15 -0
  44. package/synthetizer/worklet_system/worklet_methods/tuning_control/set_tuning.js +24 -0
  45. package/synthetizer/worklet_system/worklet_methods/tuning_control/set_tuning_semitones.js +19 -0
  46. package/synthetizer/worklet_system/worklet_methods/tuning_control/transpose_all_channels.js +15 -0
  47. package/synthetizer/worklet_system/worklet_methods/tuning_control/transpose_channel.js +31 -0
  48. package/synthetizer/worklet_system/worklet_utilities/controller_tables.js +10 -1
  49. package/synthetizer/worklet_system/worklet_utilities/lfo.js +2 -1
  50. package/synthetizer/worklet_system/worklet_utilities/modulation_envelope.js +4 -4
  51. package/synthetizer/worklet_system/worklet_utilities/modulator_curves.js +4 -5
  52. package/synthetizer/worklet_system/worklet_utilities/stereo_panner.js +18 -18
  53. package/synthetizer/worklet_system/worklet_utilities/wavetable_oscillator.js +210 -206
  54. package/synthetizer/worklet_system/worklet_utilities/worklet_processor_channel.js +354 -108
  55. package/synthetizer/worklet_system/worklet_utilities/worklet_voice.js +22 -9
  56. package/synthetizer/worklet_system/snapshot/snapshot.js +0 -311
  57. package/synthetizer/worklet_system/worklet_methods/controller_control.js +0 -260
  58. package/synthetizer/worklet_system/worklet_methods/note_off.js +0 -119
  59. package/synthetizer/worklet_system/worklet_methods/program_control.js +0 -282
  60. package/synthetizer/worklet_system/worklet_methods/tuning_control.js +0 -233
  61. package/synthetizer/worklet_system/worklet_methods/vibrato_control.js +0 -29
@@ -215,7 +215,7 @@ export function systemExclusive(messageData, channelOffset = 0)
215
215
  }
216
216
  else
217
217
  {
218
- // 2 byte tuning. Like fine tune: 0 is -100 cents, 8192 is 0 cents, 16383 is +100 cents
218
+ // 2 byte tuning. Like fine tune: 0 is -100 cents, 8192 is 0 cents, 16,383 is +100 cents
219
219
  for (let i = 0; i < 24; i += 2)
220
220
  {
221
221
  const tuning = ((messageData[7 + i] << 7) | messageData[8 + i]) - 8192;
@@ -226,11 +226,11 @@ export function systemExclusive(messageData, channelOffset = 0)
226
226
  // bit 1: 14 and 15
227
227
  if ((messageData[4] & 1) === 1)
228
228
  {
229
- this.setOctaveTuning(14 + channelOffset, newOctaveTuning);
229
+ this.workletProcessorChannels[14 + channelOffset].setOctaveTuning(newOctaveTuning);
230
230
  }
231
231
  if (((messageData[4] >> 1) & 1) === 1)
232
232
  {
233
- this.setOctaveTuning(15 + channelOffset, newOctaveTuning);
233
+ this.workletProcessorChannels[15 + channelOffset].setOctaveTuning(newOctaveTuning);
234
234
  }
235
235
 
236
236
  // bit 2: channels 7 to 13
@@ -239,7 +239,7 @@ export function systemExclusive(messageData, channelOffset = 0)
239
239
  const bit = (messageData[5] >> i) & 1;
240
240
  if (bit === 1)
241
241
  {
242
- this.setOctaveTuning(7 + i + channelOffset, newOctaveTuning);
242
+ this.workletProcessorChannels[7 + i + channelOffset].setOctaveTuning(newOctaveTuning);
243
243
  }
244
244
  }
245
245
 
@@ -249,7 +249,7 @@ export function systemExclusive(messageData, channelOffset = 0)
249
249
  const bit = (messageData[6] >> i) & 1;
250
250
  if (bit === 1)
251
251
  {
252
- this.setOctaveTuning(i + channelOffset, newOctaveTuning);
252
+ this.workletProcessorChannels[i + channelOffset].setOctaveTuning(newOctaveTuning);
253
253
  }
254
254
  }
255
255
 
@@ -330,8 +330,9 @@ export function systemExclusive(messageData, channelOffset = 0)
330
330
  {
331
331
  // this is an individual part (channel) parameter
332
332
  // determine the channel 0 means channel 10 (default), 1 means 1 etc.
333
- let channel = [9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 11, 12, 13, 14, 15][messageData[5] & 0x0F] + channelOffset;
334
- // for example 1A means A = 11, which corresponds to channel 12 (counting from 1)
333
+ const channel = [9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 11, 12, 13, 14, 15][messageData[5] & 0x0F] + channelOffset;
334
+ // for example, 0x1A means A = 11, which corresponds to channel 12 (counting from 1)
335
+ const channelObject = this.workletProcessorChannels[channel];
335
336
  switch (messageData[6])
336
337
  {
337
338
  default:
@@ -342,7 +343,7 @@ export function systemExclusive(messageData, channelOffset = 0)
342
343
  case 0x15:
343
344
  // this is the Use for Drum Part sysex (multiple drums)
344
345
  const isDrums = messageValue > 0 && messageData[5] >> 4; // if set to other than 0, is a drum channel
345
- this.setDrums(channel, isDrums);
346
+ channelObject.setDrums(isDrums);
346
347
  SpessaSynthInfo(
347
348
  `%cChannel %c${channel}%c ${isDrums ?
348
349
  "is now a drum channel"
@@ -360,7 +361,7 @@ export function systemExclusive(messageData, channelOffset = 0)
360
361
  case 0x16:
361
362
  // this is the pitch key shift sysex
362
363
  const keyShift = messageValue - 64;
363
- this.transposeChannel(channel, keyShift);
364
+ channelObject.transposeChannel(keyShift);
364
365
  SpessaSynthInfo(
365
366
  `%cChannel %c${channel}%c pitch shift. Semitones %c${keyShift}%c, with %c${arrayToHexString(
366
367
  messageData)}`,
@@ -379,7 +380,7 @@ export function systemExclusive(messageData, channelOffset = 0)
379
380
  let panpot = messageValue;
380
381
  if (panpot === 0)
381
382
  {
382
- this.workletProcessorChannels[channel].randomPan = true;
383
+ channelObject.randomPan = true;
383
384
  SpessaSynthInfo(
384
385
  `%cRandom pan is set to %cON%c for %c${channel}`,
385
386
  consoleColors.info,
@@ -390,19 +391,19 @@ export function systemExclusive(messageData, channelOffset = 0)
390
391
  }
391
392
  else
392
393
  {
393
- this.workletProcessorChannels[channel].randomPan = false;
394
- this.controllerChange(channel, midiControllers.pan, panpot);
394
+ channelObject.randomPan = false;
395
+ channelObject.controllerChange(midiControllers.pan, panpot);
395
396
  }
396
397
  break;
397
398
 
398
399
  // chorus send
399
400
  case 0x21:
400
- this.controllerChange(channel, midiControllers.chorusDepth, messageValue);
401
+ channelObject.controllerChange(midiControllers.chorusDepth, messageValue);
401
402
  break;
402
403
 
403
404
  // reverb send
404
405
  case 0x22:
405
- this.controllerChange(channel, midiControllers.reverbDepth, messageValue);
406
+ channelObject.controllerChange(midiControllers.reverbDepth, messageValue);
406
407
  break;
407
408
 
408
409
  case 0x40:
@@ -418,14 +419,14 @@ export function systemExclusive(messageData, channelOffset = 0)
418
419
  case 0x4A:
419
420
  case 0x4B:
420
421
  // scale tuning: up to 12 bytes
421
- const tuningBytes = messageData.length - 9; // data starts at 7 , minus checksum and f7
422
+ const tuningBytes = messageData.length - 9; // data starts at 7, minus checksum and f7
422
423
  // read em bytes
423
424
  const newTuning = new Int8Array(12);
424
425
  for (let i = 0; i < tuningBytes; i++)
425
426
  {
426
427
  newTuning[i] = messageData[i + 7] - 64;
427
428
  }
428
- this.setOctaveTuning(channel, newTuning);
429
+ channelObject.setOctaveTuning(newTuning);
429
430
  const cents = messageValue - 64;
430
431
  SpessaSynthInfo(
431
432
  `%cChannel %c${channel}%c octave scale tuning. Cents %c${newTuning.join(
@@ -437,7 +438,7 @@ export function systemExclusive(messageData, channelOffset = 0)
437
438
  consoleColors.info,
438
439
  consoleColors.value
439
440
  );
440
- this.setChannelTuning(channel, cents);
441
+ channelObject.setTuning(cents);
441
442
  break;
442
443
  }
443
444
  return;
@@ -623,38 +624,38 @@ export function systemExclusive(messageData, channelOffset = 0)
623
624
  // invalid channel
624
625
  return;
625
626
  }
627
+ const channelObject = this.workletProcessorChannels[channel];
626
628
  const value = messageData[6];
627
629
  switch (messageData[5])
628
630
  {
629
- // bank select
631
+ // bank-select MSB
630
632
  case 0x01:
631
- this.controllerChange(channel, midiControllers.bankSelect, value);
633
+ channelObject.controllerChange(midiControllers.bankSelect, value);
632
634
  break;
633
635
 
634
- // bank select lsb
636
+ // bank-select LSB
635
637
  case 0x02:
636
- this.controllerChange(channel, midiControllers.lsbForControl0BankSelect, value);
638
+ channelObject.controllerChange(midiControllers.lsbForControl0BankSelect, value);
637
639
  break;
638
640
 
639
641
  // program change
640
642
  case 0x03:
641
- this.programChange(channel, value);
643
+ channelObject.programChange(value);
642
644
  break;
643
645
 
644
646
  // note shift
645
647
  case 0x08:
646
- const chan = this.workletProcessorChannels[channel];
647
- if (chan.drumChannel)
648
+ if (channelObject.drumChannel)
648
649
  {
649
650
  return;
650
651
  }
651
652
  const semitones = value - 64;
652
- chan.channelTransposeKeyShift = semitones;
653
+ channelObject.channelTransposeKeyShift = semitones;
653
654
  break;
654
655
 
655
656
  // volume
656
657
  case 0x0B:
657
- this.controllerChange(channel, midiControllers.mainVolume, value);
658
+ channelObject.controllerChange(midiControllers.mainVolume, value);
658
659
  break;
659
660
 
660
661
  // pan position
@@ -663,7 +664,7 @@ export function systemExclusive(messageData, channelOffset = 0)
663
664
  if (pan === 0)
664
665
  {
665
666
  // 0 means random
666
- this.workletProcessorChannels[channel].randomPan = true;
667
+ channelObject.randomPan = true;
667
668
  SpessaSynthInfo(
668
669
  `%cRandom pan is set to %cON%c for %c${channel}`,
669
670
  consoleColors.info,
@@ -674,18 +675,18 @@ export function systemExclusive(messageData, channelOffset = 0)
674
675
  }
675
676
  else
676
677
  {
677
- this.controllerChange(channel, midiControllers.pan, pan);
678
+ channelObject.controllerChange(midiControllers.pan, pan);
678
679
  }
679
680
  break;
680
681
 
681
682
  // reverb
682
683
  case 0x13:
683
- this.controllerChange(channel, midiControllers.reverbDepth, value);
684
+ channelObject.controllerChange(midiControllers.reverbDepth, value);
684
685
  break;
685
686
 
686
687
  // chorus
687
688
  case 0x12:
688
- this.controllerChange(channel, midiControllers.chorusDepth, value);
689
+ channelObject.controllerChange(midiControllers.chorusDepth, value);
689
690
  break;
690
691
 
691
692
  default:
@@ -0,0 +1,24 @@
1
+ import { NON_CC_INDEX_OFFSET } from "../../worklet_utilities/controller_tables.js";
2
+ import { modulatorSources } from "../../../../soundfont/basic_soundfont/modulator.js";
3
+ import { computeModulators } from "../../worklet_utilities/worklet_modulator.js";
4
+
5
+ /**
6
+ * Sets the pressure of the given channel
7
+ * @this {WorkletProcessorChannel}
8
+ * @param pressure {number} the pressure of the channel
9
+ */
10
+ export function channelPressure(pressure)
11
+ {
12
+ this.midiControllers[NON_CC_INDEX_OFFSET + modulatorSources.channelPressure] = pressure << 7;
13
+ this.voices.forEach(v =>
14
+ computeModulators(
15
+ v,
16
+ this.midiControllers,
17
+ 0,
18
+ modulatorSources.channelPressure
19
+ ));
20
+ this.synth.callEvent("channelpressure", {
21
+ channel: this.channelNumber,
22
+ pressure: pressure
23
+ });
24
+ }
@@ -0,0 +1,33 @@
1
+ import { NON_CC_INDEX_OFFSET } from "../../worklet_utilities/controller_tables.js";
2
+ import { modulatorSources } from "../../../../soundfont/basic_soundfont/modulator.js";
3
+ import { computeModulators } from "../../worklet_utilities/worklet_modulator.js";
4
+
5
+ /**
6
+ * Sets the pitch of the given channel
7
+ * @this {WorkletProcessorChannel}
8
+ * @param MSB {number} SECOND byte of the MIDI pitchWheel message
9
+ * @param LSB {number} FIRST byte of the MIDI pitchWheel message
10
+ */
11
+ export function pitchWheel(MSB, LSB)
12
+ {
13
+ if (this.lockedControllers[NON_CC_INDEX_OFFSET + modulatorSources.pitchWheel])
14
+ {
15
+ return;
16
+ }
17
+ const bend = (LSB | (MSB << 7));
18
+ this.synth.callEvent("pitchwheel", {
19
+ channel: this.channelNumber,
20
+ MSB: MSB,
21
+ LSB: LSB
22
+ });
23
+ this.midiControllers[NON_CC_INDEX_OFFSET + modulatorSources.pitchWheel] = bend;
24
+ this.voices.forEach(v =>
25
+ // compute pitch modulators
26
+ computeModulators(
27
+ v,
28
+ this.midiControllers,
29
+ 0,
30
+ modulatorSources.pitchWheel
31
+ ));
32
+ this.synth.sendChannelProperties();
33
+ }
@@ -0,0 +1,31 @@
1
+ import { computeModulators } from "../../worklet_utilities/worklet_modulator.js";
2
+ import { modulatorSources } from "../../../../soundfont/basic_soundfont/modulator.js";
3
+
4
+ /**
5
+ * Sets the pressure of the given note on a specific channel
6
+ * @this {WorkletProcessorChannel}
7
+ * @param midiNote {number} 0-127
8
+ * @param pressure {number} the pressure of the note
9
+ */
10
+ export function polyPressure(midiNote, pressure)
11
+ {
12
+ this.voices.forEach(v =>
13
+ {
14
+ if (v.midiNote !== midiNote)
15
+ {
16
+ return;
17
+ }
18
+ v.pressure = pressure;
19
+ computeModulators(
20
+ v,
21
+ this.midiControllers,
22
+ 0,
23
+ modulatorSources.polyPressure
24
+ );
25
+ });
26
+ this.synth.callEvent("polypressure", {
27
+ channel: this.channelNumber,
28
+ midiNote: midiNote,
29
+ pressure: pressure
30
+ });
31
+ }
@@ -0,0 +1,15 @@
1
+ import { customControllers } from "../../worklet_utilities/controller_tables.js";
2
+
3
+ /**
4
+ * Sets the worklet's primary tuning
5
+ * @this {SpessaSynthProcessor}
6
+ * @param cents {number}
7
+ */
8
+ export function setMasterTuning(cents)
9
+ {
10
+ cents = Math.round(cents);
11
+ for (let i = 0; i < this.workletProcessorChannels.length; i++)
12
+ {
13
+ this.workletProcessorChannels[i].customControllers[customControllers.masterTuning] = cents;
14
+ }
15
+ }
@@ -0,0 +1,27 @@
1
+ import { SpessaSynthInfo } from "../../../../utils/loggin.js";
2
+ import { consoleColors } from "../../../../utils/other.js";
3
+ import { customControllers } from "../../worklet_utilities/controller_tables.js";
4
+
5
+ /**
6
+ * @this {WorkletProcessorChannel}
7
+ * @param cents {number}
8
+ */
9
+ export function setModulationDepth(cents)
10
+ {
11
+ cents = Math.round(cents);
12
+ SpessaSynthInfo(
13
+ `%cChannel ${this.channelNumber} modulation depth. Cents: %c${cents}`,
14
+ consoleColors.info,
15
+ consoleColors.value
16
+ );
17
+ /* ==============
18
+ IMPORTANT
19
+ here we convert cents into a multiplier.
20
+ midi spec assumes the default is 50 cents,
21
+ but it might be different for the soundfont,
22
+ so we create a multiplier by dividing cents by 50.
23
+ for example, if we want 100 cents, then multiplier will be 2,
24
+ which for a preset with depth of 50 will create 100.
25
+ ================ */
26
+ this.customControllers[customControllers.modulationMultiplier] = cents / 50;
27
+ }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Sets the octave tuning for a given channel
3
+ * @this {WorkletProcessorChannel}
4
+ * @param tuning {Int8Array} LENGTH of 12!
5
+ * relative cent tuning.
6
+ * min -128 max 127.
7
+ */
8
+ export function setOctaveTuning(tuning)
9
+ {
10
+ if (tuning.length !== 12)
11
+ {
12
+ throw new Error("Tuning is not the length of 12.");
13
+ }
14
+ this.channelOctaveTuning = tuning;
15
+ }
@@ -0,0 +1,24 @@
1
+ import { customControllers } from "../../worklet_utilities/controller_tables.js";
2
+ import { SpessaSynthInfo } from "../../../../utils/loggin.js";
3
+ import { consoleColors } from "../../../../utils/other.js";
4
+
5
+ /**
6
+ * Sets the channel's tuning
7
+ * @this {WorkletProcessorChannel}
8
+ * @param cents {number}
9
+ * @param log {boolean}
10
+ */
11
+ export function setTuning(cents, log = true)
12
+ {
13
+ cents = Math.round(cents);
14
+ this.customControllers[customControllers.channelTuning] = cents;
15
+ if (!log)
16
+ {
17
+ return;
18
+ }
19
+ SpessaSynthInfo(
20
+ `%cChannel ${this.channelNumber} fine tuning. Cents: %c${cents}`,
21
+ consoleColors.info,
22
+ consoleColors.value
23
+ );
24
+ }
@@ -0,0 +1,19 @@
1
+ import { customControllers } from "../../worklet_utilities/controller_tables.js";
2
+ import { SpessaSynthInfo } from "../../../../utils/loggin.js";
3
+ import { consoleColors } from "../../../../utils/other.js";
4
+
5
+ /**
6
+ * Sets the channel's tuning in semitones
7
+ * @param semitones {number}
8
+ * @this {WorkletProcessorChannel}
9
+ */
10
+ export function setTuningSemitones(semitones)
11
+ {
12
+ semitones = Math.round(semitones);
13
+ this.customControllers[customControllers.channelTuningSemitones] = semitones;
14
+ SpessaSynthInfo(
15
+ `%cChannel ${this.channelNumber} coarse tuning. Semitones: %c${semitones}`,
16
+ consoleColors.info,
17
+ consoleColors.value
18
+ );
19
+ }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Transposes all channels by given amount of semitones
3
+ * @this {SpessaSynthProcessor}
4
+ * @param semitones {number} Can be float
5
+ * @param force {boolean} defaults to false, if true transposes the channel even if it's a drum channel
6
+ */
7
+ export function transposeAllChannels(semitones, force = false)
8
+ {
9
+ this.transposition = 0;
10
+ for (let i = 0; i < this.workletProcessorChannels.length; i++)
11
+ {
12
+ this.workletProcessorChannels[i].transposeChannel(semitones, force);
13
+ }
14
+ this.transposition = semitones;
15
+ }
@@ -0,0 +1,31 @@
1
+ import { customControllers } from "../../worklet_utilities/controller_tables.js";
2
+
3
+ /**
4
+ * Transposes the channel by given amount of semitones
5
+ * @this {WorkletProcessorChannel}
6
+ * @param semitones {number} Can be float
7
+ * @param force {boolean} defaults to false, if true transposes the channel even if it's a drum channel
8
+ */
9
+ export function transposeChannel(semitones, force = false)
10
+ {
11
+ if (!this.drumChannel)
12
+ {
13
+ semitones += this.synth.transposition;
14
+ }
15
+ const keyShift = Math.trunc(semitones);
16
+ const currentTranspose = this.channelTransposeKeyShift + this.customControllers[customControllers.channelTransposeFine] / 100;
17
+ if (
18
+ (this.drumChannel && !force)
19
+ || semitones === currentTranspose
20
+ )
21
+ {
22
+ return;
23
+ }
24
+ if (keyShift !== this.channelTransposeKeyShift)
25
+ {
26
+ this.stopAllNotes(false);
27
+ }
28
+ // apply transpose
29
+ this.channelTransposeKeyShift = keyShift;
30
+ this.customControllers[customControllers.channelTransposeFine] = (semitones - keyShift) * 100;
31
+ }
@@ -22,6 +22,8 @@ setResetValue(midiControllers.balance, 64);
22
22
  setResetValue(midiControllers.expressionController, 127);
23
23
  setResetValue(midiControllers.pan, 64);
24
24
 
25
+ setResetValue(midiControllers.portamentoOnOff, 127);
26
+
25
27
  setResetValue(midiControllers.timbreHarmonicContent, 64);
26
28
  setResetValue(midiControllers.releaseTime, 64);
27
29
  setResetValue(midiControllers.attackTime, 64);
@@ -33,7 +35,7 @@ setResetValue(midiControllers.soundController8, 64);
33
35
  setResetValue(midiControllers.soundController9, 64);
34
36
  setResetValue(midiControllers.generalPurposeController6, 64);
35
37
  setResetValue(midiControllers.generalPurposeController8, 64);
36
- setResetValue(midiControllers.portamentoControl, 64);
38
+ setResetValue(midiControllers.portamentoControl, 60);
37
39
 
38
40
  // pitch wheel
39
41
  setResetValue(NON_CC_INDEX_OFFSET + modulatorSources.pitchWheel, 64);
@@ -61,4 +63,11 @@ export const dataEntryStates = {
61
63
  NRPFine: 4,
62
64
  DataCoarse: 5,
63
65
  DataFine: 6
66
+ };
67
+ /**
68
+ * This is a channel configuration enum, it is internally sent from Synthetizer via controller change
69
+ * @enum {number}
70
+ */
71
+ export const channelConfiguration = {
72
+ velocityOverride: 128 // overrides velocity for the given channel
64
73
  };
@@ -18,7 +18,8 @@ export function getLFOValue(startTime, frequency, currentTime)
18
18
  }
19
19
 
20
20
  const xVal = (currentTime - startTime) / (1 / frequency) + 0.25;
21
- // offset by -0.25, otherwise we start at -1 and can have unexpected jump in pitch or lowpass (happened with Synth Strings 2)
21
+ // offset by -0.25, otherwise we start at -1 and can have unexpected jump in pitch or low-pass
22
+ // (happened with Synth Strings 2)
22
23
 
23
24
  // triangle, not sine
24
25
  return Math.abs(xVal - (~~(xVal + 0.5))) * 4 - 1;
@@ -13,7 +13,7 @@ const MODENV_PEAK = 1;
13
13
  const CONVEX_ATTACK = new Float32Array(1000);
14
14
  for (let i = 0; i < CONVEX_ATTACK.length; i++)
15
15
  {
16
- // this makes the db linear ( i think
16
+ // this makes the db linear (I think)
17
17
  CONVEX_ATTACK[i] = getModulatorCurveValue(0, modulatorCurveTypes.convex, i / 1000, 0);
18
18
  }
19
19
 
@@ -110,7 +110,7 @@ export class WorkletModulationEnvelope
110
110
  const decayKeyExcursionCents = ((60 - voice.midiNote) * voice.modulatedGenerators[generatorTypes.keyNumToModEnvDecay]);
111
111
  const decayTime = timecentsToSeconds(voice.modulatedGenerators[generatorTypes.decayModEnv] + decayKeyExcursionCents);
112
112
  // according to the specification, the decay time is the time it takes to reach 0% from 100%.
113
- // calculate the time to reach actual sustain level
113
+ // calculate the time to reach actual sustain level,
114
114
  // for example, sustain 0.6 will be 0.4 of the decay time
115
115
  env.decayDuration = decayTime * (1 - env.sustainLevel);
116
116
 
@@ -130,7 +130,7 @@ export class WorkletModulationEnvelope
130
130
 
131
131
  /**
132
132
  * Calculates the current modulation envelope value for the given time and voice
133
- * @param voice {WorkletVoice} the voice we're working on
133
+ * @param voice {WorkletVoice} the voice we are working on
134
134
  * @param currentTime {number} in seconds
135
135
  * @param ignoreRelease {boolean} if true, it will compute the value as if the voice was not released
136
136
  * @returns {number} modenv value, from 0 to 1
@@ -141,7 +141,7 @@ export class WorkletModulationEnvelope
141
141
  if (voice.isInRelease && !ignoreRelease)
142
142
  {
143
143
  // if the voice is still in the delay phase,
144
- // start level will be 0 which will result in divide by zero
144
+ // start level will be 0 that will result in divide by zero
145
145
  if (env.releaseStartLevel === 0)
146
146
  {
147
147
  return 0;
@@ -8,12 +8,11 @@ import { modulatorCurveTypes } from "../../../soundfont/basic_soundfont/modulato
8
8
  // the length of the precomputed curve tables
9
9
  export const MOD_PRECOMPUTED_LENGTH = 16384;
10
10
 
11
- // Precalculate lookup tables for concave and convers
11
+ // Precalculate lookup tables for concave and convex curves
12
12
  const concave = new Float32Array(MOD_PRECOMPUTED_LENGTH + 1);
13
13
  const convex = new Float32Array(MOD_PRECOMPUTED_LENGTH + 1);
14
14
  // the equation is taken from FluidSynth as it's the standard for soundFonts
15
- // more precisely, this:
16
- // https://github.com/FluidSynth/fluidsynth/blob/cb8da1e1e2c0a5cff2bab6a419755b598b793384/src/gentables/gen_conv.c#L55
15
+ // more precisely, the gen_conv.c file
17
16
  concave[0] = 0;
18
17
  concave[concave.length - 1] = 1;
19
18
 
@@ -32,7 +31,7 @@ for (let i = 1; i < MOD_PRECOMPUTED_LENGTH - 1; i++)
32
31
  * @param direction {number} 0 or 1
33
32
  * @param curveType {number} see modulatorCurveTypes in modulators.js
34
33
  * @param value {number} the linear value, 0 to 1
35
- * @returns {number} the transformed value, 0 to 1 or -1 to 1
34
+ * @returns {number} the transformed value, 0 to 1, or -1 to 1
36
35
  */
37
36
  export function getModulatorCurveValue(direction, curveType, value, polarity)
38
37
  {
@@ -46,7 +45,7 @@ export function getModulatorCurveValue(direction, curveType, value, polarity)
46
45
  case modulatorCurveTypes.linear:
47
46
  if (polarity)
48
47
  {
49
- // bipolar
48
+ // bipolar curve
50
49
  return value * 2 - 1;
51
50
  }
52
51
  return value;
@@ -33,15 +33,17 @@ for (let pan = MIN_PAN; pan <= MAX_PAN; pan++)
33
33
  * @param inputBuffer {Float32Array} the input buffer in mono
34
34
  * @param outputLeft {Float32Array} left output buffer
35
35
  * @param outputRight {Float32Array} right output buffer
36
- * @param reverb {Float32Array[]} stereo reverb input
37
- * @param chorus {Float32Array[]} stereo chorus buffer
38
- * @this {SpessaSynthProcessor}
36
+ * @param reverbLeft {Float32Array} left reverb input
37
+ * @param reverbRight {Float32Array} right reverb input
38
+ * @param chorusLeft {Float32Array} left chorus buffer
39
+ * @param chorusRight {Float32Array} right chorus buffer
40
+ * @this {WorkletProcessorChannel}
39
41
  */
40
42
  export function panVoice(voice,
41
43
  inputBuffer,
42
44
  outputLeft, outputRight,
43
- reverb,
44
- chorus)
45
+ reverbLeft, reverbRight,
46
+ chorusLeft, chorusRight)
45
47
  {
46
48
  if (isNaN(inputBuffer[0]))
47
49
  {
@@ -58,40 +60,38 @@ export function panVoice(voice,
58
60
  }
59
61
  else
60
62
  {
61
- pan = Math.max(-500, Math.min(500, voice.modulatedGenerators[generatorTypes.pan]));
63
+ const target = Math.max(-500, Math.min(500, voice.modulatedGenerators[generatorTypes.pan]));
62
64
  // smooth out pan to prevent clicking
63
- voice.currentPan += (pan - voice.currentPan) * this.panSmoothingFactor;
65
+ voice.currentPan += (target - voice.currentPan) * this.synth.panSmoothingFactor;
66
+ pan = voice.currentPan;
64
67
  }
65
68
 
66
- const gain = this.currentGain;
69
+ const gain = this.synth.currentGain;
67
70
  const index = ~~(pan + 500);
68
71
  // get voice's gain levels for each channel
69
- const gainLeft = panTableLeft[index] * gain * this.panLeft;
70
- const gainRight = panTableRight[index] * gain * this.panRight;
72
+ const gainLeft = panTableLeft[index] * gain * this.synth.panLeft;
73
+ const gainRight = panTableRight[index] * gain * this.synth.panRight;
71
74
 
72
75
  // disable reverb and chorus in one output mode
73
- if (!this.oneOutputMode)
76
+ if (!this.synth.oneOutputMode)
74
77
  {
75
78
  // reverb is mono so we need to multiply by gain
76
- const reverbLevel = this.reverbGain * voice.modulatedGenerators[generatorTypes.reverbEffectsSend] / WORKLET_SYSTEM_REVERB_DIVIDER * gain;
79
+ const reverbLevel = this.synth.reverbGain * voice.modulatedGenerators[generatorTypes.reverbEffectsSend] / WORKLET_SYSTEM_REVERB_DIVIDER * gain;
77
80
  // chorus is stereo so we do not need to
78
- const chorusLevel = this.chorusGain * voice.modulatedGenerators[generatorTypes.chorusEffectsSend] / WORKLET_SYSTEM_CHORUS_DIVIDER;
81
+ const chorusLevel = this.synth.chorusGain * voice.modulatedGenerators[generatorTypes.chorusEffectsSend] / WORKLET_SYSTEM_CHORUS_DIVIDER;
79
82
 
80
83
  if (reverbLevel > 0)
81
84
  {
82
- const reverbLeft = reverb[0];
83
- const reverbRight = reverb[1];
84
85
  for (let i = 0; i < inputBuffer.length; i++)
85
86
  {
86
87
  reverbLeft[i] += reverbLevel * inputBuffer[i];
87
- reverbRight[i] += reverbLevel * inputBuffer[i];
88
88
  }
89
+ // copy as its mono
90
+ reverbRight.set(reverbLeft);
89
91
  }
90
92
 
91
93
  if (chorusLevel > 0)
92
94
  {
93
- const chorusLeft = chorus[0];
94
- const chorusRight = chorus[1];
95
95
  const chorusLeftGain = gainLeft * chorusLevel;
96
96
  const chorusRightGain = gainRight * chorusLevel;
97
97
  for (let i = 0; i < inputBuffer.length; i++)