spessasynth_lib 3.24.13 → 3.24.15

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.
Files changed (61) hide show
  1. package/midi_parser/basic_midi.js +457 -68
  2. package/midi_parser/midi_loader.js +18 -503
  3. package/midi_parser/midi_message.js +18 -5
  4. package/midi_parser/midi_sequence.js +2 -2
  5. package/package.json +1 -1
  6. package/sequencer/worklet_sequencer/process_event.js +1 -6
  7. package/synthetizer/synthetizer.js +9 -6
  8. package/synthetizer/worklet_processor.min.js +12 -12
  9. package/synthetizer/worklet_system/README.md +2 -2
  10. package/synthetizer/worklet_system/main_processor.js +106 -95
  11. package/synthetizer/worklet_system/message_protocol/handle_message.js +22 -17
  12. package/synthetizer/worklet_system/message_protocol/worklet_message.js +2 -1
  13. package/synthetizer/worklet_system/snapshot/apply_synthesizer_snapshot.js +14 -0
  14. package/synthetizer/worklet_system/snapshot/channel_snapshot.js +166 -0
  15. package/synthetizer/worklet_system/snapshot/send_synthesizer_snapshot.js +14 -0
  16. package/synthetizer/worklet_system/snapshot/synthesizer_snapshot.js +121 -0
  17. package/synthetizer/worklet_system/worklet_methods/controller_control/controller_change.js +196 -0
  18. package/synthetizer/worklet_system/worklet_methods/controller_control/master_parameters.js +34 -0
  19. package/synthetizer/worklet_system/worklet_methods/{reset_controllers.js → controller_control/reset_controllers.js} +33 -39
  20. package/synthetizer/worklet_system/worklet_methods/create_worklet_channel.js +26 -0
  21. package/synthetizer/worklet_system/worklet_methods/{data_entry.js → data_entry/data_entry_coarse.js} +38 -105
  22. package/synthetizer/worklet_system/worklet_methods/data_entry/data_entry_fine.js +64 -0
  23. package/synthetizer/worklet_system/worklet_methods/mute_channel.js +17 -0
  24. package/synthetizer/worklet_system/worklet_methods/note_on.js +36 -34
  25. package/synthetizer/worklet_system/worklet_methods/program_change.js +49 -0
  26. package/synthetizer/worklet_system/worklet_methods/{voice_control.js → render_voice.js} +37 -120
  27. package/synthetizer/worklet_system/worklet_methods/soundfont_management/clear_sound_font.js +35 -0
  28. package/synthetizer/worklet_system/worklet_methods/soundfont_management/get_preset.js +20 -0
  29. package/synthetizer/worklet_system/worklet_methods/soundfont_management/reload_sound_font.js +43 -0
  30. package/synthetizer/worklet_system/worklet_methods/soundfont_management/send_preset_list.js +31 -0
  31. package/synthetizer/worklet_system/worklet_methods/soundfont_management/set_embedded_sound_font.js +21 -0
  32. package/synthetizer/worklet_system/worklet_methods/stopping_notes/kill_note.js +19 -0
  33. package/synthetizer/worklet_system/worklet_methods/stopping_notes/note_off.js +51 -0
  34. package/synthetizer/worklet_system/worklet_methods/stopping_notes/stop_all_channels.js +16 -0
  35. package/synthetizer/worklet_system/worklet_methods/stopping_notes/stop_all_notes.js +30 -0
  36. package/synthetizer/worklet_system/worklet_methods/stopping_notes/voice_killing.js +63 -0
  37. package/synthetizer/worklet_system/worklet_methods/system_exclusive.js +31 -30
  38. package/synthetizer/worklet_system/worklet_methods/tuning_control/channel_pressure.js +24 -0
  39. package/synthetizer/worklet_system/worklet_methods/tuning_control/pitch_wheel.js +33 -0
  40. package/synthetizer/worklet_system/worklet_methods/tuning_control/poly_pressure.js +31 -0
  41. package/synthetizer/worklet_system/worklet_methods/tuning_control/set_master_tuning.js +15 -0
  42. package/synthetizer/worklet_system/worklet_methods/tuning_control/set_modulation_depth.js +27 -0
  43. package/synthetizer/worklet_system/worklet_methods/tuning_control/set_octave_tuning.js +15 -0
  44. package/synthetizer/worklet_system/worklet_methods/tuning_control/set_tuning.js +24 -0
  45. package/synthetizer/worklet_system/worklet_methods/tuning_control/set_tuning_semitones.js +19 -0
  46. package/synthetizer/worklet_system/worklet_methods/tuning_control/transpose_all_channels.js +15 -0
  47. package/synthetizer/worklet_system/worklet_methods/tuning_control/transpose_channel.js +31 -0
  48. package/synthetizer/worklet_system/worklet_utilities/controller_tables.js +10 -1
  49. package/synthetizer/worklet_system/worklet_utilities/lfo.js +2 -1
  50. package/synthetizer/worklet_system/worklet_utilities/modulation_envelope.js +4 -4
  51. package/synthetizer/worklet_system/worklet_utilities/modulator_curves.js +4 -5
  52. package/synthetizer/worklet_system/worklet_utilities/stereo_panner.js +18 -18
  53. package/synthetizer/worklet_system/worklet_utilities/wavetable_oscillator.js +210 -206
  54. package/synthetizer/worklet_system/worklet_utilities/worklet_processor_channel.js +354 -108
  55. package/synthetizer/worklet_system/worklet_utilities/worklet_voice.js +22 -9
  56. package/synthetizer/worklet_system/snapshot/snapshot.js +0 -311
  57. package/synthetizer/worklet_system/worklet_methods/controller_control.js +0 -260
  58. package/synthetizer/worklet_system/worklet_methods/note_off.js +0 -119
  59. package/synthetizer/worklet_system/worklet_methods/program_control.js +0 -282
  60. package/synthetizer/worklet_system/worklet_methods/tuning_control.js +0 -233
  61. package/synthetizer/worklet_system/worklet_methods/vibrato_control.js +0 -29
