@marmooo/midy 0.3.3 → 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 +3 -3
- package/esm/midy-GM1.d.ts.map +1 -1
- package/esm/midy-GM1.js +47 -50
- package/esm/midy-GM2.d.ts +13 -31
- package/esm/midy-GM2.d.ts.map +1 -1
- package/esm/midy-GM2.js +185 -206
- package/esm/midy-GMLite.d.ts +3 -3
- package/esm/midy-GMLite.d.ts.map +1 -1
- package/esm/midy-GMLite.js +47 -50
- package/esm/midy.d.ts +14 -31
- package/esm/midy.d.ts.map +1 -1
- package/esm/midy.js +225 -225
- package/package.json +1 -1
- package/script/midy-GM1.d.ts +3 -3
- package/script/midy-GM1.d.ts.map +1 -1
- package/script/midy-GM1.js +47 -50
- package/script/midy-GM2.d.ts +13 -31
- package/script/midy-GM2.d.ts.map +1 -1
- package/script/midy-GM2.js +185 -206
- package/script/midy-GMLite.d.ts +3 -3
- package/script/midy-GMLite.d.ts.map +1 -1
- package/script/midy-GMLite.js +47 -50
- package/script/midy.d.ts +14 -31
- package/script/midy.d.ts.map +1 -1
- package/script/midy.js +225 -225
package/esm/midy-GM2.js
CHANGED
|
@@ -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);
|
|
@@ -492,7 +460,7 @@ export class MidyGM2 {
|
|
|
492
460
|
this.timeline = midiData.timeline;
|
|
493
461
|
this.totalTime = this.calcTotalTime();
|
|
494
462
|
}
|
|
495
|
-
|
|
463
|
+
createChannelAudioNodes(audioContext) {
|
|
496
464
|
const { gainLeft, gainRight } = this.panToGain(defaultControllerState.pan.defaultValue);
|
|
497
465
|
const gainL = new GainNode(audioContext, { gain: gainLeft });
|
|
498
466
|
const gainR = new GainNode(audioContext, { gain: gainRight });
|
|
@@ -507,10 +475,9 @@ export class MidyGM2 {
|
|
|
507
475
|
};
|
|
508
476
|
}
|
|
509
477
|
resetChannelTable(channel) {
|
|
510
|
-
|
|
478
|
+
channel.controlTable.fill(-1);
|
|
511
479
|
channel.scaleOctaveTuningTable.fill(0); // [-100, 100] cent
|
|
512
|
-
channel.channelPressureTable.
|
|
513
|
-
channel.polyphonicKeyPressureTable.set([64, 64, 64, 0, 0, 0]);
|
|
480
|
+
channel.channelPressureTable.fill(-1);
|
|
514
481
|
channel.keyBasedInstrumentControlTable.fill(-1);
|
|
515
482
|
}
|
|
516
483
|
createChannels(audioContext) {
|
|
@@ -520,14 +487,16 @@ export class MidyGM2 {
|
|
|
520
487
|
isDrum: false,
|
|
521
488
|
state: new ControllerState(),
|
|
522
489
|
...this.constructor.channelSettings,
|
|
523
|
-
...this.
|
|
490
|
+
...this.createChannelAudioNodes(audioContext),
|
|
524
491
|
scheduledNotes: [],
|
|
525
492
|
sustainNotes: [],
|
|
526
493
|
sostenutoNotes: [],
|
|
527
494
|
controlTable: this.initControlTable(),
|
|
528
495
|
scaleOctaveTuningTable: new Int8Array(12), // [-64, 63] cent
|
|
529
|
-
channelPressureTable: new
|
|
496
|
+
channelPressureTable: new Int8Array(6).fill(-1),
|
|
530
497
|
keyBasedInstrumentControlTable: new Int8Array(128 * 128).fill(-1),
|
|
498
|
+
keyBasedGainLs: new Array(128),
|
|
499
|
+
keyBasedGainRs: new Array(128),
|
|
531
500
|
};
|
|
532
501
|
});
|
|
533
502
|
return channels;
|
|
@@ -569,10 +538,9 @@ export class MidyGM2 {
|
|
|
569
538
|
createBufferSource(channel, noteNumber, voiceParams, audioBuffer) {
|
|
570
539
|
const bufferSource = new AudioBufferSourceNode(this.audioContext);
|
|
571
540
|
bufferSource.buffer = audioBuffer;
|
|
572
|
-
bufferSource.loop =
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
}
|
|
541
|
+
bufferSource.loop = channel.isDrum
|
|
542
|
+
? this.isLoopDrum(channel, noteNumber)
|
|
543
|
+
: (voiceParams.sampleModes % 2 !== 0);
|
|
576
544
|
if (bufferSource.loop) {
|
|
577
545
|
bufferSource.loopStart = voiceParams.loopStart / voiceParams.sampleRate;
|
|
578
546
|
bufferSource.loopEnd = voiceParams.loopEnd / voiceParams.sampleRate;
|
|
@@ -813,7 +781,7 @@ export class MidyGM2 {
|
|
|
813
781
|
stopChannelNotes(channelNumber, velocity, force, scheduleTime) {
|
|
814
782
|
const channel = this.channels[channelNumber];
|
|
815
783
|
const promises = [];
|
|
816
|
-
this.processScheduledNotes(channel, (note) => {
|
|
784
|
+
this.processScheduledNotes(channel, scheduleTime, (note) => {
|
|
817
785
|
const promise = this.scheduleNoteOff(channelNumber, note.noteNumber, velocity, scheduleTime, force);
|
|
818
786
|
this.notePromises.push(promise);
|
|
819
787
|
promises.push(promise);
|
|
@@ -872,7 +840,7 @@ export class MidyGM2 {
|
|
|
872
840
|
const now = this.audioContext.currentTime;
|
|
873
841
|
return this.resumeTime + now - this.startTime - this.startDelay;
|
|
874
842
|
}
|
|
875
|
-
processScheduledNotes(channel, callback) {
|
|
843
|
+
processScheduledNotes(channel, scheduleTime, callback) {
|
|
876
844
|
const scheduledNotes = channel.scheduledNotes;
|
|
877
845
|
for (let i = 0; i < scheduledNotes.length; i++) {
|
|
878
846
|
const note = scheduledNotes[i];
|
|
@@ -880,6 +848,8 @@ export class MidyGM2 {
|
|
|
880
848
|
continue;
|
|
881
849
|
if (note.ending)
|
|
882
850
|
continue;
|
|
851
|
+
if (note.startTime < scheduleTime)
|
|
852
|
+
continue;
|
|
883
853
|
callback(note);
|
|
884
854
|
}
|
|
885
855
|
}
|
|
@@ -892,7 +862,7 @@ export class MidyGM2 {
|
|
|
892
862
|
if (note.ending)
|
|
893
863
|
continue;
|
|
894
864
|
if (scheduleTime < note.startTime)
|
|
895
|
-
|
|
865
|
+
break;
|
|
896
866
|
callback(note);
|
|
897
867
|
}
|
|
898
868
|
}
|
|
@@ -981,6 +951,22 @@ export class MidyGM2 {
|
|
|
981
951
|
const output = allpasses.at(-1);
|
|
982
952
|
return { input, output };
|
|
983
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
|
+
}
|
|
984
970
|
createChorusEffect(audioContext) {
|
|
985
971
|
const input = new GainNode(audioContext);
|
|
986
972
|
const output = new GainNode(audioContext);
|
|
@@ -1045,15 +1031,22 @@ export class MidyGM2 {
|
|
|
1045
1031
|
const pitchWheel = channel.state.pitchWheel * 2 - 1;
|
|
1046
1032
|
const pitchWheelSensitivity = channel.state.pitchWheelSensitivity * 12800;
|
|
1047
1033
|
const pitch = pitchWheel * pitchWheelSensitivity;
|
|
1048
|
-
const
|
|
1049
|
-
|
|
1050
|
-
|
|
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
|
+
}
|
|
1051
1044
|
}
|
|
1052
1045
|
calcNoteDetune(channel, note) {
|
|
1053
1046
|
return channel.scaleOctaveTuningTable[note.noteNumber % 12];
|
|
1054
1047
|
}
|
|
1055
1048
|
updateChannelDetune(channel, scheduleTime) {
|
|
1056
|
-
this.processScheduledNotes(channel, (note) => {
|
|
1049
|
+
this.processScheduledNotes(channel, scheduleTime, (note) => {
|
|
1057
1050
|
this.updateDetune(channel, note, scheduleTime);
|
|
1058
1051
|
});
|
|
1059
1052
|
}
|
|
@@ -1300,9 +1293,6 @@ export class MidyGM2 {
|
|
|
1300
1293
|
const note = new Note(noteNumber, velocity, startTime, voice, voiceParams);
|
|
1301
1294
|
const audioBuffer = await this.getAudioBuffer(channel.programNumber, noteNumber, velocity, voiceParams, isSF3);
|
|
1302
1295
|
note.bufferSource = this.createBufferSource(channel, noteNumber, voiceParams, audioBuffer);
|
|
1303
|
-
note.volumeNode = new GainNode(this.audioContext);
|
|
1304
|
-
note.gainL = new GainNode(this.audioContext);
|
|
1305
|
-
note.gainR = new GainNode(this.audioContext);
|
|
1306
1296
|
note.volumeEnvelopeNode = new GainNode(this.audioContext);
|
|
1307
1297
|
note.filterNode = new BiquadFilterNode(this.audioContext, {
|
|
1308
1298
|
type: "lowpass",
|
|
@@ -1335,9 +1325,6 @@ export class MidyGM2 {
|
|
|
1335
1325
|
}
|
|
1336
1326
|
note.bufferSource.connect(note.filterNode);
|
|
1337
1327
|
note.filterNode.connect(note.volumeEnvelopeNode);
|
|
1338
|
-
note.volumeEnvelopeNode.connect(note.volumeNode);
|
|
1339
|
-
note.volumeNode.connect(note.gainL);
|
|
1340
|
-
note.volumeNode.connect(note.gainR);
|
|
1341
1328
|
if (0 < state.chorusSendLevel) {
|
|
1342
1329
|
this.setChorusEffectsSend(channel, note, 0, now);
|
|
1343
1330
|
}
|
|
@@ -1396,14 +1383,7 @@ export class MidyGM2 {
|
|
|
1396
1383
|
}
|
|
1397
1384
|
this.drumExclusiveClassNotes[index] = note;
|
|
1398
1385
|
}
|
|
1399
|
-
|
|
1400
|
-
if (!channel.isDrum)
|
|
1401
|
-
return false;
|
|
1402
|
-
const programNumber = channel.programNumber;
|
|
1403
|
-
return !((programNumber === 48 && noteNumber === 88) ||
|
|
1404
|
-
(programNumber === 56 && 47 <= noteNumber && noteNumber <= 84));
|
|
1405
|
-
}
|
|
1406
|
-
async scheduleNoteOn(channelNumber, noteNumber, velocity, startTime, noteOffEvent) {
|
|
1386
|
+
async scheduleNoteOn(channelNumber, noteNumber, velocity, startTime) {
|
|
1407
1387
|
const channel = this.channels[channelNumber];
|
|
1408
1388
|
const bankNumber = this.calcBank(channel, channelNumber);
|
|
1409
1389
|
const soundFontIndex = this.soundFontTable[channel.programNumber].get(bankNumber);
|
|
@@ -1415,9 +1395,18 @@ export class MidyGM2 {
|
|
|
1415
1395
|
return;
|
|
1416
1396
|
const isSF3 = soundFont.parsed.info.version.major === 3;
|
|
1417
1397
|
const note = await this.createNote(channel, voice, noteNumber, velocity, startTime, isSF3);
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
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
|
+
}
|
|
1421
1410
|
if (0.5 <= channel.state.sustainPedal) {
|
|
1422
1411
|
channel.sustainNotes.push(note);
|
|
1423
1412
|
}
|
|
@@ -1435,9 +1424,6 @@ export class MidyGM2 {
|
|
|
1435
1424
|
note.bufferSource.disconnect();
|
|
1436
1425
|
note.filterNode.disconnect();
|
|
1437
1426
|
note.volumeEnvelopeNode.disconnect();
|
|
1438
|
-
note.volumeNode.disconnect();
|
|
1439
|
-
note.gainL.disconnect();
|
|
1440
|
-
note.gainR.disconnect();
|
|
1441
1427
|
if (note.modulationDepth) {
|
|
1442
1428
|
note.volumeDepth.disconnect();
|
|
1443
1429
|
note.modulationDepth.disconnect();
|
|
@@ -1580,9 +1566,10 @@ export class MidyGM2 {
|
|
|
1580
1566
|
const prev = channel.state.channelPressure;
|
|
1581
1567
|
const next = value / 127;
|
|
1582
1568
|
channel.state.channelPressure = next;
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
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);
|
|
1586
1573
|
}
|
|
1587
1574
|
const table = channel.channelPressureTable;
|
|
1588
1575
|
this.processActiveNotes(channel, scheduleTime, (note) => {
|
|
@@ -1642,10 +1629,12 @@ export class MidyGM2 {
|
|
|
1642
1629
|
.setValueAtTime(volumeDepth, scheduleTime);
|
|
1643
1630
|
}
|
|
1644
1631
|
setReverbEffectsSend(channel, note, prevValue, scheduleTime) {
|
|
1645
|
-
const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 91);
|
|
1646
1632
|
let value = note.voiceParams.reverbEffectsSend;
|
|
1647
|
-
if (
|
|
1648
|
-
|
|
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
|
+
}
|
|
1649
1638
|
}
|
|
1650
1639
|
if (0 < prevValue) {
|
|
1651
1640
|
if (0 < value) {
|
|
@@ -1670,10 +1659,12 @@ export class MidyGM2 {
|
|
|
1670
1659
|
}
|
|
1671
1660
|
}
|
|
1672
1661
|
setChorusEffectsSend(channel, note, prevValue, scheduleTime) {
|
|
1673
|
-
const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 93);
|
|
1674
1662
|
let value = note.voiceParams.chorusEffectsSend;
|
|
1675
|
-
if (
|
|
1676
|
-
|
|
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
|
+
}
|
|
1677
1668
|
}
|
|
1678
1669
|
if (0 < prevValue) {
|
|
1679
1670
|
if (0 < vaule) {
|
|
@@ -1691,7 +1682,7 @@ export class MidyGM2 {
|
|
|
1691
1682
|
note.chorusEffectsSend = new GainNode(this.audioContext, {
|
|
1692
1683
|
gain: value,
|
|
1693
1684
|
});
|
|
1694
|
-
note.
|
|
1685
|
+
note.volumeEnvelopeNode.connect(note.chorusEffectsSend);
|
|
1695
1686
|
}
|
|
1696
1687
|
note.chorusEffectsSend.connect(this.chorusEffect.input);
|
|
1697
1688
|
}
|
|
@@ -1775,11 +1766,12 @@ export class MidyGM2 {
|
|
|
1775
1766
|
return state;
|
|
1776
1767
|
}
|
|
1777
1768
|
applyVoiceParams(channel, controllerType, scheduleTime) {
|
|
1778
|
-
this.processScheduledNotes(channel, (note) => {
|
|
1769
|
+
this.processScheduledNotes(channel, scheduleTime, (note) => {
|
|
1779
1770
|
const controllerState = this.getControllerState(channel, note.noteNumber, note.velocity);
|
|
1780
1771
|
const voiceParams = note.voice.getParams(controllerType, controllerState);
|
|
1781
|
-
let
|
|
1782
|
-
let
|
|
1772
|
+
let applyVolumeEnvelope = false;
|
|
1773
|
+
let applyFilterEnvelope = false;
|
|
1774
|
+
let applyPitchEnvelope = false;
|
|
1783
1775
|
for (const [key, value] of Object.entries(voiceParams)) {
|
|
1784
1776
|
const prevValue = note.voiceParams[key];
|
|
1785
1777
|
if (value === prevValue)
|
|
@@ -1788,37 +1780,23 @@ export class MidyGM2 {
|
|
|
1788
1780
|
if (key in this.voiceParamsHandlers) {
|
|
1789
1781
|
this.voiceParamsHandlers[key](channel, note, prevValue, scheduleTime);
|
|
1790
1782
|
}
|
|
1791
|
-
else
|
|
1792
|
-
if (
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
if (key in voiceParams)
|
|
1799
|
-
noteVoiceParams[key] = voiceParams[key];
|
|
1800
|
-
}
|
|
1801
|
-
if (0.5 <= channel.state.portamento && 0 <= note.portamentoNoteNumber) {
|
|
1802
|
-
this.setPortamentoFilterEnvelope(channel, note, scheduleTime);
|
|
1803
|
-
}
|
|
1804
|
-
else {
|
|
1805
|
-
this.setFilterEnvelope(channel, note, scheduleTime);
|
|
1806
|
-
}
|
|
1807
|
-
this.setPitchEnvelope(note, scheduleTime);
|
|
1808
|
-
}
|
|
1809
|
-
else if (volumeEnvelopeKeySet.has(key)) {
|
|
1810
|
-
if (appliedVolumeEnvelope)
|
|
1811
|
-
continue;
|
|
1812
|
-
appliedVolumeEnvelope = true;
|
|
1813
|
-
const noteVoiceParams = note.voiceParams;
|
|
1814
|
-
for (let i = 0; i < volumeEnvelopeKeys.length; i++) {
|
|
1815
|
-
const key = volumeEnvelopeKeys[i];
|
|
1816
|
-
if (key in voiceParams)
|
|
1817
|
-
noteVoiceParams[key] = voiceParams[key];
|
|
1818
|
-
}
|
|
1819
|
-
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;
|
|
1820
1790
|
}
|
|
1821
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);
|
|
1822
1800
|
});
|
|
1823
1801
|
}
|
|
1824
1802
|
createControlChangeHandlers() {
|
|
@@ -1855,7 +1833,7 @@ export class MidyGM2 {
|
|
|
1855
1833
|
handler.call(this, channelNumber, value, scheduleTime);
|
|
1856
1834
|
const channel = this.channels[channelNumber];
|
|
1857
1835
|
this.applyVoiceParams(channel, controllerType + 128, scheduleTime);
|
|
1858
|
-
this.applyControlTable(channel, controllerType);
|
|
1836
|
+
this.applyControlTable(channel, controllerType, scheduleTime);
|
|
1859
1837
|
}
|
|
1860
1838
|
else {
|
|
1861
1839
|
console.warn(`Unsupported Control change: controllerType=${controllerType} value=${value}`);
|
|
@@ -1866,7 +1844,7 @@ export class MidyGM2 {
|
|
|
1866
1844
|
}
|
|
1867
1845
|
updateModulation(channel, scheduleTime) {
|
|
1868
1846
|
const depth = channel.state.modulationDepth * channel.modulationDepthRange;
|
|
1869
|
-
this.processScheduledNotes(channel, (note) => {
|
|
1847
|
+
this.processScheduledNotes(channel, scheduleTime, (note) => {
|
|
1870
1848
|
if (note.modulationDepth) {
|
|
1871
1849
|
note.modulationDepth.gain.setValueAtTime(depth, scheduleTime);
|
|
1872
1850
|
}
|
|
@@ -1885,7 +1863,7 @@ export class MidyGM2 {
|
|
|
1885
1863
|
this.updateModulation(channel, scheduleTime);
|
|
1886
1864
|
}
|
|
1887
1865
|
updatePortamento(channel, scheduleTime) {
|
|
1888
|
-
this.processScheduledNotes(channel, (note) => {
|
|
1866
|
+
this.processScheduledNotes(channel, scheduleTime, (note) => {
|
|
1889
1867
|
if (0.5 <= channel.state.portamento) {
|
|
1890
1868
|
if (0 <= note.portamentoNoteNumber) {
|
|
1891
1869
|
this.setPortamentoVolumeEnvelope(channel, note, scheduleTime);
|
|
@@ -1912,22 +1890,12 @@ export class MidyGM2 {
|
|
|
1912
1890
|
return;
|
|
1913
1891
|
this.updatePortamento(channel, scheduleTime);
|
|
1914
1892
|
}
|
|
1915
|
-
setKeyBasedVolume(channel, scheduleTime) {
|
|
1916
|
-
this.processScheduledNotes(channel, (note) => {
|
|
1917
|
-
const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 7);
|
|
1918
|
-
if (0 <= keyBasedValue) {
|
|
1919
|
-
note.volumeNode.gain
|
|
1920
|
-
.cancelScheduledValues(scheduleTime)
|
|
1921
|
-
.setValueAtTime(keyBasedValue / 127, scheduleTime);
|
|
1922
|
-
}
|
|
1923
|
-
});
|
|
1924
|
-
}
|
|
1925
1893
|
setVolume(channelNumber, volume, scheduleTime) {
|
|
1926
1894
|
scheduleTime ??= this.audioContext.currentTime;
|
|
1927
1895
|
const channel = this.channels[channelNumber];
|
|
1928
1896
|
channel.state.volume = volume / 127;
|
|
1929
1897
|
this.updateChannelVolume(channel, scheduleTime);
|
|
1930
|
-
this.
|
|
1898
|
+
this.updateKeyBasedVolume(channel, scheduleTime);
|
|
1931
1899
|
}
|
|
1932
1900
|
panToGain(pan) {
|
|
1933
1901
|
const theta = Math.PI / 2 * Math.max(0, pan * 127 - 1) / 126;
|
|
@@ -1936,26 +1904,12 @@ export class MidyGM2 {
|
|
|
1936
1904
|
gainRight: Math.sin(theta),
|
|
1937
1905
|
};
|
|
1938
1906
|
}
|
|
1939
|
-
setKeyBasedPan(channel, scheduleTime) {
|
|
1940
|
-
this.processScheduledNotes(channel, (note) => {
|
|
1941
|
-
const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 10);
|
|
1942
|
-
if (0 <= keyBasedValue) {
|
|
1943
|
-
const { gainLeft, gainRight } = this.panToGain(keyBasedValue / 127);
|
|
1944
|
-
note.gainL.gain
|
|
1945
|
-
.cancelScheduledValues(scheduleTime)
|
|
1946
|
-
.setValueAtTime(gainLeft, scheduleTime);
|
|
1947
|
-
note.gainR.gain
|
|
1948
|
-
.cancelScheduledValues(scheduleTime)
|
|
1949
|
-
.setValueAtTime(gainRight, scheduleTime);
|
|
1950
|
-
}
|
|
1951
|
-
});
|
|
1952
|
-
}
|
|
1953
1907
|
setPan(channelNumber, pan, scheduleTime) {
|
|
1954
1908
|
scheduleTime ??= this.audioContext.currentTime;
|
|
1955
1909
|
const channel = this.channels[channelNumber];
|
|
1956
1910
|
channel.state.pan = pan / 127;
|
|
1957
1911
|
this.updateChannelVolume(channel, scheduleTime);
|
|
1958
|
-
this.
|
|
1912
|
+
this.updateKeyBasedVolume(channel, scheduleTime);
|
|
1959
1913
|
}
|
|
1960
1914
|
setExpression(channelNumber, expression, scheduleTime) {
|
|
1961
1915
|
scheduleTime ??= this.audioContext.currentTime;
|
|
@@ -1981,6 +1935,34 @@ export class MidyGM2 {
|
|
|
1981
1935
|
.cancelScheduledValues(scheduleTime)
|
|
1982
1936
|
.setValueAtTime(volume * gainRight, scheduleTime);
|
|
1983
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
|
+
}
|
|
1984
1966
|
setSustainPedal(channelNumber, value, scheduleTime) {
|
|
1985
1967
|
const channel = this.channels[channelNumber];
|
|
1986
1968
|
if (channel.isDrum)
|
|
@@ -1988,7 +1970,7 @@ export class MidyGM2 {
|
|
|
1988
1970
|
scheduleTime ??= this.audioContext.currentTime;
|
|
1989
1971
|
channel.state.sustainPedal = value / 127;
|
|
1990
1972
|
if (64 <= value) {
|
|
1991
|
-
this.processScheduledNotes(channel, (note) => {
|
|
1973
|
+
this.processScheduledNotes(channel, scheduleTime, (note) => {
|
|
1992
1974
|
channel.sustainNotes.push(note);
|
|
1993
1975
|
});
|
|
1994
1976
|
}
|
|
@@ -2031,7 +2013,7 @@ export class MidyGM2 {
|
|
|
2031
2013
|
const state = channel.state;
|
|
2032
2014
|
scheduleTime ??= this.audioContext.currentTime;
|
|
2033
2015
|
state.softPedal = softPedal / 127;
|
|
2034
|
-
this.processScheduledNotes(channel, (note) => {
|
|
2016
|
+
this.processScheduledNotes(channel, scheduleTime, (note) => {
|
|
2035
2017
|
if (0.5 <= state.portamento && 0 <= note.portamentoNoteNumber) {
|
|
2036
2018
|
this.setPortamentoVolumeEnvelope(channel, note, scheduleTime);
|
|
2037
2019
|
this.setPortamentoFilterEnvelope(channel, note, scheduleTime);
|
|
@@ -2055,7 +2037,7 @@ export class MidyGM2 {
|
|
|
2055
2037
|
.setValueAtTime(state.reverbSendLevel, scheduleTime);
|
|
2056
2038
|
}
|
|
2057
2039
|
else {
|
|
2058
|
-
this.processScheduledNotes(channel, (note) => {
|
|
2040
|
+
this.processScheduledNotes(channel, scheduleTime, (note) => {
|
|
2059
2041
|
if (note.voiceParams.reverbEffectsSend <= 0)
|
|
2060
2042
|
return false;
|
|
2061
2043
|
if (note.reverbEffectsSend)
|
|
@@ -2065,7 +2047,7 @@ export class MidyGM2 {
|
|
|
2065
2047
|
}
|
|
2066
2048
|
else {
|
|
2067
2049
|
if (0 < reverbSendLevel) {
|
|
2068
|
-
this.processScheduledNotes(channel, (note) => {
|
|
2050
|
+
this.processScheduledNotes(channel, scheduleTime, (note) => {
|
|
2069
2051
|
this.setReverbEffectsSend(channel, note, 0, scheduleTime);
|
|
2070
2052
|
});
|
|
2071
2053
|
state.reverbSendLevel = reverbSendLevel / 127;
|
|
@@ -2088,7 +2070,7 @@ export class MidyGM2 {
|
|
|
2088
2070
|
.setValueAtTime(state.chorusSendLevel, scheduleTime);
|
|
2089
2071
|
}
|
|
2090
2072
|
else {
|
|
2091
|
-
this.processScheduledNotes(channel, (note) => {
|
|
2073
|
+
this.processScheduledNotes(channel, scheduleTime, (note) => {
|
|
2092
2074
|
if (note.voiceParams.chorusEffectsSend <= 0)
|
|
2093
2075
|
return false;
|
|
2094
2076
|
if (note.chorusEffectsSend)
|
|
@@ -2098,7 +2080,7 @@ export class MidyGM2 {
|
|
|
2098
2080
|
}
|
|
2099
2081
|
else {
|
|
2100
2082
|
if (0 < chorusSendLevel) {
|
|
2101
|
-
this.processScheduledNotes(channel, (note) => {
|
|
2083
|
+
this.processScheduledNotes(channel, scheduleTime, (note) => {
|
|
2102
2084
|
this.setChorusEffectsSend(channel, note, 0, scheduleTime);
|
|
2103
2085
|
});
|
|
2104
2086
|
state.chorusSendLevel = chorusSendLevel / 127;
|
|
@@ -2482,8 +2464,7 @@ export class MidyGM2 {
|
|
|
2482
2464
|
setReverbType(type) {
|
|
2483
2465
|
this.reverb.time = this.getReverbTimeFromType(type);
|
|
2484
2466
|
this.reverb.feedback = (type === 8) ? 0.9 : 0.8;
|
|
2485
|
-
|
|
2486
|
-
this.reverbEffect = options.reverbAlgorithm(audioContext);
|
|
2467
|
+
this.reverbEffect = this.createReverbEffect(this.audioContext);
|
|
2487
2468
|
}
|
|
2488
2469
|
getReverbTimeFromType(type) {
|
|
2489
2470
|
switch (type) {
|
|
@@ -2505,8 +2486,7 @@ export class MidyGM2 {
|
|
|
2505
2486
|
}
|
|
2506
2487
|
setReverbTime(value) {
|
|
2507
2488
|
this.reverb.time = this.getReverbTime(value);
|
|
2508
|
-
|
|
2509
|
-
this.reverbEffect = options.reverbAlgorithm(audioContext);
|
|
2489
|
+
this.reverbEffect = this.createReverbEffect(this.audioContext);
|
|
2510
2490
|
}
|
|
2511
2491
|
getReverbTime(value) {
|
|
2512
2492
|
return Math.exp((value - 40) * 0.025);
|
|
@@ -2677,50 +2657,60 @@ export class MidyGM2 {
|
|
|
2677
2657
|
}
|
|
2678
2658
|
}
|
|
2679
2659
|
getFilterCutoffControl(channel) {
|
|
2680
|
-
const
|
|
2681
|
-
|
|
2660
|
+
const channelPressureRaw = channel.channelPressureTable[1];
|
|
2661
|
+
const channelPressure = (0 <= channelPressureRaw)
|
|
2662
|
+
? (channelPressureRaw - 64) * channel.state.channelPressure
|
|
2663
|
+
: 0;
|
|
2682
2664
|
return channelPressure * 15;
|
|
2683
2665
|
}
|
|
2684
2666
|
getAmplitudeControl(channel) {
|
|
2685
|
-
const
|
|
2686
|
-
|
|
2667
|
+
const channelPressureRaw = channel.channelPressureTable[2];
|
|
2668
|
+
const channelPressure = (0 <= channelPressureRaw)
|
|
2669
|
+
? channelPressureRaw * channel.state.channelPressure
|
|
2670
|
+
: 0;
|
|
2687
2671
|
return channelPressure / 64;
|
|
2688
2672
|
}
|
|
2689
2673
|
getLFOPitchDepth(channel) {
|
|
2690
|
-
const
|
|
2691
|
-
|
|
2674
|
+
const channelPressureRaw = channel.channelPressureTable[3];
|
|
2675
|
+
const channelPressure = (0 <= channelPressureRaw)
|
|
2676
|
+
? channelPressureRaw * channel.state.channelPressure
|
|
2677
|
+
: 0;
|
|
2692
2678
|
return channelPressure / 127 * 600;
|
|
2693
2679
|
}
|
|
2694
2680
|
getLFOFilterDepth(channel) {
|
|
2695
|
-
const
|
|
2696
|
-
|
|
2681
|
+
const channelPressureRaw = channel.channelPressureTable[4];
|
|
2682
|
+
const channelPressure = (0 <= channelPressureRaw)
|
|
2683
|
+
? channelPressureRaw * channel.state.channelPressure
|
|
2684
|
+
: 0;
|
|
2697
2685
|
return channelPressure / 127 * 2400;
|
|
2698
2686
|
}
|
|
2699
2687
|
getLFOAmplitudeDepth(channel) {
|
|
2700
|
-
const
|
|
2701
|
-
|
|
2688
|
+
const channelPressureRaw = channel.channelPressureTable[5];
|
|
2689
|
+
const channelPressure = (0 <= channelPressureRaw)
|
|
2690
|
+
? channelPressureRaw * channel.state.channelPressure
|
|
2691
|
+
: 0;
|
|
2702
2692
|
return channelPressure / 127;
|
|
2703
2693
|
}
|
|
2704
2694
|
setControllerParameters(channel, note, table) {
|
|
2705
|
-
if (table[0]
|
|
2695
|
+
if (0 <= table[0])
|
|
2706
2696
|
this.updateDetune(channel, note);
|
|
2707
2697
|
if (0.5 <= channel.state.portamemento && 0 <= note.portamentoNoteNumber) {
|
|
2708
|
-
if (table[1]
|
|
2698
|
+
if (0 <= table[1])
|
|
2709
2699
|
this.setPortamentoFilterEnvelope(channel, note);
|
|
2710
|
-
if (table[2]
|
|
2700
|
+
if (0 <= table[2])
|
|
2711
2701
|
this.setPortamentoVolumeEnvelope(channel, note);
|
|
2712
2702
|
}
|
|
2713
2703
|
else {
|
|
2714
|
-
if (table[1]
|
|
2704
|
+
if (0 <= table[1])
|
|
2715
2705
|
this.setFilterEnvelope(channel, note);
|
|
2716
|
-
if (table[2]
|
|
2706
|
+
if (0 <= table[2])
|
|
2717
2707
|
this.setVolumeEnvelope(channel, note);
|
|
2718
2708
|
}
|
|
2719
|
-
if (table[3]
|
|
2709
|
+
if (0 <= table[3])
|
|
2720
2710
|
this.setModLfoToPitch(channel, note);
|
|
2721
|
-
if (table[4]
|
|
2711
|
+
if (0 <= table[4])
|
|
2722
2712
|
this.setModLfoToFilterFc(channel, note);
|
|
2723
|
-
if (table[5]
|
|
2713
|
+
if (0 <= table[5])
|
|
2724
2714
|
this.setModLfoToVolume(channel, note);
|
|
2725
2715
|
}
|
|
2726
2716
|
handlePressureSysEx(data, tableName) {
|
|
@@ -2736,26 +2726,15 @@ export class MidyGM2 {
|
|
|
2736
2726
|
}
|
|
2737
2727
|
}
|
|
2738
2728
|
initControlTable() {
|
|
2739
|
-
const
|
|
2729
|
+
const ccCount = 128;
|
|
2740
2730
|
const slotSize = 6;
|
|
2741
|
-
|
|
2742
|
-
return this.resetControlTable(table);
|
|
2731
|
+
return new Int8Array(ccCount * slotSize).fill(-1);
|
|
2743
2732
|
}
|
|
2744
|
-
|
|
2745
|
-
const channelCount = 128;
|
|
2746
|
-
const slotSize = 6;
|
|
2747
|
-
const defaultValues = [64, 64, 64, 0, 0, 0];
|
|
2748
|
-
for (let ch = 0; ch < channelCount; ch++) {
|
|
2749
|
-
const offset = ch * slotSize;
|
|
2750
|
-
table.set(defaultValues, offset);
|
|
2751
|
-
}
|
|
2752
|
-
return table;
|
|
2753
|
-
}
|
|
2754
|
-
applyControlTable(channel, controllerType) {
|
|
2733
|
+
applyControlTable(channel, controllerType, scheduleTime) {
|
|
2755
2734
|
const slotSize = 6;
|
|
2756
2735
|
const offset = controllerType * slotSize;
|
|
2757
2736
|
const table = channel.controlTable.subarray(offset, offset + slotSize);
|
|
2758
|
-
this.processScheduledNotes(channel, (note) => {
|
|
2737
|
+
this.processScheduledNotes(channel, scheduleTime, (note) => {
|
|
2759
2738
|
this.setControllerParameters(channel, note, table);
|
|
2760
2739
|
});
|
|
2761
2740
|
}
|
|
@@ -2780,7 +2759,7 @@ export class MidyGM2 {
|
|
|
2780
2759
|
handleKeyBasedInstrumentControlSysEx(data, scheduleTime) {
|
|
2781
2760
|
const channelNumber = data[4];
|
|
2782
2761
|
const channel = this.channels[channelNumber];
|
|
2783
|
-
if (channel.isDrum)
|
|
2762
|
+
if (!channel.isDrum)
|
|
2784
2763
|
return;
|
|
2785
2764
|
const keyNumber = data[5];
|
|
2786
2765
|
const table = channel.keyBasedInstrumentControlTable;
|