@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/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,26 +419,29 @@ 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) {
|
|
476
426
|
await Promise.all(this.notePromises);
|
|
477
427
|
this.notePromises = [];
|
|
478
|
-
this.exclusiveClassNotes.
|
|
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,17 +548,52 @@ 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
|
}
|
|
578
|
+
stopActiveNotes(channelNumber, velocity, force, scheduleTime) {
|
|
579
|
+
const channel = this.channels[channelNumber];
|
|
580
|
+
const promises = [];
|
|
581
|
+
this.processActiveNotes(channel, scheduleTime, (note) => {
|
|
582
|
+
const promise = this.scheduleNoteOff(channelNumber, note.noteNumber, velocity, scheduleTime, force, undefined);
|
|
583
|
+
this.notePromises.push(promise);
|
|
584
|
+
promises.push(promise);
|
|
585
|
+
});
|
|
586
|
+
return Promise.all(promises);
|
|
587
|
+
}
|
|
597
588
|
stopChannelNotes(channelNumber, velocity, force, scheduleTime) {
|
|
598
589
|
const channel = this.channels[channelNumber];
|
|
599
590
|
const promises = [];
|
|
600
591
|
this.processScheduledNotes(channel, (note) => {
|
|
601
|
-
const promise = this.scheduleNoteOff(channelNumber, note
|
|
592
|
+
const promise = this.scheduleNoteOff(channelNumber, note, velocity, scheduleTime, force);
|
|
602
593
|
this.notePromises.push(promise);
|
|
603
594
|
promises.push(promise);
|
|
604
595
|
});
|
|
605
|
-
channel.scheduledNotes
|
|
596
|
+
channel.scheduledNotes = [];
|
|
606
597
|
return Promise.all(promises);
|
|
607
598
|
}
|
|
608
599
|
stopNotes(velocity, force, scheduleTime) {
|
|
@@ -623,9 +614,6 @@ export class MidyGM1 {
|
|
|
623
614
|
if (!this.isPlaying)
|
|
624
615
|
return;
|
|
625
616
|
this.isStopping = true;
|
|
626
|
-
for (let i = 0; i < this.channels.length; i++) {
|
|
627
|
-
this.resetAllStates(i);
|
|
628
|
-
}
|
|
629
617
|
}
|
|
630
618
|
pause() {
|
|
631
619
|
if (!this.isPlaying || this.isPaused)
|
|
@@ -660,35 +648,31 @@ export class MidyGM1 {
|
|
|
660
648
|
return this.resumeTime + now - this.startTime - this.startDelay;
|
|
661
649
|
}
|
|
662
650
|
processScheduledNotes(channel, callback) {
|
|
663
|
-
channel.scheduledNotes
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
getActiveNotes(channel, scheduleTime) {
|
|
673
|
-
const activeNotes = new SparseMap(128);
|
|
674
|
-
channel.scheduledNotes.forEach((noteList) => {
|
|
675
|
-
const activeNote = this.getActiveNote(noteList, scheduleTime);
|
|
676
|
-
if (activeNote) {
|
|
677
|
-
activeNotes.set(activeNote.noteNumber, activeNote);
|
|
678
|
-
}
|
|
679
|
-
});
|
|
680
|
-
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
|
+
}
|
|
681
660
|
}
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
661
|
+
processActiveNotes(channel, scheduleTime, callback) {
|
|
662
|
+
const scheduledNotes = channel.scheduledNotes;
|
|
663
|
+
for (let i = 0; i < scheduledNotes.length; i++) {
|
|
664
|
+
const note = scheduledNotes[i];
|
|
685
665
|
if (!note)
|
|
686
|
-
|
|
666
|
+
continue;
|
|
667
|
+
if (note.ending)
|
|
668
|
+
continue;
|
|
669
|
+
const noteOffEvent = note.noteOffEvent;
|
|
670
|
+
if (noteOffEvent && noteOffEvent.startTime < scheduleTime)
|
|
671
|
+
continue;
|
|
687
672
|
if (scheduleTime < note.startTime)
|
|
688
673
|
continue;
|
|
689
|
-
|
|
674
|
+
callback(note);
|
|
690
675
|
}
|
|
691
|
-
return noteList[0];
|
|
692
676
|
}
|
|
693
677
|
cbToRatio(cb) {
|
|
694
678
|
return Math.pow(10, cb / 200);
|
|
@@ -828,7 +812,7 @@ export class MidyGM1 {
|
|
|
828
812
|
const voiceParams = voice.getAllParams(controllerState);
|
|
829
813
|
const note = new Note(noteNumber, velocity, startTime, voice, voiceParams);
|
|
830
814
|
const audioBuffer = await this.getAudioBuffer(channel.programNumber, noteNumber, velocity, voiceParams, isSF3);
|
|
831
|
-
note.bufferSource = this.createBufferSource(
|
|
815
|
+
note.bufferSource = this.createBufferSource(voiceParams, audioBuffer);
|
|
832
816
|
note.volumeEnvelopeNode = new GainNode(this.audioContext);
|
|
833
817
|
note.filterNode = new BiquadFilterNode(this.audioContext, {
|
|
834
818
|
type: "lowpass",
|
|
@@ -837,6 +821,7 @@ export class MidyGM1 {
|
|
|
837
821
|
this.setVolumeEnvelope(note, now);
|
|
838
822
|
this.setFilterEnvelope(note, now);
|
|
839
823
|
this.setPitchEnvelope(note, now);
|
|
824
|
+
this.updateDetune(channel, note, now);
|
|
840
825
|
if (0 < state.modulationDepth) {
|
|
841
826
|
this.startModulation(channel, note, now);
|
|
842
827
|
}
|
|
@@ -853,13 +838,13 @@ export class MidyGM1 {
|
|
|
853
838
|
if (prev) {
|
|
854
839
|
const [prevNote, prevChannelNumber] = prev;
|
|
855
840
|
if (prevNote && !prevNote.ending) {
|
|
856
|
-
this.scheduleNoteOff(prevChannelNumber, prevNote
|
|
841
|
+
this.scheduleNoteOff(prevChannelNumber, prevNote, 0, // velocity,
|
|
857
842
|
startTime, true);
|
|
858
843
|
}
|
|
859
844
|
}
|
|
860
845
|
this.exclusiveClassNotes[exclusiveClass] = [note, channelNumber];
|
|
861
846
|
}
|
|
862
|
-
async scheduleNoteOn(channelNumber, noteNumber, velocity, startTime) {
|
|
847
|
+
async scheduleNoteOn(channelNumber, noteNumber, velocity, startTime, noteOffEvent) {
|
|
863
848
|
const channel = this.channels[channelNumber];
|
|
864
849
|
const bankNumber = channel.bank;
|
|
865
850
|
const soundFontIndex = this.soundFontTable[channel.programNumber].get(bankNumber);
|
|
@@ -871,27 +856,28 @@ export class MidyGM1 {
|
|
|
871
856
|
return;
|
|
872
857
|
const isSF3 = soundFont.parsed.info.version.major === 3;
|
|
873
858
|
const note = await this.createNote(channel, voice, noteNumber, velocity, startTime, isSF3);
|
|
859
|
+
note.noteOffEvent = noteOffEvent;
|
|
874
860
|
note.volumeEnvelopeNode.connect(channel.gainL);
|
|
875
861
|
note.volumeEnvelopeNode.connect(channel.gainR);
|
|
876
862
|
if (0.5 <= channel.state.sustainPedal) {
|
|
877
863
|
channel.sustainNotes.push(note);
|
|
878
864
|
}
|
|
879
865
|
this.handleExclusiveClass(note, channelNumber, startTime);
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
866
|
+
const scheduledNotes = channel.scheduledNotes;
|
|
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
|
+
}
|
|
887
874
|
}
|
|
888
875
|
}
|
|
889
876
|
noteOn(channelNumber, noteNumber, velocity, scheduleTime) {
|
|
890
877
|
scheduleTime ??= this.audioContext.currentTime;
|
|
891
|
-
return this.scheduleNoteOn(channelNumber, noteNumber, velocity, scheduleTime);
|
|
878
|
+
return this.scheduleNoteOn(channelNumber, noteNumber, velocity, scheduleTime, undefined);
|
|
892
879
|
}
|
|
893
|
-
disconnectNote(note
|
|
894
|
-
scheduledNotes[index] = null;
|
|
880
|
+
disconnectNote(note) {
|
|
895
881
|
note.bufferSource.disconnect();
|
|
896
882
|
note.filterNode.disconnect();
|
|
897
883
|
note.volumeEnvelopeNode.disconnect();
|
|
@@ -901,8 +887,7 @@ export class MidyGM1 {
|
|
|
901
887
|
note.modulationLFO.stop();
|
|
902
888
|
}
|
|
903
889
|
}
|
|
904
|
-
stopNote(
|
|
905
|
-
const note = scheduledNotes[index];
|
|
890
|
+
stopNote(channel, note, endTime, stopTime) {
|
|
906
891
|
note.volumeEnvelopeNode.gain
|
|
907
892
|
.cancelScheduledValues(endTime)
|
|
908
893
|
.linearRampToValueAtTime(0, stopTime);
|
|
@@ -912,44 +897,50 @@ export class MidyGM1 {
|
|
|
912
897
|
}, stopTime);
|
|
913
898
|
return new Promise((resolve) => {
|
|
914
899
|
note.bufferSource.onended = () => {
|
|
915
|
-
|
|
900
|
+
channel.scheduledNotes[note.index] = undefined;
|
|
901
|
+
this.disconnectNote(note);
|
|
916
902
|
resolve();
|
|
917
903
|
};
|
|
918
904
|
note.bufferSource.stop(stopTime);
|
|
919
905
|
});
|
|
920
906
|
}
|
|
921
|
-
scheduleNoteOff(channelNumber,
|
|
907
|
+
scheduleNoteOff(channelNumber, note, _velocity, endTime, force) {
|
|
922
908
|
const channel = this.channels[channelNumber];
|
|
923
909
|
if (!force && 0.5 <= channel.state.sustainPedal)
|
|
924
910
|
return;
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
911
|
+
const volRelease = endTime + note.voiceParams.volRelease;
|
|
912
|
+
const modRelease = endTime + note.voiceParams.modRelease;
|
|
913
|
+
note.filterNode.frequency
|
|
914
|
+
.cancelScheduledValues(endTime)
|
|
915
|
+
.linearRampToValueAtTime(0, modRelease);
|
|
916
|
+
const stopTime = Math.min(volRelease, modRelease);
|
|
917
|
+
return this.stopNote(channel, note, endTime, stopTime);
|
|
918
|
+
}
|
|
919
|
+
findNoteOffTarget(channel, noteNumber) {
|
|
920
|
+
const scheduledNotes = channel.scheduledNotes;
|
|
928
921
|
for (let i = 0; i < scheduledNotes.length; i++) {
|
|
929
922
|
const note = scheduledNotes[i];
|
|
930
923
|
if (!note)
|
|
931
924
|
continue;
|
|
932
925
|
if (note.ending)
|
|
933
926
|
continue;
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
note
|
|
937
|
-
.cancelScheduledValues(endTime)
|
|
938
|
-
.linearRampToValueAtTime(0, modRelease);
|
|
939
|
-
const stopTime = Math.min(volRelease, modRelease);
|
|
940
|
-
return this.stopNote(endTime, stopTime, scheduledNotes, i);
|
|
927
|
+
if (note.noteNumber !== noteNumber)
|
|
928
|
+
continue;
|
|
929
|
+
return note;
|
|
941
930
|
}
|
|
942
931
|
}
|
|
943
932
|
noteOff(channelNumber, noteNumber, velocity, scheduleTime) {
|
|
944
933
|
scheduleTime ??= this.audioContext.currentTime;
|
|
945
|
-
|
|
934
|
+
const channel = this.channels[channelNumber];
|
|
935
|
+
const note = this.findNoteOffTarget(channel, noteNumber);
|
|
936
|
+
return this.scheduleNoteOff(channelNumber, note, velocity, scheduleTime, false);
|
|
946
937
|
}
|
|
947
938
|
releaseSustainPedal(channelNumber, halfVelocity, scheduleTime) {
|
|
948
939
|
const velocity = halfVelocity * 2;
|
|
949
940
|
const channel = this.channels[channelNumber];
|
|
950
941
|
const promises = [];
|
|
951
942
|
for (let i = 0; i < channel.sustainNotes.length; i++) {
|
|
952
|
-
const promise = this.
|
|
943
|
+
const promise = this.scheduleNoteOff(channelNumber, channel.sustainNotes[i], velocity, scheduleTime);
|
|
953
944
|
promises.push(promise);
|
|
954
945
|
}
|
|
955
946
|
channel.sustainNotes = [];
|
|
@@ -1105,20 +1096,19 @@ export class MidyGM1 {
|
|
|
1105
1096
|
});
|
|
1106
1097
|
}
|
|
1107
1098
|
createControlChangeHandlers() {
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
};
|
|
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;
|
|
1122
1112
|
}
|
|
1123
1113
|
handleControlChange(channelNumber, controllerType, value, scheduleTime) {
|
|
1124
1114
|
const handler = this.controlChangeHandlers[controllerType];
|
|
@@ -1304,22 +1294,29 @@ export class MidyGM1 {
|
|
|
1304
1294
|
}
|
|
1305
1295
|
allSoundOff(channelNumber, _value, scheduleTime) {
|
|
1306
1296
|
scheduleTime ??= this.audioContext.currentTime;
|
|
1307
|
-
return this.
|
|
1297
|
+
return this.stopActiveNotes(channelNumber, 0, true, scheduleTime);
|
|
1308
1298
|
}
|
|
1309
1299
|
resetAllStates(channelNumber) {
|
|
1300
|
+
const scheduleTime = this.audioContext.currentTime;
|
|
1310
1301
|
const channel = this.channels[channelNumber];
|
|
1311
1302
|
const state = channel.state;
|
|
1312
|
-
|
|
1313
|
-
|
|
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
|
+
}
|
|
1314
1311
|
}
|
|
1315
|
-
for (const
|
|
1316
|
-
channel[
|
|
1312
|
+
for (const key of Object.keys(this.constructor.channelSettings)) {
|
|
1313
|
+
channel[key] = this.constructor.channelSettings[key];
|
|
1317
1314
|
}
|
|
1318
1315
|
this.mode = "GM1";
|
|
1319
1316
|
}
|
|
1320
1317
|
// https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/rp15.pdf
|
|
1321
|
-
resetAllControllers(channelNumber) {
|
|
1322
|
-
const
|
|
1318
|
+
resetAllControllers(channelNumber, _value, scheduleTime) {
|
|
1319
|
+
const keys = [
|
|
1323
1320
|
"pitchWheel",
|
|
1324
1321
|
"expression",
|
|
1325
1322
|
"modulationDepth",
|
|
@@ -1327,10 +1324,17 @@ export class MidyGM1 {
|
|
|
1327
1324
|
];
|
|
1328
1325
|
const channel = this.channels[channelNumber];
|
|
1329
1326
|
const state = channel.state;
|
|
1330
|
-
for (let i = 0; i <
|
|
1331
|
-
const
|
|
1332
|
-
|
|
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
|
+
}
|
|
1333
1336
|
}
|
|
1337
|
+
this.setPitchBend(channelNumber, 8192, scheduleTime);
|
|
1334
1338
|
const settingTypes = [
|
|
1335
1339
|
"rpnMSB",
|
|
1336
1340
|
"rpnLSB",
|
|
@@ -1342,7 +1346,7 @@ export class MidyGM1 {
|
|
|
1342
1346
|
}
|
|
1343
1347
|
allNotesOff(channelNumber, _value, scheduleTime) {
|
|
1344
1348
|
scheduleTime ??= this.audioContext.currentTime;
|
|
1345
|
-
return this.
|
|
1349
|
+
return this.stopActiveNotes(channelNumber, 0, false, scheduleTime);
|
|
1346
1350
|
}
|
|
1347
1351
|
handleUniversalNonRealTimeExclusiveMessage(data, scheduleTime) {
|
|
1348
1352
|
switch (data[2]) {
|