spessasynth_core 3.26.42 → 3.27.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.
package/README.md CHANGED
@@ -6,10 +6,10 @@
6
6
  **A powerful SF2/DLS/MIDI JavaScript library. It works with any modern JS environment that supports WebAssembly.**
7
7
 
8
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!)
9
+ - Play MIDI files using SF2/SF3/DLS files!
10
+ - Read and write MIDI files!
11
+ - Write SF2/SF3 files!
12
+ - Convert DLS to SF2! (and back!)
13
13
  - [and more!](https://github.com/spessasus/spessasynth_core?tab=readme-ov-file#current-features)
14
14
  > **TIP:**
15
15
  > Looking for an easy-to-use WebAudioAPI browser wrapper? Try [spessasynth_lib](https://github.com/spessasus/spessasynth_lib)!
@@ -18,20 +18,23 @@ It allows you to:
18
18
  npm install --save spessasynth_core
19
19
  ```
20
20
 
21
- ### [Project site (consider giving it a star!)](https://github.com/spessasus/SpessaSynth)
21
+ ### [Project site (consider giving it a star!)](https://github.com/spessasus/spessasynth_core)
22
22
 
23
- ### [Demo (using the spessasynth_lib wrapper)](https://spessasus.github.io/SpessaSynth)
23
+ ### Made with spessasynth_core
24
+ - [SpessaSynth Online SF2/DLS MIDI Player](https://spessasus.github.io/SpessaSynth)
25
+ - [SpessaFont Online SoundFont/DLS Editor](https://spessasus.github.io/SpessaFont)
24
26
 
25
27
  ### [Documentation (in progress!)](https://github.com/spessasus/spessasynth_core/wiki/Home)
26
28
 
27
29
 
28
30
  > Note: This is the new heart of the SpessaSynth library, after the repository has been split.
29
31
 
30
- **Project index**
32
+ **SpessaSynth Project index**
31
33
 
32
- - spessasynth_core (you are here) - SF2/DLS/MIDI library
34
+ - [spessasynth_core](https://github.com/spessasus/spessasynth_core) (you are here) - SF2/DLS/MIDI library
33
35
  - [spessasynth_lib](https://github.com/spessasus/spessasynth_lib) - spessasynth_core wrapper optimized for browsers and WebAudioAPI
34
- - [SpessaSynth](https://github.com/spessasus/SpessaSynth) - online/local web player/editor application
36
+ - [SpessaSynth](https://github.com/spessasus/SpessaSynth) - online/local MIDI player/editor application
37
+ - [SpessaFont](https://github.com/spessasus/SpessaFont) - online SF2/DLS editor
35
38
 
36
39
  ## Current Features
37
40
 
package/index.js CHANGED
@@ -21,7 +21,7 @@ import { SynthesizerSnapshot } from "./src/synthetizer/audio_engine/snapshot/syn
21
21
  import { ChannelSnapshot } from "./src/synthetizer/audio_engine/snapshot/channel_snapshot.js";
22
22
 
23
23
  import { BasicSoundBank } from "./src/soundfont/basic_soundfont/basic_soundbank.js";
24
- import { BasicSample, sampleTypes } from "./src/soundfont/basic_soundfont/basic_sample.js";
24
+ import { BasicSample, CreatedSample, sampleTypes } from "./src/soundfont/basic_soundfont/basic_sample.js";
25
25
  import { BasicPresetZone } from "./src/soundfont/basic_soundfont/basic_preset_zone.js";
26
26
  import { BasicInstrument } from "./src/soundfont/basic_soundfont/basic_instrument.js";
27
27
  import { BasicPreset } from "./src/soundfont/basic_soundfont/basic_preset.js";
@@ -98,6 +98,7 @@ export {
98
98
  BasicZone,
99
99
  BasicGlobalZone,
100
100
  BasicSample,
101
+ CreatedSample,
101
102
  BasicInstrumentZone,
102
103
  BasicInstrument,
103
104
  BasicPreset,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "spessasynth_core",
3
- "version": "3.26.42",
3
+ "version": "3.27.0",
4
4
  "description": "MIDI and SoundFont2/DLS library with no compromises",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -6,7 +6,11 @@ import { stbvorbis } from "../../externals/stbvorbis_sync/stbvorbis_sync.min.js"
6
6
  const RESAMPLE_RATE = 48000;
7
7
 
8
8
  /**
9
- * @enum {number}
9
+ * @typedef {1|2|4|8|32769|32770|32772|32776} sampleTypes
10
+ */
11
+
12
+ /**
13
+ * @enum {sampleTypes}
10
14
  */
11
15
  export const sampleTypes = {
12
16
  monoSample: 1,
@@ -113,7 +117,7 @@ export class BasicSample
113
117
  * @param sampleRate {number} The sample's rate in Hz
114
118
  * @param samplePitch {number} The sample's pitch as a MIDI note number
115
119
  * @param samplePitchCorrection {number} The sample's pitch correction in cents
116
- * @param sampleType {sampleTypes|number} The sample's type, an enum that can indicate SF3
120
+ * @param sampleType {sampleTypes} The sample's type, an enum that can indicate SF3
117
121
  * @param loopStart {number} The sample's loop start relative to the sample start in sample points
118
122
  * @param loopEnd {number} The sample's loop end relative to the sample start in sample points
119
123
  */
@@ -437,4 +441,37 @@ export class BasicSample
437
441
  this.isCompressed = true;
438
442
  this.dataOverriden = false;
439
443
  }
444
+ }
445
+
446
+
447
+ export class CreatedSample extends BasicSample
448
+ {
449
+ /**
450
+ * A simplified class for creating samples
451
+ * @param name
452
+ * @param sampleRate
453
+ * @param sampleData
454
+ */
455
+ constructor(name, sampleRate, sampleData)
456
+ {
457
+ super(name, sampleRate, 60, 0, sampleTypes.monoSample, 0, sampleData.length - 1);
458
+ this.setAudioData(sampleData);
459
+ }
460
+
461
+ /**
462
+ * @param allowVorbis {boolean}
463
+ * @returns {Uint8Array}
464
+ */
465
+ getRawData(allowVorbis)
466
+ {
467
+ return super.getRawData(allowVorbis);
468
+ }
469
+
470
+ /**
471
+ * @param audioData {Float32Array}
472
+ */
473
+ setAudioData(audioData)
474
+ {
475
+ super.setAudioData(audioData);
476
+ }
440
477
  }
@@ -9,7 +9,7 @@ import { consoleColors } from "../../utils/other.js";
9
9
  import { write } from "./write_sf2/write.js";
10
10
  import { defaultModulators, Modulator } from "./modulator.js";
11
11
  import { writeDLS } from "./write_dls/write_dls.js";
12
- import { BasicSample } from "./basic_sample.js";
12
+ import { BasicSample, sampleTypes } from "./basic_sample.js";
13
13
  import { Generator } from "./generator.js";
14
14
  import { BasicInstrument } from "./basic_instrument.js";
15
15
  import { BasicPreset } from "./basic_preset.js";
@@ -158,7 +158,7 @@ class BasicSoundBank
158
158
  44100,
159
159
  65,
160
160
  20,
161
- 0,
161
+ sampleTypes.monoSample,
162
162
  0,
163
163
  127
164
164
  );
@@ -11,7 +11,7 @@ import {
11
11
  resetParameters
12
12
  } from "../engine_methods/controller_control/reset_controllers.js";
13
13
  import { renderVoice } from "../engine_methods/render_voice.js";
14
- import { panVoice } from "./stereo_panner.js";
14
+ import { panAndMixVoice } from "./stereo_panner.js";
15
15
  import { killNote } from "../engine_methods/stopping_notes/kill_note.js";
16
16
  import { setTuning } from "../engine_methods/tuning_control/set_tuning.js";
17
17
  import { setModulationDepth } from "../engine_methods/tuning_control/set_modulation_depth.js";
@@ -273,18 +273,22 @@ class MidiAudioChannel
273
273
  * @param reverbOutputRight {Float32Array} right output for reverb
274
274
  * @param chorusOutputLeft {Float32Array} left output for chorus
275
275
  * @param chorusOutputRight {Float32Array} right output for chorus
276
+ * @param startIndex {number}
277
+ * @param sampleCount {number}
276
278
  */
277
279
  renderAudio(
278
280
  outputLeft, outputRight,
279
281
  reverbOutputLeft, reverbOutputRight,
280
- chorusOutputLeft, chorusOutputRight
282
+ chorusOutputLeft, chorusOutputRight,
283
+ startIndex, sampleCount
281
284
  )
282
285
  {
283
286
  this.voices = this.voices.filter(v => !this.renderVoice(
284
287
  v, this.synth.currentSynthTime,
285
288
  outputLeft, outputRight,
286
289
  reverbOutputLeft, reverbOutputRight,
287
- chorusOutputLeft, chorusOutputRight
290
+ chorusOutputLeft, chorusOutputRight,
291
+ startIndex, sampleCount
288
292
  ));
289
293
  }
290
294
 
@@ -516,7 +520,7 @@ class MidiAudioChannel
516
520
 
517
521
  // voice
518
522
  MidiAudioChannel.prototype.renderVoice = renderVoice;
519
- MidiAudioChannel.prototype.panVoice = panVoice;
523
+ MidiAudioChannel.prototype.panAndMixVoice = panAndMixVoice;
520
524
  MidiAudioChannel.prototype.killNote = killNote;
521
525
  MidiAudioChannel.prototype.stopAllNotes = stopAllNotes;
522
526
  MidiAudioChannel.prototype.muteChannel = muteChannel;
@@ -125,7 +125,7 @@ export class SoundFontManager
125
125
 
126
126
  // noinspection JSUnusedGlobalSymbols
127
127
  /**
128
- * Adds a new soundfont with a given ID
128
+ * Adds a new soundfont with a given ID, or replaces an existing one.
129
129
  * @param font {BasicSoundBank}
130
130
  * @param id {string}
131
131
  * @param bankOffset {number}
@@ -38,13 +38,15 @@ for (let pan = MIN_PAN; pan <= MAX_PAN; pan++)
38
38
  * @param reverbRight {Float32Array} right reverb input
39
39
  * @param chorusLeft {Float32Array} left chorus buffer
40
40
  * @param chorusRight {Float32Array} right chorus buffer
41
+ * @param startIndex {number}
41
42
  * @this {MidiAudioChannel}
42
43
  */
43
- export function panVoice(voice,
44
- inputBuffer,
45
- outputLeft, outputRight,
46
- reverbLeft, reverbRight,
47
- chorusLeft, chorusRight)
44
+ export function panAndMixVoice(voice,
45
+ inputBuffer,
46
+ outputLeft, outputRight,
47
+ reverbLeft, reverbRight,
48
+ chorusLeft, chorusRight,
49
+ startIndex)
48
50
  {
49
51
  if (isNaN(inputBuffer[0]))
50
52
  {
@@ -82,10 +84,10 @@ export function panVoice(voice,
82
84
  const reverbGain = this.synth.reverbGain * this.synth.reverbSend * gain * (reverbSend / REVERB_DIVIDER);
83
85
  for (let i = 0; i < inputBuffer.length; i++)
84
86
  {
85
- reverbLeft[i] += reverbGain * inputBuffer[i];
87
+ const idx = i + startIndex;
88
+ reverbLeft[idx] += reverbGain * inputBuffer[i];
89
+ reverbRight[idx] += reverbGain * inputBuffer[i];
86
90
  }
87
- // copy as its mono
88
- reverbRight.set(reverbLeft);
89
91
  }
90
92
 
91
93
  const chorusSend = voice.modulatedGenerators[generatorTypes.chorusEffectsSend];
@@ -97,8 +99,9 @@ export function panVoice(voice,
97
99
  const chorusRightGain = gainRight * chorusGain;
98
100
  for (let i = 0; i < inputBuffer.length; i++)
99
101
  {
100
- chorusLeft[i] += chorusLeftGain * inputBuffer[i];
101
- chorusRight[i] += chorusRightGain * inputBuffer[i];
102
+ const idx = i + startIndex;
103
+ chorusLeft[idx] += chorusLeftGain * inputBuffer[i];
104
+ chorusRight[idx] += chorusRightGain * inputBuffer[i];
102
105
  }
103
106
  }
104
107
  }
@@ -108,14 +111,14 @@ export function panVoice(voice,
108
111
  {
109
112
  for (let i = 0; i < inputBuffer.length; i++)
110
113
  {
111
- outputLeft[i] += gainLeft * inputBuffer[i];
114
+ outputLeft[i + startIndex] += gainLeft * inputBuffer[i];
112
115
  }
113
116
  }
114
117
  if (gainRight > 0)
115
118
  {
116
119
  for (let i = 0; i < inputBuffer.length; i++)
117
120
  {
118
- outputRight[i] += gainRight * inputBuffer[i];
121
+ outputRight[i + startIndex] += gainRight * inputBuffer[i];
119
122
  }
120
123
  }
121
124
  }
@@ -18,6 +18,8 @@ import { generatorTypes } from "../../../soundfont/basic_soundfont/generator_typ
18
18
  * @param reverbOutputRight {Float32Array} right output for reverb
19
19
  * @param chorusOutputLeft {Float32Array} left output for chorus
20
20
  * @param chorusOutputRight {Float32Array} right output for chorus
21
+ * @param startIndex {number}
22
+ * @param sampleCount {number}
21
23
  * @this {MidiAudioChannel}
22
24
  * @returns {boolean} true if the voice is finished
23
25
  */
@@ -25,7 +27,8 @@ export function renderVoice(
25
27
  voice, timeNow,
26
28
  outputLeft, outputRight,
27
29
  reverbOutputLeft, reverbOutputRight,
28
- chorusOutputLeft, chorusOutputRight
30
+ chorusOutputLeft, chorusOutputRight,
31
+ startIndex, sampleCount
29
32
  )
30
33
  {
31
34
  // check if release
@@ -168,7 +171,7 @@ export function renderVoice(
168
171
 
169
172
 
170
173
  // SYNTHESIS
171
- const bufferOut = new Float32Array(outputLeft.length);
174
+ const bufferOut = new Float32Array(sampleCount);
172
175
 
173
176
  // wave table oscillator
174
177
  switch (this.synth.interpolationType)
@@ -191,14 +194,20 @@ export function renderVoice(
191
194
  LowpassFilter.apply(voice, bufferOut, lowpassExcursion, this.synth.filterSmoothingFactor);
192
195
 
193
196
  // vol env
194
- VolumeEnvelope.apply(voice, bufferOut, volumeExcursionCentibels, this.synth.volumeEnvelopeSmoothingFactor);
197
+ VolumeEnvelope.apply(
198
+ voice,
199
+ bufferOut,
200
+ volumeExcursionCentibels,
201
+ this.synth.volumeEnvelopeSmoothingFactor
202
+ );
195
203
 
196
- this.panVoice(
204
+ this.panAndMixVoice(
197
205
  voice,
198
206
  bufferOut,
199
207
  outputLeft, outputRight,
200
208
  reverbOutputLeft, reverbOutputRight,
201
- chorusOutputLeft, chorusOutputRight
209
+ chorusOutputLeft, chorusOutputRight,
210
+ startIndex
202
211
  );
203
212
  return voice.finished;
204
213
  }
@@ -24,7 +24,6 @@ export function setEmbeddedSoundFont(font, offset)
24
24
  {
25
25
  // the embedded bank is set as the first bank in the manager,
26
26
  // with a special ID that does not clear when reloadManager is performed.
27
- this.soundfontBankOffset = offset;
28
27
  const loadedFont = loadSoundFont(font);
29
28
  this.soundfontManager.addNewSoundFont(loadedFont, EMBEDDED_SOUND_BANK_ID, offset);
30
29
  // rearrange so the embedded is first (most important as it overrides all others)
@@ -172,6 +172,12 @@ export const SYNTHESIZER_GAIN = 1.0;
172
172
  class SpessaSynthProcessor
173
173
  {
174
174
 
175
+ /**
176
+ * Manages sound banks.
177
+ * @type {SoundFontManager}
178
+ */
179
+ soundfontManager = new SoundFontManager(this.updatePresetList.bind(this));
180
+
175
181
  /**
176
182
  * Cached voices for all presets for this synthesizer.
177
183
  * Nesting goes like this:
@@ -210,13 +216,6 @@ class SpessaSynthProcessor
210
216
  */
211
217
  tunings = [];
212
218
 
213
-
214
- /**
215
- * Bank offset for things like embedded RMIDIS. Added for every program change
216
- * @type {number}
217
- */
218
- soundfontBankOffset = 0;
219
-
220
219
  /**
221
220
  * The volume gain, set by user
222
221
  * @type {number}
@@ -345,6 +344,7 @@ class SpessaSynthProcessor
345
344
  */
346
345
  effectsEnabled;
347
346
 
347
+
348
348
  /**
349
349
  * for applying the snapshot after an override sound bank too
350
350
  * @type {SynthesizerSnapshot}
@@ -408,11 +408,6 @@ class SpessaSynthProcessor
408
408
  this.tunings.push([]);
409
409
  }
410
410
 
411
- /**
412
- * @type {SoundFontManager}
413
- */
414
- this.soundfontManager = new SoundFontManager(this.updatePresetList.bind(this));
415
-
416
411
  for (let i = 0; i < this.midiOutputsCount; i++)
417
412
  {
418
413
  this.createMidiChannel(false);
@@ -496,10 +491,17 @@ class SpessaSynthProcessor
496
491
  * @param outputs {Float32Array[]} output stereo channels (L, R)
497
492
  * @param reverb {Float32Array[]} reverb stereo channels (L, R)
498
493
  * @param chorus {Float32Array[]} chorus stereo channels (L, R)
499
- */
500
- renderAudio(outputs, reverb, chorus)
494
+ * @param startIndex {number} start offset of the passed arrays, rendering starts at this index, defaults to 0
495
+ * @param sampleCount {number} the length of the rendered buffer, defaults to float32array length - startOffset
496
+ */
497
+ renderAudio(outputs,
498
+ reverb,
499
+ chorus,
500
+ startIndex = 0,
501
+ sampleCount = 0
502
+ )
501
503
  {
502
- this.renderAudioSplit(reverb, chorus, Array(16).fill(outputs));
504
+ this.renderAudioSplit(reverb, chorus, Array(16).fill(outputs), startIndex, sampleCount);
503
505
  }
504
506
 
505
507
  /**
@@ -508,8 +510,15 @@ class SpessaSynthProcessor
508
510
  * @param reverbChannels {Float32Array[]} reverb stereo channels (L, R)
509
511
  * @param chorusChannels {Float32Array[]} chorus stereo channels (L, R)
510
512
  * @param separateChannels {Float32Array[][]} a total of 16 stereo pairs (L, R) for each MIDI channel
511
- */
512
- renderAudioSplit(reverbChannels, chorusChannels, separateChannels)
513
+ * @param startIndex {number} start offset of the passed arrays, rendering starts at this index, defaults to 0
514
+ * @param sampleCount {number} the length of the rendered buffer, defaults to float32array length - startOffset
515
+ */
516
+ renderAudioSplit(reverbChannels,
517
+ chorusChannels,
518
+ separateChannels,
519
+ startIndex = 0,
520
+ sampleCount = 0
521
+ )
513
522
  {
514
523
  // process event queue
515
524
  const time = this.currentSynthTime;
@@ -522,6 +531,10 @@ class SpessaSynthProcessor
522
531
  const chrL = chorusChannels[0];
523
532
  const chrR = chorusChannels[1];
524
533
 
534
+ // validate
535
+ startIndex = Math.max(startIndex, 0);
536
+ const quantumSize = sampleCount || separateChannels[0][0].length - startIndex;
537
+
525
538
  // for every channel
526
539
  this.totalVoicesAmount = 0;
527
540
  this.midiAudioChannels.forEach((channel, index) =>
@@ -538,7 +551,8 @@ class SpessaSynthProcessor
538
551
  channel.renderAudio(
539
552
  separateChannels[ch][0], separateChannels[ch][1],
540
553
  revL, revR,
541
- chrL, chrR
554
+ chrL, chrR,
555
+ startIndex, quantumSize
542
556
  );
543
557
 
544
558
  this.totalVoicesAmount += channel.voices.length;
@@ -550,7 +564,7 @@ class SpessaSynthProcessor
550
564
  });
551
565
 
552
566
  // advance the time appropriately
553
- this.currentSynthTime += separateChannels[0][0].length * this.sampleTime;
567
+ this.currentSynthTime += quantumSize * this.sampleTime;
554
568
  }
555
569
 
556
570
  // noinspection JSUnusedGlobalSymbols