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