spessasynth_core 3.26.4 → 3.26.6

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 (27) hide show
  1. package/package.json +1 -1
  2. package/src/midi/midi_tools/used_keys_loaded.js +26 -8
  3. package/src/sequencer/song_control.js +20 -25
  4. package/src/soundfont/basic_soundfont/basic_preset.js +1 -40
  5. package/src/soundfont/basic_soundfont/generator.js +9 -1
  6. package/src/synthetizer/audio_engine/engine_components/controller_tables.js +6 -5
  7. package/src/synthetizer/audio_engine/engine_components/dynamic_modulator_system.js +95 -0
  8. package/src/synthetizer/audio_engine/engine_components/midi_audio_channel.js +11 -11
  9. package/src/synthetizer/audio_engine/engine_components/soundfont_manager.js +45 -17
  10. package/src/synthetizer/audio_engine/engine_components/stereo_panner.js +2 -2
  11. package/src/synthetizer/audio_engine/engine_components/voice.js +51 -51
  12. package/src/synthetizer/audio_engine/engine_methods/controller_control/reset_controllers.js +3 -6
  13. package/src/synthetizer/audio_engine/engine_methods/data_entry/data_entry_coarse.js +20 -21
  14. package/src/synthetizer/audio_engine/engine_methods/note_on.js +28 -8
  15. package/src/synthetizer/audio_engine/engine_methods/program_change.js +4 -39
  16. package/src/synthetizer/audio_engine/engine_methods/render_voice.js +18 -11
  17. package/src/synthetizer/audio_engine/engine_methods/soundfont_management/embedded_sound_bank.js +43 -0
  18. package/src/synthetizer/audio_engine/engine_methods/soundfont_management/get_preset.js +0 -22
  19. package/src/synthetizer/audio_engine/engine_methods/soundfont_management/update_preset_list.js +5 -20
  20. package/src/synthetizer/audio_engine/engine_methods/stopping_notes/kill_note.js +3 -0
  21. package/src/synthetizer/audio_engine/engine_methods/stopping_notes/note_off.js +2 -1
  22. package/src/synthetizer/audio_engine/engine_methods/system_exclusive.js +442 -173
  23. package/src/synthetizer/audio_engine/engine_methods/tuning_control/channel_pressure.js +1 -0
  24. package/src/synthetizer/audio_engine/main_processor.js +27 -20
  25. package/src/synthetizer/synth_constants.js +3 -1
  26. package/src/synthetizer/audio_engine/engine_methods/soundfont_management/clear_sound_font.js +0 -32
  27. package/src/synthetizer/audio_engine/engine_methods/soundfont_management/set_embedded_sound_font.js +0 -33
@@ -7,9 +7,12 @@ import { SpessaSynthWarn } from "../../../utils/loggin.js";
7
7
  import { LowpassFilter } from "./lowpass_filter.js";
8
8
  import { VolumeEnvelope } from "./volume_envelope.js";
9
9
  import { ModulationEnvelope } from "./modulation_envelope.js";
10
- import { addAndClampGenerator, generatorTypes } from "../../../soundfont/basic_soundfont/generator.js";
10
+ import {
11
+ addAndClampGenerator,
12
+ GENERATORS_AMOUNT,
13
+ generatorTypes
14
+ } from "../../../soundfont/basic_soundfont/generator.js";
11
15
  import { Modulator } from "../../../soundfont/basic_soundfont/modulator.js";
12
- import { isSystemXG } from "../../../utils/xg_hacks.js";
13
16
 
14
17
  const EXCLUSIVE_CUTOFF_TIME = -2320;
15
18
  const EXCLUSIVE_MOD_CUTOFF_TIME = -1130; // less because filter shenanigans
@@ -162,12 +165,6 @@ class Voice
162
165
  */
163
166
  isInRelease = false;
164
167
 
