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.
@@ -0,0 +1,3 @@
1
+ export namespace libvorbis {
2
+ function init(...args: any[]): void;
3
+ }
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "spessasynth_lib",
3
- "version": "3.9.20",
3
+ "version": "3.9.22",
4
4
  "description": "No compromise MIDI and SoundFont2 Synthesizer library",
5
5
  "browser": "index.js",
6
6
  "types": "@types/index.d.ts",
@@ -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 = Array(channelsToSave).fill(-1 );
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
- programs[channel] = event.messageData[0];
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(this.midiData.embeddedSoundFont !== undefined && controllerNumber === midiControllers.bankSelect)
140
+ if(controllerNumber === midiControllers.bankSelect)
129
141
  {
130
- // special case if the RMID is embedded: subtract 1 from bank. See wiki About-RMIDI
131
- ccV--;
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] !== 0)
198
+ if(programs[channelNumber].program !== -1)
181
199
  {
182
- this.sendMIDIMessage([messageTypes.programChange | (channelNumber % 16), programs[channelNumber]]);
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
- this.synth.programChange(channelNumber, programs[channelNumber]);
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
  /**
@@ -148,7 +148,6 @@ export function loadNewSongList(midiBuffers)
148
148
  }, []);
149
149
  if(this.songs.length < 1)
150
150
  {
151
- console.log("no valid songs!")
152
151
  return;
153
152
  }
154
153
  this.songIndex = 0;
@@ -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 < this.firstNoteTime || time > this.duration)
122
+ if(time > this.duration || time < 0)
117
123
  {
118
124
  // time is 0
119
- this.setTimeTicks(this.midiData.firstNoteOn - 1);
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;