@@ -1,34 +1,29 @@
1
- import { absCentsToHz, timecentsToSeconds } from "../worklet_utilities/unit_converter.js";
2
- import { getLFOValue } from "../worklet_utilities/lfo.js";
3
- import { WorkletModulationEnvelope } from "../worklet_utilities/modulation_envelope.js";
4
- import {
5
- getSampleCubic,
6
- getSampleLinear,
7
- getSampleNearest,
8
- interpolationTypes
9
- } from "../worklet_utilities/wavetable_oscillator.js";
10
- import { MIN_NOTE_LENGTH } from "../main_processor.js";
11
1
  import { WorkletVolumeEnvelope } from "../worklet_utilities/volume_envelope.js";
2
+ import { WorkletModulationEnvelope } from "../worklet_utilities/modulation_envelope.js";
12
3
  import { generatorTypes } from "../../../soundfont/basic_soundfont/generator.js";
13
4
  import { customControllers } from "../worklet_utilities/controller_tables.js";
5
+ import { absCentsToHz, timecentsToSeconds } from "../worklet_utilities/unit_converter.js";
6
+ import { getLFOValue } from "../worklet_utilities/lfo.js";
7
+ import { interpolationTypes, WavetableOscillator } from "../worklet_utilities/wavetable_oscillator.js";
14
8
  import { WorkletLowpassFilter } from "../worklet_utilities/lowpass_filter.js";
15
9
 
16
10
  /**
17
11
  * Renders a voice to the stereo output buffer
18
- * @param channel {WorkletProcessorChannel} the voice's channel
19
12
  * @param voice {WorkletVoice} the voice to render
20
13
  * @param outputLeft {Float32Array} the left output buffer
21
14
  * @param outputRight {Float32Array} the right output buffer
22
- * @param reverbOutput {Float32Array[]} output for reverb
23
- * @param chorusOutput {Float32Array[]} output for chorus
24
- * @this {SpessaSynthProcessor}
15
+ * @param reverbOutputLeft {Float32Array} left output for reverb
16
+ * @param reverbOutputRight {Float32Array} right output for reverb
17
+ * @param chorusOutputLeft {Float32Array} left output for chorus
18
+ * @param chorusOutputRight {Float32Array} right output for chorus
19
+ * @this {WorkletProcessorChannel}
20
+ * @returns {boolean} true if the voice is finished
25
21
  */
