spessasynth_core 3.27.1 → 3.27.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "spessasynth_core",
3
- "version": "3.27.1",
3
+ "version": "3.27.3",
4
4
  "description": "MIDI and SoundFont2/DLS library with no compromises",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -28,7 +28,7 @@ export const sampleTypes = {
28
28
  * @async
29
29
  * @param audioData {Float32Array}
30
30
  * @param sampleRate {number}
31
- * @returns {Uint8Array}
31
+ * @returns {Promise<Uint8Array>}
32
32
  */
33
33
 
34
34
  export class BasicSample
@@ -116,9 +116,6 @@ export class SoundFontManager
116
116
  SpessaSynthInfo(`No soundfont with id of "${id}" found. Aborting!`);
117
117
  return;
118
118
  }
119
- delete this.soundfontList[index].soundfont.presets;
120
- delete this.soundfontList[index].soundfont.instruments;
121
- delete this.soundfontList[index].soundfont.samples;
122
119
  this.soundfontList.splice(index, 1);
123
120
  this.generatePresetList();
124
121
  }
@@ -1,3 +1,5 @@
1
+ import { interpolationTypes } from "./enums.js";
2
+
1
3
  /**
2
4
  * wavetable_oscillator.js
3
5
  * purpose: plays back raw audio data at an arbitrary playback rate
@@ -6,13 +8,45 @@
6
8
 
7
9
  export class WavetableOscillator
8
10
  {
11
+ /**
12
+ * Fills the output buffer with raw sample data using a given interpolation
13
+ * @param voice {Voice} the voice we're working on
14
+ * @param outputBuffer {Float32Array} the output buffer to write to
15
+ * @param interpolation {interpolationTypes} the interpolation type
16
+ */
17
+ static getSample(voice, outputBuffer, interpolation)
18
+ {
19
+ const step = voice.currentTuningCalculated * voice.sample.playbackStep;
20
+ // why not?
21
+ if (step === 1)
22
+ {
23
+ WavetableOscillator.getSampleNearest(voice, outputBuffer, step);
24
+ return;
25
+ }
26
+ switch (interpolation)
27
+ {
28
+ case interpolationTypes.fourthOrder:
29
+ this.getSampleHermite(voice, outputBuffer, step);
30
+ return;
31
+
32
+ case interpolationTypes.linear:
33
+ default:
34
+ this.getSampleLinear(voice, outputBuffer, step);
35
+ return;
36
+
37
+ case interpolationTypes.nearestNeighbor:
38
+ WavetableOscillator.getSampleNearest(voice, outputBuffer, step);
39
+ return;
40
+ }
41
+ }
9
42
 
10
43
  /**
11
44
  * Fills the output buffer with raw sample data using linear interpolation
12
45
  * @param voice {Voice} the voice we're working on
13
46
  * @param outputBuffer {Float32Array} the output buffer to write to
47
+ * @param step {number} the step to advance every sample
14
48
  */
15
- static getSampleLinear(voice, outputBuffer)
49
+ static getSampleLinear(voice, outputBuffer, step)
16
50
  {
17
51
  const sample = voice.sample;
18
52
  let cur = sample.cursor;
@@ -45,15 +79,11 @@ export class WavetableOscillator
45
79
  const lower = sampleData[floor];
46
80
  outputBuffer[i] = (lower + (upper - lower) * fraction);
47
81
 
48
- cur += sample.playbackStep * voice.currentTuningCalculated;
82
+ cur += step;
49
83
  }
50
84
  }
51
85
  else
52
86
  {
53
- if (sample.loopingMode === 2 && !voice.isInRelease)
54
- {
55
- return;
56
- }
57
87
  for (let i = 0; i < outputBuffer.length; i++)
58
88
  {
59
89
 
@@ -75,7 +105,7 @@ export class WavetableOscillator
75
105
  const lower = sampleData[floor];
76
106
  outputBuffer[i] = (lower + (upper - lower) * fraction);
77
107
 
78
- cur += sample.playbackStep * voice.currentTuningCalculated;
108
+ cur += step;
79
109
  }
80
110
  }
81
111
  voice.sample.cursor = cur;
@@ -85,15 +115,17 @@ export class WavetableOscillator
85
115
  * Fills the output buffer with raw sample data using no interpolation (nearest neighbor)
86
116
  * @param voice {Voice} the voice we're working on
87
117
  * @param outputBuffer {Float32Array} the output buffer to write to
118
+ * @param step {number} the step to advance every sample
88
119
  */
