spessasynth_lib 3.25.17 → 3.25.19

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 (29) hide show
  1. package/index.js +4 -0
  2. package/midi_parser/midi_builder.js +2 -1
  3. package/package.json +1 -1
  4. package/sequencer/worklet_sequencer/play.js +3 -3
  5. package/sequencer/worklet_sequencer/worklet_sequencer.js +4 -3
  6. package/soundfont/basic_soundfont/generator.js +1 -1
  7. package/soundfont/dls/read_articulation.js +35 -52
  8. package/soundfont/dls/read_region.js +1 -5
  9. package/synthetizer/synth_event_handler.js +0 -1
  10. package/synthetizer/synthetizer.js +122 -143
  11. package/synthetizer/worklet_processor.min.js +12 -12
  12. package/synthetizer/worklet_system/README.md +14 -1
  13. package/synthetizer/worklet_system/main_processor.js +199 -102
  14. package/synthetizer/worklet_system/message_protocol/worklet_message.js +25 -38
  15. package/synthetizer/worklet_system/worklet_methods/controller_control/controller_change.js +1 -1
  16. package/synthetizer/worklet_system/worklet_methods/controller_control/reset_controllers.js +1 -2
  17. package/synthetizer/worklet_system/worklet_methods/note_on.js +10 -17
  18. package/synthetizer/worklet_system/worklet_methods/program_change.js +2 -4
  19. package/synthetizer/worklet_system/worklet_methods/render_voice.js +2 -3
  20. package/synthetizer/worklet_system/worklet_methods/stopping_notes/kill_note.js +1 -1
  21. package/synthetizer/worklet_system/worklet_methods/stopping_notes/note_off.js +1 -1
  22. package/synthetizer/worklet_system/worklet_methods/stopping_notes/stop_all_notes.js +2 -2
  23. package/synthetizer/worklet_system/worklet_processor.js +334 -3
  24. package/synthetizer/worklet_system/worklet_utilities/lowpass_filter.js +20 -3
  25. package/synthetizer/worklet_system/worklet_utilities/stereo_panner.js +2 -2
  26. package/synthetizer/worklet_system/worklet_utilities/worklet_processor_channel.js +1 -1
  27. package/synthetizer/worklet_system/worklet_utilities/worklet_voice.js +24 -27
  28. package/synthetizer/worklet_system/message_protocol/handle_message.js +0 -242
  29. package/synthetizer/worklet_system/snapshot/send_synthesizer_snapshot.js +0 -14
package/index.js CHANGED
@@ -27,6 +27,8 @@ import { DEFAULT_SYNTH_CONFIG } from "./synthetizer/audio_effects/effects_config
27
27
  import { WORKLET_URL_ABSOLUTE } from './synthetizer/worklet_url.js';
28
28
  import { SynthesizerSnapshot} from "./synthetizer/worklet_system/snapshot/synthesizer_snapshot.js";
29
29
  import { ChannelSnapshot } from "./synthetizer/worklet_system/snapshot/channel_snapshot.js";
30
+ import { NON_CC_INDEX_OFFSET } from "./synthetizer/worklet_system/worklet_utilities/controller_tables.js";
31
+ import { ALL_CHANNELS_OR_DIFFERENT_ACTION } from "./synthetizer/worklet_system/message_protocol/worklet_message.js";
30
32
 
31
33
  // Export modules
