spessasynth_lib 3.20.37 → 3.20.40

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.
@@ -49,7 +49,9 @@ import { getWorkletVoices } from './worklet_utilities/worklet_voice.js'
49
49
  * purpose: manages the synthesizer (and worklet sequencer) from the AudioWorkletGlobalScope and renders the audio data
50
50
  */
51
51
 
52
- export const MIN_NOTE_LENGTH = 0.07; // if the note is released faster than that, it forced to last that long
52
+ // if the note is released faster than that, it forced to last that long
53
+ // this is used mostly for drum channels, where a lot of midis like to send instant note off after a note on
54
+ export const MIN_NOTE_LENGTH = 0.03;
53
55
 
54
56
  export const SYNTHESIZER_GAIN = 1.0;
55
57
 
@@ -1,6 +1,6 @@
1
1
  import { consoleColors } from '../../../utils/other.js'
2
2
  import { midiControllers } from '../../../midi_parser/midi_message.js'
3
- import { dataEntryStates } from '../worklet_utilities/worklet_processor_channel.js'
3
+ import { channelConfiguration, dataEntryStates } from '../worklet_utilities/worklet_processor_channel.js'
4
4
  import { computeModulators } from '../worklet_utilities/worklet_modulator.js'
5
5
  import { SpessaSynthInfo, SpessaSynthWarn } from '../../../utils/loggin.js'
6
6
  import { SYNTHESIZER_GAIN } from '../main_processor.js'
@@ -8,7 +8,7 @@ import { DEFAULT_PERCUSSION } from '../../synthetizer.js'
8
8
 
9
9
  /**
10
10
  * @param channel {number}
11
- * @param controllerNumber {midiControllers}
11
+ * @param controllerNumber {number}
12
12
  * @param controllerValue {number}
13
13
  * @param force {boolean}
14
14
  * @this {SpessaSynthProcessor}
@@ -24,6 +24,19 @@ export function controllerChange(channel, controllerNumber, controllerValue, for
24
24
  SpessaSynthWarn(`Trying to access channel ${channel} which does not exist... ignoring!`);
25
25
  return;
26
26
  }
27
+ if(controllerNumber > 127)
28
+ {
29
+ // channel configuration. force must be set to true
30
+ if(!force) return;
31
+ switch (controllerNumber)
32
+ {
33
+ default:
34
+ return;
35
+
36
+ case channelConfiguration.velocityOverride:
37
+ channelObject.velocityOverride = controllerValue;
38
+ }
39
+ }
27
40
  // lsb controller values: append them as the lower nibble of the 14 bit value
28
41
  // excluding bank select and data entry as it's handled separately
29
42
  if(
@@ -43,6 +43,12 @@ export function noteOn(channel, midiNote, velocity, enableDebugging = false, sen
43
43
  sentMidiNote = this.tunings[program]?.[midiNote].midiNote;
44
44
  }
45
45
 
46
+ // velocity override
47
+ if(channelObject.velocityOverride > 0)
48
+ {
49
+ velocity = channelObject.velocityOverride;
50
+ }
51
+
46
52
  // get voices
47
53
  const voices = this.getWorkletVoices(
48
54
  channel,
@@ -16,7 +16,7 @@ import { WorkletVolumeEnvelope } from '../worklet_utilities/volume_envelope.js'
16
16
 
17
17
 
18
18
  const HALF_PI = Math.PI / 2;
19
- export const PAN_SMOOTHING_FACTOR = 0.01;
19
+ export const PAN_SMOOTHING_FACTOR = 0.05;
20
20
  /**
21
21
  * Renders a voice to the stereo output buffer
22
22
  * @param channel {WorkletProcessorChannel} the voice's channel
@@ -1,4 +1,4 @@
1
- import { modulatorSources } from '../../../soundfont/read_sf2/modulators.js'
1
+ import { Modulator, modulatorSources } from '../../../soundfont/read_sf2/modulators.js'
2
2
  import { getModulatorCurveValue, MOD_PRECOMPUTED_LENGTH } from './modulator_curves.js'
3
3
  import { NON_CC_INDEX_OFFSET } from './worklet_processor_channel.js'
4
4
  import { generatorLimits, generatorTypes } from '../../../soundfont/read_sf2/generators.js'
@@ -21,6 +21,7 @@ export function computeWorkletModulator(controllerTable, modulator, voice)
21
21
  {
22
22
  if(modulator.transformAmount === 0)
23
23
  {
24
+ modulator.currentValue = 0;
24
25
  return 0;
25
26
  }
26
27
  // mapped to 0-16384
@@ -95,13 +96,15 @@ export function computeWorkletModulator(controllerTable, modulator, voice)
95
96
 
96
97
 
97
98
  // compute the modulator
98
- const computedValue = sourceValue * secondSrcValue * modulator.transformAmount;
99
+ let computedValue = sourceValue * secondSrcValue * modulator.transformAmount;
99
100
 
100
101
  if(modulator.transformType === 2)
101
102
  {
102
103
  // abs value
103
- return Math.abs(computedValue);
104
+ computedValue = Math.abs(computedValue);
104
105
  }
106
+
107
+ modulator.currentValue = computedValue;
105
108
  return computedValue;
106
109
  }
107
110
 
@@ -113,7 +116,9 @@ export function computeWorkletModulator(controllerTable, modulator, voice)
113
116
  * @param sourceIndex {number} enum for the source
114
117
  */
