spessasynth_lib 3.20.1 → 3.20.3

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.
@@ -1,7 +1,8 @@
1
1
  import { getWorkletVoices } from '../worklet_utilities/worklet_voice.js'
2
2
  import { generatorTypes } from '../../../soundfont/read_sf2/generators.js'
3
3
  import { computeModulators } from '../worklet_utilities/worklet_modulator.js'
4
- import { recalculateVolumeEnvelope } from '../worklet_utilities/volume_envelope.js'
4
+ import { WorkletVolumeEnvelope } from '../worklet_utilities/volume_envelope.js'
5
+ import { WorkletModulationEnvelope } from '../worklet_utilities/modulation_envelope.js'
5
6
 
6
7
  /**
7
8
  * Append the voices
@@ -72,7 +73,8 @@ export function noteOn(channel, midiNote, velocity, enableDebugging = false, sen
72
73
  this.releaseVoice(v);
73
74
  v.modulatedGenerators[generatorTypes.releaseVolEnv] = -7000; // make the release nearly instant
74
75
  v.modulatedGenerators[generatorTypes.releaseModEnv] = -7000;
75
- recalculateVolumeEnvelope(v);
76
+ WorkletVolumeEnvelope.recalculate(v);
77
+ WorkletModulationEnvelope.recalculate(v);
76
78
  }
77
79
  })
78
80
  }
@@ -2,12 +2,12 @@ import { generatorTypes } from '../../../soundfont/read_sf2/generators.js'
2
2
  import { absCentsToHz, decibelAttenuationToGain, timecentsToSeconds } from '../worklet_utilities/unit_converter.js'
3
3
  import { getLFOValue } from '../worklet_utilities/lfo.js'
4
4
  import { customControllers } from '../worklet_utilities/worklet_processor_channel.js'
5
- import { getModEnvValue } from '../worklet_utilities/modulation_envelope.js'
5
+ import { WorkletModulationEnvelope } from '../worklet_utilities/modulation_envelope.js'
6
6
  import { getOscillatorData } from '../worklet_utilities/wavetable_oscillator.js'
7
7
  import { panVoice } from '../worklet_utilities/stereo_panner.js'
8
- import { applyVolumeEnvelope, recalculateVolumeEnvelope } from '../worklet_utilities/volume_envelope.js'
9
8
  import { applyLowpassFilter } from '../worklet_utilities/lowpass_filter.js'
10
9
  import { MIN_NOTE_LENGTH } from '../main_processor.js'
10
+ import { WorkletVolumeEnvelope } from '../worklet_utilities/volume_envelope.js'
11
11
 
12
12
 
13
13
  const HALF_PI = Math.PI / 2;
@@ -36,10 +36,10 @@ export function renderVoice(
36
36
  // if not in release, check if the release time is
37
37
  if (currentTime >= voice.releaseStartTime)
38
38
  {
39
- voice.releaseStartModEnv = voice.currentModEnvValue;
39
+
40
40
  voice.isInRelease = true;
41
- recalculateVolumeEnvelope(voice);
42
- voice.volumeEnvelope.currentReleaseGain = decibelAttenuationToGain(voice.volumeEnvelope.currentAttenuationDb);
41
+ WorkletVolumeEnvelope.startRelease(voice);
42
+ WorkletModulationEnvelope.startRelease(voice);
43
43
  }
44
44
  }
45
45
 
@@ -127,7 +127,7 @@ export function renderVoice(
127
127
  // mod env
128
128
  const modEnvPitchDepth = voice.modulatedGenerators[generatorTypes.modEnvToPitch];
129
129
  const modEnvFilterDepth = voice.modulatedGenerators[generatorTypes.modEnvToFilterFc];
130
- const modEnv = getModEnvValue(voice, currentTime);
130
+ const modEnv = WorkletModulationEnvelope.getValue(voice, currentTime);
131
131
  // apply values
132
132
  lowpassCents += modEnv * modEnvFilterDepth;
133
133
  cents += modEnv * modEnvPitchDepth;
@@ -153,7 +153,7 @@ export function renderVoice(
153
153
  applyLowpassFilter(voice, bufferOut, lowpassCents);
154
154
 
155
155
  // volenv
156
- applyVolumeEnvelope(voice, bufferOut, currentTime, modLfoCentibels, this.sampleTime, this.volumeEnvelopeSmoothingFactor);
156
+ WorkletVolumeEnvelope.apply(voice, bufferOut, modLfoCentibels, this.volumeEnvelopeSmoothingFactor);
157
157
 
158
158
  // pan the voice and write out
159
159
  voice.currentPan += (pan - voice.currentPan) * this.panSmoothingFactor; // smooth out pan to prevent clicking
@@ -7,7 +7,7 @@ import { modulatorCurveTypes } from '../../../soundfont/read_sf2/modulators.js'
7
7
  * modulation_envelope.js
8
8
  * purpose: calculates the modulation envelope for the given voice
9
9
  */