32
34
  export {
@@ -38,6 +40,8 @@ export {
38
40
  DEFAULT_PERCUSSION,
39
41
  VOICE_CAP,
40
42
  DEFAULT_SYNTH_CONFIG,
43
+ ALL_CHANNELS_OR_DIFFERENT_ACTION,
44
+ NON_CC_INDEX_OFFSET,
41
45
 
42
46
  // SoundFont
43
47
  BasicSoundBank,
@@ -80,7 +80,8 @@ export class MIDIBuilder extends BasicMIDI
80
80
  }
81
81
  if (event === messageTypes.endOfTrack)
82
82
  {
83
- SpessaSynthWarn("The EndOfTrack is added automatically. Ignoring!");
83
+ SpessaSynthWarn(
84
+ "The EndOfTrack is added automatically and does not influence the duration. Consider adding a voice event instead.");
84
85
  return;
85
86
  }
86
87
  // remove the end of track
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "spessasynth_lib",
3
- "version": "3.25.17",
3
+ "version": "3.25.19",
4
4
  "description": "MIDI and SoundFont2/DLS library with no compromises",
5
5
  "browser": "index.js",
6
6
  "type": "module",
@@ -316,7 +316,7 @@ export function play(resetTime = false)
316
316
  {
317
317
  this.playingNotes.forEach(n =>
318
318
  {
319
- this.synth.noteOn(n.channel, n.midiNote, n.velocity, false, true);
319
+ this.synth.noteOn(n.channel, n.midiNote, n.velocity);
320
320
  });
321
321
  }
322
322
  this.setProcessHandler();
@@ -333,7 +333,7 @@ export function setTimeTicks(ticks)
333
333
  this.pausedTime = undefined;
334
334
  this.post(
335
335
  WorkletSequencerReturnMessageType.timeChange,
336
- currentTime - this.midiData.MIDIticksToSeconds(ticks)
336
+ this.synth.currentSynthTime - this.midiData.MIDIticksToSeconds(ticks)
337
337
  );
338
338
  const isNotFinished = this._playTo(0, ticks);
339
339
  this._recalculateStartTime(this.playedTime);
@@ -351,5 +351,5 @@ export function setTimeTicks(ticks)
351
351
  */
352
352
  export function _recalculateStartTime(time)
353
353
  {
354
- this.absoluteStartTime = currentTime - time / this._playbackRate;
354
+ this.absoluteStartTime = this.synth.currentSynthTime - time / this._playbackRate;
355
355
  }
@@ -83,7 +83,7 @@ class WorkletSequencer
83
83
  * Absolute playback startTime, bases on the synth's time
84
84
  * @type {number}
85
85
  */
86
- absoluteStartTime = currentTime;
86
+ absoluteStartTime = 0;
87
87
  /**
88
88
  * Currently playing notes (for pausing and resuming)
89
89
  * @type {{
@@ -142,6 +142,7 @@ class WorkletSequencer
142
142
  constructor(spessasynthProcessor)
143
143
  {
144
144
  this.synth = spessasynthProcessor;
145
+ this.absoluteStartTime = this.synth.currentSynthTime;
145
146
  }
146
147
 
147
148
  /**
@@ -169,7 +170,7 @@ class WorkletSequencer
169
170
  return this.pausedTime;
170
171
  }
171
172
 
172
- return (currentTime - this.absoluteStartTime) * this._playbackRate;
173
+ return (this.synth.currentSynthTime - this.absoluteStartTime) * this._playbackRate;
173
174
  }
174
175
 
175
176
  set currentTime(time)
@@ -199,7 +200,7 @@ class WorkletSequencer
199
200
  this.playingNotes = [];
200
201
  const wasPaused = this.paused && this.preservePlaybackState;
201
202
  this.pausedTime = undefined;
202
- this.post(WorkletSequencerReturnMessageType.timeChange, currentTime - time);
203
+ this.post(WorkletSequencerReturnMessageType.timeChange, this.synth.currentSynthTime - time);
203
204
  if (this.midiData.duration === 0)
204
205
  {
205
206
  SpessaSynthWarn("No duration!");
@@ -129,7 +129,7 @@ generatorLimits[generatorTypes.startloopAddrsCoarseOffset] = { min: -32768, max:
129
129
  generatorLimits[generatorTypes.keyNum] = { min: -1, max: 127, def: -1 };
130
130
  generatorLimits[generatorTypes.velocity] = { min: -1, max: 127, def: -1 };
131
131
 
132
- generatorLimits[generatorTypes.initialAttenuation] = { min: -250, max: 1440, def: 0 }; // sound blaster allows 10dB of gain (divide by 0.4)
132
+ generatorLimits[generatorTypes.initialAttenuation] = { min: 0, max: 1440, def: 0 };
133
133
 
134
134
  generatorLimits[generatorTypes.endloopAddrsCoarseOffset] = { min: -32768, max: 32768, def: 0 };
135
135
 
@@ -39,6 +39,14 @@ export function readArticulation(chunk, disableVibrato)
39
39
  const scale = readLittleEndian(artData, 4) | 0;
40
40
  const value = scale >> 16; // convert it to 16 bit as soundfont uses that
41
41
 
42
+ // modulatorConverterDebug(
43
+ // source,
44
+ // control,
45
+ // destination,
46
+ // value,
47
+ // transform
48
+ // );
49
+
42
50
  // interpret this somehow...
43
51
  // if source and control are both zero, it's a generator
44
52
  if (source === 0 && control === 0 && transform === 0)
@@ -147,6 +155,29 @@ export function readArticulation(chunk, disableVibrato)
147
155
  // if not, modulator?
148
156
  {
149
157
  let isGenerator = true;
158
+
159
+ const applyKeyToCorrection = (value, keyToGen, realGen) =>
160
+ {
161
+ // according to viena and another strange (with modulators) rendition of gm.dls in sf2,
162
+ // it shall be divided by -128
163
+ // and a strange correction needs to be applied to the real value:
164
+ // real + (60 / 128) * scale
165
+ const keyToGenValue = value / -128;
166
+ generators.push(new Generator(keyToGen, keyToGenValue));
167
+ // airfont 340 fix
168
+ if (keyToGenValue <= 120)
169
+ {
170
+ const correction = Math.round((60 / 128) * value);
171
+ generators.forEach(g =>
172
+ {
173
+ if (g.generatorType === realGen)
174
+ {
175
+ g.generatorValue += correction;
176
+ }
177
+ });
178
+ }
179
+ };
180
+
150
181
  // a few special cases which are generators:
151
182
  if (control === DLSSources.none)
152
183
  {
@@ -197,73 +228,25 @@ export function readArticulation(chunk, disableVibrato)
197
228
  // key to vol env hold
198
229
  if (source === DLSSources.keyNum && destination === DLSDestinations.volEnvHold)
199
230
  {
200
- // according to viena and another strange (with modulators) rendition of gm.dls in sf2,
201
- // it shall be divided by -128
202
- // and a strange correction needs to be applied to the real value:
203
- // real + (60 / 128) * scale
204
- generators.push(new Generator(generatorTypes.keyNumToVolEnvHold, value / -128));
205
- const correction = Math.round((60 / 128) * value);
206
- generators.forEach(g =>
207
- {
208
- if (g.generatorType === generatorTypes.holdVolEnv)
209
- {
210
- g.generatorValue += correction;
211
- }
212
- });
231
+ applyKeyToCorrection(value, generatorTypes.keyNumToVolEnvHold, generatorTypes.holdVolEnv);
213
232
  }
214
233
  else
215
234
  // key to vol env decay
216
235
  if (source === DLSSources.keyNum && destination === DLSDestinations.volEnvDecay)
217
236
  {
218
- // according to viena and another strange (with modulators) rendition of gm.dls in sf2,
219
- // it shall be divided by -128
220
- // and a strange correction needs to be applied to the real value:
221
- // real + (60 / 128) * scale
222
- generators.push(new Generator(generatorTypes.keyNumToVolEnvDecay, value / -128));
223
- const correction = Math.round((60 / 128) * value);
224
- generators.forEach(g =>
225
- {
226
- if (g.generatorType === generatorTypes.decayVolEnv)
227
- {
228
- g.generatorValue += correction;
229
- }
230
- });
237
+ applyKeyToCorrection(value, generatorTypes.keyNumToVolEnvDecay, generatorTypes.decayVolEnv);
231
238
  }
232
239
  else
233
240
  // key to mod env hold
234
241
  if (source === DLSSources.keyNum && destination === DLSDestinations.modEnvHold)
235
242
  {
236
- // according to viena and another strange (with modulators) rendition of gm.dls in sf2,
237
- // it shall be divided by -128
238
- // and a strange correction needs to be applied to the real value:
239
- // real + (60 / 128) * scale
240
- generators.push(new Generator(generatorTypes.keyNumToModEnvHold, value / -128));
241
- const correction = Math.round((60 / 128) * value);
242
- generators.forEach(g =>
243
- {
244
- if (g.generatorType === generatorTypes.holdModEnv)
245
- {
246
- g.generatorValue += correction;
247
- }
248
- });
243
+ applyKeyToCorrection(value, generatorTypes.keyNumToModEnvHold, generatorTypes.holdModEnv);
249
244
  }
250
245
  else
251
246
  // key to mod env decay
252
247
  if (source === DLSSources.keyNum && destination === DLSDestinations.modEnvDecay)
253
248
  {
254
- // according to viena and another strange (with modulators) rendition of gm.dls in sf2,
255
- // it shall be divided by -128
256
- // and a strange correction needs to be applied to the real value:
257
- // real + (60 / 128) * scale
258
- generators.push(new Generator(generatorTypes.keyNumToModEnvDecay, value / -128));
259
- const correction = Math.round((60 / 128) * value);
260
- generators.forEach(g =>
261
- {
262
- if (g.generatorType === generatorTypes.decayModEnv)
263
- {
264
- g.generatorValue += correction;
265
- }
266
- });
249
+ applyKeyToCorrection(value, generatorTypes.keyNumToModEnvDecay, generatorTypes.decayModEnv);
267
250
  }
268
251
  else
269
252
  {
@@ -37,11 +37,7 @@ export function readRegion(chunk)
37
37
  velMax = 127;
38
38
  velMin = 0;
39
39
  }
40
- if (keyMin === 0 && keyMax === 0)
41
- {
42
- keyMax = 127;
43
- keyMin = 0;
44
- }
40
+ // cannot do the same to key zones sadly
45
41
 
46
42
  const zone = new DLSZone(
47
43
  { min: keyMin, max: keyMax },
@@ -27,7 +27,6 @@
27
27
  * @property {number} channel - The MIDI channel number.
28
28
  * @property {number} program - The program number.
29
29
  * @property {number} bank - The bank number.
30
- * @property {boolean} userCalled - Indicates if the change was user-initiated.
31
30
  */
32
31
 
33
32
  /**
@@ -1,6 +1,5 @@
1
- import { IndexedByteArray } from "../utils/indexed_array.js";
2
1
  import { consoleColors } from "../utils/other.js";
3
- import { getEvent, messageTypes, midiControllers } from "../midi_parser/midi_message.js";
2
+ import { messageTypes, midiControllers } from "../midi_parser/midi_message.js";
4
3
  import { EventHandler } from "./synth_event_handler.js";
5
4
  import { FancyChorus } from "./audio_effects/fancy_chorus.js";
6
5
  import { getReverbProcessor } from "./audio_effects/reverb.js";
@@ -32,7 +31,20 @@ import { DEFAULT_SEQUENCER_OPTIONS } from "../sequencer/default_sequencer_option
32
31
  * purpose: responds to midi messages and called functions, managing the channels and passing the messages to them
33
32
  */
34
33
 
34
+ /**
35
+ * @typedef {Object} SynthMethodOptions
36
+ * @property {number} time - the audio context time when the event should execute, in seconds.
37
+ */
35
38
 
39
+ /**
40
+ * @type {SynthMethodOptions}
41
+ */
42
+ const DEFAULT_SYNTH_METHOD_OPTIONS = {
43
+ time: 0
44
+ };
45
+
46
+
47
+ // the "remote controller" of the worklet processor in the audio thread from the main thread
36
48
  export class Synthetizer
37
49
  {
38
50
 
@@ -566,20 +578,53 @@ export class Synthetizer
566
578
  });
