@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/esm/midy.js
CHANGED
|
@@ -1,5 +1,57 @@
|
|
|
1
1
|
import { parseMidi } from "midi-file";
|
|
2
2
|
import { parse, SoundFont } from "@marmooo/soundfont-parser";
|
|
3
|
+
// 2-3 times faster than Map
|
|
4
|
+
class SparseMap {
|
|
5
|
+
constructor(size) {
|
|
6
|
+
this.data = new Array(size);
|
|
7
|
+
this.activeIndices = [];
|
|
8
|
+
}
|
|
9
|
+
set(key, value) {
|
|
10
|
+
if (this.data[key] === undefined) {
|
|
11
|
+
this.activeIndices.push(key);
|
|
12
|
+
}
|
|
13
|
+
this.data[key] = value;
|
|
14
|
+
}
|
|
15
|
+
get(key) {
|
|
16
|
+
return this.data[key];
|
|
17
|
+
}
|
|
18
|
+
delete(key) {
|
|
19
|
+
if (this.data[key] !== undefined) {
|
|
20
|
+
this.data[key] = undefined;
|
|
21
|
+
const index = this.activeIndices.indexOf(key);
|
|
22
|
+
if (index !== -1) {
|
|
23
|
+
this.activeIndices.splice(index, 1);
|
|
24
|
+
}
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
has(key) {
|
|
30
|
+
return this.data[key] !== undefined;
|
|
31
|
+
}
|
|
32
|
+
get size() {
|
|
33
|
+
return this.activeIndices.length;
|
|
34
|
+
}
|
|
35
|
+
clear() {
|
|
36
|
+
for (let i = 0; i < this.activeIndices.length; i++) {
|
|
37
|
+
const key = this.activeIndices[i];
|
|
38
|
+
this.data[key] = undefined;
|
|
39
|
+
}
|
|
40
|
+
this.activeIndices = [];
|
|
41
|
+
}
|
|
42
|
+
*[Symbol.iterator]() {
|
|
43
|
+
for (let i = 0; i < this.activeIndices.length; i++) {
|
|
44
|
+
const key = this.activeIndices[i];
|
|
45
|
+
yield [key, this.data[key]];
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
forEach(callback) {
|
|
49
|
+
for (let i = 0; i < this.activeIndices.length; i++) {
|
|
50
|
+
const key = this.activeIndices[i];
|
|
51
|
+
callback(this.data[key], key, this);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
3
55
|
class Note {
|
|
4
56
|
constructor(noteNumber, velocity, startTime, voice, voiceParams) {
|
|
5
57
|
Object.defineProperty(this, "bufferSource", {
|
|
@@ -14,31 +66,37 @@ class Note {
|
|
|
14
66
|
writable: true,
|
|
15
67
|
value: void 0
|
|
16
68
|
});
|
|
69
|
+
Object.defineProperty(this, "filterDepth", {
|
|
70
|
+
enumerable: true,
|
|
71
|
+
configurable: true,
|
|
72
|
+
writable: true,
|
|
73
|
+
value: void 0
|
|
74
|
+
});
|
|
17
75
|
Object.defineProperty(this, "volumeEnvelopeNode", {
|
|
18
76
|
enumerable: true,
|
|
19
77
|
configurable: true,
|
|
20
78
|
writable: true,
|
|
21
79
|
value: void 0
|
|
22
80
|
});
|
|
23
|
-
Object.defineProperty(this, "
|
|
81
|
+
Object.defineProperty(this, "volumeDepth", {
|
|
24
82
|
enumerable: true,
|
|
25
83
|
configurable: true,
|
|
26
84
|
writable: true,
|
|
27
85
|
value: void 0
|
|
28
86
|
});
|
|
29
|
-
Object.defineProperty(this, "
|
|
87
|
+
Object.defineProperty(this, "volumeNode", {
|
|
30
88
|
enumerable: true,
|
|
31
89
|
configurable: true,
|
|
32
90
|
writable: true,
|
|
33
91
|
value: void 0
|
|
34
92
|
});
|
|
35
|
-
Object.defineProperty(this, "
|
|
93
|
+
Object.defineProperty(this, "gainL", {
|
|
36
94
|
enumerable: true,
|
|
37
95
|
configurable: true,
|
|
38
96
|
writable: true,
|
|
39
97
|
value: void 0
|
|
40
98
|
});
|
|
41
|
-
Object.defineProperty(this, "
|
|
99
|
+
Object.defineProperty(this, "gainR", {
|
|
42
100
|
enumerable: true,
|
|
43
101
|
configurable: true,
|
|
44
102
|
writable: true,
|
|
@@ -287,6 +345,18 @@ export class Midy {
|
|
|
287
345
|
writable: true,
|
|
288
346
|
value: this.initSoundFontTable()
|
|
289
347
|
});
|
|
348
|
+
Object.defineProperty(this, "audioBufferCounter", {
|
|
349
|
+
enumerable: true,
|
|
350
|
+
configurable: true,
|
|
351
|
+
writable: true,
|
|
352
|
+
value: new Map()
|
|
353
|
+
});
|
|
354
|
+
Object.defineProperty(this, "audioBufferCache", {
|
|
355
|
+
enumerable: true,
|
|
356
|
+
configurable: true,
|
|
357
|
+
writable: true,
|
|
358
|
+
value: new Map()
|
|
359
|
+
});
|
|
290
360
|
Object.defineProperty(this, "isPlaying", {
|
|
291
361
|
enumerable: true,
|
|
292
362
|
configurable: true,
|
|
@@ -339,7 +409,7 @@ export class Midy {
|
|
|
339
409
|
enumerable: true,
|
|
340
410
|
configurable: true,
|
|
341
411
|
writable: true,
|
|
342
|
-
value: new
|
|
412
|
+
value: new SparseMap(128)
|
|
343
413
|
});
|
|
344
414
|
Object.defineProperty(this, "defaultOptions", {
|
|
345
415
|
enumerable: true,
|
|
@@ -379,7 +449,7 @@ export class Midy {
|
|
|
379
449
|
initSoundFontTable() {
|
|
380
450
|
const table = new Array(128);
|
|
381
451
|
for (let i = 0; i < 128; i++) {
|
|
382
|
-
table[i] = new
|
|
452
|
+
table[i] = new SparseMap(128);
|
|
383
453
|
}
|
|
384
454
|
return table;
|
|
385
455
|
}
|
|
@@ -433,8 +503,12 @@ export class Midy {
|
|
|
433
503
|
state: new ControllerState(),
|
|
434
504
|
controlTable: this.initControlTable(),
|
|
435
505
|
...this.setChannelAudioNodes(audioContext),
|
|
436
|
-
scheduledNotes: new
|
|
437
|
-
sostenutoNotes: new
|
|
506
|
+
scheduledNotes: new SparseMap(128),
|
|
507
|
+
sostenutoNotes: new SparseMap(128),
|
|
508
|
+
scaleOctaveTuningTable: new Float32Array(12), // [-100, 100] cent
|
|
509
|
+
channelPressureTable: new Uint8Array([64, 64, 64, 0, 0, 0]),
|
|
510
|
+
polyphonicKeyPressureTable: new Uint8Array([64, 64, 64, 0, 0, 0]),
|
|
511
|
+
keyBasedInstrumentControlTable: new Int8Array(128 * 128), // [-64, 63]
|
|
438
512
|
};
|
|
439
513
|
});
|
|
440
514
|
return channels;
|
|
@@ -468,9 +542,8 @@ export class Midy {
|
|
|
468
542
|
return audioBuffer;
|
|
469
543
|
}
|
|
470
544
|
}
|
|
471
|
-
|
|
545
|
+
createNoteBufferNode(audioBuffer, voiceParams) {
|
|
472
546
|
const bufferSource = new AudioBufferSourceNode(this.audioContext);
|
|
473
|
-
const audioBuffer = await this.createNoteBuffer(voiceParams, isSF3);
|
|
474
547
|
bufferSource.buffer = audioBuffer;
|
|
475
548
|
bufferSource.loop = voiceParams.sampleModes % 2 !== 0;
|
|
476
549
|
if (bufferSource.loop) {
|
|
@@ -502,10 +575,11 @@ export class Midy {
|
|
|
502
575
|
const event = this.timeline[queueIndex];
|
|
503
576
|
if (event.startTime > t + this.lookAhead)
|
|
504
577
|
break;
|
|
578
|
+
const startTime = event.startTime + this.startDelay - offset;
|
|
505
579
|
switch (event.type) {
|
|
506
580
|
case "noteOn":
|
|
507
581
|
if (event.velocity !== 0) {
|
|
508
|
-
await this.scheduleNoteOn(event.channel, event.noteNumber, event.velocity,
|
|
582
|
+
await this.scheduleNoteOn(event.channel, event.noteNumber, event.velocity, startTime, event.portamento);
|
|
509
583
|
break;
|
|
510
584
|
}
|
|
511
585
|
/* falls through */
|
|
@@ -513,29 +587,29 @@ export class Midy {
|
|
|
513
587
|
const portamentoTarget = this.findPortamentoTarget(queueIndex);
|
|
514
588
|
if (portamentoTarget)
|
|
515
589
|
portamentoTarget.portamento = true;
|
|
516
|
-
const notePromise = this.scheduleNoteRelease(this.omni ? 0 : event.channel, event.noteNumber, event.velocity,
|
|
590
|
+
const notePromise = this.scheduleNoteRelease(this.omni ? 0 : event.channel, event.noteNumber, event.velocity, startTime, portamentoTarget?.noteNumber, false);
|
|
517
591
|
if (notePromise) {
|
|
518
592
|
this.notePromises.push(notePromise);
|
|
519
593
|
}
|
|
520
594
|
break;
|
|
521
595
|
}
|
|
522
596
|
case "noteAftertouch":
|
|
523
|
-
this.handlePolyphonicKeyPressure(event.channel, event.noteNumber, event.amount);
|
|
597
|
+
this.handlePolyphonicKeyPressure(event.channel, event.noteNumber, event.amount, startTime);
|
|
524
598
|
break;
|
|
525
599
|
case "controller":
|
|
526
|
-
this.handleControlChange(this.omni ? 0 : event.channel, event.controllerType, event.value);
|
|
600
|
+
this.handleControlChange(this.omni ? 0 : event.channel, event.controllerType, event.value, startTime);
|
|
527
601
|
break;
|
|
528
602
|
case "programChange":
|
|
529
|
-
this.handleProgramChange(event.channel, event.programNumber);
|
|
603
|
+
this.handleProgramChange(event.channel, event.programNumber, startTime);
|
|
530
604
|
break;
|
|
531
605
|
case "channelAftertouch":
|
|
532
|
-
this.handleChannelPressure(event.channel, event.amount);
|
|
606
|
+
this.handleChannelPressure(event.channel, event.amount, startTime);
|
|
533
607
|
break;
|
|
534
608
|
case "pitchBend":
|
|
535
|
-
this.setPitchBend(event.channel, event.value + 8192);
|
|
609
|
+
this.setPitchBend(event.channel, event.value + 8192, startTime);
|
|
536
610
|
break;
|
|
537
611
|
case "sysEx":
|
|
538
|
-
this.handleSysEx(event.data);
|
|
612
|
+
this.handleSysEx(event.data, startTime);
|
|
539
613
|
}
|
|
540
614
|
queueIndex++;
|
|
541
615
|
}
|
|
@@ -562,6 +636,7 @@ export class Midy {
|
|
|
562
636
|
await Promise.all(this.notePromises);
|
|
563
637
|
this.notePromises = [];
|
|
564
638
|
this.exclusiveClassMap.clear();
|
|
639
|
+
this.audioBufferCache.clear();
|
|
565
640
|
resolve();
|
|
566
641
|
return;
|
|
567
642
|
}
|
|
@@ -577,8 +652,9 @@ export class Midy {
|
|
|
577
652
|
}
|
|
578
653
|
else if (this.isStopping) {
|
|
579
654
|
await this.stopNotes(0, true);
|
|
580
|
-
this.exclusiveClassMap.clear();
|
|
581
655
|
this.notePromises = [];
|
|
656
|
+
this.exclusiveClassMap.clear();
|
|
657
|
+
this.audioBufferCache.clear();
|
|
582
658
|
resolve();
|
|
583
659
|
this.isStopping = false;
|
|
584
660
|
this.isPaused = false;
|
|
@@ -609,6 +685,9 @@ export class Midy {
|
|
|
609
685
|
secondToTicks(second, secondsPerBeat) {
|
|
610
686
|
return second * this.ticksPerBeat / secondsPerBeat;
|
|
611
687
|
}
|
|
688
|
+
getAudioBufferId(programNumber, noteNumber, velocity) {
|
|
689
|
+
return `${programNumber}:${noteNumber}:${velocity}`;
|
|
690
|
+
}
|
|
612
691
|
extractMidiData(midi) {
|
|
613
692
|
const instruments = new Set();
|
|
614
693
|
const timeline = [];
|
|
@@ -630,6 +709,8 @@ export class Midy {
|
|
|
630
709
|
switch (event.type) {
|
|
631
710
|
case "noteOn": {
|
|
632
711
|
const channel = tmpChannels[event.channel];
|
|
712
|
+
const audioBufferId = this.getAudioBufferId(channel.programNumber, event.noteNumber, event.velocity);
|
|
713
|
+
this.audioBufferCounter.set(audioBufferId, (this.audioBufferCounter.get(audioBufferId) ?? 0) + 1);
|
|
633
714
|
if (channel.programNumber < 0) {
|
|
634
715
|
channel.programNumber = event.programNumber;
|
|
635
716
|
switch (channel.bankMSB) {
|
|
@@ -679,6 +760,10 @@ export class Midy {
|
|
|
679
760
|
timeline.push(event);
|
|
680
761
|
}
|
|
681
762
|
}
|
|
763
|
+
for (const [audioBufferId, count] of this.audioBufferCounter) {
|
|
764
|
+
if (count === 1)
|
|
765
|
+
this.audioBufferCounter.delete(audioBufferId);
|
|
766
|
+
}
|
|
682
767
|
const priority = {
|
|
683
768
|
controller: 0,
|
|
684
769
|
sysEx: 1,
|
|
@@ -771,8 +856,20 @@ export class Midy {
|
|
|
771
856
|
const now = this.audioContext.currentTime;
|
|
772
857
|
return this.resumeTime + now - this.startTime - this.startDelay;
|
|
773
858
|
}
|
|
859
|
+
processScheduledNotes(channel, scheduleTime, callback) {
|
|
860
|
+
channel.scheduledNotes.forEach((noteList) => {
|
|
861
|
+
for (let i = 0; i < noteList.length; i++) {
|
|
862
|
+
const note = noteList[i];
|
|
863
|
+
if (!note)
|
|
864
|
+
continue;
|
|
865
|
+
if (scheduleTime < note.startTime)
|
|
866
|
+
continue;
|
|
867
|
+
callback(note);
|
|
868
|
+
}
|
|
869
|
+
});
|
|
870
|
+
}
|
|
774
871
|
getActiveNotes(channel, time) {
|
|
775
|
-
const activeNotes = new
|
|
872
|
+
const activeNotes = new SparseMap(128);
|
|
776
873
|
channel.scheduledNotes.forEach((noteList) => {
|
|
777
874
|
const activeNote = this.getActiveNote(noteList, time);
|
|
778
875
|
if (activeNote) {
|
|
@@ -952,14 +1049,15 @@ export class Midy {
|
|
|
952
1049
|
const note = noteList[i];
|
|
953
1050
|
if (!note)
|
|
954
1051
|
continue;
|
|
955
|
-
this.updateDetune(channel, note
|
|
1052
|
+
this.updateDetune(channel, note);
|
|
956
1053
|
}
|
|
957
1054
|
});
|
|
958
1055
|
}
|
|
959
|
-
updateDetune(channel, note
|
|
1056
|
+
updateDetune(channel, note) {
|
|
960
1057
|
const now = this.audioContext.currentTime;
|
|
961
1058
|
const noteDetune = this.calcNoteDetune(channel, note);
|
|
962
|
-
const
|
|
1059
|
+
const pitchControl = this.getPitchControl(channel, note);
|
|
1060
|
+
const detune = channel.detune + noteDetune + pitchControl;
|
|
963
1061
|
note.bufferSource.detune
|
|
964
1062
|
.cancelScheduledValues(now)
|
|
965
1063
|
.setValueAtTime(detune, now);
|
|
@@ -981,12 +1079,12 @@ export class Midy {
|
|
|
981
1079
|
.setValueAtTime(0, volDelay)
|
|
982
1080
|
.linearRampToValueAtTime(sustainVolume, portamentoTime);
|
|
983
1081
|
}
|
|
984
|
-
setVolumeEnvelope(channel, note
|
|
1082
|
+
setVolumeEnvelope(channel, note) {
|
|
985
1083
|
const now = this.audioContext.currentTime;
|
|
986
1084
|
const state = channel.state;
|
|
987
1085
|
const { voiceParams, startTime } = note;
|
|
988
1086
|
const attackVolume = this.cbToRatio(-voiceParams.initialAttenuation) *
|
|
989
|
-
(1 +
|
|
1087
|
+
(1 + this.getAmplitudeControl(channel, note));
|
|
990
1088
|
const sustainVolume = attackVolume * (1 - voiceParams.volSustain);
|
|
991
1089
|
const volDelay = startTime + voiceParams.volDelay;
|
|
992
1090
|
const volAttack = volDelay + voiceParams.volAttack * state.attackTime * 2;
|
|
@@ -1000,20 +1098,20 @@ export class Midy {
|
|
|
1000
1098
|
.setValueAtTime(attackVolume, volHold)
|
|
1001
1099
|
.linearRampToValueAtTime(sustainVolume, volDecay);
|
|
1002
1100
|
}
|
|
1003
|
-
setPitchEnvelope(note) {
|
|
1004
|
-
|
|
1101
|
+
setPitchEnvelope(note, scheduleTime) {
|
|
1102
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1005
1103
|
const { voiceParams } = note;
|
|
1006
1104
|
const baseRate = voiceParams.playbackRate;
|
|
1007
1105
|
note.bufferSource.playbackRate
|
|
1008
|
-
.cancelScheduledValues(
|
|
1009
|
-
.setValueAtTime(baseRate,
|
|
1106
|
+
.cancelScheduledValues(scheduleTime)
|
|
1107
|
+
.setValueAtTime(baseRate, scheduleTime);
|
|
1010
1108
|
const modEnvToPitch = voiceParams.modEnvToPitch;
|
|
1011
1109
|
if (modEnvToPitch === 0)
|
|
1012
1110
|
return;
|
|
1013
1111
|
const basePitch = this.rateToCent(baseRate);
|
|
1014
1112
|
const peekPitch = basePitch + modEnvToPitch;
|
|
1015
1113
|
const peekRate = this.centToRate(peekPitch);
|
|
1016
|
-
const modDelay = startTime + voiceParams.modDelay;
|
|
1114
|
+
const modDelay = note.startTime + voiceParams.modDelay;
|
|
1017
1115
|
const modAttack = modDelay + voiceParams.modAttack;
|
|
1018
1116
|
const modHold = modAttack + voiceParams.modHold;
|
|
1019
1117
|
const modDecay = modHold + voiceParams.modDecay;
|
|
@@ -1050,13 +1148,14 @@ export class Midy {
|
|
|
1050
1148
|
.setValueAtTime(adjustedBaseFreq, modDelay)
|
|
1051
1149
|
.linearRampToValueAtTime(adjustedSustainFreq, portamentoTime);
|
|
1052
1150
|
}
|
|
1053
|
-
setFilterEnvelope(channel, note
|
|
1151
|
+
setFilterEnvelope(channel, note) {
|
|
1054
1152
|
const now = this.audioContext.currentTime;
|
|
1055
1153
|
const state = channel.state;
|
|
1056
1154
|
const { voiceParams, noteNumber, startTime } = note;
|
|
1057
1155
|
const softPedalFactor = 1 -
|
|
1058
1156
|
(0.1 + (noteNumber / 127) * 0.2) * state.softPedal;
|
|
1059
|
-
const baseCent = voiceParams.initialFilterFc +
|
|
1157
|
+
const baseCent = voiceParams.initialFilterFc +
|
|
1158
|
+
this.getFilterCutoffControl(channel, note);
|
|
1060
1159
|
const baseFreq = this.centToHz(baseCent) * softPedalFactor *
|
|
1061
1160
|
state.brightness * 2;
|
|
1062
1161
|
const peekFreq = this.centToHz(baseCent + voiceParams.modEnvToFilterFc) *
|
|
@@ -1087,9 +1186,9 @@ export class Midy {
|
|
|
1087
1186
|
gain: voiceParams.modLfoToFilterFc,
|
|
1088
1187
|
});
|
|
1089
1188
|
note.modulationDepth = new GainNode(this.audioContext);
|
|
1090
|
-
this.setModLfoToPitch(channel, note
|
|
1189
|
+
this.setModLfoToPitch(channel, note);
|
|
1091
1190
|
note.volumeDepth = new GainNode(this.audioContext);
|
|
1092
|
-
this.setModLfoToVolume(
|
|
1191
|
+
this.setModLfoToVolume(channel, note);
|
|
1093
1192
|
note.modulationLFO.start(startTime + voiceParams.delayModLFO);
|
|
1094
1193
|
note.modulationLFO.connect(note.filterDepth);
|
|
1095
1194
|
note.filterDepth.connect(note.filterNode.frequency);
|
|
@@ -1110,12 +1209,31 @@ export class Midy {
|
|
|
1110
1209
|
note.vibratoLFO.connect(note.vibratoDepth);
|
|
1111
1210
|
note.vibratoDepth.connect(note.bufferSource.detune);
|
|
1112
1211
|
}
|
|
1212
|
+
async getAudioBuffer(program, noteNumber, velocity, voiceParams, isSF3) {
|
|
1213
|
+
const audioBufferId = this.getAudioBufferId(program, noteNumber, velocity);
|
|
1214
|
+
const cache = this.audioBufferCache.get(audioBufferId);
|
|
1215
|
+
if (cache) {
|
|
1216
|
+
cache.counter += 1;
|
|
1217
|
+
if (cache.maxCount <= cache.counter) {
|
|
1218
|
+
this.audioBufferCache.delete(audioBufferId);
|
|
1219
|
+
}
|
|
1220
|
+
return cache.audioBuffer;
|
|
1221
|
+
}
|
|
1222
|
+
else {
|
|
1223
|
+
const maxCount = this.audioBufferCounter.get(audioBufferId) ?? 0;
|
|
1224
|
+
const audioBuffer = await this.createNoteBuffer(voiceParams, isSF3);
|
|
1225
|
+
const cache = { audioBuffer, maxCount, counter: 1 };
|
|
1226
|
+
this.audioBufferCache.set(audioBufferId, cache);
|
|
1227
|
+
return audioBuffer;
|
|
1228
|
+
}
|
|
1229
|
+
}
|
|
1113
1230
|
async createNote(channel, voice, noteNumber, velocity, startTime, portamento, isSF3) {
|
|
1114
1231
|
const state = channel.state;
|
|
1115
1232
|
const controllerState = this.getControllerState(channel, noteNumber, velocity);
|
|
1116
1233
|
const voiceParams = voice.getAllParams(controllerState);
|
|
1117
1234
|
const note = new Note(noteNumber, velocity, startTime, voice, voiceParams);
|
|
1118
|
-
|
|
1235
|
+
const audioBuffer = await this.getAudioBuffer(channel.program, noteNumber, velocity, voiceParams, isSF3);
|
|
1236
|
+
note.bufferSource = this.createNoteBufferNode(audioBuffer, voiceParams);
|
|
1119
1237
|
note.volumeNode = new GainNode(this.audioContext);
|
|
1120
1238
|
note.gainL = new GainNode(this.audioContext);
|
|
1121
1239
|
note.gainR = new GainNode(this.audioContext);
|
|
@@ -1131,8 +1249,8 @@ export class Midy {
|
|
|
1131
1249
|
}
|
|
1132
1250
|
else {
|
|
1133
1251
|
note.portamento = false;
|
|
1134
|
-
this.setVolumeEnvelope(channel, note
|
|
1135
|
-
this.setFilterEnvelope(channel, note
|
|
1252
|
+
this.setVolumeEnvelope(channel, note);
|
|
1253
|
+
this.setFilterEnvelope(channel, note);
|
|
1136
1254
|
}
|
|
1137
1255
|
if (0 < state.vibratoDepth) {
|
|
1138
1256
|
this.startVibrato(channel, note, startTime);
|
|
@@ -1175,10 +1293,10 @@ export class Midy {
|
|
|
1175
1293
|
if (soundFontIndex === undefined)
|
|
1176
1294
|
return;
|
|
1177
1295
|
const soundFont = this.soundFonts[soundFontIndex];
|
|
1178
|
-
const isSF3 = soundFont.parsed.info.version.major === 3;
|
|
1179
1296
|
const voice = soundFont.getVoice(bankNumber, channel.program, noteNumber, velocity);
|
|
1180
1297
|
if (!voice)
|
|
1181
1298
|
return;
|
|
1299
|
+
const isSF3 = soundFont.parsed.info.version.major === 3;
|
|
1182
1300
|
const note = await this.createNote(channel, voice, noteNumber, velocity, startTime, portamento, isSF3);
|
|
1183
1301
|
note.gainL.connect(channel.gainL);
|
|
1184
1302
|
note.gainR.connect(channel.gainR);
|
|
@@ -1344,15 +1462,16 @@ export class Midy {
|
|
|
1344
1462
|
console.warn(`Unsupported MIDI message: ${messageType.toString(16)}`);
|
|
1345
1463
|
}
|
|
1346
1464
|
}
|
|
1347
|
-
handlePolyphonicKeyPressure(channelNumber, noteNumber, pressure) {
|
|
1348
|
-
|
|
1465
|
+
handlePolyphonicKeyPressure(channelNumber, noteNumber, pressure, startTime) {
|
|
1466
|
+
if (!startTime)
|
|
1467
|
+
startTime = this.audioContext.currentTime;
|
|
1349
1468
|
const channel = this.channels[channelNumber];
|
|
1350
1469
|
channel.state.polyphonicKeyPressure = pressure / 127;
|
|
1351
1470
|
const table = channel.polyphonicKeyPressureTable;
|
|
1352
|
-
const activeNotes = this.getActiveNotes(channel,
|
|
1471
|
+
const activeNotes = this.getActiveNotes(channel, startTime);
|
|
1353
1472
|
if (activeNotes.has(noteNumber)) {
|
|
1354
1473
|
const note = activeNotes.get(noteNumber);
|
|
1355
|
-
this.
|
|
1474
|
+
this.setControllerParameters(channel, note, table);
|
|
1356
1475
|
}
|
|
1357
1476
|
// this.applyVoiceParams(channel, 10);
|
|
1358
1477
|
}
|
|
@@ -1361,7 +1480,9 @@ export class Midy {
|
|
|
1361
1480
|
channel.bank = channel.bankMSB * 128 + channel.bankLSB;
|
|
1362
1481
|
channel.program = program;
|
|
1363
1482
|
}
|
|
1364
|
-
handleChannelPressure(channelNumber, value) {
|
|
1483
|
+
handleChannelPressure(channelNumber, value, startTime) {
|
|
1484
|
+
if (!startTime)
|
|
1485
|
+
startTime = this.audioContext.currentTime;
|
|
1365
1486
|
const channel = this.channels[channelNumber];
|
|
1366
1487
|
const prev = channel.state.channelPressure;
|
|
1367
1488
|
const next = value / 127;
|
|
@@ -1371,13 +1492,8 @@ export class Midy {
|
|
|
1371
1492
|
channel.detune += pressureDepth * (next - prev);
|
|
1372
1493
|
}
|
|
1373
1494
|
const table = channel.channelPressureTable;
|
|
1374
|
-
channel.
|
|
1375
|
-
|
|
1376
|
-
const note = noteList[i];
|
|
1377
|
-
if (!note)
|
|
1378
|
-
continue;
|
|
1379
|
-
this.applyDestinationSettings(channel, note, table);
|
|
1380
|
-
}
|
|
1495
|
+
this.getActiveNotes(channel, startTime).forEach((note) => {
|
|
1496
|
+
this.setControllerParameters(channel, note, table);
|
|
1381
1497
|
});
|
|
1382
1498
|
// this.applyVoiceParams(channel, 13);
|
|
1383
1499
|
}
|
|
@@ -1395,9 +1511,10 @@ export class Midy {
|
|
|
1395
1511
|
this.updateChannelDetune(channel);
|
|
1396
1512
|
this.applyVoiceParams(channel, 14);
|
|
1397
1513
|
}
|
|
1398
|
-
setModLfoToPitch(channel, note
|
|
1514
|
+
setModLfoToPitch(channel, note) {
|
|
1399
1515
|
const now = this.audioContext.currentTime;
|
|
1400
|
-
const modLfoToPitch = note.voiceParams.modLfoToPitch +
|
|
1516
|
+
const modLfoToPitch = note.voiceParams.modLfoToPitch +
|
|
1517
|
+
this.getLFOPitchDepth(channel, note);
|
|
1401
1518
|
const baseDepth = Math.abs(modLfoToPitch) + channel.state.modulationDepth;
|
|
1402
1519
|
const modulationDepth = baseDepth * Math.sign(modLfoToPitch);
|
|
1403
1520
|
note.modulationDepth.gain
|
|
@@ -1414,18 +1531,20 @@ export class Midy {
|
|
|
1414
1531
|
.cancelScheduledValues(now)
|
|
1415
1532
|
.setValueAtTime(vibratoDepth * vibratoDepthSign, now);
|
|
1416
1533
|
}
|
|
1417
|
-
setModLfoToFilterFc(
|
|
1534
|
+
setModLfoToFilterFc(channel, note) {
|
|
1418
1535
|
const now = this.audioContext.currentTime;
|
|
1419
|
-
const modLfoToFilterFc = note.voiceParams.modLfoToFilterFc +
|
|
1536
|
+
const modLfoToFilterFc = note.voiceParams.modLfoToFilterFc +
|
|
1537
|
+
this.getLFOFilterDepth(channel, note);
|
|
1420
1538
|
note.filterDepth.gain
|
|
1421
1539
|
.cancelScheduledValues(now)
|
|
1422
1540
|
.setValueAtTime(modLfoToFilterFc, now);
|
|
1423
1541
|
}
|
|
1424
|
-
setModLfoToVolume(
|
|
1542
|
+
setModLfoToVolume(channel, note) {
|
|
1425
1543
|
const now = this.audioContext.currentTime;
|
|
1426
1544
|
const modLfoToVolume = note.voiceParams.modLfoToVolume;
|
|
1427
1545
|
const baseDepth = this.cbToRatio(Math.abs(modLfoToVolume)) - 1;
|
|
1428
|
-
const volumeDepth = baseDepth * Math.sign(modLfoToVolume) *
|
|
1546
|
+
const volumeDepth = baseDepth * Math.sign(modLfoToVolume) *
|
|
1547
|
+
(1 + this.getLFOAmplitudeDepth(channel, note));
|
|
1429
1548
|
note.volumeDepth.gain
|
|
1430
1549
|
.cancelScheduledValues(now)
|
|
1431
1550
|
.setValueAtTime(volumeDepth, now);
|
|
@@ -1509,7 +1628,7 @@ export class Midy {
|
|
|
1509
1628
|
return {
|
|
1510
1629
|
modLfoToPitch: (channel, note, _prevValue) => {
|
|
1511
1630
|
if (0 < channel.state.modulationDepth) {
|
|
1512
|
-
this.setModLfoToPitch(channel, note
|
|
1631
|
+
this.setModLfoToPitch(channel, note);
|
|
1513
1632
|
}
|
|
1514
1633
|
},
|
|
1515
1634
|
vibLfoToPitch: (channel, note, _prevValue) => {
|
|
@@ -1519,12 +1638,12 @@ export class Midy {
|
|
|
1519
1638
|
},
|
|
1520
1639
|
modLfoToFilterFc: (channel, note, _prevValue) => {
|
|
1521
1640
|
if (0 < channel.state.modulationDepth) {
|
|
1522
|
-
this.setModLfoToFilterFc(
|
|
1641
|
+
this.setModLfoToFilterFc(channel, note);
|
|
1523
1642
|
}
|
|
1524
1643
|
},
|
|
1525
1644
|
modLfoToVolume: (channel, note, _prevValue) => {
|
|
1526
1645
|
if (0 < channel.state.modulationDepth) {
|
|
1527
|
-
this.setModLfoToVolume(
|
|
1646
|
+
this.setModLfoToVolume(channel, note);
|
|
1528
1647
|
}
|
|
1529
1648
|
},
|
|
1530
1649
|
chorusEffectsSend: (channel, note, prevValue) => {
|
|
@@ -1594,7 +1713,7 @@ export class Midy {
|
|
|
1594
1713
|
this.setPortamentoStartFilterEnvelope(channel, note);
|
|
1595
1714
|
}
|
|
1596
1715
|
else {
|
|
1597
|
-
this.setFilterEnvelope(channel, note
|
|
1716
|
+
this.setFilterEnvelope(channel, note);
|
|
1598
1717
|
}
|
|
1599
1718
|
this.setPitchEnvelope(note);
|
|
1600
1719
|
}
|
|
@@ -1608,7 +1727,7 @@ export class Midy {
|
|
|
1608
1727
|
if (key in voiceParams)
|
|
1609
1728
|
noteVoiceParams[key] = voiceParams[key];
|
|
1610
1729
|
}
|
|
1611
|
-
this.setVolumeEnvelope(channel, note
|
|
1730
|
+
this.setVolumeEnvelope(channel, note);
|
|
1612
1731
|
}
|
|
1613
1732
|
}
|
|
1614
1733
|
}
|
|
@@ -1652,10 +1771,10 @@ export class Midy {
|
|
|
1652
1771
|
127: this.polyOn,
|
|
1653
1772
|
};
|
|
1654
1773
|
}
|
|
1655
|
-
handleControlChange(channelNumber, controllerType, value) {
|
|
1774
|
+
handleControlChange(channelNumber, controllerType, value, startTime) {
|
|
1656
1775
|
const handler = this.controlChangeHandlers[controllerType];
|
|
1657
1776
|
if (handler) {
|
|
1658
|
-
handler.call(this, channelNumber, value);
|
|
1777
|
+
handler.call(this, channelNumber, value, startTime);
|
|
1659
1778
|
const channel = this.channels[channelNumber];
|
|
1660
1779
|
this.applyVoiceParams(channel, controllerType + 128);
|
|
1661
1780
|
this.applyControlTable(channel, controllerType);
|
|
@@ -1667,55 +1786,45 @@ export class Midy {
|
|
|
1667
1786
|
setBankMSB(channelNumber, msb) {
|
|
1668
1787
|
this.channels[channelNumber].bankMSB = msb;
|
|
1669
1788
|
}
|
|
1670
|
-
updateModulation(channel) {
|
|
1671
|
-
|
|
1789
|
+
updateModulation(channel, scheduleTime) {
|
|
1790
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1672
1791
|
const depth = channel.state.modulationDepth * channel.modulationDepthRange;
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
}
|
|
1681
|
-
else {
|
|
1682
|
-
this.setPitchEnvelope(note);
|
|
1683
|
-
this.startModulation(channel, note, now);
|
|
1684
|
-
}
|
|
1792
|
+
this.processScheduledNotes(channel, scheduleTime, (note) => {
|
|
1793
|
+
if (note.modulationDepth) {
|
|
1794
|
+
note.modulationDepth.gain.setValueAtTime(depth, scheduleTime);
|
|
1795
|
+
}
|
|
1796
|
+
else {
|
|
1797
|
+
this.setPitchEnvelope(note, scheduleTime);
|
|
1798
|
+
this.startModulation(channel, note, scheduleTime);
|
|
1685
1799
|
}
|
|
1686
1800
|
});
|
|
1687
1801
|
}
|
|
1688
|
-
setModulationDepth(channelNumber, modulation) {
|
|
1802
|
+
setModulationDepth(channelNumber, modulation, scheduleTime) {
|
|
1689
1803
|
const channel = this.channels[channelNumber];
|
|
1690
1804
|
channel.state.modulationDepth = modulation / 127;
|
|
1691
|
-
this.updateModulation(channel);
|
|
1805
|
+
this.updateModulation(channel, scheduleTime);
|
|
1692
1806
|
}
|
|
1693
1807
|
setPortamentoTime(channelNumber, portamentoTime) {
|
|
1694
1808
|
const channel = this.channels[channelNumber];
|
|
1695
1809
|
const factor = 5 * Math.log(10) / 127;
|
|
1696
1810
|
channel.state.portamentoTime = Math.exp(factor * portamentoTime);
|
|
1697
1811
|
}
|
|
1698
|
-
setKeyBasedVolume(channel) {
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
if (!note)
|
|
1704
|
-
continue;
|
|
1705
|
-
const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 7);
|
|
1706
|
-
if (keyBasedValue === 0)
|
|
1707
|
-
continue;
|
|
1812
|
+
setKeyBasedVolume(channel, scheduleTime) {
|
|
1813
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1814
|
+
this.processScheduledNotes(channel, scheduleTime, (note) => {
|
|
1815
|
+
const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 7);
|
|
1816
|
+
if (keyBasedValue !== 0) {
|
|
1708
1817
|
note.volumeNode.gain
|
|
1709
|
-
.cancelScheduledValues(
|
|
1710
|
-
.setValueAtTime(1 + keyBasedValue,
|
|
1818
|
+
.cancelScheduledValues(scheduleTime)
|
|
1819
|
+
.setValueAtTime(1 + keyBasedValue, scheduleTime);
|
|
1711
1820
|
}
|
|
1712
1821
|
});
|
|
1713
1822
|
}
|
|
1714
|
-
setVolume(channelNumber, volume) {
|
|
1823
|
+
setVolume(channelNumber, volume, scheduleTime) {
|
|
1715
1824
|
const channel = this.channels[channelNumber];
|
|
1716
1825
|
channel.state.volume = volume / 127;
|
|
1717
|
-
this.updateChannelVolume(channel);
|
|
1718
|
-
this.setKeyBasedVolume(channel);
|
|
1826
|
+
this.updateChannelVolume(channel, scheduleTime);
|
|
1827
|
+
this.setKeyBasedVolume(channel, scheduleTime);
|
|
1719
1828
|
}
|
|
1720
1829
|
panToGain(pan) {
|
|
1721
1830
|
const theta = Math.PI / 2 * Math.max(0, pan * 127 - 1) / 126;
|
|
@@ -1724,36 +1833,31 @@ export class Midy {
|
|
|
1724
1833
|
gainRight: Math.sin(theta),
|
|
1725
1834
|
};
|
|
1726
1835
|
}
|
|
1727
|
-
setKeyBasedPan(channel) {
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
if (!note)
|
|
1733
|
-
continue;
|
|
1734
|
-
const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 10);
|
|
1735
|
-
if (keyBasedValue === 0)
|
|
1736
|
-
continue;
|
|
1836
|
+
setKeyBasedPan(channel, scheduleTime) {
|
|
1837
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1838
|
+
this.processScheduledNotes(channel, scheduleTime, (note) => {
|
|
1839
|
+
const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 10);
|
|
1840
|
+
if (keyBasedValue !== 0) {
|
|
1737
1841
|
const { gainLeft, gainRight } = this.panToGain((keyBasedValue + 1) / 2);
|
|
1738
1842
|
note.gainL.gain
|
|
1739
|
-
.cancelScheduledValues(
|
|
1740
|
-
.setValueAtTime(gainLeft,
|
|
1843
|
+
.cancelScheduledValues(scheduleTime)
|
|
1844
|
+
.setValueAtTime(gainLeft, scheduleTime);
|
|
1741
1845
|
note.gainR.gain
|
|
1742
|
-
.cancelScheduledValues(
|
|
1743
|
-
.setValueAtTime(gainRight,
|
|
1846
|
+
.cancelScheduledValues(scheduleTime)
|
|
1847
|
+
.setValueAtTime(gainRight, scheduleTime);
|
|
1744
1848
|
}
|
|
1745
1849
|
});
|
|
1746
1850
|
}
|
|
1747
|
-
setPan(channelNumber, pan) {
|
|
1851
|
+
setPan(channelNumber, pan, scheduleTime) {
|
|
1748
1852
|
const channel = this.channels[channelNumber];
|
|
1749
1853
|
channel.state.pan = pan / 127;
|
|
1750
|
-
this.updateChannelVolume(channel);
|
|
1751
|
-
this.setKeyBasedPan(channel);
|
|
1854
|
+
this.updateChannelVolume(channel, scheduleTime);
|
|
1855
|
+
this.setKeyBasedPan(channel, scheduleTime);
|
|
1752
1856
|
}
|
|
1753
|
-
setExpression(channelNumber, expression) {
|
|
1857
|
+
setExpression(channelNumber, expression, scheduleTime) {
|
|
1754
1858
|
const channel = this.channels[channelNumber];
|
|
1755
1859
|
channel.state.expression = expression / 127;
|
|
1756
|
-
this.updateChannelVolume(channel);
|
|
1860
|
+
this.updateChannelVolume(channel, scheduleTime);
|
|
1757
1861
|
}
|
|
1758
1862
|
setBankLSB(channelNumber, lsb) {
|
|
1759
1863
|
this.channels[channelNumber].bankLSB = lsb;
|
|
@@ -1788,8 +1892,7 @@ export class Midy {
|
|
|
1788
1892
|
channel.state.sostenutoPedal = value / 127;
|
|
1789
1893
|
if (64 <= value) {
|
|
1790
1894
|
const now = this.audioContext.currentTime;
|
|
1791
|
-
|
|
1792
|
-
channel.sostenutoNotes = new Map(activeNotes);
|
|
1895
|
+
channel.sostenutoNotes = this.getActiveNotes(channel, now);
|
|
1793
1896
|
}
|
|
1794
1897
|
else {
|
|
1795
1898
|
this.releaseSostenutoPedal(channelNumber, value);
|
|
@@ -1829,7 +1932,7 @@ export class Midy {
|
|
|
1829
1932
|
continue;
|
|
1830
1933
|
if (note.startTime < now)
|
|
1831
1934
|
continue;
|
|
1832
|
-
this.setVolumeEnvelope(channel, note
|
|
1935
|
+
this.setVolumeEnvelope(channel, note);
|
|
1833
1936
|
}
|
|
1834
1937
|
});
|
|
1835
1938
|
}
|
|
@@ -1845,7 +1948,7 @@ export class Midy {
|
|
|
1845
1948
|
this.setPortamentoStartFilterEnvelope(channel, note);
|
|
1846
1949
|
}
|
|
1847
1950
|
else {
|
|
1848
|
-
this.setFilterEnvelope(channel, note
|
|
1951
|
+
this.setFilterEnvelope(channel, note);
|
|
1849
1952
|
}
|
|
1850
1953
|
}
|
|
1851
1954
|
});
|
|
@@ -1858,7 +1961,7 @@ export class Midy {
|
|
|
1858
1961
|
const note = noteList[i];
|
|
1859
1962
|
if (!note)
|
|
1860
1963
|
continue;
|
|
1861
|
-
this.setVolumeEnvelope(channel, note
|
|
1964
|
+
this.setVolumeEnvelope(channel, note);
|
|
1862
1965
|
}
|
|
1863
1966
|
});
|
|
1864
1967
|
}
|
|
@@ -2170,7 +2273,10 @@ export class Midy {
|
|
|
2170
2273
|
switch (data[3]) {
|
|
2171
2274
|
case 8:
|
|
2172
2275
|
// https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca21.pdf
|
|
2173
|
-
return this.handleScaleOctaveTuning1ByteFormatSysEx(data);
|
|
2276
|
+
return this.handleScaleOctaveTuning1ByteFormatSysEx(data, false);
|
|
2277
|
+
case 9:
|
|
2278
|
+
// https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca21.pdf
|
|
2279
|
+
return this.handleScaleOctaveTuning2ByteFormatSysEx(data, false);
|
|
2174
2280
|
default:
|
|
2175
2281
|
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
2176
2282
|
}
|
|
@@ -2232,8 +2338,10 @@ export class Midy {
|
|
|
2232
2338
|
case 8:
|
|
2233
2339
|
switch (data[3]) {
|
|
2234
2340
|
case 8: // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca21.pdf
|
|
2235
|
-
|
|
2236
|
-
|
|
2341
|
+
return this.handleScaleOctaveTuning1ByteFormatSysEx(data, true);
|
|
2342
|
+
case 9:
|
|
2343
|
+
// https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca21.pdf
|
|
2344
|
+
return this.handleScaleOctaveTuning2ByteFormatSysEx(data, true);
|
|
2237
2345
|
default:
|
|
2238
2346
|
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
2239
2347
|
}
|
|
@@ -2503,8 +2611,8 @@ export class Midy {
|
|
|
2503
2611
|
}
|
|
2504
2612
|
return bitmap;
|
|
2505
2613
|
}
|
|
2506
|
-
handleScaleOctaveTuning1ByteFormatSysEx(data) {
|
|
2507
|
-
if (data.length <
|
|
2614
|
+
handleScaleOctaveTuning1ByteFormatSysEx(data, realtime) {
|
|
2615
|
+
if (data.length < 19) {
|
|
2508
2616
|
console.error("Data length is too short");
|
|
2509
2617
|
return;
|
|
2510
2618
|
}
|
|
@@ -2512,67 +2620,92 @@ export class Midy {
|
|
|
2512
2620
|
for (let i = 0; i < channelBitmap.length; i++) {
|
|
2513
2621
|
if (!channelBitmap[i])
|
|
2514
2622
|
continue;
|
|
2623
|
+
const channel = this.channels[i];
|
|
2515
2624
|
for (let j = 0; j < 12; j++) {
|
|
2516
|
-
const
|
|
2517
|
-
|
|
2625
|
+
const centValue = data[j + 7] - 64;
|
|
2626
|
+
channel.scaleOctaveTuningTable[j] = centValue;
|
|
2518
2627
|
}
|
|
2628
|
+
if (realtime)
|
|
2629
|
+
this.updateChannelDetune(channel);
|
|
2519
2630
|
}
|
|
2520
2631
|
}
|
|
2521
|
-
|
|
2522
|
-
if (
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
: 0;
|
|
2526
|
-
const pressure = (polyphonicKeyPressure - 64) / 37.5; // 2400 / 64;
|
|
2527
|
-
this.updateDetune(channel, note, pressure);
|
|
2632
|
+
handleScaleOctaveTuning2ByteFormatSysEx(data, realtime) {
|
|
2633
|
+
if (data.length < 31) {
|
|
2634
|
+
console.error("Data length is too short");
|
|
2635
|
+
return;
|
|
2528
2636
|
}
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
const
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
channel.state.channelPressure;
|
|
2542
|
-
const polyphonicKeyPressure = (0 < note.pressure)
|
|
2543
|
-
? channel.polyphonicKeyPressureTable[2] * note.pressure
|
|
2544
|
-
: 0;
|
|
2545
|
-
const pressure = (channelPressure + polyphonicKeyPressure) / 128;
|
|
2546
|
-
this.setVolumeEnvelope(channel, note, pressure);
|
|
2637
|
+
const channelBitmap = this.getChannelBitmap(data);
|
|
2638
|
+
for (let i = 0; i < channelBitmap.length; i++) {
|
|
2639
|
+
if (!channelBitmap[i])
|
|
2640
|
+
continue;
|
|
2641
|
+
const channel = this.channels[i];
|
|
2642
|
+
for (let j = 0; j < 12; j++) {
|
|
2643
|
+
const index = 7 + j * 2;
|
|
2644
|
+
const msb = data[index] & 0x7F;
|
|
2645
|
+
const lsb = data[index + 1] & 0x7F;
|
|
2646
|
+
const value14bit = msb * 128 + lsb;
|
|
2647
|
+
const centValue = (value14bit - 8192) / 8.192;
|
|
2648
|
+
channel.scaleOctaveTuningTable[j] = centValue;
|
|
2547
2649
|
}
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
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
|
-
|
|
2650
|
+
if (realtime)
|
|
2651
|
+
this.updateChannelDetune(channel);
|
|
2652
|
+
}
|
|
2653
|
+
}
|
|
2654
|
+
getPitchControl(channel, note) {
|
|
2655
|
+
const polyphonicKeyPressure = (channel.polyphonicKeyPressureTable[0] - 64) *
|
|
2656
|
+
note.pressure;
|
|
2657
|
+
return polyphonicKeyPressure * note.pressure / 37.5; // 2400 / 64;
|
|
2658
|
+
}
|
|
2659
|
+
getFilterCutoffControl(channel, note) {
|
|
2660
|
+
const channelPressure = (channel.channelPressureTable[1] - 64) *
|
|
2661
|
+
channel.state.channelPressure;
|
|
2662
|
+
const polyphonicKeyPressure = (channel.polyphonicKeyPressureTable[1] - 64) *
|
|
2663
|
+
note.pressure;
|
|
2664
|
+
return (channelPressure + polyphonicKeyPressure) * 15;
|
|
2665
|
+
}
|
|
2666
|
+
getAmplitudeControl(channel, note) {
|
|
2667
|
+
const channelPressure = channel.channelPressureTable[2] *
|
|
2668
|
+
channel.state.channelPressure;
|
|
2669
|
+
const polyphonicKeyPressure = channel.polyphonicKeyPressureTable[2] *
|
|
2670
|
+
note.pressure;
|
|
2671
|
+
return (channelPressure + polyphonicKeyPressure) / 128;
|
|
2672
|
+
}
|
|
2673
|
+
getLFOPitchDepth(channel, note) {
|
|
2674
|
+
const channelPressure = channel.channelPressureTable[3] *
|
|
2675
|
+
channel.state.channelPressure;
|
|
2676
|
+
const polyphonicKeyPressure = channel.polyphonicKeyPressureTable[3] *
|
|
2677
|
+
note.pressure;
|
|
2678
|
+
return (channelPressure + polyphonicKeyPressure) / 254 * 600;
|
|
2679
|
+
}
|
|
2680
|
+
getLFOFilterDepth(channel, note) {
|
|
2681
|
+
const channelPressure = channel.channelPressureTable[4] *
|
|
2682
|
+
channel.state.channelPressure;
|
|
2683
|
+
const polyphonicKeyPressure = channel.polyphonicKeyPressureTable[4] *
|
|
2684
|
+
note.pressure;
|
|
2685
|
+
return (channelPressure + polyphonicKeyPressure) / 254 * 2400;
|
|
2686
|
+
}
|
|
2687
|
+
getLFOAmplitudeDepth(channel, note) {
|
|
2688
|
+
const channelPressure = channel.channelPressureTable[5] *
|
|
2689
|
+
channel.state.channelPressure;
|
|
2690
|
+
const polyphonicKeyPressure = channel.polyphonicKeyPressureTable[5] *
|
|
2691
|
+
note.pressure;
|
|
2692
|
+
return (channelPressure + polyphonicKeyPressure) / 254;
|
|
2693
|
+
}
|
|
2694
|
+
setControllerParameters(channel, note, table) {
|
|
2695
|
+
if (table[0] !== 64)
|
|
2696
|
+
this.updateDetune(channel, note);
|
|
2697
|
+
if (!note.portamento) {
|
|
2698
|
+
if (table[1] !== 64)
|
|
2699
|
+
this.setFilterEnvelope(channel, note);
|
|
2700
|
+
if (table[2] !== 64)
|
|
2701
|
+
this.setVolumeEnvelope(channel, note);
|
|
2702
|
+
}
|
|
2703
|
+
if (table[3] !== 0)
|
|
2704
|
+
this.setModLfoToPitch(channel, note);
|
|
2705
|
+
if (table[4] !== 0)
|
|
2706
|
+
this.setModLfoToFilterFc(channel, note);
|
|
2707
|
+
if (table[5] !== 0)
|
|
2708
|
+
this.setModLfoToVolume(channel, note);
|
|
2576
2709
|
}
|
|
2577
2710
|
handleChannelPressureSysEx(data, tableName) {
|
|
2578
2711
|
const channelNumber = data[4];
|
|
@@ -2603,7 +2736,7 @@ export class Midy {
|
|
|
2603
2736
|
const note = noteList[i];
|
|
2604
2737
|
if (!note)
|
|
2605
2738
|
continue;
|
|
2606
|
-
this.
|
|
2739
|
+
this.setControllerParameters(channel, note, table);
|
|
2607
2740
|
}
|
|
2608
2741
|
});
|
|
2609
2742
|
}
|
|
@@ -2666,10 +2799,6 @@ Object.defineProperty(Midy, "channelSettings", {
|
|
|
2666
2799
|
value: {
|
|
2667
2800
|
currentBufferSource: null,
|
|
2668
2801
|
detune: 0,
|
|
2669
|
-
scaleOctaveTuningTable: new Array(12).fill(0), // cent
|
|
2670
|
-
channelPressureTable: new Uint8Array([64, 64, 64, 0, 0, 0]),
|
|
2671
|
-
polyphonicKeyPressureTable: new Uint8Array([64, 64, 64, 0, 0, 0]),
|
|
2672
|
-
keyBasedInstrumentControlTable: new Int8Array(128 * 128), // [-64, 63]
|
|
2673
2802
|
program: 0,
|
|
2674
2803
|
bank: 121 * 128,
|
|
2675
2804
|
bankMSB: 121,
|