spessasynth_lib 3.21.9 → 3.21.12

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.
@@ -84,6 +84,11 @@ export class Sequencer {
84
84
  * @private
85
85
  */
86
86
  private _skipToFirstNoteOn;
87
+ /**
88
+ * @type {boolean}
89
+ * @private
90
+ */
91
+ private _preservePlaybackState;
87
92
  /**
88
93
  * Indicates if the sequencer should skip to first note on
89
94
  * @param val {boolean}
@@ -94,6 +99,18 @@ export class Sequencer {
94
99
  * @return {boolean}
95
100
  */
96
101
  get skipToFirstNoteOn(): boolean;
102
+ /**
103
+ * if true,
104
+ * the sequencer will stay paused when seeking or changing the playback rate
105
+ * @param val {boolean}
106
+ */
107
+ set preservePlaybackState(val: boolean);
108
+ /**
109
+ * if true,
110
+ * the sequencer will stay paused when seeking or changing the playback rate
111
+ * @returns {boolean}
112
+ */
113
+ get preservePlaybackState(): boolean;
97
114
  set currentTime(time: number);
98
115
  /**
99
116
  * @returns {number} Current playback time, in seconds
@@ -211,6 +228,11 @@ export type SequencerOptions = {
211
228
  * - if true, the sequencer will automatically start playing the MIDI
212
229
  */
213
230
  autoPlay: boolean | undefined;
231
+ /**
232
+ * - if true,
233
+ * the sequencer will stay paused when seeking or changing the playback rate
234
+ */
235
+ preservePlaybackState: boolean | typeof unescape;
214
236
  };
215
237
  import { MidiData } from "../midi_parser/midi_data.js";
216
238
  import { Synthetizer } from "../synthetizer/synthetizer.js";
@@ -14,6 +14,7 @@ export namespace WorkletSequencerMessageType {
14
14
  let changeSong: number;
15
15
  let getMIDI: number;
16
16
  let setSkipToFirstNote: number;
17
+ let setPreservePlaybackState: number;
17
18
  }
18
19
  export type WorkletSequencerReturnMessageType = number;