567
579
  }
568
580
 
581
+ /**
582
+ * sends a raw MIDI message to the synthesizer.
583
+ * @param message {number[]|Uint8Array} the midi message, each number is a byte.
584
+ * @param channelOffset {number} the channel offset of the message.
585
+ * @param eventOptions {SynthMethodOptions} additional options for this command.
586
+ */
587
+ sendMessage(message, channelOffset = 0, eventOptions = DEFAULT_SYNTH_METHOD_OPTIONS)
588
+ {
589
+ this._sendInternal(message, channelOffset, false, eventOptions);
590
+ }
591
+
592
+ /**
593
+ * @param message {number[]|Uint8Array}
594
+ * @param offset {number}
595
+ * @param force {boolean}
596
+ * @param eventOptions {SynthMethodOptions}
597
+ * @private
598
+ */
599
+ _sendInternal(message, offset, force = false, eventOptions)
600
+ {
601
+ const opts = fillWithDefaults(eventOptions ?? {}, DEFAULT_SYNTH_METHOD_OPTIONS);
602
+ this.post({
603
+ messageType: workletMessageType.midiMessage,
604
+ messageData: [new Uint8Array(message), offset, force, opts]
605
+ });
606
+ }
607
+
608
+
569
609
  /**
570
610
  * Starts playing a note
571
611
  * @param channel {number} usually 0-15: the channel to play the note.
572
612
  * @param midiNote {number} 0-127 the key number of the note.
573
613
  * @param velocity {number} 0-127 the velocity of the note (generally controls loudness).
574
- * @param enableDebugging {boolean} set to true to log technical details to console.
614
+ * @param eventOptions {SynthMethodOptions} additional options for this command.
575
615
  */