26
22
  export function renderVoice(
27
- channel,
28
23
  voice,
29
24
  outputLeft, outputRight,
30
- reverbOutput,
31
- chorusOutput
25
+ reverbOutputLeft, reverbOutputRight,
26
+ chorusOutputLeft, chorusOutputRight
32
27
  )
33
28
  {
34
29
  // check if release
@@ -56,7 +51,7 @@ export function renderVoice(
56
51
  {
57
52
  voice.finished = true;
58
53
  }
59
- return;
54
+ return voice.finished;
60
55
  }
61
56
 
62
57
  // TUNING
@@ -64,16 +59,16 @@ export function renderVoice(
64
59
 
65
60
  // calculate tuning
66
61
  let cents = voice.modulatedGenerators[generatorTypes.fineTune] // soundfont fine tune
67
- + channel.customControllers[customControllers.channelTuning] // RPN channel fine tuning
68
- + channel.customControllers[customControllers.channelTransposeFine] // custom tuning (synth.transpose)
69
- + channel.customControllers[customControllers.masterTuning] // master tuning, set by sysEx
70
- + channel.channelOctaveTuning[voice.midiNote % 12] // MTS octave tuning
71
- + channel.keyCentTuning[voice.midiNote]; // SysEx key tuning
62
+ + this.customControllers[customControllers.channelTuning] // RPN channel fine tuning
63
+ + this.customControllers[customControllers.channelTransposeFine] // custom tuning (synth.transpose)
64
+ + this.customControllers[customControllers.masterTuning] // master tuning, set by sysEx
65
+ + this.channelOctaveTuning[voice.midiNote % 12] // MTS octave tuning
66
+ + this.keyCentTuning[voice.midiNote]; // SysEx key tuning
72
67
  let semitones = voice.modulatedGenerators[generatorTypes.coarseTune] // soundfont coarse tuning
73
- + channel.customControllers[customControllers.channelTuningSemitones]; // RPN channel coarse tuning
68
+ + this.customControllers[customControllers.channelTuningSemitones]; // RPN channel coarse tuning
74
69
 
75
70
  // midi tuning standard
76
- const tuning = this.tunings[channel.preset.program]?.[voice.realKey];
71
+ const tuning = this.synth.tunings[this.preset.program]?.[voice.realKey];
77
72
  if (tuning?.midiNote >= 0)
78
73
  {
79
74
  // override key
@@ -104,7 +99,7 @@ export function renderVoice(
104
99
  const vibFreqHz = absCentsToHz(voice.modulatedGenerators[generatorTypes.freqVibLFO]);
105
100
  const lfoVal = getLFOValue(vibStart, vibFreqHz, currentTime);
106
101
  // use modulation multiplier (RPN modulation depth)
107
- cents += lfoVal * (vibratoDepth * channel.customControllers[customControllers.modulationMultiplier]);
102
+ cents += lfoVal * (vibratoDepth * this.customControllers[customControllers.modulationMultiplier]);
108
103
  }
109
104
 
110
105
  // low pass frequency
@@ -123,7 +118,7 @@ export function renderVoice(
123
118
  const modFreqHz = absCentsToHz(voice.modulatedGenerators[generatorTypes.freqModLFO]);
124
119
  const modLfoValue = getLFOValue(modStart, modFreqHz, currentTime);
125
120
  // use modulation multiplier (RPN modulation depth)
126
- cents += modLfoValue * (modPitchDepth * channel.customControllers[customControllers.modulationMultiplier]);
121
+ cents += modLfoValue * (modPitchDepth * this.customControllers[customControllers.modulationMultiplier]);
127
122
  // vole nv volume offset
128
123
  // negate the lfo value because audigy starts with increase rather than decrease
129
124
  modLfoCentibels = -modLfoValue * modVolDepth;
@@ -132,17 +127,17 @@ export function renderVoice(
132
127
  }
133
128
 
134
129
  // channel vibrato (GS NRPN)
135
- if (channel.channelVibrato.depth > 0)
130
+ if (this.channelVibrato.depth > 0)
136
131
  {
137
132
  // same as others
138
133
  const channelVibrato = getLFOValue(
139
- voice.startTime + channel.channelVibrato.delay,
140
- channel.channelVibrato.rate,
134
+ voice.startTime + this.channelVibrato.delay,
135
+ this.channelVibrato.rate,
141
136
  currentTime
142
137
  );
143
138
  if (channelVibrato)
144
139
  {
145
- cents += channelVibrato * channel.channelVibrato.depth;
140
+ cents += channelVibrato * this.channelVibrato.depth;
146
141
  }
147
142
  }
148
143
 
@@ -167,19 +162,20 @@ export function renderVoice(
167
162
  const bufferOut = new Float32Array(outputLeft.length);
168
163
 
169
164
  // wave table oscillator
170
- switch (this.interpolationType)
165
+ switch (this.synth.interpolationType)
171
166
  {
167
+ case interpolationTypes.fourthOrder:
168
+ WavetableOscillator.getSampleCubic(voice, bufferOut);
169
+ break;
170
+
172
171
  case interpolationTypes.linear:
173
172
  default:
174
- getSampleLinear(voice, bufferOut);
173
+ WavetableOscillator.getSampleLinear(voice, bufferOut);
175
174
  break;
176
175
 
177
176
  case interpolationTypes.nearestNeighbor:
178
- getSampleNearest(voice, bufferOut);
177
+ WavetableOscillator.getSampleNearest(voice, bufferOut);
179
178
  break;
180
-
181
- case interpolationTypes.fourthOrder:
182
- getSampleCubic(voice, bufferOut);
183
179
  }
184
180
 
185
181
  /* low pass filter
@@ -190,93 +186,14 @@ export function renderVoice(
190
186
  WorkletLowpassFilter.apply(voice, bufferOut, lowpassCents, initialFc > 13499);
191
187
 
192
188
  // vol env
193
- WorkletVolumeEnvelope.apply(voice, bufferOut, modLfoCentibels, this.volumeEnvelopeSmoothingFactor);
189
+ WorkletVolumeEnvelope.apply(voice, bufferOut, modLfoCentibels, this.synth.volumeEnvelopeSmoothingFactor);
194
190
 
195
191
  this.panVoice(
196
192
  voice,
197
193
  bufferOut,
198
194
  outputLeft, outputRight,
199
- reverbOutput,
200
- chorusOutput
195
+ reverbOutputLeft, reverbOutputRight,
196
+ chorusOutputLeft, chorusOutputRight
201
197
  );
202
- }
203
-
204
-
205
- /**
206
- * @param channel {WorkletProcessorChannel}
207
- * @param voice {WorkletVoice}
208
- * @return {number}
209
- */
210
- function getPriority(channel, voice)
211
- {
212
- let priority = 0;
213
- if (channel.drumChannel)
214
- {
215
- // important
216
- priority += 5;
217
- }
218
- if (voice.isInRelease)
219
- {
220
- // not important
221
- priority -= 5;
222
- }
223
- // less velocity = less important
224
- priority += voice.velocity / 25; // map to 0-5
225
- // the newer, more important
226
- priority -= voice.volumeEnvelope.state;
227
- if (voice.isInRelease)
228
- {
229
- priority -= 5;
230
- }
231
- priority -= voice.volumeEnvelope.currentAttenuationDb / 50;
232
- return priority;
233
- }
234
-
235
- /**
236
- * @this {SpessaSynthProcessor}
237
- * @param amount {number}
238
- */
239
- export function voiceKilling(amount)
240
- {
241
- let allVoices = [];
242
- for (const channel of this.workletProcessorChannels)
243
- {
244
- for (const voice of channel.voices)
245
- {
246
- if (!voice.finished)
247
- {
248
- const priority = getPriority(channel, voice);
249
- allVoices.push({ channel, voice, priority });
250
- }
251
- }
252
- }
253
-
254
- // Step 2: Sort voices by priority (ascending order)
255
- allVoices.sort((a, b) => a.priority - b.priority);
256
- const voicesToRemove = allVoices.slice(0, amount);
257
-
258
- for (const { channel, voice } of voicesToRemove)
259
- {
260
- const index = channel.voices.indexOf(voice);
261
- if (index > -1)
262
- {
263
- channel.voices.splice(index, 1);
264
- }
265
- }
266
- }
267
-
268
- /**
269
- * Stops the voice
270
- * @param voice {WorkletVoice} the voice to stop
271
- * @param minNoteLength {number} minimum note length in seconds
272
- * @this {SpessaSynthProcessor}
273
- */
274
- export function releaseVoice(voice, minNoteLength = MIN_NOTE_LENGTH)
275
- {
276
- voice.releaseStartTime = currentTime;
277
- // check if the note is shorter than the min note time, if so, extend it
278
- if (voice.releaseStartTime - voice.startTime < minNoteLength)
279
- {
280
- voice.releaseStartTime = voice.startTime + minNoteLength;
281
- }
198
+ return voice.finished;
282
199
  }
@@ -0,0 +1,35 @@
1
+ /**
2
+ * @this {SpessaSynthProcessor}
3
+ * @param sendPresets {boolean}
4
+ * @param clearOverride {boolean}
5
+ */
6
+ export function clearSoundFont(sendPresets = true, clearOverride = true)
7
+ {
8
+ this.stopAllChannels(true);
9
+ if (clearOverride)
10
+ {
11
+ delete this.overrideSoundfont;
12
+ this.overrideSoundfont = undefined;
13
+ }
14
+ this.defaultPreset = this.getPreset(0, 0);
15
+ this.drumPreset = this.getPreset(128, 0);
16
+
17
+ for (let i = 0; i < this.workletProcessorChannels.length; i++)
18
+ {
19
+ const channelObject = this.workletProcessorChannels[i];
20
+ channelObject.cachedVoices = [];
21
+ for (let j = 0; j < 128; j++)
22
+ {
23
+ channelObject.cachedVoices.push([]);
24
+ }
25
+ if (!clearOverride || (clearOverride && channelObject.presetUsesOverride))
26
+ {
27
+ channelObject.lockPreset = false;
28
+ }
29
+ channelObject.programChange(channelObject.preset.program);
30
+ }
31
+ if (sendPresets)
32
+ {
33
+ this.sendPresetList();
34
+ }
35
+ }
@@ -0,0 +1,20 @@
1
+ /**
2
+ * @this {SpessaSynthProcessor}
3
+ * @param program {number}
4
+ * @param bank {number}
5
+ * @returns {BasicPreset}
6
+ */
7
+ export function getPreset(bank, program)
8
+ {
9
+ if (this.overrideSoundfont)
10
+ {
11
+ // if override soundfont
12
+ const bankWithOffset = bank === 128 ? 128 : bank - this.soundfontBankOffset;
13
+ const preset = this.overrideSoundfont.getPresetNoFallback(bankWithOffset, program);
14
+ if (preset)
15
+ {
16
+ return preset;
17
+ }
18
+ }
19
+ return this.soundfontManager.getPreset(bank, program);
20
+ }
@@ -0,0 +1,43 @@
1
+ import { loadSoundFont } from "../../../../soundfont/load_soundfont.js";
2
+ import { returnMessageType } from "../../message_protocol/worklet_message.js";
3
+ import { SpessaSynthInfo } from "../../../../utils/loggin.js";
4
+ import { consoleColors } from "../../../../utils/other.js";
5
+
6
+ /**
7
+ * @param buffer {ArrayBuffer}
8
+ * @param isOverride {Boolean}
9
+ * @this {SpessaSynthProcessor}
10
+ */
11
+ export function reloadSoundFont(buffer, isOverride = false)
12
+ {
13
+ this.clearSoundFont(false, isOverride);
14
+ try
15
+ {
16
+ if (isOverride)
17
+ {
18
+ this.overrideSoundfont = loadSoundFont(buffer);
19
+ // assign sample offset
20
+ this.overrideSoundfont.setSampleIDOffset(this.soundfontManager.totalSoundfontOffset);
21
+ }
22
+ else
23
+ {
24
+ this.soundfontManager.reloadManager(buffer);
25
+ }
26
+ }
27
+ catch (e)
28
+ {
29
+ this.post({
30
+ messageType: returnMessageType.soundfontError,
31
+ messageData: e
32
+ });
33
+ return;
34
+ }
35
+ this.defaultPreset = this.getPreset(0, 0);
36
+ this.drumPreset = this.getPreset(128, 0);
37
+ this.workletProcessorChannels.forEach(c =>
38
+ c.programChange(c.preset.program)
39
+ );
40
+ this.post({ messageType: returnMessageType.ready, messageData: undefined });
41
+ this.sendPresetList();
42
+ SpessaSynthInfo("%cSpessaSynth is ready!", consoleColors.recognized);
43
+ }
@@ -0,0 +1,31 @@
1
+ /**
2
+ * @this {SpessaSynthProcessor}
3
+ */
4
+ export function sendPresetList()
5
+ {
6
+ /**
7
+ * @type {{bank: number, presetName: string, program: number}[]}
8
+ */
9
+ const mainFont = this.soundfontManager.getPresetList();
10
+ if (this.overrideSoundfont !== undefined)
11
+ {
12
+ this.overrideSoundfont.presets.forEach(p =>
13
+ {
14
+ const bankCheck = p.bank === 128 ? 128 : p.bank + this.soundfontBankOffset;
15
+ const exists = mainFont.find(pr => pr.bank === bankCheck && pr.program === p.program);
16
+ if (exists !== undefined)
17
+ {
18
+ exists.presetName = p.presetName;
19
+ }
20
+ else
21
+ {
22
+ mainFont.push({
23
+ presetName: p.presetName,
24
+ bank: bankCheck,
25
+ program: p.program
26
+ });
27
+ }
28
+ });
29
+ }
30
+ this.callEvent("presetlistchange", mainFont);
31
+ }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Sets the embedded (RMI soundfont)
3
+ * @param font {ArrayBuffer}
4
+ * @param offset {number}
5
+ * @this {SpessaSynthProcessor}
6
+ */
7
+ export function setEmbeddedSoundFont(font, offset)
8
+ {
9
+ // set offset
10
+ this.soundfontBankOffset = offset;
11
+ this.reloadSoundFont(font, true);
12
+ // preload all samples
13
+ this.overrideSoundfont.samples.forEach(s => s.getAudioData());
14
+
15
+ // apply snapshot again if applicable
16
+ if (this._snapshot !== undefined)
17
+ {
18
+ this.applySynthesizerSnapshot(this._snapshot);
19
+ this.resetAllControllers();
20
+ }
21
+ }
@@ -0,0 +1,19 @@
1
+ import { generatorTypes } from "../../../../soundfont/basic_soundfont/generator.js";
2
+
3
+ /**
4
+ * Stops a note nearly instantly
5
+ * @param midiNote {number}
6
+ * @this {WorkletProcessorChannel}
7
+ */
8
+ export function killNote(midiNote)
9
+ {
10
+ this.voices.forEach(v =>
11
+ {
12
+ if (v.realKey !== midiNote)
13
+ {
14
+ return;
15
+ }
16
+ v.modulatedGenerators[generatorTypes.releaseVolEnv] = -12000; // set release to be very short
17
+ v.release();
18
+ });
19
+ }
@@ -0,0 +1,51 @@
1
+ import { SpessaSynthWarn } from "../../../../utils/loggin.js";
2
+
3
+ /**
4
+ * Release a note
5
+ * @param midiNote {number}
6
+ * @this {WorkletProcessorChannel}
7
+ */
8
+ export function noteOff(midiNote)
9
+ {
10
+ if (midiNote > 127 || midiNote < 0)
11
+ {
12
+ SpessaSynthWarn(`Received a noteOn for note`, midiNote, "Ignoring.");
13
+ return;
14
+ }
15
+
16
+ let realKey = midiNote + this.channelTransposeKeyShift;
17
+
18
+ // if high performance mode, kill notes instead of stopping them
19
+ if (this.synth.highPerformanceMode)
20
+ {
21
+ // if the channel is percussion channel, do not kill the notes
22
+ if (!this.drumChannel)
23
+ {
24
+ this.killNote(realKey);
25
+ return;
26
+ }
27
+ }
28
+
29
+ const channelVoices = this.voices;
30
+ channelVoices.forEach(v =>
31
+ {
32
+ if (v.realKey !== realKey || v.isInRelease === true)
33
+ {
34
+ return;
35
+ }
36
+ // if hold pedal, move to sustain
37
+ if (this.holdPedal)
38
+ {
39
+ this.sustainedVoices.push(v);
40
+ }
41
+ else
42
+ {
43
+ v.release();
44
+ }
45
+ });
46
+ this.synth.callEvent("noteoff", {
47
+ midiNote: midiNote,
48
+ channel: this.channelNumber
49
+ });
50
+ }
51
+
@@ -0,0 +1,16 @@
1
+ import { SpessaSynthInfo } from "../../../../utils/loggin.js";
2
+ import { consoleColors } from "../../../../utils/other.js";
3
+
4
+ /**
5
+ * @this {SpessaSynthProcessor}
6
+ * @param force {boolean}
7
+ */
8
+ export function stopAllChannels(force = false)
9
+ {
10
+ SpessaSynthInfo("%cStop all received!", consoleColors.info);
11
+ for (let i = 0; i < this.workletProcessorChannels.length; i++)
12
+ {
13
+ this.workletProcessorChannels[i].stopAllNotes(force);
14
+ }
15
+ this.callEvent("stopall", undefined);
16
+ }
@@ -0,0 +1,30 @@
1
+ /**
2
+ * stops all notes on a given channel
3
+ * @param force {boolean}
4
+ * @this {WorkletProcessorChannel}
5
+ */
6
+ export function stopAllNotes(force = false)
7
+ {
8
+ if (force)
9
+ {
10
+ // force stop all
11
+ this.voices.length = 0;
12
+ this.sustainedVoices.length = 0;
13
+ this.synth.sendChannelProperties();
14
+ }
15
+ else
16
+ {
17
+ this.voices.forEach(v =>
18
+ {
19
+ if (v.isInRelease)
20
+ {
21
+ return;
22
+ }
23
+ v.release();
24
+ });
25
+ this.sustainedVoices.forEach(v =>
26
+ {
27
+ v.release();
28
+ });
29
+ }
30
+ }
@@ -0,0 +1,63 @@
1
+ /**
2
+ * @param channel {WorkletProcessorChannel}
3
+ * @param voice {WorkletVoice}
4
+ * @return {number}
5
+ */
6
+ function getPriority(channel, voice)
7
+ {
8
+ let priority = 0;
9
+ if (channel.drumChannel)
10
+ {
11
+ // important
12
+ priority += 5;
13
+ }
14
+ if (voice.isInRelease)
15
+ {
16
+ // not important
17
+ priority -= 5;
18
+ }
19
+ // less velocity = less important
20
+ priority += voice.velocity / 25; // map to 0-5
21
+ // the newer, more important
22
+ priority -= voice.volumeEnvelope.state;
23
+ if (voice.isInRelease)
24
+ {
25
+ priority -= 5;
26
+ }
27
+ priority -= voice.volumeEnvelope.currentAttenuationDb / 50;
28
+ return priority;
29
+ }
30
+
31
+ /**
32
+ * @this {SpessaSynthProcessor}
33
+ * @param amount {number}
34
+ */
35
+ export function voiceKilling(amount)
36
+ {
37
+ let allVoices = [];
38
+ for (const channel of this.workletProcessorChannels)
39
+ {
40
+ for (const voice of channel.voices)
41
+ {
42
+ if (!voice.finished)
43
+ {
44
+ const priority = getPriority(channel, voice);
45
+ allVoices.push({ channel, voice, priority });
46
+ }
47
+ }
48
+ }
49
+
50
+ // Step 2: Sort voices by priority (ascending order)
51
+ allVoices.sort((a, b) => a.priority - b.priority);
52
+ const voicesToRemove = allVoices.slice(0, amount);
53
+
54
+ for (const { channel, voice } of voicesToRemove)
55
+ {
56
+ const index = channel.voices.indexOf(voice);
57
+ if (index > -1)
58
+ {
59
+ channel.voices.splice(index, 1);
60
+ }
61
+ }
62
+ }
63
+