spessasynth_core 1.0.0

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.
Files changed (55) hide show
  1. package/.idea/inspectionProfiles/Project_Default.xml +10 -0
  2. package/.idea/jsLibraryMappings.xml +6 -0
  3. package/.idea/modules.xml +8 -0
  4. package/.idea/spessasynth_core.iml +12 -0
  5. package/.idea/vcs.xml +6 -0
  6. package/README.md +376 -0
  7. package/index.js +7 -0
  8. package/package.json +34 -0
  9. package/spessasynth_core/midi_parser/README.md +3 -0
  10. package/spessasynth_core/midi_parser/midi_loader.js +381 -0
  11. package/spessasynth_core/midi_parser/midi_message.js +231 -0
  12. package/spessasynth_core/sequencer/sequencer.js +192 -0
  13. package/spessasynth_core/sequencer/worklet_sequencer/play.js +221 -0
  14. package/spessasynth_core/sequencer/worklet_sequencer/process_event.js +138 -0
  15. package/spessasynth_core/sequencer/worklet_sequencer/process_tick.js +85 -0
  16. package/spessasynth_core/sequencer/worklet_sequencer/song_control.js +90 -0
  17. package/spessasynth_core/soundfont/README.md +4 -0
  18. package/spessasynth_core/soundfont/chunk/generators.js +205 -0
  19. package/spessasynth_core/soundfont/chunk/instruments.js +60 -0
  20. package/spessasynth_core/soundfont/chunk/modulators.js +232 -0
  21. package/spessasynth_core/soundfont/chunk/presets.js +264 -0
  22. package/spessasynth_core/soundfont/chunk/riff_chunk.js +46 -0
  23. package/spessasynth_core/soundfont/chunk/samples.js +250 -0
  24. package/spessasynth_core/soundfont/chunk/zones.js +264 -0
  25. package/spessasynth_core/soundfont/soundfont_parser.js +301 -0
  26. package/spessasynth_core/synthetizer/README.md +6 -0
  27. package/spessasynth_core/synthetizer/synthesizer.js +303 -0
  28. package/spessasynth_core/synthetizer/worklet_system/README.md +3 -0
  29. package/spessasynth_core/synthetizer/worklet_system/worklet_methods/controller_control.js +285 -0
  30. package/spessasynth_core/synthetizer/worklet_system/worklet_methods/data_entry.js +280 -0
  31. package/spessasynth_core/synthetizer/worklet_system/worklet_methods/note_off.js +102 -0
  32. package/spessasynth_core/synthetizer/worklet_system/worklet_methods/note_on.js +75 -0
  33. package/spessasynth_core/synthetizer/worklet_system/worklet_methods/program_control.js +140 -0
  34. package/spessasynth_core/synthetizer/worklet_system/worklet_methods/system_exclusive.js +265 -0
  35. package/spessasynth_core/synthetizer/worklet_system/worklet_methods/tuning_control.js +105 -0
  36. package/spessasynth_core/synthetizer/worklet_system/worklet_methods/vibrato_control.js +29 -0
  37. package/spessasynth_core/synthetizer/worklet_system/worklet_methods/voice_control.js +186 -0
  38. package/spessasynth_core/synthetizer/worklet_system/worklet_utilities/lfo.js +23 -0
  39. package/spessasynth_core/synthetizer/worklet_system/worklet_utilities/lowpass_filter.js +95 -0
  40. package/spessasynth_core/synthetizer/worklet_system/worklet_utilities/modulation_envelope.js +73 -0
  41. package/spessasynth_core/synthetizer/worklet_system/worklet_utilities/modulator_curves.js +86 -0
  42. package/spessasynth_core/synthetizer/worklet_system/worklet_utilities/stereo_panner.js +76 -0
  43. package/spessasynth_core/synthetizer/worklet_system/worklet_utilities/unit_converter.js +66 -0
  44. package/spessasynth_core/synthetizer/worklet_system/worklet_utilities/volume_envelope.js +194 -0
  45. package/spessasynth_core/synthetizer/worklet_system/worklet_utilities/wavetable_oscillator.js +83 -0
  46. package/spessasynth_core/synthetizer/worklet_system/worklet_utilities/worklet_modulator.js +173 -0
  47. package/spessasynth_core/synthetizer/worklet_system/worklet_utilities/worklet_processor_channel.js +105 -0
  48. package/spessasynth_core/synthetizer/worklet_system/worklet_utilities/worklet_voice.js +313 -0
  49. package/spessasynth_core/utils/README.md +4 -0
  50. package/spessasynth_core/utils/buffer_to_wav.js +70 -0
  51. package/spessasynth_core/utils/byte_functions.js +141 -0
  52. package/spessasynth_core/utils/loggin.js +79 -0
  53. package/spessasynth_core/utils/other.js +49 -0
  54. package/spessasynth_core/utils/shiftable_array.js +26 -0
  55. package/spessasynth_core/utils/stbvorbis_sync.js +1877 -0