576
- noteOn(channel, midiNote, velocity, enableDebugging = false)
616
+ noteOn(channel, midiNote, velocity, eventOptions = DEFAULT_SYNTH_METHOD_OPTIONS)
577
617
  {
578
- this.post({
579
- channelNumber: channel,
580
- messageType: workletMessageType.noteOn,
581
- messageData: [midiNote, velocity, enableDebugging]
582
- });
618
+ const ch = channel % 16;
619
+ const offset = channel - ch;
620
+ midiNote %= 128;
621
+ velocity %= 128;
622
+ // check for legacy "enableDebugging"
623
+ if (eventOptions === true)
624
+ {
625
+ eventOptions = DEFAULT_SYNTH_METHOD_OPTIONS;
626
+ }
627
+ this.sendMessage([messageTypes.noteOn | ch, midiNote, velocity], offset, eventOptions);
583
628
  }
584
629
 
585
630
  /**
@@ -587,25 +632,15 @@ export class Synthetizer
587
632
  * @param channel {number} usually 0-15: the channel of the note.
588
633
  * @param midiNote {number} 0-127 the key number of the note.
589
634
  * @param force {boolean} instantly kills the note if true.
635
+ * @param eventOptions {SynthMethodOptions} additional options for this command.
590
636
  */
591
- noteOff(channel, midiNote, force = false)
637
+ noteOff(channel, midiNote, force = false, eventOptions = DEFAULT_SYNTH_METHOD_OPTIONS)
592
638
  {
593
- if (force)
594
- {
595
- this.post({
596
- channelNumber: channel,
597
- messageType: workletMessageType.killNote,
598
- messageData: midiNote
599
- });
600
- }
601
- else
602
- {
603
- this.post({
604
- channelNumber: channel,
605
- messageType: workletMessageType.noteOff,
606
- messageData: midiNote
607
- });
608
- }
639
+ midiNote %= 128;
640
+
641
+ const ch = channel % 16;
642
+ const offset = channel - ch;
643
+ this._sendInternal([messageTypes.noteOff | ch, midiNote], offset, force, eventOptions);
609
644
  }
