spessasynth_lib 3.16.4 → 3.16.5
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/midi_parser/rmidi_writer.d.ts +2 -1
- package/@types/soundfont/basic_soundfont/basic_soundfont.d.ts +5 -4
- package/@types/synthetizer/worklet_system/worklet_utilities/worklet_processor_channel.d.ts +2 -2
- package/midi_parser/midi_loader.js +9 -0
- package/midi_parser/rmidi_writer.js +225 -244
- package/package.json +1 -1
- package/soundfont/basic_soundfont/basic_soundfont.js +25 -10
- package/synthetizer/worklet_processor.min.js +5 -5
- package/synthetizer/worklet_system/worklet_methods/program_control.js +6 -17
- package/synthetizer/worklet_system/worklet_utilities/worklet_processor_channel.js +1 -1
|
@@ -19,9 +19,10 @@
|
|
|
19
19
|
* @param bankOffset {number} the bank offset for RMIDI
|
|
20
20
|
* @param encoding {string} the encoding of the RMIDI info chunk
|
|
21
21
|
* @param metadata {RMIDMetadata} the metadata of the file. Optional. If provided, the encoding is forced to utf-8/
|
|
22
|
+
* @param correctBankOffset {boolean}
|
|
22
23
|
* @returns {IndexedByteArray}
|
|
23
24
|
*/
|
|
24
|
-
export function writeRMIDI(soundfontBinary: Uint8Array, mid: MIDI, soundfont: SoundFont2, bankOffset?: number, encoding?: string, metadata?: RMIDMetadata): IndexedByteArray;
|
|
25
|
+
export function writeRMIDI(soundfontBinary: Uint8Array, mid: MIDI, soundfont: SoundFont2, bankOffset?: number, encoding?: string, metadata?: RMIDMetadata, correctBankOffset?: boolean): IndexedByteArray;
|
|
25
26
|
export type RMIDINFOChunks = string;
|
|
26
27
|
export namespace RMIDINFOChunks {
|
|
27
28
|
let name: string;
|
|
@@ -53,10 +53,11 @@ export class BasicSoundFont {
|
|
|
53
53
|
/**
|
|
54
54
|
* Get the appropriate preset, undefined if not foun d
|
|
55
55
|
* @param bankNr {number}
|
|
56
|
-
* @param
|
|
56
|
+
* @param programNr {number}
|
|
57
|
+
* @param fallbackToProgram {boolean} if true, if no exact match is found, will use any bank with the given preset
|
|
57
58
|
* @return {BasicPreset}
|
|
58
59
|
*/
|
|
59
|
-
getPresetNoFallback(bankNr: number,
|
|
60
|
+
getPresetNoFallback(bankNr: number, programNr: number, fallbackToProgram?: boolean): BasicPreset;
|
|
60
61
|
/**
|
|
61
62
|
* To avoid overlapping on multiple desfonts
|
|
62
63
|
* @param offset {number}
|
|
@@ -65,10 +66,10 @@ export class BasicSoundFont {
|
|
|
65
66
|
/**
|
|
66
67
|
* Get the appropriate preset
|
|
67
68
|
* @param bankNr {number}
|
|
68
|
-
* @param
|
|
69
|
+
* @param programNr {number}
|
|
69
70
|
* @returns {BasicPreset}
|
|
70
71
|
*/
|
|
71
|
-
getPreset(bankNr: number,
|
|
72
|
+
getPreset(bankNr: number, programNr: number): BasicPreset;
|
|
72
73
|
/**
|
|
73
74
|
* gets preset by name
|
|
74
75
|
* @param presetName {string}
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* @property {number} NRPFine - the current fine value of the Non-Registered Parameter
|
|
15
15
|
* @property {number} RPValue - the current value of the Registered Parameter
|
|
16
16
|
*
|
|
17
|
-
* @property {
|
|
17
|
+
* @property {BasicPreset} preset - the channel's preset
|
|
18
18
|
* @property {boolean} lockPreset - indicates whether the program on the channel is locked
|
|
19
19
|
* @property {boolean} presetUsesOverride - indcates if the channel uses a preset from the override soundfont.
|
|
20
20
|
*
|
|
@@ -104,7 +104,7 @@ export type WorkletProcessorChannel = {
|
|
|
104
104
|
/**
|
|
105
105
|
* - the channel's preset
|
|
106
106
|
*/
|
|
107
|
-
preset:
|
|
107
|
+
preset: BasicPreset;
|
|
108
108
|
/**
|
|
109
109
|
* - indicates whether the program on the channel is locked
|
|
110
110
|
*/
|
|
@@ -117,6 +117,15 @@ class MIDI{
|
|
|
117
117
|
this.midiName = readBytesAsString(this.rawMidiName, this.rawMidiName.length, undefined, false);
|
|
118
118
|
nameDetected = true;
|
|
119
119
|
}
|
|
120
|
+
// these can be used interchangeably
|
|
121
|
+
if(this.RMIDInfo['IALB'] && !this.RMIDInfo['IPRD'])
|
|
122
|
+
{
|
|
123
|
+
this.RMIDInfo['IPRD'] = this.RMIDInfo['IALB'];
|
|
124
|
+
}
|
|
125
|
+
if(this.RMIDInfo['PRD'] && !this.RMIDInfo['IALB'])
|
|
126
|
+
{
|
|
127
|
+
this.RMIDInfo['IALB'] = this.RMIDInfo['IPRD'];
|
|
128
|
+
}
|
|
120
129
|
this.bankOffset = 1; // defaults to 1
|
|
121
130
|
if(this.RMIDInfo[RMIDINFOChunks.bankOffset])
|
|
122
131
|
{
|
|
@@ -52,9 +52,18 @@ const DEFAULT_COPYRIGHT = "Created using SpessaSynth";
|
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
let
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
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
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
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
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
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
|
-
|
|
148
|
-
|
|
149
|
-
|
|
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
|
|
193
|
-
|
|
194
|
-
|
|
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
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
{
|
|
207
|
-
//
|
|
208
|
-
|
|
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
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
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
|
-
|
|
228
|
-
{
|
|
229
|
-
|
|
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
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
channel.
|
|
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
|
-
|
|
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
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
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
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
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
|
-
|
|
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
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
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
|
|