165
- /**
166
- * MIDI channel number.
167
- * @type {number}
168
- */
169
- channelNumber = 0;
170
-
171
168
  /**
172
169
  * Velocity of the note.
173
170
  * @type {number}
@@ -272,7 +269,6 @@ class Voice
272
269
  * @param audioSample {AudioSample}
273
270
  * @param midiNote {number}
274
271
  * @param velocity {number}
275
- * @param channel {number}
276
272
  * @param currentTime {number}
277
273
  * @param targetKey {number}
278
274
  * @param realKey {number}
@@ -284,7 +280,6 @@ class Voice
284
280
  audioSample,
285
281
  midiNote,
286
282
  velocity,
287
- channel,
288
283
  currentTime,
289
284
  targetKey,
290
285
  realKey,
@@ -301,7 +296,6 @@ class Voice
301
296
 
302
297
  this.velocity = velocity;
303
298
  this.midiNote = midiNote;
304
- this.channelNumber = channel;
305
299
  this.startTime = currentTime;
306
300
  this.targetKey = targetKey;
307
301
  this.realKey = realKey;
@@ -333,7 +327,6 @@ class Voice
333
327
  sample,
334
328
  voice.midiNote,
335
329
  voice.velocity,
336
- voice.channelNumber,
337
330
  currentTime,
338
331
  voice.targetKey,
339
332
  realKey,
@@ -372,53 +365,21 @@ class Voice
372
365
  }
373
366
 
374
367
  /**
375
- * @param channel {number} a hint for the processor to recalculate sample cursors when sample dumping
368
+ * @param preset {BasicPreset} the preset to get voices for
369
+ * @param bank {number} the bank to cache the voices in
370
+ * @param program {number} program to cache the voices in
376
371
  * @param midiNote {number} the MIDI note to use
377
372
  * @param velocity {number} the velocity to use
378
373
  * @param realKey {number} the real MIDI note if the "midiNote" was changed by MIDI Tuning Standard
379
374
  * @this {SpessaSynthProcessor}
380
375
  * @returns {Voice[]} output is an array of Voices
381
376
  */
