@marmooo/midy 0.1.6 → 0.1.7
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/esm/midy-GM1.d.ts +5 -4
- package/esm/midy-GM1.d.ts.map +1 -1
- package/esm/midy-GM1.js +45 -16
- package/esm/midy-GM2.d.ts +5 -2
- package/esm/midy-GM2.d.ts.map +1 -1
- package/esm/midy-GM2.js +176 -66
- package/esm/midy-GMLite.d.ts +5 -4
- package/esm/midy-GMLite.d.ts.map +1 -1
- package/esm/midy-GMLite.js +45 -16
- package/esm/midy.d.ts +5 -2
- package/esm/midy.d.ts.map +1 -1
- package/esm/midy.js +177 -67
- package/package.json +5 -1
- package/script/midy-GM1.d.ts +5 -4
- package/script/midy-GM1.d.ts.map +1 -1
- package/script/midy-GM1.js +48 -19
- package/script/midy-GM2.d.ts +5 -2
- package/script/midy-GM2.d.ts.map +1 -1
- package/script/midy-GM2.js +179 -69
- package/script/midy-GMLite.d.ts +5 -4
- package/script/midy-GMLite.d.ts.map +1 -1
- package/script/midy-GMLite.js +48 -19
- package/script/midy.d.ts +5 -2
- package/script/midy.d.ts.map +1 -1
- package/script/midy.js +180 -70
- package/esm/deps/cdn.jsdelivr.net/npm/@marmooo/soundfont-parser@0.0.6/+esm.d.ts +0 -149
- package/esm/deps/cdn.jsdelivr.net/npm/@marmooo/soundfont-parser@0.0.6/+esm.d.ts.map +0 -1
- package/esm/deps/cdn.jsdelivr.net/npm/@marmooo/soundfont-parser@0.0.6/+esm.js +0 -180
- package/esm/deps/cdn.jsdelivr.net/npm/midi-file@1.2.4/+esm.d.ts +0 -84
- package/esm/deps/cdn.jsdelivr.net/npm/midi-file@1.2.4/+esm.d.ts.map +0 -1
- package/esm/deps/cdn.jsdelivr.net/npm/midi-file@1.2.4/+esm.js +0 -216
- package/script/deps/cdn.jsdelivr.net/npm/@marmooo/soundfont-parser@0.0.6/+esm.d.ts +0 -149
- package/script/deps/cdn.jsdelivr.net/npm/@marmooo/soundfont-parser@0.0.6/+esm.d.ts.map +0 -1
- package/script/deps/cdn.jsdelivr.net/npm/@marmooo/soundfont-parser@0.0.6/+esm.js +0 -190
- package/script/deps/cdn.jsdelivr.net/npm/midi-file@1.2.4/+esm.d.ts +0 -84
- package/script/deps/cdn.jsdelivr.net/npm/midi-file@1.2.4/+esm.d.ts.map +0 -1
- package/script/deps/cdn.jsdelivr.net/npm/midi-file@1.2.4/+esm.js +0 -221
package/script/midy-GM2.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.MidyGM2 = void 0;
|
|
4
|
-
const
|
|
5
|
-
const
|
|
4
|
+
const midi_file_1 = require("midi-file");
|
|
5
|
+
const soundfont_parser_1 = require("@marmooo/soundfont-parser");
|
|
6
6
|
class Note {
|
|
7
7
|
constructor(noteNumber, velocity, startTime, instrumentKey) {
|
|
8
8
|
Object.defineProperty(this, "bufferSource", {
|
|
@@ -53,6 +53,18 @@ class Note {
|
|
|
53
53
|
writable: true,
|
|
54
54
|
value: void 0
|
|
55
55
|
});
|
|
56
|
+
Object.defineProperty(this, "reverbEffectsSend", {
|
|
57
|
+
enumerable: true,
|
|
58
|
+
configurable: true,
|
|
59
|
+
writable: true,
|
|
60
|
+
value: void 0
|
|
61
|
+
});
|
|
62
|
+
Object.defineProperty(this, "chorusEffectsSend", {
|
|
63
|
+
enumerable: true,
|
|
64
|
+
configurable: true,
|
|
65
|
+
writable: true,
|
|
66
|
+
value: void 0
|
|
67
|
+
});
|
|
56
68
|
this.noteNumber = noteNumber;
|
|
57
69
|
this.velocity = velocity;
|
|
58
70
|
this.startTime = startTime;
|
|
@@ -208,6 +220,12 @@ class MidyGM2 {
|
|
|
208
220
|
writable: true,
|
|
209
221
|
value: []
|
|
210
222
|
});
|
|
223
|
+
Object.defineProperty(this, "exclusiveClassMap", {
|
|
224
|
+
enumerable: true,
|
|
225
|
+
configurable: true,
|
|
226
|
+
writable: true,
|
|
227
|
+
value: new Map()
|
|
228
|
+
});
|
|
211
229
|
Object.defineProperty(this, "defaultOptions", {
|
|
212
230
|
enumerable: true,
|
|
213
231
|
configurable: true,
|
|
@@ -264,14 +282,14 @@ class MidyGM2 {
|
|
|
264
282
|
async loadSoundFont(soundFontUrl) {
|
|
265
283
|
const response = await fetch(soundFontUrl);
|
|
266
284
|
const arrayBuffer = await response.arrayBuffer();
|
|
267
|
-
const parsed = (0,
|
|
268
|
-
const soundFont = new
|
|
285
|
+
const parsed = (0, soundfont_parser_1.parse)(new Uint8Array(arrayBuffer));
|
|
286
|
+
const soundFont = new soundfont_parser_1.SoundFont(parsed);
|
|
269
287
|
this.addSoundFont(soundFont);
|
|
270
288
|
}
|
|
271
289
|
async loadMIDI(midiUrl) {
|
|
272
290
|
const response = await fetch(midiUrl);
|
|
273
291
|
const arrayBuffer = await response.arrayBuffer();
|
|
274
|
-
const midi = (0,
|
|
292
|
+
const midi = (0, midi_file_1.parseMidi)(new Uint8Array(arrayBuffer));
|
|
275
293
|
this.ticksPerBeat = midi.header.ticksPerBeat;
|
|
276
294
|
const midiData = this.extractMidiData(midi);
|
|
277
295
|
this.instruments = midiData.instruments;
|
|
@@ -311,19 +329,25 @@ class MidyGM2 {
|
|
|
311
329
|
const sampleStart = instrumentKey.start;
|
|
312
330
|
const sampleEnd = instrumentKey.sample.length + instrumentKey.end;
|
|
313
331
|
if (isSF3) {
|
|
314
|
-
const sample = instrumentKey.sample
|
|
315
|
-
const
|
|
332
|
+
const sample = instrumentKey.sample;
|
|
333
|
+
const start = sample.byteOffset + sampleStart;
|
|
334
|
+
const end = sample.byteOffset + sampleEnd;
|
|
335
|
+
const buffer = sample.buffer.slice(start, end);
|
|
336
|
+
const audioBuffer = await this.audioContext.decodeAudioData(buffer);
|
|
316
337
|
return audioBuffer;
|
|
317
338
|
}
|
|
318
339
|
else {
|
|
319
|
-
const sample = instrumentKey.sample
|
|
340
|
+
const sample = instrumentKey.sample;
|
|
341
|
+
const start = sample.byteOffset + sampleStart;
|
|
342
|
+
const end = sample.byteOffset + sampleEnd;
|
|
343
|
+
const buffer = sample.buffer.slice(start, end);
|
|
320
344
|
const audioBuffer = new AudioBuffer({
|
|
321
345
|
numberOfChannels: 1,
|
|
322
346
|
length: sample.length,
|
|
323
347
|
sampleRate: instrumentKey.sampleRate,
|
|
324
348
|
});
|
|
325
349
|
const channelData = audioBuffer.getChannelData(0);
|
|
326
|
-
const int16Array = new Int16Array(
|
|
350
|
+
const int16Array = new Int16Array(buffer);
|
|
327
351
|
for (let i = 0; i < int16Array.length; i++) {
|
|
328
352
|
channelData[i] = int16Array[i] / 32768;
|
|
329
353
|
}
|
|
@@ -421,6 +445,7 @@ class MidyGM2 {
|
|
|
421
445
|
if (queueIndex >= this.timeline.length) {
|
|
422
446
|
await Promise.all(this.notePromises);
|
|
423
447
|
this.notePromises = [];
|
|
448
|
+
this.exclusiveClassMap.clear();
|
|
424
449
|
resolve();
|
|
425
450
|
return;
|
|
426
451
|
}
|
|
@@ -436,6 +461,7 @@ class MidyGM2 {
|
|
|
436
461
|
}
|
|
437
462
|
else if (this.isStopping) {
|
|
438
463
|
await this.stopNotes(0, true);
|
|
464
|
+
this.exclusiveClassMap.clear();
|
|
439
465
|
this.notePromises = [];
|
|
440
466
|
resolve();
|
|
441
467
|
this.isStopping = false;
|
|
@@ -444,6 +470,7 @@ class MidyGM2 {
|
|
|
444
470
|
}
|
|
445
471
|
else if (this.isSeeking) {
|
|
446
472
|
this.stopNotes(0, true);
|
|
473
|
+
this.exclusiveClassMap.clear();
|
|
447
474
|
this.startTime = this.audioContext.currentTime;
|
|
448
475
|
queueIndex = this.getQueueIndex(this.resumeTime);
|
|
449
476
|
offset = this.resumeTime - this.startTime;
|
|
@@ -672,14 +699,14 @@ class MidyGM2 {
|
|
|
672
699
|
return impulse;
|
|
673
700
|
}
|
|
674
701
|
createConvolutionReverb(audioContext, impulse) {
|
|
675
|
-
const
|
|
702
|
+
const input = new GainNode(audioContext);
|
|
676
703
|
const convolverNode = new ConvolverNode(audioContext, {
|
|
677
704
|
buffer: impulse,
|
|
678
705
|
});
|
|
679
|
-
|
|
706
|
+
input.connect(convolverNode);
|
|
680
707
|
return {
|
|
681
|
-
input
|
|
682
|
-
output,
|
|
708
|
+
input,
|
|
709
|
+
output: convolverNode,
|
|
683
710
|
convolverNode,
|
|
684
711
|
};
|
|
685
712
|
}
|
|
@@ -721,7 +748,6 @@ class MidyGM2 {
|
|
|
721
748
|
// M.R.Schroeder, "Natural Sounding Artificial Reverberation", J.Audio Eng. Soc., vol.10, p.219, 1962
|
|
722
749
|
createSchroederReverb(audioContext, combFeedbacks, combDelays, allpassFeedbacks, allpassDelays) {
|
|
723
750
|
const input = new GainNode(audioContext);
|
|
724
|
-
const output = new GainNode(audioContext);
|
|
725
751
|
const mergerGain = new GainNode(audioContext);
|
|
726
752
|
for (let i = 0; i < combDelays.length; i++) {
|
|
727
753
|
const comb = this.createCombFilter(audioContext, input, combDelays[i], combFeedbacks[i]);
|
|
@@ -732,7 +758,7 @@ class MidyGM2 {
|
|
|
732
758
|
const allpass = this.createAllpassFilter(audioContext, (i === 0) ? mergerGain : allpasses.at(-1), allpassDelays[i], allpassFeedbacks[i]);
|
|
733
759
|
allpasses.push(allpass);
|
|
734
760
|
}
|
|
735
|
-
allpasses.at(-1)
|
|
761
|
+
const output = allpasses.at(-1);
|
|
736
762
|
return { input, output };
|
|
737
763
|
}
|
|
738
764
|
createChorusEffect(audioContext) {
|
|
@@ -963,6 +989,20 @@ class MidyGM2 {
|
|
|
963
989
|
}
|
|
964
990
|
note.bufferSource.connect(note.filterNode);
|
|
965
991
|
note.filterNode.connect(note.volumeNode);
|
|
992
|
+
if (0 < channel.reverbSendLevel && 0 < instrumentKey.reverbEffectsSend) {
|
|
993
|
+
note.reverbEffectsSend = new GainNode(this.audioContext, {
|
|
994
|
+
gain: instrumentKey.reverbEffectsSend,
|
|
995
|
+
});
|
|
996
|
+
note.volumeNode.connect(note.reverbEffectsSend);
|
|
997
|
+
note.reverbEffectsSend.connect(this.reverbEffect.input);
|
|
998
|
+
}
|
|
999
|
+
if (0 < channel.chorusSendLevel && 0 < instrumentKey.chorusEffectsSend) {
|
|
1000
|
+
note.chorusEffectsSend = new GainNode(this.audioContext, {
|
|
1001
|
+
gain: instrumentKey.chorusEffectsSend,
|
|
1002
|
+
});
|
|
1003
|
+
note.volumeNode.connect(note.chorusEffectsSend);
|
|
1004
|
+
note.chorusEffectsSend.connect(this.chorusEffect.input);
|
|
1005
|
+
}
|
|
966
1006
|
note.bufferSource.start(startTime);
|
|
967
1007
|
return note;
|
|
968
1008
|
}
|
|
@@ -992,6 +1032,19 @@ class MidyGM2 {
|
|
|
992
1032
|
if (channel.sostenutoPedal) {
|
|
993
1033
|
channel.sostenutoNotes.set(noteNumber, note);
|
|
994
1034
|
}
|
|
1035
|
+
const exclusiveClass = instrumentKey.exclusiveClass;
|
|
1036
|
+
if (exclusiveClass !== 0) {
|
|
1037
|
+
if (this.exclusiveClassMap.has(exclusiveClass)) {
|
|
1038
|
+
const prevEntry = this.exclusiveClassMap.get(exclusiveClass);
|
|
1039
|
+
const [prevNote, prevChannelNumber] = prevEntry;
|
|
1040
|
+
if (!prevNote.ending) {
|
|
1041
|
+
this.scheduleNoteRelease(prevChannelNumber, prevNote.noteNumber, 0, // velocity,
|
|
1042
|
+
startTime, undefined, // portamentoNoteNumber
|
|
1043
|
+
true);
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
this.exclusiveClassMap.set(exclusiveClass, [note, channelNumber]);
|
|
1047
|
+
}
|
|
995
1048
|
const scheduledNotes = channel.scheduledNotes;
|
|
996
1049
|
if (scheduledNotes.has(noteNumber)) {
|
|
997
1050
|
scheduledNotes.get(noteNumber).push(note);
|
|
@@ -1004,15 +1057,15 @@ class MidyGM2 {
|
|
|
1004
1057
|
const now = this.audioContext.currentTime;
|
|
1005
1058
|
return this.scheduleNoteOn(channelNumber, noteNumber, velocity, now, portamento);
|
|
1006
1059
|
}
|
|
1007
|
-
stopNote(
|
|
1060
|
+
stopNote(endTime, stopTime, scheduledNotes, index) {
|
|
1008
1061
|
const note = scheduledNotes[index];
|
|
1009
1062
|
note.volumeNode.gain
|
|
1010
|
-
.cancelScheduledValues(
|
|
1011
|
-
.linearRampToValueAtTime(0,
|
|
1063
|
+
.cancelScheduledValues(endTime)
|
|
1064
|
+
.linearRampToValueAtTime(0, stopTime);
|
|
1012
1065
|
note.ending = true;
|
|
1013
1066
|
this.scheduleTask(() => {
|
|
1014
1067
|
note.bufferSource.loop = false;
|
|
1015
|
-
},
|
|
1068
|
+
}, stopTime);
|
|
1016
1069
|
return new Promise((resolve) => {
|
|
1017
1070
|
note.bufferSource.onended = () => {
|
|
1018
1071
|
scheduledNotes[index] = null;
|
|
@@ -1028,12 +1081,18 @@ class MidyGM2 {
|
|
|
1028
1081
|
note.vibratoDepth.disconnect();
|
|
1029
1082
|
note.vibratoLFO.stop();
|
|
1030
1083
|
}
|
|
1084
|
+
if (note.reverbEffectsSend) {
|
|
1085
|
+
note.reverbEffectsSend.disconnect();
|
|
1086
|
+
}
|
|
1087
|
+
if (note.chorusEffectsSend) {
|
|
1088
|
+
note.chorusEffectsSend.disconnect();
|
|
1089
|
+
}
|
|
1031
1090
|
resolve();
|
|
1032
1091
|
};
|
|
1033
|
-
note.bufferSource.stop(
|
|
1092
|
+
note.bufferSource.stop(stopTime);
|
|
1034
1093
|
});
|
|
1035
1094
|
}
|
|
1036
|
-
scheduleNoteRelease(channelNumber, noteNumber, _velocity,
|
|
1095
|
+
scheduleNoteRelease(channelNumber, noteNumber, _velocity, endTime, portamentoNoteNumber, force) {
|
|
1037
1096
|
const channel = this.channels[channelNumber];
|
|
1038
1097
|
if (!force) {
|
|
1039
1098
|
if (channel.sustainPedal)
|
|
@@ -1051,21 +1110,22 @@ class MidyGM2 {
|
|
|
1051
1110
|
if (note.ending)
|
|
1052
1111
|
continue;
|
|
1053
1112
|
if (portamentoNoteNumber === undefined) {
|
|
1054
|
-
const
|
|
1055
|
-
const modRelease =
|
|
1113
|
+
const volRelease = endTime + note.instrumentKey.volRelease;
|
|
1114
|
+
const modRelease = endTime + note.instrumentKey.modRelease;
|
|
1056
1115
|
note.filterNode.frequency
|
|
1057
|
-
.cancelScheduledValues(
|
|
1116
|
+
.cancelScheduledValues(endTime)
|
|
1058
1117
|
.linearRampToValueAtTime(0, modRelease);
|
|
1059
|
-
|
|
1118
|
+
const stopTime = Math.min(volRelease, modRelease);
|
|
1119
|
+
return this.stopNote(endTime, stopTime, scheduledNotes, i);
|
|
1060
1120
|
}
|
|
1061
1121
|
else {
|
|
1062
|
-
const portamentoTime =
|
|
1122
|
+
const portamentoTime = endTime + channel.portamentoTime;
|
|
1063
1123
|
const detuneChange = (portamentoNoteNumber - noteNumber) * 100;
|
|
1064
1124
|
const detune = note.bufferSource.detune.value + detuneChange;
|
|
1065
1125
|
note.bufferSource.detune
|
|
1066
|
-
.cancelScheduledValues(
|
|
1126
|
+
.cancelScheduledValues(endTime)
|
|
1067
1127
|
.linearRampToValueAtTime(detune, portamentoTime);
|
|
1068
|
-
return this.stopNote(
|
|
1128
|
+
return this.stopNote(endTime, portamentoTime, scheduledNotes, i);
|
|
1069
1129
|
}
|
|
1070
1130
|
}
|
|
1071
1131
|
}
|
|
@@ -1280,20 +1340,44 @@ class MidyGM2 {
|
|
|
1280
1340
|
if (0 < reverbSendLevel) {
|
|
1281
1341
|
const now = this.audioContext.currentTime;
|
|
1282
1342
|
channel.reverbSendLevel = reverbSendLevel / 127;
|
|
1283
|
-
reverbEffect.
|
|
1284
|
-
reverbEffect.
|
|
1343
|
+
reverbEffect.input.gain.cancelScheduledValues(now);
|
|
1344
|
+
reverbEffect.input.gain.setValueAtTime(channel.reverbSendLevel, now);
|
|
1285
1345
|
}
|
|
1286
1346
|
else {
|
|
1287
|
-
channel.
|
|
1347
|
+
channel.scheduledNotes.forEach((noteList) => {
|
|
1348
|
+
for (let i = 0; i < noteList.length; i++) {
|
|
1349
|
+
const note = noteList[i];
|
|
1350
|
+
if (!note)
|
|
1351
|
+
continue;
|
|
1352
|
+
if (note.instrumentKey.reverbEffectsSend <= 0)
|
|
1353
|
+
continue;
|
|
1354
|
+
note.reverbEffectsSend.disconnect();
|
|
1355
|
+
}
|
|
1356
|
+
});
|
|
1288
1357
|
}
|
|
1289
1358
|
}
|
|
1290
1359
|
else {
|
|
1291
1360
|
if (0 < reverbSendLevel) {
|
|
1292
|
-
channel.merger.connect(reverbEffect.input);
|
|
1293
1361
|
const now = this.audioContext.currentTime;
|
|
1362
|
+
channel.scheduledNotes.forEach((noteList) => {
|
|
1363
|
+
for (let i = 0; i < noteList.length; i++) {
|
|
1364
|
+
const note = noteList[i];
|
|
1365
|
+
if (!note)
|
|
1366
|
+
continue;
|
|
1367
|
+
if (note.instrumentKey.reverbEffectsSend <= 0)
|
|
1368
|
+
continue;
|
|
1369
|
+
if (!note.reverbEffectsSend) {
|
|
1370
|
+
note.reverbEffectsSend = new GainNode(this.audioContext, {
|
|
1371
|
+
gain: note.instrumentKey.reverbEffectsSend,
|
|
1372
|
+
});
|
|
1373
|
+
note.volumeNode.connect(note.reverbEffectsSend);
|
|
1374
|
+
}
|
|
1375
|
+
note.reverbEffectsSend.connect(reverbEffect.input);
|
|
1376
|
+
}
|
|
1377
|
+
});
|
|
1294
1378
|
channel.reverbSendLevel = reverbSendLevel / 127;
|
|
1295
|
-
reverbEffect.
|
|
1296
|
-
reverbEffect.
|
|
1379
|
+
reverbEffect.input.gain.cancelScheduledValues(now);
|
|
1380
|
+
reverbEffect.input.gain.setValueAtTime(channel.reverbSendLevel, now);
|
|
1297
1381
|
}
|
|
1298
1382
|
}
|
|
1299
1383
|
}
|
|
@@ -1304,20 +1388,44 @@ class MidyGM2 {
|
|
|
1304
1388
|
if (0 < chorusSendLevel) {
|
|
1305
1389
|
const now = this.audioContext.currentTime;
|
|
1306
1390
|
channel.chorusSendLevel = chorusSendLevel / 127;
|
|
1307
|
-
chorusEffect.
|
|
1308
|
-
chorusEffect.
|
|
1391
|
+
chorusEffect.input.gain.cancelScheduledValues(now);
|
|
1392
|
+
chorusEffect.input.gain.setValueAtTime(channel.chorusSendLevel, now);
|
|
1309
1393
|
}
|
|
1310
1394
|
else {
|
|
1311
|
-
channel.
|
|
1395
|
+
channel.scheduledNotes.forEach((noteList) => {
|
|
1396
|
+
for (let i = 0; i < noteList.length; i++) {
|
|
1397
|
+
const note = noteList[i];
|
|
1398
|
+
if (!note)
|
|
1399
|
+
continue;
|
|
1400
|
+
if (note.instrumentKey.chorusEffectsSend <= 0)
|
|
1401
|
+
continue;
|
|
1402
|
+
note.chorusEffectsSend.disconnect();
|
|
1403
|
+
}
|
|
1404
|
+
});
|
|
1312
1405
|
}
|
|
1313
1406
|
}
|
|
1314
1407
|
else {
|
|
1315
1408
|
if (0 < chorusSendLevel) {
|
|
1316
|
-
channel.merger.connect(chorusEffect.input);
|
|
1317
1409
|
const now = this.audioContext.currentTime;
|
|
1410
|
+
channel.scheduledNotes.forEach((noteList) => {
|
|
1411
|
+
for (let i = 0; i < noteList.length; i++) {
|
|
1412
|
+
const note = noteList[i];
|
|
1413
|
+
if (!note)
|
|
1414
|
+
continue;
|
|
1415
|
+
if (note.instrumentKey.chorusEffectsSend <= 0)
|
|
1416
|
+
continue;
|
|
1417
|
+
if (!note.chorusEffectsSend) {
|
|
1418
|
+
note.chorusEffectsSend = new GainNode(this.audioContext, {
|
|
1419
|
+
gain: note.instrumentKey.chorusEffectsSend,
|
|
1420
|
+
});
|
|
1421
|
+
note.volumeNode.connect(note.chorusEffectsSend);
|
|
1422
|
+
}
|
|
1423
|
+
note.chorusEffectsSend.connect(chorusEffect.input);
|
|
1424
|
+
}
|
|
1425
|
+
});
|
|
1318
1426
|
channel.chorusSendLevel = chorusSendLevel / 127;
|
|
1319
|
-
chorusEffect.
|
|
1320
|
-
chorusEffect.
|
|
1427
|
+
chorusEffect.input.gain.cancelScheduledValues(now);
|
|
1428
|
+
chorusEffect.input.gain.setValueAtTime(channel.chorusSendLevel, now);
|
|
1321
1429
|
}
|
|
1322
1430
|
}
|
|
1323
1431
|
}
|
|
@@ -1659,10 +1767,8 @@ class MidyGM2 {
|
|
|
1659
1767
|
}
|
|
1660
1768
|
setReverbTime(value) {
|
|
1661
1769
|
this.reverb.time = this.getReverbTime(value);
|
|
1662
|
-
const { audioContext,
|
|
1663
|
-
|
|
1664
|
-
channels[i].reverbEffect = options.reverbAlgorithm(audioContext);
|
|
1665
|
-
}
|
|
1770
|
+
const { audioContext, options } = this;
|
|
1771
|
+
this.reverbEffect = options.reverbAlgorithm(audioContext);
|
|
1666
1772
|
}
|
|
1667
1773
|
getReverbTime(value) {
|
|
1668
1774
|
return Math.pow(Math.E, (value - 40) * 0.025);
|
|
@@ -1739,10 +1845,7 @@ class MidyGM2 {
|
|
|
1739
1845
|
const now = this.audioContext.currentTime;
|
|
1740
1846
|
const modRate = this.getChorusModRate(value);
|
|
1741
1847
|
this.chorus.modRate = modRate;
|
|
1742
|
-
|
|
1743
|
-
const lfo = this.channels[i].chorusEffect.lfo;
|
|
1744
|
-
lfo.frequency.setValueAtTime(modRate, now);
|
|
1745
|
-
}
|
|
1848
|
+
this.chorusEffect.lfo.frequency.setValueAtTime(modRate, now);
|
|
1746
1849
|
}
|
|
1747
1850
|
getChorusModRate(value) {
|
|
1748
1851
|
return value * 0.122; // Hz
|
|
@@ -1751,12 +1854,9 @@ class MidyGM2 {
|
|
|
1751
1854
|
const now = this.audioContext.currentTime;
|
|
1752
1855
|
const modDepth = this.getChorusModDepth(value);
|
|
1753
1856
|
this.chorus.modDepth = modDepth;
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
.cancelScheduledValues(now)
|
|
1758
|
-
.setValueAtTime(modDepth / 2, now);
|
|
1759
|
-
}
|
|
1857
|
+
this.chorusEffect.lfoGain.gain
|
|
1858
|
+
.cancelScheduledValues(now)
|
|
1859
|
+
.setValueAtTime(modDepth / 2, now);
|
|
1760
1860
|
}
|
|
1761
1861
|
getChorusModDepth(value) {
|
|
1762
1862
|
return (value + 1) / 3200; // second
|
|
@@ -1765,14 +1865,11 @@ class MidyGM2 {
|
|
|
1765
1865
|
const now = this.audioContext.currentTime;
|
|
1766
1866
|
const feedback = this.getChorusFeedback(value);
|
|
1767
1867
|
this.chorus.feedback = feedback;
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
.cancelScheduledValues(now)
|
|
1774
|
-
.setValueAtTime(feedback, now);
|
|
1775
|
-
}
|
|
1868
|
+
const chorusEffect = this.chorusEffect;
|
|
1869
|
+
for (let i = 0; i < chorusEffect.feedbackGains.length; i++) {
|
|
1870
|
+
chorusEffect.feedbackGains[i].gain
|
|
1871
|
+
.cancelScheduledValues(now)
|
|
1872
|
+
.setValueAtTime(feedback, now);
|
|
1776
1873
|
}
|
|
1777
1874
|
}
|
|
1778
1875
|
getChorusFeedback(value) {
|
|
@@ -1780,15 +1877,28 @@ class MidyGM2 {
|
|
|
1780
1877
|
}
|
|
1781
1878
|
setChorusSendToReverb(value) {
|
|
1782
1879
|
const sendToReverb = this.getChorusSendToReverb(value);
|
|
1783
|
-
|
|
1784
|
-
|
|
1880
|
+
const sendGain = this.chorusEffect.sendGain;
|
|
1881
|
+
if (0 < this.chorus.sendToReverb) {
|
|
1785
1882
|
this.chorus.sendToReverb = sendToReverb;
|
|
1786
|
-
|
|
1787
|
-
.
|
|
1788
|
-
.
|
|
1883
|
+
if (0 < sendToReverb) {
|
|
1884
|
+
const now = this.audioContext.currentTime;
|
|
1885
|
+
sendGain.gain
|
|
1886
|
+
.cancelScheduledValues(now)
|
|
1887
|
+
.setValueAtTime(sendToReverb, now);
|
|
1888
|
+
}
|
|
1889
|
+
else {
|
|
1890
|
+
sendGain.disconnect();
|
|
1891
|
+
}
|
|
1789
1892
|
}
|
|
1790
|
-
else
|
|
1791
|
-
this.
|
|
1893
|
+
else {
|
|
1894
|
+
this.chorus.sendToReverb = sendToReverb;
|
|
1895
|
+
if (0 < sendToReverb) {
|
|
1896
|
+
const now = this.audioContext.currentTime;
|
|
1897
|
+
sendGain.connect(this.reverbEffect.input);
|
|
1898
|
+
sendGain.gain
|
|
1899
|
+
.cancelScheduledValues(now)
|
|
1900
|
+
.setValueAtTime(sendToReverb, now);
|
|
1901
|
+
}
|
|
1792
1902
|
}
|
|
1793
1903
|
}
|
|
1794
1904
|
getChorusSendToReverb(value) {
|
package/script/midy-GMLite.d.ts
CHANGED
|
@@ -35,6 +35,7 @@ export class MidyGMLite {
|
|
|
35
35
|
timeline: any[];
|
|
36
36
|
instruments: any[];
|
|
37
37
|
notePromises: any[];
|
|
38
|
+
exclusiveClassMap: Map<any, any>;
|
|
38
39
|
audioContext: any;
|
|
39
40
|
masterGain: any;
|
|
40
41
|
controlChangeHandlers: {
|
|
@@ -96,11 +97,11 @@ export class MidyGMLite {
|
|
|
96
97
|
createNote(channel: any, instrumentKey: any, noteNumber: any, velocity: any, startTime: any, isSF3: any): Promise<Note>;
|
|
97
98
|
scheduleNoteOn(channelNumber: any, noteNumber: any, velocity: any, startTime: any): Promise<void>;
|
|
98
99
|
noteOn(channelNumber: any, noteNumber: any, velocity: any): Promise<void>;
|
|
99
|
-
stopNote(
|
|
100
|
-
scheduleNoteRelease(channelNumber: any, noteNumber: any, _velocity: any,
|
|
101
|
-
releaseNote(channelNumber: any, noteNumber: any, velocity: any):
|
|
100
|
+
stopNote(endTime: any, stopTime: any, scheduledNotes: any, index: any): Promise<any>;
|
|
101
|
+
scheduleNoteRelease(channelNumber: any, noteNumber: any, _velocity: any, endTime: any, force: any): Promise<any> | undefined;
|
|
102
|
+
releaseNote(channelNumber: any, noteNumber: any, velocity: any): Promise<any> | undefined;
|
|
102
103
|
releaseSustainPedal(channelNumber: any, halfVelocity: any): any[];
|
|
103
|
-
handleMIDIMessage(statusByte: any, data1: any, data2: any): void | Promise<
|
|
104
|
+
handleMIDIMessage(statusByte: any, data1: any, data2: any): void | Promise<any>;
|
|
104
105
|
handleProgramChange(channelNumber: any, program: any): void;
|
|
105
106
|
handlePitchBendMessage(channelNumber: any, lsb: any, msb: any): void;
|
|
106
107
|
setPitchBend(channelNumber: any, pitchBend: any): void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"midy-GMLite.d.ts","sourceRoot":"","sources":["../src/midy-GMLite.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"midy-GMLite.d.ts","sourceRoot":"","sources":["../src/midy-GMLite.js"],"names":[],"mappings":"AAmBA;IAoBE;;;;;;;;;MASE;IAEF;;;;;;;MAOE;IAEF,+BAOC;IA9CD,qBAAmB;IACnB,kBAAc;IACd,0BAAwB;IACxB,kBAAc;IACd,mBAAiB;IACjB,kBAAc;IACd,mBAAe;IACf,kBAAgB;IAChB,sBAA2C;IAC3C,mBAAkB;IAClB,mBAAkB;IAClB,kBAAiB;IACjB,oBAAmB;IACnB,mBAAkB;IAClB,gBAAc;IACd,mBAAiB;IACjB,oBAAkB;IAClB,iCAA8B;IAuB5B,kBAAgC;IAChC,gBAA4C;IAC5C;;;;;;;;;;;;;MAA+D;IAC/D,gBAAiD;IAKnD,4BAMC;IAED,mCAWC;IAED,gDAMC;IAED,sCASC;IAED;;;;MAeC;IAED,yCAUC;IAED,+DA2BC;IAED,mEAWC;IAED,2EA+CC;IAED,mCAOC;IAED,0BAkDC;IAED,uDAEC;IAED,wDAEC;IAED;;;MAgEC;IAED,+EAmBC;IAED,qDAKC;IAED,uBAKC;IAED,aAGC;IAED,cAKC;IAED,wBAIC;IAED,0BAKC;IAED,wBAOC;IAED,sBAGC;IAED,uDASC;IAED,6CAQC;IAED,2BAEC;IAED,4BAEC;IAED,yCAEC;IAED,mFAGC;IAED,mCAeC;IAED,+CAwBC;IAED,6CAIC;IAED,mCAsBC;IAED,+DA0BC;IAED,wHAgCC;IAED,kGAgDC;IAED,0EAGC;IAED,qFA4BC;IAED,6HAuBC;IAED,0FAGC;IAED,kEAeC;IAED,gFAiBC;IAED,4DAGC;IAED,qEAGC;IAED,uDAOC;IAED;;;;;;;;;;;;;MAeC;IAED,2EASC;IAED,qCAkBC;IAED,8DAIC;IACD,iDAIC;IAED;;;MAMC;IAED,2CAIC;IAED,yDAIC;IAED,mDAGC;IAED,wCAUC;IAED,sDAMC;IAED,oCAYC;IAED,gDAEC;IAED,gDAEC;IAED,mDAGC;IAED,oDAaC;IAED,kDAKC;IAED,iEAOC;IAED,+CAEC;IAED,8CAEC;IAED,+CAEC;IAED,4DAgBC;IAED,oBASC;IAED,yDAaC;IAED,yCAGC;IAED,mCAQC;IAED,wCAEC;IAED,6BASC;IAED,0DAUC;CACF;AA5gCD;IAQE,gFAKC;IAZD,kBAAa;IACb,gBAAW;IACX,gBAAW;IACX,iBAAY;IACZ,mBAAc;IACd,qBAAgB;IAGd,gBAA4B;IAC5B,cAAwB;IACxB,eAA0B;IAC1B,mBAAkC;CAErC"}
|
package/script/midy-GMLite.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.MidyGMLite = void 0;
|
|
4
|
-
const
|
|
5
|
-
const
|
|
4
|
+
const midi_file_1 = require("midi-file");
|
|
5
|
+
const soundfont_parser_1 = require("@marmooo/soundfont-parser");
|
|
6
6
|
class Note {
|
|
7
7
|
constructor(noteNumber, velocity, startTime, instrumentKey) {
|
|
8
8
|
Object.defineProperty(this, "bufferSource", {
|
|
@@ -151,6 +151,12 @@ class MidyGMLite {
|
|
|
151
151
|
writable: true,
|
|
152
152
|
value: []
|
|
153
153
|
});
|
|
154
|
+
Object.defineProperty(this, "exclusiveClassMap", {
|
|
155
|
+
enumerable: true,
|
|
156
|
+
configurable: true,
|
|
157
|
+
writable: true,
|
|
158
|
+
value: new Map()
|
|
159
|
+
});
|
|
154
160
|
this.audioContext = audioContext;
|
|
155
161
|
this.masterGain = new GainNode(audioContext);
|
|
156
162
|
this.controlChangeHandlers = this.createControlChangeHandlers();
|
|
@@ -180,14 +186,14 @@ class MidyGMLite {
|
|
|
180
186
|
async loadSoundFont(soundFontUrl) {
|
|
181
187
|
const response = await fetch(soundFontUrl);
|
|
182
188
|
const arrayBuffer = await response.arrayBuffer();
|
|
183
|
-
const parsed = (0,
|
|
184
|
-
const soundFont = new
|
|
189
|
+
const parsed = (0, soundfont_parser_1.parse)(new Uint8Array(arrayBuffer));
|
|
190
|
+
const soundFont = new soundfont_parser_1.SoundFont(parsed);
|
|
185
191
|
this.addSoundFont(soundFont);
|
|
186
192
|
}
|
|
187
193
|
async loadMIDI(midiUrl) {
|
|
188
194
|
const response = await fetch(midiUrl);
|
|
189
195
|
const arrayBuffer = await response.arrayBuffer();
|
|
190
|
-
const midi = (0,
|
|
196
|
+
const midi = (0, midi_file_1.parseMidi)(new Uint8Array(arrayBuffer));
|
|
191
197
|
this.ticksPerBeat = midi.header.ticksPerBeat;
|
|
192
198
|
const midiData = this.extractMidiData(midi);
|
|
193
199
|
this.instruments = midiData.instruments;
|
|
@@ -223,19 +229,25 @@ class MidyGMLite {
|
|
|
223
229
|
const sampleStart = instrumentKey.start;
|
|
224
230
|
const sampleEnd = instrumentKey.sample.length + instrumentKey.end;
|
|
225
231
|
if (isSF3) {
|
|
226
|
-
const sample = instrumentKey.sample
|
|
227
|
-
const
|
|
232
|
+
const sample = instrumentKey.sample;
|
|
233
|
+
const start = sample.byteOffset + sampleStart;
|
|
234
|
+
const end = sample.byteOffset + sampleEnd;
|
|
235
|
+
const buffer = sample.buffer.slice(start, end);
|
|
236
|
+
const audioBuffer = await this.audioContext.decodeAudioData(buffer);
|
|
228
237
|
return audioBuffer;
|
|
229
238
|
}
|
|
230
239
|
else {
|
|
231
|
-
const sample = instrumentKey.sample
|
|
240
|
+
const sample = instrumentKey.sample;
|
|
241
|
+
const start = sample.byteOffset + sampleStart;
|
|
242
|
+
const end = sample.byteOffset + sampleEnd;
|
|
243
|
+
const buffer = sample.buffer.slice(start, end);
|
|
232
244
|
const audioBuffer = new AudioBuffer({
|
|
233
245
|
numberOfChannels: 1,
|
|
234
246
|
length: sample.length,
|
|
235
247
|
sampleRate: instrumentKey.sampleRate,
|
|
236
248
|
});
|
|
237
249
|
const channelData = audioBuffer.getChannelData(0);
|
|
238
|
-
const int16Array = new Int16Array(
|
|
250
|
+
const int16Array = new Int16Array(buffer);
|
|
239
251
|
for (let i = 0; i < int16Array.length; i++) {
|
|
240
252
|
channelData[i] = int16Array[i] / 32768;
|
|
241
253
|
}
|
|
@@ -309,6 +321,7 @@ class MidyGMLite {
|
|
|
309
321
|
if (queueIndex >= this.timeline.length) {
|
|
310
322
|
await Promise.all(this.notePromises);
|
|
311
323
|
this.notePromises = [];
|
|
324
|
+
this.exclusiveClassMap.clear();
|
|
312
325
|
resolve();
|
|
313
326
|
return;
|
|
314
327
|
}
|
|
@@ -324,6 +337,7 @@ class MidyGMLite {
|
|
|
324
337
|
}
|
|
325
338
|
else if (this.isStopping) {
|
|
326
339
|
await this.stopNotes(0, true);
|
|
340
|
+
this.exclusiveClassMap.clear();
|
|
327
341
|
this.notePromises = [];
|
|
328
342
|
resolve();
|
|
329
343
|
this.isStopping = false;
|
|
@@ -332,6 +346,7 @@ class MidyGMLite {
|
|
|
332
346
|
}
|
|
333
347
|
else if (this.isSeeking) {
|
|
334
348
|
this.stopNotes(0, true);
|
|
349
|
+
this.exclusiveClassMap.clear();
|
|
335
350
|
this.startTime = this.audioContext.currentTime;
|
|
336
351
|
queueIndex = this.getQueueIndex(this.resumeTime);
|
|
337
352
|
offset = this.resumeTime - this.startTime;
|
|
@@ -637,6 +652,19 @@ class MidyGMLite {
|
|
|
637
652
|
const note = await this.createNote(channel, instrumentKey, noteNumber, velocity, startTime, isSF3);
|
|
638
653
|
note.volumeNode.connect(channel.gainL);
|
|
639
654
|
note.volumeNode.connect(channel.gainR);
|
|
655
|
+
const exclusiveClass = instrumentKey.exclusiveClass;
|
|
656
|
+
if (exclusiveClass !== 0) {
|
|
657
|
+
if (this.exclusiveClassMap.has(exclusiveClass)) {
|
|
658
|
+
const prevEntry = this.exclusiveClassMap.get(exclusiveClass);
|
|
659
|
+
const [prevNote, prevChannelNumber] = prevEntry;
|
|
660
|
+
if (!prevNote.ending) {
|
|
661
|
+
this.scheduleNoteRelease(prevChannelNumber, prevNote.noteNumber, 0, // velocity,
|
|
662
|
+
startTime, undefined, // portamentoNoteNumber
|
|
663
|
+
true);
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
this.exclusiveClassMap.set(exclusiveClass, [note, channelNumber]);
|
|
667
|
+
}
|
|
640
668
|
const scheduledNotes = channel.scheduledNotes;
|
|
641
669
|
if (scheduledNotes.has(noteNumber)) {
|
|
642
670
|
scheduledNotes.get(noteNumber).push(note);
|
|
@@ -649,15 +677,15 @@ class MidyGMLite {
|
|
|
649
677
|
const now = this.audioContext.currentTime;
|
|
650
678
|
return this.scheduleNoteOn(channelNumber, noteNumber, velocity, now);
|
|
651
679
|
}
|
|
652
|
-
stopNote(
|
|
680
|
+
stopNote(endTime, stopTime, scheduledNotes, index) {
|
|
653
681
|
const note = scheduledNotes[index];
|
|
654
682
|
note.volumeNode.gain
|
|
655
|
-
.cancelScheduledValues(
|
|
656
|
-
.linearRampToValueAtTime(0,
|
|
683
|
+
.cancelScheduledValues(endTime)
|
|
684
|
+
.linearRampToValueAtTime(0, stopTime);
|
|
657
685
|
note.ending = true;
|
|
658
686
|
this.scheduleTask(() => {
|
|
659
687
|
note.bufferSource.loop = false;
|
|
660
|
-
},
|
|
688
|
+
}, stopTime);
|
|
661
689
|
return new Promise((resolve) => {
|
|
662
690
|
note.bufferSource.onended = () => {
|
|
663
691
|
scheduledNotes[index] = null;
|
|
@@ -675,10 +703,10 @@ class MidyGMLite {
|
|
|
675
703
|
}
|
|
676
704
|
resolve();
|
|
677
705
|
};
|
|
678
|
-
note.bufferSource.stop(
|
|
706
|
+
note.bufferSource.stop(stopTime);
|
|
679
707
|
});
|
|
680
708
|
}
|
|
681
|
-
scheduleNoteRelease(channelNumber, noteNumber, _velocity,
|
|
709
|
+
scheduleNoteRelease(channelNumber, noteNumber, _velocity, endTime, force) {
|
|
682
710
|
const channel = this.channels[channelNumber];
|
|
683
711
|
if (!force && channel.sustainPedal)
|
|
684
712
|
return;
|
|
@@ -691,12 +719,13 @@ class MidyGMLite {
|
|
|
691
719
|
continue;
|
|
692
720
|
if (note.ending)
|
|
693
721
|
continue;
|
|
694
|
-
const
|
|
695
|
-
const modRelease =
|
|
722
|
+
const volRelease = endTime + note.instrumentKey.volRelease;
|
|
723
|
+
const modRelease = endTime + note.instrumentKey.modRelease;
|
|
696
724
|
note.filterNode.frequency
|
|
697
|
-
.cancelScheduledValues(
|
|
725
|
+
.cancelScheduledValues(endTime)
|
|
698
726
|
.linearRampToValueAtTime(0, modRelease);
|
|
699
|
-
|
|
727
|
+
const stopTime = Math.min(volRelease, modRelease);
|
|
728
|
+
return this.stopNote(endTime, stopTime, scheduledNotes, i);
|
|
700
729
|
}
|
|
701
730
|
}
|
|
702
731
|
releaseNote(channelNumber, noteNumber, velocity) {
|