@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/README.md
CHANGED
|
@@ -23,6 +23,7 @@ This library provides several files depending on the implementation level.
|
|
|
23
23
|
- [@marmooo/midi-player](https://marmooo.github.io/midi-player/) - GUI library
|
|
24
24
|
- [Humidy](https://marmooo.github.io/humidy/) - GM2 MIDI mixer app
|
|
25
25
|
- [Timidy](https://marmooo.github.io/timidy/) - Timidity++ style MIDI player
|
|
26
|
+
- [4x4pad](https://marmooo.github.io/4x4pad/) - 4x4 grid style MIDI controller
|
|
26
27
|
|
|
27
28
|
## Support Status
|
|
28
29
|
|
package/esm/midy-GM1.d.ts
CHANGED
|
@@ -31,6 +31,8 @@ export class MidyGM1 extends EventTarget {
|
|
|
31
31
|
isPaused: boolean;
|
|
32
32
|
isStopping: boolean;
|
|
33
33
|
isSeeking: boolean;
|
|
34
|
+
totalTimeEventTypes: Set<string>;
|
|
35
|
+
tempo: number;
|
|
34
36
|
loop: boolean;
|
|
35
37
|
playPromise: any;
|
|
36
38
|
timeline: any[];
|
|
@@ -94,7 +96,6 @@ export class MidyGM1 extends EventTarget {
|
|
|
94
96
|
currentTime(): number;
|
|
95
97
|
processScheduledNotes(channel: any, callback: any): Promise<void>;
|
|
96
98
|
processActiveNotes(channel: any, scheduleTime: any, callback: any): Promise<void>;
|
|
97
|
-
cbToRatio(cb: any): number;
|
|
98
99
|
rateToCent(rate: any): number;
|
|
99
100
|
centToRate(cent: any): number;
|
|
100
101
|
centToHz(cent: any): number;
|
package/esm/midy-GM1.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"midy-GM1.d.ts","sourceRoot":"","sources":["../src/midy-GM1.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"midy-GM1.d.ts","sourceRoot":"","sources":["../src/midy-GM1.js"],"names":[],"mappings":"AAuGA;IA+BE;;;;;;;;;;;MAWE;IAEF,+BAgBC;IA3DD,aAAa;IACb,oBAAiB;IACjB,qBAAmB;IACnB,kBAAc;IACd,0BAAwB;IACxB,kBAAc;IACd,mBAAiB;IACjB,kBAAc;IACd,mBAAe;IACf,kBAAgB;IAChB,0BAAuD;IACvD,4BAAyB;IACzB,0BAAuB;IACvB,kCAA+B;IAC/B,mBAAkB;IAClB,mBAAkB;IAClB,kBAAiB;IACjB,oBAAmB;IACnB,mBAAkB;IAClB,iCAEG;IACH,cAAU;IACV,cAAa;IACb,iBAAY;IACZ,gBAAc;IACd,oBAAkB;IAClB,sBAAwB;IACxB,2BAAqC;IAiBnC,kBAAgC;IAChC,kBAA8C;IAC9C,eAAwD;IACxD,qBAGE;IACF,uBAAmD;IACnD;;;;;;;;;;;MAA2D;IAC3D,6BAA+D;IAC/D,gBAAiD;IAMnD,mCASC;IAED,2DAYC;IAED,yCAmBC;IAED,oCASC;IAED,sBAoCC;IAED,8DAeC;IAED;;;;MAeC;IAED,yCAaC;IAED,kDASC;IAED,4DASC;IAED,gEAsDC;IAED,mCASC;IAED,uBAUC;IAED,yDAqCC;IAED,2BA0EC;IAED,uDAEC;IAED,wDAEC;IAED,qCAKC;IAED;;;MAwDC;IAED,kGAeC;IAED,mGAeC;IAED,wEAQC;IAED,uBAMC;IAED,sBAIC;IAED,uBAMC;IAED,wBAIC;IAED,0BAKC;IAED,wBAYC;IAED,sBAIC;IAED,kEAWC;IAED,kFAYC;IAED,8BAEC;IAED,8BAEC;IAED,4BAEC;IAED,qCAMC;IAED,2DAIC;IAED,+DAIC;IAED,sDAeC;IAED,qDAoBC;IAED,6CAIC;IAED,sDA6BC;IAED,kEAqBC;IAED,4GAkCC;IAED,uEAyCC;IAED,0EAiBC;IAED,oEASC;IAED,0FAwBC;IAED,gCASC;IAED,iEAqBC;IAED,4FAmBC;IAED,6CAUC;IAED,qDAUC;IAED,qFAeC;IAED,+BAmBC;IAED,kDAOC;IAED,sFA2BC;IAED,mFAGC;IAED,wFAGC;IAED,sEAUC;IAED,mEAYC;IAED,wDAKC;IAED,sDAOC;IAED,mDAMC;IAED,kDAKC;IAED;;;;;;;;;;;MAiCC;IAED,oFAMC;IAED,6EA2BC;IAED,qCAeC;IAED,+FAWC;IAED,wDAUC;IAED,iFAKC;IAED,oEAKC;IAED;;;MAMC;IAED,8DAKC;IAED,4EAKC;IAED,sEAGC;IAED,2DAUC;IAED,yEAWC;IAED,kFAeC;IAED,2DAMC;IAED,uDAkBC;IAED,gDAEC;IAED,gDAEC;IAED,sEAGC;IAED,qEAKC;IAED,2EAUC;IAED,iEAMC;IAED,uEAQC;IAED,mEAKC;IAED,yEAQC;IAED,gFAGC;IAED,6CAqBC;IAGD,8EAgCC;IAED,gFAGC;IAED,+EAgBC;IAED,qCAUC;IAED,4EAaC;IAED,4DAGC;IAED,qDAKC;IAED,gDAYC;IAGD,6DAgBC;CACF"}
|
package/esm/midy-GM1.js
CHANGED
|
@@ -14,6 +14,12 @@ class Note {
|
|
|
14
14
|
writable: true,
|
|
15
15
|
value: void 0
|
|
16
16
|
});
|
|
17
|
+
Object.defineProperty(this, "adjustedBaseFreq", {
|
|
18
|
+
enumerable: true,
|
|
19
|
+
configurable: true,
|
|
20
|
+
writable: true,
|
|
21
|
+
value: 20000
|
|
22
|
+
});
|
|
17
23
|
Object.defineProperty(this, "index", {
|
|
18
24
|
enumerable: true,
|
|
19
25
|
configurable: true,
|
|
@@ -147,6 +153,11 @@ const pitchEnvelopeKeys = [
|
|
|
147
153
|
"playbackRate",
|
|
148
154
|
];
|
|
149
155
|
const pitchEnvelopeKeySet = new Set(pitchEnvelopeKeys);
|
|
156
|
+
function cbToRatio(cb) {
|
|
157
|
+
return Math.pow(10, cb / 200);
|
|
158
|
+
}
|
|
159
|
+
const decayCurve = 1 / (-Math.log(cbToRatio(-1000)));
|
|
160
|
+
const releaseCurve = 1 / (-Math.log(cbToRatio(-600)));
|
|
150
161
|
export class MidyGM1 extends EventTarget {
|
|
151
162
|
constructor(audioContext) {
|
|
152
163
|
super();
|
|
@@ -264,6 +275,20 @@ export class MidyGM1 extends EventTarget {
|
|
|
264
275
|
writable: true,
|
|
265
276
|
value: false
|
|
266
277
|
});
|
|
278
|
+
Object.defineProperty(this, "totalTimeEventTypes", {
|
|
279
|
+
enumerable: true,
|
|
280
|
+
configurable: true,
|
|
281
|
+
writable: true,
|
|
282
|
+
value: new Set([
|
|
283
|
+
"noteOff",
|
|
284
|
+
])
|
|
285
|
+
});
|
|
286
|
+
Object.defineProperty(this, "tempo", {
|
|
287
|
+
enumerable: true,
|
|
288
|
+
configurable: true,
|
|
289
|
+
writable: true,
|
|
290
|
+
value: 1
|
|
291
|
+
});
|
|
267
292
|
Object.defineProperty(this, "loop", {
|
|
268
293
|
enumerable: true,
|
|
269
294
|
configurable: true,
|
|
@@ -372,13 +397,13 @@ export class MidyGM1 extends EventTarget {
|
|
|
372
397
|
this.totalTime = this.calcTotalTime();
|
|
373
398
|
}
|
|
374
399
|
cacheVoiceIds() {
|
|
375
|
-
const timeline = this
|
|
400
|
+
const { channels, timeline, voiceCounter } = this;
|
|
376
401
|
for (let i = 0; i < timeline.length; i++) {
|
|
377
402
|
const event = timeline[i];
|
|
378
403
|
switch (event.type) {
|
|
379
404
|
case "noteOn": {
|
|
380
|
-
const audioBufferId = this.getVoiceId(
|
|
381
|
-
|
|
405
|
+
const audioBufferId = this.getVoiceId(channels[event.channel], event.noteNumber, event.velocity);
|
|
406
|
+
voiceCounter.set(audioBufferId, (voiceCounter.get(audioBufferId) ?? 0) + 1);
|
|
382
407
|
break;
|
|
383
408
|
}
|
|
384
409
|
case "controller":
|
|
@@ -393,9 +418,9 @@ export class MidyGM1 extends EventTarget {
|
|
|
393
418
|
this.setProgramChange(event.channel, event.programNumber, event.startTime);
|
|
394
419
|
}
|
|
395
420
|
}
|
|
396
|
-
for (const [audioBufferId, count] of
|
|
421
|
+
for (const [audioBufferId, count] of voiceCounter) {
|
|
397
422
|
if (count === 1)
|
|
398
|
-
|
|
423
|
+
voiceCounter.delete(audioBufferId);
|
|
399
424
|
}
|
|
400
425
|
this.GM1SystemOn();
|
|
401
426
|
}
|
|
@@ -404,7 +429,12 @@ export class MidyGM1 extends EventTarget {
|
|
|
404
429
|
const bankTable = this.soundFontTable[programNumber];
|
|
405
430
|
if (!bankTable)
|
|
406
431
|
return;
|
|
407
|
-
|
|
432
|
+
let bank = channel.isDrum ? 128 : 0;
|
|
433
|
+
if (bankTable[bank] === undefined) {
|
|
434
|
+
if (channel.isDrum)
|
|
435
|
+
return;
|
|
436
|
+
bank = 0;
|
|
437
|
+
}
|
|
408
438
|
const soundFontIndex = bankTable[bank];
|
|
409
439
|
if (soundFontIndex === undefined)
|
|
410
440
|
return;
|
|
@@ -462,11 +492,13 @@ export class MidyGM1 extends EventTarget {
|
|
|
462
492
|
const lookAheadCheckTime = scheduleTime + timeOffset + this.lookAhead;
|
|
463
493
|
const schedulingOffset = this.startDelay - timeOffset;
|
|
464
494
|
const timeline = this.timeline;
|
|
495
|
+
const inverseTempo = 1 / this.tempo;
|
|
465
496
|
while (queueIndex < timeline.length) {
|
|
466
497
|
const event = timeline[queueIndex];
|
|
467
|
-
|
|
498
|
+
const t = event.startTime * inverseTempo;
|
|
499
|
+
if (lookAheadCheckTime < t)
|
|
468
500
|
break;
|
|
469
|
-
const startTime =
|
|
501
|
+
const startTime = t + schedulingOffset;
|
|
470
502
|
switch (event.type) {
|
|
471
503
|
case "noteOn":
|
|
472
504
|
this.noteOn(event.channel, event.noteNumber, event.velocity, startTime);
|
|
@@ -492,8 +524,10 @@ export class MidyGM1 extends EventTarget {
|
|
|
492
524
|
return queueIndex;
|
|
493
525
|
}
|
|
494
526
|
getQueueIndex(second) {
|
|
495
|
-
|
|
496
|
-
|
|
527
|
+
const timeline = this.timeline;
|
|
528
|
+
const inverseTempo = 1 / this.tempo;
|
|
529
|
+
for (let i = 0; i < timeline.length; i++) {
|
|
530
|
+
if (second <= timeline[i].startTime * inverseTempo) {
|
|
497
531
|
return i;
|
|
498
532
|
}
|
|
499
533
|
}
|
|
@@ -504,40 +538,44 @@ export class MidyGM1 extends EventTarget {
|
|
|
504
538
|
this.drumExclusiveClassNotes.fill(undefined);
|
|
505
539
|
this.voiceCache.clear();
|
|
506
540
|
this.realtimeVoiceCache.clear();
|
|
507
|
-
|
|
508
|
-
|
|
541
|
+
const channels = this.channels;
|
|
542
|
+
for (let i = 0; i < channels.length; i++) {
|
|
543
|
+
channels[i].scheduledNotes = [];
|
|
509
544
|
this.resetChannelStates(i);
|
|
510
545
|
}
|
|
511
546
|
}
|
|
512
547
|
updateStates(queueIndex, nextQueueIndex) {
|
|
548
|
+
const { timeline, resumeTime } = this;
|
|
549
|
+
const inverseTempo = 1 / this.tempo;
|
|
513
550
|
const now = this.audioContext.currentTime;
|
|
514
551
|
if (nextQueueIndex < queueIndex)
|
|
515
552
|
queueIndex = 0;
|
|
516
553
|
for (let i = queueIndex; i < nextQueueIndex; i++) {
|
|
517
|
-
const event =
|
|
554
|
+
const event = timeline[i];
|
|
518
555
|
switch (event.type) {
|
|
519
556
|
case "controller":
|
|
520
|
-
this.setControlChange(event.channel, event.controllerType, event.value, now -
|
|
557
|
+
this.setControlChange(event.channel, event.controllerType, event.value, now - resumeTime + event.startTime * inverseTempo);
|
|
521
558
|
break;
|
|
522
559
|
case "programChange":
|
|
523
|
-
this.setProgramChange(event.channel, event.programNumber, now -
|
|
560
|
+
this.setProgramChange(event.channel, event.programNumber, now - resumeTime + event.startTime * inverseTempo);
|
|
524
561
|
break;
|
|
525
562
|
case "pitchBend":
|
|
526
|
-
this.setPitchBend(event.channel, event.value + 8192, now -
|
|
563
|
+
this.setPitchBend(event.channel, event.value + 8192, now - resumeTime + event.startTime * inverseTempo);
|
|
527
564
|
break;
|
|
528
565
|
case "sysEx":
|
|
529
|
-
this.handleSysEx(event.data, now -
|
|
566
|
+
this.handleSysEx(event.data, now - resumeTime + event.startTime * inverseTempo);
|
|
530
567
|
}
|
|
531
568
|
}
|
|
532
569
|
}
|
|
533
570
|
async playNotes() {
|
|
534
|
-
|
|
535
|
-
|
|
571
|
+
const audioContext = this.audioContext;
|
|
572
|
+
if (audioContext.state === "suspended") {
|
|
573
|
+
await audioContext.resume();
|
|
536
574
|
}
|
|
537
575
|
const paused = this.isPaused;
|
|
538
576
|
this.isPlaying = true;
|
|
539
577
|
this.isPaused = false;
|
|
540
|
-
this.startTime =
|
|
578
|
+
this.startTime = audioContext.currentTime;
|
|
541
579
|
if (paused) {
|
|
542
580
|
this.dispatchEvent(new Event("resumed"));
|
|
543
581
|
}
|
|
@@ -548,42 +586,41 @@ export class MidyGM1 extends EventTarget {
|
|
|
548
586
|
let exitReason;
|
|
549
587
|
this.notePromises = [];
|
|
550
588
|
while (true) {
|
|
551
|
-
const now =
|
|
552
|
-
if (this.
|
|
589
|
+
const now = audioContext.currentTime;
|
|
590
|
+
if (this.totalTime < this.currentTime() ||
|
|
591
|
+
this.timeline.length <= queueIndex) {
|
|
553
592
|
await this.stopNotes(0, true, now);
|
|
554
593
|
if (this.loop) {
|
|
555
|
-
this.notePromises = [];
|
|
556
594
|
this.resetAllStates();
|
|
557
|
-
this.startTime =
|
|
595
|
+
this.startTime = audioContext.currentTime;
|
|
558
596
|
this.resumeTime = 0;
|
|
559
597
|
queueIndex = 0;
|
|
560
598
|
this.dispatchEvent(new Event("looped"));
|
|
561
599
|
continue;
|
|
562
600
|
}
|
|
563
601
|
else {
|
|
564
|
-
await
|
|
602
|
+
await audioContext.suspend();
|
|
565
603
|
exitReason = "ended";
|
|
566
604
|
break;
|
|
567
605
|
}
|
|
568
606
|
}
|
|
569
607
|
if (this.isPausing) {
|
|
570
608
|
await this.stopNotes(0, true, now);
|
|
571
|
-
await
|
|
572
|
-
this.notePromises = [];
|
|
609
|
+
await audioContext.suspend();
|
|
573
610
|
this.isPausing = false;
|
|
574
611
|
exitReason = "paused";
|
|
575
612
|
break;
|
|
576
613
|
}
|
|
577
614
|
else if (this.isStopping) {
|
|
578
615
|
await this.stopNotes(0, true, now);
|
|
579
|
-
await
|
|
616
|
+
await audioContext.suspend();
|
|
580
617
|
this.isStopping = false;
|
|
581
618
|
exitReason = "stopped";
|
|
582
619
|
break;
|
|
583
620
|
}
|
|
584
621
|
else if (this.isSeeking) {
|
|
585
622
|
this.stopNotes(0, true, now);
|
|
586
|
-
this.startTime =
|
|
623
|
+
this.startTime = audioContext.currentTime;
|
|
587
624
|
const nextQueueIndex = this.getQueueIndex(this.resumeTime);
|
|
588
625
|
this.updateStates(queueIndex, nextQueueIndex);
|
|
589
626
|
queueIndex = nextQueueIndex;
|
|
@@ -596,7 +633,6 @@ export class MidyGM1 extends EventTarget {
|
|
|
596
633
|
await this.scheduleTask(() => { }, waitTime);
|
|
597
634
|
}
|
|
598
635
|
if (exitReason !== "paused") {
|
|
599
|
-
this.notePromises = [];
|
|
600
636
|
this.resetAllStates();
|
|
601
637
|
}
|
|
602
638
|
this.isPlaying = false;
|
|
@@ -694,11 +730,13 @@ export class MidyGM1 extends EventTarget {
|
|
|
694
730
|
return Promise.all(promises);
|
|
695
731
|
}
|
|
696
732
|
stopNotes(velocity, force, scheduleTime) {
|
|
697
|
-
const
|
|
698
|
-
for (let i = 0; i <
|
|
699
|
-
|
|
733
|
+
const channels = this.channels;
|
|
734
|
+
for (let i = 0; i < channels.length; i++) {
|
|
735
|
+
this.stopChannelNotes(i, velocity, force, scheduleTime);
|
|
700
736
|
}
|
|
701
|
-
|
|
737
|
+
const stopPromise = Promise.all(this.notePromises);
|
|
738
|
+
this.notePromises = [];
|
|
739
|
+
return stopPromise;
|
|
702
740
|
}
|
|
703
741
|
async start() {
|
|
704
742
|
if (this.isPlaying || this.isPaused)
|
|
@@ -736,11 +774,17 @@ export class MidyGM1 extends EventTarget {
|
|
|
736
774
|
}
|
|
737
775
|
}
|
|
738
776
|
calcTotalTime() {
|
|
777
|
+
const totalTimeEventTypes = this.totalTimeEventTypes;
|
|
778
|
+
const timeline = this.timeline;
|
|
779
|
+
const inverseTempo = 1 / this.tempo;
|
|
739
780
|
let totalTime = 0;
|
|
740
|
-
for (let i = 0; i <
|
|
741
|
-
const event =
|
|
742
|
-
if (
|
|
743
|
-
|
|
781
|
+
for (let i = 0; i < timeline.length; i++) {
|
|
782
|
+
const event = timeline[i];
|
|
783
|
+
if (!totalTimeEventTypes.has(event.type))
|
|
784
|
+
continue;
|
|
785
|
+
const t = event.startTime * inverseTempo;
|
|
786
|
+
if (totalTime < t)
|
|
787
|
+
totalTime = t;
|
|
744
788
|
}
|
|
745
789
|
return totalTime + this.startDelay;
|
|
746
790
|
}
|
|
@@ -780,9 +824,6 @@ export class MidyGM1 extends EventTarget {
|
|
|
780
824
|
}
|
|
781
825
|
await Promise.all(tasks);
|
|
782
826
|
}
|
|
783
|
-
cbToRatio(cb) {
|
|
784
|
-
return Math.pow(10, cb / 200);
|
|
785
|
-
}
|
|
786
827
|
rateToCent(rate) {
|
|
787
828
|
return 1200 * Math.log2(rate);
|
|
788
829
|
}
|
|
@@ -811,26 +852,26 @@ export class MidyGM1 extends EventTarget {
|
|
|
811
852
|
}
|
|
812
853
|
setVolumeEnvelope(note, scheduleTime) {
|
|
813
854
|
const { voiceParams, startTime } = note;
|
|
814
|
-
const attackVolume =
|
|
855
|
+
const attackVolume = cbToRatio(-voiceParams.initialAttenuation);
|
|
815
856
|
const sustainVolume = attackVolume * (1 - voiceParams.volSustain);
|
|
816
857
|
const volDelay = startTime + voiceParams.volDelay;
|
|
817
858
|
const volAttack = volDelay + voiceParams.volAttack;
|
|
818
859
|
const volHold = volAttack + voiceParams.volHold;
|
|
819
|
-
const
|
|
860
|
+
const decayDuration = voiceParams.volDecay;
|
|
820
861
|
note.volumeEnvelopeNode.gain
|
|
821
862
|
.cancelScheduledValues(scheduleTime)
|
|
822
863
|
.setValueAtTime(0, startTime)
|
|
823
|
-
.setValueAtTime(
|
|
824
|
-
.
|
|
864
|
+
.setValueAtTime(0, volDelay)
|
|
865
|
+
.linearRampToValueAtTime(attackVolume, volAttack)
|
|
825
866
|
.setValueAtTime(attackVolume, volHold)
|
|
826
|
-
.
|
|
867
|
+
.setTargetAtTime(sustainVolume, volHold, decayDuration * decayCurve);
|
|
827
868
|
}
|
|
828
869
|
setPitchEnvelope(note, scheduleTime) {
|
|
829
870
|
const { voiceParams } = note;
|
|
830
871
|
const baseRate = voiceParams.playbackRate;
|
|
831
872
|
note.bufferSource.playbackRate
|
|
832
873
|
.cancelScheduledValues(scheduleTime)
|
|
833
|
-
.setValueAtTime(baseRate,
|
|
874
|
+
.setValueAtTime(baseRate, note.startTime);
|
|
834
875
|
const modEnvToPitch = voiceParams.modEnvToPitch;
|
|
835
876
|
if (modEnvToPitch === 0)
|
|
836
877
|
return;
|
|
@@ -840,12 +881,12 @@ export class MidyGM1 extends EventTarget {
|
|
|
840
881
|
const modDelay = note.startTime + voiceParams.modDelay;
|
|
841
882
|
const modAttack = modDelay + voiceParams.modAttack;
|
|
842
883
|
const modHold = modAttack + voiceParams.modHold;
|
|
843
|
-
const
|
|
884
|
+
const decayDuration = voiceParams.modDecay;
|
|
844
885
|
note.bufferSource.playbackRate
|
|
845
886
|
.setValueAtTime(baseRate, modDelay)
|
|
846
|
-
.
|
|
887
|
+
.linearRampToValueAtTime(peekRate, modAttack)
|
|
847
888
|
.setValueAtTime(peekRate, modHold)
|
|
848
|
-
.
|
|
889
|
+
.setTargetAtTime(baseRate, modHold, decayDuration * decayCurve);
|
|
849
890
|
}
|
|
850
891
|
clampCutoffFrequency(frequency) {
|
|
851
892
|
const minFrequency = 20; // min Hz of initialFilterFc
|
|
@@ -854,36 +895,42 @@ export class MidyGM1 extends EventTarget {
|
|
|
854
895
|
}
|
|
855
896
|
setFilterEnvelope(note, scheduleTime) {
|
|
856
897
|
const { voiceParams, startTime } = note;
|
|
857
|
-
const
|
|
858
|
-
const
|
|
859
|
-
const
|
|
860
|
-
|
|
898
|
+
const modEnvToFilterFc = voiceParams.modEnvToFilterFc;
|
|
899
|
+
const baseCent = voiceParams.initialFilterFc;
|
|
900
|
+
const peekCent = baseCent + modEnvToFilterFc;
|
|
901
|
+
const sustainCent = baseCent +
|
|
902
|
+
modEnvToFilterFc * (1 - voiceParams.modSustain);
|
|
903
|
+
const baseFreq = this.centToHz(baseCent);
|
|
904
|
+
const peekFreq = this.centToHz(peekCent);
|
|
905
|
+
const sustainFreq = this.centToHz(sustainCent);
|
|
861
906
|
const adjustedBaseFreq = this.clampCutoffFrequency(baseFreq);
|
|
862
907
|
const adjustedPeekFreq = this.clampCutoffFrequency(peekFreq);
|
|
863
908
|
const adjustedSustainFreq = this.clampCutoffFrequency(sustainFreq);
|
|
864
909
|
const modDelay = startTime + voiceParams.modDelay;
|
|
865
910
|
const modAttack = modDelay + voiceParams.modAttack;
|
|
866
911
|
const modHold = modAttack + voiceParams.modHold;
|
|
867
|
-
const
|
|
912
|
+
const decayDuration = voiceParams.modDecay;
|
|
913
|
+
note.adjustedBaseFreq = adjustedBaseFreq;
|
|
868
914
|
note.filterNode.frequency
|
|
869
915
|
.cancelScheduledValues(scheduleTime)
|
|
870
916
|
.setValueAtTime(adjustedBaseFreq, startTime)
|
|
871
917
|
.setValueAtTime(adjustedBaseFreq, modDelay)
|
|
872
|
-
.
|
|
918
|
+
.linearRampToValueAtTime(adjustedPeekFreq, modAttack)
|
|
873
919
|
.setValueAtTime(adjustedPeekFreq, modHold)
|
|
874
|
-
.
|
|
920
|
+
.setTargetAtTime(adjustedSustainFreq, modHold, decayDuration * decayCurve);
|
|
875
921
|
}
|
|
876
922
|
startModulation(channel, note, scheduleTime) {
|
|
923
|
+
const audioContext = this.audioContext;
|
|
877
924
|
const { voiceParams } = note;
|
|
878
|
-
note.modulationLFO = new OscillatorNode(
|
|
925
|
+
note.modulationLFO = new OscillatorNode(audioContext, {
|
|
879
926
|
frequency: this.centToHz(voiceParams.freqModLFO),
|
|
880
927
|
});
|
|
881
|
-
note.filterDepth = new GainNode(
|
|
928
|
+
note.filterDepth = new GainNode(audioContext, {
|
|
882
929
|
gain: voiceParams.modLfoToFilterFc,
|
|
883
930
|
});
|
|
884
|
-
note.modulationDepth = new GainNode(
|
|
931
|
+
note.modulationDepth = new GainNode(audioContext);
|
|
885
932
|
this.setModLfoToPitch(channel, note, scheduleTime);
|
|
886
|
-
note.volumeDepth = new GainNode(
|
|
933
|
+
note.volumeDepth = new GainNode(audioContext);
|
|
887
934
|
this.setModLfoToVolume(note, scheduleTime);
|
|
888
935
|
note.modulationLFO.start(note.startTime + voiceParams.delayModLFO);
|
|
889
936
|
note.modulationLFO.connect(note.filterDepth);
|
|
@@ -922,7 +969,8 @@ export class MidyGM1 extends EventTarget {
|
|
|
922
969
|
}
|
|
923
970
|
}
|
|
924
971
|
async setNoteAudioNode(channel, note, realtime) {
|
|
925
|
-
const
|
|
972
|
+
const audioContext = this.audioContext;
|
|
973
|
+
const now = audioContext.currentTime;
|
|
926
974
|
const { noteNumber, velocity, startTime } = note;
|
|
927
975
|
const state = channel.state;
|
|
928
976
|
const controllerState = this.getControllerState(channel, noteNumber, velocity);
|
|
@@ -930,8 +978,8 @@ export class MidyGM1 extends EventTarget {
|
|
|
930
978
|
note.voiceParams = voiceParams;
|
|
931
979
|
const audioBuffer = await this.getAudioBuffer(channel, noteNumber, velocity, voiceParams, realtime);
|
|
932
980
|
note.bufferSource = this.createBufferSource(voiceParams, audioBuffer);
|
|
933
|
-
note.volumeEnvelopeNode = new GainNode(
|
|
934
|
-
note.filterNode = new BiquadFilterNode(
|
|
981
|
+
note.volumeEnvelopeNode = new GainNode(audioContext);
|
|
982
|
+
note.filterNode = new BiquadFilterNode(audioContext, {
|
|
935
983
|
type: "lowpass",
|
|
936
984
|
Q: voiceParams.initialFilterQ / 10, // dB
|
|
937
985
|
});
|
|
@@ -990,7 +1038,12 @@ export class MidyGM1 extends EventTarget {
|
|
|
990
1038
|
const bankTable = this.soundFontTable[programNumber];
|
|
991
1039
|
if (!bankTable)
|
|
992
1040
|
return;
|
|
993
|
-
|
|
1041
|
+
let bank = channel.isDrum ? 128 : 0;
|
|
1042
|
+
if (bankTable[bank] === undefined) {
|
|
1043
|
+
if (channel.isDrum)
|
|
1044
|
+
return;
|
|
1045
|
+
bank = 0;
|
|
1046
|
+
}
|
|
994
1047
|
const soundFontIndex = bankTable[bank];
|
|
995
1048
|
if (soundFontIndex === undefined)
|
|
996
1049
|
return;
|
|
@@ -1014,24 +1067,24 @@ export class MidyGM1 extends EventTarget {
|
|
|
1014
1067
|
}
|
|
1015
1068
|
releaseNote(channel, note, endTime) {
|
|
1016
1069
|
endTime ??= this.audioContext.currentTime;
|
|
1017
|
-
const
|
|
1070
|
+
const duration = note.voiceParams.volRelease * releaseTime;
|
|
1071
|
+
const volRelease = endTime + duration;
|
|
1018
1072
|
const modRelease = endTime + note.voiceParams.modRelease;
|
|
1019
|
-
const stopTime = Math.min(volRelease, modRelease);
|
|
1020
1073
|
note.filterNode.frequency
|
|
1021
1074
|
.cancelScheduledValues(endTime)
|
|
1022
|
-
.linearRampToValueAtTime(
|
|
1075
|
+
.linearRampToValueAtTime(note.adjustedBaseFreq, modRelease);
|
|
1023
1076
|
note.volumeEnvelopeNode.gain
|
|
1024
1077
|
.cancelScheduledValues(endTime)
|
|
1025
|
-
.
|
|
1078
|
+
.setTargetAtTime(0, endTime, duration * releaseCurve);
|
|
1026
1079
|
return new Promise((resolve) => {
|
|
1027
1080
|
this.scheduleTask(() => {
|
|
1028
1081
|
const bufferSource = note.bufferSource;
|
|
1029
1082
|
bufferSource.loop = false;
|
|
1030
|
-
bufferSource.stop(
|
|
1083
|
+
bufferSource.stop(volRelease);
|
|
1031
1084
|
this.disconnectNote(note);
|
|
1032
1085
|
channel.scheduledNotes[note.index] = undefined;
|
|
1033
1086
|
resolve();
|
|
1034
|
-
},
|
|
1087
|
+
}, volRelease);
|
|
1035
1088
|
});
|
|
1036
1089
|
}
|
|
1037
1090
|
noteOff(channelNumber, noteNumber, _velocity, endTime, force) {
|
|
@@ -1166,7 +1219,7 @@ export class MidyGM1 extends EventTarget {
|
|
|
1166
1219
|
}
|
|
1167
1220
|
setModLfoToVolume(note, scheduleTime) {
|
|
1168
1221
|
const modLfoToVolume = note.voiceParams.modLfoToVolume;
|
|
1169
|
-
const baseDepth =
|
|
1222
|
+
const baseDepth = cbToRatio(Math.abs(modLfoToVolume)) - 1;
|
|
1170
1223
|
const volumeDepth = baseDepth * Math.sign(modLfoToVolume);
|
|
1171
1224
|
note.volumeDepth.gain
|
|
1172
1225
|
.cancelScheduledValues(scheduleTime)
|
|
@@ -1542,15 +1595,16 @@ export class MidyGM1 extends EventTarget {
|
|
|
1542
1595
|
}
|
|
1543
1596
|
}
|
|
1544
1597
|
GM1SystemOn(scheduleTime) {
|
|
1598
|
+
const channels = this.channels;
|
|
1545
1599
|
if (!(0 <= scheduleTime))
|
|
1546
1600
|
scheduleTime = this.audioContext.currentTime;
|
|
1547
1601
|
this.mode = "GM1";
|
|
1548
|
-
for (let i = 0; i <
|
|
1602
|
+
for (let i = 0; i < channels.length; i++) {
|
|
1549
1603
|
this.allSoundOff(i, 0, scheduleTime);
|
|
1550
|
-
const channel =
|
|
1604
|
+
const channel = channels[i];
|
|
1551
1605
|
channel.isDrum = false;
|
|
1552
1606
|
}
|
|
1553
|
-
|
|
1607
|
+
channels[9].isDrum = true;
|
|
1554
1608
|
}
|
|
1555
1609
|
handleUniversalRealTimeExclusiveMessage(data, scheduleTime) {
|
|
1556
1610
|
switch (data[2]) {
|
package/esm/midy-GM2.d.ts
CHANGED
|
@@ -50,6 +50,8 @@ export class MidyGM2 extends EventTarget {
|
|
|
50
50
|
isPaused: boolean;
|
|
51
51
|
isStopping: boolean;
|
|
52
52
|
isSeeking: boolean;
|
|
53
|
+
totalTimeEventTypes: Set<string>;
|
|
54
|
+
tempo: number;
|
|
53
55
|
loop: boolean;
|
|
54
56
|
playPromise: any;
|
|
55
57
|
timeline: any[];
|
|
@@ -156,7 +158,6 @@ export class MidyGM2 extends EventTarget {
|
|
|
156
158
|
delayNodes: any[];
|
|
157
159
|
feedbackGains: any[];
|
|
158
160
|
};
|
|
159
|
-
cbToRatio(cb: any): number;
|
|
160
161
|
rateToCent(rate: any): number;
|
|
161
162
|
centToRate(cent: any): number;
|
|
162
163
|
centToHz(cent: any): number;
|
package/esm/midy-GM2.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"midy-GM2.d.ts","sourceRoot":"","sources":["../src/midy-GM2.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"midy-GM2.d.ts","sourceRoot":"","sources":["../src/midy-GM2.js"],"names":[],"mappings":"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"}
|