382
- export function getVoices(channel,
383
- midiNote,
384
- velocity,
385
- realKey)
377
+ export function getVoicesForPreset(preset, bank, program, midiNote, velocity, realKey)
386
378
  {
387
379
  /**
388
380
  * @type {Voice[]}
389
381
  */
390
- let voices;
391
- const channelObject = this.midiAudioChannels[channel];
392
-
393
- // override patch
394
- const overridePatch = this.keyModifierManager.hasOverridePatch(channel, midiNote);
395
-
396
- let bank = channelObject.getBankSelect();
397
- let program = channelObject.preset.program;
398
- if (overridePatch)
399
- {
400
- const override = this.keyModifierManager.getPatch(channel, midiNote);
401
- bank = override.bank;
402
- program = override.program;
403
- }
404
-
405
- const cached = this.getCachedVoice(bank, program, midiNote, velocity);
406
- // if cached, return it!
407
- if (cached !== undefined)
408
- {
409
- return cached.map(v => Voice.copy(v, this.currentSynthTime, realKey));
410
- }
411
-
412
- // not cached...
413
- let preset = channelObject.preset;
414
- if (overridePatch)
415
- {
416
- preset = this.soundfontManager.getPreset(bank, program, isSystemXG(this.system));
417
- }
418
- /**
419
- * @returns {Voice[]}
420
- */
421
- voices = preset.getSamplesAndGenerators(midiNote, velocity)
382
+ const voices = preset.getSamplesAndGenerators(midiNote, velocity)
422
383
  .reduce((voices, sampleAndGenerators) =>
423
384
  {
424
385
  if (sampleAndGenerators.sample.getAudioData() === undefined)
@@ -428,7 +389,7 @@ export function getVoices(channel,
428
389
  }
429
390
 
430
391
  // create the generator list
431
- const generators = new Int16Array(60);
392
+ const generators = new Int16Array(GENERATORS_AMOUNT);
432
393
  // apply and sum the gens
433
394
  for (let i = 0; i < 60; i++)
434
395
  {
@@ -501,7 +462,6 @@ export function getVoices(channel,
501
462
  audioSample,
502
463
  midiNote,
503
464
  velocity,
504
- channel,
505
465
  this.currentSynthTime,
506
466
  targetKey,
507
467
  realKey,
@@ -515,4 +475,44 @@ export function getVoices(channel,
515
475
  this.setCachedVoice(bank, program, midiNote, velocity, voices.map(v =>
516
476
  Voice.copy(v, this.currentSynthTime, realKey)));
517
477
  return voices;
478
+ }
479
+
480
+ /**
481
+ * @param channel {number} a hint for the processor to recalculate sample cursors when sample dumping
482
+ * @param midiNote {number} the MIDI note to use
483
+ * @param velocity {number} the velocity to use
484
+ * @param realKey {number} the real MIDI note if the "midiNote" was changed by MIDI Tuning Standard
485
+ * @this {SpessaSynthProcessor}
486
+ * @returns {Voice[]} output is an array of Voices
487
+ */
488
+ export function getVoices(channel, midiNote, velocity, realKey)
489
+ {
490
+ const channelObject = this.midiAudioChannels[channel];
491
+
492
+ // override patch
493
+ const overridePatch = this.keyModifierManager.hasOverridePatch(channel, midiNote);
494
+
495
+ let bank = channelObject.getBankSelect();
496
+ let program = channelObject.preset.program;
497
+ if (overridePatch)
498
+ {
499
+ const override = this.keyModifierManager.getPatch(channel, midiNote);
500
+ bank = override.bank;
501
+ program = override.program;
502
+ }
503
+
504
+ const cached = this.getCachedVoice(bank, program, midiNote, velocity);
505
+ // if cached, return it!
506
+ if (cached !== undefined)
507
+ {
508
+ return cached.map(v => Voice.copy(v, this.currentSynthTime, realKey));
509
+ }
510
+
511
+ // not cached...
512
+ let preset = channelObject.preset;
513
+ if (overridePatch)
514
+ {
515
+ preset = this.getPreset(bank, program);
516
+ }
517
+ return this.getVoicesForPreset(preset, bank, program, midiNote, velocity, realKey);
518
518
  }
@@ -43,7 +43,6 @@ export function resetAllControllers(log = true)
43
43
  if (channelNumber % 16 === DEFAULT_PERCUSSION)
44
44
  {
45
45
  ch.setPreset(this.drumPreset);
46
- ch.presetUsesOverride = this.defaultDrumsUsesOverride;
47
46
  ch.drumChannel = true;
48
47
  this.callEvent("drumchange", {
49
48
  channel: channelNumber,
@@ -53,7 +52,6 @@ export function resetAllControllers(log = true)
53
52
  else
54
53
  {
55
54
  ch.drumChannel = false;
56
- ch.presetUsesOverride = this.defaultDrumsUsesOverride;
57
55
  ch.setPreset(this.defaultPreset);
58
56
  this.callEvent("drumchange", {
59
57
  channel: channelNumber,
@@ -70,13 +68,11 @@ export function resetAllControllers(log = true)
70
68
  }
71
69
 
72
70
  const presetBank = ch.preset.bank;
73
- const sentBank = presetBank === 128 ? 128 : (ch.presetUsesOverride ? presetBank + this.soundfontBankOffset : presetBank);
74
-
75
71
  // call program change
76
72
  this.callEvent("programchange", {
77
73
  channel: channelNumber,
78
74
  program: ch.preset.program,
79
- bank: sentBank
75
+ bank: presetBank
80
76
  });
81
77
 
82
78
  for (let ccNum = 0; ccNum < 128; ccNum++)
@@ -154,12 +150,13 @@ export function resetControllers()
154
150
  this.holdPedal = false;
155
151
  this.randomPan = false;
156
152
 
153
+ this.sysExModulators.resetModulators();
154
+
157
155
  // reset custom controllers
158
156
  // special case: transpose does not get affected
159
157
  const transpose = this.customControllers[customControllers.channelTransposeFine];
160
158
  this.customControllers.set(customResetArray);
161
159
  this.setCustomController(customControllers.channelTransposeFine, transpose);
162
-
163
160
  this.resetParameters();
164
161
 
165
162
  }
@@ -16,23 +16,29 @@ const registeredParameterTypes = {
16
16
  resetParameters: 0x3FFF
17
17
  };
18
18
 
19
+ /**
20
+ * @enum {number}
21
+ */
22
+ const nonRegisteredGSMSB = {
23
+ partParameter: 0x01
24
+ };
25
+
19
26
  /**
20
27
  * https://cdn.roland.com/assets/media/pdf/SC-88PRO_OM.pdf
21
28
  * http://hummer.stanford.edu/sig/doc/classes/MidiOutput/rpn.html
22
29
  * @enum {number}
23
30
  */
24
- const nonRegisteredParameterNumbers = {
25
- partParameter: 0x01,
26
-
31
+ const nonRegisteredGSLSB = {
27
32
  vibratoRate: 0x08,
28
33
  vibratoDepth: 0x09,
29
34
  vibratoDelay: 0x0A,
30
35
 
31
- EGAttackTime: 0x64,
32
- EGReleaseTime: 0x66,
33
-
34
36
  TVFFilterCutoff: 0x20,
35
- drumReverb: 0x1D
37
+ TVFFilterResonance: 0x21,
38
+
39
+ EGAttackTime: 0x63,
40
+ EGReleaseTime: 0x66
41
+
36
42
  };
37
43
 
38
44
 
@@ -111,7 +117,7 @@ export function dataEntryCoarse(dataValue)
111
117
  break;
112
118
 
113
119
  // part parameters: vibrato, cutoff
114
- case nonRegisteredParameterNumbers.partParameter:
120
+ case nonRegisteredGSMSB.partParameter:
115
121
  switch (NRPNFine)
116
122
  {
117
123
  default:
@@ -133,7 +139,7 @@ export function dataEntryCoarse(dataValue)
133
139
  break;
134
140
 
135
141
  // vibrato rate
136
- case nonRegisteredParameterNumbers.vibratoRate:
142
+ case nonRegisteredGSLSB.vibratoRate:
137
143
  if (dataValue === 64)
138
144
  {
139
145
  return;
@@ -144,7 +150,7 @@ export function dataEntryCoarse(dataValue)
144
150
  break;
145
151
 
146
152
  // vibrato depth
147
- case nonRegisteredParameterNumbers.vibratoDepth:
153
+ case nonRegisteredGSLSB.vibratoDepth:
148
154
  if (dataValue === 64)
149
155
  {
150
156
  return;
@@ -155,7 +161,7 @@ export function dataEntryCoarse(dataValue)
155
161
  break;
156
162
 
157
163
  // vibrato delay
158
- case nonRegisteredParameterNumbers.vibratoDelay:
164
+ case nonRegisteredGSLSB.vibratoDelay:
159
165
  if (dataValue === 64)
160
166
  {
161
167
  return;
@@ -166,34 +172,27 @@ export function dataEntryCoarse(dataValue)
166
172
  break;
167
173
 
168
174
  // filter cutoff
169
- case nonRegisteredParameterNumbers.TVFFilterCutoff:
175
+ case nonRegisteredGSLSB.TVFFilterCutoff:
170
176
  // affect the "brightness" controller as we have a default modulator that controls it
171
177
  this.controllerChange(midiControllers.brightness, dataValue);
172
178
  coolInfo("Filter cutoff", dataValue.toString(), "");
173
179
  break;
174
180
 
175
181
  // attack time
176
- case nonRegisteredParameterNumbers.EGAttackTime:
182
+ case nonRegisteredGSLSB.EGAttackTime:
177
183
  // affect the "attack time" controller as we have a default modulator that controls it
178
184
  this.controllerChange(midiControllers.attackTime, dataValue);
179
185
  coolInfo("EG attack time", dataValue.toString(), "");
180
186
  break;
181
187
 
182
188
  // release time
183
- case nonRegisteredParameterNumbers.EGReleaseTime:
189
+ case nonRegisteredGSLSB.EGReleaseTime:
184
190
  // affect the "release time" controller as we have a default modulator that controls it
185
191
  this.controllerChange(midiControllers.releaseTime, dataValue);
186
192
  coolInfo("EG release time", dataValue.toString(), "");
187
193
  break;
188
194
  }
189
195
  break;
190
-
191
- // drum reverb
192
- case nonRegisteredParameterNumbers.drumReverb:
193
- const reverb = dataValue;
194
- this.controllerChange(midiControllers.reverbDepth, reverb);
195
- coolInfo("GS Drum reverb", reverb.toString(), "percent");
196
- break;
197
196
  }
198
197
  break;
199
198
 
@@ -2,6 +2,8 @@ import { computeModulators } from "../engine_components/compute_modulator.js";
2
2
  import { generatorTypes } from "../../../soundfont/basic_soundfont/generator.js";
3
3
  import { midiControllers } from "../../../midi/midi_message.js";
4
4
  import { portamentoTimeToSeconds } from "./portamento_time.js";
5
+ import { customControllers } from "../engine_components/controller_tables.js";
6
+ import { Modulator } from "../../../soundfont/basic_soundfont/modulator.js";
5
7
 
6
8
  /**
7
9
  * sends a "MIDI Note on message"
@@ -27,8 +29,8 @@ export function noteOn(midiNote, velocity)
27
29
  return;
28
30
  }
29
31
 
30
- const realKey = midiNote + this.channelTransposeKeyShift;
31
- let sentMidiNote = realKey;
32
+ const realKey = midiNote + this.channelTransposeKeyShift + this.customControllers[customControllers.channelKeyShift];
33
+ let internalMidiNote = realKey;
32
34
 
33
35
  if (realKey > 127 || realKey < 0)
34
36
  {
@@ -38,7 +40,7 @@ export function noteOn(midiNote, velocity)
38
40
  const tune = this.synth.tunings[program]?.[realKey]?.midiNote;
39
41
  if (tune >= 0)
40
42
  {
41
- sentMidiNote = tune;
43
+ internalMidiNote = tune;
42
44
  }
43
45
 
44
46
  // velocity override
@@ -66,7 +68,7 @@ export function noteOn(midiNote, velocity)
66
68
  const currentFromKey = control >> 7;
67
69
  if (
68
70
  !this.drumChannel && // no portamento on drum channel
69
- currentFromKey !== sentMidiNote && // if the same note, there's no portamento
71
+ currentFromKey !== internalMidiNote && // if the same note, there's no portamento
70
72
  this.midiControllers[midiControllers.portamentoOnOff] >= 8192 && // (64 << 7)
71
73
  portamentoTime > 0 // 0 duration is no portamento
72
74
  )
@@ -74,18 +76,17 @@ export function noteOn(midiNote, velocity)
74
76
  // a value of one means the initial portamento
75
77
  if (control !== 1)
76
78
  {
77
- const diff = Math.abs(sentMidiNote - currentFromKey);
79
+ const diff = Math.abs(internalMidiNote - currentFromKey);
78
80
  portamentoDuration = portamentoTimeToSeconds(portamentoTime, diff);
79
81
  portamentoFromKey = currentFromKey;
80
82
  }
81
83
  // set portamento control to previous value
82
- this.controllerChange(midiControllers.portamentoControl, sentMidiNote);
84
+ this.controllerChange(midiControllers.portamentoControl, internalMidiNote);
83
85
  }
84
-
85
86
  // get voices
86
87
  const voices = this.synth.getVoices(
87
88
  this.channelNumber,
88
- sentMidiNote,
89
+ internalMidiNote,
89
90
  velocity,
90
91
  realKey
91
92
  );
@@ -98,6 +99,9 @@ export function noteOn(midiNote, velocity)
98
99
  panOverride = Math.round(Math.random() * 1000 - 500);
99
100
  }
100
101
 
102
+ // dynamic modulators (sysEx)
103
+ const dynamicModulators = this.sysExModulators.getModulators();
104
+
101
105
  // add voices
102
106
  const channelVoices = this.voices;
103
107
  voices.forEach(voice =>
@@ -112,6 +116,22 @@ export function noteOn(midiNote, velocity)
112
116
  // apply gain override
113
117
  voice.gain = voiceGain;
114
118
 
119
+ dynamicModulators.forEach(mod =>
120
+ {
121
+ const existingModIndex = voice.modulators.findIndex(voiceMod => Modulator.isIdentical(voiceMod, mod));
122
+
123
+ // replace or add
124
+ if (existingModIndex !== -1)
125
+ {
126
+ voice.modulators[existingModIndex] = Modulator.copy(mod);
127
+ }
128
+ else
129
+ {
130
+ voice.modulators.push(Modulator.copy(mod));
131
+ }
132
+ });
133
+
134
+
115
135
  // apply exclusive class
116
136
  const exclusive = voice.exclusiveClass;
117
137
  if (exclusive !== 0)
@@ -11,51 +11,16 @@ export function programChange(programNumber)
11
11
  }
12
12
  // always 128 for percussion
13
13
  let bank = this.getBankSelect();
14
- let sentBank;
15
- /**
16
- * @type {BasicPreset}
17
- */
18
- let preset;
19
14
 
20
15
  const isXG = this.isXGChannel;
21
- // check if override
22
- if (this.synth.overrideSoundfont)
23
- {
24
- const bankWithOffset = bank === 128 ? 128 : bank - this.synth.soundfontBankOffset;
25
- const p = this.synth.overrideSoundfont.getPresetNoFallback(
26
- bankWithOffset,
27
- programNumber,
28
- isXG
29
- );
30
- if (p)
31
- {
32
- sentBank = p.bank === 128 ? 128 : p.bank + this.synth.soundfontBankOffset;
33
- preset = p;
34
- this.presetUsesOverride = true;
35
- }
36
- else
37
- {
38
- preset = this.synth.soundfontManager.getPreset(bank, programNumber, isXG);
39
- const offset = this.synth.soundfontManager.soundfontList
40
- .find(s => s.soundfont === preset.parentSoundBank).bankOffset;
41
- sentBank = preset.bank - offset;
42
- this.presetUsesOverride = false;
43
- }
44
- }
45
- else
46
- {
47
- preset = this.synth.soundfontManager.getPreset(bank, programNumber, isXG);
48
- const offset = this.synth.soundfontManager.soundfontList
49
- .find(s => s.soundfont === preset.parentSoundBank).bankOffset;
50
- sentBank = preset.bank - offset;
51
- this.presetUsesOverride = false;
52
- }
16
+ const p = this.synth.soundfontManager.getPreset(bank, programNumber, isXG);
17
+ const preset = p.preset;
53
18
  this.setPreset(preset);
54
- this.sentBank = sentBank;
19
+ this.sentBank = Math.min(128, preset.bank + p.bankOffset);
55
20
  this.synth.callEvent("programchange", {
56
21
  channel: this.channelNumber,
57
22
  program: preset.program,
58
- bank: sentBank
23
+ bank: this.sentBank
59
24
  });
60
25
  this.sendChannelProperty();
61
26
  }
@@ -88,26 +88,33 @@ export function renderVoice(
88
88
  // calculate tuning by key using soundfont's scale tuning
89
89
  cents += (targetKey - voice.sample.rootKey) * voice.modulatedGenerators[generatorTypes.scaleTuning];
90
90
 
91
+ // low pass excursion with LFO and mod envelope
92
+ let lowpassExcursion = 0;
93
+ let volumeExcursionCentibels = 0;
94
+
91
95
  // vibrato LFO
92
- const vibratoDepth = voice.modulatedGenerators[generatorTypes.vibLfoToPitch];
93
- if (vibratoDepth !== 0)
96
+ const vibPitchDepth = voice.modulatedGenerators[generatorTypes.vibLfoToPitch];
97
+ const vibVolDepth = voice.modulatedGenerators[generatorTypes.vibLfoToVolume];
98
+ const vibFilterDepth = voice.modulatedGenerators[generatorTypes.vibLfoToFilterFc];
99
+ if (vibPitchDepth !== 0 || vibVolDepth !== 0 || vibFilterDepth !== 0)
94
100
  {
95
101
  // calculate start time and lfo value
96
102
  const vibStart = voice.startTime + timecentsToSeconds(voice.modulatedGenerators[generatorTypes.delayVibLFO]);
97
103
  const vibFreqHz = absCentsToHz(voice.modulatedGenerators[generatorTypes.freqVibLFO]);
98
- const lfoVal = getLFOValue(vibStart, vibFreqHz, timeNow);
104
+ const vibLfoValue = getLFOValue(vibStart, vibFreqHz, timeNow);
99
105
  // use modulation multiplier (RPN modulation depth)
100
- cents += lfoVal * (vibratoDepth * this.customControllers[customControllers.modulationMultiplier]);
106
+ cents += vibLfoValue * (vibPitchDepth * this.customControllers[customControllers.modulationMultiplier]);
107
+ // vol env volume offset
108
+ // negate the lfo value because audigy starts with increase rather than decrease
109
+ volumeExcursionCentibels += -vibLfoValue * vibVolDepth;
110
+ // low pass frequency
111
+ lowpassExcursion += vibLfoValue * vibFilterDepth;
101
112
  }
102
113
 
103
- // low pass excursion with LFO and mod envelope
104
- let lowpassExcursion = 0;
105
-
106
114
  // mod LFO
107
115
  const modPitchDepth = voice.modulatedGenerators[generatorTypes.modLfoToPitch];
108
116
  const modVolDepth = voice.modulatedGenerators[generatorTypes.modLfoToVolume];
109
117
  const modFilterDepth = voice.modulatedGenerators[generatorTypes.modLfoToFilterFc];
110
- let modLfoCentibels = 0;
111
118
  // don't compute mod lfo unless necessary
112
119
  if (modPitchDepth !== 0 || modFilterDepth !== 0 || modVolDepth !== 0)
113
120
  {
@@ -117,9 +124,9 @@ export function renderVoice(
117
124
  const modLfoValue = getLFOValue(modStart, modFreqHz, timeNow);
118
125
  // use modulation multiplier (RPN modulation depth)
119
126
  cents += modLfoValue * (modPitchDepth * this.customControllers[customControllers.modulationMultiplier]);
120
- // vole nv volume offset
127
+ // vol env volume offset
121
128
  // negate the lfo value because audigy starts with increase rather than decrease
122
- modLfoCentibels = -modLfoValue * modVolDepth;
129
+ volumeExcursionCentibels += -modLfoValue * modVolDepth;
123
130
  // low pass frequency
124
131
  lowpassExcursion += modLfoValue * modFilterDepth;
125
132
  }
@@ -184,7 +191,7 @@ export function renderVoice(
184
191
  LowpassFilter.apply(voice, bufferOut, lowpassExcursion, this.synth.filterSmoothingFactor);
185
192
 
186
193
  // vol env
187
- VolumeEnvelope.apply(voice, bufferOut, modLfoCentibels, this.synth.volumeEnvelopeSmoothingFactor);
194
+ VolumeEnvelope.apply(voice, bufferOut, volumeExcursionCentibels, this.synth.volumeEnvelopeSmoothingFactor);
188
195
 
189
196
  this.panVoice(
190
197
  voice,
@@ -0,0 +1,43 @@
1
+ import { loadSoundFont } from "../../../../soundfont/load_soundfont.js";
2
+ import { SpessaSynthInfo } from "../../../../utils/loggin.js";
3
+ import { consoleColors } from "../../../../utils/other.js";
4
+ import { EMBEDDED_SOUND_BANK_ID } from "../../../synth_constants.js";
5
+
6
+ /**
7
+ * @this {SpessaSynthProcessor}
8
+ */
9
+ export function clearEmbeddedBank()
10
+ {
11
+ if (this.soundfontManager.soundfontList.some(s => s.id === EMBEDDED_SOUND_BANK_ID))
12
+ {
13
+ this.soundfontManager.deleteSoundFont(EMBEDDED_SOUND_BANK_ID);
14
+ }
15
+ }
16
+
17
+ /**
18
+ * Sets the embedded (RMI soundfont)
19
+ * @param font {ArrayBuffer}
20
+ * @param offset {number}
21
+ * @this {SpessaSynthProcessor}
22
+ */
23
+ export function setEmbeddedSoundFont(font, offset)
24
+ {
25
+ // the embedded bank is set as the first bank in the manager,
26
+ // with a special ID that does not clear when reloadManager is performed.
27
+ this.soundfontBankOffset = offset;
28
+ const loadedFont = loadSoundFont(font);
29
+ this.soundfontManager.addNewSoundFont(loadedFont, EMBEDDED_SOUND_BANK_ID, offset);
30
+ // rearrange so the embedded is first (most important as it overrides all others)
31
+ const order = this.soundfontManager.getCurrentSoundFontOrder();
32
+ order.pop();
33
+ order.unshift(EMBEDDED_SOUND_BANK_ID);
34
+ this.soundfontManager.rearrangeSoundFonts(order);
35
+
36
+
37
+ // apply snapshot again if applicable
38
+ if (this._snapshot !== undefined)
39
+ {
40
+ this.applySynthesizerSnapshot(this._snapshot);
41
+ }
42
+ SpessaSynthInfo(`%cEmbedded sound bank set at offset %c${offset}`, consoleColors.recognized, consoleColors.value);
43
+ }
@@ -1,22 +0,0 @@
1
- import { isSystemXG } from "../../../../utils/xg_hacks.js";
2
-
3
- /**
4
- * @this {SpessaSynthProcessor}
5
- * @param program {number}
6
- * @param bank {number}
7
- * @returns {BasicPreset}
8
- */
9
- export function getPreset(bank, program)
10
- {
11
- if (this.overrideSoundfont)
12
- {
13
- // if override soundfont
14
- const bankWithOffset = bank === 128 ? 128 : bank - this.soundfontBankOffset;
15
- const preset = this.overrideSoundfont.getPresetNoFallback(bankWithOffset, program, isSystemXG(this.system));
16
- if (preset)
17
- {
18
- return preset;
19
- }
20
- }
21
- return this.soundfontManager.getPreset(bank, program, isSystemXG(this.system));
22
- }
@@ -7,28 +7,13 @@ export function updatePresetList()
7
7
  * @type {{bank: number, presetName: string, program: number}[]}
8
8
  */
9
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
10
  this.clearCache();
31
11
  this.callEvent("presetlistchange", mainFont);
32
12
  this.getDefaultPresets();
13
+ // unlock presets
14
+ this.midiAudioChannels.forEach(c =>
15
+ {
16
+ c.setPresetLock(false);
17
+ });
33
18
  this.resetAllControllers(false);
34
19
  }
@@ -1,4 +1,5 @@
1
1
  import { generatorTypes } from "../../../../soundfont/basic_soundfont/generator.js";
2
+ import { customControllers } from "../../engine_components/controller_tables.js";
2
3
 
3
4
  /**
4
5
  * Stops a note nearly instantly
@@ -8,6 +9,8 @@ import { generatorTypes } from "../../../../soundfont/basic_soundfont/generator.
8
9
  */
9
10
  export function killNote(midiNote, releaseTime = -12000)
10
11
  {
12
+ midiNote += this.customControllers[customControllers.channelKeyShift];
13
+
11
14
  this.voices.forEach(v =>
12
15
  {
13
16
  if (v.realKey !== midiNote)
@@ -1,4 +1,5 @@
1
1
  import { SpessaSynthWarn } from "../../../../utils/loggin.js";
2
+ import { customControllers } from "../../engine_components/controller_tables.js";
2
3
 
3
4
  /**
4
5
  * Release a note
@@ -13,7 +14,7 @@ export function noteOff(midiNote)
13
14
  return;
14
15
  }
15
16
 
16
- let realKey = midiNote + this.channelTransposeKeyShift;
17
+ let realKey = midiNote + this.channelTransposeKeyShift + this.customControllers[customControllers.channelKeyShift];
17
18
 
18
19
  // if high performance mode, kill notes instead of stopping them
19
20
  if (this.synth.highPerformanceMode)