spessasynth_lib 3.16.4 → 3.17.0

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.
Files changed (61) hide show
  1. package/@types/index.d.ts +5 -4
  2. package/@types/midi_parser/basic_midi.d.ts +125 -0
  3. package/@types/midi_parser/midi_builder.d.ts +69 -0
  4. package/@types/midi_parser/midi_data.d.ts +2 -2
  5. package/@types/midi_parser/midi_editor.d.ts +4 -4
  6. package/@types/midi_parser/midi_loader.d.ts +3 -100
  7. package/@types/midi_parser/midi_writer.d.ts +2 -2
  8. package/@types/midi_parser/rmidi_writer.d.ts +4 -3
  9. package/@types/midi_parser/used_keys_loaded.d.ts +2 -2
  10. package/@types/sequencer/sequencer.d.ts +1 -1
  11. package/@types/soundfont/basic_soundfont/basic_soundfont.d.ts +5 -4
  12. package/@types/soundfont/basic_soundfont/write_sf2/soundfont_trimmer.d.ts +2 -2
  13. package/@types/soundfont/dls/dls_preset.d.ts +11 -0
  14. package/@types/soundfont/dls/dls_soundfont.d.ts +24 -0
  15. package/@types/soundfont/dls/read_instrument.d.ts +5 -0
  16. package/@types/soundfont/dls/read_instrument_list.d.ts +5 -0
  17. package/@types/soundfont/load_soundfont.d.ts +6 -0
  18. package/@types/soundfont/soundfont.d.ts +2 -1
  19. package/@types/synthetizer/synthetizer.d.ts +2 -2
  20. package/@types/synthetizer/worklet_system/worklet_utilities/worklet_processor_channel.d.ts +2 -2
  21. package/@types/utils/byte_functions/little_endian.d.ts +1 -1
  22. package/README.md +17 -15
  23. package/index.js +6 -4
  24. package/midi_parser/basic_midi.js +146 -0
  25. package/midi_parser/midi_builder.js +281 -0
  26. package/midi_parser/midi_data.js +1 -1
  27. package/midi_parser/midi_editor.js +2 -2
  28. package/midi_parser/midi_loader.js +17 -53
  29. package/midi_parser/midi_writer.js +1 -1
  30. package/midi_parser/rmidi_writer.js +227 -246
  31. package/midi_parser/used_keys_loaded.js +1 -1
  32. package/package.json +1 -1
  33. package/sequencer/sequencer.js +1 -1
  34. package/sequencer/worklet_sequencer/song_control.js +3 -3
  35. package/sequencer/worklet_sequencer/worklet_sequencer.js +1 -1
  36. package/soundfont/basic_soundfont/basic_soundfont.js +25 -10
  37. package/soundfont/basic_soundfont/riff_chunk.js +2 -2
  38. package/soundfont/basic_soundfont/write_sf2/soundfont_trimmer.js +1 -1
  39. package/soundfont/dls/dls_preset.js +25 -0
  40. package/soundfont/dls/dls_soundfont.js +93 -0
  41. package/soundfont/dls/read_instrument.js +22 -0
  42. package/soundfont/dls/read_instrument_list.js +17 -0
  43. package/soundfont/load_soundfont.js +21 -0
  44. package/soundfont/read_sf2/instruments.js +2 -2
  45. package/soundfont/read_sf2/modulators.js +5 -5
  46. package/soundfont/read_sf2/presets.js +7 -7
  47. package/soundfont/read_sf2/samples.js +8 -8
  48. package/soundfont/read_sf2/zones.js +5 -5
  49. package/soundfont/soundfont.js +8 -3
  50. package/synthetizer/synthetizer.js +1 -1
  51. package/synthetizer/worklet_processor.min.js +7 -6
  52. package/synthetizer/worklet_system/main_processor.js +1 -2
  53. package/synthetizer/worklet_system/worklet_methods/program_control.js +12 -20
  54. package/synthetizer/worklet_system/worklet_methods/worklet_soundfont_manager/worklet_soundfont_manager.js +5 -5
  55. package/synthetizer/worklet_system/worklet_utilities/worklet_processor_channel.js +1 -1
  56. package/utils/byte_functions/little_endian.js +1 -1
  57. /package/@types/{midi_handler → external_midi}/midi_handler.d.ts +0 -0
  58. /package/@types/{midi_handler → external_midi}/web_midi_link.d.ts +0 -0
  59. /package/{midi_handler → external_midi}/README.md +0 -0
  60. /package/{midi_handler → external_midi}/midi_handler.js +0 -0
  61. /package/{midi_handler → external_midi}/web_midi_link.js +0 -0
