spessasynth_lib 3.10.0 → 3.11.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/midi_parser/midi_data.d.ts +17 -0
- package/@types/midi_parser/midi_loader.d.ts +19 -3
- package/@types/midi_parser/rmidi_writer.d.ts +14 -1
- package/midi_parser/midi_data.js +21 -1
- package/midi_parser/midi_editor.js +15 -0
- package/midi_parser/midi_loader.js +74 -24
- package/midi_parser/rmidi_writer.js +84 -16
- package/midi_parser/used_keys_loaded.js +4 -8
- package/package.json +1 -1
- package/sequencer/worklet_sequencer/play.js +2 -2
- package/sequencer/worklet_sequencer/song_control.js +2 -1
- package/synthetizer/worklet_processor.min.js +7 -7
- package/synthetizer/worklet_system/worklet_methods/program_control.js +8 -2
- package/synthetizer/worklet_system/worklet_methods/system_exclusive.js +1 -1
|
@@ -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';
|
package/midi_parser/midi_data.js
CHANGED
|
@@ -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://
|
|
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.
|
|
473
|
+
if(this.midiName.length < 1)
|
|
430
474
|
{
|
|
431
|
-
|
|
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
|
-
|
|
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
|
|
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.
|
|
144
|
-
|
|
145
|
-
|
|
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
|
|
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 +
|
|
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
|
|
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
|
-
|
|
231
|
-
|
|
232
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 :
|
|
135
|
+
ch.bank = ch.drums ? 128 : realBank;
|
|
140
136
|
updateString(ch);
|
|
141
137
|
}
|
|
142
138
|
else
|
|
143
139
|
{
|
|
144
|
-
ch.bank = ch.drums ? 128 :
|
|
140
|
+
ch.bank = ch.drums ? 128 : realBank;
|
|
145
141
|
}
|
|
146
142
|
continue;
|
|
147
143
|
}
|
|
148
|
-
channelPresets[channel].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
|
@@ -190,7 +190,7 @@ export function _playTo(time, ticks = undefined)
|
|
|
190
190
|
});
|
|
191
191
|
|
|
192
192
|
// restore programs
|
|
193
|
-
if(programs[channelNumber].program
|
|
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
|
|
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 =
|
|
62
|
+
this.synth.soundfontBankOffset = this.midiData.bankOffset;
|
|
62
63
|
// preload all samples
|
|
63
64
|
this.synth.soundfont.samples.forEach(s => s.getAudioData());
|
|
64
65
|
}
|