@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.js
CHANGED
|
@@ -249,6 +249,12 @@ const volumeEnvelopeKeys = [
|
|
|
249
249
|
const volumeEnvelopeKeySet = new Set(volumeEnvelopeKeys);
|
|
250
250
|
class Midy {
|
|
251
251
|
constructor(audioContext, options = this.defaultOptions) {
|
|
252
|
+
Object.defineProperty(this, "mode", {
|
|
253
|
+
enumerable: true,
|
|
254
|
+
configurable: true,
|
|
255
|
+
writable: true,
|
|
256
|
+
value: "GM2"
|
|
257
|
+
});
|
|
252
258
|
Object.defineProperty(this, "ticksPerBeat", {
|
|
253
259
|
enumerable: true,
|
|
254
260
|
configurable: true,
|
|
@@ -294,18 +300,6 @@ class Midy {
|
|
|
294
300
|
delayTimes: this.generateDistributedArray(0.02, 2, 0.5),
|
|
295
301
|
}
|
|
296
302
|
});
|
|
297
|
-
Object.defineProperty(this, "mono", {
|
|
298
|
-
enumerable: true,
|
|
299
|
-
configurable: true,
|
|
300
|
-
writable: true,
|
|
301
|
-
value: false
|
|
302
|
-
}); // CC#124, CC#125
|
|
303
|
-
Object.defineProperty(this, "omni", {
|
|
304
|
-
enumerable: true,
|
|
305
|
-
configurable: true,
|
|
306
|
-
writable: true,
|
|
307
|
-
value: false
|
|
308
|
-
}); // CC#126, CC#127
|
|
309
303
|
Object.defineProperty(this, "noteCheckInterval", {
|
|
310
304
|
enumerable: true,
|
|
311
305
|
configurable: true,
|
|
@@ -439,6 +433,11 @@ class Midy {
|
|
|
439
433
|
this.audioContext = audioContext;
|
|
440
434
|
this.options = { ...this.defaultOptions, ...options };
|
|
441
435
|
this.masterVolume = new GainNode(audioContext);
|
|
436
|
+
this.scheduler = new GainNode(audioContext, { gain: 0 });
|
|
437
|
+
this.schedulerBuffer = new AudioBuffer({
|
|
438
|
+
length: 1,
|
|
439
|
+
sampleRate: audioContext.sampleRate,
|
|
440
|
+
});
|
|
442
441
|
this.voiceParamsHandlers = this.createVoiceParamsHandlers();
|
|
443
442
|
this.controlChangeHandlers = this.createControlChangeHandlers();
|
|
444
443
|
this.channels = this.createChannels(audioContext);
|
|
@@ -447,6 +446,7 @@ class Midy {
|
|
|
447
446
|
this.chorusEffect.output.connect(this.masterVolume);
|
|
448
447
|
this.reverbEffect.output.connect(this.masterVolume);
|
|
449
448
|
this.masterVolume.connect(audioContext.destination);
|
|
449
|
+
this.scheduler.connect(audioContext.destination);
|
|
450
450
|
this.GM2SystemOn();
|
|
451
451
|
}
|
|
452
452
|
initSoundFontTable() {
|
|
@@ -507,6 +507,7 @@ class Midy {
|
|
|
507
507
|
controlTable: this.initControlTable(),
|
|
508
508
|
...this.setChannelAudioNodes(audioContext),
|
|
509
509
|
scheduledNotes: new SparseMap(128),
|
|
510
|
+
sustainNotes: [],
|
|
510
511
|
sostenutoNotes: new SparseMap(128),
|
|
511
512
|
scaleOctaveTuningTable: new Float32Array(12), // [-100, 100] cent
|
|
512
513
|
channelPressureTable: new Uint8Array([64, 64, 64, 0, 0, 0]),
|
|
@@ -545,10 +546,24 @@ class Midy {
|
|
|
545
546
|
return audioBuffer;
|
|
546
547
|
}
|
|
547
548
|
}
|
|
548
|
-
|
|
549
|
+
calcLoopMode(channel, note, voiceParams) {
|
|
550
|
+
if (channel.isDrum) {
|
|
551
|
+
const noteNumber = note.noteNumber;
|
|
552
|
+
if (noteNumber === 88 || 47 <= noteNumber && noteNumber <= 84) {
|
|
553
|
+
return true;
|
|
554
|
+
}
|
|
555
|
+
else {
|
|
556
|
+
return false;
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
else {
|
|
560
|
+
return voiceParams.sampleModes % 2 !== 0;
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
createBufferSource(channel, note, voiceParams, audioBuffer) {
|
|
549
564
|
const bufferSource = new AudioBufferSourceNode(this.audioContext);
|
|
550
565
|
bufferSource.buffer = audioBuffer;
|
|
551
|
-
bufferSource.loop =
|
|
566
|
+
bufferSource.loop = this.calcLoopMode(channel, note, voiceParams);
|
|
552
567
|
if (bufferSource.loop) {
|
|
553
568
|
bufferSource.loopStart = voiceParams.loopStart / voiceParams.sampleRate;
|
|
554
569
|
bufferSource.loopEnd = voiceParams.loopEnd / voiceParams.sampleRate;
|
|
@@ -590,7 +605,7 @@ class Midy {
|
|
|
590
605
|
const portamentoTarget = this.findPortamentoTarget(queueIndex);
|
|
591
606
|
if (portamentoTarget)
|
|
592
607
|
portamentoTarget.portamento = true;
|
|
593
|
-
const notePromise = this.scheduleNoteOff(
|
|
608
|
+
const notePromise = this.scheduleNoteOff(event.channel, event.noteNumber, event.velocity, startTime, false, // force
|
|
594
609
|
portamentoTarget?.noteNumber);
|
|
595
610
|
if (notePromise) {
|
|
596
611
|
this.notePromises.push(notePromise);
|
|
@@ -601,7 +616,7 @@ class Midy {
|
|
|
601
616
|
this.handlePolyphonicKeyPressure(event.channel, event.noteNumber, event.amount, startTime);
|
|
602
617
|
break;
|
|
603
618
|
case "controller":
|
|
604
|
-
this.handleControlChange(
|
|
619
|
+
this.handleControlChange(event.channel, event.controllerType, event.value, startTime);
|
|
605
620
|
break;
|
|
606
621
|
case "programChange":
|
|
607
622
|
this.handleProgramChange(event.channel, event.programNumber, startTime);
|
|
@@ -797,15 +812,10 @@ class Midy {
|
|
|
797
812
|
stopChannelNotes(channelNumber, velocity, force, scheduleTime) {
|
|
798
813
|
const channel = this.channels[channelNumber];
|
|
799
814
|
const promises = [];
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
continue;
|
|
805
|
-
const promise = this.scheduleNoteOff(channelNumber, note.noteNumber, velocity, scheduleTime, force, undefined);
|
|
806
|
-
this.notePromises.push(promise);
|
|
807
|
-
promises.push(promise);
|
|
808
|
-
}
|
|
815
|
+
this.processScheduledNotes(channel, (note) => {
|
|
816
|
+
const promise = this.scheduleNoteOff(channelNumber, note.noteNumber, velocity, scheduleTime, force, undefined);
|
|
817
|
+
this.notePromises.push(promise);
|
|
818
|
+
promises.push(promise);
|
|
809
819
|
});
|
|
810
820
|
channel.scheduledNotes.clear();
|
|
811
821
|
return Promise.all(promises);
|
|
@@ -861,14 +871,12 @@ class Midy {
|
|
|
861
871
|
const now = this.audioContext.currentTime;
|
|
862
872
|
return this.resumeTime + now - this.startTime - this.startDelay;
|
|
863
873
|
}
|
|
864
|
-
processScheduledNotes(channel,
|
|
874
|
+
processScheduledNotes(channel, callback) {
|
|
865
875
|
channel.scheduledNotes.forEach((noteList) => {
|
|
866
876
|
for (let i = 0; i < noteList.length; i++) {
|
|
867
877
|
const note = noteList[i];
|
|
868
878
|
if (!note)
|
|
869
879
|
continue;
|
|
870
|
-
if (scheduleTime < note.startTime)
|
|
871
|
-
continue;
|
|
872
880
|
callback(note);
|
|
873
881
|
}
|
|
874
882
|
});
|
|
@@ -1035,7 +1043,9 @@ class Midy {
|
|
|
1035
1043
|
return 8.176 * this.centToRate(cent);
|
|
1036
1044
|
}
|
|
1037
1045
|
calcChannelDetune(channel) {
|
|
1038
|
-
const masterTuning =
|
|
1046
|
+
const masterTuning = channel.isDrum
|
|
1047
|
+
? 0
|
|
1048
|
+
: this.masterCoarseTuning + this.masterFineTuning;
|
|
1039
1049
|
const channelTuning = channel.coarseTuning + channel.fineTuning;
|
|
1040
1050
|
const tuning = masterTuning + channelTuning;
|
|
1041
1051
|
const pitchWheel = channel.state.pitchWheel * 2 - 1;
|
|
@@ -1049,7 +1059,7 @@ class Midy {
|
|
|
1049
1059
|
return channel.scaleOctaveTuningTable[note.noteNumber % 12];
|
|
1050
1060
|
}
|
|
1051
1061
|
updateChannelDetune(channel, scheduleTime) {
|
|
1052
|
-
this.processScheduledNotes(channel,
|
|
1062
|
+
this.processScheduledNotes(channel, (note) => {
|
|
1053
1063
|
this.updateDetune(channel, note, scheduleTime);
|
|
1054
1064
|
});
|
|
1055
1065
|
}
|
|
@@ -1062,9 +1072,8 @@ class Midy {
|
|
|
1062
1072
|
.setValueAtTime(detune, scheduleTime);
|
|
1063
1073
|
}
|
|
1064
1074
|
getPortamentoTime(channel) {
|
|
1065
|
-
const factor = 5 * Math.log(10)
|
|
1066
|
-
|
|
1067
|
-
return Math.log(time) / factor;
|
|
1075
|
+
const factor = 5 * Math.log(10) * 127;
|
|
1076
|
+
return channel.state.portamentoTime * factor;
|
|
1068
1077
|
}
|
|
1069
1078
|
setPortamentoStartVolumeEnvelope(channel, note, scheduleTime) {
|
|
1070
1079
|
const { voiceParams, startTime } = note;
|
|
@@ -1228,7 +1237,7 @@ class Midy {
|
|
|
1228
1237
|
const voiceParams = voice.getAllParams(controllerState);
|
|
1229
1238
|
const note = new Note(noteNumber, velocity, startTime, voice, voiceParams);
|
|
1230
1239
|
const audioBuffer = await this.getAudioBuffer(channel.program, noteNumber, velocity, voiceParams, isSF3);
|
|
1231
|
-
note.bufferSource = this.
|
|
1240
|
+
note.bufferSource = this.createBufferSource(channel, note, voiceParams, audioBuffer);
|
|
1232
1241
|
note.volumeNode = new GainNode(this.audioContext);
|
|
1233
1242
|
note.gainL = new GainNode(this.audioContext);
|
|
1234
1243
|
note.gainR = new GainNode(this.audioContext);
|
|
@@ -1254,7 +1263,7 @@ class Midy {
|
|
|
1254
1263
|
if (0 < state.modulationDepth) {
|
|
1255
1264
|
this.startModulation(channel, note, now);
|
|
1256
1265
|
}
|
|
1257
|
-
if (
|
|
1266
|
+
if (channel.mono && channel.currentBufferSource) {
|
|
1258
1267
|
channel.currentBufferSource.stop(startTime);
|
|
1259
1268
|
channel.currentBufferSource = note.bufferSource;
|
|
1260
1269
|
}
|
|
@@ -1272,14 +1281,21 @@ class Midy {
|
|
|
1272
1281
|
note.bufferSource.start(startTime);
|
|
1273
1282
|
return note;
|
|
1274
1283
|
}
|
|
1275
|
-
calcBank(channel
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1284
|
+
calcBank(channel) {
|
|
1285
|
+
switch (this.mode) {
|
|
1286
|
+
case "GM1":
|
|
1287
|
+
if (channel.isDrum)
|
|
1288
|
+
return 128;
|
|
1289
|
+
return 0;
|
|
1290
|
+
case "GM2":
|
|
1291
|
+
if (channel.bankMSB === 121)
|
|
1292
|
+
return 0;
|
|
1293
|
+
if (channel.isDrum)
|
|
1294
|
+
return 128;
|
|
1295
|
+
return channel.bank;
|
|
1296
|
+
default:
|
|
1297
|
+
return channel.bank;
|
|
1281
1298
|
}
|
|
1282
|
-
return channel.bank;
|
|
1283
1299
|
}
|
|
1284
1300
|
async scheduleNoteOn(channelNumber, noteNumber, velocity, startTime, portamento) {
|
|
1285
1301
|
const channel = this.channels[channelNumber];
|
|
@@ -1295,15 +1311,15 @@ class Midy {
|
|
|
1295
1311
|
const note = await this.createNote(channel, voice, noteNumber, velocity, startTime, portamento, isSF3);
|
|
1296
1312
|
note.gainL.connect(channel.gainL);
|
|
1297
1313
|
note.gainR.connect(channel.gainR);
|
|
1298
|
-
if (channel.state.
|
|
1299
|
-
channel.
|
|
1314
|
+
if (0.5 <= channel.state.sustainPedal) {
|
|
1315
|
+
channel.sustainNotes.push(note);
|
|
1300
1316
|
}
|
|
1301
1317
|
const exclusiveClass = note.voiceParams.exclusiveClass;
|
|
1302
1318
|
if (exclusiveClass !== 0) {
|
|
1303
1319
|
if (this.exclusiveClassMap.has(exclusiveClass)) {
|
|
1304
1320
|
const prevEntry = this.exclusiveClassMap.get(exclusiveClass);
|
|
1305
1321
|
const [prevNote, prevChannelNumber] = prevEntry;
|
|
1306
|
-
if (!prevNote.ending) {
|
|
1322
|
+
if (prevNote && !prevNote.ending) {
|
|
1307
1323
|
this.scheduleNoteOff(prevChannelNumber, prevNote.noteNumber, 0, // velocity,
|
|
1308
1324
|
startTime, true, // force
|
|
1309
1325
|
undefined);
|
|
@@ -1365,7 +1381,7 @@ class Midy {
|
|
|
1365
1381
|
const channel = this.channels[channelNumber];
|
|
1366
1382
|
const state = channel.state;
|
|
1367
1383
|
if (!force) {
|
|
1368
|
-
if (0.5
|
|
1384
|
+
if (0.5 <= state.sustainPedal)
|
|
1369
1385
|
return;
|
|
1370
1386
|
if (channel.sostenutoNotes.has(noteNumber))
|
|
1371
1387
|
return;
|
|
@@ -1410,28 +1426,27 @@ class Midy {
|
|
|
1410
1426
|
const velocity = halfVelocity * 2;
|
|
1411
1427
|
const channel = this.channels[channelNumber];
|
|
1412
1428
|
const promises = [];
|
|
1413
|
-
|
|
1414
|
-
const
|
|
1415
|
-
const promise = this.noteOff(channelNumber, noteNumber, velocity);
|
|
1429
|
+
for (let i = 0; i < channel.sustainNotes.length; i++) {
|
|
1430
|
+
const promise = this.noteOff(channelNumber, channel.sustainNotes[i].noteNumber, velocity, scheduleTime);
|
|
1416
1431
|
promises.push(promise);
|
|
1417
|
-
}
|
|
1432
|
+
}
|
|
1433
|
+
channel.sustainNotes = [];
|
|
1418
1434
|
return promises;
|
|
1419
1435
|
}
|
|
1420
|
-
releaseSostenutoPedal(channelNumber, halfVelocity) {
|
|
1436
|
+
releaseSostenutoPedal(channelNumber, halfVelocity, scheduleTime) {
|
|
1421
1437
|
const velocity = halfVelocity * 2;
|
|
1422
1438
|
const channel = this.channels[channelNumber];
|
|
1423
1439
|
const promises = [];
|
|
1424
1440
|
channel.state.sostenutoPedal = 0;
|
|
1425
|
-
channel.sostenutoNotes.forEach((
|
|
1426
|
-
const
|
|
1427
|
-
const promise = this.noteOff(channelNumber, noteNumber, velocity);
|
|
1441
|
+
channel.sostenutoNotes.forEach((note) => {
|
|
1442
|
+
const promise = this.noteOff(channelNumber, note.noteNumber, velocity, scheduleTime);
|
|
1428
1443
|
promises.push(promise);
|
|
1429
1444
|
});
|
|
1430
1445
|
channel.sostenutoNotes.clear();
|
|
1431
1446
|
return promises;
|
|
1432
1447
|
}
|
|
1433
1448
|
handleMIDIMessage(statusByte, data1, data2, scheduleTime) {
|
|
1434
|
-
const channelNumber =
|
|
1449
|
+
const channelNumber = statusByte & 0x0F;
|
|
1435
1450
|
const messageType = statusByte & 0xF0;
|
|
1436
1451
|
switch (messageType) {
|
|
1437
1452
|
case 0x80:
|
|
@@ -1467,9 +1482,21 @@ class Midy {
|
|
|
1467
1482
|
const channel = this.channels[channelNumber];
|
|
1468
1483
|
channel.bank = channel.bankMSB * 128 + channel.bankLSB;
|
|
1469
1484
|
channel.program = program;
|
|
1485
|
+
if (this.mode === "GM2") {
|
|
1486
|
+
switch (channel.bankMSB) {
|
|
1487
|
+
case 120:
|
|
1488
|
+
channel.isDrum = true;
|
|
1489
|
+
break;
|
|
1490
|
+
case 121:
|
|
1491
|
+
channel.isDrum = false;
|
|
1492
|
+
break;
|
|
1493
|
+
}
|
|
1494
|
+
}
|
|
1470
1495
|
}
|
|
1471
1496
|
handleChannelPressure(channelNumber, value, scheduleTime) {
|
|
1472
1497
|
const channel = this.channels[channelNumber];
|
|
1498
|
+
if (channel.isDrum)
|
|
1499
|
+
return;
|
|
1473
1500
|
const prev = channel.state.channelPressure;
|
|
1474
1501
|
const next = value / 127;
|
|
1475
1502
|
channel.state.channelPressure = next;
|
|
@@ -1488,8 +1515,10 @@ class Midy {
|
|
|
1488
1515
|
this.setPitchBend(channelNumber, pitchBend, scheduleTime);
|
|
1489
1516
|
}
|
|
1490
1517
|
setPitchBend(channelNumber, value, scheduleTime) {
|
|
1491
|
-
scheduleTime ??= this.audioContext.currentTime;
|
|
1492
1518
|
const channel = this.channels[channelNumber];
|
|
1519
|
+
if (channel.isDrum)
|
|
1520
|
+
return;
|
|
1521
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1493
1522
|
const state = channel.state;
|
|
1494
1523
|
const prev = state.pitchWheel * 2 - 1;
|
|
1495
1524
|
const next = (value - 8192) / 8192;
|
|
@@ -1659,53 +1688,48 @@ class Midy {
|
|
|
1659
1688
|
return state;
|
|
1660
1689
|
}
|
|
1661
1690
|
applyVoiceParams(channel, controllerType, scheduleTime) {
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1691
|
+
this.processScheduledNotes(channel, (note) => {
|
|
1692
|
+
const controllerState = this.getControllerState(channel, note.noteNumber, note.velocity);
|
|
1693
|
+
const voiceParams = note.voice.getParams(controllerType, controllerState);
|
|
1694
|
+
let appliedFilterEnvelope = false;
|
|
1695
|
+
let appliedVolumeEnvelope = false;
|
|
1696
|
+
for (const [key, value] of Object.entries(voiceParams)) {
|
|
1697
|
+
const prevValue = note.voiceParams[key];
|
|
1698
|
+
if (value === prevValue)
|
|
1666
1699
|
continue;
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
if (value === prevValue)
|
|
1700
|
+
note.voiceParams[key] = value;
|
|
1701
|
+
if (key in this.voiceParamsHandlers) {
|
|
1702
|
+
this.voiceParamsHandlers[key](channel, note, prevValue, scheduleTime);
|
|
1703
|
+
}
|
|
1704
|
+
else if (filterEnvelopeKeySet.has(key)) {
|
|
1705
|
+
if (appliedFilterEnvelope)
|
|
1674
1706
|
continue;
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1707
|
+
appliedFilterEnvelope = true;
|
|
1708
|
+
const noteVoiceParams = note.voiceParams;
|
|
1709
|
+
for (let i = 0; i < filterEnvelopeKeys.length; i++) {
|
|
1710
|
+
const key = filterEnvelopeKeys[i];
|
|
1711
|
+
if (key in voiceParams)
|
|
1712
|
+
noteVoiceParams[key] = voiceParams[key];
|
|
1678
1713
|
}
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
continue;
|
|
1682
|
-
appliedFilterEnvelope = true;
|
|
1683
|
-
const noteVoiceParams = note.voiceParams;
|
|
1684
|
-
for (let i = 0; i < filterEnvelopeKeys.length; i++) {
|
|
1685
|
-
const key = filterEnvelopeKeys[i];
|
|
1686
|
-
if (key in voiceParams)
|
|
1687
|
-
noteVoiceParams[key] = voiceParams[key];
|
|
1688
|
-
}
|
|
1689
|
-
if (note.portamento) {
|
|
1690
|
-
this.setPortamentoStartFilterEnvelope(channel, note, scheduleTime);
|
|
1691
|
-
}
|
|
1692
|
-
else {
|
|
1693
|
-
this.setFilterEnvelope(channel, note, scheduleTime);
|
|
1694
|
-
}
|
|
1695
|
-
this.setPitchEnvelope(note, scheduleTime);
|
|
1714
|
+
if (note.portamento) {
|
|
1715
|
+
this.setPortamentoStartFilterEnvelope(channel, note, scheduleTime);
|
|
1696
1716
|
}
|
|
1697
|
-
else
|
|
1698
|
-
|
|
1699
|
-
continue;
|
|
1700
|
-
appliedVolumeEnvelope = true;
|
|
1701
|
-
const noteVoiceParams = note.voiceParams;
|
|
1702
|
-
for (let i = 0; i < volumeEnvelopeKeys.length; i++) {
|
|
1703
|
-
const key = volumeEnvelopeKeys[i];
|
|
1704
|
-
if (key in voiceParams)
|
|
1705
|
-
noteVoiceParams[key] = voiceParams[key];
|
|
1706
|
-
}
|
|
1707
|
-
this.setVolumeEnvelope(channel, note, scheduleTime);
|
|
1717
|
+
else {
|
|
1718
|
+
this.setFilterEnvelope(channel, note, scheduleTime);
|
|
1708
1719
|
}
|
|
1720
|
+
this.setPitchEnvelope(note, scheduleTime);
|
|
1721
|
+
}
|
|
1722
|
+
else if (volumeEnvelopeKeySet.has(key)) {
|
|
1723
|
+
if (appliedVolumeEnvelope)
|
|
1724
|
+
continue;
|
|
1725
|
+
appliedVolumeEnvelope = true;
|
|
1726
|
+
const noteVoiceParams = note.voiceParams;
|
|
1727
|
+
for (let i = 0; i < volumeEnvelopeKeys.length; i++) {
|
|
1728
|
+
const key = volumeEnvelopeKeys[i];
|
|
1729
|
+
if (key in voiceParams)
|
|
1730
|
+
noteVoiceParams[key] = voiceParams[key];
|
|
1731
|
+
}
|
|
1732
|
+
this.setVolumeEnvelope(channel, note, scheduleTime);
|
|
1709
1733
|
}
|
|
1710
1734
|
}
|
|
1711
1735
|
});
|
|
@@ -1764,9 +1788,8 @@ class Midy {
|
|
|
1764
1788
|
this.channels[channelNumber].bankMSB = msb;
|
|
1765
1789
|
}
|
|
1766
1790
|
updateModulation(channel, scheduleTime) {
|
|
1767
|
-
scheduleTime ??= this.audioContext.currentTime;
|
|
1768
1791
|
const depth = channel.state.modulationDepth * channel.modulationDepthRange;
|
|
1769
|
-
this.processScheduledNotes(channel,
|
|
1792
|
+
this.processScheduledNotes(channel, (note) => {
|
|
1770
1793
|
if (note.modulationDepth) {
|
|
1771
1794
|
note.modulationDepth.gain.setValueAtTime(depth, scheduleTime);
|
|
1772
1795
|
}
|
|
@@ -1778,17 +1801,18 @@ class Midy {
|
|
|
1778
1801
|
}
|
|
1779
1802
|
setModulationDepth(channelNumber, modulation, scheduleTime) {
|
|
1780
1803
|
const channel = this.channels[channelNumber];
|
|
1804
|
+
if (channel.isDrum)
|
|
1805
|
+
return;
|
|
1806
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1781
1807
|
channel.state.modulationDepth = modulation / 127;
|
|
1782
1808
|
this.updateModulation(channel, scheduleTime);
|
|
1783
1809
|
}
|
|
1784
1810
|
setPortamentoTime(channelNumber, portamentoTime) {
|
|
1785
1811
|
const channel = this.channels[channelNumber];
|
|
1786
|
-
|
|
1787
|
-
channel.state.portamentoTime = Math.exp(factor * portamentoTime);
|
|
1812
|
+
channel.state.portamentoTime = portamentoTime / 127;
|
|
1788
1813
|
}
|
|
1789
1814
|
setKeyBasedVolume(channel, scheduleTime) {
|
|
1790
|
-
|
|
1791
|
-
this.processScheduledNotes(channel, scheduleTime, (note) => {
|
|
1815
|
+
this.processScheduledNotes(channel, (note) => {
|
|
1792
1816
|
const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 7);
|
|
1793
1817
|
if (keyBasedValue !== 0) {
|
|
1794
1818
|
note.volumeNode.gain
|
|
@@ -1798,6 +1822,7 @@ class Midy {
|
|
|
1798
1822
|
});
|
|
1799
1823
|
}
|
|
1800
1824
|
setVolume(channelNumber, volume, scheduleTime) {
|
|
1825
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1801
1826
|
const channel = this.channels[channelNumber];
|
|
1802
1827
|
channel.state.volume = volume / 127;
|
|
1803
1828
|
this.updateChannelVolume(channel, scheduleTime);
|
|
@@ -1811,8 +1836,7 @@ class Midy {
|
|
|
1811
1836
|
};
|
|
1812
1837
|
}
|
|
1813
1838
|
setKeyBasedPan(channel, scheduleTime) {
|
|
1814
|
-
|
|
1815
|
-
this.processScheduledNotes(channel, scheduleTime, (note) => {
|
|
1839
|
+
this.processScheduledNotes(channel, (note) => {
|
|
1816
1840
|
const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 10);
|
|
1817
1841
|
if (keyBasedValue !== 0) {
|
|
1818
1842
|
const { gainLeft, gainRight } = this.panToGain((keyBasedValue + 1) / 2);
|
|
@@ -1826,12 +1850,14 @@ class Midy {
|
|
|
1826
1850
|
});
|
|
1827
1851
|
}
|
|
1828
1852
|
setPan(channelNumber, pan, scheduleTime) {
|
|
1853
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1829
1854
|
const channel = this.channels[channelNumber];
|
|
1830
1855
|
channel.state.pan = pan / 127;
|
|
1831
1856
|
this.updateChannelVolume(channel, scheduleTime);
|
|
1832
1857
|
this.setKeyBasedPan(channel, scheduleTime);
|
|
1833
1858
|
}
|
|
1834
1859
|
setExpression(channelNumber, expression, scheduleTime) {
|
|
1860
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1835
1861
|
const channel = this.channels[channelNumber];
|
|
1836
1862
|
channel.state.expression = expression / 127;
|
|
1837
1863
|
this.updateChannelVolume(channel, scheduleTime);
|
|
@@ -1855,144 +1881,156 @@ class Midy {
|
|
|
1855
1881
|
.setValueAtTime(volume * gainRight, scheduleTime);
|
|
1856
1882
|
}
|
|
1857
1883
|
setSustainPedal(channelNumber, value, scheduleTime) {
|
|
1884
|
+
const channel = this.channels[channelNumber];
|
|
1885
|
+
if (channel.isDrum)
|
|
1886
|
+
return;
|
|
1858
1887
|
scheduleTime ??= this.audioContext.currentTime;
|
|
1859
|
-
|
|
1860
|
-
if (
|
|
1888
|
+
channel.state.sustainPedal = value / 127;
|
|
1889
|
+
if (64 <= value) {
|
|
1890
|
+
this.processScheduledNotes(channel, (note) => {
|
|
1891
|
+
channel.sustainNotes.push(note);
|
|
1892
|
+
});
|
|
1893
|
+
}
|
|
1894
|
+
else {
|
|
1861
1895
|
this.releaseSustainPedal(channelNumber, value, scheduleTime);
|
|
1862
1896
|
}
|
|
1863
1897
|
}
|
|
1864
1898
|
setPortamento(channelNumber, value) {
|
|
1865
|
-
this.channels[channelNumber]
|
|
1899
|
+
const channel = this.channels[channelNumber];
|
|
1900
|
+
if (channel.isDrum)
|
|
1901
|
+
return;
|
|
1902
|
+
channel.state.portamento = value / 127;
|
|
1866
1903
|
}
|
|
1867
1904
|
setSostenutoPedal(channelNumber, value, scheduleTime) {
|
|
1868
1905
|
const channel = this.channels[channelNumber];
|
|
1906
|
+
if (channel.isDrum)
|
|
1907
|
+
return;
|
|
1908
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1869
1909
|
channel.state.sostenutoPedal = value / 127;
|
|
1870
1910
|
if (64 <= value) {
|
|
1871
1911
|
channel.sostenutoNotes = this.getActiveNotes(channel, scheduleTime);
|
|
1872
1912
|
}
|
|
1873
1913
|
else {
|
|
1874
|
-
this.releaseSostenutoPedal(channelNumber, value);
|
|
1914
|
+
this.releaseSostenutoPedal(channelNumber, value, scheduleTime);
|
|
1875
1915
|
}
|
|
1876
1916
|
}
|
|
1877
|
-
setSoftPedal(channelNumber, softPedal,
|
|
1917
|
+
setSoftPedal(channelNumber, softPedal, scheduleTime) {
|
|
1878
1918
|
const channel = this.channels[channelNumber];
|
|
1919
|
+
if (channel.isDrum)
|
|
1920
|
+
return;
|
|
1921
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1879
1922
|
channel.state.softPedal = softPedal / 127;
|
|
1923
|
+
this.processScheduledNotes(channel, (note) => {
|
|
1924
|
+
if (note.portamento) {
|
|
1925
|
+
this.setPortamentoStartVolumeEnvelope(channel, note, scheduleTime);
|
|
1926
|
+
this.setPortamentoStartFilterEnvelope(channel, note, scheduleTime);
|
|
1927
|
+
}
|
|
1928
|
+
else {
|
|
1929
|
+
this.setVolumeEnvelope(channel, note, scheduleTime);
|
|
1930
|
+
this.setFilterEnvelope(channel, note, scheduleTime);
|
|
1931
|
+
}
|
|
1932
|
+
});
|
|
1880
1933
|
}
|
|
1881
1934
|
setFilterResonance(channelNumber, filterResonance, scheduleTime) {
|
|
1882
1935
|
const channel = this.channels[channelNumber];
|
|
1936
|
+
if (channel.isDrum)
|
|
1937
|
+
return;
|
|
1938
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1883
1939
|
const state = channel.state;
|
|
1884
1940
|
state.filterResonance = filterResonance / 64;
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
if (!note)
|
|
1889
|
-
continue;
|
|
1890
|
-
const Q = note.voiceParams.initialFilterQ / 5 * state.filterResonance;
|
|
1891
|
-
note.filterNode.Q.setValueAtTime(Q, scheduleTime);
|
|
1892
|
-
}
|
|
1941
|
+
this.processScheduledNotes(channel, (note) => {
|
|
1942
|
+
const Q = note.voiceParams.initialFilterQ / 5 * state.filterResonance;
|
|
1943
|
+
note.filterNode.Q.setValueAtTime(Q, scheduleTime);
|
|
1893
1944
|
});
|
|
1894
1945
|
}
|
|
1895
1946
|
setReleaseTime(channelNumber, releaseTime, _scheduleTime) {
|
|
1896
1947
|
const channel = this.channels[channelNumber];
|
|
1948
|
+
if (channel.isDrum)
|
|
1949
|
+
return;
|
|
1950
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1897
1951
|
channel.state.releaseTime = releaseTime / 64;
|
|
1898
1952
|
}
|
|
1899
1953
|
setAttackTime(channelNumber, attackTime, scheduleTime) {
|
|
1900
1954
|
const channel = this.channels[channelNumber];
|
|
1955
|
+
if (channel.isDrum)
|
|
1956
|
+
return;
|
|
1957
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1901
1958
|
channel.state.attackTime = attackTime / 64;
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
continue;
|
|
1907
|
-
if (note.startTime < scheduleTime)
|
|
1908
|
-
continue;
|
|
1909
|
-
this.setVolumeEnvelope(channel, note);
|
|
1910
|
-
}
|
|
1959
|
+
this.processScheduledNotes(channel, (note) => {
|
|
1960
|
+
if (note.startTime < scheduleTime)
|
|
1961
|
+
return false;
|
|
1962
|
+
this.setVolumeEnvelope(channel, note);
|
|
1911
1963
|
});
|
|
1912
1964
|
}
|
|
1913
1965
|
setBrightness(channelNumber, brightness, scheduleTime) {
|
|
1914
1966
|
const channel = this.channels[channelNumber];
|
|
1967
|
+
if (channel.isDrum)
|
|
1968
|
+
return;
|
|
1969
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1915
1970
|
channel.state.brightness = brightness / 64;
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
this.setPortamentoStartFilterEnvelope(channel, note, scheduleTime);
|
|
1923
|
-
}
|
|
1924
|
-
else {
|
|
1925
|
-
this.setFilterEnvelope(channel, note);
|
|
1926
|
-
}
|
|
1971
|
+
this.processScheduledNotes(channel, (note) => {
|
|
1972
|
+
if (note.portamento) {
|
|
1973
|
+
this.setPortamentoStartFilterEnvelope(channel, note, scheduleTime);
|
|
1974
|
+
}
|
|
1975
|
+
else {
|
|
1976
|
+
this.setFilterEnvelope(channel, note);
|
|
1927
1977
|
}
|
|
1928
1978
|
});
|
|
1929
1979
|
}
|
|
1930
1980
|
setDecayTime(channelNumber, dacayTime, scheduleTime) {
|
|
1931
1981
|
const channel = this.channels[channelNumber];
|
|
1982
|
+
if (channel.isDrum)
|
|
1983
|
+
return;
|
|
1984
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1932
1985
|
channel.state.decayTime = dacayTime / 64;
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
const note = noteList[i];
|
|
1936
|
-
if (!note)
|
|
1937
|
-
continue;
|
|
1938
|
-
this.setVolumeEnvelope(channel, note, scheduleTime);
|
|
1939
|
-
}
|
|
1986
|
+
this.processScheduledNotes(channel, (note) => {
|
|
1987
|
+
this.setVolumeEnvelope(channel, note, scheduleTime);
|
|
1940
1988
|
});
|
|
1941
1989
|
}
|
|
1942
1990
|
setVibratoRate(channelNumber, vibratoRate, scheduleTime) {
|
|
1943
1991
|
const channel = this.channels[channelNumber];
|
|
1992
|
+
if (channel.isDrum)
|
|
1993
|
+
return;
|
|
1994
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1944
1995
|
channel.state.vibratoRate = vibratoRate / 64;
|
|
1945
1996
|
if (channel.vibratoDepth <= 0)
|
|
1946
1997
|
return;
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
const note = noteList[i];
|
|
1950
|
-
if (!note)
|
|
1951
|
-
continue;
|
|
1952
|
-
this.setVibLfoToPitch(channel, note, scheduleTime);
|
|
1953
|
-
}
|
|
1998
|
+
this.processScheduledNotes(channel, (note) => {
|
|
1999
|
+
this.setVibLfoToPitch(channel, note, scheduleTime);
|
|
1954
2000
|
});
|
|
1955
2001
|
}
|
|
1956
2002
|
setVibratoDepth(channelNumber, vibratoDepth, scheduleTime) {
|
|
1957
2003
|
const channel = this.channels[channelNumber];
|
|
2004
|
+
if (channel.isDrum)
|
|
2005
|
+
return;
|
|
2006
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1958
2007
|
const prev = channel.state.vibratoDepth;
|
|
1959
2008
|
channel.state.vibratoDepth = vibratoDepth / 64;
|
|
1960
2009
|
if (0 < prev) {
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
const note = noteList[i];
|
|
1964
|
-
if (!note)
|
|
1965
|
-
continue;
|
|
1966
|
-
this.setFreqVibLFO(channel, note, scheduleTime);
|
|
1967
|
-
}
|
|
2010
|
+
this.processScheduledNotes(channel, (note) => {
|
|
2011
|
+
this.setFreqVibLFO(channel, note, scheduleTime);
|
|
1968
2012
|
});
|
|
1969
2013
|
}
|
|
1970
2014
|
else {
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
const note = noteList[i];
|
|
1974
|
-
if (!note)
|
|
1975
|
-
continue;
|
|
1976
|
-
this.startVibrato(channel, note, scheduleTime);
|
|
1977
|
-
}
|
|
2015
|
+
this.processScheduledNotes(channel, (note) => {
|
|
2016
|
+
this.startVibrato(channel, note, scheduleTime);
|
|
1978
2017
|
});
|
|
1979
2018
|
}
|
|
1980
2019
|
}
|
|
1981
2020
|
setVibratoDelay(channelNumber, vibratoDelay) {
|
|
1982
2021
|
const channel = this.channels[channelNumber];
|
|
2022
|
+
if (channel.isDrum)
|
|
2023
|
+
return;
|
|
2024
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1983
2025
|
channel.state.vibratoDelay = vibratoDelay / 64;
|
|
1984
2026
|
if (0 < channel.state.vibratoDepth) {
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
const note = noteList[i];
|
|
1988
|
-
if (!note)
|
|
1989
|
-
continue;
|
|
1990
|
-
this.startVibrato(channel, note, scheduleTime);
|
|
1991
|
-
}
|
|
2027
|
+
this.processScheduledNotes(channel, (note) => {
|
|
2028
|
+
this.startVibrato(channel, note, scheduleTime);
|
|
1992
2029
|
});
|
|
1993
2030
|
}
|
|
1994
2031
|
}
|
|
1995
2032
|
setReverbSendLevel(channelNumber, reverbSendLevel, scheduleTime) {
|
|
2033
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1996
2034
|
const channel = this.channels[channelNumber];
|
|
1997
2035
|
const state = channel.state;
|
|
1998
2036
|
const reverbEffect = this.reverbEffect;
|
|
@@ -2004,27 +2042,17 @@ class Midy {
|
|
|
2004
2042
|
.setValueAtTime(state.reverbSendLevel, scheduleTime);
|
|
2005
2043
|
}
|
|
2006
2044
|
else {
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
continue;
|
|
2012
|
-
if (note.voiceParams.reverbEffectsSend <= 0)
|
|
2013
|
-
continue;
|
|
2014
|
-
note.reverbEffectsSend.disconnect();
|
|
2015
|
-
}
|
|
2045
|
+
this.processScheduledNotes(channel, (note) => {
|
|
2046
|
+
if (note.voiceParams.reverbEffectsSend <= 0)
|
|
2047
|
+
return false;
|
|
2048
|
+
note.reverbEffectsSend.disconnect();
|
|
2016
2049
|
});
|
|
2017
2050
|
}
|
|
2018
2051
|
}
|
|
2019
2052
|
else {
|
|
2020
2053
|
if (0 < reverbSendLevel) {
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
const note = noteList[i];
|
|
2024
|
-
if (!note)
|
|
2025
|
-
continue;
|
|
2026
|
-
this.setReverbEffectsSend(channel, note, 0, scheduleTime);
|
|
2027
|
-
}
|
|
2054
|
+
this.processScheduledNotes(channel, (note) => {
|
|
2055
|
+
this.setReverbEffectsSend(channel, note, 0, scheduleTime);
|
|
2028
2056
|
});
|
|
2029
2057
|
state.reverbSendLevel = reverbSendLevel / 127;
|
|
2030
2058
|
reverbEffect.input.gain
|
|
@@ -2034,6 +2062,7 @@ class Midy {
|
|
|
2034
2062
|
}
|
|
2035
2063
|
}
|
|
2036
2064
|
setChorusSendLevel(channelNumber, chorusSendLevel, scheduleTime) {
|
|
2065
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
2037
2066
|
const channel = this.channels[channelNumber];
|
|
2038
2067
|
const state = channel.state;
|
|
2039
2068
|
const chorusEffect = this.chorusEffect;
|
|
@@ -2045,27 +2074,17 @@ class Midy {
|
|
|
2045
2074
|
.setValueAtTime(state.chorusSendLevel, scheduleTime);
|
|
2046
2075
|
}
|
|
2047
2076
|
else {
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
continue;
|
|
2053
|
-
if (note.voiceParams.chorusEffectsSend <= 0)
|
|
2054
|
-
continue;
|
|
2055
|
-
note.chorusEffectsSend.disconnect();
|
|
2056
|
-
}
|
|
2077
|
+
this.processScheduledNotes(channel, (note) => {
|
|
2078
|
+
if (note.voiceParams.chorusEffectsSend <= 0)
|
|
2079
|
+
return false;
|
|
2080
|
+
note.chorusEffectsSend.disconnect();
|
|
2057
2081
|
});
|
|
2058
2082
|
}
|
|
2059
2083
|
}
|
|
2060
2084
|
else {
|
|
2061
2085
|
if (0 < chorusSendLevel) {
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
const note = noteList[i];
|
|
2065
|
-
if (!note)
|
|
2066
|
-
continue;
|
|
2067
|
-
this.setChorusEffectsSend(channel, note, 0, scheduleTime);
|
|
2068
|
-
}
|
|
2086
|
+
this.processScheduledNotes(channel, (note) => {
|
|
2087
|
+
this.setChorusEffectsSend(channel, note, 0, scheduleTime);
|
|
2069
2088
|
});
|
|
2070
2089
|
state.chorusSendLevel = chorusSendLevel / 127;
|
|
2071
2090
|
chorusEffect.input.gain
|
|
@@ -2110,15 +2129,15 @@ class Midy {
|
|
|
2110
2129
|
break;
|
|
2111
2130
|
case 1:
|
|
2112
2131
|
channel.dataLSB += value;
|
|
2113
|
-
this.handleFineTuningRPN(channelNumber);
|
|
2132
|
+
this.handleFineTuningRPN(channelNumber, scheduleTime);
|
|
2114
2133
|
break;
|
|
2115
2134
|
case 2:
|
|
2116
2135
|
channel.dataMSB += value;
|
|
2117
|
-
this.handleCoarseTuningRPN(channelNumber);
|
|
2136
|
+
this.handleCoarseTuningRPN(channelNumber, scheduleTime);
|
|
2118
2137
|
break;
|
|
2119
2138
|
case 5:
|
|
2120
2139
|
channel.dataLSB += value;
|
|
2121
|
-
this.handleModulationDepthRangeRPN(channelNumber);
|
|
2140
|
+
this.handleModulationDepthRangeRPN(channelNumber, scheduleTime);
|
|
2122
2141
|
break;
|
|
2123
2142
|
default:
|
|
2124
2143
|
console.warn(`Channel ${channelNumber}: Unsupported RPN MSB=${channel.rpnMSB} LSB=${channel.rpnLSB}`);
|
|
@@ -2149,8 +2168,10 @@ class Midy {
|
|
|
2149
2168
|
this.setPitchBendRange(channelNumber, pitchBendRange, scheduleTime);
|
|
2150
2169
|
}
|
|
2151
2170
|
setPitchBendRange(channelNumber, value, scheduleTime) {
|
|
2152
|
-
scheduleTime ??= this.audioContext.currentTime;
|
|
2153
2171
|
const channel = this.channels[channelNumber];
|
|
2172
|
+
if (channel.isDrum)
|
|
2173
|
+
return;
|
|
2174
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
2154
2175
|
const state = channel.state;
|
|
2155
2176
|
const prev = state.pitchWheelSensitivity;
|
|
2156
2177
|
const next = value / 128;
|
|
@@ -2159,44 +2180,53 @@ class Midy {
|
|
|
2159
2180
|
this.updateChannelDetune(channel, scheduleTime);
|
|
2160
2181
|
this.applyVoiceParams(channel, 16, scheduleTime);
|
|
2161
2182
|
}
|
|
2162
|
-
handleFineTuningRPN(channelNumber) {
|
|
2183
|
+
handleFineTuningRPN(channelNumber, scheduleTime) {
|
|
2163
2184
|
const channel = this.channels[channelNumber];
|
|
2164
2185
|
this.limitData(channel, 0, 127, 0, 127);
|
|
2165
2186
|
const fineTuning = channel.dataMSB * 128 + channel.dataLSB;
|
|
2166
|
-
this.setFineTuning(channelNumber, fineTuning);
|
|
2187
|
+
this.setFineTuning(channelNumber, fineTuning, scheduleTime);
|
|
2167
2188
|
}
|
|
2168
|
-
setFineTuning(channelNumber, value) {
|
|
2189
|
+
setFineTuning(channelNumber, value, scheduleTime) {
|
|
2169
2190
|
const channel = this.channels[channelNumber];
|
|
2191
|
+
if (channel.isDrum)
|
|
2192
|
+
return;
|
|
2193
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
2170
2194
|
const prev = channel.fineTuning;
|
|
2171
2195
|
const next = (value - 8192) / 8.192; // cent
|
|
2172
2196
|
channel.fineTuning = next;
|
|
2173
2197
|
channel.detune += next - prev;
|
|
2174
|
-
this.updateChannelDetune(channel);
|
|
2198
|
+
this.updateChannelDetune(channel, scheduleTime);
|
|
2175
2199
|
}
|
|
2176
|
-
handleCoarseTuningRPN(channelNumber) {
|
|
2200
|
+
handleCoarseTuningRPN(channelNumber, scheduleTime) {
|
|
2177
2201
|
const channel = this.channels[channelNumber];
|
|
2178
2202
|
this.limitDataMSB(channel, 0, 127);
|
|
2179
2203
|
const coarseTuning = channel.dataMSB;
|
|
2180
|
-
this.setCoarseTuning(channelNumber, coarseTuning);
|
|
2204
|
+
this.setCoarseTuning(channelNumber, coarseTuning, scheduleTime);
|
|
2181
2205
|
}
|
|
2182
|
-
setCoarseTuning(channelNumber, value) {
|
|
2206
|
+
setCoarseTuning(channelNumber, value, scheduleTime) {
|
|
2183
2207
|
const channel = this.channels[channelNumber];
|
|
2208
|
+
if (channel.isDrum)
|
|
2209
|
+
return;
|
|
2210
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
2184
2211
|
const prev = channel.coarseTuning;
|
|
2185
2212
|
const next = (value - 64) * 100; // cent
|
|
2186
2213
|
channel.coarseTuning = next;
|
|
2187
2214
|
channel.detune += next - prev;
|
|
2188
|
-
this.updateChannelDetune(channel);
|
|
2215
|
+
this.updateChannelDetune(channel, scheduleTime);
|
|
2189
2216
|
}
|
|
2190
|
-
handleModulationDepthRangeRPN(channelNumber) {
|
|
2217
|
+
handleModulationDepthRangeRPN(channelNumber, scheduleTime) {
|
|
2191
2218
|
const channel = this.channels[channelNumber];
|
|
2192
2219
|
this.limitData(channel, 0, 127, 0, 127);
|
|
2193
2220
|
const modulationDepthRange = (dataMSB + dataLSB / 128) * 100;
|
|
2194
|
-
this.setModulationDepthRange(channelNumber, modulationDepthRange);
|
|
2221
|
+
this.setModulationDepthRange(channelNumber, modulationDepthRange, scheduleTime);
|
|
2195
2222
|
}
|
|
2196
|
-
setModulationDepthRange(channelNumber, modulationDepthRange) {
|
|
2223
|
+
setModulationDepthRange(channelNumber, modulationDepthRange, scheduleTime) {
|
|
2197
2224
|
const channel = this.channels[channelNumber];
|
|
2225
|
+
if (channel.isDrum)
|
|
2226
|
+
return;
|
|
2227
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
2198
2228
|
channel.modulationDepthRange = modulationDepthRange;
|
|
2199
|
-
this.updateModulation(channel);
|
|
2229
|
+
this.updateModulation(channel, scheduleTime);
|
|
2200
2230
|
}
|
|
2201
2231
|
allSoundOff(channelNumber, _value, scheduleTime) {
|
|
2202
2232
|
scheduleTime ??= this.audioContext.currentTime;
|
|
@@ -2232,17 +2262,21 @@ class Midy {
|
|
|
2232
2262
|
scheduleTime ??= this.audioContext.currentTime;
|
|
2233
2263
|
return this.stopChannelNotes(channelNumber, 0, false, scheduleTime);
|
|
2234
2264
|
}
|
|
2235
|
-
omniOff() {
|
|
2236
|
-
this.
|
|
2265
|
+
omniOff(channelNumber, value, scheduleTime) {
|
|
2266
|
+
this.allNotesOff(channelNumber, value, scheduleTime);
|
|
2237
2267
|
}
|
|
2238
|
-
omniOn() {
|
|
2239
|
-
this.
|
|
2268
|
+
omniOn(channelNumber, value, scheduleTime) {
|
|
2269
|
+
this.allNotesOff(channelNumber, value, scheduleTime);
|
|
2240
2270
|
}
|
|
2241
|
-
monoOn() {
|
|
2242
|
-
|
|
2271
|
+
monoOn(channelNumber, value, scheduleTime) {
|
|
2272
|
+
const channel = this.channels[channelNumber];
|
|
2273
|
+
this.allNotesOff(channelNumber, value, scheduleTime);
|
|
2274
|
+
channel.mono = true;
|
|
2243
2275
|
}
|
|
2244
|
-
polyOn() {
|
|
2245
|
-
|
|
2276
|
+
polyOn(channelNumber, value, scheduleTime) {
|
|
2277
|
+
const channel = this.channels[channelNumber];
|
|
2278
|
+
this.allNotesOff(channelNumber, value, scheduleTime);
|
|
2279
|
+
channel.mono = false;
|
|
2246
2280
|
}
|
|
2247
2281
|
handleUniversalNonRealTimeExclusiveMessage(data, scheduleTime) {
|
|
2248
2282
|
switch (data[2]) {
|
|
@@ -2261,12 +2295,12 @@ class Midy {
|
|
|
2261
2295
|
case 9:
|
|
2262
2296
|
switch (data[3]) {
|
|
2263
2297
|
case 1:
|
|
2264
|
-
this.GM1SystemOn();
|
|
2298
|
+
this.GM1SystemOn(scheduleTime);
|
|
2265
2299
|
break;
|
|
2266
2300
|
case 2: // GM System Off
|
|
2267
2301
|
break;
|
|
2268
2302
|
case 3:
|
|
2269
|
-
this.GM2SystemOn();
|
|
2303
|
+
this.GM2SystemOn(scheduleTime);
|
|
2270
2304
|
break;
|
|
2271
2305
|
default:
|
|
2272
2306
|
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
@@ -2276,25 +2310,35 @@ class Midy {
|
|
|
2276
2310
|
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
2277
2311
|
}
|
|
2278
2312
|
}
|
|
2279
|
-
GM1SystemOn() {
|
|
2313
|
+
GM1SystemOn(scheduleTime) {
|
|
2314
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
2315
|
+
this.mode = "GM1";
|
|
2280
2316
|
for (let i = 0; i < this.channels.length; i++) {
|
|
2317
|
+
this.allSoundOff(i, 0, scheduleTime);
|
|
2281
2318
|
const channel = this.channels[i];
|
|
2282
2319
|
channel.bankMSB = 0;
|
|
2283
2320
|
channel.bankLSB = 0;
|
|
2284
2321
|
channel.bank = 0;
|
|
2322
|
+
channel.isDrum = false;
|
|
2285
2323
|
}
|
|
2286
2324
|
this.channels[9].bankMSB = 1;
|
|
2287
2325
|
this.channels[9].bank = 128;
|
|
2326
|
+
this.channels[9].isDrum = true;
|
|
2288
2327
|
}
|
|
2289
|
-
GM2SystemOn() {
|
|
2328
|
+
GM2SystemOn(scheduleTime) {
|
|
2329
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
2330
|
+
this.mode = "GM2";
|
|
2290
2331
|
for (let i = 0; i < this.channels.length; i++) {
|
|
2332
|
+
this.allSoundOff(i, 0, scheduleTime);
|
|
2291
2333
|
const channel = this.channels[i];
|
|
2292
2334
|
channel.bankMSB = 121;
|
|
2293
2335
|
channel.bankLSB = 0;
|
|
2294
2336
|
channel.bank = 121 * 128;
|
|
2337
|
+
channel.isDrum = false;
|
|
2295
2338
|
}
|
|
2296
2339
|
this.channels[9].bankMSB = 120;
|
|
2297
2340
|
this.channels[9].bank = 120 * 128;
|
|
2341
|
+
this.channels[9].isDrum = true;
|
|
2298
2342
|
}
|
|
2299
2343
|
handleUniversalRealTimeExclusiveMessage(data, scheduleTime) {
|
|
2300
2344
|
switch (data[2]) {
|
|
@@ -2370,8 +2414,14 @@ class Midy {
|
|
|
2370
2414
|
const prev = this.masterFineTuning;
|
|
2371
2415
|
const next = (value - 8192) / 8.192; // cent
|
|
2372
2416
|
this.masterFineTuning = next;
|
|
2373
|
-
|
|
2374
|
-
this.
|
|
2417
|
+
const detuneChange = next - prev;
|
|
2418
|
+
for (let i = 0; i < this.channels.length; i++) {
|
|
2419
|
+
const channel = this.channels[i];
|
|
2420
|
+
if (channel.isDrum)
|
|
2421
|
+
continue;
|
|
2422
|
+
channel.detune += detuneChange;
|
|
2423
|
+
this.updateChannelDetune(channel, scheduleTime);
|
|
2424
|
+
}
|
|
2375
2425
|
}
|
|
2376
2426
|
handleMasterCoarseTuningSysEx(data, scheduleTime) {
|
|
2377
2427
|
const coarseTuning = data[4];
|
|
@@ -2381,8 +2431,14 @@ class Midy {
|
|
|
2381
2431
|
const prev = this.masterCoarseTuning;
|
|
2382
2432
|
const next = (value - 64) * 100; // cent
|
|
2383
2433
|
this.masterCoarseTuning = next;
|
|
2384
|
-
|
|
2385
|
-
this.
|
|
2434
|
+
const detuneChange = next - prev;
|
|
2435
|
+
for (let i = 0; i < this.channels.length; i++) {
|
|
2436
|
+
const channel = this.channels[i];
|
|
2437
|
+
if (channel.isDrum)
|
|
2438
|
+
continue;
|
|
2439
|
+
channel.detune += detuneChange;
|
|
2440
|
+
this.updateChannelDetune(channel, scheduleTime);
|
|
2441
|
+
}
|
|
2386
2442
|
}
|
|
2387
2443
|
handleGlobalParameterControlSysEx(data, scheduleTime) {
|
|
2388
2444
|
if (data[7] === 1) {
|
|
@@ -2594,6 +2650,8 @@ class Midy {
|
|
|
2594
2650
|
if (!channelBitmap[i])
|
|
2595
2651
|
continue;
|
|
2596
2652
|
const channel = this.channels[i];
|
|
2653
|
+
if (channel.isDrum)
|
|
2654
|
+
continue;
|
|
2597
2655
|
for (let j = 0; j < 12; j++) {
|
|
2598
2656
|
const centValue = data[j + 7] - 64;
|
|
2599
2657
|
channel.scaleOctaveTuningTable[j] = centValue;
|
|
@@ -2612,6 +2670,8 @@ class Midy {
|
|
|
2612
2670
|
if (!channelBitmap[i])
|
|
2613
2671
|
continue;
|
|
2614
2672
|
const channel = this.channels[i];
|
|
2673
|
+
if (channel.isDrum)
|
|
2674
|
+
continue;
|
|
2615
2675
|
for (let j = 0; j < 12; j++) {
|
|
2616
2676
|
const index = 7 + j * 2;
|
|
2617
2677
|
const msb = data[index] & 0x7F;
|
|
@@ -2682,7 +2742,10 @@ class Midy {
|
|
|
2682
2742
|
}
|
|
2683
2743
|
handlePressureSysEx(data, tableName) {
|
|
2684
2744
|
const channelNumber = data[4];
|
|
2685
|
-
const
|
|
2745
|
+
const channel = this.channels[channelNumber];
|
|
2746
|
+
if (channel.isDrum)
|
|
2747
|
+
return;
|
|
2748
|
+
const table = channel[tableName];
|
|
2686
2749
|
for (let i = 5; i < data.length - 1; i += 2) {
|
|
2687
2750
|
const pp = data[i];
|
|
2688
2751
|
const rr = data[i + 1];
|
|
@@ -2704,19 +2767,17 @@ class Midy {
|
|
|
2704
2767
|
const slotSize = 6;
|
|
2705
2768
|
const offset = controllerType * slotSize;
|
|
2706
2769
|
const table = channel.controlTable.subarray(offset, offset + slotSize);
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
const note = noteList[i];
|
|
2710
|
-
if (!note)
|
|
2711
|
-
continue;
|
|
2712
|
-
this.setControllerParameters(channel, note, table);
|
|
2713
|
-
}
|
|
2770
|
+
this.processScheduledNotes(channel, (note) => {
|
|
2771
|
+
this.setControllerParameters(channel, note, table);
|
|
2714
2772
|
});
|
|
2715
2773
|
}
|
|
2716
2774
|
handleControlChangeSysEx(data) {
|
|
2717
2775
|
const channelNumber = data[4];
|
|
2776
|
+
const channel = this.channels[channelNumber];
|
|
2777
|
+
if (channel.isDrum)
|
|
2778
|
+
return;
|
|
2718
2779
|
const controllerType = data[5];
|
|
2719
|
-
const table =
|
|
2780
|
+
const table = channel.controlTable[controllerType];
|
|
2720
2781
|
for (let i = 6; i < data.length - 1; i += 2) {
|
|
2721
2782
|
const pp = data[i];
|
|
2722
2783
|
const rr = data[i + 1];
|
|
@@ -2730,8 +2791,11 @@ class Midy {
|
|
|
2730
2791
|
}
|
|
2731
2792
|
handleKeyBasedInstrumentControlSysEx(data, scheduleTime) {
|
|
2732
2793
|
const channelNumber = data[4];
|
|
2794
|
+
const channel = this.channels[channelNumber];
|
|
2795
|
+
if (channel.isDrum)
|
|
2796
|
+
return;
|
|
2733
2797
|
const keyNumber = data[5];
|
|
2734
|
-
const table =
|
|
2798
|
+
const table = channel.keyBasedInstrumentControlTable;
|
|
2735
2799
|
for (let i = 6; i < data.length - 1; i += 2) {
|
|
2736
2800
|
const controllerType = data[i];
|
|
2737
2801
|
const value = data[i + 1];
|
|
@@ -2752,13 +2816,20 @@ class Midy {
|
|
|
2752
2816
|
}
|
|
2753
2817
|
scheduleTask(callback, scheduleTime) {
|
|
2754
2818
|
return new Promise((resolve) => {
|
|
2755
|
-
const bufferSource = new AudioBufferSourceNode(this.audioContext
|
|
2819
|
+
const bufferSource = new AudioBufferSourceNode(this.audioContext, {
|
|
2820
|
+
buffer: this.schedulerBuffer,
|
|
2821
|
+
});
|
|
2822
|
+
bufferSource.connect(this.scheduler);
|
|
2756
2823
|
bufferSource.onended = () => {
|
|
2757
|
-
|
|
2758
|
-
|
|
2824
|
+
try {
|
|
2825
|
+
callback();
|
|
2826
|
+
}
|
|
2827
|
+
finally {
|
|
2828
|
+
bufferSource.disconnect();
|
|
2829
|
+
resolve();
|
|
2830
|
+
}
|
|
2759
2831
|
};
|
|
2760
2832
|
bufferSource.start(scheduleTime);
|
|
2761
|
-
bufferSource.stop(scheduleTime);
|
|
2762
2833
|
});
|
|
2763
2834
|
}
|
|
2764
2835
|
}
|
|
@@ -2769,6 +2840,7 @@ Object.defineProperty(Midy, "channelSettings", {
|
|
|
2769
2840
|
writable: true,
|
|
2770
2841
|
value: {
|
|
2771
2842
|
currentBufferSource: null,
|
|
2843
|
+
isDrum: false,
|
|
2772
2844
|
detune: 0,
|
|
2773
2845
|
program: 0,
|
|
2774
2846
|
bank: 121 * 128,
|
|
@@ -2778,8 +2850,9 @@ Object.defineProperty(Midy, "channelSettings", {
|
|
|
2778
2850
|
dataLSB: 0,
|
|
2779
2851
|
rpnMSB: 127,
|
|
2780
2852
|
rpnLSB: 127,
|
|
2853
|
+
mono: false, // CC#124, CC#125
|
|
2854
|
+
modulationDepthRange: 50, // cent
|
|
2781
2855
|
fineTuning: 0, // cb
|
|
2782
2856
|
coarseTuning: 0, // cb
|
|
2783
|
-
modulationDepthRange: 50, // cent
|
|
2784
2857
|
}
|
|
2785
2858
|
});
|