spessasynth_core 4.0.15 → 4.0.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -3743,6 +3743,7 @@ interface SequencerEventData {
3743
3743
  };
3744
3744
  /**
3745
3745
  * Called when the playback stops.
3746
+ * @deprecated use songEnded instead.
3746
3747
  */
3747
3748
  pause: {
3748
3749
  /**
@@ -3750,6 +3751,10 @@ interface SequencerEventData {
3750
3751
  */
3751
3752
  isFinished: boolean;
3752
3753
  };
3754
+ /**
3755
+ * Called when the playback stops.
3756
+ */
3757
+ songEnded: object;
3753
3758
  /**
3754
3759
  * Called when the song changes.
3755
3760
  */
@@ -3817,6 +3822,11 @@ declare class SpessaSynthSequencer {
3817
3822
  * This is used by spessasynth_lib to pass them over to Web MIDI API.
3818
3823
  */
3819
3824
  externalMIDIPlayback: boolean;
3825
+ /**
3826
+ * If the notes that were playing when the sequencer was paused should be re-triggered.
3827
+ * Defaults to true.
3828
+ */
3829
+ retriggerPausedNotes: boolean;
3820
3830
  /**
3821
3831
  * The loop count of the sequencer.
3822
3832
  * If infinite, it will loop forever.
@@ -3828,6 +3838,15 @@ declare class SpessaSynthSequencer {
3828
3838
  * Defaults to true.
3829
3839
  */
3830
3840
  skipToFirstNoteOn: boolean;
3841
+ /**
3842
+ * Indicates if the sequencer has finished playing.
3843
+ */
3844
+ isFinished: boolean;
3845
+ /**
3846
+ * Indicates if the synthesizer should preload the voices for the newly loaded sequence.
3847
+ * Recommended.
3848
+ */
3849
+ preload: boolean;
3831
3850
  /**
3832
3851
  * Called when the sequencer calls an event.
3833
3852
  * @param event The event
@@ -3987,18 +4006,10 @@ declare class SpessaSynthSequencer {
3987
4006
  */
3988
4007
  protected addNewMIDIPort(): void;
3989
4008
  protected sendMIDIMessage(message: number[]): void;
4009
+ protected sendMIDIAllOff(): void;
3990
4010
  protected sendMIDIReset(): void;
3991
4011
  protected loadCurrentSong(): void;
3992
4012
  protected shuffleSongIndexes(): void;
3993
- protected sendMIDICC(channel: number, type: number, value: number): void;
3994
- protected sendMIDIProgramChange(channel: number, program: number): void;
3995
- /**
3996
- * Sets the pitch of the given channel
3997
- * @param channel usually 0-15: the channel to change pitch
3998
- * @param MSB SECOND byte of the MIDI pitchWheel message
3999
- * @param LSB FIRST byte of the MIDI pitchWheel message
4000
- */
4001
- protected sendMIDIPitchWheel(channel: number, MSB: number, LSB: number): void;
4002
4013
  /**
4003
4014
  * Sets the time in MIDI ticks.
4004
4015
  * @param ticks the MIDI ticks to set the time to.
@@ -4009,6 +4020,16 @@ declare class SpessaSynthSequencer {
4009
4020
  * @param time the time in seconds to recalculate the start time for.
4010
4021
  */
4011
4022
  protected recalculateStartTime(time: number): void;
4023
+ protected sendMIDINoteOn(channel: number, midiNote: number, velocity: number): void;
4024
+ protected sendMIDINoteOff(channel: number, midiNote: number): void;
4025
+ protected sendMIDICC(channel: number, type: MIDIController, value: number): void;
4026
+ protected sendMIDIProgramChange(channel: number, program: number): void;
4027
+ /**
4028
+ * Sets the pitch of the given channel
4029
+ * @param channel usually 0-15: the channel to change pitch
4030
+ * @param pitch the 14-bit pitch value
4031
+ */
4032
+ protected sendMIDIPitchWheel(channel: number, pitch: number): void;
4012
4033
  }
