@marmooo/midy 0.2.7 → 0.2.9
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/README.md +2 -2
- package/esm/midy-GM1.d.ts +14 -10
- package/esm/midy-GM1.d.ts.map +1 -1
- package/esm/midy-GM1.js +125 -86
- package/esm/midy-GM2.d.ts +35 -31
- package/esm/midy-GM2.d.ts.map +1 -1
- package/esm/midy-GM2.js +263 -187
- package/esm/midy-GMLite.d.ts +11 -5
- package/esm/midy-GMLite.d.ts.map +1 -1
- package/esm/midy-GMLite.js +118 -76
- package/esm/midy.d.ts +35 -31
- package/esm/midy.d.ts.map +1 -1
- package/esm/midy.js +326 -253
- package/package.json +1 -1
- package/script/midy-GM1.d.ts +14 -10
- package/script/midy-GM1.d.ts.map +1 -1
- package/script/midy-GM1.js +125 -86
- package/script/midy-GM2.d.ts +35 -31
- package/script/midy-GM2.d.ts.map +1 -1
- package/script/midy-GM2.js +263 -187
- package/script/midy-GMLite.d.ts +11 -5
- package/script/midy-GMLite.d.ts.map +1 -1
- package/script/midy-GMLite.js +118 -76
- package/script/midy.d.ts +35 -31
- package/script/midy.d.ts.map +1 -1
- package/script/midy.js +326 -253
package/script/midy-GM2.js
CHANGED
|
@@ -242,6 +242,12 @@ const volumeEnvelopeKeys = [
|
|
|
242
242
|
const volumeEnvelopeKeySet = new Set(volumeEnvelopeKeys);
|
|
243
243
|
class MidyGM2 {
|
|
244
244
|
constructor(audioContext, options = this.defaultOptions) {
|
|
245
|
+
Object.defineProperty(this, "mode", {
|
|
246
|
+
enumerable: true,
|
|
247
|
+
configurable: true,
|
|
248
|
+
writable: true,
|
|
249
|
+
value: "GM2"
|
|
250
|
+
});
|
|
245
251
|
Object.defineProperty(this, "ticksPerBeat", {
|
|
246
252
|
enumerable: true,
|
|
247
253
|
configurable: true,
|
|
@@ -287,18 +293,6 @@ class MidyGM2 {
|
|
|
287
293
|
delayTimes: this.generateDistributedArray(0.02, 2, 0.5),
|
|
288
294
|
}
|
|
289
295
|
});
|
|
290
|
-
Object.defineProperty(this, "mono", {
|
|
291
|
-
enumerable: true,
|
|
292
|
-
configurable: true,
|
|
293
|
-
writable: true,
|
|
294
|
-
value: false
|
|
295
|
-
}); // CC#124, CC#125
|
|
296
|
-
Object.defineProperty(this, "omni", {
|
|
297
|
-
enumerable: true,
|
|
298
|
-
configurable: true,
|
|
299
|
-
writable: true,
|
|
300
|
-
value: false
|
|
301
|
-
}); // CC#126, CC#127
|
|
302
296
|
Object.defineProperty(this, "noteCheckInterval", {
|
|
303
297
|
enumerable: true,
|
|
304
298
|
configurable: true,
|
|
@@ -432,6 +426,11 @@ class MidyGM2 {
|
|
|
432
426
|
this.audioContext = audioContext;
|
|
433
427
|
this.options = { ...this.defaultOptions, ...options };
|
|
434
428
|
this.masterVolume = new GainNode(audioContext);
|
|
429
|
+
this.scheduler = new GainNode(audioContext, { gain: 0 });
|
|
430
|
+
this.schedulerBuffer = new AudioBuffer({
|
|
431
|
+
length: 1,
|
|
432
|
+
sampleRate: audioContext.sampleRate,
|
|
433
|
+
});
|
|
435
434
|
this.voiceParamsHandlers = this.createVoiceParamsHandlers();
|
|
436
435
|
this.controlChangeHandlers = this.createControlChangeHandlers();
|
|
437
436
|
this.channels = this.createChannels(audioContext);
|
|
@@ -440,6 +439,7 @@ class MidyGM2 {
|
|
|
440
439
|
this.chorusEffect.output.connect(this.masterVolume);
|
|
441
440
|
this.reverbEffect.output.connect(this.masterVolume);
|
|
442
441
|
this.masterVolume.connect(audioContext.destination);
|
|
442
|
+
this.scheduler.connect(audioContext.destination);
|
|
443
443
|
this.GM2SystemOn();
|
|
444
444
|
}
|
|
445
445
|
initSoundFontTable() {
|
|
@@ -500,6 +500,7 @@ class MidyGM2 {
|
|
|
500
500
|
controlTable: this.initControlTable(),
|
|
501
501
|
...this.setChannelAudioNodes(audioContext),
|
|
502
502
|
scheduledNotes: new SparseMap(128),
|
|
503
|
+
sustainNotes: [],
|
|
503
504
|
sostenutoNotes: new SparseMap(128),
|
|
504
505
|
scaleOctaveTuningTable: new Int8Array(12), // [-64, 63] cent
|
|
505
506
|
channelPressureTable: new Uint8Array([64, 64, 64, 0, 0, 0]),
|
|
@@ -537,10 +538,24 @@ class MidyGM2 {
|
|
|
537
538
|
return audioBuffer;
|
|
538
539
|
}
|
|
539
540
|
}
|
|
540
|
-
|
|
541
|
+
calcLoopMode(channel, note, voiceParams) {
|
|
542
|
+
if (channel.isDrum) {
|
|
543
|
+
const noteNumber = note.noteNumber;
|
|
544
|
+
if (noteNumber === 88 || 47 <= noteNumber && noteNumber <= 84) {
|
|
545
|
+
return true;
|
|
546
|
+
}
|
|
547
|
+
else {
|
|
548
|
+
return false;
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
else {
|
|
552
|
+
return voiceParams.sampleModes % 2 !== 0;
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
createBufferSource(channel, note, voiceParams, audioBuffer) {
|
|
541
556
|
const bufferSource = new AudioBufferSourceNode(this.audioContext);
|
|
542
557
|
bufferSource.buffer = audioBuffer;
|
|
543
|
-
bufferSource.loop =
|
|
558
|
+
bufferSource.loop = this.calcLoopMode(channel, note, voiceParams);
|
|
544
559
|
if (bufferSource.loop) {
|
|
545
560
|
bufferSource.loopStart = voiceParams.loopStart / voiceParams.sampleRate;
|
|
546
561
|
bufferSource.loopEnd = voiceParams.loopEnd / voiceParams.sampleRate;
|
|
@@ -582,7 +597,7 @@ class MidyGM2 {
|
|
|
582
597
|
const portamentoTarget = this.findPortamentoTarget(queueIndex);
|
|
583
598
|
if (portamentoTarget)
|
|
584
599
|
portamentoTarget.portamento = true;
|
|
585
|
-
const notePromise = this.scheduleNoteOff(
|
|
600
|
+
const notePromise = this.scheduleNoteOff(event.channel, event.noteNumber, event.velocity, startTime, false, // force
|
|
586
601
|
portamentoTarget?.noteNumber);
|
|
587
602
|
if (notePromise) {
|
|
588
603
|
this.notePromises.push(notePromise);
|
|
@@ -590,7 +605,7 @@ class MidyGM2 {
|
|
|
590
605
|
break;
|
|
591
606
|
}
|
|
592
607
|
case "controller":
|
|
593
|
-
this.handleControlChange(
|
|
608
|
+
this.handleControlChange(event.channel, event.controllerType, event.value, startTime);
|
|
594
609
|
break;
|
|
595
610
|
case "programChange":
|
|
596
611
|
this.handleProgramChange(event.channel, event.programNumber, startTime);
|
|
@@ -786,15 +801,10 @@ class MidyGM2 {
|
|
|
786
801
|
stopChannelNotes(channelNumber, velocity, force, scheduleTime) {
|
|
787
802
|
const channel = this.channels[channelNumber];
|
|
788
803
|
const promises = [];
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
continue;
|
|
794
|
-
const promise = this.scheduleNoteOff(channelNumber, note.noteNumber, velocity, scheduleTime, force, undefined);
|
|
795
|
-
this.notePromises.push(promise);
|
|
796
|
-
promises.push(promise);
|
|
797
|
-
}
|
|
804
|
+
this.processScheduledNotes(channel, (note) => {
|
|
805
|
+
const promise = this.scheduleNoteOff(channelNumber, note.noteNumber, velocity, scheduleTime, force, undefined);
|
|
806
|
+
this.notePromises.push(promise);
|
|
807
|
+
promises.push(promise);
|
|
798
808
|
});
|
|
799
809
|
channel.scheduledNotes.clear();
|
|
800
810
|
return Promise.all(promises);
|
|
@@ -850,14 +860,12 @@ class MidyGM2 {
|
|
|
850
860
|
const now = this.audioContext.currentTime;
|
|
851
861
|
return this.resumeTime + now - this.startTime - this.startDelay;
|
|
852
862
|
}
|
|
853
|
-
processScheduledNotes(channel,
|
|
863
|
+
processScheduledNotes(channel, callback) {
|
|
854
864
|
channel.scheduledNotes.forEach((noteList) => {
|
|
855
865
|
for (let i = 0; i < noteList.length; i++) {
|
|
856
866
|
const note = noteList[i];
|
|
857
867
|
if (!note)
|
|
858
868
|
continue;
|
|
859
|
-
if (scheduleTime < note.startTime)
|
|
860
|
-
continue;
|
|
861
869
|
callback(note);
|
|
862
870
|
}
|
|
863
871
|
});
|
|
@@ -1024,7 +1032,9 @@ class MidyGM2 {
|
|
|
1024
1032
|
return 8.176 * this.centToRate(cent);
|
|
1025
1033
|
}
|
|
1026
1034
|
calcChannelDetune(channel) {
|
|
1027
|
-
const masterTuning =
|
|
1035
|
+
const masterTuning = channel.isDrum
|
|
1036
|
+
? 0
|
|
1037
|
+
: this.masterCoarseTuning + this.masterFineTuning;
|
|
1028
1038
|
const channelTuning = channel.coarseTuning + channel.fineTuning;
|
|
1029
1039
|
const tuning = masterTuning + channelTuning;
|
|
1030
1040
|
const pitchWheel = channel.state.pitchWheel * 2 - 1;
|
|
@@ -1038,7 +1048,7 @@ class MidyGM2 {
|
|
|
1038
1048
|
return channel.scaleOctaveTuningTable[note.noteNumber % 12];
|
|
1039
1049
|
}
|
|
1040
1050
|
updateChannelDetune(channel, scheduleTime) {
|
|
1041
|
-
this.processScheduledNotes(channel,
|
|
1051
|
+
this.processScheduledNotes(channel, (note) => {
|
|
1042
1052
|
this.updateDetune(channel, note, scheduleTime);
|
|
1043
1053
|
});
|
|
1044
1054
|
}
|
|
@@ -1050,9 +1060,8 @@ class MidyGM2 {
|
|
|
1050
1060
|
.setValueAtTime(detune, scheduleTime);
|
|
1051
1061
|
}
|
|
1052
1062
|
getPortamentoTime(channel) {
|
|
1053
|
-
const factor = 5 * Math.log(10)
|
|
1054
|
-
|
|
1055
|
-
return Math.log(time) / factor;
|
|
1063
|
+
const factor = 5 * Math.log(10) * 127;
|
|
1064
|
+
return channel.state.portamentoTime * factor;
|
|
1056
1065
|
}
|
|
1057
1066
|
setPortamentoStartVolumeEnvelope(channel, note, scheduleTime) {
|
|
1058
1067
|
const { voiceParams, startTime } = note;
|
|
@@ -1213,7 +1222,7 @@ class MidyGM2 {
|
|
|
1213
1222
|
const voiceParams = voice.getAllParams(controllerState);
|
|
1214
1223
|
const note = new Note(noteNumber, velocity, startTime, voice, voiceParams);
|
|
1215
1224
|
const audioBuffer = await this.getAudioBuffer(channel.program, noteNumber, velocity, voiceParams, isSF3);
|
|
1216
|
-
note.bufferSource = this.
|
|
1225
|
+
note.bufferSource = this.createBufferSource(channel, note, voiceParams, audioBuffer);
|
|
1217
1226
|
note.volumeNode = new GainNode(this.audioContext);
|
|
1218
1227
|
note.gainL = new GainNode(this.audioContext);
|
|
1219
1228
|
note.gainR = new GainNode(this.audioContext);
|
|
@@ -1239,7 +1248,7 @@ class MidyGM2 {
|
|
|
1239
1248
|
if (0 < state.modulationDepth) {
|
|
1240
1249
|
this.startModulation(channel, note, now);
|
|
1241
1250
|
}
|
|
1242
|
-
if (
|
|
1251
|
+
if (channel.mono && channel.currentBufferSource) {
|
|
1243
1252
|
channel.currentBufferSource.stop(startTime);
|
|
1244
1253
|
channel.currentBufferSource = note.bufferSource;
|
|
1245
1254
|
}
|
|
@@ -1257,14 +1266,21 @@ class MidyGM2 {
|
|
|
1257
1266
|
note.bufferSource.start(startTime);
|
|
1258
1267
|
return note;
|
|
1259
1268
|
}
|
|
1260
|
-
calcBank(channel
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1269
|
+
calcBank(channel) {
|
|
1270
|
+
switch (this.mode) {
|
|
1271
|
+
case "GM1":
|
|
1272
|
+
if (channel.isDrum)
|
|
1273
|
+
return 128;
|
|
1274
|
+
return 0;
|
|
1275
|
+
case "GM2":
|
|
1276
|
+
if (channel.bankMSB === 121)
|
|
1277
|
+
return 0;
|
|
1278
|
+
if (channel.isDrum)
|
|
1279
|
+
return 128;
|
|
1280
|
+
return channel.bank;
|
|
1281
|
+
default:
|
|
1282
|
+
return channel.bank;
|
|
1266
1283
|
}
|
|
1267
|
-
return channel.bank;
|
|
1268
1284
|
}
|
|
1269
1285
|
async scheduleNoteOn(channelNumber, noteNumber, velocity, startTime, portamento) {
|
|
1270
1286
|
const channel = this.channels[channelNumber];
|
|
@@ -1280,15 +1296,15 @@ class MidyGM2 {
|
|
|
1280
1296
|
const note = await this.createNote(channel, voice, noteNumber, velocity, startTime, portamento, isSF3);
|
|
1281
1297
|
note.gainL.connect(channel.gainL);
|
|
1282
1298
|
note.gainR.connect(channel.gainR);
|
|
1283
|
-
if (channel.state.
|
|
1284
|
-
channel.
|
|
1299
|
+
if (0.5 <= channel.state.sustainPedal) {
|
|
1300
|
+
channel.sustainNotes.push(note);
|
|
1285
1301
|
}
|
|
1286
1302
|
const exclusiveClass = note.voiceParams.exclusiveClass;
|
|
1287
1303
|
if (exclusiveClass !== 0) {
|
|
1288
1304
|
if (this.exclusiveClassMap.has(exclusiveClass)) {
|
|
1289
1305
|
const prevEntry = this.exclusiveClassMap.get(exclusiveClass);
|
|
1290
1306
|
const [prevNote, prevChannelNumber] = prevEntry;
|
|
1291
|
-
if (!prevNote.ending) {
|
|
1307
|
+
if (prevNote && !prevNote.ending) {
|
|
1292
1308
|
this.scheduleNoteOff(prevChannelNumber, prevNote.noteNumber, 0, // velocity,
|
|
1293
1309
|
startTime, true, // force
|
|
1294
1310
|
undefined);
|
|
@@ -1350,7 +1366,7 @@ class MidyGM2 {
|
|
|
1350
1366
|
const channel = this.channels[channelNumber];
|
|
1351
1367
|
const state = channel.state;
|
|
1352
1368
|
if (!force) {
|
|
1353
|
-
if (0.5
|
|
1369
|
+
if (0.5 <= state.sustainPedal)
|
|
1354
1370
|
return;
|
|
1355
1371
|
if (channel.sostenutoNotes.has(noteNumber))
|
|
1356
1372
|
return;
|
|
@@ -1394,28 +1410,27 @@ class MidyGM2 {
|
|
|
1394
1410
|
const velocity = halfVelocity * 2;
|
|
1395
1411
|
const channel = this.channels[channelNumber];
|
|
1396
1412
|
const promises = [];
|
|
1397
|
-
|
|
1398
|
-
const
|
|
1399
|
-
const promise = this.noteOff(channelNumber, noteNumber, velocity);
|
|
1413
|
+
for (let i = 0; i < channel.sustainNotes.length; i++) {
|
|
1414
|
+
const promise = this.noteOff(channelNumber, channel.sustainNotes[i].noteNumber, velocity, scheduleTime);
|
|
1400
1415
|
promises.push(promise);
|
|
1401
|
-
}
|
|
1416
|
+
}
|
|
1417
|
+
channel.sustainNotes = [];
|
|
1402
1418
|
return promises;
|
|
1403
1419
|
}
|
|
1404
|
-
releaseSostenutoPedal(channelNumber, halfVelocity) {
|
|
1420
|
+
releaseSostenutoPedal(channelNumber, halfVelocity, scheduleTime) {
|
|
1405
1421
|
const velocity = halfVelocity * 2;
|
|
1406
1422
|
const channel = this.channels[channelNumber];
|
|
1407
1423
|
const promises = [];
|
|
1408
1424
|
channel.state.sostenutoPedal = 0;
|
|
1409
|
-
channel.sostenutoNotes.forEach((
|
|
1410
|
-
const
|
|
1411
|
-
const promise = this.noteOff(channelNumber, noteNumber, velocity);
|
|
1425
|
+
channel.sostenutoNotes.forEach((note) => {
|
|
1426
|
+
const promise = this.noteOff(channelNumber, note.noteNumber, velocity, scheduleTime);
|
|
1412
1427
|
promises.push(promise);
|
|
1413
1428
|
});
|
|
1414
1429
|
channel.sostenutoNotes.clear();
|
|
1415
1430
|
return promises;
|
|
1416
1431
|
}
|
|
1417
1432
|
handleMIDIMessage(statusByte, data1, data2, scheduleTime) {
|
|
1418
|
-
const channelNumber =
|
|
1433
|
+
const channelNumber = statusByte & 0x0F;
|
|
1419
1434
|
const messageType = statusByte & 0xF0;
|
|
1420
1435
|
switch (messageType) {
|
|
1421
1436
|
case 0x80:
|
|
@@ -1438,9 +1453,21 @@ class MidyGM2 {
|
|
|
1438
1453
|
const channel = this.channels[channelNumber];
|
|
1439
1454
|
channel.bank = channel.bankMSB * 128 + channel.bankLSB;
|
|
1440
1455
|
channel.program = program;
|
|
1456
|
+
if (this.mode === "GM2") {
|
|
1457
|
+
switch (channel.bankMSB) {
|
|
1458
|
+
case 120:
|
|
1459
|
+
channel.isDrum = true;
|
|
1460
|
+
break;
|
|
1461
|
+
case 121:
|
|
1462
|
+
channel.isDrum = false;
|
|
1463
|
+
break;
|
|
1464
|
+
}
|
|
1465
|
+
}
|
|
1441
1466
|
}
|
|
1442
1467
|
handleChannelPressure(channelNumber, value, scheduleTime) {
|
|
1443
1468
|
const channel = this.channels[channelNumber];
|
|
1469
|
+
if (channel.isDrum)
|
|
1470
|
+
return;
|
|
1444
1471
|
const prev = channel.state.channelPressure;
|
|
1445
1472
|
const next = value / 127;
|
|
1446
1473
|
channel.state.channelPressure = next;
|
|
@@ -1459,8 +1486,10 @@ class MidyGM2 {
|
|
|
1459
1486
|
this.setPitchBend(channelNumber, pitchBend, scheduleTime);
|
|
1460
1487
|
}
|
|
1461
1488
|
setPitchBend(channelNumber, value, scheduleTime) {
|
|
1462
|
-
scheduleTime ??= this.audioContext.currentTime;
|
|
1463
1489
|
const channel = this.channels[channelNumber];
|
|
1490
|
+
if (channel.isDrum)
|
|
1491
|
+
return;
|
|
1492
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1464
1493
|
const state = channel.state;
|
|
1465
1494
|
const prev = state.pitchWheel * 2 - 1;
|
|
1466
1495
|
const next = (value - 8192) / 8192;
|
|
@@ -1630,53 +1659,48 @@ class MidyGM2 {
|
|
|
1630
1659
|
return state;
|
|
1631
1660
|
}
|
|
1632
1661
|
applyVoiceParams(channel, controllerType, scheduleTime) {
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1662
|
+
this.processScheduledNotes(channel, (note) => {
|
|
1663
|
+
const controllerState = this.getControllerState(channel, note.noteNumber, note.velocity);
|
|
1664
|
+
const voiceParams = note.voice.getParams(controllerType, controllerState);
|
|
1665
|
+
let appliedFilterEnvelope = false;
|
|
1666
|
+
let appliedVolumeEnvelope = false;
|
|
1667
|
+
for (const [key, value] of Object.entries(voiceParams)) {
|
|
1668
|
+
const prevValue = note.voiceParams[key];
|
|
1669
|
+
if (value === prevValue)
|
|
1637
1670
|
continue;
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
if (value === prevValue)
|
|
1671
|
+
note.voiceParams[key] = value;
|
|
1672
|
+
if (key in this.voiceParamsHandlers) {
|
|
1673
|
+
this.voiceParamsHandlers[key](channel, note, prevValue, scheduleTime);
|
|
1674
|
+
}
|
|
1675
|
+
else if (filterEnvelopeKeySet.has(key)) {
|
|
1676
|
+
if (appliedFilterEnvelope)
|
|
1645
1677
|
continue;
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1678
|
+
appliedFilterEnvelope = true;
|
|
1679
|
+
const noteVoiceParams = note.voiceParams;
|
|
1680
|
+
for (let i = 0; i < filterEnvelopeKeys.length; i++) {
|
|
1681
|
+
const key = filterEnvelopeKeys[i];
|
|
1682
|
+
if (key in voiceParams)
|
|
1683
|
+
noteVoiceParams[key] = voiceParams[key];
|
|
1649
1684
|
}
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
continue;
|
|
1653
|
-
appliedFilterEnvelope = true;
|
|
1654
|
-
const noteVoiceParams = note.voiceParams;
|
|
1655
|
-
for (let i = 0; i < filterEnvelopeKeys.length; i++) {
|
|
1656
|
-
const key = filterEnvelopeKeys[i];
|
|
1657
|
-
if (key in voiceParams)
|
|
1658
|
-
noteVoiceParams[key] = voiceParams[key];
|
|
1659
|
-
}
|
|
1660
|
-
if (note.portamento) {
|
|
1661
|
-
this.setPortamentoStartFilterEnvelope(channel, note, scheduleTime);
|
|
1662
|
-
}
|
|
1663
|
-
else {
|
|
1664
|
-
this.setFilterEnvelope(channel, note, scheduleTime);
|
|
1665
|
-
}
|
|
1666
|
-
this.setPitchEnvelope(note, scheduleTime);
|
|
1685
|
+
if (note.portamento) {
|
|
1686
|
+
this.setPortamentoStartFilterEnvelope(channel, note, scheduleTime);
|
|
1667
1687
|
}
|
|
1668
|
-
else
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1688
|
+
else {
|
|
1689
|
+
this.setFilterEnvelope(channel, note, scheduleTime);
|
|
1690
|
+
}
|
|
1691
|
+
this.setPitchEnvelope(note, scheduleTime);
|
|
1692
|
+
}
|
|
1693
|
+
else if (volumeEnvelopeKeySet.has(key)) {
|
|
1694
|
+
if (appliedVolumeEnvelope)
|
|
1695
|
+
continue;
|
|
1696
|
+
appliedVolumeEnvelope = true;
|
|
1697
|
+
const noteVoiceParams = note.voiceParams;
|
|
1698
|
+
for (let i = 0; i < volumeEnvelopeKeys.length; i++) {
|
|
1699
|
+
const key = volumeEnvelopeKeys[i];
|
|
1700
|
+
if (key in voiceParams)
|
|
1701
|
+
noteVoiceParams[key] = voiceParams[key];
|
|
1679
1702
|
}
|
|
1703
|
+
this.setVolumeEnvelope(channel, note, scheduleTime);
|
|
1680
1704
|
}
|
|
1681
1705
|
}
|
|
1682
1706
|
});
|
|
@@ -1725,9 +1749,8 @@ class MidyGM2 {
|
|
|
1725
1749
|
this.channels[channelNumber].bankMSB = msb;
|
|
1726
1750
|
}
|
|
1727
1751
|
updateModulation(channel, scheduleTime) {
|
|
1728
|
-
scheduleTime ??= this.audioContext.currentTime;
|
|
1729
1752
|
const depth = channel.state.modulationDepth * channel.modulationDepthRange;
|
|
1730
|
-
this.processScheduledNotes(channel,
|
|
1753
|
+
this.processScheduledNotes(channel, (note) => {
|
|
1731
1754
|
if (note.modulationDepth) {
|
|
1732
1755
|
note.modulationDepth.gain.setValueAtTime(depth, scheduleTime);
|
|
1733
1756
|
}
|
|
@@ -1739,17 +1762,18 @@ class MidyGM2 {
|
|
|
1739
1762
|
}
|
|
1740
1763
|
setModulationDepth(channelNumber, modulation, scheduleTime) {
|
|
1741
1764
|
const channel = this.channels[channelNumber];
|
|
1765
|
+
if (channel.isDrum)
|
|
1766
|
+
return;
|
|
1767
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1742
1768
|
channel.state.modulationDepth = modulation / 127;
|
|
1743
1769
|
this.updateModulation(channel, scheduleTime);
|
|
1744
1770
|
}
|
|
1745
1771
|
setPortamentoTime(channelNumber, portamentoTime) {
|
|
1746
1772
|
const channel = this.channels[channelNumber];
|
|
1747
|
-
|
|
1748
|
-
channel.state.portamentoTime = Math.exp(factor * portamentoTime);
|
|
1773
|
+
channel.state.portamentoTime = portamentoTime / 127;
|
|
1749
1774
|
}
|
|
1750
1775
|
setKeyBasedVolume(channel, scheduleTime) {
|
|
1751
|
-
|
|
1752
|
-
this.processScheduledNotes(channel, scheduleTime, (note) => {
|
|
1776
|
+
this.processScheduledNotes(channel, (note) => {
|
|
1753
1777
|
const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 7);
|
|
1754
1778
|
if (keyBasedValue !== 0) {
|
|
1755
1779
|
note.volumeNode.gain
|
|
@@ -1759,6 +1783,7 @@ class MidyGM2 {
|
|
|
1759
1783
|
});
|
|
1760
1784
|
}
|
|
1761
1785
|
setVolume(channelNumber, volume, scheduleTime) {
|
|
1786
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1762
1787
|
const channel = this.channels[channelNumber];
|
|
1763
1788
|
channel.state.volume = volume / 127;
|
|
1764
1789
|
this.updateChannelVolume(channel, scheduleTime);
|
|
@@ -1772,8 +1797,7 @@ class MidyGM2 {
|
|
|
1772
1797
|
};
|
|
1773
1798
|
}
|
|
1774
1799
|
setKeyBasedPan(channel, scheduleTime) {
|
|
1775
|
-
|
|
1776
|
-
this.processScheduledNotes(channel, scheduleTime, (note) => {
|
|
1800
|
+
this.processScheduledNotes(channel, (note) => {
|
|
1777
1801
|
const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 10);
|
|
1778
1802
|
if (keyBasedValue !== 0) {
|
|
1779
1803
|
const { gainLeft, gainRight } = this.panToGain((keyBasedValue + 1) / 2);
|
|
@@ -1787,12 +1811,14 @@ class MidyGM2 {
|
|
|
1787
1811
|
});
|
|
1788
1812
|
}
|
|
1789
1813
|
setPan(channelNumber, pan, scheduleTime) {
|
|
1814
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1790
1815
|
const channel = this.channels[channelNumber];
|
|
1791
1816
|
channel.state.pan = pan / 127;
|
|
1792
1817
|
this.updateChannelVolume(channel, scheduleTime);
|
|
1793
1818
|
this.setKeyBasedPan(channel, scheduleTime);
|
|
1794
1819
|
}
|
|
1795
1820
|
setExpression(channelNumber, expression, scheduleTime) {
|
|
1821
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1796
1822
|
const channel = this.channels[channelNumber];
|
|
1797
1823
|
channel.state.expression = expression / 127;
|
|
1798
1824
|
this.updateChannelVolume(channel, scheduleTime);
|
|
@@ -1816,30 +1842,58 @@ class MidyGM2 {
|
|
|
1816
1842
|
.setValueAtTime(volume * gainRight, scheduleTime);
|
|
1817
1843
|
}
|
|
1818
1844
|
setSustainPedal(channelNumber, value, scheduleTime) {
|
|
1845
|
+
const channel = this.channels[channelNumber];
|
|
1846
|
+
if (channel.isDrum)
|
|
1847
|
+
return;
|
|
1819
1848
|
scheduleTime ??= this.audioContext.currentTime;
|
|
1820
|
-
|
|
1821
|
-
if (
|
|
1849
|
+
channel.state.sustainPedal = value / 127;
|
|
1850
|
+
if (64 <= value) {
|
|
1851
|
+
this.processScheduledNotes(channel, (note) => {
|
|
1852
|
+
channel.sustainNotes.push(note);
|
|
1853
|
+
});
|
|
1854
|
+
}
|
|
1855
|
+
else {
|
|
1822
1856
|
this.releaseSustainPedal(channelNumber, value, scheduleTime);
|
|
1823
1857
|
}
|
|
1824
1858
|
}
|
|
1825
1859
|
setPortamento(channelNumber, value) {
|
|
1826
|
-
this.channels[channelNumber]
|
|
1860
|
+
const channel = this.channels[channelNumber];
|
|
1861
|
+
if (channel.isDrum)
|
|
1862
|
+
return;
|
|
1863
|
+
channel.state.portamento = value / 127;
|
|
1827
1864
|
}
|
|
1828
1865
|
setSostenutoPedal(channelNumber, value, scheduleTime) {
|
|
1829
1866
|
const channel = this.channels[channelNumber];
|
|
1867
|
+
if (channel.isDrum)
|
|
1868
|
+
return;
|
|
1869
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1830
1870
|
channel.state.sostenutoPedal = value / 127;
|
|
1831
1871
|
if (64 <= value) {
|
|
1832
1872
|
channel.sostenutoNotes = this.getActiveNotes(channel, scheduleTime);
|
|
1833
1873
|
}
|
|
1834
1874
|
else {
|
|
1835
|
-
this.releaseSostenutoPedal(channelNumber, value);
|
|
1875
|
+
this.releaseSostenutoPedal(channelNumber, value, scheduleTime);
|
|
1836
1876
|
}
|
|
1837
1877
|
}
|
|
1838
|
-
setSoftPedal(channelNumber, softPedal,
|
|
1878
|
+
setSoftPedal(channelNumber, softPedal, scheduleTime) {
|
|
1839
1879
|
const channel = this.channels[channelNumber];
|
|
1880
|
+
if (channel.isDrum)
|
|
1881
|
+
return;
|
|
1882
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1840
1883
|
channel.state.softPedal = softPedal / 127;
|
|
1884
|
+
this.processScheduledNotes(channel, (note) => {
|
|
1885
|
+
if (note.portamento) {
|
|
1886
|
+
this.setPortamentoStartVolumeEnvelope(channel, note, scheduleTime);
|
|
1887
|
+
this.setPortamentoStartFilterEnvelope(channel, note, scheduleTime);
|
|
1888
|
+
}
|
|
1889
|
+
else {
|
|
1890
|
+
this.setVolumeEnvelope(channel, note, scheduleTime);
|
|
1891
|
+
this.setFilterEnvelope(channel, note, scheduleTime);
|
|
1892
|
+
}
|
|
1893
|
+
});
|
|
1841
1894
|
}
|
|
1842
1895
|
setReverbSendLevel(channelNumber, reverbSendLevel, scheduleTime) {
|
|
1896
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1843
1897
|
const channel = this.channels[channelNumber];
|
|
1844
1898
|
const state = channel.state;
|
|
1845
1899
|
const reverbEffect = this.reverbEffect;
|
|
@@ -1851,27 +1905,17 @@ class MidyGM2 {
|
|
|
1851
1905
|
.setValueAtTime(state.reverbSendLevel, scheduleTime);
|
|
1852
1906
|
}
|
|
1853
1907
|
else {
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
continue;
|
|
1859
|
-
if (note.voiceParams.reverbEffectsSend <= 0)
|
|
1860
|
-
continue;
|
|
1861
|
-
note.reverbEffectsSend.disconnect();
|
|
1862
|
-
}
|
|
1908
|
+
this.processScheduledNotes(channel, (note) => {
|
|
1909
|
+
if (note.voiceParams.reverbEffectsSend <= 0)
|
|
1910
|
+
return false;
|
|
1911
|
+
note.reverbEffectsSend.disconnect();
|
|
1863
1912
|
});
|
|
1864
1913
|
}
|
|
1865
1914
|
}
|
|
1866
1915
|
else {
|
|
1867
1916
|
if (0 < reverbSendLevel) {
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
const note = noteList[i];
|
|
1871
|
-
if (!note)
|
|
1872
|
-
continue;
|
|
1873
|
-
this.setReverbEffectsSend(channel, note, 0, scheduleTime);
|
|
1874
|
-
}
|
|
1917
|
+
this.processScheduledNotes(channel, (note) => {
|
|
1918
|
+
this.setReverbEffectsSend(channel, note, 0, scheduleTime);
|
|
1875
1919
|
});
|
|
1876
1920
|
state.reverbSendLevel = reverbSendLevel / 127;
|
|
1877
1921
|
reverbEffect.input.gain
|
|
@@ -1881,6 +1925,7 @@ class MidyGM2 {
|
|
|
1881
1925
|
}
|
|
1882
1926
|
}
|
|
1883
1927
|
setChorusSendLevel(channelNumber, chorusSendLevel, scheduleTime) {
|
|
1928
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1884
1929
|
const channel = this.channels[channelNumber];
|
|
1885
1930
|
const state = channel.state;
|
|
1886
1931
|
const chorusEffect = this.chorusEffect;
|
|
@@ -1892,27 +1937,17 @@ class MidyGM2 {
|
|
|
1892
1937
|
.setValueAtTime(state.chorusSendLevel, scheduleTime);
|
|
1893
1938
|
}
|
|
1894
1939
|
else {
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
continue;
|
|
1900
|
-
if (note.voiceParams.chorusEffectsSend <= 0)
|
|
1901
|
-
continue;
|
|
1902
|
-
note.chorusEffectsSend.disconnect();
|
|
1903
|
-
}
|
|
1940
|
+
this.processScheduledNotes(channel, (note) => {
|
|
1941
|
+
if (note.voiceParams.chorusEffectsSend <= 0)
|
|
1942
|
+
return false;
|
|
1943
|
+
note.chorusEffectsSend.disconnect();
|
|
1904
1944
|
});
|
|
1905
1945
|
}
|
|
1906
1946
|
}
|
|
1907
1947
|
else {
|
|
1908
1948
|
if (0 < chorusSendLevel) {
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
const note = noteList[i];
|
|
1912
|
-
if (!note)
|
|
1913
|
-
continue;
|
|
1914
|
-
this.setChorusEffectsSend(channel, note, 0, scheduleTime);
|
|
1915
|
-
}
|
|
1949
|
+
this.processScheduledNotes(channel, (note) => {
|
|
1950
|
+
this.setChorusEffectsSend(channel, note, 0, scheduleTime);
|
|
1916
1951
|
});
|
|
1917
1952
|
state.chorusSendLevel = chorusSendLevel / 127;
|
|
1918
1953
|
chorusEffect.input.gain
|
|
@@ -1955,13 +1990,13 @@ class MidyGM2 {
|
|
|
1955
1990
|
this.handlePitchBendRangeRPN(channelNumber, scheduleTime);
|
|
1956
1991
|
break;
|
|
1957
1992
|
case 1:
|
|
1958
|
-
this.handleFineTuningRPN(channelNumber);
|
|
1993
|
+
this.handleFineTuningRPN(channelNumber, scheduleTime);
|
|
1959
1994
|
break;
|
|
1960
1995
|
case 2:
|
|
1961
|
-
this.handleCoarseTuningRPN(channelNumber);
|
|
1996
|
+
this.handleCoarseTuningRPN(channelNumber, scheduleTime);
|
|
1962
1997
|
break;
|
|
1963
1998
|
case 5:
|
|
1964
|
-
this.handleModulationDepthRangeRPN(channelNumber);
|
|
1999
|
+
this.handleModulationDepthRangeRPN(channelNumber, scheduleTime);
|
|
1965
2000
|
break;
|
|
1966
2001
|
default:
|
|
1967
2002
|
console.warn(`Channel ${channelNumber}: Unsupported RPN MSB=${channel.rpnMSB} LSB=${channel.rpnLSB}`);
|
|
@@ -1984,8 +2019,10 @@ class MidyGM2 {
|
|
|
1984
2019
|
this.setPitchBendRange(channelNumber, pitchBendRange, scheduleTime);
|
|
1985
2020
|
}
|
|
1986
2021
|
setPitchBendRange(channelNumber, value, scheduleTime) {
|
|
1987
|
-
scheduleTime ??= this.audioContext.currentTime;
|
|
1988
2022
|
const channel = this.channels[channelNumber];
|
|
2023
|
+
if (channel.isDrum)
|
|
2024
|
+
return;
|
|
2025
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1989
2026
|
const state = channel.state;
|
|
1990
2027
|
const prev = state.pitchWheelSensitivity;
|
|
1991
2028
|
const next = value / 128;
|
|
@@ -1994,44 +2031,53 @@ class MidyGM2 {
|
|
|
1994
2031
|
this.updateChannelDetune(channel, scheduleTime);
|
|
1995
2032
|
this.applyVoiceParams(channel, 16, scheduleTime);
|
|
1996
2033
|
}
|
|
1997
|
-
handleFineTuningRPN(channelNumber) {
|
|
2034
|
+
handleFineTuningRPN(channelNumber, scheduleTime) {
|
|
1998
2035
|
const channel = this.channels[channelNumber];
|
|
1999
2036
|
this.limitData(channel, 0, 127, 0, 127);
|
|
2000
2037
|
const fineTuning = channel.dataMSB * 128 + channel.dataLSB;
|
|
2001
|
-
this.setFineTuning(channelNumber, fineTuning);
|
|
2038
|
+
this.setFineTuning(channelNumber, fineTuning, scheduleTime);
|
|
2002
2039
|
}
|
|
2003
|
-
setFineTuning(channelNumber, value) {
|
|
2040
|
+
setFineTuning(channelNumber, value, scheduleTime) {
|
|
2004
2041
|
const channel = this.channels[channelNumber];
|
|
2042
|
+
if (channel.isDrum)
|
|
2043
|
+
return;
|
|
2044
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
2005
2045
|
const prev = channel.fineTuning;
|
|
2006
2046
|
const next = (value - 8192) / 8.192; // cent
|
|
2007
2047
|
channel.fineTuning = next;
|
|
2008
2048
|
channel.detune += next - prev;
|
|
2009
|
-
this.updateChannelDetune(channel);
|
|
2049
|
+
this.updateChannelDetune(channel, scheduleTime);
|
|
2010
2050
|
}
|
|
2011
|
-
handleCoarseTuningRPN(channelNumber) {
|
|
2051
|
+
handleCoarseTuningRPN(channelNumber, scheduleTime) {
|
|
2012
2052
|
const channel = this.channels[channelNumber];
|
|
2013
2053
|
this.limitDataMSB(channel, 0, 127);
|
|
2014
2054
|
const coarseTuning = channel.dataMSB;
|
|
2015
|
-
this.setCoarseTuning(channelNumber, coarseTuning);
|
|
2055
|
+
this.setCoarseTuning(channelNumber, coarseTuning, scheduleTime);
|
|
2016
2056
|
}
|
|
2017
|
-
setCoarseTuning(channelNumber, value) {
|
|
2057
|
+
setCoarseTuning(channelNumber, value, scheduleTime) {
|
|
2018
2058
|
const channel = this.channels[channelNumber];
|
|
2059
|
+
if (channel.isDrum)
|
|
2060
|
+
return;
|
|
2061
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
2019
2062
|
const prev = channel.coarseTuning;
|
|
2020
2063
|
const next = (value - 64) * 100; // cent
|
|
2021
2064
|
channel.coarseTuning = next;
|
|
2022
2065
|
channel.detune += next - prev;
|
|
2023
|
-
this.updateChannelDetune(channel);
|
|
2066
|
+
this.updateChannelDetune(channel, scheduleTime);
|
|
2024
2067
|
}
|
|
2025
|
-
handleModulationDepthRangeRPN(channelNumber) {
|
|
2068
|
+
handleModulationDepthRangeRPN(channelNumber, scheduleTime) {
|
|
2026
2069
|
const channel = this.channels[channelNumber];
|
|
2027
2070
|
this.limitData(channel, 0, 127, 0, 127);
|
|
2028
2071
|
const modulationDepthRange = (dataMSB + dataLSB / 128) * 100;
|
|
2029
|
-
this.setModulationDepthRange(channelNumber, modulationDepthRange);
|
|
2072
|
+
this.setModulationDepthRange(channelNumber, modulationDepthRange, scheduleTime);
|
|
2030
2073
|
}
|
|
2031
|
-
setModulationDepthRange(channelNumber, modulationDepthRange) {
|
|
2074
|
+
setModulationDepthRange(channelNumber, modulationDepthRange, scheduleTime) {
|
|
2032
2075
|
const channel = this.channels[channelNumber];
|
|
2076
|
+
if (channel.isDrum)
|
|
2077
|
+
return;
|
|
2078
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
2033
2079
|
channel.modulationDepthRange = modulationDepthRange;
|
|
2034
|
-
this.updateModulation(channel);
|
|
2080
|
+
this.updateModulation(channel, scheduleTime);
|
|
2035
2081
|
}
|
|
2036
2082
|
allSoundOff(channelNumber, _value, scheduleTime) {
|
|
2037
2083
|
scheduleTime ??= this.audioContext.currentTime;
|
|
@@ -2067,17 +2113,21 @@ class MidyGM2 {
|
|
|
2067
2113
|
scheduleTime ??= this.audioContext.currentTime;
|
|
2068
2114
|
return this.stopChannelNotes(channelNumber, 0, false, scheduleTime);
|
|
2069
2115
|
}
|
|
2070
|
-
omniOff() {
|
|
2071
|
-
this.
|
|
2116
|
+
omniOff(channelNumber, value, scheduleTime) {
|
|
2117
|
+
this.allNotesOff(channelNumber, value, scheduleTime);
|
|
2072
2118
|
}
|
|
2073
|
-
omniOn() {
|
|
2074
|
-
this.
|
|
2119
|
+
omniOn(channelNumber, value, scheduleTime) {
|
|
2120
|
+
this.allNotesOff(channelNumber, value, scheduleTime);
|
|
2075
2121
|
}
|
|
2076
|
-
monoOn() {
|
|
2077
|
-
|
|
2122
|
+
monoOn(channelNumber, value, scheduleTime) {
|
|
2123
|
+
const channel = this.channels[channelNumber];
|
|
2124
|
+
this.allNotesOff(channelNumber, value, scheduleTime);
|
|
2125
|
+
channel.mono = true;
|
|
2078
2126
|
}
|
|
2079
|
-
polyOn() {
|
|
2080
|
-
|
|
2127
|
+
polyOn(channelNumber, value, scheduleTime) {
|
|
2128
|
+
const channel = this.channels[channelNumber];
|
|
2129
|
+
this.allNotesOff(channelNumber, value, scheduleTime);
|
|
2130
|
+
channel.mono = false;
|
|
2081
2131
|
}
|
|
2082
2132
|
handleUniversalNonRealTimeExclusiveMessage(data, scheduleTime) {
|
|
2083
2133
|
switch (data[2]) {
|
|
@@ -2093,12 +2143,12 @@ class MidyGM2 {
|
|
|
2093
2143
|
case 9:
|
|
2094
2144
|
switch (data[3]) {
|
|
2095
2145
|
case 1:
|
|
2096
|
-
this.GM1SystemOn();
|
|
2146
|
+
this.GM1SystemOn(scheduleTime);
|
|
2097
2147
|
break;
|
|
2098
2148
|
case 2: // GM System Off
|
|
2099
2149
|
break;
|
|
2100
2150
|
case 3:
|
|
2101
|
-
this.GM2SystemOn();
|
|
2151
|
+
this.GM2SystemOn(scheduleTime);
|
|
2102
2152
|
break;
|
|
2103
2153
|
default:
|
|
2104
2154
|
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
@@ -2108,25 +2158,35 @@ class MidyGM2 {
|
|
|
2108
2158
|
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
2109
2159
|
}
|
|
2110
2160
|
}
|
|
2111
|
-
GM1SystemOn() {
|
|
2161
|
+
GM1SystemOn(scheduleTime) {
|
|
2162
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
2163
|
+
this.mode = "GM1";
|
|
2112
2164
|
for (let i = 0; i < this.channels.length; i++) {
|
|
2165
|
+
this.allSoundOff(i, 0, scheduleTime);
|
|
2113
2166
|
const channel = this.channels[i];
|
|
2114
2167
|
channel.bankMSB = 0;
|
|
2115
2168
|
channel.bankLSB = 0;
|
|
2116
2169
|
channel.bank = 0;
|
|
2170
|
+
channel.isDrum = false;
|
|
2117
2171
|
}
|
|
2118
2172
|
this.channels[9].bankMSB = 1;
|
|
2119
2173
|
this.channels[9].bank = 128;
|
|
2174
|
+
this.channels[9].isDrum = true;
|
|
2120
2175
|
}
|
|
2121
|
-
GM2SystemOn() {
|
|
2176
|
+
GM2SystemOn(scheduleTime) {
|
|
2177
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
2178
|
+
this.mode = "GM2";
|
|
2122
2179
|
for (let i = 0; i < this.channels.length; i++) {
|
|
2180
|
+
this.allSoundOff(i, 0, scheduleTime);
|
|
2123
2181
|
const channel = this.channels[i];
|
|
2124
2182
|
channel.bankMSB = 121;
|
|
2125
2183
|
channel.bankLSB = 0;
|
|
2126
2184
|
channel.bank = 121 * 128;
|
|
2185
|
+
channel.isDrum = false;
|
|
2127
2186
|
}
|
|
2128
2187
|
this.channels[9].bankMSB = 120;
|
|
2129
2188
|
this.channels[9].bank = 120 * 128;
|
|
2189
|
+
this.channels[9].isDrum = true;
|
|
2130
2190
|
}
|
|
2131
2191
|
handleUniversalRealTimeExclusiveMessage(data, scheduleTime) {
|
|
2132
2192
|
switch (data[2]) {
|
|
@@ -2189,8 +2249,14 @@ class MidyGM2 {
|
|
|
2189
2249
|
const prev = this.masterFineTuning;
|
|
2190
2250
|
const next = (value - 8192) / 8.192; // cent
|
|
2191
2251
|
this.masterFineTuning = next;
|
|
2192
|
-
|
|
2193
|
-
this.
|
|
2252
|
+
const detuneChange = next - prev;
|
|
2253
|
+
for (let i = 0; i < this.channels.length; i++) {
|
|
2254
|
+
const channel = this.channels[i];
|
|
2255
|
+
if (channel.isDrum)
|
|
2256
|
+
continue;
|
|
2257
|
+
channel.detune += detuneChange;
|
|
2258
|
+
this.updateChannelDetune(channel, scheduleTime);
|
|
2259
|
+
}
|
|
2194
2260
|
}
|
|
2195
2261
|
handleMasterCoarseTuningSysEx(data, scheduleTime) {
|
|
2196
2262
|
const coarseTuning = data[4];
|
|
@@ -2200,8 +2266,14 @@ class MidyGM2 {
|
|
|
2200
2266
|
const prev = this.masterCoarseTuning;
|
|
2201
2267
|
const next = (value - 64) * 100; // cent
|
|
2202
2268
|
this.masterCoarseTuning = next;
|
|
2203
|
-
|
|
2204
|
-
this.
|
|
2269
|
+
const detuneChange = next - prev;
|
|
2270
|
+
for (let i = 0; i < this.channels.length; i++) {
|
|
2271
|
+
const channel = this.channels[i];
|
|
2272
|
+
if (channel.isDrum)
|
|
2273
|
+
continue;
|
|
2274
|
+
channel.detune += detuneChange;
|
|
2275
|
+
this.updateChannelDetune(channel, scheduleTime);
|
|
2276
|
+
}
|
|
2205
2277
|
}
|
|
2206
2278
|
handleGlobalParameterControlSysEx(data, scheduleTime) {
|
|
2207
2279
|
if (data[7] === 1) {
|
|
@@ -2486,13 +2558,8 @@ class MidyGM2 {
|
|
|
2486
2558
|
const slotSize = 6;
|
|
2487
2559
|
const offset = controllerType * slotSize;
|
|
2488
2560
|
const table = channel.controlTable.subarray(offset, offset + slotSize);
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
const note = noteList[i];
|
|
2492
|
-
if (!note)
|
|
2493
|
-
continue;
|
|
2494
|
-
this.setControllerParameters(channel, note, table);
|
|
2495
|
-
}
|
|
2561
|
+
this.processScheduledNotes(channel, (note) => {
|
|
2562
|
+
this.setControllerParameters(channel, note, table);
|
|
2496
2563
|
});
|
|
2497
2564
|
}
|
|
2498
2565
|
handleControlChangeSysEx(data) {
|
|
@@ -2534,13 +2601,20 @@ class MidyGM2 {
|
|
|
2534
2601
|
}
|
|
2535
2602
|
scheduleTask(callback, scheduleTime) {
|
|
2536
2603
|
return new Promise((resolve) => {
|
|
2537
|
-
const bufferSource = new AudioBufferSourceNode(this.audioContext
|
|
2604
|
+
const bufferSource = new AudioBufferSourceNode(this.audioContext, {
|
|
2605
|
+
buffer: this.schedulerBuffer,
|
|
2606
|
+
});
|
|
2607
|
+
bufferSource.connect(this.scheduler);
|
|
2538
2608
|
bufferSource.onended = () => {
|
|
2539
|
-
|
|
2540
|
-
|
|
2609
|
+
try {
|
|
2610
|
+
callback();
|
|
2611
|
+
}
|
|
2612
|
+
finally {
|
|
2613
|
+
bufferSource.disconnect();
|
|
2614
|
+
resolve();
|
|
2615
|
+
}
|
|
2541
2616
|
};
|
|
2542
2617
|
bufferSource.start(scheduleTime);
|
|
2543
|
-
bufferSource.stop(scheduleTime);
|
|
2544
2618
|
});
|
|
2545
2619
|
}
|
|
2546
2620
|
}
|
|
@@ -2551,6 +2625,7 @@ Object.defineProperty(MidyGM2, "channelSettings", {
|
|
|
2551
2625
|
writable: true,
|
|
2552
2626
|
value: {
|
|
2553
2627
|
currentBufferSource: null,
|
|
2628
|
+
isDrum: false,
|
|
2554
2629
|
detune: 0,
|
|
2555
2630
|
program: 0,
|
|
2556
2631
|
bank: 121 * 128,
|
|
@@ -2560,8 +2635,9 @@ Object.defineProperty(MidyGM2, "channelSettings", {
|
|
|
2560
2635
|
dataLSB: 0,
|
|
2561
2636
|
rpnMSB: 127,
|
|
2562
2637
|
rpnLSB: 127,
|
|
2638
|
+
mono: false, // CC#124, CC#125
|
|
2639
|
+
modulationDepthRange: 50, // cent
|
|
2563
2640
|
fineTuning: 0, // cb
|
|
2564
2641
|
coarseTuning: 0, // cb
|
|
2565
|
-
modulationDepthRange: 50, // cent
|
|
2566
2642
|
}
|
|
2567
2643
|
});
|