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.
- package/@types/soundfont/read_sf2/modulators.d.ts +5 -0
- package/@types/synthetizer/synthetizer.d.ts +7 -0
- package/@types/synthetizer/worklet_system/worklet_utilities/worklet_processor_channel.d.ts +12 -0
- package/package.json +1 -1
- package/soundfont/read_sf2/modulators.js +15 -0
- package/synthetizer/synthetizer.js +17 -0
- package/synthetizer/worklet_processor.min.js +9 -9
- package/synthetizer/worklet_system/main_processor.js +3 -1
- package/synthetizer/worklet_system/worklet_methods/controller_control.js +15 -2
- package/synthetizer/worklet_system/worklet_methods/note_on.js +6 -0
- package/synthetizer/worklet_system/worklet_methods/voice_control.js +1 -1
- package/synthetizer/worklet_system/worklet_utilities/worklet_modulator.js +13 -7
- package/synthetizer/worklet_system/worklet_utilities/worklet_processor_channel.js +12 -0
- package/synthetizer/worklet_system/worklet_utilities/worklet_voice.js +4 -3
|
@@ -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
|
-
|
|
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 {
|
|
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.
|
|
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
|
-
|
|
99
|
+
let computedValue = sourceValue * secondSrcValue * modulator.transformAmount;
|
|
99
100
|
|
|
100
101
|
if(modulator.transformType === 2)
|
|
101
102
|
{
|
|
102
103
|
// abs value
|
|
103
|
-
|
|
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
|
|
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
|
-
//
|
|
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
|
|
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.
|
|
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
|
-
|
|
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;
|