610
645
 
611
646
  /**
@@ -628,20 +663,25 @@ export class Synthetizer
628
663
  * @param controllerNumber {number} 0-127 the MIDI CC number.
629
664
  * @param controllerValue {number} 0-127 the controller value.
630
665
  * @param force {boolean} forces the controller-change message, even if it's locked or gm system is set and the cc is bank select.
666
+ * @param eventOptions {SynthMethodOptions} additional options for this command.
631
667
  */
632
- controllerChange(channel, controllerNumber, controllerValue, force = false)
668
+ controllerChange(channel, controllerNumber, controllerValue, force = false, eventOptions = DEFAULT_SYNTH_METHOD_OPTIONS)
633
669
  {
634
670
  if (controllerNumber > 127 || controllerNumber < 0)
635
671
  {
636
672
  throw new Error(`Invalid controller number: ${controllerNumber}`);
637
673
  }
638
- controllerValue = Math.floor(controllerValue);
639
- controllerNumber = Math.floor(controllerNumber);
640
- this.post({
641
- channelNumber: channel,
642
- messageType: workletMessageType.ccChange,
643
- messageData: [controllerNumber, controllerValue, force]
644
- });
674
+ controllerValue = Math.floor(controllerValue) % 128;
675
+ controllerNumber = Math.floor(controllerNumber) % 128;
676
+ // controller change has its own message for the force property
677
+ const ch = channel % 16;
678
+ const offset = channel - ch;
679
+ this._sendInternal(
680
+ [messageTypes.controllerChange | ch, controllerNumber, controllerValue],
681
+ offset,
682
+ force,
683
+ eventOptions
684
+ );
645
685
  }
646
686
 