4013
4034
 
4014
4035
  declare const DEFAULT_MASTER_PARAMETERS: MasterParameterType;
package/dist/index.js CHANGED
@@ -3942,10 +3942,12 @@ function loadNewSequenceInternal(parsedMidi) {
3942
3942
  if (parsedMidi.duration === 0) {
3943
3943
  SpessaSynthWarn("This MIDI file has a duration of exactly 0 seconds.");
3944
3944
  this.pausedTime = 0;
3945
+ this.isFinished = true;
3945
3946
  return;
3946
3947
  }
3947
3948
  this.oneTickToSeconds = 60 / (120 * parsedMidi.timeDivision);
3948
3949
  this._midiData = parsedMidi;
3950
+ this.isFinished = false;
3949
3951
  this.synth.clearEmbeddedBank();
3950
3952
  if (this._midiData.embeddedSoundBank !== void 0) {
3951
3953
  SpessaSynthInfo(
@@ -3957,23 +3959,33 @@ function loadNewSequenceInternal(parsedMidi) {
3957
3959
  this._midiData.bankOffset
3958
3960
  );
3959
3961
  }
3960
- SpessaSynthGroupCollapsed("%cPreloading samples...", consoleColors.info);
3961
- const used = this._midiData.getUsedProgramsAndKeys(
3962
- this.synth.soundBankManager
3963
- );
3964
- used.forEach((combos, preset) => {
3965
- SpessaSynthInfo(
3966
- `%cPreloading used samples on %c${preset.name}%c...`,
3967
- consoleColors.info,
3968
- consoleColors.recognized,
3962
+ if (this.preload) {
3963
+ SpessaSynthGroupCollapsed(
3964
+ "%cPreloading samples...",
3969
3965
  consoleColors.info
3970
3966
  );
3971
- for (const combo of combos) {
3972
- const [midiNote, velocity] = combo.split("-").map(Number);
3973
- this.synth.getVoicesForPreset(preset, midiNote, velocity, midiNote);
3974
- }
3975
- });
3976
- SpessaSynthGroupEnd();
3967
+ const used = this._midiData.getUsedProgramsAndKeys(
3968
+ this.synth.soundBankManager
3969
+ );
3970
+ used.forEach((combos, preset) => {
3971
+ SpessaSynthInfo(
3972
+ `%cPreloading used samples on %c${preset.name}%c...`,
3973
+ consoleColors.info,
3974
+ consoleColors.recognized,
3975
+ consoleColors.info
3976
+ );
3977
+ for (const combo of combos) {
3978
+ const [midiNote, velocity] = combo.split("-").map(Number);
3979
+ this.synth.getVoicesForPreset(
3980
+ preset,
3981
+ midiNote,
3982
+ velocity,
3983
+ midiNote
3984
+ );
3985
+ }
3986
+ });
3987
+ SpessaSynthGroupEnd();
3988
+ }
3977
3989
  this.currentMIDIPorts = this._midiData.tracks.map((t) => t.port);
3978
3990
  this.midiPortChannelOffset = 0;
3979
3991
  this.midiPortChannelOffsets = {};
@@ -4722,12 +4734,7 @@ function setTimeToInternal(time, ticks = void 0) {
4722
4734
  return false;
4723
4735
  }
4724
4736
  this.oneTickToSeconds = 60 / (120 * this._midiData.timeDivision);
4725
- if (this.externalMIDIPlayback) {
4726
- this.sendMIDIReset();
4727
- } else {
4728
- this.synth.resetAllControllers();
4729
- this.synth.stopAllChannels(false);
4730
- }
4737
+ this.sendMIDIReset();
4731
4738
  this.playedTime = 0;
4732
4739
  this.eventIndexes = Array(this._midiData.tracks.length).fill(0);
4733
4740
  const channelsToSave = this.synth.midiChannels.length;
@@ -4811,15 +4818,7 @@ function setTimeToInternal(time, ticks = void 0) {
4811
4818
  } else if (controllerNumber === midiControllers.resetAllControllers) {
4812
4819
  resetAllControllers(channel);
4813
4820
  }
4814
- if (this.externalMIDIPlayback) {
4815
- this.sendMIDICC(channel, controllerNumber, ccV);
4816
- } else {
4817
- this.synth.controllerChange(
4818
- channel,
4819
- controllerNumber,
4820
- ccV
4821
- );
4822
- }
4821
+ this.sendMIDICC(channel, controllerNumber, ccV);
4823
4822
  } else {
4824
4823
  savedControllers[channel] ??= Array.from(
4825
4824
  defaultControllerArray
@@ -4847,70 +4846,28 @@ function setTimeToInternal(time, ticks = void 0) {
4847
4846
  }
4848
4847
  this.playedTime += this.oneTickToSeconds * (nextEvent.ticks - event.ticks);
4849
4848
  }
4850
- if (this.externalMIDIPlayback) {
4851
- for (let channelNumber = 0; channelNumber < channelsToSave; channelNumber++) {
4852
- if (pitchWheels[channelNumber] !== void 0) {
4853
- this.sendMIDIPitchWheel(
4854
- channelNumber,
4855
- pitchWheels[channelNumber] >> 7,
4856
- pitchWheels[channelNumber] & 127
4857
- );
4858
- }
4859
- if (savedControllers[channelNumber] !== void 0) {
4860
- savedControllers[channelNumber].forEach((value, index) => {
4861
- if (value !== defaultControllerArray[index] && !isCCNonSkippable(index)) {
4862
- this.sendMIDICC(channelNumber, index, value);
4863
- }
4864
- });
4865
- }
4866
- if (programs[channelNumber].program >= 0 && programs[channelNumber].actualBank >= 0) {
4867
- const bank = programs[channelNumber].actualBank;
4849
+ for (let channel = 0; channel < channelsToSave; channel++) {
4850
+ if (pitchWheels[channel] !== void 0) {
4851
+ this.sendMIDIPitchWheel(channel, pitchWheels[channel]);
4852
+ }
4853
+ if (savedControllers[channel] !== void 0) {
4854
+ savedControllers[channel].forEach((value, index) => {
4855
+ if (value !== defaultControllerArray[index] && !isCCNonSkippable(index)) {
4856
+ this.sendMIDICC(channel, index, value);
4857
+ }
4858
+ });
4859
+ }
4860
+ if (programs[channel].actualBank >= 0) {
4861
+ const p = programs[channel];
4862
+ if (p.program !== -1) {
4868
4863
  this.sendMIDICC(
4869
- channelNumber,
4864
+ channel,
4870
4865
  midiControllers.bankSelect,
4871
- bank
4872
- );
4873
- this.sendMIDIProgramChange(
4874
- channelNumber,
4875
- programs[channelNumber].program
4876
- );
4877
- }
4878
- }
4879
- } else {
4880
- for (let channelNumber = 0; channelNumber < channelsToSave; channelNumber++) {
4881
- if (pitchWheels[channelNumber] !== void 0) {
4882
- this.synth.pitchWheel(
4883
- channelNumber,
4884
- pitchWheels[channelNumber]
4866
+ p.actualBank
4885
4867
  );
4886
- }
4887
- if (savedControllers[channelNumber] !== void 0) {
4888
- savedControllers[channelNumber].forEach((value, index) => {
4889
- if (value !== defaultControllerArray[index] && !isCCNonSkippable(index)) {
4890
- this.synth.controllerChange(
4891
- channelNumber,
4892
- index,
4893
- value
4894
- );
4895
- }
4896
- });
4897
- }
4898
- if (programs[channelNumber].actualBank >= 0) {
4899
- const p = programs[channelNumber];
4900
- if (p.program !== -1) {
4901
- this.synth.controllerChange(
4902
- channelNumber,
4903
- midiControllers.bankSelect,
4904
- p.actualBank
4905
- );
4906
- this.synth.programChange(channelNumber, p.program);
4907
- } else {
4908
- this.synth.controllerChange(
4909
- channelNumber,
4910
- midiControllers.bankSelect,
4911
- p.bank
4912
- );
4913
- }
4868
+ this.sendMIDIProgramChange(channel, p.program);
4869
+ } else {
4870
+ this.sendMIDICC(channel, midiControllers.bankSelect, p.bank);
4914
4871
  }
4915
4872
  }
4916
4873
  }
@@ -4946,6 +4903,11 @@ var SpessaSynthSequencer = class {
4946
4903
  * This is used by spessasynth_lib to pass them over to Web MIDI API.
4947
4904
  */
4948
4905
  externalMIDIPlayback = false;
4906
+ /**
4907
+ * If the notes that were playing when the sequencer was paused should be re-triggered.
4908
+ * Defaults to true.
4909
+ */
4910
+ retriggerPausedNotes = false;
4949
4911
  /**
4950
4912
  * The loop count of the sequencer.
4951
4913
  * If infinite, it will loop forever.
@@ -4957,6 +4919,15 @@ var SpessaSynthSequencer = class {
4957
4919
  * Defaults to true.
4958
4920
  */
4959
4921
  skipToFirstNoteOn = true;
4922
+ /**
4923
+ * Indicates if the sequencer has finished playing.
4924
+ */
4925
+ isFinished = false;
4926
+ /**
4927
+ * Indicates if the synthesizer should preload the voices for the newly loaded sequence.
4928
+ * Recommended.
4929
+ */
4930
+ preload = false;
4960
4931
  /**
4961
4932
  * Called when the sequencer calls an event.
4962
4933
  * @param event The event
@@ -5168,9 +5139,9 @@ var SpessaSynthSequencer = class {
5168
5139
  if (this.paused) {
5169
5140
  this.recalculateStartTime(this.pausedTime ?? 0);
5170
5141
  }
5171
- if (!this.externalMIDIPlayback) {
5142
+ if (this.retriggerPausedNotes) {
5172
5143
  this.playingNotes.forEach((n) => {
5173
- this.synth.noteOn(n.channel, n.midiNote, n.velocity);
5144
+ this.sendMIDINoteOn(n.channel, n.midiNote, n.velocity);
5174
5145
  });
5175
5146
  }
5176
5147
  this.pausedTime = void 0;
@@ -5208,8 +5179,12 @@ var SpessaSynthSequencer = class {
5208
5179
  }
5209
5180
  this.stop();
5210
5181
  this.callEvent("pause", { isFinished });
5182
+ if (isFinished) {
5183
+ this.callEvent("songEnded", {});
5184
+ }
5211
5185
  }
5212
5186
  songIsFinished() {
5187
+ this.isFinished = true;
5213
5188
  if (this.songs.length === 1) {
5214
5189
  this.pauseInternal(true);
5215
5190
  return;
@@ -5223,21 +5198,7 @@ var SpessaSynthSequencer = class {
5223
5198
  */
5224
5199
  stop() {
5225
5200
  this.pausedTime = this.currentTime;
5226
- for (let i = 0; i < 16; i++) {
5227
- this.synth.controllerChange(i, midiControllers.sustainPedal, 0);
5228
- }
5229
- this.synth.stopAllChannels();
5230
- if (this.externalMIDIPlayback) {
5231
- for (const note of this.playingNotes) {
5232
- this.sendMIDIMessage([
5233
- midiMessageTypes.noteOff | note.channel % 16,
5234
- note.midiNote
5235
- ]);
5236
- }
5237
- for (let c = 0; c < MIDI_CHANNEL_COUNT; c++) {
5238
- this.sendMIDICC(c, midiControllers.allNotesOff, 0);
5239
- }
5240
- }
5201
+ this.sendMIDIAllOff();
5241
5202
  }
5242
5203
  /**
5243
5204
  * @returns the index of the first to the current played time
@@ -5267,24 +5228,36 @@ var SpessaSynthSequencer = class {
5267
5228
  }
5268
5229
  sendMIDIMessage(message) {
5269
5230
  if (!this.externalMIDIPlayback) {
5231
+ SpessaSynthWarn(
5232
+ `Attempting to send ${arrayToHexString(message)} to the synthesizer via sendMIDIMessage. This shouldn't happen!`
5233
+ );
5270
5234
  return;
5271
5235
  }
5272
5236
  this.callEvent("midiMessage", { message });
5273
5237
  }
5238
+ sendMIDIAllOff() {
5239
+ for (let i = 0; i < 16; i++) {
5240
+ this.sendMIDICC(i, midiControllers.sustainPedal, 0);
5241
+ }
5242
+ if (!this.externalMIDIPlayback) {
5243
+ this.synth.stopAllChannels();
5244
+ return;
5245
+ }
5246
+ this.playingNotes.forEach((note) => {
5247
+ this.sendMIDINoteOff(note.channel, note.midiNote);
5248
+ });
5249
+ for (let c = 0; c < MIDI_CHANNEL_COUNT; c++) {
5250
+ this.sendMIDICC(c, midiControllers.allNotesOff, 0);
5251
+ this.sendMIDICC(c, midiControllers.allSoundOff, 0);
5252
+ }
5253
+ }
5274
5254
  sendMIDIReset() {
5275
- this.sendMIDIMessage([midiMessageTypes.reset]);
5276
- for (let ch = 0; ch < MIDI_CHANNEL_COUNT; ch++) {
5277
- this.sendMIDIMessage([
5278
- midiMessageTypes.controllerChange | ch,
5279
- midiControllers.allSoundOff,
5280
- 0
5281
- ]);
5282
- this.sendMIDIMessage([
5283
- midiMessageTypes.controllerChange | ch,
5284
- midiControllers.resetAllControllers,
5285
- 0
5286
- ]);
5255
+ this.sendMIDIAllOff();
5256
+ if (!this.externalMIDIPlayback) {
5257
+ this.synth.resetAllControllers();
5258
+ return;
5287
5259
  }
5260
+ this.sendMIDIMessage([midiMessageTypes.reset]);
5288
5261
  }
5289
5262
  loadCurrentSong() {
5290
5263
  let index = this._songIndex;
@@ -5302,11 +5275,65 @@ var SpessaSynthSequencer = class {
5302
5275
  indexes.splice(indexes.indexOf(index), 1);
5303
5276
  }
5304
5277
  }
5305
- sendMIDICC(channel, type, value) {
5278
+ /**
5279
+ * Sets the time in MIDI ticks.
5280
+ * @param ticks the MIDI ticks to set the time to.
5281
+ */
5282
+ setTimeTicks(ticks) {
5283
+ if (!this._midiData) {
5284
+ return;
5285
+ }
5286
+ this.playingNotes = [];
5287
+ const seconds = this._midiData.midiTicksToSeconds(ticks);
5288
+ this.callEvent("timeChange", { newTime: seconds });
5289
+ const isNotFinished = this.setTimeTo(0, ticks);
5290
+ this.recalculateStartTime(this.playedTime);
5291
+ if (!isNotFinished) {
5292
+ return;
5293
+ }
5294
+ }
5295
+ /**
5296
+ * Recalculates the absolute start time of the sequencer.
5297
+ * @param time the time in seconds to recalculate the start time for.
5298
+ */
5299
+ recalculateStartTime(time) {
5300
+ this.absoluteStartTime = this.synth.currentSynthTime - time / this._playbackRate;
5301
+ }
5302
+ /*
5303
+ SEND MIDI METHOD ABSTRACTIONS
5304
+ These abstract the difference between spessasynth and external MIDI
5305
+ */
5306
+ sendMIDINoteOn(channel, midiNote, velocity) {
5307
+ if (!this.externalMIDIPlayback) {
5308
+ this.synth.noteOn(channel, midiNote, velocity);
5309
+ return;
5310
+ }
5311
+ channel %= 16;
5312
+ this.sendMIDIMessage([
5313
+ midiMessageTypes.noteOn | channel,
5314
+ midiNote,
5315
+ velocity
5316
+ ]);
5317
+ }
5318
+ sendMIDINoteOff(channel, midiNote) {
5319
+ if (!this.externalMIDIPlayback) {
5320
+ this.synth.noteOff(channel, midiNote);
5321
+ return;
5322
+ }
5306
5323
  channel %= 16;
5324
+ this.sendMIDIMessage([
5325
+ midiMessageTypes.noteOff | channel,
5326
+ midiNote,
5327
+ 64
5328
+ // Make sure to send velocity as well
5329
+ ]);
5330
+ }
5331
+ sendMIDICC(channel, type, value) {
5307
5332
  if (!this.externalMIDIPlayback) {
5333
+ this.synth.controllerChange(channel, type, value);
5308
5334
  return;
5309
5335
  }
5336
+ channel %= 16;
5310
5337
  this.sendMIDIMessage([
5311
5338
  midiMessageTypes.controllerChange | channel,
5312
5339
  type,
@@ -5314,10 +5341,11 @@ var SpessaSynthSequencer = class {
5314
5341
  ]);
5315
5342
  }
5316
5343
  sendMIDIProgramChange(channel, program) {
5317
- channel %= 16;
5318
5344
  if (!this.externalMIDIPlayback) {
5345
+ this.synth.programChange(channel, program);
5319
5346
  return;
5320
5347
  }
5348
+ channel %= 16;
5321
5349
  this.sendMIDIMessage([
5322
5350
  midiMessageTypes.programChange | channel,
5323
5351
  program
@@ -5326,39 +5354,19 @@ var SpessaSynthSequencer = class {
5326
5354
  /**
5327
5355
  * Sets the pitch of the given channel
5328
5356
  * @param channel usually 0-15: the channel to change pitch
5329
- * @param MSB SECOND byte of the MIDI pitchWheel message
5330
- * @param LSB FIRST byte of the MIDI pitchWheel message
5357
+ * @param pitch the 14-bit pitch value
5331
5358
  */
5332
- sendMIDIPitchWheel(channel, MSB, LSB) {
5333
- channel %= 16;
5359
+ sendMIDIPitchWheel(channel, pitch) {
5334
5360
  if (!this.externalMIDIPlayback) {
5361
+ this.synth.pitchWheel(channel, pitch);
5335
5362
  return;
5336
5363
  }
5337
- this.sendMIDIMessage([midiMessageTypes.pitchWheel | channel, LSB, MSB]);
5338
- }
5339
- /**
5340
- * Sets the time in MIDI ticks.
5341
- * @param ticks the MIDI ticks to set the time to.
5342
- */
5343
- setTimeTicks(ticks) {
5344
- if (!this._midiData) {
5345
- return;
5346
- }
5347
- this.playingNotes = [];
5348
- const seconds = this._midiData.midiTicksToSeconds(ticks);
5349
- this.callEvent("timeChange", { newTime: seconds });
5350
- const isNotFinished = this.setTimeTo(0, ticks);
5351
- this.recalculateStartTime(this.playedTime);
5352
- if (!isNotFinished) {
5353
- return;
5354
- }
5355
- }
5356
- /**
5357
- * Recalculates the absolute start time of the sequencer.
5358
- * @param time the time in seconds to recalculate the start time for.
5359
- */
5360
- recalculateStartTime(time) {
5361
- this.absoluteStartTime = this.synth.currentSynthTime - time / this._playbackRate;
5364
+ channel %= 16;
5365
+ this.sendMIDIMessage([
5366
+ midiMessageTypes.pitchWheel | channel,
5367
+ pitch & 127,
5368
+ pitch >> 7
5369
+ ]);
5362
5370
  }
5363
5371
  };
5364
5372