spessasynth_core 4.2.0 → 4.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1022,6 +1022,7 @@ var MIN_NOTE_LENGTH = 0.03;
1022
1022
  var MIN_EXCLUSIVE_LENGTH = 0.07;
1023
1023
  var SYNTHESIZER_GAIN = 1;
1024
1024
  var SPESSA_BUFSIZE = 128;
1025
+ var EFX_SENDS_GAIN_CORRECTION = 1 / Math.cos(Math.PI / 4) ** 2;
1025
1026
 
1026
1027
  // src/utils/midi_hacks.ts
1027
1028
  var XG_SFX_VOICE = 64;
@@ -2316,10 +2317,7 @@ function modifyMIDIInternal(midi, {
2316
2317
  sendAddress(targetTicks, 64, 3, 0, [
2317
2318
  p.type >> 8,
2318
2319
  p.type & 127
2319
- ]),
2320
- sendAddress(targetTicks, 64, 3, 23, [p.sendLevelToReverb]),
2321
- sendAddress(targetTicks, 64, 3, 24, [p.sendLevelToChorus]),
2322
- sendAddress(targetTicks, 64, 3, 25, [p.sendLevelToDelay])
2320
+ ])
2323
2321
  );
2324
2322
  }
2325
2323
  if (!addedGs && programChanges.length > 0) {
@@ -5066,7 +5064,7 @@ var SpessaSynthSequencer = class {
5066
5064
  songs = [];
5067
5065
  /**
5068
5066
  * The shuffled song indexes.
5069
- * This is used when shuffleMode is enabled.
5067
+ * This is used when shuffle mode is enabled.
5070
5068
  */
5071
5069
  shuffledSongIndexes = [];
5072
5070
  /**
@@ -5191,7 +5189,7 @@ var SpessaSynthSequencer = class {
5191
5189
  // noinspection JSUnusedGlobalSymbols
5192
5190
  /**
5193
5191
  * The current song index in the song list.
5194
- * If shuffleMode is enabled, this is the index of the shuffled song list.
5192
+ * If shuffle mode is enabled, this is the index of the shuffled song list.
5195
5193
  */
5196
5194
  get songIndex() {
5197
5195
  return this._songIndex;
@@ -5199,7 +5197,7 @@ var SpessaSynthSequencer = class {
5199
5197
  // noinspection JSUnusedGlobalSymbols
5200
5198
  /**
5201
5199
  * The current song index in the song list.
5202
- * If shuffleMode is enabled, this is the index of the shuffled song list.
5200
+ * If shuffle mode is enabled, this is the index of the shuffled song list.
5203
5201
  */
5204
5202
  set songIndex(value) {
5205
5203
  this._songIndex = value;
@@ -5211,6 +5209,7 @@ var SpessaSynthSequencer = class {
5211
5209
  /**
5212
5210
  * Controls if the sequencer should shuffle the songs in the song list.
5213
5211
  * If true, the sequencer will play the songs in a random order.
5212
+ * Songs are shuffled on a `loadNewSongList` call.
5214
5213
  */
5215
5214
  get shuffleMode() {
5216
5215
  return this._shuffleMode;
@@ -5219,16 +5218,10 @@ var SpessaSynthSequencer = class {
5219
5218
  /**
5220
5219
  * Controls if the sequencer should shuffle the songs in the song list.
5221
5220
  * If true, the sequencer will play the songs in a random order.
5221
+ * Songs are shuffled on a `loadNewSongList` call.
5222
5222
  */
5223
5223
  set shuffleMode(on) {
5224
5224
  this._shuffleMode = on;
5225
- if (on) {
5226
- this.shuffleSongIndexes();
5227
- this._songIndex = 0;
5228
- this.loadCurrentSong();
5229
- } else {
5230
- this._songIndex = this.shuffledSongIndexes[this._songIndex];
5231
- }
5232
5225
  }
5233
5226
  /**
5234
5227
  * Internal playback rate.
@@ -5457,7 +5450,7 @@ var SpessaSynthSequencer = class {
5457
5450
  }
5458
5451
  shuffleSongIndexes() {
5459
5452
  const indexes = this.songs.map((_, i) => i);
5460
- this.shuffledSongIndexes = [];
5453
+ this.shuffledSongIndexes.length = 0;
5461
5454
  while (indexes.length > 0) {
5462
5455
  const index = indexes[Math.floor(Math.random() * indexes.length)];
5463
5456
  this.shuffledSongIndexes.push(index);
@@ -5553,17 +5546,6 @@ var SpessaSynthSequencer = class {
5553
5546
  value
5554
5547
  ]);
5555
5548
  }
5556
- sendMIDIProgramChange(channel, program) {
5557
- if (!this.externalMIDIPlayback) {
5558
- this.synth.programChange(channel, program);
5559
- return;
5560
- }
5561
- channel %= 16;
5562
- this.sendMIDIMessage([
5563
- midiMessageTypes.programChange | channel,
5564
- program
5565
- ]);
5566
- }
5567
5549
  /**
5568
5550
  * Sets the pitch of the given channel
5569
5551
  * @param channel usually 0-15: the channel to change pitch
@@ -6511,6 +6493,7 @@ var DelayLine = class {
6511
6493
  };
6512
6494
 
6513
6495
  // src/synthesizer/audio_engine/effects/reverb/reverb.ts
6496
+ var DELAY_GAIN = 1.5;
6514
6497
  var SpessaSynthReverb = class {
6515
6498
  /**
6516
6499
  * Dattorro reverb processor.
@@ -6841,7 +6824,7 @@ var SpessaSynthReverb = class {
6841
6824
  }
6842
6825
  updateGain() {
6843
6826
  this.dattorro.gain = this._level / 348 * this.characterGainCoefficient;
6844
- this.delayGain = this._level / 127;
6827
+ this.delayGain = this._level / 127 * DELAY_GAIN;
6845
6828
  }
6846
6829
  updateTime() {
6847
6830
  const t = this._time / 127;
@@ -6858,6 +6841,7 @@ var SpessaSynthReverb = class {
6858
6841
  };
6859
6842
 
6860
6843
  // src/synthesizer/audio_engine/effects/chorus/chorus.ts
6844
+ var CHORUS_GAIN = 1.3;
6861
6845
  var SpessaSynthChorus = class {
6862
6846
  /**
6863
6847
  * Cutoff frequency
@@ -6879,7 +6863,7 @@ var SpessaSynthChorus = class {
6879
6863
  sampleRate;
6880
6864
  phase = 0;
6881
6865
  write = 0;
6882
- gain = 1;
6866
+ gain = 0.5;
6883
6867
  reverbGain = 0;
6884
6868
  delayGain = 0;
6885
6869
  depthSamples = 0;
@@ -6962,7 +6946,7 @@ var SpessaSynthChorus = class {
6962
6946
  return this._level;
6963
6947
  }
6964
6948
  set level(value) {
6965
- this.gain = value / 127;
6949
+ this.gain = value / 127 * CHORUS_GAIN;
6966
6950
  this._level = value;
6967
6951
  }
6968
6952
  process(input, outputLeft, outputRight, outputReverb, outputDelay, startIndex, sampleCount) {
@@ -7035,7 +7019,6 @@ var SpessaSynthChorus = class {
7035
7019
  };
7036
7020
  }
7037
7021
  };
7038
- var chorus_default = SpessaSynthChorus;
7039
7022
 
7040
7023
  // src/synthesizer/audio_engine/effects/delay/delay.ts
7041
7024
  var delayTimeSegments = [
@@ -7049,6 +7032,7 @@ var delayTimeSegments = [
7049
7032
  { start: 90, end: 105, timeStart: 200, resolution: 20 },
7050
7033
  { start: 105, end: 116, timeStart: 500, resolution: 50 }
7051
7034
  ];
7035
+ var DELAY_GAIN2 = 1.66;
7052
7036
  var SpessaSynthDelay = class {
7053
7037
  /**
7054
7038
  * Cutoff frequency
@@ -7117,7 +7101,7 @@ var SpessaSynthDelay = class {
7117
7101
  }
7118
7102
  set level(value) {
7119
7103
  this._level = value;
7120
- this.gain = value / 127;
7104
+ this.gain = value / 127 * DELAY_GAIN2;
7121
7105
  }
7122
7106
  _levelCenter = 127;
7123
7107
  get levelCenter() {
@@ -7253,7 +7237,7 @@ function getDefaultSynthOptions(sampleRate) {
7253
7237
  return {
7254
7238
  ...DEFAULT_SYNTH_OPTIONS,
7255
7239
  reverbProcessor: new SpessaSynthReverb(sampleRate),
7256
- chorusProcessor: new chorus_default(sampleRate),
7240
+ chorusProcessor: new SpessaSynthChorus(sampleRate),
7257
7241
  delayProcessor: new SpessaSynthDelay(sampleRate)
7258
7242
  };
7259
7243
  }
@@ -7685,9 +7669,6 @@ var SynthesizerSnapshot = class _SynthesizerSnapshot {
7685
7669
  is.type >> 8,
7686
7670
  is.type & 127
7687
7671
  ]);
7688
- sendAddress2(processor, 64, 3, 23, [is.sendLevelToReverb]);
7689
- sendAddress2(processor, 64, 3, 24, [is.sendLevelToChorus]);
7690
- sendAddress2(processor, 64, 3, 25, [is.sendLevelToDelay]);
7691
7672
  for (let i = 0; i < is.params.length; i++) {
7692
7673
  if (is.params[i] !== 255)
7693
7674
  sendAddress2(processor, 64, 3, 3 + i, [is.params[i]]);
@@ -12936,8 +12917,8 @@ function setMasterParameterInternal(parameter, value) {
12936
12917
  case "masterPan": {
12937
12918
  let pan = value;
12938
12919
  pan = pan / 2 + 0.5;
12939
- this.panLeft = 1 - pan;
12940
- this.panRight = pan;
12920
+ this.panLeft = Math.cos(Math.PI / 2 * pan);
12921
+ this.panRight = Math.sin(Math.PI / 2 * pan);
12941
12922
  break;
12942
12923
  }
12943
12924
  case "voiceCap": {
@@ -13586,12 +13567,13 @@ function handleGS(syx, channelOffset = 0) {
13586
13567
  if (addr2 === 3) {
13587
13568
  if (this.masterParameters.insertionEffectLock)
13588
13569
  return;
13570
+ if (addr3 >= 3 && addr3 <= 25)
13571
+ this.insertionParams[addr3 - 3] = data;
13589
13572
  if (addr3 >= 3 && addr3 <= 22) {
13590
13573
  this.insertionProcessor.setParameter(
13591
13574
  addr3,
13592
13575
  data
13593
13576
  );
13594
- this.insertionParams[addr3 - 3] = data;
13595
13577
  coolInfo2(`EFX Parameter ${addr3 - 2}`, data);
13596
13578
  this.callEvent("effectChange", {
13597
13579
  effect: "insertion",
@@ -13620,7 +13602,7 @@ function handleGS(syx, channelOffset = 0) {
13620
13602
  consoleColors.warn
13621
13603
  );
13622
13604
  }
13623
- this.insertionParams.fill(255);
13605
+ this.resetInsertionParams();
13624
13606
  this.insertionProcessor.reset();
13625
13607
  this.callEvent("effectChange", {
13626
13608
  effect: "insertion",
@@ -13630,7 +13612,7 @@ function handleGS(syx, channelOffset = 0) {
13630
13612
  return;
13631
13613
  }
13632
13614
  case 23: {
13633
- this.insertionProcessor.sendLevelToReverb = data / 127;
13615
+ this.insertionProcessor.sendLevelToReverb = data / 127 * EFX_SENDS_GAIN_CORRECTION;
13634
13616
  coolInfo2("EFX Send Level to Reverb", data);
13635
13617
  this.callEvent("effectChange", {
13636
13618
  effect: "insertion",
@@ -13640,7 +13622,7 @@ function handleGS(syx, channelOffset = 0) {
13640
13622
  return;
13641
13623
  }
13642
13624
  case 24: {
13643
- this.insertionProcessor.sendLevelToChorus = data / 127;
13625
+ this.insertionProcessor.sendLevelToChorus = data / 127 * EFX_SENDS_GAIN_CORRECTION;
13644
13626
  coolInfo2("EFX Send Level to Chorus", data);
13645
13627
  this.callEvent("effectChange", {
13646
13628
  effect: "insertion",
@@ -13650,7 +13632,7 @@ function handleGS(syx, channelOffset = 0) {
13650
13632
  return;
13651
13633
  }
13652
13634
  case 25: {
13653
- this.insertionProcessor.sendLevelToDelay = data / 127;
13635
+ this.insertionProcessor.sendLevelToDelay = data / 127 * EFX_SENDS_GAIN_CORRECTION;
13654
13636
  this.delayActive = true;
13655
13637
  coolInfo2("EFX Send Level to Delay", data);
13656
13638
  this.callEvent("effectChange", {
@@ -16636,11 +16618,13 @@ var SynthesizerCore = class {
16636
16618
  /**
16637
16619
  * The pan of the left channel.
16638
16620
  */
16639
- panLeft = 0.5;
16621
+ panLeft = Math.cos(Math.PI / 4);
16622
+ // Center
16640
16623
  /**
16641
16624
  * The pan of the right channel.
16642
16625
  */
16643
- panRight = 0.5;
16626
+ panRight = Math.cos(Math.PI / 4);
16627
+ // Center
16644
16628
  /**
16645
16629
  * Synth's default (reset) preset.
16646
16630
  */
@@ -16734,10 +16718,12 @@ var SynthesizerCore = class {
16734
16718
  portSelectChannelOffset = 0;
16735
16719
  /**
16736
16720
  * For insertion snapshot tracking
16721
+ * 20 parameters (0-19) + 3 sends
16722
+ * Index to gs is Addr3 - 3 (for example EFX PARAMETER 1 is 0x03 and here it's 0)
16737
16723
  * note: 255 means "no change"
16738
16724
  * @protected
16739
16725
  */
16740
- insertionParams = new Uint8Array(20).fill(255);
16726
+ insertionParams = new Uint8Array(23).fill(255);
16741
16727
  /**
16742
16728
  * Last time the priorities were assigned.
16743
16729
  * Used to prevent assigning priorities multiple times when more than one voice is triggered during a quantum.
@@ -16767,6 +16753,7 @@ var SynthesizerCore = class {
16767
16753
  this.delayProcessor = options.delayProcessor;
16768
16754
  for (const insertion of insertionList)
16769
16755
  this.registerInsertionProcessor(insertion);
16756
+ this.resetInsertionParams();
16770
16757
  this.voices = [];
16771
16758
  for (let i = 0; i < this.masterParameters.voiceCap; i++) {
16772
16759
  this.voices.push(new Voice(this.sampleRate));
@@ -16888,21 +16875,19 @@ var SynthesizerCore = class {
16888
16875
  * Processes a raw MIDI message.
16889
16876
  * @param message The message to process.
16890
16877
  * @param channelOffset The channel offset for the message.
16891
- * @param force If true, forces the message to be processed.
16892
16878
  * @param options Additional options for scheduling the message.
16893
16879
  */
16894
- processMessage(message, channelOffset = 0, force = false, options = DEFAULT_SYNTH_METHOD_OPTIONS) {
16880
+ processMessage(message, channelOffset = 0, options = DEFAULT_SYNTH_METHOD_OPTIONS) {
16895
16881
  const time = options.time;
16896
16882
  if (time > this.currentTime) {
16897
16883
  this.eventQueue.push({
16898
16884
  message,
16899
16885
  channelOffset,
16900
- force,
16901
16886
  time
16902
16887
  });
16903
16888
  this.eventQueue.sort((e1, e2) => e1.time - e2.time);
16904
16889
  } else {
16905
- this.processMessageInternal(message, channelOffset, force);
16890
+ this.processMessageInternal(message, channelOffset);
16906
16891
  }
16907
16892
  }
16908
16893
  destroySynthProcessor() {
@@ -17063,11 +17048,7 @@ var SynthesizerCore = class {
17063
17048
  while (this.eventQueue[0]?.time <= time) {
17064
17049
  const q = this.eventQueue.shift();
17065
17050
  if (q) {
17066
- this.processMessageInternal(
17067
- q.message,
17068
- q.channelOffset,
17069
- q.force
17070
- );
17051
+ this.processMessageInternal(q.message, q.channelOffset);
17071
17052
  }
17072
17053
  }
17073
17054
  }
@@ -17210,15 +17191,6 @@ var SynthesizerCore = class {
17210
17191
  getInsertionSnapshot() {
17211
17192
  return {
17212
17193
  type: this.insertionProcessor.type,
17213
- sendLevelToReverb: Math.floor(
17214
- this.insertionProcessor.sendLevelToReverb * 127
17215
- ),
17216
- sendLevelToChorus: Math.floor(
17217
- this.insertionProcessor.sendLevelToChorus * 127
17218
- ),
17219
- sendLevelToDelay: Math.floor(
17220
- this.insertionProcessor.sendLevelToDelay * 127
17221
- ),
17222
17194
  params: this.insertionParams.slice(),
17223
17195
  channels: this.midiChannels.map((c) => c.insertionEnabled)
17224
17196
  };
@@ -17229,15 +17201,21 @@ var SynthesizerCore = class {
17229
17201
  callEvent(eventName, eventData) {
17230
17202
  this.eventCallbackHandler(eventName, eventData);
17231
17203
  }
17204
+ resetInsertionParams() {
17205
+ this.insertionParams.fill(255);
17206
+ this.insertionParams[20] = 40;
17207
+ this.insertionParams[21] = 0;
17208
+ this.insertionParams[22] = 0;
17209
+ }
17232
17210
  resetInsertion() {
17233
17211
  if (this.masterParameters.insertionEffectLock) return;
17234
17212
  this.insertionActive = false;
17235
17213
  this.insertionProcessor = this.insertionFallback;
17236
17214
  this.insertionProcessor.reset();
17237
- this.insertionProcessor.sendLevelToReverb = 40 / 127;
17215
+ this.insertionProcessor.sendLevelToReverb = 40 / 127 * EFX_SENDS_GAIN_CORRECTION;
17238
17216
  this.insertionProcessor.sendLevelToChorus = 0;
17239
17217
  this.insertionProcessor.sendLevelToDelay = 0;
17240
- this.insertionParams.fill(255);
17218
+ this.resetInsertionParams();
17241
17219
  this.callEvent("effectChange", {
17242
17220
  effect: "insertion",
17243
17221
  parameter: 0,
@@ -17286,7 +17264,7 @@ var SynthesizerCore = class {
17286
17264
  * This is a special delay in which the delayed sounds move left and right.
17287
17265
  * It is effective when you are listening in stereo.
17288
17266
  */
17289
- default: {
17267
+ case 0: {
17290
17268
  rev.character = 0;
17291
17269
  rev.preLowpass = 3;
17292
17270
  rev.time = 80;
@@ -17336,6 +17314,10 @@ var SynthesizerCore = class {
17336
17314
  rev.delayFeedback = 32;
17337
17315
  break;
17338
17316
  }
17317
+ default: {
17318
+ SpessaSynthWarn(`Invalid reverb macro: ${macro}`);
17319
+ return;
17320
+ }
17339
17321
  }
17340
17322
  this.callEvent("effectChange", {
17341
17323
  effect: "reverb",
@@ -17369,7 +17351,7 @@ var SynthesizerCore = class {
17369
17351
  * Short Delay (FB)
17370
17352
  * This is a short delay with many repeats.
17371
17353
  */
17372
- default: {
17354
+ case 0: {
17373
17355
  chr.feedback = 0;
17374
17356
  chr.delay = 112;
17375
17357
  chr.rate = 3;
@@ -17425,6 +17407,10 @@ var SynthesizerCore = class {
17425
17407
  chr.depth = 127;
17426
17408
  break;
17427
17409
  }
17410
+ default: {
17411
+ SpessaSynthWarn(`Invalid chorus macro: ${macro}`);
17412
+ return;
17413
+ }
17428
17414
  }
17429
17415
  this.callEvent("effectChange", {
17430
17416
  effect: "chorus",
@@ -17464,8 +17450,7 @@ var SynthesizerCore = class {
17464
17450
  * but the pan positioning is different from the effects listed above.
17465
17451
  * It is effective when listening in stereo.
17466
17452
  */
17467
- case 0:
17468
- default: {
17453
+ case 0: {
17469
17454
  dly.timeCenter = 97;
17470
17455
  dly.timeRatioRight = dly.timeRatioLeft = 1;
17471
17456
  dly.feedback = 80;
@@ -17550,6 +17535,10 @@ var SynthesizerCore = class {
17550
17535
  dly.feedback = 40;
17551
17536
  break;
17552
17537
  }
17538
+ default: {
17539
+ SpessaSynthWarn(`Invalid delay macro: ${macro}`);
17540
+ return;
17541
+ }
17553
17542
  }
17554
17543
  this.callEvent("effectChange", {
17555
17544
  effect: "delay",
@@ -17572,7 +17561,7 @@ var SynthesizerCore = class {
17572
17561
  const p = new proc(this.sampleRate);
17573
17562
  this.insertionEffects.set(p.type, p);
17574
17563
  }
17575
- processMessageInternal(message, channelOffset, force) {
17564
+ processMessageInternal(message, channelOffset) {
17576
17565
  const statusByteData = getEvent(message[0]);
17577
17566
  const channelNumber = statusByteData.channel + channelOffset;
17578
17567
  switch (statusByteData.status) {
@@ -17586,11 +17575,7 @@ var SynthesizerCore = class {
17586
17575
  break;
17587
17576
  }
17588
17577
  case midiMessageTypes.noteOff: {
17589
- if (force) {
17590
- this.midiChannels[channelNumber].killNote(message[1]);
17591
- } else {
17592
- this.noteOff(channelNumber, message[1]);
17593
- }
17578
+ this.noteOff(channelNumber, message[1]);
17594
17579
  break;
17595
17580
  }
17596
17581
  case midiMessageTypes.pitchWheel: {
@@ -22362,7 +22347,6 @@ var SpessaSynthProcessor = class {
22362
22347
  * Processes a raw MIDI message.
22363
22348
  * @param message The message to process.
22364
22349
  * @param channelOffset The channel offset for the message.
22365
- * @param force If true, forces the message to be processed.
22366
22350
  * @param options Additional options for scheduling the message.
22367
22351
  */
22368
22352
  processMessage;