spessasynth_lib 3.24.16 → 3.24.18
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 +1 -1
- package/sequencer/sequencer.js +35 -2
- package/sequencer/worklet_sequencer/events.js +20 -7
- package/sequencer/worklet_sequencer/process_event.js +2 -6
- package/sequencer/worklet_sequencer/sequencer_message.js +8 -1
- package/sequencer/worklet_sequencer/song_control.js +5 -4
- package/sequencer/worklet_sequencer/worklet_sequencer.js +147 -86
- package/synthetizer/worklet_processor.min.js +9 -9
- package/synthetizer/worklet_system/worklet_methods/controller_control/controller_change.js +4 -4
- package/synthetizer/worklet_system/worklet_methods/controller_control/reset_controllers.js +1 -1
- package/synthetizer/worklet_system/worklet_utilities/lowpass_filter.js +56 -48
package/package.json
CHANGED
package/sequencer/sequencer.js
CHANGED
|
@@ -3,6 +3,7 @@ import { Synthetizer } from "../synthetizer/synthetizer.js";
|
|
|
3
3
|
import { messageTypes } from "../midi_parser/midi_message.js";
|
|
4
4
|
import { workletMessageType } from "../synthetizer/worklet_system/message_protocol/worklet_message.js";
|
|
5
5
|
import {
|
|
6
|
+
SongChangeType,
|
|
6
7
|
WorkletSequencerMessageType,
|
|
7
8
|
WorkletSequencerReturnMessageType
|
|
8
9
|
} from "./worklet_sequencer/sequencer_message.js";
|
|
@@ -261,6 +262,38 @@ export class Sequencer
|
|
|
261
262
|
this._playbackRate = value;
|
|
262
263
|
}
|
|
263
264
|
|
|
265
|
+
/**
|
|
266
|
+
* @type {boolean}
|
|
267
|
+
* @private
|
|
268
|
+
*/
|
|
269
|
+
_shuffleSongs = false;
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Indicates if the song order is random
|
|
273
|
+
* @returns {boolean}
|
|
274
|
+
*/
|
|
275
|
+
get shuffleSongs()
|
|
276
|
+
{
|
|
277
|
+
return this._shuffleSongs;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Indicates if the song order is random
|
|
282
|
+
* @param value {boolean}
|
|
283
|
+
*/
|
|
284
|
+
set shuffleSongs(value)
|
|
285
|
+
{
|
|
286
|
+
this._shuffleSongs = value;
|
|
287
|
+
if (value)
|
|
288
|
+
{
|
|
289
|
+
this._sendMessage(WorkletSequencerMessageType.changeSong, SongChangeType.shuffleOn);
|
|
290
|
+
}
|
|
291
|
+
else
|
|
292
|
+
{
|
|
293
|
+
this._sendMessage(WorkletSequencerMessageType.changeSong, SongChangeType.shuffleOff);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
264
297
|
/**
|
|
265
298
|
* Indicates if the sequencer should skip to first note on
|
|
266
299
|
* @return {boolean}
|
|
@@ -446,12 +479,12 @@ export class Sequencer
|
|
|
446
479
|
|
|
447
480
|
nextSong()
|
|
448
481
|
{
|
|
449
|
-
this._sendMessage(WorkletSequencerMessageType.changeSong,
|
|
482
|
+
this._sendMessage(WorkletSequencerMessageType.changeSong, SongChangeType.forwards);
|
|
450
483
|
}
|
|
451
484
|
|
|
452
485
|
previousSong()
|
|
453
486
|
{
|
|
454
|
-
this._sendMessage(WorkletSequencerMessageType.changeSong,
|
|
487
|
+
this._sendMessage(WorkletSequencerMessageType.changeSong, SongChangeType.backwards);
|
|
455
488
|
}
|
|
456
489
|
|
|
457
490
|
/**
|
|
@@ -2,7 +2,7 @@ import {
|
|
|
2
2
|
ALL_CHANNELS_OR_DIFFERENT_ACTION,
|
|
3
3
|
returnMessageType
|
|
4
4
|
} from "../../synthetizer/worklet_system/message_protocol/worklet_message.js";
|
|
5
|
-
import { WorkletSequencerMessageType, WorkletSequencerReturnMessageType } from "./sequencer_message.js";
|
|
5
|
+
import { SongChangeType, WorkletSequencerMessageType, WorkletSequencerReturnMessageType } from "./sequencer_message.js";
|
|
6
6
|
import { messageTypes, midiControllers } from "../../midi_parser/midi_message.js";
|
|
7
7
|
import { MIDI_CHANNEL_COUNT } from "../../synthetizer/synthetizer.js";
|
|
8
8
|
|
|
@@ -60,13 +60,26 @@ export function processMessage(messageType, messageData)
|
|
|
60
60
|
break;
|
|
61
61
|
|
|
62
62
|
case WorkletSequencerMessageType.changeSong:
|
|
63
|
-
|
|
63
|
+
switch (messageData)
|
|
64
64
|
{
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
65
|
+
case SongChangeType.forwards:
|
|
66
|
+
this.nextSong();
|
|
67
|
+
break;
|
|
68
|
+
|
|
69
|
+
case SongChangeType.backwards:
|
|
70
|
+
this.previousSong();
|
|
71
|
+
break;
|
|
72
|
+
|
|
73
|
+
case SongChangeType.shuffleOff:
|
|
74
|
+
this.shuffleMode = false;
|
|
75
|
+
this.songIndex = this.shuffledSongIndexes[this.songIndex];
|
|
76
|
+
break;
|
|
77
|
+
|
|
78
|
+
case SongChangeType.shuffleOn:
|
|
79
|
+
this.shuffleMode = true;
|
|
80
|
+
this.shuffleSongIndexes();
|
|
81
|
+
this.songIndex = 0;
|
|
82
|
+
this.loadCurrentSong();
|
|
70
83
|
}
|
|
71
84
|
break;
|
|
72
85
|
|
|
@@ -13,10 +13,6 @@ import { readBytesAsUintBigEndian } from "../../utils/byte_functions/big_endian.
|
|
|
13
13
|
*/
|
|
14
14
|
export function _processEvent(event, trackIndex)
|
|
15
15
|
{
|
|
16
|
-
if (this.ignoreEvents)
|
|
17
|
-
{
|
|
18
|
-
return;
|
|
19
|
-
}
|
|
20
16
|
if (this.sendMIDIMessages)
|
|
21
17
|
{
|
|
22
18
|
if (event.messageStatusByte >= 0x80)
|
|
@@ -100,7 +96,7 @@ export function _processEvent(event, trackIndex)
|
|
|
100
96
|
}
|
|
101
97
|
break;
|
|
102
98
|
|
|
103
|
-
//
|
|
99
|
+
// recognized but ignored
|
|
104
100
|
case messageTypes.timeSignature:
|
|
105
101
|
case messageTypes.endOfTrack:
|
|
106
102
|
case messageTypes.midiChannelPrefix:
|
|
@@ -123,7 +119,7 @@ export function _processEvent(event, trackIndex)
|
|
|
123
119
|
if (statusByteData.status === messageTypes.lyric)
|
|
124
120
|
{
|
|
125
121
|
lyricsIndex = Math.min(
|
|
126
|
-
this.midiData.lyricsTicks.indexOf(event.ticks)
|
|
122
|
+
this.midiData.lyricsTicks.indexOf(event.ticks),
|
|
127
123
|
this.midiData.lyrics.length - 1
|
|
128
124
|
);
|
|
129
125
|
}
|
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
export const SongChangeType = {
|
|
2
|
+
backwards: 0,
|
|
3
|
+
forwards: 1,
|
|
4
|
+
shuffleOn: 2,
|
|
5
|
+
shuffleOff: 3
|
|
6
|
+
};
|
|
7
|
+
|
|
1
8
|
/**
|
|
2
9
|
* @enum {number}
|
|
3
10
|
* @property {number} loadNewSongList - 0 -> [...song<MIDI>]
|
|
@@ -8,7 +15,7 @@
|
|
|
8
15
|
* @property {number} changeMIDIMessageSending - 5 -> sendMIDIMessages<boolean>
|
|
9
16
|
* @property {number} setPlaybackRate - 6 -> playbackRate<number>
|
|
10
17
|
* @property {number} setLoop - 7 -> [loop<boolean>, count<number]
|
|
11
|
-
* @property {number} changeSong - 8 ->
|
|
18
|
+
* @property {number} changeSong - 8 -> changeType<number> 0 - back, 1 - forward, 2 - shuffle ON, 3 - shuffle OFF
|
|
12
19
|
* @property {number} getMIDI - 9 -> (no data)
|
|
13
20
|
* @property {number} setSkipToFirstNote -10 -> skipToFirstNoteOn<boolean>
|
|
14
21
|
* @property {number} setPreservePlaybackState -11 -> preservePlaybackState<boolean>
|
|
@@ -120,7 +120,7 @@ export function loadNewSequence(parsedMidi, autoPlay = true)
|
|
|
120
120
|
});
|
|
121
121
|
|
|
122
122
|
/**
|
|
123
|
-
* Same as
|
|
123
|
+
* Same as "audio.duration" property (seconds)
|
|
124
124
|
* @type {number}
|
|
125
125
|
*/
|
|
126
126
|
this.duration = this.midiData.duration;
|
|
@@ -188,7 +188,8 @@ export function loadNewSongList(midiBuffers, autoPlay = true)
|
|
|
188
188
|
{
|
|
189
189
|
this.loop = false;
|
|
190
190
|
}
|
|
191
|
-
this.
|
|
191
|
+
this.shuffleSongIndexes();
|
|
192
|
+
this.loadCurrentSong(autoPlay);
|
|
192
193
|
}
|
|
193
194
|
|
|
194
195
|
/**
|
|
@@ -203,7 +204,7 @@ export function nextSong()
|
|
|
203
204
|
}
|
|
204
205
|
this.songIndex++;
|
|
205
206
|
this.songIndex %= this.songs.length;
|
|
206
|
-
this.
|
|
207
|
+
this.loadCurrentSong();
|
|
207
208
|
}
|
|
208
209
|
|
|
209
210
|
/**
|
|
@@ -221,5 +222,5 @@ export function previousSong()
|
|
|
221
222
|
{
|
|
222
223
|
this.songIndex = this.songs.length - 1;
|
|
223
224
|
}
|
|
224
|
-
this.
|
|
225
|
+
this.loadCurrentSong();
|
|
225
226
|
}
|
|
@@ -18,100 +18,139 @@ import { MIDI_CHANNEL_COUNT } from "../../synthetizer/synthetizer.js";
|
|
|
18
18
|
|
|
19
19
|
class WorkletSequencer
|
|
20
20
|
{
|
|
21
|
+
/**
|
|
22
|
+
* All the sequencer's songs
|
|
23
|
+
* @type {BasicMIDI[]}
|
|
24
|
+
*/
|
|
25
|
+
songs = [];
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Current song index
|
|
29
|
+
* @type {number}
|
|
30
|
+
*/
|
|
31
|
+
songIndex = 0;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* shuffled song indexes
|
|
35
|
+
* @type {number[]}
|
|
36
|
+
*/
|
|
37
|
+
shuffledSongIndexes = [];
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* the synth to use
|
|
41
|
+
* @type {SpessaSynthProcessor}
|
|
42
|
+
*/
|
|
43
|
+
synth;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* if the sequencer is active
|
|
47
|
+
* @type {boolean}
|
|
48
|
+
*/
|
|
49
|
+
isActive = false;
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* If the event should instead be sent back to the main thread instead of synth
|
|
53
|
+
* @type {boolean}
|
|
54
|
+
*/
|
|
55
|
+
sendMIDIMessages = false;
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* sequencer's loop count
|
|
59
|
+
* @type {number}
|
|
60
|
+
*/
|
|
61
|
+
loopCount = Infinity;
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* event's number in this.events
|
|
65
|
+
* @type {number[]}
|
|
66
|
+
*/
|
|
67
|
+
eventIndex = [];
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* tracks the time that has already been played
|
|
71
|
+
* @type {number}
|
|
72
|
+
*/
|
|
73
|
+
playedTime = 0;
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* The (relative) time when the sequencer was paused. If it's not paused, then it's undefined.
|
|
77
|
+
* @type {number}
|
|
78
|
+
*/
|
|
79
|
+
pausedTime = undefined;
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Absolute playback startTime, bases on the synth's time
|
|
83
|
+
* @type {number}
|
|
84
|
+
*/
|
|
85
|
+
absoluteStartTime = currentTime;
|
|
86
|
+
/**
|
|
87
|
+
* Currently playing notes (for pausing and resuming)
|
|
88
|
+
* @type {{
|
|
89
|
+
* midiNote: number,
|
|
90
|
+
* channel: number,
|
|
91
|
+
* velocity: number
|
|
92
|
+
* }[]}
|
|
93
|
+
*/
|
|
94
|
+
playingNotes = [];
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* controls if the sequencer loops (defaults to true)
|
|
98
|
+
* @type {boolean}
|
|
99
|
+
*/
|
|
100
|
+
loop = true;
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* controls if the songs are ordered randomly
|
|
104
|
+
* @type {boolean}
|
|
105
|
+
*/
|
|
106
|
+
shuffleMode = false;
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* the current track data
|
|
110
|
+
* @type {BasicMIDI}
|
|
111
|
+
*/
|
|
112
|
+
midiData = undefined;
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* midi port number for the corresponding track
|
|
116
|
+
* @type {number[]}
|
|
117
|
+
*/
|
|
118
|
+
midiPorts = [];
|
|
119
|
+
midiPortChannelOffset = 0;
|
|
120
|
+
/**
|
|
121
|
+
* stored as:
|
|
122
|
+
* Object<midi port, channel offset>
|
|
123
|
+
* @type {Object<number, number>}
|
|
124
|
+
*/
|
|
125
|
+
midiPortChannelOffsets = {};
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* @type {boolean}
|
|
129
|
+
* @private
|
|
130
|
+
*/
|
|
131
|
+
_skipToFirstNoteOn = true;
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* If true, seq will stay paused when seeking or changing the playback rate
|
|
135
|
+
* @type {boolean}
|
|
136
|
+
*/
|
|
137
|
+
preservePlaybackState = false;
|
|
138
|
+
|
|
21
139
|
/**
|
|
22
140
|
* @param spessasynthProcessor {SpessaSynthProcessor}
|
|
23
141
|
*/
|
|
24
142
|
constructor(spessasynthProcessor)
|
|
25
143
|
{
|
|
26
144
|
this.synth = spessasynthProcessor;
|
|
27
|
-
this.ignoreEvents = false;
|
|
28
|
-
this.isActive = false;
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* If the event should instead be sent back to the main thread instead of synth
|
|
32
|
-
* @type {boolean}
|
|
33
|
-
*/
|
|
34
|
-
this.sendMIDIMessages = false;
|
|
35
|
-
|
|
36
|
-
this.loopCount = Infinity;
|
|
37
|
-
|
|
38
|
-
// event's number in this.events
|
|
39
|
-
/**
|
|
40
|
-
* @type {number[]}
|
|
41
|
-
*/
|
|
42
|
-
this.eventIndex = [];
|
|
43
|
-
this.songIndex = 0;
|
|
44
|
-
|
|
45
|
-
// tracks the time that we have already played
|
|
46
|
-
/**
|
|
47
|
-
* @type {number}
|
|
48
|
-
*/
|
|
49
|
-
this.playedTime = 0;
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* The (relative) time when the sequencer was paused. If it's not paused then it's undefined.
|
|
53
|
-
* @type {number}
|
|
54
|
-
*/
|
|
55
|
-
this.pausedTime = undefined;
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* Absolute playback startTime, bases on the synth's time
|
|
59
|
-
* @type {number}
|
|
60
|
-
*/
|
|
61
|
-
this.absoluteStartTime = currentTime;
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* Controls the playback's rate
|
|
65
|
-
* @type {number}
|
|
66
|
-
*/
|
|
67
|
-
this._playbackRate = 1;
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* Currently playing notes (for pausing and resuming)
|
|
71
|
-
* @type {{
|
|
72
|
-
* midiNote: number,
|
|
73
|
-
* channel: number,
|
|
74
|
-
* velocity: number
|
|
75
|
-
* }[]}
|
|
76
|
-
*/
|
|
77
|
-
this.playingNotes = [];
|
|
78
|
-
|
|
79
|
-
// controls if the sequencer loops (defaults to true)
|
|
80
|
-
this.loop = true;
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
* the current track data
|
|
84
|
-
* @type {BasicMIDI}
|
|
85
|
-
*/
|
|
86
|
-
this.midiData = undefined;
|
|
87
|
-
|
|
88
|
-
/**
|
|
89
|
-
* midi port number for the corresponding track
|
|
90
|
-
* @type {number[]}
|
|
91
|
-
*/
|
|
92
|
-
this.midiPorts = [];
|
|
93
|
-
|
|
94
|
-
this.midiPortChannelOffset = 0;
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* midi port: channel offset
|
|
98
|
-
* @type {Object<number, number>}
|
|
99
|
-
*/
|
|
100
|
-
this.midiPortChannelOffsets = {};
|
|
101
|
-
|
|
102
|
-
/**
|
|
103
|
-
* @type {boolean}
|
|
104
|
-
* @private
|
|
105
|
-
*/
|
|
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;
|
|
113
145
|
}
|
|
114
146
|
|
|
147
|
+
/**
|
|
148
|
+
* Controls the playback's rate
|
|
149
|
+
* @type {number}
|
|
150
|
+
* @private
|
|
151
|
+
*/
|
|
152
|
+
_playbackRate = 1;
|
|
153
|
+
|
|
115
154
|
/**
|
|
116
155
|
* @param value {number}
|
|
117
156
|
*/
|
|
@@ -229,6 +268,16 @@ class WorkletSequencer
|
|
|
229
268
|
}
|
|
230
269
|
}
|
|
231
270
|
|
|
271
|
+
loadCurrentSong(autoPlay = true)
|
|
272
|
+
{
|
|
273
|
+
let index = this.songIndex;
|
|
274
|
+
if (this.shuffleMode)
|
|
275
|
+
{
|
|
276
|
+
index = this.shuffledSongIndexes[this.songIndex];
|
|
277
|
+
}
|
|
278
|
+
this.loadNewSequence(this.songs[index], autoPlay);
|
|
279
|
+
}
|
|
280
|
+
|
|
232
281
|
_resetTimers()
|
|
233
282
|
{
|
|
234
283
|
this.playedTime = 0;
|
|
@@ -244,6 +293,18 @@ class WorkletSequencer
|
|
|
244
293
|
{
|
|
245
294
|
this.isActive = false;
|
|
246
295
|
}
|
|
296
|
+
|
|
297
|
+
shuffleSongIndexes()
|
|
298
|
+
{
|
|
299
|
+
const indexes = this.songs.map((_, i) => i);
|
|
300
|
+
this.shuffledSongIndexes = [];
|
|
301
|
+
while (indexes.length > 0)
|
|
302
|
+
{
|
|
303
|
+
const index = indexes[Math.floor(Math.random() * indexes.length)];
|
|
304
|
+
this.shuffledSongIndexes.push(index);
|
|
305
|
+
indexes.splice(indexes.indexOf(index), 1);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
247
308
|
}
|
|
248
309
|
|
|
249
310
|
// Web MIDI sending
|