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.
- package/@types/sequencer/sequencer.d.ts +22 -0
- package/@types/sequencer/worklet_sequencer/sequencer_message.d.ts +1 -0
- package/@types/synthetizer/synthetizer.d.ts +5 -5
- package/@types/synthetizer/worklet_system/worklet_utilities/worklet_processor_channel.d.ts +15 -0
- package/README.md +3 -3
- package/package.json +1 -1
- package/sequencer/sequencer.js +49 -5
- package/sequencer/worklet_sequencer/events.js +3 -0
- package/sequencer/worklet_sequencer/play.js +2 -0
- package/sequencer/worklet_sequencer/sequencer_message.js +3 -1
- package/sequencer/worklet_sequencer/worklet_sequencer.js +14 -4
- package/synthetizer/synthetizer.js +3 -2
- package/synthetizer/worklet_processor.min.js +10 -10
- package/synthetizer/worklet_system/main_processor.js +0 -0
- package/synthetizer/worklet_system/worklet_methods/controller_control.js +6 -10
- package/synthetizer/worklet_system/worklet_methods/program_control.js +5 -5
- package/synthetizer/worklet_system/worklet_methods/reset_controllers.js +3 -2
- package/synthetizer/worklet_system/worklet_methods/snapshot.js +3 -3
- package/synthetizer/worklet_system/worklet_methods/system_exclusive.js +1 -1
- package/synthetizer/worklet_system/worklet_utilities/stereo_panner.js +0 -0
- package/synthetizer/worklet_system/worklet_utilities/worklet_modulator.js +0 -0
- package/synthetizer/worklet_system/worklet_utilities/worklet_processor_channel.js +26 -0
|
@@ -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 {
|
|
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:
|
|
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 {
|
|
307
|
+
* @param message {number[]|Uint8Array} the midi message, each number is a byte
|
|
307
308
|
*/
|
|
308
|
-
sendMessage(message:
|
|
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:** *
|
|
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
package/sequencer/sequencer.js
CHANGED
|
@@ -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
|
|
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.
|
|
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.
|
|
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
|
-
|
|
164
|
+
this._playTo(time);
|
|
158
165
|
this._recalculateStartTime(time);
|
|
159
|
-
if (
|
|
166
|
+
if (wasPaused)
|
|
160
167
|
{
|
|
161
|
-
|
|
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 {
|
|
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 {
|
|
756
|
+
* @param message {number[]|Uint8Array} the midi message, each number is a byte
|
|
756
757
|
*/
|
|
757
758
|
sendMessage(message)
|
|
758
759
|
{
|