@marmooo/midy 0.2.4 → 0.2.6
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 +44 -28
- package/esm/midy-GM1.d.ts.map +1 -1
- package/esm/midy-GM1.js +159 -101
- package/esm/midy-GM2.d.ts +54 -33
- package/esm/midy-GM2.d.ts.map +1 -1
- package/esm/midy-GM2.js +260 -156
- package/esm/midy-GMLite.d.ts +38 -19
- package/esm/midy-GMLite.d.ts.map +1 -1
- package/esm/midy-GMLite.js +157 -49
- package/esm/midy.d.ts +57 -35
- package/esm/midy.d.ts.map +1 -1
- package/esm/midy.js +312 -183
- package/package.json +1 -1
- package/script/midy-GM1.d.ts +44 -28
- package/script/midy-GM1.d.ts.map +1 -1
- package/script/midy-GM1.js +159 -101
- package/script/midy-GM2.d.ts +54 -33
- package/script/midy-GM2.d.ts.map +1 -1
- package/script/midy-GM2.js +260 -156
- package/script/midy-GMLite.d.ts +38 -19
- package/script/midy-GMLite.d.ts.map +1 -1
- package/script/midy-GMLite.js +157 -49
- package/script/midy.d.ts +57 -35
- package/script/midy.d.ts.map +1 -1
- package/script/midy.js +312 -183
package/script/midy.js
CHANGED
|
@@ -3,6 +3,58 @@ 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
|
+
}
|
|
6
58
|
class Note {
|
|
7
59
|
constructor(noteNumber, velocity, startTime, voice, voiceParams) {
|
|
8
60
|
Object.defineProperty(this, "bufferSource", {
|
|
@@ -17,31 +69,37 @@ class Note {
|
|
|
17
69
|
writable: true,
|
|
18
70
|
value: void 0
|
|
19
71
|
});
|
|
72
|
+
Object.defineProperty(this, "filterDepth", {
|
|
73
|
+
enumerable: true,
|
|
74
|
+
configurable: true,
|
|
75
|
+
writable: true,
|
|
76
|
+
value: void 0
|
|
77
|
+
});
|
|
20
78
|
Object.defineProperty(this, "volumeEnvelopeNode", {
|
|
21
79
|
enumerable: true,
|
|
22
80
|
configurable: true,
|
|
23
81
|
writable: true,
|
|
24
82
|
value: void 0
|
|
25
83
|
});
|
|
26
|
-
Object.defineProperty(this, "
|
|
84
|
+
Object.defineProperty(this, "volumeDepth", {
|
|
27
85
|
enumerable: true,
|
|
28
86
|
configurable: true,
|
|
29
87
|
writable: true,
|
|
30
88
|
value: void 0
|
|
31
89
|
});
|
|
32
|
-
Object.defineProperty(this, "
|
|
90
|
+
Object.defineProperty(this, "volumeNode", {
|
|
33
91
|
enumerable: true,
|
|
34
92
|
configurable: true,
|
|
35
93
|
writable: true,
|
|
36
94
|
value: void 0
|
|
37
95
|
});
|
|
38
|
-
Object.defineProperty(this, "
|
|
96
|
+
Object.defineProperty(this, "gainL", {
|
|
39
97
|
enumerable: true,
|
|
40
98
|
configurable: true,
|
|
41
99
|
writable: true,
|
|
42
100
|
value: void 0
|
|
43
101
|
});
|
|
44
|
-
Object.defineProperty(this, "
|
|
102
|
+
Object.defineProperty(this, "gainR", {
|
|
45
103
|
enumerable: true,
|
|
46
104
|
configurable: true,
|
|
47
105
|
writable: true,
|
|
@@ -290,6 +348,18 @@ class Midy {
|
|
|
290
348
|
writable: true,
|
|
291
349
|
value: this.initSoundFontTable()
|
|
292
350
|
});
|
|
351
|
+
Object.defineProperty(this, "audioBufferCounter", {
|
|
352
|
+
enumerable: true,
|
|
353
|
+
configurable: true,
|
|
354
|
+
writable: true,
|
|
355
|
+
value: new Map()
|
|
356
|
+
});
|
|
357
|
+
Object.defineProperty(this, "audioBufferCache", {
|
|
358
|
+
enumerable: true,
|
|
359
|
+
configurable: true,
|
|
360
|
+
writable: true,
|
|
361
|
+
value: new Map()
|
|
362
|
+
});
|
|
293
363
|
Object.defineProperty(this, "isPlaying", {
|
|
294
364
|
enumerable: true,
|
|
295
365
|
configurable: true,
|
|
@@ -342,7 +412,7 @@ class Midy {
|
|
|
342
412
|
enumerable: true,
|
|
343
413
|
configurable: true,
|
|
344
414
|
writable: true,
|
|
345
|
-
value: new
|
|
415
|
+
value: new SparseMap(128)
|
|
346
416
|
});
|
|
347
417
|
Object.defineProperty(this, "defaultOptions", {
|
|
348
418
|
enumerable: true,
|
|
@@ -382,7 +452,7 @@ class Midy {
|
|
|
382
452
|
initSoundFontTable() {
|
|
383
453
|
const table = new Array(128);
|
|
384
454
|
for (let i = 0; i < 128; i++) {
|
|
385
|
-
table[i] = new
|
|
455
|
+
table[i] = new SparseMap(128);
|
|
386
456
|
}
|
|
387
457
|
return table;
|
|
388
458
|
}
|
|
@@ -436,8 +506,12 @@ class Midy {
|
|
|
436
506
|
state: new ControllerState(),
|
|
437
507
|
controlTable: this.initControlTable(),
|
|
438
508
|
...this.setChannelAudioNodes(audioContext),
|
|
439
|
-
scheduledNotes: new
|
|
440
|
-
sostenutoNotes: new
|
|
509
|
+
scheduledNotes: new SparseMap(128),
|
|
510
|
+
sostenutoNotes: new SparseMap(128),
|
|
511
|
+
scaleOctaveTuningTable: new Float32Array(12), // [-100, 100] cent
|
|
512
|
+
channelPressureTable: new Uint8Array([64, 64, 64, 0, 0, 0]),
|
|
513
|
+
polyphonicKeyPressureTable: new Uint8Array([64, 64, 64, 0, 0, 0]),
|
|
514
|
+
keyBasedInstrumentControlTable: new Int8Array(128 * 128), // [-64, 63]
|
|
441
515
|
};
|
|
442
516
|
});
|
|
443
517
|
return channels;
|
|
@@ -471,9 +545,8 @@ class Midy {
|
|
|
471
545
|
return audioBuffer;
|
|
472
546
|
}
|
|
473
547
|
}
|
|
474
|
-
|
|
548
|
+
createNoteBufferNode(audioBuffer, voiceParams) {
|
|
475
549
|
const bufferSource = new AudioBufferSourceNode(this.audioContext);
|
|
476
|
-
const audioBuffer = await this.createNoteBuffer(voiceParams, isSF3);
|
|
477
550
|
bufferSource.buffer = audioBuffer;
|
|
478
551
|
bufferSource.loop = voiceParams.sampleModes % 2 !== 0;
|
|
479
552
|
if (bufferSource.loop) {
|
|
@@ -505,10 +578,11 @@ class Midy {
|
|
|
505
578
|
const event = this.timeline[queueIndex];
|
|
506
579
|
if (event.startTime > t + this.lookAhead)
|
|
507
580
|
break;
|
|
581
|
+
const startTime = event.startTime + this.startDelay - offset;
|
|
508
582
|
switch (event.type) {
|
|
509
583
|
case "noteOn":
|
|
510
584
|
if (event.velocity !== 0) {
|
|
511
|
-
await this.scheduleNoteOn(event.channel, event.noteNumber, event.velocity,
|
|
585
|
+
await this.scheduleNoteOn(event.channel, event.noteNumber, event.velocity, startTime, event.portamento);
|
|
512
586
|
break;
|
|
513
587
|
}
|
|
514
588
|
/* falls through */
|
|
@@ -516,29 +590,29 @@ class Midy {
|
|
|
516
590
|
const portamentoTarget = this.findPortamentoTarget(queueIndex);
|
|
517
591
|
if (portamentoTarget)
|
|
518
592
|
portamentoTarget.portamento = true;
|
|
519
|
-
const notePromise = this.scheduleNoteRelease(this.omni ? 0 : event.channel, event.noteNumber, event.velocity,
|
|
593
|
+
const notePromise = this.scheduleNoteRelease(this.omni ? 0 : event.channel, event.noteNumber, event.velocity, startTime, portamentoTarget?.noteNumber, false);
|
|
520
594
|
if (notePromise) {
|
|
521
595
|
this.notePromises.push(notePromise);
|
|
522
596
|
}
|
|
523
597
|
break;
|
|
524
598
|
}
|
|
525
599
|
case "noteAftertouch":
|
|
526
|
-
this.handlePolyphonicKeyPressure(event.channel, event.noteNumber, event.amount);
|
|
600
|
+
this.handlePolyphonicKeyPressure(event.channel, event.noteNumber, event.amount, startTime);
|
|
527
601
|
break;
|
|
528
602
|
case "controller":
|
|
529
|
-
this.handleControlChange(this.omni ? 0 : event.channel, event.controllerType, event.value);
|
|
603
|
+
this.handleControlChange(this.omni ? 0 : event.channel, event.controllerType, event.value, startTime);
|
|
530
604
|
break;
|
|
531
605
|
case "programChange":
|
|
532
|
-
this.handleProgramChange(event.channel, event.programNumber);
|
|
606
|
+
this.handleProgramChange(event.channel, event.programNumber, startTime);
|
|
533
607
|
break;
|
|
534
608
|
case "channelAftertouch":
|
|
535
|
-
this.handleChannelPressure(event.channel, event.amount);
|
|
609
|
+
this.handleChannelPressure(event.channel, event.amount, startTime);
|
|
536
610
|
break;
|
|
537
611
|
case "pitchBend":
|
|
538
|
-
this.setPitchBend(event.channel, event.value + 8192);
|
|
612
|
+
this.setPitchBend(event.channel, event.value + 8192, startTime);
|
|
539
613
|
break;
|
|
540
614
|
case "sysEx":
|
|
541
|
-
this.handleSysEx(event.data);
|
|
615
|
+
this.handleSysEx(event.data, startTime);
|
|
542
616
|
}
|
|
543
617
|
queueIndex++;
|
|
544
618
|
}
|
|
@@ -565,6 +639,7 @@ class Midy {
|
|
|
565
639
|
await Promise.all(this.notePromises);
|
|
566
640
|
this.notePromises = [];
|
|
567
641
|
this.exclusiveClassMap.clear();
|
|
642
|
+
this.audioBufferCache.clear();
|
|
568
643
|
resolve();
|
|
569
644
|
return;
|
|
570
645
|
}
|
|
@@ -580,8 +655,9 @@ class Midy {
|
|
|
580
655
|
}
|
|
581
656
|
else if (this.isStopping) {
|
|
582
657
|
await this.stopNotes(0, true);
|
|
583
|
-
this.exclusiveClassMap.clear();
|
|
584
658
|
this.notePromises = [];
|
|
659
|
+
this.exclusiveClassMap.clear();
|
|
660
|
+
this.audioBufferCache.clear();
|
|
585
661
|
resolve();
|
|
586
662
|
this.isStopping = false;
|
|
587
663
|
this.isPaused = false;
|
|
@@ -612,6 +688,9 @@ class Midy {
|
|
|
612
688
|
secondToTicks(second, secondsPerBeat) {
|
|
613
689
|
return second * this.ticksPerBeat / secondsPerBeat;
|
|
614
690
|
}
|
|
691
|
+
getAudioBufferId(programNumber, noteNumber, velocity) {
|
|
692
|
+
return `${programNumber}:${noteNumber}:${velocity}`;
|
|
693
|
+
}
|
|
615
694
|
extractMidiData(midi) {
|
|
616
695
|
const instruments = new Set();
|
|
617
696
|
const timeline = [];
|
|
@@ -633,6 +712,8 @@ class Midy {
|
|
|
633
712
|
switch (event.type) {
|
|
634
713
|
case "noteOn": {
|
|
635
714
|
const channel = tmpChannels[event.channel];
|
|
715
|
+
const audioBufferId = this.getAudioBufferId(channel.programNumber, event.noteNumber, event.velocity);
|
|
716
|
+
this.audioBufferCounter.set(audioBufferId, (this.audioBufferCounter.get(audioBufferId) ?? 0) + 1);
|
|
636
717
|
if (channel.programNumber < 0) {
|
|
637
718
|
channel.programNumber = event.programNumber;
|
|
638
719
|
switch (channel.bankMSB) {
|
|
@@ -682,6 +763,10 @@ class Midy {
|
|
|
682
763
|
timeline.push(event);
|
|
683
764
|
}
|
|
684
765
|
}
|
|
766
|
+
for (const [audioBufferId, count] of this.audioBufferCounter) {
|
|
767
|
+
if (count === 1)
|
|
768
|
+
this.audioBufferCounter.delete(audioBufferId);
|
|
769
|
+
}
|
|
685
770
|
const priority = {
|
|
686
771
|
controller: 0,
|
|
687
772
|
sysEx: 1,
|
|
@@ -774,8 +859,20 @@ class Midy {
|
|
|
774
859
|
const now = this.audioContext.currentTime;
|
|
775
860
|
return this.resumeTime + now - this.startTime - this.startDelay;
|
|
776
861
|
}
|
|
862
|
+
processScheduledNotes(channel, scheduleTime, callback) {
|
|
863
|
+
channel.scheduledNotes.forEach((noteList) => {
|
|
864
|
+
for (let i = 0; i < noteList.length; i++) {
|
|
865
|
+
const note = noteList[i];
|
|
866
|
+
if (!note)
|
|
867
|
+
continue;
|
|
868
|
+
if (scheduleTime < note.startTime)
|
|
869
|
+
continue;
|
|
870
|
+
callback(note);
|
|
871
|
+
}
|
|
872
|
+
});
|
|
873
|
+
}
|
|
777
874
|
getActiveNotes(channel, time) {
|
|
778
|
-
const activeNotes = new
|
|
875
|
+
const activeNotes = new SparseMap(128);
|
|
779
876
|
channel.scheduledNotes.forEach((noteList) => {
|
|
780
877
|
const activeNote = this.getActiveNote(noteList, time);
|
|
781
878
|
if (activeNote) {
|
|
@@ -955,14 +1052,15 @@ class Midy {
|
|
|
955
1052
|
const note = noteList[i];
|
|
956
1053
|
if (!note)
|
|
957
1054
|
continue;
|
|
958
|
-
this.updateDetune(channel, note
|
|
1055
|
+
this.updateDetune(channel, note);
|
|
959
1056
|
}
|
|
960
1057
|
});
|
|
961
1058
|
}
|
|
962
|
-
updateDetune(channel, note
|
|
1059
|
+
updateDetune(channel, note) {
|
|
963
1060
|
const now = this.audioContext.currentTime;
|
|
964
1061
|
const noteDetune = this.calcNoteDetune(channel, note);
|
|
965
|
-
const
|
|
1062
|
+
const pitchControl = this.getPitchControl(channel, note);
|
|
1063
|
+
const detune = channel.detune + noteDetune + pitchControl;
|
|
966
1064
|
note.bufferSource.detune
|
|
967
1065
|
.cancelScheduledValues(now)
|
|
968
1066
|
.setValueAtTime(detune, now);
|
|
@@ -984,12 +1082,12 @@ class Midy {
|
|
|
984
1082
|
.setValueAtTime(0, volDelay)
|
|
985
1083
|
.linearRampToValueAtTime(sustainVolume, portamentoTime);
|
|
986
1084
|
}
|
|
987
|
-
setVolumeEnvelope(channel, note
|
|
1085
|
+
setVolumeEnvelope(channel, note) {
|
|
988
1086
|
const now = this.audioContext.currentTime;
|
|
989
1087
|
const state = channel.state;
|
|
990
1088
|
const { voiceParams, startTime } = note;
|
|
991
1089
|
const attackVolume = this.cbToRatio(-voiceParams.initialAttenuation) *
|
|
992
|
-
(1 +
|
|
1090
|
+
(1 + this.getAmplitudeControl(channel, note));
|
|
993
1091
|
const sustainVolume = attackVolume * (1 - voiceParams.volSustain);
|
|
994
1092
|
const volDelay = startTime + voiceParams.volDelay;
|
|
995
1093
|
const volAttack = volDelay + voiceParams.volAttack * state.attackTime * 2;
|
|
@@ -1003,20 +1101,20 @@ class Midy {
|
|
|
1003
1101
|
.setValueAtTime(attackVolume, volHold)
|
|
1004
1102
|
.linearRampToValueAtTime(sustainVolume, volDecay);
|
|
1005
1103
|
}
|
|
1006
|
-
setPitchEnvelope(note) {
|
|
1007
|
-
|
|
1104
|
+
setPitchEnvelope(note, scheduleTime) {
|
|
1105
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1008
1106
|
const { voiceParams } = note;
|
|
1009
1107
|
const baseRate = voiceParams.playbackRate;
|
|
1010
1108
|
note.bufferSource.playbackRate
|
|
1011
|
-
.cancelScheduledValues(
|
|
1012
|
-
.setValueAtTime(baseRate,
|
|
1109
|
+
.cancelScheduledValues(scheduleTime)
|
|
1110
|
+
.setValueAtTime(baseRate, scheduleTime);
|
|
1013
1111
|
const modEnvToPitch = voiceParams.modEnvToPitch;
|
|
1014
1112
|
if (modEnvToPitch === 0)
|
|
1015
1113
|
return;
|
|
1016
1114
|
const basePitch = this.rateToCent(baseRate);
|
|
1017
1115
|
const peekPitch = basePitch + modEnvToPitch;
|
|
1018
1116
|
const peekRate = this.centToRate(peekPitch);
|
|
1019
|
-
const modDelay = startTime + voiceParams.modDelay;
|
|
1117
|
+
const modDelay = note.startTime + voiceParams.modDelay;
|
|
1020
1118
|
const modAttack = modDelay + voiceParams.modAttack;
|
|
1021
1119
|
const modHold = modAttack + voiceParams.modHold;
|
|
1022
1120
|
const modDecay = modHold + voiceParams.modDecay;
|
|
@@ -1053,13 +1151,14 @@ class Midy {
|
|
|
1053
1151
|
.setValueAtTime(adjustedBaseFreq, modDelay)
|
|
1054
1152
|
.linearRampToValueAtTime(adjustedSustainFreq, portamentoTime);
|
|
1055
1153
|
}
|
|
1056
|
-
setFilterEnvelope(channel, note
|
|
1154
|
+
setFilterEnvelope(channel, note) {
|
|
1057
1155
|
const now = this.audioContext.currentTime;
|
|
1058
1156
|
const state = channel.state;
|
|
1059
1157
|
const { voiceParams, noteNumber, startTime } = note;
|
|
1060
1158
|
const softPedalFactor = 1 -
|
|
1061
1159
|
(0.1 + (noteNumber / 127) * 0.2) * state.softPedal;
|
|
1062
|
-
const baseCent = voiceParams.initialFilterFc +
|
|
1160
|
+
const baseCent = voiceParams.initialFilterFc +
|
|
1161
|
+
this.getFilterCutoffControl(channel, note);
|
|
1063
1162
|
const baseFreq = this.centToHz(baseCent) * softPedalFactor *
|
|
1064
1163
|
state.brightness * 2;
|
|
1065
1164
|
const peekFreq = this.centToHz(baseCent + voiceParams.modEnvToFilterFc) *
|
|
@@ -1090,9 +1189,9 @@ class Midy {
|
|
|
1090
1189
|
gain: voiceParams.modLfoToFilterFc,
|
|
1091
1190
|
});
|
|
1092
1191
|
note.modulationDepth = new GainNode(this.audioContext);
|
|
1093
|
-
this.setModLfoToPitch(channel, note
|
|
1192
|
+
this.setModLfoToPitch(channel, note);
|
|
1094
1193
|
note.volumeDepth = new GainNode(this.audioContext);
|
|
1095
|
-
this.setModLfoToVolume(
|
|
1194
|
+
this.setModLfoToVolume(channel, note);
|
|
1096
1195
|
note.modulationLFO.start(startTime + voiceParams.delayModLFO);
|
|
1097
1196
|
note.modulationLFO.connect(note.filterDepth);
|
|
1098
1197
|
note.filterDepth.connect(note.filterNode.frequency);
|
|
@@ -1113,12 +1212,31 @@ class Midy {
|
|
|
1113
1212
|
note.vibratoLFO.connect(note.vibratoDepth);
|
|
1114
1213
|
note.vibratoDepth.connect(note.bufferSource.detune);
|
|
1115
1214
|
}
|
|
1215
|
+
async getAudioBuffer(program, noteNumber, velocity, voiceParams, isSF3) {
|
|
1216
|
+
const audioBufferId = this.getAudioBufferId(program, noteNumber, velocity);
|
|
1217
|
+
const cache = this.audioBufferCache.get(audioBufferId);
|
|
1218
|
+
if (cache) {
|
|
1219
|
+
cache.counter += 1;
|
|
1220
|
+
if (cache.maxCount <= cache.counter) {
|
|
1221
|
+
this.audioBufferCache.delete(audioBufferId);
|
|
1222
|
+
}
|
|
1223
|
+
return cache.audioBuffer;
|
|
1224
|
+
}
|
|
1225
|
+
else {
|
|
1226
|
+
const maxCount = this.audioBufferCounter.get(audioBufferId) ?? 0;
|
|
1227
|
+
const audioBuffer = await this.createNoteBuffer(voiceParams, isSF3);
|
|
1228
|
+
const cache = { audioBuffer, maxCount, counter: 1 };
|
|
1229
|
+
this.audioBufferCache.set(audioBufferId, cache);
|
|
1230
|
+
return audioBuffer;
|
|
1231
|
+
}
|
|
1232
|
+
}
|
|
1116
1233
|
async createNote(channel, voice, noteNumber, velocity, startTime, portamento, isSF3) {
|
|
1117
1234
|
const state = channel.state;
|
|
1118
1235
|
const controllerState = this.getControllerState(channel, noteNumber, velocity);
|
|
1119
1236
|
const voiceParams = voice.getAllParams(controllerState);
|
|
1120
1237
|
const note = new Note(noteNumber, velocity, startTime, voice, voiceParams);
|
|
1121
|
-
|
|
1238
|
+
const audioBuffer = await this.getAudioBuffer(channel.program, noteNumber, velocity, voiceParams, isSF3);
|
|
1239
|
+
note.bufferSource = this.createNoteBufferNode(audioBuffer, voiceParams);
|
|
1122
1240
|
note.volumeNode = new GainNode(this.audioContext);
|
|
1123
1241
|
note.gainL = new GainNode(this.audioContext);
|
|
1124
1242
|
note.gainR = new GainNode(this.audioContext);
|
|
@@ -1134,8 +1252,8 @@ class Midy {
|
|
|
1134
1252
|
}
|
|
1135
1253
|
else {
|
|
1136
1254
|
note.portamento = false;
|
|
1137
|
-
this.setVolumeEnvelope(channel, note
|
|
1138
|
-
this.setFilterEnvelope(channel, note
|
|
1255
|
+
this.setVolumeEnvelope(channel, note);
|
|
1256
|
+
this.setFilterEnvelope(channel, note);
|
|
1139
1257
|
}
|
|
1140
1258
|
if (0 < state.vibratoDepth) {
|
|
1141
1259
|
this.startVibrato(channel, note, startTime);
|
|
@@ -1178,10 +1296,10 @@ class Midy {
|
|
|
1178
1296
|
if (soundFontIndex === undefined)
|
|
1179
1297
|
return;
|
|
1180
1298
|
const soundFont = this.soundFonts[soundFontIndex];
|
|
1181
|
-
const isSF3 = soundFont.parsed.info.version.major === 3;
|
|
1182
1299
|
const voice = soundFont.getVoice(bankNumber, channel.program, noteNumber, velocity);
|
|
1183
1300
|
if (!voice)
|
|
1184
1301
|
return;
|
|
1302
|
+
const isSF3 = soundFont.parsed.info.version.major === 3;
|
|
1185
1303
|
const note = await this.createNote(channel, voice, noteNumber, velocity, startTime, portamento, isSF3);
|
|
1186
1304
|
note.gainL.connect(channel.gainL);
|
|
1187
1305
|
note.gainR.connect(channel.gainR);
|
|
@@ -1347,15 +1465,16 @@ class Midy {
|
|
|
1347
1465
|
console.warn(`Unsupported MIDI message: ${messageType.toString(16)}`);
|
|
1348
1466
|
}
|
|
1349
1467
|
}
|
|
1350
|
-
handlePolyphonicKeyPressure(channelNumber, noteNumber, pressure) {
|
|
1351
|
-
|
|
1468
|
+
handlePolyphonicKeyPressure(channelNumber, noteNumber, pressure, startTime) {
|
|
1469
|
+
if (!startTime)
|
|
1470
|
+
startTime = this.audioContext.currentTime;
|
|
1352
1471
|
const channel = this.channels[channelNumber];
|
|
1353
1472
|
channel.state.polyphonicKeyPressure = pressure / 127;
|
|
1354
1473
|
const table = channel.polyphonicKeyPressureTable;
|
|
1355
|
-
const activeNotes = this.getActiveNotes(channel,
|
|
1474
|
+
const activeNotes = this.getActiveNotes(channel, startTime);
|
|
1356
1475
|
if (activeNotes.has(noteNumber)) {
|
|
1357
1476
|
const note = activeNotes.get(noteNumber);
|
|
1358
|
-
this.
|
|
1477
|
+
this.setControllerParameters(channel, note, table);
|
|
1359
1478
|
}
|
|
1360
1479
|
// this.applyVoiceParams(channel, 10);
|
|
1361
1480
|
}
|
|
@@ -1364,7 +1483,9 @@ class Midy {
|
|
|
1364
1483
|
channel.bank = channel.bankMSB * 128 + channel.bankLSB;
|
|
1365
1484
|
channel.program = program;
|
|
1366
1485
|
}
|
|
1367
|
-
handleChannelPressure(channelNumber, value) {
|
|
1486
|
+
handleChannelPressure(channelNumber, value, startTime) {
|
|
1487
|
+
if (!startTime)
|
|
1488
|
+
startTime = this.audioContext.currentTime;
|
|
1368
1489
|
const channel = this.channels[channelNumber];
|
|
1369
1490
|
const prev = channel.state.channelPressure;
|
|
1370
1491
|
const next = value / 127;
|
|
@@ -1374,13 +1495,8 @@ class Midy {
|
|
|
1374
1495
|
channel.detune += pressureDepth * (next - prev);
|
|
1375
1496
|
}
|
|
1376
1497
|
const table = channel.channelPressureTable;
|
|
1377
|
-
channel.
|
|
1378
|
-
|
|
1379
|
-
const note = noteList[i];
|
|
1380
|
-
if (!note)
|
|
1381
|
-
continue;
|
|
1382
|
-
this.applyDestinationSettings(channel, note, table);
|
|
1383
|
-
}
|
|
1498
|
+
this.getActiveNotes(channel, startTime).forEach((note) => {
|
|
1499
|
+
this.setControllerParameters(channel, note, table);
|
|
1384
1500
|
});
|
|
1385
1501
|
// this.applyVoiceParams(channel, 13);
|
|
1386
1502
|
}
|
|
@@ -1398,9 +1514,10 @@ class Midy {
|
|
|
1398
1514
|
this.updateChannelDetune(channel);
|
|
1399
1515
|
this.applyVoiceParams(channel, 14);
|
|
1400
1516
|
}
|
|
1401
|
-
setModLfoToPitch(channel, note
|
|
1517
|
+
setModLfoToPitch(channel, note) {
|
|
1402
1518
|
const now = this.audioContext.currentTime;
|
|
1403
|
-
const modLfoToPitch = note.voiceParams.modLfoToPitch +
|
|
1519
|
+
const modLfoToPitch = note.voiceParams.modLfoToPitch +
|
|
1520
|
+
this.getLFOPitchDepth(channel, note);
|
|
1404
1521
|
const baseDepth = Math.abs(modLfoToPitch) + channel.state.modulationDepth;
|
|
1405
1522
|
const modulationDepth = baseDepth * Math.sign(modLfoToPitch);
|
|
1406
1523
|
note.modulationDepth.gain
|
|
@@ -1417,18 +1534,20 @@ class Midy {
|
|
|
1417
1534
|
.cancelScheduledValues(now)
|
|
1418
1535
|
.setValueAtTime(vibratoDepth * vibratoDepthSign, now);
|
|
1419
1536
|
}
|
|
1420
|
-
setModLfoToFilterFc(
|
|
1537
|
+
setModLfoToFilterFc(channel, note) {
|
|
1421
1538
|
const now = this.audioContext.currentTime;
|
|
1422
|
-
const modLfoToFilterFc = note.voiceParams.modLfoToFilterFc +
|
|
1539
|
+
const modLfoToFilterFc = note.voiceParams.modLfoToFilterFc +
|
|
1540
|
+
this.getLFOFilterDepth(channel, note);
|
|
1423
1541
|
note.filterDepth.gain
|
|
1424
1542
|
.cancelScheduledValues(now)
|
|
1425
1543
|
.setValueAtTime(modLfoToFilterFc, now);
|
|
1426
1544
|
}
|
|
1427
|
-
setModLfoToVolume(
|
|
1545
|
+
setModLfoToVolume(channel, note) {
|
|
1428
1546
|
const now = this.audioContext.currentTime;
|
|
1429
1547
|
const modLfoToVolume = note.voiceParams.modLfoToVolume;
|
|
1430
1548
|
const baseDepth = this.cbToRatio(Math.abs(modLfoToVolume)) - 1;
|
|
1431
|
-
const volumeDepth = baseDepth * Math.sign(modLfoToVolume) *
|
|
1549
|
+
const volumeDepth = baseDepth * Math.sign(modLfoToVolume) *
|
|
1550
|
+
(1 + this.getLFOAmplitudeDepth(channel, note));
|
|
1432
1551
|
note.volumeDepth.gain
|
|
1433
1552
|
.cancelScheduledValues(now)
|
|
1434
1553
|
.setValueAtTime(volumeDepth, now);
|
|
@@ -1512,7 +1631,7 @@ class Midy {
|
|
|
1512
1631
|
return {
|
|
1513
1632
|
modLfoToPitch: (channel, note, _prevValue) => {
|
|
1514
1633
|
if (0 < channel.state.modulationDepth) {
|
|
1515
|
-
this.setModLfoToPitch(channel, note
|
|
1634
|
+
this.setModLfoToPitch(channel, note);
|
|
1516
1635
|
}
|
|
1517
1636
|
},
|
|
1518
1637
|
vibLfoToPitch: (channel, note, _prevValue) => {
|
|
@@ -1522,12 +1641,12 @@ class Midy {
|
|
|
1522
1641
|
},
|
|
1523
1642
|
modLfoToFilterFc: (channel, note, _prevValue) => {
|
|
1524
1643
|
if (0 < channel.state.modulationDepth) {
|
|
1525
|
-
this.setModLfoToFilterFc(
|
|
1644
|
+
this.setModLfoToFilterFc(channel, note);
|
|
1526
1645
|
}
|
|
1527
1646
|
},
|
|
1528
1647
|
modLfoToVolume: (channel, note, _prevValue) => {
|
|
1529
1648
|
if (0 < channel.state.modulationDepth) {
|
|
1530
|
-
this.setModLfoToVolume(
|
|
1649
|
+
this.setModLfoToVolume(channel, note);
|
|
1531
1650
|
}
|
|
1532
1651
|
},
|
|
1533
1652
|
chorusEffectsSend: (channel, note, prevValue) => {
|
|
@@ -1597,7 +1716,7 @@ class Midy {
|
|
|
1597
1716
|
this.setPortamentoStartFilterEnvelope(channel, note);
|
|
1598
1717
|
}
|
|
1599
1718
|
else {
|
|
1600
|
-
this.setFilterEnvelope(channel, note
|
|
1719
|
+
this.setFilterEnvelope(channel, note);
|
|
1601
1720
|
}
|
|
1602
1721
|
this.setPitchEnvelope(note);
|
|
1603
1722
|
}
|
|
@@ -1611,7 +1730,7 @@ class Midy {
|
|
|
1611
1730
|
if (key in voiceParams)
|
|
1612
1731
|
noteVoiceParams[key] = voiceParams[key];
|
|
1613
1732
|
}
|
|
1614
|
-
this.setVolumeEnvelope(channel, note
|
|
1733
|
+
this.setVolumeEnvelope(channel, note);
|
|
1615
1734
|
}
|
|
1616
1735
|
}
|
|
1617
1736
|
}
|
|
@@ -1655,10 +1774,10 @@ class Midy {
|
|
|
1655
1774
|
127: this.polyOn,
|
|
1656
1775
|
};
|
|
1657
1776
|
}
|
|
1658
|
-
handleControlChange(channelNumber, controllerType, value) {
|
|
1777
|
+
handleControlChange(channelNumber, controllerType, value, startTime) {
|
|
1659
1778
|
const handler = this.controlChangeHandlers[controllerType];
|
|
1660
1779
|
if (handler) {
|
|
1661
|
-
handler.call(this, channelNumber, value);
|
|
1780
|
+
handler.call(this, channelNumber, value, startTime);
|
|
1662
1781
|
const channel = this.channels[channelNumber];
|
|
1663
1782
|
this.applyVoiceParams(channel, controllerType + 128);
|
|
1664
1783
|
this.applyControlTable(channel, controllerType);
|
|
@@ -1670,55 +1789,45 @@ class Midy {
|
|
|
1670
1789
|
setBankMSB(channelNumber, msb) {
|
|
1671
1790
|
this.channels[channelNumber].bankMSB = msb;
|
|
1672
1791
|
}
|
|
1673
|
-
updateModulation(channel) {
|
|
1674
|
-
|
|
1792
|
+
updateModulation(channel, scheduleTime) {
|
|
1793
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1675
1794
|
const depth = channel.state.modulationDepth * channel.modulationDepthRange;
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
}
|
|
1684
|
-
else {
|
|
1685
|
-
this.setPitchEnvelope(note);
|
|
1686
|
-
this.startModulation(channel, note, now);
|
|
1687
|
-
}
|
|
1795
|
+
this.processScheduledNotes(channel, scheduleTime, (note) => {
|
|
1796
|
+
if (note.modulationDepth) {
|
|
1797
|
+
note.modulationDepth.gain.setValueAtTime(depth, scheduleTime);
|
|
1798
|
+
}
|
|
1799
|
+
else {
|
|
1800
|
+
this.setPitchEnvelope(note, scheduleTime);
|
|
1801
|
+
this.startModulation(channel, note, scheduleTime);
|
|
1688
1802
|
}
|
|
1689
1803
|
});
|
|
1690
1804
|
}
|
|
1691
|
-
setModulationDepth(channelNumber, modulation) {
|
|
1805
|
+
setModulationDepth(channelNumber, modulation, scheduleTime) {
|
|
1692
1806
|
const channel = this.channels[channelNumber];
|
|
1693
1807
|
channel.state.modulationDepth = modulation / 127;
|
|
1694
|
-
this.updateModulation(channel);
|
|
1808
|
+
this.updateModulation(channel, scheduleTime);
|
|
1695
1809
|
}
|
|
1696
1810
|
setPortamentoTime(channelNumber, portamentoTime) {
|
|
1697
1811
|
const channel = this.channels[channelNumber];
|
|
1698
1812
|
const factor = 5 * Math.log(10) / 127;
|
|
1699
1813
|
channel.state.portamentoTime = Math.exp(factor * portamentoTime);
|
|
1700
1814
|
}
|
|
1701
|
-
setKeyBasedVolume(channel) {
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
if (!note)
|
|
1707
|
-
continue;
|
|
1708
|
-
const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 7);
|
|
1709
|
-
if (keyBasedValue === 0)
|
|
1710
|
-
continue;
|
|
1815
|
+
setKeyBasedVolume(channel, scheduleTime) {
|
|
1816
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1817
|
+
this.processScheduledNotes(channel, scheduleTime, (note) => {
|
|
1818
|
+
const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 7);
|
|
1819
|
+
if (keyBasedValue !== 0) {
|
|
1711
1820
|
note.volumeNode.gain
|
|
1712
|
-
.cancelScheduledValues(
|
|
1713
|
-
.setValueAtTime(1 + keyBasedValue,
|
|
1821
|
+
.cancelScheduledValues(scheduleTime)
|
|
1822
|
+
.setValueAtTime(1 + keyBasedValue, scheduleTime);
|
|
1714
1823
|
}
|
|
1715
1824
|
});
|
|
1716
1825
|
}
|
|
1717
|
-
setVolume(channelNumber, volume) {
|
|
1826
|
+
setVolume(channelNumber, volume, scheduleTime) {
|
|
1718
1827
|
const channel = this.channels[channelNumber];
|
|
1719
1828
|
channel.state.volume = volume / 127;
|
|
1720
|
-
this.updateChannelVolume(channel);
|
|
1721
|
-
this.setKeyBasedVolume(channel);
|
|
1829
|
+
this.updateChannelVolume(channel, scheduleTime);
|
|
1830
|
+
this.setKeyBasedVolume(channel, scheduleTime);
|
|
1722
1831
|
}
|
|
1723
1832
|
panToGain(pan) {
|
|
1724
1833
|
const theta = Math.PI / 2 * Math.max(0, pan * 127 - 1) / 126;
|
|
@@ -1727,36 +1836,31 @@ class Midy {
|
|
|
1727
1836
|
gainRight: Math.sin(theta),
|
|
1728
1837
|
};
|
|
1729
1838
|
}
|
|
1730
|
-
setKeyBasedPan(channel) {
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
if (!note)
|
|
1736
|
-
continue;
|
|
1737
|
-
const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 10);
|
|
1738
|
-
if (keyBasedValue === 0)
|
|
1739
|
-
continue;
|
|
1839
|
+
setKeyBasedPan(channel, scheduleTime) {
|
|
1840
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1841
|
+
this.processScheduledNotes(channel, scheduleTime, (note) => {
|
|
1842
|
+
const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 10);
|
|
1843
|
+
if (keyBasedValue !== 0) {
|
|
1740
1844
|
const { gainLeft, gainRight } = this.panToGain((keyBasedValue + 1) / 2);
|
|
1741
1845
|
note.gainL.gain
|
|
1742
|
-
.cancelScheduledValues(
|
|
1743
|
-
.setValueAtTime(gainLeft,
|
|
1846
|
+
.cancelScheduledValues(scheduleTime)
|
|
1847
|
+
.setValueAtTime(gainLeft, scheduleTime);
|
|
1744
1848
|
note.gainR.gain
|
|
1745
|
-
.cancelScheduledValues(
|
|
1746
|
-
.setValueAtTime(gainRight,
|
|
1849
|
+
.cancelScheduledValues(scheduleTime)
|
|
1850
|
+
.setValueAtTime(gainRight, scheduleTime);
|
|
1747
1851
|
}
|
|
1748
1852
|
});
|
|
1749
1853
|
}
|
|
1750
|
-
setPan(channelNumber, pan) {
|
|
1854
|
+
setPan(channelNumber, pan, scheduleTime) {
|
|
1751
1855
|
const channel = this.channels[channelNumber];
|
|
1752
1856
|
channel.state.pan = pan / 127;
|
|
1753
|
-
this.updateChannelVolume(channel);
|
|
1754
|
-
this.setKeyBasedPan(channel);
|
|
1857
|
+
this.updateChannelVolume(channel, scheduleTime);
|
|
1858
|
+
this.setKeyBasedPan(channel, scheduleTime);
|
|
1755
1859
|
}
|
|
1756
|
-
setExpression(channelNumber, expression) {
|
|
1860
|
+
setExpression(channelNumber, expression, scheduleTime) {
|
|
1757
1861
|
const channel = this.channels[channelNumber];
|
|
1758
1862
|
channel.state.expression = expression / 127;
|
|
1759
|
-
this.updateChannelVolume(channel);
|
|
1863
|
+
this.updateChannelVolume(channel, scheduleTime);
|
|
1760
1864
|
}
|
|
1761
1865
|
setBankLSB(channelNumber, lsb) {
|
|
1762
1866
|
this.channels[channelNumber].bankLSB = lsb;
|
|
@@ -1791,8 +1895,7 @@ class Midy {
|
|
|
1791
1895
|
channel.state.sostenutoPedal = value / 127;
|
|
1792
1896
|
if (64 <= value) {
|
|
1793
1897
|
const now = this.audioContext.currentTime;
|
|
1794
|
-
|
|
1795
|
-
channel.sostenutoNotes = new Map(activeNotes);
|
|
1898
|
+
channel.sostenutoNotes = this.getActiveNotes(channel, now);
|
|
1796
1899
|
}
|
|
1797
1900
|
else {
|
|
1798
1901
|
this.releaseSostenutoPedal(channelNumber, value);
|
|
@@ -1832,7 +1935,7 @@ class Midy {
|
|
|
1832
1935
|
continue;
|
|
1833
1936
|
if (note.startTime < now)
|
|
1834
1937
|
continue;
|
|
1835
|
-
this.setVolumeEnvelope(channel, note
|
|
1938
|
+
this.setVolumeEnvelope(channel, note);
|
|
1836
1939
|
}
|
|
1837
1940
|
});
|
|
1838
1941
|
}
|
|
@@ -1848,7 +1951,7 @@ class Midy {
|
|
|
1848
1951
|
this.setPortamentoStartFilterEnvelope(channel, note);
|
|
1849
1952
|
}
|
|
1850
1953
|
else {
|
|
1851
|
-
this.setFilterEnvelope(channel, note
|
|
1954
|
+
this.setFilterEnvelope(channel, note);
|
|
1852
1955
|
}
|
|
1853
1956
|
}
|
|
1854
1957
|
});
|
|
@@ -1861,7 +1964,7 @@ class Midy {
|
|
|
1861
1964
|
const note = noteList[i];
|
|
1862
1965
|
if (!note)
|
|
1863
1966
|
continue;
|
|
1864
|
-
this.setVolumeEnvelope(channel, note
|
|
1967
|
+
this.setVolumeEnvelope(channel, note);
|
|
1865
1968
|
}
|
|
1866
1969
|
});
|
|
1867
1970
|
}
|
|
@@ -2173,7 +2276,10 @@ class Midy {
|
|
|
2173
2276
|
switch (data[3]) {
|
|
2174
2277
|
case 8:
|
|
2175
2278
|
// https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca21.pdf
|
|
2176
|
-
return this.handleScaleOctaveTuning1ByteFormatSysEx(data);
|
|
2279
|
+
return this.handleScaleOctaveTuning1ByteFormatSysEx(data, false);
|
|
2280
|
+
case 9:
|
|
2281
|
+
// https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca21.pdf
|
|
2282
|
+
return this.handleScaleOctaveTuning2ByteFormatSysEx(data, false);
|
|
2177
2283
|
default:
|
|
2178
2284
|
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
2179
2285
|
}
|
|
@@ -2235,8 +2341,10 @@ class Midy {
|
|
|
2235
2341
|
case 8:
|
|
2236
2342
|
switch (data[3]) {
|
|
2237
2343
|
case 8: // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca21.pdf
|
|
2238
|
-
|
|
2239
|
-
|
|
2344
|
+
return this.handleScaleOctaveTuning1ByteFormatSysEx(data, true);
|
|
2345
|
+
case 9:
|
|
2346
|
+
// https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca21.pdf
|
|
2347
|
+
return this.handleScaleOctaveTuning2ByteFormatSysEx(data, true);
|
|
2240
2348
|
default:
|
|
2241
2349
|
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
2242
2350
|
}
|
|
@@ -2506,8 +2614,8 @@ class Midy {
|
|
|
2506
2614
|
}
|
|
2507
2615
|
return bitmap;
|
|
2508
2616
|
}
|
|
2509
|
-
handleScaleOctaveTuning1ByteFormatSysEx(data) {
|
|
2510
|
-
if (data.length <
|
|
2617
|
+
handleScaleOctaveTuning1ByteFormatSysEx(data, realtime) {
|
|
2618
|
+
if (data.length < 19) {
|
|
2511
2619
|
console.error("Data length is too short");
|
|
2512
2620
|
return;
|
|
2513
2621
|
}
|
|
@@ -2515,67 +2623,92 @@ class Midy {
|
|
|
2515
2623
|
for (let i = 0; i < channelBitmap.length; i++) {
|
|
2516
2624
|
if (!channelBitmap[i])
|
|
2517
2625
|
continue;
|
|
2626
|
+
const channel = this.channels[i];
|
|
2518
2627
|
for (let j = 0; j < 12; j++) {
|
|
2519
|
-
const
|
|
2520
|
-
|
|
2628
|
+
const centValue = data[j + 7] - 64;
|
|
2629
|
+
channel.scaleOctaveTuningTable[j] = centValue;
|
|
2521
2630
|
}
|
|
2631
|
+
if (realtime)
|
|
2632
|
+
this.updateChannelDetune(channel);
|
|
2522
2633
|
}
|
|
2523
2634
|
}
|
|
2524
|
-
|
|
2525
|
-
if (
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
: 0;
|
|
2529
|
-
const pressure = (polyphonicKeyPressure - 64) / 37.5; // 2400 / 64;
|
|
2530
|
-
this.updateDetune(channel, note, pressure);
|
|
2635
|
+
handleScaleOctaveTuning2ByteFormatSysEx(data, realtime) {
|
|
2636
|
+
if (data.length < 31) {
|
|
2637
|
+
console.error("Data length is too short");
|
|
2638
|
+
return;
|
|
2531
2639
|
}
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
const
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
channel.state.channelPressure;
|
|
2545
|
-
const polyphonicKeyPressure = (0 < note.pressure)
|
|
2546
|
-
? channel.polyphonicKeyPressureTable[2] * note.pressure
|
|
2547
|
-
: 0;
|
|
2548
|
-
const pressure = (channelPressure + polyphonicKeyPressure) / 128;
|
|
2549
|
-
this.setVolumeEnvelope(channel, note, pressure);
|
|
2640
|
+
const channelBitmap = this.getChannelBitmap(data);
|
|
2641
|
+
for (let i = 0; i < channelBitmap.length; i++) {
|
|
2642
|
+
if (!channelBitmap[i])
|
|
2643
|
+
continue;
|
|
2644
|
+
const channel = this.channels[i];
|
|
2645
|
+
for (let j = 0; j < 12; j++) {
|
|
2646
|
+
const index = 7 + j * 2;
|
|
2647
|
+
const msb = data[index] & 0x7F;
|
|
2648
|
+
const lsb = data[index + 1] & 0x7F;
|
|
2649
|
+
const value14bit = msb * 128 + lsb;
|
|
2650
|
+
const centValue = (value14bit - 8192) / 8.192;
|
|
2651
|
+
channel.scaleOctaveTuningTable[j] = centValue;
|
|
2550
2652
|
}
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2653
|
+
if (realtime)
|
|
2654
|
+
this.updateChannelDetune(channel);
|
|
2655
|
+
}
|
|
2656
|
+
}
|
|
2657
|
+
getPitchControl(channel, note) {
|
|
2658
|
+
const polyphonicKeyPressure = (channel.polyphonicKeyPressureTable[0] - 64) *
|
|
2659
|
+
note.pressure;
|
|
2660
|
+
return polyphonicKeyPressure * note.pressure / 37.5; // 2400 / 64;
|
|
2661
|
+
}
|
|
2662
|
+
getFilterCutoffControl(channel, note) {
|
|
2663
|
+
const channelPressure = (channel.channelPressureTable[1] - 64) *
|
|
2664
|
+
channel.state.channelPressure;
|
|
2665
|
+
const polyphonicKeyPressure = (channel.polyphonicKeyPressureTable[1] - 64) *
|
|
2666
|
+
note.pressure;
|
|
2667
|
+
return (channelPressure + polyphonicKeyPressure) * 15;
|
|
2668
|
+
}
|
|
2669
|
+
getAmplitudeControl(channel, note) {
|
|
2670
|
+
const channelPressure = channel.channelPressureTable[2] *
|
|
2671
|
+
channel.state.channelPressure;
|
|
2672
|
+
const polyphonicKeyPressure = channel.polyphonicKeyPressureTable[2] *
|
|
2673
|
+
note.pressure;
|
|
2674
|
+
return (channelPressure + polyphonicKeyPressure) / 128;
|
|
2675
|
+
}
|
|
2676
|
+
getLFOPitchDepth(channel, note) {
|
|
2677
|
+
const channelPressure = channel.channelPressureTable[3] *
|
|
2678
|
+
channel.state.channelPressure;
|
|
2679
|
+
const polyphonicKeyPressure = channel.polyphonicKeyPressureTable[3] *
|
|
2680
|
+
note.pressure;
|
|
2681
|
+
return (channelPressure + polyphonicKeyPressure) / 254 * 600;
|
|
2682
|
+
}
|
|
2683
|
+
getLFOFilterDepth(channel, note) {
|
|
2684
|
+
const channelPressure = channel.channelPressureTable[4] *
|
|
2685
|
+
channel.state.channelPressure;
|
|
2686
|
+
const polyphonicKeyPressure = channel.polyphonicKeyPressureTable[4] *
|
|
2687
|
+
note.pressure;
|
|
2688
|
+
return (channelPressure + polyphonicKeyPressure) / 254 * 2400;
|
|
2689
|
+
}
|
|
2690
|
+
getLFOAmplitudeDepth(channel, note) {
|
|
2691
|
+
const channelPressure = channel.channelPressureTable[5] *
|
|
2692
|
+
channel.state.channelPressure;
|
|
2693
|
+
const polyphonicKeyPressure = channel.polyphonicKeyPressureTable[5] *
|
|
2694
|
+
note.pressure;
|
|
2695
|
+
return (channelPressure + polyphonicKeyPressure) / 254;
|
|
2696
|
+
}
|
|
2697
|
+
setControllerParameters(channel, note, table) {
|
|
2698
|
+
if (table[0] !== 64)
|
|
2699
|
+
this.updateDetune(channel, note);
|
|
2700
|
+
if (!note.portamento) {
|
|
2701
|
+
if (table[1] !== 64)
|
|
2702
|
+
this.setFilterEnvelope(channel, note);
|
|
2703
|
+
if (table[2] !== 64)
|
|
2704
|
+
this.setVolumeEnvelope(channel, note);
|
|
2705
|
+
}
|
|
2706
|
+
if (table[3] !== 0)
|
|
2707
|
+
this.setModLfoToPitch(channel, note);
|
|
2708
|
+
if (table[4] !== 0)
|
|
2709
|
+
this.setModLfoToFilterFc(channel, note);
|
|
2710
|
+
if (table[5] !== 0)
|
|
2711
|
+
this.setModLfoToVolume(channel, note);
|
|
2579
2712
|
}
|
|
2580
2713
|
handleChannelPressureSysEx(data, tableName) {
|
|
2581
2714
|
const channelNumber = data[4];
|
|
@@ -2606,7 +2739,7 @@ class Midy {
|
|
|
2606
2739
|
const note = noteList[i];
|
|
2607
2740
|
if (!note)
|
|
2608
2741
|
continue;
|
|
2609
|
-
this.
|
|
2742
|
+
this.setControllerParameters(channel, note, table);
|
|
2610
2743
|
}
|
|
2611
2744
|
});
|
|
2612
2745
|
}
|
|
@@ -2670,10 +2803,6 @@ Object.defineProperty(Midy, "channelSettings", {
|
|
|
2670
2803
|
value: {
|
|
2671
2804
|
currentBufferSource: null,
|
|
2672
2805
|
detune: 0,
|
|
2673
|
-
scaleOctaveTuningTable: new Array(12).fill(0), // cent
|
|
2674
|
-
channelPressureTable: new Uint8Array([64, 64, 64, 0, 0, 0]),
|
|
2675
|
-
polyphonicKeyPressureTable: new Uint8Array([64, 64, 64, 0, 0, 0]),
|
|
2676
|
-
keyBasedInstrumentControlTable: new Int8Array(128 * 128), // [-64, 63]
|
|
2677
2806
|
program: 0,
|
|
2678
2807
|
bank: 121 * 128,
|
|
2679
2808
|
bankMSB: 121,
|