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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "spessasynth_core",
3
- "version": "3.26.4",
3
+ "version": "3.26.6",
4
4
  "description": "MIDI and SoundFont2/DLS library with no compromises",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -4,11 +4,12 @@ import { messageTypes, midiControllers } from "../midi_message.js";
4
4
  import { DEFAULT_PERCUSSION } from "../../synthetizer/synth_constants.js";
5
5
  import { chooseBank, isSystemXG, parseBankSelect } from "../../utils/xg_hacks.js";
6
6
  import { isGSDrumsOn, isXGOn } from "../../utils/sysex_detector.js";
7
+ import { SoundFontManager } from "../../synthetizer/audio_engine/engine_components/soundfont_manager.js";
7
8
 
8
9
  /**
9
10
  * Gets the used programs and keys for this MIDI file with a given sound bank
10
11
  * @this {BasicMIDI}
11
- * @param soundfont {BasicSoundBank|SoundFontManager} - the sound bank
12
+ * @param soundfont {SoundFontManager|BasicSoundBank} - the sound bank
12
13
  * @returns {Object<string, Set<string>>} Object<bank:program, Set<key-velocity>>
13
14
  */
14
15
  export function getUsedProgramsAndKeys(soundfont)
@@ -44,9 +45,27 @@ export function getUsedProgramsAndKeys(soundfont)
44
45
  {
45
46
  const bank = chooseBank(ch.bank, ch.bankLSB, ch.drums, isSystemXG(system));
46
47
  // check if this exists in the soundfont
47
- let exists = soundfont.getPreset(bank, ch.program, isSystemXG(system));
48
- ch.actualBank = exists.bank;
49
- ch.program = exists.program;
48
+ let existsBank, existsProgram;
49
+ if (soundfont instanceof SoundFontManager)
50
+ {
51
+ /**
52
+ * @type {{preset: BasicPreset, bankOffset: number}}
53
+ */
54
+ let exists = soundfont.getPreset(bank, ch.program, isSystemXG(system));
55
+ existsBank = exists.preset.bank + exists.bankOffset;
56
+ existsProgram = exists.preset.program;
57
+ }
58
+ else
59
+ {
60
+ /**
61
+ * @type {BasicPreset}
62
+ */
63
+ let exists = soundfont.getPreset(bank, ch.program, isSystemXG(system));
64
+ existsBank = exists.bank;
65
+ existsProgram = exists.program;
66
+ }
67
+ ch.actualBank = existsBank;
68
+ ch.program = existsProgram;
50
69
  ch.string = ch.actualBank + ":" + ch.program;
51
70
  if (!usedProgramsAndKeys[ch.string])
52
71
  {
@@ -147,19 +166,18 @@ export function getUsedProgramsAndKeys(soundfont)
147
166
  continue;
148
167
  }
149
168
  const bank = event.messageData[1];
150
- const realBank = Math.max(0, bank - mid.bankOffset);
151
169
  if (isLSB)
152
170
  {
153
- ch.bankLSB = realBank;
171
+ ch.bankLSB = bank;
154
172
  }
155
173
  else
156
174
  {
157
- ch.bank = realBank;
175
+ ch.bank = bank;
158
176
  }
159
177
  // interpret the bank
160
178
  const intepretation = parseBankSelect(
161
179
  ch.bank,
162
- realBank,
180
+ bank,
163
181
  system,
164
182
  isLSB,
165
183
  ch.drums,
@@ -57,41 +57,36 @@ export function loadNewSequence(parsedMidi, autoPlay = true)
57
57
  */
58
58
  this.midiData = parsedMidi;
59
59
 
60
+ // clear old embedded bank if exists
61
+ this.synth.clearEmbeddedBank();
62
+
60
63
  // check for embedded soundfont
61
64
  if (this.midiData.embeddedSoundFont !== undefined)
62
65
  {
63
66
  SpessaSynthInfo("%cEmbedded soundfont detected! Using it.", consoleColors.recognized);
64
67
  this.synth.setEmbeddedSoundFont(this.midiData.embeddedSoundFont, this.midiData.bankOffset);
65
68
  }
66
- else
69
+
70
+ SpessaSynthGroupCollapsed("%cPreloading samples...", consoleColors.info);
71
+ // smart preloading: load only samples used in the midi!
72
+ const used = this.midiData.getUsedProgramsAndKeys(this.synth.soundfontManager);
73
+ for (const [programBank, combos] of Object.entries(used))
67
74
  {
68
- if (this.synth.overrideSoundfont)
69
- {
70
- // clean up the embedded soundfont
71
- this.synth.clearSoundFont(true, true);
72
- }
73
- SpessaSynthGroupCollapsed("%cPreloading samples...", consoleColors.info);
74
- // smart preloading: load only samples used in the midi!
75
- const used = this.midiData.getUsedProgramsAndKeys(this.synth.soundfontManager);
76
- for (const [programBank, combos] of Object.entries(used))
75
+ const [bank, program] = programBank.split(":").map(Number);
76
+ const preset = this.synth.getPreset(bank, program);
77
+ SpessaSynthInfo(
78
+ `%cPreloading used samples on %c${preset.presetName}%c...`,
79
+ consoleColors.info,
80
+ consoleColors.recognized,
81
+ consoleColors.info
82
+ );
83
+ for (const combo of combos)
77
84
  {
78
- const bank = parseInt(programBank.split(":")[0]);
79
- const program = parseInt(programBank.split(":")[1]);
80
- const preset = this.synth.getPreset(bank, program);
81
- SpessaSynthInfo(
82
- `%cPreloading used samples on %c${preset.presetName}%c...`,
83
- consoleColors.info,
84
- consoleColors.recognized,
85
- consoleColors.info
86
- );
87
- for (const combo of combos)
88
- {
89
- const split = combo.split("-");
90
- preset.preloadSpecific(parseInt(split[0]), parseInt(split[1]));
91
- }
85
+ const [midiNote, velocity] = combo.split("-").map(Number);
86
+ this.synth.getVoicesForPreset(preset, bank, program, midiNote, velocity, midiNote);
92
87
  }
93
- SpessaSynthGroupEnd();
94
88
  }
89
+ SpessaSynthGroupEnd();
95
90
 
96
91
  /**
97
92
  * the midi track data
@@ -44,12 +44,6 @@ export class BasicPreset
44
44
  */
45
45
  presetZones = [];
46
46
 
47
- /**
48
- * Stores already found getSamplesAndGenerators for reuse
49
- * @type {SampleAndGenerators[][][]}
50
- */
51
- foundSamplesAndGenerators = [];
52
-
53
47
  /**
54
48
  * unused metadata
55
49
  * @type {number}
@@ -73,15 +67,6 @@ export class BasicPreset
73
67
  constructor(parentSoundBank)
74
68
  {
75
69
  this.parentSoundBank = parentSoundBank;
76
- for (let i = 0; i < 128; i++)
77
- {
78
- this.foundSamplesAndGenerators[i] = [];
79
- }
80
- }
81
-
82
- clearCache()
83
- {
84
- this.foundSamplesAndGenerators = [];
85
70
  }
86
71
 
87
72
  /**
@@ -136,34 +121,13 @@ export class BasicPreset
136
121
  }
137
122
 
138
123
  /**
139
- * Preloads a specific key/velocity combo
140
- * @param key {number}
141
- * @param velocity {number}
142
- */
143
- preloadSpecific(key, velocity)
144
- {
145
- this.getSamplesAndGenerators(key, velocity).forEach(samandgen =>
146
- {
147
- if (!samandgen.sample.isSampleLoaded)
148
- {
149
- samandgen.sample.getAudioData();
150
- }
151
- });
152
- }
153
-
154
- /**
155
- * Returns generatorTranslator and generators for given note
124
+ * Returns samples and generators for given note
156
125
  * @param midiNote {number}
157
126
  * @param velocity {number}
158
127
  * @returns {SampleAndGenerators[]}
159
128
  */
160
129
  getSamplesAndGenerators(midiNote, velocity)
161
130
  {
162
- const memorized = this.foundSamplesAndGenerators[midiNote][velocity];
163
- if (memorized)
164
- {
165
- return memorized;
166
- }
167
131
 
168
132
  if (this.presetZones.length < 1)
169
133
  {
@@ -333,9 +297,6 @@ export class BasicPreset
333
297
  });
334
298
  });
