spessasynth_lib 3.10.0 → 3.11.1

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.
@@ -92,6 +92,23 @@ export class MidiData {
92
92
  * @type {Uint8Array}
93
93
  */
94
94
  rawMidiName: Uint8Array;
95
+ /**
96
+ * Indicates if the midi has an embedded soundfont
97
+ * @type {boolean}
98
+ */
99
+ isEmbedded: boolean;
100
+ /**
101
+ * The RMID Info data if RMID, otherwise undefined
102
+ * @type {Object<string, IndexedByteArray>}
103
+ */
104
+ RMIDInfo: {
105
+ [x: string]: IndexedByteArray;
106
+ };
107
+ /**
108
+ * The bank offset for RMIDI
109
+ * @type {number}
110
+ */
111
+ bankOffset: number;
95
112
  }
96
113
  /**
97
114
  *
@@ -14,11 +14,29 @@ export class MIDI {
14
14
  * @type {ArrayBuffer}
15
15
  */
16
16
  embeddedSoundFont: ArrayBuffer;
17
+ /**
18
+ * The RMID Info data if RMID, otherwise undefined
19
+ * @type {Object<string, IndexedByteArray>}
20
+ */
21
+ RMIDInfo: {
22
+ [x: string]: IndexedByteArray;
23
+ };
24
+ /**
25
+ * The bank offset for RMIDI
26
+ * @type {number}
27
+ */
28
+ bankOffset: number;
17
29
  /**
18
30
  * Contains the copyright strings
19
31
  * @type {string}
20
32
  */
21
33
  copyright: string;
34
+ /**
35
+ * The MIDI name
36
+ * @type {string}
37
+ */
38
+ midiName: string;
39
+ rawMidiName: Uint8Array;
22
40
  format: number;
23
41
  tracksAmount: number;
24
42
  timeDivision: number;
@@ -76,8 +94,6 @@ export class MIDI {
76
94
  start: number;
77
95
  end: number;
78
96
  };
79
- midiName: string;
80
- rawMidiName: Uint8Array;
81
97
  fileName: string;
82
98
  /**
83
99
  * The total playback time, in seconds
@@ -101,5 +117,5 @@ export class MIDI {
101
117
  */
102
118
  private _ticksToSeconds;
103
119
  }
104
- import { MidiMessage } from './midi_message.js';
105
120
  import { IndexedByteArray } from '../utils/indexed_array.js';
121
+ import { MidiMessage } from './midi_message.js';
@@ -3,7 +3,20 @@
3
3
  * @param soundfontBinary {Uint8Array}
4
4
  * @param mid {MIDI}
5
5
  * @param soundfont {SoundFont2}
6
+ * @param bankOffset {number} the bank offset for RMIDI
7
+ * @param encoding {string} the encoding of the RMIDI info chunk
6
8
  * @returns {IndexedByteArray}
7
9
  */
8
- export function writeRMIDI(soundfontBinary: Uint8Array, mid: MIDI, soundfont: SoundFont2): IndexedByteArray;
10
+ export function writeRMIDI(soundfontBinary: Uint8Array, mid: MIDI, soundfont: SoundFont2, bankOffset?: number, encoding?: string): IndexedByteArray;
11
+ export type RMIDINFOChunks = string;
12
+ export namespace RMIDINFOChunks {
13
+ let name: string;
14
+ let copyright: string;
15
+ let creationDate: string;
16
+ let comment: string;
17
+ let engineer: string;
18
+ let software: string;
19
+ let encoding: string;
20
+ let bankOffset: string;
21
+ }
9
22
  import { IndexedByteArray } from '../utils/indexed_array.js';
@@ -97,6 +97,23 @@ export class MidiData
97
97
  * @type {Uint8Array}
98
98
  */
99
99
  this.rawMidiName = midi.rawMidiName;
