spessasynth_core 3.26.13 → 3.26.15
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 +7 -0
- package/index.js +15 -5
- package/package.json +1 -1
- package/src/midi/README.md +1 -1
- package/src/midi/basic_midi.js +1 -1
- package/src/midi/midi_builder.js +3 -2
- package/src/midi/midi_tools/rmidi_writer.js +4 -4
- package/src/sequencer/sequencer_engine.js +1 -0
- package/src/soundfont/basic_soundfont/generator.js +55 -52
- package/src/soundfont/basic_soundfont/riff_chunk.js +1 -1
- package/src/soundfont/basic_soundfont/write_dls/ins.js +2 -2
- package/src/soundfont/basic_soundfont/write_dls/wave.js +2 -2
- package/src/soundfont/basic_soundfont/write_dls/write_dls.js +2 -2
- package/src/soundfont/basic_soundfont/write_sf2/write.js +5 -8
- package/src/soundfont/load_soundfont.js +2 -2
- package/src/synthetizer/audio_engine/engine_components/compute_modulator.js +15 -6
- package/src/synthetizer/audio_engine/engine_components/controller_tables.js +2 -1
- package/src/synthetizer/audio_engine/engine_components/lowpass_filter.js +1 -1
- package/src/synthetizer/audio_engine/engine_components/midi_audio_channel.js +37 -2
- package/src/synthetizer/audio_engine/engine_components/voice.js +2 -1
- package/src/synthetizer/audio_engine/engine_components/wavetable_oscillator.js +1 -1
- package/src/synthetizer/audio_engine/engine_methods/controller_control/controller_change.js +31 -4
- package/src/synthetizer/audio_engine/engine_methods/controller_control/reset_controllers.js +2 -0
- package/src/synthetizer/audio_engine/engine_methods/data_entry/data_entry_coarse.js +17 -1
- package/src/synthetizer/audio_engine/engine_methods/data_entry/data_entry_fine.js +4 -0
- package/src/synthetizer/audio_engine/engine_methods/note_on.js +1 -2
- package/src/synthetizer/audio_engine/engine_methods/tuning_control/channel_pressure.js +1 -3
- package/src/synthetizer/audio_engine/engine_methods/tuning_control/pitch_wheel.js +1 -3
- package/src/synthetizer/audio_engine/engine_methods/tuning_control/poly_pressure.js +1 -3
- package/src/utils/byte_functions/string.js +5 -14
package/README.md
CHANGED
|
@@ -5,6 +5,12 @@
|
|
|
5
5
|
|
|
6
6
|
**A powerful SF2/DLS/MIDI JavaScript library. It works with any modern JS environment that supports WebAssembly.**
|
|
7
7
|
|
|
8
|
+
It allows you to:
|
|
9
|
+
- Play MIDI files using SF2/SF3/DLS files
|
|
10
|
+
- Write MIDI files
|
|
11
|
+
- Write SF2/SF3 files
|
|
12
|
+
- Convert DLS to SF2 (and back!)
|
|
13
|
+
- [and more!](https://github.com/spessasus/spessasynth_core?tab=readme-ov-file#current-features)
|
|
8
14
|
> **TIP:**
|
|
9
15
|
> Looking for an easy-to-use WebAudioAPI browser wrapper? Try [spessasynth_lib](https://github.com/spessasus/spessasynth_lib)!
|
|
10
16
|
|
|
@@ -63,6 +69,7 @@ npm install --save spessasynth_core
|
|
|
63
69
|
- **Sound Controllers:** Real-time filter and envelope control!
|
|
64
70
|
- **MIDI Tuning Standard Support:** [more info here](https://github.com/spessasus/spessasynth_core/wiki/MIDI-Implementation#midi-tuning-standard)
|
|
65
71
|
- [Full **RPN** and limited **NRPN** support](https://github.com/spessasus/spessasynth_core/wiki/MIDI-Implementation#supported-registered-parameters)
|
|
72
|
+
- **SoundFont2 NRPN Support**
|
|
66
73
|
- [**AWE32** NRPN Compatibility Layer](https://github.com/spessasus/spessasynth_core/wiki/MIDI-Implementation#awe32-nrpn-compatibility-layer)
|
|
67
74
|
- Supports some [**Roland GS** and **Yamaha XG** system exclusives](https://github.com/spessasus/spessasynth_core/wiki/MIDI-Implementation#supported-system-exclusives)
|
|
68
75
|
|
package/index.js
CHANGED
|
@@ -23,8 +23,8 @@ import { BasicSample } from "./src/soundfont/basic_soundfont/basic_sample.js";
|
|
|
23
23
|
import { BasicInstrumentZone, BasicPresetZone } from "./src/soundfont/basic_soundfont/basic_zones.js";
|
|
24
24
|
import { BasicInstrument } from "./src/soundfont/basic_soundfont/basic_instrument.js";
|
|
25
25
|
import { BasicPreset } from "./src/soundfont/basic_soundfont/basic_preset.js";
|
|
26
|
-
import { Generator } from "./src/soundfont/basic_soundfont/generator.js";
|
|
27
|
-
import { Modulator, modulatorSources } from "./src/soundfont/basic_soundfont/modulator.js";
|
|
26
|
+
import { Generator, generatorTypes } from "./src/soundfont/basic_soundfont/generator.js";
|
|
27
|
+
import { Modulator, modulatorCurveTypes, modulatorSources } from "./src/soundfont/basic_soundfont/modulator.js";
|
|
28
28
|
import { loadSoundFont } from "./src/soundfont/load_soundfont.js";
|
|
29
29
|
|
|
30
30
|
import { MIDI } from "./src/midi/midi_loader.js";
|
|
@@ -49,7 +49,8 @@ import { readBytesAsString } from "./src/utils/byte_functions/string.js";
|
|
|
49
49
|
import { readVariableLengthQuantity } from "./src/utils/byte_functions/variable_length_quantity.js";
|
|
50
50
|
import { consoleColors } from "./src/utils/other.js";
|
|
51
51
|
import { inflateSync } from "./src/externals/fflate/fflate.min.js";
|
|
52
|
-
|
|
52
|
+
import { DLSDestinations } from "./src/soundfont/dls/dls_destinations.js";
|
|
53
|
+
import { DLSSources } from "./src/soundfont/dls/dls_sources.js";
|
|
53
54
|
// you shouldn't use these...
|
|
54
55
|
const SpessaSynthCoreUtils = {
|
|
55
56
|
consoleColors,
|
|
@@ -64,6 +65,7 @@ const SpessaSynthCoreUtils = {
|
|
|
64
65
|
inflateSync
|
|
65
66
|
};
|
|
66
67
|
|
|
68
|
+
// see All-NPN-Exports.md in the wiki
|
|
67
69
|
export {
|
|
68
70
|
// synth and seq
|
|
69
71
|
SpessaSynthSequencer,
|
|
@@ -71,6 +73,7 @@ export {
|
|
|
71
73
|
SynthesizerSnapshot,
|
|
72
74
|
ChannelSnapshot,
|
|
73
75
|
KeyModifier,
|
|
76
|
+
|
|
74
77
|
masterParameterType,
|
|
75
78
|
channelConfiguration,
|
|
76
79
|
interpolationTypes,
|
|
@@ -92,7 +95,13 @@ export {
|
|
|
92
95
|
BasicPresetZone,
|
|
93
96
|
Generator,
|
|
94
97
|
Modulator,
|
|
98
|
+
|
|
95
99
|
modulatorSources,
|
|
100
|
+
modulatorCurveTypes,
|
|
101
|
+
generatorTypes,
|
|
102
|
+
DLSSources,
|
|
103
|
+
DLSDestinations,
|
|
104
|
+
|
|
96
105
|
|
|
97
106
|
// MIDI
|
|
98
107
|
MIDI,
|
|
@@ -100,13 +109,14 @@ export {
|
|
|
100
109
|
BasicMIDI,
|
|
101
110
|
MIDIBuilder,
|
|
102
111
|
MIDIMessage,
|
|
112
|
+
|
|
103
113
|
RMIDINFOChunks,
|
|
114
|
+
midiControllers,
|
|
115
|
+
messageTypes,
|
|
104
116
|
|
|
105
117
|
// utils
|
|
106
118
|
IndexedByteArray,
|
|
107
119
|
audioToWav,
|
|
108
120
|
SpessaSynthLogging,
|
|
109
|
-
midiControllers,
|
|
110
|
-
messageTypes,
|
|
111
121
|
SpessaSynthCoreUtils
|
|
112
122
|
};
|
package/package.json
CHANGED
package/src/midi/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
## This is the MIDI file parsing folder.
|
|
2
2
|
|
|
3
|
-
The code here is responsible for parsing the MIDI files and interpreting the
|
|
3
|
+
The code here is responsible for parsing the MIDI files and interpreting the messages.
|
|
4
4
|
All the events are defined in the `midi_message.js` file.
|
|
5
5
|
|
|
6
6
|
### MIDI Classes hierarchy
|
package/src/midi/basic_midi.js
CHANGED
|
@@ -402,7 +402,7 @@ class BasicMIDI extends MIDISequenceData
|
|
|
402
402
|
}
|
|
403
403
|
}
|
|
404
404
|
|
|
405
|
-
// fix empty port channel offsets (do a copy to turn empty slots into undefined so map goes over them)
|
|
405
|
+
// fix empty port channel offsets (do a copy to turn empty slots into undefined so the map goes over them)
|
|
406
406
|
this.midiPortChannelOffsets = [...this.midiPortChannelOffsets].map(o => o ?? 0);
|
|
407
407
|
|
|
408
408
|
// fix midi ports:
|
package/src/midi/midi_builder.js
CHANGED
|
@@ -126,8 +126,9 @@ export class MIDIBuilder extends BasicMIDI
|
|
|
126
126
|
* @param track {number} the track number to use
|
|
127
127
|
* @param channel {number} the channel to use
|
|
128
128
|
* @param midiNote {number} the midi note of the key release
|
|
129
|
+
* @param velocity {number} optional and unsupported by spessasynth
|
|
129
130
|
*/
|
|
130
|
-
addNoteOff(ticks, track, channel, midiNote)
|
|
131
|
+
addNoteOff(ticks, track, channel, midiNote, velocity = 64)
|
|
131
132
|
{
|
|
132
133
|
channel %= 16;
|
|
133
134
|
midiNote %= 128;
|
|
@@ -135,7 +136,7 @@ export class MIDIBuilder extends BasicMIDI
|
|
|
135
136
|
ticks,
|
|
136
137
|
track,
|
|
137
138
|
messageTypes.noteOff | channel,
|
|
138
|
-
[midiNote,
|
|
139
|
+
[midiNote, velocity]
|
|
139
140
|
);
|
|
140
141
|
}
|
|
141
142
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { combineArrays, IndexedByteArray } from "../../utils/indexed_array.js";
|
|
2
2
|
import { writeRIFFOddSize } from "../../soundfont/basic_soundfont/riff_chunk.js";
|
|
3
|
-
import { getStringBytes
|
|
3
|
+
import { getStringBytes } from "../../utils/byte_functions/string.js";
|
|
4
4
|
import { messageTypes, midiControllers, MIDIMessage } from "../midi_message.js";
|
|
5
5
|
import { getGsOn } from "./midi_editor.js";
|
|
6
6
|
import { SpessaSynthGroup, SpessaSynthGroupEnd, SpessaSynthInfo } from "../../utils/loggin.js";
|
|
@@ -459,7 +459,7 @@ export function writeRMIDI(
|
|
|
459
459
|
minute: "numeric"
|
|
460
460
|
});
|
|
461
461
|
infoContent.push(
|
|
462
|
-
writeRIFFOddSize(RMIDINFOChunks.creationDate,
|
|
462
|
+
writeRIFFOddSize(RMIDINFOChunks.creationDate, getStringBytes(today, true), true)
|
|
463
463
|
);
|
|
464
464
|
}
|
|
465
465
|
// comment
|
|
@@ -525,7 +525,7 @@ export function writeRMIDI(
|
|
|
525
525
|
// use midi copyright if possible
|
|
526
526
|
const copyright = mid.copyright.length > 0 ? mid.copyright : DEFAULT_COPYRIGHT;
|
|
527
527
|
infoContent.push(
|
|
528
|
-
writeRIFFOddSize(RMIDINFOChunks.copyright,
|
|
528
|
+
writeRIFFOddSize(RMIDINFOChunks.copyright, getStringBytes(copyright, true))
|
|
529
529
|
);
|
|
530
530
|
}
|
|
531
531
|
|
|
@@ -542,7 +542,7 @@ export function writeRMIDI(
|
|
|
542
542
|
encoding = FORCED_ENCODING;
|
|
543
543
|
}
|
|
544
544
|
// encoding
|
|
545
|
-
infoContent.push(writeRIFFOddSize(RMIDINFOChunks.encoding,
|
|
545
|
+
infoContent.push(writeRIFFOddSize(RMIDINFOChunks.encoding, getStringBytes(encoding, true)));
|
|
546
546
|
|
|
547
547
|
// combine and write out
|
|
548
548
|
const infodata = combineArrays(infoContent);
|
|
@@ -73,81 +73,84 @@ export const GENERATORS_AMOUNT = Object.keys(generatorTypes).length;
|
|
|
73
73
|
export const MAX_GENERATOR = Math.max(...Object.values(generatorTypes));
|
|
74
74
|
|
|
75
75
|
/**
|
|
76
|
-
* @type {{min: number, max: number, def: number}[]}
|
|
76
|
+
* @type {{min: number, max: number, def: number, nrpn: number}[]}
|
|
77
|
+
* min: minimum value, max: maximum value, def: default value, nrpn: nprn scale...
|
|
77
78
|
*/
|
|
78
|
-
|
|
79
|
+
const generatorLimits = [];
|
|
79
80
|
// offsets
|
|
80
|
-
generatorLimits[generatorTypes.startAddrsOffset] = { min: 0, max: 32768, def: 0 };
|
|
81
|
-
generatorLimits[generatorTypes.endAddrOffset] = { min: -32768, max: 32768, def: 0 };
|
|
82
|
-
generatorLimits[generatorTypes.startloopAddrsOffset] = { min: -32768, max: 32768, def: 0 };
|
|
83
|
-
generatorLimits[generatorTypes.endloopAddrsOffset] = { min: -32768, max: 32768, def: 0 };
|
|
84
|
-
generatorLimits[generatorTypes.startAddrsCoarseOffset] = { min: 0, max: 32768, def: 0 };
|
|
81
|
+
generatorLimits[generatorTypes.startAddrsOffset] = { min: 0, max: 32768, def: 0, nrpn: 1 };
|
|
82
|
+
generatorLimits[generatorTypes.endAddrOffset] = { min: -32768, max: 32768, def: 0, nrpn: 1 };
|
|
83
|
+
generatorLimits[generatorTypes.startloopAddrsOffset] = { min: -32768, max: 32768, def: 0, nrpn: 1 };
|
|
84
|
+
generatorLimits[generatorTypes.endloopAddrsOffset] = { min: -32768, max: 32768, def: 0, nrpn: 1 };
|
|
85
|
+
generatorLimits[generatorTypes.startAddrsCoarseOffset] = { min: 0, max: 32768, def: 0, nrpn: 1 };
|
|
85
86
|
|
|
86
87
|
// pitch influence
|
|
87
|
-
generatorLimits[generatorTypes.modLfoToPitch] = { min: -12000, max: 12000, def: 0 };
|
|
88
|
-
generatorLimits[generatorTypes.vibLfoToPitch] = { min: -12000, max: 12000, def: 0 };
|
|
89
|
-
generatorLimits[generatorTypes.modEnvToPitch] = { min: -12000, max: 12000, def: 0 };
|
|
88
|
+
generatorLimits[generatorTypes.modLfoToPitch] = { min: -12000, max: 12000, def: 0, nrpn: 2 };
|
|
89
|
+
generatorLimits[generatorTypes.vibLfoToPitch] = { min: -12000, max: 12000, def: 0, nrpn: 2 };
|
|
90
|
+
generatorLimits[generatorTypes.modEnvToPitch] = { min: -12000, max: 12000, def: 0, nrpn: 2 };
|
|
90
91
|
|
|
91
92
|
// lowpass
|
|
92
|
-
generatorLimits[generatorTypes.initialFilterFc] = { min: 1500, max: 13500, def: 13500 };
|
|
93
|
-
generatorLimits[generatorTypes.initialFilterQ] = { min: 0, max: 960, def: 0 };
|
|
94
|
-
generatorLimits[generatorTypes.modLfoToFilterFc] = { min: -12000, max: 12000, def: 0 };
|
|
95
|
-
generatorLimits[generatorTypes.vibLfoToFilterFc] = { min: -12000, max: 12000, def: 0 }; // NON-STANDARD
|
|
96
|
-
generatorLimits[generatorTypes.modEnvToFilterFc] = { min: -12000, max: 12000, def: 0 };
|
|
93
|
+
generatorLimits[generatorTypes.initialFilterFc] = { min: 1500, max: 13500, def: 13500, nrpn: 2 };
|
|
94
|
+
generatorLimits[generatorTypes.initialFilterQ] = { min: 0, max: 960, def: 0, nrpn: 1 };
|
|
95
|
+
generatorLimits[generatorTypes.modLfoToFilterFc] = { min: -12000, max: 12000, def: 0, nrpn: 2 };
|
|
96
|
+
generatorLimits[generatorTypes.vibLfoToFilterFc] = { min: -12000, max: 12000, def: 0, nrpn: 2 }; // NON-STANDARD
|
|
97
|
+
generatorLimits[generatorTypes.modEnvToFilterFc] = { min: -12000, max: 12000, def: 0, nrpn: 2 };
|
|
97
98
|
|
|
98
|
-
generatorLimits[generatorTypes.endAddrsCoarseOffset] = { min: -32768, max: 32768, def: 0 };
|
|
99
|
+
generatorLimits[generatorTypes.endAddrsCoarseOffset] = { min: -32768, max: 32768, def: 0, nrpn: 1 };
|
|
99
100
|
|
|
100
|
-
generatorLimits[generatorTypes.modLfoToVolume] = { min: -960, max: 960, def: 0 };
|
|
101
|
-
generatorLimits[generatorTypes.vibLfoToVolume] = { min: -960, max: 960, def: 0 }; // NON-STANDARD
|
|
101
|
+
generatorLimits[generatorTypes.modLfoToVolume] = { min: -960, max: 960, def: 0, nrpn: 1 };
|
|
102
|
+
generatorLimits[generatorTypes.vibLfoToVolume] = { min: -960, max: 960, def: 0, nrpn: 1 }; // NON-STANDARD
|
|
102
103
|
|
|
103
104
|
// effects, pan
|
|
104
|
-
generatorLimits[generatorTypes.chorusEffectsSend] = { min: 0, max: 1000, def: 0 };
|
|
105
|
-
generatorLimits[generatorTypes.reverbEffectsSend] = { min: 0, max: 1000, def: 0 };
|
|
106
|
-
generatorLimits[generatorTypes.pan] = { min: -500, max: 500, def: 0 };
|
|
105
|
+
generatorLimits[generatorTypes.chorusEffectsSend] = { min: 0, max: 1000, def: 0, nrpn: 1 };
|
|
106
|
+
generatorLimits[generatorTypes.reverbEffectsSend] = { min: 0, max: 1000, def: 0, nrpn: 1 };
|
|
107
|
+
generatorLimits[generatorTypes.pan] = { min: -500, max: 500, def: 0, nrpn: 1 };
|
|
107
108
|
|
|
108
109
|
// lfo
|
|
109
|
-
generatorLimits[generatorTypes.delayModLFO] = { min: -12000, max: 5000, def: -12000 };
|
|
110
|
-
generatorLimits[generatorTypes.freqModLFO] = { min: -16000, max: 4500, def: 0 };
|
|
111
|
-
generatorLimits[generatorTypes.delayVibLFO] = { min: -12000, max: 5000, def: -12000 };
|
|
112
|
-
generatorLimits[generatorTypes.freqVibLFO] = { min: -16000, max: 4500, def: 0 };
|
|
110
|
+
generatorLimits[generatorTypes.delayModLFO] = { min: -12000, max: 5000, def: -12000, nrpn: 2 };
|
|
111
|
+
generatorLimits[generatorTypes.freqModLFO] = { min: -16000, max: 4500, def: 0, nrpn: 4 };
|
|
112
|
+
generatorLimits[generatorTypes.delayVibLFO] = { min: -12000, max: 5000, def: -12000, nrpn: 2 };
|
|
113
|
+
generatorLimits[generatorTypes.freqVibLFO] = { min: -16000, max: 4500, def: 0, nrpn: 4 };
|
|
113
114
|
|
|
114
115
|
// mod env
|
|
115
|
-
generatorLimits[generatorTypes.delayModEnv] = { min: -32768, max: 5000, def: -32768 }; // -32,768 indicates instant phase,
|
|
116
|
+
generatorLimits[generatorTypes.delayModEnv] = { min: -32768, max: 5000, def: -32768, nrpn: 2 }; // -32,768 indicates instant phase,
|
|
116
117
|
// this is done to prevent click at the start of filter modenv
|
|
117
|
-
generatorLimits[generatorTypes.attackModEnv] = { min: -32768, max: 8000, def: -32768 };
|
|
118
|
-
generatorLimits[generatorTypes.holdModEnv] = { min: -12000, max: 5000, def: -12000 };
|
|
119
|
-
generatorLimits[generatorTypes.decayModEnv] = { min: -12000, max: 8000, def: -12000 };
|
|
120
|
-
generatorLimits[generatorTypes.sustainModEnv] = { min: 0, max: 1000, def: 0 };
|
|
121
|
-
generatorLimits[generatorTypes.releaseModEnv] = { min: -7200, max: 8000, def: -12000 }; // min is set to -7200 to prevent lowpass clicks
|
|
118
|
+
generatorLimits[generatorTypes.attackModEnv] = { min: -32768, max: 8000, def: -32768, nrpn: 2 };
|
|
119
|
+
generatorLimits[generatorTypes.holdModEnv] = { min: -12000, max: 5000, def: -12000, nrpn: 2 };
|
|
120
|
+
generatorLimits[generatorTypes.decayModEnv] = { min: -12000, max: 8000, def: -12000, nrpn: 2 };
|
|
121
|
+
generatorLimits[generatorTypes.sustainModEnv] = { min: 0, max: 1000, def: 0, nrpn: 1 };
|
|
122
|
+
generatorLimits[generatorTypes.releaseModEnv] = { min: -7200, max: 8000, def: -12000, nrpn: 2 }; // min is set to -7200 to prevent lowpass clicks
|
|
122
123
|
// key num to mod env
|
|
123
|
-
generatorLimits[generatorTypes.keyNumToModEnvHold] = { min: -1200, max: 1200, def: 0 };
|
|
124
|
-
generatorLimits[generatorTypes.keyNumToModEnvDecay] = { min: -1200, max: 1200, def: 0 };
|
|
124
|
+
generatorLimits[generatorTypes.keyNumToModEnvHold] = { min: -1200, max: 1200, def: 0, nrpn: 1 };
|
|
125
|
+
generatorLimits[generatorTypes.keyNumToModEnvDecay] = { min: -1200, max: 1200, def: 0, nrpn: 1 };
|
|
125
126
|
|
|
126
127
|
// vol env
|
|
127
|
-
generatorLimits[generatorTypes.delayVolEnv] = { min: -12000, max: 5000, def: -12000 };
|
|
128
|
-
generatorLimits[generatorTypes.attackVolEnv] = { min: -12000, max: 8000, def: -12000 };
|
|
129
|
-
generatorLimits[generatorTypes.holdVolEnv] = { min: -12000, max: 5000, def: -12000 };
|
|
130
|
-
generatorLimits[generatorTypes.decayVolEnv] = { min: -12000, max: 8000, def: -12000 };
|
|
131
|
-
generatorLimits[generatorTypes.sustainVolEnv] = { min: 0, max: 1440, def: 0 };
|
|
132
|
-
generatorLimits[generatorTypes.releaseVolEnv] = { min: -7200, max: 8000, def: -12000 }; // min is set to -7200 prevent clicks
|
|
128
|
+
generatorLimits[generatorTypes.delayVolEnv] = { min: -12000, max: 5000, def: -12000, nrpn: 2 };
|
|
129
|
+
generatorLimits[generatorTypes.attackVolEnv] = { min: -12000, max: 8000, def: -12000, nrpn: 2 };
|
|
130
|
+
generatorLimits[generatorTypes.holdVolEnv] = { min: -12000, max: 5000, def: -12000, nrpn: 2 };
|
|
131
|
+
generatorLimits[generatorTypes.decayVolEnv] = { min: -12000, max: 8000, def: -12000, nrpn: 2 };
|
|
132
|
+
generatorLimits[generatorTypes.sustainVolEnv] = { min: 0, max: 1440, def: 0, nrpn: 1 };
|
|
133
|
+
generatorLimits[generatorTypes.releaseVolEnv] = { min: -7200, max: 8000, def: -12000, nrpn: 2 }; // min is set to -7200 prevent clicks
|
|
133
134
|
// key num to vol env
|
|
134
|
-
generatorLimits[generatorTypes.keyNumToVolEnvHold] = { min: -1200, max: 1200, def: 0 };
|
|
135
|
-
generatorLimits[generatorTypes.keyNumToVolEnvDecay] = { min: -1200, max: 1200, def: 0 };
|
|
135
|
+
generatorLimits[generatorTypes.keyNumToVolEnvHold] = { min: -1200, max: 1200, def: 0, nrpn: 1 };
|
|
136
|
+
generatorLimits[generatorTypes.keyNumToVolEnvDecay] = { min: -1200, max: 1200, def: 0, nrpn: 1 };
|
|
136
137
|
|
|
137
|
-
generatorLimits[generatorTypes.startloopAddrsCoarseOffset] = { min: -32768, max: 32768, def: 0 };
|
|
138
|
-
generatorLimits[generatorTypes.keyNum] = { min: -1, max: 127, def: -1 };
|
|
139
|
-
generatorLimits[generatorTypes.velocity] = { min: -1, max: 127, def: -1 };
|
|
138
|
+
generatorLimits[generatorTypes.startloopAddrsCoarseOffset] = { min: -32768, max: 32768, def: 0, nrpn: 1 };
|
|
139
|
+
generatorLimits[generatorTypes.keyNum] = { min: -1, max: 127, def: -1, nrpn: 1 };
|
|
140
|
+
generatorLimits[generatorTypes.velocity] = { min: -1, max: 127, def: -1, nrpn: 1 };
|
|
140
141
|
|
|
141
|
-
generatorLimits[generatorTypes.initialAttenuation] = { min: 0, max: 1440, def: 0 };
|
|
142
|
+
generatorLimits[generatorTypes.initialAttenuation] = { min: 0, max: 1440, def: 0, nrpn: 1 };
|
|
142
143
|
|
|
143
|
-
generatorLimits[generatorTypes.endloopAddrsCoarseOffset] = { min: -32768, max: 32768, def: 0 };
|
|
144
|
+
generatorLimits[generatorTypes.endloopAddrsCoarseOffset] = { min: -32768, max: 32768, def: 0, nrpn: 1 };
|
|
144
145
|
|
|
145
|
-
generatorLimits[generatorTypes.coarseTune] = { min: -120, max: 120, def: 0 };
|
|
146
|
-
generatorLimits[generatorTypes.fineTune] = { min: -12700, max: 12700, def: 0 }; // this generator is used as initial pitch, hence this range
|
|
147
|
-
generatorLimits[generatorTypes.scaleTuning] = { min: 0, max: 1200, def: 100 };
|
|
148
|
-
generatorLimits[generatorTypes.exclusiveClass] = { min: 0, max: 99999, def: 0 };
|
|
149
|
-
generatorLimits[generatorTypes.overridingRootKey] = { min: 0 - 1, max: 127, def: -1 };
|
|
150
|
-
generatorLimits[generatorTypes.sampleModes] = { min: 0, max: 3, def: 0 };
|
|
146
|
+
generatorLimits[generatorTypes.coarseTune] = { min: -120, max: 120, def: 0, nrpn: 1 };
|
|
147
|
+
generatorLimits[generatorTypes.fineTune] = { min: -12700, max: 12700, def: 0, nrpn: 1 }; // this generator is used as initial pitch, hence this range
|
|
148
|
+
generatorLimits[generatorTypes.scaleTuning] = { min: 0, max: 1200, def: 100, nrpn: 1 };
|
|
149
|
+
generatorLimits[generatorTypes.exclusiveClass] = { min: 0, max: 99999, def: 0, nrpn: 0 };
|
|
150
|
+
generatorLimits[generatorTypes.overridingRootKey] = { min: 0 - 1, max: 127, def: -1, nrpn: 0 };
|
|
151
|
+
generatorLimits[generatorTypes.sampleModes] = { min: 0, max: 3, def: 0, nrpn: 0 };
|
|
152
|
+
|
|
153
|
+
export { generatorLimits };
|
|
151
154
|
|
|
152
155
|
export class Generator
|
|
153
156
|
{
|
|
@@ -93,7 +93,7 @@ export function writeRIFFChunk(chunk, prepend = undefined)
|
|
|
93
93
|
* @param header {string}
|
|
94
94
|
* @param data {Uint8Array}
|
|
95
95
|
* @param addZeroByte {Boolean}
|
|
96
|
-
* @param isList {boolean}
|
|
96
|
+
* @param isList {boolean} adds "LIST" as the chunk type and writes the actual type at the start of the data
|
|
97
97
|
* @returns {IndexedByteArray}
|
|
98
98
|
*/
|
|
99
99
|
export function writeRIFFOddSize(header, data, addZeroByte = false, isList = false)
|
|
@@ -3,10 +3,10 @@ import { combineZones } from "./combine_zones.js";
|
|
|
3
3
|
import { writeRIFFOddSize } from "../riff_chunk.js";
|
|
4
4
|
import { writeDword } from "../../../utils/byte_functions/little_endian.js";
|
|
5
5
|
import { writeDLSRegion } from "./rgn2.js";
|
|
6
|
-
import { getStringBytesZero } from "../../../utils/byte_functions/string.js";
|
|
7
6
|
import { writeArticulator } from "./art2.js";
|
|
8
7
|
import { SpessaSynthGroupCollapsed, SpessaSynthGroupEnd } from "../../../utils/loggin.js";
|
|
9
8
|
import { consoleColors } from "../../../utils/other.js";
|
|
9
|
+
import { getStringBytes } from "../../../utils/byte_functions/string.js";
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
12
|
* @this {BasicSoundBank}
|
|
@@ -84,7 +84,7 @@ export function writeIns(preset)
|
|
|
84
84
|
// writeINFO
|
|
85
85
|
const inam = writeRIFFOddSize(
|
|
86
86
|
"INAM",
|
|
87
|
-
|
|
87
|
+
getStringBytes(preset.presetName, true)
|
|
88
88
|
);
|
|
89
89
|
const info = writeRIFFOddSize(
|
|
90
90
|
"INFO",
|
|
@@ -2,9 +2,9 @@ import { combineArrays, IndexedByteArray } from "../../../utils/indexed_array.js
|
|
|
2
2
|
import { writeDword, writeWord } from "../../../utils/byte_functions/little_endian.js";
|
|
3
3
|
import { writeRIFFOddSize } from "../riff_chunk.js";
|
|
4
4
|
import { writeWavesample } from "./wsmp.js";
|
|
5
|
-
import { getStringBytesZero } from "../../../utils/byte_functions/string.js";
|
|
6
5
|
import { SpessaSynthInfo } from "../../../utils/loggin.js";
|
|
7
6
|
import { consoleColors } from "../../../utils/other.js";
|
|
7
|
+
import { getStringBytes } from "../../../utils/byte_functions/string.js";
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* @param sample {BasicSample}
|
|
@@ -66,7 +66,7 @@ export function writeDLSSample(sample)
|
|
|
66
66
|
|
|
67
67
|
const inam = writeRIFFOddSize(
|
|
68
68
|
"INAM",
|
|
69
|
-
|
|
69
|
+
getStringBytes(sample.sampleName, true)
|
|
70
70
|
);
|
|
71
71
|
const info = writeRIFFOddSize(
|
|
72
72
|
"INFO",
|
|
@@ -2,7 +2,7 @@ import { writeRIFFOddSize } from "../riff_chunk.js";
|
|
|
2
2
|
import { writeDword } from "../../../utils/byte_functions/little_endian.js";
|
|
3
3
|
import { combineArrays, IndexedByteArray } from "../../../utils/indexed_array.js";
|
|
4
4
|
import { writeLins } from "./lins.js";
|
|
5
|
-
import {
|
|
5
|
+
import { getStringBytes, writeStringAsBytes } from "../../../utils/byte_functions/string.js";
|
|
6
6
|
import { writeWavePool } from "./wvpl.js";
|
|
7
7
|
import { SpessaSynthGroupCollapsed, SpessaSynthGroupEnd, SpessaSynthInfo } from "../../../utils/loggin.js";
|
|
8
8
|
import { consoleColors } from "../../../utils/other.js";
|
|
@@ -80,7 +80,7 @@ export function writeDLS()
|
|
|
80
80
|
infos.push(
|
|
81
81
|
writeRIFFOddSize(
|
|
82
82
|
info,
|
|
83
|
-
|
|
83
|
+
getStringBytes(data, true),
|
|
84
84
|
true
|
|
85
85
|
)
|
|
86
86
|
);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { combineArrays, IndexedByteArray } from "../../../utils/indexed_array.js";
|
|
2
|
-
import { RiffChunk, writeRIFFChunk } from "../riff_chunk.js";
|
|
2
|
+
import { RiffChunk, writeRIFFChunk, writeRIFFOddSize } from "../riff_chunk.js";
|
|
3
3
|
import { writeStringAsBytes } from "../../../utils/byte_functions/string.js";
|
|
4
4
|
import { consoleColors } from "../../../utils/other.js";
|
|
5
5
|
import { getIGEN } from "./igen.js";
|
|
@@ -97,20 +97,17 @@ export function write(options = DEFAULT_WRITE_OPTIONS)
|
|
|
97
97
|
}
|
|
98
98
|
else
|
|
99
99
|
{
|
|
100
|
-
|
|
100
|
+
// pad with zero
|
|
101
|
+
const arr = new IndexedByteArray(data.length + 1);
|
|
101
102
|
writeStringAsBytes(arr, data);
|
|
102
103
|
infoArrays.push(writeRIFFChunk(new RiffChunk(
|
|
103
104
|
type,
|
|
104
|
-
|
|
105
|
+
arr.length,
|
|
105
106
|
arr
|
|
106
107
|
)));
|
|
107
108
|
}
|
|
108
109
|
}
|
|
109
|
-
const
|
|
110
|
-
new IndexedByteArray([73, 78, 70, 79]), // INFO
|
|
111
|
-
...infoArrays
|
|
112
|
-
]);
|
|
113
|
-
const infoChunk = writeRIFFChunk(new RiffChunk("LIST", combined.length, combined));
|
|
110
|
+
const infoChunk = writeRIFFOddSize("INFO", combineArrays(infoArrays), false, true);
|
|
114
111
|
|
|
115
112
|
SpessaSynthInfo(
|
|
116
113
|
"%cWriting SDTA...",
|
|
@@ -4,8 +4,8 @@ import { DLSSoundFont } from "./dls/dls_soundfont.js";
|
|
|
4
4
|
import { SoundFont2 } from "./read_sf2/soundfont.js";
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
|
-
* Loads a soundfont file
|
|
8
|
-
* @param buffer {ArrayBuffer}
|
|
7
|
+
* Loads a soundfont or dls file
|
|
8
|
+
* @param buffer {ArrayBuffer} the binary file to load
|
|
9
9
|
* @returns {BasicSoundBank}
|
|
10
10
|
*/
|
|
11
11
|
export function loadSoundFont(buffer)
|
|
@@ -27,7 +27,7 @@ export function computeModulator(controllerTable, modulator, voice)
|
|
|
27
27
|
modulator.currentValue = 0;
|
|
28
28
|
return 0;
|
|
29
29
|
}
|
|
30
|
-
// mapped to 0-
|
|
30
|
+
// mapped to 0-16,384
|
|
31
31
|
let rawSourceValue;
|
|
32
32
|
if (modulator.sourceUsesCC)
|
|
33
33
|
{
|
|
@@ -121,14 +121,23 @@ export function computeModulator(controllerTable, modulator, voice)
|
|
|
121
121
|
/**
|
|
122
122
|
* Computes modulators of a given voice. Source and index indicate what modulators shall be computed
|
|
123
123
|
* @param voice {Voice} the voice to compute modulators for
|
|
124
|
-
* @param controllerTable {Int16Array} all midi controllers as 14bit values + the non-controller indexes, starting at 128
|
|
125
124
|
* @param sourceUsesCC {number} what modulators should be computed, -1 means all, 0 means modulator source enum 1 means midi controller
|
|
126
125
|
* @param sourceIndex {number} enum for the source
|
|
126
|
+
* @this {MidiAudioChannel}
|
|
127
127
|
*/
|
|
128
|
-
export function computeModulators(voice,
|
|
128
|
+
export function computeModulators(voice, sourceUsesCC = -1, sourceIndex = 0)
|
|
129
129
|
{
|
|
130
130
|
const modulators = voice.modulators;
|
|
131
|
-
|
|
131
|
+
let generators = voice.generators;
|
|
132
|
+
// apply offsets if enabled
|
|
133
|
+
if (this.generatorOffsetsEnabled)
|
|
134
|
+
{
|
|
135
|
+
generators = new Int16Array(generators);
|
|
136
|
+
for (let i = 0; i < generators.length; i++)
|
|
137
|
+
{
|
|
138
|
+
generators[i] += this.generatorOffsets[i];
|
|
139
|
+
}
|
|
140
|
+
}
|
|
132
141
|
const modulatedGenerators = voice.modulatedGenerators;
|
|
133
142
|
|
|
134
143
|
if (sourceUsesCC === -1)
|
|
@@ -144,7 +153,7 @@ export function computeModulators(voice, controllerTable, sourceUsesCC = -1, sou
|
|
|
144
153
|
return;
|
|
145
154
|
}
|
|
146
155
|
const newValue = modulatedGenerators[mod.modulatorDestination] + computeModulator(
|
|
147
|
-
|
|
156
|
+
this.midiControllers,
|
|
148
157
|
mod,
|
|
149
158
|
voice
|
|
150
159
|
);
|
|
@@ -186,7 +195,7 @@ export function computeModulators(voice, controllerTable, sourceUsesCC = -1, sou
|
|
|
186
195
|
// Reset this destination
|
|
187
196
|
modulatedGenerators[destination] = generators[destination];
|
|
188
197
|
// compute our modulator
|
|
189
|
-
computeModulator(
|
|
198
|
+
computeModulator(this.midiControllers, mod, voice);
|
|
190
199
|
// sum the values of all modulators for this destination
|
|
191
200
|
modulators.forEach(m =>
|
|
192
201
|
{
|
|
@@ -63,7 +63,8 @@ export const customControllers = {
|
|
|
63
63
|
modulationMultiplier: 2, // cents, set by modulation depth RPN
|
|
64
64
|
masterTuning: 3, // cents, set by system exclusive
|
|
65
65
|
channelTuningSemitones: 4, // semitones, for RPN coarse tuning
|
|
66
|
-
channelKeyShift: 5 // key shift: for system exclusive
|
|
66
|
+
channelKeyShift: 5, // key shift: for system exclusive
|
|
67
|
+
sf2NPRNGeneratorLSB: 6 // sf2 NPRN LSB for selecting a generator value
|
|
67
68
|
};
|
|
68
69
|
export const CUSTOM_CONTROLLER_TABLE_SIZE = Object.keys(customControllers).length;
|
|
69
70
|
export const customResetArray = new Float32Array(CUSTOM_CONTROLLER_TABLE_SIZE);
|
|
@@ -129,7 +129,7 @@ export class LowpassFilter
|
|
|
129
129
|
|
|
130
130
|
/**
|
|
131
131
|
* Applies a low-pass filter to the given buffer
|
|
132
|
-
* @param voice {Voice} the voice we
|
|
132
|
+
* @param voice {Voice} the voice we are working on
|
|
133
133
|
* @param outputBuffer {Float32Array} the buffer to apply the filter to
|
|
134
134
|
* @param fcExcursion {number} the addition of modenv and mod lfo in cents to the filter
|
|
135
135
|
* @param smoothingFactor {number} filter's cutoff frequency smoothing factor
|
|
@@ -32,7 +32,7 @@ import { chooseBank, isSystemXG, parseBankSelect } from "../../../utils/xg_hacks
|
|
|
32
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";
|
|
35
|
+
import { generatorLimits, GENERATORS_AMOUNT } from "../../../soundfont/basic_soundfont/generator.js";
|
|
36
36
|
import { computeModulators } from "./compute_modulator.js";
|
|
37
37
|
|
|
38
38
|
/**
|
|
@@ -90,6 +90,19 @@ class MidiAudioChannel
|
|
|
90
90
|
*/
|
|
91
91
|
sysExModulators = new DynamicModulatorSystem();
|
|
92
92
|
|
|
93
|
+
/**
|
|
94
|
+
* An array of offsets generators for SF2 nrpn support.
|
|
95
|
+
* A value of 0 means no change; -10 means 10 lower, etc.
|
|
96
|
+
* @type {Int16Array}
|
|
97
|
+
*/
|
|
98
|
+
generatorOffsets = new Int16Array(GENERATORS_AMOUNT);
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* A small optimization that disables applying offsets until at least one is set.
|
|
102
|
+
* @type {boolean}
|
|
103
|
+
*/
|
|
104
|
+
generatorOffsetsEnabled = false;
|
|
105
|
+
|
|
93
106
|
/**
|
|
94
107
|
* An array of override generators for AWE32 support.
|
|
95
108
|
* A value of 32,767 means unchanged, as it is not allowed anywhere.
|
|
@@ -226,6 +239,7 @@ class MidiAudioChannel
|
|
|
226
239
|
this.preset = preset;
|
|
227
240
|
this.channelNumber = channelNumber;
|
|
228
241
|
this.resetGeneratorOverrides();
|
|
242
|
+
this.resetGeneratorOffsets();
|
|
229
243
|
}
|
|
230
244
|
|
|
231
245
|
get isXGChannel()
|
|
@@ -475,10 +489,30 @@ class MidiAudioChannel
|
|
|
475
489
|
this.voices.forEach(v =>
|
|
476
490
|
{
|
|
477
491
|
v.generators[gen] = value;
|
|
478
|
-
computeModulators(v
|
|
492
|
+
this.computeModulators(v);
|
|
479
493
|
});
|
|
480
494
|
}
|
|
481
495
|
}
|
|
496
|
+
|
|
497
|
+
resetGeneratorOffsets()
|
|
498
|
+
{
|
|
499
|
+
this.generatorOffsets.fill(0);
|
|
500
|
+
this.generatorOffsetsEnabled = false;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
/**
|
|
504
|
+
* @param gen {generatorTypes}
|
|
505
|
+
* @param value {number}
|
|
506
|
+
*/
|
|
507
|
+
setGeneratorOffset(gen, value)
|
|
508
|
+
{
|
|
509
|
+
this.generatorOffsets[gen] = value * generatorLimits[gen].nrpn;
|
|
510
|
+
this.generatorOffsetsEnabled = true;
|
|
511
|
+
this.voices.forEach(v =>
|
|
512
|
+
{
|
|
513
|
+
this.computeModulators(v);
|
|
514
|
+
});
|
|
515
|
+
}
|
|
482
516
|
}
|
|
483
517
|
|
|
484
518
|
// voice
|
|
@@ -487,6 +521,7 @@ MidiAudioChannel.prototype.panVoice = panVoice;
|
|
|
487
521
|
MidiAudioChannel.prototype.killNote = killNote;
|
|
488
522
|
MidiAudioChannel.prototype.stopAllNotes = stopAllNotes;
|
|
489
523
|
MidiAudioChannel.prototype.muteChannel = muteChannel;
|
|
524
|
+
MidiAudioChannel.prototype.computeModulators = computeModulators;
|
|
490
525
|
|
|
491
526
|
// MIDI messages
|
|
492
527
|
MidiAudioChannel.prototype.noteOn = noteOn;
|
|
@@ -400,7 +400,8 @@ export function getVoicesForPreset(preset, bank, program, midiNote, velocity, re
|
|
|
400
400
|
);
|
|
401
401
|
}
|
|
402
402
|
|
|
403
|
-
//
|
|
403
|
+
// EMU initial attenuation correction, multiply initial attenuation by 0.4!
|
|
404
|
+
// all EMU sound cards have this quirk and all sf2 editors and players emulate it too
|
|
404
405
|
generators[generatorTypes.initialAttenuation] = Math.floor(generators[generatorTypes.initialAttenuation] * 0.4);
|
|
405
406
|
|
|
406
407
|
// key override
|
|
@@ -172,7 +172,7 @@ export class WavetableOscillator
|
|
|
172
172
|
let y1 = y0 + 1; // point after the cursor
|
|
173
173
|
let y2 = y1 + 1; // point 1 after the cursor
|
|
174
174
|
let y3 = y2 + 1; // point 2 after the cursor
|
|
175
|
-
const t = cur - y0; // distance from y0 to cursor
|
|
175
|
+
const t = cur - y0; // the distance from y0 to cursor
|
|
176
176
|
// y0 is not handled here
|
|
177
177
|
// as it's math.floor of cur which is handled above
|
|
178
178
|
if (y1 >= sample.loopEnd)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { midiControllers } from "../../../../midi/midi_message.js";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { channelConfiguration, customControllers, dataEntryStates } from "../../engine_components/controller_tables.js";
|
|
3
|
+
import { nonRegisteredMSB } from "../data_entry/data_entry_coarse.js";
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* @param controllerNumber {number}
|
|
@@ -42,7 +42,7 @@ export function controllerChange(controllerNumber, controllerValue, force = fals
|
|
|
42
42
|
}
|
|
43
43
|
// append the lower nibble to the main controller
|
|
44
44
|
this.midiControllers[actualCCNum] = (this.midiControllers[actualCCNum] & 0x3F80) | (controllerValue & 0x7F);
|
|
45
|
-
this.voices.forEach(v => computeModulators(v,
|
|
45
|
+
this.voices.forEach(v => this.computeModulators(v, 1, actualCCNum));
|
|
46
46
|
}
|
|
47
47
|
if (this.lockedControllers[controllerNumber])
|
|
48
48
|
{
|
|
@@ -83,10 +83,37 @@ export function controllerChange(controllerNumber, controllerValue, force = fals
|
|
|
83
83
|
break;
|
|
84
84
|
|
|
85
85
|
case midiControllers.NRPNMsb:
|
|
86
|
+
// sfspec section 9.6.2
|
|
87
|
+
this.customControllers[customControllers.sf2NPRNGeneratorLSB] = 0;
|
|
86
88
|
this.dataEntryState = dataEntryStates.NRPCoarse;
|
|
87
89
|
break;
|
|
88
90
|
|
|
89
91
|
case midiControllers.NRPNLsb:
|
|
92
|
+
if (this.midiControllers[midiControllers.NRPNMsb] >> 7 === nonRegisteredMSB.SF2)
|
|
93
|
+
{
|
|
94
|
+
// if a <100 value has already been sent, reset!
|
|
95
|
+
if (this.customControllers[customControllers.sf2NPRNGeneratorLSB] % 100 !== 0)
|
|
96
|
+
{
|
|
97
|
+
this.customControllers[customControllers.sf2NPRNGeneratorLSB] = 0;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (controllerValue === 100)
|
|
101
|
+
{
|
|
102
|
+
this.customControllers[customControllers.sf2NPRNGeneratorLSB] += 100;
|
|
103
|
+
}
|
|
104
|
+
else if (controllerValue === 101)
|
|
105
|
+
{
|
|
106
|
+
this.customControllers[customControllers.sf2NPRNGeneratorLSB] += 1000;
|
|
107
|
+
}
|
|
108
|
+
else if (controllerValue === 102)
|
|
109
|
+
{
|
|
110
|
+
this.customControllers[customControllers.sf2NPRNGeneratorLSB] += 10000;
|
|
111
|
+
}
|
|
112
|
+
else if (controllerValue < 100)
|
|
113
|
+
{
|
|
114
|
+
this.customControllers[customControllers.sf2NPRNGeneratorLSB] += controllerValue;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
90
117
|
this.dataEntryState = dataEntryStates.NRPFine;
|
|
91
118
|
break;
|
|
92
119
|
|
|
@@ -120,7 +147,7 @@ export function controllerChange(controllerNumber, controllerValue, force = fals
|
|
|
120
147
|
|
|
121
148
|
// default: just compute modulators
|
|
122
149
|
default:
|
|
123
|
-
this.voices.forEach(v => computeModulators(v,
|
|
150
|
+
this.voices.forEach(v => this.computeModulators(v, 1, controllerNumber));
|
|
124
151
|
break;
|
|
125
152
|
}
|
|
126
153
|
}
|
|
@@ -220,6 +220,7 @@ export function resetControllersRP15Compliant()
|
|
|
220
220
|
}
|
|
221
221
|
}
|
|
222
222
|
this.resetGeneratorOverrides();
|
|
223
|
+
this.resetGeneratorOffsets();
|
|
223
224
|
}
|
|
224
225
|
|
|
225
226
|
/**
|
|
@@ -236,6 +237,7 @@ export function resetParameters()
|
|
|
236
237
|
this.midiControllers[midiControllers.RPNLsb] = 127 << 7;
|
|
237
238
|
this.midiControllers[midiControllers.RPNMsb] = 127 << 7;
|
|
238
239
|
this.resetGeneratorOverrides();
|
|
240
|
+
this.resetGeneratorOffsets();
|
|
239
241
|
SpessaSynthInfo(
|
|
240
242
|
"%cResetting Registered and Non-Registered Parameters!",
|
|
241
243
|
consoleColors.info
|
|
@@ -21,7 +21,8 @@ export const registeredParameterTypes = {
|
|
|
21
21
|
*/
|
|
22
22
|
export const nonRegisteredMSB = {
|
|
23
23
|
partParameter: 0x01,
|
|
24
|
-
awe32: 0x7F
|
|
24
|
+
awe32: 0x7F,
|
|
25
|
+
SF2: 120
|
|
25
26
|
};
|
|
26
27
|
|
|
27
28
|
/**
|
|
@@ -98,6 +99,7 @@ export function dataEntryCoarse(dataValue)
|
|
|
98
99
|
* @type {number}
|
|
99
100
|
*/
|
|
100
101
|
const NRPNFine = this.midiControllers[midiControllers.NRPNLsb] >> 7;
|
|
102
|
+
const dataEntryFine = this.midiControllers[midiControllers.lsbForControl6DataEntry] >> 7;
|
|
101
103
|
switch (NRPNCoarse)
|
|
102
104
|
{
|
|
103
105
|
default:
|
|
@@ -199,6 +201,20 @@ export function dataEntryCoarse(dataValue)
|
|
|
199
201
|
|
|
200
202
|
case nonRegisteredMSB.awe32:
|
|
201
203
|
break;
|
|
204
|
+
|
|
205
|
+
// SF2 NRPN
|
|
206
|
+
case nonRegisteredMSB.SF2:
|
|
207
|
+
if (NRPNFine > 100)
|
|
208
|
+
{
|
|
209
|
+
// sfspec:
|
|
210
|
+
// Note that NRPN Select LSB greater than 100 are for setup only, and should not be used on their own to select a
|
|
211
|
+
// generator parameter.
|
|
212
|
+
break;
|
|
213
|
+
}
|
|
214
|
+
const gen = this.customControllers[customControllers.sf2NPRNGeneratorLSB];
|
|
215
|
+
const offset = (dataValue << 7 | dataEntryFine) - 8192;
|
|
216
|
+
this.setGeneratorOffset(gen, offset);
|
|
217
|
+
break;
|
|
202
218
|
}
|
|
203
219
|
break;
|
|
204
220
|
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { computeModulators } from "../engine_components/compute_modulator.js";
|
|
2
1
|
import { generatorTypes } from "../../../soundfont/basic_soundfont/generator.js";
|
|
3
2
|
import { midiControllers } from "../../../midi/midi_message.js";
|
|
4
3
|
import { portamentoTimeToSeconds } from "./portamento_time.js";
|
|
@@ -159,7 +158,7 @@ export function noteOn(midiNote, velocity)
|
|
|
159
158
|
});
|
|
160
159
|
}
|
|
161
160
|
// compute all modulators
|
|
162
|
-
computeModulators(voice
|
|
161
|
+
this.computeModulators(voice);
|
|
163
162
|
// modulate sample offsets (these are not real time)
|
|
164
163
|
const cursorStartOffset = voice.modulatedGenerators[generatorTypes.startAddrsOffset] + voice.modulatedGenerators[generatorTypes.startAddrsCoarseOffset] * 32768;
|
|
165
164
|
const endOffset = voice.modulatedGenerators[generatorTypes.endAddrOffset] + voice.modulatedGenerators[generatorTypes.endAddrsCoarseOffset] * 32768;
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { NON_CC_INDEX_OFFSET } from "../../engine_components/controller_tables.js";
|
|
2
2
|
import { modulatorSources } from "../../../../soundfont/basic_soundfont/modulator.js";
|
|
3
|
-
import { computeModulators } from "../../engine_components/compute_modulator.js";
|
|
4
3
|
|
|
5
4
|
/**
|
|
6
5
|
* Sets the pressure of the given channel
|
|
@@ -12,9 +11,8 @@ export function channelPressure(pressure)
|
|
|
12
11
|
this.midiControllers[NON_CC_INDEX_OFFSET + modulatorSources.channelPressure] = pressure << 7;
|
|
13
12
|
this.updateChannelTuning();
|
|
14
13
|
this.voices.forEach(v =>
|
|
15
|
-
computeModulators(
|
|
14
|
+
this.computeModulators(
|
|
16
15
|
v,
|
|
17
|
-
this.midiControllers,
|
|
18
16
|
0,
|
|
19
17
|
modulatorSources.channelPressure
|
|
20
18
|
));
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { NON_CC_INDEX_OFFSET } from "../../engine_components/controller_tables.js";
|
|
2
2
|
import { modulatorSources } from "../../../../soundfont/basic_soundfont/modulator.js";
|
|
3
|
-
import { computeModulators } from "../../engine_components/compute_modulator.js";
|
|
4
3
|
|
|
5
4
|
/**
|
|
6
5
|
* Sets the pitch of the given channel
|
|
@@ -23,9 +22,8 @@ export function pitchWheel(MSB, LSB)
|
|
|
23
22
|
this.midiControllers[NON_CC_INDEX_OFFSET + modulatorSources.pitchWheel] = bend;
|
|
24
23
|
this.voices.forEach(v =>
|
|
25
24
|
// compute pitch modulators
|
|
26
|
-
computeModulators(
|
|
25
|
+
this.computeModulators(
|
|
27
26
|
v,
|
|
28
|
-
this.midiControllers,
|
|
29
27
|
0,
|
|
30
28
|
modulatorSources.pitchWheel
|
|
31
29
|
));
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { computeModulators } from "../../engine_components/compute_modulator.js";
|
|
2
1
|
import { modulatorSources } from "../../../../soundfont/basic_soundfont/modulator.js";
|
|
3
2
|
|
|
4
3
|
/**
|
|
@@ -16,9 +15,8 @@ export function polyPressure(midiNote, pressure)
|
|
|
16
15
|
return;
|
|
17
16
|
}
|
|
18
17
|
v.pressure = pressure;
|
|
19
|
-
computeModulators(
|
|
18
|
+
this.computeModulators(
|
|
20
19
|
v,
|
|
21
|
-
this.midiControllers,
|
|
22
20
|
0,
|
|
23
21
|
modulatorSources.polyPressure
|
|
24
22
|
);
|
|
@@ -51,30 +51,21 @@ export function readBytesAsString(dataArray, bytes, encoding = undefined, trimEn
|
|
|
51
51
|
|
|
52
52
|
/**
|
|
53
53
|
* @param string {string}
|
|
54
|
-
* @param
|
|
54
|
+
* @param addZero {boolean} adds a zero terminator at the end
|
|
55
55
|
* @returns {IndexedByteArray}
|
|
56
56
|
*/
|
|
57
|
-
export function getStringBytes(string,
|
|
57
|
+
export function getStringBytes(string, addZero = false)
|
|
58
58
|
{
|
|
59
59
|
let len = string.length;
|
|
60
|
-
if (
|
|
60
|
+
if (addZero)
|
|
61
61
|
{
|
|
62
|
-
len =
|
|
62
|
+
len = len + 1;
|
|
63
63
|
}
|
|
64
64
|
const arr = new IndexedByteArray(len);
|
|
65
|
-
writeStringAsBytes(arr, string
|
|
65
|
+
writeStringAsBytes(arr, string);
|
|
66
66
|
return arr;
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
-
/**
|
|
70
|
-
* @param string {string}
|
|
71
|
-
* @returns {IndexedByteArray}
|
|
72
|
-
*/
|
|
73
|
-
export function getStringBytesZero(string)
|
|
74
|
-
{
|
|
75
|
-
return getStringBytes(string, string.length + 1);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
69
|
/**
|
|
79
70
|
* @param string {string}
|
|
80
71
|
* @param outArray {IndexedByteArray}
|