spessasynth_lib 3.24.15 → 3.24.18
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/package.json +1 -1
- package/sequencer/sequencer.js +35 -2
- package/sequencer/worklet_sequencer/events.js +20 -7
- package/sequencer/worklet_sequencer/process_event.js +2 -6
- package/sequencer/worklet_sequencer/sequencer_message.js +8 -1
- package/sequencer/worklet_sequencer/song_control.js +5 -4
- package/sequencer/worklet_sequencer/worklet_sequencer.js +147 -86
- package/synthetizer/synthetizer.js +4 -1
- package/synthetizer/worklet_processor.min.js +9 -9
- package/synthetizer/worklet_system/worklet_methods/controller_control/controller_change.js +4 -4
- package/synthetizer/worklet_system/worklet_methods/controller_control/reset_controllers.js +1 -1
- package/synthetizer/worklet_system/worklet_methods/stopping_notes/kill_note.js +3 -2
- package/synthetizer/worklet_system/worklet_methods/stopping_notes/note_off.js +5 -1
- package/synthetizer/worklet_system/worklet_utilities/lowpass_filter.js +56 -48
|
@@ -47,6 +47,10 @@ export function controllerChange(controllerNumber, controllerValue, force = fals
|
|
|
47
47
|
this.midiControllers[actualCCNum] = (this.midiControllers[actualCCNum] & 0x3F80) | (controllerValue & 0x7F);
|
|
48
48
|
this.voices.forEach(v => computeModulators(v, this.midiControllers, 1, actualCCNum));
|
|
49
49
|
}
|
|
50
|
+
if (this.lockedControllers[controllerNumber])
|
|
51
|
+
{
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
50
54
|
switch (controllerNumber)
|
|
51
55
|
{
|
|
52
56
|
case midiControllers.allNotesOff:
|
|
@@ -180,10 +184,6 @@ export function controllerChange(controllerNumber, controllerValue, force = fals
|
|
|
180
184
|
|
|
181
185
|
// default: apply the controller to the table
|
|
182
186
|
default:
|
|
183
|
-
if (this.lockedControllers[controllerNumber])
|
|
184
|
-
{
|
|
185
|
-
return;
|
|
186
|
-
}
|
|
187
187
|
this.midiControllers[controllerNumber] = controllerValue << 7;
|
|
188
188
|
this.voices.forEach(v => computeModulators(v, this.midiControllers, 1, controllerNumber));
|
|
189
189
|
break;
|
|
@@ -3,9 +3,10 @@ import { generatorTypes } from "../../../../soundfont/basic_soundfont/generator.
|
|
|
3
3
|
/**
|
|
4
4
|
* Stops a note nearly instantly
|
|
5
5
|
* @param midiNote {number}
|
|
6
|
+
* @param releaseTime {number} ticks
|
|
6
7
|
* @this {WorkletProcessorChannel}
|
|
7
8
|
*/
|
|
8
|
-
export function killNote(midiNote)
|
|
9
|
+
export function killNote(midiNote, releaseTime = -12000)
|
|
9
10
|
{
|
|
10
11
|
this.voices.forEach(v =>
|
|
11
12
|
{
|
|
@@ -13,7 +14,7 @@ export function killNote(midiNote)
|
|
|
13
14
|
{
|
|
14
15
|
return;
|
|
15
16
|
}
|
|
16
|
-
v.modulatedGenerators[generatorTypes.releaseVolEnv] =
|
|
17
|
+
v.modulatedGenerators[generatorTypes.releaseVolEnv] = releaseTime; // set release to be very short
|
|
17
18
|
v.release();
|
|
18
19
|
});
|
|
19
20
|
}
|
|
@@ -21,7 +21,11 @@ export function noteOff(midiNote)
|
|
|
21
21
|
// if the channel is percussion channel, do not kill the notes
|
|
22
22
|
if (!this.drumChannel)
|
|
23
23
|
{
|
|
24
|
-
this.killNote(realKey);
|
|
24
|
+
this.killNote(realKey, -6950);
|
|
25
|
+
this.synth.callEvent("noteoff", {
|
|
26
|
+
midiNote: midiNote,
|
|
27
|
+
channel: this.channelNumber
|
|
28
|
+
});
|
|
25
29
|
return;
|
|
26
30
|
}
|
|
27
31
|
}
|
|
@@ -146,55 +146,63 @@ export class WorkletLowpassFilter
|
|
|
146
146
|
const cutoffCents = ~~filter.cutoffCents; // Math.floor
|
|
147
147
|
const qCb = filter.reasonanceCb;
|
|
148
148
|
// check if these coefficients were already cached
|
|
149
|
-
|
|
149
|
+
const cached = WorkletLowpassFilter.cachedCoefficients?.[qCb]?.[cutoffCents];
|
|
150
|
+
if (cached !== undefined)
|
|
150
151
|
{
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
152
|
+
filter.a0 = cached.a0;
|
|
153
|
+
filter.a1 = cached.a1;
|
|
154
|
+
filter.a2 = cached.a2;
|
|
155
|
+
filter.a3 = cached.a3;
|
|
156
|
+
filter.a4 = cached.a4;
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
let cutoffHz = absCentsToHz(cutoffCents);
|
|
160
|
+
|
|
161
|
+
// fix cutoff on low frequencies (fluid_iir_filter.c line 392)
|
|
162
|
+
cutoffHz = Math.min(cutoffHz, 0.45 * sampleRate);
|
|
163
|
+
|
|
164
|
+
const qDb = qCb / 10;
|
|
165
|
+
// correct the filter gain, like fluid does
|
|
166
|
+
const reasonanceGain = decibelAttenuationToGain(-1 * (qDb - 3.01)); // -1 because it's attenuation, and we don't want attenuation
|
|
167
|
+
|
|
168
|
+
// reduce the gain by the Q factor (fluid_iir_filter.c line 250)
|
|
169
|
+
const qGain = 1 / Math.sqrt(decibelAttenuationToGain(-qDb));
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
// initial filtering code was ported from meltysynth created by sinshu.
|
|
173
|
+
let w = 2 * Math.PI * cutoffHz / sampleRate; // we're in the AudioWorkletGlobalScope so we can use sampleRate
|
|
174
|
+
let cosw = Math.cos(w);
|
|
175
|
+
let alpha = Math.sin(w) / (2 * reasonanceGain);
|
|
176
|
+
|
|
177
|
+
let b1 = (1 - cosw) * qGain;
|
|
178
|
+
let b0 = b1 / 2;
|
|
179
|
+
let b2 = b0;
|
|
180
|
+
let a0 = 1 + alpha;
|
|
181
|
+
let a1 = -2 * cosw;
|
|
182
|
+
let a2 = 1 - alpha;
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* set coefficients
|
|
186
|
+
* @type {CachedCoefficient}
|
|
187
|
+
*/
|
|
188
|
+
const toCache = {};
|
|
189
|
+
toCache.a0 = b0 / a0;
|
|
190
|
+
toCache.a1 = b1 / a0;
|
|
191
|
+
toCache.a2 = b2 / a0;
|
|
192
|
+
toCache.a3 = a1 / a0;
|
|
193
|
+
toCache.a4 = a2 / a0;
|
|
194
|
+
filter.a0 = toCache.a0;
|
|
195
|
+
filter.a1 = toCache.a1;
|
|
196
|
+
filter.a2 = toCache.a2;
|
|
197
|
+
filter.a3 = toCache.a3;
|
|
198
|
+
filter.a4 = toCache.a4;
|
|
199
|
+
|
|
200
|
+
if (WorkletLowpassFilter.cachedCoefficients[qCb] === undefined)
|
|
201
|
+
{
|
|
202
|
+
WorkletLowpassFilter.cachedCoefficients[qCb] = [];
|
|
192
203
|
}
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
filter.a2 = cached.a2;
|
|
197
|
-
filter.a3 = cached.a3;
|
|
198
|
-
filter.a4 = cached.a4;
|
|
204
|
+
WorkletLowpassFilter.cachedCoefficients[qCb][cutoffCents] = toCache;
|
|
205
|
+
|
|
206
|
+
|
|
199
207
|
}
|
|
200
208
|
}
|