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.
- package/midi_parser/basic_midi.js +457 -68
- package/midi_parser/midi_loader.js +18 -503
- package/midi_parser/midi_message.js +18 -5
- package/midi_parser/midi_sequence.js +2 -2
- package/package.json +1 -1
- package/sequencer/worklet_sequencer/process_event.js +1 -6
- package/synthetizer/synthetizer.js +9 -6
- package/synthetizer/worklet_processor.min.js +12 -12
- package/synthetizer/worklet_system/README.md +2 -2
- package/synthetizer/worklet_system/main_processor.js +106 -95
- package/synthetizer/worklet_system/message_protocol/handle_message.js +22 -17
- package/synthetizer/worklet_system/message_protocol/worklet_message.js +2 -1
- package/synthetizer/worklet_system/snapshot/apply_synthesizer_snapshot.js +14 -0
- package/synthetizer/worklet_system/snapshot/channel_snapshot.js +166 -0
- package/synthetizer/worklet_system/snapshot/send_synthesizer_snapshot.js +14 -0
- package/synthetizer/worklet_system/snapshot/synthesizer_snapshot.js +121 -0
- package/synthetizer/worklet_system/worklet_methods/controller_control/controller_change.js +196 -0
- package/synthetizer/worklet_system/worklet_methods/controller_control/master_parameters.js +34 -0
- package/synthetizer/worklet_system/worklet_methods/{reset_controllers.js → controller_control/reset_controllers.js} +33 -39
- package/synthetizer/worklet_system/worklet_methods/create_worklet_channel.js +26 -0
- package/synthetizer/worklet_system/worklet_methods/{data_entry.js → data_entry/data_entry_coarse.js} +38 -105
- package/synthetizer/worklet_system/worklet_methods/data_entry/data_entry_fine.js +64 -0
- package/synthetizer/worklet_system/worklet_methods/mute_channel.js +17 -0
- package/synthetizer/worklet_system/worklet_methods/note_on.js +36 -34
- package/synthetizer/worklet_system/worklet_methods/program_change.js +49 -0
- package/synthetizer/worklet_system/worklet_methods/{voice_control.js → render_voice.js} +37 -120
- package/synthetizer/worklet_system/worklet_methods/soundfont_management/clear_sound_font.js +35 -0
- package/synthetizer/worklet_system/worklet_methods/soundfont_management/get_preset.js +20 -0
- package/synthetizer/worklet_system/worklet_methods/soundfont_management/reload_sound_font.js +43 -0
- package/synthetizer/worklet_system/worklet_methods/soundfont_management/send_preset_list.js +31 -0
- package/synthetizer/worklet_system/worklet_methods/soundfont_management/set_embedded_sound_font.js +21 -0
- package/synthetizer/worklet_system/worklet_methods/stopping_notes/kill_note.js +19 -0
- package/synthetizer/worklet_system/worklet_methods/stopping_notes/note_off.js +51 -0
- package/synthetizer/worklet_system/worklet_methods/stopping_notes/stop_all_channels.js +16 -0
- package/synthetizer/worklet_system/worklet_methods/stopping_notes/stop_all_notes.js +30 -0
- package/synthetizer/worklet_system/worklet_methods/stopping_notes/voice_killing.js +63 -0
- package/synthetizer/worklet_system/worklet_methods/system_exclusive.js +31 -30
- package/synthetizer/worklet_system/worklet_methods/tuning_control/channel_pressure.js +24 -0
- package/synthetizer/worklet_system/worklet_methods/tuning_control/pitch_wheel.js +33 -0
- package/synthetizer/worklet_system/worklet_methods/tuning_control/poly_pressure.js +31 -0
- package/synthetizer/worklet_system/worklet_methods/tuning_control/set_master_tuning.js +15 -0
- package/synthetizer/worklet_system/worklet_methods/tuning_control/set_modulation_depth.js +27 -0
- package/synthetizer/worklet_system/worklet_methods/tuning_control/set_octave_tuning.js +15 -0
- package/synthetizer/worklet_system/worklet_methods/tuning_control/set_tuning.js +24 -0
- package/synthetizer/worklet_system/worklet_methods/tuning_control/set_tuning_semitones.js +19 -0
- package/synthetizer/worklet_system/worklet_methods/tuning_control/transpose_all_channels.js +15 -0
- package/synthetizer/worklet_system/worklet_methods/tuning_control/transpose_channel.js +31 -0
- package/synthetizer/worklet_system/worklet_utilities/controller_tables.js +10 -1
- package/synthetizer/worklet_system/worklet_utilities/lfo.js +2 -1
- package/synthetizer/worklet_system/worklet_utilities/modulation_envelope.js +4 -4
- package/synthetizer/worklet_system/worklet_utilities/modulator_curves.js +4 -5
- package/synthetizer/worklet_system/worklet_utilities/stereo_panner.js +18 -18
- package/synthetizer/worklet_system/worklet_utilities/wavetable_oscillator.js +210 -206
- package/synthetizer/worklet_system/worklet_utilities/worklet_processor_channel.js +354 -108
- package/synthetizer/worklet_system/worklet_utilities/worklet_voice.js +22 -9
- package/synthetizer/worklet_system/snapshot/snapshot.js +0 -311
- package/synthetizer/worklet_system/worklet_methods/controller_control.js +0 -260
- package/synthetizer/worklet_system/worklet_methods/note_off.js +0 -119
- package/synthetizer/worklet_system/worklet_methods/program_control.js +0 -282
- package/synthetizer/worklet_system/worklet_methods/tuning_control.js +0 -233
- 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
|
|
23
|
-
* @param
|
|
24
|
-
* @
|
|
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
|
-
|
|
31
|
-
|
|
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
|
-
+
|
|
68
|
-
+
|
|
69
|
-
+
|
|
70
|
-
+
|
|
71
|
-
+
|
|
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
|
-
+
|
|
68
|
+
+ this.customControllers[customControllers.channelTuningSemitones]; // RPN channel coarse tuning
|
|
74
69
|
|
|
75
70
|
// midi tuning standard
|
|
76
|
-
const tuning = this.tunings[
|
|
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 *
|
|
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 *
|
|
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 (
|
|
130
|
+
if (this.channelVibrato.depth > 0)
|
|
136
131
|
{
|
|
137
132
|
// same as others
|
|
138
133
|
const channelVibrato = getLFOValue(
|
|
139
|
-
voice.startTime +
|
|
140
|
-
|
|
134
|
+
voice.startTime + this.channelVibrato.delay,
|
|
135
|
+
this.channelVibrato.rate,
|
|
141
136
|
currentTime
|
|
142
137
|
);
|
|
143
138
|
if (channelVibrato)
|
|
144
139
|
{
|
|
145
|
-
cents += channelVibrato *
|
|
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
|
-
|
|
200
|
-
|
|
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
|
+
}
|
package/synthetizer/worklet_system/worklet_methods/soundfont_management/set_embedded_sound_font.js
ADDED
|
@@ -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
|
+
|