@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/script/midy.js
CHANGED
|
@@ -3,60 +3,20 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.Midy = 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
|
+
});
|
|
14
|
+
Object.defineProperty(this, "ending", {
|
|
15
|
+
enumerable: true,
|
|
16
|
+
configurable: true,
|
|
17
|
+
writable: true,
|
|
18
|
+
value: false
|
|
19
|
+
});
|
|
60
20
|
Object.defineProperty(this, "bufferSource", {
|
|
61
21
|
enumerable: true,
|
|
62
22
|
configurable: true,
|
|
@@ -141,11 +101,11 @@ class Note {
|
|
|
141
101
|
writable: true,
|
|
142
102
|
value: void 0
|
|
143
103
|
});
|
|
144
|
-
Object.defineProperty(this, "
|
|
104
|
+
Object.defineProperty(this, "portamentoNoteNumber", {
|
|
145
105
|
enumerable: true,
|
|
146
106
|
configurable: true,
|
|
147
107
|
writable: true,
|
|
148
|
-
value:
|
|
108
|
+
value: -1
|
|
149
109
|
});
|
|
150
110
|
Object.defineProperty(this, "pressure", {
|
|
151
111
|
enumerable: true,
|
|
@@ -207,7 +167,7 @@ const defaultControllerState = {
|
|
|
207
167
|
portamentoTime: { type: 128 + 5, defaultValue: 0 },
|
|
208
168
|
// dataMSB: { type: 128 + 6, defaultValue: 0, },
|
|
209
169
|
volume: { type: 128 + 7, defaultValue: 100 / 127 },
|
|
210
|
-
pan: { type: 128 + 10, defaultValue:
|
|
170
|
+
pan: { type: 128 + 10, defaultValue: 64 / 127 },
|
|
211
171
|
expression: { type: 128 + 11, defaultValue: 1 },
|
|
212
172
|
// bankLSB: { type: 128 + 32, defaultValue: 0, },
|
|
213
173
|
// dataLSB: { type: 128 + 38, defaultValue: 0, },
|
|
@@ -215,14 +175,14 @@ const defaultControllerState = {
|
|
|
215
175
|
portamento: { type: 128 + 65, defaultValue: 0 },
|
|
216
176
|
sostenutoPedal: { type: 128 + 66, defaultValue: 0 },
|
|
217
177
|
softPedal: { type: 128 + 67, defaultValue: 0 },
|
|
218
|
-
filterResonance: { type: 128 + 71, defaultValue:
|
|
219
|
-
releaseTime: { type: 128 + 72, defaultValue:
|
|
220
|
-
attackTime: { type: 128 + 73, defaultValue:
|
|
221
|
-
brightness: { type: 128 + 74, defaultValue:
|
|
222
|
-
decayTime: { type: 128 + 75, defaultValue:
|
|
223
|
-
vibratoRate: { type: 128 + 76, defaultValue:
|
|
224
|
-
vibratoDepth: { type: 128 + 77, defaultValue:
|
|
225
|
-
vibratoDelay: { type: 128 + 78, defaultValue:
|
|
178
|
+
filterResonance: { type: 128 + 71, defaultValue: 64 / 127 },
|
|
179
|
+
releaseTime: { type: 128 + 72, defaultValue: 64 / 127 },
|
|
180
|
+
attackTime: { type: 128 + 73, defaultValue: 64 / 127 },
|
|
181
|
+
brightness: { type: 128 + 74, defaultValue: 64 / 127 },
|
|
182
|
+
decayTime: { type: 128 + 75, defaultValue: 64 / 127 },
|
|
183
|
+
vibratoRate: { type: 128 + 76, defaultValue: 64 / 127 },
|
|
184
|
+
vibratoDepth: { type: 128 + 77, defaultValue: 64 / 127 },
|
|
185
|
+
vibratoDelay: { type: 128 + 78, defaultValue: 64 / 127 },
|
|
226
186
|
reverbSendLevel: { type: 128 + 91, defaultValue: 0 },
|
|
227
187
|
chorusSendLevel: { type: 128 + 93, defaultValue: 0 },
|
|
228
188
|
// dataIncrement: { type: 128 + 96, defaultValue: 0 },
|
|
@@ -497,7 +457,7 @@ class Midy {
|
|
|
497
457
|
initSoundFontTable() {
|
|
498
458
|
const table = new Array(128);
|
|
499
459
|
for (let i = 0; i < 128; i++) {
|
|
500
|
-
table[i] = new
|
|
460
|
+
table[i] = new Map();
|
|
501
461
|
}
|
|
502
462
|
return table;
|
|
503
463
|
}
|
|
@@ -513,17 +473,37 @@ class Midy {
|
|
|
513
473
|
}
|
|
514
474
|
}
|
|
515
475
|
}
|
|
516
|
-
async loadSoundFont(
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
476
|
+
async loadSoundFont(input) {
|
|
477
|
+
let uint8Array;
|
|
478
|
+
if (typeof input === "string") {
|
|
479
|
+
const response = await fetch(input);
|
|
480
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
481
|
+
uint8Array = new Uint8Array(arrayBuffer);
|
|
482
|
+
}
|
|
483
|
+
else if (input instanceof Uint8Array) {
|
|
484
|
+
uint8Array = input;
|
|
485
|
+
}
|
|
486
|
+
else {
|
|
487
|
+
throw new TypeError("input must be a URL string or Uint8Array");
|
|
488
|
+
}
|
|
489
|
+
const parsed = (0, soundfont_parser_1.parse)(uint8Array);
|
|
520
490
|
const soundFont = new soundfont_parser_1.SoundFont(parsed);
|
|
521
491
|
this.addSoundFont(soundFont);
|
|
522
492
|
}
|
|
523
|
-
async loadMIDI(
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
493
|
+
async loadMIDI(input) {
|
|
494
|
+
let uint8Array;
|
|
495
|
+
if (typeof input === "string") {
|
|
496
|
+
const response = await fetch(input);
|
|
497
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
498
|
+
uint8Array = new Uint8Array(arrayBuffer);
|
|
499
|
+
}
|
|
500
|
+
else if (input instanceof Uint8Array) {
|
|
501
|
+
uint8Array = input;
|
|
502
|
+
}
|
|
503
|
+
else {
|
|
504
|
+
throw new TypeError("input must be a URL string or Uint8Array");
|
|
505
|
+
}
|
|
506
|
+
const midi = (0, midi_file_1.parseMidi)(uint8Array);
|
|
527
507
|
this.ticksPerBeat = midi.header.ticksPerBeat;
|
|
528
508
|
const midiData = this.extractMidiData(midi);
|
|
529
509
|
this.instruments = midiData.instruments;
|
|
@@ -544,22 +524,29 @@ class Midy {
|
|
|
544
524
|
merger,
|
|
545
525
|
};
|
|
546
526
|
}
|
|
527
|
+
resetChannelTable(channel) {
|
|
528
|
+
this.resetControlTable(channel.controlTable);
|
|
529
|
+
channel.scaleOctaveTuningTable.fill(0); // [-100, 100] cent
|
|
530
|
+
channel.channelPressureTable.set([64, 64, 64, 0, 0, 0]);
|
|
531
|
+
channel.polyphonicKeyPressureTable.set([64, 64, 64, 0, 0, 0]);
|
|
532
|
+
channel.keyBasedInstrumentControlTable.fill(-1);
|
|
533
|
+
}
|
|
547
534
|
createChannels(audioContext) {
|
|
548
535
|
const channels = Array.from({ length: this.numChannels }, () => {
|
|
549
536
|
return {
|
|
550
537
|
currentBufferSource: null,
|
|
551
538
|
isDrum: false,
|
|
552
|
-
...this.constructor.channelSettings,
|
|
553
539
|
state: new ControllerState(),
|
|
554
|
-
|
|
540
|
+
...this.constructor.channelSettings,
|
|
555
541
|
...this.setChannelAudioNodes(audioContext),
|
|
556
|
-
scheduledNotes:
|
|
542
|
+
scheduledNotes: [],
|
|
557
543
|
sustainNotes: [],
|
|
558
|
-
sostenutoNotes:
|
|
544
|
+
sostenutoNotes: [],
|
|
545
|
+
controlTable: this.initControlTable(),
|
|
559
546
|
scaleOctaveTuningTable: new Float32Array(12), // [-100, 100] cent
|
|
560
547
|
channelPressureTable: new Uint8Array([64, 64, 64, 0, 0, 0]),
|
|
561
548
|
polyphonicKeyPressureTable: new Uint8Array([64, 64, 64, 0, 0, 0]),
|
|
562
|
-
keyBasedInstrumentControlTable: new Int8Array(128 * 128)
|
|
549
|
+
keyBasedInstrumentControlTable: new Int8Array(128 * 128).fill(-1),
|
|
563
550
|
};
|
|
564
551
|
});
|
|
565
552
|
return channels;
|
|
@@ -593,56 +580,39 @@ class Midy {
|
|
|
593
580
|
return audioBuffer;
|
|
594
581
|
}
|
|
595
582
|
}
|
|
596
|
-
|
|
583
|
+
isLoopDrum(channel, noteNumber) {
|
|
584
|
+
const programNumber = channel.programNumber;
|
|
585
|
+
return ((programNumber === 48 && noteNumber === 88) ||
|
|
586
|
+
(programNumber === 56 && 47 <= noteNumber && noteNumber <= 84));
|
|
587
|
+
}
|
|
588
|
+
createBufferSource(channel, noteNumber, voiceParams, audioBuffer) {
|
|
597
589
|
const bufferSource = new AudioBufferSourceNode(this.audioContext);
|
|
598
590
|
bufferSource.buffer = audioBuffer;
|
|
599
591
|
bufferSource.loop = voiceParams.sampleModes % 2 !== 0;
|
|
592
|
+
if (channel.isDrum) {
|
|
593
|
+
bufferSource.loop = this.isLoopDrum(channel, noteNumber);
|
|
594
|
+
}
|
|
600
595
|
if (bufferSource.loop) {
|
|
601
596
|
bufferSource.loopStart = voiceParams.loopStart / voiceParams.sampleRate;
|
|
602
597
|
bufferSource.loopEnd = voiceParams.loopEnd / voiceParams.sampleRate;
|
|
603
598
|
}
|
|
604
599
|
return bufferSource;
|
|
605
600
|
}
|
|
606
|
-
|
|
607
|
-
const endEvent = this.timeline[queueIndex];
|
|
608
|
-
if (!this.channels[endEvent.channel].portamento)
|
|
609
|
-
return;
|
|
610
|
-
const endTime = endEvent.startTime;
|
|
611
|
-
let target;
|
|
612
|
-
while (++queueIndex < this.timeline.length) {
|
|
613
|
-
const event = this.timeline[queueIndex];
|
|
614
|
-
if (endTime !== event.startTime)
|
|
615
|
-
break;
|
|
616
|
-
if (event.type !== "noteOn")
|
|
617
|
-
continue;
|
|
618
|
-
if (!target || event.noteNumber < target.noteNumber) {
|
|
619
|
-
target = event;
|
|
620
|
-
}
|
|
621
|
-
}
|
|
622
|
-
return target;
|
|
623
|
-
}
|
|
624
|
-
async scheduleTimelineEvents(t, offset, queueIndex) {
|
|
601
|
+
async scheduleTimelineEvents(t, resumeTime, queueIndex) {
|
|
625
602
|
while (queueIndex < this.timeline.length) {
|
|
626
603
|
const event = this.timeline[queueIndex];
|
|
627
604
|
if (event.startTime > t + this.lookAhead)
|
|
628
605
|
break;
|
|
629
|
-
const
|
|
606
|
+
const delay = this.startDelay - resumeTime;
|
|
607
|
+
const startTime = event.startTime + delay;
|
|
630
608
|
switch (event.type) {
|
|
631
609
|
case "noteOn":
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
break;
|
|
635
|
-
}
|
|
636
|
-
/* falls through */
|
|
610
|
+
await this.scheduleNoteOn(event.channel, event.noteNumber, event.velocity, startTime);
|
|
611
|
+
break;
|
|
637
612
|
case "noteOff": {
|
|
638
|
-
const
|
|
639
|
-
if (
|
|
640
|
-
portamentoTarget.portamento = true;
|
|
641
|
-
const notePromise = this.scheduleNoteOff(event.channel, event.noteNumber, event.velocity, startTime, false, // force
|
|
642
|
-
portamentoTarget?.noteNumber);
|
|
643
|
-
if (notePromise) {
|
|
613
|
+
const notePromise = this.scheduleNoteOff(event.channel, event.noteNumber, event.velocity, startTime, false);
|
|
614
|
+
if (notePromise)
|
|
644
615
|
this.notePromises.push(notePromise);
|
|
645
|
-
}
|
|
646
616
|
break;
|
|
647
617
|
}
|
|
648
618
|
case "noteAftertouch":
|
|
@@ -681,7 +651,7 @@ class Midy {
|
|
|
681
651
|
this.isPaused = false;
|
|
682
652
|
this.startTime = this.audioContext.currentTime;
|
|
683
653
|
let queueIndex = this.getQueueIndex(this.resumeTime);
|
|
684
|
-
let
|
|
654
|
+
let resumeTime = this.resumeTime - this.startTime;
|
|
685
655
|
this.notePromises = [];
|
|
686
656
|
const schedulePlayback = async () => {
|
|
687
657
|
if (queueIndex >= this.timeline.length) {
|
|
@@ -690,18 +660,21 @@ class Midy {
|
|
|
690
660
|
this.exclusiveClassNotes.fill(undefined);
|
|
691
661
|
this.drumExclusiveClassNotes.fill(undefined);
|
|
692
662
|
this.audioBufferCache.clear();
|
|
663
|
+
for (let i = 0; i < this.channels.length; i++) {
|
|
664
|
+
this.resetAllStates(i);
|
|
665
|
+
}
|
|
693
666
|
resolve();
|
|
694
667
|
return;
|
|
695
668
|
}
|
|
696
669
|
const now = this.audioContext.currentTime;
|
|
697
|
-
const t = now +
|
|
698
|
-
queueIndex = await this.scheduleTimelineEvents(t,
|
|
670
|
+
const t = now + resumeTime;
|
|
671
|
+
queueIndex = await this.scheduleTimelineEvents(t, resumeTime, queueIndex);
|
|
699
672
|
if (this.isPausing) {
|
|
700
673
|
await this.stopNotes(0, true, now);
|
|
701
674
|
this.notePromises = [];
|
|
702
|
-
resolve();
|
|
703
675
|
this.isPausing = false;
|
|
704
676
|
this.isPaused = true;
|
|
677
|
+
resolve();
|
|
705
678
|
return;
|
|
706
679
|
}
|
|
707
680
|
else if (this.isStopping) {
|
|
@@ -710,9 +683,12 @@ class Midy {
|
|
|
710
683
|
this.exclusiveClassNotes.fill(undefined);
|
|
711
684
|
this.drumExclusiveClassNotes.fill(undefined);
|
|
712
685
|
this.audioBufferCache.clear();
|
|
713
|
-
|
|
686
|
+
for (let i = 0; i < this.channels.length; i++) {
|
|
687
|
+
this.resetAllStates(i);
|
|
688
|
+
}
|
|
714
689
|
this.isStopping = false;
|
|
715
690
|
this.isPaused = false;
|
|
691
|
+
resolve();
|
|
716
692
|
return;
|
|
717
693
|
}
|
|
718
694
|
else if (this.isSeeking) {
|
|
@@ -721,7 +697,7 @@ class Midy {
|
|
|
721
697
|
this.drumExclusiveClassNotes.fill(undefined);
|
|
722
698
|
this.startTime = this.audioContext.currentTime;
|
|
723
699
|
queueIndex = this.getQueueIndex(this.resumeTime);
|
|
724
|
-
|
|
700
|
+
resumeTime = this.resumeTime - this.startTime;
|
|
725
701
|
this.isSeeking = false;
|
|
726
702
|
await schedulePlayback();
|
|
727
703
|
}
|
|
@@ -744,6 +720,7 @@ class Midy {
|
|
|
744
720
|
return `${programNumber}:${noteNumber}:${velocity}`;
|
|
745
721
|
}
|
|
746
722
|
extractMidiData(midi) {
|
|
723
|
+
this.audioBufferCounter.clear();
|
|
747
724
|
const instruments = new Set();
|
|
748
725
|
const timeline = [];
|
|
749
726
|
const tmpChannels = new Array(this.channels.length);
|
|
@@ -848,9 +825,8 @@ class Midy {
|
|
|
848
825
|
stopActiveNotes(channelNumber, velocity, force, scheduleTime) {
|
|
849
826
|
const channel = this.channels[channelNumber];
|
|
850
827
|
const promises = [];
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
const promise = this.scheduleNoteOff(channelNumber, note.noteNumber, velocity, scheduleTime, force, undefined);
|
|
828
|
+
this.processActiveNotes(channel, scheduleTime, (note) => {
|
|
829
|
+
const promise = this.scheduleNoteOff(channelNumber, note.noteNumber, velocity, scheduleTime, force);
|
|
854
830
|
this.notePromises.push(promise);
|
|
855
831
|
promises.push(promise);
|
|
856
832
|
});
|
|
@@ -860,11 +836,11 @@ class Midy {
|
|
|
860
836
|
const channel = this.channels[channelNumber];
|
|
861
837
|
const promises = [];
|
|
862
838
|
this.processScheduledNotes(channel, (note) => {
|
|
863
|
-
const promise = this.scheduleNoteOff(channelNumber, note.noteNumber, velocity, scheduleTime, force
|
|
839
|
+
const promise = this.scheduleNoteOff(channelNumber, note.noteNumber, velocity, scheduleTime, force);
|
|
864
840
|
this.notePromises.push(promise);
|
|
865
841
|
promises.push(promise);
|
|
866
842
|
});
|
|
867
|
-
channel.scheduledNotes
|
|
843
|
+
channel.scheduledNotes = [];
|
|
868
844
|
return Promise.all(promises);
|
|
869
845
|
}
|
|
870
846
|
stopNotes(velocity, force, scheduleTime) {
|
|
@@ -885,9 +861,6 @@ class Midy {
|
|
|
885
861
|
if (!this.isPlaying)
|
|
886
862
|
return;
|
|
887
863
|
this.isStopping = true;
|
|
888
|
-
for (let i = 0; i < this.channels.length; i++) {
|
|
889
|
-
this.resetAllStates(i);
|
|
890
|
-
}
|
|
891
864
|
}
|
|
892
865
|
pause() {
|
|
893
866
|
if (!this.isPlaying || this.isPaused)
|
|
@@ -922,37 +895,28 @@ class Midy {
|
|
|
922
895
|
return this.resumeTime + now - this.startTime - this.startDelay;
|
|
923
896
|
}
|
|
924
897
|
processScheduledNotes(channel, callback) {
|
|
925
|
-
channel.scheduledNotes
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
});
|
|
935
|
-
}
|
|
936
|
-
getActiveNotes(channel, scheduleTime) {
|
|
937
|
-
const activeNotes = new SparseMap(128);
|
|
938
|
-
channel.scheduledNotes.forEach((noteList) => {
|
|
939
|
-
const activeNote = this.getActiveNote(noteList, scheduleTime);
|
|
940
|
-
if (activeNote) {
|
|
941
|
-
activeNotes.set(activeNote.noteNumber, activeNote);
|
|
942
|
-
}
|
|
943
|
-
});
|
|
944
|
-
return activeNotes;
|
|
898
|
+
const scheduledNotes = channel.scheduledNotes;
|
|
899
|
+
for (let i = 0; i < scheduledNotes.length; i++) {
|
|
900
|
+
const note = scheduledNotes[i];
|
|
901
|
+
if (!note)
|
|
902
|
+
continue;
|
|
903
|
+
if (note.ending)
|
|
904
|
+
continue;
|
|
905
|
+
callback(note);
|
|
906
|
+
}
|
|
945
907
|
}
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
908
|
+
processActiveNotes(channel, scheduleTime, callback) {
|
|
909
|
+
const scheduledNotes = channel.scheduledNotes;
|
|
910
|
+
for (let i = 0; i < scheduledNotes.length; i++) {
|
|
911
|
+
const note = scheduledNotes[i];
|
|
949
912
|
if (!note)
|
|
950
|
-
|
|
913
|
+
continue;
|
|
914
|
+
if (note.ending)
|
|
915
|
+
continue;
|
|
951
916
|
if (scheduleTime < note.startTime)
|
|
952
917
|
continue;
|
|
953
|
-
|
|
918
|
+
callback(note);
|
|
954
919
|
}
|
|
955
|
-
return noteList[0];
|
|
956
920
|
}
|
|
957
921
|
createConvolutionReverbImpulse(audioContext, decay, preDecay) {
|
|
958
922
|
const sampleRate = audioContext.sampleRate;
|
|
@@ -1119,24 +1083,94 @@ class Midy {
|
|
|
1119
1083
|
const noteDetune = this.calcNoteDetune(channel, note);
|
|
1120
1084
|
const pitchControl = this.getPitchControl(channel, note);
|
|
1121
1085
|
const detune = channel.detune + noteDetune + pitchControl;
|
|
1122
|
-
note.
|
|
1123
|
-
.
|
|
1124
|
-
.
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1086
|
+
if (0.5 <= channel.state.portamento && 0 <= note.portamentoNoteNumber) {
|
|
1087
|
+
const startTime = note.startTime;
|
|
1088
|
+
const deltaCent = (note.noteNumber - note.portamentoNoteNumber) * 100;
|
|
1089
|
+
const portamentoTime = startTime + this.getPortamentoTime(channel, note);
|
|
1090
|
+
note.bufferSource.detune
|
|
1091
|
+
.cancelScheduledValues(scheduleTime)
|
|
1092
|
+
.setValueAtTime(detune - deltaCent, scheduleTime)
|
|
1093
|
+
.linearRampToValueAtTime(detune, portamentoTime);
|
|
1094
|
+
}
|
|
1095
|
+
else {
|
|
1096
|
+
note.bufferSource.detune
|
|
1097
|
+
.cancelScheduledValues(scheduleTime)
|
|
1098
|
+
.setValueAtTime(detune, scheduleTime);
|
|
1099
|
+
}
|
|
1100
|
+
}
|
|
1101
|
+
getPortamentoTime(channel, note) {
|
|
1102
|
+
const deltaSemitone = Math.abs(note.noteNumber - note.portamentoNoteNumber);
|
|
1103
|
+
const value = Math.ceil(channel.state.portamentoTime * 127);
|
|
1104
|
+
return deltaSemitone / this.getPitchIncrementSpeed(value) / 10;
|
|
1105
|
+
}
|
|
1106
|
+
getPitchIncrementSpeed(value) {
|
|
1107
|
+
const points = [
|
|
1108
|
+
[0, 1000],
|
|
1109
|
+
[6, 100],
|
|
1110
|
+
[16, 20],
|
|
1111
|
+
[32, 10],
|
|
1112
|
+
[48, 5],
|
|
1113
|
+
[64, 2.5],
|
|
1114
|
+
[80, 1],
|
|
1115
|
+
[96, 0.4],
|
|
1116
|
+
[112, 0.15],
|
|
1117
|
+
[127, 0.01],
|
|
1118
|
+
];
|
|
1119
|
+
const logPoints = new Array(points.length);
|
|
1120
|
+
for (let i = 0; i < points.length; i++) {
|
|
1121
|
+
const [x, y] = points[i];
|
|
1122
|
+
if (value === x)
|
|
1123
|
+
return y;
|
|
1124
|
+
logPoints[i] = [x, Math.log(y)];
|
|
1125
|
+
}
|
|
1126
|
+
let startIndex = 0;
|
|
1127
|
+
for (let i = 1; i < logPoints.length; i++) {
|
|
1128
|
+
if (value <= logPoints[i][0]) {
|
|
1129
|
+
startIndex = i - 1;
|
|
1130
|
+
break;
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
1133
|
+
const [x0, y0] = logPoints[startIndex];
|
|
1134
|
+
const [x1, y1] = logPoints[startIndex + 1];
|
|
1135
|
+
const h = x1 - x0;
|
|
1136
|
+
const t = (value - x0) / h;
|
|
1137
|
+
let m0, m1;
|
|
1138
|
+
if (startIndex === 0) {
|
|
1139
|
+
m0 = (y1 - y0) / h;
|
|
1140
|
+
}
|
|
1141
|
+
else {
|
|
1142
|
+
const [xPrev, yPrev] = logPoints[startIndex - 1];
|
|
1143
|
+
m0 = (y1 - yPrev) / (x1 - xPrev);
|
|
1144
|
+
}
|
|
1145
|
+
if (startIndex === logPoints.length - 2) {
|
|
1146
|
+
m1 = (y1 - y0) / h;
|
|
1147
|
+
}
|
|
1148
|
+
else {
|
|
1149
|
+
const [xNext, yNext] = logPoints[startIndex + 2];
|
|
1150
|
+
m1 = (yNext - y0) / (xNext - x0);
|
|
1151
|
+
}
|
|
1152
|
+
// Cubic Hermite Spline
|
|
1153
|
+
const t2 = t * t;
|
|
1154
|
+
const t3 = t2 * t;
|
|
1155
|
+
const h00 = 2 * t3 - 3 * t2 + 1;
|
|
1156
|
+
const h10 = t3 - 2 * t2 + t;
|
|
1157
|
+
const h01 = -2 * t3 + 3 * t2;
|
|
1158
|
+
const h11 = t3 - t2;
|
|
1159
|
+
const y = h00 * y0 + h01 * y1 + h * (h10 * m0 + h11 * m1);
|
|
1160
|
+
return Math.exp(y);
|
|
1161
|
+
}
|
|
1162
|
+
setPortamentoVolumeEnvelope(channel, note, scheduleTime) {
|
|
1163
|
+
const state = channel.state;
|
|
1131
1164
|
const { voiceParams, startTime } = note;
|
|
1132
|
-
const attackVolume = this.cbToRatio(-voiceParams.initialAttenuation)
|
|
1165
|
+
const attackVolume = this.cbToRatio(-voiceParams.initialAttenuation) *
|
|
1166
|
+
(1 + this.getAmplitudeControl(channel, note));
|
|
1133
1167
|
const sustainVolume = attackVolume * (1 - voiceParams.volSustain);
|
|
1134
1168
|
const volDelay = startTime + voiceParams.volDelay;
|
|
1135
|
-
const
|
|
1169
|
+
const volAttack = volDelay + voiceParams.volAttack * state.attackTime * 2;
|
|
1170
|
+
const volHold = volAttack + voiceParams.volHold;
|
|
1136
1171
|
note.volumeEnvelopeNode.gain
|
|
1137
1172
|
.cancelScheduledValues(scheduleTime)
|
|
1138
|
-
.setValueAtTime(
|
|
1139
|
-
.linearRampToValueAtTime(sustainVolume, portamentoTime);
|
|
1173
|
+
.setValueAtTime(sustainVolume, volHold);
|
|
1140
1174
|
}
|
|
1141
1175
|
setVolumeEnvelope(channel, note, scheduleTime) {
|
|
1142
1176
|
const state = channel.state;
|
|
@@ -1156,6 +1190,12 @@ class Midy {
|
|
|
1156
1190
|
.setValueAtTime(attackVolume, volHold)
|
|
1157
1191
|
.linearRampToValueAtTime(sustainVolume, volDecay);
|
|
1158
1192
|
}
|
|
1193
|
+
setPortamentoPitchEnvelope(note, scheduleTime) {
|
|
1194
|
+
const baseRate = note.voiceParams.playbackRate;
|
|
1195
|
+
note.bufferSource.playbackRate
|
|
1196
|
+
.cancelScheduledValues(scheduleTime)
|
|
1197
|
+
.setValueAtTime(baseRate, scheduleTime);
|
|
1198
|
+
}
|
|
1159
1199
|
setPitchEnvelope(note, scheduleTime) {
|
|
1160
1200
|
const { voiceParams } = note;
|
|
1161
1201
|
const baseRate = voiceParams.playbackRate;
|
|
@@ -1183,20 +1223,20 @@ class Midy {
|
|
|
1183
1223
|
const maxFrequency = 20000; // max Hz of initialFilterFc
|
|
1184
1224
|
return Math.max(minFrequency, Math.min(frequency, maxFrequency));
|
|
1185
1225
|
}
|
|
1186
|
-
|
|
1226
|
+
setPortamentoFilterEnvelope(channel, note, scheduleTime) {
|
|
1187
1227
|
const state = channel.state;
|
|
1188
|
-
const { voiceParams,
|
|
1189
|
-
const softPedalFactor =
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1228
|
+
const { voiceParams, startTime } = note;
|
|
1229
|
+
const softPedalFactor = this.getSoftPedalFactor(channel, note);
|
|
1230
|
+
const baseCent = voiceParams.initialFilterFc +
|
|
1231
|
+
this.getFilterCutoffControl(channel, note);
|
|
1232
|
+
const baseFreq = this.centToHz(baseCent) * softPedalFactor *
|
|
1193
1233
|
state.brightness * 2;
|
|
1194
1234
|
const peekFreq = this.centToHz(voiceParams.initialFilterFc + voiceParams.modEnvToFilterFc) * softPedalFactor * state.brightness * 2;
|
|
1195
1235
|
const sustainFreq = baseFreq +
|
|
1196
1236
|
(peekFreq - baseFreq) * (1 - voiceParams.modSustain);
|
|
1197
1237
|
const adjustedBaseFreq = this.clampCutoffFrequency(baseFreq);
|
|
1198
1238
|
const adjustedSustainFreq = this.clampCutoffFrequency(sustainFreq);
|
|
1199
|
-
const portamentoTime = startTime + this.getPortamentoTime(channel);
|
|
1239
|
+
const portamentoTime = startTime + this.getPortamentoTime(channel, note);
|
|
1200
1240
|
const modDelay = startTime + voiceParams.modDelay;
|
|
1201
1241
|
note.filterNode.frequency
|
|
1202
1242
|
.cancelScheduledValues(scheduleTime)
|
|
@@ -1206,9 +1246,8 @@ class Midy {
|
|
|
1206
1246
|
}
|
|
1207
1247
|
setFilterEnvelope(channel, note, scheduleTime) {
|
|
1208
1248
|
const state = channel.state;
|
|
1209
|
-
const { voiceParams,
|
|
1210
|
-
const softPedalFactor =
|
|
1211
|
-
(0.1 + (noteNumber / 127) * 0.2) * state.softPedal;
|
|
1249
|
+
const { voiceParams, startTime } = note;
|
|
1250
|
+
const softPedalFactor = this.getSoftPedalFactor(channel, note);
|
|
1212
1251
|
const baseCent = voiceParams.initialFilterFc +
|
|
1213
1252
|
this.getFilterCutoffControl(channel, note);
|
|
1214
1253
|
const baseFreq = this.centToHz(baseCent) * softPedalFactor *
|
|
@@ -1282,14 +1321,14 @@ class Midy {
|
|
|
1282
1321
|
return audioBuffer;
|
|
1283
1322
|
}
|
|
1284
1323
|
}
|
|
1285
|
-
async createNote(channel, voice, noteNumber, velocity, startTime,
|
|
1324
|
+
async createNote(channel, voice, noteNumber, velocity, startTime, isSF3) {
|
|
1286
1325
|
const now = this.audioContext.currentTime;
|
|
1287
1326
|
const state = channel.state;
|
|
1288
1327
|
const controllerState = this.getControllerState(channel, noteNumber, velocity);
|
|
1289
1328
|
const voiceParams = voice.getAllParams(controllerState);
|
|
1290
1329
|
const note = new Note(noteNumber, velocity, startTime, voice, voiceParams);
|
|
1291
1330
|
const audioBuffer = await this.getAudioBuffer(channel.programNumber, noteNumber, velocity, voiceParams, isSF3);
|
|
1292
|
-
note.bufferSource = this.createBufferSource(voiceParams, audioBuffer);
|
|
1331
|
+
note.bufferSource = this.createBufferSource(channel, noteNumber, voiceParams, audioBuffer);
|
|
1293
1332
|
note.volumeNode = new GainNode(this.audioContext);
|
|
1294
1333
|
note.gainL = new GainNode(this.audioContext);
|
|
1295
1334
|
note.gainR = new GainNode(this.audioContext);
|
|
@@ -1298,20 +1337,24 @@ class Midy {
|
|
|
1298
1337
|
type: "lowpass",
|
|
1299
1338
|
Q: voiceParams.initialFilterQ / 5 * state.filterResonance, // dB
|
|
1300
1339
|
});
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1340
|
+
const prevNote = channel.scheduledNotes.at(-1);
|
|
1341
|
+
if (prevNote && prevNote.noteNumber !== noteNumber) {
|
|
1342
|
+
note.portamentoNoteNumber = prevNote.noteNumber;
|
|
1343
|
+
}
|
|
1344
|
+
if (0.5 <= channel.state.portamento && 0 <= note.portamentoNoteNumber) {
|
|
1345
|
+
this.setPortamentoVolumeEnvelope(channel, note, now);
|
|
1346
|
+
this.setPortamentoFilterEnvelope(channel, note, now);
|
|
1347
|
+
this.setPortamentoPitchEnvelope(note, now);
|
|
1305
1348
|
}
|
|
1306
1349
|
else {
|
|
1307
|
-
note.portamento = false;
|
|
1308
1350
|
this.setVolumeEnvelope(channel, note, now);
|
|
1309
1351
|
this.setFilterEnvelope(channel, note, now);
|
|
1352
|
+
this.setPitchEnvelope(note, now);
|
|
1310
1353
|
}
|
|
1354
|
+
this.updateDetune(channel, note, now);
|
|
1311
1355
|
if (0 < state.vibratoDepth) {
|
|
1312
1356
|
this.startVibrato(channel, note, now);
|
|
1313
1357
|
}
|
|
1314
|
-
this.setPitchEnvelope(note, now);
|
|
1315
1358
|
if (0 < state.modulationDepth) {
|
|
1316
1359
|
this.startModulation(channel, note, now);
|
|
1317
1360
|
}
|
|
@@ -1324,10 +1367,10 @@ class Midy {
|
|
|
1324
1367
|
note.volumeEnvelopeNode.connect(note.volumeNode);
|
|
1325
1368
|
note.volumeNode.connect(note.gainL);
|
|
1326
1369
|
note.volumeNode.connect(note.gainR);
|
|
1327
|
-
if (0 <
|
|
1370
|
+
if (0 < state.chorusSendLevel) {
|
|
1328
1371
|
this.setChorusEffectsSend(channel, note, 0, now);
|
|
1329
1372
|
}
|
|
1330
|
-
if (0 <
|
|
1373
|
+
if (0 < state.reverbSendLevel) {
|
|
1331
1374
|
this.setReverbEffectsSend(channel, note, 0, now);
|
|
1332
1375
|
}
|
|
1333
1376
|
note.bufferSource.start(startTime);
|
|
@@ -1358,8 +1401,7 @@ class Midy {
|
|
|
1358
1401
|
const [prevNote, prevChannelNumber] = prev;
|
|
1359
1402
|
if (prevNote && !prevNote.ending) {
|
|
1360
1403
|
this.scheduleNoteOff(prevChannelNumber, prevNote.noteNumber, 0, // velocity,
|
|
1361
|
-
startTime, true
|
|
1362
|
-
undefined);
|
|
1404
|
+
startTime, true);
|
|
1363
1405
|
}
|
|
1364
1406
|
}
|
|
1365
1407
|
this.exclusiveClassNotes[exclusiveClass] = [note, channelNumber];
|
|
@@ -1379,19 +1421,11 @@ class Midy {
|
|
|
1379
1421
|
const prevNote = this.drumExclusiveClassNotes[index];
|
|
1380
1422
|
if (prevNote && !prevNote.ending) {
|
|
1381
1423
|
this.scheduleNoteOff(channelNumber, prevNote.noteNumber, 0, // velocity,
|
|
1382
|
-
startTime, true
|
|
1383
|
-
undefined);
|
|
1424
|
+
startTime, true);
|
|
1384
1425
|
}
|
|
1385
1426
|
this.drumExclusiveClassNotes[index] = note;
|
|
1386
1427
|
}
|
|
1387
|
-
|
|
1388
|
-
if (!channel.isDrum)
|
|
1389
|
-
return false;
|
|
1390
|
-
const programNumber = channel.programNumber;
|
|
1391
|
-
return !((programNumber === 48 && noteNumber === 88) ||
|
|
1392
|
-
(programNumber === 56 && 47 <= noteNumber && noteNumber <= 84));
|
|
1393
|
-
}
|
|
1394
|
-
async scheduleNoteOn(channelNumber, noteNumber, velocity, startTime, portamento) {
|
|
1428
|
+
async scheduleNoteOn(channelNumber, noteNumber, velocity, startTime, noteOffEvent) {
|
|
1395
1429
|
const channel = this.channels[channelNumber];
|
|
1396
1430
|
const bankNumber = this.calcBank(channel, channelNumber);
|
|
1397
1431
|
const soundFontIndex = this.soundFontTable[channel.programNumber].get(bankNumber);
|
|
@@ -1402,7 +1436,8 @@ class Midy {
|
|
|
1402
1436
|
if (!voice)
|
|
1403
1437
|
return;
|
|
1404
1438
|
const isSF3 = soundFont.parsed.info.version.major === 3;
|
|
1405
|
-
const note = await this.createNote(channel, voice, noteNumber, velocity, startTime,
|
|
1439
|
+
const note = await this.createNote(channel, voice, noteNumber, velocity, startTime, isSF3);
|
|
1440
|
+
note.noteOffEvent = noteOffEvent;
|
|
1406
1441
|
note.gainL.connect(channel.gainL);
|
|
1407
1442
|
note.gainR.connect(channel.gainR);
|
|
1408
1443
|
if (0.5 <= channel.state.sustainPedal) {
|
|
@@ -1411,31 +1446,12 @@ class Midy {
|
|
|
1411
1446
|
this.handleExclusiveClass(note, channelNumber, startTime);
|
|
1412
1447
|
this.handleDrumExclusiveClass(note, channelNumber, startTime);
|
|
1413
1448
|
const scheduledNotes = channel.scheduledNotes;
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
noteList.push(note);
|
|
1417
|
-
}
|
|
1418
|
-
else {
|
|
1419
|
-
noteList = [note];
|
|
1420
|
-
scheduledNotes.set(noteNumber, noteList);
|
|
1421
|
-
}
|
|
1422
|
-
if (this.isDrumNoteOffException(channel, noteNumber)) {
|
|
1423
|
-
const stopTime = startTime + note.bufferSource.buffer.duration;
|
|
1424
|
-
const index = noteList.length - 1;
|
|
1425
|
-
const promise = new Promise((resolve) => {
|
|
1426
|
-
note.bufferSource.onended = () => {
|
|
1427
|
-
noteList[index] = undefined;
|
|
1428
|
-
this.disconnectNote(note);
|
|
1429
|
-
resolve();
|
|
1430
|
-
};
|
|
1431
|
-
note.bufferSource.stop(stopTime);
|
|
1432
|
-
});
|
|
1433
|
-
this.notePromises.push(promise);
|
|
1434
|
-
}
|
|
1449
|
+
note.index = scheduledNotes.length;
|
|
1450
|
+
scheduledNotes.push(note);
|
|
1435
1451
|
}
|
|
1436
1452
|
noteOn(channelNumber, noteNumber, velocity, scheduleTime) {
|
|
1437
1453
|
scheduleTime ??= this.audioContext.currentTime;
|
|
1438
|
-
return this.scheduleNoteOn(channelNumber, noteNumber, velocity, scheduleTime,
|
|
1454
|
+
return this.scheduleNoteOn(channelNumber, noteNumber, velocity, scheduleTime, undefined);
|
|
1439
1455
|
}
|
|
1440
1456
|
disconnectNote(note) {
|
|
1441
1457
|
note.bufferSource.disconnect();
|
|
@@ -1460,84 +1476,72 @@ class Midy {
|
|
|
1460
1476
|
note.chorusEffectsSend.disconnect();
|
|
1461
1477
|
}
|
|
1462
1478
|
}
|
|
1463
|
-
|
|
1464
|
-
const
|
|
1479
|
+
releaseNote(channel, note, endTime) {
|
|
1480
|
+
const volRelease = endTime +
|
|
1481
|
+
note.voiceParams.volRelease * channel.state.releaseTime * 2;
|
|
1482
|
+
const modRelease = endTime + note.voiceParams.modRelease;
|
|
1483
|
+
const stopTime = Math.min(volRelease, modRelease);
|
|
1484
|
+
note.filterNode.frequency
|
|
1485
|
+
.cancelScheduledValues(endTime)
|
|
1486
|
+
.linearRampToValueAtTime(0, modRelease);
|
|
1465
1487
|
note.volumeEnvelopeNode.gain
|
|
1466
1488
|
.cancelScheduledValues(endTime)
|
|
1467
|
-
.linearRampToValueAtTime(0,
|
|
1468
|
-
note.ending = true;
|
|
1469
|
-
this.scheduleTask(() => {
|
|
1470
|
-
note.bufferSource.loop = false;
|
|
1471
|
-
}, stopTime);
|
|
1489
|
+
.linearRampToValueAtTime(0, volRelease);
|
|
1472
1490
|
return new Promise((resolve) => {
|
|
1473
|
-
|
|
1474
|
-
|
|
1491
|
+
this.scheduleTask(() => {
|
|
1492
|
+
const bufferSource = note.bufferSource;
|
|
1493
|
+
bufferSource.loop = false;
|
|
1494
|
+
bufferSource.stop(stopTime);
|
|
1475
1495
|
this.disconnectNote(note);
|
|
1496
|
+
channel.scheduledNotes[note.index] = undefined;
|
|
1476
1497
|
resolve();
|
|
1477
|
-
};
|
|
1478
|
-
note.bufferSource.stop(stopTime);
|
|
1498
|
+
}, stopTime);
|
|
1479
1499
|
});
|
|
1480
1500
|
}
|
|
1481
|
-
|
|
1482
|
-
for (let i = 0; i < noteList.length; i++) {
|
|
1483
|
-
const note = noteList[i];
|
|
1484
|
-
if (!note)
|
|
1485
|
-
continue;
|
|
1486
|
-
if (note.ending)
|
|
1487
|
-
continue;
|
|
1488
|
-
return [note, i];
|
|
1489
|
-
}
|
|
1490
|
-
}
|
|
1491
|
-
scheduleNoteOff(channelNumber, noteNumber, _velocity, endTime, force, portamentoNoteNumber) {
|
|
1501
|
+
scheduleNoteOff(channelNumber, noteNumber, _velocity, endTime, force) {
|
|
1492
1502
|
const channel = this.channels[channelNumber];
|
|
1493
|
-
if (this.isDrumNoteOffException(channel, noteNumber))
|
|
1494
|
-
return;
|
|
1495
1503
|
const state = channel.state;
|
|
1496
1504
|
if (!force) {
|
|
1497
|
-
if (
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
return;
|
|
1508
|
-
const [note, i] = noteOffTarget;
|
|
1509
|
-
if (0.5 <= state.portamento && portamentoNoteNumber !== undefined) {
|
|
1510
|
-
const portamentoTime = endTime + this.getPortamentoTime(channel);
|
|
1511
|
-
const deltaNote = portamentoNoteNumber - noteNumber;
|
|
1512
|
-
const baseRate = note.voiceParams.playbackRate;
|
|
1513
|
-
const targetRate = baseRate * Math.pow(2, deltaNote / 12);
|
|
1514
|
-
note.bufferSource.playbackRate
|
|
1515
|
-
.cancelScheduledValues(endTime)
|
|
1516
|
-
.linearRampToValueAtTime(targetRate, portamentoTime);
|
|
1517
|
-
return this.stopNote(endTime, portamentoTime, noteList, i);
|
|
1505
|
+
if (channel.isDrum) {
|
|
1506
|
+
if (!this.isLoopDrum(channel, noteNumber))
|
|
1507
|
+
return;
|
|
1508
|
+
}
|
|
1509
|
+
else {
|
|
1510
|
+
if (0.5 <= state.sustainPedal)
|
|
1511
|
+
return;
|
|
1512
|
+
if (0.5 <= state.sostenutoPedal)
|
|
1513
|
+
return;
|
|
1514
|
+
}
|
|
1518
1515
|
}
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1516
|
+
const note = this.findNoteOffTarget(channel, noteNumber);
|
|
1517
|
+
if (!note)
|
|
1518
|
+
return;
|
|
1519
|
+
note.ending = true;
|
|
1520
|
+
this.releaseNote(channel, note, endTime);
|
|
1521
|
+
}
|
|
1522
|
+
findNoteOffTarget(channel, noteNumber) {
|
|
1523
|
+
const scheduledNotes = channel.scheduledNotes;
|
|
1524
|
+
for (let i = 0; i < scheduledNotes.length; i++) {
|
|
1525
|
+
const note = scheduledNotes[i];
|
|
1526
|
+
if (!note)
|
|
1527
|
+
continue;
|
|
1528
|
+
if (note.ending)
|
|
1529
|
+
continue;
|
|
1530
|
+
if (note.noteNumber !== noteNumber)
|
|
1531
|
+
continue;
|
|
1532
|
+
return note;
|
|
1528
1533
|
}
|
|
1529
1534
|
}
|
|
1530
1535
|
noteOff(channelNumber, noteNumber, velocity, scheduleTime) {
|
|
1531
1536
|
scheduleTime ??= this.audioContext.currentTime;
|
|
1532
|
-
return this.scheduleNoteOff(channelNumber, noteNumber, velocity, scheduleTime, false
|
|
1533
|
-
undefined);
|
|
1537
|
+
return this.scheduleNoteOff(channelNumber, noteNumber, velocity, scheduleTime, false);
|
|
1534
1538
|
}
|
|
1535
1539
|
releaseSustainPedal(channelNumber, halfVelocity, scheduleTime) {
|
|
1536
1540
|
const velocity = halfVelocity * 2;
|
|
1537
1541
|
const channel = this.channels[channelNumber];
|
|
1538
1542
|
const promises = [];
|
|
1539
1543
|
for (let i = 0; i < channel.sustainNotes.length; i++) {
|
|
1540
|
-
const promise = this.
|
|
1544
|
+
const promise = this.scheduleNoteOff(channelNumber, channel.sustainNotes[i].noteNumber, velocity, scheduleTime);
|
|
1541
1545
|
promises.push(promise);
|
|
1542
1546
|
}
|
|
1543
1547
|
channel.sustainNotes = [];
|
|
@@ -1547,12 +1551,14 @@ class Midy {
|
|
|
1547
1551
|
const velocity = halfVelocity * 2;
|
|
1548
1552
|
const channel = this.channels[channelNumber];
|
|
1549
1553
|
const promises = [];
|
|
1554
|
+
const sostenutoNotes = channel.sostenutoNotes;
|
|
1550
1555
|
channel.state.sostenutoPedal = 0;
|
|
1551
|
-
|
|
1552
|
-
const
|
|
1556
|
+
for (let i = 0; i < sostenutoNotes.length; i++) {
|
|
1557
|
+
const note = sostenutoNotes[i];
|
|
1558
|
+
const promise = this.scheduleNoteOff(channelNumber, note.noteNumber, velocity, scheduleTime);
|
|
1553
1559
|
promises.push(promise);
|
|
1554
|
-
}
|
|
1555
|
-
channel.sostenutoNotes
|
|
1560
|
+
}
|
|
1561
|
+
channel.sostenutoNotes = [];
|
|
1556
1562
|
return promises;
|
|
1557
1563
|
}
|
|
1558
1564
|
handleMIDIMessage(statusByte, data1, data2, scheduleTime) {
|
|
@@ -1581,11 +1587,11 @@ class Midy {
|
|
|
1581
1587
|
const channel = this.channels[channelNumber];
|
|
1582
1588
|
channel.state.polyphonicKeyPressure = pressure / 127;
|
|
1583
1589
|
const table = channel.polyphonicKeyPressureTable;
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
}
|
|
1590
|
+
this.processActiveNotes(channel, scheduleTime, (note) => {
|
|
1591
|
+
if (note.noteNumber === noteNumber) {
|
|
1592
|
+
this.setControllerParameters(channel, note, table);
|
|
1593
|
+
}
|
|
1594
|
+
});
|
|
1589
1595
|
this.applyVoiceParams(channel, 10);
|
|
1590
1596
|
}
|
|
1591
1597
|
handleProgramChange(channelNumber, programNumber, _scheduleTime) {
|
|
@@ -1615,7 +1621,7 @@ class Midy {
|
|
|
1615
1621
|
channel.detune += pressureDepth * (next - prev);
|
|
1616
1622
|
}
|
|
1617
1623
|
const table = channel.channelPressureTable;
|
|
1618
|
-
this.
|
|
1624
|
+
this.processActiveNotes(channel, scheduleTime, (note) => {
|
|
1619
1625
|
this.setControllerParameters(channel, note, table);
|
|
1620
1626
|
});
|
|
1621
1627
|
this.applyVoiceParams(channel, 13);
|
|
@@ -1672,10 +1678,13 @@ class Midy {
|
|
|
1672
1678
|
.setValueAtTime(volumeDepth, scheduleTime);
|
|
1673
1679
|
}
|
|
1674
1680
|
setReverbEffectsSend(channel, note, prevValue, scheduleTime) {
|
|
1681
|
+
const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 91);
|
|
1682
|
+
let value = note.voiceParams.reverbEffectsSend;
|
|
1683
|
+
if (0 <= keyBasedValue) {
|
|
1684
|
+
value *= keyBasedValue / 127 / channel.state.reverbSendLevel;
|
|
1685
|
+
}
|
|
1675
1686
|
if (0 < prevValue) {
|
|
1676
|
-
if (0 <
|
|
1677
|
-
const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 91);
|
|
1678
|
-
const value = note.voiceParams.reverbEffectsSend + keyBasedValue;
|
|
1687
|
+
if (0 < value) {
|
|
1679
1688
|
note.reverbEffectsSend.gain
|
|
1680
1689
|
.cancelScheduledValues(scheduleTime)
|
|
1681
1690
|
.setValueAtTime(value, scheduleTime);
|
|
@@ -1685,10 +1694,10 @@ class Midy {
|
|
|
1685
1694
|
}
|
|
1686
1695
|
}
|
|
1687
1696
|
else {
|
|
1688
|
-
if (0 <
|
|
1697
|
+
if (0 < value) {
|
|
1689
1698
|
if (!note.reverbEffectsSend) {
|
|
1690
1699
|
note.reverbEffectsSend = new GainNode(this.audioContext, {
|
|
1691
|
-
gain:
|
|
1700
|
+
gain: value,
|
|
1692
1701
|
});
|
|
1693
1702
|
note.volumeNode.connect(note.reverbEffectsSend);
|
|
1694
1703
|
}
|
|
@@ -1697,10 +1706,13 @@ class Midy {
|
|
|
1697
1706
|
}
|
|
1698
1707
|
}
|
|
1699
1708
|
setChorusEffectsSend(channel, note, prevValue, scheduleTime) {
|
|
1709
|
+
const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 93);
|
|
1710
|
+
let value = note.voiceParams.chorusEffectsSend;
|
|
1711
|
+
if (0 <= keyBasedValue) {
|
|
1712
|
+
value *= keyBasedValue / 127 / channel.state.chorusSendLevel;
|
|
1713
|
+
}
|
|
1700
1714
|
if (0 < prevValue) {
|
|
1701
|
-
if (0 <
|
|
1702
|
-
const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 93);
|
|
1703
|
-
const value = note.voiceParams.chorusEffectsSend + keyBasedValue;
|
|
1715
|
+
if (0 < vaule) {
|
|
1704
1716
|
note.chorusEffectsSend.gain
|
|
1705
1717
|
.cancelScheduledValues(scheduleTime)
|
|
1706
1718
|
.setValueAtTime(value, scheduleTime);
|
|
@@ -1710,10 +1722,10 @@ class Midy {
|
|
|
1710
1722
|
}
|
|
1711
1723
|
}
|
|
1712
1724
|
else {
|
|
1713
|
-
if (0 <
|
|
1725
|
+
if (0 < value) {
|
|
1714
1726
|
if (!note.chorusEffectsSend) {
|
|
1715
1727
|
note.chorusEffectsSend = new GainNode(this.audioContext, {
|
|
1716
|
-
gain:
|
|
1728
|
+
gain: value,
|
|
1717
1729
|
});
|
|
1718
1730
|
note.volumeNode.connect(note.chorusEffectsSend);
|
|
1719
1731
|
}
|
|
@@ -1823,8 +1835,8 @@ class Midy {
|
|
|
1823
1835
|
if (key in voiceParams)
|
|
1824
1836
|
noteVoiceParams[key] = voiceParams[key];
|
|
1825
1837
|
}
|
|
1826
|
-
if (0.5 <= channel.state.portamento && note.
|
|
1827
|
-
this.
|
|
1838
|
+
if (0.5 <= channel.state.portamento && 0 <= note.portamentoNoteNumber) {
|
|
1839
|
+
this.setPortamentoFilterEnvelope(channel, note, scheduleTime);
|
|
1828
1840
|
}
|
|
1829
1841
|
else {
|
|
1830
1842
|
this.setFilterEnvelope(channel, note, scheduleTime);
|
|
@@ -1847,42 +1859,42 @@ class Midy {
|
|
|
1847
1859
|
});
|
|
1848
1860
|
}
|
|
1849
1861
|
createControlChangeHandlers() {
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1862
|
+
const handlers = new Array(128);
|
|
1863
|
+
handlers[0] = this.setBankMSB;
|
|
1864
|
+
handlers[1] = this.setModulationDepth;
|
|
1865
|
+
handlers[5] = this.setPortamentoTime;
|
|
1866
|
+
handlers[6] = this.dataEntryMSB;
|
|
1867
|
+
handlers[7] = this.setVolume;
|
|
1868
|
+
handlers[10] = this.setPan;
|
|
1869
|
+
handlers[11] = this.setExpression;
|
|
1870
|
+
handlers[32] = this.setBankLSB;
|
|
1871
|
+
handlers[38] = this.dataEntryLSB;
|
|
1872
|
+
handlers[64] = this.setSustainPedal;
|
|
1873
|
+
handlers[65] = this.setPortamento;
|
|
1874
|
+
handlers[66] = this.setSostenutoPedal;
|
|
1875
|
+
handlers[67] = this.setSoftPedal;
|
|
1876
|
+
handlers[71] = this.setFilterResonance;
|
|
1877
|
+
handlers[72] = this.setReleaseTime;
|
|
1878
|
+
handlers[73] = this.setAttackTime;
|
|
1879
|
+
handlers[74] = this.setBrightness;
|
|
1880
|
+
handlers[75] = this.setDecayTime;
|
|
1881
|
+
handlers[76] = this.setVibratoRate;
|
|
1882
|
+
handlers[77] = this.setVibratoDepth;
|
|
1883
|
+
handlers[78] = this.setVibratoDelay;
|
|
1884
|
+
handlers[91] = this.setReverbSendLevel;
|
|
1885
|
+
handlers[93] = this.setChorusSendLevel;
|
|
1886
|
+
handlers[96] = this.dataIncrement;
|
|
1887
|
+
handlers[97] = this.dataDecrement;
|
|
1888
|
+
handlers[100] = this.setRPNLSB;
|
|
1889
|
+
handlers[101] = this.setRPNMSB;
|
|
1890
|
+
handlers[120] = this.allSoundOff;
|
|
1891
|
+
handlers[121] = this.resetAllControllers;
|
|
1892
|
+
handlers[123] = this.allNotesOff;
|
|
1893
|
+
handlers[124] = this.omniOff;
|
|
1894
|
+
handlers[125] = this.omniOn;
|
|
1895
|
+
handlers[126] = this.monoOn;
|
|
1896
|
+
handlers[127] = this.polyOn;
|
|
1897
|
+
return handlers;
|
|
1886
1898
|
}
|
|
1887
1899
|
handleControlChange(channelNumber, controllerType, value, scheduleTime) {
|
|
1888
1900
|
const handler = this.controlChangeHandlers[controllerType];
|
|
@@ -1919,17 +1931,41 @@ class Midy {
|
|
|
1919
1931
|
channel.state.modulationDepth = modulation / 127;
|
|
1920
1932
|
this.updateModulation(channel, scheduleTime);
|
|
1921
1933
|
}
|
|
1922
|
-
|
|
1934
|
+
updatePortamento(channel, scheduleTime) {
|
|
1935
|
+
this.processScheduledNotes(channel, (note) => {
|
|
1936
|
+
if (0.5 <= channel.state.portamento) {
|
|
1937
|
+
if (0 <= note.portamentoNoteNumber) {
|
|
1938
|
+
this.setPortamentoVolumeEnvelope(channel, note, scheduleTime);
|
|
1939
|
+
this.setPortamentoFilterEnvelope(channel, note, scheduleTime);
|
|
1940
|
+
this.setPortamentoPitchEnvelope(note, scheduleTime);
|
|
1941
|
+
this.updateDetune(channel, note, scheduleTime);
|
|
1942
|
+
}
|
|
1943
|
+
}
|
|
1944
|
+
else {
|
|
1945
|
+
if (0 <= note.portamentoNoteNumber) {
|
|
1946
|
+
this.setVolumeEnvelope(channel, note, scheduleTime);
|
|
1947
|
+
this.setFilterEnvelope(channel, note, scheduleTime);
|
|
1948
|
+
this.setPitchEnvelope(note, scheduleTime);
|
|
1949
|
+
this.updateDetune(channel, note, scheduleTime);
|
|
1950
|
+
}
|
|
1951
|
+
}
|
|
1952
|
+
});
|
|
1953
|
+
}
|
|
1954
|
+
setPortamentoTime(channelNumber, portamentoTime, scheduleTime) {
|
|
1923
1955
|
const channel = this.channels[channelNumber];
|
|
1956
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1924
1957
|
channel.state.portamentoTime = portamentoTime / 127;
|
|
1958
|
+
if (channel.isDrum)
|
|
1959
|
+
return;
|
|
1960
|
+
this.updatePortamento(channel, scheduleTime);
|
|
1925
1961
|
}
|
|
1926
1962
|
setKeyBasedVolume(channel, scheduleTime) {
|
|
1927
1963
|
this.processScheduledNotes(channel, (note) => {
|
|
1928
1964
|
const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 7);
|
|
1929
|
-
if (
|
|
1965
|
+
if (0 <= keyBasedValue) {
|
|
1930
1966
|
note.volumeNode.gain
|
|
1931
1967
|
.cancelScheduledValues(scheduleTime)
|
|
1932
|
-
.setValueAtTime(
|
|
1968
|
+
.setValueAtTime(keyBasedValue / 127, scheduleTime);
|
|
1933
1969
|
}
|
|
1934
1970
|
});
|
|
1935
1971
|
}
|
|
@@ -1950,8 +1986,8 @@ class Midy {
|
|
|
1950
1986
|
setKeyBasedPan(channel, scheduleTime) {
|
|
1951
1987
|
this.processScheduledNotes(channel, (note) => {
|
|
1952
1988
|
const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 10);
|
|
1953
|
-
if (
|
|
1954
|
-
const { gainLeft, gainRight } = this.panToGain(
|
|
1989
|
+
if (0 <= keyBasedValue) {
|
|
1990
|
+
const { gainLeft, gainRight } = this.panToGain(keyBasedValue / 127);
|
|
1955
1991
|
note.gainL.gain
|
|
1956
1992
|
.cancelScheduledValues(scheduleTime)
|
|
1957
1993
|
.setValueAtTime(gainLeft, scheduleTime);
|
|
@@ -1979,7 +2015,7 @@ class Midy {
|
|
|
1979
2015
|
}
|
|
1980
2016
|
dataEntryLSB(channelNumber, value, scheduleTime) {
|
|
1981
2017
|
this.channels[channelNumber].dataLSB = value;
|
|
1982
|
-
this.handleRPN(channelNumber, scheduleTime);
|
|
2018
|
+
this.handleRPN(channelNumber, 0, scheduleTime);
|
|
1983
2019
|
}
|
|
1984
2020
|
updateChannelVolume(channel, scheduleTime) {
|
|
1985
2021
|
const state = channel.state;
|
|
@@ -2007,11 +2043,13 @@ class Midy {
|
|
|
2007
2043
|
this.releaseSustainPedal(channelNumber, value, scheduleTime);
|
|
2008
2044
|
}
|
|
2009
2045
|
}
|
|
2010
|
-
setPortamento(channelNumber, value) {
|
|
2046
|
+
setPortamento(channelNumber, value, scheduleTime) {
|
|
2011
2047
|
const channel = this.channels[channelNumber];
|
|
2012
2048
|
if (channel.isDrum)
|
|
2013
2049
|
return;
|
|
2050
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
2014
2051
|
channel.state.portamento = value / 127;
|
|
2052
|
+
this.updatePortamento(channel, scheduleTime);
|
|
2015
2053
|
}
|
|
2016
2054
|
setSostenutoPedal(channelNumber, value, scheduleTime) {
|
|
2017
2055
|
const channel = this.channels[channelNumber];
|
|
@@ -2020,12 +2058,19 @@ class Midy {
|
|
|
2020
2058
|
scheduleTime ??= this.audioContext.currentTime;
|
|
2021
2059
|
channel.state.sostenutoPedal = value / 127;
|
|
2022
2060
|
if (64 <= value) {
|
|
2023
|
-
|
|
2061
|
+
const sostenutoNotes = [];
|
|
2062
|
+
this.processActiveNotes(channel, scheduleTime, (note) => {
|
|
2063
|
+
sostenutoNotes.push(note);
|
|
2064
|
+
});
|
|
2065
|
+
channel.sostenutoNotes = sostenutoNotes;
|
|
2024
2066
|
}
|
|
2025
2067
|
else {
|
|
2026
2068
|
this.releaseSostenutoPedal(channelNumber, value, scheduleTime);
|
|
2027
2069
|
}
|
|
2028
2070
|
}
|
|
2071
|
+
getSoftPedalFactor(channel, note) {
|
|
2072
|
+
return 1 - (0.1 + (note.noteNumber / 127) * 0.2) * channel.state.softPedal;
|
|
2073
|
+
}
|
|
2029
2074
|
setSoftPedal(channelNumber, softPedal, scheduleTime) {
|
|
2030
2075
|
const channel = this.channels[channelNumber];
|
|
2031
2076
|
if (channel.isDrum)
|
|
@@ -2034,9 +2079,9 @@ class Midy {
|
|
|
2034
2079
|
scheduleTime ??= this.audioContext.currentTime;
|
|
2035
2080
|
state.softPedal = softPedal / 127;
|
|
2036
2081
|
this.processScheduledNotes(channel, (note) => {
|
|
2037
|
-
if (0.5 <= state.portamento && note.
|
|
2038
|
-
this.
|
|
2039
|
-
this.
|
|
2082
|
+
if (0.5 <= state.portamento && 0 <= note.portamentoNoteNumber) {
|
|
2083
|
+
this.setPortamentoVolumeEnvelope(channel, note, scheduleTime);
|
|
2084
|
+
this.setPortamentoFilterEnvelope(channel, note, scheduleTime);
|
|
2040
2085
|
}
|
|
2041
2086
|
else {
|
|
2042
2087
|
this.setVolumeEnvelope(channel, note, scheduleTime);
|
|
@@ -2050,7 +2095,7 @@ class Midy {
|
|
|
2050
2095
|
return;
|
|
2051
2096
|
scheduleTime ??= this.audioContext.currentTime;
|
|
2052
2097
|
const state = channel.state;
|
|
2053
|
-
state.filterResonance = filterResonance /
|
|
2098
|
+
state.filterResonance = filterResonance / 127;
|
|
2054
2099
|
this.processScheduledNotes(channel, (note) => {
|
|
2055
2100
|
const Q = note.voiceParams.initialFilterQ / 5 * state.filterResonance;
|
|
2056
2101
|
note.filterNode.Q.setValueAtTime(Q, scheduleTime);
|
|
@@ -2061,14 +2106,14 @@ class Midy {
|
|
|
2061
2106
|
if (channel.isDrum)
|
|
2062
2107
|
return;
|
|
2063
2108
|
scheduleTime ??= this.audioContext.currentTime;
|
|
2064
|
-
channel.state.releaseTime = releaseTime /
|
|
2109
|
+
channel.state.releaseTime = releaseTime / 127;
|
|
2065
2110
|
}
|
|
2066
2111
|
setAttackTime(channelNumber, attackTime, scheduleTime) {
|
|
2067
2112
|
const channel = this.channels[channelNumber];
|
|
2068
2113
|
if (channel.isDrum)
|
|
2069
2114
|
return;
|
|
2070
2115
|
scheduleTime ??= this.audioContext.currentTime;
|
|
2071
|
-
channel.state.attackTime = attackTime /
|
|
2116
|
+
channel.state.attackTime = attackTime / 127;
|
|
2072
2117
|
this.processScheduledNotes(channel, (note) => {
|
|
2073
2118
|
if (note.startTime < scheduleTime)
|
|
2074
2119
|
return false;
|
|
@@ -2081,10 +2126,10 @@ class Midy {
|
|
|
2081
2126
|
return;
|
|
2082
2127
|
const state = channel.state;
|
|
2083
2128
|
scheduleTime ??= this.audioContext.currentTime;
|
|
2084
|
-
state.brightness = brightness /
|
|
2129
|
+
state.brightness = brightness / 127;
|
|
2085
2130
|
this.processScheduledNotes(channel, (note) => {
|
|
2086
|
-
if (0.5 <= state.portamento && note.
|
|
2087
|
-
this.
|
|
2131
|
+
if (0.5 <= state.portamento && 0 <= note.portamentoNoteNumber) {
|
|
2132
|
+
this.setPortamentoFilterEnvelope(channel, note, scheduleTime);
|
|
2088
2133
|
}
|
|
2089
2134
|
else {
|
|
2090
2135
|
this.setFilterEnvelope(channel, note);
|
|
@@ -2096,7 +2141,7 @@ class Midy {
|
|
|
2096
2141
|
if (channel.isDrum)
|
|
2097
2142
|
return;
|
|
2098
2143
|
scheduleTime ??= this.audioContext.currentTime;
|
|
2099
|
-
channel.state.decayTime = dacayTime /
|
|
2144
|
+
channel.state.decayTime = dacayTime / 127;
|
|
2100
2145
|
this.processScheduledNotes(channel, (note) => {
|
|
2101
2146
|
this.setVolumeEnvelope(channel, note, scheduleTime);
|
|
2102
2147
|
});
|
|
@@ -2106,7 +2151,7 @@ class Midy {
|
|
|
2106
2151
|
if (channel.isDrum)
|
|
2107
2152
|
return;
|
|
2108
2153
|
scheduleTime ??= this.audioContext.currentTime;
|
|
2109
|
-
channel.state.vibratoRate = vibratoRate /
|
|
2154
|
+
channel.state.vibratoRate = vibratoRate / 127;
|
|
2110
2155
|
if (channel.vibratoDepth <= 0)
|
|
2111
2156
|
return;
|
|
2112
2157
|
this.processScheduledNotes(channel, (note) => {
|
|
@@ -2119,7 +2164,7 @@ class Midy {
|
|
|
2119
2164
|
return;
|
|
2120
2165
|
scheduleTime ??= this.audioContext.currentTime;
|
|
2121
2166
|
const prev = channel.state.vibratoDepth;
|
|
2122
|
-
channel.state.vibratoDepth = vibratoDepth /
|
|
2167
|
+
channel.state.vibratoDepth = vibratoDepth / 127;
|
|
2123
2168
|
if (0 < prev) {
|
|
2124
2169
|
this.processScheduledNotes(channel, (note) => {
|
|
2125
2170
|
this.setFreqVibLFO(channel, note, scheduleTime);
|
|
@@ -2131,12 +2176,12 @@ class Midy {
|
|
|
2131
2176
|
});
|
|
2132
2177
|
}
|
|
2133
2178
|
}
|
|
2134
|
-
setVibratoDelay(channelNumber, vibratoDelay) {
|
|
2179
|
+
setVibratoDelay(channelNumber, vibratoDelay, scheduleTime) {
|
|
2135
2180
|
const channel = this.channels[channelNumber];
|
|
2136
2181
|
if (channel.isDrum)
|
|
2137
2182
|
return;
|
|
2138
2183
|
scheduleTime ??= this.audioContext.currentTime;
|
|
2139
|
-
channel.state.vibratoDelay = vibratoDelay /
|
|
2184
|
+
channel.state.vibratoDelay = vibratoDelay / 127;
|
|
2140
2185
|
if (0 < channel.state.vibratoDepth) {
|
|
2141
2186
|
this.processScheduledNotes(channel, (note) => {
|
|
2142
2187
|
this.startVibrato(channel, note, scheduleTime);
|
|
@@ -2159,7 +2204,8 @@ class Midy {
|
|
|
2159
2204
|
this.processScheduledNotes(channel, (note) => {
|
|
2160
2205
|
if (note.voiceParams.reverbEffectsSend <= 0)
|
|
2161
2206
|
return false;
|
|
2162
|
-
note.reverbEffectsSend
|
|
2207
|
+
if (note.reverbEffectsSend)
|
|
2208
|
+
note.reverbEffectsSend.disconnect();
|
|
2163
2209
|
});
|
|
2164
2210
|
}
|
|
2165
2211
|
}
|
|
@@ -2191,7 +2237,8 @@ class Midy {
|
|
|
2191
2237
|
this.processScheduledNotes(channel, (note) => {
|
|
2192
2238
|
if (note.voiceParams.chorusEffectsSend <= 0)
|
|
2193
2239
|
return false;
|
|
2194
|
-
note.chorusEffectsSend
|
|
2240
|
+
if (note.chorusEffectsSend)
|
|
2241
|
+
note.chorusEffectsSend.disconnect();
|
|
2195
2242
|
});
|
|
2196
2243
|
}
|
|
2197
2244
|
}
|
|
@@ -2258,12 +2305,14 @@ class Midy {
|
|
|
2258
2305
|
}
|
|
2259
2306
|
}
|
|
2260
2307
|
// https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/rp18.pdf
|
|
2261
|
-
dataIncrement(channelNumber) {
|
|
2262
|
-
this.
|
|
2308
|
+
dataIncrement(channelNumber, scheduleTime) {
|
|
2309
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
2310
|
+
this.handleRPN(channelNumber, 1, scheduleTime);
|
|
2263
2311
|
}
|
|
2264
2312
|
// https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/rp18.pdf
|
|
2265
|
-
dataDecrement(channelNumber) {
|
|
2266
|
-
this.
|
|
2313
|
+
dataDecrement(channelNumber, scheduleTime) {
|
|
2314
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
2315
|
+
this.handleRPN(channelNumber, -1, scheduleTime);
|
|
2267
2316
|
}
|
|
2268
2317
|
setRPNMSB(channelNumber, value) {
|
|
2269
2318
|
this.channels[channelNumber].rpnMSB = value;
|
|
@@ -2273,7 +2322,7 @@ class Midy {
|
|
|
2273
2322
|
}
|
|
2274
2323
|
dataEntryMSB(channelNumber, value, scheduleTime) {
|
|
2275
2324
|
this.channels[channelNumber].dataMSB = value;
|
|
2276
|
-
this.handleRPN(channelNumber, scheduleTime);
|
|
2325
|
+
this.handleRPN(channelNumber, 0, scheduleTime);
|
|
2277
2326
|
}
|
|
2278
2327
|
handlePitchBendRangeRPN(channelNumber, scheduleTime) {
|
|
2279
2328
|
const channel = this.channels[channelNumber];
|
|
@@ -2347,21 +2396,29 @@ class Midy {
|
|
|
2347
2396
|
return this.stopActiveNotes(channelNumber, 0, true, scheduleTime);
|
|
2348
2397
|
}
|
|
2349
2398
|
resetAllStates(channelNumber) {
|
|
2399
|
+
const scheduleTime = this.audioContext.currentTime;
|
|
2350
2400
|
const channel = this.channels[channelNumber];
|
|
2351
2401
|
const state = channel.state;
|
|
2352
|
-
|
|
2353
|
-
|
|
2402
|
+
const entries = Object.entries(defaultControllerState);
|
|
2403
|
+
for (const [key, { type, defaultValue }] of entries) {
|
|
2404
|
+
if (128 <= type) {
|
|
2405
|
+
this.handleControlChange(channelNumber, type - 128, Math.ceil(defaultValue * 127), scheduleTime);
|
|
2406
|
+
}
|
|
2407
|
+
else {
|
|
2408
|
+
state[key] = defaultValue;
|
|
2409
|
+
}
|
|
2354
2410
|
}
|
|
2355
|
-
for (const
|
|
2356
|
-
channel[
|
|
2411
|
+
for (const key of Object.keys(this.constructor.channelSettings)) {
|
|
2412
|
+
channel[key] = this.constructor.channelSettings[key];
|
|
2357
2413
|
}
|
|
2414
|
+
this.resetChannelTable(channel);
|
|
2358
2415
|
this.mode = "GM2";
|
|
2359
2416
|
this.masterFineTuning = 0; // cb
|
|
2360
2417
|
this.masterCoarseTuning = 0; // cb
|
|
2361
2418
|
}
|
|
2362
2419
|
// https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/rp15.pdf
|
|
2363
|
-
resetAllControllers(channelNumber) {
|
|
2364
|
-
const
|
|
2420
|
+
resetAllControllers(channelNumber, _value, scheduleTime) {
|
|
2421
|
+
const keys = [
|
|
2365
2422
|
"polyphonicKeyPressure",
|
|
2366
2423
|
"channelPressure",
|
|
2367
2424
|
"pitchWheel",
|
|
@@ -2374,10 +2431,17 @@ class Midy {
|
|
|
2374
2431
|
];
|
|
2375
2432
|
const channel = this.channels[channelNumber];
|
|
2376
2433
|
const state = channel.state;
|
|
2377
|
-
for (let i = 0; i <
|
|
2378
|
-
const
|
|
2379
|
-
|
|
2434
|
+
for (let i = 0; i < keys.length; i++) {
|
|
2435
|
+
const key = keys[i];
|
|
2436
|
+
const { type, defaultValue } = defaultControllerState[key];
|
|
2437
|
+
if (128 <= type) {
|
|
2438
|
+
this.handleControlChange(channelNumber, type - 128, Math.ceil(defaultValue * 127), scheduleTime);
|
|
2439
|
+
}
|
|
2440
|
+
else {
|
|
2441
|
+
state[key] = defaultValue;
|
|
2442
|
+
}
|
|
2380
2443
|
}
|
|
2444
|
+
this.setPitchBend(channelNumber, 8192, scheduleTime);
|
|
2381
2445
|
const settingTypes = [
|
|
2382
2446
|
"rpnMSB",
|
|
2383
2447
|
"rpnLSB",
|
|
@@ -2622,7 +2686,7 @@ class Midy {
|
|
|
2622
2686
|
this.reverbEffect = options.reverbAlgorithm(audioContext);
|
|
2623
2687
|
}
|
|
2624
2688
|
getReverbTime(value) {
|
|
2625
|
-
return Math.
|
|
2689
|
+
return Math.exp((value - 40) * 0.025);
|
|
2626
2690
|
}
|
|
2627
2691
|
// mean free path equation
|
|
2628
2692
|
// https://repository.dl.itc.u-tokyo.ac.jp/record/8550/files/A31912.pdf
|
|
@@ -2856,7 +2920,13 @@ class Midy {
|
|
|
2856
2920
|
setControllerParameters(channel, note, table) {
|
|
2857
2921
|
if (table[0] !== 64)
|
|
2858
2922
|
this.updateDetune(channel, note);
|
|
2859
|
-
if (
|
|
2923
|
+
if (0.5 <= channel.state.portamemento && 0 <= note.portamentoNoteNumber) {
|
|
2924
|
+
if (table[1] !== 64)
|
|
2925
|
+
this.setPortamentoFilterEnvelope(channel, note);
|
|
2926
|
+
if (table[2] !== 64)
|
|
2927
|
+
this.setPortamentoVolumeEnvelope(channel, note);
|
|
2928
|
+
}
|
|
2929
|
+
else {
|
|
2860
2930
|
if (table[1] !== 64)
|
|
2861
2931
|
this.setFilterEnvelope(channel, note);
|
|
2862
2932
|
if (table[2] !== 64)
|
|
@@ -2884,8 +2954,13 @@ class Midy {
|
|
|
2884
2954
|
initControlTable() {
|
|
2885
2955
|
const channelCount = 128;
|
|
2886
2956
|
const slotSize = 6;
|
|
2887
|
-
const defaultValues = [64, 64, 64, 0, 0, 0];
|
|
2888
2957
|
const table = new Uint8Array(channelCount * slotSize);
|
|
2958
|
+
return this.resetControlTable(table);
|
|
2959
|
+
}
|
|
2960
|
+
resetControlTable(table) {
|
|
2961
|
+
const channelCount = 128;
|
|
2962
|
+
const slotSize = 6;
|
|
2963
|
+
const defaultValues = [64, 64, 64, 0, 0, 0];
|
|
2889
2964
|
for (let ch = 0; ch < channelCount; ch++) {
|
|
2890
2965
|
const offset = ch * slotSize;
|
|
2891
2966
|
table.set(defaultValues, offset);
|
|
@@ -2916,7 +2991,7 @@ class Midy {
|
|
|
2916
2991
|
getKeyBasedInstrumentControlValue(channel, keyNumber, controllerType) {
|
|
2917
2992
|
const index = keyNumber * 128 + controllerType;
|
|
2918
2993
|
const controlValue = channel.keyBasedInstrumentControlTable[index];
|
|
2919
|
-
return
|
|
2994
|
+
return controlValue;
|
|
2920
2995
|
}
|
|
2921
2996
|
handleKeyBasedInstrumentControlSysEx(data, scheduleTime) {
|
|
2922
2997
|
const channelNumber = data[4];
|
|
@@ -2929,7 +3004,7 @@ class Midy {
|
|
|
2929
3004
|
const controllerType = data[i];
|
|
2930
3005
|
const value = data[i + 1];
|
|
2931
3006
|
const index = keyNumber * 128 + controllerType;
|
|
2932
|
-
table[index] = value
|
|
3007
|
+
table[index] = value;
|
|
2933
3008
|
}
|
|
2934
3009
|
this.handleChannelPressure(channelNumber, channel.state.channelPressure * 127, scheduleTime);
|
|
2935
3010
|
}
|