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 +12 -17
- package/dist/index.js +180 -177
- package/dist/index.js.map +1 -1
- package/package.json +6 -6
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
6206
|
-
|
|
6207
|
-
|
|
6208
|
-
|
|
6209
|
-
|
|
6210
|
-
|
|
6211
|
-
|
|
6212
|
-
|
|
6213
|
-
const
|
|
6214
|
-
|
|
6215
|
-
|
|
6216
|
-
|
|
6217
|
-
|
|
6218
|
-
|
|
6219
|
-
|
|
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.
|
|
6241
|
-
|
|
6242
|
-
|
|
6243
|
-
|
|
6244
|
-
|
|
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
|
-
|
|
6263
|
-
|
|
6264
|
-
|
|
6265
|
-
|
|
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
|
-
|
|
6285
|
-
|
|
6286
|
-
|
|
6287
|
-
|
|
6288
|
-
|
|
6289
|
-
|
|
6290
|
-
|
|
6291
|
-
|
|
6292
|
-
|
|
6293
|
-
|
|
6294
|
-
|
|
6295
|
-
|
|
6296
|
-
|
|
6297
|
-
|
|
6298
|
-
|
|
6299
|
-
|
|
6300
|
-
|
|
6301
|
-
|
|
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
|
-
|
|
6306
|
-
|
|
6307
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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)
|
|
11406
|
-
|
|
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
|
|
11373
|
+
const outputGain = core.masterParameters.masterGain * core.midiVolume * voiceGain;
|
|
11438
11374
|
const index = pan + 500 | 0;
|
|
11439
|
-
const gainLeft = panTableLeft$1[index] *
|
|
11440
|
-
const gainRight = panTableRight$1[index] *
|
|
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 *
|
|
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) *
|
|
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 =
|
|
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${
|
|
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
|
-
|
|
11826
|
-
|
|
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
|
|
11851
|
-
const
|
|
11852
|
-
if (
|
|
11853
|
-
switch (
|
|
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${
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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;
|