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