335
299
  });
336
-
337
- // save and return
338
- this.foundSamplesAndGenerators[midiNote][velocity] = parsedGeneratorsAndSamples;
339
300
  return parsedGeneratorsAndSamples;
340
301
  }
341
302
  }
@@ -63,8 +63,14 @@ export const generatorTypes = {
63
63
  exclusiveClass: 57, // sample - = cut = choke group
64
64
  overridingRootKey: 58, // sample - can override the sample's original pitch
65
65
  unused5: 59, // unused
66
- endOper: 60 // end marker
66
+ endOper: 60, // end marker
67
+
68
+ // additional generators that are used in system exclusives and will not be saved
69
+ vibLfoToVolume: 61,
70
+ vibLfoToFilterFc: 62
67
71
  };
72
+ export const GENERATORS_AMOUNT = Object.keys(generatorTypes).length;
73
+
68
74
  /**
69
75
  * @type {{min: number, max: number, def: number}[]}
70
76
  */
@@ -85,11 +91,13 @@ generatorLimits[generatorTypes.modEnvToPitch] = { min: -12000, max: 12000, def:
85
91
  generatorLimits[generatorTypes.initialFilterFc] = { min: 1500, max: 13500, def: 13500 };
86
92
  generatorLimits[generatorTypes.initialFilterQ] = { min: 0, max: 960, def: 0 };
87
93
  generatorLimits[generatorTypes.modLfoToFilterFc] = { min: -12000, max: 12000, def: 0 };
94
+ generatorLimits[generatorTypes.vibLfoToFilterFc] = { min: -12000, max: 12000, def: 0 }; // NON-STANDARD
88
95
  generatorLimits[generatorTypes.modEnvToFilterFc] = { min: -12000, max: 12000, def: 0 };
89
96
 
90
97
  generatorLimits[generatorTypes.endAddrsCoarseOffset] = { min: -32768, max: 32768, def: 0 };
91
98
 
92
99
  generatorLimits[generatorTypes.modLfoToVolume] = { min: -960, max: 960, def: 0 };
100
+ generatorLimits[generatorTypes.vibLfoToVolume] = { min: -960, max: 960, def: 0 }; // NON-STANDARD
93
101
 
94
102
  // effects, pan
95
103
  generatorLimits[generatorTypes.chorusEffectsSend] = { min: 0, max: 1000, def: 0 };
@@ -57,12 +57,13 @@ setResetValue(NON_CC_INDEX_OFFSET + modulatorSources.pitchWheelRange, 2);
57
57
  * @enum {number}
58
58
  */
59
59
  export const customControllers = {
60
- channelTuning: 0, // cents, RPN for fine tuning
61
- channelTransposeFine: 1, // cents, only the decimal tuning, (e.g., transpose is 4.5,
60
+ channelTuning: 0, // cents, RPN for fine tuning
61
+ channelTransposeFine: 1, // cents, only the decimal tuning, (e.g., transpose is 4.5,
62
62
  // then shift by 4 keys + tune by 50 cents)
63
- modulationMultiplier: 2, // cents, set by modulation depth RPN
64
- masterTuning: 3, // cents, set by system exclusive
65
- channelTuningSemitones: 4 // semitones, for RPN coarse tuning
63
+ modulationMultiplier: 2, // cents, set by modulation depth RPN
64
+ masterTuning: 3, // cents, set by system exclusive
65
+ channelTuningSemitones: 4, // semitones, for RPN coarse tuning
66
+ channelKeyShift: 5 // key shift: for system exclusive
66
67
  };
67
68
  export const CUSTOM_CONTROLLER_TABLE_SIZE = Object.keys(customControllers).length;
68
69
  export const customResetArray = new Float32Array(CUSTOM_CONTROLLER_TABLE_SIZE);
@@ -0,0 +1,95 @@
1
+ import { getModSourceEnum, Modulator, modulatorCurveTypes } from "../../../soundfont/basic_soundfont/modulator.js";
2
+ import { NON_CC_INDEX_OFFSET } from "./controller_tables.js";
3
+
4
+ /**
5
+ * A class for dynamic modulators
6
+ * that are assigned for more complex system exclusive messages
7
+ */
8
+ export class DynamicModulatorSystem
9
+ {
10
+ /**
11
+ * the current dynamic modulator list
12
+ * @type {{mod: Modulator, id: string}[]}
13
+ */
14
+ modulatorList = [];
15
+
16
+ resetModulators()
17
+ {
18
+ this.modulatorList = [];
19
+ }
20
+
21
+ /**
22
+ * @returns {Modulator[]}
23
+ */
24
+ getModulators()
25
+ {
26
+ return this.modulatorList.map(m => m.mod);
27
+ }
28
+
29
+ /**
30
+ * @param source {number}
31
+ * @param destination {generatorTypes}
32
+ * @param isBipolar {boolean}
33
+ * @param isNegative {boolean}
34
+ */
35
+ _getModulatorId(source, destination, isBipolar, isNegative)
36
+ {
37
+ return `${source}-${destination}-${isBipolar}-${isNegative}`;
38
+ }
39
+
40
+ /**
41
+ * @param id {string}
42
+ * @private
43
+ */
44
+ _deleteModulator(id)
45
+ {
46
+ this.modulatorList = this.modulatorList.filter(m => m.id !== id);
47
+ }
48
+
49
+ /**
50
+ * @param source {number} like in midiControllers: values below NON_CC_INDEX_OFFSET are CCs,
51
+ * above are regular modulator sources
52
+ * @param destination {generatorTypes}
53
+ * @param amount {number}
54
+ * @param isBipolar {boolean}
55
+ * @param isNegative {boolean}
56
+ */
57
+ setModulator(source, destination, amount, isBipolar = false, isNegative = false)
58
+ {
59
+ const id = this._getModulatorId(source, destination, isBipolar, isNegative);
60
+ if (amount === 0)
61
+ {
62
+ this._deleteModulator(id);
63
+ }
64
+ const mod = this.modulatorList.find(m => m.id === id);
65
+ if (mod)
66
+ {
67
+ mod.mod.transformAmount = amount;
68
+ }
69
+ else
70
+ {
71
+ let srcNum, isCC;
72
+ if (source >= NON_CC_INDEX_OFFSET)
73
+ {
74
+ srcNum = source - NON_CC_INDEX_OFFSET;
75
+ isCC = false;
76
+ }
77
+ else
78
+ {
79
+ srcNum = source;
80
+ isCC = true;
81
+ }
82
+ const modulator = new Modulator(
83
+ getModSourceEnum(modulatorCurveTypes.linear, isBipolar, 0, isCC, srcNum),
84
+ 0x0, // linear no controller
85
+ destination,
86
+ amount,
87
+ 0
88
+ );
89
+ this.modulatorList.push({
90
+ mod: modulator,
91
+ id: id
92
+ });
93
+ }
94
+ }
95
+ }
@@ -31,6 +31,7 @@ import { programChange } from "../engine_methods/program_change.js";
31
31
  import { chooseBank, isSystemXG, parseBankSelect } from "../../../utils/xg_hacks.js";
32
32
  import { DEFAULT_PERCUSSION } from "../../synth_constants.js";
33
33
  import { modulatorSources } from "../../../soundfont/basic_soundfont/modulator.js";
34
+ import { DynamicModulatorSystem } from "./dynamic_modulator_system.js";
34
35
 
35
36
  /**
36
37
  * This class represents a single MIDI Channel within the synthesizer.
@@ -81,6 +82,12 @@ class MidiAudioChannel
81
82
  */
82
83
  channelTuningCents = 0;
83
84
 
85
+ /**
86
+ * A system for dynamic modulator assignment for system exclusives
87
+ * @type {DynamicModulatorSystem}
88
+ */
89
+ sysExModulators = new DynamicModulatorSystem();
90
+
84
91
  /**
85
92
  * Indicates whether the sustain (hold) pedal is active.
86
93
  * @type {boolean}
@@ -147,12 +154,6 @@ class MidiAudioChannel
147
154
  */
148
155
  lockedSystem = "gs";
149
156
 
150
- /**
151
- * Indicates whether the channel uses a preset from the override soundfont.
152
- * @type {boolean}
153
- */
154
- presetUsesOverride = false;
155
-
156
157
  /**
157
158
  * Indicates whether the GS NRPN parameters are enabled for this channel.
158
159
  * @type {boolean}
@@ -229,10 +230,10 @@ class MidiAudioChannel
229
230
  updateChannelTuning()
230
231
  {
231
232
  this.channelTuningCents =
232
- this.customControllers[customControllers.channelTuning] // RPN channel fine tuning
233
- + this.customControllers[customControllers.channelTransposeFine] // user tuning (transpose)
234
- + this.customControllers[customControllers.masterTuning] // master tuning, set by sysEx
235
- + (this.customControllers[customControllers.channelTuningSemitones] * 100); // RPN channel coarse tuning
233
+ this.customControllers[customControllers.channelTuning] // RPN channel fine tuning
234
+ + this.customControllers[customControllers.channelTransposeFine] // user tuning (transpose)
235
+ + this.customControllers[customControllers.masterTuning] // master tuning, set by sysEx
236
+ + (this.customControllers[customControllers.channelTuningSemitones] * 100); // RPN channel coarse tuning
236
237
  }
237
238
 
238
239
  /**
@@ -361,7 +362,6 @@ class MidiAudioChannel
361
362
  {
362
363
  this.drumChannel = false;
363
364
  }
364
- this.presetUsesOverride = false;
365
365
  this.synth.callEvent("drumchange", {
366
366
  channel: this.channelNumber,
367
367
  isDrumChannel: this.drumChannel
@@ -1,5 +1,6 @@
1
- import { SpessaSynthWarn } from "../../../utils/loggin.js";
1
+ import { SpessaSynthInfo, SpessaSynthWarn } from "../../../utils/loggin.js";
2
2
  import { isXGDrums } from "../../../utils/xg_hacks.js";
3
+ import { EMBEDDED_SOUND_BANK_ID } from "../../synth_constants.js";
3
4
 
4
5
  /**
5
6
  * @typedef {Object} SoundFontType
@@ -47,7 +48,8 @@ export class SoundFontManager
47
48
  const presets = new Set();
48
49
  for (const p of font.soundfont.presets)
49
50
  {
50
- const presetString = `${p.bank + font.bankOffset}-${p.program}`;
51
+ const bank = Math.min(128, p.bank + font.bankOffset);
52
+ const presetString = `${bank}-${p.program}`;
51
53
  if (presets.has(presetString))
52
54
  {
53
55
  continue;
@@ -86,11 +88,8 @@ export class SoundFontManager
86
88
  */
87
89
  reloadManager(soundFont)
88
90
  {
89
- /**
90
- * All the soundfonts, ordered from the most important to the least.
91
- * @type {SoundFontType[]}
92
- */
93
- this.soundfontList = [];
91
+ // do not clear the embedded bank
92
+ this.soundfontList = this.soundfontList.filter(sf => sf.id === EMBEDDED_SOUND_BANK_ID);
94
93
  this.soundfontList.push({
95
94
  id: "main",
96
95
  bankOffset: 0,
@@ -114,7 +113,7 @@ export class SoundFontManager
114
113
  const index = this.soundfontList.findIndex(s => s.id === id);
115
114
  if (index === -1)
116
115
  {
117
- SpessaSynthWarn(`No soundfont with id of "${id}" found. Aborting!`);
116
+ SpessaSynthInfo(`No soundfont with id of "${id}" found. Aborting!`);
118
117
  return;
119
118
  }
120
119
  delete this.soundfontList[index].soundfont.presets;
@@ -145,6 +144,15 @@ export class SoundFontManager
145
144
  this.generatePresetList();
146
145
  }
147
146
 
147
+ /**
148
+ * Gets the current soundfont order
149
+ * @returns {string[]}
150
+ */
151
+ getCurrentSoundFontOrder()
152
+ {
153
+ return this.soundfontList.map(s => s.id);
154
+ }
155
+
148
156
  // noinspection JSUnusedGlobalSymbols
149
157
  /**
150
158
  * Rearranges the soundfonts
@@ -163,7 +171,7 @@ export class SoundFontManager
163
171
  * @param bankNumber {number}
164
172
  * @param programNumber {number}
165
173
  * @param allowXGDrums {boolean} if true, allows XG drum banks (120, 126 and 127) as drum preset
166
- * @returns {BasicPreset} the preset
174
+ * @returns {{preset: BasicPreset, bankOffset: number}} the preset and its bank offset
167
175
  */
168
176
  getPreset(bankNumber, programNumber, allowXGDrums = false)
169
177
  {
@@ -171,21 +179,24 @@ export class SoundFontManager
171
179
  {
172
180
  throw new Error("No soundfonts! Did you forget to add one?");
173
181
  }
182
+ const isDrum = bankNumber === 128 || (allowXGDrums && isXGDrums(bankNumber));
174
183
  for (const sf of this.soundfontList)
175
184
  {
176
185
  // check for the preset (with given offset)
177
186
  const preset = sf.soundfont.getPresetNoFallback(
178
- bankNumber - sf.bankOffset,
187
+ bankNumber === 128 ? 128 : bankNumber - sf.bankOffset,
179
188
  programNumber,
180
189
  allowXGDrums
181
190
  );
182
191
  if (preset !== undefined)
183
192
  {
184
- return preset;
193
+ return {
194
+ preset: preset,
195
+ bankOffset: sf.bankOffset
196
+ };
185
197
  }
186
198
  // if not found, advance to the next soundfont
187
199
  }
188
- const isDrum = bankNumber === 128 || (allowXGDrums && isXGDrums(bankNumber));
189
200
  // if none found, return the first correct preset found
190
201
  if (!isDrum)
191
202
  {
@@ -195,11 +206,18 @@ export class SoundFontManager
195
206
  allowXGDrums));
196
207
  if (preset)
197
208
  {
198
- return preset;
209
+ return {
210
+ preset: preset,
211
+ bankOffset: sf.bankOffset
212
+ };
199
213
  }
200
214
  }
201
215
  // if nothing at all, use the first preset
202
- return this.soundfontList[0].soundfont.presets[0];
216
+ const sf = this.soundfontList[0];
217
+ return {
218
+ preset: sf.soundfont.presets[0],
219
+ bankOffset: sf.bankOffset
220
+ };
203
221
  }
204
222
  else
205
223
  {
@@ -209,17 +227,27 @@ export class SoundFontManager
209
227
  const p = sf.soundfont.presets.find(p => p.isDrumPreset(allowXGDrums) && p.program === programNumber);
210
228
  if (p)
211
229
  {
212
- return p;
230
+ return {
231
+ preset: p,
232
+ bankOffset: sf.bankOffset
233
+ };
213
234
  }
214
235
  // check for any drum preset
215
236
  const preset = sf.soundfont.presets.find(p => p.isDrumPreset(allowXGDrums));
216
237
  if (preset)
217
238
  {
218
- return preset;
239
+ return {
240
+ preset: preset,
241
+ bankOffset: sf.bankOffset
242
+ };
219
243
  }
220
244
  }
221
245
  // if nothing at all, use the first preset
222
- return this.soundfontList[0].soundfont.presets[0];
246
+ const sf = this.soundfontList[0];
247
+ return {
248
+ preset: sf.soundfont.presets[0],
249
+ bankOffset: sf.bankOffset
250
+ };
223
251
  }
224
252
  }
225
253
 
@@ -78,7 +78,7 @@ export function panVoice(voice,
78
78
  if (reverbSend > 0)
79
79
  {
80
80
  // reverb is mono so we need to multiply by gain
81
- const reverbGain = this.synth.reverbGain * gain * (reverbSend / REVERB_DIVIDER);
81
+ const reverbGain = this.synth.reverbGain * this.synth.reverbSend * gain * (reverbSend / REVERB_DIVIDER);
82
82
  for (let i = 0; i < inputBuffer.length; i++)
83
83
  {
84
84
  reverbLeft[i] += reverbGain * inputBuffer[i];
@@ -91,7 +91,7 @@ export function panVoice(voice,
91
91
  if (chorusSend > 0)
92
92
  {
93
93
  // chorus is stereo so we do not need to
94
- const chorusGain = this.synth.chorusGain * chorusSend / CHORUS_DIVIDER;
94
+ const chorusGain = this.synth.chorusGain * this.synth.chorusSend * (chorusSend / CHORUS_DIVIDER);
95
95
  const chorusLeftGain = gainLeft * chorusGain;
96
96
  const chorusRightGain = gainRight * chorusGain;
97
97
  for (let i = 0; i < inputBuffer.length; i++)