89
- static getSampleNearest(voice, outputBuffer)
120
+ static getSampleNearest(voice, outputBuffer, step)
90
121
  {
91
122
  const sample = voice.sample;
92
123
  let cur = sample.cursor;
93
- const loopLength = sample.loopEnd - sample.loopStart;
94
124
  const sampleData = sample.sampleData;
95
- if (voice.sample.isLooping)
125
+
126
+ if (sample.isLooping)
96
127
  {
128
+ const loopLength = sample.loopEnd - sample.loopStart;
97
129
  for (let i = 0; i < outputBuffer.length; i++)
98
130
  {
99
131
  // check for loop
@@ -111,15 +143,11 @@ export class WavetableOscillator
111
143
  }
112
144
 
113
145
  outputBuffer[i] = sampleData[ceil];
114
- cur += sample.playbackStep * voice.currentTuningCalculated;
146
+ cur += step;
115
147
  }
116
148
  }
117
149
  else
118
150
  {
119
- if (sample.loopingMode === 2 && !voice.isInRelease)
120
- {
121
- return;
122
- }
123
151
  for (let i = 0; i < outputBuffer.length; i++)
124
152
  {
125
153
 
@@ -133,9 +161,8 @@ export class WavetableOscillator
133
161
  return;
134
162
  }
135
163
 
136
- //nearest neighbor (uncomment to use)
137
164
  outputBuffer[i] = sampleData[ceil];
138
- cur += sample.playbackStep * voice.currentTuningCalculated;
165
+ cur += step;
139
166
  }
140
167
  }
141
168
  sample.cursor = cur;
@@ -143,11 +170,12 @@ export class WavetableOscillator
143
170
 
144
171
 
145
172
  /**
146
- * Fills the output buffer with raw sample data using cubic interpolation
173
+ * Fills the output buffer with raw sample data using Hermite interpolation
147
174
  * @param voice {Voice} the voice we're working on
148
175
  * @param outputBuffer {Float32Array} the output buffer to write to
176
+ * @param step {number} the step to advance every sample
149
177
  */
150
- static getSampleCubic(voice, outputBuffer)
178
+ static getSampleHermite(voice, outputBuffer, step)
151
179
  {
152
180
  const sample = voice.sample;
153
181
  let cur = sample.cursor;
@@ -158,21 +186,18 @@ export class WavetableOscillator
158
186
  const loopLength = sample.loopEnd - sample.loopStart;
159
187
  for (let i = 0; i < outputBuffer.length; i++)
160
188
  {
161
- // check for loop
189
+ // check for loop (it can exceed the end point multiple times)
162
190
  while (cur >= sample.loopEnd)
163
191
  {
164
192
  cur -= loopLength;
165
193
  }
166
194
 
167
- // math comes from
168
- // https://stackoverflow.com/questions/1125666/how-do-you-do-bicubic-or-other-non-linear-interpolation-of-re-sampled-audio-da
169
-
170
195
  // grab the 4 points
171
- const y0 = ~~cur; // point before the cursor. twice bitwise not is just a faster Math.floor
196
+ const y0 = ~~cur; // point before the cursor. twice bitwise-not is just a faster Math.floor
172
197
  let y1 = y0 + 1; // point after the cursor
173
- let y2 = y1 + 1; // point 1 after the cursor
174
- let y3 = y2 + 1; // point 2 after the cursor
175
- const t = cur - y0; // the distance from y0 to cursor
198
+ let y2 = y0 + 2; // point 1 after the cursor
199
+ let y3 = y0 + 3; // point 2 after the cursor
200
+ const t = cur - y0; // the distance from y0 to cursor [0;1]
176
201
  // y0 is not handled here
177
202
  // as it's math.floor of cur which is handled above
178
203
  if (y1 >= sample.loopEnd)
@@ -189,40 +214,33 @@ export class WavetableOscillator
189
214
  }
190
215
 
191
216
  // grab the samples
192
- const x0 = sampleData[y0];
193
- const x1 = sampleData[y1];
194
- const x2 = sampleData[y2];
195
- const x3 = sampleData[y3];
217
+ const xm1 = sampleData[y0];
218
+ const x0 = sampleData[y1];
219
+ const x1 = sampleData[y2];
220
+ const x2 = sampleData[y3];
196
221
 
197
222
  // interpolate
198
- // const c0 = x1
199
- const c1 = 0.5 * (x2 - x0);
200
- const c2 = x0 - (2.5 * x1) + (2 * x2) - (0.5 * x3);
201
- const c3 = (0.5 * (x3 - x0)) + (1.5 * (x1 - x2));
202
- outputBuffer[i] = (((((c3 * t) + c2) * t) + c1) * t) + x1;
203
-
223
+ // https://www.musicdsp.org/en/latest/Other/93-hermite-interpollation.html
224
+ const c = (x1 - xm1) * 0.5;
225
+ const v = x0 - x1;
226
+ const w = c + v;
227
+ const a = w + v + (x2 - x0) * 0.5;
228
+ const b = w + a;
229
+ outputBuffer[i] = ((((a * t) - b) * t + c) * t + x0);
204
230
 
205
- cur += sample.playbackStep * voice.currentTuningCalculated;
231
+ cur += step;
206
232
  }