647
687
  /**
@@ -660,14 +700,14 @@ export class Synthetizer
660
700
  * Applies pressure to a given channel.
661
701
  * @param channel {number} usually 0-15: the channel to change the controller.
662
702
  * @param pressure {number} 0-127: the pressure to apply.
703
+ * @param eventOptions {SynthMethodOptions} additional options for this command.
663
704
  */
664
- channelPressure(channel, pressure)
705
+ channelPressure(channel, pressure, eventOptions = DEFAULT_SYNTH_METHOD_OPTIONS)
665
706
  {
666
- this.post({
667
- channelNumber: channel,
668
- messageType: workletMessageType.channelPressure,
669
- messageData: pressure
670
- });
707
+ const ch = channel % 16;
708
+ const offset = channel - ch;
709
+ pressure %= 128;
710
+ this.sendMessage([messageTypes.channelPressure | ch, pressure], offset, eventOptions);
671
711
  }
672
712
 
673
713
  /**
@@ -675,14 +715,29 @@ export class Synthetizer
675
715
  * @param channel {number} usually 0-15: the channel to change the controller.
676
716
  * @param midiNote {number} 0-127: the MIDI note.
677
717
  * @param pressure {number} 0-127: the pressure to apply.
718
+ * @param eventOptions {SynthMethodOptions} additional options for this command.
678
719
  */
679
- polyPressure(channel, midiNote, pressure)
720
+ polyPressure(channel, midiNote, pressure, eventOptions = DEFAULT_SYNTH_METHOD_OPTIONS)
680
721
  {
681
- this.post({
682
- channelNumber: channel,
683
- messageType: workletMessageType.polyPressure,
684
- messageData: [midiNote, pressure]
685
- });
722
+ const ch = channel % 16;
723
+ const offset = channel - ch;
724
+ midiNote %= 128;
725
+ pressure %= 128;
726
+ this.sendMessage([messageTypes.polyPressure | ch, midiNote, pressure], offset, eventOptions);
727
+ }
728
+
729
+ /**
730
+ * Sets the pitch of the given channel.
731
+ * @param channel {number} usually 0-15: the channel to change pitch.
732
+ * @param MSB {number} SECOND byte of the MIDI pitchWheel message.
733
+ * @param LSB {number} FIRST byte of the MIDI pitchWheel message.
734
+ * @param eventOptions {SynthMethodOptions} additional options for this command.
735
+ */
736
+ pitchWheel(channel, MSB, LSB, eventOptions = DEFAULT_SYNTH_METHOD_OPTIONS)
737
+ {
738
+ const ch = channel % 16;
739
+ const offset = channel - ch;
740
+ this.sendMessage([messageTypes.pitchBend | ch, LSB, MSB], offset, eventOptions);
686
741
  }
687
742
 
688
743
  /**
@@ -697,21 +752,6 @@ export class Synthetizer
697
752
  this.worklet.port.postMessage(data);
698
753
  }
699
754
 
700
- /**
701
- * Sets the pitch of the given channel.
702
- * @param channel {number} usually 0-15: the channel to change pitch.
703
- * @param MSB {number} SECOND byte of the MIDI pitchWheel message.
704
- * @param LSB {number} FIRST byte of the MIDI pitchWheel message.
705
- */
706
- pitchWheel(channel, MSB, LSB)
707
- {
708
- this.post({
709
- channelNumber: channel,
710
- messageType: workletMessageType.pitchWheel,
711
- messageData: [MSB, LSB]
712
- });
713
- }
714
-
715
755
  /**
716
756
  * Transposes the synthetizer's pitch by given semitones amount (percussion channels don’t get affected).
717
757
  * @param semitones {number} the semitones to transpose by.
@@ -776,16 +816,14 @@ export class Synthetizer
776
816
  * Changes the patch for a given channel
777
817
  * @param channel {number} usually 0-15: the channel to change
778
818
  * @param programNumber {number} 0-127 the MIDI patch number
779
- * @param userChange {boolean} indicates if user has called the program change.
780
819
  * defaults to false
781
820
  */
782
- programChange(channel, programNumber, userChange = false)
821
+ programChange(channel, programNumber)
783
822
  {
784
- this.post({
785
- channelNumber: channel,
786
- messageType: workletMessageType.programChange,
787
- messageData: [programNumber, userChange]
788
- });
823
+ const ch = channel % 16;
824
+ const offset = channel - ch;
825
+ programNumber %= 128;
826
+ this.sendMessage([messageTypes.programChange | ch, programNumber], offset);
789
827
  }
