spessasynth_core 3.26.10 → 3.26.12
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/README.md +1 -0
- package/package.json +1 -1
- package/src/midi/midi_tools/midi_writer.js +6 -0
- package/src/synthetizer/audio_engine/engine_components/dynamic_modulator_system.js +0 -8
- package/src/synthetizer/audio_engine/engine_components/midi_audio_channel.js +43 -2
- package/src/synthetizer/audio_engine/engine_methods/controller_control/reset_controllers.js +6 -1
- package/src/synthetizer/audio_engine/engine_methods/data_entry/awe32.js +198 -0
- package/src/synthetizer/audio_engine/engine_methods/data_entry/data_entry_coarse.js +10 -4
- package/src/synthetizer/audio_engine/engine_methods/data_entry/data_entry_fine.js +43 -4
- package/src/synthetizer/audio_engine/engine_methods/note_on.js +17 -4
- package/src/synthetizer/synth_constants.js +3 -1
package/README.md
CHANGED
|
@@ -63,6 +63,7 @@ npm install --save spessasynth_core
|
|
|
63
63
|
- **Sound Controllers:** Real-time filter and envelope control!
|
|
64
64
|
- **MIDI Tuning Standard Support:** [more info here](https://github.com/spessasus/spessasynth_core/wiki/MIDI-Implementation#midi-tuning-standard)
|
|
65
65
|
- [Full **RPN** and limited **NRPN** support](https://github.com/spessasus/spessasynth_core/wiki/MIDI-Implementation#supported-registered-parameters)
|
|
66
|
+
- [**AWE32** NRPN Compatibility Layer](https://github.com/spessasus/spessasynth_core/wiki/MIDI-Implementation#awe32-nrpn-compatibility-layer)
|
|
66
67
|
- Supports some [**Roland GS** and **Yamaha XG** system exclusives](https://github.com/spessasus/spessasynth_core/wiki/MIDI-Implementation#supported-system-exclusives)
|
|
67
68
|
|
|
68
69
|
### Powerful and Fast MIDI Sequencer
|
package/package.json
CHANGED
|
@@ -36,12 +36,18 @@ export function writeMIDI()
|
|
|
36
36
|
// this is a meta-message
|
|
37
37
|
// syntax is FF<type><length><data>
|
|
38
38
|
messageData = [0xff, event.messageStatusByte, ...writeVariableLengthQuantity(event.messageData.length), ...event.messageData];
|
|
39
|
+
// RP-001:
|
|
40
|
+
// Sysex events and meta-events cancel any running status which was in effect.
|
|
41
|
+
runningByte = undefined;
|
|
39
42
|
}
|
|
40
43
|
else if (event.messageStatusByte === messageTypes.systemExclusive)
|
|
41
44
|
{
|
|
42
45
|
// this is a system exclusive message
|
|
43
46
|
// syntax is F0<length><data>
|
|
44
47
|
messageData = [0xf0, ...writeVariableLengthQuantity(event.messageData.length), ...event.messageData];
|
|
48
|
+
// RP-001:
|
|
49
|
+
// Sysex events and meta-events cancel any running status which was in effect.
|
|
50
|
+
runningByte = undefined;
|
|
45
51
|
}
|
|
46
52
|
else
|
|
47
53
|
{
|
|
@@ -18,14 +18,6 @@ export class DynamicModulatorSystem
|
|
|
18
18
|
this.modulatorList = [];
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
/**
|
|
22
|
-
* @returns {Modulator[]}
|
|
23
|
-
*/
|
|
24
|
-
getModulators()
|
|
25
|
-
{
|
|
26
|
-
return this.modulatorList.map(m => m.mod);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
21
|
/**
|
|
30
22
|
* @param source {number}
|
|
31
23
|
* @param destination {generatorTypes}
|
|
@@ -29,9 +29,11 @@ import { pitchWheel } from "../engine_methods/tuning_control/pitch_wheel.js";
|
|
|
29
29
|
import { setOctaveTuning } from "../engine_methods/tuning_control/set_octave_tuning.js";
|
|
30
30
|
import { programChange } from "../engine_methods/program_change.js";
|
|
31
31
|
import { chooseBank, isSystemXG, parseBankSelect } from "../../../utils/xg_hacks.js";
|
|
32
|
-
import { DEFAULT_PERCUSSION } from "../../synth_constants.js";
|
|
32
|
+
import { DEFAULT_PERCUSSION, GENERATOR_OVERRIDE_NO_CHANGE_VALUE } from "../../synth_constants.js";
|
|
33
33
|
import { modulatorSources } from "../../../soundfont/basic_soundfont/modulator.js";
|
|
34
34
|
import { DynamicModulatorSystem } from "./dynamic_modulator_system.js";
|
|
35
|
+
import { GENERATORS_AMOUNT } from "../../../soundfont/basic_soundfont/generator.js";
|
|
36
|
+
import { computeModulators } from "./compute_modulator.js";
|
|
35
37
|
|
|
36
38
|
/**
|
|
37
39
|
* This class represents a single MIDI Channel within the synthesizer.
|
|
@@ -83,11 +85,24 @@ class MidiAudioChannel
|
|
|
83
85
|
channelTuningCents = 0;
|
|
84
86
|
|
|
85
87
|
/**
|
|
86
|
-
* A system for dynamic modulator assignment for system exclusives
|
|
88
|
+
* A system for dynamic modulator assignment for advanced system exclusives.
|
|
87
89
|
* @type {DynamicModulatorSystem}
|
|
88
90
|
*/
|
|
89
91
|
sysExModulators = new DynamicModulatorSystem();
|
|
90
92
|
|
|
93
|
+
/**
|
|
94
|
+
* An array of override generators for AWE32 support.
|
|
95
|
+
* A value of 32,767 means unchanged, as it is not allowed anywhere.
|
|
96
|
+
* @type {Int16Array}
|
|
97
|
+
*/
|
|
98
|
+
generatorOverrides = new Int16Array(GENERATORS_AMOUNT);
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* A small optimization that disables applying overrides until at least one is set.
|
|
102
|
+
* @type {boolean}
|
|
103
|
+
*/
|
|
104
|
+
generatorOverridesEnabled = false;
|
|
105
|
+
|
|
91
106
|
/**
|
|
92
107
|
* Indicates whether the sustain (hold) pedal is active.
|
|
93
108
|
* @type {boolean}
|
|
@@ -210,6 +225,7 @@ class MidiAudioChannel
|
|
|
210
225
|
this.synth = synth;
|
|
211
226
|
this.preset = preset;
|
|
212
227
|
this.channelNumber = channelNumber;
|
|
228
|
+
this.resetGeneratorOverrides();
|
|
213
229
|
}
|
|
214
230
|
|
|
215
231
|
get isXGChannel()
|
|
@@ -438,6 +454,31 @@ class MidiAudioChannel
|
|
|
438
454
|
};
|
|
439
455
|
this.synth?.onChannelPropertyChange?.(data, this.channelNumber);
|
|
440
456
|
}
|
|
457
|
+
|
|
458
|
+
resetGeneratorOverrides()
|
|
459
|
+
{
|
|
460
|
+
this.generatorOverrides.fill(GENERATOR_OVERRIDE_NO_CHANGE_VALUE);
|
|
461
|
+
this.generatorOverridesEnabled = false;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
/**
|
|
465
|
+
* @param gen {generatorTypes}
|
|
466
|
+
* @param value {number}
|
|
467
|
+
* @param realtime {boolean}
|
|
468
|
+
*/
|
|
469
|
+
setGeneratorOverride(gen, value, realtime = false)
|
|
470
|
+
{
|
|
471
|
+
this.generatorOverrides[gen] = value;
|
|
472
|
+
this.generatorOverridesEnabled = true;
|
|
473
|
+
if (realtime)
|
|
474
|
+
{
|
|
475
|
+
this.voices.forEach(v =>
|
|
476
|
+
{
|
|
477
|
+
v.generators[gen] = value;
|
|
478
|
+
computeModulators(v, this.midiControllers);
|
|
479
|
+
});
|
|
480
|
+
}
|
|
481
|
+
}
|
|
441
482
|
}
|
|
442
483
|
|
|
443
484
|
// voice
|
|
@@ -219,6 +219,7 @@ export function resetControllersRP15Compliant()
|
|
|
219
219
|
}
|
|
220
220
|
}
|
|
221
221
|
}
|
|
222
|
+
this.resetGeneratorOverrides();
|
|
222
223
|
}
|
|
223
224
|
|
|
224
225
|
/**
|
|
@@ -228,9 +229,13 @@ export function resetParameters()
|
|
|
228
229
|
{
|
|
229
230
|
/**
|
|
230
231
|
* reset the state machine to idle
|
|
231
|
-
* @type {string}
|
|
232
232
|
*/
|
|
233
233
|
this.dataEntryState = dataEntryStates.Idle;
|
|
234
|
+
this.midiControllers[midiControllers.NRPNLsb] = 127 << 7;
|
|
235
|
+
this.midiControllers[midiControllers.NRPNMsb] = 127 << 7;
|
|
236
|
+
this.midiControllers[midiControllers.RPNLsb] = 127 << 7;
|
|
237
|
+
this.midiControllers[midiControllers.RPNMsb] = 127 << 7;
|
|
238
|
+
this.resetGeneratorOverrides();
|
|
234
239
|
SpessaSynthInfo(
|
|
235
240
|
"%cResetting Registered and Non-Registered Parameters!",
|
|
236
241
|
consoleColors.info
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import { generatorTypes } from "../../../../soundfont/basic_soundfont/generator.js";
|
|
2
|
+
import { SpessaSynthWarn } from "../../../../utils/loggin.js";
|
|
3
|
+
import { consoleColors } from "../../../../utils/other.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* http://archive.gamedev.net/archive/reference/articles/article445.html
|
|
7
|
+
* https://github.com/user-attachments/files/15757220/adip301.pdf
|
|
8
|
+
* @type {generatorTypes[]}
|
|
9
|
+
*/
|
|
10
|
+
const AWE_NRPN_GENERATOR_MAPPINGS = [
|
|
11
|
+
generatorTypes.delayModLFO,
|
|
12
|
+
generatorTypes.freqModLFO,
|
|
13
|
+
|
|
14
|
+
generatorTypes.delayVibLFO,
|
|
15
|
+
generatorTypes.freqVibLFO,
|
|
16
|
+
|
|
17
|
+
generatorTypes.delayModEnv,
|
|
18
|
+
generatorTypes.attackModEnv,
|
|
19
|
+
generatorTypes.holdModEnv,
|
|
20
|
+
generatorTypes.decayModEnv,
|
|
21
|
+
generatorTypes.sustainModEnv,
|
|
22
|
+
generatorTypes.releaseModEnv,
|
|
23
|
+
|
|
24
|
+
generatorTypes.delayVolEnv,
|
|
25
|
+
generatorTypes.attackVolEnv,
|
|
26
|
+
generatorTypes.holdVolEnv,
|
|
27
|
+
generatorTypes.decayVolEnv,
|
|
28
|
+
generatorTypes.sustainVolEnv,
|
|
29
|
+
generatorTypes.releaseVolEnv,
|
|
30
|
+
|
|
31
|
+
generatorTypes.fineTune,
|
|
32
|
+
|
|
33
|
+
generatorTypes.modLfoToPitch,
|
|
34
|
+
generatorTypes.vibLfoToPitch,
|
|
35
|
+
generatorTypes.modEnvToPitch,
|
|
36
|
+
generatorTypes.modLfoToVolume,
|
|
37
|
+
|
|
38
|
+
generatorTypes.initialFilterFc,
|
|
39
|
+
generatorTypes.initialFilterQ,
|
|
40
|
+
|
|
41
|
+
generatorTypes.modLfoToFilterFc,
|
|
42
|
+
generatorTypes.modEnvToFilterFc,
|
|
43
|
+
|
|
44
|
+
generatorTypes.chorusEffectsSend,
|
|
45
|
+
generatorTypes.reverbEffectsSend
|
|
46
|
+
];
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Function that emulates AWE32 similarly to fluidsynth
|
|
50
|
+
* https://github.com/FluidSynth/fluidsynth/wiki/FluidFeatures
|
|
51
|
+
*
|
|
52
|
+
* Note: This makes use of findings by mrbumpy409:
|
|
53
|
+
* https://github.com/fluidSynth/fluidsynth/issues/1473
|
|
54
|
+
*
|
|
55
|
+
* The excellent test files are available here, also collected and converted by mrbumpy409:
|
|
56
|
+
* https://github.com/mrbumpy409/AWE32-midi-conversions
|
|
57
|
+
* @this {MidiAudioChannel}
|
|
58
|
+
* @param aweGen {number}
|
|
59
|
+
* @param dataLSB {number}
|
|
60
|
+
* @param dataMSB {number}
|
|
61
|
+
*/
|
|
62
|
+
export function handleAWE32NRPN(aweGen, dataLSB, dataMSB)
|
|
63
|
+
{
|
|
64
|
+
const clip = (v, min, max) => Math.max(min, Math.min(max, v));
|
|
65
|
+
const msecToTimecents = ms => Math.max(-32768, 1200 * Math.log2(ms / 1000));
|
|
66
|
+
const hzToCents = hz => 6900 + 1200 * Math.log2(hz / 440);
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
let dataValue = (dataMSB << 7) | dataLSB;
|
|
70
|
+
// center the value
|
|
71
|
+
// though ranges reported as 0 to 127 only use LSB
|
|
72
|
+
dataValue -= 8192;
|
|
73
|
+
const generator = AWE_NRPN_GENERATOR_MAPPINGS[aweGen];
|
|
74
|
+
if (!generator)
|
|
75
|
+
{
|
|
76
|
+
SpessaSynthWarn(
|
|
77
|
+
`Invalid AWE32 LSB: %c${aweGen}`,
|
|
78
|
+
consoleColors.unrecognized
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
let milliseconds, hertz, centibels, cents;
|
|
82
|
+
switch (generator)
|
|
83
|
+
{
|
|
84
|
+
default:
|
|
85
|
+
// this should not happen
|
|
86
|
+
break;
|
|
87
|
+
|
|
88
|
+
// delays
|
|
89
|
+
case generatorTypes.delayModLFO:
|
|
90
|
+
case generatorTypes.delayVibLFO:
|
|
91
|
+
case generatorTypes.delayVolEnv:
|
|
92
|
+
case generatorTypes.delayModEnv:
|
|
93
|
+
milliseconds = 4 * clip(dataValue, 0, 5900);
|
|
94
|
+
// convert to timecents
|
|
95
|
+
this.setGeneratorOverride(generator, msecToTimecents(milliseconds));
|
|
96
|
+
break;
|
|
97
|
+
|
|
98
|
+
// attacks
|
|
99
|
+
case generatorTypes.attackVolEnv:
|
|
100
|
+
case generatorTypes.attackModEnv:
|
|
101
|
+
milliseconds = clip(dataValue, 0, 5940);
|
|
102
|
+
// convert to timecents
|
|
103
|
+
this.setGeneratorOverride(generator, msecToTimecents(milliseconds));
|
|
104
|
+
break;
|
|
105
|
+
|
|
106
|
+
// holds
|
|
107
|
+
case generatorTypes.holdVolEnv:
|
|
108
|
+
case generatorTypes.holdModEnv:
|
|
109
|
+
milliseconds = clip(dataValue, 0, 8191);
|
|
110
|
+
// convert to timecents
|
|
111
|
+
this.setGeneratorOverride(generator, msecToTimecents(milliseconds));
|
|
112
|
+
break;
|
|
113
|
+
|
|
114
|
+
// decays and releases (share clips and units)
|
|
115
|
+
case generatorTypes.decayModEnv:
|
|
116
|
+
case generatorTypes.decayVolEnv:
|
|
117
|
+
case generatorTypes.releaseVolEnv:
|
|
118
|
+
case generatorTypes.releaseModEnv:
|
|
119
|
+
milliseconds = 4 * clip(dataValue, 0, 5940);
|
|
120
|
+
// convert to timecents
|
|
121
|
+
this.setGeneratorOverride(generator, msecToTimecents(milliseconds));
|
|
122
|
+
break;
|
|
123
|
+
|
|
124
|
+
// lfo frequencies
|
|
125
|
+
case generatorTypes.freqVibLFO:
|
|
126
|
+
case generatorTypes.freqModLFO:
|
|
127
|
+
hertz = 0.084 * dataLSB;
|
|
128
|
+
// convert to abs cents
|
|
129
|
+
this.setGeneratorOverride(generator, hzToCents(hertz), true);
|
|
130
|
+
break;
|
|
131
|
+
|
|
132
|
+
// sustains
|
|
133
|
+
case generatorTypes.sustainVolEnv:
|
|
134
|
+
case generatorTypes.sustainModEnv:
|
|
135
|
+
// 0.75 dB is 7.5 cB
|
|
136
|
+
centibels = dataLSB * 7.5;
|
|
137
|
+
this.setGeneratorOverride(generator, centibels);
|
|
138
|
+
break;
|
|
139
|
+
|
|
140
|
+
// pitch
|
|
141
|
+
case generatorTypes.fineTune:
|
|
142
|
+
// data is already centered
|
|
143
|
+
this.setGeneratorOverride(generator, dataValue, true);
|
|
144
|
+
break;
|
|
145
|
+
|
|
146
|
+
// lfo to pitch
|
|
147
|
+
case generatorTypes.modLfoToPitch:
|
|
148
|
+
case generatorTypes.vibLfoToPitch:
|
|
149
|
+
cents = clip(dataValue, -127, 127) * 9.375;
|
|
150
|
+
this.setGeneratorOverride(generator, cents, true);
|
|
151
|
+
break;
|
|
152
|
+
|
|
153
|
+
// env to pitch
|
|
154
|
+
case generatorTypes.modEnvToPitch:
|
|
155
|
+
cents = clip(dataValue, -127, 127) * 9.375;
|
|
156
|
+
this.setGeneratorOverride(generator, cents);
|
|
157
|
+
break;
|
|
158
|
+
|
|
159
|
+
// mod lfo to vol
|
|
160
|
+
case generatorTypes.modLfoToVolume:
|
|
161
|
+
// 0.1875 dB is 1.875 cB
|
|
162
|
+
centibels = 1.875 * dataLSB;
|
|
163
|
+
this.setGeneratorOverride(generator, centibels, true);
|
|
164
|
+
break;
|
|
165
|
+
|
|
166
|
+
// filter fc
|
|
167
|
+
case generatorTypes.initialFilterFc:
|
|
168
|
+
// minimum: 100 Hz -> 4335 cents
|
|
169
|
+
const fcCents = 4335 + 59 * dataLSB;
|
|
170
|
+
this.setGeneratorOverride(generator, fcCents, true);
|
|
171
|
+
break;
|
|
172
|
+
|
|
173
|
+
// filter Q
|
|
174
|
+
case generatorTypes.initialFilterQ:
|
|
175
|
+
// note: this uses the "modulator-ish" approach proposed by mrbumpy409
|
|
176
|
+
// here https://github.com/FluidSynth/fluidsynth/issues/1473
|
|
177
|
+
centibels = 215 * (dataLSB / 127);
|
|
178
|
+
this.setGeneratorOverride(generator, centibels, true);
|
|
179
|
+
break;
|
|
180
|
+
|
|
181
|
+
// to filterFc
|
|
182
|
+
case generatorTypes.modLfoToFilterFc:
|
|
183
|
+
cents = clip(dataValue, -64, 63) * 56.25;
|
|
184
|
+
this.setGeneratorOverride(generator, cents, true);
|
|
185
|
+
break;
|
|
186
|
+
|
|
187
|
+
case generatorTypes.modEnvToFilterFc:
|
|
188
|
+
cents = clip(dataValue, -64, 63) * 56.25;
|
|
189
|
+
this.setGeneratorOverride(generator, cents);
|
|
190
|
+
break;
|
|
191
|
+
|
|
192
|
+
// effects
|
|
193
|
+
case generatorTypes.chorusEffectsSend:
|
|
194
|
+
case generatorTypes.reverbEffectsSend:
|
|
195
|
+
this.setGeneratorOverride(generator, clip(dataValue, 0, 255) * (1000 / 255));
|
|
196
|
+
break;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
@@ -8,7 +8,7 @@ import { modulatorSources } from "../../../../soundfont/basic_soundfont/modulato
|
|
|
8
8
|
/**
|
|
9
9
|
* @enum {number}
|
|
10
10
|
*/
|
|
11
|
-
const registeredParameterTypes = {
|
|
11
|
+
export const registeredParameterTypes = {
|
|
12
12
|
pitchBendRange: 0x0000,
|
|
13
13
|
fineTuning: 0x0001,
|
|
14
14
|
coarseTuning: 0x0002,
|
|
@@ -19,8 +19,9 @@ const registeredParameterTypes = {
|
|
|
19
19
|
/**
|
|
20
20
|
* @enum {number}
|
|
21
21
|
*/
|
|
22
|
-
const
|
|
23
|
-
partParameter: 0x01
|
|
22
|
+
export const nonRegisteredMSB = {
|
|
23
|
+
partParameter: 0x01,
|
|
24
|
+
awe32: 0x7F
|
|
24
25
|
};
|
|
25
26
|
|
|
26
27
|
/**
|
|
@@ -50,6 +51,8 @@ const nonRegisteredGSLSB = {
|
|
|
50
51
|
*/
|
|
51
52
|
export function dataEntryCoarse(dataValue)
|
|
52
53
|
{
|
|
54
|
+
// store in cc table
|
|
55
|
+
this.midiControllers[midiControllers.dataEntryMsb] = dataValue << 7;
|
|
53
56
|
const addDefaultVibrato = () =>
|
|
54
57
|
{
|
|
55
58
|
if (this.channelVibrato.delay === 0 && this.channelVibrato.rate === 0 && this.channelVibrato.depth === 0)
|
|
@@ -117,7 +120,7 @@ export function dataEntryCoarse(dataValue)
|
|
|
117
120
|
break;
|
|
118
121
|
|
|
119
122
|
// part parameters: vibrato, cutoff
|
|
120
|
-
case
|
|
123
|
+
case nonRegisteredMSB.partParameter:
|
|
121
124
|
switch (NRPNFine)
|
|
122
125
|
{
|
|
123
126
|
default:
|
|
@@ -193,6 +196,9 @@ export function dataEntryCoarse(dataValue)
|
|
|
193
196
|
break;
|
|
194
197
|
}
|
|
195
198
|
break;
|
|
199
|
+
|
|
200
|
+
case nonRegisteredMSB.awe32:
|
|
201
|
+
break;
|
|
196
202
|
}
|
|
197
203
|
break;
|
|
198
204
|
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { consoleColors } from "../../../../utils/other.js";
|
|
2
|
-
import { SpessaSynthInfo } from "../../../../utils/loggin.js";
|
|
2
|
+
import { SpessaSynthInfo, SpessaSynthWarn } from "../../../../utils/loggin.js";
|
|
3
3
|
import { modulatorSources } from "../../../../soundfont/basic_soundfont/modulator.js";
|
|
4
4
|
import { customControllers, dataEntryStates, NON_CC_INDEX_OFFSET } from "../../engine_components/controller_tables.js";
|
|
5
5
|
import { midiControllers } from "../../../../midi/midi_message.js";
|
|
6
|
+
import { nonRegisteredMSB, registeredParameterTypes } from "./data_entry_coarse.js";
|
|
7
|
+
import { handleAWE32NRPN } from "./awe32.js";
|
|
6
8
|
|
|
7
9
|
/**
|
|
8
10
|
* Executes a data entry for an RPN tuning
|
|
@@ -12,6 +14,8 @@ import { midiControllers } from "../../../../midi/midi_message.js";
|
|
|
12
14
|
*/
|
|
13
15
|
export function dataEntryFine(dataValue)
|
|
14
16
|
{
|
|
17
|
+
// store in cc table
|
|
18
|
+
this.midiControllers[midiControllers.lsbForControl6DataEntry] = dataValue << 7;
|
|
15
19
|
switch (this.dataEntryState)
|
|
16
20
|
{
|
|
17
21
|
default:
|
|
@@ -26,7 +30,7 @@ export function dataEntryFine(dataValue)
|
|
|
26
30
|
break;
|
|
27
31
|
|
|
28
32
|
// pitch bend range fine tune
|
|
29
|
-
case
|
|
33
|
+
case registeredParameterTypes.pitchBendRange:
|
|
30
34
|
if (dataValue === 0)
|
|
31
35
|
{
|
|
32
36
|
break;
|
|
@@ -42,7 +46,7 @@ export function dataEntryFine(dataValue)
|
|
|
42
46
|
break;
|
|
43
47
|
|
|
44
48
|
// fine-tuning
|
|
45
|
-
case
|
|
49
|
+
case registeredParameterTypes.fineTuning:
|
|
46
50
|
// grab the data and shift
|
|
47
51
|
const coarse = this.customControllers[customControllers.channelTuning];
|
|
48
52
|
const finalTuning = (coarse << 7) | dataValue;
|
|
@@ -50,7 +54,7 @@ export function dataEntryFine(dataValue)
|
|
|
50
54
|
break;
|
|
51
55
|
|
|
52
56
|
// modulation depth
|
|
53
|
-
case
|
|
57
|
+
case registeredParameterTypes.modulationDepth:
|
|
54
58
|
const currentModulationDepthCents = this.customControllers[customControllers.modulationMultiplier] * 50;
|
|
55
59
|
let cents = currentModulationDepthCents + (dataValue / 128) * 100;
|
|
56
60
|
this.setModulationDepth(cents);
|
|
@@ -61,6 +65,41 @@ export function dataEntryFine(dataValue)
|
|
|
61
65
|
break;
|
|
62
66
|
|
|
63
67
|
}
|
|
68
|
+
break;
|
|
64
69
|
|
|
70
|
+
case dataEntryStates.NRPFine:
|
|
71
|
+
/**
|
|
72
|
+
* @type {number}
|
|
73
|
+
*/
|
|
74
|
+
const NRPNCoarse = this.midiControllers[midiControllers.NRPNMsb] >> 7;
|
|
75
|
+
/**
|
|
76
|
+
* @type {number}
|
|
77
|
+
*/
|
|
78
|
+
const NRPNFine = this.midiControllers[midiControllers.NRPNLsb] >> 7;
|
|
79
|
+
switch (NRPNCoarse)
|
|
80
|
+
{
|
|
81
|
+
default:
|
|
82
|
+
SpessaSynthWarn(
|
|
83
|
+
`%cUnrecognized NRPN LSB for %c${this.channelNumber}%c: %c(0x${NRPNFine.toString(16)
|
|
84
|
+
.toUpperCase()} 0x${NRPNFine.toString(
|
|
85
|
+
16).toUpperCase()})%c data value: %c${dataValue}`,
|
|
86
|
+
consoleColors.warn,
|
|
87
|
+
consoleColors.recognized,
|
|
88
|
+
consoleColors.warn,
|
|
89
|
+
consoleColors.unrecognized,
|
|
90
|
+
consoleColors.warn,
|
|
91
|
+
consoleColors.value
|
|
92
|
+
);
|
|
93
|
+
break;
|
|
94
|
+
|
|
95
|
+
case nonRegisteredMSB.awe32:
|
|
96
|
+
handleAWE32NRPN.call(
|
|
97
|
+
this,
|
|
98
|
+
NRPNFine,
|
|
99
|
+
dataValue,
|
|
100
|
+
this.midiControllers[midiControllers.dataEntryMsb] >> 7
|
|
101
|
+
);
|
|
102
|
+
break;
|
|
103
|
+
}
|
|
65
104
|
}
|
|
66
105
|
}
|
|
@@ -4,6 +4,7 @@ import { midiControllers } from "../../../midi/midi_message.js";
|
|
|
4
4
|
import { portamentoTimeToSeconds } from "./portamento_time.js";
|
|
5
5
|
import { customControllers } from "../engine_components/controller_tables.js";
|
|
6
6
|
import { Modulator } from "../../../soundfont/basic_soundfont/modulator.js";
|
|
7
|
+
import { GENERATOR_OVERRIDE_NO_CHANGE_VALUE } from "../../synth_constants.js";
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* sends a "MIDI Note on message"
|
|
@@ -99,9 +100,6 @@ export function noteOn(midiNote, velocity)
|
|
|
99
100
|
panOverride = Math.round(Math.random() * 1000 - 500);
|
|
100
101
|
}
|
|
101
102
|
|
|
102
|
-
// dynamic modulators (sysEx)
|
|
103
|
-
const dynamicModulators = this.sysExModulators.getModulators();
|
|
104
|
-
|
|
105
103
|
// add voices
|
|
106
104
|
const channelVoices = this.voices;
|
|
107
105
|
voices.forEach(voice =>
|
|
@@ -116,8 +114,10 @@ export function noteOn(midiNote, velocity)
|
|
|
116
114
|
// apply gain override
|
|
117
115
|
voice.gain = voiceGain;
|
|
118
116
|
|
|
119
|
-
|
|
117
|
+
// dynamic modulators (if none, this won't iterate over anything)
|
|
118
|
+
this.sysExModulators.modulatorList.forEach(m =>
|
|
120
119
|
{
|
|
120
|
+
const mod = m.mod;
|
|
121
121
|
const existingModIndex = voice.modulators.findIndex(voiceMod => Modulator.isIdentical(voiceMod, mod));
|
|
122
122
|
|
|
123
123
|
// replace or add
|
|
@@ -131,6 +131,19 @@ export function noteOn(midiNote, velocity)
|
|
|
131
131
|
}
|
|
132
132
|
});
|
|
133
133
|
|
|
134
|
+
// apply generator override
|
|
135
|
+
if (this.generatorOverridesEnabled)
|
|
136
|
+
{
|
|
137
|
+
this.generatorOverrides.forEach((overrideValue, generatorType) =>
|
|
138
|
+
{
|
|
139
|
+
if (overrideValue === GENERATOR_OVERRIDE_NO_CHANGE_VALUE)
|
|
140
|
+
{
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
voice.generators[generatorType] = overrideValue;
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
|
|
134
147
|
|
|
135
148
|
// apply exclusive class
|
|
136
149
|
const exclusive = voice.exclusiveClass;
|
|
@@ -21,4 +21,6 @@ export const DEFAULT_SYNTH_MODE = "gs";
|
|
|
21
21
|
|
|
22
22
|
export const ALL_CHANNELS_OR_DIFFERENT_ACTION = -1;
|
|
23
23
|
|
|
24
|
-
export const EMBEDDED_SOUND_BANK_ID = `SPESSASYNTH_EMBEDDED_BANK_${Math.random()}`;
|
|
24
|
+
export const EMBEDDED_SOUND_BANK_ID = `SPESSASYNTH_EMBEDDED_BANK_${Math.random()}`;
|
|
25
|
+
|
|
26
|
+
export const GENERATOR_OVERRIDE_NO_CHANGE_VALUE = 32767;
|