@marmooo/midy 0.4.2 → 0.4.3
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 +1 -0
- package/esm/midy-GM1.d.ts +2 -1
- package/esm/midy-GM1.d.ts.map +1 -1
- package/esm/midy-GM1.js +129 -75
- package/esm/midy-GM2.d.ts +2 -1
- package/esm/midy-GM2.d.ts.map +1 -1
- package/esm/midy-GM2.js +164 -109
- package/esm/midy-GMLite.d.ts +5 -4
- package/esm/midy-GMLite.d.ts.map +1 -1
- package/esm/midy-GMLite.js +130 -76
- package/esm/midy.d.ts +2 -1
- package/esm/midy.d.ts.map +1 -1
- package/esm/midy.js +174 -118
- package/package.json +1 -1
- package/script/midy-GM1.d.ts +2 -1
- package/script/midy-GM1.d.ts.map +1 -1
- package/script/midy-GM1.js +129 -75
- package/script/midy-GM2.d.ts +2 -1
- package/script/midy-GM2.d.ts.map +1 -1
- package/script/midy-GM2.js +164 -109
- package/script/midy-GMLite.d.ts +5 -4
- package/script/midy-GMLite.d.ts.map +1 -1
- package/script/midy-GMLite.js +130 -76
- package/script/midy.d.ts +2 -1
- package/script/midy.d.ts.map +1 -1
- package/script/midy.js +174 -118
package/script/midy-GM2.js
CHANGED
|
@@ -17,6 +17,12 @@ class Note {
|
|
|
17
17
|
writable: true,
|
|
18
18
|
value: void 0
|
|
19
19
|
});
|
|
20
|
+
Object.defineProperty(this, "adjustedBaseFreq", {
|
|
21
|
+
enumerable: true,
|
|
22
|
+
configurable: true,
|
|
23
|
+
writable: true,
|
|
24
|
+
value: 20000
|
|
25
|
+
});
|
|
20
26
|
Object.defineProperty(this, "index", {
|
|
21
27
|
enumerable: true,
|
|
22
28
|
configurable: true,
|
|
@@ -228,6 +234,12 @@ const pitchEnvelopeKeys = [
|
|
|
228
234
|
"playbackRate",
|
|
229
235
|
];
|
|
230
236
|
const pitchEnvelopeKeySet = new Set(pitchEnvelopeKeys);
|
|
237
|
+
const defaultPressureValues = new Int8Array([64, 64, 64, 0, 0, 0]);
|
|
238
|
+
function cbToRatio(cb) {
|
|
239
|
+
return Math.pow(10, cb / 200);
|
|
240
|
+
}
|
|
241
|
+
const decayCurve = 1 / (-Math.log(cbToRatio(-1000)));
|
|
242
|
+
const releaseCurve = 1 / (-Math.log(cbToRatio(-600)));
|
|
231
243
|
class MidyGM2 extends EventTarget {
|
|
232
244
|
constructor(audioContext) {
|
|
233
245
|
super();
|
|
@@ -391,6 +403,20 @@ class MidyGM2 extends EventTarget {
|
|
|
391
403
|
writable: true,
|
|
392
404
|
value: false
|
|
393
405
|
});
|
|
406
|
+
Object.defineProperty(this, "totalTimeEventTypes", {
|
|
407
|
+
enumerable: true,
|
|
408
|
+
configurable: true,
|
|
409
|
+
writable: true,
|
|
410
|
+
value: new Set([
|
|
411
|
+
"noteOff",
|
|
412
|
+
])
|
|
413
|
+
});
|
|
414
|
+
Object.defineProperty(this, "tempo", {
|
|
415
|
+
enumerable: true,
|
|
416
|
+
configurable: true,
|
|
417
|
+
writable: true,
|
|
418
|
+
value: 1
|
|
419
|
+
});
|
|
394
420
|
Object.defineProperty(this, "loop", {
|
|
395
421
|
enumerable: true,
|
|
396
422
|
configurable: true,
|
|
@@ -510,13 +536,13 @@ class MidyGM2 extends EventTarget {
|
|
|
510
536
|
this.totalTime = this.calcTotalTime();
|
|
511
537
|
}
|
|
512
538
|
cacheVoiceIds() {
|
|
513
|
-
const timeline = this
|
|
539
|
+
const { channels, timeline, voiceCounter } = this;
|
|
514
540
|
for (let i = 0; i < timeline.length; i++) {
|
|
515
541
|
const event = timeline[i];
|
|
516
542
|
switch (event.type) {
|
|
517
543
|
case "noteOn": {
|
|
518
|
-
const audioBufferId = this.getVoiceId(
|
|
519
|
-
|
|
544
|
+
const audioBufferId = this.getVoiceId(channels[event.channel], event.noteNumber, event.velocity);
|
|
545
|
+
voiceCounter.set(audioBufferId, (voiceCounter.get(audioBufferId) ?? 0) + 1);
|
|
520
546
|
break;
|
|
521
547
|
}
|
|
522
548
|
case "controller":
|
|
@@ -531,9 +557,9 @@ class MidyGM2 extends EventTarget {
|
|
|
531
557
|
this.setProgramChange(event.channel, event.programNumber, event.startTime);
|
|
532
558
|
}
|
|
533
559
|
}
|
|
534
|
-
for (const [audioBufferId, count] of
|
|
560
|
+
for (const [audioBufferId, count] of voiceCounter) {
|
|
535
561
|
if (count === 1)
|
|
536
|
-
|
|
562
|
+
voiceCounter.delete(audioBufferId);
|
|
537
563
|
}
|
|
538
564
|
this.GM2SystemOn();
|
|
539
565
|
}
|
|
@@ -542,8 +568,12 @@ class MidyGM2 extends EventTarget {
|
|
|
542
568
|
const bankTable = this.soundFontTable[programNumber];
|
|
543
569
|
if (!bankTable)
|
|
544
570
|
return;
|
|
545
|
-
|
|
546
|
-
|
|
571
|
+
let bank = channel.isDrum ? 128 : channel.bankLSB;
|
|
572
|
+
if (bankTable[bank] === undefined) {
|
|
573
|
+
if (channel.isDrum)
|
|
574
|
+
return;
|
|
575
|
+
bank = 0;
|
|
576
|
+
}
|
|
547
577
|
const soundFontIndex = bankTable[bank];
|
|
548
578
|
if (soundFontIndex === undefined)
|
|
549
579
|
return;
|
|
@@ -569,7 +599,7 @@ class MidyGM2 extends EventTarget {
|
|
|
569
599
|
resetChannelTable(channel) {
|
|
570
600
|
channel.controlTable.fill(-1);
|
|
571
601
|
channel.scaleOctaveTuningTable.fill(0); // [-100, 100] cent
|
|
572
|
-
channel.channelPressureTable.
|
|
602
|
+
channel.channelPressureTable.set(defaultPressureValues);
|
|
573
603
|
channel.keyBasedTable.fill(-1);
|
|
574
604
|
}
|
|
575
605
|
createChannels(audioContext) {
|
|
@@ -585,7 +615,7 @@ class MidyGM2 extends EventTarget {
|
|
|
585
615
|
sostenutoNotes: [],
|
|
586
616
|
controlTable: this.initControlTable(),
|
|
587
617
|
scaleOctaveTuningTable: new Int8Array(12), // [-64, 63] cent
|
|
588
|
-
channelPressureTable: new Int8Array(
|
|
618
|
+
channelPressureTable: new Int8Array(defaultPressureValues),
|
|
589
619
|
keyBasedTable: new Int8Array(128 * 128).fill(-1),
|
|
590
620
|
keyBasedGainLs: new Array(128),
|
|
591
621
|
keyBasedGainRs: new Array(128),
|
|
@@ -621,11 +651,13 @@ class MidyGM2 extends EventTarget {
|
|
|
621
651
|
const lookAheadCheckTime = scheduleTime + timeOffset + this.lookAhead;
|
|
622
652
|
const schedulingOffset = this.startDelay - timeOffset;
|
|
623
653
|
const timeline = this.timeline;
|
|
654
|
+
const inverseTempo = 1 / this.tempo;
|
|
624
655
|
while (queueIndex < timeline.length) {
|
|
625
656
|
const event = timeline[queueIndex];
|
|
626
|
-
|
|
657
|
+
const t = event.startTime * inverseTempo;
|
|
658
|
+
if (lookAheadCheckTime < t)
|
|
627
659
|
break;
|
|
628
|
-
const startTime =
|
|
660
|
+
const startTime = t + schedulingOffset;
|
|
629
661
|
switch (event.type) {
|
|
630
662
|
case "noteOn":
|
|
631
663
|
this.noteOn(event.channel, event.noteNumber, event.velocity, startTime);
|
|
@@ -654,8 +686,10 @@ class MidyGM2 extends EventTarget {
|
|
|
654
686
|
return queueIndex;
|
|
655
687
|
}
|
|
656
688
|
getQueueIndex(second) {
|
|
657
|
-
|
|
658
|
-
|
|
689
|
+
const timeline = this.timeline;
|
|
690
|
+
const inverseTempo = 1 / this.tempo;
|
|
691
|
+
for (let i = 0; i < timeline.length; i++) {
|
|
692
|
+
if (second <= timeline[i].startTime * inverseTempo) {
|
|
659
693
|
return i;
|
|
660
694
|
}
|
|
661
695
|
}
|
|
@@ -666,40 +700,44 @@ class MidyGM2 extends EventTarget {
|
|
|
666
700
|
this.drumExclusiveClassNotes.fill(undefined);
|
|
667
701
|
this.voiceCache.clear();
|
|
668
702
|
this.realtimeVoiceCache.clear();
|
|
669
|
-
|
|
670
|
-
|
|
703
|
+
const channels = this.channels;
|
|
704
|
+
for (let i = 0; i < channels.length; i++) {
|
|
705
|
+
channels[i].scheduledNotes = [];
|
|
671
706
|
this.resetChannelStates(i);
|
|
672
707
|
}
|
|
673
708
|
}
|
|
674
709
|
updateStates(queueIndex, nextQueueIndex) {
|
|
710
|
+
const { timeline, resumeTime } = this;
|
|
711
|
+
const inverseTempo = 1 / this.tempo;
|
|
675
712
|
const now = this.audioContext.currentTime;
|
|
676
713
|
if (nextQueueIndex < queueIndex)
|
|
677
714
|
queueIndex = 0;
|
|
678
715
|
for (let i = queueIndex; i < nextQueueIndex; i++) {
|
|
679
|
-
const event =
|
|
716
|
+
const event = timeline[i];
|
|
680
717
|
switch (event.type) {
|
|
681
718
|
case "controller":
|
|
682
|
-
this.setControlChange(event.channel, event.controllerType, event.value, now -
|
|
719
|
+
this.setControlChange(event.channel, event.controllerType, event.value, now - resumeTime + event.startTime * inverseTempo);
|
|
683
720
|
break;
|
|
684
721
|
case "programChange":
|
|
685
|
-
this.setProgramChange(event.channel, event.programNumber, now -
|
|
722
|
+
this.setProgramChange(event.channel, event.programNumber, now - resumeTime + event.startTime * inverseTempo);
|
|
686
723
|
break;
|
|
687
724
|
case "pitchBend":
|
|
688
|
-
this.setPitchBend(event.channel, event.value + 8192, now -
|
|
725
|
+
this.setPitchBend(event.channel, event.value + 8192, now - resumeTime + event.startTime * inverseTempo);
|
|
689
726
|
break;
|
|
690
727
|
case "sysEx":
|
|
691
|
-
this.handleSysEx(event.data, now -
|
|
728
|
+
this.handleSysEx(event.data, now - resumeTime + event.startTime * inverseTempo);
|
|
692
729
|
}
|
|
693
730
|
}
|
|
694
731
|
}
|
|
695
732
|
async playNotes() {
|
|
696
|
-
|
|
697
|
-
|
|
733
|
+
const audioContext = this.audioContext;
|
|
734
|
+
if (audioContext.state === "suspended") {
|
|
735
|
+
await audioContext.resume();
|
|
698
736
|
}
|
|
699
737
|
const paused = this.isPaused;
|
|
700
738
|
this.isPlaying = true;
|
|
701
739
|
this.isPaused = false;
|
|
702
|
-
this.startTime =
|
|
740
|
+
this.startTime = audioContext.currentTime;
|
|
703
741
|
if (paused) {
|
|
704
742
|
this.dispatchEvent(new Event("resumed"));
|
|
705
743
|
}
|
|
@@ -710,49 +748,48 @@ class MidyGM2 extends EventTarget {
|
|
|
710
748
|
let exitReason;
|
|
711
749
|
this.notePromises = [];
|
|
712
750
|
while (true) {
|
|
713
|
-
const now =
|
|
751
|
+
const now = audioContext.currentTime;
|
|
714
752
|
if (0 < this.lastActiveSensing &&
|
|
715
753
|
this.activeSensingThreshold < performance.now() - this.lastActiveSensing) {
|
|
716
754
|
await this.stopNotes(0, true, now);
|
|
717
|
-
await
|
|
755
|
+
await audioContext.suspend();
|
|
718
756
|
exitReason = "aborted";
|
|
719
757
|
break;
|
|
720
758
|
}
|
|
721
|
-
if (this.
|
|
759
|
+
if (this.totalTime < this.currentTime() ||
|
|
760
|
+
this.timeline.length <= queueIndex) {
|
|
722
761
|
await this.stopNotes(0, true, now);
|
|
723
762
|
if (this.loop) {
|
|
724
|
-
this.notePromises = [];
|
|
725
763
|
this.resetAllStates();
|
|
726
|
-
this.startTime =
|
|
764
|
+
this.startTime = audioContext.currentTime;
|
|
727
765
|
this.resumeTime = 0;
|
|
728
766
|
queueIndex = 0;
|
|
729
767
|
this.dispatchEvent(new Event("looped"));
|
|
730
768
|
continue;
|
|
731
769
|
}
|
|
732
770
|
else {
|
|
733
|
-
await
|
|
771
|
+
await audioContext.suspend();
|
|
734
772
|
exitReason = "ended";
|
|
735
773
|
break;
|
|
736
774
|
}
|
|
737
775
|
}
|
|
738
776
|
if (this.isPausing) {
|
|
739
777
|
await this.stopNotes(0, true, now);
|
|
740
|
-
await
|
|
741
|
-
this.notePromises = [];
|
|
778
|
+
await audioContext.suspend();
|
|
742
779
|
this.isPausing = false;
|
|
743
780
|
exitReason = "paused";
|
|
744
781
|
break;
|
|
745
782
|
}
|
|
746
783
|
else if (this.isStopping) {
|
|
747
784
|
await this.stopNotes(0, true, now);
|
|
748
|
-
await
|
|
785
|
+
await audioContext.suspend();
|
|
749
786
|
this.isStopping = false;
|
|
750
787
|
exitReason = "stopped";
|
|
751
788
|
break;
|
|
752
789
|
}
|
|
753
790
|
else if (this.isSeeking) {
|
|
754
791
|
this.stopNotes(0, true, now);
|
|
755
|
-
this.startTime =
|
|
792
|
+
this.startTime = audioContext.currentTime;
|
|
756
793
|
const nextQueueIndex = this.getQueueIndex(this.resumeTime);
|
|
757
794
|
this.updateStates(queueIndex, nextQueueIndex);
|
|
758
795
|
queueIndex = nextQueueIndex;
|
|
@@ -765,7 +802,6 @@ class MidyGM2 extends EventTarget {
|
|
|
765
802
|
await this.scheduleTask(() => { }, waitTime);
|
|
766
803
|
}
|
|
767
804
|
if (exitReason !== "paused") {
|
|
768
|
-
this.notePromises = [];
|
|
769
805
|
this.resetAllStates();
|
|
770
806
|
this.lastActiveSensing = 0;
|
|
771
807
|
}
|
|
@@ -894,11 +930,13 @@ class MidyGM2 extends EventTarget {
|
|
|
894
930
|
return Promise.all(promises);
|
|
895
931
|
}
|
|
896
932
|
stopNotes(velocity, force, scheduleTime) {
|
|
897
|
-
const
|
|
898
|
-
for (let i = 0; i <
|
|
899
|
-
|
|
933
|
+
const channels = this.channels;
|
|
934
|
+
for (let i = 0; i < channels.length; i++) {
|
|
935
|
+
this.stopChannelNotes(i, velocity, force, scheduleTime);
|
|
900
936
|
}
|
|
901
|
-
|
|
937
|
+
const stopPromise = Promise.all(this.notePromises);
|
|
938
|
+
this.notePromises = [];
|
|
939
|
+
return stopPromise;
|
|
902
940
|
}
|
|
903
941
|
async start() {
|
|
904
942
|
if (this.isPlaying || this.isPaused)
|
|
@@ -936,11 +974,17 @@ class MidyGM2 extends EventTarget {
|
|
|
936
974
|
}
|
|
937
975
|
}
|
|
938
976
|
calcTotalTime() {
|
|
977
|
+
const totalTimeEventTypes = this.totalTimeEventTypes;
|
|
978
|
+
const timeline = this.timeline;
|
|
979
|
+
const inverseTempo = 1 / this.tempo;
|
|
939
980
|
let totalTime = 0;
|
|
940
|
-
for (let i = 0; i <
|
|
941
|
-
const event =
|
|
942
|
-
if (
|
|
943
|
-
|
|
981
|
+
for (let i = 0; i < timeline.length; i++) {
|
|
982
|
+
const event = timeline[i];
|
|
983
|
+
if (!totalTimeEventTypes.has(event.type))
|
|
984
|
+
continue;
|
|
985
|
+
const t = event.startTime * inverseTempo;
|
|
986
|
+
if (totalTime < t)
|
|
987
|
+
totalTime = t;
|
|
944
988
|
}
|
|
945
989
|
return totalTime + this.startDelay;
|
|
946
990
|
}
|
|
@@ -1122,9 +1166,6 @@ class MidyGM2 extends EventTarget {
|
|
|
1122
1166
|
feedbackGains,
|
|
1123
1167
|
};
|
|
1124
1168
|
}
|
|
1125
|
-
cbToRatio(cb) {
|
|
1126
|
-
return Math.pow(10, cb / 200);
|
|
1127
|
-
}
|
|
1128
1169
|
rateToCent(rate) {
|
|
1129
1170
|
return 1200 * Math.log2(rate);
|
|
1130
1171
|
}
|
|
@@ -1243,45 +1284,45 @@ class MidyGM2 extends EventTarget {
|
|
|
1243
1284
|
}
|
|
1244
1285
|
setPortamentoVolumeEnvelope(channel, note, scheduleTime) {
|
|
1245
1286
|
const { voiceParams, startTime } = note;
|
|
1246
|
-
const attackVolume =
|
|
1287
|
+
const attackVolume = cbToRatio(-voiceParams.initialAttenuation) *
|
|
1247
1288
|
(1 + this.getAmplitudeControl(channel));
|
|
1248
1289
|
const sustainVolume = attackVolume * (1 - voiceParams.volSustain);
|
|
1249
|
-
const
|
|
1250
|
-
const volAttack = volDelay + voiceParams.volAttack;
|
|
1251
|
-
const volHold = volAttack + voiceParams.volHold;
|
|
1290
|
+
const portamentoTime = startTime + this.getPortamentoTime(channel, note);
|
|
1252
1291
|
note.volumeEnvelopeNode.gain
|
|
1253
1292
|
.cancelScheduledValues(scheduleTime)
|
|
1254
|
-
.
|
|
1293
|
+
.linearRampToValueAtTime(sustainVolume, portamentoTime);
|
|
1255
1294
|
}
|
|
1256
1295
|
setVolumeEnvelope(channel, note, scheduleTime) {
|
|
1257
1296
|
const { voiceParams, startTime } = note;
|
|
1258
|
-
const attackVolume =
|
|
1297
|
+
const attackVolume = cbToRatio(-voiceParams.initialAttenuation) *
|
|
1259
1298
|
(1 + this.getAmplitudeControl(channel));
|
|
1260
1299
|
const sustainVolume = attackVolume * (1 - voiceParams.volSustain);
|
|
1261
1300
|
const volDelay = startTime + voiceParams.volDelay;
|
|
1262
1301
|
const volAttack = volDelay + voiceParams.volAttack;
|
|
1263
1302
|
const volHold = volAttack + voiceParams.volHold;
|
|
1264
|
-
const
|
|
1303
|
+
const decayDuration = voiceParams.volDecay;
|
|
1265
1304
|
note.volumeEnvelopeNode.gain
|
|
1266
1305
|
.cancelScheduledValues(scheduleTime)
|
|
1267
1306
|
.setValueAtTime(0, startTime)
|
|
1268
|
-
.setValueAtTime(
|
|
1269
|
-
.
|
|
1307
|
+
.setValueAtTime(0, volDelay)
|
|
1308
|
+
.linearRampToValueAtTime(attackVolume, volAttack)
|
|
1270
1309
|
.setValueAtTime(attackVolume, volHold)
|
|
1271
|
-
.
|
|
1310
|
+
.setTargetAtTime(sustainVolume, volHold, decayDuration * decayCurve);
|
|
1272
1311
|
}
|
|
1273
1312
|
setPortamentoPitchEnvelope(note, scheduleTime) {
|
|
1274
1313
|
const baseRate = note.voiceParams.playbackRate;
|
|
1314
|
+
const portamentoTime = note.startTime +
|
|
1315
|
+
this.getPortamentoTime(channel, note);
|
|
1275
1316
|
note.bufferSource.playbackRate
|
|
1276
1317
|
.cancelScheduledValues(scheduleTime)
|
|
1277
|
-
.
|
|
1318
|
+
.linearRampToValueAtTime(baseRate, portamentoTime);
|
|
1278
1319
|
}
|
|
1279
1320
|
setPitchEnvelope(note, scheduleTime) {
|
|
1280
1321
|
const { voiceParams } = note;
|
|
1281
1322
|
const baseRate = voiceParams.playbackRate;
|
|
1282
1323
|
note.bufferSource.playbackRate
|
|
1283
1324
|
.cancelScheduledValues(scheduleTime)
|
|
1284
|
-
.setValueAtTime(baseRate,
|
|
1325
|
+
.setValueAtTime(baseRate, note.startTime);
|
|
1285
1326
|
const modEnvToPitch = voiceParams.modEnvToPitch;
|
|
1286
1327
|
if (modEnvToPitch === 0)
|
|
1287
1328
|
return;
|
|
@@ -1291,12 +1332,12 @@ class MidyGM2 extends EventTarget {
|
|
|
1291
1332
|
const modDelay = note.startTime + voiceParams.modDelay;
|
|
1292
1333
|
const modAttack = modDelay + voiceParams.modAttack;
|
|
1293
1334
|
const modHold = modAttack + voiceParams.modHold;
|
|
1294
|
-
const
|
|
1335
|
+
const decayDuration = voiceParams.modDecay;
|
|
1295
1336
|
note.bufferSource.playbackRate
|
|
1296
1337
|
.setValueAtTime(baseRate, modDelay)
|
|
1297
|
-
.
|
|
1338
|
+
.linearRampToValueAtTime(peekRate, modAttack)
|
|
1298
1339
|
.setValueAtTime(peekRate, modHold)
|
|
1299
|
-
.
|
|
1340
|
+
.setTargetAtTime(baseRate, modHold, decayDuration * decayCurve);
|
|
1300
1341
|
}
|
|
1301
1342
|
clampCutoffFrequency(frequency) {
|
|
1302
1343
|
const minFrequency = 20; // min Hz of initialFilterFc
|
|
@@ -1305,17 +1346,18 @@ class MidyGM2 extends EventTarget {
|
|
|
1305
1346
|
}
|
|
1306
1347
|
setPortamentoFilterEnvelope(channel, note, scheduleTime) {
|
|
1307
1348
|
const { voiceParams, startTime } = note;
|
|
1308
|
-
const
|
|
1349
|
+
const scale = this.getSoftPedalFactor(channel, note);
|
|
1309
1350
|
const baseCent = voiceParams.initialFilterFc +
|
|
1310
1351
|
this.getFilterCutoffControl(channel);
|
|
1311
|
-
const
|
|
1312
|
-
|
|
1313
|
-
const
|
|
1314
|
-
|
|
1315
|
-
const adjustedBaseFreq = this.clampCutoffFrequency(baseFreq);
|
|
1316
|
-
const adjustedSustainFreq = this.clampCutoffFrequency(sustainFreq);
|
|
1352
|
+
const sustainCent = baseCent +
|
|
1353
|
+
voiceParams.modEnvToFilterFc * (1 - voiceParams.modSustain);
|
|
1354
|
+
const baseFreq = this.centToHz(baseCent);
|
|
1355
|
+
const sustainFreq = this.centToHz(sustainCent);
|
|
1356
|
+
const adjustedBaseFreq = this.clampCutoffFrequency(baseFreq * scale);
|
|
1357
|
+
const adjustedSustainFreq = this.clampCutoffFrequency(sustainFreq * scale);
|
|
1317
1358
|
const portamentoTime = startTime + this.getPortamentoTime(channel, note);
|
|
1318
1359
|
const modDelay = startTime + voiceParams.modDelay;
|
|
1360
|
+
note.adjustedBaseFreq = adjustedSustainFreq;
|
|
1319
1361
|
note.filterNode.frequency
|
|
1320
1362
|
.cancelScheduledValues(scheduleTime)
|
|
1321
1363
|
.setValueAtTime(adjustedBaseFreq, startTime)
|
|
@@ -1324,40 +1366,44 @@ class MidyGM2 extends EventTarget {
|
|
|
1324
1366
|
}
|
|
1325
1367
|
setFilterEnvelope(channel, note, scheduleTime) {
|
|
1326
1368
|
const { voiceParams, startTime } = note;
|
|
1327
|
-
const
|
|
1369
|
+
const modEnvToFilterFc = voiceParams.modEnvToFilterFc;
|
|
1328
1370
|
const baseCent = voiceParams.initialFilterFc +
|
|
1329
1371
|
this.getFilterCutoffControl(channel);
|
|
1372
|
+
const peekCent = baseCent + modEnvToFilterFc;
|
|
1373
|
+
const sustainCent = baseCent +
|
|
1374
|
+
modEnvToFilterFc * (1 - voiceParams.modSustain);
|
|
1375
|
+
const softPedalFactor = this.getSoftPedalFactor(channel, note);
|
|
1330
1376
|
const baseFreq = this.centToHz(baseCent) * softPedalFactor;
|
|
1331
|
-
const peekFreq = this.centToHz(
|
|
1332
|
-
|
|
1333
|
-
const sustainFreq = baseFreq +
|
|
1334
|
-
(peekFreq - baseFreq) * (1 - voiceParams.modSustain);
|
|
1377
|
+
const peekFreq = this.centToHz(peekCent) * softPedalFactor;
|
|
1378
|
+
const sustainFreq = this.centToHz(sustainCent) * softPedalFactor;
|
|
1335
1379
|
const adjustedBaseFreq = this.clampCutoffFrequency(baseFreq);
|
|
1336
1380
|
const adjustedPeekFreq = this.clampCutoffFrequency(peekFreq);
|
|
1337
1381
|
const adjustedSustainFreq = this.clampCutoffFrequency(sustainFreq);
|
|
1338
1382
|
const modDelay = startTime + voiceParams.modDelay;
|
|
1339
1383
|
const modAttack = modDelay + voiceParams.modAttack;
|
|
1340
1384
|
const modHold = modAttack + voiceParams.modHold;
|
|
1341
|
-
const
|
|
1385
|
+
const decayDuration = voiceParams.modDecay;
|
|
1386
|
+
note.adjustedBaseFreq = adjustedBaseFreq;
|
|
1342
1387
|
note.filterNode.frequency
|
|
1343
1388
|
.cancelScheduledValues(scheduleTime)
|
|
1344
1389
|
.setValueAtTime(adjustedBaseFreq, startTime)
|
|
1345
1390
|
.setValueAtTime(adjustedBaseFreq, modDelay)
|
|
1346
|
-
.
|
|
1391
|
+
.linearRampToValueAtTime(adjustedPeekFreq, modAttack)
|
|
1347
1392
|
.setValueAtTime(adjustedPeekFreq, modHold)
|
|
1348
|
-
.
|
|
1393
|
+
.setTargetAtTime(adjustedSustainFreq, modHold, decayDuration * decayCurve);
|
|
1349
1394
|
}
|
|
1350
1395
|
startModulation(channel, note, scheduleTime) {
|
|
1396
|
+
const audioContext = this.audioContext;
|
|
1351
1397
|
const { voiceParams } = note;
|
|
1352
|
-
note.modulationLFO = new OscillatorNode(
|
|
1398
|
+
note.modulationLFO = new OscillatorNode(audioContext, {
|
|
1353
1399
|
frequency: this.centToHz(voiceParams.freqModLFO),
|
|
1354
1400
|
});
|
|
1355
|
-
note.filterDepth = new GainNode(
|
|
1401
|
+
note.filterDepth = new GainNode(audioContext, {
|
|
1356
1402
|
gain: voiceParams.modLfoToFilterFc,
|
|
1357
1403
|
});
|
|
1358
|
-
note.modulationDepth = new GainNode(
|
|
1404
|
+
note.modulationDepth = new GainNode(audioContext);
|
|
1359
1405
|
this.setModLfoToPitch(channel, note, scheduleTime);
|
|
1360
|
-
note.volumeDepth = new GainNode(
|
|
1406
|
+
note.volumeDepth = new GainNode(audioContext);
|
|
1361
1407
|
this.setModLfoToVolume(note, scheduleTime);
|
|
1362
1408
|
note.modulationLFO.start(note.startTime + voiceParams.delayModLFO);
|
|
1363
1409
|
note.modulationLFO.connect(note.filterDepth);
|
|
@@ -1410,7 +1456,8 @@ class MidyGM2 extends EventTarget {
|
|
|
1410
1456
|
}
|
|
1411
1457
|
}
|
|
1412
1458
|
async setNoteAudioNode(channel, note, realtime) {
|
|
1413
|
-
const
|
|
1459
|
+
const audioContext = this.audioContext;
|
|
1460
|
+
const now = audioContext.currentTime;
|
|
1414
1461
|
const { noteNumber, velocity, startTime } = note;
|
|
1415
1462
|
const state = channel.state;
|
|
1416
1463
|
const controllerState = this.getControllerState(channel, noteNumber, velocity);
|
|
@@ -1418,8 +1465,8 @@ class MidyGM2 extends EventTarget {
|
|
|
1418
1465
|
note.voiceParams = voiceParams;
|
|
1419
1466
|
const audioBuffer = await this.getAudioBuffer(channel, noteNumber, velocity, voiceParams, realtime);
|
|
1420
1467
|
note.bufferSource = this.createBufferSource(channel, noteNumber, voiceParams, audioBuffer);
|
|
1421
|
-
note.volumeEnvelopeNode = new GainNode(
|
|
1422
|
-
note.filterNode = new BiquadFilterNode(
|
|
1468
|
+
note.volumeEnvelopeNode = new GainNode(audioContext);
|
|
1469
|
+
note.filterNode = new BiquadFilterNode(audioContext, {
|
|
1423
1470
|
type: "lowpass",
|
|
1424
1471
|
Q: voiceParams.initialFilterQ / 10, // dB
|
|
1425
1472
|
});
|
|
@@ -1532,8 +1579,12 @@ class MidyGM2 extends EventTarget {
|
|
|
1532
1579
|
const bankTable = this.soundFontTable[programNumber];
|
|
1533
1580
|
if (!bankTable)
|
|
1534
1581
|
return;
|
|
1535
|
-
|
|
1536
|
-
|
|
1582
|
+
let bank = channel.isDrum ? 128 : channel.bankLSB;
|
|
1583
|
+
if (bankTable[bank] === undefined) {
|
|
1584
|
+
if (channel.isDrum)
|
|
1585
|
+
return;
|
|
1586
|
+
bank = 0;
|
|
1587
|
+
}
|
|
1537
1588
|
const soundFontIndex = bankTable[bank];
|
|
1538
1589
|
if (soundFontIndex === undefined)
|
|
1539
1590
|
return;
|
|
@@ -1567,24 +1618,24 @@ class MidyGM2 extends EventTarget {
|
|
|
1567
1618
|
}
|
|
1568
1619
|
releaseNote(channel, note, endTime) {
|
|
1569
1620
|
endTime ??= this.audioContext.currentTime;
|
|
1570
|
-
const
|
|
1621
|
+
const duration = note.voiceParams.volRelease;
|
|
1622
|
+
const volRelease = endTime + duration;
|
|
1571
1623
|
const modRelease = endTime + note.voiceParams.modRelease;
|
|
1572
|
-
const stopTime = Math.min(volRelease, modRelease);
|
|
1573
1624
|
note.filterNode.frequency
|
|
1574
1625
|
.cancelScheduledValues(endTime)
|
|
1575
|
-
.linearRampToValueAtTime(
|
|
1626
|
+
.linearRampToValueAtTime(note.adjustedBaseFreq, modRelease);
|
|
1576
1627
|
note.volumeEnvelopeNode.gain
|
|
1577
1628
|
.cancelScheduledValues(endTime)
|
|
1578
|
-
.
|
|
1629
|
+
.setTargetAtTime(0, endTime, duration * releaseCurve);
|
|
1579
1630
|
return new Promise((resolve) => {
|
|
1580
1631
|
this.scheduleTask(() => {
|
|
1581
1632
|
const bufferSource = note.bufferSource;
|
|
1582
1633
|
bufferSource.loop = false;
|
|
1583
|
-
bufferSource.stop(
|
|
1634
|
+
bufferSource.stop(volRelease);
|
|
1584
1635
|
this.disconnectNote(note);
|
|
1585
1636
|
channel.scheduledNotes[note.index] = undefined;
|
|
1586
1637
|
resolve();
|
|
1587
|
-
},
|
|
1638
|
+
}, volRelease);
|
|
1588
1639
|
});
|
|
1589
1640
|
}
|
|
1590
1641
|
noteOff(channelNumber, noteNumber, _velocity, endTime, force) {
|
|
@@ -1706,6 +1757,8 @@ class MidyGM2 extends EventTarget {
|
|
|
1706
1757
|
}
|
|
1707
1758
|
}
|
|
1708
1759
|
setChannelPressure(channelNumber, value, scheduleTime) {
|
|
1760
|
+
if (!(0 <= scheduleTime))
|
|
1761
|
+
scheduleTime = this.audioContext.currentTime;
|
|
1709
1762
|
const channel = this.channels[channelNumber];
|
|
1710
1763
|
if (channel.isDrum)
|
|
1711
1764
|
return;
|
|
@@ -1779,7 +1832,7 @@ class MidyGM2 extends EventTarget {
|
|
|
1779
1832
|
}
|
|
1780
1833
|
setModLfoToVolume(channel, note, scheduleTime) {
|
|
1781
1834
|
const modLfoToVolume = note.voiceParams.modLfoToVolume;
|
|
1782
|
-
const baseDepth =
|
|
1835
|
+
const baseDepth = cbToRatio(Math.abs(modLfoToVolume)) - 1;
|
|
1783
1836
|
const volumeDepth = baseDepth * Math.sign(modLfoToVolume) *
|
|
1784
1837
|
(1 + this.getLFOAmplitudeDepth(channel));
|
|
1785
1838
|
note.volumeDepth.gain
|
|
@@ -2467,32 +2520,34 @@ class MidyGM2 extends EventTarget {
|
|
|
2467
2520
|
}
|
|
2468
2521
|
}
|
|
2469
2522
|
GM1SystemOn(scheduleTime) {
|
|
2523
|
+
const channels = this.channels;
|
|
2470
2524
|
if (!(0 <= scheduleTime))
|
|
2471
2525
|
scheduleTime = this.audioContext.currentTime;
|
|
2472
2526
|
this.mode = "GM1";
|
|
2473
|
-
for (let i = 0; i <
|
|
2527
|
+
for (let i = 0; i < channels.length; i++) {
|
|
2474
2528
|
this.allSoundOff(i, 0, scheduleTime);
|
|
2475
|
-
const channel =
|
|
2529
|
+
const channel = channels[i];
|
|
2476
2530
|
channel.bankMSB = 0;
|
|
2477
2531
|
channel.bankLSB = 0;
|
|
2478
2532
|
channel.isDrum = false;
|
|
2479
2533
|
}
|
|
2480
|
-
|
|
2481
|
-
|
|
2534
|
+
channels[9].bankMSB = 1;
|
|
2535
|
+
channels[9].isDrum = true;
|
|
2482
2536
|
}
|
|
2483
2537
|
GM2SystemOn(scheduleTime) {
|
|
2538
|
+
const channels = this.channels;
|
|
2484
2539
|
if (!(0 <= scheduleTime))
|
|
2485
2540
|
scheduleTime = this.audioContext.currentTime;
|
|
2486
2541
|
this.mode = "GM2";
|
|
2487
|
-
for (let i = 0; i <
|
|
2542
|
+
for (let i = 0; i < channels.length; i++) {
|
|
2488
2543
|
this.allSoundOff(i, 0, scheduleTime);
|
|
2489
|
-
const channel =
|
|
2544
|
+
const channel = channels[i];
|
|
2490
2545
|
channel.bankMSB = 121;
|
|
2491
2546
|
channel.bankLSB = 0;
|
|
2492
2547
|
channel.isDrum = false;
|
|
2493
2548
|
}
|
|
2494
|
-
|
|
2495
|
-
|
|
2549
|
+
channels[9].bankMSB = 120;
|
|
2550
|
+
channels[9].isDrum = true;
|
|
2496
2551
|
}
|
|
2497
2552
|
handleUniversalRealTimeExclusiveMessage(data, scheduleTime) {
|
|
2498
2553
|
switch (data[2]) {
|
|
@@ -2806,9 +2861,9 @@ class MidyGM2 extends EventTarget {
|
|
|
2806
2861
|
getAmplitudeControl(channel) {
|
|
2807
2862
|
const channelPressureRaw = channel.channelPressureTable[2];
|
|
2808
2863
|
const channelPressure = (0 <= channelPressureRaw)
|
|
2809
|
-
?
|
|
2864
|
+
? channel.state.channelPressure * 127 / channelPressureRaw
|
|
2810
2865
|
: 0;
|
|
2811
|
-
return channelPressure
|
|
2866
|
+
return channelPressure;
|
|
2812
2867
|
}
|
|
2813
2868
|
getLFOPitchDepth(channel) {
|
|
2814
2869
|
const channelPressureRaw = channel.channelPressureTable[3];
|
|
@@ -2832,27 +2887,27 @@ class MidyGM2 extends EventTarget {
|
|
|
2832
2887
|
return channelPressure / 127;
|
|
2833
2888
|
}
|
|
2834
2889
|
setEffects(channel, note, table, scheduleTime) {
|
|
2835
|
-
if (0
|
|
2890
|
+
if (0 < table[0])
|
|
2836
2891
|
this.updateDetune(channel, note, scheduleTime);
|
|
2837
2892
|
if (0.5 <= channel.state.portamemento && 0 <= note.portamentoNoteNumber) {
|
|
2838
|
-
if (0
|
|
2893
|
+
if (0 < table[1]) {
|
|
2839
2894
|
this.setPortamentoFilterEnvelope(channel, note, scheduleTime);
|
|
2840
2895
|
}
|
|
2841
|
-
if (0
|
|
2896
|
+
if (0 < table[2]) {
|
|
2842
2897
|
this.setPortamentoVolumeEnvelope(channel, note, scheduleTime);
|
|
2843
2898
|
}
|
|
2844
2899
|
}
|
|
2845
2900
|
else {
|
|
2846
|
-
if (0
|
|
2901
|
+
if (0 < table[1])
|
|
2847
2902
|
this.setFilterEnvelope(channel, note, scheduleTime);
|
|
2848
|
-
if (0
|
|
2903
|
+
if (0 < table[2])
|
|
2849
2904
|
this.setVolumeEnvelope(channel, note, scheduleTime);
|
|
2850
2905
|
}
|
|
2851
|
-
if (0
|
|
2906
|
+
if (0 < table[3])
|
|
2852
2907
|
this.setModLfoToPitch(channel, note, scheduleTime);
|
|
2853
|
-
if (0
|
|
2908
|
+
if (0 < table[4])
|
|
2854
2909
|
this.setModLfoToFilterFc(channel, note, scheduleTime);
|
|
2855
|
-
if (0
|
|
2910
|
+
if (0 < table[5])
|
|
2856
2911
|
this.setModLfoToVolume(channel, note, scheduleTime);
|
|
2857
2912
|
}
|
|
2858
2913
|
handlePressureSysEx(data, tableName, scheduleTime) {
|
package/script/midy-GMLite.d.ts
CHANGED
|
@@ -29,6 +29,8 @@ export class MidyGMLite extends EventTarget {
|
|
|
29
29
|
isPaused: boolean;
|
|
30
30
|
isStopping: boolean;
|
|
31
31
|
isSeeking: boolean;
|
|
32
|
+
totalTimeEventTypes: Set<string>;
|
|
33
|
+
tempo: number;
|
|
32
34
|
loop: boolean;
|
|
33
35
|
playPromise: any;
|
|
34
36
|
timeline: any[];
|
|
@@ -93,7 +95,6 @@ export class MidyGMLite extends EventTarget {
|
|
|
93
95
|
currentTime(): number;
|
|
94
96
|
processScheduledNotes(channel: any, callback: any): Promise<void>;
|
|
95
97
|
processActiveNotes(channel: any, scheduleTime: any, callback: any): Promise<void>;
|
|
96
|
-
cbToRatio(cb: any): number;
|
|
97
98
|
rateToCent(rate: any): number;
|
|
98
99
|
centToRate(cent: any): number;
|
|
99
100
|
centToHz(cent: any): number;
|
|
@@ -113,13 +114,13 @@ export class MidyGMLite extends EventTarget {
|
|
|
113
114
|
noteOn(channelNumber: any, noteNumber: any, velocity: any, startTime: any): Promise<void>;
|
|
114
115
|
disconnectNote(note: any): void;
|
|
115
116
|
releaseNote(channel: any, note: any, endTime: any): Promise<any>;
|
|
116
|
-
noteOff(channelNumber: any, noteNumber: any, _velocity: any, endTime: any, force: any):
|
|
117
|
+
noteOff(channelNumber: any, noteNumber: any, _velocity: any, endTime: any, force: any): any;
|
|
117
118
|
setNoteIndex(channel: any, index: any): void;
|
|
118
119
|
findNoteOffIndex(channel: any, noteNumber: any): any;
|
|
119
|
-
releaseSustainPedal(channelNumber: any, halfVelocity: any, scheduleTime: any):
|
|
120
|
+
releaseSustainPedal(channelNumber: any, halfVelocity: any, scheduleTime: any): any[];
|
|
120
121
|
createMessageHandlers(): any[];
|
|
121
122
|
handleMessage(data: any, scheduleTime: any): void;
|
|
122
|
-
handleChannelMessage(statusByte: any, data1: any, data2: any, scheduleTime: any):
|
|
123
|
+
handleChannelMessage(statusByte: any, data1: any, data2: any, scheduleTime: any): any;
|
|
123
124
|
setProgramChange(channelNumber: any, programNumber: any, _scheduleTime: any): void;
|
|
124
125
|
handlePitchBendMessage(channelNumber: any, lsb: any, msb: any, scheduleTime: any): void;
|
|
125
126
|
setPitchBend(channelNumber: any, value: any, scheduleTime: any): void;
|