790
828
 
791
829
  /**
@@ -796,11 +834,14 @@ export class Synthetizer
796
834
  */
797
835
  velocityOverride(channel, velocity)
798
836
  {
799
- this.post({
800
- channelNumber: channel,
801
- messageType: workletMessageType.ccChange,
802
- messageData: [channelConfiguration.velocityOverride, velocity, true]
803
- });
837
+ const ch = channel % 16;
838
+ const offset = channel - ch;
839
+ this._sendInternal(
840
+ [messageTypes.controllerChange | ch, channelConfiguration.velocityOverride, velocity],
841
+ offset,
842
+ true,
843
+ DEFAULT_SYNTH_METHOD_OPTIONS
844
+ );
804
845
  }
805
846
 
806
847
  /**
@@ -851,14 +892,16 @@ export class Synthetizer
851
892
  * @param messageData {number[]|ArrayLike|Uint8Array} the message's data
852
893
  * (excluding the F0 byte, but including the F7 at the end).
853
894
  * @param channelOffset {number} channel offset for the system exclusive message, defaults to zero.
895
+ * @param eventOptions {SynthMethodOptions} additional options for this command.
854
896
  */
855
- systemExclusive(messageData, channelOffset = 0)
897
+ systemExclusive(messageData, channelOffset = 0, eventOptions = DEFAULT_SYNTH_METHOD_OPTIONS)
856
898
  {
857
- this.post({
858
- channelNumber: ALL_CHANNELS_OR_DIFFERENT_ACTION,
859
- messageType: workletMessageType.systemExclusive,
860
- messageData: [Array.from(messageData), channelOffset]
861
- });
899
+ this._sendInternal(
900
+ [messageTypes.systemExclusive, ...Array.from(messageData)],
901
+ channelOffset,
902
+ false,
903
+ eventOptions
904
+ );
862
905
  }
863
906
 
864
907
  // noinspection JSUnusedGlobalSymbols
@@ -919,70 +962,6 @@ export class Synthetizer
919
962
  });
920
963
  }
921
964
 
922
- /**
923
- * sends a raw MIDI message to the synthesizer.
924
- * @param message {number[]|Uint8Array} the midi message, each number is a byte.
925
- * @param channelOffset {number} the channel offset of the message.
926
- */
927
- sendMessage(message, channelOffset = 0)
928
- {
929
- // discard as soon as possible if high perf
930
- const statusByteData = getEvent(message[0]);
931
-
932
- statusByteData.channel += channelOffset;
933
- // process the event
934
- switch (statusByteData.status)
935
- {
936
- case messageTypes.noteOn:
937
- const velocity = message[2];
938
- if (velocity > 0)
939
- {
940
- this.noteOn(statusByteData.channel, message[1], velocity);
941
- }
942
- else
943
- {
944
- this.noteOff(statusByteData.channel, message[1]);
945
- }
946
- break;
947
-
948
- case messageTypes.noteOff:
949
- this.noteOff(statusByteData.channel, message[1]);
950
- break;
951
-
952
- case messageTypes.pitchBend:
953
- this.pitchWheel(statusByteData.channel, message[2], message[1]);
954
- break;
955
-
956
- case messageTypes.controllerChange:
957
- this.controllerChange(statusByteData.channel, message[1], message[2]);
958
- break;
959
-
960
- case messageTypes.programChange:
961
- this.programChange(statusByteData.channel, message[1]);
962
- break;
963
-
964
- case messageTypes.polyPressure:
965
- this.polyPressure(statusByteData.channel, message[0], message[1]);
966
- break;
967
-
968
- case messageTypes.channelPressure:
969
- this.channelPressure(statusByteData.channel, message[1]);
970
- break;
971
-
972
- case messageTypes.systemExclusive:
973
- this.systemExclusive(new IndexedByteArray(message.slice(1)));
974
- break;
975
-
976
- case messageTypes.reset:
977
- this.stopAll(true);
978
- this.resetControllers();
979
- break;
980
-
981
- default:
982
- break;
983
- }
984
- }
985
-
986
965
  /**
987
966
  * Updates the reverb processor with a new impulse response.
988
967
  * @param buffer {AudioBuffer} the new reverb impulse response.