spessasynth_lib 3.20.37 → 3.20.41

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
@@ -57,6 +57,10 @@ export function getSampleLinear(voice, outputBuffer)
57
57
  }
58
58
  else
59
59
  {
60
+ if(sample.loopingMode === 2 && !voice.isInRelease)
61
+ {
62
+ return;
63
+ }
60
64
  for (let i = 0; i < outputBuffer.length; i++)
61
65
  {
62
66
 
@@ -119,6 +123,10 @@ export function getSampleNearest(voice, outputBuffer)
119
123
  }
120
124
  else
121
125
  {
126
+ if(sample.loopingMode === 2 && !voice.isInRelease)
127
+ {
128
+ return;
129
+ }
122
130
  for (let i = 0; i < outputBuffer.length; i++)
123
131
  {
124
132
 
@@ -198,6 +206,10 @@ export function getSampleCubic(voice, outputBuffer)
198
206
  }
199
207
  else
200
208
  {
209
+ if(sample.loopingMode === 2 && !voice.isInRelease)
210
+ {
211
+ return;
212
+ }
201
213
  for (let i = 0; i < outputBuffer.length; i++)
202
214
  {
203
215
 
@@ -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
  {
@@ -86,8 +87,9 @@ class WorkletSample
86
87
  * Looping mode of the sample:
87
88
  * 0 - no loop
88
89
  * 1 - loop
89
- * 2 - loop then play when released
90
- * @type {0|1|2}
90
+ * 2 - UNOFFICIAL: polyphone 2.4 added start on release
91
+ * 3 - loop then play when released
92
+ * @type {0|1|2|3}
91
93
  */
92
94
  loopingMode = 0;
93
95
 
@@ -298,7 +300,7 @@ class WorkletVoice
298
300
  currentTime,
299
301
  voice.targetKey,
300
302
  voice.generators,
301
- voice.modulators.slice()
303
+ voice.modulators.map(m => Modulator.copy(m))
302
304
  );
303
305
  }
304
306
  }
@@ -329,7 +331,7 @@ export function getWorkletVoices(channel,
329
331
  const cached = channelObject.cachedVoices[midiNote][velocity];
330
332
  if(cached !== undefined)
331
333
  {
332
- workletVoices = cached.map(v => WorkletVoice.copy(v, currentTime));
334
+ return cached.map(v => WorkletVoice.copy(v, currentTime));
333
335
  }
334
336
  else
335
337
  {
@@ -371,15 +373,6 @@ export function getWorkletVoices(channel,
371
373
  let loopStart = (sampleAndGenerators.sample.sampleLoopStartIndex / 2);
372
374
  let loopEnd = (sampleAndGenerators.sample.sampleLoopEndIndex / 2);
373
375
  let loopingMode = generators[generatorTypes.sampleModes];
374
- const sampleLength = sampleAndGenerators.sample.getAudioData().length;
375
- // clamp loop
376
- loopStart = Math.min(Math.max(0, loopStart), sampleLength);
377
- // clamp loop
378
- loopEnd = Math.min(Math.max(0, loopEnd), sampleLength);
379
- if (loopEnd - loopStart < 1)
380
- {
381
- loopingMode = 0;
382
- }
383
376
  /**
384
377
  * create the worklet sample
385
378
  * offsets are calculated at note on time (to allow for modulation of them)
@@ -425,7 +418,7 @@ export function getWorkletVoices(channel,
425
418
  currentTime,
426
419
  targetKey,
427
420
  generators,
428
- sampleAndGenerators.modulators
421
+ sampleAndGenerators.modulators.map(m => Modulator.copy(m))
429
422
  )
430
423
  );
431
424
  return voices;