@marmooo/midy 0.3.0 → 0.3.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 +2 -1
- package/esm/midy-GM1.d.ts +13 -50
- package/esm/midy-GM1.d.ts.map +1 -1
- package/esm/midy-GM1.js +164 -160
- package/esm/midy-GM2.d.ts +27 -83
- package/esm/midy-GM2.d.ts.map +1 -1
- package/esm/midy-GM2.js +390 -275
- package/esm/midy-GMLite.d.ts +12 -49
- package/esm/midy-GMLite.d.ts.map +1 -1
- package/esm/midy-GMLite.js +170 -163
- package/esm/midy.d.ts +31 -107
- package/esm/midy.d.ts.map +1 -1
- package/esm/midy.js +423 -306
- package/package.json +1 -1
- package/script/midy-GM1.d.ts +13 -50
- package/script/midy-GM1.d.ts.map +1 -1
- package/script/midy-GM1.js +164 -160
- package/script/midy-GM2.d.ts +27 -83
- package/script/midy-GM2.d.ts.map +1 -1
- package/script/midy-GM2.js +390 -275
- package/script/midy-GMLite.d.ts +12 -49
- package/script/midy-GMLite.d.ts.map +1 -1
- package/script/midy-GMLite.js +170 -163
- package/script/midy.d.ts +31 -107
- package/script/midy.d.ts.map +1 -1
- package/script/midy.js +423 -306
package/script/midy-GM1.js
CHANGED
|
@@ -3,60 +3,14 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.MidyGM1 = void 0;
|
|
4
4
|
const midi_file_1 = require("midi-file");
|
|
5
5
|
const soundfont_parser_1 = require("@marmooo/soundfont-parser");
|
|
6
|
-
// 2-3 times faster than Map
|
|
7
|
-
class SparseMap {
|
|
8
|
-
constructor(size) {
|
|
9
|
-
this.data = new Array(size);
|
|
10
|
-
this.activeIndices = [];
|
|
11
|
-
}
|
|
12
|
-
set(key, value) {
|
|
13
|
-
if (this.data[key] === undefined) {
|
|
14
|
-
this.activeIndices.push(key);
|
|
15
|
-
}
|
|
16
|
-
this.data[key] = value;
|
|
17
|
-
}
|
|
18
|
-
get(key) {
|
|
19
|
-
return this.data[key];
|
|
20
|
-
}
|
|
21
|
-
delete(key) {
|
|
22
|
-
if (this.data[key] !== undefined) {
|
|
23
|
-
this.data[key] = undefined;
|
|
24
|
-
const index = this.activeIndices.indexOf(key);
|
|
25
|
-
if (index !== -1) {
|
|
26
|
-
this.activeIndices.splice(index, 1);
|
|
27
|
-
}
|
|
28
|
-
return true;
|
|
29
|
-
}
|
|
30
|
-
return false;
|
|
31
|
-
}
|
|
32
|
-
has(key) {
|
|
33
|
-
return this.data[key] !== undefined;
|
|
34
|
-
}
|
|
35
|
-
get size() {
|
|
36
|
-
return this.activeIndices.length;
|
|
37
|
-
}
|
|
38
|
-
clear() {
|
|
39
|
-
for (let i = 0; i < this.activeIndices.length; i++) {
|
|
40
|
-
const key = this.activeIndices[i];
|
|
41
|
-
this.data[key] = undefined;
|
|
42
|
-
}
|
|
43
|
-
this.activeIndices = [];
|
|
44
|
-
}
|
|
45
|
-
*[Symbol.iterator]() {
|
|
46
|
-
for (let i = 0; i < this.activeIndices.length; i++) {
|
|
47
|
-
const key = this.activeIndices[i];
|
|
48
|
-
yield [key, this.data[key]];
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
forEach(callback) {
|
|
52
|
-
for (let i = 0; i < this.activeIndices.length; i++) {
|
|
53
|
-
const key = this.activeIndices[i];
|
|
54
|
-
callback(this.data[key], key, this);
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
6
|
class Note {
|
|
59
7
|
constructor(noteNumber, velocity, startTime, voice, voiceParams) {
|
|
8
|
+
Object.defineProperty(this, "index", {
|
|
9
|
+
enumerable: true,
|
|
10
|
+
configurable: true,
|
|
11
|
+
writable: true,
|
|
12
|
+
value: -1
|
|
13
|
+
});
|
|
60
14
|
Object.defineProperty(this, "bufferSource", {
|
|
61
15
|
enumerable: true,
|
|
62
16
|
configurable: true,
|
|
@@ -117,7 +71,7 @@ const defaultControllerState = {
|
|
|
117
71
|
modulationDepth: { type: 128 + 1, defaultValue: 0 },
|
|
118
72
|
// dataMSB: { type: 128 + 6, defaultValue: 0, },
|
|
119
73
|
volume: { type: 128 + 7, defaultValue: 100 / 127 },
|
|
120
|
-
pan: { type: 128 + 10, defaultValue:
|
|
74
|
+
pan: { type: 128 + 10, defaultValue: 64 / 127 },
|
|
121
75
|
expression: { type: 128 + 11, defaultValue: 1 },
|
|
122
76
|
// bankLSB: { type: 128 + 32, defaultValue: 0, },
|
|
123
77
|
// dataLSB: { type: 128 + 38, defaultValue: 0, },
|
|
@@ -322,7 +276,7 @@ class MidyGM1 {
|
|
|
322
276
|
initSoundFontTable() {
|
|
323
277
|
const table = new Array(128);
|
|
324
278
|
for (let i = 0; i < 128; i++) {
|
|
325
|
-
table[i] = new
|
|
279
|
+
table[i] = new Map();
|
|
326
280
|
}
|
|
327
281
|
return table;
|
|
328
282
|
}
|
|
@@ -374,10 +328,10 @@ class MidyGM1 {
|
|
|
374
328
|
return {
|
|
375
329
|
currentBufferSource: null,
|
|
376
330
|
isDrum: false,
|
|
377
|
-
...this.constructor.channelSettings,
|
|
378
331
|
state: new ControllerState(),
|
|
332
|
+
...this.constructor.channelSettings,
|
|
379
333
|
...this.setChannelAudioNodes(audioContext),
|
|
380
|
-
scheduledNotes:
|
|
334
|
+
scheduledNotes: [],
|
|
381
335
|
sustainNotes: [],
|
|
382
336
|
};
|
|
383
337
|
});
|
|
@@ -412,7 +366,7 @@ class MidyGM1 {
|
|
|
412
366
|
return audioBuffer;
|
|
413
367
|
}
|
|
414
368
|
}
|
|
415
|
-
createBufferSource(
|
|
369
|
+
createBufferSource(voiceParams, audioBuffer) {
|
|
416
370
|
const bufferSource = new AudioBufferSourceNode(this.audioContext);
|
|
417
371
|
bufferSource.buffer = audioBuffer;
|
|
418
372
|
bufferSource.loop = voiceParams.sampleModes % 2 !== 0;
|
|
@@ -422,24 +376,20 @@ class MidyGM1 {
|
|
|
422
376
|
}
|
|
423
377
|
return bufferSource;
|
|
424
378
|
}
|
|
425
|
-
async scheduleTimelineEvents(t,
|
|
379
|
+
async scheduleTimelineEvents(t, resumeTime, queueIndex) {
|
|
426
380
|
while (queueIndex < this.timeline.length) {
|
|
427
381
|
const event = this.timeline[queueIndex];
|
|
428
382
|
if (event.startTime > t + this.lookAhead)
|
|
429
383
|
break;
|
|
430
|
-
const
|
|
384
|
+
const delay = this.startDelay - resumeTime;
|
|
385
|
+
const startTime = event.startTime + delay;
|
|
431
386
|
switch (event.type) {
|
|
432
|
-
case "noteOn":
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
case "noteOff": {
|
|
439
|
-
const notePromise = this.scheduleNoteOff(event.channel, event.noteNumber, event.velocity, startTime, false);
|
|
440
|
-
if (notePromise) {
|
|
441
|
-
this.notePromises.push(notePromise);
|
|
442
|
-
}
|
|
387
|
+
case "noteOn": {
|
|
388
|
+
const noteOffEvent = {
|
|
389
|
+
...event.noteOffEvent,
|
|
390
|
+
startTime: event.noteOffEvent.startTime + delay,
|
|
391
|
+
};
|
|
392
|
+
await this.scheduleNoteOn(event.channel, event.noteNumber, event.velocity, startTime, noteOffEvent);
|
|
443
393
|
break;
|
|
444
394
|
}
|
|
445
395
|
case "controller":
|
|
@@ -472,26 +422,29 @@ class MidyGM1 {
|
|
|
472
422
|
this.isPaused = false;
|
|
473
423
|
this.startTime = this.audioContext.currentTime;
|
|
474
424
|
let queueIndex = this.getQueueIndex(this.resumeTime);
|
|
475
|
-
let
|
|
425
|
+
let resumeTime = this.resumeTime - this.startTime;
|
|
476
426
|
this.notePromises = [];
|
|
477
427
|
const schedulePlayback = async () => {
|
|
478
428
|
if (queueIndex >= this.timeline.length) {
|
|
479
429
|
await Promise.all(this.notePromises);
|
|
480
430
|
this.notePromises = [];
|
|
481
|
-
this.exclusiveClassNotes.
|
|
431
|
+
this.exclusiveClassNotes.fill(undefined);
|
|
482
432
|
this.audioBufferCache.clear();
|
|
433
|
+
for (let i = 0; i < this.channels.length; i++) {
|
|
434
|
+
this.resetAllStates(i);
|
|
435
|
+
}
|
|
483
436
|
resolve();
|
|
484
437
|
return;
|
|
485
438
|
}
|
|
486
439
|
const now = this.audioContext.currentTime;
|
|
487
|
-
const t = now +
|
|
488
|
-
queueIndex = await this.scheduleTimelineEvents(t,
|
|
440
|
+
const t = now + resumeTime;
|
|
441
|
+
queueIndex = await this.scheduleTimelineEvents(t, resumeTime, queueIndex);
|
|
489
442
|
if (this.isPausing) {
|
|
490
443
|
await this.stopNotes(0, true, now);
|
|
491
444
|
this.notePromises = [];
|
|
492
|
-
resolve();
|
|
493
445
|
this.isPausing = false;
|
|
494
446
|
this.isPaused = true;
|
|
447
|
+
resolve();
|
|
495
448
|
return;
|
|
496
449
|
}
|
|
497
450
|
else if (this.isStopping) {
|
|
@@ -499,9 +452,12 @@ class MidyGM1 {
|
|
|
499
452
|
this.notePromises = [];
|
|
500
453
|
this.exclusiveClassNotes.fill(undefined);
|
|
501
454
|
this.audioBufferCache.clear();
|
|
502
|
-
|
|
455
|
+
for (let i = 0; i < this.channels.length; i++) {
|
|
456
|
+
this.resetAllStates(i);
|
|
457
|
+
}
|
|
503
458
|
this.isStopping = false;
|
|
504
459
|
this.isPaused = false;
|
|
460
|
+
resolve();
|
|
505
461
|
return;
|
|
506
462
|
}
|
|
507
463
|
else if (this.isSeeking) {
|
|
@@ -509,7 +465,7 @@ class MidyGM1 {
|
|
|
509
465
|
this.exclusiveClassNotes.fill(undefined);
|
|
510
466
|
this.startTime = this.audioContext.currentTime;
|
|
511
467
|
queueIndex = this.getQueueIndex(this.resumeTime);
|
|
512
|
-
|
|
468
|
+
resumeTime = this.resumeTime - this.startTime;
|
|
513
469
|
this.isSeeking = false;
|
|
514
470
|
await schedulePlayback();
|
|
515
471
|
}
|
|
@@ -595,17 +551,52 @@ class MidyGM1 {
|
|
|
595
551
|
prevTempoTicks = event.ticks;
|
|
596
552
|
}
|
|
597
553
|
}
|
|
554
|
+
const activeNotes = new Array(this.channels.length * 128);
|
|
555
|
+
for (let i = 0; i < activeNotes.length; i++) {
|
|
556
|
+
activeNotes[i] = [];
|
|
557
|
+
}
|
|
558
|
+
for (let i = 0; i < timeline.length; i++) {
|
|
559
|
+
const event = timeline[i];
|
|
560
|
+
switch (event.type) {
|
|
561
|
+
case "noteOn": {
|
|
562
|
+
const index = event.channel * 128 + event.noteNumber;
|
|
563
|
+
activeNotes[index].push(event);
|
|
564
|
+
break;
|
|
565
|
+
}
|
|
566
|
+
case "noteOff": {
|
|
567
|
+
const index = event.channel * 128 + event.noteNumber;
|
|
568
|
+
const noteOn = activeNotes[index].pop();
|
|
569
|
+
if (noteOn) {
|
|
570
|
+
noteOn.noteOffEvent = event;
|
|
571
|
+
}
|
|
572
|
+
else {
|
|
573
|
+
const eventString = JSON.stringify(event, null, 2);
|
|
574
|
+
console.warn(`noteOff without matching noteOn: ${eventString}`);
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
}
|
|
598
579
|
return { instruments, timeline };
|
|
599
580
|
}
|
|
581
|
+
stopActiveNotes(channelNumber, velocity, force, scheduleTime) {
|
|
582
|
+
const channel = this.channels[channelNumber];
|
|
583
|
+
const promises = [];
|
|
584
|
+
this.processActiveNotes(channel, scheduleTime, (note) => {
|
|
585
|
+
const promise = this.scheduleNoteOff(channelNumber, note.noteNumber, velocity, scheduleTime, force, undefined);
|
|
586
|
+
this.notePromises.push(promise);
|
|
587
|
+
promises.push(promise);
|
|
588
|
+
});
|
|
589
|
+
return Promise.all(promises);
|
|
590
|
+
}
|
|
600
591
|
stopChannelNotes(channelNumber, velocity, force, scheduleTime) {
|
|
601
592
|
const channel = this.channels[channelNumber];
|
|
602
593
|
const promises = [];
|
|
603
594
|
this.processScheduledNotes(channel, (note) => {
|
|
604
|
-
const promise = this.scheduleNoteOff(channelNumber, note
|
|
595
|
+
const promise = this.scheduleNoteOff(channelNumber, note, velocity, scheduleTime, force);
|
|
605
596
|
this.notePromises.push(promise);
|
|
606
597
|
promises.push(promise);
|
|
607
598
|
});
|
|
608
|
-
channel.scheduledNotes
|
|
599
|
+
channel.scheduledNotes = [];
|
|
609
600
|
return Promise.all(promises);
|
|
610
601
|
}
|
|
611
602
|
stopNotes(velocity, force, scheduleTime) {
|
|
@@ -626,9 +617,6 @@ class MidyGM1 {
|
|
|
626
617
|
if (!this.isPlaying)
|
|
627
618
|
return;
|
|
628
619
|
this.isStopping = true;
|
|
629
|
-
for (let i = 0; i < this.channels.length; i++) {
|
|
630
|
-
this.resetAllStates(i);
|
|
631
|
-
}
|
|
632
620
|
}
|
|
633
621
|
pause() {
|
|
634
622
|
if (!this.isPlaying || this.isPaused)
|
|
@@ -663,35 +651,31 @@ class MidyGM1 {
|
|
|
663
651
|
return this.resumeTime + now - this.startTime - this.startDelay;
|
|
664
652
|
}
|
|
665
653
|
processScheduledNotes(channel, callback) {
|
|
666
|
-
channel.scheduledNotes
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
getActiveNotes(channel, scheduleTime) {
|
|
676
|
-
const activeNotes = new SparseMap(128);
|
|
677
|
-
channel.scheduledNotes.forEach((noteList) => {
|
|
678
|
-
const activeNote = this.getActiveNote(noteList, scheduleTime);
|
|
679
|
-
if (activeNote) {
|
|
680
|
-
activeNotes.set(activeNote.noteNumber, activeNote);
|
|
681
|
-
}
|
|
682
|
-
});
|
|
683
|
-
return activeNotes;
|
|
654
|
+
const scheduledNotes = channel.scheduledNotes;
|
|
655
|
+
for (let i = 0; i < scheduledNotes.length; i++) {
|
|
656
|
+
const note = scheduledNotes[i];
|
|
657
|
+
if (!note)
|
|
658
|
+
continue;
|
|
659
|
+
if (note.ending)
|
|
660
|
+
continue;
|
|
661
|
+
callback(note);
|
|
662
|
+
}
|
|
684
663
|
}
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
664
|
+
processActiveNotes(channel, scheduleTime, callback) {
|
|
665
|
+
const scheduledNotes = channel.scheduledNotes;
|
|
666
|
+
for (let i = 0; i < scheduledNotes.length; i++) {
|
|
667
|
+
const note = scheduledNotes[i];
|
|
688
668
|
if (!note)
|
|
689
|
-
|
|
669
|
+
continue;
|
|
670
|
+
if (note.ending)
|
|
671
|
+
continue;
|
|
672
|
+
const noteOffEvent = note.noteOffEvent;
|
|
673
|
+
if (noteOffEvent && noteOffEvent.startTime < scheduleTime)
|
|
674
|
+
continue;
|
|
690
675
|
if (scheduleTime < note.startTime)
|
|
691
676
|
continue;
|
|
692
|
-
|
|
677
|
+
callback(note);
|
|
693
678
|
}
|
|
694
|
-
return noteList[0];
|
|
695
679
|
}
|
|
696
680
|
cbToRatio(cb) {
|
|
697
681
|
return Math.pow(10, cb / 200);
|
|
@@ -831,7 +815,7 @@ class MidyGM1 {
|
|
|
831
815
|
const voiceParams = voice.getAllParams(controllerState);
|
|
832
816
|
const note = new Note(noteNumber, velocity, startTime, voice, voiceParams);
|
|
833
817
|
const audioBuffer = await this.getAudioBuffer(channel.programNumber, noteNumber, velocity, voiceParams, isSF3);
|
|
834
|
-
note.bufferSource = this.createBufferSource(
|
|
818
|
+
note.bufferSource = this.createBufferSource(voiceParams, audioBuffer);
|
|
835
819
|
note.volumeEnvelopeNode = new GainNode(this.audioContext);
|
|
836
820
|
note.filterNode = new BiquadFilterNode(this.audioContext, {
|
|
837
821
|
type: "lowpass",
|
|
@@ -840,6 +824,7 @@ class MidyGM1 {
|
|
|
840
824
|
this.setVolumeEnvelope(note, now);
|
|
841
825
|
this.setFilterEnvelope(note, now);
|
|
842
826
|
this.setPitchEnvelope(note, now);
|
|
827
|
+
this.updateDetune(channel, note, now);
|
|
843
828
|
if (0 < state.modulationDepth) {
|
|
844
829
|
this.startModulation(channel, note, now);
|
|
845
830
|
}
|
|
@@ -856,13 +841,13 @@ class MidyGM1 {
|
|
|
856
841
|
if (prev) {
|
|
857
842
|
const [prevNote, prevChannelNumber] = prev;
|
|
858
843
|
if (prevNote && !prevNote.ending) {
|
|
859
|
-
this.scheduleNoteOff(prevChannelNumber, prevNote
|
|
844
|
+
this.scheduleNoteOff(prevChannelNumber, prevNote, 0, // velocity,
|
|
860
845
|
startTime, true);
|
|
861
846
|
}
|
|
862
847
|
}
|
|
863
848
|
this.exclusiveClassNotes[exclusiveClass] = [note, channelNumber];
|
|
864
849
|
}
|
|
865
|
-
async scheduleNoteOn(channelNumber, noteNumber, velocity, startTime) {
|
|
850
|
+
async scheduleNoteOn(channelNumber, noteNumber, velocity, startTime, noteOffEvent) {
|
|
866
851
|
const channel = this.channels[channelNumber];
|
|
867
852
|
const bankNumber = channel.bank;
|
|
868
853
|
const soundFontIndex = this.soundFontTable[channel.programNumber].get(bankNumber);
|
|
@@ -874,27 +859,28 @@ class MidyGM1 {
|
|
|
874
859
|
return;
|
|
875
860
|
const isSF3 = soundFont.parsed.info.version.major === 3;
|
|
876
861
|
const note = await this.createNote(channel, voice, noteNumber, velocity, startTime, isSF3);
|
|
862
|
+
note.noteOffEvent = noteOffEvent;
|
|
877
863
|
note.volumeEnvelopeNode.connect(channel.gainL);
|
|
878
864
|
note.volumeEnvelopeNode.connect(channel.gainR);
|
|
879
865
|
if (0.5 <= channel.state.sustainPedal) {
|
|
880
866
|
channel.sustainNotes.push(note);
|
|
881
867
|
}
|
|
882
868
|
this.handleExclusiveClass(note, channelNumber, startTime);
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
869
|
+
const scheduledNotes = channel.scheduledNotes;
|
|
870
|
+
note.index = scheduledNotes.length;
|
|
871
|
+
scheduledNotes.push(note);
|
|
872
|
+
if (noteOffEvent) {
|
|
873
|
+
const notePromise = this.scheduleNoteOff(channelNumber, note, noteOffEvent.velocity, noteOffEvent.startTime, false);
|
|
874
|
+
if (notePromise) {
|
|
875
|
+
this.notePromises.push(notePromise);
|
|
876
|
+
}
|
|
890
877
|
}
|
|
891
878
|
}
|
|
892
879
|
noteOn(channelNumber, noteNumber, velocity, scheduleTime) {
|
|
893
880
|
scheduleTime ??= this.audioContext.currentTime;
|
|
894
|
-
return this.scheduleNoteOn(channelNumber, noteNumber, velocity, scheduleTime);
|
|
881
|
+
return this.scheduleNoteOn(channelNumber, noteNumber, velocity, scheduleTime, undefined);
|
|
895
882
|
}
|
|
896
|
-
disconnectNote(note
|
|
897
|
-
scheduledNotes[index] = null;
|
|
883
|
+
disconnectNote(note) {
|
|
898
884
|
note.bufferSource.disconnect();
|
|
899
885
|
note.filterNode.disconnect();
|
|
900
886
|
note.volumeEnvelopeNode.disconnect();
|
|
@@ -904,8 +890,7 @@ class MidyGM1 {
|
|
|
904
890
|
note.modulationLFO.stop();
|
|
905
891
|
}
|
|
906
892
|
}
|
|
907
|
-
stopNote(
|
|
908
|
-
const note = scheduledNotes[index];
|
|
893
|
+
stopNote(channel, note, endTime, stopTime) {
|
|
909
894
|
note.volumeEnvelopeNode.gain
|
|
910
895
|
.cancelScheduledValues(endTime)
|
|
911
896
|
.linearRampToValueAtTime(0, stopTime);
|
|
@@ -915,44 +900,50 @@ class MidyGM1 {
|
|
|
915
900
|
}, stopTime);
|
|
916
901
|
return new Promise((resolve) => {
|
|
917
902
|
note.bufferSource.onended = () => {
|
|
918
|
-
|
|
903
|
+
channel.scheduledNotes[note.index] = undefined;
|
|
904
|
+
this.disconnectNote(note);
|
|
919
905
|
resolve();
|
|
920
906
|
};
|
|
921
907
|
note.bufferSource.stop(stopTime);
|
|
922
908
|
});
|
|
923
909
|
}
|
|
924
|
-
scheduleNoteOff(channelNumber,
|
|
910
|
+
scheduleNoteOff(channelNumber, note, _velocity, endTime, force) {
|
|
925
911
|
const channel = this.channels[channelNumber];
|
|
926
912
|
if (!force && 0.5 <= channel.state.sustainPedal)
|
|
927
913
|
return;
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
914
|
+
const volRelease = endTime + note.voiceParams.volRelease;
|
|
915
|
+
const modRelease = endTime + note.voiceParams.modRelease;
|
|
916
|
+
note.filterNode.frequency
|
|
917
|
+
.cancelScheduledValues(endTime)
|
|
918
|
+
.linearRampToValueAtTime(0, modRelease);
|
|
919
|
+
const stopTime = Math.min(volRelease, modRelease);
|
|
920
|
+
return this.stopNote(channel, note, endTime, stopTime);
|
|
921
|
+
}
|
|
922
|
+
findNoteOffTarget(channel, noteNumber) {
|
|
923
|
+
const scheduledNotes = channel.scheduledNotes;
|
|
931
924
|
for (let i = 0; i < scheduledNotes.length; i++) {
|
|
932
925
|
const note = scheduledNotes[i];
|
|
933
926
|
if (!note)
|
|
934
927
|
continue;
|
|
935
928
|
if (note.ending)
|
|
936
929
|
continue;
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
note
|
|
940
|
-
.cancelScheduledValues(endTime)
|
|
941
|
-
.linearRampToValueAtTime(0, modRelease);
|
|
942
|
-
const stopTime = Math.min(volRelease, modRelease);
|
|
943
|
-
return this.stopNote(endTime, stopTime, scheduledNotes, i);
|
|
930
|
+
if (note.noteNumber !== noteNumber)
|
|
931
|
+
continue;
|
|
932
|
+
return note;
|
|
944
933
|
}
|
|
945
934
|
}
|
|
946
935
|
noteOff(channelNumber, noteNumber, velocity, scheduleTime) {
|
|
947
936
|
scheduleTime ??= this.audioContext.currentTime;
|
|
948
|
-
|
|
937
|
+
const channel = this.channels[channelNumber];
|
|
938
|
+
const note = this.findNoteOffTarget(channel, noteNumber);
|
|
939
|
+
return this.scheduleNoteOff(channelNumber, note, velocity, scheduleTime, false);
|
|
949
940
|
}
|
|
950
941
|
releaseSustainPedal(channelNumber, halfVelocity, scheduleTime) {
|
|
951
942
|
const velocity = halfVelocity * 2;
|
|
952
943
|
const channel = this.channels[channelNumber];
|
|
953
944
|
const promises = [];
|
|
954
945
|
for (let i = 0; i < channel.sustainNotes.length; i++) {
|
|
955
|
-
const promise = this.
|
|
946
|
+
const promise = this.scheduleNoteOff(channelNumber, channel.sustainNotes[i], velocity, scheduleTime);
|
|
956
947
|
promises.push(promise);
|
|
957
948
|
}
|
|
958
949
|
channel.sustainNotes = [];
|
|
@@ -1108,20 +1099,19 @@ class MidyGM1 {
|
|
|
1108
1099
|
});
|
|
1109
1100
|
}
|
|
1110
1101
|
createControlChangeHandlers() {
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
};
|
|
1102
|
+
const handlers = new Array(128);
|
|
1103
|
+
handlers[1] = this.setModulationDepth;
|
|
1104
|
+
handlers[6] = this.dataEntryMSB;
|
|
1105
|
+
handlers[7] = this.setVolume;
|
|
1106
|
+
handlers[10] = this.setPan;
|
|
1107
|
+
handlers[11] = this.setExpression;
|
|
1108
|
+
handlers[38] = this.dataEntryLSB;
|
|
1109
|
+
handlers[64] = this.setSustainPedal;
|
|
1110
|
+
handlers[100] = this.setRPNLSB;
|
|
1111
|
+
handlers[101] = this.setRPNMSB;
|
|
1112
|
+
handlers[120] = this.allSoundOff;
|
|
1113
|
+
handlers[121] = this.resetAllControllers;
|
|
1114
|
+
return handlers;
|
|
1125
1115
|
}
|
|
1126
1116
|
handleControlChange(channelNumber, controllerType, value, scheduleTime) {
|
|
1127
1117
|
const handler = this.controlChangeHandlers[controllerType];
|
|
@@ -1307,22 +1297,29 @@ class MidyGM1 {
|
|
|
1307
1297
|
}
|
|
1308
1298
|
allSoundOff(channelNumber, _value, scheduleTime) {
|
|
1309
1299
|
scheduleTime ??= this.audioContext.currentTime;
|
|
1310
|
-
return this.
|
|
1300
|
+
return this.stopActiveNotes(channelNumber, 0, true, scheduleTime);
|
|
1311
1301
|
}
|
|
1312
1302
|
resetAllStates(channelNumber) {
|
|
1303
|
+
const scheduleTime = this.audioContext.currentTime;
|
|
1313
1304
|
const channel = this.channels[channelNumber];
|
|
1314
1305
|
const state = channel.state;
|
|
1315
|
-
|
|
1316
|
-
|
|
1306
|
+
const entries = Object.entries(defaultControllerState);
|
|
1307
|
+
for (const [key, { type, defaultValue }] of entries) {
|
|
1308
|
+
if (128 <= type) {
|
|
1309
|
+
this.handleControlChange(channelNumber, type - 128, Math.ceil(defaultValue * 127), scheduleTime);
|
|
1310
|
+
}
|
|
1311
|
+
else {
|
|
1312
|
+
state[key] = defaultValue;
|
|
1313
|
+
}
|
|
1317
1314
|
}
|
|
1318
|
-
for (const
|
|
1319
|
-
channel[
|
|
1315
|
+
for (const key of Object.keys(this.constructor.channelSettings)) {
|
|
1316
|
+
channel[key] = this.constructor.channelSettings[key];
|
|
1320
1317
|
}
|
|
1321
1318
|
this.mode = "GM1";
|
|
1322
1319
|
}
|
|
1323
1320
|
// https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/rp15.pdf
|
|
1324
|
-
resetAllControllers(channelNumber) {
|
|
1325
|
-
const
|
|
1321
|
+
resetAllControllers(channelNumber, _value, scheduleTime) {
|
|
1322
|
+
const keys = [
|
|
1326
1323
|
"pitchWheel",
|
|
1327
1324
|
"expression",
|
|
1328
1325
|
"modulationDepth",
|
|
@@ -1330,10 +1327,17 @@ class MidyGM1 {
|
|
|
1330
1327
|
];
|
|
1331
1328
|
const channel = this.channels[channelNumber];
|
|
1332
1329
|
const state = channel.state;
|
|
1333
|
-
for (let i = 0; i <
|
|
1334
|
-
const
|
|
1335
|
-
|
|
1330
|
+
for (let i = 0; i < keys.length; i++) {
|
|
1331
|
+
const key = keys[i];
|
|
1332
|
+
const { type, defaultValue } = defaultControllerState[key];
|
|
1333
|
+
if (128 <= type) {
|
|
1334
|
+
this.handleControlChange(channelNumber, type - 128, Math.ceil(defaultValue * 127), scheduleTime);
|
|
1335
|
+
}
|
|
1336
|
+
else {
|
|
1337
|
+
state[key] = defaultValue;
|
|
1338
|
+
}
|
|
1336
1339
|
}
|
|
1340
|
+
this.setPitchBend(channelNumber, 8192, scheduleTime);
|
|
1337
1341
|
const settingTypes = [
|
|
1338
1342
|
"rpnMSB",
|
|
1339
1343
|
"rpnLSB",
|
|
@@ -1345,7 +1349,7 @@ class MidyGM1 {
|
|
|
1345
1349
|
}
|
|
1346
1350
|
allNotesOff(channelNumber, _value, scheduleTime) {
|
|
1347
1351
|
scheduleTime ??= this.audioContext.currentTime;
|
|
1348
|
-
return this.
|
|
1352
|
+
return this.stopActiveNotes(channelNumber, 0, false, scheduleTime);
|
|
1349
1353
|
}
|
|
1350
1354
|
handleUniversalNonRealTimeExclusiveMessage(data, scheduleTime) {
|
|
1351
1355
|
switch (data[2]) {
|