@marmooo/midy 0.3.2 → 0.3.4
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 +11 -10
- package/esm/midy-GM1.d.ts.map +1 -1
- package/esm/midy-GM1.js +114 -123
- package/esm/midy-GM2.d.ts +26 -42
- package/esm/midy-GM2.d.ts.map +1 -1
- package/esm/midy-GM2.js +295 -328
- package/esm/midy-GMLite.d.ts +12 -11
- package/esm/midy-GMLite.d.ts.map +1 -1
- package/esm/midy-GMLite.js +124 -141
- package/esm/midy.d.ts +27 -43
- package/esm/midy.d.ts.map +1 -1
- package/esm/midy.js +335 -352
- package/package.json +1 -1
- package/script/midy-GM1.d.ts +11 -10
- package/script/midy-GM1.d.ts.map +1 -1
- package/script/midy-GM1.js +114 -123
- package/script/midy-GM2.d.ts +26 -42
- package/script/midy-GM2.d.ts.map +1 -1
- package/script/midy-GM2.js +295 -328
- package/script/midy-GMLite.d.ts +12 -11
- package/script/midy-GMLite.d.ts.map +1 -1
- package/script/midy-GMLite.js +124 -141
- package/script/midy.d.ts +27 -43
- package/script/midy.d.ts.map +1 -1
- package/script/midy.js +335 -352
package/script/midy-GM2.js
CHANGED
|
@@ -11,11 +11,11 @@ class Note {
|
|
|
11
11
|
writable: true,
|
|
12
12
|
value: -1
|
|
13
13
|
});
|
|
14
|
-
Object.defineProperty(this, "
|
|
14
|
+
Object.defineProperty(this, "ending", {
|
|
15
15
|
enumerable: true,
|
|
16
16
|
configurable: true,
|
|
17
17
|
writable: true,
|
|
18
|
-
value:
|
|
18
|
+
value: false
|
|
19
19
|
});
|
|
20
20
|
Object.defineProperty(this, "bufferSource", {
|
|
21
21
|
enumerable: true,
|
|
@@ -47,24 +47,6 @@ class Note {
|
|
|
47
47
|
writable: true,
|
|
48
48
|
value: void 0
|
|
49
49
|
});
|
|
50
|
-
Object.defineProperty(this, "volumeNode", {
|
|
51
|
-
enumerable: true,
|
|
52
|
-
configurable: true,
|
|
53
|
-
writable: true,
|
|
54
|
-
value: void 0
|
|
55
|
-
});
|
|
56
|
-
Object.defineProperty(this, "gainL", {
|
|
57
|
-
enumerable: true,
|
|
58
|
-
configurable: true,
|
|
59
|
-
writable: true,
|
|
60
|
-
value: void 0
|
|
61
|
-
});
|
|
62
|
-
Object.defineProperty(this, "gainR", {
|
|
63
|
-
enumerable: true,
|
|
64
|
-
configurable: true,
|
|
65
|
-
writable: true,
|
|
66
|
-
value: void 0
|
|
67
|
-
});
|
|
68
50
|
Object.defineProperty(this, "modulationLFO", {
|
|
69
51
|
enumerable: true,
|
|
70
52
|
configurable: true,
|
|
@@ -202,6 +184,16 @@ class ControllerState {
|
|
|
202
184
|
}
|
|
203
185
|
}
|
|
204
186
|
}
|
|
187
|
+
const volumeEnvelopeKeys = [
|
|
188
|
+
"volDelay",
|
|
189
|
+
"volAttack",
|
|
190
|
+
"volHold",
|
|
191
|
+
"volDecay",
|
|
192
|
+
"volSustain",
|
|
193
|
+
"volRelease",
|
|
194
|
+
"initialAttenuation",
|
|
195
|
+
];
|
|
196
|
+
const volumeEnvelopeKeySet = new Set(volumeEnvelopeKeys);
|
|
205
197
|
const filterEnvelopeKeys = [
|
|
206
198
|
"modEnvToPitch",
|
|
207
199
|
"initialFilterFc",
|
|
@@ -211,22 +203,20 @@ const filterEnvelopeKeys = [
|
|
|
211
203
|
"modHold",
|
|
212
204
|
"modDecay",
|
|
213
205
|
"modSustain",
|
|
214
|
-
"modRelease",
|
|
215
|
-
"playbackRate",
|
|
216
206
|
];
|
|
217
207
|
const filterEnvelopeKeySet = new Set(filterEnvelopeKeys);
|
|
218
|
-
const
|
|
219
|
-
"
|
|
220
|
-
"
|
|
221
|
-
"
|
|
222
|
-
"
|
|
223
|
-
"
|
|
224
|
-
"
|
|
225
|
-
"
|
|
208
|
+
const pitchEnvelopeKeys = [
|
|
209
|
+
"modEnvToPitch",
|
|
210
|
+
"modDelay",
|
|
211
|
+
"modAttack",
|
|
212
|
+
"modHold",
|
|
213
|
+
"modDecay",
|
|
214
|
+
"modSustain",
|
|
215
|
+
"playbackRate",
|
|
226
216
|
];
|
|
227
|
-
const
|
|
217
|
+
const pitchEnvelopeKeySet = new Set(pitchEnvelopeKeys);
|
|
228
218
|
class MidyGM2 {
|
|
229
|
-
constructor(audioContext
|
|
219
|
+
constructor(audioContext) {
|
|
230
220
|
Object.defineProperty(this, "mode", {
|
|
231
221
|
enumerable: true,
|
|
232
222
|
configurable: true,
|
|
@@ -250,6 +240,7 @@ class MidyGM2 {
|
|
|
250
240
|
configurable: true,
|
|
251
241
|
writable: true,
|
|
252
242
|
value: {
|
|
243
|
+
algorithm: "SchroederReverb",
|
|
253
244
|
time: this.getReverbTime(64),
|
|
254
245
|
feedback: 0.8,
|
|
255
246
|
}
|
|
@@ -398,30 +389,7 @@ class MidyGM2 {
|
|
|
398
389
|
writable: true,
|
|
399
390
|
value: new Array(this.numChannels * drumExclusiveClassCount)
|
|
400
391
|
});
|
|
401
|
-
Object.defineProperty(this, "defaultOptions", {
|
|
402
|
-
enumerable: true,
|
|
403
|
-
configurable: true,
|
|
404
|
-
writable: true,
|
|
405
|
-
value: {
|
|
406
|
-
reverbAlgorithm: (audioContext) => {
|
|
407
|
-
const { time: rt60, feedback } = this.reverb;
|
|
408
|
-
// const delay = this.calcDelay(rt60, feedback);
|
|
409
|
-
// const impulse = this.createConvolutionReverbImpulse(
|
|
410
|
-
// audioContext,
|
|
411
|
-
// rt60,
|
|
412
|
-
// delay,
|
|
413
|
-
// );
|
|
414
|
-
// return this.createConvolutionReverb(audioContext, impulse);
|
|
415
|
-
const combFeedbacks = this.generateDistributedArray(feedback, 4);
|
|
416
|
-
const combDelays = combFeedbacks.map((feedback) => this.calcDelay(rt60, feedback));
|
|
417
|
-
const allpassFeedbacks = this.generateDistributedArray(feedback, 4);
|
|
418
|
-
const allpassDelays = allpassFeedbacks.map((feedback) => this.calcDelay(rt60, feedback));
|
|
419
|
-
return this.createSchroederReverb(audioContext, combFeedbacks, combDelays, allpassFeedbacks, allpassDelays);
|
|
420
|
-
},
|
|
421
|
-
}
|
|
422
|
-
});
|
|
423
392
|
this.audioContext = audioContext;
|
|
424
|
-
this.options = { ...this.defaultOptions, ...options };
|
|
425
393
|
this.masterVolume = new GainNode(audioContext);
|
|
426
394
|
this.scheduler = new GainNode(audioContext, { gain: 0 });
|
|
427
395
|
this.schedulerBuffer = new AudioBuffer({
|
|
@@ -431,7 +399,7 @@ class MidyGM2 {
|
|
|
431
399
|
this.voiceParamsHandlers = this.createVoiceParamsHandlers();
|
|
432
400
|
this.controlChangeHandlers = this.createControlChangeHandlers();
|
|
433
401
|
this.channels = this.createChannels(audioContext);
|
|
434
|
-
this.reverbEffect = this.
|
|
402
|
+
this.reverbEffect = this.createReverbEffect(audioContext);
|
|
435
403
|
this.chorusEffect = this.createChorusEffect(audioContext);
|
|
436
404
|
this.chorusEffect.output.connect(this.masterVolume);
|
|
437
405
|
this.reverbEffect.output.connect(this.masterVolume);
|
|
@@ -458,24 +426,44 @@ class MidyGM2 {
|
|
|
458
426
|
}
|
|
459
427
|
}
|
|
460
428
|
}
|
|
461
|
-
async loadSoundFont(
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
429
|
+
async loadSoundFont(input) {
|
|
430
|
+
let uint8Array;
|
|
431
|
+
if (typeof input === "string") {
|
|
432
|
+
const response = await fetch(input);
|
|
433
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
434
|
+
uint8Array = new Uint8Array(arrayBuffer);
|
|
435
|
+
}
|
|
436
|
+
else if (input instanceof Uint8Array) {
|
|
437
|
+
uint8Array = input;
|
|
438
|
+
}
|
|
439
|
+
else {
|
|
440
|
+
throw new TypeError("input must be a URL string or Uint8Array");
|
|
441
|
+
}
|
|
442
|
+
const parsed = (0, soundfont_parser_1.parse)(uint8Array);
|
|
465
443
|
const soundFont = new soundfont_parser_1.SoundFont(parsed);
|
|
466
444
|
this.addSoundFont(soundFont);
|
|
467
445
|
}
|
|
468
|
-
async loadMIDI(
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
446
|
+
async loadMIDI(input) {
|
|
447
|
+
let uint8Array;
|
|
448
|
+
if (typeof input === "string") {
|
|
449
|
+
const response = await fetch(input);
|
|
450
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
451
|
+
uint8Array = new Uint8Array(arrayBuffer);
|
|
452
|
+
}
|
|
453
|
+
else if (input instanceof Uint8Array) {
|
|
454
|
+
uint8Array = input;
|
|
455
|
+
}
|
|
456
|
+
else {
|
|
457
|
+
throw new TypeError("input must be a URL string or Uint8Array");
|
|
458
|
+
}
|
|
459
|
+
const midi = (0, midi_file_1.parseMidi)(uint8Array);
|
|
472
460
|
this.ticksPerBeat = midi.header.ticksPerBeat;
|
|
473
461
|
const midiData = this.extractMidiData(midi);
|
|
474
462
|
this.instruments = midiData.instruments;
|
|
475
463
|
this.timeline = midiData.timeline;
|
|
476
464
|
this.totalTime = this.calcTotalTime();
|
|
477
465
|
}
|
|
478
|
-
|
|
466
|
+
createChannelAudioNodes(audioContext) {
|
|
479
467
|
const { gainLeft, gainRight } = this.panToGain(defaultControllerState.pan.defaultValue);
|
|
480
468
|
const gainL = new GainNode(audioContext, { gain: gainLeft });
|
|
481
469
|
const gainR = new GainNode(audioContext, { gain: gainRight });
|
|
@@ -490,10 +478,10 @@ class MidyGM2 {
|
|
|
490
478
|
};
|
|
491
479
|
}
|
|
492
480
|
resetChannelTable(channel) {
|
|
493
|
-
|
|
481
|
+
channel.controlTable.fill(-1);
|
|
494
482
|
channel.scaleOctaveTuningTable.fill(0); // [-100, 100] cent
|
|
495
|
-
channel.channelPressureTable.
|
|
496
|
-
channel.keyBasedInstrumentControlTable.fill(
|
|
483
|
+
channel.channelPressureTable.fill(-1);
|
|
484
|
+
channel.keyBasedInstrumentControlTable.fill(-1);
|
|
497
485
|
}
|
|
498
486
|
createChannels(audioContext) {
|
|
499
487
|
const channels = Array.from({ length: this.numChannels }, () => {
|
|
@@ -502,14 +490,16 @@ class MidyGM2 {
|
|
|
502
490
|
isDrum: false,
|
|
503
491
|
state: new ControllerState(),
|
|
504
492
|
...this.constructor.channelSettings,
|
|
505
|
-
...this.
|
|
493
|
+
...this.createChannelAudioNodes(audioContext),
|
|
506
494
|
scheduledNotes: [],
|
|
507
495
|
sustainNotes: [],
|
|
508
496
|
sostenutoNotes: [],
|
|
509
497
|
controlTable: this.initControlTable(),
|
|
510
498
|
scaleOctaveTuningTable: new Int8Array(12), // [-64, 63] cent
|
|
511
|
-
channelPressureTable: new
|
|
512
|
-
keyBasedInstrumentControlTable: new Int8Array(128 * 128)
|
|
499
|
+
channelPressureTable: new Int8Array(6).fill(-1),
|
|
500
|
+
keyBasedInstrumentControlTable: new Int8Array(128 * 128).fill(-1),
|
|
501
|
+
keyBasedGainLs: new Array(128),
|
|
502
|
+
keyBasedGainRs: new Array(128),
|
|
513
503
|
};
|
|
514
504
|
});
|
|
515
505
|
return channels;
|
|
@@ -543,10 +533,17 @@ class MidyGM2 {
|
|
|
543
533
|
return audioBuffer;
|
|
544
534
|
}
|
|
545
535
|
}
|
|
546
|
-
|
|
536
|
+
isLoopDrum(channel, noteNumber) {
|
|
537
|
+
const programNumber = channel.programNumber;
|
|
538
|
+
return ((programNumber === 48 && noteNumber === 88) ||
|
|
539
|
+
(programNumber === 56 && 47 <= noteNumber && noteNumber <= 84));
|
|
540
|
+
}
|
|
541
|
+
createBufferSource(channel, noteNumber, voiceParams, audioBuffer) {
|
|
547
542
|
const bufferSource = new AudioBufferSourceNode(this.audioContext);
|
|
548
543
|
bufferSource.buffer = audioBuffer;
|
|
549
|
-
bufferSource.loop =
|
|
544
|
+
bufferSource.loop = channel.isDrum
|
|
545
|
+
? this.isLoopDrum(channel, noteNumber)
|
|
546
|
+
: (voiceParams.sampleModes % 2 !== 0);
|
|
550
547
|
if (bufferSource.loop) {
|
|
551
548
|
bufferSource.loopStart = voiceParams.loopStart / voiceParams.sampleRate;
|
|
552
549
|
bufferSource.loopEnd = voiceParams.loopEnd / voiceParams.sampleRate;
|
|
@@ -561,12 +558,13 @@ class MidyGM2 {
|
|
|
561
558
|
const delay = this.startDelay - resumeTime;
|
|
562
559
|
const startTime = event.startTime + delay;
|
|
563
560
|
switch (event.type) {
|
|
564
|
-
case "noteOn":
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
561
|
+
case "noteOn":
|
|
562
|
+
await this.scheduleNoteOn(event.channel, event.noteNumber, event.velocity, startTime);
|
|
563
|
+
break;
|
|
564
|
+
case "noteOff": {
|
|
565
|
+
const notePromise = this.scheduleNoteOff(event.channel, event.noteNumber, event.velocity, startTime, false);
|
|
566
|
+
if (notePromise)
|
|
567
|
+
this.notePromises.push(notePromise);
|
|
570
568
|
break;
|
|
571
569
|
}
|
|
572
570
|
case "controller":
|
|
@@ -671,6 +669,7 @@ class MidyGM2 {
|
|
|
671
669
|
return `${programNumber}:${noteNumber}:${velocity}`;
|
|
672
670
|
}
|
|
673
671
|
extractMidiData(midi) {
|
|
672
|
+
this.audioBufferCounter.clear();
|
|
674
673
|
const instruments = new Set();
|
|
675
674
|
const timeline = [];
|
|
676
675
|
const tmpChannels = new Array(this.channels.length);
|
|
@@ -770,38 +769,13 @@ class MidyGM2 {
|
|
|
770
769
|
prevTempoTicks = event.ticks;
|
|
771
770
|
}
|
|
772
771
|
}
|
|
773
|
-
const activeNotes = new Array(this.channels.length * 128);
|
|
774
|
-
for (let i = 0; i < activeNotes.length; i++) {
|
|
775
|
-
activeNotes[i] = [];
|
|
776
|
-
}
|
|
777
|
-
for (let i = 0; i < timeline.length; i++) {
|
|
778
|
-
const event = timeline[i];
|
|
779
|
-
switch (event.type) {
|
|
780
|
-
case "noteOn": {
|
|
781
|
-
const index = event.channel * 128 + event.noteNumber;
|
|
782
|
-
activeNotes[index].push(event);
|
|
783
|
-
break;
|
|
784
|
-
}
|
|
785
|
-
case "noteOff": {
|
|
786
|
-
const index = event.channel * 128 + event.noteNumber;
|
|
787
|
-
const noteOn = activeNotes[index].pop();
|
|
788
|
-
if (noteOn) {
|
|
789
|
-
noteOn.noteOffEvent = event;
|
|
790
|
-
}
|
|
791
|
-
else {
|
|
792
|
-
const eventString = JSON.stringify(event, null, 2);
|
|
793
|
-
console.warn(`noteOff without matching noteOn: ${eventString}`);
|
|
794
|
-
}
|
|
795
|
-
}
|
|
796
|
-
}
|
|
797
|
-
}
|
|
798
772
|
return { instruments, timeline };
|
|
799
773
|
}
|
|
800
774
|
stopActiveNotes(channelNumber, velocity, force, scheduleTime) {
|
|
801
775
|
const channel = this.channels[channelNumber];
|
|
802
776
|
const promises = [];
|
|
803
777
|
this.processActiveNotes(channel, scheduleTime, (note) => {
|
|
804
|
-
const promise = this.scheduleNoteOff(channelNumber, note.noteNumber, velocity, scheduleTime, force
|
|
778
|
+
const promise = this.scheduleNoteOff(channelNumber, note.noteNumber, velocity, scheduleTime, force);
|
|
805
779
|
this.notePromises.push(promise);
|
|
806
780
|
promises.push(promise);
|
|
807
781
|
});
|
|
@@ -810,8 +784,8 @@ class MidyGM2 {
|
|
|
810
784
|
stopChannelNotes(channelNumber, velocity, force, scheduleTime) {
|
|
811
785
|
const channel = this.channels[channelNumber];
|
|
812
786
|
const promises = [];
|
|
813
|
-
this.processScheduledNotes(channel, (note) => {
|
|
814
|
-
const promise = this.scheduleNoteOff(channelNumber, note, velocity, scheduleTime, force);
|
|
787
|
+
this.processScheduledNotes(channel, scheduleTime, (note) => {
|
|
788
|
+
const promise = this.scheduleNoteOff(channelNumber, note.noteNumber, velocity, scheduleTime, force);
|
|
815
789
|
this.notePromises.push(promise);
|
|
816
790
|
promises.push(promise);
|
|
817
791
|
});
|
|
@@ -869,7 +843,7 @@ class MidyGM2 {
|
|
|
869
843
|
const now = this.audioContext.currentTime;
|
|
870
844
|
return this.resumeTime + now - this.startTime - this.startDelay;
|
|
871
845
|
}
|
|
872
|
-
processScheduledNotes(channel, callback) {
|
|
846
|
+
processScheduledNotes(channel, scheduleTime, callback) {
|
|
873
847
|
const scheduledNotes = channel.scheduledNotes;
|
|
874
848
|
for (let i = 0; i < scheduledNotes.length; i++) {
|
|
875
849
|
const note = scheduledNotes[i];
|
|
@@ -877,6 +851,8 @@ class MidyGM2 {
|
|
|
877
851
|
continue;
|
|
878
852
|
if (note.ending)
|
|
879
853
|
continue;
|
|
854
|
+
if (note.startTime < scheduleTime)
|
|
855
|
+
continue;
|
|
880
856
|
callback(note);
|
|
881
857
|
}
|
|
882
858
|
}
|
|
@@ -888,11 +864,8 @@ class MidyGM2 {
|
|
|
888
864
|
continue;
|
|
889
865
|
if (note.ending)
|
|
890
866
|
continue;
|
|
891
|
-
const noteOffEvent = note.noteOffEvent;
|
|
892
|
-
if (noteOffEvent && noteOffEvent.startTime < scheduleTime)
|
|
893
|
-
continue;
|
|
894
867
|
if (scheduleTime < note.startTime)
|
|
895
|
-
|
|
868
|
+
break;
|
|
896
869
|
callback(note);
|
|
897
870
|
}
|
|
898
871
|
}
|
|
@@ -981,6 +954,22 @@ class MidyGM2 {
|
|
|
981
954
|
const output = allpasses.at(-1);
|
|
982
955
|
return { input, output };
|
|
983
956
|
}
|
|
957
|
+
createReverbEffect(audioContext) {
|
|
958
|
+
const { algorithm, time: rt60, feedback } = this.reverb;
|
|
959
|
+
switch (algorithm) {
|
|
960
|
+
case "ConvolutionReverb": {
|
|
961
|
+
const impulse = this.createConvolutionReverbImpulse(audioContext, rt60, this.calcDelay(rt60, feedback));
|
|
962
|
+
return this.createConvolutionReverb(audioContext, impulse);
|
|
963
|
+
}
|
|
964
|
+
case "SchroederReverb": {
|
|
965
|
+
const combFeedbacks = this.generateDistributedArray(feedback, 4);
|
|
966
|
+
const combDelays = combFeedbacks.map((feedback) => this.calcDelay(rt60, feedback));
|
|
967
|
+
const allpassFeedbacks = this.generateDistributedArray(feedback, 4);
|
|
968
|
+
const allpassDelays = allpassFeedbacks.map((feedback) => this.calcDelay(rt60, feedback));
|
|
969
|
+
return this.createSchroederReverb(audioContext, combFeedbacks, combDelays, allpassFeedbacks, allpassDelays);
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
}
|
|
984
973
|
createChorusEffect(audioContext) {
|
|
985
974
|
const input = new GainNode(audioContext);
|
|
986
975
|
const output = new GainNode(audioContext);
|
|
@@ -1045,22 +1034,28 @@ class MidyGM2 {
|
|
|
1045
1034
|
const pitchWheel = channel.state.pitchWheel * 2 - 1;
|
|
1046
1035
|
const pitchWheelSensitivity = channel.state.pitchWheelSensitivity * 12800;
|
|
1047
1036
|
const pitch = pitchWheel * pitchWheelSensitivity;
|
|
1048
|
-
const
|
|
1049
|
-
|
|
1050
|
-
|
|
1037
|
+
const channelPressureRaw = channel.channelPressureTable[0];
|
|
1038
|
+
if (0 <= channelPressureRaw) {
|
|
1039
|
+
const channelPressureDepth = (channelPressureRaw - 64) / 37.5; // 2400 / 64;
|
|
1040
|
+
const channelPressure = channelPressureDepth *
|
|
1041
|
+
channel.state.channelPressure;
|
|
1042
|
+
return tuning + pitch + channelPressure;
|
|
1043
|
+
}
|
|
1044
|
+
else {
|
|
1045
|
+
return tuning + pitch;
|
|
1046
|
+
}
|
|
1051
1047
|
}
|
|
1052
1048
|
calcNoteDetune(channel, note) {
|
|
1053
1049
|
return channel.scaleOctaveTuningTable[note.noteNumber % 12];
|
|
1054
1050
|
}
|
|
1055
1051
|
updateChannelDetune(channel, scheduleTime) {
|
|
1056
|
-
this.processScheduledNotes(channel, (note) => {
|
|
1052
|
+
this.processScheduledNotes(channel, scheduleTime, (note) => {
|
|
1057
1053
|
this.updateDetune(channel, note, scheduleTime);
|
|
1058
1054
|
});
|
|
1059
1055
|
}
|
|
1060
1056
|
updateDetune(channel, note, scheduleTime) {
|
|
1061
1057
|
const noteDetune = this.calcNoteDetune(channel, note);
|
|
1062
|
-
const
|
|
1063
|
-
const detune = channel.detune + noteDetune + pitchControl;
|
|
1058
|
+
const detune = channel.detune + noteDetune;
|
|
1064
1059
|
if (0.5 <= channel.state.portamento && 0 <= note.portamentoNoteNumber) {
|
|
1065
1060
|
const startTime = note.startTime;
|
|
1066
1061
|
const deltaCent = (note.noteNumber - note.portamentoNoteNumber) * 100;
|
|
@@ -1200,10 +1195,8 @@ class MidyGM2 {
|
|
|
1200
1195
|
return Math.max(minFrequency, Math.min(frequency, maxFrequency));
|
|
1201
1196
|
}
|
|
1202
1197
|
setPortamentoFilterEnvelope(channel, note, scheduleTime) {
|
|
1203
|
-
const
|
|
1204
|
-
const
|
|
1205
|
-
const softPedalFactor = 1 -
|
|
1206
|
-
(0.1 + (noteNumber / 127) * 0.2) * state.softPedal;
|
|
1198
|
+
const { voiceParams, startTime } = note;
|
|
1199
|
+
const softPedalFactor = this.getSoftPedalFactor(channel, note);
|
|
1207
1200
|
const baseCent = voiceParams.initialFilterFc +
|
|
1208
1201
|
this.getFilterCutoffControl(channel);
|
|
1209
1202
|
const baseFreq = this.centToHz(baseCent) * softPedalFactor;
|
|
@@ -1221,10 +1214,8 @@ class MidyGM2 {
|
|
|
1221
1214
|
.linearRampToValueAtTime(adjustedSustainFreq, portamentoTime);
|
|
1222
1215
|
}
|
|
1223
1216
|
setFilterEnvelope(channel, note, scheduleTime) {
|
|
1224
|
-
const
|
|
1225
|
-
const
|
|
1226
|
-
const softPedalFactor = 1 -
|
|
1227
|
-
(0.1 + (noteNumber / 127) * 0.2) * state.softPedal;
|
|
1217
|
+
const { voiceParams, startTime } = note;
|
|
1218
|
+
const softPedalFactor = this.getSoftPedalFactor(channel, note);
|
|
1228
1219
|
const baseCent = voiceParams.initialFilterFc +
|
|
1229
1220
|
this.getFilterCutoffControl(channel);
|
|
1230
1221
|
const baseFreq = this.centToHz(baseCent) * softPedalFactor;
|
|
@@ -1304,10 +1295,7 @@ class MidyGM2 {
|
|
|
1304
1295
|
const voiceParams = voice.getAllParams(controllerState);
|
|
1305
1296
|
const note = new Note(noteNumber, velocity, startTime, voice, voiceParams);
|
|
1306
1297
|
const audioBuffer = await this.getAudioBuffer(channel.programNumber, noteNumber, velocity, voiceParams, isSF3);
|
|
1307
|
-
note.bufferSource = this.createBufferSource(voiceParams, audioBuffer);
|
|
1308
|
-
note.volumeNode = new GainNode(this.audioContext);
|
|
1309
|
-
note.gainL = new GainNode(this.audioContext);
|
|
1310
|
-
note.gainR = new GainNode(this.audioContext);
|
|
1298
|
+
note.bufferSource = this.createBufferSource(channel, noteNumber, voiceParams, audioBuffer);
|
|
1311
1299
|
note.volumeEnvelopeNode = new GainNode(this.audioContext);
|
|
1312
1300
|
note.filterNode = new BiquadFilterNode(this.audioContext, {
|
|
1313
1301
|
type: "lowpass",
|
|
@@ -1340,13 +1328,10 @@ class MidyGM2 {
|
|
|
1340
1328
|
}
|
|
1341
1329
|
note.bufferSource.connect(note.filterNode);
|
|
1342
1330
|
note.filterNode.connect(note.volumeEnvelopeNode);
|
|
1343
|
-
|
|
1344
|
-
note.volumeNode.connect(note.gainL);
|
|
1345
|
-
note.volumeNode.connect(note.gainR);
|
|
1346
|
-
if (0 < channel.chorusSendLevel) {
|
|
1331
|
+
if (0 < state.chorusSendLevel) {
|
|
1347
1332
|
this.setChorusEffectsSend(channel, note, 0, now);
|
|
1348
1333
|
}
|
|
1349
|
-
if (0 <
|
|
1334
|
+
if (0 < state.reverbSendLevel) {
|
|
1350
1335
|
this.setReverbEffectsSend(channel, note, 0, now);
|
|
1351
1336
|
}
|
|
1352
1337
|
note.bufferSource.start(startTime);
|
|
@@ -1376,7 +1361,7 @@ class MidyGM2 {
|
|
|
1376
1361
|
if (prev) {
|
|
1377
1362
|
const [prevNote, prevChannelNumber] = prev;
|
|
1378
1363
|
if (prevNote && !prevNote.ending) {
|
|
1379
|
-
this.scheduleNoteOff(prevChannelNumber, prevNote, 0, // velocity,
|
|
1364
|
+
this.scheduleNoteOff(prevChannelNumber, prevNote.noteNumber, 0, // velocity,
|
|
1380
1365
|
startTime, true);
|
|
1381
1366
|
}
|
|
1382
1367
|
}
|
|
@@ -1396,19 +1381,12 @@ class MidyGM2 {
|
|
|
1396
1381
|
channelNumber;
|
|
1397
1382
|
const prevNote = this.drumExclusiveClassNotes[index];
|
|
1398
1383
|
if (prevNote && !prevNote.ending) {
|
|
1399
|
-
this.scheduleNoteOff(channelNumber, prevNote, 0, // velocity,
|
|
1384
|
+
this.scheduleNoteOff(channelNumber, prevNote.noteNumber, 0, // velocity,
|
|
1400
1385
|
startTime, true);
|
|
1401
1386
|
}
|
|
1402
1387
|
this.drumExclusiveClassNotes[index] = note;
|
|
1403
1388
|
}
|
|
1404
|
-
|
|
1405
|
-
if (!channel.isDrum)
|
|
1406
|
-
return false;
|
|
1407
|
-
const programNumber = channel.programNumber;
|
|
1408
|
-
return !((programNumber === 48 && noteNumber === 88) ||
|
|
1409
|
-
(programNumber === 56 && 47 <= noteNumber && noteNumber <= 84));
|
|
1410
|
-
}
|
|
1411
|
-
async scheduleNoteOn(channelNumber, noteNumber, velocity, startTime, noteOffEvent) {
|
|
1389
|
+
async scheduleNoteOn(channelNumber, noteNumber, velocity, startTime) {
|
|
1412
1390
|
const channel = this.channels[channelNumber];
|
|
1413
1391
|
const bankNumber = this.calcBank(channel, channelNumber);
|
|
1414
1392
|
const soundFontIndex = this.soundFontTable[channel.programNumber].get(bankNumber);
|
|
@@ -1420,9 +1398,18 @@ class MidyGM2 {
|
|
|
1420
1398
|
return;
|
|
1421
1399
|
const isSF3 = soundFont.parsed.info.version.major === 3;
|
|
1422
1400
|
const note = await this.createNote(channel, voice, noteNumber, velocity, startTime, isSF3);
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1401
|
+
if (channel.isDrum) {
|
|
1402
|
+
const audioContext = this.audioContext;
|
|
1403
|
+
const { gainL, gainR } = this.createChannelAudioNodes(audioContext);
|
|
1404
|
+
channel.keyBasedGainLs[noteNumber] = gainL;
|
|
1405
|
+
channel.keyBasedGainRs[noteNumber] = gainR;
|
|
1406
|
+
note.volumeEnvelopeNode.connect(gainL);
|
|
1407
|
+
note.volumeEnvelopeNode.connect(gainR);
|
|
1408
|
+
}
|
|
1409
|
+
else {
|
|
1410
|
+
note.volumeEnvelopeNode.connect(channel.gainL);
|
|
1411
|
+
note.volumeEnvelopeNode.connect(channel.gainR);
|
|
1412
|
+
}
|
|
1426
1413
|
if (0.5 <= channel.state.sustainPedal) {
|
|
1427
1414
|
channel.sustainNotes.push(note);
|
|
1428
1415
|
}
|
|
@@ -1431,31 +1418,6 @@ class MidyGM2 {
|
|
|
1431
1418
|
const scheduledNotes = channel.scheduledNotes;
|
|
1432
1419
|
note.index = scheduledNotes.length;
|
|
1433
1420
|
scheduledNotes.push(note);
|
|
1434
|
-
if (this.isDrumNoteOffException(channel, noteNumber)) {
|
|
1435
|
-
const stopTime = startTime + note.bufferSource.buffer.duration;
|
|
1436
|
-
const promise = new Promise((resolve) => {
|
|
1437
|
-
note.bufferSource.onended = () => {
|
|
1438
|
-
scheduledNotes[note.index] = undefined;
|
|
1439
|
-
this.disconnectNote(note);
|
|
1440
|
-
resolve();
|
|
1441
|
-
};
|
|
1442
|
-
note.bufferSource.stop(stopTime);
|
|
1443
|
-
});
|
|
1444
|
-
this.notePromises.push(promise);
|
|
1445
|
-
}
|
|
1446
|
-
else if (noteOffEvent) {
|
|
1447
|
-
if (0.5 <= channel.state.portamento && 0 <= note.portamentoNoteNumber) {
|
|
1448
|
-
const portamentoTime = this.getPortamentoTime(channel, note);
|
|
1449
|
-
const portamentoEndTime = startTime + portamentoTime;
|
|
1450
|
-
const notePromise = this.scheduleNoteOff(channelNumber, note, 0, // velocity
|
|
1451
|
-
Math.max(noteOffEvent.startTime, portamentoEndTime), false);
|
|
1452
|
-
this.notePromises.push(notePromise);
|
|
1453
|
-
}
|
|
1454
|
-
else {
|
|
1455
|
-
const notePromise = this.scheduleNoteOff(channelNumber, note, noteOffEvent.velocity, noteOffEvent.startTime, false);
|
|
1456
|
-
this.notePromises.push(notePromise);
|
|
1457
|
-
}
|
|
1458
|
-
}
|
|
1459
1421
|
}
|
|
1460
1422
|
noteOn(channelNumber, noteNumber, velocity, scheduleTime) {
|
|
1461
1423
|
scheduleTime ??= this.audioContext.currentTime;
|
|
@@ -1465,9 +1427,6 @@ class MidyGM2 {
|
|
|
1465
1427
|
note.bufferSource.disconnect();
|
|
1466
1428
|
note.filterNode.disconnect();
|
|
1467
1429
|
note.volumeEnvelopeNode.disconnect();
|
|
1468
|
-
note.volumeNode.disconnect();
|
|
1469
|
-
note.gainL.disconnect();
|
|
1470
|
-
note.gainR.disconnect();
|
|
1471
1430
|
if (note.modulationDepth) {
|
|
1472
1431
|
note.volumeDepth.disconnect();
|
|
1473
1432
|
note.modulationDepth.disconnect();
|
|
@@ -1484,41 +1443,47 @@ class MidyGM2 {
|
|
|
1484
1443
|
note.chorusEffectsSend.disconnect();
|
|
1485
1444
|
}
|
|
1486
1445
|
}
|
|
1487
|
-
|
|
1446
|
+
releaseNote(channel, note, endTime) {
|
|
1447
|
+
const volRelease = endTime + note.voiceParams.volRelease;
|
|
1448
|
+
const modRelease = endTime + note.voiceParams.modRelease;
|
|
1449
|
+
const stopTime = Math.min(volRelease, modRelease);
|
|
1450
|
+
note.filterNode.frequency
|
|
1451
|
+
.cancelScheduledValues(endTime)
|
|
1452
|
+
.linearRampToValueAtTime(0, modRelease);
|
|
1488
1453
|
note.volumeEnvelopeNode.gain
|
|
1489
1454
|
.cancelScheduledValues(endTime)
|
|
1490
|
-
.linearRampToValueAtTime(0,
|
|
1491
|
-
note.ending = true;
|
|
1492
|
-
this.scheduleTask(() => {
|
|
1493
|
-
note.bufferSource.loop = false;
|
|
1494
|
-
}, stopTime);
|
|
1455
|
+
.linearRampToValueAtTime(0, volRelease);
|
|
1495
1456
|
return new Promise((resolve) => {
|
|
1496
|
-
|
|
1497
|
-
|
|
1457
|
+
this.scheduleTask(() => {
|
|
1458
|
+
const bufferSource = note.bufferSource;
|
|
1459
|
+
bufferSource.loop = false;
|
|
1460
|
+
bufferSource.stop(stopTime);
|
|
1498
1461
|
this.disconnectNote(note);
|
|
1462
|
+
channel.scheduledNotes[note.index] = undefined;
|
|
1499
1463
|
resolve();
|
|
1500
|
-
};
|
|
1501
|
-
note.bufferSource.stop(stopTime);
|
|
1464
|
+
}, stopTime);
|
|
1502
1465
|
});
|
|
1503
1466
|
}
|
|
1504
|
-
scheduleNoteOff(channelNumber,
|
|
1467
|
+
scheduleNoteOff(channelNumber, noteNumber, _velocity, endTime, force) {
|
|
1505
1468
|
const channel = this.channels[channelNumber];
|
|
1506
|
-
if (this.isDrumNoteOffException(channel, note.noteNumber))
|
|
1507
|
-
return;
|
|
1508
1469
|
const state = channel.state;
|
|
1509
1470
|
if (!force) {
|
|
1510
|
-
if (
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1471
|
+
if (channel.isDrum) {
|
|
1472
|
+
if (!this.isLoopDrum(channel, noteNumber))
|
|
1473
|
+
return;
|
|
1474
|
+
}
|
|
1475
|
+
else {
|
|
1476
|
+
if (0.5 <= state.sustainPedal)
|
|
1477
|
+
return;
|
|
1478
|
+
if (0.5 <= state.sostenutoPedal)
|
|
1479
|
+
return;
|
|
1480
|
+
}
|
|
1514
1481
|
}
|
|
1515
|
-
const
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
const stopTime = Math.min(volRelease, modRelease);
|
|
1521
|
-
return this.stopNote(channel, note, endTime, stopTime);
|
|
1482
|
+
const note = this.findNoteOffTarget(channel, noteNumber);
|
|
1483
|
+
if (!note)
|
|
1484
|
+
return;
|
|
1485
|
+
note.ending = true;
|
|
1486
|
+
this.releaseNote(channel, note, endTime);
|
|
1522
1487
|
}
|
|
1523
1488
|
findNoteOffTarget(channel, noteNumber) {
|
|
1524
1489
|
const scheduledNotes = channel.scheduledNotes;
|
|
@@ -1535,16 +1500,14 @@ class MidyGM2 {
|
|
|
1535
1500
|
}
|
|
1536
1501
|
noteOff(channelNumber, noteNumber, velocity, scheduleTime) {
|
|
1537
1502
|
scheduleTime ??= this.audioContext.currentTime;
|
|
1538
|
-
|
|
1539
|
-
const note = this.findNoteOffTarget(channel, noteNumber);
|
|
1540
|
-
return this.scheduleNoteOff(channelNumber, note, velocity, scheduleTime, false);
|
|
1503
|
+
return this.scheduleNoteOff(channelNumber, noteNumber, velocity, scheduleTime, false);
|
|
1541
1504
|
}
|
|
1542
1505
|
releaseSustainPedal(channelNumber, halfVelocity, scheduleTime) {
|
|
1543
1506
|
const velocity = halfVelocity * 2;
|
|
1544
1507
|
const channel = this.channels[channelNumber];
|
|
1545
1508
|
const promises = [];
|
|
1546
1509
|
for (let i = 0; i < channel.sustainNotes.length; i++) {
|
|
1547
|
-
const promise = this.scheduleNoteOff(channelNumber, channel.sustainNotes[i], velocity, scheduleTime);
|
|
1510
|
+
const promise = this.scheduleNoteOff(channelNumber, channel.sustainNotes[i].noteNumber, velocity, scheduleTime);
|
|
1548
1511
|
promises.push(promise);
|
|
1549
1512
|
}
|
|
1550
1513
|
channel.sustainNotes = [];
|
|
@@ -1558,7 +1521,7 @@ class MidyGM2 {
|
|
|
1558
1521
|
channel.state.sostenutoPedal = 0;
|
|
1559
1522
|
for (let i = 0; i < sostenutoNotes.length; i++) {
|
|
1560
1523
|
const note = sostenutoNotes[i];
|
|
1561
|
-
const promise = this.scheduleNoteOff(channelNumber, note, velocity, scheduleTime);
|
|
1524
|
+
const promise = this.scheduleNoteOff(channelNumber, note.noteNumber, velocity, scheduleTime);
|
|
1562
1525
|
promises.push(promise);
|
|
1563
1526
|
}
|
|
1564
1527
|
channel.sostenutoNotes = [];
|
|
@@ -1606,9 +1569,10 @@ class MidyGM2 {
|
|
|
1606
1569
|
const prev = channel.state.channelPressure;
|
|
1607
1570
|
const next = value / 127;
|
|
1608
1571
|
channel.state.channelPressure = next;
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1572
|
+
const channelPressureRaw = channel.channelPressureTable[0];
|
|
1573
|
+
if (0 <= channelPressureRaw) {
|
|
1574
|
+
const channelPressureDepth = (channelPressureRaw - 64) / 37.5; // 2400 / 64;
|
|
1575
|
+
channel.detune += channelPressureDepth * (next - prev);
|
|
1612
1576
|
}
|
|
1613
1577
|
const table = channel.channelPressureTable;
|
|
1614
1578
|
this.processActiveNotes(channel, scheduleTime, (note) => {
|
|
@@ -1668,10 +1632,15 @@ class MidyGM2 {
|
|
|
1668
1632
|
.setValueAtTime(volumeDepth, scheduleTime);
|
|
1669
1633
|
}
|
|
1670
1634
|
setReverbEffectsSend(channel, note, prevValue, scheduleTime) {
|
|
1635
|
+
let value = note.voiceParams.reverbEffectsSend;
|
|
1636
|
+
if (channel.isDrum) {
|
|
1637
|
+
const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 91);
|
|
1638
|
+
if (0 <= keyBasedValue) {
|
|
1639
|
+
value *= keyBasedValue / 127 / channel.state.reverbSendLevel;
|
|
1640
|
+
}
|
|
1641
|
+
}
|
|
1671
1642
|
if (0 < prevValue) {
|
|
1672
|
-
if (0 <
|
|
1673
|
-
const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 91);
|
|
1674
|
-
const value = note.voiceParams.reverbEffectsSend + keyBasedValue;
|
|
1643
|
+
if (0 < value) {
|
|
1675
1644
|
note.reverbEffectsSend.gain
|
|
1676
1645
|
.cancelScheduledValues(scheduleTime)
|
|
1677
1646
|
.setValueAtTime(value, scheduleTime);
|
|
@@ -1681,10 +1650,10 @@ class MidyGM2 {
|
|
|
1681
1650
|
}
|
|
1682
1651
|
}
|
|
1683
1652
|
else {
|
|
1684
|
-
if (0 <
|
|
1653
|
+
if (0 < value) {
|
|
1685
1654
|
if (!note.reverbEffectsSend) {
|
|
1686
1655
|
note.reverbEffectsSend = new GainNode(this.audioContext, {
|
|
1687
|
-
gain:
|
|
1656
|
+
gain: value,
|
|
1688
1657
|
});
|
|
1689
1658
|
note.volumeNode.connect(note.reverbEffectsSend);
|
|
1690
1659
|
}
|
|
@@ -1693,10 +1662,15 @@ class MidyGM2 {
|
|
|
1693
1662
|
}
|
|
1694
1663
|
}
|
|
1695
1664
|
setChorusEffectsSend(channel, note, prevValue, scheduleTime) {
|
|
1665
|
+
let value = note.voiceParams.chorusEffectsSend;
|
|
1666
|
+
if (channel.isDrum) {
|
|
1667
|
+
const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 93);
|
|
1668
|
+
if (0 <= keyBasedValue) {
|
|
1669
|
+
value *= keyBasedValue / 127 / channel.state.chorusSendLevel;
|
|
1670
|
+
}
|
|
1671
|
+
}
|
|
1696
1672
|
if (0 < prevValue) {
|
|
1697
|
-
if (0 <
|
|
1698
|
-
const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 93);
|
|
1699
|
-
const value = note.voiceParams.chorusEffectsSend + keyBasedValue;
|
|
1673
|
+
if (0 < vaule) {
|
|
1700
1674
|
note.chorusEffectsSend.gain
|
|
1701
1675
|
.cancelScheduledValues(scheduleTime)
|
|
1702
1676
|
.setValueAtTime(value, scheduleTime);
|
|
@@ -1706,12 +1680,12 @@ class MidyGM2 {
|
|
|
1706
1680
|
}
|
|
1707
1681
|
}
|
|
1708
1682
|
else {
|
|
1709
|
-
if (0 <
|
|
1683
|
+
if (0 < value) {
|
|
1710
1684
|
if (!note.chorusEffectsSend) {
|
|
1711
1685
|
note.chorusEffectsSend = new GainNode(this.audioContext, {
|
|
1712
|
-
gain:
|
|
1686
|
+
gain: value,
|
|
1713
1687
|
});
|
|
1714
|
-
note.
|
|
1688
|
+
note.volumeEnvelopeNode.connect(note.chorusEffectsSend);
|
|
1715
1689
|
}
|
|
1716
1690
|
note.chorusEffectsSend.connect(this.chorusEffect.input);
|
|
1717
1691
|
}
|
|
@@ -1795,11 +1769,12 @@ class MidyGM2 {
|
|
|
1795
1769
|
return state;
|
|
1796
1770
|
}
|
|
1797
1771
|
applyVoiceParams(channel, controllerType, scheduleTime) {
|
|
1798
|
-
this.processScheduledNotes(channel, (note) => {
|
|
1772
|
+
this.processScheduledNotes(channel, scheduleTime, (note) => {
|
|
1799
1773
|
const controllerState = this.getControllerState(channel, note.noteNumber, note.velocity);
|
|
1800
1774
|
const voiceParams = note.voice.getParams(controllerType, controllerState);
|
|
1801
|
-
let
|
|
1802
|
-
let
|
|
1775
|
+
let applyVolumeEnvelope = false;
|
|
1776
|
+
let applyFilterEnvelope = false;
|
|
1777
|
+
let applyPitchEnvelope = false;
|
|
1803
1778
|
for (const [key, value] of Object.entries(voiceParams)) {
|
|
1804
1779
|
const prevValue = note.voiceParams[key];
|
|
1805
1780
|
if (value === prevValue)
|
|
@@ -1808,37 +1783,23 @@ class MidyGM2 {
|
|
|
1808
1783
|
if (key in this.voiceParamsHandlers) {
|
|
1809
1784
|
this.voiceParamsHandlers[key](channel, note, prevValue, scheduleTime);
|
|
1810
1785
|
}
|
|
1811
|
-
else
|
|
1812
|
-
if (
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
if (key in voiceParams)
|
|
1819
|
-
noteVoiceParams[key] = voiceParams[key];
|
|
1820
|
-
}
|
|
1821
|
-
if (0.5 <= channel.state.portamento && 0 <= note.portamentoNoteNumber) {
|
|
1822
|
-
this.setPortamentoFilterEnvelope(channel, note, scheduleTime);
|
|
1823
|
-
}
|
|
1824
|
-
else {
|
|
1825
|
-
this.setFilterEnvelope(channel, note, scheduleTime);
|
|
1826
|
-
}
|
|
1827
|
-
this.setPitchEnvelope(note, scheduleTime);
|
|
1828
|
-
}
|
|
1829
|
-
else if (volumeEnvelopeKeySet.has(key)) {
|
|
1830
|
-
if (appliedVolumeEnvelope)
|
|
1831
|
-
continue;
|
|
1832
|
-
appliedVolumeEnvelope = true;
|
|
1833
|
-
const noteVoiceParams = note.voiceParams;
|
|
1834
|
-
for (let i = 0; i < volumeEnvelopeKeys.length; i++) {
|
|
1835
|
-
const key = volumeEnvelopeKeys[i];
|
|
1836
|
-
if (key in voiceParams)
|
|
1837
|
-
noteVoiceParams[key] = voiceParams[key];
|
|
1838
|
-
}
|
|
1839
|
-
this.setVolumeEnvelope(channel, note, scheduleTime);
|
|
1786
|
+
else {
|
|
1787
|
+
if (volumeEnvelopeKeySet.has(key))
|
|
1788
|
+
applyVolumeEnvelope = true;
|
|
1789
|
+
if (filterEnvelopeKeySet.has(key))
|
|
1790
|
+
applyFilterEnvelope = true;
|
|
1791
|
+
if (pitchEnvelopeKeySet.has(key))
|
|
1792
|
+
applyPitchEnvelope = true;
|
|
1840
1793
|
}
|
|
1841
1794
|
}
|
|
1795
|
+
if (applyVolumeEnvelope) {
|
|
1796
|
+
this.setVolumeEnvelope(channel, note, scheduleTime);
|
|
1797
|
+
}
|
|
1798
|
+
if (applyFilterEnvelope) {
|
|
1799
|
+
this.setFilterEnvelope(channel, note, scheduleTime);
|
|
1800
|
+
}
|
|
1801
|
+
if (applyPitchEnvelope)
|
|
1802
|
+
this.setPitchEnvelope(note, scheduleTime);
|
|
1842
1803
|
});
|
|
1843
1804
|
}
|
|
1844
1805
|
createControlChangeHandlers() {
|
|
@@ -1875,7 +1836,7 @@ class MidyGM2 {
|
|
|
1875
1836
|
handler.call(this, channelNumber, value, scheduleTime);
|
|
1876
1837
|
const channel = this.channels[channelNumber];
|
|
1877
1838
|
this.applyVoiceParams(channel, controllerType + 128, scheduleTime);
|
|
1878
|
-
this.applyControlTable(channel, controllerType);
|
|
1839
|
+
this.applyControlTable(channel, controllerType, scheduleTime);
|
|
1879
1840
|
}
|
|
1880
1841
|
else {
|
|
1881
1842
|
console.warn(`Unsupported Control change: controllerType=${controllerType} value=${value}`);
|
|
@@ -1886,7 +1847,7 @@ class MidyGM2 {
|
|
|
1886
1847
|
}
|
|
1887
1848
|
updateModulation(channel, scheduleTime) {
|
|
1888
1849
|
const depth = channel.state.modulationDepth * channel.modulationDepthRange;
|
|
1889
|
-
this.processScheduledNotes(channel, (note) => {
|
|
1850
|
+
this.processScheduledNotes(channel, scheduleTime, (note) => {
|
|
1890
1851
|
if (note.modulationDepth) {
|
|
1891
1852
|
note.modulationDepth.gain.setValueAtTime(depth, scheduleTime);
|
|
1892
1853
|
}
|
|
@@ -1905,7 +1866,7 @@ class MidyGM2 {
|
|
|
1905
1866
|
this.updateModulation(channel, scheduleTime);
|
|
1906
1867
|
}
|
|
1907
1868
|
updatePortamento(channel, scheduleTime) {
|
|
1908
|
-
this.processScheduledNotes(channel, (note) => {
|
|
1869
|
+
this.processScheduledNotes(channel, scheduleTime, (note) => {
|
|
1909
1870
|
if (0.5 <= channel.state.portamento) {
|
|
1910
1871
|
if (0 <= note.portamentoNoteNumber) {
|
|
1911
1872
|
this.setPortamentoVolumeEnvelope(channel, note, scheduleTime);
|
|
@@ -1932,22 +1893,12 @@ class MidyGM2 {
|
|
|
1932
1893
|
return;
|
|
1933
1894
|
this.updatePortamento(channel, scheduleTime);
|
|
1934
1895
|
}
|
|
1935
|
-
setKeyBasedVolume(channel, scheduleTime) {
|
|
1936
|
-
this.processScheduledNotes(channel, (note) => {
|
|
1937
|
-
const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 7);
|
|
1938
|
-
if (keyBasedValue !== 0) {
|
|
1939
|
-
note.volumeNode.gain
|
|
1940
|
-
.cancelScheduledValues(scheduleTime)
|
|
1941
|
-
.setValueAtTime(1 + keyBasedValue, scheduleTime);
|
|
1942
|
-
}
|
|
1943
|
-
});
|
|
1944
|
-
}
|
|
1945
1896
|
setVolume(channelNumber, volume, scheduleTime) {
|
|
1946
1897
|
scheduleTime ??= this.audioContext.currentTime;
|
|
1947
1898
|
const channel = this.channels[channelNumber];
|
|
1948
1899
|
channel.state.volume = volume / 127;
|
|
1949
1900
|
this.updateChannelVolume(channel, scheduleTime);
|
|
1950
|
-
this.
|
|
1901
|
+
this.updateKeyBasedVolume(channel, scheduleTime);
|
|
1951
1902
|
}
|
|
1952
1903
|
panToGain(pan) {
|
|
1953
1904
|
const theta = Math.PI / 2 * Math.max(0, pan * 127 - 1) / 126;
|
|
@@ -1956,26 +1907,12 @@ class MidyGM2 {
|
|
|
1956
1907
|
gainRight: Math.sin(theta),
|
|
1957
1908
|
};
|
|
1958
1909
|
}
|
|
1959
|
-
setKeyBasedPan(channel, scheduleTime) {
|
|
1960
|
-
this.processScheduledNotes(channel, (note) => {
|
|
1961
|
-
const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 10);
|
|
1962
|
-
if (keyBasedValue !== 0) {
|
|
1963
|
-
const { gainLeft, gainRight } = this.panToGain((keyBasedValue + 1) / 2);
|
|
1964
|
-
note.gainL.gain
|
|
1965
|
-
.cancelScheduledValues(scheduleTime)
|
|
1966
|
-
.setValueAtTime(gainLeft, scheduleTime);
|
|
1967
|
-
note.gainR.gain
|
|
1968
|
-
.cancelScheduledValues(scheduleTime)
|
|
1969
|
-
.setValueAtTime(gainRight, scheduleTime);
|
|
1970
|
-
}
|
|
1971
|
-
});
|
|
1972
|
-
}
|
|
1973
1910
|
setPan(channelNumber, pan, scheduleTime) {
|
|
1974
1911
|
scheduleTime ??= this.audioContext.currentTime;
|
|
1975
1912
|
const channel = this.channels[channelNumber];
|
|
1976
1913
|
channel.state.pan = pan / 127;
|
|
1977
1914
|
this.updateChannelVolume(channel, scheduleTime);
|
|
1978
|
-
this.
|
|
1915
|
+
this.updateKeyBasedVolume(channel, scheduleTime);
|
|
1979
1916
|
}
|
|
1980
1917
|
setExpression(channelNumber, expression, scheduleTime) {
|
|
1981
1918
|
scheduleTime ??= this.audioContext.currentTime;
|
|
@@ -2001,6 +1938,34 @@ class MidyGM2 {
|
|
|
2001
1938
|
.cancelScheduledValues(scheduleTime)
|
|
2002
1939
|
.setValueAtTime(volume * gainRight, scheduleTime);
|
|
2003
1940
|
}
|
|
1941
|
+
updateKeyBasedVolume(channel, scheduleTime) {
|
|
1942
|
+
if (!channel.isDrum)
|
|
1943
|
+
return;
|
|
1944
|
+
const state = channel.state;
|
|
1945
|
+
const defaultVolume = state.volume * state.expression;
|
|
1946
|
+
const defaultPan = state.pan;
|
|
1947
|
+
for (let i = 0; i < 128; i++) {
|
|
1948
|
+
const gainL = channel.keyBasedGainLs[i];
|
|
1949
|
+
const gainR = channel.keyBasedGainLs[i];
|
|
1950
|
+
if (!gainL)
|
|
1951
|
+
continue;
|
|
1952
|
+
if (!gainR)
|
|
1953
|
+
continue;
|
|
1954
|
+
const keyBasedVolume = this.getKeyBasedInstrumentControlValue(channel, i, 7);
|
|
1955
|
+
const volume = (0 <= keyBasedVolume)
|
|
1956
|
+
? defaultVolume * keyBasedVolume / 64
|
|
1957
|
+
: defaultVolume;
|
|
1958
|
+
const keyBasedPan = this.getKeyBasedInstrumentControlValue(channel, i, 10);
|
|
1959
|
+
const pan = (0 <= keyBasedPan) ? keyBasedPan / 127 : defaultPan;
|
|
1960
|
+
const { gainLeft, gainRight } = this.panToGain(pan);
|
|
1961
|
+
gainL.gain
|
|
1962
|
+
.cancelScheduledValues(scheduleTime)
|
|
1963
|
+
.setValueAtTime(volume * gainLeft, scheduleTime);
|
|
1964
|
+
gainR.gain
|
|
1965
|
+
.cancelScheduledValues(scheduleTime)
|
|
1966
|
+
.setValueAtTime(volume * gainRight, scheduleTime);
|
|
1967
|
+
}
|
|
1968
|
+
}
|
|
2004
1969
|
setSustainPedal(channelNumber, value, scheduleTime) {
|
|
2005
1970
|
const channel = this.channels[channelNumber];
|
|
2006
1971
|
if (channel.isDrum)
|
|
@@ -2008,7 +1973,7 @@ class MidyGM2 {
|
|
|
2008
1973
|
scheduleTime ??= this.audioContext.currentTime;
|
|
2009
1974
|
channel.state.sustainPedal = value / 127;
|
|
2010
1975
|
if (64 <= value) {
|
|
2011
|
-
this.processScheduledNotes(channel, (note) => {
|
|
1976
|
+
this.processScheduledNotes(channel, scheduleTime, (note) => {
|
|
2012
1977
|
channel.sustainNotes.push(note);
|
|
2013
1978
|
});
|
|
2014
1979
|
}
|
|
@@ -2041,6 +2006,9 @@ class MidyGM2 {
|
|
|
2041
2006
|
this.releaseSostenutoPedal(channelNumber, value, scheduleTime);
|
|
2042
2007
|
}
|
|
2043
2008
|
}
|
|
2009
|
+
getSoftPedalFactor(channel, note) {
|
|
2010
|
+
return 1 - (0.1 + (note.noteNumber / 127) * 0.2) * channel.state.softPedal;
|
|
2011
|
+
}
|
|
2044
2012
|
setSoftPedal(channelNumber, softPedal, scheduleTime) {
|
|
2045
2013
|
const channel = this.channels[channelNumber];
|
|
2046
2014
|
if (channel.isDrum)
|
|
@@ -2048,7 +2016,7 @@ class MidyGM2 {
|
|
|
2048
2016
|
const state = channel.state;
|
|
2049
2017
|
scheduleTime ??= this.audioContext.currentTime;
|
|
2050
2018
|
state.softPedal = softPedal / 127;
|
|
2051
|
-
this.processScheduledNotes(channel, (note) => {
|
|
2019
|
+
this.processScheduledNotes(channel, scheduleTime, (note) => {
|
|
2052
2020
|
if (0.5 <= state.portamento && 0 <= note.portamentoNoteNumber) {
|
|
2053
2021
|
this.setPortamentoVolumeEnvelope(channel, note, scheduleTime);
|
|
2054
2022
|
this.setPortamentoFilterEnvelope(channel, note, scheduleTime);
|
|
@@ -2072,16 +2040,17 @@ class MidyGM2 {
|
|
|
2072
2040
|
.setValueAtTime(state.reverbSendLevel, scheduleTime);
|
|
2073
2041
|
}
|
|
2074
2042
|
else {
|
|
2075
|
-
this.processScheduledNotes(channel, (note) => {
|
|
2043
|
+
this.processScheduledNotes(channel, scheduleTime, (note) => {
|
|
2076
2044
|
if (note.voiceParams.reverbEffectsSend <= 0)
|
|
2077
2045
|
return false;
|
|
2078
|
-
note.reverbEffectsSend
|
|
2046
|
+
if (note.reverbEffectsSend)
|
|
2047
|
+
note.reverbEffectsSend.disconnect();
|
|
2079
2048
|
});
|
|
2080
2049
|
}
|
|
2081
2050
|
}
|
|
2082
2051
|
else {
|
|
2083
2052
|
if (0 < reverbSendLevel) {
|
|
2084
|
-
this.processScheduledNotes(channel, (note) => {
|
|
2053
|
+
this.processScheduledNotes(channel, scheduleTime, (note) => {
|
|
2085
2054
|
this.setReverbEffectsSend(channel, note, 0, scheduleTime);
|
|
2086
2055
|
});
|
|
2087
2056
|
state.reverbSendLevel = reverbSendLevel / 127;
|
|
@@ -2104,16 +2073,17 @@ class MidyGM2 {
|
|
|
2104
2073
|
.setValueAtTime(state.chorusSendLevel, scheduleTime);
|
|
2105
2074
|
}
|
|
2106
2075
|
else {
|
|
2107
|
-
this.processScheduledNotes(channel, (note) => {
|
|
2076
|
+
this.processScheduledNotes(channel, scheduleTime, (note) => {
|
|
2108
2077
|
if (note.voiceParams.chorusEffectsSend <= 0)
|
|
2109
2078
|
return false;
|
|
2110
|
-
note.chorusEffectsSend
|
|
2079
|
+
if (note.chorusEffectsSend)
|
|
2080
|
+
note.chorusEffectsSend.disconnect();
|
|
2111
2081
|
});
|
|
2112
2082
|
}
|
|
2113
2083
|
}
|
|
2114
2084
|
else {
|
|
2115
2085
|
if (0 < chorusSendLevel) {
|
|
2116
|
-
this.processScheduledNotes(channel, (note) => {
|
|
2086
|
+
this.processScheduledNotes(channel, scheduleTime, (note) => {
|
|
2117
2087
|
this.setChorusEffectsSend(channel, note, 0, scheduleTime);
|
|
2118
2088
|
});
|
|
2119
2089
|
state.chorusSendLevel = chorusSendLevel / 127;
|
|
@@ -2497,8 +2467,7 @@ class MidyGM2 {
|
|
|
2497
2467
|
setReverbType(type) {
|
|
2498
2468
|
this.reverb.time = this.getReverbTimeFromType(type);
|
|
2499
2469
|
this.reverb.feedback = (type === 8) ? 0.9 : 0.8;
|
|
2500
|
-
|
|
2501
|
-
this.reverbEffect = options.reverbAlgorithm(audioContext);
|
|
2470
|
+
this.reverbEffect = this.createReverbEffect(this.audioContext);
|
|
2502
2471
|
}
|
|
2503
2472
|
getReverbTimeFromType(type) {
|
|
2504
2473
|
switch (type) {
|
|
@@ -2520,8 +2489,7 @@ class MidyGM2 {
|
|
|
2520
2489
|
}
|
|
2521
2490
|
setReverbTime(value) {
|
|
2522
2491
|
this.reverb.time = this.getReverbTime(value);
|
|
2523
|
-
|
|
2524
|
-
this.reverbEffect = options.reverbAlgorithm(audioContext);
|
|
2492
|
+
this.reverbEffect = this.createReverbEffect(this.audioContext);
|
|
2525
2493
|
}
|
|
2526
2494
|
getReverbTime(value) {
|
|
2527
2495
|
return Math.exp((value - 40) * 0.025);
|
|
@@ -2692,50 +2660,60 @@ class MidyGM2 {
|
|
|
2692
2660
|
}
|
|
2693
2661
|
}
|
|
2694
2662
|
getFilterCutoffControl(channel) {
|
|
2695
|
-
const
|
|
2696
|
-
|
|
2663
|
+
const channelPressureRaw = channel.channelPressureTable[1];
|
|
2664
|
+
const channelPressure = (0 <= channelPressureRaw)
|
|
2665
|
+
? (channelPressureRaw - 64) * channel.state.channelPressure
|
|
2666
|
+
: 0;
|
|
2697
2667
|
return channelPressure * 15;
|
|
2698
2668
|
}
|
|
2699
2669
|
getAmplitudeControl(channel) {
|
|
2700
|
-
const
|
|
2701
|
-
|
|
2670
|
+
const channelPressureRaw = channel.channelPressureTable[2];
|
|
2671
|
+
const channelPressure = (0 <= channelPressureRaw)
|
|
2672
|
+
? channelPressureRaw * channel.state.channelPressure
|
|
2673
|
+
: 0;
|
|
2702
2674
|
return channelPressure / 64;
|
|
2703
2675
|
}
|
|
2704
2676
|
getLFOPitchDepth(channel) {
|
|
2705
|
-
const
|
|
2706
|
-
|
|
2677
|
+
const channelPressureRaw = channel.channelPressureTable[3];
|
|
2678
|
+
const channelPressure = (0 <= channelPressureRaw)
|
|
2679
|
+
? channelPressureRaw * channel.state.channelPressure
|
|
2680
|
+
: 0;
|
|
2707
2681
|
return channelPressure / 127 * 600;
|
|
2708
2682
|
}
|
|
2709
2683
|
getLFOFilterDepth(channel) {
|
|
2710
|
-
const
|
|
2711
|
-
|
|
2684
|
+
const channelPressureRaw = channel.channelPressureTable[4];
|
|
2685
|
+
const channelPressure = (0 <= channelPressureRaw)
|
|
2686
|
+
? channelPressureRaw * channel.state.channelPressure
|
|
2687
|
+
: 0;
|
|
2712
2688
|
return channelPressure / 127 * 2400;
|
|
2713
2689
|
}
|
|
2714
2690
|
getLFOAmplitudeDepth(channel) {
|
|
2715
|
-
const
|
|
2716
|
-
|
|
2691
|
+
const channelPressureRaw = channel.channelPressureTable[5];
|
|
2692
|
+
const channelPressure = (0 <= channelPressureRaw)
|
|
2693
|
+
? channelPressureRaw * channel.state.channelPressure
|
|
2694
|
+
: 0;
|
|
2717
2695
|
return channelPressure / 127;
|
|
2718
2696
|
}
|
|
2719
2697
|
setControllerParameters(channel, note, table) {
|
|
2720
|
-
if (table[0]
|
|
2698
|
+
if (0 <= table[0])
|
|
2721
2699
|
this.updateDetune(channel, note);
|
|
2722
2700
|
if (0.5 <= channel.state.portamemento && 0 <= note.portamentoNoteNumber) {
|
|
2723
|
-
if (table[1]
|
|
2701
|
+
if (0 <= table[1])
|
|
2724
2702
|
this.setPortamentoFilterEnvelope(channel, note);
|
|
2725
|
-
if (table[2]
|
|
2703
|
+
if (0 <= table[2])
|
|
2726
2704
|
this.setPortamentoVolumeEnvelope(channel, note);
|
|
2727
2705
|
}
|
|
2728
2706
|
else {
|
|
2729
|
-
if (table[1]
|
|
2707
|
+
if (0 <= table[1])
|
|
2730
2708
|
this.setFilterEnvelope(channel, note);
|
|
2731
|
-
if (table[2]
|
|
2709
|
+
if (0 <= table[2])
|
|
2732
2710
|
this.setVolumeEnvelope(channel, note);
|
|
2733
2711
|
}
|
|
2734
|
-
if (table[3]
|
|
2712
|
+
if (0 <= table[3])
|
|
2735
2713
|
this.setModLfoToPitch(channel, note);
|
|
2736
|
-
if (table[4]
|
|
2714
|
+
if (0 <= table[4])
|
|
2737
2715
|
this.setModLfoToFilterFc(channel, note);
|
|
2738
|
-
if (table[5]
|
|
2716
|
+
if (0 <= table[5])
|
|
2739
2717
|
this.setModLfoToVolume(channel, note);
|
|
2740
2718
|
}
|
|
2741
2719
|
handlePressureSysEx(data, tableName) {
|
|
@@ -2751,26 +2729,15 @@ class MidyGM2 {
|
|
|
2751
2729
|
}
|
|
2752
2730
|
}
|
|
2753
2731
|
initControlTable() {
|
|
2754
|
-
const
|
|
2732
|
+
const ccCount = 128;
|
|
2755
2733
|
const slotSize = 6;
|
|
2756
|
-
|
|
2757
|
-
return this.resetControlTable(table);
|
|
2734
|
+
return new Int8Array(ccCount * slotSize).fill(-1);
|
|
2758
2735
|
}
|
|
2759
|
-
|
|
2760
|
-
const channelCount = 128;
|
|
2761
|
-
const slotSize = 6;
|
|
2762
|
-
const defaultValues = [64, 64, 64, 0, 0, 0];
|
|
2763
|
-
for (let ch = 0; ch < channelCount; ch++) {
|
|
2764
|
-
const offset = ch * slotSize;
|
|
2765
|
-
table.set(defaultValues, offset);
|
|
2766
|
-
}
|
|
2767
|
-
return table;
|
|
2768
|
-
}
|
|
2769
|
-
applyControlTable(channel, controllerType) {
|
|
2736
|
+
applyControlTable(channel, controllerType, scheduleTime) {
|
|
2770
2737
|
const slotSize = 6;
|
|
2771
2738
|
const offset = controllerType * slotSize;
|
|
2772
2739
|
const table = channel.controlTable.subarray(offset, offset + slotSize);
|
|
2773
|
-
this.processScheduledNotes(channel, (note) => {
|
|
2740
|
+
this.processScheduledNotes(channel, scheduleTime, (note) => {
|
|
2774
2741
|
this.setControllerParameters(channel, note, table);
|
|
2775
2742
|
});
|
|
2776
2743
|
}
|
|
@@ -2790,12 +2757,12 @@ class MidyGM2 {
|
|
|
2790
2757
|
getKeyBasedInstrumentControlValue(channel, keyNumber, controllerType) {
|
|
2791
2758
|
const index = keyNumber * 128 + controllerType;
|
|
2792
2759
|
const controlValue = channel.keyBasedInstrumentControlTable[index];
|
|
2793
|
-
return
|
|
2760
|
+
return controlValue;
|
|
2794
2761
|
}
|
|
2795
2762
|
handleKeyBasedInstrumentControlSysEx(data, scheduleTime) {
|
|
2796
2763
|
const channelNumber = data[4];
|
|
2797
2764
|
const channel = this.channels[channelNumber];
|
|
2798
|
-
if (channel.isDrum)
|
|
2765
|
+
if (!channel.isDrum)
|
|
2799
2766
|
return;
|
|
2800
2767
|
const keyNumber = data[5];
|
|
2801
2768
|
const table = channel.keyBasedInstrumentControlTable;
|
|
@@ -2803,7 +2770,7 @@ class MidyGM2 {
|
|
|
2803
2770
|
const controllerType = data[i];
|
|
2804
2771
|
const value = data[i + 1];
|
|
2805
2772
|
const index = keyNumber * 128 + controllerType;
|
|
2806
|
-
table[index] = value
|
|
2773
|
+
table[index] = value;
|
|
2807
2774
|
}
|
|
2808
2775
|
this.handleChannelPressure(channelNumber, channel.state.channelPressure * 127, scheduleTime);
|
|
2809
2776
|
}
|