@marmooo/midy 0.3.1 → 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/esm/midy-GM1.d.ts +11 -50
- package/esm/midy-GM1.d.ts.map +1 -1
- package/esm/midy-GM1.js +150 -169
- package/esm/midy-GM2.d.ts +25 -83
- package/esm/midy-GM2.d.ts.map +1 -1
- package/esm/midy-GM2.js +376 -285
- package/esm/midy-GMLite.d.ts +10 -49
- package/esm/midy-GMLite.d.ts.map +1 -1
- package/esm/midy-GMLite.js +155 -171
- package/esm/midy.d.ts +28 -106
- package/esm/midy.d.ts.map +1 -1
- package/esm/midy.js +405 -313
- package/package.json +1 -1
- package/script/midy-GM1.d.ts +11 -50
- package/script/midy-GM1.d.ts.map +1 -1
- package/script/midy-GM1.js +150 -169
- package/script/midy-GM2.d.ts +25 -83
- package/script/midy-GM2.d.ts.map +1 -1
- package/script/midy-GM2.js +376 -285
- package/script/midy-GMLite.d.ts +10 -49
- package/script/midy-GMLite.d.ts.map +1 -1
- package/script/midy-GMLite.js +155 -171
- package/script/midy.d.ts +28 -106
- package/script/midy.d.ts.map +1 -1
- package/script/midy.js +405 -313
package/esm/midy-GM1.js
CHANGED
|
@@ -1,59 +1,13 @@
|
|
|
1
1
|
import { parseMidi } from "midi-file";
|
|
2
2
|
import { parse, SoundFont } from "@marmooo/soundfont-parser";
|
|
3
|
-
// 2-3 times faster than Map
|
|
4
|
-
class SparseMap {
|
|
5
|
-
constructor(size) {
|
|
6
|
-
this.data = new Array(size);
|
|
7
|
-
this.activeIndices = [];
|
|
8
|
-
}
|
|
9
|
-
set(key, value) {
|
|
10
|
-
if (this.data[key] === undefined) {
|
|
11
|
-
this.activeIndices.push(key);
|
|
12
|
-
}
|
|
13
|
-
this.data[key] = value;
|
|
14
|
-
}
|
|
15
|
-
get(key) {
|
|
16
|
-
return this.data[key];
|
|
17
|
-
}
|
|
18
|
-
delete(key) {
|
|
19
|
-
if (this.data[key] !== undefined) {
|
|
20
|
-
this.data[key] = undefined;
|
|
21
|
-
const index = this.activeIndices.indexOf(key);
|
|
22
|
-
if (index !== -1) {
|
|
23
|
-
this.activeIndices.splice(index, 1);
|
|
24
|
-
}
|
|
25
|
-
return true;
|
|
26
|
-
}
|
|
27
|
-
return false;
|
|
28
|
-
}
|
|
29
|
-
has(key) {
|
|
30
|
-
return this.data[key] !== undefined;
|
|
31
|
-
}
|
|
32
|
-
get size() {
|
|
33
|
-
return this.activeIndices.length;
|
|
34
|
-
}
|
|
35
|
-
clear() {
|
|
36
|
-
for (let i = 0; i < this.activeIndices.length; i++) {
|
|
37
|
-
const key = this.activeIndices[i];
|
|
38
|
-
this.data[key] = undefined;
|
|
39
|
-
}
|
|
40
|
-
this.activeIndices = [];
|
|
41
|
-
}
|
|
42
|
-
*[Symbol.iterator]() {
|
|
43
|
-
for (let i = 0; i < this.activeIndices.length; i++) {
|
|
44
|
-
const key = this.activeIndices[i];
|
|
45
|
-
yield [key, this.data[key]];
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
forEach(callback) {
|
|
49
|
-
for (let i = 0; i < this.activeIndices.length; i++) {
|
|
50
|
-
const key = this.activeIndices[i];
|
|
51
|
-
callback(this.data[key], key, this);
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
3
|
class Note {
|
|
56
4
|
constructor(noteNumber, velocity, startTime, voice, voiceParams) {
|
|
5
|
+
Object.defineProperty(this, "index", {
|
|
6
|
+
enumerable: true,
|
|
7
|
+
configurable: true,
|
|
8
|
+
writable: true,
|
|
9
|
+
value: -1
|
|
10
|
+
});
|
|
57
11
|
Object.defineProperty(this, "bufferSource", {
|
|
58
12
|
enumerable: true,
|
|
59
13
|
configurable: true,
|
|
@@ -114,7 +68,7 @@ const defaultControllerState = {
|
|
|
114
68
|
modulationDepth: { type: 128 + 1, defaultValue: 0 },
|
|
115
69
|
// dataMSB: { type: 128 + 6, defaultValue: 0, },
|
|
116
70
|
volume: { type: 128 + 7, defaultValue: 100 / 127 },
|
|
117
|
-
pan: { type: 128 + 10, defaultValue:
|
|
71
|
+
pan: { type: 128 + 10, defaultValue: 64 / 127 },
|
|
118
72
|
expression: { type: 128 + 11, defaultValue: 1 },
|
|
119
73
|
// bankLSB: { type: 128 + 32, defaultValue: 0, },
|
|
120
74
|
// dataLSB: { type: 128 + 38, defaultValue: 0, },
|
|
@@ -319,7 +273,7 @@ export class MidyGM1 {
|
|
|
319
273
|
initSoundFontTable() {
|
|
320
274
|
const table = new Array(128);
|
|
321
275
|
for (let i = 0; i < 128; i++) {
|
|
322
|
-
table[i] = new
|
|
276
|
+
table[i] = new Map();
|
|
323
277
|
}
|
|
324
278
|
return table;
|
|
325
279
|
}
|
|
@@ -371,10 +325,10 @@ export class MidyGM1 {
|
|
|
371
325
|
return {
|
|
372
326
|
currentBufferSource: null,
|
|
373
327
|
isDrum: false,
|
|
374
|
-
...this.constructor.channelSettings,
|
|
375
328
|
state: new ControllerState(),
|
|
329
|
+
...this.constructor.channelSettings,
|
|
376
330
|
...this.setChannelAudioNodes(audioContext),
|
|
377
|
-
scheduledNotes:
|
|
331
|
+
scheduledNotes: [],
|
|
378
332
|
sustainNotes: [],
|
|
379
333
|
};
|
|
380
334
|
});
|
|
@@ -409,7 +363,7 @@ export class MidyGM1 {
|
|
|
409
363
|
return audioBuffer;
|
|
410
364
|
}
|
|
411
365
|
}
|
|
412
|
-
createBufferSource(
|
|
366
|
+
createBufferSource(voiceParams, audioBuffer) {
|
|
413
367
|
const bufferSource = new AudioBufferSourceNode(this.audioContext);
|
|
414
368
|
bufferSource.buffer = audioBuffer;
|
|
415
369
|
bufferSource.loop = voiceParams.sampleModes % 2 !== 0;
|
|
@@ -419,24 +373,20 @@ export class MidyGM1 {
|
|
|
419
373
|
}
|
|
420
374
|
return bufferSource;
|
|
421
375
|
}
|
|
422
|
-
async scheduleTimelineEvents(t,
|
|
376
|
+
async scheduleTimelineEvents(t, resumeTime, queueIndex) {
|
|
423
377
|
while (queueIndex < this.timeline.length) {
|
|
424
378
|
const event = this.timeline[queueIndex];
|
|
425
379
|
if (event.startTime > t + this.lookAhead)
|
|
426
380
|
break;
|
|
427
|
-
const
|
|
381
|
+
const delay = this.startDelay - resumeTime;
|
|
382
|
+
const startTime = event.startTime + delay;
|
|
428
383
|
switch (event.type) {
|
|
429
|
-
case "noteOn":
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
case "noteOff": {
|
|
436
|
-
const notePromise = this.scheduleNoteOff(event.channel, event.noteNumber, event.velocity, startTime, false);
|
|
437
|
-
if (notePromise) {
|
|
438
|
-
this.notePromises.push(notePromise);
|
|
439
|
-
}
|
|
384
|
+
case "noteOn": {
|
|
385
|
+
const noteOffEvent = {
|
|
386
|
+
...event.noteOffEvent,
|
|
387
|
+
startTime: event.noteOffEvent.startTime + delay,
|
|
388
|
+
};
|
|
389
|
+
await this.scheduleNoteOn(event.channel, event.noteNumber, event.velocity, startTime, noteOffEvent);
|
|
440
390
|
break;
|
|
441
391
|
}
|
|
442
392
|
case "controller":
|
|
@@ -469,7 +419,7 @@ export class MidyGM1 {
|
|
|
469
419
|
this.isPaused = false;
|
|
470
420
|
this.startTime = this.audioContext.currentTime;
|
|
471
421
|
let queueIndex = this.getQueueIndex(this.resumeTime);
|
|
472
|
-
let
|
|
422
|
+
let resumeTime = this.resumeTime - this.startTime;
|
|
473
423
|
this.notePromises = [];
|
|
474
424
|
const schedulePlayback = async () => {
|
|
475
425
|
if (queueIndex >= this.timeline.length) {
|
|
@@ -477,18 +427,21 @@ export class MidyGM1 {
|
|
|
477
427
|
this.notePromises = [];
|
|
478
428
|
this.exclusiveClassNotes.fill(undefined);
|
|
479
429
|
this.audioBufferCache.clear();
|
|
430
|
+
for (let i = 0; i < this.channels.length; i++) {
|
|
431
|
+
this.resetAllStates(i);
|
|
432
|
+
}
|
|
480
433
|
resolve();
|
|
481
434
|
return;
|
|
482
435
|
}
|
|
483
436
|
const now = this.audioContext.currentTime;
|
|
484
|
-
const t = now +
|
|
485
|
-
queueIndex = await this.scheduleTimelineEvents(t,
|
|
437
|
+
const t = now + resumeTime;
|
|
438
|
+
queueIndex = await this.scheduleTimelineEvents(t, resumeTime, queueIndex);
|
|
486
439
|
if (this.isPausing) {
|
|
487
440
|
await this.stopNotes(0, true, now);
|
|
488
441
|
this.notePromises = [];
|
|
489
|
-
resolve();
|
|
490
442
|
this.isPausing = false;
|
|
491
443
|
this.isPaused = true;
|
|
444
|
+
resolve();
|
|
492
445
|
return;
|
|
493
446
|
}
|
|
494
447
|
else if (this.isStopping) {
|
|
@@ -496,9 +449,12 @@ export class MidyGM1 {
|
|
|
496
449
|
this.notePromises = [];
|
|
497
450
|
this.exclusiveClassNotes.fill(undefined);
|
|
498
451
|
this.audioBufferCache.clear();
|
|
499
|
-
|
|
452
|
+
for (let i = 0; i < this.channels.length; i++) {
|
|
453
|
+
this.resetAllStates(i);
|
|
454
|
+
}
|
|
500
455
|
this.isStopping = false;
|
|
501
456
|
this.isPaused = false;
|
|
457
|
+
resolve();
|
|
502
458
|
return;
|
|
503
459
|
}
|
|
504
460
|
else if (this.isSeeking) {
|
|
@@ -506,7 +462,7 @@ export class MidyGM1 {
|
|
|
506
462
|
this.exclusiveClassNotes.fill(undefined);
|
|
507
463
|
this.startTime = this.audioContext.currentTime;
|
|
508
464
|
queueIndex = this.getQueueIndex(this.resumeTime);
|
|
509
|
-
|
|
465
|
+
resumeTime = this.resumeTime - this.startTime;
|
|
510
466
|
this.isSeeking = false;
|
|
511
467
|
await schedulePlayback();
|
|
512
468
|
}
|
|
@@ -592,13 +548,37 @@ export class MidyGM1 {
|
|
|
592
548
|
prevTempoTicks = event.ticks;
|
|
593
549
|
}
|
|
594
550
|
}
|
|
551
|
+
const activeNotes = new Array(this.channels.length * 128);
|
|
552
|
+
for (let i = 0; i < activeNotes.length; i++) {
|
|
553
|
+
activeNotes[i] = [];
|
|
554
|
+
}
|
|
555
|
+
for (let i = 0; i < timeline.length; i++) {
|
|
556
|
+
const event = timeline[i];
|
|
557
|
+
switch (event.type) {
|
|
558
|
+
case "noteOn": {
|
|
559
|
+
const index = event.channel * 128 + event.noteNumber;
|
|
560
|
+
activeNotes[index].push(event);
|
|
561
|
+
break;
|
|
562
|
+
}
|
|
563
|
+
case "noteOff": {
|
|
564
|
+
const index = event.channel * 128 + event.noteNumber;
|
|
565
|
+
const noteOn = activeNotes[index].pop();
|
|
566
|
+
if (noteOn) {
|
|
567
|
+
noteOn.noteOffEvent = event;
|
|
568
|
+
}
|
|
569
|
+
else {
|
|
570
|
+
const eventString = JSON.stringify(event, null, 2);
|
|
571
|
+
console.warn(`noteOff without matching noteOn: ${eventString}`);
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
}
|
|
595
576
|
return { instruments, timeline };
|
|
596
577
|
}
|
|
597
578
|
stopActiveNotes(channelNumber, velocity, force, scheduleTime) {
|
|
598
579
|
const channel = this.channels[channelNumber];
|
|
599
580
|
const promises = [];
|
|
600
|
-
|
|
601
|
-
activeNotes.forEach((note) => {
|
|
581
|
+
this.processActiveNotes(channel, scheduleTime, (note) => {
|
|
602
582
|
const promise = this.scheduleNoteOff(channelNumber, note.noteNumber, velocity, scheduleTime, force, undefined);
|
|
603
583
|
this.notePromises.push(promise);
|
|
604
584
|
promises.push(promise);
|
|
@@ -609,11 +589,11 @@ export class MidyGM1 {
|
|
|
609
589
|
const channel = this.channels[channelNumber];
|
|
610
590
|
const promises = [];
|
|
611
591
|
this.processScheduledNotes(channel, (note) => {
|
|
612
|
-
const promise = this.scheduleNoteOff(channelNumber, note
|
|
592
|
+
const promise = this.scheduleNoteOff(channelNumber, note, velocity, scheduleTime, force);
|
|
613
593
|
this.notePromises.push(promise);
|
|
614
594
|
promises.push(promise);
|
|
615
595
|
});
|
|
616
|
-
channel.scheduledNotes
|
|
596
|
+
channel.scheduledNotes = [];
|
|
617
597
|
return Promise.all(promises);
|
|
618
598
|
}
|
|
619
599
|
stopNotes(velocity, force, scheduleTime) {
|
|
@@ -634,9 +614,6 @@ export class MidyGM1 {
|
|
|
634
614
|
if (!this.isPlaying)
|
|
635
615
|
return;
|
|
636
616
|
this.isStopping = true;
|
|
637
|
-
for (let i = 0; i < this.channels.length; i++) {
|
|
638
|
-
this.resetAllStates(i);
|
|
639
|
-
}
|
|
640
617
|
}
|
|
641
618
|
pause() {
|
|
642
619
|
if (!this.isPlaying || this.isPaused)
|
|
@@ -671,37 +648,31 @@ export class MidyGM1 {
|
|
|
671
648
|
return this.resumeTime + now - this.startTime - this.startDelay;
|
|
672
649
|
}
|
|
673
650
|
processScheduledNotes(channel, callback) {
|
|
674
|
-
channel.scheduledNotes
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
});
|
|
684
|
-
}
|
|
685
|
-
getActiveNotes(channel, scheduleTime) {
|
|
686
|
-
const activeNotes = new SparseMap(128);
|
|
687
|
-
channel.scheduledNotes.forEach((noteList) => {
|
|
688
|
-
const activeNote = this.getActiveNote(noteList, scheduleTime);
|
|
689
|
-
if (activeNote) {
|
|
690
|
-
activeNotes.set(activeNote.noteNumber, activeNote);
|
|
691
|
-
}
|
|
692
|
-
});
|
|
693
|
-
return activeNotes;
|
|
651
|
+
const scheduledNotes = channel.scheduledNotes;
|
|
652
|
+
for (let i = 0; i < scheduledNotes.length; i++) {
|
|
653
|
+
const note = scheduledNotes[i];
|
|
654
|
+
if (!note)
|
|
655
|
+
continue;
|
|
656
|
+
if (note.ending)
|
|
657
|
+
continue;
|
|
658
|
+
callback(note);
|
|
659
|
+
}
|
|
694
660
|
}
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
661
|
+
processActiveNotes(channel, scheduleTime, callback) {
|
|
662
|
+
const scheduledNotes = channel.scheduledNotes;
|
|
663
|
+
for (let i = 0; i < scheduledNotes.length; i++) {
|
|
664
|
+
const note = scheduledNotes[i];
|
|
698
665
|
if (!note)
|
|
699
|
-
|
|
666
|
+
continue;
|
|
667
|
+
if (note.ending)
|
|
668
|
+
continue;
|
|
669
|
+
const noteOffEvent = note.noteOffEvent;
|
|
670
|
+
if (noteOffEvent && noteOffEvent.startTime < scheduleTime)
|
|
671
|
+
continue;
|
|
700
672
|
if (scheduleTime < note.startTime)
|
|
701
673
|
continue;
|
|
702
|
-
|
|
674
|
+
callback(note);
|
|
703
675
|
}
|
|
704
|
-
return noteList[0];
|
|
705
676
|
}
|
|
706
677
|
cbToRatio(cb) {
|
|
707
678
|
return Math.pow(10, cb / 200);
|
|
@@ -841,7 +812,7 @@ export class MidyGM1 {
|
|
|
841
812
|
const voiceParams = voice.getAllParams(controllerState);
|
|
842
813
|
const note = new Note(noteNumber, velocity, startTime, voice, voiceParams);
|
|
843
814
|
const audioBuffer = await this.getAudioBuffer(channel.programNumber, noteNumber, velocity, voiceParams, isSF3);
|
|
844
|
-
note.bufferSource = this.createBufferSource(
|
|
815
|
+
note.bufferSource = this.createBufferSource(voiceParams, audioBuffer);
|
|
845
816
|
note.volumeEnvelopeNode = new GainNode(this.audioContext);
|
|
846
817
|
note.filterNode = new BiquadFilterNode(this.audioContext, {
|
|
847
818
|
type: "lowpass",
|
|
@@ -850,6 +821,7 @@ export class MidyGM1 {
|
|
|
850
821
|
this.setVolumeEnvelope(note, now);
|
|
851
822
|
this.setFilterEnvelope(note, now);
|
|
852
823
|
this.setPitchEnvelope(note, now);
|
|
824
|
+
this.updateDetune(channel, note, now);
|
|
853
825
|
if (0 < state.modulationDepth) {
|
|
854
826
|
this.startModulation(channel, note, now);
|
|
855
827
|
}
|
|
@@ -866,13 +838,13 @@ export class MidyGM1 {
|
|
|
866
838
|
if (prev) {
|
|
867
839
|
const [prevNote, prevChannelNumber] = prev;
|
|
868
840
|
if (prevNote && !prevNote.ending) {
|
|
869
|
-
this.scheduleNoteOff(prevChannelNumber, prevNote
|
|
841
|
+
this.scheduleNoteOff(prevChannelNumber, prevNote, 0, // velocity,
|
|
870
842
|
startTime, true);
|
|
871
843
|
}
|
|
872
844
|
}
|
|
873
845
|
this.exclusiveClassNotes[exclusiveClass] = [note, channelNumber];
|
|
874
846
|
}
|
|
875
|
-
async scheduleNoteOn(channelNumber, noteNumber, velocity, startTime) {
|
|
847
|
+
async scheduleNoteOn(channelNumber, noteNumber, velocity, startTime, noteOffEvent) {
|
|
876
848
|
const channel = this.channels[channelNumber];
|
|
877
849
|
const bankNumber = channel.bank;
|
|
878
850
|
const soundFontIndex = this.soundFontTable[channel.programNumber].get(bankNumber);
|
|
@@ -884,6 +856,7 @@ export class MidyGM1 {
|
|
|
884
856
|
return;
|
|
885
857
|
const isSF3 = soundFont.parsed.info.version.major === 3;
|
|
886
858
|
const note = await this.createNote(channel, voice, noteNumber, velocity, startTime, isSF3);
|
|
859
|
+
note.noteOffEvent = noteOffEvent;
|
|
887
860
|
note.volumeEnvelopeNode.connect(channel.gainL);
|
|
888
861
|
note.volumeEnvelopeNode.connect(channel.gainR);
|
|
889
862
|
if (0.5 <= channel.state.sustainPedal) {
|
|
@@ -891,18 +864,18 @@ export class MidyGM1 {
|
|
|
891
864
|
}
|
|
892
865
|
this.handleExclusiveClass(note, channelNumber, startTime);
|
|
893
866
|
const scheduledNotes = channel.scheduledNotes;
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
867
|
+
note.index = scheduledNotes.length;
|
|
868
|
+
scheduledNotes.push(note);
|
|
869
|
+
if (noteOffEvent) {
|
|
870
|
+
const notePromise = this.scheduleNoteOff(channelNumber, note, noteOffEvent.velocity, noteOffEvent.startTime, false);
|
|
871
|
+
if (notePromise) {
|
|
872
|
+
this.notePromises.push(notePromise);
|
|
873
|
+
}
|
|
901
874
|
}
|
|
902
875
|
}
|
|
903
876
|
noteOn(channelNumber, noteNumber, velocity, scheduleTime) {
|
|
904
877
|
scheduleTime ??= this.audioContext.currentTime;
|
|
905
|
-
return this.scheduleNoteOn(channelNumber, noteNumber, velocity, scheduleTime);
|
|
878
|
+
return this.scheduleNoteOn(channelNumber, noteNumber, velocity, scheduleTime, undefined);
|
|
906
879
|
}
|
|
907
880
|
disconnectNote(note) {
|
|
908
881
|
note.bufferSource.disconnect();
|
|
@@ -914,8 +887,7 @@ export class MidyGM1 {
|
|
|
914
887
|
note.modulationLFO.stop();
|
|
915
888
|
}
|
|
916
889
|
}
|
|
917
|
-
stopNote(
|
|
918
|
-
const note = noteList[index];
|
|
890
|
+
stopNote(channel, note, endTime, stopTime) {
|
|
919
891
|
note.volumeEnvelopeNode.gain
|
|
920
892
|
.cancelScheduledValues(endTime)
|
|
921
893
|
.linearRampToValueAtTime(0, stopTime);
|
|
@@ -925,54 +897,50 @@ export class MidyGM1 {
|
|
|
925
897
|
}, stopTime);
|
|
926
898
|
return new Promise((resolve) => {
|
|
927
899
|
note.bufferSource.onended = () => {
|
|
928
|
-
|
|
900
|
+
channel.scheduledNotes[note.index] = undefined;
|
|
929
901
|
this.disconnectNote(note);
|
|
930
902
|
resolve();
|
|
931
903
|
};
|
|
932
904
|
note.bufferSource.stop(stopTime);
|
|
933
905
|
});
|
|
934
906
|
}
|
|
935
|
-
|
|
936
|
-
for (let i = 0; i < noteList.length; i++) {
|
|
937
|
-
const note = noteList[i];
|
|
938
|
-
if (!note)
|
|
939
|
-
continue;
|
|
940
|
-
if (note.ending)
|
|
941
|
-
continue;
|
|
942
|
-
return [note, i];
|
|
943
|
-
}
|
|
944
|
-
}
|
|
945
|
-
scheduleNoteOff(channelNumber, noteNumber, _velocity, endTime, force) {
|
|
907
|
+
scheduleNoteOff(channelNumber, note, _velocity, endTime, force) {
|
|
946
908
|
const channel = this.channels[channelNumber];
|
|
947
909
|
if (!force && 0.5 <= channel.state.sustainPedal)
|
|
948
910
|
return;
|
|
949
|
-
if (!channel.scheduledNotes.has(noteNumber))
|
|
950
|
-
return;
|
|
951
|
-
const noteList = channel.scheduledNotes.get(noteNumber);
|
|
952
|
-
if (!noteList)
|
|
953
|
-
return; // be careful with drum channel
|
|
954
|
-
const noteOffTarget = this.findNoteOffTarget(noteList, endTime);
|
|
955
|
-
if (!noteOffTarget)
|
|
956
|
-
return;
|
|
957
|
-
const [note, i] = noteOffTarget;
|
|
958
911
|
const volRelease = endTime + note.voiceParams.volRelease;
|
|
959
912
|
const modRelease = endTime + note.voiceParams.modRelease;
|
|
960
913
|
note.filterNode.frequency
|
|
961
914
|
.cancelScheduledValues(endTime)
|
|
962
915
|
.linearRampToValueAtTime(0, modRelease);
|
|
963
916
|
const stopTime = Math.min(volRelease, modRelease);
|
|
964
|
-
return this.stopNote(
|
|
917
|
+
return this.stopNote(channel, note, endTime, stopTime);
|
|
918
|
+
}
|
|
919
|
+
findNoteOffTarget(channel, noteNumber) {
|
|
920
|
+
const scheduledNotes = channel.scheduledNotes;
|
|
921
|
+
for (let i = 0; i < scheduledNotes.length; i++) {
|
|
922
|
+
const note = scheduledNotes[i];
|
|
923
|
+
if (!note)
|
|
924
|
+
continue;
|
|
925
|
+
if (note.ending)
|
|
926
|
+
continue;
|
|
927
|
+
if (note.noteNumber !== noteNumber)
|
|
928
|
+
continue;
|
|
929
|
+
return note;
|
|
930
|
+
}
|
|
965
931
|
}
|
|
966
932
|
noteOff(channelNumber, noteNumber, velocity, scheduleTime) {
|
|
967
933
|
scheduleTime ??= this.audioContext.currentTime;
|
|
968
|
-
|
|
934
|
+
const channel = this.channels[channelNumber];
|
|
935
|
+
const note = this.findNoteOffTarget(channel, noteNumber);
|
|
936
|
+
return this.scheduleNoteOff(channelNumber, note, velocity, scheduleTime, false);
|
|
969
937
|
}
|
|
970
938
|
releaseSustainPedal(channelNumber, halfVelocity, scheduleTime) {
|
|
971
939
|
const velocity = halfVelocity * 2;
|
|
972
940
|
const channel = this.channels[channelNumber];
|
|
973
941
|
const promises = [];
|
|
974
942
|
for (let i = 0; i < channel.sustainNotes.length; i++) {
|
|
975
|
-
const promise = this.
|
|
943
|
+
const promise = this.scheduleNoteOff(channelNumber, channel.sustainNotes[i], velocity, scheduleTime);
|
|
976
944
|
promises.push(promise);
|
|
977
945
|
}
|
|
978
946
|
channel.sustainNotes = [];
|
|
@@ -1128,20 +1096,19 @@ export class MidyGM1 {
|
|
|
1128
1096
|
});
|
|
1129
1097
|
}
|
|
1130
1098
|
createControlChangeHandlers() {
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
};
|
|
1099
|
+
const handlers = new Array(128);
|
|
1100
|
+
handlers[1] = this.setModulationDepth;
|
|
1101
|
+
handlers[6] = this.dataEntryMSB;
|
|
1102
|
+
handlers[7] = this.setVolume;
|
|
1103
|
+
handlers[10] = this.setPan;
|
|
1104
|
+
handlers[11] = this.setExpression;
|
|
1105
|
+
handlers[38] = this.dataEntryLSB;
|
|
1106
|
+
handlers[64] = this.setSustainPedal;
|
|
1107
|
+
handlers[100] = this.setRPNLSB;
|
|
1108
|
+
handlers[101] = this.setRPNMSB;
|
|
1109
|
+
handlers[120] = this.allSoundOff;
|
|
1110
|
+
handlers[121] = this.resetAllControllers;
|
|
1111
|
+
return handlers;
|
|
1145
1112
|
}
|
|
1146
1113
|
handleControlChange(channelNumber, controllerType, value, scheduleTime) {
|
|
1147
1114
|
const handler = this.controlChangeHandlers[controllerType];
|
|
@@ -1330,19 +1297,26 @@ export class MidyGM1 {
|
|
|
1330
1297
|
return this.stopActiveNotes(channelNumber, 0, true, scheduleTime);
|
|
1331
1298
|
}
|
|
1332
1299
|
resetAllStates(channelNumber) {
|
|
1300
|
+
const scheduleTime = this.audioContext.currentTime;
|
|
1333
1301
|
const channel = this.channels[channelNumber];
|
|
1334
1302
|
const state = channel.state;
|
|
1335
|
-
|
|
1336
|
-
|
|
1303
|
+
const entries = Object.entries(defaultControllerState);
|
|
1304
|
+
for (const [key, { type, defaultValue }] of entries) {
|
|
1305
|
+
if (128 <= type) {
|
|
1306
|
+
this.handleControlChange(channelNumber, type - 128, Math.ceil(defaultValue * 127), scheduleTime);
|
|
1307
|
+
}
|
|
1308
|
+
else {
|
|
1309
|
+
state[key] = defaultValue;
|
|
1310
|
+
}
|
|
1337
1311
|
}
|
|
1338
|
-
for (const
|
|
1339
|
-
channel[
|
|
1312
|
+
for (const key of Object.keys(this.constructor.channelSettings)) {
|
|
1313
|
+
channel[key] = this.constructor.channelSettings[key];
|
|
1340
1314
|
}
|
|
1341
1315
|
this.mode = "GM1";
|
|
1342
1316
|
}
|
|
1343
1317
|
// https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/rp15.pdf
|
|
1344
|
-
resetAllControllers(channelNumber) {
|
|
1345
|
-
const
|
|
1318
|
+
resetAllControllers(channelNumber, _value, scheduleTime) {
|
|
1319
|
+
const keys = [
|
|
1346
1320
|
"pitchWheel",
|
|
1347
1321
|
"expression",
|
|
1348
1322
|
"modulationDepth",
|
|
@@ -1350,10 +1324,17 @@ export class MidyGM1 {
|
|
|
1350
1324
|
];
|
|
1351
1325
|
const channel = this.channels[channelNumber];
|
|
1352
1326
|
const state = channel.state;
|
|
1353
|
-
for (let i = 0; i <
|
|
1354
|
-
const
|
|
1355
|
-
|
|
1327
|
+
for (let i = 0; i < keys.length; i++) {
|
|
1328
|
+
const key = keys[i];
|
|
1329
|
+
const { type, defaultValue } = defaultControllerState[key];
|
|
1330
|
+
if (128 <= type) {
|
|
1331
|
+
this.handleControlChange(channelNumber, type - 128, Math.ceil(defaultValue * 127), scheduleTime);
|
|
1332
|
+
}
|
|
1333
|
+
else {
|
|
1334
|
+
state[key] = defaultValue;
|
|
1335
|
+
}
|
|
1356
1336
|
}
|
|
1337
|
+
this.setPitchBend(channelNumber, 8192, scheduleTime);
|
|
1357
1338
|
const settingTypes = [
|
|
1358
1339
|
"rpnMSB",
|
|
1359
1340
|
"rpnLSB",
|