spessasynth_lib 3.9.20 → 3.9.22
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/externals/libvorbis/OggVorbisEncoder.min.d.ts +3 -0
- package/@types/sequencer/sequencer.d.ts +23 -13
- package/@types/sequencer/worklet_sequencer/sequencer_message.d.ts +1 -0
- package/index.js +1 -0
- package/package.json +1 -1
- package/sequencer/sequencer.js +44 -1
- package/sequencer/worklet_sequencer/events.js +5 -0
- package/sequencer/worklet_sequencer/play.js +48 -26
- package/sequencer/worklet_sequencer/sequencer_message.js +3 -1
- package/sequencer/worklet_sequencer/song_control.js +0 -1
- package/sequencer/worklet_sequencer/worklet_sequencer.js +23 -2
- package/synthetizer/worklet_processor.min.js +5 -5
- package/synthetizer/worklet_processor.min.js.map +0 -7
|
@@ -1,22 +1,11 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* sequencer.js
|
|
3
|
-
* purpose: plays back the midi file decoded by midi_loader.js, including support for multi-channel midis (adding channels when more than 1 midi port is detected)
|
|
4
|
-
*/
|
|
5
|
-
/**
|
|
6
|
-
* @typedef MidFile {Object}
|
|
7
|
-
* @property {ArrayBuffer} binary - the binary data of the file.
|
|
8
|
-
* @property {string|undefined} altName - the alternative name for the file
|
|
9
|
-
*/
|
|
10
|
-
/**
|
|
11
|
-
* @typedef {MIDI|MidFile} MIDIFile
|
|
12
|
-
*/
|
|
13
1
|
export class Sequencer {
|
|
14
2
|
/**
|
|
15
3
|
* Creates a new Midi sequencer for playing back MIDI files
|
|
16
4
|
* @param midiBinaries {MIDIFile[]} List of the buffers of the MIDI files
|
|
17
5
|
* @param synth {Synthetizer} synth to send events to
|
|
6
|
+
* @param options {SequencerOptions} the sequencer's options
|
|
18
7
|
*/
|
|
19
|
-
constructor(midiBinaries: MIDIFile[], synth: Synthetizer);
|
|
8
|
+
constructor(midiBinaries: MIDIFile[], synth: Synthetizer, options?: SequencerOptions);
|
|
20
9
|
ignoreEvents: boolean;
|
|
21
10
|
synth: Synthetizer;
|
|
22
11
|
highResTimeOffset: number;
|
|
@@ -47,6 +36,21 @@ export class Sequencer {
|
|
|
47
36
|
* @type {number}
|
|
48
37
|
*/
|
|
49
38
|
duration: number;
|
|
39
|
+
/**
|
|
40
|
+
* @type {boolean}
|
|
41
|
+
* @private
|
|
42
|
+
*/
|
|
43
|
+
private _skipToFirstNoteOn;
|
|
44
|
+
/**
|
|
45
|
+
* Indicates if the sequencer should skip to first note on
|
|
46
|
+
* @param val {boolean}
|
|
47
|
+
*/
|
|
48
|
+
set skipToFirstNoteOn(val: boolean);
|
|
49
|
+
/**
|
|
50
|
+
* Indicates if the sequencer should skip to first note on
|
|
51
|
+
* @return {boolean}
|
|
52
|
+
*/
|
|
53
|
+
get skipToFirstNoteOn(): boolean;
|
|
50
54
|
resetMIDIOut(): void;
|
|
51
55
|
set loop(value: boolean);
|
|
52
56
|
get loop(): boolean;
|
|
@@ -186,6 +190,12 @@ export type MidFile = {
|
|
|
186
190
|
altName: string | undefined;
|
|
187
191
|
};
|
|
188
192
|
export type MIDIFile = MIDI | MidFile;
|
|
193
|
+
export type SequencerOptions = {
|
|
194
|
+
/**
|
|
195
|
+
* - if true, the sequencer will skip to the first note
|
|
196
|
+
*/
|
|
197
|
+
skipToFirstNoteOn: boolean;
|
|
198
|
+
};
|
|
189
199
|
import { Synthetizer } from '../synthetizer/synthetizer.js';
|
|
190
200
|
import { MidiData } from '../midi_parser/midi_data.js';
|
|
191
201
|
import { MIDI } from '../midi_parser/midi_loader.js';
|
|
@@ -13,6 +13,7 @@ export namespace WorkletSequencerMessageType {
|
|
|
13
13
|
let setLoop: number;
|
|
14
14
|
let changeSong: number;
|
|
15
15
|
let getMIDI: number;
|
|
16
|
+
let setSkipToFirstNote: number;
|
|
16
17
|
}
|
|
17
18
|
export type WorkletSequencerReturnMessageType = number;
|
|
18
19
|
export namespace WorkletSequencerReturnMessageType {
|
package/index.js
CHANGED
|
@@ -26,6 +26,7 @@ import { NON_CC_INDEX_OFFSET } from './synthetizer/worklet_system/worklet_utilit
|
|
|
26
26
|
import { modulatorSources } from './soundfont/read/modulators.js';
|
|
27
27
|
import { ALL_CHANNELS_OR_DIFFERENT_ACTION } from './synthetizer/worklet_system/message_protocol/worklet_message.js';
|
|
28
28
|
import { trimSoundfont } from './soundfont/write/soundfont_trimmer.js';
|
|
29
|
+
import { OggVorbisEncoder } from './externals/libvorbis/OggVorbisEncoder.min.js';
|
|
29
30
|
import { WORKLET_URL_ABSOLUTE } from './synthetizer/worklet_url.js'
|
|
30
31
|
|
|
31
32
|
// Export modules
|
package/package.json
CHANGED
package/sequencer/sequencer.js
CHANGED
|
@@ -24,14 +24,26 @@ import { DUMMY_MIDI_DATA, MidiData } from '../midi_parser/midi_data.js'
|
|
|
24
24
|
* @typedef {MIDI|MidFile} MIDIFile
|
|
25
25
|
*/
|
|
26
26
|
|
|
27
|
+
/**
|
|
28
|
+
* @typedef {Object} SequencerOptions
|
|
29
|
+
* @property {boolean} skipToFirstNoteOn - if true, the sequencer will skip to the first note
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* @type {SequencerOptions}
|
|
34
|
+
*/
|
|
35
|
+
const DEFAULT_OPTIONS = {
|
|
36
|
+
skipToFirstNoteOn: true,
|
|
37
|
+
}
|
|
27
38
|
export class Sequencer
|
|
28
39
|
{
|
|
29
40
|
/**
|
|
30
41
|
* Creates a new Midi sequencer for playing back MIDI files
|
|
31
42
|
* @param midiBinaries {MIDIFile[]} List of the buffers of the MIDI files
|
|
32
43
|
* @param synth {Synthetizer} synth to send events to
|
|
44
|
+
* @param options {SequencerOptions} the sequencer's options
|
|
33
45
|
*/
|
|
34
|
-
constructor(midiBinaries, synth)
|
|
46
|
+
constructor(midiBinaries, synth, options = DEFAULT_OPTIONS)
|
|
35
47
|
{
|
|
36
48
|
this.ignoreEvents = false;
|
|
37
49
|
this.synth = synth;
|
|
@@ -73,11 +85,42 @@ export class Sequencer
|
|
|
73
85
|
|
|
74
86
|
this.synth.sequencerCallbackFunction = this._handleMessage.bind(this);
|
|
75
87
|
|
|
88
|
+
/**
|
|
89
|
+
* @type {boolean}
|
|
90
|
+
* @private
|
|
91
|
+
*/
|
|
92
|
+
this._skipToFirstNoteOn = options?.skipToFirstNoteOn ?? true;
|
|
93
|
+
|
|
94
|
+
if(this._skipToFirstNoteOn === false)
|
|
95
|
+
{
|
|
96
|
+
// setter sends message
|
|
97
|
+
this._sendMessage(WorkletSequencerMessageType.setSkipToFirstNote, false);
|
|
98
|
+
}
|
|
99
|
+
|
|
76
100
|
this.loadNewSongList(midiBinaries);
|
|
77
101
|
|
|
78
102
|
window.addEventListener("beforeunload", this.resetMIDIOut.bind(this))
|
|
79
103
|
}
|
|
80
104
|
|
|
105
|
+
/**
|
|
106
|
+
* Indicates if the sequencer should skip to first note on
|
|
107
|
+
* @return {boolean}
|
|
108
|
+
*/
|
|
109
|
+
get skipToFirstNoteOn()
|
|
110
|
+
{
|
|
111
|
+
return this._skipToFirstNoteOn;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Indicates if the sequencer should skip to first note on
|
|
116
|
+
* @param val {boolean}
|
|
117
|
+
*/
|
|
118
|
+
set skipToFirstNoteOn(val)
|
|
119
|
+
{
|
|
120
|
+
this._skipToFirstNoteOn = val;
|
|
121
|
+
this._sendMessage(WorkletSequencerMessageType.setSkipToFirstNote, this._skipToFirstNoteOn);
|
|
122
|
+
}
|
|
123
|
+
|
|
81
124
|
resetMIDIOut()
|
|
82
125
|
{
|
|
83
126
|
if(!this.MIDIout)
|
|
@@ -58,6 +58,11 @@ export function processMessage(messageType, messageData)
|
|
|
58
58
|
|
|
59
59
|
case WorkletSequencerMessageType.getMIDI:
|
|
60
60
|
this.post(WorkletSequencerReturnMessageType.getMIDI, this.midiData);
|
|
61
|
+
break;
|
|
62
|
+
|
|
63
|
+
case WorkletSequencerMessageType.setSkipToFirstNote:
|
|
64
|
+
this._skipToFirstNoteOn = messageData;
|
|
65
|
+
break;
|
|
61
66
|
}
|
|
62
67
|
}
|
|
63
68
|
|
|
@@ -45,9 +45,32 @@ export function _playTo(time, ticks = undefined)
|
|
|
45
45
|
|
|
46
46
|
/**
|
|
47
47
|
* Save programs here and send them only after
|
|
48
|
-
* @type {number[]}
|
|
48
|
+
* @type {{program: number, bank: number, actualBank: number}[]}
|
|
49
49
|
*/
|
|
50
|
-
const programs =
|
|
50
|
+
const programs = [];
|
|
51
|
+
for (let i = 0; i < channelsToSave; i++)
|
|
52
|
+
{
|
|
53
|
+
programs.push({
|
|
54
|
+
program: -1,
|
|
55
|
+
bank: 0,
|
|
56
|
+
actualBank: 0,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const isCCNonSkippable = controllerNumber => (
|
|
61
|
+
controllerNumber === midiControllers.dataDecrement ||
|
|
62
|
+
controllerNumber === midiControllers.dataIncrement ||
|
|
63
|
+
controllerNumber === midiControllers.dataEntryMsb ||
|
|
64
|
+
controllerNumber === midiControllers.dataDecrement ||
|
|
65
|
+
controllerNumber === midiControllers.lsbForControl6DataEntry ||
|
|
66
|
+
controllerNumber === midiControllers.RPNLsb ||
|
|
67
|
+
controllerNumber === midiControllers.RPNMsb ||
|
|
68
|
+
controllerNumber === midiControllers.NRPNLsb ||
|
|
69
|
+
controllerNumber === midiControllers.NRPNMsb ||
|
|
70
|
+
controllerNumber === midiControllers.bankSelect ||
|
|
71
|
+
controllerNumber === midiControllers.lsbForControl0BankSelect||
|
|
72
|
+
controllerNumber === midiControllers.resetAllControllers
|
|
73
|
+
);
|
|
51
74
|
|
|
52
75
|
/**
|
|
53
76
|
* Save controllers here and send them only after
|
|
@@ -97,26 +120,15 @@ export function _playTo(time, ticks = undefined)
|
|
|
97
120
|
break;
|
|
98
121
|
|
|
99
122
|
case messageTypes.programChange:
|
|
100
|
-
|
|
123
|
+
const p = programs[channel];
|
|
124
|
+
p.program = event.messageData[0];
|
|
125
|
+
p.actualBank = p.bank;
|
|
101
126
|
break;
|
|
102
127
|
|
|
103
128
|
case messageTypes.controllerChange:
|
|
104
129
|
// do not skip data entries
|
|
105
130
|
const controllerNumber = event.messageData[0];
|
|
106
|
-
if(
|
|
107
|
-
controllerNumber === midiControllers.dataDecrement ||
|
|
108
|
-
controllerNumber === midiControllers.dataIncrement ||
|
|
109
|
-
controllerNumber === midiControllers.dataEntryMsb ||
|
|
110
|
-
controllerNumber === midiControllers.dataDecrement ||
|
|
111
|
-
controllerNumber === midiControllers.lsbForControl6DataEntry ||
|
|
112
|
-
controllerNumber === midiControllers.RPNLsb ||
|
|
113
|
-
controllerNumber === midiControllers.RPNMsb ||
|
|
114
|
-
controllerNumber === midiControllers.NRPNLsb ||
|
|
115
|
-
controllerNumber === midiControllers.NRPNMsb ||
|
|
116
|
-
controllerNumber === midiControllers.bankSelect ||
|
|
117
|
-
controllerNumber === midiControllers.lsbForControl0BankSelect||
|
|
118
|
-
controllerNumber === midiControllers.resetAllControllers
|
|
119
|
-
)
|
|
131
|
+
if(isCCNonSkippable(controllerNumber))
|
|
120
132
|
{
|
|
121
133
|
if(this.sendMIDIMessages)
|
|
122
134
|
{
|
|
@@ -125,10 +137,16 @@ export function _playTo(time, ticks = undefined)
|
|
|
125
137
|
else
|
|
126
138
|
{
|
|
127
139
|
let ccV = event.messageData[1];
|
|
128
|
-
if(
|
|
140
|
+
if(controllerNumber === midiControllers.bankSelect)
|
|
129
141
|
{
|
|
130
|
-
|
|
131
|
-
|
|
142
|
+
if(this.midiData.embeddedSoundFont !== undefined)
|
|
143
|
+
{
|
|
144
|
+
// special case if the RMID is embedded: subtract 1 from bank. See wiki About-RMIDI
|
|
145
|
+
ccV--;
|
|
146
|
+
}
|
|
147
|
+
// add the bank to saved
|
|
148
|
+
programs[channel].bank = ccV;
|
|
149
|
+
break;
|
|
132
150
|
}
|
|
133
151
|
this.synth.controllerChange(channel, controllerNumber, ccV);
|
|
134
152
|
}
|
|
@@ -170,16 +188,18 @@ export function _playTo(time, ticks = undefined)
|
|
|
170
188
|
|
|
171
189
|
// every controller that has changed
|
|
172
190
|
savedControllers[channelNumber].forEach((value, index) => {
|
|
173
|
-
if(value !== defaultControllerArray[index])
|
|
191
|
+
if(value !== defaultControllerArray[index] && !isCCNonSkippable(index))
|
|
174
192
|
{
|
|
175
193
|
this.sendMIDIMessage([messageTypes.controllerChange | (channelNumber % 16), index, value])
|
|
176
194
|
}
|
|
177
195
|
});
|
|
178
196
|
|
|
179
197
|
// restore programs
|
|
180
|
-
if(programs[channelNumber] !==
|
|
198
|
+
if(programs[channelNumber].program !== -1)
|
|
181
199
|
{
|
|
182
|
-
|
|
200
|
+
const bank = programs[channelNumber].actualBank;
|
|
201
|
+
this.sendMIDIMessage([messageTypes.controllerChange | (channelNumber % 16), midiControllers.bankSelect, bank]);
|
|
202
|
+
this.sendMIDIMessage([messageTypes.programChange | (channelNumber % 16), programs[channelNumber].program]);
|
|
183
203
|
}
|
|
184
204
|
}
|
|
185
205
|
}
|
|
@@ -197,16 +217,18 @@ export function _playTo(time, ticks = undefined)
|
|
|
197
217
|
{
|
|
198
218
|
// every controller that has changed
|
|
199
219
|
savedControllers[channelNumber].forEach((value, index) => {
|
|
200
|
-
if(value !== defaultControllerArray[index])
|
|
220
|
+
if(value !== defaultControllerArray[index] && !isCCNonSkippable(index))
|
|
201
221
|
{
|
|
202
222
|
this.synth.controllerChange(channelNumber, index, value);
|
|
203
223
|
}
|
|
204
224
|
})
|
|
205
225
|
}
|
|
206
226
|
// restore programs
|
|
207
|
-
if(programs[channelNumber] !== -1)
|
|
227
|
+
if(programs[channelNumber].program !== -1)
|
|
208
228
|
{
|
|
209
|
-
|
|
229
|
+
const bank = programs[channelNumber].actualBank;
|
|
230
|
+
this.synth.controllerChange(channelNumber, midiControllers.bankSelect, bank);
|
|
231
|
+
this.synth.programChange(channelNumber, programs[channelNumber].program);
|
|
210
232
|
}
|
|
211
233
|
}
|
|
212
234
|
}
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
* @property {number} setLoop - 7 -> loop<boolean>
|
|
11
11
|
* @property {number} changeSong - 8 -> goForwards<boolean> if true, next song, if false, previous
|
|
12
12
|
* @property {number} getMIDI - 9 -> (no data)
|
|
13
|
+
* @property {number} setSkipToFirstNote -10 -> skipToFirstNoteOn<boolean>
|
|
13
14
|
*/
|
|
14
15
|
export const WorkletSequencerMessageType = {
|
|
15
16
|
loadNewSongList: 0,
|
|
@@ -21,7 +22,8 @@ export const WorkletSequencerMessageType = {
|
|
|
21
22
|
setPlaybackRate: 6,
|
|
22
23
|
setLoop: 7,
|
|
23
24
|
changeSong: 8,
|
|
24
|
-
getMIDI: 9
|
|
25
|
+
getMIDI: 9,
|
|
26
|
+
setSkipToFirstNote: 10
|
|
25
27
|
}
|
|
26
28
|
|
|
27
29
|
/**
|
|
@@ -88,6 +88,12 @@ class WorkletSequencer
|
|
|
88
88
|
* @type {Object<number, number>}
|
|
89
89
|
*/
|
|
90
90
|
this.midiPortChannelOffsets = {};
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* @type {boolean}
|
|
94
|
+
* @private
|
|
95
|
+
*/
|
|
96
|
+
this._skipToFirstNoteOn = true;
|
|
91
97
|
}
|
|
92
98
|
|
|
93
99
|
/**
|
|
@@ -113,12 +119,27 @@ class WorkletSequencer
|
|
|
113
119
|
|
|
114
120
|
set currentTime(time)
|
|
115
121
|
{
|
|
116
|
-
if(time
|
|
122
|
+
if(time > this.duration || time < 0)
|
|
117
123
|
{
|
|
118
124
|
// time is 0
|
|
119
|
-
|
|
125
|
+
if(this._skipToFirstNoteOn)
|
|
126
|
+
{
|
|
127
|
+
this.setTimeTicks(this.midiData.firstNoteOn - 1);
|
|
128
|
+
}
|
|
129
|
+
else
|
|
130
|
+
{
|
|
131
|
+
this.setTimeTicks(0);
|
|
132
|
+
}
|
|
120
133
|
return;
|
|
121
134
|
}
|
|
135
|
+
if(this._skipToFirstNoteOn)
|
|
136
|
+
{
|
|
137
|
+
if(time < this.firstNoteTime)
|
|
138
|
+
{
|
|
139
|
+
this.setTimeTicks(this.midiData.firstNoteOn - 1);
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
122
143
|
this.stop();
|
|
123
144
|
this.playingNotes = [];
|
|
124
145
|
this.pausedTime = undefined;
|