19
20
  export namespace WorkletSequencerReturnMessageType {
@@ -292,9 +292,10 @@ export class Synthetizer {
292
292
  reloadSoundFont(soundFontBuffer: ArrayBuffer): Promise<void>;
293
293
  /**
294
294
  * Sends a MIDI Sysex message to the synthesizer
295
- * @param messageData {IndexedByteArray} the message's data (excluding the F0 byte, but including the F7 at the end)
295
+ * @param messageData {number[]|ArrayLike|Uint8Array} the message's data
296
+ * (excluding the F0 byte, but including the F7 at the end)
296
297
  */
297
- systemExclusive(messageData: IndexedByteArray): void;
298
+ systemExclusive(messageData: number[] | ArrayLike<any> | Uint8Array): void;
298
299
  /**
299
300
  * Toggles drums on a given channel
300
301
  * @param channel {number}
@@ -303,9 +304,9 @@ export class Synthetizer {
303
304
  setDrums(channel: number, isDrum: boolean): void;
304
305
  /**
305
306
  * sends a raw MIDI message to the synthesizer
306
- * @param message {ArrayLike<number>} the midi message, each number is a byte
307
+ * @param message {number[]|Uint8Array} the midi message, each number is a byte
307
308
  */
308
- sendMessage(message: ArrayLike<number>): void;
309
+ sendMessage(message: number[] | Uint8Array): void;
309
310
  /**
310
311
  * Updates the reverb processor with a new impulse response
311
312
  * @param buffer {AudioBuffer} the new reverb impulse response
@@ -339,4 +340,3 @@ export type StartRenderingDataConfig = {
339
340
  import { EventHandler } from "./synth_event_handler.js";
340
341
  import { SoundfontManager } from "./synth_soundfont_manager.js";
341
342
  import { FancyChorus } from "./audio_effects/fancy_chorus.js";
342
- import { IndexedByteArray } from "../utils/indexed_array.js";
@@ -16,6 +16,7 @@
16
16
  * @property {number} NRPFine - the current fine value of the Non-Registered Parameter
17
17
  * @property {number} RPValue - the current value of the Registered Parameter
18
18
  *
19
+ * @property {number} bank - the channel's bank number
19
20
  * @property {BasicPreset} preset - the channel's preset
20
21
  * @property {boolean} lockPreset - indicates whether the program on the channel is locked
21
22
  * @property {boolean} presetUsesOverride - indcates if the channel uses a preset from the override soundfont.
@@ -36,6 +37,16 @@
36
37
  * @this {SpessaSynthProcessor}
37
38
  */
38
39
  export function createWorkletChannel(this: SpessaSynthProcessor, sendEvent?: boolean): void;
40
+ /**
41
+ * @param channel {WorkletProcessorChannel}
42
+ * @param bank {number}
43
+ */
44
+ export function setBankSelect(channel: WorkletProcessorChannel, bank: number): void;
45
+ /**
46
+ * @param channel {WorkletProcessorChannel}
47
+ * @returns {number}
48
+ */
49
+ export function getBankSelect(channel: WorkletProcessorChannel): number;
39
50
  /**
40
51
  * This is a channel configuration enum, it is internally sent from Synthetizer via controller change
41
52
  */
@@ -96,6 +107,10 @@ export type WorkletProcessorChannel = {
96
107
  * - the current value of the Registered Parameter
97
108
  */
98
109
  RPValue: number;
110
+ /**
111
+ * - the channel's bank number
112
+ */
113
+ bank: number;
99
114
  /**
100
115
  * - the channel's preset
101
116
  */
package/README.md CHANGED
@@ -43,14 +43,15 @@ document.getElementById("button").onclick = async () =>
43
43
  - **Excellent SoundFont support:**
44
44
  - **Full Generator Support**
45
45
  - **Full Modulator Support:** *First (to my knowledge) JavaScript SoundFont synth with that feature!*
46
+ - **GeneralUserGS Certified:** *[See more here!](https://github.com/mrbumpy409/GeneralUser-GS/blob/main/documentation/README.md)*
46
47
  - **SoundFont3 Support:** Play compressed SoundFonts!
47
48
  - **Experimental SF2Pack Support:** Play soundfonts compressed with BASSMIDI! (*Note: only works with vorbis compression*)
48
49
  - **Can load very large SoundFonts:** up to 4GB! *Note: Only Firefox handles this well; Chromium has a hard-coded memory limit*
49
50
  - **Soundfont manager:** Stack multiple soundfonts!
50
- - **DLS Level 1 and 2 Support:** *internally converted to sf2*
51
+ - **DLS Level 1 and 2 Support:** *works with gm.dls!*
51
52
  - **Reverb and chorus support:** [customizable!](https://github.com/spessasus/SpessaSynth/wiki/Synthetizer-Class#effects-configuration-object)
52
53
  - **Export audio files** using [OfflineAudioContext](https://developer.mozilla.org/en-US/docs/Web/API/OfflineAudioContext)
53
- - **[Custom modulators for additional controllers](https://github.com/spessasus/SpessaSynth/wiki/Modulator-Class#default-modulators):** Why not?
54
+ - **[Custom modulators for additional controllers](https://github.com/spessasus/SpessaSynth/wiki/Modulator-Class#default-modulators):** *Why not?*
54
55
  - **Written using AudioWorklets:**
55
56
  - Runs in a **separate thread** for maximum performance
56
57
  - Supported by all modern browsers
@@ -60,7 +61,6 @@ document.getElementById("button").onclick = async () =>
60
61
  - **MIDI Tuning Standard Support:** [more info here](https://github.com/spessasus/SpessaSynth/wiki/MIDI-Implementation#midi-tuning-standard)
61
62
  - [Full **RPN** and limited **NRPN** support](https://github.com/spessasus/SpessaSynth/wiki/MIDI-Implementation#supported-registered-parameters)
62
63
  - Supports some [**Roland GS** and **Yamaha XG** system exclusives](https://github.com/spessasus/SpessaSynth/wiki/MIDI-Implementation#supported-system-exclusives)
63
-
64
64
  - **High-performance mode:** Play Rush E! _note: may kill your browser ;)_
65
65
 
66
66
  ### Built-in Powerful and Fast Sequencer
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "spessasynth_lib",
3
- "version": "3.21.9",
3
+ "version": "3.21.12",
4
4
  "description": "MIDI and SoundFont2/DLS library with no compromises",
5
5
  "browser": "index.js",
6
6
  "types": "@types/index.d.ts",
@@ -28,6 +28,8 @@ import { DUMMY_MIDI_DATA, MidiData } from "../midi_parser/midi_data.js";
28
28
  * @typedef {Object} SequencerOptions
29
29
  * @property {boolean|undefined} skipToFirstNoteOn - if true, the sequencer will skip to the first note
30
30
  * @property {boolean|undefined} autoPlay - if true, the sequencer will automatically start playing the MIDI
31
+ * @property {boolean|unescape} preservePlaybackState - if true,
32
+ * the sequencer will stay paused when seeking or changing the playback rate
31
33
  */
32
34
 
33
35
  /**
@@ -35,7 +37,8 @@ import { DUMMY_MIDI_DATA, MidiData } from "../midi_parser/midi_data.js";
35
37
  */
36
38
  const DEFAULT_OPTIONS = {
37
39
  skipToFirstNoteOn: true,
38
- autoPlay: true
40
+ autoPlay: true,
41
+ preservePlaybackState: false
39
42
  };
40
43
 
41
44
  export class Sequencer
@@ -138,7 +141,12 @@ export class Sequencer
138
141
  * @type {boolean}
139
142
  * @private
140
143
  */
141
- this._skipToFirstNoteOn = options?.skipToFirstNoteOn ?? true;
144
+ this._skipToFirstNoteOn = options?.skipToFirstNoteOn || true;
145
+ /**
146
+ * @type {boolean}
147
+ * @private
148
+ */
149
+ this._preservePlaybackState = options?.preservePlaybackState || false;
142
150
 
143
151
  if (this._skipToFirstNoteOn === false)
144
152
  {
@@ -146,7 +154,12 @@ export class Sequencer
146
154
  this._sendMessage(WorkletSequencerMessageType.setSkipToFirstNote, false);
147
155
  }
148
156
 
149
- this.loadNewSongList(midiBinaries, options?.autoPlay ?? false);
157
+ if (this._preservePlaybackState === true)
158
+ {
159
+ this._sendMessage(WorkletSequencerMessageType.setPreservePlaybackState, true);
160
+ }
161
+
162
+ this.loadNewSongList(midiBinaries, options?.autoPlay || true);
150
163
 
151
164
  window.addEventListener("beforeunload", this.resetMIDIOut.bind(this));
152
165
  }
@@ -170,6 +183,27 @@ export class Sequencer
170
183
  this._sendMessage(WorkletSequencerMessageType.setSkipToFirstNote, this._skipToFirstNoteOn);
171
184
  }
172
185
 
186
+ /**
187
+ * if true,
188
+ * the sequencer will stay paused when seeking or changing the playback rate
189
+ * @returns {boolean}
190
+ */
191
+ get preservePlaybackState()
192
+ {
193
+ return this._preservePlaybackState;
194
+ }
195
+
196
+ /**
197
+ * if true,
198
+ * the sequencer will stay paused when seeking or changing the playback rate
199
+ * @param val {boolean}
200
+ */
201
+ set preservePlaybackState(val)
202
+ {
203
+ this._preservePlaybackState = val;
204
+ this._sendMessage(WorkletSequencerMessageType.setPreservePlaybackState, val);
205
+ }
206
+
173
207
  /**
174
208
  * @returns {number} Current playback time, in seconds
175
209
  */
@@ -186,7 +220,10 @@ export class Sequencer
186
220
 
187
221
  set currentTime(time)
188
222
  {
189
- this.unpause();
223
+ if (!this._preservePlaybackState)
224
+ {
225
+ this.unpause();
226
+ }
190
227
  this._sendMessage(WorkletSequencerMessageType.setTime, time);
191
228
  }
192
229
 
@@ -395,8 +432,15 @@ export class Sequencer
395
432
  // message data is absolute time
396
433
  const time = this.synth.currentTime - messageData;
397
434
  Object.entries(this.onTimeChange).forEach((callback) => callback[1](time));
398
- this.unpause();
399
435
  this._recalculateStartTime(time);
436
+ if (this.paused && this._preservePlaybackState)
437
+ {
438
+ this.pausedTime = time;
439
+ }
440
+ else
441
+ {
442
+ this.unpause();
443
+ }
400
444
  break;
401
445
 
402
446
  case WorkletSequencerReturnMessageType.pause:
@@ -65,6 +65,9 @@ export function processMessage(messageType, messageData)
65
65
  case WorkletSequencerMessageType.setSkipToFirstNote:
66
66
  this._skipToFirstNoteOn = messageData;
67
67
  break;
68
+
69
+ case WorkletSequencerMessageType.setPreservePlaybackState:
70
+ this.preservePlaybackState = messageData;
68
71
  }
69
72
  }
70
73
 
@@ -254,12 +254,14 @@ export function play(resetTime = false)
254
254
  // reset the time if necesarry
255
255
  if (resetTime)
256
256
  {
257
+ this.pausedTime = undefined;
257
258
  this.currentTime = 0;
258
259
  return;
259
260
  }
260
261
 
261
262
  if (this.currentTime >= this.duration)
262
263
  {
264
+ this.pausedTime = undefined;
263
265
  this.currentTime = 0;
264
266
  return;
265
267
  }
@@ -11,6 +11,7 @@
11
11
  * @property {number} changeSong - 8 -> goForwards<boolean> if true, next song, if false, previous
12
12
  * @property {number} getMIDI - 9 -> (no data)
13
13
  * @property {number} setSkipToFirstNote -10 -> skipToFirstNoteOn<boolean>
14
+ * @property {number} setPreservePlaybackState -11 -> preservePlaybackState<boolean>
14
15
  */
15
16
  export const WorkletSequencerMessageType = {
16
17
  loadNewSongList: 0,
@@ -23,7 +24,8 @@ export const WorkletSequencerMessageType = {
23
24
  setLoop: 7,
24
25
  changeSong: 8,
25
26
  getMIDI: 9,
26
- setSkipToFirstNote: 10
27
+ setSkipToFirstNote: 10,
28
+ setPreservePlaybackState: 11
27
29
  };
28
30
 
29
31
  /**
@@ -104,6 +104,12 @@ class WorkletSequencer
104
104
  * @private
105
105
  */
106
106
  this._skipToFirstNoteOn = true;
107
+
108
+ /**
109
+ * If true, seq will stay paused when seeking or changing the playback rate
110
+ * @type {boolean}
111
+ */
112
+ this.preservePlaybackState = false;
107
113
  }
108
114
 
109
115
  /**
@@ -152,15 +158,19 @@ class WorkletSequencer
152
158
  }
153
159
  this.stop();
154
160
  this.playingNotes = [];
161
+ const wasPaused = this.paused && this.preservePlaybackState;
155
162
  this.pausedTime = undefined;
156
163
  this.post(WorkletSequencerReturnMessageType.timeChange, currentTime - time);
157
- const isNotFinished = this._playTo(time);
164
+ this._playTo(time);
158
165
  this._recalculateStartTime(time);
159
- if (!isNotFinished)
166
+ if (wasPaused)
160
167
  {
161
- return;
168
+ this.pause();
169
+ }
170
+ else
171
+ {
172
+ this.play();
162
173
  }
163
- this.play();
164
174
  }
165
175
 
166
176
  /**
@@ -725,7 +725,8 @@ export class Synthetizer
725
725
 
726
726
  /**
727
727
  * Sends a MIDI Sysex message to the synthesizer
728
- * @param messageData {IndexedByteArray} the message's data (excluding the F0 byte, but including the F7 at the end)
728
+ * @param messageData {number[]|ArrayLike|Uint8Array} the message's data
729
+ * (excluding the F0 byte, but including the F7 at the end)
729
730
  */
730
731
  systemExclusive(messageData)
731
732
  {
@@ -752,7 +753,7 @@ export class Synthetizer
752
753
 
753
754
  /**
754
755
  * sends a raw MIDI message to the synthesizer
755
- * @param message {ArrayLike<number>} the midi message, each number is a byte
756
+ * @param message {number[]|Uint8Array} the midi message, each number is a byte
756
757
  */
757
758
  sendMessage(message)
758
759
  {