@marmooo/midy 0.0.2 → 0.0.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 +6 -14
- package/esm/midy-GM1.d.ts.map +1 -1
- package/esm/midy-GM1.js +104 -155
- package/esm/midy-GM2.d.ts +17 -107
- package/esm/midy-GM2.d.ts.map +1 -1
- package/esm/midy-GM2.js +152 -133
- package/esm/midy-GMLite.d.ts +6 -14
- package/esm/midy-GMLite.d.ts.map +1 -1
- package/esm/midy-GMLite.js +104 -155
- package/esm/midy.d.ts +47 -10
- package/esm/midy.d.ts.map +1 -1
- package/esm/midy.js +173 -129
- package/package.json +1 -1
- package/script/midy-GM1.d.ts +6 -14
- package/script/midy-GM1.d.ts.map +1 -1
- package/script/midy-GM1.js +104 -155
- package/script/midy-GM2.d.ts +17 -107
- package/script/midy-GM2.d.ts.map +1 -1
- package/script/midy-GM2.js +152 -133
- package/script/midy-GMLite.d.ts +6 -14
- package/script/midy-GMLite.d.ts.map +1 -1
- package/script/midy-GMLite.js +104 -155
- package/script/midy.d.ts +47 -10
- package/script/midy.d.ts.map +1 -1
- package/script/midy.js +173 -129
package/script/midy.js
CHANGED
|
@@ -11,12 +11,6 @@ class Midy {
|
|
|
11
11
|
writable: true,
|
|
12
12
|
value: 120
|
|
13
13
|
});
|
|
14
|
-
Object.defineProperty(this, "secondsPerBeat", {
|
|
15
|
-
enumerable: true,
|
|
16
|
-
configurable: true,
|
|
17
|
-
writable: true,
|
|
18
|
-
value: 0.5
|
|
19
|
-
});
|
|
20
14
|
Object.defineProperty(this, "totalTime", {
|
|
21
15
|
enumerable: true,
|
|
22
16
|
configurable: true,
|
|
@@ -177,10 +171,10 @@ class Midy {
|
|
|
177
171
|
const response = await fetch(midiUrl);
|
|
178
172
|
const arrayBuffer = await response.arrayBuffer();
|
|
179
173
|
const midi = (0, _esm_js_1.parseMidi)(new Uint8Array(arrayBuffer));
|
|
174
|
+
this.ticksPerBeat = midi.header.ticksPerBeat;
|
|
180
175
|
const midiData = this.extractMidiData(midi);
|
|
181
176
|
this.instruments = midiData.instruments;
|
|
182
177
|
this.timeline = midiData.timeline;
|
|
183
|
-
this.ticksPerBeat = midi.header.ticksPerBeat;
|
|
184
178
|
this.totalTime = this.calcTotalTime();
|
|
185
179
|
}
|
|
186
180
|
setChannelAudioNodes(audioContext) {
|
|
@@ -215,6 +209,12 @@ class Midy {
|
|
|
215
209
|
...this.setChannelAudioNodes(audioContext),
|
|
216
210
|
scheduledNotes: new Map(),
|
|
217
211
|
sostenutoNotes: new Map(),
|
|
212
|
+
polyphonicKeyPressure: {
|
|
213
|
+
...Midy.controllerDestinationSettings,
|
|
214
|
+
},
|
|
215
|
+
channelPressure: {
|
|
216
|
+
...Midy.controllerDestinationSettings,
|
|
217
|
+
},
|
|
218
218
|
};
|
|
219
219
|
});
|
|
220
220
|
return channels;
|
|
@@ -266,28 +266,36 @@ class Midy {
|
|
|
266
266
|
async scheduleTimelineEvents(t, offset, queueIndex) {
|
|
267
267
|
while (queueIndex < this.timeline.length) {
|
|
268
268
|
const event = this.timeline[queueIndex];
|
|
269
|
-
|
|
270
|
-
if (time > t + this.lookAhead)
|
|
269
|
+
if (event.startTime > t + this.lookAhead)
|
|
271
270
|
break;
|
|
272
271
|
switch (event.type) {
|
|
273
|
-
case "controller":
|
|
274
|
-
this.handleControlChange(this.omni ? 0 : event.channel, event.controllerType, event.value);
|
|
275
|
-
break;
|
|
276
272
|
case "noteOn":
|
|
277
|
-
|
|
278
|
-
|
|
273
|
+
if (event.velocity !== 0) {
|
|
274
|
+
await this.scheduleNoteOn(event.channel, event.noteNumber, event.velocity, event.startTime + this.startDelay - offset);
|
|
275
|
+
break;
|
|
276
|
+
}
|
|
277
|
+
/* falls through */
|
|
279
278
|
case "noteOff": {
|
|
280
|
-
const notePromise = this.scheduleNoteRelease(this.omni ? 0 : event.channel, event.noteNumber, event.velocity,
|
|
279
|
+
const notePromise = this.scheduleNoteRelease(this.omni ? 0 : event.channel, event.noteNumber, event.velocity, event.startTime + this.startDelay - offset);
|
|
281
280
|
if (notePromise) {
|
|
282
281
|
this.notePromises.push(notePromise);
|
|
283
282
|
}
|
|
284
283
|
break;
|
|
285
284
|
}
|
|
285
|
+
case "noteAftertouch":
|
|
286
|
+
this.handlePolyphonicKeyPressure(event.channel, event.noteNumber, event.amount);
|
|
287
|
+
break;
|
|
288
|
+
case "controller":
|
|
289
|
+
this.handleControlChange(this.omni ? 0 : event.channel, event.controllerType, event.value);
|
|
290
|
+
break;
|
|
286
291
|
case "programChange":
|
|
287
292
|
this.handleProgramChange(event.channel, event.programNumber);
|
|
288
293
|
break;
|
|
289
|
-
case "
|
|
290
|
-
this.
|
|
294
|
+
case "channelAftertouch":
|
|
295
|
+
this.handleChannelPressure(event.channel, event.amount);
|
|
296
|
+
break;
|
|
297
|
+
case "pitchBend":
|
|
298
|
+
this.handlePitchBend(event.channel, event.value);
|
|
291
299
|
break;
|
|
292
300
|
case "sysEx":
|
|
293
301
|
this.handleSysEx(event.data);
|
|
@@ -297,9 +305,8 @@ class Midy {
|
|
|
297
305
|
return queueIndex;
|
|
298
306
|
}
|
|
299
307
|
getQueueIndex(second) {
|
|
300
|
-
const ticks = this.secondToTicks(second, this.secondsPerBeat);
|
|
301
308
|
for (let i = 0; i < this.timeline.length; i++) {
|
|
302
|
-
if (
|
|
309
|
+
if (second <= this.timeline[i].startTime) {
|
|
303
310
|
return i;
|
|
304
311
|
}
|
|
305
312
|
}
|
|
@@ -441,18 +448,28 @@ class Midy {
|
|
|
441
448
|
timeline.push(event);
|
|
442
449
|
});
|
|
443
450
|
});
|
|
451
|
+
const priority = {
|
|
452
|
+
setTempo: 0,
|
|
453
|
+
controller: 1,
|
|
454
|
+
};
|
|
444
455
|
timeline.sort((a, b) => {
|
|
445
|
-
if (a.ticks !== b.ticks)
|
|
456
|
+
if (a.ticks !== b.ticks)
|
|
446
457
|
return a.ticks - b.ticks;
|
|
447
|
-
|
|
448
|
-
if (a.type !== "controller" && b.type === "controller") {
|
|
449
|
-
return -1;
|
|
450
|
-
}
|
|
451
|
-
if (a.type === "controller" && b.type !== "controller") {
|
|
452
|
-
return 1;
|
|
453
|
-
}
|
|
454
|
-
return 0;
|
|
458
|
+
return (priority[a.type] || 2) - (priority[b.type] || 2);
|
|
455
459
|
});
|
|
460
|
+
let prevTempoTime = 0;
|
|
461
|
+
let prevTempoTicks = 0;
|
|
462
|
+
let secondsPerBeat = 0.5;
|
|
463
|
+
for (let i = 0; i < timeline.length; i++) {
|
|
464
|
+
const event = timeline[i];
|
|
465
|
+
const timeFromPrevTempo = this.ticksToSecond(event.ticks - prevTempoTicks, secondsPerBeat);
|
|
466
|
+
event.startTime = prevTempoTime + timeFromPrevTempo;
|
|
467
|
+
if (event.type === "setTempo") {
|
|
468
|
+
prevTempoTime += this.ticksToSecond(event.ticks - prevTempoTicks, secondsPerBeat);
|
|
469
|
+
secondsPerBeat = event.microsecondsPerBeat / 1000000;
|
|
470
|
+
prevTempoTicks = event.ticks;
|
|
471
|
+
}
|
|
472
|
+
}
|
|
456
473
|
return { instruments, timeline };
|
|
457
474
|
}
|
|
458
475
|
stopNotes() {
|
|
@@ -504,32 +521,12 @@ class Midy {
|
|
|
504
521
|
}
|
|
505
522
|
}
|
|
506
523
|
calcTotalTime() {
|
|
507
|
-
const endOfTracks = [];
|
|
508
|
-
let prevTicks = 0;
|
|
509
524
|
let totalTime = 0;
|
|
510
|
-
let secondsPerBeat = 0.5;
|
|
511
525
|
for (let i = 0; i < this.timeline.length; i++) {
|
|
512
526
|
const event = this.timeline[i];
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
const durationTicks = event.ticks - prevTicks;
|
|
516
|
-
totalTime += this.ticksToSecond(durationTicks, secondsPerBeat);
|
|
517
|
-
secondsPerBeat = event.microsecondsPerBeat / 1000000;
|
|
518
|
-
prevTicks = event.ticks;
|
|
519
|
-
break;
|
|
520
|
-
}
|
|
521
|
-
case "endOfTrack":
|
|
522
|
-
endOfTracks.push(event);
|
|
523
|
-
}
|
|
527
|
+
if (totalTime < event.startTime)
|
|
528
|
+
totalTime = event.startTime;
|
|
524
529
|
}
|
|
525
|
-
let maxTicks = 0;
|
|
526
|
-
for (let i = 0; i < endOfTracks.length; i++) {
|
|
527
|
-
const event = endOfTracks[i];
|
|
528
|
-
if (maxTicks < event.ticks)
|
|
529
|
-
maxTicks = event.ticks;
|
|
530
|
-
}
|
|
531
|
-
const durationTicks = maxTicks - prevTicks;
|
|
532
|
-
totalTime += this.ticksToSecond(durationTicks, secondsPerBeat);
|
|
533
530
|
return totalTime;
|
|
534
531
|
}
|
|
535
532
|
currentTime() {
|
|
@@ -557,11 +554,8 @@ class Midy {
|
|
|
557
554
|
const lfo = new OscillatorNode(audioContext, {
|
|
558
555
|
frequency: 5,
|
|
559
556
|
});
|
|
560
|
-
const lfoGain = new GainNode(audioContext);
|
|
561
|
-
lfo.connect(lfoGain);
|
|
562
557
|
return {
|
|
563
558
|
lfo,
|
|
564
|
-
lfoGain,
|
|
565
559
|
};
|
|
566
560
|
}
|
|
567
561
|
createReverbEffect(audioContext, options = {}) {
|
|
@@ -666,15 +660,19 @@ class Midy {
|
|
|
666
660
|
centToHz(cent) {
|
|
667
661
|
return 8.176 * Math.pow(2, cent / 1200);
|
|
668
662
|
}
|
|
669
|
-
|
|
663
|
+
calcSemitoneOffset(channel) {
|
|
670
664
|
const masterTuning = this.masterCoarseTuning + this.masterFineTuning;
|
|
671
665
|
const channelTuning = channel.coarseTuning + channel.fineTuning;
|
|
672
666
|
const tuning = masterTuning + channelTuning;
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
667
|
+
return channel.pitchBend * channel.pitchBendRange + tuning;
|
|
668
|
+
}
|
|
669
|
+
calcPlaybackRate(noteInfo, noteNumber, semitoneOffset) {
|
|
670
|
+
return noteInfo.playbackRate(noteNumber) * Math.pow(2, semitoneOffset / 12);
|
|
671
|
+
}
|
|
672
|
+
async createNoteAudioChain(channel, noteInfo, noteNumber, velocity, startTime, isSF3) {
|
|
676
673
|
const bufferSource = await this.createNoteBufferNode(noteInfo, isSF3);
|
|
677
|
-
|
|
674
|
+
const semitoneOffset = this.calcSemitoneOffset(channel);
|
|
675
|
+
bufferSource.playbackRate.value = this.calcPlaybackRate(noteInfo, noteNumber, semitoneOffset);
|
|
678
676
|
// volume envelope
|
|
679
677
|
const gainNode = new GainNode(this.audioContext, {
|
|
680
678
|
gain: 0,
|
|
@@ -693,12 +691,6 @@ class Midy {
|
|
|
693
691
|
.exponentialRampToValueAtTime(attackVolume, volAttack)
|
|
694
692
|
.setValueAtTime(attackVolume, volHold)
|
|
695
693
|
.linearRampToValueAtTime(sustainVolume, volDecay);
|
|
696
|
-
if (channel.modulation > 0) {
|
|
697
|
-
const lfoGain = channel.modulationEffect.lfoGain;
|
|
698
|
-
lfoGain.connect(bufferSource.detune);
|
|
699
|
-
lfoGain.gain.cancelScheduledValues(startTime + channel.vibratoDelay);
|
|
700
|
-
lfoGain.gain.setValueAtTime(channel.modulation, startTime + channel.vibratoDelay);
|
|
701
|
-
}
|
|
702
694
|
// filter envelope
|
|
703
695
|
const softPedalFactor = 1 -
|
|
704
696
|
(0.1 + (noteNumber / 127) * 0.2) * channel.softPedal;
|
|
@@ -724,6 +716,19 @@ class Midy {
|
|
|
724
716
|
.exponentialRampToValueAtTime(adjustedPeekFreq, modAttack)
|
|
725
717
|
.setValueAtTime(adjustedPeekFreq, modHold)
|
|
726
718
|
.linearRampToValueAtTime(adjustedSustainFreq, modDecay);
|
|
719
|
+
let lfoGain;
|
|
720
|
+
if (channel.modulation > 0) {
|
|
721
|
+
const vibratoDelay = startTime + channel.vibratoDelay;
|
|
722
|
+
const vibratoAttack = vibratoDelay + 0.1;
|
|
723
|
+
lfoGain = new GainNode(this.audioContext, {
|
|
724
|
+
gain: 0,
|
|
725
|
+
});
|
|
726
|
+
lfoGain.gain
|
|
727
|
+
.setValueAtTime(1e-6, vibratoDelay) // exponentialRampToValueAtTime() requires a non-zero value
|
|
728
|
+
.exponentialRampToValueAtTime(channel.modulation, vibratoAttack);
|
|
729
|
+
channel.modulationEffect.lfo.connect(lfoGain);
|
|
730
|
+
lfoGain.connect(bufferSource.detune);
|
|
731
|
+
}
|
|
727
732
|
bufferSource.connect(filterNode);
|
|
728
733
|
filterNode.connect(gainNode);
|
|
729
734
|
if (this.mono && channel.currentBufferSource) {
|
|
@@ -731,7 +736,7 @@ class Midy {
|
|
|
731
736
|
channel.currentBufferSource = bufferSource;
|
|
732
737
|
}
|
|
733
738
|
bufferSource.start(startTime, noteInfo.start / noteInfo.sampleRate);
|
|
734
|
-
return { bufferSource, gainNode, filterNode };
|
|
739
|
+
return { bufferSource, gainNode, filterNode, lfoGain };
|
|
735
740
|
}
|
|
736
741
|
calcBank(channel, channelNumber) {
|
|
737
742
|
if (channel.bankMSB === 121) {
|
|
@@ -753,7 +758,7 @@ class Midy {
|
|
|
753
758
|
const noteInfo = soundFont.getInstrumentKey(bankNumber, channel.program, noteNumber);
|
|
754
759
|
if (!noteInfo)
|
|
755
760
|
return;
|
|
756
|
-
const { bufferSource, gainNode, filterNode } = await this
|
|
761
|
+
const { bufferSource, gainNode, filterNode, lfoGain } = await this
|
|
757
762
|
.createNoteAudioChain(channel, noteInfo, noteNumber, velocity, startTime, isSF3);
|
|
758
763
|
this.connectNoteEffects(channel, gainNode);
|
|
759
764
|
if (channel.sostenutoPedal) {
|
|
@@ -767,11 +772,12 @@ class Midy {
|
|
|
767
772
|
}
|
|
768
773
|
const scheduledNotes = channel.scheduledNotes;
|
|
769
774
|
const scheduledNote = {
|
|
770
|
-
gainNode,
|
|
771
|
-
filterNode,
|
|
772
775
|
bufferSource,
|
|
773
|
-
|
|
776
|
+
filterNode,
|
|
777
|
+
gainNode,
|
|
778
|
+
lfoGain,
|
|
774
779
|
noteInfo,
|
|
780
|
+
noteNumber,
|
|
775
781
|
startTime,
|
|
776
782
|
};
|
|
777
783
|
if (scheduledNotes.has(noteNumber)) {
|
|
@@ -800,7 +806,7 @@ class Midy {
|
|
|
800
806
|
continue;
|
|
801
807
|
if (targetNote.ending)
|
|
802
808
|
continue;
|
|
803
|
-
const { bufferSource, filterNode, gainNode, noteInfo } = targetNote;
|
|
809
|
+
const { bufferSource, filterNode, gainNode, lfoGain, noteInfo } = targetNote;
|
|
804
810
|
const velocityRate = (velocity + 127) / 127;
|
|
805
811
|
const volEndTime = stopTime + noteInfo.volRelease * velocityRate;
|
|
806
812
|
gainNode.gain.cancelScheduledValues(stopTime);
|
|
@@ -822,6 +828,8 @@ class Midy {
|
|
|
822
828
|
bufferSource.disconnect(0);
|
|
823
829
|
filterNode.disconnect(0);
|
|
824
830
|
gainNode.disconnect(0);
|
|
831
|
+
if (lfoGain)
|
|
832
|
+
lfoGain.disconnect(0);
|
|
825
833
|
resolve();
|
|
826
834
|
};
|
|
827
835
|
bufferSource.stop(volEndTime);
|
|
@@ -832,41 +840,34 @@ class Midy {
|
|
|
832
840
|
const now = this.audioContext.currentTime;
|
|
833
841
|
return this.scheduleNoteRelease(channelNumber, noteNumber, velocity, now);
|
|
834
842
|
}
|
|
835
|
-
releaseSustainPedal(channelNumber) {
|
|
836
|
-
const
|
|
843
|
+
releaseSustainPedal(channelNumber, halfVelocity) {
|
|
844
|
+
const velocity = halfVelocity * 2;
|
|
837
845
|
const channel = this.channels[channelNumber];
|
|
846
|
+
const promises = [];
|
|
838
847
|
channel.sustainPedal = false;
|
|
839
848
|
channel.scheduledNotes.forEach((scheduledNotes) => {
|
|
840
849
|
scheduledNotes.forEach((scheduledNote) => {
|
|
841
850
|
if (scheduledNote) {
|
|
842
|
-
const {
|
|
843
|
-
const
|
|
844
|
-
|
|
845
|
-
gainNode.gain.linearRampToValueAtTime(0, volEndTime);
|
|
846
|
-
const maxFreq = this.audioContext.sampleRate / 2;
|
|
847
|
-
const baseFreq = this.centToHz(noteInfo.initialFilterFc);
|
|
848
|
-
const adjustedBaseFreq = Math.min(maxFreq, baseFreq);
|
|
849
|
-
const modEndTime = now + noteInfo.modRelease;
|
|
850
|
-
filterNode.frequency
|
|
851
|
-
.cancelScheduledValues(stopTime)
|
|
852
|
-
.linearRampToValueAtTime(adjustedBaseFreq, modEndTime);
|
|
853
|
-
bufferSource.stop(volEndTime);
|
|
851
|
+
const { noteNumber } = scheduledNote;
|
|
852
|
+
const promise = this.releaseNote(channelNumber, noteNumber, velocity);
|
|
853
|
+
promises.push(promise);
|
|
854
854
|
}
|
|
855
855
|
});
|
|
856
856
|
});
|
|
857
|
+
return promises;
|
|
857
858
|
}
|
|
858
|
-
|
|
859
|
-
const
|
|
859
|
+
releaseSostenutoPedal(channelNumber, halfVelocity) {
|
|
860
|
+
const velocity = halfVelocity * 2;
|
|
860
861
|
const channel = this.channels[channelNumber];
|
|
862
|
+
const promises = [];
|
|
861
863
|
channel.sostenutoPedal = false;
|
|
862
864
|
channel.sostenutoNotes.forEach((activeNote) => {
|
|
863
|
-
const {
|
|
864
|
-
const
|
|
865
|
-
|
|
866
|
-
gainNode.gain.linearRampToValueAtTime(0, now + fadeTime);
|
|
867
|
-
bufferSource.stop(now + fadeTime);
|
|
865
|
+
const { noteNumber } = activeNote;
|
|
866
|
+
const promise = this.releaseNote(channelNumber, noteNumber, velocity);
|
|
867
|
+
promises.push(promise);
|
|
868
868
|
});
|
|
869
869
|
channel.sostenutoNotes.clear();
|
|
870
|
+
return promises;
|
|
870
871
|
}
|
|
871
872
|
handleMIDIMessage(statusByte, data1, data2) {
|
|
872
873
|
const channelNumber = omni ? 0 : statusByte & 0x0F;
|
|
@@ -877,7 +878,7 @@ class Midy {
|
|
|
877
878
|
case 0x90:
|
|
878
879
|
return this.noteOn(channelNumber, data1, data2);
|
|
879
880
|
case 0xA0:
|
|
880
|
-
return this.handlePolyphonicKeyPressure(channelNumber, data1, data2);
|
|
881
|
+
return; // this.handlePolyphonicKeyPressure(channelNumber, data1, data2);
|
|
881
882
|
case 0xB0:
|
|
882
883
|
return this.handleControlChange(channelNumber, data1, data2);
|
|
883
884
|
case 0xC0:
|
|
@@ -885,7 +886,7 @@ class Midy {
|
|
|
885
886
|
case 0xD0:
|
|
886
887
|
return this.handleChannelPressure(channelNumber, data1);
|
|
887
888
|
case 0xE0:
|
|
888
|
-
return this.
|
|
889
|
+
return this.handlePitchBendMessage(channelNumber, data1, data2);
|
|
889
890
|
default:
|
|
890
891
|
console.warn(`Unsupported MIDI message: ${messageType.toString(16)}`);
|
|
891
892
|
}
|
|
@@ -893,17 +894,16 @@ class Midy {
|
|
|
893
894
|
handlePolyphonicKeyPressure(channelNumber, noteNumber, pressure) {
|
|
894
895
|
const now = this.audioContext.currentTime;
|
|
895
896
|
const channel = this.channels[channelNumber];
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
if (
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
});
|
|
897
|
+
pressure /= 64;
|
|
898
|
+
const activeNotes = this.getActiveNotes(channel);
|
|
899
|
+
if (channel.polyphonicKeyPressure.amplitudeControl !== 1) {
|
|
900
|
+
if (activeNotes.has(noteNumber)) {
|
|
901
|
+
const activeNote = activeNotes.get(noteNumber);
|
|
902
|
+
const gain = activeNote.gainNode.gain.value;
|
|
903
|
+
activeNote.gainNode.gain
|
|
904
|
+
.cancelScheduledValues(now)
|
|
905
|
+
.setValueAtTime(gain * pressure, now);
|
|
906
|
+
}
|
|
907
907
|
}
|
|
908
908
|
}
|
|
909
909
|
handleProgramChange(channelNumber, program) {
|
|
@@ -912,11 +912,37 @@ class Midy {
|
|
|
912
912
|
channel.program = program;
|
|
913
913
|
}
|
|
914
914
|
handleChannelPressure(channelNumber, pressure) {
|
|
915
|
-
this.
|
|
915
|
+
const now = this.audioContext.currentTime;
|
|
916
|
+
const channel = this.channels[channelNumber];
|
|
917
|
+
pressure /= 64;
|
|
918
|
+
channel.channelPressure = pressure;
|
|
919
|
+
const activeNotes = this.getActiveNotes(channel);
|
|
920
|
+
if (channel.channelPressure.amplitudeControl !== 1) {
|
|
921
|
+
activeNotes.forEach((activeNote) => {
|
|
922
|
+
const gain = activeNote.gainNode.gain.value;
|
|
923
|
+
activeNote.gainNode.gain
|
|
924
|
+
.cancelScheduledValues(now)
|
|
925
|
+
.setValueAtTime(gain * pressure, now);
|
|
926
|
+
});
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
handlePitchBendMessage(channelNumber, lsb, msb) {
|
|
930
|
+
const pitchBend = msb * 128 + lsb;
|
|
931
|
+
this.handlePitchBend(channelNumber, pitchBend);
|
|
916
932
|
}
|
|
917
|
-
handlePitchBend(channelNumber,
|
|
918
|
-
const
|
|
919
|
-
this.channels[channelNumber]
|
|
933
|
+
handlePitchBend(channelNumber, pitchBend) {
|
|
934
|
+
const now = this.audioContext.currentTime;
|
|
935
|
+
const channel = this.channels[channelNumber];
|
|
936
|
+
channel.pitchBend = (pitchBend - 8192) / 8192;
|
|
937
|
+
const semitoneOffset = this.calcSemitoneOffset(channel);
|
|
938
|
+
const activeNotes = this.getActiveNotes(channel);
|
|
939
|
+
activeNotes.forEach((activeNote) => {
|
|
940
|
+
const { bufferSource, noteInfo, noteNumber } = activeNote;
|
|
941
|
+
const playbackRate = calcPlaybackRate(noteInfo, noteNumber, semitoneOffset);
|
|
942
|
+
bufferSource.playbackRate
|
|
943
|
+
.cancelScheduledValues(now)
|
|
944
|
+
.setValueAtTime(playbackRate * pressure, now);
|
|
945
|
+
});
|
|
920
946
|
}
|
|
921
947
|
handleControlChange(channelNumber, controller, value) {
|
|
922
948
|
switch (controller) {
|
|
@@ -987,13 +1013,9 @@ class Midy {
|
|
|
987
1013
|
this.channels[channelNumber].bankMSB = msb;
|
|
988
1014
|
}
|
|
989
1015
|
setModulation(channelNumber, modulation) {
|
|
990
|
-
const now = this.audioContext.currentTime;
|
|
991
1016
|
const channel = this.channels[channelNumber];
|
|
992
|
-
channel.modulation = (modulation
|
|
993
|
-
channel.modulationDepthRange;
|
|
994
|
-
const lfoGain = channel.modulationEffect.lfoGain;
|
|
995
|
-
lfoGain.gain.cancelScheduledValues(now);
|
|
996
|
-
lfoGain.gain.setValueAtTime(channel.modulation, now);
|
|
1017
|
+
channel.modulation = (modulation / 127) *
|
|
1018
|
+
(channel.modulationDepthRange * 100);
|
|
997
1019
|
}
|
|
998
1020
|
setPortamentoTime(channelNumber, portamentoTime) {
|
|
999
1021
|
this.channels[channelNumber].portamentoTime = portamentoTime / 127;
|
|
@@ -1028,7 +1050,7 @@ class Midy {
|
|
|
1028
1050
|
const isOn = value >= 64;
|
|
1029
1051
|
this.channels[channelNumber].sustainPedal = isOn;
|
|
1030
1052
|
if (!isOn) {
|
|
1031
|
-
this.releaseSustainPedal(channelNumber);
|
|
1053
|
+
this.releaseSustainPedal(channelNumber, value);
|
|
1032
1054
|
}
|
|
1033
1055
|
}
|
|
1034
1056
|
setPortamento(channelNumber, value) {
|
|
@@ -1057,25 +1079,29 @@ class Midy {
|
|
|
1057
1079
|
const activeNotes = this.getActiveNotes(channel);
|
|
1058
1080
|
channel.sostenutoNotes = new Map(activeNotes);
|
|
1059
1081
|
}
|
|
1082
|
+
else {
|
|
1083
|
+
this.releaseSostenutoPedal(channelNumber, value);
|
|
1084
|
+
}
|
|
1060
1085
|
}
|
|
1061
1086
|
setSoftPedal(channelNumber, softPedal) {
|
|
1062
1087
|
const channel = this.channels[channelNumber];
|
|
1063
1088
|
channel.softPedal = softPedal / 127;
|
|
1064
|
-
this.updateChannelGain(channel);
|
|
1065
1089
|
}
|
|
1066
1090
|
setVibratoRate(channelNumber, vibratoRate) {
|
|
1067
1091
|
const now = this.audioContext.currentTime;
|
|
1068
1092
|
const channel = this.channels[channelNumber];
|
|
1069
1093
|
channel.vibratoRate = vibratoRate / 127 * 4 + 3; // 3-7Hz
|
|
1070
|
-
channel.modulationEffect.lfo.frequency
|
|
1071
|
-
|
|
1094
|
+
channel.modulationEffect.lfo.frequency
|
|
1095
|
+
.cancelScheduledValues(now)
|
|
1096
|
+
.setValueAtTime(channel.vibratoRate, now);
|
|
1072
1097
|
}
|
|
1073
1098
|
setVibratoDepth(channelNumber, vibratoDepth) {
|
|
1074
1099
|
const now = this.audioContext.currentTime;
|
|
1075
1100
|
const channel = this.channels[channelNumber];
|
|
1076
1101
|
channel.vibratoDepth = vibratoDepth / 127;
|
|
1077
|
-
channel.modulationEffect.lfoGain.gain
|
|
1078
|
-
|
|
1102
|
+
channel.modulationEffect.lfoGain.gain
|
|
1103
|
+
.cancelScheduledValues(now)
|
|
1104
|
+
.setValueAtTime(channel.vibratoDepth, now);
|
|
1079
1105
|
}
|
|
1080
1106
|
setVibratoDelay(channelNumber, vibratoDelay) {
|
|
1081
1107
|
// Access Virus: 0-10sec
|
|
@@ -1261,10 +1287,10 @@ class Midy {
|
|
|
1261
1287
|
switch (data[3]) {
|
|
1262
1288
|
// case 1:
|
|
1263
1289
|
// // TODO
|
|
1264
|
-
// return this.
|
|
1290
|
+
// return this.setChannelPressure();
|
|
1265
1291
|
// case 3:
|
|
1266
1292
|
// // TODO
|
|
1267
|
-
// return this.
|
|
1293
|
+
// return this.setControlChange();
|
|
1268
1294
|
default:
|
|
1269
1295
|
console.warn(`Unsupported Exclusive Message ${data}`);
|
|
1270
1296
|
}
|
|
@@ -1283,20 +1309,25 @@ class Midy {
|
|
|
1283
1309
|
}
|
|
1284
1310
|
}
|
|
1285
1311
|
handleMasterVolumeSysEx(data) {
|
|
1286
|
-
const volume = (data[5] * 128 + data[4]
|
|
1312
|
+
const volume = (data[5] * 128 + data[4]) / 16383;
|
|
1287
1313
|
this.handleMasterVolume(volume);
|
|
1288
1314
|
}
|
|
1289
1315
|
handleMasterVolume(volume) {
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1316
|
+
if (volume < 0 && 1 < volume) {
|
|
1317
|
+
console.error("Master Volume is out of range");
|
|
1318
|
+
}
|
|
1319
|
+
else {
|
|
1320
|
+
const now = this.audioContext.currentTime;
|
|
1321
|
+
this.masterGain.gain.cancelScheduledValues(now);
|
|
1322
|
+
this.masterGain.gain.setValueAtTime(volume * volume, now);
|
|
1323
|
+
}
|
|
1293
1324
|
}
|
|
1294
1325
|
handleMasterFineTuningSysEx(data) {
|
|
1295
1326
|
const fineTuning = (data[5] * 128 + data[4] - 8192) / 8192;
|
|
1296
1327
|
this.handleMasterFineTuning(fineTuning);
|
|
1297
1328
|
}
|
|
1298
1329
|
handleMasterFineTuning(fineTuning) {
|
|
1299
|
-
if (fineTuning <
|
|
1330
|
+
if (fineTuning < -1 && 1 < fineTuning) {
|
|
1300
1331
|
console.error("Master Fine Tuning value is out of range");
|
|
1301
1332
|
}
|
|
1302
1333
|
else {
|
|
@@ -1347,7 +1378,7 @@ Object.defineProperty(Midy, "channelSettings", {
|
|
|
1347
1378
|
writable: true,
|
|
1348
1379
|
value: {
|
|
1349
1380
|
currentBufferSource: null,
|
|
1350
|
-
volume:
|
|
1381
|
+
volume: 100 / 127,
|
|
1351
1382
|
pan: 0,
|
|
1352
1383
|
portamentoTime: 0,
|
|
1353
1384
|
reverb: 0,
|
|
@@ -1364,7 +1395,7 @@ Object.defineProperty(Midy, "channelSettings", {
|
|
|
1364
1395
|
pitchBend: 0,
|
|
1365
1396
|
fineTuning: 0,
|
|
1366
1397
|
coarseTuning: 0,
|
|
1367
|
-
modulationDepthRange:
|
|
1398
|
+
modulationDepthRange: 0.5,
|
|
1368
1399
|
}
|
|
1369
1400
|
});
|
|
1370
1401
|
Object.defineProperty(Midy, "effectSettings", {
|
|
@@ -1384,3 +1415,16 @@ Object.defineProperty(Midy, "effectSettings", {
|
|
|
1384
1415
|
pitchBendRange: 2,
|
|
1385
1416
|
}
|
|
1386
1417
|
});
|
|
1418
|
+
Object.defineProperty(Midy, "controllerDestinationSettings", {
|
|
1419
|
+
enumerable: true,
|
|
1420
|
+
configurable: true,
|
|
1421
|
+
writable: true,
|
|
1422
|
+
value: {
|
|
1423
|
+
pitchControl: 0,
|
|
1424
|
+
filterCutoffControl: 0,
|
|
1425
|
+
amplitudeControl: 1,
|
|
1426
|
+
lfoPitchDepth: 0,
|
|
1427
|
+
lfoFilterDepth: 0,
|
|
1428
|
+
lfoAmplitudeDepth: 0,
|
|
1429
|
+
}
|
|
1430
|
+
});
|