207
233
  }
208
234
  else
209
235
  {
210
- if (sample.loopingMode === 2 && !voice.isInRelease)
211
- {
212
- return;
213
- }
214
236
  for (let i = 0; i < outputBuffer.length; i++)
215
237
  {
216
-
217
- // math comes from
218
- // https://stackoverflow.com/questions/1125666/how-do-you-do-bicubic-or-other-non-linear-interpolation-of-re-sampled-audio-da
219
-
220
238
  // grab the 4 points
221
- const y0 = ~~cur; // point before the cursor. twice bitwise not is just a faster Math.floor
239
+ const y0 = ~~cur; // point before the cursor. twice bitwise-not is just a faster Math.floor
222
240
  let y1 = y0 + 1; // point after the cursor
223
- let y2 = y1 + 1; // point 1 after the cursor
224
- let y3 = y2 + 1; // point 2 after the cursor
225
- const t = cur - y0; // distance from y0 to cursor
241
+ let y2 = y0 + 2; // point 1 after the cursor
242
+ let y3 = y0 + 3; // point 2 after the cursor
243
+ const t = cur - y0; // the distance from y0 to cursor [0;1]
226
244
 
227
245
  // flag as finished if needed
228
246
  if (y1 >= sample.end ||
@@ -234,18 +252,21 @@ export class WavetableOscillator
234
252
  }
235
253
 
236
254
  // grab the samples
237
- const x0 = sampleData[y0];
238
- const x1 = sampleData[y1];
239
- const x2 = sampleData[y2];
240
- const x3 = sampleData[y3];
255
+ const xm1 = sampleData[y0];
256
+ const x0 = sampleData[y1];
257
+ const x1 = sampleData[y2];
258
+ const x2 = sampleData[y3];
241
259
 
242
260
  // interpolate
243
- const c1 = 0.5 * (x2 - x0);
244
- const c2 = x0 - (2.5 * x1) + (2 * x2) - (0.5 * x3);
245
- const c3 = (0.5 * (x3 - x0)) + (1.5 * (x1 - x2));
246
- outputBuffer[i] = (((((c3 * t) + c2) * t) + c1) * t) + x1;
261
+ // https://www.musicdsp.org/en/latest/Other/93-hermite-interpollation.html
262
+ const c = (x1 - xm1) * 0.5;
263
+ const v = x0 - x1;
264
+ const w = c + v;
265
+ const a = w + v + (x2 - x0) * 0.5;
266
+ const b = w + a;
267
+ outputBuffer[i] = ((((a * t) - b) * t + c) * t + x0);
247
268
 
248
- cur += sample.playbackStep * voice.currentTuningCalculated;
269
+ cur += step;
249
270
  }
250
271
  }
251
272
  voice.sample.cursor = cur;
@@ -5,7 +5,6 @@ import { absCentsToHz, timecentsToSeconds } from "../engine_components/unit_conv
5
5
  import { getLFOValue } from "../engine_components/lfo.js";
6
6
  import { WavetableOscillator } from "../engine_components/wavetable_oscillator.js";
7
7
  import { LowpassFilter } from "../engine_components/lowpass_filter.js";
8
- import { interpolationTypes } from "../engine_components/enums.js";
9
8
  import { generatorTypes } from "../../../soundfont/basic_soundfont/generator_types.js";
10
9
 
11
10
  /**
@@ -173,23 +172,22 @@ export function renderVoice(
173
172
  // SYNTHESIS
174
173
  const bufferOut = new Float32Array(sampleCount);
175
174
 
176
- // wave table oscillator
177
- switch (this.synth.interpolationType)
175
+
176
+ // looping mode 2: start on release. process only volEnv
177
+ if (voice.sample.loopingMode === 2 && !voice.isInRelease)
178
178
  {
179
- case interpolationTypes.fourthOrder:
180
- WavetableOscillator.getSampleCubic(voice, bufferOut);
181
- break;
182
-
183
- case interpolationTypes.linear:
184
- default:
185
- WavetableOscillator.getSampleLinear(voice, bufferOut);
186
- break;
187
-
188
- case interpolationTypes.nearestNeighbor:
189
- WavetableOscillator.getSampleNearest(voice, bufferOut);
190
- break;
179
+ VolumeEnvelope.apply(
180
+ voice,
181
+ bufferOut,
182
+ volumeExcursionCentibels,
183
+ this.synth.volumeEnvelopeSmoothingFactor
184
+ );
185
+ return voice.finished;
191
186
  }
192
187
 
188
+ // wave table oscillator
189
+ WavetableOscillator.getSample(voice, bufferOut, this.synth.interpolationType);
190
+
193
191
  // low pass filter
194
192
  LowpassFilter.apply(voice, bufferOut, lowpassExcursion, this.synth.filterSmoothingFactor);
195
193