@@ -47,14 +47,23 @@ const DEFAULT_COPYRIGHT = "Created using SpessaSynth";
47
47
  /**
48
48
  * Writes an RMIDI file
49
49
  * @param soundfontBinary {Uint8Array}
50
- * @param mid {MIDI}
51
- * @param soundfont {SoundFont2}
50
+ * @param mid {BasicMIDI}
51
+ * @param soundfont {BasicSoundFont}
52
52
  * @param bankOffset {number} the bank offset for RMIDI
53
53
  * @param encoding {string} the encoding of the RMIDI info chunk
54
54
  * @param metadata {RMIDMetadata} the metadata of the file. Optional. If provided, the encoding is forced to utf-8/
55
+ * @param correctBankOffset {boolean}
55
56
  * @returns {IndexedByteArray}
56
57
  */
57
- export function writeRMIDI(soundfontBinary, mid, soundfont, bankOffset = 0, encoding = "Shift_JIS", metadata = {})
58
+ export function writeRMIDI(
59
+ soundfontBinary,
60
+ mid,
61
+ soundfont,
62
+ bankOffset = 0,
63
+ encoding = "Shift_JIS",
64
+ metadata = {},
65
+ correctBankOffset = true
66
+ )
58
67
  {
59
68
  SpessaSynthGroup("%cWriting the RMIDI File...", consoleColors.info);
60
69
  SpessaSynthInfo(`%cConfiguration: Bank offset: %c${bankOffset}%c, encoding: %c${encoding}`,
@@ -64,271 +73,243 @@ export function writeRMIDI(soundfontBinary, mid, soundfont, bankOffset = 0, enco
64
73
  consoleColors.value);
65
74
  SpessaSynthInfo("metadata", metadata);
66
75
  SpessaSynthInfo("Initial bank offset", mid.bankOffset);
67
- // add offset to bank. See https://github.com/spessasus/sf2-rmidi-specification#readme
68
- // also fix presets that don't exists since midiplayer6 doesn't seem to default to 0 when nonextistent...
69
- let system = "gm";
70
- /**
71
- * The unwanted system messages such as gm/gm2 on
72
- * @type {{tNum: number, e: MidiMessage}[]}
73
- */
74
- let unwantedSystems = [];
75
- /**
76
- * indexes for tracks
77
- * @type {number[]}
78
- */
79
- const eventIndexes = Array(mid.tracks.length).fill(0);
80
- let remainingTracks = mid.tracks.length;
81
- function findFirstEventIndex()
82
- {
83
- let index = 0;
84
- let ticks = Infinity;
85
- mid.tracks.forEach((track, i) => {
86
- if(eventIndexes[i] >= track.length)
87
- {
88
- return;
89
- }
90
- if(track[eventIndexes[i]].ticks < ticks)
91
- {
92
- index = i;
93
- ticks = track[eventIndexes[i]].ticks;
94
- }
95
- });
96
- return index;
97
- }
98
- // it copies midiPorts everywhere else, but here 0 works so DO NOT CHANGE!!!!!!!
99
- const ports = Array(mid.tracks.length).fill(0);
100
- const channelsAmount = 16 + mid.midiPortChannelOffsets.reduce((max, cur) => cur > max ? cur: max);
101
- /**
102
- * @type {{
103
- * program: number,
104
- * drums: boolean,
105
- * lastBank: MidiMessage,
106
- * hasBankSelect: boolean
107
- * }[]}
108
- */
109
- const channelsInfo = [];
110
- for (let i = 0; i < channelsAmount; i++)
76
+ if(correctBankOffset)
111
77
  {
112
- channelsInfo.push({
113
- program: 0,
114
- drums: i % 16 === DEFAULT_PERCUSSION, // drums appear on 9 every 16 channels,
115
- lastBank: undefined,
116
- hasBankSelect: false,
117
- });
118
- }
119
- while(remainingTracks > 0)
120
- {
121
- let trackNum = findFirstEventIndex();
122
- const track = mid.tracks[trackNum];
123
- if(eventIndexes[trackNum] >= track.length)
124
- {
125
- remainingTracks--;
126
- continue;
127
- }
128
- const e = track[eventIndexes[trackNum]];
129
- eventIndexes[trackNum]++;
78
+ // add offset to bank.
79
+ // See https://github.com/spessasus/sf2-rmidi-specification#readme
80
+ // also fix presets that don't exists
81
+ // since midiplayer6 doesn't seem to default to 0 when nonextistent...
82
+ let system = "gm";
83
+ /**
84
+ * The unwanted system messages such as gm/gm2 on
85
+ * @type {{tNum: number, e: MidiMessage}[]}
86
+ */
87
+ let unwantedSystems = [];
88
+ /**
89
+ * indexes for tracks
90
+ * @type {number[]}
91
+ */
92
+ const eventIndexes = Array(mid.tracks.length).fill(0);
93
+ let remainingTracks = mid.tracks.length;
130
94
 
131
- let portOffset = mid.midiPortChannelOffsets[ports[trackNum]];
132
- if(e.messageStatusByte === messageTypes.midiPort)
133
- {
134
- ports[trackNum] = e.messageData[0];
135
- continue;
95
+ function findFirstEventIndex() {
96
+ let index = 0;
97
+ let ticks = Infinity;
98
+ mid.tracks.forEach((track, i) => {
99
+ if (eventIndexes[i] >= track.length) {
100
+ return;
101
+ }
102
+ if (track[eventIndexes[i]].ticks < ticks) {
103
+ index = i;
104
+ ticks = track[eventIndexes[i]].ticks;
105
+ }
106
+ });
107
+ return index;
136
108
  }
137
- const status = e.messageStatusByte & 0xF0;
138
- if(
139
- status !== messageTypes.controllerChange &&
140
- status !== messageTypes.programChange &&
141
- status !== messageTypes.systemExclusive
142
- )
143
- {
144
- continue
109
+
110
+ // it copies midiPorts everywhere else, but here 0 works so DO NOT CHANGE!!!!!!!
111
+ const ports = Array(mid.tracks.length).fill(0);
112
+ const channelsAmount = 16 + mid.midiPortChannelOffsets.reduce((max, cur) => cur > max ? cur : max);
113
+ /**
114
+ * @type {{
115
+ * program: number,
116
+ * drums: boolean,
117
+ * lastBank: MidiMessage,
118
+ * hasBankSelect: boolean
119
+ * }[]}
120
+ */
121
+ const channelsInfo = [];
122
+ for (let i = 0; i < channelsAmount; i++) {
123
+ channelsInfo.push({
124
+ program: 0,
125
+ drums: i % 16 === DEFAULT_PERCUSSION, // drums appear on 9 every 16 channels,
126
+ lastBank: undefined,
127
+ hasBankSelect: false,
128
+ });
145
129
  }
130
+ while (remainingTracks > 0) {
131
+ let trackNum = findFirstEventIndex();
132
+ const track = mid.tracks[trackNum];
133
+ if (eventIndexes[trackNum] >= track.length) {
134
+ remainingTracks--;
135
+ continue;
136
+ }
137
+ const e = track[eventIndexes[trackNum]];
138
+ eventIndexes[trackNum]++;
146
139
 
147
- if(status === messageTypes.systemExclusive)
148
- {
149
- // check for drum sysex
150
- if(
151
- e.messageData[0] !== 0x41 || // roland
152
- e.messageData[2] !== 0x42 || // GS
153
- e.messageData[3] !== 0x12 || // GS
154
- e.messageData[4] !== 0x40 || // system parameter
155
- (e.messageData[5] & 0x10 ) === 0 || // part parameter
156
- e.messageData[6] !== 0x15 // drum part
157
- )
158
- {
159
- // check for XG
160
- if(
161
- e.messageData[0] === 0x43 && // yamaha
162
- e.messageData[2] === 0x4C && // sXG ON
163
- e.messageData[5] === 0x7E &&
164
- e.messageData[6] === 0x00
165
- )
166
- {
167
- system = "xg";
168
- }
169
- else
170
- if(
171
- e.messageData[0] === 0x41 // roland
172
- && e.messageData[2] === 0x42 // GS
173
- && e.messageData[6] === 0x7F // Mode set
174
- )
175
- {
176
- system = "gs";
177
- }
178
- else
179
- if(
180
- e.messageData[0] === 0x7E // non realtime
181
- && e.messageData[2] === 0x09 // gm system
182
- )
183
- {
184
- system = "gm";
185
- unwantedSystems.push({
186
- tNum: trackNum,
187
- e: e
188
- });
189
- }
140
+ let portOffset = mid.midiPortChannelOffsets[ports[trackNum]];
141
+ if (e.messageStatusByte === messageTypes.midiPort) {
142
+ ports[trackNum] = e.messageData[0];
190
143
  continue;
191
144
  }
192
- const sysexChannel = [9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 11, 12, 13, 14, 15][e.messageData[5] & 0x0F] + portOffset;
193
- channelsInfo[sysexChannel].drums = !!(e.messageData[7] > 0 && e.messageData[5] >> 4);
194
- continue;
195
- }
145
+ const status = e.messageStatusByte & 0xF0;
146
+ if (
147
+ status !== messageTypes.controllerChange &&
148
+ status !== messageTypes.programChange &&
149
+ status !== messageTypes.systemExclusive
150
+ ) {
151
+ continue
152
+ }
196
153
 
197
- // program change
198
- const chNum = (e.messageStatusByte & 0xF) + portOffset;
199
- const channel = channelsInfo[chNum];
200
- if(status === messageTypes.programChange)
201
- {
202
- // check if the preset for this program exists
203
- if(channel.drums)
204
- {
205
- if(soundfont.presets.findIndex(p => p.program === e.messageData[0] && p.bank === 128) === -1)
206
- {
207
- // doesn't exist. pick any preset that has the 128 bank.
208
- e.messageData[0] = soundfont.presets.find(p => p.bank === 128)?.program || 0;
154
+ if (status === messageTypes.systemExclusive) {
155
+ // check for drum sysex
156
+ if (
157
+ e.messageData[0] !== 0x41 || // roland
158
+ e.messageData[2] !== 0x42 || // GS
159
+ e.messageData[3] !== 0x12 || // GS
160
+ e.messageData[4] !== 0x40 || // system parameter
161
+ (e.messageData[5] & 0x10) === 0 || // part parameter
162
+ e.messageData[6] !== 0x15 // drum part
163
+ ) {
164
+ // check for XG
165
+ if (
166
+ e.messageData[0] === 0x43 && // yamaha
167
+ e.messageData[2] === 0x4C && // sXG ON
168
+ e.messageData[5] === 0x7E &&
169
+ e.messageData[6] === 0x00
170
+ ) {
171
+ system = "xg";
172
+ } else if (
173
+ e.messageData[0] === 0x41 // roland
174
+ && e.messageData[2] === 0x42 // GS
175
+ && e.messageData[6] === 0x7F // Mode set
176
+ ) {
177
+ system = "gs";
178
+ } else if (
179
+ e.messageData[0] === 0x7E // non realtime
180
+ && e.messageData[2] === 0x09 // gm system
181
+ ) {
182
+ system = "gm";
183
+ unwantedSystems.push({
184
+ tNum: trackNum,
185
+ e: e
186
+ });
187
+ }
188
+ continue;
209
189
  }
190
+ const sysexChannel = [9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 11, 12, 13, 14, 15][e.messageData[5] & 0x0F] + portOffset;
191
+ channelsInfo[sysexChannel].drums = !!(e.messageData[7] > 0 && e.messageData[5] >> 4);
192
+ continue;
210
193
  }
211
- else
212
- {
213
- if (soundfont.presets.findIndex(p => p.program === e.messageData[0] && p.bank !== 128) === -1)
214
- {
215
- // doesn't exist. pick any preset that does not have the 128 bank.
216
- e.messageData[0] = soundfont.presets.find(p => p.bank !== 128)?.program || 0;
194
+
195
+ // program change
196
+ const chNum = (e.messageStatusByte & 0xF) + portOffset;
197
+ const channel = channelsInfo[chNum];
198
+ if (status === messageTypes.programChange) {
199
+ // check if the preset for this program exists
200
+ if (channel.drums) {
201
+ if (soundfont.presets.findIndex(p => p.program === e.messageData[0] && p.bank === 128) === -1) {
202
+ // doesn't exist. pick any preset that has the 128 bank.
203
+ e.messageData[0] = soundfont.presets.find(p => p.bank === 128)?.program || 0;
204
+ }
205
+ } else {
206
+ if (soundfont.presets.findIndex(p => p.program === e.messageData[0] && p.bank !== 128) === -1) {
207
+ // doesn't exist. pick any preset that does not have the 128 bank.
208
+ e.messageData[0] = soundfont.presets.find(p => p.bank !== 128)?.program || 0;
209
+ }
210
+ }
211
+ channel.program = e.messageData[0];
212
+ // check if this preset exists for program and bank
213
+ const realBank = Math.max(0, channel.lastBank?.messageData[1] - mid.bankOffset); // make sure to take the previous bank offset into account
214
+ const bank = channel.drums ? 128 : realBank;
215
+ if (channel.lastBank === undefined) {
216
+ continue;
217
+ }
218
+ if (system === "xg" && channel.drums) {
219
+ // drums override: set bank to 127
220
+ channelsInfo[chNum].lastBank.messageData[1] = 127;
221
+ }
222
+
223
+ if (soundfont.presets.findIndex(p => p.bank === bank && p.program === e.messageData[0]) === -1) {
224
+ // no preset with this bank. find this program with any bank
225
+ const targetBank = (soundfont.presets.find(p => p.program === e.messageData[0])?.bank + bankOffset) || bankOffset;
226
+ channel.lastBank.messageData[1] = targetBank;
227
+ SpessaSynthInfo(`%cNo preset %c${bank}:${e.messageData[0]}%c. Changing bank to ${targetBank}.`,
228
+ consoleColors.info,
229
+ consoleColors.recognized,
230
+ consoleColors.info);
231
+ } else {
232
+ // there is a preset with this bank. add offset. For drums add the normal offset.
233
+ const newBank = (bank === 128 ? 0 : realBank) + bankOffset;
234
+ channel.lastBank.messageData[1] = newBank;
235
+ SpessaSynthInfo(`%cPreset %c${bank}:${e.messageData[0]}%c exists. Changing bank to ${newBank}.`,
236
+ consoleColors.info,
237
+ consoleColors.recognized,
238
+ consoleColors.info);
217
239
  }
218
- }
219
- channel.program = e.messageData[0];
220
- // check if this preset exists for program and bank
221
- const realBank = Math.max(0,channel.lastBank?.messageData[1] - mid.bankOffset); // make sure to take the previous bank offset into account
222
- const bank = channel.drums ? 128 : realBank;
223
- if(channel.lastBank === undefined)
224
- {
225
240
  continue;
226
241
  }
227
- if(system === "xg" && channel.drums)
228
- {
229
- // drums override: set bank to 127
230
- channelsInfo[chNum].lastBank.messageData[1] = 127;
231
- }
232
-
233
- if(soundfont.presets.findIndex(p => p.bank === bank && p.program === e.messageData[0]) === -1)
234
- {
235
- // no preset with this bank. find this program with any bank
236
- const targetBank = (soundfont.presets.find(p => p.program === e.messageData[0])?.bank + bankOffset) || bankOffset;
237
- channel.lastBank.messageData[1] = targetBank;
238
- SpessaSynthInfo(`%cNo preset %c${bank}:${e.messageData[0]}%c. Changing bank to ${targetBank}.`,
239
- consoleColors.info,
240
- consoleColors.recognized,
241
- consoleColors.info);
242
+ // we only care about bank select
243
+ if (e.messageData[0] !== midiControllers.bankSelect) {
244
+ continue;
242
245
  }
243
- else
244
- {
245
- // there is a preset with this bank. add offset. For drums add the normal offset.
246
- const newBank = (bank === 128 ? 0 : realBank) + bankOffset;
247
- channel.lastBank.messageData[1] = newBank;
248
- SpessaSynthInfo(`%cPreset %c${bank}:${e.messageData[0]}%c exists. Changing bank to ${newBank}.`,
249
- consoleColors.info,
250
- consoleColors.recognized,
251
- consoleColors.info);
246
+ // bank select
247
+ channel.hasBankSelect = true;
248
+ if (system === "xg") {
249
+ // check for xg drums
250
+ channel.drums = e.messageData[1] === 120 || e.messageData[1] === 126 || e.messageData[1] === 127;
252
251
  }
253
- continue;
252
+ channel.lastBank = e;
254
253
  }
255
- // we only care about bank select
256
- if(e.messageData[0] !== midiControllers.bankSelect)
257
- {
258
- continue;
259
- }
260
- // bank select
261
- channel.hasBankSelect = true;
262
- if(system === "xg")
263
- {
264
- // check for xg drums
265
- channel.drums = e.messageData[1] === 120 || e.messageData[1] === 126 || e.messageData[1] === 127;
266
- }
267
- channel.lastBank = e;
268
- }
269
254
 
270
- // add missing bank selects
271
- // add all bank selects that are missing for this track
272
- channelsInfo.forEach((has, ch) => {
273
- if(has.hasBankSelect === true)
274
- {
275
- return;
276
- }
277
- // find first program change (for the given channel)
278
- const midiChannel = ch % 16;
279
- const status = messageTypes.programChange | midiChannel;
280
- // find track with this channel being used
281
- const portOffset = Math.floor(ch / 16) * 16;
282
- const port = mid.midiPortChannelOffsets.indexOf(portOffset);
283
- const track = mid.tracks.find((t, tNum) => mid.midiPorts[tNum] === port && mid.usedChannelsOnTrack[tNum].has(midiChannel));
284
- if(track === undefined)
285
- {
286
- // this channel is not used at all
287
- return;
288
- }
289
- let indexToAdd = track.findIndex(e => e.messageStatusByte === status);
290
- if(indexToAdd === -1)
291
- {
292
- // no program change...
293
- // add programs if they are missing from the track (need them to activate bank 1 for the embedded sfont)
294
- const programIndex = track.findIndex(e => (e.messageStatusByte > 0x80 && e.messageStatusByte < 0xF0) && (e.messageStatusByte & 0xF) === midiChannel);
295
- if(programIndex === -1)
296
- {
297
- // no voices??? skip
255
+ // add missing bank selects
256
+ // add all bank selects that are missing for this track
257
+ channelsInfo.forEach((has, ch) => {
258
+ if (has.hasBankSelect === true) {
259
+ return;
260
+ }
261
+ // find first program change (for the given channel)
262
+ const midiChannel = ch % 16;
263
+ const status = messageTypes.programChange | midiChannel;
264
+ // find track with this channel being used
265
+ const portOffset = Math.floor(ch / 16) * 16;
266
+ const port = mid.midiPortChannelOffsets.indexOf(portOffset);
267
+ const track = mid.tracks.find((t, tNum) => mid.midiPorts[tNum] === port && mid.usedChannelsOnTrack[tNum].has(midiChannel));
268
+ if (track === undefined) {
269
+ // this channel is not used at all
298
270
  return;
299
271
  }
300
- const programTicks = track[programIndex].ticks;
301
- const targetProgram = soundfont.getPreset(0, 0).program;
302
- track.splice(programIndex, 0, new MidiMessage(
303
- programTicks,
304
- messageTypes.programChange | midiChannel,
305
- new IndexedByteArray([targetProgram])
272
+ let indexToAdd = track.findIndex(e => e.messageStatusByte === status);
273
+ if (indexToAdd === -1) {
274
+ // no program change...
275
+ // add programs if they are missing from the track
276
+ // (need them to activate bank 1 for the embedded sfont)
277
+ const programIndex = track.findIndex(e => (e.messageStatusByte > 0x80 && e.messageStatusByte < 0xF0) && (e.messageStatusByte & 0xF) === midiChannel);
278
+ if (programIndex === -1) {
279
+ // no voices??? skip
280
+ return;
281
+ }
282
+ const programTicks = track[programIndex].ticks;
283
+ const targetProgram = soundfont.getPreset(0, 0).program;
284
+ track.splice(programIndex, 0, new MidiMessage(
285
+ programTicks,
286
+ messageTypes.programChange | midiChannel,
287
+ new IndexedByteArray([targetProgram])
288
+ ));
289
+ indexToAdd = programIndex;
290
+ }
291
+ SpessaSynthInfo(`%cAdding bank select for %c${ch}`,
292
+ consoleColors.info,
293
+ consoleColors.recognized)
294
+ const ticks = track[indexToAdd].ticks;
295
+ const targetBank = (soundfont.getPreset(0, has.program)?.bank + bankOffset) || bankOffset;
296
+ track.splice(indexToAdd, 0, new MidiMessage(
297
+ ticks,
298
+ messageTypes.controllerChange | midiChannel,
299
+ new IndexedByteArray([midiControllers.bankSelect, targetBank])
306
300
  ));
307
- indexToAdd = programIndex;
308
- }
309
- SpessaSynthInfo(`%cAdding bank select for %c${ch}`,
310
- consoleColors.info,
311
- consoleColors.recognized)
312
- const ticks = track[indexToAdd].ticks;
313
- const targetBank = (soundfont.getPreset(0, has.program)?.bank + bankOffset) || bankOffset;
314
- track.splice(indexToAdd,0, new MidiMessage(
315
- ticks,
316
- messageTypes.controllerChange | midiChannel,
317
- new IndexedByteArray([midiControllers.bankSelect, targetBank])
318
- ));
319
- });
301
+ });
320
302
 
321
- // make sure to put xg if gm
322
- if(system !== "gs" && system !== "xg")
323
- {
324
- for(const m of unwantedSystems)
325
- {
326
- mid.tracks[m.tNum].splice(mid.tracks[m.tNum].indexOf(m.e), 1);
327
- }
328
- let index = 0;
329
- if(mid.tracks[0][0].messageStatusByte === messageTypes.trackName)
330
- index++;
303
+ // make sure to put xg if gm
304
+ if (system !== "gs" && system !== "xg") {
305
+ for (const m of unwantedSystems) {
306
+ mid.tracks[m.tNum].splice(mid.tracks[m.tNum].indexOf(m.e), 1);
307
+ }
308
+ let index = 0;
309
+ if (mid.tracks[0][0].messageStatusByte === messageTypes.trackName)
310
+ index++;
331
311
  mid.tracks[0].splice(index, 0, getGsOn(0));
312
+ }
332
313
  }
333
314
  const newMid = new IndexedByteArray(writeMIDIFile(mid).buffer);
334
315
 
@@ -4,7 +4,7 @@ import { DEFAULT_PERCUSSION } from '../synthetizer/synthetizer.js'
4
4
  import { messageTypes, midiControllers } from './midi_message.js'
5
5
 
6
6
  /**
7
- * @param mid {MIDI}
7
+ * @param mid {BasicMIDI}
8
8
  * @param soundfont {{getPreset: function(number, number): BasicPreset}}
9
9
  * @returns {Object<string, Set<string>>}
10
10
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "spessasynth_lib",
3
- "version": "3.16.4",
3
+ "version": "3.17.0",
4
4
  "description": "No compromise MIDI and SoundFont2 Synthesizer library",
5
5
  "browser": "index.js",
6
6
  "types": "@types/index.d.ts",
@@ -21,7 +21,7 @@ import { DUMMY_MIDI_DATA, MidiData } from '../midi_parser/midi_data.js'
21
21
  */
22
22
 
23
23
  /**
24
- * @typedef {MIDI|MidFile} MIDIFile
24
+ * @typedef {BasicMIDI|MidFile} MIDIFile
25
25
  */
26
26
 
27
27
  /**
@@ -35,7 +35,7 @@ export function assignMIDIPort(trackNum, port)
35
35
 
36
36
  /**
37
37
  * Loads a new sequence
38
- * @param parsedMidi {MIDI}
38
+ * @param parsedMidi {BasicMIDI}
39
39
  * @this {WorkletSequencer}
40
40
  */
41
41
  export function loadNewSequence(parsedMidi)
@@ -49,7 +49,7 @@ export function loadNewSequence(parsedMidi)
49
49
  this.oneTickToSeconds = 60 / (120 * parsedMidi.timeDivision)
50
50
 
51
51
  /**
52
- * @type {MIDI}
52
+ * @type {BasicMIDI}
53
53
  */
54
54
  this.midiData = parsedMidi;
55
55
 
@@ -135,7 +135,7 @@ export function loadNewSongList(midiBuffers)
135
135
  {
136
136
  /**
137
137
  * parse the MIDIs (only the array buffers, MIDI is unchanged)
138
- * @type {MIDI[]}
138
+ * @type {BasicMIDI[]}
139
139
  */
140
140
  this.songs = midiBuffers.reduce((mids, b) => {
141
141
  if(b.duration)
@@ -71,7 +71,7 @@ class WorkletSequencer
71
71
 
72
72
  /**
73
73
  * the current track data
74
- * @type {MIDI}
74
+ * @type {BasicMIDI}
75
75
  */
76
76
  this.midiData = undefined;
77
77
 
@@ -93,12 +93,27 @@ class BasicSoundFont
93
93
  /**
94
94
  * Get the appropriate preset, undefined if not foun d
95
95
  * @param bankNr {number}
96
- * @param presetNr {number}
96
+ * @param programNr {number}
97
+ * @param fallbackToProgram {boolean} if true, if no exact match is found, will use any bank with the given preset
97
98
  * @return {BasicPreset}
98
99
  */
99
- getPresetNoFallback(bankNr, presetNr)
100
+ getPresetNoFallback(bankNr, programNr, fallbackToProgram = false)
100
101
  {
101
- return this.presets.find(p => p.bank === bankNr && p.program === presetNr);
102
+ const p = this.presets.find(p => p.bank === bankNr && p.program === programNr);
103
+ if(p)
104
+ {
105
+ return p;
106
+ }
107
+ if(fallbackToProgram === false)
108
+ {
109
+ return undefined;
110
+ }
111
+ if(bankNr === 128)
112
+ {
113
+ // any drum preset
114
+ return this.presets.find(p => p.bank === 128);
115
+ }
116
+ return this.presets.find(p => p.program === programNr);
102
117
  }
103
118
 
104
119
  /**
@@ -113,18 +128,18 @@ class BasicSoundFont
113
128
  /**
114
129
  * Get the appropriate preset
115
130
  * @param bankNr {number}
116
- * @param presetNr {number}
131
+ * @param programNr {number}
117
132
  * @returns {BasicPreset}
118
133
  */
119
- getPreset(bankNr, presetNr)
134
+ getPreset(bankNr, programNr)
120
135
  {
121
- let preset = this.presets.find(p => p.bank === bankNr && p.program === presetNr);
136
+ let preset = this.presets.find(p => p.bank === bankNr && p.program === programNr);
122
137
  if (!preset)
123
138
  {
124
- preset = this.presets.find(p => p.program === presetNr && p.bank !== 128);
139
+ preset = this.presets.find(p => p.program === programNr && p.bank !== 128);
125
140
  if(bankNr === 128)
126
141
  {
127
- preset = this.presets.find(p => p.bank === 128 && p.program === presetNr);
142
+ preset = this.presets.find(p => p.bank === 128 && p.program === programNr);
128
143
  if(!preset)
129
144
  {
130
145
  preset = this.presets.find(p => p.bank === 128);
@@ -132,14 +147,14 @@ class BasicSoundFont
132
147
  }
133
148
  if(preset)
134
149
  {
135
- SpessaSynthWarn(`%cPreset ${bankNr}.${presetNr} not found. Replaced with %c${preset.presetName} (${preset.bank}.${preset.program})`,
150
+ SpessaSynthWarn(`%cPreset ${bankNr}.${programNr} not found. Replaced with %c${preset.presetName} (${preset.bank}.${preset.program})`,
136
151
  consoleColors.warn,
137
152
  consoleColors.recognized);
138
153
  }
139
154
  }
140
155
  if(!preset)
141
156
  {
142
- SpessaSynthWarn(`Preset ${presetNr} not found. Defaulting to`, this.presets[0].presetName);
157
+ SpessaSynthWarn(`Preset ${programNr} not found. Defaulting to`, this.presets[0].presetName);
143
158
  preset = this.presets[0];
144
159
  }
145
160
  return preset;