spessasynth_lib 3.20.26 → 3.20.28

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.
@@ -97,16 +97,17 @@ export class WorkletLowpassFilter
97
97
  */
98
98
  static apply(voice, outputBuffer, cutoffCents)
99
99
  {
100
- if(cutoffCents > 13499)
100
+ if(cutoffCents > 13499 && voice.filter.reasonanceCb === 0)
101
101
  {
102
102
  return; // filter is open
103
103
  }
104
104
 
105
+ const filter = voice.filter
105
106
  // check if the frequency has changed. if so, calculate new coefficients
106
- if(voice.filter.cutoffCents !== cutoffCents || voice.filter.reasonanceCb !== voice.modulatedGenerators[generatorTypes.initialFilterQ])
107
+ if(filter.cutoffCents !== cutoffCents || filter.reasonanceCb !== voice.modulatedGenerators[generatorTypes.initialFilterQ])
107
108
  {
108
- voice.filter.cutoffCents = cutoffCents;
109
- voice.filter.reasonanceCb = voice.modulatedGenerators[generatorTypes.initialFilterQ];
109
+ filter.cutoffCents = cutoffCents;
110
+ filter.reasonanceCb = voice.modulatedGenerators[generatorTypes.initialFilterQ];
110
111
  WorkletLowpassFilter.calculateCoefficients(voice);
111
112
  }
112
113
 
@@ -114,17 +115,17 @@ export class WorkletLowpassFilter
114
115
  for (let i = 0; i < outputBuffer.length; i++)
115
116
  {
116
117
  let input = outputBuffer[i];
117
- let filtered = voice.filter.a0 * input
118
- + voice.filter.a1 * voice.filter.x1
119
- + voice.filter.a2 * voice.filter.x2
120
- - voice.filter.a3 * voice.filter.y1
121
- - voice.filter.a4 * voice.filter.y2;
118
+ let filtered = filter.a0 * input
119
+ + filter.a1 * filter.x1
120
+ + filter.a2 * filter.x2
121
+ - filter.a3 * filter.y1
122
+ - filter.a4 * filter.y2;
122
123
 
123
124
  // set buffer
124
- voice.filter.x2 = voice.filter.x1;
125
- voice.filter.x1 = input;
126
- voice.filter.y2 = voice.filter.y1;
127
- voice.filter.y1 = filtered;
125
+ filter.x2 = filter.x1;
126
+ filter.x1 = input;
127
+ filter.y2 = filter.y1;
128
+ filter.y1 = filtered;
128
129
 
129
130
  outputBuffer[i] = filtered;
130
131
  }
@@ -135,27 +136,28 @@ export class WorkletLowpassFilter
135
136
  */
136
137
  static calculateCoefficients(voice)
137
138
  {
138
- voice.filter.cutoffHz = absCentsToHz(voice.filter.cutoffCents);
139
+ const filter = voice.filter;
140
+ filter.cutoffHz = absCentsToHz(filter.cutoffCents);
139
141
 
140
142
  // fix cutoff on low frequencies (fluid_iir_filter.c line 392)
141
- if(voice.filter.cutoffHz > 0.45 * sampleRate)
143
+ if(filter.cutoffHz > 0.45 * sampleRate)
142
144
  {
143
- voice.filter.cutoffHz = 0.45 * sampleRate;
145
+ filter.cutoffHz = 0.45 * sampleRate;
144
146
  }
145
147
 
146
148
  // adjust the filterQ (fluid_iir_filter.c line 204)
147
- const qDb = (voice.filter.reasonanceCb / 10) - 3.01;
148
- voice.filter.reasonanceGain = decibelAttenuationToGain(-1 * qDb); // -1 because it's attenuation and we don't want attenuation
149
+ const qDb = (filter.reasonanceCb / 10) - 3.01;
150
+ filter.reasonanceGain = decibelAttenuationToGain(-1 * qDb); // -1 because it's attenuation and we don't want attenuation
149
151
 
150
152
  // reduce the gain by the Q factor (fluid_iir_filter.c line 250)
151
- const qGain = 1 / Math.sqrt(voice.filter.reasonanceGain);
153
+ const qGain = 1 / Math.sqrt(filter.reasonanceGain);
152
154
 
153
155
 
154
156
  // code is ported from https://github.com/sinshu/meltysynth/ to work with js.
155
157
  // I'm too dumb to understand the math behind this...
156
- let w = 2 * Math.PI * voice.filter.cutoffHz / sampleRate; // we're in the audioworkletglobalscope so we can use sampleRate
158
+ let w = 2 * Math.PI * filter.cutoffHz / sampleRate; // we're in the audioworkletglobalscope so we can use sampleRate
157
159
  let cosw = Math.cos(w);
158
- let alpha = Math.sin(w) / (2 * voice.filter.reasonanceGain);
160
+ let alpha = Math.sin(w) / (2 * filter.reasonanceGain);
159
161
 
160
162
  let b1 = (1 - cosw) * qGain;
161
163
  let b0 = b1 / 2;
@@ -165,10 +167,10 @@ export class WorkletLowpassFilter
165
167
  let a2 = 1 - alpha;
166
168
 
167
169
  // set coefficients
168
- voice.filter.a0 = b0 / a0;
169
- voice.filter.a1 = b1 / a0;
170
- voice.filter.a2 = b2 / a0;
171
- voice.filter.a3 = a1 / a0;
172
- voice.filter.a4 = a2 / a0;
170
+ filter.a0 = b0 / a0;
171
+ filter.a1 = b1 / a0;
172
+ filter.a2 = b2 / a0;
173
+ filter.a3 = a1 / a0;
174
+ filter.a4 = a2 / a0;
173
175
  }
174
176
  }