100
+
101
+ /**
102
+ * Indicates if the midi has an embedded soundfont
103
+ * @type {boolean}
104
+ */
105
+ this.isEmbedded = midi.embeddedSoundFont !== undefined;
106
+
107
+ /**
108
+ * The RMID Info data if RMID, otherwise undefined
109
+ * @type {Object<string, IndexedByteArray>}
110
+ */
111
+ this.RMIDInfo = midi.RMIDInfo;
112
+ /**
113
+ * The bank offset for RMIDI
114
+ * @type {number}
115
+ */
116
+ this.bankOffset = midi.bankOffset;
100
117
  }
101
118
  }
102
119
 
@@ -124,5 +141,8 @@ export const DUMMY_MIDI_DATA = {
124
141
  rawMidiName: new Uint8Array([76, 111, 97, 100, 105, 110, 103, 46, 46, 46]), // "Loading..."
125
142
  usedChannelsOnTrack: [],
126
143
  timeDivision: 0,
127
- keyRange: {min: 0, max: 127}
144
+ keyRange: {min: 0, max: 127},
145
+ isEmbedded: false,
146
+ RMIDInfo: undefined,
147
+ bankOffset: 0
128
148
  };
@@ -186,6 +186,21 @@ export function modifyMIDI(
186
186
  midiSystem = "xg";
187
187
  addedGs = true; // flag as true so gs won't get added
188
188
  }
189
+ else
190
+ // check for xg program change
191
+ if (
192
+ message.messageData[0] === 0x43 // yamaha
193
+ && message.messageData[2] === 0x4C // XG
194
+ && message.messageData[3] === 0x08 // part parameter
195
+ && message.messageData[5] === 0x03 // program change
196
+ )
197
+ {
198
+ programChanges.push({
199
+ track: trackNum,
200
+ message: message,
201
+ channel: message.messageData[4]
202
+ });
203
+ }
189
204
  }
190
205
  })
191
206
  });
@@ -6,6 +6,8 @@ import { readRIFFChunk } from '../soundfont/read/riff_chunk.js'
6
6
  import { readVariableLengthQuantity } from '../utils/byte_functions/variable_length_quantity.js'
7
7
  import { readBytesAsUintBigEndian } from '../utils/byte_functions/big_endian.js'
8
8
  import { readBytesAsString } from '../utils/byte_functions/string.js'
9
+ import { readBytesAsUintLittleEndian } from '../utils/byte_functions/little_endian.js'
10
+ import { RMIDINFOChunks } from './rmidi_writer.js'
9
11
 
10
12
  /**
11
13
  * midi_loader.js
@@ -29,17 +31,36 @@ class MIDI{
29
31
  */
30
32
  this.embeddedSoundFont = undefined;
31
33
 
34
+ /**
35
+ * The RMID Info data if RMID, otherwise undefined
36
+ * @type {Object<string, IndexedByteArray>}
37
+ */
38
+ this.RMIDInfo = undefined;
39
+ /**
40
+ * The bank offset for RMIDI
41
+ * @type {number}
42
+ */
43
+ this.bankOffset = 0;
44
+
32
45
  /**
33
46
  * Contains the copyright strings
34
47
  * @type {string}
35
48
  */
36
49
  this.copyright = "";
37
50
 
51
+ /**
52
+ * The MIDI name
53
+ * @type {string}
54
+ */
55
+ this.midiName = "";
56
+
57
+ this.rawMidiName = new Uint8Array(0);
58
+
38
59
  const initialString = readBytesAsString(binaryData, 4);
39
60
  binaryData.currentIndex -= 4;
40
61
  if(initialString === "RIFF")
41
62
  {
42
- // possibly an RMID file (https://web.archive.org/web/20110610135604/http://www.midi.org/about-midi/rp29spec(rmid).pdf)
63
+ // possibly an RMID file (https://github.com/spessasus/SpessaSynth/wiki/About-RMIDI)
43
64
  // skip size
44
65
  binaryData.currentIndex += 8;
45
66
  const rmid = readBytesAsString(binaryData, 4, undefined, false);
@@ -71,6 +92,34 @@ class MIDI{
71
92
  this.embeddedSoundFont = binaryData.slice(startIndex, startIndex + currentChunk.size).buffer;
72
93
  }
73
94
  }
