spessasynth_lib 3.9.12 → 3.9.14
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 +5 -0
- package/@types/midi_parser/midi_loader.d.ts +5 -0
- package/@types/sequencer/sequencer.d.ts +11 -0
- package/midi_parser/midi_data.js +7 -0
- package/midi_parser/midi_editor.js +149 -128
- package/midi_parser/midi_loader.js +19 -1
- package/midi_parser/used_keys_loaded.js +125 -90
- package/package.json +2 -5
- package/sequencer/sequencer.js +21 -0
- package/sequencer/worklet_sequencer/song_control.js +3 -3
- package/synthetizer/worklet_processor.min.js +7 -7
- package/synthetizer/worklet_system/worklet_utilities/volume_envelope.js +1 -0
|
@@ -12,9 +12,8 @@ export function getUsedProgramsAndKeys(mid, soundfont)
|
|
|
12
12
|
SpessaSynthGroupCollapsed("%cSearching for all used programs and keys...",
|
|
13
13
|
consoleColors.info);
|
|
14
14
|
// find every bank:program combo and every key:velocity for each. Make sure to care about ports and drums
|
|
15
|
-
const channelsAmount = 16 +
|
|
15
|
+
const channelsAmount = 16 + Math.max.apply(undefined, mid.midiPortChannelOffsets);
|
|
16
16
|
/**
|
|
17
|
-
*
|
|
18
17
|
* @type {{program: number, bank: number, drums: boolean, string: string}[]}
|
|
19
18
|
*/
|
|
20
19
|
const channelPresets = [];
|
|
@@ -24,7 +23,7 @@ export function getUsedProgramsAndKeys(mid, soundfont)
|
|
|
24
23
|
program: 0,
|
|
25
24
|
bank: bank,
|
|
26
25
|
drums: i % 16 === DEFAULT_PERCUSSION, // drums appear on 9 every 16 channels,
|
|
27
|
-
string: `${bank}:0
|
|
26
|
+
string: `${bank}:0`,
|
|
28
27
|
});
|
|
29
28
|
}
|
|
30
29
|
|
|
@@ -54,109 +53,145 @@ export function getUsedProgramsAndKeys(mid, soundfont)
|
|
|
54
53
|
* @type {Object<string, Set<string>>}
|
|
55
54
|
*/
|
|
56
55
|
const usedProgramsAndKeys = {};
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
)
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* indexes for tracks
|
|
59
|
+
* @type {number[]}
|
|
60
|
+
*/
|
|
61
|
+
const eventIndexes = Array(mid.tracks.length).fill(0);
|
|
62
|
+
let remainingTracks = mid.tracks.length;
|
|
63
|
+
function findFirstEventIndex()
|
|
64
|
+
{
|
|
65
|
+
let index = 0;
|
|
66
|
+
let ticks = Infinity;
|
|
67
|
+
mid.tracks.forEach((track, i) => {
|
|
68
|
+
if(eventIndexes[i] >= track.length)
|
|
70
69
|
{
|
|
71
|
-
|
|
70
|
+
return;
|
|
72
71
|
}
|
|
73
|
-
|
|
74
|
-
let ch = channelPresets[channel];
|
|
75
|
-
switch(status)
|
|
72
|
+
if(track[eventIndexes[i]].ticks < ticks)
|
|
76
73
|
{
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
74
|
+
index = i;
|
|
75
|
+
ticks = track[eventIndexes[i]].ticks;
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
return index;
|
|
79
|
+
}
|
|
80
|
+
const ports = mid.midiPorts.slice();
|
|
81
|
+
// check for xg
|
|
82
|
+
let system = "gs";
|
|
83
|
+
while(remainingTracks > 0)
|
|
84
|
+
{
|
|
85
|
+
let trackNum = findFirstEventIndex();
|
|
86
|
+
const track = mid.tracks[trackNum];
|
|
87
|
+
if(eventIndexes[trackNum] >= track.length)
|
|
88
|
+
{
|
|
89
|
+
remainingTracks--;
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
const event = track[eventIndexes[trackNum]];
|
|
93
|
+
eventIndexes[trackNum]++;
|
|
81
94
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
}
|
|
106
|
-
continue;
|
|
107
|
-
}
|
|
108
|
-
channelPresets[channel].bank = bank;
|
|
109
|
-
// do not update the data, bank change doesnt change the preset
|
|
110
|
-
break;
|
|
95
|
+
if(event.messageStatusByte === messageTypes.midiPort)
|
|
96
|
+
{
|
|
97
|
+
ports[trackNum] = event.messageData[0];
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
const status = event.messageStatusByte & 0xF0;
|
|
101
|
+
if(
|
|
102
|
+
status !== messageTypes.noteOn &&
|
|
103
|
+
status !== messageTypes.controllerChange &&
|
|
104
|
+
status !== messageTypes.programChange &&
|
|
105
|
+
status !== messageTypes.systemExclusive
|
|
106
|
+
)
|
|
107
|
+
{
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
const channel = (event.messageStatusByte & 0xF) + mid.midiPortChannelOffsets[ports[trackNum]] || 0;
|
|
111
|
+
let ch = channelPresets[channel];
|
|
112
|
+
switch(status)
|
|
113
|
+
{
|
|
114
|
+
case messageTypes.programChange:
|
|
115
|
+
ch.program = event.messageData[0];
|
|
116
|
+
updateString(ch);
|
|
117
|
+
break;
|
|
111
118
|
|
|
112
|
-
|
|
113
|
-
|
|
119
|
+
case messageTypes.controllerChange:
|
|
120
|
+
if(event.messageData[0] !== midiControllers.bankSelect)
|
|
121
|
+
{
|
|
122
|
+
// we only care about bank select
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
if(system === "gs" && ch.drums)
|
|
126
|
+
{
|
|
127
|
+
// gs drums get changed via sysex, ignore here
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
const bank = event.messageData[1];
|
|
131
|
+
if(system === "xg")
|
|
132
|
+
{
|
|
133
|
+
// check for xg drums
|
|
134
|
+
const drumsBool = bank === 120 || bank === 126 || bank === 127;
|
|
135
|
+
if(drumsBool !== ch.drums)
|
|
114
136
|
{
|
|
115
|
-
//
|
|
116
|
-
|
|
137
|
+
// drum change is a program change
|
|
138
|
+
ch.drums = drumsBool;
|
|
139
|
+
ch.bank = ch.drums ? 128 : bank;
|
|
140
|
+
updateString(ch);
|
|
117
141
|
}
|
|
118
|
-
|
|
142
|
+
else
|
|
119
143
|
{
|
|
120
|
-
|
|
144
|
+
ch.bank = ch.drums ? 128 : bank;
|
|
121
145
|
}
|
|
122
|
-
|
|
123
|
-
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
channelPresets[channel].bank = bank;
|
|
149
|
+
// do not update the data, bank change doesnt change the preset
|
|
150
|
+
break;
|
|
124
151
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
152
|
+
case messageTypes.noteOn:
|
|
153
|
+
if(event.messageData[1] === 0)
|
|
154
|
+
{
|
|
155
|
+
// that's a note off
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
usedProgramsAndKeys[ch.string].add(`${event.messageData[0]}-${event.messageData[1]}`);
|
|
159
|
+
break;
|
|
160
|
+
|
|
161
|
+
case messageTypes.systemExclusive:
|
|
162
|
+
// check for drum sysex
|
|
163
|
+
if(
|
|
164
|
+
event.messageData[0] !== 0x41 || // roland
|
|
165
|
+
event.messageData[2] !== 0x42 || // GS
|
|
166
|
+
event.messageData[3] !== 0x12 || // GS
|
|
167
|
+
event.messageData[4] !== 0x40 || // system parameter
|
|
168
|
+
(event.messageData[5] & 0x10 ) === 0 || // part parameter
|
|
169
|
+
event.messageData[6] !== 0x15 // drum pars
|
|
134
170
|
|
|
171
|
+
)
|
|
172
|
+
{
|
|
173
|
+
// check for XG
|
|
174
|
+
if(
|
|
175
|
+
event.messageData[0] === 0x43 && // yamaha
|
|
176
|
+
event.messageData[2] === 0x4C && // sXG ON
|
|
177
|
+
event.messageData[5] === 0x7E &&
|
|
178
|
+
event.messageData[6] === 0x00
|
|
135
179
|
)
|
|
136
180
|
{
|
|
137
|
-
|
|
138
|
-
if(
|
|
139
|
-
event.messageData[0] === 0x43 && // yamaha
|
|
140
|
-
event.messageData[2] === 0x4C && // sXG ON
|
|
141
|
-
event.messageData[5] === 0x7E &&
|
|
142
|
-
event.messageData[6] === 0x00
|
|
143
|
-
)
|
|
144
|
-
{
|
|
145
|
-
system = "xg";
|
|
146
|
-
}
|
|
147
|
-
continue;
|
|
181
|
+
system = "xg";
|
|
148
182
|
}
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
const sysexChannel = [9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 11, 12, 13, 14, 15][event.messageData[5] & 0x0F] + mid.midiPortChannelOffsets[ports[trackNum]];
|
|
186
|
+
const isDrum = !!(event.messageData[7] > 0 && event.messageData[5] >> 4);
|
|
187
|
+
ch = channelPresets[sysexChannel];
|
|
188
|
+
ch.drums = isDrum;
|
|
189
|
+
ch.bank = isDrum ? 128 : 0;
|
|
190
|
+
updateString(ch);
|
|
191
|
+
break;
|
|
156
192
|
|
|
157
|
-
}
|
|
158
193
|
}
|
|
159
|
-
}
|
|
194
|
+
}
|
|
160
195
|
for(const key of Object.keys(usedProgramsAndKeys))
|
|
161
196
|
{
|
|
162
197
|
if(usedProgramsAndKeys[key].size === 0)
|
package/package.json
CHANGED
|
@@ -1,13 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "spessasynth_lib",
|
|
3
|
-
"version": "3.9.
|
|
4
|
-
"description": "No compromise
|
|
3
|
+
"version": "3.9.14",
|
|
4
|
+
"description": "No compromise MIDI SoundFont2 library",
|
|
5
5
|
"browser": "index.js",
|
|
6
6
|
"types": "@types/index.d.ts",
|
|
7
7
|
"type": "module",
|
|
8
|
-
"directories": {
|
|
9
|
-
"lib": "lib"
|
|
10
|
-
},
|
|
11
8
|
"scripts": {
|
|
12
9
|
"test": "echo \"Error: no test specified\" && exit 1"
|
|
13
10
|
},
|
package/sequencer/sequencer.js
CHANGED
|
@@ -190,6 +190,10 @@ export class Sequencer
|
|
|
190
190
|
case WorkletSequencerReturnMessageType.pause:
|
|
191
191
|
this.pausedTime = this.currentTime;
|
|
192
192
|
this.isFinished = messageData;
|
|
193
|
+
if(this.isFinished)
|
|
194
|
+
{
|
|
195
|
+
Object.entries(this.onSongEnded).forEach((callback) => callback[1]());
|
|
196
|
+
}
|
|
193
197
|
break;
|
|
194
198
|
|
|
195
199
|
case WorkletSequencerReturnMessageType.midiError:
|
|
@@ -240,6 +244,17 @@ export class Sequencer
|
|
|
240
244
|
callback(this.midiData);
|
|
241
245
|
}
|
|
242
246
|
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Adds a new event that gets called when the song ends
|
|
250
|
+
* @param callback {function}
|
|
251
|
+
* @param id {string} must be unique
|
|
252
|
+
*/
|
|
253
|
+
addOnSongEndedEvent(callback, id)
|
|
254
|
+
{
|
|
255
|
+
this.onSongEnded[id] = callback;
|
|
256
|
+
}
|
|
257
|
+
|
|
243
258
|
/**
|
|
244
259
|
* Adds a new event that gets called when the time changes
|
|
245
260
|
* @param callback {function(number)} the new time, in seconds
|
|
@@ -436,4 +451,10 @@ export class Sequencer
|
|
|
436
451
|
* @private
|
|
437
452
|
*/
|
|
438
453
|
onTimeChange = {};
|
|
454
|
+
|
|
455
|
+
/**
|
|
456
|
+
* @type {Object<string, function>}
|
|
457
|
+
* @private
|
|
458
|
+
*/
|
|
459
|
+
onSongEnded = {};
|
|
439
460
|
}
|
|
@@ -88,12 +88,12 @@ export function loadNewSequence(parsedMidi)
|
|
|
88
88
|
*/
|
|
89
89
|
this.tracks = this.midiData.tracks;
|
|
90
90
|
|
|
91
|
-
// clear last port data
|
|
92
|
-
this.midiPortChannelOffset = 0;
|
|
93
|
-
this.midiPortChannelOffsets = {};
|
|
94
91
|
// copy over the port data
|
|
95
92
|
this.midiPorts = this.midiData.midiPorts;
|
|
96
93
|
|
|
94
|
+
// clear last port data
|
|
95
|
+
this.midiPortChannelOffset = 0;
|
|
96
|
+
this.midiPortChannelOffsets = {};
|
|
97
97
|
// assign port offsets
|
|
98
98
|
this.midiData.midiPorts.forEach((port, trackIndex) => {
|
|
99
99
|
this.assignMIDIPort(trackIndex, port);
|