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