spessasynth_core 3.26.5 → 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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "spessasynth_core",
3
- "version": "3.26.5",
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",
@@ -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
  /**
@@ -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++)
@@ -7,7 +7,11 @@ 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
16
 
13
17
  const EXCLUSIVE_CUTOFF_TIME = -2320;
@@ -385,7 +389,7 @@ export function getVoicesForPreset(preset, bank, program, midiNote, velocity, re
385
389
  }
386
390
 
387
391
  // create the generator list
388
- const generators = new Int16Array(60);
392
+ const generators = new Int16Array(GENERATORS_AMOUNT);
389
393
  // apply and sum the gens
390
394
  for (let i = 0; i < 60; i++)
391
395
  {
@@ -150,12 +150,13 @@ export function resetControllers()
150
150
  this.holdPedal = false;
151
151
  this.randomPan = false;
152
152
 
153
+ this.sysExModulators.resetModulators();
154
+
153
155
  // reset custom controllers
154
156
  // special case: transpose does not get affected
155
157
  const transpose = this.customControllers[customControllers.channelTransposeFine];
156
158
  this.customControllers.set(customResetArray);
157
159
  this.setCustomController(customControllers.channelTransposeFine, transpose);
158
-
159
160
  this.resetParameters();
160
161
 
161
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)
@@ -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,
@@ -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)