@marmooo/midy 0.2.9 → 0.3.1
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 -1
- package/esm/midy-GM1.d.ts +11 -7
- package/esm/midy-GM1.d.ts.map +1 -1
- package/esm/midy-GM1.js +114 -74
- package/esm/midy-GM2.d.ts +17 -11
- package/esm/midy-GM2.d.ts.map +1 -1
- package/esm/midy-GM2.js +250 -124
- package/esm/midy-GMLite.d.ts +14 -9
- package/esm/midy-GMLite.d.ts.map +1 -1
- package/esm/midy-GMLite.js +167 -81
- package/esm/midy.d.ts +20 -14
- package/esm/midy.d.ts.map +1 -1
- package/esm/midy.js +257 -129
- package/package.json +1 -1
- package/script/midy-GM1.d.ts +11 -7
- package/script/midy-GM1.d.ts.map +1 -1
- package/script/midy-GM1.js +114 -74
- package/script/midy-GM2.d.ts +17 -11
- package/script/midy-GM2.d.ts.map +1 -1
- package/script/midy-GM2.js +250 -124
- package/script/midy-GMLite.d.ts +14 -9
- package/script/midy-GMLite.d.ts.map +1 -1
- package/script/midy-GMLite.js +167 -81
- package/script/midy.d.ts +20 -14
- package/script/midy.d.ts.map +1 -1
- package/script/midy.js +257 -129
package/script/midy-GMLite.d.ts
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
export class MidyGMLite {
|
|
2
2
|
static channelSettings: {
|
|
3
|
-
currentBufferSource: null;
|
|
4
|
-
isDrum: boolean;
|
|
5
3
|
detune: number;
|
|
6
|
-
|
|
4
|
+
programNumber: number;
|
|
7
5
|
bank: number;
|
|
8
6
|
dataMSB: number;
|
|
9
7
|
dataLSB: number;
|
|
@@ -13,6 +11,7 @@ export class MidyGMLite {
|
|
|
13
11
|
};
|
|
14
12
|
constructor(audioContext: any);
|
|
15
13
|
mode: string;
|
|
14
|
+
numChannels: number;
|
|
16
15
|
ticksPerBeat: number;
|
|
17
16
|
totalTime: number;
|
|
18
17
|
noteCheckInterval: number;
|
|
@@ -32,7 +31,8 @@ export class MidyGMLite {
|
|
|
32
31
|
timeline: any[];
|
|
33
32
|
instruments: any[];
|
|
34
33
|
notePromises: any[];
|
|
35
|
-
|
|
34
|
+
exclusiveClassNotes: any[];
|
|
35
|
+
drumExclusiveClassNotes: any[];
|
|
36
36
|
audioContext: any;
|
|
37
37
|
masterVolume: any;
|
|
38
38
|
scheduler: any;
|
|
@@ -75,8 +75,7 @@ export class MidyGMLite {
|
|
|
75
75
|
};
|
|
76
76
|
createChannels(audioContext: any): any[];
|
|
77
77
|
createNoteBuffer(voiceParams: any, isSF3: any): Promise<any>;
|
|
78
|
-
|
|
79
|
-
createBufferSource(channel: any, voiceParams: any, audioBuffer: any): any;
|
|
78
|
+
createBufferSource(voiceParams: any, audioBuffer: any): any;
|
|
80
79
|
scheduleTimelineEvents(t: any, offset: any, queueIndex: any): Promise<any>;
|
|
81
80
|
getQueueIndex(second: any): number;
|
|
82
81
|
playNotes(): Promise<any>;
|
|
@@ -87,6 +86,7 @@ export class MidyGMLite {
|
|
|
87
86
|
instruments: Set<any>;
|
|
88
87
|
timeline: any[];
|
|
89
88
|
};
|
|
89
|
+
stopActiveNotes(channelNumber: any, velocity: any, force: any, scheduleTime: any): Promise<any[]>;
|
|
90
90
|
stopChannelNotes(channelNumber: any, velocity: any, force: any, scheduleTime: any): Promise<any[]>;
|
|
91
91
|
stopNotes(velocity: any, force: any, scheduleTime: any): Promise<any[]>;
|
|
92
92
|
start(): Promise<void>;
|
|
@@ -111,16 +111,20 @@ export class MidyGMLite {
|
|
|
111
111
|
clampCutoffFrequency(frequency: any): number;
|
|
112
112
|
setFilterEnvelope(note: any, scheduleTime: any): void;
|
|
113
113
|
startModulation(channel: any, note: any, scheduleTime: any): void;
|
|
114
|
-
getAudioBuffer(
|
|
114
|
+
getAudioBuffer(programNumber: any, noteNumber: any, velocity: any, voiceParams: any, isSF3: any): Promise<any>;
|
|
115
115
|
createNote(channel: any, voice: any, noteNumber: any, velocity: any, startTime: any, isSF3: any): Promise<Note>;
|
|
116
|
+
handleExclusiveClass(note: any, channelNumber: any, startTime: any): void;
|
|
117
|
+
handleDrumExclusiveClass(note: any, channelNumber: any, startTime: any): void;
|
|
116
118
|
scheduleNoteOn(channelNumber: any, noteNumber: any, velocity: any, startTime: any): Promise<void>;
|
|
117
119
|
noteOn(channelNumber: any, noteNumber: any, velocity: any, scheduleTime: any): Promise<void>;
|
|
118
|
-
|
|
120
|
+
disconnectNote(note: any): void;
|
|
121
|
+
stopNote(endTime: any, stopTime: any, noteList: any, index: any): Promise<any>;
|
|
122
|
+
findNoteOffTarget(noteList: any): any[] | undefined;
|
|
119
123
|
scheduleNoteOff(channelNumber: any, noteNumber: any, _velocity: any, endTime: any, force: any): Promise<any> | undefined;
|
|
120
124
|
noteOff(channelNumber: any, noteNumber: any, velocity: any, scheduleTime: any): Promise<any> | undefined;
|
|
121
125
|
releaseSustainPedal(channelNumber: any, halfVelocity: any, scheduleTime: any): (Promise<any> | undefined)[];
|
|
122
126
|
handleMIDIMessage(statusByte: any, data1: any, data2: any, scheduleTime: any): void | Promise<any>;
|
|
123
|
-
handleProgramChange(channelNumber: any,
|
|
127
|
+
handleProgramChange(channelNumber: any, programNumber: any, _scheduleTime: any): void;
|
|
124
128
|
handlePitchBendMessage(channelNumber: any, lsb: any, msb: any, scheduleTime: any): void;
|
|
125
129
|
setPitchBend(channelNumber: any, value: any, scheduleTime: any): void;
|
|
126
130
|
setModLfoToPitch(channel: any, note: any, scheduleTime: any): void;
|
|
@@ -177,6 +181,7 @@ export class MidyGMLite {
|
|
|
177
181
|
handlePitchBendRangeRPN(channelNumber: any, scheduleTime: any): void;
|
|
178
182
|
setPitchBendRange(channelNumber: any, value: any, scheduleTime: any): void;
|
|
179
183
|
allSoundOff(channelNumber: any, _value: any, scheduleTime: any): Promise<any[]>;
|
|
184
|
+
resetAllStates(channelNumber: any): void;
|
|
180
185
|
resetAllControllers(channelNumber: any): void;
|
|
181
186
|
allNotesOff(channelNumber: any, _value: any, scheduleTime: any): Promise<any[]>;
|
|
182
187
|
handleUniversalNonRealTimeExclusiveMessage(data: any, scheduleTime: any): void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"midy-GMLite.d.ts","sourceRoot":"","sources":["../src/midy-GMLite.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"midy-GMLite.d.ts","sourceRoot":"","sources":["../src/midy-GMLite.js"],"names":[],"mappings":"AA+JA;IA2BE;;;;;;;;;MASE;IAEF,+BAcC;IAnDD,aAAa;IACb,oBAAiB;IACjB,qBAAmB;IACnB,kBAAc;IACd,0BAAwB;IACxB,kBAAc;IACd,mBAAiB;IACjB,kBAAc;IACd,mBAAe;IACf,kBAAgB;IAChB,sBAA2C;IAC3C,kCAA+B;IAC/B,gCAA6B;IAC7B,mBAAkB;IAClB,mBAAkB;IAClB,kBAAiB;IACjB,oBAAmB;IACnB,mBAAkB;IAClB,gBAAc;IACd,mBAAiB;IACjB,oBAAkB;IAClB,2BAAqC;IACrC,+BAEE;IAcA,kBAAgC;IAChC,kBAA8C;IAC9C,eAAwD;IACxD,qBAGE;IACF;;;;;;;;;;;MAA2D;IAC3D;;;;;;;;;;;;;MAA+D;IAC/D,gBAAiD;IAMnD,4BAMC;IAED,mCAWC;IAED,gDAMC;IAED,sCASC;IAED;;;;MAeC;IAED,yCAaC;IAED,6DA2BC;IAED,4DASC;IAED,2EAsDC;IAED,mCAOC;IAED,0BAoDC;IAED,uDAEC;IAED,wDAEC;IAED,6EAEC;IAED;;;MA4EC;IAED,kGAiBC;IAED,mGAgBC;IAED,wEAMC;IAED,uBAKC;IAED,aAMC;IAED,cAKC;IAED,wBAIC;IAED,0BAKC;IAED,wBAOC;IAED,sBAGC;IAED,yDASC;IAED,2DASC;IAED,qDAQC;IAED,2BAEC;IAED,8BAEC;IAED,8BAEC;IAED,4BAEC;IAED,wCAIC;IAED,2DAIC;IAED,+DAIC;IAED,sDAeC;IAED,qDAoBC;IAED,6CAIC;IAED,sDAsBC;IAED,kEAoBC;IAED,+GA0BC;IAED,gHAwCC;IAED,0EAiBC;IAED,8EAiBC;IAED,kGAoDC;IAED,6FAQC;IAED,gCASC;IAED,+EAiBC;IAED,oDAOC;IAED,yHAuBC;IAED,yGASC;IAED,4GAeC;IAED,mGA2BC;IAED,sFAGC;IAED,wFAGC;IAED,sEAUC;IAED,mEAQC;IAED,wDAKC;IAED,sDAOC;IAED,mDAMC;IAED,kDAKC;IAED;;;;;;;;;;;MA2BC;IAED,oFAMC;IAED,6EA2CC;IAED;;;;;;;;;;;;;MAeC;IAED,kGAWC;IAED,wDAUC;IAED,iFAKC;IAED,oEAKC;IAED;;;MAMC;IAED,8DAKC;IAED,4EAKC;IAED,sEAGC;IAED,2DAUC;IAED,yEAWC;IAED,kFAeC;IAED,uDAYC;IAED,gDAEC;IAED,gDAEC;IAED,sEAGC;IAED,qEAKC;IAED,2EAUC;IAED,gFAGC;IAED,yCAUC;IAGD,8CAqBC;IAED,gFAGC;IAED,+EAgBC;IAED,qCAWC;IAED,4EAaC;IAED,4DAGC;IAED,sDASC;IAED,gDAYC;IAGD,6DAgBC;CACF;AA1/CD;IACE,uBAGC;IAFC,YAA2B;IAC3B,qBAAuB;IAGzB,gCAKC;IAED,mBAEC;IAED,0BAUC;IAED,uBAEC;IAED,mBAEC;IAED,cAMC;IASD,6BAKC;IAZD,qDAKC;CAQF;AAED;IASE,0FAMC;IAdD,kBAAa;IACb,gBAAW;IACX,iBAAY;IACZ,wBAAmB;IACnB,iBAAY;IACZ,mBAAc;IACd,qBAAgB;IAGd,gBAA4B;IAC5B,cAAwB;IACxB,eAA0B;IAC1B,WAAkB;IAClB,iBAA8B;CAEjC"}
|
package/script/midy-GMLite.js
CHANGED
|
@@ -106,6 +106,19 @@ class Note {
|
|
|
106
106
|
this.voiceParams = voiceParams;
|
|
107
107
|
}
|
|
108
108
|
}
|
|
109
|
+
const drumExclusiveClasses = new Uint8Array(128);
|
|
110
|
+
drumExclusiveClasses[42] = 1;
|
|
111
|
+
drumExclusiveClasses[44] = 1;
|
|
112
|
+
drumExclusiveClasses[46] = 1, // HH
|
|
113
|
+
drumExclusiveClasses[71] = 2;
|
|
114
|
+
drumExclusiveClasses[72] = 2; // Whistle
|
|
115
|
+
drumExclusiveClasses[73] = 3;
|
|
116
|
+
drumExclusiveClasses[74] = 3; // Guiro
|
|
117
|
+
drumExclusiveClasses[78] = 4;
|
|
118
|
+
drumExclusiveClasses[79] = 4; // Cuica
|
|
119
|
+
drumExclusiveClasses[80] = 5;
|
|
120
|
+
drumExclusiveClasses[81] = 5; // Triangle
|
|
121
|
+
const drumExclusiveClassCount = 5;
|
|
109
122
|
// normalized to 0-1 for use with the SF2 modulator model
|
|
110
123
|
const defaultControllerState = {
|
|
111
124
|
noteOnVelocity: { type: 2, defaultValue: 0 },
|
|
@@ -179,6 +192,12 @@ class MidyGMLite {
|
|
|
179
192
|
writable: true,
|
|
180
193
|
value: "GM1"
|
|
181
194
|
});
|
|
195
|
+
Object.defineProperty(this, "numChannels", {
|
|
196
|
+
enumerable: true,
|
|
197
|
+
configurable: true,
|
|
198
|
+
writable: true,
|
|
199
|
+
value: 16
|
|
200
|
+
});
|
|
182
201
|
Object.defineProperty(this, "ticksPerBeat", {
|
|
183
202
|
enumerable: true,
|
|
184
203
|
configurable: true,
|
|
@@ -293,11 +312,17 @@ class MidyGMLite {
|
|
|
293
312
|
writable: true,
|
|
294
313
|
value: []
|
|
295
314
|
});
|
|
296
|
-
Object.defineProperty(this, "
|
|
315
|
+
Object.defineProperty(this, "exclusiveClassNotes", {
|
|
316
|
+
enumerable: true,
|
|
317
|
+
configurable: true,
|
|
318
|
+
writable: true,
|
|
319
|
+
value: new Array(128)
|
|
320
|
+
});
|
|
321
|
+
Object.defineProperty(this, "drumExclusiveClassNotes", {
|
|
297
322
|
enumerable: true,
|
|
298
323
|
configurable: true,
|
|
299
324
|
writable: true,
|
|
300
|
-
value: new
|
|
325
|
+
value: new Array(this.numChannels * drumExclusiveClassCount)
|
|
301
326
|
});
|
|
302
327
|
this.audioContext = audioContext;
|
|
303
328
|
this.masterVolume = new GainNode(audioContext);
|
|
@@ -364,8 +389,10 @@ class MidyGMLite {
|
|
|
364
389
|
};
|
|
365
390
|
}
|
|
366
391
|
createChannels(audioContext) {
|
|
367
|
-
const channels = Array.from({ length:
|
|
392
|
+
const channels = Array.from({ length: this.numChannels }, () => {
|
|
368
393
|
return {
|
|
394
|
+
currentBufferSource: null,
|
|
395
|
+
isDrum: false,
|
|
369
396
|
...this.constructor.channelSettings,
|
|
370
397
|
state: new ControllerState(),
|
|
371
398
|
...this.setChannelAudioNodes(audioContext),
|
|
@@ -404,18 +431,10 @@ class MidyGMLite {
|
|
|
404
431
|
return audioBuffer;
|
|
405
432
|
}
|
|
406
433
|
}
|
|
407
|
-
|
|
408
|
-
if (channel.isDrum) {
|
|
409
|
-
return false;
|
|
410
|
-
}
|
|
411
|
-
else {
|
|
412
|
-
return voiceParams.sampleModes % 2 !== 0;
|
|
413
|
-
}
|
|
414
|
-
}
|
|
415
|
-
createBufferSource(channel, voiceParams, audioBuffer) {
|
|
434
|
+
createBufferSource(voiceParams, audioBuffer) {
|
|
416
435
|
const bufferSource = new AudioBufferSourceNode(this.audioContext);
|
|
417
436
|
bufferSource.buffer = audioBuffer;
|
|
418
|
-
bufferSource.loop =
|
|
437
|
+
bufferSource.loop = voiceParams.sampleModes % 2 !== 0;
|
|
419
438
|
if (bufferSource.loop) {
|
|
420
439
|
bufferSource.loopStart = voiceParams.loopStart / voiceParams.sampleRate;
|
|
421
440
|
bufferSource.loopEnd = voiceParams.loopEnd / voiceParams.sampleRate;
|
|
@@ -430,7 +449,7 @@ class MidyGMLite {
|
|
|
430
449
|
const startTime = event.startTime + this.startDelay - offset;
|
|
431
450
|
switch (event.type) {
|
|
432
451
|
case "noteOn":
|
|
433
|
-
if (event.velocity
|
|
452
|
+
if (0 < event.velocity) {
|
|
434
453
|
await this.scheduleNoteOn(event.channel, event.noteNumber, event.velocity, startTime);
|
|
435
454
|
break;
|
|
436
455
|
}
|
|
@@ -478,7 +497,7 @@ class MidyGMLite {
|
|
|
478
497
|
if (queueIndex >= this.timeline.length) {
|
|
479
498
|
await Promise.all(this.notePromises);
|
|
480
499
|
this.notePromises = [];
|
|
481
|
-
this.
|
|
500
|
+
this.exclusiveClassNotes.fill(undefined);
|
|
482
501
|
this.audioBufferCache.clear();
|
|
483
502
|
resolve();
|
|
484
503
|
return;
|
|
@@ -497,7 +516,7 @@ class MidyGMLite {
|
|
|
497
516
|
else if (this.isStopping) {
|
|
498
517
|
await this.stopNotes(0, true, now);
|
|
499
518
|
this.notePromises = [];
|
|
500
|
-
this.
|
|
519
|
+
this.exclusiveClassNotes.fill(undefined);
|
|
501
520
|
this.audioBufferCache.clear();
|
|
502
521
|
resolve();
|
|
503
522
|
this.isStopping = false;
|
|
@@ -506,7 +525,7 @@ class MidyGMLite {
|
|
|
506
525
|
}
|
|
507
526
|
else if (this.isSeeking) {
|
|
508
527
|
this.stopNotes(0, true, now);
|
|
509
|
-
this.
|
|
528
|
+
this.exclusiveClassNotes.fill(undefined);
|
|
510
529
|
this.startTime = this.audioContext.currentTime;
|
|
511
530
|
queueIndex = this.getQueueIndex(this.resumeTime);
|
|
512
531
|
offset = this.resumeTime - this.startTime;
|
|
@@ -534,7 +553,7 @@ class MidyGMLite {
|
|
|
534
553
|
extractMidiData(midi) {
|
|
535
554
|
const instruments = new Set();
|
|
536
555
|
const timeline = [];
|
|
537
|
-
const tmpChannels = new Array(
|
|
556
|
+
const tmpChannels = new Array(this.channels.length);
|
|
538
557
|
for (let i = 0; i < tmpChannels.length; i++) {
|
|
539
558
|
tmpChannels[i] = {
|
|
540
559
|
programNumber: -1,
|
|
@@ -597,6 +616,17 @@ class MidyGMLite {
|
|
|
597
616
|
}
|
|
598
617
|
return { instruments, timeline };
|
|
599
618
|
}
|
|
619
|
+
stopActiveNotes(channelNumber, velocity, force, scheduleTime) {
|
|
620
|
+
const channel = this.channels[channelNumber];
|
|
621
|
+
const promises = [];
|
|
622
|
+
const activeNotes = this.getActiveNotes(channel, scheduleTime);
|
|
623
|
+
activeNotes.forEach((note) => {
|
|
624
|
+
const promise = this.scheduleNoteOff(channelNumber, note.noteNumber, velocity, scheduleTime, force, undefined);
|
|
625
|
+
this.notePromises.push(promise);
|
|
626
|
+
promises.push(promise);
|
|
627
|
+
});
|
|
628
|
+
return Promise.all(promises);
|
|
629
|
+
}
|
|
600
630
|
stopChannelNotes(channelNumber, velocity, force, scheduleTime) {
|
|
601
631
|
const channel = this.channels[channelNumber];
|
|
602
632
|
const promises = [];
|
|
@@ -626,6 +656,9 @@ class MidyGMLite {
|
|
|
626
656
|
if (!this.isPlaying)
|
|
627
657
|
return;
|
|
628
658
|
this.isStopping = true;
|
|
659
|
+
for (let i = 0; i < this.channels.length; i++) {
|
|
660
|
+
this.resetAllStates(i);
|
|
661
|
+
}
|
|
629
662
|
}
|
|
630
663
|
pause() {
|
|
631
664
|
if (!this.isPlaying || this.isPaused)
|
|
@@ -665,6 +698,8 @@ class MidyGMLite {
|
|
|
665
698
|
const note = noteList[i];
|
|
666
699
|
if (!note)
|
|
667
700
|
continue;
|
|
701
|
+
if (note.ending)
|
|
702
|
+
continue;
|
|
668
703
|
callback(note);
|
|
669
704
|
}
|
|
670
705
|
});
|
|
@@ -801,8 +836,8 @@ class MidyGMLite {
|
|
|
801
836
|
note.modulationLFO.connect(note.volumeDepth);
|
|
802
837
|
note.volumeDepth.connect(note.volumeEnvelopeNode.gain);
|
|
803
838
|
}
|
|
804
|
-
async getAudioBuffer(
|
|
805
|
-
const audioBufferId = this.getAudioBufferId(
|
|
839
|
+
async getAudioBuffer(programNumber, noteNumber, velocity, voiceParams, isSF3) {
|
|
840
|
+
const audioBufferId = this.getAudioBufferId(programNumber, noteNumber, velocity);
|
|
806
841
|
const cache = this.audioBufferCache.get(audioBufferId);
|
|
807
842
|
if (cache) {
|
|
808
843
|
cache.counter += 1;
|
|
@@ -825,8 +860,8 @@ class MidyGMLite {
|
|
|
825
860
|
const controllerState = this.getControllerState(channel, noteNumber, velocity);
|
|
826
861
|
const voiceParams = voice.getAllParams(controllerState);
|
|
827
862
|
const note = new Note(noteNumber, velocity, startTime, voice, voiceParams);
|
|
828
|
-
const audioBuffer = await this.getAudioBuffer(channel.
|
|
829
|
-
note.bufferSource = this.createBufferSource(
|
|
863
|
+
const audioBuffer = await this.getAudioBuffer(channel.programNumber, noteNumber, velocity, voiceParams, isSF3);
|
|
864
|
+
note.bufferSource = this.createBufferSource(voiceParams, audioBuffer);
|
|
830
865
|
note.volumeEnvelopeNode = new GainNode(this.audioContext);
|
|
831
866
|
note.filterNode = new BiquadFilterNode(this.audioContext, {
|
|
832
867
|
type: "lowpass",
|
|
@@ -843,14 +878,43 @@ class MidyGMLite {
|
|
|
843
878
|
note.bufferSource.start(startTime);
|
|
844
879
|
return note;
|
|
845
880
|
}
|
|
881
|
+
handleExclusiveClass(note, channelNumber, startTime) {
|
|
882
|
+
const exclusiveClass = note.voiceParams.exclusiveClass;
|
|
883
|
+
if (exclusiveClass === 0)
|
|
884
|
+
return;
|
|
885
|
+
const prev = this.exclusiveClassNotes[exclusiveClass];
|
|
886
|
+
if (prev) {
|
|
887
|
+
const [prevNote, prevChannelNumber] = prev;
|
|
888
|
+
if (prevNote && !prevNote.ending) {
|
|
889
|
+
this.scheduleNoteOff(prevChannelNumber, prevNote.noteNumber, 0, // velocity,
|
|
890
|
+
startTime, true);
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
this.exclusiveClassNotes[exclusiveClass] = [note, channelNumber];
|
|
894
|
+
}
|
|
895
|
+
handleDrumExclusiveClass(note, channelNumber, startTime) {
|
|
896
|
+
const channel = this.channels[channelNumber];
|
|
897
|
+
if (!channel.isDrum)
|
|
898
|
+
return;
|
|
899
|
+
const drumExclusiveClass = drumExclusiveClasses[noteNumber];
|
|
900
|
+
if (drumExclusiveClass === 0)
|
|
901
|
+
return;
|
|
902
|
+
const index = drumExclusiveClass * this.channels.length + channelNumber;
|
|
903
|
+
const prevNote = this.drumExclusiveClassNotes[index];
|
|
904
|
+
if (prevNote && !prevNote.ending) {
|
|
905
|
+
this.scheduleNoteOff(channelNumber, prevNote.noteNumber, 0, // velocity,
|
|
906
|
+
startTime, true);
|
|
907
|
+
}
|
|
908
|
+
this.drumExclusiveClassNotes[index] = note;
|
|
909
|
+
}
|
|
846
910
|
async scheduleNoteOn(channelNumber, noteNumber, velocity, startTime) {
|
|
847
911
|
const channel = this.channels[channelNumber];
|
|
848
912
|
const bankNumber = channel.bank;
|
|
849
|
-
const soundFontIndex = this.soundFontTable[channel.
|
|
913
|
+
const soundFontIndex = this.soundFontTable[channel.programNumber].get(bankNumber);
|
|
850
914
|
if (soundFontIndex === undefined)
|
|
851
915
|
return;
|
|
852
916
|
const soundFont = this.soundFonts[soundFontIndex];
|
|
853
|
-
const voice = soundFont.getVoice(bankNumber, channel.
|
|
917
|
+
const voice = soundFont.getVoice(bankNumber, channel.programNumber, noteNumber, velocity);
|
|
854
918
|
if (!voice)
|
|
855
919
|
return;
|
|
856
920
|
const isSF3 = soundFont.parsed.info.version.major === 3;
|
|
@@ -860,32 +924,47 @@ class MidyGMLite {
|
|
|
860
924
|
if (0.5 <= channel.state.sustainPedal) {
|
|
861
925
|
channel.sustainNotes.push(note);
|
|
862
926
|
}
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
if (this.exclusiveClassMap.has(exclusiveClass)) {
|
|
866
|
-
const prevEntry = this.exclusiveClassMap.get(exclusiveClass);
|
|
867
|
-
const [prevNote, prevChannelNumber] = prevEntry;
|
|
868
|
-
if (prevNote && !prevNote.ending) {
|
|
869
|
-
this.scheduleNoteOff(prevChannelNumber, prevNote.noteNumber, 0, // velocity,
|
|
870
|
-
startTime, true);
|
|
871
|
-
}
|
|
872
|
-
}
|
|
873
|
-
this.exclusiveClassMap.set(exclusiveClass, [note, channelNumber]);
|
|
874
|
-
}
|
|
927
|
+
this.handleExclusiveClass(note, channelNumber, startTime);
|
|
928
|
+
this.handleDrumExclusiveClass(note, channelNumber, startTime);
|
|
875
929
|
const scheduledNotes = channel.scheduledNotes;
|
|
876
|
-
|
|
877
|
-
|
|
930
|
+
let noteList = scheduledNotes.get(noteNumber);
|
|
931
|
+
if (noteList) {
|
|
932
|
+
noteList.push(note);
|
|
878
933
|
}
|
|
879
934
|
else {
|
|
880
|
-
|
|
935
|
+
noteList = [note];
|
|
936
|
+
scheduledNotes.set(noteNumber, noteList);
|
|
937
|
+
}
|
|
938
|
+
if (channel.isDrum) {
|
|
939
|
+
const stopTime = startTime + note.bufferSource.buffer.duration;
|
|
940
|
+
const index = noteList.length - 1;
|
|
941
|
+
const promise = new Promise((resolve) => {
|
|
942
|
+
note.bufferSource.onended = () => {
|
|
943
|
+
noteList[index] = undefined;
|
|
944
|
+
this.disconnectNote(note);
|
|
945
|
+
resolve();
|
|
946
|
+
};
|
|
947
|
+
note.bufferSource.stop(stopTime);
|
|
948
|
+
});
|
|
949
|
+
this.notePromises.push(promise);
|
|
881
950
|
}
|
|
882
951
|
}
|
|
883
952
|
noteOn(channelNumber, noteNumber, velocity, scheduleTime) {
|
|
884
953
|
scheduleTime ??= this.audioContext.currentTime;
|
|
885
954
|
return this.scheduleNoteOn(channelNumber, noteNumber, velocity, scheduleTime);
|
|
886
955
|
}
|
|
887
|
-
|
|
888
|
-
|
|
956
|
+
disconnectNote(note) {
|
|
957
|
+
note.bufferSource.disconnect();
|
|
958
|
+
note.filterNode.disconnect();
|
|
959
|
+
note.volumeEnvelopeNode.disconnect();
|
|
960
|
+
if (note.modulationDepth) {
|
|
961
|
+
note.volumeDepth.disconnect();
|
|
962
|
+
note.modulationDepth.disconnect();
|
|
963
|
+
note.modulationLFO.stop();
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
stopNote(endTime, stopTime, noteList, index) {
|
|
967
|
+
const note = noteList[index];
|
|
889
968
|
note.volumeEnvelopeNode.gain
|
|
890
969
|
.cancelScheduledValues(endTime)
|
|
891
970
|
.linearRampToValueAtTime(0, stopTime);
|
|
@@ -895,41 +974,45 @@ class MidyGMLite {
|
|
|
895
974
|
}, stopTime);
|
|
896
975
|
return new Promise((resolve) => {
|
|
897
976
|
note.bufferSource.onended = () => {
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
note.filterNode.disconnect();
|
|
901
|
-
note.volumeEnvelopeNode.disconnect();
|
|
902
|
-
if (note.modulationDepth) {
|
|
903
|
-
note.volumeDepth.disconnect();
|
|
904
|
-
note.modulationDepth.disconnect();
|
|
905
|
-
note.modulationLFO.stop();
|
|
906
|
-
}
|
|
977
|
+
noteList[index] = undefined;
|
|
978
|
+
this.disconnectNote(note);
|
|
907
979
|
resolve();
|
|
908
980
|
};
|
|
909
981
|
note.bufferSource.stop(stopTime);
|
|
910
982
|
});
|
|
911
983
|
}
|
|
984
|
+
findNoteOffTarget(noteList) {
|
|
985
|
+
for (let i = 0; i < noteList.length; i++) {
|
|
986
|
+
const note = noteList[i];
|
|
987
|
+
if (!note)
|
|
988
|
+
continue;
|
|
989
|
+
if (note.ending)
|
|
990
|
+
continue;
|
|
991
|
+
return [note, i];
|
|
992
|
+
}
|
|
993
|
+
}
|
|
912
994
|
scheduleNoteOff(channelNumber, noteNumber, _velocity, endTime, force) {
|
|
913
995
|
const channel = this.channels[channelNumber];
|
|
996
|
+
if (channel.isDrum)
|
|
997
|
+
return;
|
|
914
998
|
if (!force && 0.5 <= channel.state.sustainPedal)
|
|
915
999
|
return;
|
|
916
1000
|
if (!channel.scheduledNotes.has(noteNumber))
|
|
917
1001
|
return;
|
|
918
|
-
const
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
}
|
|
1002
|
+
const noteList = channel.scheduledNotes.get(noteNumber);
|
|
1003
|
+
if (!noteList)
|
|
1004
|
+
return; // be careful with drum channel
|
|
1005
|
+
const noteOffTarget = this.findNoteOffTarget(noteList, endTime);
|
|
1006
|
+
if (!noteOffTarget)
|
|
1007
|
+
return;
|
|
1008
|
+
const [note, i] = noteOffTarget;
|
|
1009
|
+
const volRelease = endTime + note.voiceParams.volRelease;
|
|
1010
|
+
const modRelease = endTime + note.voiceParams.modRelease;
|
|
1011
|
+
note.filterNode.frequency
|
|
1012
|
+
.cancelScheduledValues(endTime)
|
|
1013
|
+
.linearRampToValueAtTime(0, modRelease);
|
|
1014
|
+
const stopTime = Math.min(volRelease, modRelease);
|
|
1015
|
+
return this.stopNote(endTime, stopTime, noteList, i);
|
|
933
1016
|
}
|
|
934
1017
|
noteOff(channelNumber, noteNumber, velocity, scheduleTime) {
|
|
935
1018
|
scheduleTime ??= this.audioContext.currentTime;
|
|
@@ -964,9 +1047,9 @@ class MidyGMLite {
|
|
|
964
1047
|
console.warn(`Unsupported MIDI message: ${messageType.toString(16)}`);
|
|
965
1048
|
}
|
|
966
1049
|
}
|
|
967
|
-
handleProgramChange(channelNumber,
|
|
1050
|
+
handleProgramChange(channelNumber, programNumber, _scheduleTime) {
|
|
968
1051
|
const channel = this.channels[channelNumber];
|
|
969
|
-
channel.
|
|
1052
|
+
channel.programNumber = programNumber;
|
|
970
1053
|
}
|
|
971
1054
|
handlePitchBendMessage(channelNumber, lsb, msb, scheduleTime) {
|
|
972
1055
|
const pitchBend = msb * 128 + lsb;
|
|
@@ -974,8 +1057,6 @@ class MidyGMLite {
|
|
|
974
1057
|
}
|
|
975
1058
|
setPitchBend(channelNumber, value, scheduleTime) {
|
|
976
1059
|
const channel = this.channels[channelNumber];
|
|
977
|
-
if (channel.isDrum)
|
|
978
|
-
return;
|
|
979
1060
|
scheduleTime ??= this.audioContext.currentTime;
|
|
980
1061
|
const state = channel.state;
|
|
981
1062
|
const prev = state.pitchWheel * 2 - 1;
|
|
@@ -1138,8 +1219,6 @@ class MidyGMLite {
|
|
|
1138
1219
|
}
|
|
1139
1220
|
setModulationDepth(channelNumber, modulation, scheduleTime) {
|
|
1140
1221
|
const channel = this.channels[channelNumber];
|
|
1141
|
-
if (channel.isDrum)
|
|
1142
|
-
return;
|
|
1143
1222
|
scheduleTime ??= this.audioContext.currentTime;
|
|
1144
1223
|
channel.state.modulationDepth = modulation / 127;
|
|
1145
1224
|
this.updateModulation(channel, scheduleTime);
|
|
@@ -1186,8 +1265,6 @@ class MidyGMLite {
|
|
|
1186
1265
|
}
|
|
1187
1266
|
setSustainPedal(channelNumber, value, scheduleTime) {
|
|
1188
1267
|
const channel = this.channels[channelNumber];
|
|
1189
|
-
if (channel.isDrum)
|
|
1190
|
-
return;
|
|
1191
1268
|
scheduleTime ??= this.audioContext.currentTime;
|
|
1192
1269
|
channel.state.sustainPedal = value / 127;
|
|
1193
1270
|
if (64 <= value) {
|
|
@@ -1246,8 +1323,6 @@ class MidyGMLite {
|
|
|
1246
1323
|
}
|
|
1247
1324
|
setPitchBendRange(channelNumber, value, scheduleTime) {
|
|
1248
1325
|
const channel = this.channels[channelNumber];
|
|
1249
|
-
if (channel.isDrum)
|
|
1250
|
-
return;
|
|
1251
1326
|
scheduleTime ??= this.audioContext.currentTime;
|
|
1252
1327
|
const state = channel.state;
|
|
1253
1328
|
const prev = state.pitchWheelSensitivity;
|
|
@@ -1259,14 +1334,26 @@ class MidyGMLite {
|
|
|
1259
1334
|
}
|
|
1260
1335
|
allSoundOff(channelNumber, _value, scheduleTime) {
|
|
1261
1336
|
scheduleTime ??= this.audioContext.currentTime;
|
|
1262
|
-
return this.
|
|
1337
|
+
return this.stopActiveNotes(channelNumber, 0, true, scheduleTime);
|
|
1338
|
+
}
|
|
1339
|
+
resetAllStates(channelNumber) {
|
|
1340
|
+
const channel = this.channels[channelNumber];
|
|
1341
|
+
const state = channel.state;
|
|
1342
|
+
for (const type of Object.keys(defaultControllerState)) {
|
|
1343
|
+
state[type] = defaultControllerState[type].defaultValue;
|
|
1344
|
+
}
|
|
1345
|
+
for (const type of Object.keys(this.constructor.channelSettings)) {
|
|
1346
|
+
channel[type] = this.constructor.channelSettings[type];
|
|
1347
|
+
}
|
|
1348
|
+
this.mode = "GM1";
|
|
1263
1349
|
}
|
|
1350
|
+
// https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/rp15.pdf
|
|
1264
1351
|
resetAllControllers(channelNumber) {
|
|
1265
1352
|
const stateTypes = [
|
|
1353
|
+
"pitchWheel",
|
|
1266
1354
|
"expression",
|
|
1267
1355
|
"modulationDepth",
|
|
1268
1356
|
"sustainPedal",
|
|
1269
|
-
"pitchWheelSensitivity",
|
|
1270
1357
|
];
|
|
1271
1358
|
const channel = this.channels[channelNumber];
|
|
1272
1359
|
const state = channel.state;
|
|
@@ -1285,7 +1372,7 @@ class MidyGMLite {
|
|
|
1285
1372
|
}
|
|
1286
1373
|
allNotesOff(channelNumber, _value, scheduleTime) {
|
|
1287
1374
|
scheduleTime ??= this.audioContext.currentTime;
|
|
1288
|
-
return this.
|
|
1375
|
+
return this.stopActiveNotes(channelNumber, 0, false, scheduleTime);
|
|
1289
1376
|
}
|
|
1290
1377
|
handleUniversalNonRealTimeExclusiveMessage(data, scheduleTime) {
|
|
1291
1378
|
switch (data[2]) {
|
|
@@ -1355,6 +1442,7 @@ class MidyGMLite {
|
|
|
1355
1442
|
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
1356
1443
|
}
|
|
1357
1444
|
}
|
|
1445
|
+
// https://github.com/marmooo/js-timer-benchmark
|
|
1358
1446
|
scheduleTask(callback, scheduleTime) {
|
|
1359
1447
|
return new Promise((resolve) => {
|
|
1360
1448
|
const bufferSource = new AudioBufferSourceNode(this.audioContext, {
|
|
@@ -1380,10 +1468,8 @@ Object.defineProperty(MidyGMLite, "channelSettings", {
|
|
|
1380
1468
|
configurable: true,
|
|
1381
1469
|
writable: true,
|
|
1382
1470
|
value: {
|
|
1383
|
-
currentBufferSource: null,
|
|
1384
|
-
isDrum: false,
|
|
1385
1471
|
detune: 0,
|
|
1386
|
-
|
|
1472
|
+
programNumber: 0,
|
|
1387
1473
|
bank: 0,
|
|
1388
1474
|
dataMSB: 0,
|
|
1389
1475
|
dataLSB: 0,
|