spessasynth_lib 3.12.0 → 3.12.3
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/midi_parser/used_keys_loaded.d.ts +1 -0
- package/@types/synthetizer/worklet_system/worklet_utilities/worklet_processor_channel.d.ts +5 -0
- package/midi_parser/rmidi_writer.js +5 -1
- package/midi_parser/used_keys_loaded.js +1 -0
- package/package.json +1 -1
- package/sequencer/worklet_sequencer/song_control.js +8 -3
- package/synthetizer/worklet_processor.min.js +6 -6
- package/synthetizer/worklet_system/main_processor.js +11 -2
- package/synthetizer/worklet_system/worklet_methods/note_on.js +14 -8
- package/synthetizer/worklet_system/worklet_methods/program_control.js +121 -26
- package/synthetizer/worklet_system/worklet_methods/reset_controllers.js +1 -0
- package/synthetizer/worklet_system/worklet_utilities/wavetable_oscillator.js +4 -0
- package/synthetizer/worklet_system/worklet_utilities/worklet_processor_channel.js +2 -0
- package/synthetizer/worklet_system/worklet_utilities/worklet_voice.js +6 -3
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
transposeChannel,
|
|
23
23
|
} from './worklet_methods/tuning_control.js'
|
|
24
24
|
import {
|
|
25
|
+
clearSoundFont, getPreset,
|
|
25
26
|
programChange,
|
|
26
27
|
reloadSoundFont,
|
|
27
28
|
sampleDump,
|
|
@@ -114,6 +115,12 @@ class SpessaSynthProcessor extends AudioWorkletProcessor {
|
|
|
114
115
|
|
|
115
116
|
this.highPerformanceMode = false;
|
|
116
117
|
|
|
118
|
+
/**
|
|
119
|
+
* Overrides the main soundfont (embedded for example
|
|
120
|
+
* @type {SoundFont2}
|
|
121
|
+
*/
|
|
122
|
+
this.overrideSoundfont = undefined;
|
|
123
|
+
|
|
117
124
|
/**
|
|
118
125
|
* the pan of the right channel
|
|
119
126
|
* @type {number}
|
|
@@ -136,8 +143,8 @@ class SpessaSynthProcessor extends AudioWorkletProcessor {
|
|
|
136
143
|
}
|
|
137
144
|
this.sendPresetList();
|
|
138
145
|
|
|
139
|
-
this.defaultPreset = this.
|
|
140
|
-
this.drumPreset = this.
|
|
146
|
+
this.defaultPreset = this.getPreset(0, 0);
|
|
147
|
+
this.drumPreset = this.getPreset(128, 0);
|
|
141
148
|
|
|
142
149
|
/**
|
|
143
150
|
* @type {Float32Array[]}
|
|
@@ -355,9 +362,11 @@ SpessaSynthProcessor.prototype.pitchWheel = pitchWheel;
|
|
|
355
362
|
|
|
356
363
|
// program related
|
|
357
364
|
SpessaSynthProcessor.prototype.programChange = programChange;
|
|
365
|
+
SpessaSynthProcessor.prototype.getPreset = getPreset;
|
|
358
366
|
SpessaSynthProcessor.prototype.setPreset = setPreset;
|
|
359
367
|
SpessaSynthProcessor.prototype.setDrums = setDrums;
|
|
360
368
|
SpessaSynthProcessor.prototype.reloadSoundFont = reloadSoundFont;
|
|
369
|
+
SpessaSynthProcessor.prototype.clearSoundFont = clearSoundFont;
|
|
361
370
|
SpessaSynthProcessor.prototype.sampleDump = sampleDump;
|
|
362
371
|
SpessaSynthProcessor.prototype.sendPresetList = sendPresetList;
|
|
363
372
|
|
|
@@ -21,16 +21,17 @@ export function noteOn(channel, midiNote, velocity, enableDebugging = false, sen
|
|
|
21
21
|
return;
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
+
const channelObject = this.workletProcessorChannels[channel]
|
|
24
25
|
if (
|
|
25
26
|
(this.highPerformanceMode && this.totalVoicesAmount > 200 && velocity < 40) ||
|
|
26
27
|
(this.highPerformanceMode && velocity < 10) ||
|
|
27
|
-
(
|
|
28
|
+
(channelObject.isMuted)
|
|
28
29
|
)
|
|
29
30
|
{
|
|
30
31
|
return;
|
|
31
32
|
}
|
|
32
33
|
|
|
33
|
-
midiNote +=
|
|
34
|
+
midiNote += channelObject.channelTransposeKeyShift;
|
|
34
35
|
|
|
35
36
|
if(midiNote > 127 || midiNote < 0)
|
|
36
37
|
{
|
|
@@ -42,16 +43,21 @@ export function noteOn(channel, midiNote, velocity, enableDebugging = false, sen
|
|
|
42
43
|
channel,
|
|
43
44
|
midiNote,
|
|
44
45
|
velocity,
|
|
45
|
-
|
|
46
|
+
channelObject.preset,
|
|
46
47
|
startTime,
|
|
47
48
|
sampleRate,
|
|
48
|
-
data => this.sampleDump(
|
|
49
|
-
|
|
49
|
+
data => this.sampleDump(
|
|
50
|
+
data.channel,
|
|
51
|
+
data.sampleID,
|
|
52
|
+
data.sampleData
|
|
53
|
+
),
|
|
54
|
+
channelObject.cachedVoices,
|
|
55
|
+
channelObject.presetUsesOverride ? this.soundfont.samples.length : 0, // this is done to prevent samples overlapping
|
|
50
56
|
enableDebugging
|
|
51
57
|
);
|
|
52
58
|
|
|
53
59
|
// add voices and exclusive class apply
|
|
54
|
-
const channelVoices =
|
|
60
|
+
const channelVoices = channelObject.voices;
|
|
55
61
|
voices.forEach(voice => {
|
|
56
62
|
const exclusive = voice.generators[generatorTypes.exclusiveClass];
|
|
57
63
|
if(exclusive !== 0)
|
|
@@ -67,7 +73,7 @@ export function noteOn(channel, midiNote, velocity, enableDebugging = false, sen
|
|
|
67
73
|
})
|
|
68
74
|
}
|
|
69
75
|
// compute all modulators
|
|
70
|
-
computeModulators(voice,
|
|
76
|
+
computeModulators(voice, channelObject.midiControllers);
|
|
71
77
|
// set initial pan to avoid split second changing from middle to the correct value
|
|
72
78
|
voice.currentPan = ((Math.max(-500, Math.min(500, voice.modulatedGenerators[generatorTypes.pan] )) + 500) / 1000) // 0 to 1
|
|
73
79
|
});
|
|
@@ -83,7 +89,7 @@ export function noteOn(channel, midiNote, velocity, enableDebugging = false, sen
|
|
|
83
89
|
{
|
|
84
90
|
this.sendChannelProperties();
|
|
85
91
|
this.callEvent("noteon", {
|
|
86
|
-
midiNote: midiNote -
|
|
92
|
+
midiNote: midiNote - channelObject.channelTransposeKeyShift,
|
|
87
93
|
channel: channel,
|
|
88
94
|
velocity: velocity,
|
|
89
95
|
});
|
|
@@ -24,18 +24,67 @@ export function programChange(channel, programNumber, userChange=false)
|
|
|
24
24
|
return;
|
|
25
25
|
}
|
|
26
26
|
// always 128 for percussion
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
|
|
27
|
+
const bank = channelObject.drumChannel ? 128 : channelObject.midiControllers[midiControllers.bankSelect];
|
|
28
|
+
let sentBank;
|
|
29
|
+
let preset;
|
|
30
|
+
|
|
31
|
+
// check if override
|
|
32
|
+
if(this.overrideSoundfont)
|
|
33
|
+
{
|
|
34
|
+
const bankWithOffset = bank === 128 ? 128 : Math.max(0, bank - this.soundfontBankOffset);
|
|
35
|
+
const p = this.overrideSoundfont.presets.find(p => p.program === programNumber && p.bank === bankWithOffset);
|
|
36
|
+
if(p)
|
|
37
|
+
{
|
|
38
|
+
sentBank = bank;
|
|
39
|
+
preset = p;
|
|
40
|
+
channelObject.presetUsesOverride = true;
|
|
41
|
+
}
|
|
42
|
+
else
|
|
43
|
+
{
|
|
44
|
+
preset = this.soundfont.getPreset(bank, programNumber);
|
|
45
|
+
sentBank = preset.bank;
|
|
46
|
+
channelObject.presetUsesOverride = false;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
else
|
|
50
|
+
{
|
|
51
|
+
preset = this.soundfont.getPreset(bank, programNumber);
|
|
52
|
+
sentBank = preset.bank;
|
|
53
|
+
channelObject.presetUsesOverride = false;
|
|
54
|
+
}
|
|
55
|
+
|
|
30
56
|
this.setPreset(channel, preset);
|
|
31
57
|
this.callEvent("programchange",{
|
|
32
58
|
channel: channel,
|
|
33
59
|
program: preset.program,
|
|
34
|
-
bank:
|
|
60
|
+
bank: sentBank,
|
|
35
61
|
userCalled: userChange
|
|
36
62
|
});
|
|
37
63
|
}
|
|
38
64
|
|
|
65
|
+
/**
|
|
66
|
+
* @this {SpessaSynthProcessor}
|
|
67
|
+
* @param program {number}
|
|
68
|
+
* @param bank {number}
|
|
69
|
+
* @returns {Preset}
|
|
70
|
+
*/
|
|
71
|
+
export function getPreset(bank, program)
|
|
72
|
+
{
|
|
73
|
+
if(this.overrideSoundfont)
|
|
74
|
+
{
|
|
75
|
+
// if overriden soundfont
|
|
76
|
+
const bankWithOffset = bank === 128 ? 128 : Math.max(0, bank - this.soundfontBankOffset);
|
|
77
|
+
const preset = this.overrideSoundfont.presets.find(p => p.program === program && p.bank === bankWithOffset);
|
|
78
|
+
if(preset)
|
|
79
|
+
{
|
|
80
|
+
return preset;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return this.soundfont.getPreset(bank, program);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
|
|
39
88
|
/**
|
|
40
89
|
* @param channel {number}
|
|
41
90
|
* @param preset {Preset}
|
|
@@ -51,7 +100,8 @@ export function setPreset(channel, preset)
|
|
|
51
100
|
|
|
52
101
|
// reset cached voices
|
|
53
102
|
this.workletProcessorChannels[channel].cachedVoices = [];
|
|
54
|
-
for (let i = 0; i < 128; i++)
|
|
103
|
+
for (let i = 0; i < 128; i++)
|
|
104
|
+
{
|
|
55
105
|
this.workletProcessorChannels[channel].cachedVoices.push([]);
|
|
56
106
|
}
|
|
57
107
|
}
|
|
@@ -78,13 +128,14 @@ export function setDrums(channel, isDrum)
|
|
|
78
128
|
// clear transpose
|
|
79
129
|
channelObject.channelTransposeKeyShift = 0;
|
|
80
130
|
channelObject.drumChannel = true;
|
|
81
|
-
this.setPreset(channel, this.
|
|
131
|
+
this.setPreset(channel, this.getPreset(128, channelObject.preset.program));
|
|
82
132
|
}
|
|
83
133
|
else
|
|
84
134
|
{
|
|
85
135
|
channelObject.drumChannel = false;
|
|
86
|
-
this.setPreset(channel, this.
|
|
136
|
+
this.setPreset(channel, this.getPreset(channelObject.midiControllers[midiControllers.bankSelect], channelObject.preset.program));
|
|
87
137
|
}
|
|
138
|
+
channelObject.presetUsesOverride = false;
|
|
88
139
|
this.callEvent("drumchange",{
|
|
89
140
|
channel: channel,
|
|
90
141
|
isDrumChannel: channelObject.drumChannel
|
|
@@ -97,37 +148,44 @@ export function setDrums(channel, isDrum)
|
|
|
97
148
|
*/
|
|
98
149
|
export function sendPresetList()
|
|
99
150
|
{
|
|
100
|
-
|
|
151
|
+
const mainFont = this.soundfont.presets.map(p => {
|
|
101
152
|
return {presetName: p.presetName, bank: p.bank, program: p.program};
|
|
102
|
-
})
|
|
153
|
+
});
|
|
154
|
+
if(this.overrideSoundfont !== undefined)
|
|
155
|
+
{
|
|
156
|
+
this.overrideSoundfont.presets.forEach(p => {
|
|
157
|
+
const bankCheck = p.bank === 128 ? 128 : p.bank + this.soundfontBankOffset;
|
|
158
|
+
const exists = mainFont.find(pr => pr.bank === bankCheck && pr.program === p.program);
|
|
159
|
+
if(exists !== undefined)
|
|
160
|
+
{
|
|
161
|
+
exists.presetName = p.presetName;
|
|
162
|
+
}
|
|
163
|
+
else
|
|
164
|
+
{
|
|
165
|
+
mainFont.push({presetName: p.presetName, bank: bankCheck, program: p.program});
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
this.callEvent("presetlistchange", mainFont);
|
|
103
170
|
}
|
|
104
171
|
|
|
105
172
|
/**
|
|
106
|
-
* @param buffer {ArrayBuffer}
|
|
107
173
|
* @this {SpessaSynthProcessor}
|
|
174
|
+
* @param sendPresets {boolean}
|
|
175
|
+
* @param clearOverride {boolean}
|
|
108
176
|
*/
|
|
109
|
-
export function
|
|
177
|
+
export function clearSoundFont(sendPresets = true, clearOverride = true)
|
|
110
178
|
{
|
|
111
179
|
this.stopAllChannels(true);
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
try {
|
|
116
|
-
this.soundfont = new SoundFont2(buffer);
|
|
117
|
-
}
|
|
118
|
-
catch (e)
|
|
180
|
+
clearSamplesList();
|
|
181
|
+
if(clearOverride)
|
|
119
182
|
{
|
|
120
|
-
this.
|
|
121
|
-
messageType: returnMessageType.soundfontError,
|
|
122
|
-
messageData: e
|
|
123
|
-
});
|
|
124
|
-
return;
|
|
183
|
+
delete this.overrideSoundfont;
|
|
125
184
|
}
|
|
126
|
-
clearSamplesList();
|
|
127
185
|
delete this.workletDumpedSamplesList;
|
|
128
186
|
this.workletDumpedSamplesList = [];
|
|
129
|
-
this.defaultPreset = this.
|
|
130
|
-
this.drumPreset = this.
|
|
187
|
+
this.defaultPreset = this.getPreset(0, 0);
|
|
188
|
+
this.drumPreset = this.getPreset(128, 0);
|
|
131
189
|
|
|
132
190
|
for(let i = 0; i < this.workletProcessorChannels.length; i++)
|
|
133
191
|
{
|
|
@@ -140,6 +198,43 @@ export function reloadSoundFont(buffer)
|
|
|
140
198
|
channelObject.lockPreset = false;
|
|
141
199
|
this.programChange(i, channelObject.preset.program);
|
|
142
200
|
}
|
|
201
|
+
if(sendPresets)
|
|
202
|
+
{
|
|
203
|
+
this.sendPresetList();
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* @param buffer {ArrayBuffer}
|
|
209
|
+
* @param isOverride {Boolean}
|
|
210
|
+
* @this {SpessaSynthProcessor}
|
|
211
|
+
*/
|
|
212
|
+
export function reloadSoundFont(buffer, isOverride = false)
|
|
213
|
+
{
|
|
214
|
+
this.clearSoundFont(false, isOverride);
|
|
215
|
+
if(!isOverride)
|
|
216
|
+
{
|
|
217
|
+
delete this.soundfont;
|
|
218
|
+
}
|
|
219
|
+
try
|
|
220
|
+
{
|
|
221
|
+
if(isOverride)
|
|
222
|
+
{
|
|
223
|
+
this.overrideSoundfont = new SoundFont2(buffer);
|
|
224
|
+
}
|
|
225
|
+
else
|
|
226
|
+
{
|
|
227
|
+
this.soundfont = new SoundFont2(buffer);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
catch (e)
|
|
231
|
+
{
|
|
232
|
+
this.post({
|
|
233
|
+
messageType: returnMessageType.soundfontError,
|
|
234
|
+
messageData: e
|
|
235
|
+
});
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
143
238
|
this.post({messageType: returnMessageType.ready, messageData: undefined});
|
|
144
239
|
this.sendPresetList();
|
|
145
240
|
SpessaSynthInfo("%cSpessaSynth is ready!", consoleColors.recognized);
|
|
@@ -29,6 +29,7 @@ export function resetAllControllers()
|
|
|
29
29
|
// if preset is unlocked, switch to non drums and call event
|
|
30
30
|
if(!ch.lockPreset)
|
|
31
31
|
{
|
|
32
|
+
ch.presetUsesOverride = true;
|
|
32
33
|
ch.midiControllers[midiControllers.bankSelect] = 0;
|
|
33
34
|
if (channelNumber % 16 === DEFAULT_PERCUSSION)
|
|
34
35
|
{
|
|
@@ -69,6 +69,10 @@ export function getOscillatorData(voice, sampleData, outputBuffer)
|
|
|
69
69
|
return;
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
+
// cur += voice.sample.playbackStep * voice.currentTuningCalculated;
|
|
73
|
+
// outputBuffer[i] = sampleData[ceil];
|
|
74
|
+
// continue;
|
|
75
|
+
|
|
72
76
|
const fraction = cur - floor;
|
|
73
77
|
|
|
74
78
|
// grab the samples and interpolate
|
|
@@ -17,6 +17,7 @@ import { modulatorSources } from '../../../soundfont/read/modulators.js'
|
|
|
17
17
|
*
|
|
18
18
|
* @property {Preset} preset - the channel's preset
|
|
19
19
|
* @property {boolean} lockPreset - indicates whether the program on the channel is locked
|
|
20
|
+
* @property {boolean} presetUsesOverride - indcates if the channel uses a preset from the override soundfont.
|
|
20
21
|
*
|
|
21
22
|
* @property {boolean} lockVibrato - indicates whether the custom vibrato is locked
|
|
22
23
|
* @property {Object} channelVibrato - vibrato settings for the channel
|
|
@@ -53,6 +54,7 @@ export function createWorkletChannel(sendEvent = false)
|
|
|
53
54
|
sustainedVoices: [],
|
|
54
55
|
cachedVoices: [],
|
|
55
56
|
preset: this.defaultPreset,
|
|
57
|
+
presetUsesOverride: false,
|
|
56
58
|
|
|
57
59
|
channelTransposeKeyShift: 0,
|
|
58
60
|
channelVibrato: {delay: 0, depth: 0, rate: 0},
|
|
@@ -126,6 +126,7 @@ function deepClone(obj) {
|
|
|
126
126
|
* @param sampleRate {number}
|
|
127
127
|
* @param sampleDumpCallback {function({channel: number, sampleID: number, sampleData: Float32Array})}
|
|
128
128
|
* @param cachedVoices {WorkletVoice[][][]} first is midi note, second is velocity. output is an array of WorkletVoices
|
|
129
|
+
* @param sampleIDOffset {number}
|
|
129
130
|
* @param debug {boolean}
|
|
130
131
|
* @returns {WorkletVoice[]}
|
|
131
132
|
*/
|
|
@@ -137,6 +138,7 @@ export function getWorkletVoices(channel,
|
|
|
137
138
|
sampleRate,
|
|
138
139
|
sampleDumpCallback,
|
|
139
140
|
cachedVoices,
|
|
141
|
+
sampleIDOffset,
|
|
140
142
|
debug=false)
|
|
141
143
|
{
|
|
142
144
|
/**
|
|
@@ -159,9 +161,10 @@ export function getWorkletVoices(channel,
|
|
|
159
161
|
*/
|
|
160
162
|
workletVoices = preset.getSamplesAndGenerators(midiNote, velocity).reduce((voices, sampleAndGenerators) => {
|
|
161
163
|
// dump the sample if haven't already
|
|
162
|
-
|
|
164
|
+
const sampleID = sampleAndGenerators.sampleID + sampleIDOffset;
|
|
165
|
+
if (globalDumpedSamplesList[sampleID] !== true)
|
|
163
166
|
{
|
|
164
|
-
dumpSample(channel, sampleAndGenerators.sample,
|
|
167
|
+
dumpSample(channel, sampleAndGenerators.sample, sampleID, sampleDumpCallback);
|
|
165
168
|
}
|
|
166
169
|
if(sampleAndGenerators.sample.sampleData === undefined)
|
|
167
170
|
{
|
|
@@ -205,7 +208,7 @@ export function getWorkletVoices(channel,
|
|
|
205
208
|
* @type {WorkletSample}
|
|
206
209
|
*/
|
|
207
210
|
const workletSample = {
|
|
208
|
-
sampleID:
|
|
211
|
+
sampleID: sampleID,
|
|
209
212
|
playbackStep: (sampleAndGenerators.sample.sampleRate / sampleRate) * Math.pow(2, sampleAndGenerators.sample.samplePitchCorrection / 1200),// cent tuning
|
|
210
213
|
cursor: generators[generatorTypes.startAddrsOffset] + (generators[generatorTypes.startAddrsCoarseOffset] * 32768),
|
|
211
214
|
rootKey: rootKey,
|