@marmooo/midy 0.4.0 → 0.4.2
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 +14 -1
- package/esm/midy-GM1.d.ts +8 -7
- package/esm/midy-GM1.d.ts.map +1 -1
- package/esm/midy-GM1.js +138 -81
- package/esm/midy-GM2.d.ts +8 -7
- package/esm/midy-GM2.d.ts.map +1 -1
- package/esm/midy-GM2.js +161 -96
- package/esm/midy-GMLite.d.ts +8 -7
- package/esm/midy-GMLite.d.ts.map +1 -1
- package/esm/midy-GMLite.js +126 -72
- package/esm/midy.d.ts +17 -12
- package/esm/midy.d.ts.map +1 -1
- package/esm/midy.js +275 -123
- package/package.json +2 -2
- package/script/midy-GM1.d.ts +8 -7
- package/script/midy-GM1.d.ts.map +1 -1
- package/script/midy-GM1.js +138 -81
- package/script/midy-GM2.d.ts +8 -7
- package/script/midy-GM2.d.ts.map +1 -1
- package/script/midy-GM2.js +161 -96
- package/script/midy-GMLite.d.ts +8 -7
- package/script/midy-GMLite.d.ts.map +1 -1
- package/script/midy-GMLite.js +126 -72
- package/script/midy.d.ts +17 -12
- package/script/midy.d.ts.map +1 -1
- package/script/midy.js +275 -123
package/README.md
CHANGED
|
@@ -22,6 +22,7 @@ This library provides several files depending on the implementation level.
|
|
|
22
22
|
|
|
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
|
+
- [Timidy](https://marmooo.github.io/timidy/) - Timidity++ style MIDI player
|
|
25
26
|
|
|
26
27
|
## Support Status
|
|
27
28
|
|
|
@@ -54,7 +55,7 @@ All implementations follow the specification.
|
|
|
54
55
|
import { Midy } from "midy.js";
|
|
55
56
|
|
|
56
57
|
const audioContext = new AudioContext();
|
|
57
|
-
await audioContext.suspend();
|
|
58
|
+
if (audioContext.state === "running") await audioContext.suspend();
|
|
58
59
|
const midy = new Midy(audioContext);
|
|
59
60
|
await midy.loadMIDI("test.mid");
|
|
60
61
|
await midy.loadSoundFont("test.sf3");
|
|
@@ -64,6 +65,7 @@ await midy.start();
|
|
|
64
65
|
### Playback
|
|
65
66
|
|
|
66
67
|
```js
|
|
68
|
+
midy.loop = true;
|
|
67
69
|
await midy.start();
|
|
68
70
|
await midy.stop();
|
|
69
71
|
await midy.pause();
|
|
@@ -71,6 +73,17 @@ await midy.resume();
|
|
|
71
73
|
midy.seekTo(second);
|
|
72
74
|
```
|
|
73
75
|
|
|
76
|
+
### Events
|
|
77
|
+
|
|
78
|
+
```js
|
|
79
|
+
midy.addEventListener("looped", func);
|
|
80
|
+
midy.addEventListener("started", func);
|
|
81
|
+
midy.addEventListener("stopped", func);
|
|
82
|
+
midy.addEventListener("paused", func);
|
|
83
|
+
midy.addEventListener("resumed", func);
|
|
84
|
+
midy.addEventListener("seeked", func);
|
|
85
|
+
```
|
|
86
|
+
|
|
74
87
|
### MIDI Message
|
|
75
88
|
|
|
76
89
|
There are functions that handle MIDI messages as they are, as well as simplified
|
package/esm/midy-GM1.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export class MidyGM1 {
|
|
1
|
+
export class MidyGM1 extends EventTarget {
|
|
2
2
|
static channelSettings: {
|
|
3
3
|
scheduleIndex: number;
|
|
4
4
|
detune: number;
|
|
@@ -31,6 +31,7 @@ export class MidyGM1 {
|
|
|
31
31
|
isPaused: boolean;
|
|
32
32
|
isStopping: boolean;
|
|
33
33
|
isSeeking: boolean;
|
|
34
|
+
loop: boolean;
|
|
34
35
|
playPromise: any;
|
|
35
36
|
timeline: any[];
|
|
36
37
|
notePromises: any[];
|
|
@@ -69,7 +70,7 @@ export class MidyGM1 {
|
|
|
69
70
|
createChannels(audioContext: any): any[];
|
|
70
71
|
createAudioBuffer(voiceParams: any): Promise<any>;
|
|
71
72
|
createBufferSource(voiceParams: any, audioBuffer: any): any;
|
|
72
|
-
scheduleTimelineEvents(scheduleTime: any, queueIndex: any):
|
|
73
|
+
scheduleTimelineEvents(scheduleTime: any, queueIndex: any): any;
|
|
73
74
|
getQueueIndex(second: any): number;
|
|
74
75
|
resetAllStates(): void;
|
|
75
76
|
updateStates(queueIndex: any, nextQueueIndex: any): void;
|
|
@@ -91,8 +92,8 @@ export class MidyGM1 {
|
|
|
91
92
|
seekTo(second: any): void;
|
|
92
93
|
calcTotalTime(): number;
|
|
93
94
|
currentTime(): number;
|
|
94
|
-
processScheduledNotes(channel: any, callback: any): void
|
|
95
|
-
processActiveNotes(channel: any, scheduleTime: any, callback: any): void
|
|
95
|
+
processScheduledNotes(channel: any, callback: any): Promise<void>;
|
|
96
|
+
processActiveNotes(channel: any, scheduleTime: any, callback: any): Promise<void>;
|
|
96
97
|
cbToRatio(cb: any): number;
|
|
97
98
|
rateToCent(rate: any): number;
|
|
98
99
|
centToRate(cent: any): number;
|
|
@@ -112,13 +113,13 @@ export class MidyGM1 {
|
|
|
112
113
|
noteOn(channelNumber: any, noteNumber: any, velocity: any, startTime: any): Promise<void>;
|
|
113
114
|
disconnectNote(note: any): void;
|
|
114
115
|
releaseNote(channel: any, note: any, endTime: any): Promise<any>;
|
|
115
|
-
noteOff(channelNumber: any, noteNumber: any,
|
|
116
|
+
noteOff(channelNumber: any, noteNumber: any, _velocity: any, endTime: any, force: any): any;
|
|
116
117
|
setNoteIndex(channel: any, index: any): void;
|
|
117
118
|
findNoteOffIndex(channel: any, noteNumber: any): any;
|
|
118
|
-
releaseSustainPedal(channelNumber: any, halfVelocity: any, scheduleTime: any):
|
|
119
|
+
releaseSustainPedal(channelNumber: any, halfVelocity: any, scheduleTime: any): any[];
|
|
119
120
|
createMessageHandlers(): any[];
|
|
120
121
|
handleMessage(data: any, scheduleTime: any): void;
|
|
121
|
-
handleChannelMessage(statusByte: any, data1: any, data2: any, scheduleTime: any):
|
|
122
|
+
handleChannelMessage(statusByte: any, data1: any, data2: any, scheduleTime: any): any;
|
|
122
123
|
setProgramChange(channelNumber: any, programNumber: any, _scheduleTime: any): void;
|
|
123
124
|
handlePitchBendMessage(channelNumber: any, lsb: any, msb: any, scheduleTime: any): void;
|
|
124
125
|
setPitchBend(channelNumber: any, value: any, scheduleTime: any): void;
|
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":"AA+FA;IA2BE;;;;;;;;;;;MAWE;IAEF,+BAgBC;IAvDD,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,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,8DAWC;IAED;;;;MAeC;IAED,yCAaC;IAED,kDASC;IAED,4DASC;IAED,gEAoDC;IAED,mCAOC;IAED,uBASC;IAED,yDAgCC;IAED,2BAyEC;IAED,uDAEC;IAED,wDAEC;IAED,qCAKC;IAED;;;MAwDC;IAED,kGAeC;IAED,mGAeC;IAED,wEAMC;IAED,uBAMC;IAED,sBAIC;IAED,uBAMC;IAED,wBAIC;IAED,0BAKC;IAED,wBAOC;IAED,sBAIC;IAED,kEAWC;IAED,kFAYC;IAED,2BAEC;IAED,8BAEC;IAED,8BAEC;IAED,4BAEC;IAED,qCAMC;IAED,2DAIC;IAED,+DAIC;IAED,sDAeC;IAED,qDAoBC;IAED,6CAIC;IAED,sDAsBC;IAED,kEAoBC;IAED,4GAkCC;IAED,uEAwCC;IAED,0EAiBC;IAED,oEASC;IAED,0FAoBC;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,qCASC;IAED,4EAaC;IAED,4DAGC;IAED,qDAKC;IAED,gDAYC;IAGD,6DAgBC;CACF"}
|
package/esm/midy-GM1.js
CHANGED
|
@@ -26,12 +26,6 @@ class Note {
|
|
|
26
26
|
writable: true,
|
|
27
27
|
value: false
|
|
28
28
|
});
|
|
29
|
-
Object.defineProperty(this, "pending", {
|
|
30
|
-
enumerable: true,
|
|
31
|
-
configurable: true,
|
|
32
|
-
writable: true,
|
|
33
|
-
value: true
|
|
34
|
-
});
|
|
35
29
|
Object.defineProperty(this, "bufferSource", {
|
|
36
30
|
enumerable: true,
|
|
37
31
|
configurable: true,
|
|
@@ -77,6 +71,9 @@ class Note {
|
|
|
77
71
|
this.noteNumber = noteNumber;
|
|
78
72
|
this.velocity = velocity;
|
|
79
73
|
this.startTime = startTime;
|
|
74
|
+
this.ready = new Promise((resolve) => {
|
|
75
|
+
this.resolveReady = resolve;
|
|
76
|
+
});
|
|
80
77
|
}
|
|
81
78
|
}
|
|
82
79
|
// normalized to 0-1 for use with the SF2 modulator model
|
|
@@ -86,11 +83,11 @@ const defaultControllerState = {
|
|
|
86
83
|
pitchWheel: { type: 14, defaultValue: 8192 / 16383 },
|
|
87
84
|
pitchWheelSensitivity: { type: 16, defaultValue: 2 / 128 },
|
|
88
85
|
link: { type: 127, defaultValue: 0 },
|
|
89
|
-
|
|
86
|
+
modulationDepthMSB: { type: 128 + 1, defaultValue: 0 },
|
|
90
87
|
// dataMSB: { type: 128 + 6, defaultValue: 0, },
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
88
|
+
volumeMSB: { type: 128 + 7, defaultValue: 100 / 127 },
|
|
89
|
+
panMSB: { type: 128 + 10, defaultValue: 64 / 127 },
|
|
90
|
+
expressionMSB: { type: 128 + 11, defaultValue: 1 },
|
|
94
91
|
// dataLSB: { type: 128 + 38, defaultValue: 0, },
|
|
95
92
|
sustainPedal: { type: 128 + 64, defaultValue: 0 },
|
|
96
93
|
// rpnLSB: { type: 128 + 100, defaultValue: 127 },
|
|
@@ -150,8 +147,9 @@ const pitchEnvelopeKeys = [
|
|
|
150
147
|
"playbackRate",
|
|
151
148
|
];
|
|
152
149
|
const pitchEnvelopeKeySet = new Set(pitchEnvelopeKeys);
|
|
153
|
-
export class MidyGM1 {
|
|
150
|
+
export class MidyGM1 extends EventTarget {
|
|
154
151
|
constructor(audioContext) {
|
|
152
|
+
super();
|
|
155
153
|
Object.defineProperty(this, "mode", {
|
|
156
154
|
enumerable: true,
|
|
157
155
|
configurable: true,
|
|
@@ -266,6 +264,12 @@ export class MidyGM1 {
|
|
|
266
264
|
writable: true,
|
|
267
265
|
value: false
|
|
268
266
|
});
|
|
267
|
+
Object.defineProperty(this, "loop", {
|
|
268
|
+
enumerable: true,
|
|
269
|
+
configurable: true,
|
|
270
|
+
writable: true,
|
|
271
|
+
value: false
|
|
272
|
+
});
|
|
269
273
|
Object.defineProperty(this, "playPromise", {
|
|
270
274
|
enumerable: true,
|
|
271
275
|
configurable: true,
|
|
@@ -410,7 +414,7 @@ export class MidyGM1 {
|
|
|
410
414
|
return soundFontIndex * (2 ** 32) + (instrument << 16) + sampleID;
|
|
411
415
|
}
|
|
412
416
|
createChannelAudioNodes(audioContext) {
|
|
413
|
-
const { gainLeft, gainRight } = this.panToGain(defaultControllerState.
|
|
417
|
+
const { gainLeft, gainRight } = this.panToGain(defaultControllerState.panMSB.defaultValue);
|
|
414
418
|
const gainL = new GainNode(audioContext, { gain: gainLeft });
|
|
415
419
|
const gainR = new GainNode(audioContext, { gain: gainRight });
|
|
416
420
|
const merger = new ChannelMergerNode(audioContext, { numberOfInputs: 2 });
|
|
@@ -438,10 +442,9 @@ export class MidyGM1 {
|
|
|
438
442
|
return channels;
|
|
439
443
|
}
|
|
440
444
|
async createAudioBuffer(voiceParams) {
|
|
441
|
-
const sample = voiceParams
|
|
442
|
-
const
|
|
443
|
-
const
|
|
444
|
-
const audioBuffer = await sample.toAudioBuffer(this.audioContext, sampleStart, sampleEnd);
|
|
445
|
+
const { sample, start, end } = voiceParams;
|
|
446
|
+
const sampleEnd = sample.data.length + end;
|
|
447
|
+
const audioBuffer = await sample.toAudioBuffer(this.audioContext, start, sampleEnd);
|
|
445
448
|
return audioBuffer;
|
|
446
449
|
}
|
|
447
450
|
createBufferSource(voiceParams, audioBuffer) {
|
|
@@ -454,7 +457,7 @@ export class MidyGM1 {
|
|
|
454
457
|
}
|
|
455
458
|
return bufferSource;
|
|
456
459
|
}
|
|
457
|
-
|
|
460
|
+
scheduleTimelineEvents(scheduleTime, queueIndex) {
|
|
458
461
|
const timeOffset = this.resumeTime - this.startTime;
|
|
459
462
|
const lookAheadCheckTime = scheduleTime + timeOffset + this.lookAhead;
|
|
460
463
|
const schedulingOffset = this.startDelay - timeOffset;
|
|
@@ -466,12 +469,10 @@ export class MidyGM1 {
|
|
|
466
469
|
const startTime = event.startTime + schedulingOffset;
|
|
467
470
|
switch (event.type) {
|
|
468
471
|
case "noteOn":
|
|
469
|
-
|
|
472
|
+
this.noteOn(event.channel, event.noteNumber, event.velocity, startTime);
|
|
470
473
|
break;
|
|
471
474
|
case "noteOff": {
|
|
472
|
-
|
|
473
|
-
if (notePromise)
|
|
474
|
-
this.notePromises.push(notePromise);
|
|
475
|
+
this.noteOff(event.channel, event.noteNumber, event.velocity, startTime, false);
|
|
475
476
|
break;
|
|
476
477
|
}
|
|
477
478
|
case "controller":
|
|
@@ -509,22 +510,23 @@ export class MidyGM1 {
|
|
|
509
510
|
}
|
|
510
511
|
}
|
|
511
512
|
updateStates(queueIndex, nextQueueIndex) {
|
|
513
|
+
const now = this.audioContext.currentTime;
|
|
512
514
|
if (nextQueueIndex < queueIndex)
|
|
513
515
|
queueIndex = 0;
|
|
514
516
|
for (let i = queueIndex; i < nextQueueIndex; i++) {
|
|
515
517
|
const event = this.timeline[i];
|
|
516
518
|
switch (event.type) {
|
|
517
519
|
case "controller":
|
|
518
|
-
this.setControlChange(event.channel, event.controllerType, event.value,
|
|
520
|
+
this.setControlChange(event.channel, event.controllerType, event.value, now - this.resumeTime + event.startTime);
|
|
519
521
|
break;
|
|
520
522
|
case "programChange":
|
|
521
|
-
this.setProgramChange(event.channel, event.programNumber,
|
|
523
|
+
this.setProgramChange(event.channel, event.programNumber, now - this.resumeTime + event.startTime);
|
|
522
524
|
break;
|
|
523
525
|
case "pitchBend":
|
|
524
|
-
this.setPitchBend(event.channel, event.value + 8192,
|
|
526
|
+
this.setPitchBend(event.channel, event.value + 8192, now - this.resumeTime + event.startTime);
|
|
525
527
|
break;
|
|
526
528
|
case "sysEx":
|
|
527
|
-
this.handleSysEx(event.data,
|
|
529
|
+
this.handleSysEx(event.data, now - this.resumeTime + event.startTime);
|
|
528
530
|
}
|
|
529
531
|
}
|
|
530
532
|
}
|
|
@@ -532,44 +534,80 @@ export class MidyGM1 {
|
|
|
532
534
|
if (this.audioContext.state === "suspended") {
|
|
533
535
|
await this.audioContext.resume();
|
|
534
536
|
}
|
|
537
|
+
const paused = this.isPaused;
|
|
535
538
|
this.isPlaying = true;
|
|
536
539
|
this.isPaused = false;
|
|
537
540
|
this.startTime = this.audioContext.currentTime;
|
|
541
|
+
if (paused) {
|
|
542
|
+
this.dispatchEvent(new Event("resumed"));
|
|
543
|
+
}
|
|
544
|
+
else {
|
|
545
|
+
this.dispatchEvent(new Event("started"));
|
|
546
|
+
}
|
|
538
547
|
let queueIndex = this.getQueueIndex(this.resumeTime);
|
|
539
|
-
let
|
|
548
|
+
let exitReason;
|
|
540
549
|
this.notePromises = [];
|
|
541
|
-
while (
|
|
550
|
+
while (true) {
|
|
542
551
|
const now = this.audioContext.currentTime;
|
|
552
|
+
if (this.timeline.length <= queueIndex) {
|
|
553
|
+
await this.stopNotes(0, true, now);
|
|
554
|
+
if (this.loop) {
|
|
555
|
+
this.notePromises = [];
|
|
556
|
+
this.resetAllStates();
|
|
557
|
+
this.startTime = this.audioContext.currentTime;
|
|
558
|
+
this.resumeTime = 0;
|
|
559
|
+
queueIndex = 0;
|
|
560
|
+
this.dispatchEvent(new Event("looped"));
|
|
561
|
+
continue;
|
|
562
|
+
}
|
|
563
|
+
else {
|
|
564
|
+
await this.audioContext.suspend();
|
|
565
|
+
exitReason = "ended";
|
|
566
|
+
break;
|
|
567
|
+
}
|
|
568
|
+
}
|
|
543
569
|
if (this.isPausing) {
|
|
544
570
|
await this.stopNotes(0, true, now);
|
|
545
571
|
await this.audioContext.suspend();
|
|
546
572
|
this.notePromises = [];
|
|
573
|
+
this.isPausing = false;
|
|
574
|
+
exitReason = "paused";
|
|
547
575
|
break;
|
|
548
576
|
}
|
|
549
577
|
else if (this.isStopping) {
|
|
550
578
|
await this.stopNotes(0, true, now);
|
|
551
579
|
await this.audioContext.suspend();
|
|
552
|
-
|
|
580
|
+
this.isStopping = false;
|
|
581
|
+
exitReason = "stopped";
|
|
553
582
|
break;
|
|
554
583
|
}
|
|
555
584
|
else if (this.isSeeking) {
|
|
556
|
-
|
|
585
|
+
this.stopNotes(0, true, now);
|
|
557
586
|
this.startTime = this.audioContext.currentTime;
|
|
558
587
|
const nextQueueIndex = this.getQueueIndex(this.resumeTime);
|
|
559
588
|
this.updateStates(queueIndex, nextQueueIndex);
|
|
560
589
|
queueIndex = nextQueueIndex;
|
|
561
590
|
this.isSeeking = false;
|
|
591
|
+
this.dispatchEvent(new Event("seeked"));
|
|
562
592
|
continue;
|
|
563
593
|
}
|
|
564
|
-
queueIndex =
|
|
594
|
+
queueIndex = this.scheduleTimelineEvents(now, queueIndex);
|
|
565
595
|
const waitTime = now + this.noteCheckInterval;
|
|
566
596
|
await this.scheduleTask(() => { }, waitTime);
|
|
567
597
|
}
|
|
568
|
-
if (
|
|
598
|
+
if (exitReason !== "paused") {
|
|
569
599
|
this.notePromises = [];
|
|
570
600
|
this.resetAllStates();
|
|
571
601
|
}
|
|
572
602
|
this.isPlaying = false;
|
|
603
|
+
if (exitReason === "paused") {
|
|
604
|
+
this.isPaused = true;
|
|
605
|
+
this.dispatchEvent(new Event("paused"));
|
|
606
|
+
}
|
|
607
|
+
else {
|
|
608
|
+
this.isPaused = false;
|
|
609
|
+
this.dispatchEvent(new Event(exitReason));
|
|
610
|
+
}
|
|
573
611
|
}
|
|
574
612
|
ticksToSecond(ticks, secondsPerBeat) {
|
|
575
613
|
return ticks * secondsPerBeat / this.ticksPerBeat;
|
|
@@ -676,24 +714,20 @@ export class MidyGM1 {
|
|
|
676
714
|
return;
|
|
677
715
|
this.isStopping = true;
|
|
678
716
|
await this.playPromise;
|
|
679
|
-
this.isStopping = false;
|
|
680
717
|
}
|
|
681
718
|
async pause() {
|
|
682
719
|
if (!this.isPlaying || this.isPaused)
|
|
683
720
|
return;
|
|
684
721
|
const now = this.audioContext.currentTime;
|
|
685
|
-
this.resumeTime = now
|
|
722
|
+
this.resumeTime = now + this.resumeTime - this.startTime;
|
|
686
723
|
this.isPausing = true;
|
|
687
724
|
await this.playPromise;
|
|
688
|
-
this.isPausing = false;
|
|
689
|
-
this.isPaused = true;
|
|
690
725
|
}
|
|
691
726
|
async resume() {
|
|
692
727
|
if (!this.isPaused)
|
|
693
728
|
return;
|
|
694
729
|
this.playPromise = this.playNotes();
|
|
695
730
|
await this.playPromise;
|
|
696
|
-
this.isPaused = false;
|
|
697
731
|
}
|
|
698
732
|
seekTo(second) {
|
|
699
733
|
this.resumeTime = second;
|
|
@@ -716,19 +750,23 @@ export class MidyGM1 {
|
|
|
716
750
|
const now = this.audioContext.currentTime;
|
|
717
751
|
return now + this.resumeTime - this.startTime;
|
|
718
752
|
}
|
|
719
|
-
processScheduledNotes(channel, callback) {
|
|
753
|
+
async processScheduledNotes(channel, callback) {
|
|
720
754
|
const scheduledNotes = channel.scheduledNotes;
|
|
755
|
+
const tasks = [];
|
|
721
756
|
for (let i = channel.scheduleIndex; i < scheduledNotes.length; i++) {
|
|
722
757
|
const note = scheduledNotes[i];
|
|
723
758
|
if (!note)
|
|
724
759
|
continue;
|
|
725
760
|
if (note.ending)
|
|
726
761
|
continue;
|
|
727
|
-
callback(note);
|
|
762
|
+
const task = note.ready.then(() => callback(note));
|
|
763
|
+
tasks.push(task);
|
|
728
764
|
}
|
|
765
|
+
await Promise.all(tasks);
|
|
729
766
|
}
|
|
730
|
-
processActiveNotes(channel, scheduleTime, callback) {
|
|
767
|
+
async processActiveNotes(channel, scheduleTime, callback) {
|
|
731
768
|
const scheduledNotes = channel.scheduledNotes;
|
|
769
|
+
const tasks = [];
|
|
732
770
|
for (let i = channel.scheduleIndex; i < scheduledNotes.length; i++) {
|
|
733
771
|
const note = scheduledNotes[i];
|
|
734
772
|
if (!note)
|
|
@@ -737,8 +775,10 @@ export class MidyGM1 {
|
|
|
737
775
|
continue;
|
|
738
776
|
if (scheduleTime < note.startTime)
|
|
739
777
|
break;
|
|
740
|
-
callback(note);
|
|
778
|
+
const task = note.ready.then(() => callback(note));
|
|
779
|
+
tasks.push(task);
|
|
741
780
|
}
|
|
781
|
+
await Promise.all(tasks);
|
|
742
782
|
}
|
|
743
783
|
cbToRatio(cb) {
|
|
744
784
|
return Math.pow(10, cb / 200);
|
|
@@ -904,7 +944,13 @@ export class MidyGM1 {
|
|
|
904
944
|
}
|
|
905
945
|
note.bufferSource.connect(note.filterNode);
|
|
906
946
|
note.filterNode.connect(note.volumeEnvelopeNode);
|
|
907
|
-
|
|
947
|
+
if (voiceParams.sample.type === "compressed") {
|
|
948
|
+
const offset = voiceParams.start / audioBuffer.sampleRate;
|
|
949
|
+
note.bufferSource.start(startTime, offset);
|
|
950
|
+
}
|
|
951
|
+
else {
|
|
952
|
+
note.bufferSource.start(startTime);
|
|
953
|
+
}
|
|
908
954
|
return note;
|
|
909
955
|
}
|
|
910
956
|
handleExclusiveClass(note, channelNumber, startTime) {
|
|
@@ -954,11 +1000,7 @@ export class MidyGM1 {
|
|
|
954
1000
|
return;
|
|
955
1001
|
await this.setNoteAudioNode(channel, note, realtime);
|
|
956
1002
|
this.setNoteRouting(channelNumber, note, startTime);
|
|
957
|
-
note.
|
|
958
|
-
const off = note.offEvent;
|
|
959
|
-
if (off) {
|
|
960
|
-
this.noteOff(channelNumber, noteNumber, off.velocity, off.startTime);
|
|
961
|
-
}
|
|
1003
|
+
note.resolveReady();
|
|
962
1004
|
}
|
|
963
1005
|
disconnectNote(note) {
|
|
964
1006
|
note.bufferSource.disconnect();
|
|
@@ -992,7 +1034,7 @@ export class MidyGM1 {
|
|
|
992
1034
|
}, stopTime);
|
|
993
1035
|
});
|
|
994
1036
|
}
|
|
995
|
-
noteOff(channelNumber, noteNumber,
|
|
1037
|
+
noteOff(channelNumber, noteNumber, _velocity, endTime, force) {
|
|
996
1038
|
const channel = this.channels[channelNumber];
|
|
997
1039
|
if (!force && 0.5 <= channel.state.sustainPedal)
|
|
998
1040
|
return;
|
|
@@ -1000,13 +1042,13 @@ export class MidyGM1 {
|
|
|
1000
1042
|
if (index < 0)
|
|
1001
1043
|
return;
|
|
1002
1044
|
const note = channel.scheduledNotes[index];
|
|
1003
|
-
if (note.pending) {
|
|
1004
|
-
note.offEvent = { velocity, startTime: endTime };
|
|
1005
|
-
return;
|
|
1006
|
-
}
|
|
1007
1045
|
note.ending = true;
|
|
1008
1046
|
this.setNoteIndex(channel, index);
|
|
1009
|
-
|
|
1047
|
+
const promise = note.ready.then(() => {
|
|
1048
|
+
return this.releaseNote(channel, note, endTime);
|
|
1049
|
+
});
|
|
1050
|
+
this.notePromises.push(promise);
|
|
1051
|
+
return promise;
|
|
1010
1052
|
}
|
|
1011
1053
|
setNoteIndex(channel, index) {
|
|
1012
1054
|
let allEnds = true;
|
|
@@ -1092,7 +1134,8 @@ export class MidyGM1 {
|
|
|
1092
1134
|
}
|
|
1093
1135
|
setPitchBend(channelNumber, value, scheduleTime) {
|
|
1094
1136
|
const channel = this.channels[channelNumber];
|
|
1095
|
-
|
|
1137
|
+
if (!(0 <= scheduleTime))
|
|
1138
|
+
scheduleTime = this.audioContext.currentTime;
|
|
1096
1139
|
const state = channel.state;
|
|
1097
1140
|
const prev = state.pitchWheel * 2 - 1;
|
|
1098
1141
|
const next = (value - 8192) / 8192;
|
|
@@ -1104,11 +1147,12 @@ export class MidyGM1 {
|
|
|
1104
1147
|
setModLfoToPitch(channel, note, scheduleTime) {
|
|
1105
1148
|
if (note.modulationDepth) {
|
|
1106
1149
|
const modLfoToPitch = note.voiceParams.modLfoToPitch;
|
|
1107
|
-
const baseDepth = Math.abs(modLfoToPitch) +
|
|
1108
|
-
|
|
1150
|
+
const baseDepth = Math.abs(modLfoToPitch) +
|
|
1151
|
+
channel.state.modulationDepthMSB;
|
|
1152
|
+
const depth = baseDepth * Math.sign(modLfoToPitch);
|
|
1109
1153
|
note.modulationDepth.gain
|
|
1110
1154
|
.cancelScheduledValues(scheduleTime)
|
|
1111
|
-
.setValueAtTime(
|
|
1155
|
+
.setValueAtTime(depth, scheduleTime);
|
|
1112
1156
|
}
|
|
1113
1157
|
else {
|
|
1114
1158
|
this.startModulation(channel, note, scheduleTime);
|
|
@@ -1145,18 +1189,18 @@ export class MidyGM1 {
|
|
|
1145
1189
|
createVoiceParamsHandlers() {
|
|
1146
1190
|
return {
|
|
1147
1191
|
modLfoToPitch: (channel, note, scheduleTime) => {
|
|
1148
|
-
if (0 < channel.state.
|
|
1192
|
+
if (0 < channel.state.modulationDepthMSB) {
|
|
1149
1193
|
this.setModLfoToPitch(channel, note, scheduleTime);
|
|
1150
1194
|
}
|
|
1151
1195
|
},
|
|
1152
1196
|
vibLfoToPitch: (_channel, _note, _scheduleTime) => { },
|
|
1153
1197
|
modLfoToFilterFc: (channel, note, scheduleTime) => {
|
|
1154
|
-
if (0 < channel.state.
|
|
1198
|
+
if (0 < channel.state.modulationDepthMSB) {
|
|
1155
1199
|
this.setModLfoToFilterFc(note, scheduleTime);
|
|
1156
1200
|
}
|
|
1157
1201
|
},
|
|
1158
1202
|
modLfoToVolume: (channel, note, scheduleTime) => {
|
|
1159
|
-
if (0 < channel.state.
|
|
1203
|
+
if (0 < channel.state.modulationDepthMSB) {
|
|
1160
1204
|
this.setModLfoToVolume(note, scheduleTime);
|
|
1161
1205
|
}
|
|
1162
1206
|
},
|
|
@@ -1168,7 +1212,7 @@ export class MidyGM1 {
|
|
|
1168
1212
|
}
|
|
1169
1213
|
},
|
|
1170
1214
|
freqModLFO: (_channel, note, scheduleTime) => {
|
|
1171
|
-
if (0 < channel.state.
|
|
1215
|
+
if (0 < channel.state.modulationDepthMSB) {
|
|
1172
1216
|
this.setFreqModLFO(note, scheduleTime);
|
|
1173
1217
|
}
|
|
1174
1218
|
},
|
|
@@ -1243,7 +1287,8 @@ export class MidyGM1 {
|
|
|
1243
1287
|
}
|
|
1244
1288
|
}
|
|
1245
1289
|
updateModulation(channel, scheduleTime) {
|
|
1246
|
-
const depth = channel.state.
|
|
1290
|
+
const depth = channel.state.modulationDepthMSB *
|
|
1291
|
+
channel.modulationDepthRange;
|
|
1247
1292
|
this.processScheduledNotes(channel, (note) => {
|
|
1248
1293
|
if (note.modulationDepth) {
|
|
1249
1294
|
note.modulationDepth.gain.setValueAtTime(depth, scheduleTime);
|
|
@@ -1255,14 +1300,16 @@ export class MidyGM1 {
|
|
|
1255
1300
|
}
|
|
1256
1301
|
setModulationDepth(channelNumber, modulation, scheduleTime) {
|
|
1257
1302
|
const channel = this.channels[channelNumber];
|
|
1258
|
-
|
|
1259
|
-
|
|
1303
|
+
if (!(0 <= scheduleTime))
|
|
1304
|
+
scheduleTime = this.audioContext.currentTime;
|
|
1305
|
+
channel.state.modulationDepthMSB = modulation / 127;
|
|
1260
1306
|
this.updateModulation(channel, scheduleTime);
|
|
1261
1307
|
}
|
|
1262
1308
|
setVolume(channelNumber, volume, scheduleTime) {
|
|
1263
|
-
|
|
1309
|
+
if (!(0 <= scheduleTime))
|
|
1310
|
+
scheduleTime = this.audioContext.currentTime;
|
|
1264
1311
|
const channel = this.channels[channelNumber];
|
|
1265
|
-
channel.state.
|
|
1312
|
+
channel.state.volumeMSB = volume / 127;
|
|
1266
1313
|
this.updateChannelVolume(channel, scheduleTime);
|
|
1267
1314
|
}
|
|
1268
1315
|
panToGain(pan) {
|
|
@@ -1273,15 +1320,17 @@ export class MidyGM1 {
|
|
|
1273
1320
|
};
|
|
1274
1321
|
}
|
|
1275
1322
|
setPan(channelNumber, pan, scheduleTime) {
|
|
1276
|
-
|
|
1323
|
+
if (!(0 <= scheduleTime))
|
|
1324
|
+
scheduleTime = this.audioContext.currentTime;
|
|
1277
1325
|
const channel = this.channels[channelNumber];
|
|
1278
|
-
channel.state.
|
|
1326
|
+
channel.state.panMSB = pan / 127;
|
|
1279
1327
|
this.updateChannelVolume(channel, scheduleTime);
|
|
1280
1328
|
}
|
|
1281
1329
|
setExpression(channelNumber, expression, scheduleTime) {
|
|
1282
|
-
|
|
1330
|
+
if (!(0 <= scheduleTime))
|
|
1331
|
+
scheduleTime = this.audioContext.currentTime;
|
|
1283
1332
|
const channel = this.channels[channelNumber];
|
|
1284
|
-
channel.state.
|
|
1333
|
+
channel.state.expressionMSB = expression / 127;
|
|
1285
1334
|
this.updateChannelVolume(channel, scheduleTime);
|
|
1286
1335
|
}
|
|
1287
1336
|
dataEntryLSB(channelNumber, value, scheduleTime) {
|
|
@@ -1290,8 +1339,8 @@ export class MidyGM1 {
|
|
|
1290
1339
|
}
|
|
1291
1340
|
updateChannelVolume(channel, scheduleTime) {
|
|
1292
1341
|
const state = channel.state;
|
|
1293
|
-
const volume = state.
|
|
1294
|
-
const { gainLeft, gainRight } = this.panToGain(state.
|
|
1342
|
+
const volume = state.volumeMSB * state.expressionMSB;
|
|
1343
|
+
const { gainLeft, gainRight } = this.panToGain(state.panMSB);
|
|
1295
1344
|
channel.gainL.gain
|
|
1296
1345
|
.cancelScheduledValues(scheduleTime)
|
|
1297
1346
|
.setValueAtTime(volume * gainLeft, scheduleTime);
|
|
@@ -1301,7 +1350,8 @@ export class MidyGM1 {
|
|
|
1301
1350
|
}
|
|
1302
1351
|
setSustainPedal(channelNumber, value, scheduleTime) {
|
|
1303
1352
|
const channel = this.channels[channelNumber];
|
|
1304
|
-
|
|
1353
|
+
if (!(0 <= scheduleTime))
|
|
1354
|
+
scheduleTime = this.audioContext.currentTime;
|
|
1305
1355
|
channel.state.sustainPedal = value / 127;
|
|
1306
1356
|
if (64 <= value) {
|
|
1307
1357
|
this.processScheduledNotes(channel, (note) => {
|
|
@@ -1373,7 +1423,8 @@ export class MidyGM1 {
|
|
|
1373
1423
|
}
|
|
1374
1424
|
setPitchBendRange(channelNumber, value, scheduleTime) {
|
|
1375
1425
|
const channel = this.channels[channelNumber];
|
|
1376
|
-
|
|
1426
|
+
if (!(0 <= scheduleTime))
|
|
1427
|
+
scheduleTime = this.audioContext.currentTime;
|
|
1377
1428
|
const state = channel.state;
|
|
1378
1429
|
const prev = state.pitchWheelSensitivity;
|
|
1379
1430
|
const next = value / 12800;
|
|
@@ -1391,7 +1442,8 @@ export class MidyGM1 {
|
|
|
1391
1442
|
}
|
|
1392
1443
|
setFineTuning(channelNumber, value, scheduleTime) {
|
|
1393
1444
|
const channel = this.channels[channelNumber];
|
|
1394
|
-
|
|
1445
|
+
if (!(0 <= scheduleTime))
|
|
1446
|
+
scheduleTime = this.audioContext.currentTime;
|
|
1395
1447
|
const prev = channel.fineTuning;
|
|
1396
1448
|
const next = value;
|
|
1397
1449
|
channel.fineTuning = next;
|
|
@@ -1406,7 +1458,8 @@ export class MidyGM1 {
|
|
|
1406
1458
|
}
|
|
1407
1459
|
setCoarseTuning(channelNumber, value, scheduleTime) {
|
|
1408
1460
|
const channel = this.channels[channelNumber];
|
|
1409
|
-
|
|
1461
|
+
if (!(0 <= scheduleTime))
|
|
1462
|
+
scheduleTime = this.audioContext.currentTime;
|
|
1410
1463
|
const prev = channel.coarseTuning;
|
|
1411
1464
|
const next = value;
|
|
1412
1465
|
channel.coarseTuning = next;
|
|
@@ -1414,7 +1467,8 @@ export class MidyGM1 {
|
|
|
1414
1467
|
this.updateChannelDetune(channel, scheduleTime);
|
|
1415
1468
|
}
|
|
1416
1469
|
allSoundOff(channelNumber, _value, scheduleTime) {
|
|
1417
|
-
|
|
1470
|
+
if (!(0 <= scheduleTime))
|
|
1471
|
+
scheduleTime = this.audioContext.currentTime;
|
|
1418
1472
|
return this.stopActiveNotes(channelNumber, 0, true, scheduleTime);
|
|
1419
1473
|
}
|
|
1420
1474
|
resetChannelStates(channelNumber) {
|
|
@@ -1439,8 +1493,8 @@ export class MidyGM1 {
|
|
|
1439
1493
|
resetAllControllers(channelNumber, _value, scheduleTime) {
|
|
1440
1494
|
const keys = [
|
|
1441
1495
|
"pitchWheel",
|
|
1442
|
-
"
|
|
1443
|
-
"
|
|
1496
|
+
"expressionMSB",
|
|
1497
|
+
"modulationDepthMSB",
|
|
1444
1498
|
"sustainPedal",
|
|
1445
1499
|
];
|
|
1446
1500
|
const channel = this.channels[channelNumber];
|
|
@@ -1466,7 +1520,8 @@ export class MidyGM1 {
|
|
|
1466
1520
|
}
|
|
1467
1521
|
}
|
|
1468
1522
|
allNotesOff(channelNumber, _value, scheduleTime) {
|
|
1469
|
-
|
|
1523
|
+
if (!(0 <= scheduleTime))
|
|
1524
|
+
scheduleTime = this.audioContext.currentTime;
|
|
1470
1525
|
return this.stopActiveNotes(channelNumber, 0, false, scheduleTime);
|
|
1471
1526
|
}
|
|
1472
1527
|
handleUniversalNonRealTimeExclusiveMessage(data, scheduleTime) {
|
|
@@ -1487,7 +1542,8 @@ export class MidyGM1 {
|
|
|
1487
1542
|
}
|
|
1488
1543
|
}
|
|
1489
1544
|
GM1SystemOn(scheduleTime) {
|
|
1490
|
-
|
|
1545
|
+
if (!(0 <= scheduleTime))
|
|
1546
|
+
scheduleTime = this.audioContext.currentTime;
|
|
1491
1547
|
this.mode = "GM1";
|
|
1492
1548
|
for (let i = 0; i < this.channels.length; i++) {
|
|
1493
1549
|
this.allSoundOff(i, 0, scheduleTime);
|
|
@@ -1515,7 +1571,8 @@ export class MidyGM1 {
|
|
|
1515
1571
|
this.setMasterVolume(volume, scheduleTime);
|
|
1516
1572
|
}
|
|
1517
1573
|
setMasterVolume(value, scheduleTime) {
|
|
1518
|
-
|
|
1574
|
+
if (!(0 <= scheduleTime))
|
|
1575
|
+
scheduleTime = this.audioContext.currentTime;
|
|
1519
1576
|
this.masterVolume.gain
|
|
1520
1577
|
.cancelScheduledValues(scheduleTime)
|
|
1521
1578
|
.setValueAtTime(value * value, scheduleTime);
|