@@ -0,0 +1,280 @@
1
+ import { consoleColors } from '../../../utils/other.js'
2
+ import { midiControllers } from '../../../midi_parser/midi_message.js'
3
+ import {
4
+ customControllers,
5
+ dataEntryStates,
6
+ NON_CC_INDEX_OFFSET,
7
+ } from '../worklet_utilities/worklet_processor_channel.js'
8
+ import { modulatorSources } from '../../../soundfont/chunk/modulators.js'
9
+ import { SpessaSynthInfo, SpessaSynthWarn } from '../../../utils/loggin.js'
10
+
11
+ /**
12
+ * Executes a data entry for an NRP for a sc88pro NRP (because touhou yes) and RPN tuning
13
+ * @param channel {number}
14
+ * @param dataValue {number} dataEntryCoarse MSB
15
+ * @this {Synthesizer}
16
+ * @private
17
+ */
18
+ export function dataEntryCoarse(channel, dataValue)
19
+ {
20
+ const channelObject = this.workletProcessorChannels[channel];
21
+ let addDefaultVibrato = () =>
22
+ {
23
+ if(channelObject.channelVibrato.delay === 0 && channelObject.channelVibrato.rate === 0 && channelObject.channelVibrato.depth === 0)
24
+ {
25
+ channelObject.channelVibrato.depth = 50;
26
+ channelObject.channelVibrato.rate = 8;
27
+ channelObject.channelVibrato.delay = 0.6;
28
+ }
29
+ }
30
+ switch(channelObject.dataEntryState)
31
+ {
32
+ default:
33
+ case dataEntryStates.Idle:
34
+ break;
35
+
36
+ // https://cdn.roland.com/assets/media/pdf/SC-88PRO_OM.pdf
37
+ // http://hummer.stanford.edu/sig/doc/classes/MidiOutput/rpn.html
38
+ case dataEntryStates.NRPFine:
39
+ switch(channelObject.NRPCoarse)
40
+ {
41
+ default:
42
+ if(dataValue === 64)
43
+ {
44
+ // default value
45
+ return;
46
+ }
47
+ SpessaSynthWarn(
48
+ `%cUnrecognized NRPN for %c${channel}%c: %c(0x${channelObject.NRPCoarse.toString(16).toUpperCase()} 0x${channelObject.NRPFine.toString(16).toUpperCase()})%c data value: %c${dataValue}`,
49
+ consoleColors.warn,
50
+ consoleColors.recognized,
51
+ consoleColors.warn,
52
+ consoleColors.unrecognized,
53
+ consoleColors.warn,
54
+ consoleColors.value);
55
+ break;
56
+
57
+ case 0x01:
58
+ switch(channelObject.NRPFine)
59
+ {
60
+ default:
61
+ if(dataValue === 64)
62
+ {
63
+ // default value
64
+ return;
65
+ }
66
+ SpessaSynthWarn(
67
+ `%cUnrecognized NRPN for %c${channel}%c: %c(0x${channelObject.NRPCoarse.toString(16)} 0x${channelObject.NRPFine.toString(16)})%c data value: %c${dataValue}`,
68
+ consoleColors.warn,
69
+ consoleColors.recognized,
70
+ consoleColors.warn,
71
+ consoleColors.unrecognized,
72
+ consoleColors.warn,
73
+ consoleColors.value);
74
+ break;
75
+
76
+ // vibrato rate
77
+ case 0x08:
78
+ if(channelObject.lockVibrato)
79
+ {
80
+ return;
81
+ }
82
+ if(dataValue === 64)
83
+ {
84
+ return;
85
+ }
86
+ addDefaultVibrato();
87
+ channelObject.channelVibrato.rate = (dataValue / 64) * 8;
88
+ SpessaSynthInfo(`%cVibrato rate for channel %c${channel}%c is now set to %c${channelObject.channelVibrato.rate}%cHz.`,
89
+ consoleColors.info,
90
+ consoleColors.recognized,
91
+ consoleColors.info,
92
+ consoleColors.value,
93
+ consoleColors.info);
94
+ break;
95
+
96
+ // vibrato depth
97
+ case 0x09:
98
+ if(channelObject.lockVibrato)
99
+ {
100
+ return;
101
+ }
102
+ if(dataValue === 64)
103
+ {
104
+ return;
105
+ }
106
+ addDefaultVibrato();
107
+ channelObject.channelVibrato.depth = dataValue / 2;
108
+ SpessaSynthInfo(`%cVibrato depth for %c${channel}%c is now set to %c${channelObject.channelVibrato.depth}%c cents range of detune.`,
109
+ consoleColors.info,
110
+ consoleColors.recognized,
111
+ consoleColors.info,
112
+ consoleColors.value,
113
+ consoleColors.info);
114
+ break;
115
+
116
+ // vibrato delay
117
+ case 0x0A:
118
+ if(channelObject.lockVibrato)
119
+ {
120
+ return;
121
+ }
122
+ if(dataValue === 64)
123
+ {
124
+ return;
125
+ }
126
+ addDefaultVibrato();
127
+ channelObject.channelVibrato.delay = (dataValue / 64) / 3;
128
+ SpessaSynthInfo(`%cVibrato delay for %c${channel}%c is now set to %c${channelObject.channelVibrato.delay}%c seconds.`,
129
+ consoleColors.info,
130
+ consoleColors.recognized,
131
+ consoleColors.info,
132
+ consoleColors.value,
133
+ consoleColors.info);
134
+ break;
135
+
136
+ // filter cutoff
137
+ case 0x20:
138
+ // affect the "brightness" controller as we have a default modulator that controls it
139
+ const ccValue = dataValue;
140
+ this.controllerChange(channel, midiControllers.brightness, dataValue)
141
+ SpessaSynthInfo(`%cFilter cutoff for %c${channel}%c is now set to %c${ccValue}`,
142
+ consoleColors.info,
143
+ consoleColors.recognized,
144
+ consoleColors.info,
145
+ consoleColors.value);
146
+ }
147
+ break;
148
+
149
+ // drum reverb
150
+ case 0x1D:
151
+ if(!channelObject.percussionChannel)
152
+ {
153
+ return;
154
+ }
155
+ const reverb = dataValue;
156
+ this.controllerChange(channel, midiControllers.effects1Depth, reverb);
157
+ SpessaSynthInfo(
158
+ `%cGS Drum reverb for %c${channel}%c: %c${reverb}`,
159
+ consoleColors.info,
160
+ consoleColors.recognized,
161
+ consoleColors.info,
162
+ consoleColors.value);
163
+ break;
164
+
165
+ // drum chorus
166
+ case 0x1E:
167
+ if(!channelObject.percussionChannel)
168
+ {
169
+ return;
170
+ }
171
+ const chorus = dataValue;
172
+ this.controllerChange(channel, midiControllers.effects3Depth, chorus);
173
+ SpessaSynthInfo(
174
+ `%cGS Drum chorus for %c${channel}%c: %c${chorus}`,
175
+ consoleColors.info,
176
+ consoleColors.recognized,
177
+ consoleColors.info,
178
+ consoleColors.value);
179
+ }
180
+ break;
181
+
182
+ case dataEntryStates.RPCoarse:
183
+ case dataEntryStates.RPFine:
184
+ switch(channelObject.RPValue)
185
+ {
186
+ default:
187
+ SpessaSynthWarn(
188
+ `%cUnrecognized RPN for %c${channel}%c: %c(0x${channelObject.RPValue.toString(16)})%c data value: %c${dataValue}`,
189
+ consoleColors.warn,
190
+ consoleColors.recognized,
191
+ consoleColors.warn,
192
+ consoleColors.unrecognized,
193
+ consoleColors.warn,
194
+ consoleColors.value);
195
+ break;
196
+
197
+ // pitch bend range
198
+ case 0x0000:
199
+ channelObject.midiControllers[NON_CC_INDEX_OFFSET + modulatorSources.pitchWheelRange] = dataValue << 7;
200
+ SpessaSynthInfo(`%cChannel ${channel} bend range. Semitones: %c${dataValue}`,
201
+ consoleColors.info,
202
+ consoleColors.value);
203
+ break;
204
+
205
+ // coarse tuning
206
+ case 0x0002:
207
+ // semitones
208
+ this.setChannelTuning(channel, (dataValue - 64) * 100);
209
+ break;
210
+
211
+ // fine tuning
212
+ case 0x0001:
213
+ // note: this will not work properly unless the lsb is sent!
214
+ // here we store the raw value to then adjust in fine
215
+ this.setChannelTuning(channel, (dataValue - 64));
216
+ break;
217
+
218
+ // modulation depth
219
+ case 0x0005:
220
+ this.setModulationDepth(channel, dataValue * 100);
221
+ break
222
+
223
+ case 0x3FFF:
224
+ this.resetParameters(channel);
225
+ break;
226
+
227
+ }
228
+
229
+ }
230
+ }
231
+
232
+ /**
233
+ * Executes a data entry for an RPN tuning
234
+ * @param channel {number}
235
+ * @param dataValue {number} dataEntry LSB
236
+ * @this {Synthesizer}
237
+ * @private
238
+ */
239
+ export function dataEntryFine(channel, dataValue)
240
+ {
241
+ const channelObject = this.workletProcessorChannels[channel];
242
+ switch (channelObject.dataEntryState)
243
+ {
244
+ default:
245
+ break;
246
+
247
+ case dataEntryStates.RPCoarse:
248
+ case dataEntryStates.RPFine:
249
+ switch(channelObject.RPValue)
250
+ {
251
+ default:
252
+ break;
253
+
254
+ // pitch bend range fine tune is not supported in the SoundFont2 format. (pitchbend range is in semitones rather than cents)
255
+ case 0x0000:
256
+ break;
257
+
258
+ // fine tuning
259
+ case 0x0001:
260
+ // grab the data and shift
261
+ const coarse = channelObject.customControllers[customControllers.channelTuning];
262
+ const finalTuning = (coarse << 7) | dataValue;
263
+ this.setChannelTuning(channel, finalTuning * 0.0122); // multiply by 8192 / 100 (cent increment)
264
+ break;
265
+
266
+ // modulation depth
267
+ case 0x0005:
268
+ const currentModulationDepthCents = channelObject.customControllers[customControllers.modulationMultiplier] * 50;
269
+ let cents = currentModulationDepthCents + (dataValue / 128) * 100;
270
+ this.setModulationDepth(channel, cents);
271
+ break
272
+
273
+ case 0x3FFF:
274
+ this.resetParameters(channel);
275
+ break;
276
+
277
+ }
278
+
279
+ }
280
+ }
@@ -0,0 +1,102 @@
1
+ import { generatorTypes } from '../../../soundfont/chunk/generators.js'
2
+ import { consoleColors } from '../../../utils/other.js'
3
+ import { SpessaSynthInfo, SpessaSynthWarn } from '../../../utils/loggin.js'
4
+
5
+ /**
6
+ * Release a note
7
+ * @param channel {number}
8
+ * @param midiNote {number}
9
+ * @this {Synthesizer}
10
+ */
11
+ export function noteOff(channel, midiNote)
12
+ {
13
+ if(midiNote > 127 || midiNote < 0)
14
+ {
15
+ SpessaSynthWarn(`Received a noteOn for note`, midiNote, "Ignoring.");
16
+ return;
17
+ }
18
+
19
+ // if high performance mode, kill notes instead of stopping them
20
+ if(this.highPerformanceMode)
21
+ {
22
+ // if the channel is percussion channel, do not kill the notes
23
+ if(!this.workletProcessorChannels[channel].drumChannel)
24
+ {
25
+ this.killNote(channel, midiNote);
26
+ return;
27
+ }
28
+ }
29
+
30
+ const channelVoices = this.workletProcessorChannels[channel].voices;
31
+ channelVoices.forEach(v => {
32
+ if(v.midiNote !== midiNote || v.isInRelease === true)
33
+ {
34
+ return;
35
+ }
36
+ // if hold pedal, move to sustain
37
+ if(this.workletProcessorChannels[channel].holdPedal) {
38
+ this.workletProcessorChannels[channel].sustainedVoices.push(v);
39
+ }
40
+ else
41
+ {
42
+ this.releaseVoice(v);
43
+ }
44
+ });
45
+ }
46
+
47
+ /**
48
+ * Stops a note nearly instantly
49
+ * @param channel {number}
50
+ * @param midiNote {number}
51
+ * @this {Synthesizer}
52
+ */
53
+ export function killNote(channel, midiNote)
54
+ {
55
+ this.workletProcessorChannels[channel].voices.forEach(v => {
56
+ if(v.midiNote !== midiNote)
57
+ {
58
+ return;
59
+ }
60
+ v.modulatedGenerators[generatorTypes.releaseVolEnv] = -12000; // set release to be very short
61
+ this.releaseVoice(v);
62
+ });
63
+ }
64
+
65
+ /**
66
+ * stops all notes
67
+ * @param channel {number}
68
+ * @param force {boolean}
69
+ * @this {Synthesizer}
70
+ */
71
+ export function stopAll(channel, force = false)
72
+ {
73
+ const channelVoices = this.workletProcessorChannels[channel].voices;
74
+ if(force)
75
+ {
76
+ // force stop all
77
+ channelVoices.length = 0;
78
+ this.workletProcessorChannels[channel].sustainedVoices.length = 0;
79
+ }
80
+ else
81
+ {
82
+ channelVoices.forEach(v => {
83
+ if(v.isInRelease) return;
84
+ this.releaseVoice(v);
85
+ });
86
+ this.workletProcessorChannels[channel].sustainedVoices.forEach(v => {
87
+ this.releaseVoice(v);
88
+ })
89
+ }
90
+ }
91
+
92
+ /**
93
+ * @this {Synthesizer}
94
+ * @param force {boolean}
95
+ */
96
+ export function stopAllChannels(force = false)
97
+ {
98
+ SpessaSynthInfo("%cStop all received!", consoleColors.info);
99
+ for (let i = 0; i < this.workletProcessorChannels.length; i++) {
100
+ this.stopAll(i, force);
101
+ }
102
+ }
@@ -0,0 +1,75 @@
1
+ import { getWorkletVoices } from '../worklet_utilities/worklet_voice.js'
2
+ import { generatorTypes } from '../../../soundfont/chunk/generators.js'
3
+ import { computeModulators } from '../worklet_utilities/worklet_modulator.js'
4
+ import { VOICE_CAP } from "../../synthesizer.js";
5
+ import { SpessaSynthWarn } from '../../../utils/loggin.js'
6
+
7
+ /**
8
+ * Append the voices
9
+ * @param channel {number}
10
+ * @param midiNote {number}
11
+ * @param velocity {number}
12
+ * @param enableDebugging {boolean}
13
+ * @this {Synthesizer}
14
+ */
15
+ export function noteOn(channel, midiNote, velocity, enableDebugging = false)
16
+ {
17
+ if (velocity === 0) {
18
+ this.noteOff(channel, midiNote);
19
+ return;
20
+ }
21
+
22
+ if (
23
+ (this.highPerformanceMode && this.totalVoicesAmount > 200 && velocity < 40) ||
24
+ (this.highPerformanceMode && velocity < 10) ||
25
+ (this.workletProcessorChannels[channel].isMuted)
26
+ ) {
27
+ return;
28
+ }
29
+
30
+ if(midiNote > 127 || midiNote < 0)
31
+ {
32
+ SpessaSynthWarn(`Received a noteOn for note`, midiNote, "Ignoring.");
33
+ return;
34
+ }
35
+
36
+
37
+ // get voices
38
+ const voices = getWorkletVoices(
39
+ channel,
40
+ midiNote,
41
+ velocity,
42
+ this.workletProcessorChannels[channel].preset,
43
+ this.currentTime,
44
+ this.sampleRate,
45
+ data => this.sampleDump(data.channel, data.sampleID, data.sampleData),
46
+ this.workletProcessorChannels[channel].cachedVoices,
47
+ enableDebugging);
48
+
49
+ // add voices and exclusive class apply
50
+ const channelVoices = this.workletProcessorChannels[channel].voices;
51
+ voices.forEach(voice => {
52
+ const exclusive = voice.generators[generatorTypes.exclusiveClass];
53
+ if(exclusive !== 0)
54
+ {
55
+ channelVoices.forEach(v => {
56
+ if(v.generators[generatorTypes.exclusiveClass] === exclusive)
57
+ {
58
+ this.releaseVoice(v);
59
+ v.generators[generatorTypes.releaseVolEnv] = -7200; // make the release nearly instant
60
+ computeModulators(v, this.workletProcessorChannels[channel].midiControllers);
61
+ }
62
+ })
63
+ }
64
+ computeModulators(voice, this.workletProcessorChannels[channel].midiControllers);
65
+ voice.currentAttenuationDb = 100;
66
+ })
67
+ channelVoices.push(...voices);
68
+
69
+ this.totalVoicesAmount += voices.length;
70
+ // cap the voices
71
+ if(this.totalVoicesAmount > VOICE_CAP)
72
+ {
73
+ this.voiceKilling(this.totalVoicesAmount - VOICE_CAP);
74
+ }
75
+ }
@@ -0,0 +1,140 @@
1
+ import { midiControllers } from '../../../midi_parser/midi_message.js'
2
+ import { SoundFont2 } from '../../../soundfont/soundfont_parser.js'
3
+ import { clearSamplesList } from '../worklet_utilities/worklet_voice.js'
4
+ import { generatorTypes } from '../../../soundfont/chunk/generators.js'
5
+
6
+ /**
7
+ * executes a program change
8
+ * @param channel {number}
9
+ * @param programNumber {number}
10
+ * @param userChange {boolean}
11
+ * @this {Synthesizer}
12
+ */
13
+ export function programChange(channel, programNumber, userChange=false)
14
+ {
15
+ /**
16
+ * @type {WorkletProcessorChannel}
17
+ */
18
+ const channelObject = this.workletProcessorChannels[channel];
19
+ if(channelObject.lockPreset)
20
+ {
21
+ return;
22
+ }
23
+ // always 128 for percussion
24
+ const bank = (channelObject.drumChannel ? 128 : channelObject.midiControllers[midiControllers.bankSelect]);
25
+ const preset = this.soundfont.getPreset(bank, programNumber);
26
+ this.setPreset(channel, preset);
27
+ }
28
+
29
+ /**
30
+ * @param channel {number}
31
+ * @param preset {Preset}
32
+ * @this {Synthesizer}
33
+ */
34
+ export function setPreset(channel, preset)
35
+ {
36
+ if(this.workletProcessorChannels[channel].lockPreset)
37
+ {
38
+ return;
39
+ }
40
+ this.workletProcessorChannels[channel].preset = preset;
41
+
42
+ // reset cached voices
43
+ this.workletProcessorChannels[channel].cachedVoices = [];
44
+ for (let i = 0; i < 128; i++) {
45
+ this.workletProcessorChannels[channel].cachedVoices.push([]);
46
+ }
47
+ }
48
+
49
+ /**
50
+ * Toggles drums on a given channel
51
+ * @param channel {number}
52
+ * @param isDrum {boolean}
53
+ * @this {Synthesizer}
54
+ */
55
+ export function setDrums(channel, isDrum)
56
+ {
57
+ const channelObject = this.workletProcessorChannels[channel];
58
+ if(isDrum)
59
+ {
60
+ channelObject.drumChannel = true;
61
+ this.setPreset(channel, this.soundfont.getPreset(128, channelObject.preset.program));
62
+ }
63
+ else
64
+ {
65
+ channelObject.percussionChannel = false;
66
+ this.setPreset(channel, this.soundfont.getPreset(0, channelObject.preset.program));
67
+ }
68
+ }
69
+
70
+ /**
71
+ * @param buffer {ArrayBuffer}
72
+ * @this {Synthesizer}
73
+ */
74
+ export function reloadSoundFont(buffer)
75
+ {
76
+ this.stopAllChannels(true);
77
+ delete this.soundfont;
78
+ clearSamplesList();
79
+ delete this.workletDumpedSamplesList;
80
+ this.workletDumpedSamplesList = [];
81
+
82
+
83
+ this.soundfont = new SoundFont2(buffer);
84
+ this.defaultPreset = this.soundfont.getPreset(0, 0);
85
+ this.drumPreset = this.soundfont.getPreset(128, 0);
86
+
87
+ for(let i = 0; i < this.workletProcessorChannels.length; i++)
88
+ {
89
+ const channelObject = this.workletProcessorChannels[i];
90
+ channelObject.cachedVoices = [];
91
+ for (let j = 0; j < 128; j++) {
92
+ channelObject.cachedVoices.push([]);
93
+ }
94
+ channelObject.lockPreset = false;
95
+ this.programChange(i, channelObject.preset.program);
96
+ }
97
+ }
98
+
99
+ /**
100
+ * saves a sample
101
+ * @param channel {number}
102
+ * @param sampleID {number}
103
+ * @param sampleData {Float32Array}
104
+ * @this {Synthesizer}
105
+ */
106
+ export function sampleDump(channel, sampleID, sampleData)
107
+ {
108
+ this.workletDumpedSamplesList[sampleID] = sampleData;
109
+ // the sample maybe was loaded after the voice was sent... adjust the end position!
110
+
111
+ // not for all channels because the system tells us for what channel this voice was dumped! yay!
112
+ this.workletProcessorChannels[channel].voices.forEach(v => {
113
+ if(v.sample.sampleID !== sampleID)
114
+ {
115
+ return;
116
+ }
117
+ v.sample.end = sampleData.length - 1 + v.generators[generatorTypes.endAddrOffset] + (v.generators[generatorTypes.endAddrsCoarseOffset] * 32768);
118
+ // calculate for how long the sample has been playing and move the cursor there
119
+ v.sample.cursor = (v.sample.playbackStep * this.sampleRate) * (this.currentTime - v.startTime);
120
+ if(v.sample.loopingMode === 0) // no loop
121
+ {
122
+ if (v.sample.cursor >= v.sample.end)
123
+ {
124
+ v.finished = true;
125
+ return;
126
+ }
127
+ }
128
+ else
129
+ {
130
+ // go through modulo (adjust cursor if the sample has looped
131
+ if(v.sample.cursor > v.sample.loopEnd)
132
+ {
133
+ v.sample.cursor = v.sample.cursor % (v.sample.loopEnd - v.sample.loopStart) + v.sample.loopStart - 1;
134
+ }
135
+ }
136
+ // set start time to current!
137
+ v.startTime = this.currentTime;
138
+ })
139
+
140
+ }