115
118
  export function computeModulators(voice, controllerTable, sourceUsesCC = -1, sourceIndex = 0) {
116
- const { modulators, generators, modulatedGenerators } = voice;
119
+ const modulators = voice.modulators;
120
+ const generators = voice.generators;
121
+ const modulatedGenerators = voice.modulatedGenerators;
117
122
 
118
123
  // Modulation envelope is cheap to recalculate
119
124
  // why here and not at the bottom?
@@ -158,13 +163,14 @@ export function computeModulators(voice, controllerTable, sourceUsesCC = -1, sou
158
163
  {
159
164
  // Reset this destination
160
165
  modulatedGenerators[destination] = generators[destination];
161
- // Compute all modulators for this destination
166
+ // compute our modulator
167
+ computeWorkletModulator(controllerTable, mod, voice);
168
+ // sum the values of all modulators for this destination
162
169
  modulators.forEach(m => {
163
170
  if (m.modulatorDestination === destination)
164
171
  {
165
172
  const limits = generatorLimits[mod.modulatorDestination];
166
- const current = modulatedGenerators[mod.modulatorDestination];
167
- const newValue = current + computeWorkletModulator(controllerTable, m, voice);
173
+ const newValue = modulatedGenerators[mod.modulatorDestination] + m.currentValue;
168
174
  modulatedGenerators[mod.modulatorDestination] = Math.max(limits.min, Math.min(newValue, limits.max));
169
175
  }
170
176
  });
@@ -11,6 +11,7 @@ import { modulatorSources } from '../../../soundfont/read_sf2/modulators.js'
11
11
  * @property {Int16Array} keyCentTuning - tuning of individual keys in cents
12
12
  * @property {boolean} holdPedal - indicates whether the hold pedal is active
13
13
  * @property {boolean} drumChannel - indicates whether the channel is a drum channel
14
+ * @property {number} velocityOverride - overrides velocity if > 0 otherwise disabled
14
15
  *
15
16
  * @property {dataEntryStates} dataEntryState - the current state of the data entry
16
17
  * @property {number} NRPCoarse - the current coarse value of the Non-Registered Parameter
@@ -62,6 +63,8 @@ export function createWorkletChannel(sendEvent = false)
62
63
  channelOctaveTuning: new Int8Array(12),
63
64
  keyCentTuning: new Int16Array(128),
64
65
  channelVibrato: {delay: 0, depth: 0, rate: 0},
66
+ velocityOverride: 0,
67
+
65
68
  lockGSNRPNParams: false,
66
69
  holdPedal: false,
67
70
  isMuted: false,
@@ -92,6 +95,7 @@ resetArray[midiControllers.expressionController] = 127 << 7;
92
95
  resetArray[midiControllers.pan] = 64 << 7;
93
96
  resetArray[midiControllers.releaseTime] = 64 << 7;
94
97
  resetArray[midiControllers.brightness] = 64 << 7;
98
+ resetArray[midiControllers.timbreHarmonicContent] = 64 << 7;
95
99
  resetArray[NON_CC_INDEX_OFFSET + modulatorSources.pitchWheel] = 8192;
96
100
  resetArray[NON_CC_INDEX_OFFSET + modulatorSources.pitchWheelRange] = 2 << 7;
97
101
 
@@ -119,3 +123,11 @@ export const customControllers = {
119
123
  export const CUSTOM_CONTROLLER_TABLE_SIZE = Object.keys(customControllers).length;
120
124
  export const customResetArray = new Float32Array(CUSTOM_CONTROLLER_TABLE_SIZE);
121
125
  customResetArray[customControllers.modulationMultiplier] = 1;
126
+
127
+ /**
128
+ * This is a channel configuration enum, it is internally sent from Synthetizer via controller change
129
+ * @enum {number}
130
+ */
131
+ export const channelConfiguration = {
132
+ velocityOverride: 128, // overrides velocity for the given channel
133
+ }
@@ -3,6 +3,7 @@
3
3
  * purpose: prepares workletvoices from sample and generator data and manages sample dumping
4
4
  * note: sample dumping means sending it over to the AudioWorkletGlobalScope
5
5
  */
6
+ import { Modulator } from '../../../soundfont/read_sf2/modulators.js'
6
7
 
7
8
  class WorkletSample
8
9
  {
@@ -298,7 +299,7 @@ class WorkletVoice
298
299
  currentTime,
299
300
  voice.targetKey,
300
301
  voice.generators,
301
- voice.modulators.slice()
302
+ voice.modulators.map(m => Modulator.copy(m))
302
303
  );
303
304
  }
304
305
  }
@@ -329,7 +330,7 @@ export function getWorkletVoices(channel,
329
330
  const cached = channelObject.cachedVoices[midiNote][velocity];
330
331
  if(cached !== undefined)
331
332
  {
332
- workletVoices = cached.map(v => WorkletVoice.copy(v, currentTime));
333
+ return cached.map(v => WorkletVoice.copy(v, currentTime));
333
334
  }
334
335
  else
335
336
  {
@@ -425,7 +426,7 @@ export function getWorkletVoices(channel,
425
426
  currentTime,
426
427
  targetKey,
427
428
  generators,
428
- sampleAndGenerators.modulators
429
+ sampleAndGenerators.modulators.map(m => Modulator.copy(m))
429
430
  )
430
431
  );
431
432
  return voices;