95
+ else if(currentChunk.header === "LIST")
96
+ {
97
+ const type = readBytesAsString(currentChunk.chunkData, 4);
98
+ if(type === "INFO")
99
+ {
100
+ SpessaSynthInfo("%cFound RMIDI INFO chunk!", consoleColors.recognized);
101
+ this.RMIDInfo = {};
102
+ while(currentChunk.chunkData.currentIndex <= currentChunk.size)
103
+ {
104
+ const infoChunk = readRIFFChunk(currentChunk.chunkData, true);
105
+ this.RMIDInfo[infoChunk.header] = infoChunk.chunkData;
106
+ }
107
+ if(this.RMIDInfo['ICOP'])
108
+ {
109
+ this.copyright += readBytesAsString(this.RMIDInfo['ICOP'], this.RMIDInfo['ICOP'].length);
110
+ }
111
+ if(this.RMIDInfo['INAM'])
112
+ {
113
+ this.rawMidiName = this.RMIDInfo[RMIDINFOChunks.name];
114
+ this.midiName = readBytesAsString(this.rawMidiName, this.rawMidiName.length);
115
+ }
116
+ this.bankOffset = 1; // defaults to 1
117
+ if(this.RMIDInfo[RMIDINFOChunks.bankOffset])
118
+ {
119
+ this.bankOffset = readBytesAsUintLittleEndian(this.RMIDInfo[RMIDINFOChunks.bankOffset], 2);
120
+ }
121
+ }
122
+ }
74
123
  }
75
124
  }
76
125
  else
