spessasynth_core 4.2.9 → 4.2.11

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
@@ -362,6 +362,10 @@ declare class VolumeEnvelope {
362
362
  * The sample rate in Hz.
363
363
  */
364
364
  readonly sampleRate: number;
365
+ /**
366
+ * The target gain for the current rendering block.
367
+ */
368
+ outputGain: number;
365
369
  /**
366
370
  * The current attenuation of the envelope in cB.
367
371
  */
@@ -425,21 +429,10 @@ declare class VolumeEnvelope {
425
429
  * We can't do that with modulated as it can silence the volume and then raise it again, and the voice must keep playing.
426
430
  */
427
431
  private canEndOnSilentSustain;
428
- private readonly gainSmoothing;
429
- private currentGain;
430
432
  /**
431
433
  * @param sampleRate Hz
432
434
  */
433
435
  constructor(sampleRate: number);
434
- /**
435
- * Applies volume envelope gain to the given output buffer.
436
- * Essentially we use approach of 100dB is silence, 0dB is peak.
437
- * @param sampleCount the amount of samples to write
438
- * @param buffer the audio buffer to modify
439
- * @param gainTarget the gain target to smooth.
440
- * @returns if the voice is still active
441
- */
442
- process(sampleCount: number, buffer: Float32Array, gainTarget: number): boolean;
443
436
  /**
444
437
  * Starts the release phase in the envelope.
445
438
  * @param voice the voice this envelope belongs to.
@@ -450,13 +443,15 @@ declare class VolumeEnvelope {
450
443
  * @param voice The voice this envelope belongs to
451
444
  */
452
445
  init(voice: Voice): void;
446
+ /**
447
+ * Calculates the gain value for the last sample in the block and writes it to `outputGain`.
448
+ * Essentially we use approach of 100dB is silence, 0dB is peak.
449
+ * @param sampleCount the amount of samples to write
450
+ * @param gainTarget the gain to apply.
451
+ * @returns if the voice has finished.
452
+ */
453
+ process(sampleCount: number, gainTarget: number): boolean;
453
454
  private timecentsToSamples;
454
- private releasePhase;
455
- private delayPhase;
456
- private attackPhase;
457
- private holdPhase;
458
- private decayPhase;
459
- private sustainPhase;
460
455
  }
461
456
  //#endregion
462
457
  //#region src/synthesizer/audio_engine/engine_components/dsp_chain/modulation_envelope.d.ts
package/dist/index.js CHANGED
@@ -1650,8 +1650,13 @@ function getUsedProgramsAndKeys(mid, soundBank) {
1650
1650
  const usedProgramsAndKeys = /* @__PURE__ */ new Map();
1651
1651
  const ports = mid.tracks.map((t) => t.port);
1652
1652
  mid.iterate((event, trackNum) => {
1653
- if (event.statusByte === midiMessageTypes.midiPort) {
1654
- ports[trackNum] = event.data[0];
1653
+ if (event.statusByte === midiMessageTypes.midiPort && mid.tracks[trackNum].channels.size > 0) {
1654
+ let port = event.data[0];
1655
+ if (mid.portChannelOffsetMap[port] === void 0) {
1656
+ SpessaSynthWarn(`Invalid port ${port} on track ${trackNum}. (No offset found in the MIDI map.`);
1657
+ port = 0;
1658
+ }
1659
+ ports[trackNum] = port;
1655
1660
  return;
1656
1661
  }
1657
1662
  const status = event.statusByte & 240;
@@ -3599,13 +3604,15 @@ function processEventInternal(event, trackIndex) {
3599
3604
  case midiMessageTypes.systemExclusive:
3600
3605
  this.synth.systemExclusive(event.data, offset);
3601
3606
  break;
3602
- case midiMessageTypes.setTempo:
3603
- this.oneTickToSeconds = 60 / (6e7 / readBigEndian(event.data, 3) * this._midiData.timeDivision);
3607
+ case midiMessageTypes.setTempo: {
3608
+ const tempoBPM = 6e7 / readBigEndian(event.data, 3);
3609
+ this.oneTickToSeconds = 60 / (tempoBPM * this._midiData.timeDivision);
3604
3610
  if (this.oneTickToSeconds === 0) {
3605
3611
  this.oneTickToSeconds = 60 / (120 * this._midiData.timeDivision);
3606
3612
  SpessaSynthInfo("invalid tempo! falling back to 120 BPM");
3607
3613
  }
3608
3614
  break;
3615
+ }
3609
3616
  case midiMessageTypes.timeSignature:
3610
3617
  case midiMessageTypes.endOfTrack:
3611
3618
  case midiMessageTypes.midiChannelPrefix:
@@ -4455,11 +4462,13 @@ function setTimeToInternal(time, ticks = void 0) {
4455
4462
  }
4456
4463
  break;
4457
4464
  }
4458
- case midiMessageTypes.setTempo:
4459
- this.oneTickToSeconds = 60 / (6e7 / readBigEndian(event.data, 3) * this._midiData.timeDivision);
4465
+ case midiMessageTypes.setTempo: {
4466
+ const tempoBPM = 6e7 / readBigEndian(event.data, 3);
4467
+ this.oneTickToSeconds = 60 / (tempoBPM * this._midiData.timeDivision);
4460
4468
  savedTempo = event;
4461
4469
  savedTempoTrack = trackIndex;
4462
4470
  break;
4471
+ }
4463
4472
  default:
4464
4473
  this.processEvent(event, trackIndex);
4465
4474
  break;
@@ -4875,7 +4884,8 @@ var SpessaSynthSequencer = class {
4875
4884
  const idx = track.events.findIndex((e) => e.ticks >= targetTicks);
4876
4885
  this.eventIndexes.push(idx === -1 ? track.events.length : idx);
4877
4886
  }
4878
- this.oneTickToSeconds = 60 / (this._midiData.tempoChanges.find((t) => t.ticks <= targetTicks).tempo * this._midiData.timeDivision);
4887
+ const targetTempo = this._midiData.tempoChanges.find((t) => t.ticks <= targetTicks);
4888
+ this.oneTickToSeconds = 60 / (targetTempo.tempo * this._midiData.timeDivision);
4879
4889
  }
4880
4890
  sendMIDINoteOn(channel, midiNote, velocity) {
4881
4891
  if (!this.externalMIDIPlayback) {
@@ -6043,13 +6053,16 @@ var LowpassFilter = class LowpassFilter {
6043
6053
  */
6044
6054
  const CB_SILENCE = 960;
6045
6055
  const PERCEIVED_CB_SILENCE = 900;
6046
- const GAIN_SMOOTHING_FACTOR$1 = .01;
6047
6056
  var VolumeEnvelope = class {
6048
6057
  /**
6049
6058
  * The sample rate in Hz.
6050
6059
  */
6051
6060
  sampleRate;
6052
6061
  /**
6062
+ * The target gain for the current rendering block.
6063
+ */
6064
+ outputGain = 0;
6065
+ /**
6053
6066
  * The current attenuation of the envelope in cB.
6054
6067
  */
6055
6068
  attenuationCb = CB_SILENCE;
@@ -6112,32 +6125,11 @@ var VolumeEnvelope = class {
6112
6125
  * We can't do that with modulated as it can silence the volume and then raise it again, and the voice must keep playing.
6113
6126
  */
6114
6127
  canEndOnSilentSustain = false;
6115
- gainSmoothing;
6116
- currentGain = 0;
6117
6128
  /**
6118
6129
  * @param sampleRate Hz
6119
6130
  */
6120
6131
  constructor(sampleRate) {
6121
6132
  this.sampleRate = sampleRate;
6122
- this.gainSmoothing = GAIN_SMOOTHING_FACTOR$1 * (44100 / sampleRate);
6123
- }
6124
- /**
6125
- * Applies volume envelope gain to the given output buffer.
6126
- * Essentially we use approach of 100dB is silence, 0dB is peak.
6127
- * @param sampleCount the amount of samples to write
6128
- * @param buffer the audio buffer to modify
6129
- * @param gainTarget the gain target to smooth.
6130
- * @returns if the voice is still active
6131
- */
6132
- process(sampleCount, buffer, gainTarget) {
6133
- if (this.enteredRelease) return this.releasePhase(sampleCount, buffer, gainTarget);
6134
- switch (this.state) {
6135
- case 0: return this.delayPhase(sampleCount, buffer, gainTarget, 0);
6136
- case 1: return this.attackPhase(sampleCount, buffer, gainTarget, 0);
6137
- case 2: return this.holdPhase(sampleCount, buffer, gainTarget, 0);
6138
- case 3: return this.decayPhase(sampleCount, buffer, gainTarget, 0);
6139
- case 4: return this.sustainPhase(sampleCount, buffer, gainTarget, 0);
6140
- }
6141
6133
  }
6142
6134
  /**
6143
6135
  * Starts the release phase in the envelope.
@@ -6189,7 +6181,7 @@ var VolumeEnvelope = class {
6189
6181
  this.state = 0;
6190
6182
  this.sampleTime = 0;
6191
6183
  this.canEndOnSilentSustain = voice.modulatedGenerators[generatorTypes.sustainVolEnv] >= PERCEIVED_CB_SILENCE;
6192
- this.currentGain = CENTIBEL_LOOKUP_TABLE[voice.modulatedGenerators[generatorTypes.initialAttenuation] - MIN_CENTIBELS | 0];
6184
+ this.outputGain = 0;
6193
6185
  this.sustainCb = Math.min(CB_SILENCE, voice.modulatedGenerators[generatorTypes.sustainVolEnv]);
6194
6186
  this.attackDuration = this.timecentsToSamples(voice.modulatedGenerators[generatorTypes.attackVolEnv]);
6195
6187
  const keyNumAddition = (60 - voice.targetKey) * voice.modulatedGenerators[generatorTypes.keyNumToVolEnvDecay];
@@ -6202,132 +6194,66 @@ var VolumeEnvelope = class {
6202
6194
  this.decayEnd = this.decayDuration + this.holdEnd;
6203
6195
  if (this.attackEnd === 0) this.state = 2;
6204
6196
  }
6205
- timecentsToSamples(tc) {
6206
- return Math.max(0, Math.floor(timecentsToSeconds(tc) * this.sampleRate));
6207
- }
6208
- releasePhase(sampleCount, buffer, gainTarget) {
6209
- let { sampleTime, currentGain, attenuationCb } = this;
6210
- const { releaseStartTimeSamples, releaseStartCb, releaseDuration, gainSmoothing } = this;
6211
- let elapsedRelease = sampleTime - releaseStartTimeSamples;
6212
- const cbDifference = CB_SILENCE - releaseStartCb;
6213
- const smooth = currentGain !== gainTarget;
6214
- for (let i = 0; i < sampleCount; i++) {
6215
- if (smooth) currentGain += (gainTarget - currentGain) * gainSmoothing;
6216
- attenuationCb = elapsedRelease / releaseDuration * cbDifference + releaseStartCb;
6217
- buffer[i] *= CENTIBEL_LOOKUP_TABLE[attenuationCb - MIN_CENTIBELS | 0] * currentGain;
6218
- sampleTime++;
6219
- elapsedRelease++;
6220
- }
6221
- this.sampleTime = sampleTime;
6222
- this.currentGain = currentGain;
6223
- this.attenuationCb = attenuationCb;
6224
- return attenuationCb < PERCEIVED_CB_SILENCE;
6225
- }
6226
- delayPhase(sampleCount, buffer, gainTarget, filledBuffer) {
6227
- const { delayEnd } = this;
6228
- let { sampleTime } = this;
6229
- if (sampleTime < delayEnd) {
6230
- this.attenuationCb = CB_SILENCE;
6231
- const delaySamples = Math.min(delayEnd - sampleTime, sampleCount);
6232
- buffer.fill(0, filledBuffer, filledBuffer + delaySamples);
6233
- filledBuffer += delaySamples;
6234
- sampleTime += delaySamples;
6235
- if (filledBuffer >= sampleCount) {
6236
- this.sampleTime = sampleTime;
6237
- return true;
6238
- }
6197
+ /**
6198
+ * Calculates the gain value for the last sample in the block and writes it to `outputGain`.
6199
+ * Essentially we use approach of 100dB is silence, 0dB is peak.
6200
+ * @param sampleCount the amount of samples to write
6201
+ * @param gainTarget the gain to apply.
6202
+ * @returns if the voice has finished.
6203
+ */
6204
+ process(sampleCount, gainTarget) {
6205
+ const { releaseStartTimeSamples, releaseStartCb, releaseDuration, delayEnd, attackEnd, attackDuration, holdEnd, decayEnd, decayDuration, sustainCb } = this;
6206
+ const sampleTime = this.sampleTime += sampleCount;
6207
+ if (this.enteredRelease) {
6208
+ const elapsedRelease = sampleTime - releaseStartTimeSamples;
6209
+ const cbDifference = CB_SILENCE - releaseStartCb;
6210
+ this.attenuationCb = elapsedRelease / releaseDuration * cbDifference + releaseStartCb;
6211
+ this.outputGain = CENTIBEL_LOOKUP_TABLE[this.attenuationCb - MIN_CENTIBELS | 0] * gainTarget;
6212
+ return this.attenuationCb < PERCEIVED_CB_SILENCE;
6239
6213
  }
6240
- this.sampleTime = sampleTime;
6241
- this.state++;
6242
- return this.attackPhase(sampleCount, buffer, gainTarget, filledBuffer);
6243
- }
6244
- attackPhase(sampleCount, buffer, gainTarget, filledBuffer) {
6245
- const { attackEnd, attackDuration, gainSmoothing } = this;
6246
- let { sampleTime, currentGain } = this;
6247
- const smooth = currentGain !== gainTarget;
6248
- if (sampleTime < attackEnd) {
6249
- this.attenuationCb = 0;
6250
- while (sampleTime < attackEnd) {
6251
- if (smooth) currentGain += (gainTarget - currentGain) * gainSmoothing;
6252
- const linearGain = 1 - (attackEnd - sampleTime) / attackDuration;
6253
- buffer[filledBuffer] *= linearGain * currentGain;
6254
- sampleTime++;
6255
- if (++filledBuffer >= sampleCount) {
6256
- this.sampleTime = sampleTime;
6257
- this.currentGain = currentGain;
6214
+ switch (this.state) {
6215
+ case 0:
6216
+ if (sampleTime < delayEnd) {
6217
+ this.attenuationCb = CB_SILENCE;
6218
+ this.outputGain = 0;
6258
6219
  return true;
6259
6220
  }
6260
- }
6261
- }
6262
- this.sampleTime = sampleTime;
6263
- this.currentGain = currentGain;
6264
- this.state++;
6265
- return this.holdPhase(sampleCount, buffer, gainTarget, filledBuffer);
6266
- }
6267
- holdPhase(sampleCount, buffer, gainTarget, filledBuffer) {
6268
- const { holdEnd, gainSmoothing } = this;
6269
- let { sampleTime, currentGain } = this;
6270
- const smooth = currentGain !== gainTarget;
6271
- if (sampleTime < holdEnd) {
6272
- this.attenuationCb = 0;
6273
- while (sampleTime < holdEnd) {
6274
- if (smooth) currentGain += (gainTarget - currentGain) * gainSmoothing;
6275
- buffer[filledBuffer] *= currentGain;
6276
- sampleTime++;
6277
- if (++filledBuffer >= sampleCount) {
6278
- this.sampleTime = sampleTime;
6279
- this.currentGain = currentGain;
6221
+ this.state++;
6222
+ case 1:
6223
+ if (sampleTime < attackEnd) {
6224
+ this.attenuationCb = 0;
6225
+ const linearGain = 1 - (attackEnd - sampleTime) / attackDuration;
6226
+ this.outputGain = linearGain * gainTarget;
6280
6227
  return true;
6281
6228
  }
6282
- }
6283
- }
6284
- this.sampleTime = sampleTime;
6285
- this.currentGain = currentGain;
6286
- this.state++;
6287
- return this.decayPhase(sampleCount, buffer, gainTarget, filledBuffer);
6288
- }
6289
- decayPhase(sampleCount, buffer, gainTarget, filledBuffer) {
6290
- const { decayDuration, decayEnd, gainSmoothing, sustainCb } = this;
6291
- let { sampleTime, currentGain, attenuationCb } = this;
6292
- const smooth = currentGain !== gainTarget;
6293
- if (sampleTime < decayEnd) while (sampleTime < decayEnd) {
6294
- if (smooth) currentGain += (gainTarget - currentGain) * gainSmoothing;
6295
- attenuationCb = (1 - (decayEnd - sampleTime) / decayDuration) * sustainCb;
6296
- buffer[filledBuffer] *= currentGain * CENTIBEL_LOOKUP_TABLE[attenuationCb - MIN_CENTIBELS | 0];
6297
- sampleTime++;
6298
- if (++filledBuffer >= sampleCount) {
6299
- this.sampleTime = sampleTime;
6300
- this.currentGain = currentGain;
6301
- this.attenuationCb = attenuationCb;
6229
+ this.state++;
6230
+ case 2:
6231
+ if (sampleTime < holdEnd) {
6232
+ this.attenuationCb = 0;
6233
+ this.outputGain = gainTarget;
6234
+ return true;
6235
+ }
6236
+ this.state++;
6237
+ case 3:
6238
+ if (sampleTime < decayEnd) {
6239
+ this.attenuationCb = (1 - (decayEnd - sampleTime) / decayDuration) * sustainCb;
6240
+ this.outputGain = gainTarget * CENTIBEL_LOOKUP_TABLE[this.attenuationCb - MIN_CENTIBELS | 0];
6241
+ return true;
6242
+ }
6243
+ this.state++;
6244
+ case 4:
6245
+ if (this.canEndOnSilentSustain && sustainCb >= PERCEIVED_CB_SILENCE) {
6246
+ this.attenuationCb = CB_SILENCE;
6247
+ this.outputGain = 0;
6248
+ return false;
6249
+ }
6250
+ this.attenuationCb = sustainCb;
6251
+ this.outputGain = gainTarget * CENTIBEL_LOOKUP_TABLE[sustainCb - MIN_CENTIBELS | 0];
6302
6252
  return true;
6303
- }
6304
6253
  }
6305
- this.sampleTime = sampleTime;
6306
- this.currentGain = currentGain;
6307
- this.attenuationCb = attenuationCb;
6308
- this.state++;
6309
- return this.sustainPhase(sampleCount, buffer, gainTarget, filledBuffer);
6310
- }
6311
- sustainPhase(sampleCount, buffer, gainTarget, filledBuffer) {
6312
- const { sustainCb, gainSmoothing } = this;
6313
- if (this.canEndOnSilentSustain && sustainCb >= PERCEIVED_CB_SILENCE) {
6314
- buffer.fill(0, filledBuffer, sampleCount);
6315
- return false;
6316
- }
6317
- let { sampleTime, currentGain } = this;
6318
- const smooth = currentGain !== gainTarget;
6319
- if (filledBuffer < sampleCount) {
6320
- this.attenuationCb = sustainCb;
6321
- while (filledBuffer < sampleCount) {
6322
- if (smooth) currentGain += (gainTarget - currentGain) * gainSmoothing;
6323
- buffer[filledBuffer] *= currentGain * CENTIBEL_LOOKUP_TABLE[sustainCb - MIN_CENTIBELS | 0];
6324
- sampleTime++;
6325
- filledBuffer++;
6326
- }
6327
- }
6328
- this.sampleTime = sampleTime;
6329
- this.currentGain = currentGain;
6330
- return true;
6254
+ }
6255
+ timecentsToSamples(tc) {
6256
+ return Math.max(0, Math.floor(timecentsToSeconds(tc) * this.sampleRate));
6331
6257
  }
6332
6258
  };
6333
6259
  //#endregion
@@ -6466,7 +6392,8 @@ var ModulationEnvelope = class {
6466
6392
  startRelease(voice) {
6467
6393
  this.releaseStartLevel = this.currentValue;
6468
6394
  this.enteredRelease = true;
6469
- this.releaseDuration = this.tc2Sec(Math.max(voice.modulatedGenerators[generatorTypes.releaseModEnv], -7200)) * this.releaseStartLevel;
6395
+ const releaseTime = this.tc2Sec(Math.max(voice.modulatedGenerators[generatorTypes.releaseModEnv], -7200));
6396
+ this.releaseDuration = releaseTime * this.releaseStartLevel;
6470
6397
  }
6471
6398
  /**
6472
6399
  * Initializes the modulation envelope.
@@ -6477,7 +6404,8 @@ var ModulationEnvelope = class {
6477
6404
  this.sustainLevel = 1 - voice.modulatedGenerators[generatorTypes.sustainModEnv] / 1e3;
6478
6405
  this.attackDuration = this.tc2Sec(voice.modulatedGenerators[generatorTypes.attackModEnv]);
6479
6406
  const decayKeyExcursionCents = (60 - voice.midiNote) * voice.modulatedGenerators[generatorTypes.keyNumToModEnvDecay];
6480
- this.decayDuration = this.tc2Sec(voice.modulatedGenerators[generatorTypes.decayModEnv] + decayKeyExcursionCents) * (1 - this.sustainLevel);
6407
+ const decayTime = this.tc2Sec(voice.modulatedGenerators[generatorTypes.decayModEnv] + decayKeyExcursionCents);
6408
+ this.decayDuration = decayTime * (1 - this.sustainLevel);
6481
6409
  const holdKeyExcursionCents = (60 - voice.midiNote) * voice.modulatedGenerators[generatorTypes.keyNumToModEnvHold];
6482
6410
  this.holdDuration = this.tc2Sec(holdKeyExcursionCents + voice.modulatedGenerators[generatorTypes.holdModEnv]);
6483
6411
  this.delayEnd = voice.startTime + this.tc2Sec(voice.modulatedGenerators[generatorTypes.delayModEnv]);
@@ -11386,12 +11314,15 @@ function renderVoice(voice, timeNow, outputL, outputR, startIndex, sampleCount)
11386
11314
  voice.tuningRatio = Math.pow(2, centsTotal / 1200);
11387
11315
  }
11388
11316
  const gainTarget = cbAttenuationToGain(modulated[generatorTypes.initialAttenuation]) * cbAttenuationToGain(volumeExcursionCentibels);
11389
- const buffer = core.voiceBuffer;
11390
11317
  if (voice.loopingMode === 2 && !voice.isInRelease) {
11391
- voice.isActive = voice.volEnv.process(sampleCount, buffer, gainTarget);
11318
+ voice.isActive = voice.volEnv.process(sampleCount, gainTarget);
11392
11319
  return;
11393
11320
  }
11321
+ const buffer = core.voiceBuffer;
11394
11322
  voice.isActive = voice.wavetable.process(sampleCount, voice.tuningRatio, buffer);
11323
+ let gain = voice.volEnv.outputGain;
11324
+ const envActive = voice.volEnv.process(sampleCount, gainTarget);
11325
+ const gainInc = (voice.volEnv.outputGain - gain) / sampleCount;
11395
11326
  {
11396
11327
  const f = voice.filter;
11397
11328
  const initialFc = modulated[generatorTypes.initialFilterFc];
@@ -11402,8 +11333,13 @@ function renderVoice(voice, timeNow, outputL, outputR, startIndex, sampleCount)
11402
11333
  }
11403
11334
  const targetCutoff = f.currentInitialFc + lowpassExcursion;
11404
11335
  const modulatedResonance = modulated[generatorTypes.initialFilterQ];
11405
- if (f.currentInitialFc > 13499 && targetCutoff > 13499 && modulatedResonance === 0) f.currentInitialFc = 13500;
11406
- else {
11336
+ if (f.currentInitialFc > 13499 && targetCutoff > 13499 && modulatedResonance === 0) {
11337
+ f.currentInitialFc = 13500;
11338
+ for (let i = 0; i < sampleCount; i++) {
11339
+ buffer[i] *= gain;
11340
+ gain += gainInc;
11341
+ }
11342
+ } else {
11407
11343
  if (Math.abs(f.lastTargetCutoff - targetCutoff) > 1 || f.resonanceCb !== modulatedResonance) {
11408
11344
  f.lastTargetCutoff = targetCutoff;
11409
11345
  f.resonanceCb = modulatedResonance;
@@ -11418,7 +11354,8 @@ function renderVoice(voice, timeNow, outputL, outputR, startIndex, sampleCount)
11418
11354
  x1 = input;
11419
11355
  y2 = y1;
11420
11356
  y1 = filtered;
11421
- buffer[i] = filtered;
11357
+ buffer[i] = filtered * gain;
11358
+ gain += gainInc;
11422
11359
  }
11423
11360
  f.x1 = x1;
11424
11361
  f.x2 = x2;
@@ -11426,7 +11363,6 @@ function renderVoice(voice, timeNow, outputL, outputR, startIndex, sampleCount)
11426
11363
  f.y2 = y2;
11427
11364
  }
11428
11365
  }
11429
- const envActive = voice.volEnv.process(sampleCount, buffer, gainTarget);
11430
11366
  voice.isActive = voice.isActive && envActive;
11431
11367
  let pan;
11432
11368
  if (voice.overridePan) pan = voice.overridePan;
@@ -11434,10 +11370,10 @@ function renderVoice(voice, timeNow, outputL, outputR, startIndex, sampleCount)
11434
11370
  voice.currentPan += (modulated[generatorTypes.pan] - voice.currentPan) * core.panSmoothingFactor;
11435
11371
  pan = voice.currentPan;
11436
11372
  }
11437
- const gain = core.masterParameters.masterGain * core.midiVolume * voiceGain;
11373
+ const outputGain = core.masterParameters.masterGain * core.midiVolume * voiceGain;
11438
11374
  const index = pan + 500 | 0;
11439
- const gainLeft = panTableLeft$1[index] * gain * core.panLeft;
11440
- const gainRight = panTableRight$1[index] * gain * core.panRight;
11375
+ const gainLeft = panTableLeft$1[index] * outputGain * core.panLeft;
11376
+ const gainRight = panTableRight$1[index] * outputGain * core.panRight;
11441
11377
  if (this.insertionEnabled) {
11442
11378
  const insertionL = core.insertionInputL;
11443
11379
  const insertionR = core.insertionInputR;
@@ -11457,20 +11393,20 @@ function renderVoice(voice, timeNow, outputL, outputR, startIndex, sampleCount)
11457
11393
  if (!core.enableEffects) return;
11458
11394
  const reverbSend = modulated[generatorTypes.reverbEffectsSend] * voice.reverbSend;
11459
11395
  if (reverbSend > 0) {
11460
- const reverbGain = core.masterParameters.reverbGain * gain * (reverbSend / 1e3);
11396
+ const reverbGain = core.masterParameters.reverbGain * outputGain * (reverbSend / 1e3);
11461
11397
  const reverb = core.reverbInput;
11462
11398
  for (let i = 0; i < sampleCount; i++) reverb[i] += reverbGain * buffer[i];
11463
11399
  }
11464
11400
  const chorusSend = modulated[generatorTypes.chorusEffectsSend] * voice.chorusSend;
11465
11401
  if (chorusSend > 0) {
11466
- const chorusGain = core.masterParameters.chorusGain * (chorusSend / 1e3) * gain;
11402
+ const chorusGain = core.masterParameters.chorusGain * (chorusSend / 1e3) * outputGain;
11467
11403
  const chorus = core.chorusInput;
11468
11404
  for (let i = 0; i < sampleCount; i++) chorus[i] += chorusGain * buffer[i];
11469
11405
  }
11470
11406
  if (core.delayActive) {
11471
11407
  const delaySend = this.midiControllers[midiControllers.variationDepth] * voice.delaySend;
11472
11408
  if (delaySend > 0) {
11473
- const delayGain = gain * core.masterParameters.delayGain * ((delaySend >> 7) / 127);
11409
+ const delayGain = outputGain * core.masterParameters.delayGain * ((delaySend >> 7) / 127);
11474
11410
  const delay = core.delayInput;
11475
11411
  for (let i = 0; i < sampleCount; i++) delay[i] += delayGain * buffer[i];
11476
11412
  }
@@ -11542,7 +11478,7 @@ function dataEntryCoarse(dataCoarse) {
11542
11478
  switch (paramCoarse) {
11543
11479
  default:
11544
11480
  if (dataCoarse === 64) return;
11545
- SpessaSynthInfo(`%cUnrecognized NRPN for %c${this.channel}%c: %c(0x${paramFine.toString(16).toUpperCase()} 0x${paramFine.toString(16).toUpperCase()})%c data value: %c${dataCoarse}`, consoleColors.warn, consoleColors.recognized, consoleColors.warn, consoleColors.unrecognized, consoleColors.warn, consoleColors.value);
11481
+ SpessaSynthInfo(`%cUnrecognized NRPN for %c${this.channel}%c: %c(0x${paramCoarse.toString(16).toUpperCase()} 0x${paramFine.toString(16).toUpperCase()})%c data value: %c${dataCoarse}`, consoleColors.warn, consoleColors.recognized, consoleColors.warn, consoleColors.unrecognized, consoleColors.warn, consoleColors.value);
11546
11482
  break;
11547
11483
  case nonRegisteredMSB.partParameter: {
11548
11484
  const paramLock = this.synthCore.masterParameters.nprnParamLock;
@@ -11821,9 +11757,12 @@ function dataEntryFine(dataValue) {
11821
11757
  switch (this.dataEntryState) {
11822
11758
  default: break;
11823
11759
  case dataEntryStates.RPCoarse:
11824
- case dataEntryStates.RPFine:
11825
- switch (this.midiControllers[midiControllers.registeredParameterMSB] | this.midiControllers[midiControllers.registeredParameterLSB] >> 7) {
11826
- default: break;
11760
+ case dataEntryStates.RPFine: {
11761
+ const rpnValue = this.midiControllers[midiControllers.registeredParameterMSB] | this.midiControllers[midiControllers.registeredParameterLSB] >> 7;
11762
+ switch (rpnValue) {
11763
+ default:
11764
+ SpessaSynthInfo(`%cUnrecognized RPN LSB for %c${this.channel}%c: %c(0x${rpnValue.toString(16)})%c data value: %c${dataValue}`, consoleColors.warn, consoleColors.recognized, consoleColors.warn, consoleColors.unrecognized, consoleColors.warn, consoleColors.value);
11765
+ break;
11827
11766
  case registeredParameterTypes.pitchWheelRange: {
11828
11767
  if (dataValue === 0) break;
11829
11768
  this.midiControllers[128 + modulatorSources.pitchWheelRange] |= dataValue;
@@ -11846,16 +11785,17 @@ function dataEntryFine(dataValue) {
11846
11785
  break;
11847
11786
  }
11848
11787
  break;
11788
+ }
11849
11789
  case dataEntryStates.NRPFine: {
11850
- const NRPNCoarse = this.midiControllers[midiControllers.nonRegisteredParameterMSB] >> 7;
11851
- const NRPNFine = this.midiControllers[midiControllers.nonRegisteredParameterLSB] >> 7;
11852
- if (NRPNCoarse === nonRegisteredMSB.SF2) return;
11853
- switch (NRPNCoarse) {
11790
+ const paramCoarse = this.midiControllers[midiControllers.nonRegisteredParameterMSB] >> 7;
11791
+ const paramFine = this.midiControllers[midiControllers.nonRegisteredParameterLSB] >> 7;
11792
+ if (paramCoarse === nonRegisteredMSB.SF2 || paramCoarse >= nonRegisteredMSB.drumPitch && paramFine <= nonRegisteredMSB.drumDelay || paramCoarse === nonRegisteredMSB.partParameter) return;
11793
+ switch (paramCoarse) {
11854
11794
  default:
11855
- SpessaSynthInfo(`%cUnrecognized NRPN LSB for %c${this.channel}%c: %c(0x${NRPNFine.toString(16).toUpperCase()} 0x${NRPNFine.toString(16).toUpperCase()})%c data value: %c${dataValue}`, consoleColors.warn, consoleColors.recognized, consoleColors.warn, consoleColors.unrecognized, consoleColors.warn, consoleColors.value);
11795
+ SpessaSynthInfo(`%cUnrecognized NRPN LSB for %c${this.channel}%c: %c(0x${paramCoarse.toString(16).toUpperCase()} 0x${paramFine.toString(16).toUpperCase()})%c data value: %c${dataValue}`, consoleColors.warn, consoleColors.recognized, consoleColors.warn, consoleColors.unrecognized, consoleColors.warn, consoleColors.value);
11856
11796
  break;
11857
11797
  case nonRegisteredMSB.awe32:
11858
- handleAWE32NRPN.call(this, NRPNFine, dataValue, this.midiControllers[midiControllers.dataEntryMSB] >> 7);
11798
+ handleAWE32NRPN.call(this, paramFine, dataValue, this.midiControllers[midiControllers.dataEntryMSB] >> 7);
11859
11799
  break;
11860
11800
  }
11861
11801
  }
@@ -18121,7 +18061,8 @@ var SpessaSynthReverb = class {
18121
18061
  set preLowpass(value) {
18122
18062
  this._preLowpass = value;
18123
18063
  this.preLPFfc = 8e3 * .63 ** this._preLowpass;
18124
- this.preLPFa = 1 - Math.exp(-2 * Math.PI * this.preLPFfc / this.sampleRate);
18064
+ const decay = Math.exp(-2 * Math.PI * this.preLPFfc / this.sampleRate);
18065
+ this.preLPFa = 1 - decay;
18125
18066
  this.updateLowpass();
18126
18067
  }
18127
18068
  /**
@@ -18282,7 +18223,8 @@ var SpessaSynthChorus = class {
18282
18223
  set preLowpass(value) {
18283
18224
  this._preLowpass = value;
18284
18225
  this.preLPFfc = 8e3 * .63 ** this._preLowpass;
18285
- this.preLPFa = 1 - Math.exp(-2 * Math.PI * this.preLPFfc / this.sampleRate);
18226
+ const decay = Math.exp(-2 * Math.PI * this.preLPFfc / this.sampleRate);
18227
+ this.preLPFa = 1 - decay;
18286
18228
  }
18287
18229
  _depth = 0;
18288
18230
  get depth() {
@@ -18314,7 +18256,8 @@ var SpessaSynthChorus = class {
18314
18256
  }
18315
18257
  set rate(value) {
18316
18258
  this._rate = value;
18317
- this.rateInc = 15.5 * (value / 127) / this.sampleRate;
18259
+ const rate = 15.5 * (value / 127);
18260
+ this.rateInc = rate / this.sampleRate;
18318
18261
  }
18319
18262
  _level = 64;
18320
18263
  get level() {
@@ -18501,7 +18444,8 @@ var SpessaSynthDelay = class {
18501
18444
  set preLowpass(value) {
18502
18445
  this._preLowpass = value;
18503
18446
  this.preLPFfc = 8e3 * .63 ** this._preLowpass;
18504
- this.preLPFa = 1 - Math.exp(-2 * Math.PI * this.preLPFfc / this.sampleRate);
18447
+ const decay = Math.exp(-2 * Math.PI * this.preLPFfc / this.sampleRate);
18448
+ this.preLPFa = 1 - decay;
18505
18449
  }
18506
18450
  _levelRight = 0;
18507
18451
  get levelRight() {
@@ -19218,6 +19162,25 @@ var SynthesizerCore = class {
19218
19162
  rev.preDelayTime = 0;
19219
19163
  rev.character = macro;
19220
19164
  switch (macro) {
19165
+ /**
19166
+ * REVERB MACRO is a macro parameter that allows global setting of reverb parameters.
19167
+ * When you select the reverb type with REVERB MACRO, each reverb parameter will be set to their most
19168
+ * suitable value.
19169
+ *
19170
+ * Room1, Room2, Room3
19171
+ * These reverbs simulate the reverberation of a room. They provide a well-defined
19172
+ * spacious reverberation.
19173
+ * Hall1, Hall2
19174
+ * These reverbs simulate the reverberation of a concert hall. They provide a deeper
19175
+ * reverberation than the Room reverbs.
19176
+ * Plate
19177
+ * This simulates a plate reverb (a studio device using a metal plate).
19178
+ * Delay
19179
+ * This is a conventional delay that produces echo effects.
19180
+ * Panning Delay
19181
+ * This is a special delay in which the delayed sounds move left and right.
19182
+ * It is effective when you are listening in stereo.
19183
+ */
19221
19184
  case 0:
19222
19185
  rev.character = 0;
19223
19186
  rev.preLowpass = 3;
@@ -19279,6 +19242,23 @@ var SynthesizerCore = class {
19279
19242
  chr.sendLevelToDelay = 0;
19280
19243
  chr.sendLevelToReverb = 0;
19281
19244
  switch (macro) {
19245
+ /**
19246
+ * CHORUS MACRO is a macro parameter that allows global setting of chorus parameters.
19247
+ * When you select the chorus type with CHORUS MACRO, each chorus parameter will be set to their
19248
+ * most suitable value.
19249
+ *
19250
+ * Chorus1, Chorus2, Chorus3, Chorus4
19251
+ * These are conventional chorus effects that add spaciousness and depth to the
19252
+ * sound.
19253
+ * Feedback Chorus
19254
+ * This is a chorus with a flanger-like effect and a soft sound.
19255
+ * Flanger
19256
+ * This is an effect sounding somewhat like a jet airplane taking off and landing.
19257
+ * Short Delay
19258
+ * This is a delay with a short delay time.
19259
+ * Short Delay (FB)
19260
+ * This is a short delay with many repeats.
19261
+ */
19282
19262
  case 0:
19283
19263
  chr.feedback = 0;
19284
19264
  chr.delay = 112;
@@ -19346,6 +19326,29 @@ var SynthesizerCore = class {
19346
19326
  dly.levelRight = dly.levelLeft = 0;
19347
19327
  dly.levelCenter = 127;
19348
19328
  switch (macro) {
19329
+ /**
19330
+ * DELAY MACRO is a macro parameter that allows global setting of delay parameters. When you select the delay type with DELAY MACRO, each delay parameter will be set to their most
19331
+ * suitable value.
19332
+ *
19333
+ * Delay1, Delay2, Delay3
19334
+ * These are conventional delays. 1, 2 and 3 have progressively longer delay times.
19335
+ * Delay4
19336
+ * This is a delay with a rather short delay time.
19337
+ * Pan Delay1. Pan Delay2. Pan Delay3
19338
+ * The delay sound moves between left and right. This is effective when listening in
19339
+ * stereo. 1, 2 and 3 have progressively longer delay times.
19340
+ * Pan Delay4
19341
+ * This is a rather short delay with the delayed sound moving between left and
19342
+ * right.
19343
+ * It is effective when listening in stereo.
19344
+ * Dly To Rev
19345
+ * Reverb is added to the delay sound, which moves between left and right.
19346
+ * It is effective when listening in stereo.
19347
+ * PanRepeat
19348
+ * The delay sound moves between left and right,
19349
+ * but the pan positioning is different from the effects listed above.
19350
+ * It is effective when listening in stereo.
19351
+ */
19349
19352
  case 0:
19350
19353
  dly.timeCenter = 97;
19351
19354
  dly.timeRatioRight = dly.timeRatioLeft = 1;