@@ -111,7 +111,7 @@ export class WorkletModulationEnvelope
111
111
  const holdKeyExcursionCents = ((60 - voice.midiNote) * voice.modulatedGenerators[generatorTypes.keyNumToModEnvHold]);
112
112
  env.holdDuration = timecentsToSeconds(holdKeyExcursionCents + voice.modulatedGenerators[generatorTypes.holdModEnv]);
113
113
 
114
- const releaseTime = timecentsToSeconds(voice.modulatedGenerators[generatorTypes.releaseVolEnv]);
114
+ const releaseTime = timecentsToSeconds(voice.modulatedGenerators[generatorTypes.releaseModEnv]);
115
115
  // release time is from the full level to 0%
116
116
  // to get the actual time, multiply by the release start level
117
117
  env.releaseDuration = releaseTime * env.releaseStartLevel;
@@ -9,7 +9,7 @@ import { generatorTypes } from '../../../soundfont/read_sf2/generators.js'
9
9
  export const VOLUME_ENVELOPE_SMOOTHING_FACTOR = 0.001;
10
10
 
11
11
  const DB_SILENCE = 100;
12
- const PERCEIVED_DB_SILENCE = 96;
12
+ const PERCEIVED_DB_SILENCE = 90;
13
13
 
14
14
  /**
15
15
  * VOL ENV STATES:
@@ -25,10 +25,18 @@ export class WorkletVolumeEnvelope
25
25
  {
26
26
  /**
27
27
  * @param sampleRate {number} Hz
28
+ * @param initialDecay {number} cb
28
29
  */
29
- constructor(sampleRate)
30
+ constructor(sampleRate, initialDecay)
30
31
  {
31
32
  this.sampleRate = sampleRate;
33
+ /**
34
+ * if sustain stge is silent,
35
+ * then we can turn off the voice when it is silent.
36
+ * We can't do that with modulated as it can silence the volume and then raise it again and the voice must keep playing
37
+ * @type {boolean}
38
+ */
39
+ this.canEndOnSilentSustain = initialDecay / 10 >= PERCEIVED_DB_SILENCE;
32
40
  }
33
41
 
34
42
  /**
@@ -383,6 +391,10 @@ export class WorkletVolumeEnvelope
383
391
  // fallthrough
384
392
 
385
393
  case 4:
394
+ if(env.canEndOnSilentSustain && env.sustainDbRelative >= PERCEIVED_DB_SILENCE)
395
+ {
396
+ voice.finished = true;
397
+ }
386
398
  // sustain phase: stay at sustain
387
399
  while(true)
388
400
  {
@@ -142,7 +142,7 @@ class WorkletVoice
142
142
  this.channelNumber = channel;
143
143
  this.startTime = currentTime;
144
144
  this.targetKey = targetKey;
145
- this.volumeEnvelope = new WorkletVolumeEnvelope(sampleRate);
145
+ this.volumeEnvelope = new WorkletVolumeEnvelope(sampleRate, generators[generatorTypes.sustainVolEnv]);
146
146
  }
147
147
  /**
148
148
  * Sample ID for voice.