@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/esm/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;
|
package/esm/midy-GMLite.d.ts.map
CHANGED
|
@@ -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/esm/midy-GMLite.js
CHANGED
|
@@ -103,6 +103,19 @@ class Note {
|
|
|
103
103
|
this.voiceParams = voiceParams;
|
|
104
104
|
}
|
|
105
105
|
}
|
|
106
|
+
const drumExclusiveClasses = new Uint8Array(128);
|
|
107
|
+
drumExclusiveClasses[42] = 1;
|
|
108
|
+
drumExclusiveClasses[44] = 1;
|
|
109
|
+
drumExclusiveClasses[46] = 1, // HH
|
|
110
|
+
drumExclusiveClasses[71] = 2;
|
|
111
|
+
drumExclusiveClasses[72] = 2; // Whistle
|
|
112
|
+
drumExclusiveClasses[73] = 3;
|
|
113
|
+
drumExclusiveClasses[74] = 3; // Guiro
|
|
114
|
+
drumExclusiveClasses[78] = 4;
|
|
115
|
+
drumExclusiveClasses[79] = 4; // Cuica
|
|
116
|
+
drumExclusiveClasses[80] = 5;
|
|
117
|
+
drumExclusiveClasses[81] = 5; // Triangle
|
|
118
|
+
const drumExclusiveClassCount = 5;
|
|
106
119
|
// normalized to 0-1 for use with the SF2 modulator model
|
|
107
120
|
const defaultControllerState = {
|
|
108
121
|
noteOnVelocity: { type: 2, defaultValue: 0 },
|
|
@@ -176,6 +189,12 @@ export class MidyGMLite {
|
|
|
176
189
|
writable: true,
|
|
177
190
|
value: "GM1"
|
|
178
191
|
});
|
|
192
|
+
Object.defineProperty(this, "numChannels", {
|
|
193
|
+
enumerable: true,
|
|
194
|
+
configurable: true,
|
|
195
|
+
writable: true,
|
|
196
|
+
value: 16
|
|
197
|
+
});
|
|
179
198
|
Object.defineProperty(this, "ticksPerBeat", {
|
|
180
199
|
enumerable: true,
|
|
181
200
|
configurable: true,
|
|
@@ -290,11 +309,17 @@ export class MidyGMLite {
|
|
|
290
309
|
writable: true,
|
|
291
310
|
value: []
|
|
292
311
|
});
|
|
293
|
-
Object.defineProperty(this, "
|
|
312
|
+
Object.defineProperty(this, "exclusiveClassNotes", {
|
|
313
|
+
enumerable: true,
|
|
314
|
+
configurable: true,
|
|
315
|
+
writable: true,
|
|
316
|
+
value: new Array(128)
|
|
317
|
+
});
|
|
318
|
+
Object.defineProperty(this, "drumExclusiveClassNotes", {
|
|
294
319
|
enumerable: true,
|
|
295
320
|
configurable: true,
|
|
296
321
|
writable: true,
|
|
297
|
-
value: new
|
|
322
|
+
value: new Array(this.numChannels * drumExclusiveClassCount)
|
|
298
323
|
});
|
|
299
324
|
this.audioContext = audioContext;
|
|
300
325
|
this.masterVolume = new GainNode(audioContext);
|
|
@@ -361,8 +386,10 @@ export class MidyGMLite {
|
|
|
361
386
|
};
|
|
362
387
|
}
|
|
363
388
|
createChannels(audioContext) {
|
|
364
|
-
const channels = Array.from({ length:
|
|
389
|
+
const channels = Array.from({ length: this.numChannels }, () => {
|
|
365
390
|
return {
|
|
391
|
+
currentBufferSource: null,
|
|
392
|
+
isDrum: false,
|
|
366
393
|
...this.constructor.channelSettings,
|
|
367
394
|
state: new ControllerState(),
|
|
368
395
|
...this.setChannelAudioNodes(audioContext),
|
|
@@ -401,18 +428,10 @@ export class MidyGMLite {
|
|
|
401
428
|
return audioBuffer;
|
|
402
429
|
}
|
|
403
430
|
}
|
|
404
|
-
|
|
405
|
-
if (channel.isDrum) {
|
|
406
|
-
return false;
|
|
407
|
-
}
|
|
408
|
-
else {
|
|
409
|
-
return voiceParams.sampleModes % 2 !== 0;
|
|
410
|
-
}
|
|
411
|
-
}
|
|
412
|
-
createBufferSource(channel, voiceParams, audioBuffer) {
|
|
431
|
+
createBufferSource(voiceParams, audioBuffer) {
|
|
413
432
|
const bufferSource = new AudioBufferSourceNode(this.audioContext);
|
|
414
433
|
bufferSource.buffer = audioBuffer;
|
|
415
|
-
bufferSource.loop =
|
|
434
|
+
bufferSource.loop = voiceParams.sampleModes % 2 !== 0;
|
|
416
435
|
if (bufferSource.loop) {
|
|
417
436
|
bufferSource.loopStart = voiceParams.loopStart / voiceParams.sampleRate;
|
|
418
437
|
bufferSource.loopEnd = voiceParams.loopEnd / voiceParams.sampleRate;
|
|
@@ -427,7 +446,7 @@ export class MidyGMLite {
|
|
|
427
446
|
const startTime = event.startTime + this.startDelay - offset;
|
|
428
447
|
switch (event.type) {
|
|
429
448
|
case "noteOn":
|
|
430
|
-
if (event.velocity
|
|
449
|
+
if (0 < event.velocity) {
|
|
431
450
|
await this.scheduleNoteOn(event.channel, event.noteNumber, event.velocity, startTime);
|
|
432
451
|
break;
|
|
433
452
|
}
|
|
@@ -475,7 +494,7 @@ export class MidyGMLite {
|
|
|
475
494
|
if (queueIndex >= this.timeline.length) {
|
|
476
495
|
await Promise.all(this.notePromises);
|
|
477
496
|
this.notePromises = [];
|
|
478
|
-
this.
|
|
497
|
+
this.exclusiveClassNotes.fill(undefined);
|
|
479
498
|
this.audioBufferCache.clear();
|
|
480
499
|
resolve();
|
|
481
500
|
return;
|
|
@@ -494,7 +513,7 @@ export class MidyGMLite {
|
|
|
494
513
|
else if (this.isStopping) {
|
|
495
514
|
await this.stopNotes(0, true, now);
|
|
496
515
|
this.notePromises = [];
|
|
497
|
-
this.
|
|
516
|
+
this.exclusiveClassNotes.fill(undefined);
|
|
498
517
|
this.audioBufferCache.clear();
|
|
499
518
|
resolve();
|
|
500
519
|
this.isStopping = false;
|
|
@@ -503,7 +522,7 @@ export class MidyGMLite {
|
|
|
503
522
|
}
|
|
504
523
|
else if (this.isSeeking) {
|
|
505
524
|
this.stopNotes(0, true, now);
|
|
506
|
-
this.
|
|
525
|
+
this.exclusiveClassNotes.fill(undefined);
|
|
507
526
|
this.startTime = this.audioContext.currentTime;
|
|
508
527
|
queueIndex = this.getQueueIndex(this.resumeTime);
|
|
509
528
|
offset = this.resumeTime - this.startTime;
|
|
@@ -531,7 +550,7 @@ export class MidyGMLite {
|
|
|
531
550
|
extractMidiData(midi) {
|
|
532
551
|
const instruments = new Set();
|
|
533
552
|
const timeline = [];
|
|
534
|
-
const tmpChannels = new Array(
|
|
553
|
+
const tmpChannels = new Array(this.channels.length);
|
|
535
554
|
for (let i = 0; i < tmpChannels.length; i++) {
|
|
536
555
|
tmpChannels[i] = {
|
|
537
556
|
programNumber: -1,
|
|
@@ -594,6 +613,17 @@ export class MidyGMLite {
|
|
|
594
613
|
}
|
|
595
614
|
return { instruments, timeline };
|
|
596
615
|
}
|
|
616
|
+
stopActiveNotes(channelNumber, velocity, force, scheduleTime) {
|
|
617
|
+
const channel = this.channels[channelNumber];
|
|
618
|
+
const promises = [];
|
|
619
|
+
const activeNotes = this.getActiveNotes(channel, scheduleTime);
|
|
620
|
+
activeNotes.forEach((note) => {
|
|
621
|
+
const promise = this.scheduleNoteOff(channelNumber, note.noteNumber, velocity, scheduleTime, force, undefined);
|
|
622
|
+
this.notePromises.push(promise);
|
|
623
|
+
promises.push(promise);
|
|
624
|
+
});
|
|
625
|
+
return Promise.all(promises);
|
|
626
|
+
}
|
|
597
627
|
stopChannelNotes(channelNumber, velocity, force, scheduleTime) {
|
|
598
628
|
const channel = this.channels[channelNumber];
|
|
599
629
|
const promises = [];
|
|
@@ -623,6 +653,9 @@ export class MidyGMLite {
|
|
|
623
653
|
if (!this.isPlaying)
|
|
624
654
|
return;
|
|
625
655
|
this.isStopping = true;
|
|
656
|
+
for (let i = 0; i < this.channels.length; i++) {
|
|
657
|
+
this.resetAllStates(i);
|
|
658
|
+
}
|
|
626
659
|
}
|
|
627
660
|
pause() {
|
|
628
661
|
if (!this.isPlaying || this.isPaused)
|
|
@@ -662,6 +695,8 @@ export class MidyGMLite {
|
|
|
662
695
|
const note = noteList[i];
|
|
663
696
|
if (!note)
|
|
664
697
|
continue;
|
|
698
|
+
if (note.ending)
|
|
699
|
+
continue;
|
|
665
700
|
callback(note);
|
|
666
701
|
}
|
|
667
702
|
});
|
|
@@ -798,8 +833,8 @@ export class MidyGMLite {
|
|
|
798
833
|
note.modulationLFO.connect(note.volumeDepth);
|
|
799
834
|
note.volumeDepth.connect(note.volumeEnvelopeNode.gain);
|
|
800
835
|
}
|
|
801
|
-
async getAudioBuffer(
|
|
802
|
-
const audioBufferId = this.getAudioBufferId(
|
|
836
|
+
async getAudioBuffer(programNumber, noteNumber, velocity, voiceParams, isSF3) {
|
|
837
|
+
const audioBufferId = this.getAudioBufferId(programNumber, noteNumber, velocity);
|
|
803
838
|
const cache = this.audioBufferCache.get(audioBufferId);
|
|
804
839
|
if (cache) {
|
|
805
840
|
cache.counter += 1;
|
|
@@ -822,8 +857,8 @@ export class MidyGMLite {
|
|
|
822
857
|
const controllerState = this.getControllerState(channel, noteNumber, velocity);
|
|
823
858
|
const voiceParams = voice.getAllParams(controllerState);
|
|
824
859
|
const note = new Note(noteNumber, velocity, startTime, voice, voiceParams);
|
|
825
|
-
const audioBuffer = await this.getAudioBuffer(channel.
|
|
826
|
-
note.bufferSource = this.createBufferSource(
|
|
860
|
+
const audioBuffer = await this.getAudioBuffer(channel.programNumber, noteNumber, velocity, voiceParams, isSF3);
|
|
861
|
+
note.bufferSource = this.createBufferSource(voiceParams, audioBuffer);
|
|
827
862
|
note.volumeEnvelopeNode = new GainNode(this.audioContext);
|
|
828
863
|
note.filterNode = new BiquadFilterNode(this.audioContext, {
|
|
829
864
|
type: "lowpass",
|
|
@@ -840,14 +875,43 @@ export class MidyGMLite {
|
|
|
840
875
|
note.bufferSource.start(startTime);
|
|
841
876
|
return note;
|
|
842
877
|
}
|
|
878
|
+
handleExclusiveClass(note, channelNumber, startTime) {
|
|
879
|
+
const exclusiveClass = note.voiceParams.exclusiveClass;
|
|
880
|
+
if (exclusiveClass === 0)
|
|
881
|
+
return;
|
|
882
|
+
const prev = this.exclusiveClassNotes[exclusiveClass];
|
|
883
|
+
if (prev) {
|
|
884
|
+
const [prevNote, prevChannelNumber] = prev;
|
|
885
|
+
if (prevNote && !prevNote.ending) {
|
|
886
|
+
this.scheduleNoteOff(prevChannelNumber, prevNote.noteNumber, 0, // velocity,
|
|
887
|
+
startTime, true);
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
this.exclusiveClassNotes[exclusiveClass] = [note, channelNumber];
|
|
891
|
+
}
|
|
892
|
+
handleDrumExclusiveClass(note, channelNumber, startTime) {
|
|
893
|
+
const channel = this.channels[channelNumber];
|
|
894
|
+
if (!channel.isDrum)
|
|
895
|
+
return;
|
|
896
|
+
const drumExclusiveClass = drumExclusiveClasses[noteNumber];
|
|
897
|
+
if (drumExclusiveClass === 0)
|
|
898
|
+
return;
|
|
899
|
+
const index = drumExclusiveClass * this.channels.length + channelNumber;
|
|
900
|
+
const prevNote = this.drumExclusiveClassNotes[index];
|
|
901
|
+
if (prevNote && !prevNote.ending) {
|
|
902
|
+
this.scheduleNoteOff(channelNumber, prevNote.noteNumber, 0, // velocity,
|
|
903
|
+
startTime, true);
|
|
904
|
+
}
|
|
905
|
+
this.drumExclusiveClassNotes[index] = note;
|
|
906
|
+
}
|
|
843
907
|
async scheduleNoteOn(channelNumber, noteNumber, velocity, startTime) {
|
|
844
908
|
const channel = this.channels[channelNumber];
|
|
845
909
|
const bankNumber = channel.bank;
|
|
846
|
-
const soundFontIndex = this.soundFontTable[channel.
|
|
910
|
+
const soundFontIndex = this.soundFontTable[channel.programNumber].get(bankNumber);
|
|
847
911
|
if (soundFontIndex === undefined)
|
|
848
912
|
return;
|
|
849
913
|
const soundFont = this.soundFonts[soundFontIndex];
|
|
850
|
-
const voice = soundFont.getVoice(bankNumber, channel.
|
|
914
|
+
const voice = soundFont.getVoice(bankNumber, channel.programNumber, noteNumber, velocity);
|
|
851
915
|
if (!voice)
|
|
852
916
|
return;
|
|
853
917
|
const isSF3 = soundFont.parsed.info.version.major === 3;
|
|
@@ -857,32 +921,47 @@ export class MidyGMLite {
|
|
|
857
921
|
if (0.5 <= channel.state.sustainPedal) {
|
|
858
922
|
channel.sustainNotes.push(note);
|
|
859
923
|
}
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
if (this.exclusiveClassMap.has(exclusiveClass)) {
|
|
863
|
-
const prevEntry = this.exclusiveClassMap.get(exclusiveClass);
|
|
864
|
-
const [prevNote, prevChannelNumber] = prevEntry;
|
|
865
|
-
if (prevNote && !prevNote.ending) {
|
|
866
|
-
this.scheduleNoteOff(prevChannelNumber, prevNote.noteNumber, 0, // velocity,
|
|
867
|
-
startTime, true);
|
|
868
|
-
}
|
|
869
|
-
}
|
|
870
|
-
this.exclusiveClassMap.set(exclusiveClass, [note, channelNumber]);
|
|
871
|
-
}
|
|
924
|
+
this.handleExclusiveClass(note, channelNumber, startTime);
|
|
925
|
+
this.handleDrumExclusiveClass(note, channelNumber, startTime);
|
|
872
926
|
const scheduledNotes = channel.scheduledNotes;
|
|
873
|
-
|
|
874
|
-
|
|
927
|
+
let noteList = scheduledNotes.get(noteNumber);
|
|
928
|
+
if (noteList) {
|
|
929
|
+
noteList.push(note);
|
|
875
930
|
}
|
|
876
931
|
else {
|
|
877
|
-
|
|
932
|
+
noteList = [note];
|
|
933
|
+
scheduledNotes.set(noteNumber, noteList);
|
|
934
|
+
}
|
|
935
|
+
if (channel.isDrum) {
|
|
936
|
+
const stopTime = startTime + note.bufferSource.buffer.duration;
|
|
937
|
+
const index = noteList.length - 1;
|
|
938
|
+
const promise = new Promise((resolve) => {
|
|
939
|
+
note.bufferSource.onended = () => {
|
|
940
|
+
noteList[index] = undefined;
|
|
941
|
+
this.disconnectNote(note);
|
|
942
|
+
resolve();
|
|
943
|
+
};
|
|
944
|
+
note.bufferSource.stop(stopTime);
|
|
945
|
+
});
|
|
946
|
+
this.notePromises.push(promise);
|
|
878
947
|
}
|
|
879
948
|
}
|
|
880
949
|
noteOn(channelNumber, noteNumber, velocity, scheduleTime) {
|
|
881
950
|
scheduleTime ??= this.audioContext.currentTime;
|
|
882
951
|
return this.scheduleNoteOn(channelNumber, noteNumber, velocity, scheduleTime);
|
|
883
952
|
}
|
|
884
|
-
|
|
885
|
-
|
|
953
|
+
disconnectNote(note) {
|
|
954
|
+
note.bufferSource.disconnect();
|
|
955
|
+
note.filterNode.disconnect();
|
|
956
|
+
note.volumeEnvelopeNode.disconnect();
|
|
957
|
+
if (note.modulationDepth) {
|
|
958
|
+
note.volumeDepth.disconnect();
|
|
959
|
+
note.modulationDepth.disconnect();
|
|
960
|
+
note.modulationLFO.stop();
|
|
961
|
+
}
|
|
962
|
+
}
|
|
963
|
+
stopNote(endTime, stopTime, noteList, index) {
|
|
964
|
+
const note = noteList[index];
|
|
886
965
|
note.volumeEnvelopeNode.gain
|
|
887
966
|
.cancelScheduledValues(endTime)
|
|
888
967
|
.linearRampToValueAtTime(0, stopTime);
|
|
@@ -892,41 +971,45 @@ export class MidyGMLite {
|
|
|
892
971
|
}, stopTime);
|
|
893
972
|
return new Promise((resolve) => {
|
|
894
973
|
note.bufferSource.onended = () => {
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
note.filterNode.disconnect();
|
|
898
|
-
note.volumeEnvelopeNode.disconnect();
|
|
899
|
-
if (note.modulationDepth) {
|
|
900
|
-
note.volumeDepth.disconnect();
|
|
901
|
-
note.modulationDepth.disconnect();
|
|
902
|
-
note.modulationLFO.stop();
|
|
903
|
-
}
|
|
974
|
+
noteList[index] = undefined;
|
|
975
|
+
this.disconnectNote(note);
|
|
904
976
|
resolve();
|
|
905
977
|
};
|
|
906
978
|
note.bufferSource.stop(stopTime);
|
|
907
979
|
});
|
|
908
980
|
}
|
|
981
|
+
findNoteOffTarget(noteList) {
|
|
982
|
+
for (let i = 0; i < noteList.length; i++) {
|
|
983
|
+
const note = noteList[i];
|
|
984
|
+
if (!note)
|
|
985
|
+
continue;
|
|
986
|
+
if (note.ending)
|
|
987
|
+
continue;
|
|
988
|
+
return [note, i];
|
|
989
|
+
}
|
|
990
|
+
}
|
|
909
991
|
scheduleNoteOff(channelNumber, noteNumber, _velocity, endTime, force) {
|
|
910
992
|
const channel = this.channels[channelNumber];
|
|
993
|
+
if (channel.isDrum)
|
|
994
|
+
return;
|
|
911
995
|
if (!force && 0.5 <= channel.state.sustainPedal)
|
|
912
996
|
return;
|
|
913
997
|
if (!channel.scheduledNotes.has(noteNumber))
|
|
914
998
|
return;
|
|
915
|
-
const
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
}
|
|
999
|
+
const noteList = channel.scheduledNotes.get(noteNumber);
|
|
1000
|
+
if (!noteList)
|
|
1001
|
+
return; // be careful with drum channel
|
|
1002
|
+
const noteOffTarget = this.findNoteOffTarget(noteList, endTime);
|
|
1003
|
+
if (!noteOffTarget)
|
|
1004
|
+
return;
|
|
1005
|
+
const [note, i] = noteOffTarget;
|
|
1006
|
+
const volRelease = endTime + note.voiceParams.volRelease;
|
|
1007
|
+
const modRelease = endTime + note.voiceParams.modRelease;
|
|
1008
|
+
note.filterNode.frequency
|
|
1009
|
+
.cancelScheduledValues(endTime)
|
|
1010
|
+
.linearRampToValueAtTime(0, modRelease);
|
|
1011
|
+
const stopTime = Math.min(volRelease, modRelease);
|
|
1012
|
+
return this.stopNote(endTime, stopTime, noteList, i);
|
|
930
1013
|
}
|
|
931
1014
|
noteOff(channelNumber, noteNumber, velocity, scheduleTime) {
|
|
932
1015
|
scheduleTime ??= this.audioContext.currentTime;
|
|
@@ -961,9 +1044,9 @@ export class MidyGMLite {
|
|
|
961
1044
|
console.warn(`Unsupported MIDI message: ${messageType.toString(16)}`);
|
|
962
1045
|
}
|
|
963
1046
|
}
|
|
964
|
-
handleProgramChange(channelNumber,
|
|
1047
|
+
handleProgramChange(channelNumber, programNumber, _scheduleTime) {
|
|
965
1048
|
const channel = this.channels[channelNumber];
|
|
966
|
-
channel.
|
|
1049
|
+
channel.programNumber = programNumber;
|
|
967
1050
|
}
|
|
968
1051
|
handlePitchBendMessage(channelNumber, lsb, msb, scheduleTime) {
|
|
969
1052
|
const pitchBend = msb * 128 + lsb;
|
|
@@ -971,8 +1054,6 @@ export class MidyGMLite {
|
|
|
971
1054
|
}
|
|
972
1055
|
setPitchBend(channelNumber, value, scheduleTime) {
|
|
973
1056
|
const channel = this.channels[channelNumber];
|
|
974
|
-
if (channel.isDrum)
|
|
975
|
-
return;
|
|
976
1057
|
scheduleTime ??= this.audioContext.currentTime;
|
|
977
1058
|
const state = channel.state;
|
|
978
1059
|
const prev = state.pitchWheel * 2 - 1;
|
|
@@ -1135,8 +1216,6 @@ export class MidyGMLite {
|
|
|
1135
1216
|
}
|
|
1136
1217
|
setModulationDepth(channelNumber, modulation, scheduleTime) {
|
|
1137
1218
|
const channel = this.channels[channelNumber];
|
|
1138
|
-
if (channel.isDrum)
|
|
1139
|
-
return;
|
|
1140
1219
|
scheduleTime ??= this.audioContext.currentTime;
|
|
1141
1220
|
channel.state.modulationDepth = modulation / 127;
|
|
1142
1221
|
this.updateModulation(channel, scheduleTime);
|
|
@@ -1183,8 +1262,6 @@ export class MidyGMLite {
|
|
|
1183
1262
|
}
|
|
1184
1263
|
setSustainPedal(channelNumber, value, scheduleTime) {
|
|
1185
1264
|
const channel = this.channels[channelNumber];
|
|
1186
|
-
if (channel.isDrum)
|
|
1187
|
-
return;
|
|
1188
1265
|
scheduleTime ??= this.audioContext.currentTime;
|
|
1189
1266
|
channel.state.sustainPedal = value / 127;
|
|
1190
1267
|
if (64 <= value) {
|
|
@@ -1243,8 +1320,6 @@ export class MidyGMLite {
|
|
|
1243
1320
|
}
|
|
1244
1321
|
setPitchBendRange(channelNumber, value, scheduleTime) {
|
|
1245
1322
|
const channel = this.channels[channelNumber];
|
|
1246
|
-
if (channel.isDrum)
|
|
1247
|
-
return;
|
|
1248
1323
|
scheduleTime ??= this.audioContext.currentTime;
|
|
1249
1324
|
const state = channel.state;
|
|
1250
1325
|
const prev = state.pitchWheelSensitivity;
|
|
@@ -1256,14 +1331,26 @@ export class MidyGMLite {
|
|
|
1256
1331
|
}
|
|
1257
1332
|
allSoundOff(channelNumber, _value, scheduleTime) {
|
|
1258
1333
|
scheduleTime ??= this.audioContext.currentTime;
|
|
1259
|
-
return this.
|
|
1334
|
+
return this.stopActiveNotes(channelNumber, 0, true, scheduleTime);
|
|
1335
|
+
}
|
|
1336
|
+
resetAllStates(channelNumber) {
|
|
1337
|
+
const channel = this.channels[channelNumber];
|
|
1338
|
+
const state = channel.state;
|
|
1339
|
+
for (const type of Object.keys(defaultControllerState)) {
|
|
1340
|
+
state[type] = defaultControllerState[type].defaultValue;
|
|
1341
|
+
}
|
|
1342
|
+
for (const type of Object.keys(this.constructor.channelSettings)) {
|
|
1343
|
+
channel[type] = this.constructor.channelSettings[type];
|
|
1344
|
+
}
|
|
1345
|
+
this.mode = "GM1";
|
|
1260
1346
|
}
|
|
1347
|
+
// https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/rp15.pdf
|
|
1261
1348
|
resetAllControllers(channelNumber) {
|
|
1262
1349
|
const stateTypes = [
|
|
1350
|
+
"pitchWheel",
|
|
1263
1351
|
"expression",
|
|
1264
1352
|
"modulationDepth",
|
|
1265
1353
|
"sustainPedal",
|
|
1266
|
-
"pitchWheelSensitivity",
|
|
1267
1354
|
];
|
|
1268
1355
|
const channel = this.channels[channelNumber];
|
|
1269
1356
|
const state = channel.state;
|
|
@@ -1282,7 +1369,7 @@ export class MidyGMLite {
|
|
|
1282
1369
|
}
|
|
1283
1370
|
allNotesOff(channelNumber, _value, scheduleTime) {
|
|
1284
1371
|
scheduleTime ??= this.audioContext.currentTime;
|
|
1285
|
-
return this.
|
|
1372
|
+
return this.stopActiveNotes(channelNumber, 0, false, scheduleTime);
|
|
1286
1373
|
}
|
|
1287
1374
|
handleUniversalNonRealTimeExclusiveMessage(data, scheduleTime) {
|
|
1288
1375
|
switch (data[2]) {
|
|
@@ -1352,6 +1439,7 @@ export class MidyGMLite {
|
|
|
1352
1439
|
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
1353
1440
|
}
|
|
1354
1441
|
}
|
|
1442
|
+
// https://github.com/marmooo/js-timer-benchmark
|
|
1355
1443
|
scheduleTask(callback, scheduleTime) {
|
|
1356
1444
|
return new Promise((resolve) => {
|
|
1357
1445
|
const bufferSource = new AudioBufferSourceNode(this.audioContext, {
|
|
@@ -1376,10 +1464,8 @@ Object.defineProperty(MidyGMLite, "channelSettings", {
|
|
|
1376
1464
|
configurable: true,
|
|
1377
1465
|
writable: true,
|
|
1378
1466
|
value: {
|
|
1379
|
-
currentBufferSource: null,
|
|
1380
|
-
isDrum: false,
|
|
1381
1467
|
detune: 0,
|
|
1382
|
-
|
|
1468
|
+
programNumber: 0,
|
|
1383
1469
|
bank: 0,
|
|
1384
1470
|
dataMSB: 0,
|
|
1385
1471
|
dataLSB: 0,
|