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.
- package/@types/index.d.ts +5 -4
- package/@types/midi_parser/basic_midi.d.ts +125 -0
- package/@types/midi_parser/midi_builder.d.ts +69 -0
- package/@types/midi_parser/midi_data.d.ts +2 -2
- package/@types/midi_parser/midi_editor.d.ts +4 -4
- package/@types/midi_parser/midi_loader.d.ts +3 -100
- package/@types/midi_parser/midi_writer.d.ts +2 -2
- package/@types/midi_parser/rmidi_writer.d.ts +4 -3
- package/@types/midi_parser/used_keys_loaded.d.ts +2 -2
- package/@types/sequencer/sequencer.d.ts +1 -1
- package/@types/soundfont/basic_soundfont/basic_soundfont.d.ts +5 -4
- package/@types/soundfont/basic_soundfont/write_sf2/soundfont_trimmer.d.ts +2 -2
- package/@types/soundfont/dls/dls_preset.d.ts +11 -0
- package/@types/soundfont/dls/dls_soundfont.d.ts +24 -0
- package/@types/soundfont/dls/read_instrument.d.ts +5 -0
- package/@types/soundfont/dls/read_instrument_list.d.ts +5 -0
- package/@types/soundfont/load_soundfont.d.ts +6 -0
- package/@types/soundfont/soundfont.d.ts +2 -1
- package/@types/synthetizer/synthetizer.d.ts +2 -2
- package/@types/synthetizer/worklet_system/worklet_utilities/worklet_processor_channel.d.ts +2 -2
- package/@types/utils/byte_functions/little_endian.d.ts +1 -1
- package/README.md +17 -15
- package/index.js +6 -4
- package/midi_parser/basic_midi.js +146 -0
- package/midi_parser/midi_builder.js +281 -0
- package/midi_parser/midi_data.js +1 -1
- package/midi_parser/midi_editor.js +2 -2
- package/midi_parser/midi_loader.js +17 -53
- package/midi_parser/midi_writer.js +1 -1
- package/midi_parser/rmidi_writer.js +227 -246
- package/midi_parser/used_keys_loaded.js +1 -1
- package/package.json +1 -1
- package/sequencer/sequencer.js +1 -1
- package/sequencer/worklet_sequencer/song_control.js +3 -3
- package/sequencer/worklet_sequencer/worklet_sequencer.js +1 -1
- package/soundfont/basic_soundfont/basic_soundfont.js +25 -10
- package/soundfont/basic_soundfont/riff_chunk.js +2 -2
- package/soundfont/basic_soundfont/write_sf2/soundfont_trimmer.js +1 -1
- package/soundfont/dls/dls_preset.js +25 -0
- package/soundfont/dls/dls_soundfont.js +93 -0
- package/soundfont/dls/read_instrument.js +22 -0
- package/soundfont/dls/read_instrument_list.js +17 -0
- package/soundfont/load_soundfont.js +21 -0
- package/soundfont/read_sf2/instruments.js +2 -2
- package/soundfont/read_sf2/modulators.js +5 -5
- package/soundfont/read_sf2/presets.js +7 -7
- package/soundfont/read_sf2/samples.js +8 -8
- package/soundfont/read_sf2/zones.js +5 -5
- package/soundfont/soundfont.js +8 -3
- package/synthetizer/synthetizer.js +1 -1
- package/synthetizer/worklet_processor.min.js +7 -6
- package/synthetizer/worklet_system/main_processor.js +1 -2
- package/synthetizer/worklet_system/worklet_methods/program_control.js +12 -20
- package/synthetizer/worklet_system/worklet_methods/worklet_soundfont_manager/worklet_soundfont_manager.js +5 -5
- package/synthetizer/worklet_system/worklet_utilities/worklet_processor_channel.js +1 -1
- package/utils/byte_functions/little_endian.js +1 -1
- /package/@types/{midi_handler → external_midi}/midi_handler.d.ts +0 -0
- /package/@types/{midi_handler → external_midi}/web_midi_link.d.ts +0 -0
- /package/{midi_handler → external_midi}/README.md +0 -0
- /package/{midi_handler → external_midi}/midi_handler.js +0 -0
- /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 {
|
|
51
|
-
* @param soundfont {
|
|
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(
|
|
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
|
|
|
@@ -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 {
|
|
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
package/sequencer/sequencer.js
CHANGED
|
@@ -35,7 +35,7 @@ export function assignMIDIPort(trackNum, port)
|
|
|
35
35
|
|
|
36
36
|
/**
|
|
37
37
|
* Loads a new sequence
|
|
38
|
-
* @param parsedMidi {
|
|
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 {
|
|
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 {
|
|
138
|
+
* @type {BasicMIDI[]}
|
|
139
139
|
*/
|
|
140
140
|
this.songs = midiBuffers.reduce((mids, b) => {
|
|
141
141
|
if(b.duration)
|
|
@@ -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
|
|
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,
|
|
100
|
+
getPresetNoFallback(bankNr, programNr, fallbackToProgram = false)
|
|
100
101
|
{
|
|
101
|
-
|
|
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
|
|
131
|
+
* @param programNr {number}
|
|
117
132
|
* @returns {BasicPreset}
|
|
118
133
|
*/
|
|
119
|
-
getPreset(bankNr,
|
|
134
|
+
getPreset(bankNr, programNr)
|
|
120
135
|
{
|
|
121
|
-
let preset = this.presets.find(p => p.bank === bankNr && p.program ===
|
|
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 ===
|
|
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 ===
|
|
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}.${
|
|
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 ${
|
|
157
|
+
SpessaSynthWarn(`Preset ${programNr} not found. Defaulting to`, this.presets[0].presetName);
|
|
143
158
|
preset = this.presets[0];
|
|
144
159
|
}
|
|
145
160
|
return preset;
|