@@ -420,39 +469,40 @@ class MIDI{
420
469
  */
421
470
  this.loop = {start: loopStart, end: loopEnd};
422
471
 
423
- // get track name
424
- this.midiName = "";
425
-
426
- this.rawMidiName = new Uint8Array(0);
427
-
428
472
  // midi name
429
- if(this.tracks.length > 1)
473
+ if(this.midiName.length < 1)
430
474
  {
431
- // if more than 1 track and the first track has no notes, just find the first trackName in the first track
432
- if(this.tracks[0].find(
433
- message => message.messageStatusByte >= messageTypes.noteOn
434
- &&
435
- message.messageStatusByte < messageTypes.systemExclusive
436
- ) === undefined)
475
+ if (this.tracks.length > 1)
437
476
  {
477
+ // if more than 1 track and the first track has no notes, just find the first trackName in the first track
478
+ if (
479
+ this.tracks[0].find(
480
+ message => message.messageStatusByte >= messageTypes.noteOn
481
+ &&
482
+ message.messageStatusByte < messageTypes.systemExclusive
483
+ ) === undefined
484
+ )
485
+ {
486
+
487
+ let name = this.tracks[0].find(message => message.messageStatusByte === messageTypes.trackName);
488
+ if (name)
489
+ {
490
+ this.rawMidiName = name.messageData;
491
+ this.midiName = readBytesAsString(name.messageData, name.messageData.length, undefined, false);
492
+ }
493
+ }
494
+ }
495
+ else
496
+ {
497
+ // if only 1 track, find the first "track name" event
438
498
  let name = this.tracks[0].find(message => message.messageStatusByte === messageTypes.trackName);
439
- if(name)
499
+ if (name)
440
500
  {
441
501
  this.rawMidiName = name.messageData;
442
502
  this.midiName = readBytesAsString(name.messageData, name.messageData.length, undefined, false);
443
503
  }
444
504
  }
445
505
  }
446
- else
447
- {
448
- // if only 1 track, find the first "track name" event
449
- let name = this.tracks[0].find(message => message.messageStatusByte === messageTypes.trackName);
450
- if(name)
451
- {
452
- this.rawMidiName = name.messageData;
453
- this.midiName = readBytesAsString(name.messageData, name.messageData.length, undefined, false);
454
- }
455
- }
456
506
 
457
507
  this.fileName = fileName;
458
508
  this.midiName = this.midiName.trim();
@@ -5,19 +5,42 @@ import { getStringBytes } from '../utils/byte_functions/string.js'
5
5
  import { messageTypes, midiControllers, MidiMessage } from './midi_message.js'
6
6
  import { DEFAULT_PERCUSSION } from '../synthetizer/synthetizer.js'
7
7
  import { getGsOn } from './midi_editor.js'
8
- import { SpessaSynthInfo } from '../utils/loggin.js'
8
+ import { SpessaSynthGroup, SpessaSynthGroupEnd, SpessaSynthInfo } from '../utils/loggin.js'
9
9
  import { consoleColors } from '../utils/other.js'
10
+ import { writeLittleEndian } from '../utils/byte_functions/little_endian.js'
11
+
12
+ /**
13
+ * @enum {string}
14
+ */
15
+ export const RMIDINFOChunks = {
16
+ name: "INAM",
17
+ copyright: "ICOP",
18
+ creationDate: "ICRT",
19
+ comment: "ICMT",
20
+ engineer: "IENG",
21
+ software: "ISFT",
22
+ encoding: "IENC",
23
+ bankOffset: "DBNK"
24
+ }
10
25
 
11
26
  /**
12
27
  *
13
28
  * @param soundfontBinary {Uint8Array}
14
29
  * @param mid {MIDI}
15
30
  * @param soundfont {SoundFont2}
31
+ * @param bankOffset {number} the bank offset for RMIDI
32
+ * @param encoding {string} the encoding of the RMIDI info chunk
16
33
  * @returns {IndexedByteArray}
17
34
  */
18
- export function writeRMIDI(soundfontBinary, mid, soundfont)
35
+ export function writeRMIDI(soundfontBinary, mid, soundfont, bankOffset = 0, encoding = "Shift_JIS")
19
36
  {
20
- // add 1 to bank. See wiki About-RMIDI
37
+ SpessaSynthGroup("%cWriting the RMIDI File...", consoleColors.info);
38
+ SpessaSynthInfo(`%cConfiguration: Bank offset: %c${bankOffset}%c, encoding: %c${encoding}`,
39
+ consoleColors.info,
40
+ consoleColors.value,
41
+ consoleColors.info,
42
+ consoleColors.value);
43
+ // add offset to bank. See wiki About-RMIDI
21
44
  // also fix presets that don't exists since midiplayer6 doesn't seem to default to 0 when nonextistent...
22
45
  let system = "gm";
23
46
  /**
@@ -25,6 +48,8 @@ export function writeRMIDI(soundfontBinary, mid, soundfont)
25
48
  * @type {{tNum: number, e: MidiMessage}[]}
26
49
  */
27
50
  let unwantedSystems = [];
51
+ const channelsAmount = 16 + mid.midiPortChannelOffsets.reduce((max, cur) => cur > max ? cur: max);
52
+ const channelHasBankSelects = Array(channelsAmount).fill(false);
28
53
  mid.tracks.forEach((t, trackNum) => {
29
54
  /**
30
55
  * @type {boolean[]}
@@ -45,6 +70,7 @@ export function writeRMIDI(soundfontBinary, mid, soundfont)
45
70
  let drums = Array(16).fill(false);
46
71
  drums[DEFAULT_PERCUSSION] = true;
47
72
  let programs = Array(16).fill(0);
73
+ const portOffset = mid.midiPortChannelOffsets[mid.midiPorts[trackNum]];
48
74
  t.forEach(e => {
49
75
  const status = e.messageStatusByte & 0xF0;
50
76
  if(
@@ -114,6 +140,7 @@ export function writeRMIDI(soundfontBinary, mid, soundfont)
114
140
  {
115
141
  if(soundfont.presets.findIndex(p => p.program === e.messageData[0] && p.bank === 128) === -1)
116
142
  {
143
+ // doesn't exist. pick any preset that has the 128 bank.
117
144
  e.messageData[0] = soundfont.presets.find(p => p.bank === 128)?.program || 0;
118
145
  }
119
146
  }
@@ -121,12 +148,14 @@ export function writeRMIDI(soundfontBinary, mid, soundfont)
121
148
  {
122
149
  if (soundfont.presets.findIndex(p => p.program === e.messageData[0] && p.bank !== 128) === -1)
123
150
  {
151
+ // doesn't exist. pick any preset that does not have the 128 bank.
124
152
  e.messageData[0] = soundfont.presets.find(p => p.bank !== 128)?.program || 0;
125
153
  }
126
154
  }
127
155
  programs[e.messageStatusByte & 0xf] = e.messageData[0];
128
156
  // check if this preset exists for program and bank
129
- const bank = lastBankChanges[chNum]?.messageData[1];
157
+ const realBank = lastBankChanges[chNum]?.messageData[1] - mid.bankOffset; // make sure to take the previous bank offset into account
158
+ const bank = drums[chNum] ? 128 : realBank;
130
159
  if(bank === undefined)
131
160
  {
132
161
  return;
@@ -140,17 +169,18 @@ export function writeRMIDI(soundfontBinary, mid, soundfont)
140
169
 
141
170
  if(soundfont.presets.findIndex(p => p.bank === bank && p.program === e.messageData[0]) === -1)
142
171
  {
143
- // no preset with this bank. set to 1 (0)
144
- lastBankChanges[chNum].messageData[1] = 1;
145
- SpessaSynthInfo(`%cNo preset %c${bank}:${e.messageData[0]}%c. Changing bank to 1.`,
172
+ // no preset with this bank. find this program with any bank
173
+ const targetPreset = (soundfont.presets.find(p => p.program === e.messageData[0])?.bank + bankOffset) || bankOffset;
174
+ lastBankChanges[chNum].messageData[1] = targetPreset;
175
+ SpessaSynthInfo(`%cNo preset %c${bank}:${e.messageData[0]}%c. Changing bank to ${targetPreset}.`,
146
176
  consoleColors.info,
147
177
  consoleColors.recognized,
148
178
  consoleColors.info);
149
179
  }
150
180
  else
151
181
  {
152
- // there is a preset with this bank. add 1
153
- lastBankChanges[chNum].messageData[1]++;
182
+ // there is a preset with this bank. add offset
183
+ lastBankChanges[chNum].messageData[1] = realBank + bankOffset;
154
184
  SpessaSynthInfo(`%cPreset %c${bank}:${e.messageData[0]}%c exists. Changing bank to ${lastBankChanges[chNum].messageData[1]}.`,
155
185
  consoleColors.info,
156
186
  consoleColors.recognized,
@@ -164,6 +194,7 @@ export function writeRMIDI(soundfontBinary, mid, soundfont)
164
194
  }
165
195
  // bank select
166
196
  hasBankSelects[chNum] = true;
197
+ channelHasBankSelects[chNum + portOffset] = true;
167
198
  if(system === "xg")
168
199
  {
169
200
  // check for xg drums
@@ -177,6 +208,11 @@ export function writeRMIDI(soundfontBinary, mid, soundfont)
177
208
  {
178
209
  return;
179
210
  }
211
+ // if this channel has bank selects but not in this track specifically, ignore too
212
+ if(channelHasBankSelects[ch + portOffset] === true)
213
+ {
214
+ return;
215
+ }
180
216
  // find first program change (for the given channel)
181
217
  const status = messageTypes.programChange | ch;
182
218
  let indexToAdd = t.findIndex(e => e.messageStatusByte === status);
@@ -200,7 +236,7 @@ export function writeRMIDI(soundfontBinary, mid, soundfont)
200
236
  indexToAdd = programIndex;
201
237
  }
202
238
  const ticks = t[indexToAdd].ticks;
203
- const targetBank = (soundfont.getPreset(0, programs[ch])?.bank + 1) || 1;
239
+ const targetBank = (soundfont.getPreset(0, programs[ch])?.bank + bankOffset) || bankOffset;
204
240
  t.splice(indexToAdd,0, new MidiMessage(
205
241
  ticks,
206
242
  messageTypes.controllerChange | ch,
@@ -222,30 +258,60 @@ export function writeRMIDI(soundfontBinary, mid, soundfont)
222
258
  }
223
259
  const newMid = new IndexedByteArray(writeMIDIFile(mid).buffer);
224
260
 
225
- // infodata for MidiPlayer6
261
+ // infodata for RMID
226
262
  const today = new Date().toLocaleString();
263
+ const DBNK = new IndexedByteArray(2);
264
+ writeLittleEndian(DBNK, bankOffset, 2);
265
+ const ICOP = mid.copyright.length > 0 ? getStringBytes(mid.copyright) : getStringBytes("Created by SpessaSynth");
227
266
  const infodata = combineArrays([
267
+ // icop: copyright
228
268
  writeRIFFChunk(
229
269
  new RiffChunk(
230
- "ICOP",
231
- 11,
232
- getStringBytes("SpessaSynth")
270
+ RMIDINFOChunks.copyright,
271
+ ICOP.length,
272
+ ICOP
233
273
  ),
234
274
  new IndexedByteArray([73, 78, 70, 79]) // "INFO"
235
275
  ),
276
+ // inam: name
236
277
  writeRIFFChunk(
237
278
  new RiffChunk(
238
- "INAM",
279
+ RMIDINFOChunks.name,
239
280
  mid.rawMidiName.length,
240
281
  new IndexedByteArray(mid.rawMidiName.buffer)
241
282
  ),
242
283
  ),
284
+ // icrd: creation date
243
285
  writeRIFFChunk(
244
286
  new RiffChunk(
245
- "ICRD",
287
+ RMIDINFOChunks.creationDate,
246
288
  today.length,
247
289
  getStringBytes(today)
248
290
  )
291
+ ),
292
+ // isft: software used
293
+ writeRIFFChunk(
294
+ new RiffChunk(
295
+ RMIDINFOChunks.software,
296
+ 11,
297
+ getStringBytes("SpessaSynth"),
298
+ )
299
+ ),
300
+ // ienc: encoding for the info chunk
301
+ writeRIFFChunk(
302
+ new RiffChunk(
303
+ RMIDINFOChunks.encoding,
304
+ encoding.length,
305
+ getStringBytes(encoding)
306
+ )
307
+ ),
308
+ // dbnk: bank offset
309
+ writeRIFFChunk(
310
+ new RiffChunk(
311
+ RMIDINFOChunks.bankOffset,
312
+ 2,
313
+ DBNK
314
+ )
249
315
  )
250
316
  ]);
251
317
 
@@ -263,6 +329,8 @@ export function writeRMIDI(soundfontBinary, mid, soundfont)
263
329
  )),
264
330
  soundfontBinary
265
331
  ]);
332
+ SpessaSynthInfo("%cFinished!", consoleColors.info)
333
+ SpessaSynthGroupEnd();
266
334
  return writeRIFFChunk(new RiffChunk(
267
335
  "RIFF",
268
336
  rmiddata.length,
@@ -31,11 +31,6 @@ export function getUsedProgramsAndKeys(mid, soundfont)
31
31
  {
32
32
  // check if this exists in the soundfont
33
33
  let exists = soundfont.getPreset(ch.bank, ch.program);
34
- if(exists.bank !== ch.bank && mid.embeddedSoundFont)
35
- {
36
- // maybe it doesn't exists becase RMIDI has a bank shift?
37
- exists = soundfont.getPreset(ch.bank - 1, ch.program);
38
- }
39
34
  ch.bank = exists.bank;
40
35
  ch.program = exists.program;
41
36
  ch.string = ch.bank + ":" + ch.program;
@@ -128,6 +123,7 @@ export function getUsedProgramsAndKeys(mid, soundfont)
128
123
  continue;
129
124
  }
130
125
  const bank = event.messageData[1];
126
+ const realBank = Math.max(0, bank - mid.bankOffset);
131
127
  if(system === "xg")
132
128
  {
133
129
  // check for xg drums
@@ -136,16 +132,16 @@ export function getUsedProgramsAndKeys(mid, soundfont)
136
132
  {
137
133
  // drum change is a program change
138
134
  ch.drums = drumsBool;
139
- ch.bank = ch.drums ? 128 : bank;
135
+ ch.bank = ch.drums ? 128 : realBank;
140
136
  updateString(ch);
141
137
  }
142
138
  else
143
139
  {
144
- ch.bank = ch.drums ? 128 : bank;
140
+ ch.bank = ch.drums ? 128 : realBank;
145
141
  }
146
142
  continue;
147
143
  }
148
- channelPresets[channel].bank = bank;
144
+ channelPresets[channel].bank = realBank;
149
145
  // do not update the data, bank change doesnt change the preset
150
146
  break;
151
147
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "spessasynth_lib",
3
- "version": "3.10.0",
3
+ "version": "3.11.1",
4
4
  "description": "No compromise MIDI and SoundFont2 Synthesizer library",
5
5
  "browser": "index.js",
6
6
  "types": "@types/index.d.ts",
@@ -190,7 +190,7 @@ export function _playTo(time, ticks = undefined)
190
190
  });
191
191
 
192
192
  // restore programs
193
- if(programs[channelNumber].program !== -1)
193
+ if(programs[channelNumber].program >= 0 && programs[channelNumber].actualBank >= 0)
194
194
  {
195
195
  const bank = programs[channelNumber].actualBank;
196
196
  this.sendMIDIMessage([messageTypes.controllerChange | (channelNumber % 16), midiControllers.bankSelect, bank]);
@@ -219,7 +219,7 @@ export function _playTo(time, ticks = undefined)
219
219
  })
220
220
  }
221
221
  // restore programs
222
- if(programs[channelNumber].program !== -1)
222
+ if(programs[channelNumber].program >= 0 && programs[channelNumber].actualBank >= 0)
223
223
  {
224
224
  const bank = programs[channelNumber].actualBank;
225
225
  this.synth.controllerChange(channelNumber, midiControllers.bankSelect, bank);
@@ -56,9 +56,10 @@ export function loadNewSequence(parsedMidi)
56
56
  // check for embedded soundfont
57
57
  if(this.midiData.embeddedSoundFont !== undefined)
58
58
  {
59
+ SpessaSynthInfo("%cEmbedded soundfont detected! Using it.", consoleColors.recognized);
59
60
  this.synth.reloadSoundFont(this.midiData.embeddedSoundFont);
60
61
  // set offet
61
- this.synth.soundfontBankOffset = 1;
62
+ this.synth.soundfontBankOffset = this.midiData.bankOffset;
62
63
  // preload all samples
63
64
  this.synth.soundfont.samples.forEach(s => s.getAudioData());
64
65
  }
@@ -45,6 +45,11 @@ export function readRIFFChunk(dataArray, readData = true, forceShift = false) {
45
45
  dataArray.currentIndex += size;
46
46
  }
47
47
 
48
+ if(size % 2 !== 0)
49
+ {
50
+ dataArray.currentIndex++;
51
+ }
52
+
48
53
  return new RiffChunk(header, size, chunkData)
49
54
  }
50
55