@marmooo/midy 0.3.1 → 0.3.3
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 +17 -55
- package/esm/midy-GM1.d.ts.map +1 -1
- package/esm/midy-GM1.js +159 -184
- package/esm/midy-GM2.d.ts +34 -90
- package/esm/midy-GM2.d.ts.map +1 -1
- package/esm/midy-GM2.js +421 -342
- package/esm/midy-GMLite.d.ts +17 -55
- package/esm/midy-GMLite.d.ts.map +1 -1
- package/esm/midy-GMLite.js +169 -199
- package/esm/midy.d.ts +37 -114
- package/esm/midy.d.ts.map +1 -1
- package/esm/midy.js +451 -376
- package/package.json +1 -1
- package/script/midy-GM1.d.ts +17 -55
- package/script/midy-GM1.d.ts.map +1 -1
- package/script/midy-GM1.js +159 -184
- package/script/midy-GM2.d.ts +34 -90
- package/script/midy-GM2.d.ts.map +1 -1
- package/script/midy-GM2.js +421 -342
- package/script/midy-GMLite.d.ts +17 -55
- package/script/midy-GMLite.d.ts.map +1 -1
- package/script/midy-GMLite.js +169 -199
- package/script/midy.d.ts +37 -114
- package/script/midy.d.ts.map +1 -1
- package/script/midy.js +451 -376
package/esm/midy-GM1.js
CHANGED
|
@@ -1,59 +1,19 @@
|
|
|
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
|
+
});
|
|
11
|
+
Object.defineProperty(this, "ending", {
|
|
12
|
+
enumerable: true,
|
|
13
|
+
configurable: true,
|
|
14
|
+
writable: true,
|
|
15
|
+
value: false
|
|
16
|
+
});
|
|
57
17
|
Object.defineProperty(this, "bufferSource", {
|
|
58
18
|
enumerable: true,
|
|
59
19
|
configurable: true,
|
|
@@ -114,7 +74,7 @@ const defaultControllerState = {
|
|
|
114
74
|
modulationDepth: { type: 128 + 1, defaultValue: 0 },
|
|
115
75
|
// dataMSB: { type: 128 + 6, defaultValue: 0, },
|
|
116
76
|
volume: { type: 128 + 7, defaultValue: 100 / 127 },
|
|
117
|
-
pan: { type: 128 + 10, defaultValue:
|
|
77
|
+
pan: { type: 128 + 10, defaultValue: 64 / 127 },
|
|
118
78
|
expression: { type: 128 + 11, defaultValue: 1 },
|
|
119
79
|
// bankLSB: { type: 128 + 32, defaultValue: 0, },
|
|
120
80
|
// dataLSB: { type: 128 + 38, defaultValue: 0, },
|
|
@@ -319,7 +279,7 @@ export class MidyGM1 {
|
|
|
319
279
|
initSoundFontTable() {
|
|
320
280
|
const table = new Array(128);
|
|
321
281
|
for (let i = 0; i < 128; i++) {
|
|
322
|
-
table[i] = new
|
|
282
|
+
table[i] = new Map();
|
|
323
283
|
}
|
|
324
284
|
return table;
|
|
325
285
|
}
|
|
@@ -335,17 +295,37 @@ export class MidyGM1 {
|
|
|
335
295
|
}
|
|
336
296
|
}
|
|
337
297
|
}
|
|
338
|
-
async loadSoundFont(
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
298
|
+
async loadSoundFont(input) {
|
|
299
|
+
let uint8Array;
|
|
300
|
+
if (typeof input === "string") {
|
|
301
|
+
const response = await fetch(input);
|
|
302
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
303
|
+
uint8Array = new Uint8Array(arrayBuffer);
|
|
304
|
+
}
|
|
305
|
+
else if (input instanceof Uint8Array) {
|
|
306
|
+
uint8Array = input;
|
|
307
|
+
}
|
|
308
|
+
else {
|
|
309
|
+
throw new TypeError("input must be a URL string or Uint8Array");
|
|
310
|
+
}
|
|
311
|
+
const parsed = parse(uint8Array);
|
|
342
312
|
const soundFont = new SoundFont(parsed);
|
|
343
313
|
this.addSoundFont(soundFont);
|
|
344
314
|
}
|
|
345
|
-
async loadMIDI(
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
315
|
+
async loadMIDI(input) {
|
|
316
|
+
let uint8Array;
|
|
317
|
+
if (typeof input === "string") {
|
|
318
|
+
const response = await fetch(input);
|
|
319
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
320
|
+
uint8Array = new Uint8Array(arrayBuffer);
|
|
321
|
+
}
|
|
322
|
+
else if (input instanceof Uint8Array) {
|
|
323
|
+
uint8Array = input;
|
|
324
|
+
}
|
|
325
|
+
else {
|
|
326
|
+
throw new TypeError("input must be a URL string or Uint8Array");
|
|
327
|
+
}
|
|
328
|
+
const midi = parseMidi(uint8Array);
|
|
349
329
|
this.ticksPerBeat = midi.header.ticksPerBeat;
|
|
350
330
|
const midiData = this.extractMidiData(midi);
|
|
351
331
|
this.instruments = midiData.instruments;
|
|
@@ -371,10 +351,10 @@ export class MidyGM1 {
|
|
|
371
351
|
return {
|
|
372
352
|
currentBufferSource: null,
|
|
373
353
|
isDrum: false,
|
|
374
|
-
...this.constructor.channelSettings,
|
|
375
354
|
state: new ControllerState(),
|
|
355
|
+
...this.constructor.channelSettings,
|
|
376
356
|
...this.setChannelAudioNodes(audioContext),
|
|
377
|
-
scheduledNotes:
|
|
357
|
+
scheduledNotes: [],
|
|
378
358
|
sustainNotes: [],
|
|
379
359
|
};
|
|
380
360
|
});
|
|
@@ -409,7 +389,7 @@ export class MidyGM1 {
|
|
|
409
389
|
return audioBuffer;
|
|
410
390
|
}
|
|
411
391
|
}
|
|
412
|
-
createBufferSource(
|
|
392
|
+
createBufferSource(voiceParams, audioBuffer) {
|
|
413
393
|
const bufferSource = new AudioBufferSourceNode(this.audioContext);
|
|
414
394
|
bufferSource.buffer = audioBuffer;
|
|
415
395
|
bufferSource.loop = voiceParams.sampleModes % 2 !== 0;
|
|
@@ -419,24 +399,21 @@ export class MidyGM1 {
|
|
|
419
399
|
}
|
|
420
400
|
return bufferSource;
|
|
421
401
|
}
|
|
422
|
-
async scheduleTimelineEvents(t,
|
|
402
|
+
async scheduleTimelineEvents(t, resumeTime, queueIndex) {
|
|
423
403
|
while (queueIndex < this.timeline.length) {
|
|
424
404
|
const event = this.timeline[queueIndex];
|
|
425
405
|
if (event.startTime > t + this.lookAhead)
|
|
426
406
|
break;
|
|
427
|
-
const
|
|
407
|
+
const delay = this.startDelay - resumeTime;
|
|
408
|
+
const startTime = event.startTime + delay;
|
|
428
409
|
switch (event.type) {
|
|
429
410
|
case "noteOn":
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
break;
|
|
433
|
-
}
|
|
434
|
-
/* falls through */
|
|
411
|
+
await this.scheduleNoteOn(event.channel, event.noteNumber, event.velocity, startTime);
|
|
412
|
+
break;
|
|
435
413
|
case "noteOff": {
|
|
436
414
|
const notePromise = this.scheduleNoteOff(event.channel, event.noteNumber, event.velocity, startTime, false);
|
|
437
|
-
if (notePromise)
|
|
415
|
+
if (notePromise)
|
|
438
416
|
this.notePromises.push(notePromise);
|
|
439
|
-
}
|
|
440
417
|
break;
|
|
441
418
|
}
|
|
442
419
|
case "controller":
|
|
@@ -469,7 +446,7 @@ export class MidyGM1 {
|
|
|
469
446
|
this.isPaused = false;
|
|
470
447
|
this.startTime = this.audioContext.currentTime;
|
|
471
448
|
let queueIndex = this.getQueueIndex(this.resumeTime);
|
|
472
|
-
let
|
|
449
|
+
let resumeTime = this.resumeTime - this.startTime;
|
|
473
450
|
this.notePromises = [];
|
|
474
451
|
const schedulePlayback = async () => {
|
|
475
452
|
if (queueIndex >= this.timeline.length) {
|
|
@@ -477,18 +454,21 @@ export class MidyGM1 {
|
|
|
477
454
|
this.notePromises = [];
|
|
478
455
|
this.exclusiveClassNotes.fill(undefined);
|
|
479
456
|
this.audioBufferCache.clear();
|
|
457
|
+
for (let i = 0; i < this.channels.length; i++) {
|
|
458
|
+
this.resetAllStates(i);
|
|
459
|
+
}
|
|
480
460
|
resolve();
|
|
481
461
|
return;
|
|
482
462
|
}
|
|
483
463
|
const now = this.audioContext.currentTime;
|
|
484
|
-
const t = now +
|
|
485
|
-
queueIndex = await this.scheduleTimelineEvents(t,
|
|
464
|
+
const t = now + resumeTime;
|
|
465
|
+
queueIndex = await this.scheduleTimelineEvents(t, resumeTime, queueIndex);
|
|
486
466
|
if (this.isPausing) {
|
|
487
467
|
await this.stopNotes(0, true, now);
|
|
488
468
|
this.notePromises = [];
|
|
489
|
-
resolve();
|
|
490
469
|
this.isPausing = false;
|
|
491
470
|
this.isPaused = true;
|
|
471
|
+
resolve();
|
|
492
472
|
return;
|
|
493
473
|
}
|
|
494
474
|
else if (this.isStopping) {
|
|
@@ -496,9 +476,12 @@ export class MidyGM1 {
|
|
|
496
476
|
this.notePromises = [];
|
|
497
477
|
this.exclusiveClassNotes.fill(undefined);
|
|
498
478
|
this.audioBufferCache.clear();
|
|
499
|
-
|
|
479
|
+
for (let i = 0; i < this.channels.length; i++) {
|
|
480
|
+
this.resetAllStates(i);
|
|
481
|
+
}
|
|
500
482
|
this.isStopping = false;
|
|
501
483
|
this.isPaused = false;
|
|
484
|
+
resolve();
|
|
502
485
|
return;
|
|
503
486
|
}
|
|
504
487
|
else if (this.isSeeking) {
|
|
@@ -506,7 +489,7 @@ export class MidyGM1 {
|
|
|
506
489
|
this.exclusiveClassNotes.fill(undefined);
|
|
507
490
|
this.startTime = this.audioContext.currentTime;
|
|
508
491
|
queueIndex = this.getQueueIndex(this.resumeTime);
|
|
509
|
-
|
|
492
|
+
resumeTime = this.resumeTime - this.startTime;
|
|
510
493
|
this.isSeeking = false;
|
|
511
494
|
await schedulePlayback();
|
|
512
495
|
}
|
|
@@ -529,6 +512,7 @@ export class MidyGM1 {
|
|
|
529
512
|
return `${programNumber}:${noteNumber}:${velocity}`;
|
|
530
513
|
}
|
|
531
514
|
extractMidiData(midi) {
|
|
515
|
+
this.audioBufferCounter.clear();
|
|
532
516
|
const instruments = new Set();
|
|
533
517
|
const timeline = [];
|
|
534
518
|
const tmpChannels = new Array(this.channels.length);
|
|
@@ -597,9 +581,8 @@ export class MidyGM1 {
|
|
|
597
581
|
stopActiveNotes(channelNumber, velocity, force, scheduleTime) {
|
|
598
582
|
const channel = this.channels[channelNumber];
|
|
599
583
|
const promises = [];
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
const promise = this.scheduleNoteOff(channelNumber, note.noteNumber, velocity, scheduleTime, force, undefined);
|
|
584
|
+
this.processActiveNotes(channel, scheduleTime, (note) => {
|
|
585
|
+
const promise = this.scheduleNoteOff(channelNumber, note.noteNumber, velocity, scheduleTime, force);
|
|
603
586
|
this.notePromises.push(promise);
|
|
604
587
|
promises.push(promise);
|
|
605
588
|
});
|
|
@@ -613,7 +596,7 @@ export class MidyGM1 {
|
|
|
613
596
|
this.notePromises.push(promise);
|
|
614
597
|
promises.push(promise);
|
|
615
598
|
});
|
|
616
|
-
channel.scheduledNotes
|
|
599
|
+
channel.scheduledNotes = [];
|
|
617
600
|
return Promise.all(promises);
|
|
618
601
|
}
|
|
619
602
|
stopNotes(velocity, force, scheduleTime) {
|
|
@@ -634,9 +617,6 @@ export class MidyGM1 {
|
|
|
634
617
|
if (!this.isPlaying)
|
|
635
618
|
return;
|
|
636
619
|
this.isStopping = true;
|
|
637
|
-
for (let i = 0; i < this.channels.length; i++) {
|
|
638
|
-
this.resetAllStates(i);
|
|
639
|
-
}
|
|
640
620
|
}
|
|
641
621
|
pause() {
|
|
642
622
|
if (!this.isPlaying || this.isPaused)
|
|
@@ -671,37 +651,28 @@ export class MidyGM1 {
|
|
|
671
651
|
return this.resumeTime + now - this.startTime - this.startDelay;
|
|
672
652
|
}
|
|
673
653
|
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;
|
|
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
|
+
}
|
|
694
663
|
}
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
664
|
+
processActiveNotes(channel, scheduleTime, callback) {
|
|
665
|
+
const scheduledNotes = channel.scheduledNotes;
|
|
666
|
+
for (let i = 0; i < scheduledNotes.length; i++) {
|
|
667
|
+
const note = scheduledNotes[i];
|
|
698
668
|
if (!note)
|
|
699
|
-
|
|
669
|
+
continue;
|
|
670
|
+
if (note.ending)
|
|
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
|
}
|
|
@@ -872,7 +844,7 @@ export class MidyGM1 {
|
|
|
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,12 @@ export class MidyGM1 {
|
|
|
891
864
|
}
|
|
892
865
|
this.handleExclusiveClass(note, channelNumber, startTime);
|
|
893
866
|
const scheduledNotes = channel.scheduledNotes;
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
noteList.push(note);
|
|
897
|
-
}
|
|
898
|
-
else {
|
|
899
|
-
noteList = [note];
|
|
900
|
-
scheduledNotes.set(noteNumber, noteList);
|
|
901
|
-
}
|
|
867
|
+
note.index = scheduledNotes.length;
|
|
868
|
+
scheduledNotes.push(note);
|
|
902
869
|
}
|
|
903
870
|
noteOn(channelNumber, noteNumber, velocity, scheduleTime) {
|
|
904
871
|
scheduleTime ??= this.audioContext.currentTime;
|
|
905
|
-
return this.scheduleNoteOn(channelNumber, noteNumber, velocity, scheduleTime);
|
|
872
|
+
return this.scheduleNoteOn(channelNumber, noteNumber, velocity, scheduleTime, undefined);
|
|
906
873
|
}
|
|
907
874
|
disconnectNote(note) {
|
|
908
875
|
note.bufferSource.disconnect();
|
|
@@ -914,54 +881,49 @@ export class MidyGM1 {
|
|
|
914
881
|
note.modulationLFO.stop();
|
|
915
882
|
}
|
|
916
883
|
}
|
|
917
|
-
|
|
918
|
-
const
|
|
884
|
+
releaseNote(channel, note, endTime) {
|
|
885
|
+
const volRelease = endTime + note.voiceParams.volRelease;
|
|
886
|
+
const modRelease = endTime + note.voiceParams.modRelease;
|
|
887
|
+
const stopTime = Math.min(volRelease, modRelease);
|
|
888
|
+
note.filterNode.frequency
|
|
889
|
+
.cancelScheduledValues(endTime)
|
|
890
|
+
.linearRampToValueAtTime(0, modRelease);
|
|
919
891
|
note.volumeEnvelopeNode.gain
|
|
920
892
|
.cancelScheduledValues(endTime)
|
|
921
|
-
.linearRampToValueAtTime(0,
|
|
922
|
-
note.ending = true;
|
|
923
|
-
this.scheduleTask(() => {
|
|
924
|
-
note.bufferSource.loop = false;
|
|
925
|
-
}, stopTime);
|
|
893
|
+
.linearRampToValueAtTime(0, volRelease);
|
|
926
894
|
return new Promise((resolve) => {
|
|
927
|
-
|
|
928
|
-
|
|
895
|
+
this.scheduleTask(() => {
|
|
896
|
+
const bufferSource = note.bufferSource;
|
|
897
|
+
bufferSource.loop = false;
|
|
898
|
+
bufferSource.stop(stopTime);
|
|
929
899
|
this.disconnectNote(note);
|
|
900
|
+
channel.scheduledNotes[note.index] = undefined;
|
|
930
901
|
resolve();
|
|
931
|
-
};
|
|
932
|
-
note.bufferSource.stop(stopTime);
|
|
902
|
+
}, stopTime);
|
|
933
903
|
});
|
|
934
904
|
}
|
|
935
|
-
findNoteOffTarget(noteList) {
|
|
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
905
|
scheduleNoteOff(channelNumber, noteNumber, _velocity, endTime, force) {
|
|
946
906
|
const channel = this.channels[channelNumber];
|
|
947
907
|
if (!force && 0.5 <= channel.state.sustainPedal)
|
|
948
908
|
return;
|
|
949
|
-
|
|
909
|
+
const note = this.findNoteOffTarget(channel, noteNumber);
|
|
910
|
+
if (!note)
|
|
950
911
|
return;
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
.
|
|
963
|
-
|
|
964
|
-
|
|
912
|
+
note.ending = true;
|
|
913
|
+
this.releaseNote(channel, note, endTime);
|
|
914
|
+
}
|
|
915
|
+
findNoteOffTarget(channel, noteNumber) {
|
|
916
|
+
const scheduledNotes = channel.scheduledNotes;
|
|
917
|
+
for (let i = 0; i < scheduledNotes.length; i++) {
|
|
918
|
+
const note = scheduledNotes[i];
|
|
919
|
+
if (!note)
|
|
920
|
+
continue;
|
|
921
|
+
if (note.ending)
|
|
922
|
+
continue;
|
|
923
|
+
if (note.noteNumber !== noteNumber)
|
|
924
|
+
continue;
|
|
925
|
+
return note;
|
|
926
|
+
}
|
|
965
927
|
}
|
|
966
928
|
noteOff(channelNumber, noteNumber, velocity, scheduleTime) {
|
|
967
929
|
scheduleTime ??= this.audioContext.currentTime;
|
|
@@ -972,7 +934,7 @@ export class MidyGM1 {
|
|
|
972
934
|
const channel = this.channels[channelNumber];
|
|
973
935
|
const promises = [];
|
|
974
936
|
for (let i = 0; i < channel.sustainNotes.length; i++) {
|
|
975
|
-
const promise = this.
|
|
937
|
+
const promise = this.scheduleNoteOff(channelNumber, channel.sustainNotes[i].noteNumber, velocity, scheduleTime);
|
|
976
938
|
promises.push(promise);
|
|
977
939
|
}
|
|
978
940
|
channel.sustainNotes = [];
|
|
@@ -1128,20 +1090,19 @@ export class MidyGM1 {
|
|
|
1128
1090
|
});
|
|
1129
1091
|
}
|
|
1130
1092
|
createControlChangeHandlers() {
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
};
|
|
1093
|
+
const handlers = new Array(128);
|
|
1094
|
+
handlers[1] = this.setModulationDepth;
|
|
1095
|
+
handlers[6] = this.dataEntryMSB;
|
|
1096
|
+
handlers[7] = this.setVolume;
|
|
1097
|
+
handlers[10] = this.setPan;
|
|
1098
|
+
handlers[11] = this.setExpression;
|
|
1099
|
+
handlers[38] = this.dataEntryLSB;
|
|
1100
|
+
handlers[64] = this.setSustainPedal;
|
|
1101
|
+
handlers[100] = this.setRPNLSB;
|
|
1102
|
+
handlers[101] = this.setRPNMSB;
|
|
1103
|
+
handlers[120] = this.allSoundOff;
|
|
1104
|
+
handlers[121] = this.resetAllControllers;
|
|
1105
|
+
return handlers;
|
|
1145
1106
|
}
|
|
1146
1107
|
handleControlChange(channelNumber, controllerType, value, scheduleTime) {
|
|
1147
1108
|
const handler = this.controlChangeHandlers[controllerType];
|
|
@@ -1330,19 +1291,26 @@ export class MidyGM1 {
|
|
|
1330
1291
|
return this.stopActiveNotes(channelNumber, 0, true, scheduleTime);
|
|
1331
1292
|
}
|
|
1332
1293
|
resetAllStates(channelNumber) {
|
|
1294
|
+
const scheduleTime = this.audioContext.currentTime;
|
|
1333
1295
|
const channel = this.channels[channelNumber];
|
|
1334
1296
|
const state = channel.state;
|
|
1335
|
-
|
|
1336
|
-
|
|
1297
|
+
const entries = Object.entries(defaultControllerState);
|
|
1298
|
+
for (const [key, { type, defaultValue }] of entries) {
|
|
1299
|
+
if (128 <= type) {
|
|
1300
|
+
this.handleControlChange(channelNumber, type - 128, Math.ceil(defaultValue * 127), scheduleTime);
|
|
1301
|
+
}
|
|
1302
|
+
else {
|
|
1303
|
+
state[key] = defaultValue;
|
|
1304
|
+
}
|
|
1337
1305
|
}
|
|
1338
|
-
for (const
|
|
1339
|
-
channel[
|
|
1306
|
+
for (const key of Object.keys(this.constructor.channelSettings)) {
|
|
1307
|
+
channel[key] = this.constructor.channelSettings[key];
|
|
1340
1308
|
}
|
|
1341
1309
|
this.mode = "GM1";
|
|
1342
1310
|
}
|
|
1343
1311
|
// https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/rp15.pdf
|
|
1344
|
-
resetAllControllers(channelNumber) {
|
|
1345
|
-
const
|
|
1312
|
+
resetAllControllers(channelNumber, _value, scheduleTime) {
|
|
1313
|
+
const keys = [
|
|
1346
1314
|
"pitchWheel",
|
|
1347
1315
|
"expression",
|
|
1348
1316
|
"modulationDepth",
|
|
@@ -1350,10 +1318,17 @@ export class MidyGM1 {
|
|
|
1350
1318
|
];
|
|
1351
1319
|
const channel = this.channels[channelNumber];
|
|
1352
1320
|
const state = channel.state;
|
|
1353
|
-
for (let i = 0; i <
|
|
1354
|
-
const
|
|
1355
|
-
|
|
1321
|
+
for (let i = 0; i < keys.length; i++) {
|
|
1322
|
+
const key = keys[i];
|
|
1323
|
+
const { type, defaultValue } = defaultControllerState[key];
|
|
1324
|
+
if (128 <= type) {
|
|
1325
|
+
this.handleControlChange(channelNumber, type - 128, Math.ceil(defaultValue * 127), scheduleTime);
|
|
1326
|
+
}
|
|
1327
|
+
else {
|
|
1328
|
+
state[key] = defaultValue;
|
|
1329
|
+
}
|
|
1356
1330
|
}
|
|
1331
|
+
this.setPitchBend(channelNumber, 8192, scheduleTime);
|
|
1357
1332
|
const settingTypes = [
|
|
1358
1333
|
"rpnMSB",
|
|
1359
1334
|
"rpnLSB",
|