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.
@@ -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.soundfont.getPreset(0, 0);
140
- this.drumPreset = this.soundfont.getPreset(128, 0);
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
- (this.workletProcessorChannels[channel].isMuted)
28
+ (channelObject.isMuted)
28
29
  )
29
30
  {
30
31
  return;
31
32
  }
32
33
 
33
- midiNote += this.workletProcessorChannels[channel].channelTransposeKeyShift;
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
- this.workletProcessorChannels[channel].preset,
46
+ channelObject.preset,
46
47
  startTime,
47
48
  sampleRate,
48
- data => this.sampleDump(data.channel, data.sampleID, data.sampleData),
49
- this.workletProcessorChannels[channel].cachedVoices,
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 = this.workletProcessorChannels[channel].voices;
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, this.workletProcessorChannels[channel].midiControllers);
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 - this.workletProcessorChannels[channel].channelTransposeKeyShift,
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 bankWithOffset = Math.max(0, channelObject.midiControllers[midiControllers.bankSelect] - this.soundfontBankOffset);
28
- const bank = channelObject.drumChannel ? 128 : bankWithOffset;
29
- const preset = this.soundfont.getPreset(bank, programNumber);
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: preset.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.soundfont.getPreset(128, channelObject.preset.program));
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.soundfont.getPreset(channelObject.midiControllers[midiControllers.bankSelect], channelObject.preset.program));
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
- this.callEvent("presetlistchange", this.soundfont.presets.map(p => {
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 reloadSoundFont(buffer)
177
+ export function clearSoundFont(sendPresets = true, clearOverride = true)
110
178
  {
111
179
  this.stopAllChannels(true);
112
- delete this.soundfont;
113
-
114
-
115
- try {
116
- this.soundfont = new SoundFont2(buffer);
117
- }
118
- catch (e)
180
+ clearSamplesList();
181
+ if(clearOverride)
119
182
  {
120
- this.post({
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.soundfont.getPreset(0, 0);
130
- this.drumPreset = this.soundfont.getPreset(128, 0);
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
- if (globalDumpedSamplesList[sampleAndGenerators.sampleID] !== true)
164
+ const sampleID = sampleAndGenerators.sampleID + sampleIDOffset;
165
+ if (globalDumpedSamplesList[sampleID] !== true)
163
166
  {
164
- dumpSample(channel, sampleAndGenerators.sample, sampleAndGenerators.sampleID, sampleDumpCallback);
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: sampleAndGenerators.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,