10
- const PEAK = 1;
10
+ const MODENV_PEAK = 1;
11
11
 
12
12
  // 1000 should be precise enough
13
13
  const CONVEX_ATTACK = new Float32Array(1000);
@@ -16,58 +16,156 @@ for (let i = 0; i < CONVEX_ATTACK.length; i++) {
16
16
  CONVEX_ATTACK[i] = getModulatorCurveValue(0, modulatorCurveTypes.convex, i / 1000, 0);
17
17
  }
18
18
 
19
- /**
20
- * Calculates the current modulation envelope value for the given time and voice
21
- * @param voice {WorkletVoice} the voice we're working on
22
- * @param currentTime {number} in seconds
23
- * @returns {number} modenv value, from 0 to 1
24
- */
25
- export function getModEnvValue(voice, currentTime)
19
+ export class WorkletModulationEnvelope
26
20
  {
27
- // calculate env times
28
- let attack = timecentsToSeconds(voice.modulatedGenerators[generatorTypes.attackModEnv]);
29
- let decay = timecentsToSeconds(voice.modulatedGenerators[generatorTypes.decayModEnv] + ((60 - voice.midiNote) * voice.modulatedGenerators[generatorTypes.keyNumToModEnvDecay]));
30
- let hold = timecentsToSeconds(voice.modulatedGenerators[generatorTypes.holdModEnv] + ((60 - voice.midiNote) * voice.modulatedGenerators[generatorTypes.keyNumToModEnvHold]));
21
+ /**
22
+ * The attack duration, in seconds
23
+ * @type {number}
24
+ */
25
+ attackDuration = 0;
26
+ /**
27
+ * The decay duration, in seconds
28
+ * @type {number}
29
+ */
30
+ decayDuration = 0;
31
31
 
32
- // calculate absolute times
33
- if(voice.isInRelease && voice.releaseStartTime < currentTime)
34
- {
35
- let release = timecentsToSeconds(voice.modulatedGenerators[generatorTypes.releaseModEnv]);
36
- if(voice.modulatedGenerators[generatorTypes.releaseModEnv] < -7199)
37
- {
38
- // prevent lowpass bugs if release is instant
39
- return voice.releaseStartModEnv;
40
- }
41
- return (1 - (currentTime - voice.releaseStartTime) / release) * voice.releaseStartModEnv;
42
- }
32
+ /**
33
+ * The hold duration, in seconds
34
+ * @type {number}
35
+ */
36
+ holdDuration = 0;
43
37
 
44
- let sustain = 1 - (voice.modulatedGenerators[generatorTypes.sustainModEnv] / 1000);
45
- let delayEnd = timecentsToSeconds(voice.modulatedGenerators[generatorTypes.delayModEnv]) + voice.startTime;
46
- let attackEnd = attack + delayEnd;
47
- let holdEnd = hold + attackEnd;
48
- let decayEnd = decay + holdEnd;
38
+ /**
39
+ * Release duration, in seconds
40
+ * @type {number}
41
+ */
42
+ releaseDuration = 0;
49
43
 
50
- let modEnvVal
51
- if(currentTime < delayEnd)
52
- {
53
- modEnvVal = 0; // delay
54
- }
55
- else if(currentTime < attackEnd)
56
- {
57
- modEnvVal = CONVEX_ATTACK[~~((1 - (attackEnd - currentTime) / attack) * 1000)]; // convex attack
58
- }
59
- else if(currentTime < holdEnd)
44
+ /**
45
+ * The sustain level 0-1
46
+ * @type {number}
47
+ */
48
+ sustainLevel = 0;
49
+
50
+ /**
51
+ * Delay phase end time in seconds, absolute (audio context time)
52
+ * @type {number}
53
+ */
54
+ delayEnd = 0;
55
+ /**
56
+ * Attack phase end time in seconds, absolute (audio context time)
57
+ * @type {number}
58
+ */
59
+ attackEnd = 0;
60
+ /**
61
+ * Hold phase end time in seconds, absolute (audio context time)
62
+ * @type {number}
63
+ */
64
+ holdEnd = 0;
65
+ /**
66
+ * Decay phase end time in seconds, absolute (audio context time)
67
+ * @type {number}
68
+ */
69
+ decayEnd = 0;
70
+
71
+ /**
72
+ * The level of the envelope when the release phase starts
73
+ * @type {number}
74
+ */
75
+ releaseStartLevel = 0;
76
+
77
+ /**
78
+ * The current modulation envelope value
79
+ * @type {number}
80
+ */
81
+ currentValue = 0;
82
+
83
+ /**
84
+ * Starts the release phase in the envelope
85
+ * @param voice {WorkletVoice} the voice this envelope belongs to
86
+ */
87
+ static startRelease(voice)
60
88
  {
61
- modEnvVal = PEAK; // peak
89
+ voice.modulationEnvelope.releaseStartLevel = voice.modulationEnvelope.currentValue;
90
+ WorkletModulationEnvelope.recalculate(voice);
62
91
  }
63
- else if(currentTime < decayEnd)
92
+
93
+ /**
94
+ * @param voice {WorkletVoice} the voice to recalculate
95
+ */
96
+ static recalculate(voice)
64
97
  {
65
- modEnvVal = (1 - (decayEnd - currentTime) / decay) * (sustain - PEAK) + PEAK; // decay
98
+ const env = voice.modulationEnvelope;
99
+
100
+ env.sustainLevel = 1 - (voice.modulatedGenerators[generatorTypes.sustainModEnv] / 1000);
101
+
102
+ env.attackDuration = timecentsToSeconds(voice.modulatedGenerators[generatorTypes.attackModEnv]);
103
+
104
+ const decayKeyExcursionCents = ((60 - voice.midiNote) * voice.modulatedGenerators[generatorTypes.keyNumToModEnvDecay]);
105
+ const decayTime = timecentsToSeconds(voice.modulatedGenerators[generatorTypes.decayModEnv] + decayKeyExcursionCents);
106
+ // according to the specification, the decay time is the time it takes to reach 0% from 100%.
107
+ // calculate the time to reach actual sustain level
108
+ // for example, sustain 0.6 will be 0.4 of the decay time
109
+ env.decayDuration = decayTime * (1 - env.sustainLevel);
110
+
111
+ const holdKeyExcursionCents = ((60 - voice.midiNote) * voice.modulatedGenerators[generatorTypes.keyNumToModEnvHold]);
112
+ env.holdDuration = timecentsToSeconds(holdKeyExcursionCents + voice.modulatedGenerators[generatorTypes.holdModEnv]);
113
+
114
+ const releaseTime = timecentsToSeconds(voice.modulatedGenerators[generatorTypes.releaseVolEnv]);
115
+ // release time is from the full level to 0%
116
+ // to get the actual time, multiply by the release start level
117
+ env.releaseDuration = releaseTime * env.releaseStartLevel;
118
+
119
+ env.delayEnd = voice.startTime + timecentsToSeconds(voice.modulatedGenerators[generatorTypes.delayModEnv]);
120
+ env.attackEnd = env.delayEnd + env.attackDuration;
121
+ env.holdEnd = env.attackEnd + env.holdDuration;
122
+ env.decayEnd = env.holdEnd + env.decayDuration;
66
123
  }
67
- else
124
+
125
+ /**
126
+ * FIXME: GeneralUserGS 808 toms sound incorrect
127
+ * Calculates the current modulation envelope value for the given time and voice
128
+ * @param voice {WorkletVoice} the voice we're working on
129
+ * @param currentTime {number} in seconds
130
+ * @returns {number} modenv value, from 0 to 1
131
+ */
132
+ static getValue(voice, currentTime)
68
133
  {
69
- modEnvVal = sustain; // sustain
134
+ const env = voice.modulationEnvelope;
135
+ if(voice.isInRelease)
136
+ {
137
+ if(voice.modulatedGenerators[generatorTypes.releaseModEnv] < -7199)
138
+ {
139
+ // prevent lowpass bugs if release is instant
140
+ return env.releaseStartLevel;
141
+ }
142
+ return Math.max(0, (1 - (currentTime - voice.releaseStartTime) / env.releaseDuration) * env.releaseStartLevel);
143
+ }
144
+
145
+ if(currentTime < env.delayEnd)
146
+ {
147
+ env.currentValue = 0; // delay
148
+ }
149
+ else if(currentTime < env.attackEnd)
150
+ {
151
+ // modulation envelope uses convex curve for attack
152
+ env.currentValue = CONVEX_ATTACK[~~((1 - (env.attackEnd - currentTime) / env.attackDuration) * 1000)];
153
+ }
154
+ else if(currentTime < env.holdEnd)
155
+ {
156
+ // hold: stay at 1
157
+ env.currentValue = MODENV_PEAK;
158
+ }
159
+ else if(currentTime < env.decayEnd)
160
+ {
161
+ // decay: linear ramp from 1 to sustain level
162
+ env.currentValue = (1 - (env.decayEnd - currentTime) / env.decayDuration) * (env.sustainLevel - MODENV_PEAK) + MODENV_PEAK;
163
+ }
164
+ else
165
+ {
166
+ // sustain: stay at sustain level
167
+ env.currentValue = env.sustainLevel;
168
+ }
169
+ return env.currentValue;
70
170
  }
71
- voice.currentModEnvValue = modEnvVal;
72
- return modEnvVal;
73
171
  }
@@ -1,5 +1,5 @@
1
- export const WORKLET_SYSTEM_REVERB_DIVIDER = 500;
2
- export const WORKLET_SYSTEM_CHORUS_DIVIDER = 500;
1
+ export const WORKLET_SYSTEM_REVERB_DIVIDER = 800;
2
+ export const WORKLET_SYSTEM_CHORUS_DIVIDER = 800;
3
3
  /**
4
4
  * stereo_panner.js
5
5
  * purpose: pans a given voice out to the stereo output and to the effects' outputs