@marmooo/midy 0.4.6 → 0.4.7
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 -0
- package/esm/midy-GM1.d.ts.map +1 -1
- package/esm/midy-GM1.js +115 -53
- package/esm/midy-GM2.d.ts +13 -4
- package/esm/midy-GM2.d.ts.map +1 -1
- package/esm/midy-GM2.js +251 -157
- package/esm/midy-GMLite.d.ts +2 -0
- package/esm/midy-GMLite.d.ts.map +1 -1
- package/esm/midy-GMLite.js +109 -53
- package/esm/midy.d.ts +29 -11
- package/esm/midy.d.ts.map +1 -1
- package/esm/midy.js +328 -221
- package/package.json +3 -2
- package/script/midy-GM1.d.ts +3 -0
- package/script/midy-GM1.d.ts.map +1 -1
- package/script/midy-GM1.js +115 -53
- package/script/midy-GM2.d.ts +13 -4
- package/script/midy-GM2.d.ts.map +1 -1
- package/script/midy-GM2.js +251 -157
- package/script/midy-GMLite.d.ts +2 -0
- package/script/midy-GMLite.d.ts.map +1 -1
- package/script/midy-GMLite.js +109 -53
- package/script/midy.d.ts +29 -11
- package/script/midy.d.ts.map +1 -1
- package/script/midy.js +328 -221
package/script/midy-GM2.js
CHANGED
|
@@ -3,6 +3,16 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.MidyGM2 = void 0;
|
|
4
4
|
const midi_file_1 = require("midi-file");
|
|
5
5
|
const soundfont_parser_1 = require("@marmooo/soundfont-parser");
|
|
6
|
+
const ogg_vorbis_1 = require("@wasm-audio-decoders/ogg-vorbis");
|
|
7
|
+
let decoderPromise = null;
|
|
8
|
+
let decoderQueue = Promise.resolve();
|
|
9
|
+
function initDecoder() {
|
|
10
|
+
if (!decoderPromise) {
|
|
11
|
+
const instance = new ogg_vorbis_1.OggVorbisDecoderWebWorker();
|
|
12
|
+
decoderPromise = instance.ready.then(() => instance);
|
|
13
|
+
}
|
|
14
|
+
return decoderPromise;
|
|
15
|
+
}
|
|
6
16
|
class Note {
|
|
7
17
|
constructor(noteNumber, velocity, startTime) {
|
|
8
18
|
Object.defineProperty(this, "voice", {
|
|
@@ -41,49 +51,49 @@ class Note {
|
|
|
41
51
|
writable: true,
|
|
42
52
|
value: void 0
|
|
43
53
|
});
|
|
44
|
-
Object.defineProperty(this, "
|
|
54
|
+
Object.defineProperty(this, "filterEnvelopeNode", {
|
|
45
55
|
enumerable: true,
|
|
46
56
|
configurable: true,
|
|
47
57
|
writable: true,
|
|
48
58
|
value: void 0
|
|
49
59
|
});
|
|
50
|
-
Object.defineProperty(this, "
|
|
60
|
+
Object.defineProperty(this, "volumeEnvelopeNode", {
|
|
51
61
|
enumerable: true,
|
|
52
62
|
configurable: true,
|
|
53
63
|
writable: true,
|
|
54
64
|
value: void 0
|
|
55
65
|
});
|
|
56
|
-
Object.defineProperty(this, "
|
|
66
|
+
Object.defineProperty(this, "modLfo", {
|
|
57
67
|
enumerable: true,
|
|
58
68
|
configurable: true,
|
|
59
69
|
writable: true,
|
|
60
70
|
value: void 0
|
|
61
|
-
});
|
|
62
|
-
Object.defineProperty(this, "
|
|
71
|
+
}); // CC#1 modulation LFO
|
|
72
|
+
Object.defineProperty(this, "modLfoToPitch", {
|
|
63
73
|
enumerable: true,
|
|
64
74
|
configurable: true,
|
|
65
75
|
writable: true,
|
|
66
76
|
value: void 0
|
|
67
77
|
});
|
|
68
|
-
Object.defineProperty(this, "
|
|
78
|
+
Object.defineProperty(this, "modLfoToFilterFc", {
|
|
69
79
|
enumerable: true,
|
|
70
80
|
configurable: true,
|
|
71
81
|
writable: true,
|
|
72
82
|
value: void 0
|
|
73
83
|
});
|
|
74
|
-
Object.defineProperty(this, "
|
|
84
|
+
Object.defineProperty(this, "modLfoToVolume", {
|
|
75
85
|
enumerable: true,
|
|
76
86
|
configurable: true,
|
|
77
87
|
writable: true,
|
|
78
88
|
value: void 0
|
|
79
89
|
});
|
|
80
|
-
Object.defineProperty(this, "
|
|
90
|
+
Object.defineProperty(this, "vibLfo", {
|
|
81
91
|
enumerable: true,
|
|
82
92
|
configurable: true,
|
|
83
93
|
writable: true,
|
|
84
94
|
value: void 0
|
|
85
|
-
});
|
|
86
|
-
Object.defineProperty(this, "
|
|
95
|
+
}); // vibrato LFO
|
|
96
|
+
Object.defineProperty(this, "vibLfoToPitch", {
|
|
87
97
|
enumerable: true,
|
|
88
98
|
configurable: true,
|
|
89
99
|
writable: true,
|
|
@@ -234,7 +244,20 @@ const pitchEnvelopeKeys = [
|
|
|
234
244
|
"playbackRate",
|
|
235
245
|
];
|
|
236
246
|
const pitchEnvelopeKeySet = new Set(pitchEnvelopeKeys);
|
|
247
|
+
const effectParameters = [
|
|
248
|
+
2400 / 64, // cent
|
|
249
|
+
9600 / 64, // cent
|
|
250
|
+
1 / 64,
|
|
251
|
+
600 / 127, // cent
|
|
252
|
+
2400 / 127, // cent
|
|
253
|
+
1 / 127,
|
|
254
|
+
];
|
|
255
|
+
const pressureBaselines = new Int8Array([64, 64, 0, 0, 0, 0]);
|
|
237
256
|
const defaultPressureValues = new Int8Array([64, 64, 64, 0, 0, 0]);
|
|
257
|
+
const defaultControlValues = new Int8Array([
|
|
258
|
+
...[-1, -1, -1, -1, -1, -1],
|
|
259
|
+
...defaultPressureValues,
|
|
260
|
+
]);
|
|
238
261
|
function cbToRatio(cb) {
|
|
239
262
|
return Math.pow(10, cb / 200);
|
|
240
263
|
}
|
|
@@ -383,6 +406,12 @@ class MidyGM2 extends EventTarget {
|
|
|
383
406
|
writable: true,
|
|
384
407
|
value: new Map()
|
|
385
408
|
});
|
|
409
|
+
Object.defineProperty(this, "decodeMethod", {
|
|
410
|
+
enumerable: true,
|
|
411
|
+
configurable: true,
|
|
412
|
+
writable: true,
|
|
413
|
+
value: "wasm-audio-decoders"
|
|
414
|
+
});
|
|
386
415
|
Object.defineProperty(this, "isPlaying", {
|
|
387
416
|
enumerable: true,
|
|
388
417
|
configurable: true,
|
|
@@ -480,6 +509,7 @@ class MidyGM2 extends EventTarget {
|
|
|
480
509
|
this.voiceParamsHandlers = this.createVoiceParamsHandlers();
|
|
481
510
|
this.controlChangeHandlers = this.createControlChangeHandlers();
|
|
482
511
|
this.keyBasedControllerHandlers = this.createKeyBasedControllerHandlers();
|
|
512
|
+
this.effectHandlers = this.createEffectHandlers();
|
|
483
513
|
this.channels = this.createChannels(audioContext);
|
|
484
514
|
this.reverbEffect = this.createReverbEffect(audioContext);
|
|
485
515
|
this.chorusEffect = this.createChorusEffect(audioContext);
|
|
@@ -607,7 +637,7 @@ class MidyGM2 extends EventTarget {
|
|
|
607
637
|
};
|
|
608
638
|
}
|
|
609
639
|
resetChannelTable(channel) {
|
|
610
|
-
channel.controlTable.
|
|
640
|
+
channel.controlTable.set(defaultControlValues);
|
|
611
641
|
channel.scaleOctaveTuningTable.fill(0); // [-100, 100] cent
|
|
612
642
|
channel.channelPressureTable.set(defaultPressureValues);
|
|
613
643
|
channel.keyBasedTable.fill(-1);
|
|
@@ -623,7 +653,7 @@ class MidyGM2 extends EventTarget {
|
|
|
623
653
|
scheduledNotes: [],
|
|
624
654
|
sustainNotes: [],
|
|
625
655
|
sostenutoNotes: [],
|
|
626
|
-
controlTable:
|
|
656
|
+
controlTable: new Int8Array(defaultControlValues),
|
|
627
657
|
scaleOctaveTuningTable: new Int8Array(12), // [-64, 63] cent
|
|
628
658
|
channelPressureTable: new Int8Array(defaultPressureValues),
|
|
629
659
|
keyBasedTable: new Int8Array(128 * 128).fill(-1),
|
|
@@ -633,11 +663,57 @@ class MidyGM2 extends EventTarget {
|
|
|
633
663
|
});
|
|
634
664
|
return channels;
|
|
635
665
|
}
|
|
666
|
+
decodeOggVorbis(sample) {
|
|
667
|
+
const task = decoderQueue.then(async () => {
|
|
668
|
+
const decoder = await initDecoder();
|
|
669
|
+
const slice = sample.data.slice();
|
|
670
|
+
const { channelData, sampleRate, errors } = await decoder.decodeFile(slice);
|
|
671
|
+
if (0 < errors.length) {
|
|
672
|
+
throw new Error(errors.join(", "));
|
|
673
|
+
}
|
|
674
|
+
const audioBuffer = new AudioBuffer({
|
|
675
|
+
numberOfChannels: channelData.length,
|
|
676
|
+
length: channelData[0].length,
|
|
677
|
+
sampleRate,
|
|
678
|
+
});
|
|
679
|
+
for (let ch = 0; ch < channelData.length; ch++) {
|
|
680
|
+
audioBuffer.getChannelData(ch).set(channelData[ch]);
|
|
681
|
+
}
|
|
682
|
+
return audioBuffer;
|
|
683
|
+
});
|
|
684
|
+
decoderQueue = task.catch(() => { });
|
|
685
|
+
return task;
|
|
686
|
+
}
|
|
636
687
|
async createAudioBuffer(voiceParams) {
|
|
637
|
-
const
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
688
|
+
const sample = voiceParams.sample;
|
|
689
|
+
if (sample.type === "compressed") {
|
|
690
|
+
switch (this.decodeMethod) {
|
|
691
|
+
case "decodeAudioData": {
|
|
692
|
+
// https://jakearchibald.com/2016/sounds-fun/
|
|
693
|
+
// https://github.com/WebAudio/web-audio-api/issues/1091
|
|
694
|
+
// decodeAudioData() has priming issues on Safari
|
|
695
|
+
const arrayBuffer = sample.data.slice().buffer;
|
|
696
|
+
return await this.audioContext.decodeAudioData(arrayBuffer);
|
|
697
|
+
}
|
|
698
|
+
case "wasm-audio-decoders":
|
|
699
|
+
return await this.decodeOggVorbis(sample);
|
|
700
|
+
default:
|
|
701
|
+
throw new Error(`Unknown decodeMethod: ${this.decodeMethod}`);
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
else {
|
|
705
|
+
const data = sample.data;
|
|
706
|
+
const end = data.length + voiceParams.end;
|
|
707
|
+
const subarray = data.subarray(voiceParams.start, end);
|
|
708
|
+
const pcm = sample.decodePCM(subarray);
|
|
709
|
+
const audioBuffer = new AudioBuffer({
|
|
710
|
+
numberOfChannels: 1,
|
|
711
|
+
length: pcm.length,
|
|
712
|
+
sampleRate: sample.sampleHeader.sampleRate,
|
|
713
|
+
});
|
|
714
|
+
audioBuffer.getChannelData(0).set(pcm);
|
|
715
|
+
return audioBuffer;
|
|
716
|
+
}
|
|
641
717
|
}
|
|
642
718
|
isLoopDrum(channel, noteNumber) {
|
|
643
719
|
const programNumber = channel.programNumber;
|
|
@@ -711,7 +787,7 @@ class MidyGM2 extends EventTarget {
|
|
|
711
787
|
this.voiceCache.clear();
|
|
712
788
|
this.realtimeVoiceCache.clear();
|
|
713
789
|
const channels = this.channels;
|
|
714
|
-
for (let ch = 0;
|
|
790
|
+
for (let ch = 0; ch < channels.length; ch++) {
|
|
715
791
|
channels[ch].scheduledNotes = [];
|
|
716
792
|
this.resetChannelStates(ch);
|
|
717
793
|
}
|
|
@@ -1201,16 +1277,8 @@ class MidyGM2 extends EventTarget {
|
|
|
1201
1277
|
const pitchWheel = channel.state.pitchWheel * 2 - 1;
|
|
1202
1278
|
const pitchWheelSensitivity = channel.state.pitchWheelSensitivity * 12800;
|
|
1203
1279
|
const pitch = pitchWheel * pitchWheelSensitivity;
|
|
1204
|
-
const
|
|
1205
|
-
|
|
1206
|
-
const channelPressureDepth = (channelPressureRaw - 64) / 37.5; // 2400 / 64;
|
|
1207
|
-
const channelPressure = channelPressureDepth *
|
|
1208
|
-
channel.state.channelPressure;
|
|
1209
|
-
return tuning + pitch + channelPressure;
|
|
1210
|
-
}
|
|
1211
|
-
else {
|
|
1212
|
-
return tuning + pitch;
|
|
1213
|
-
}
|
|
1280
|
+
const effect = this.getChannelPitchControl(channel);
|
|
1281
|
+
return tuning + pitch + effect;
|
|
1214
1282
|
}
|
|
1215
1283
|
updateChannelDetune(channel, scheduleTime) {
|
|
1216
1284
|
this.processScheduledNotes(channel, (note) => {
|
|
@@ -1228,8 +1296,7 @@ class MidyGM2 extends EventTarget {
|
|
|
1228
1296
|
calcNoteDetune(channel, note) {
|
|
1229
1297
|
const noteDetune = note.voiceParams.detune +
|
|
1230
1298
|
this.calcScaleOctaveTuning(channel, note);
|
|
1231
|
-
|
|
1232
|
-
return channel.detune + noteDetune + pitchControl;
|
|
1299
|
+
return channel.detune + noteDetune;
|
|
1233
1300
|
}
|
|
1234
1301
|
getPortamentoTime(channel, note) {
|
|
1235
1302
|
const deltaSemitone = Math.abs(note.noteNumber - note.portamentoNoteNumber);
|
|
@@ -1386,7 +1453,7 @@ class MidyGM2 extends EventTarget {
|
|
|
1386
1453
|
const portamentoTime = startTime + this.getPortamentoTime(channel, note);
|
|
1387
1454
|
const modDelay = startTime + voiceParams.modDelay;
|
|
1388
1455
|
note.adjustedBaseFreq = adjustedSustainFreq;
|
|
1389
|
-
note.
|
|
1456
|
+
note.filterEnvelopeNode.frequency
|
|
1390
1457
|
.cancelScheduledValues(scheduleTime)
|
|
1391
1458
|
.setValueAtTime(adjustedBaseFreq, startTime)
|
|
1392
1459
|
.setValueAtTime(adjustedBaseFreq, modDelay)
|
|
@@ -1412,7 +1479,7 @@ class MidyGM2 extends EventTarget {
|
|
|
1412
1479
|
const modHold = modAttack + voiceParams.modHold;
|
|
1413
1480
|
const decayDuration = voiceParams.modDecay;
|
|
1414
1481
|
note.adjustedBaseFreq = adjustedBaseFreq;
|
|
1415
|
-
note.
|
|
1482
|
+
note.filterEnvelopeNode.frequency
|
|
1416
1483
|
.cancelScheduledValues(scheduleTime)
|
|
1417
1484
|
.setValueAtTime(adjustedBaseFreq, startTime)
|
|
1418
1485
|
.setValueAtTime(adjustedBaseFreq, modDelay)
|
|
@@ -1423,37 +1490,37 @@ class MidyGM2 extends EventTarget {
|
|
|
1423
1490
|
startModulation(channel, note, scheduleTime) {
|
|
1424
1491
|
const audioContext = this.audioContext;
|
|
1425
1492
|
const { voiceParams } = note;
|
|
1426
|
-
note.
|
|
1493
|
+
note.modLfo = new OscillatorNode(audioContext, {
|
|
1427
1494
|
frequency: this.centToHz(voiceParams.freqModLFO),
|
|
1428
1495
|
});
|
|
1429
|
-
note.
|
|
1496
|
+
note.modLfoToFilterFc = new GainNode(audioContext, {
|
|
1430
1497
|
gain: voiceParams.modLfoToFilterFc,
|
|
1431
1498
|
});
|
|
1432
|
-
note.
|
|
1499
|
+
note.modLfoToPitch = new GainNode(audioContext);
|
|
1433
1500
|
this.setModLfoToPitch(channel, note, scheduleTime);
|
|
1434
|
-
note.
|
|
1501
|
+
note.modLfoToVolume = new GainNode(audioContext);
|
|
1435
1502
|
this.setModLfoToVolume(note, scheduleTime);
|
|
1436
|
-
note.
|
|
1437
|
-
note.
|
|
1438
|
-
note.
|
|
1439
|
-
note.
|
|
1440
|
-
note.
|
|
1441
|
-
note.
|
|
1442
|
-
note.
|
|
1503
|
+
note.modLfo.start(note.startTime + voiceParams.delayModLFO);
|
|
1504
|
+
note.modLfo.connect(note.modLfoToFilterFc);
|
|
1505
|
+
note.modLfoToFilterFc.connect(note.filterEnvelopeNode.frequency);
|
|
1506
|
+
note.modLfo.connect(note.modLfoToPitch);
|
|
1507
|
+
note.modLfoToPitch.connect(note.bufferSource.detune);
|
|
1508
|
+
note.modLfo.connect(note.modLfoToVolume);
|
|
1509
|
+
note.modLfoToVolume.connect(note.volumeEnvelopeNode.gain);
|
|
1443
1510
|
}
|
|
1444
1511
|
startVibrato(channel, note, scheduleTime) {
|
|
1445
1512
|
const { voiceParams } = note;
|
|
1446
1513
|
const state = channel.state;
|
|
1447
1514
|
const vibratoRate = state.vibratoRate * 2;
|
|
1448
1515
|
const vibratoDelay = state.vibratoDelay * 2;
|
|
1449
|
-
note.
|
|
1516
|
+
note.vibLfo = new OscillatorNode(this.audioContext, {
|
|
1450
1517
|
frequency: this.centToHz(voiceParams.freqVibLFO) * vibratoRate,
|
|
1451
1518
|
});
|
|
1452
|
-
note.
|
|
1453
|
-
note.
|
|
1519
|
+
note.vibLfo.start(note.startTime + voiceParams.delayVibLFO * vibratoDelay);
|
|
1520
|
+
note.vibLfoToPitch = new GainNode(this.audioContext);
|
|
1454
1521
|
this.setVibLfoToPitch(channel, note, scheduleTime);
|
|
1455
|
-
note.
|
|
1456
|
-
note.
|
|
1522
|
+
note.vibLfo.connect(note.vibLfoToPitch);
|
|
1523
|
+
note.vibLfoToPitch.connect(note.bufferSource.detune);
|
|
1457
1524
|
}
|
|
1458
1525
|
async getAudioBuffer(channel, noteNumber, velocity, voiceParams, realtime) {
|
|
1459
1526
|
const audioBufferId = this.getVoiceId(channel, noteNumber, velocity);
|
|
@@ -1494,7 +1561,7 @@ class MidyGM2 extends EventTarget {
|
|
|
1494
1561
|
const audioBuffer = await this.getAudioBuffer(channel, noteNumber, velocity, voiceParams, realtime);
|
|
1495
1562
|
note.bufferSource = this.createBufferSource(channel, noteNumber, voiceParams, audioBuffer);
|
|
1496
1563
|
note.volumeEnvelopeNode = new GainNode(audioContext);
|
|
1497
|
-
note.
|
|
1564
|
+
note.filterEnvelopeNode = new BiquadFilterNode(audioContext, {
|
|
1498
1565
|
type: "lowpass",
|
|
1499
1566
|
Q: voiceParams.initialFilterQ / 10, // dB
|
|
1500
1567
|
});
|
|
@@ -1524,8 +1591,8 @@ class MidyGM2 extends EventTarget {
|
|
|
1524
1591
|
channel.currentBufferSource.stop(startTime);
|
|
1525
1592
|
channel.currentBufferSource = note.bufferSource;
|
|
1526
1593
|
}
|
|
1527
|
-
note.bufferSource.connect(note.
|
|
1528
|
-
note.
|
|
1594
|
+
note.bufferSource.connect(note.filterEnvelopeNode);
|
|
1595
|
+
note.filterEnvelopeNode.connect(note.volumeEnvelopeNode);
|
|
1529
1596
|
this.setChorusSend(channel, note, now);
|
|
1530
1597
|
this.setReverbSend(channel, note, now);
|
|
1531
1598
|
if (voiceParams.sample.type === "compressed") {
|
|
@@ -1606,8 +1673,6 @@ class MidyGM2 extends EventTarget {
|
|
|
1606
1673
|
scheduledNotes.push(note);
|
|
1607
1674
|
const programNumber = channel.programNumber;
|
|
1608
1675
|
const bankTable = this.soundFontTable[programNumber];
|
|
1609
|
-
if (!bankTable)
|
|
1610
|
-
return;
|
|
1611
1676
|
let bank = channel.isDrum ? 128 : channel.bankLSB;
|
|
1612
1677
|
if (bankTable[bank] === undefined) {
|
|
1613
1678
|
if (channel.isDrum)
|
|
@@ -1627,16 +1692,16 @@ class MidyGM2 extends EventTarget {
|
|
|
1627
1692
|
}
|
|
1628
1693
|
disconnectNote(note) {
|
|
1629
1694
|
note.bufferSource.disconnect();
|
|
1630
|
-
note.
|
|
1695
|
+
note.filterEnvelopeNode.disconnect();
|
|
1631
1696
|
note.volumeEnvelopeNode.disconnect();
|
|
1632
|
-
if (note.
|
|
1633
|
-
note.
|
|
1634
|
-
note.
|
|
1635
|
-
note.
|
|
1697
|
+
if (note.modLfoToPitch) {
|
|
1698
|
+
note.modLfoToVolume.disconnect();
|
|
1699
|
+
note.modLfoToPitch.disconnect();
|
|
1700
|
+
note.modLfo.stop();
|
|
1636
1701
|
}
|
|
1637
|
-
if (note.
|
|
1638
|
-
note.
|
|
1639
|
-
note.
|
|
1702
|
+
if (note.vibLfoToPitch) {
|
|
1703
|
+
note.vibLfoToPitch.disconnect();
|
|
1704
|
+
note.vibLfo.stop();
|
|
1640
1705
|
}
|
|
1641
1706
|
if (note.reverbSend) {
|
|
1642
1707
|
note.reverbSend.disconnect();
|
|
@@ -1649,7 +1714,7 @@ class MidyGM2 extends EventTarget {
|
|
|
1649
1714
|
endTime ??= this.audioContext.currentTime;
|
|
1650
1715
|
const volDuration = note.voiceParams.volRelease;
|
|
1651
1716
|
const volRelease = endTime + volDuration;
|
|
1652
|
-
note.
|
|
1717
|
+
note.filterEnvelopeNode.frequency
|
|
1653
1718
|
.cancelScheduledValues(endTime)
|
|
1654
1719
|
.setTargetAtTime(note.adjustedBaseFreq, endTime, note.voiceParams.modRelease * releaseCurve);
|
|
1655
1720
|
note.volumeEnvelopeNode.gain
|
|
@@ -1790,17 +1855,12 @@ class MidyGM2 extends EventTarget {
|
|
|
1790
1855
|
const channel = this.channels[channelNumber];
|
|
1791
1856
|
if (channel.isDrum)
|
|
1792
1857
|
return;
|
|
1793
|
-
const prev = channel
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
if (0 <= channelPressureRaw) {
|
|
1798
|
-
const channelPressureDepth = (channelPressureRaw - 64) / 37.5; // 2400 / 64;
|
|
1799
|
-
channel.detune += channelPressureDepth * (next - prev);
|
|
1800
|
-
}
|
|
1801
|
-
const table = channel.channelPressureTable;
|
|
1858
|
+
const prev = this.calcChannelPressureEffectValue(channel, 0);
|
|
1859
|
+
channel.state.channelPressure = value / 127;
|
|
1860
|
+
const next = this.calcChannelPressureEffectValue(channel, 0);
|
|
1861
|
+
channel.detune += next - prev;
|
|
1802
1862
|
this.processActiveNotes(channel, scheduleTime, (note) => {
|
|
1803
|
-
this.
|
|
1863
|
+
this.setPressureEffects(channel, note, scheduleTime);
|
|
1804
1864
|
});
|
|
1805
1865
|
this.applyVoiceParams(channel, 13, scheduleTime);
|
|
1806
1866
|
}
|
|
@@ -1823,13 +1883,13 @@ class MidyGM2 extends EventTarget {
|
|
|
1823
1883
|
this.applyVoiceParams(channel, 14, scheduleTime);
|
|
1824
1884
|
}
|
|
1825
1885
|
setModLfoToPitch(channel, note, scheduleTime) {
|
|
1826
|
-
if (note.
|
|
1886
|
+
if (note.modLfoToPitch) {
|
|
1827
1887
|
const modLfoToPitch = note.voiceParams.modLfoToPitch +
|
|
1828
1888
|
this.getLFOPitchDepth(channel, note);
|
|
1829
1889
|
const baseDepth = Math.abs(modLfoToPitch) +
|
|
1830
1890
|
channel.state.modulationDepthMSB;
|
|
1831
1891
|
const depth = baseDepth * Math.sign(modLfoToPitch);
|
|
1832
|
-
note.
|
|
1892
|
+
note.modLfoToPitch.gain
|
|
1833
1893
|
.cancelScheduledValues(scheduleTime)
|
|
1834
1894
|
.setValueAtTime(depth, scheduleTime);
|
|
1835
1895
|
}
|
|
@@ -1838,12 +1898,12 @@ class MidyGM2 extends EventTarget {
|
|
|
1838
1898
|
}
|
|
1839
1899
|
}
|
|
1840
1900
|
setVibLfoToPitch(channel, note, scheduleTime) {
|
|
1841
|
-
if (note.
|
|
1901
|
+
if (note.vibLfoToPitch) {
|
|
1842
1902
|
const vibratoDepth = channel.state.vibratoDepth * 2;
|
|
1843
1903
|
const vibLfoToPitch = note.voiceParams.vibLfoToPitch;
|
|
1844
1904
|
const baseDepth = Math.abs(vibLfoToPitch) * vibratoDepth;
|
|
1845
1905
|
const depth = baseDepth * Math.sign(vibLfoToPitch);
|
|
1846
|
-
note.
|
|
1906
|
+
note.vibLfoToPitch.gain
|
|
1847
1907
|
.cancelScheduledValues(scheduleTime)
|
|
1848
1908
|
.setValueAtTime(depth, scheduleTime);
|
|
1849
1909
|
}
|
|
@@ -1854,18 +1914,18 @@ class MidyGM2 extends EventTarget {
|
|
|
1854
1914
|
setModLfoToFilterFc(channel, note, scheduleTime) {
|
|
1855
1915
|
const modLfoToFilterFc = note.voiceParams.modLfoToFilterFc +
|
|
1856
1916
|
this.getLFOFilterDepth(channel);
|
|
1857
|
-
note.
|
|
1917
|
+
note.modLfoToFilterFc.gain
|
|
1858
1918
|
.cancelScheduledValues(scheduleTime)
|
|
1859
1919
|
.setValueAtTime(modLfoToFilterFc, scheduleTime);
|
|
1860
1920
|
}
|
|
1861
1921
|
setModLfoToVolume(channel, note, scheduleTime) {
|
|
1862
1922
|
const modLfoToVolume = note.voiceParams.modLfoToVolume;
|
|
1863
1923
|
const baseDepth = cbToRatio(Math.abs(modLfoToVolume)) - 1;
|
|
1864
|
-
const
|
|
1924
|
+
const depth = baseDepth * Math.sign(modLfoToVolume) *
|
|
1865
1925
|
(1 + this.getLFOAmplitudeDepth(channel));
|
|
1866
|
-
note.
|
|
1926
|
+
note.modLfoToVolume.gain
|
|
1867
1927
|
.cancelScheduledValues(scheduleTime)
|
|
1868
|
-
.setValueAtTime(
|
|
1928
|
+
.setValueAtTime(depth, scheduleTime);
|
|
1869
1929
|
}
|
|
1870
1930
|
setReverbSend(channel, note, scheduleTime) {
|
|
1871
1931
|
let value = note.voiceParams.reverbEffectsSend *
|
|
@@ -1930,13 +1990,13 @@ class MidyGM2 extends EventTarget {
|
|
|
1930
1990
|
setDelayModLFO(note) {
|
|
1931
1991
|
const startTime = note.startTime + note.voiceParams.delayModLFO;
|
|
1932
1992
|
try {
|
|
1933
|
-
note.
|
|
1993
|
+
note.modLfo.start(startTime);
|
|
1934
1994
|
}
|
|
1935
1995
|
catch { /* empty */ }
|
|
1936
1996
|
}
|
|
1937
1997
|
setFreqModLFO(note, scheduleTime) {
|
|
1938
1998
|
const freqModLFO = note.voiceParams.freqModLFO;
|
|
1939
|
-
note.
|
|
1999
|
+
note.modLfo.frequency
|
|
1940
2000
|
.cancelScheduledValues(scheduleTime)
|
|
1941
2001
|
.setValueAtTime(freqModLFO, scheduleTime);
|
|
1942
2002
|
}
|
|
@@ -1945,14 +2005,14 @@ class MidyGM2 extends EventTarget {
|
|
|
1945
2005
|
const value = note.voiceParams.delayVibLFO;
|
|
1946
2006
|
const startTime = note.startTime + value * vibratoDelay;
|
|
1947
2007
|
try {
|
|
1948
|
-
note.
|
|
2008
|
+
note.vibLfo.start(startTime);
|
|
1949
2009
|
}
|
|
1950
2010
|
catch { /* empty */ }
|
|
1951
2011
|
}
|
|
1952
2012
|
setFreqVibLFO(channel, note, scheduleTime) {
|
|
1953
2013
|
const vibratoRate = channel.state.vibratoRate * 2;
|
|
1954
2014
|
const freqVibLFO = note.voiceParams.freqVibLFO;
|
|
1955
|
-
note.
|
|
2015
|
+
note.vibLfo.frequency
|
|
1956
2016
|
.cancelScheduledValues(scheduleTime)
|
|
1957
2017
|
.setValueAtTime(freqVibLFO * vibratoRate, scheduleTime);
|
|
1958
2018
|
}
|
|
@@ -1996,7 +2056,7 @@ class MidyGM2 extends EventTarget {
|
|
|
1996
2056
|
},
|
|
1997
2057
|
delayVibLFO: (channel, note, _scheduleTime) => {
|
|
1998
2058
|
if (0 < channel.state.vibratoDepth) {
|
|
1999
|
-
setDelayVibLFO(channel, note);
|
|
2059
|
+
this.setDelayVibLFO(channel, note);
|
|
2000
2060
|
}
|
|
2001
2061
|
},
|
|
2002
2062
|
freqVibLFO: (channel, note, scheduleTime) => {
|
|
@@ -2085,12 +2145,16 @@ class MidyGM2 extends EventTarget {
|
|
|
2085
2145
|
return handlers;
|
|
2086
2146
|
}
|
|
2087
2147
|
setControlChange(channelNumber, controllerType, value, scheduleTime) {
|
|
2148
|
+
if (!(0 <= scheduleTime))
|
|
2149
|
+
scheduleTime = this.audioContext.currentTime;
|
|
2088
2150
|
const handler = this.controlChangeHandlers[controllerType];
|
|
2089
2151
|
if (handler) {
|
|
2090
2152
|
handler.call(this, channelNumber, value, scheduleTime);
|
|
2091
2153
|
const channel = this.channels[channelNumber];
|
|
2092
2154
|
this.applyVoiceParams(channel, controllerType + 128, scheduleTime);
|
|
2093
|
-
this.
|
|
2155
|
+
this.processActiveNotes(channel, scheduleTime, (note) => {
|
|
2156
|
+
this.setControlChangeEffects(channel, note, scheduleTime);
|
|
2157
|
+
});
|
|
2094
2158
|
}
|
|
2095
2159
|
else {
|
|
2096
2160
|
console.warn(`Unsupported Control change: controllerType=${controllerType} value=${value}`);
|
|
@@ -2103,8 +2167,8 @@ class MidyGM2 extends EventTarget {
|
|
|
2103
2167
|
const depth = channel.state.modulationDepthMSB *
|
|
2104
2168
|
channel.modulationDepthRange;
|
|
2105
2169
|
this.processScheduledNotes(channel, (note) => {
|
|
2106
|
-
if (note.
|
|
2107
|
-
note.
|
|
2170
|
+
if (note.modLfoToPitch) {
|
|
2171
|
+
note.modLfoToPitch.gain.setValueAtTime(depth, scheduleTime);
|
|
2108
2172
|
}
|
|
2109
2173
|
else {
|
|
2110
2174
|
this.startModulation(channel, note, scheduleTime);
|
|
@@ -2198,7 +2262,8 @@ class MidyGM2 extends EventTarget {
|
|
|
2198
2262
|
}
|
|
2199
2263
|
updateChannelVolume(channel, scheduleTime) {
|
|
2200
2264
|
const state = channel.state;
|
|
2201
|
-
const
|
|
2265
|
+
const effect = this.getChannelAmplitudeControl(channel);
|
|
2266
|
+
const gain = state.volumeMSB * state.expressionMSB * (1 + effect);
|
|
2202
2267
|
const { gainLeft, gainRight } = this.panToGain(state.panMSB);
|
|
2203
2268
|
channel.gainL.gain
|
|
2204
2269
|
.cancelScheduledValues(scheduleTime)
|
|
@@ -2647,8 +2712,8 @@ class MidyGM2 extends EventTarget {
|
|
|
2647
2712
|
this.masterFineTuning = next;
|
|
2648
2713
|
const detuneChange = next - prev;
|
|
2649
2714
|
const channels = this.channels;
|
|
2650
|
-
for (let ch = 0;
|
|
2651
|
-
const channel =
|
|
2715
|
+
for (let ch = 0; ch < channels.length; ch++) {
|
|
2716
|
+
const channel = channels[ch];
|
|
2652
2717
|
if (channel.isDrum)
|
|
2653
2718
|
continue;
|
|
2654
2719
|
channel.detune += detuneChange;
|
|
@@ -2666,7 +2731,7 @@ class MidyGM2 extends EventTarget {
|
|
|
2666
2731
|
const detuneChange = next - prev;
|
|
2667
2732
|
const channels = this.channels;
|
|
2668
2733
|
for (let ch = 0; ch < channels.length; ch++) {
|
|
2669
|
-
const channel =
|
|
2734
|
+
const channel = channels[ch];
|
|
2670
2735
|
if (channel.isDrum)
|
|
2671
2736
|
continue;
|
|
2672
2737
|
channel.detune += detuneChange;
|
|
@@ -2891,70 +2956,106 @@ class MidyGM2 extends EventTarget {
|
|
|
2891
2956
|
this.updateChannelDetune(channel, scheduleTime);
|
|
2892
2957
|
}
|
|
2893
2958
|
}
|
|
2959
|
+
calcEffectValue(channel, destination) {
|
|
2960
|
+
return this.calcChannelEffectValue(channel, destination);
|
|
2961
|
+
}
|
|
2962
|
+
calcChannelEffectValue(channel, destination) {
|
|
2963
|
+
return this.calcControlChangeEffectValue(channel, destination) +
|
|
2964
|
+
this.calcChannelPressureEffectValue(channel, destination);
|
|
2965
|
+
}
|
|
2966
|
+
calcControlChangeEffectValue(channel, destination) {
|
|
2967
|
+
const controlType = channel.controlTable[destination];
|
|
2968
|
+
if (controlType < 0)
|
|
2969
|
+
return 0;
|
|
2970
|
+
const pressure = channel.state.array[controlType];
|
|
2971
|
+
if (pressure <= 0)
|
|
2972
|
+
return 0;
|
|
2973
|
+
const baseline = pressureBaselines[destination];
|
|
2974
|
+
const tableValue = channel.controlTable[destination + 6];
|
|
2975
|
+
const value = (tableValue - baseline) * pressure;
|
|
2976
|
+
return value * effectParameters[destination];
|
|
2977
|
+
}
|
|
2978
|
+
calcChannelPressureEffectValue(channel, destination) {
|
|
2979
|
+
const pressure = channel.state.channelPressure;
|
|
2980
|
+
if (pressure <= 0)
|
|
2981
|
+
return 0;
|
|
2982
|
+
const baseline = pressureBaselines[destination];
|
|
2983
|
+
const tableValue = channel.channelPressureTable[destination];
|
|
2984
|
+
const value = (tableValue - baseline) * pressure;
|
|
2985
|
+
return value * effectParameters[destination];
|
|
2986
|
+
}
|
|
2987
|
+
getChannelPitchControl(channel) {
|
|
2988
|
+
return this.calcChannelEffectValue(channel, 0);
|
|
2989
|
+
}
|
|
2990
|
+
getPitchControl(channel, note) {
|
|
2991
|
+
return this.calcEffectValue(channel, note, 0);
|
|
2992
|
+
}
|
|
2894
2993
|
getFilterCutoffControl(channel) {
|
|
2895
|
-
|
|
2896
|
-
|
|
2897
|
-
|
|
2898
|
-
|
|
2899
|
-
return channelPressure * 15;
|
|
2900
|
-
}
|
|
2901
|
-
getAmplitudeControl(channel) {
|
|
2902
|
-
const channelPressureRaw = channel.channelPressureTable[2];
|
|
2903
|
-
const channelPressure = (0 <= channelPressureRaw)
|
|
2904
|
-
? channel.state.channelPressure * 127 / channelPressureRaw
|
|
2905
|
-
: 0;
|
|
2906
|
-
return channelPressure;
|
|
2994
|
+
return this.calcEffectValue(channel, 1);
|
|
2995
|
+
}
|
|
2996
|
+
getChannelAmplitudeControl(channel) {
|
|
2997
|
+
return this.calcChannelEffectValue(channel, 2);
|
|
2907
2998
|
}
|
|
2908
2999
|
getLFOPitchDepth(channel) {
|
|
2909
|
-
|
|
2910
|
-
const channelPressure = (0 <= channelPressureRaw)
|
|
2911
|
-
? channelPressureRaw * channel.state.channelPressure
|
|
2912
|
-
: 0;
|
|
2913
|
-
return channelPressure / 127 * 600;
|
|
3000
|
+
return this.calcEffectValue(channel, 3);
|
|
2914
3001
|
}
|
|
2915
3002
|
getLFOFilterDepth(channel) {
|
|
2916
|
-
|
|
2917
|
-
const channelPressure = (0 <= channelPressureRaw)
|
|
2918
|
-
? channelPressureRaw * channel.state.channelPressure
|
|
2919
|
-
: 0;
|
|
2920
|
-
return channelPressure / 127 * 2400;
|
|
3003
|
+
return this.calcEffectValue(channel, 4);
|
|
2921
3004
|
}
|
|
2922
3005
|
getLFOAmplitudeDepth(channel) {
|
|
2923
|
-
|
|
2924
|
-
|
|
2925
|
-
|
|
2926
|
-
|
|
2927
|
-
|
|
2928
|
-
}
|
|
2929
|
-
setEffects(channel, note, table, scheduleTime) {
|
|
2930
|
-
if (0 < table[0]) {
|
|
3006
|
+
return this.calcEffectValue(channel, 5);
|
|
3007
|
+
}
|
|
3008
|
+
createEffectHandlers() {
|
|
3009
|
+
const handlers = new Array(6);
|
|
3010
|
+
handlers[0] = (channel, note, scheduleTime) => {
|
|
2931
3011
|
if (this.isPortamento(channel, note)) {
|
|
2932
3012
|
this.setPortamentoDetune(channel, note, scheduleTime);
|
|
2933
3013
|
}
|
|
2934
3014
|
else {
|
|
2935
3015
|
this.setDetune(channel, note, scheduleTime);
|
|
2936
3016
|
}
|
|
2937
|
-
}
|
|
2938
|
-
|
|
2939
|
-
if (0
|
|
3017
|
+
};
|
|
3018
|
+
handlers[1] = (channel, note, scheduleTime) => {
|
|
3019
|
+
if (0.5 <= channel.state.portamento && 0 <= note.portamentoNoteNumber) {
|
|
2940
3020
|
this.setPortamentoFilterEnvelope(channel, note, scheduleTime);
|
|
2941
3021
|
}
|
|
2942
|
-
|
|
3022
|
+
else {
|
|
3023
|
+
this.setFilterEnvelope(channel, note, scheduleTime);
|
|
3024
|
+
}
|
|
3025
|
+
};
|
|
3026
|
+
handlers[2] = (channel, note, scheduleTime) => {
|
|
3027
|
+
if (0.5 <= channel.state.portamemento && 0 <= note.portamentoNoteNumber) {
|
|
2943
3028
|
this.setPortamentoVolumeEnvelope(channel, note, scheduleTime);
|
|
2944
3029
|
}
|
|
2945
|
-
|
|
2946
|
-
else {
|
|
2947
|
-
if (0 < table[1])
|
|
2948
|
-
this.setFilterEnvelope(channel, note, scheduleTime);
|
|
2949
|
-
if (0 < table[2])
|
|
3030
|
+
else {
|
|
2950
3031
|
this.setVolumeEnvelope(channel, note, scheduleTime);
|
|
3032
|
+
}
|
|
3033
|
+
};
|
|
3034
|
+
handlers[3] = (channel, note, scheduleTime) => this.setModLfoToPitch(channel, note, scheduleTime);
|
|
3035
|
+
handlers[4] = (channel, note, scheduleTime) => this.setModLfoToFilterFc(channel, note, scheduleTime);
|
|
3036
|
+
handlers[5] = (channel, note, scheduleTime) => this.setModLfoToVolume(channel, note, scheduleTime);
|
|
3037
|
+
return handlers;
|
|
3038
|
+
}
|
|
3039
|
+
setControlChangeEffects(channel, note, scheduleTime) {
|
|
3040
|
+
const handlers = this.effectHandlers;
|
|
3041
|
+
for (let i = 0; i < handlers.length; i++) {
|
|
3042
|
+
const baseline = pressureBaselines[i];
|
|
3043
|
+
const tableValue = channel.controlTable[i + 6];
|
|
3044
|
+
if (baseline === tableValue)
|
|
3045
|
+
continue;
|
|
3046
|
+
handlers[i](channel, note, scheduleTime);
|
|
3047
|
+
}
|
|
3048
|
+
}
|
|
3049
|
+
setPressureEffects(channel, note, tableName, scheduleTime) {
|
|
3050
|
+
const handlers = this.effectHandlers;
|
|
3051
|
+
const table = channel[tableName];
|
|
3052
|
+
for (let i = 0; i < handlers.length; i++) {
|
|
3053
|
+
const baseline = pressureBaselines[i];
|
|
3054
|
+
const tableValue = table[i];
|
|
3055
|
+
if (baseline === tableValue)
|
|
3056
|
+
continue;
|
|
3057
|
+
handlers[i](channel, note, scheduleTime);
|
|
2951
3058
|
}
|
|
2952
|
-
if (0 < table[3])
|
|
2953
|
-
this.setModLfoToPitch(channel, note, scheduleTime);
|
|
2954
|
-
if (0 < table[4])
|
|
2955
|
-
this.setModLfoToFilterFc(channel, note, scheduleTime);
|
|
2956
|
-
if (0 < table[5])
|
|
2957
|
-
this.setModLfoToVolume(channel, note, scheduleTime);
|
|
2958
3059
|
}
|
|
2959
3060
|
handlePressureSysEx(data, tableName, scheduleTime) {
|
|
2960
3061
|
const channelNumber = data[4];
|
|
@@ -2966,39 +3067,32 @@ class MidyGM2 extends EventTarget {
|
|
|
2966
3067
|
const pp = data[i];
|
|
2967
3068
|
const rr = data[i + 1];
|
|
2968
3069
|
table[pp] = rr;
|
|
3070
|
+
const handler = this.effectHandlers[pp];
|
|
3071
|
+
this.processActiveNotes(channel, scheduleTime, (note) => {
|
|
3072
|
+
if (handler)
|
|
3073
|
+
handler(channel, note, scheduleTime);
|
|
3074
|
+
});
|
|
2969
3075
|
}
|
|
2970
|
-
this.processActiveNotes(channel, scheduleTime, (note) => {
|
|
2971
|
-
this.setEffects(channel, note, table, scheduleTime);
|
|
2972
|
-
});
|
|
2973
|
-
}
|
|
2974
|
-
initControlTable() {
|
|
2975
|
-
const ccCount = 128;
|
|
2976
|
-
const slotSize = 6;
|
|
2977
|
-
return new Int8Array(ccCount * slotSize).fill(-1);
|
|
2978
|
-
}
|
|
2979
|
-
setControlChangeEffects(channel, controllerType, scheduleTime) {
|
|
2980
|
-
const slotSize = 6;
|
|
2981
|
-
const offset = controllerType * slotSize;
|
|
2982
|
-
const table = channel.controlTable.subarray(offset, offset + slotSize);
|
|
2983
|
-
this.processScheduledNotes(channel, (note) => {
|
|
2984
|
-
this.setEffects(channel, note, table, scheduleTime);
|
|
2985
|
-
});
|
|
2986
3076
|
}
|
|
2987
3077
|
handleControlChangeSysEx(data, scheduleTime) {
|
|
2988
3078
|
const channelNumber = data[4];
|
|
2989
3079
|
const channel = this.channels[channelNumber];
|
|
2990
3080
|
if (channel.isDrum)
|
|
2991
3081
|
return;
|
|
2992
|
-
const slotSize = 6;
|
|
2993
|
-
const controllerType = data[5];
|
|
2994
|
-
const offset = controllerType * slotSize;
|
|
2995
3082
|
const table = channel.controlTable;
|
|
3083
|
+
table.set(defaultControlValues);
|
|
3084
|
+
const controllerType = data[5];
|
|
2996
3085
|
for (let i = 6; i < data.length; i += 2) {
|
|
2997
3086
|
const pp = data[i];
|
|
2998
3087
|
const rr = data[i + 1];
|
|
2999
|
-
table[
|
|
3088
|
+
table[pp] = controllerType;
|
|
3089
|
+
table[pp + 6] = rr;
|
|
3090
|
+
const handler = this.effectHandlers[pp];
|
|
3091
|
+
this.processActiveNotes(channel, scheduleTime, (note) => {
|
|
3092
|
+
if (handler)
|
|
3093
|
+
handler(channel, note, scheduleTime);
|
|
3094
|
+
});
|
|
3000
3095
|
}
|
|
3001
|
-
this.setControlChangeEffects(channel, controllerType, scheduleTime);
|
|
3002
3096
|
}
|
|
3003
3097
|
getKeyBasedValue(channel, keyNumber, controllerType) {
|
|
3004
3098
|
const index = keyNumber * 128 + controllerType;
|