@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/esm/midy.js
CHANGED
|
@@ -8,12 +8,6 @@ export class Midy {
|
|
|
8
8
|
writable: true,
|
|
9
9
|
value: 120
|
|
10
10
|
});
|
|
11
|
-
Object.defineProperty(this, "secondsPerBeat", {
|
|
12
|
-
enumerable: true,
|
|
13
|
-
configurable: true,
|
|
14
|
-
writable: true,
|
|
15
|
-
value: 0.5
|
|
16
|
-
});
|
|
17
11
|
Object.defineProperty(this, "totalTime", {
|
|
18
12
|
enumerable: true,
|
|
19
13
|
configurable: true,
|
|
@@ -174,10 +168,10 @@ export class Midy {
|
|
|
174
168
|
const response = await fetch(midiUrl);
|
|
175
169
|
const arrayBuffer = await response.arrayBuffer();
|
|
176
170
|
const midi = parseMidi(new Uint8Array(arrayBuffer));
|
|
171
|
+
this.ticksPerBeat = midi.header.ticksPerBeat;
|
|
177
172
|
const midiData = this.extractMidiData(midi);
|
|
178
173
|
this.instruments = midiData.instruments;
|
|
179
174
|
this.timeline = midiData.timeline;
|
|
180
|
-
this.ticksPerBeat = midi.header.ticksPerBeat;
|
|
181
175
|
this.totalTime = this.calcTotalTime();
|
|
182
176
|
}
|
|
183
177
|
setChannelAudioNodes(audioContext) {
|
|
@@ -212,6 +206,12 @@ export class Midy {
|
|
|
212
206
|
...this.setChannelAudioNodes(audioContext),
|
|
213
207
|
scheduledNotes: new Map(),
|
|
214
208
|
sostenutoNotes: new Map(),
|
|
209
|
+
polyphonicKeyPressure: {
|
|
210
|
+
...Midy.controllerDestinationSettings,
|
|
211
|
+
},
|
|
212
|
+
channelPressure: {
|
|
213
|
+
...Midy.controllerDestinationSettings,
|
|
214
|
+
},
|
|
215
215
|
};
|
|
216
216
|
});
|
|
217
217
|
return channels;
|
|
@@ -263,28 +263,36 @@ export class Midy {
|
|
|
263
263
|
async scheduleTimelineEvents(t, offset, queueIndex) {
|
|
264
264
|
while (queueIndex < this.timeline.length) {
|
|
265
265
|
const event = this.timeline[queueIndex];
|
|
266
|
-
|
|
267
|
-
if (time > t + this.lookAhead)
|
|
266
|
+
if (event.startTime > t + this.lookAhead)
|
|
268
267
|
break;
|
|
269
268
|
switch (event.type) {
|
|
270
|
-
case "controller":
|
|
271
|
-
this.handleControlChange(this.omni ? 0 : event.channel, event.controllerType, event.value);
|
|
272
|
-
break;
|
|
273
269
|
case "noteOn":
|
|
274
|
-
|
|
275
|
-
|
|
270
|
+
if (event.velocity !== 0) {
|
|
271
|
+
await this.scheduleNoteOn(event.channel, event.noteNumber, event.velocity, event.startTime + this.startDelay - offset);
|
|
272
|
+
break;
|
|
273
|
+
}
|
|
274
|
+
/* falls through */
|
|
276
275
|
case "noteOff": {
|
|
277
|
-
const notePromise = this.scheduleNoteRelease(this.omni ? 0 : event.channel, event.noteNumber, event.velocity,
|
|
276
|
+
const notePromise = this.scheduleNoteRelease(this.omni ? 0 : event.channel, event.noteNumber, event.velocity, event.startTime + this.startDelay - offset);
|
|
278
277
|
if (notePromise) {
|
|
279
278
|
this.notePromises.push(notePromise);
|
|
280
279
|
}
|
|
281
280
|
break;
|
|
282
281
|
}
|
|
282
|
+
case "noteAftertouch":
|
|
283
|
+
this.handlePolyphonicKeyPressure(event.channel, event.noteNumber, event.amount);
|
|
284
|
+
break;
|
|
285
|
+
case "controller":
|
|
286
|
+
this.handleControlChange(this.omni ? 0 : event.channel, event.controllerType, event.value);
|
|
287
|
+
break;
|
|
283
288
|
case "programChange":
|
|
284
289
|
this.handleProgramChange(event.channel, event.programNumber);
|
|
285
290
|
break;
|
|
286
|
-
case "
|
|
287
|
-
this.
|
|
291
|
+
case "channelAftertouch":
|
|
292
|
+
this.handleChannelPressure(event.channel, event.amount);
|
|
293
|
+
break;
|
|
294
|
+
case "pitchBend":
|
|
295
|
+
this.handlePitchBend(event.channel, event.value);
|
|
288
296
|
break;
|
|
289
297
|
case "sysEx":
|
|
290
298
|
this.handleSysEx(event.data);
|
|
@@ -294,9 +302,8 @@ export class Midy {
|
|
|
294
302
|
return queueIndex;
|
|
295
303
|
}
|
|
296
304
|
getQueueIndex(second) {
|
|
297
|
-
const ticks = this.secondToTicks(second, this.secondsPerBeat);
|
|
298
305
|
for (let i = 0; i < this.timeline.length; i++) {
|
|
299
|
-
if (
|
|
306
|
+
if (second <= this.timeline[i].startTime) {
|
|
300
307
|
return i;
|
|
301
308
|
}
|
|
302
309
|
}
|
|
@@ -438,18 +445,28 @@ export class Midy {
|
|
|
438
445
|
timeline.push(event);
|
|
439
446
|
});
|
|
440
447
|
});
|
|
448
|
+
const priority = {
|
|
449
|
+
setTempo: 0,
|
|
450
|
+
controller: 1,
|
|
451
|
+
};
|
|
441
452
|
timeline.sort((a, b) => {
|
|
442
|
-
if (a.ticks !== b.ticks)
|
|
453
|
+
if (a.ticks !== b.ticks)
|
|
443
454
|
return a.ticks - b.ticks;
|
|
444
|
-
|
|
445
|
-
if (a.type !== "controller" && b.type === "controller") {
|
|
446
|
-
return -1;
|
|
447
|
-
}
|
|
448
|
-
if (a.type === "controller" && b.type !== "controller") {
|
|
449
|
-
return 1;
|
|
450
|
-
}
|
|
451
|
-
return 0;
|
|
455
|
+
return (priority[a.type] || 2) - (priority[b.type] || 2);
|
|
452
456
|
});
|
|
457
|
+
let prevTempoTime = 0;
|
|
458
|
+
let prevTempoTicks = 0;
|
|
459
|
+
let secondsPerBeat = 0.5;
|
|
460
|
+
for (let i = 0; i < timeline.length; i++) {
|
|
461
|
+
const event = timeline[i];
|
|
462
|
+
const timeFromPrevTempo = this.ticksToSecond(event.ticks - prevTempoTicks, secondsPerBeat);
|
|
463
|
+
event.startTime = prevTempoTime + timeFromPrevTempo;
|
|
464
|
+
if (event.type === "setTempo") {
|
|
465
|
+
prevTempoTime += this.ticksToSecond(event.ticks - prevTempoTicks, secondsPerBeat);
|
|
466
|
+
secondsPerBeat = event.microsecondsPerBeat / 1000000;
|
|
467
|
+
prevTempoTicks = event.ticks;
|
|
468
|
+
}
|
|
469
|
+
}
|
|
453
470
|
return { instruments, timeline };
|
|
454
471
|
}
|
|
455
472
|
stopNotes() {
|
|
@@ -501,32 +518,12 @@ export class Midy {
|
|
|
501
518
|
}
|
|
502
519
|
}
|
|
503
520
|
calcTotalTime() {
|
|
504
|
-
const endOfTracks = [];
|
|
505
|
-
let prevTicks = 0;
|
|
506
521
|
let totalTime = 0;
|
|
507
|
-
let secondsPerBeat = 0.5;
|
|
508
522
|
for (let i = 0; i < this.timeline.length; i++) {
|
|
509
523
|
const event = this.timeline[i];
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
const durationTicks = event.ticks - prevTicks;
|
|
513
|
-
totalTime += this.ticksToSecond(durationTicks, secondsPerBeat);
|
|
514
|
-
secondsPerBeat = event.microsecondsPerBeat / 1000000;
|
|
515
|
-
prevTicks = event.ticks;
|
|
516
|
-
break;
|
|
517
|
-
}
|
|
518
|
-
case "endOfTrack":
|
|
519
|
-
endOfTracks.push(event);
|
|
520
|
-
}
|
|
524
|
+
if (totalTime < event.startTime)
|
|
525
|
+
totalTime = event.startTime;
|
|
521
526
|
}
|
|
522
|
-
let maxTicks = 0;
|
|
523
|
-
for (let i = 0; i < endOfTracks.length; i++) {
|
|
524
|
-
const event = endOfTracks[i];
|
|
525
|
-
if (maxTicks < event.ticks)
|
|
526
|
-
maxTicks = event.ticks;
|
|
527
|
-
}
|
|
528
|
-
const durationTicks = maxTicks - prevTicks;
|
|
529
|
-
totalTime += this.ticksToSecond(durationTicks, secondsPerBeat);
|
|
530
527
|
return totalTime;
|
|
531
528
|
}
|
|
532
529
|
currentTime() {
|
|
@@ -554,11 +551,8 @@ export class Midy {
|
|
|
554
551
|
const lfo = new OscillatorNode(audioContext, {
|
|
555
552
|
frequency: 5,
|
|
556
553
|
});
|
|
557
|
-
const lfoGain = new GainNode(audioContext);
|
|
558
|
-
lfo.connect(lfoGain);
|
|
559
554
|
return {
|
|
560
555
|
lfo,
|
|
561
|
-
lfoGain,
|
|
562
556
|
};
|
|
563
557
|
}
|
|
564
558
|
createReverbEffect(audioContext, options = {}) {
|
|
@@ -663,15 +657,19 @@ export class Midy {
|
|
|
663
657
|
centToHz(cent) {
|
|
664
658
|
return 8.176 * Math.pow(2, cent / 1200);
|
|
665
659
|
}
|
|
666
|
-
|
|
660
|
+
calcSemitoneOffset(channel) {
|
|
667
661
|
const masterTuning = this.masterCoarseTuning + this.masterFineTuning;
|
|
668
662
|
const channelTuning = channel.coarseTuning + channel.fineTuning;
|
|
669
663
|
const tuning = masterTuning + channelTuning;
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
664
|
+
return channel.pitchBend * channel.pitchBendRange + tuning;
|
|
665
|
+
}
|
|
666
|
+
calcPlaybackRate(noteInfo, noteNumber, semitoneOffset) {
|
|
667
|
+
return noteInfo.playbackRate(noteNumber) * Math.pow(2, semitoneOffset / 12);
|
|
668
|
+
}
|
|
669
|
+
async createNoteAudioChain(channel, noteInfo, noteNumber, velocity, startTime, isSF3) {
|
|
673
670
|
const bufferSource = await this.createNoteBufferNode(noteInfo, isSF3);
|
|
674
|
-
|
|
671
|
+
const semitoneOffset = this.calcSemitoneOffset(channel);
|
|
672
|
+
bufferSource.playbackRate.value = this.calcPlaybackRate(noteInfo, noteNumber, semitoneOffset);
|
|
675
673
|
// volume envelope
|
|
676
674
|
const gainNode = new GainNode(this.audioContext, {
|
|
677
675
|
gain: 0,
|
|
@@ -690,12 +688,6 @@ export class Midy {
|
|
|
690
688
|
.exponentialRampToValueAtTime(attackVolume, volAttack)
|
|
691
689
|
.setValueAtTime(attackVolume, volHold)
|
|
692
690
|
.linearRampToValueAtTime(sustainVolume, volDecay);
|
|
693
|
-
if (channel.modulation > 0) {
|
|
694
|
-
const lfoGain = channel.modulationEffect.lfoGain;
|
|
695
|
-
lfoGain.connect(bufferSource.detune);
|
|
696
|
-
lfoGain.gain.cancelScheduledValues(startTime + channel.vibratoDelay);
|
|
697
|
-
lfoGain.gain.setValueAtTime(channel.modulation, startTime + channel.vibratoDelay);
|
|
698
|
-
}
|
|
699
691
|
// filter envelope
|
|
700
692
|
const softPedalFactor = 1 -
|
|
701
693
|
(0.1 + (noteNumber / 127) * 0.2) * channel.softPedal;
|
|
@@ -721,6 +713,19 @@ export class Midy {
|
|
|
721
713
|
.exponentialRampToValueAtTime(adjustedPeekFreq, modAttack)
|
|
722
714
|
.setValueAtTime(adjustedPeekFreq, modHold)
|
|
723
715
|
.linearRampToValueAtTime(adjustedSustainFreq, modDecay);
|
|
716
|
+
let lfoGain;
|
|
717
|
+
if (channel.modulation > 0) {
|
|
718
|
+
const vibratoDelay = startTime + channel.vibratoDelay;
|
|
719
|
+
const vibratoAttack = vibratoDelay + 0.1;
|
|
720
|
+
lfoGain = new GainNode(this.audioContext, {
|
|
721
|
+
gain: 0,
|
|
722
|
+
});
|
|
723
|
+
lfoGain.gain
|
|
724
|
+
.setValueAtTime(1e-6, vibratoDelay) // exponentialRampToValueAtTime() requires a non-zero value
|
|
725
|
+
.exponentialRampToValueAtTime(channel.modulation, vibratoAttack);
|
|
726
|
+
channel.modulationEffect.lfo.connect(lfoGain);
|
|
727
|
+
lfoGain.connect(bufferSource.detune);
|
|
728
|
+
}
|
|
724
729
|
bufferSource.connect(filterNode);
|
|
725
730
|
filterNode.connect(gainNode);
|
|
726
731
|
if (this.mono && channel.currentBufferSource) {
|
|
@@ -728,7 +733,7 @@ export class Midy {
|
|
|
728
733
|
channel.currentBufferSource = bufferSource;
|
|
729
734
|
}
|
|
730
735
|
bufferSource.start(startTime, noteInfo.start / noteInfo.sampleRate);
|
|
731
|
-
return { bufferSource, gainNode, filterNode };
|
|
736
|
+
return { bufferSource, gainNode, filterNode, lfoGain };
|
|
732
737
|
}
|
|
733
738
|
calcBank(channel, channelNumber) {
|
|
734
739
|
if (channel.bankMSB === 121) {
|
|
@@ -750,7 +755,7 @@ export class Midy {
|
|
|
750
755
|
const noteInfo = soundFont.getInstrumentKey(bankNumber, channel.program, noteNumber);
|
|
751
756
|
if (!noteInfo)
|
|
752
757
|
return;
|
|
753
|
-
const { bufferSource, gainNode, filterNode } = await this
|
|
758
|
+
const { bufferSource, gainNode, filterNode, lfoGain } = await this
|
|
754
759
|
.createNoteAudioChain(channel, noteInfo, noteNumber, velocity, startTime, isSF3);
|
|
755
760
|
this.connectNoteEffects(channel, gainNode);
|
|
756
761
|
if (channel.sostenutoPedal) {
|
|
@@ -764,11 +769,12 @@ export class Midy {
|
|
|
764
769
|
}
|
|
765
770
|
const scheduledNotes = channel.scheduledNotes;
|
|
766
771
|
const scheduledNote = {
|
|
767
|
-
gainNode,
|
|
768
|
-
filterNode,
|
|
769
772
|
bufferSource,
|
|
770
|
-
|
|
773
|
+
filterNode,
|
|
774
|
+
gainNode,
|
|
775
|
+
lfoGain,
|
|
771
776
|
noteInfo,
|
|
777
|
+
noteNumber,
|
|
772
778
|
startTime,
|
|
773
779
|
};
|
|
774
780
|
if (scheduledNotes.has(noteNumber)) {
|
|
@@ -797,7 +803,7 @@ export class Midy {
|
|
|
797
803
|
continue;
|
|
798
804
|
if (targetNote.ending)
|
|
799
805
|
continue;
|
|
800
|
-
const { bufferSource, filterNode, gainNode, noteInfo } = targetNote;
|
|
806
|
+
const { bufferSource, filterNode, gainNode, lfoGain, noteInfo } = targetNote;
|
|
801
807
|
const velocityRate = (velocity + 127) / 127;
|
|
802
808
|
const volEndTime = stopTime + noteInfo.volRelease * velocityRate;
|
|
803
809
|
gainNode.gain.cancelScheduledValues(stopTime);
|
|
@@ -819,6 +825,8 @@ export class Midy {
|
|
|
819
825
|
bufferSource.disconnect(0);
|
|
820
826
|
filterNode.disconnect(0);
|
|
821
827
|
gainNode.disconnect(0);
|
|
828
|
+
if (lfoGain)
|
|
829
|
+
lfoGain.disconnect(0);
|
|
822
830
|
resolve();
|
|
823
831
|
};
|
|
824
832
|
bufferSource.stop(volEndTime);
|
|
@@ -829,41 +837,34 @@ export class Midy {
|
|
|
829
837
|
const now = this.audioContext.currentTime;
|
|
830
838
|
return this.scheduleNoteRelease(channelNumber, noteNumber, velocity, now);
|
|
831
839
|
}
|
|
832
|
-
releaseSustainPedal(channelNumber) {
|
|
833
|
-
const
|
|
840
|
+
releaseSustainPedal(channelNumber, halfVelocity) {
|
|
841
|
+
const velocity = halfVelocity * 2;
|
|
834
842
|
const channel = this.channels[channelNumber];
|
|
843
|
+
const promises = [];
|
|
835
844
|
channel.sustainPedal = false;
|
|
836
845
|
channel.scheduledNotes.forEach((scheduledNotes) => {
|
|
837
846
|
scheduledNotes.forEach((scheduledNote) => {
|
|
838
847
|
if (scheduledNote) {
|
|
839
|
-
const {
|
|
840
|
-
const
|
|
841
|
-
|
|
842
|
-
gainNode.gain.linearRampToValueAtTime(0, volEndTime);
|
|
843
|
-
const maxFreq = this.audioContext.sampleRate / 2;
|
|
844
|
-
const baseFreq = this.centToHz(noteInfo.initialFilterFc);
|
|
845
|
-
const adjustedBaseFreq = Math.min(maxFreq, baseFreq);
|
|
846
|
-
const modEndTime = now + noteInfo.modRelease;
|
|
847
|
-
filterNode.frequency
|
|
848
|
-
.cancelScheduledValues(stopTime)
|
|
849
|
-
.linearRampToValueAtTime(adjustedBaseFreq, modEndTime);
|
|
850
|
-
bufferSource.stop(volEndTime);
|
|
848
|
+
const { noteNumber } = scheduledNote;
|
|
849
|
+
const promise = this.releaseNote(channelNumber, noteNumber, velocity);
|
|
850
|
+
promises.push(promise);
|
|
851
851
|
}
|
|
852
852
|
});
|
|
853
853
|
});
|
|
854
|
+
return promises;
|
|
854
855
|
}
|
|
855
|
-
|
|
856
|
-
const
|
|
856
|
+
releaseSostenutoPedal(channelNumber, halfVelocity) {
|
|
857
|
+
const velocity = halfVelocity * 2;
|
|
857
858
|
const channel = this.channels[channelNumber];
|
|
859
|
+
const promises = [];
|
|
858
860
|
channel.sostenutoPedal = false;
|
|
859
861
|
channel.sostenutoNotes.forEach((activeNote) => {
|
|
860
|
-
const {
|
|
861
|
-
const
|
|
862
|
-
|
|
863
|
-
gainNode.gain.linearRampToValueAtTime(0, now + fadeTime);
|
|
864
|
-
bufferSource.stop(now + fadeTime);
|
|
862
|
+
const { noteNumber } = activeNote;
|
|
863
|
+
const promise = this.releaseNote(channelNumber, noteNumber, velocity);
|
|
864
|
+
promises.push(promise);
|
|
865
865
|
});
|
|
866
866
|
channel.sostenutoNotes.clear();
|
|
867
|
+
return promises;
|
|
867
868
|
}
|
|
868
869
|
handleMIDIMessage(statusByte, data1, data2) {
|
|
869
870
|
const channelNumber = omni ? 0 : statusByte & 0x0F;
|
|
@@ -874,7 +875,7 @@ export class Midy {
|
|
|
874
875
|
case 0x90:
|
|
875
876
|
return this.noteOn(channelNumber, data1, data2);
|
|
876
877
|
case 0xA0:
|
|
877
|
-
return this.handlePolyphonicKeyPressure(channelNumber, data1, data2);
|
|
878
|
+
return; // this.handlePolyphonicKeyPressure(channelNumber, data1, data2);
|
|
878
879
|
case 0xB0:
|
|
879
880
|
return this.handleControlChange(channelNumber, data1, data2);
|
|
880
881
|
case 0xC0:
|
|
@@ -882,7 +883,7 @@ export class Midy {
|
|
|
882
883
|
case 0xD0:
|
|
883
884
|
return this.handleChannelPressure(channelNumber, data1);
|
|
884
885
|
case 0xE0:
|
|
885
|
-
return this.
|
|
886
|
+
return this.handlePitchBendMessage(channelNumber, data1, data2);
|
|
886
887
|
default:
|
|
887
888
|
console.warn(`Unsupported MIDI message: ${messageType.toString(16)}`);
|
|
888
889
|
}
|
|
@@ -890,17 +891,16 @@ export class Midy {
|
|
|
890
891
|
handlePolyphonicKeyPressure(channelNumber, noteNumber, pressure) {
|
|
891
892
|
const now = this.audioContext.currentTime;
|
|
892
893
|
const channel = this.channels[channelNumber];
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
if (
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
});
|
|
894
|
+
pressure /= 64;
|
|
895
|
+
const activeNotes = this.getActiveNotes(channel);
|
|
896
|
+
if (channel.polyphonicKeyPressure.amplitudeControl !== 1) {
|
|
897
|
+
if (activeNotes.has(noteNumber)) {
|
|
898
|
+
const activeNote = activeNotes.get(noteNumber);
|
|
899
|
+
const gain = activeNote.gainNode.gain.value;
|
|
900
|
+
activeNote.gainNode.gain
|
|
901
|
+
.cancelScheduledValues(now)
|
|
902
|
+
.setValueAtTime(gain * pressure, now);
|
|
903
|
+
}
|
|
904
904
|
}
|
|
905
905
|
}
|
|
906
906
|
handleProgramChange(channelNumber, program) {
|
|
@@ -909,11 +909,37 @@ export class Midy {
|
|
|
909
909
|
channel.program = program;
|
|
910
910
|
}
|
|
911
911
|
handleChannelPressure(channelNumber, pressure) {
|
|
912
|
-
this.
|
|
912
|
+
const now = this.audioContext.currentTime;
|
|
913
|
+
const channel = this.channels[channelNumber];
|
|
914
|
+
pressure /= 64;
|
|
915
|
+
channel.channelPressure = pressure;
|
|
916
|
+
const activeNotes = this.getActiveNotes(channel);
|
|
917
|
+
if (channel.channelPressure.amplitudeControl !== 1) {
|
|
918
|
+
activeNotes.forEach((activeNote) => {
|
|
919
|
+
const gain = activeNote.gainNode.gain.value;
|
|
920
|
+
activeNote.gainNode.gain
|
|
921
|
+
.cancelScheduledValues(now)
|
|
922
|
+
.setValueAtTime(gain * pressure, now);
|
|
923
|
+
});
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
handlePitchBendMessage(channelNumber, lsb, msb) {
|
|
927
|
+
const pitchBend = msb * 128 + lsb;
|
|
928
|
+
this.handlePitchBend(channelNumber, pitchBend);
|
|
913
929
|
}
|
|
914
|
-
handlePitchBend(channelNumber,
|
|
915
|
-
const
|
|
916
|
-
this.channels[channelNumber]
|
|
930
|
+
handlePitchBend(channelNumber, pitchBend) {
|
|
931
|
+
const now = this.audioContext.currentTime;
|
|
932
|
+
const channel = this.channels[channelNumber];
|
|
933
|
+
channel.pitchBend = (pitchBend - 8192) / 8192;
|
|
934
|
+
const semitoneOffset = this.calcSemitoneOffset(channel);
|
|
935
|
+
const activeNotes = this.getActiveNotes(channel);
|
|
936
|
+
activeNotes.forEach((activeNote) => {
|
|
937
|
+
const { bufferSource, noteInfo, noteNumber } = activeNote;
|
|
938
|
+
const playbackRate = calcPlaybackRate(noteInfo, noteNumber, semitoneOffset);
|
|
939
|
+
bufferSource.playbackRate
|
|
940
|
+
.cancelScheduledValues(now)
|
|
941
|
+
.setValueAtTime(playbackRate * pressure, now);
|
|
942
|
+
});
|
|
917
943
|
}
|
|
918
944
|
handleControlChange(channelNumber, controller, value) {
|
|
919
945
|
switch (controller) {
|
|
@@ -984,13 +1010,9 @@ export class Midy {
|
|
|
984
1010
|
this.channels[channelNumber].bankMSB = msb;
|
|
985
1011
|
}
|
|
986
1012
|
setModulation(channelNumber, modulation) {
|
|
987
|
-
const now = this.audioContext.currentTime;
|
|
988
1013
|
const channel = this.channels[channelNumber];
|
|
989
|
-
channel.modulation = (modulation
|
|
990
|
-
channel.modulationDepthRange;
|
|
991
|
-
const lfoGain = channel.modulationEffect.lfoGain;
|
|
992
|
-
lfoGain.gain.cancelScheduledValues(now);
|
|
993
|
-
lfoGain.gain.setValueAtTime(channel.modulation, now);
|
|
1014
|
+
channel.modulation = (modulation / 127) *
|
|
1015
|
+
(channel.modulationDepthRange * 100);
|
|
994
1016
|
}
|
|
995
1017
|
setPortamentoTime(channelNumber, portamentoTime) {
|
|
996
1018
|
this.channels[channelNumber].portamentoTime = portamentoTime / 127;
|
|
@@ -1025,7 +1047,7 @@ export class Midy {
|
|
|
1025
1047
|
const isOn = value >= 64;
|
|
1026
1048
|
this.channels[channelNumber].sustainPedal = isOn;
|
|
1027
1049
|
if (!isOn) {
|
|
1028
|
-
this.releaseSustainPedal(channelNumber);
|
|
1050
|
+
this.releaseSustainPedal(channelNumber, value);
|
|
1029
1051
|
}
|
|
1030
1052
|
}
|
|
1031
1053
|
setPortamento(channelNumber, value) {
|
|
@@ -1054,25 +1076,29 @@ export class Midy {
|
|
|
1054
1076
|
const activeNotes = this.getActiveNotes(channel);
|
|
1055
1077
|
channel.sostenutoNotes = new Map(activeNotes);
|
|
1056
1078
|
}
|
|
1079
|
+
else {
|
|
1080
|
+
this.releaseSostenutoPedal(channelNumber, value);
|
|
1081
|
+
}
|
|
1057
1082
|
}
|
|
1058
1083
|
setSoftPedal(channelNumber, softPedal) {
|
|
1059
1084
|
const channel = this.channels[channelNumber];
|
|
1060
1085
|
channel.softPedal = softPedal / 127;
|
|
1061
|
-
this.updateChannelGain(channel);
|
|
1062
1086
|
}
|
|
1063
1087
|
setVibratoRate(channelNumber, vibratoRate) {
|
|
1064
1088
|
const now = this.audioContext.currentTime;
|
|
1065
1089
|
const channel = this.channels[channelNumber];
|
|
1066
1090
|
channel.vibratoRate = vibratoRate / 127 * 4 + 3; // 3-7Hz
|
|
1067
|
-
channel.modulationEffect.lfo.frequency
|
|
1068
|
-
|
|
1091
|
+
channel.modulationEffect.lfo.frequency
|
|
1092
|
+
.cancelScheduledValues(now)
|
|
1093
|
+
.setValueAtTime(channel.vibratoRate, now);
|
|
1069
1094
|
}
|
|
1070
1095
|
setVibratoDepth(channelNumber, vibratoDepth) {
|
|
1071
1096
|
const now = this.audioContext.currentTime;
|
|
1072
1097
|
const channel = this.channels[channelNumber];
|
|
1073
1098
|
channel.vibratoDepth = vibratoDepth / 127;
|
|
1074
|
-
channel.modulationEffect.lfoGain.gain
|
|
1075
|
-
|
|
1099
|
+
channel.modulationEffect.lfoGain.gain
|
|
1100
|
+
.cancelScheduledValues(now)
|
|
1101
|
+
.setValueAtTime(channel.vibratoDepth, now);
|
|
1076
1102
|
}
|
|
1077
1103
|
setVibratoDelay(channelNumber, vibratoDelay) {
|
|
1078
1104
|
// Access Virus: 0-10sec
|
|
@@ -1258,10 +1284,10 @@ export class Midy {
|
|
|
1258
1284
|
switch (data[3]) {
|
|
1259
1285
|
// case 1:
|
|
1260
1286
|
// // TODO
|
|
1261
|
-
// return this.
|
|
1287
|
+
// return this.setChannelPressure();
|
|
1262
1288
|
// case 3:
|
|
1263
1289
|
// // TODO
|
|
1264
|
-
// return this.
|
|
1290
|
+
// return this.setControlChange();
|
|
1265
1291
|
default:
|
|
1266
1292
|
console.warn(`Unsupported Exclusive Message ${data}`);
|
|
1267
1293
|
}
|
|
@@ -1280,20 +1306,25 @@ export class Midy {
|
|
|
1280
1306
|
}
|
|
1281
1307
|
}
|
|
1282
1308
|
handleMasterVolumeSysEx(data) {
|
|
1283
|
-
const volume = (data[5] * 128 + data[4]
|
|
1309
|
+
const volume = (data[5] * 128 + data[4]) / 16383;
|
|
1284
1310
|
this.handleMasterVolume(volume);
|
|
1285
1311
|
}
|
|
1286
1312
|
handleMasterVolume(volume) {
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1313
|
+
if (volume < 0 && 1 < volume) {
|
|
1314
|
+
console.error("Master Volume is out of range");
|
|
1315
|
+
}
|
|
1316
|
+
else {
|
|
1317
|
+
const now = this.audioContext.currentTime;
|
|
1318
|
+
this.masterGain.gain.cancelScheduledValues(now);
|
|
1319
|
+
this.masterGain.gain.setValueAtTime(volume * volume, now);
|
|
1320
|
+
}
|
|
1290
1321
|
}
|
|
1291
1322
|
handleMasterFineTuningSysEx(data) {
|
|
1292
1323
|
const fineTuning = (data[5] * 128 + data[4] - 8192) / 8192;
|
|
1293
1324
|
this.handleMasterFineTuning(fineTuning);
|
|
1294
1325
|
}
|
|
1295
1326
|
handleMasterFineTuning(fineTuning) {
|
|
1296
|
-
if (fineTuning <
|
|
1327
|
+
if (fineTuning < -1 && 1 < fineTuning) {
|
|
1297
1328
|
console.error("Master Fine Tuning value is out of range");
|
|
1298
1329
|
}
|
|
1299
1330
|
else {
|
|
@@ -1343,7 +1374,7 @@ Object.defineProperty(Midy, "channelSettings", {
|
|
|
1343
1374
|
writable: true,
|
|
1344
1375
|
value: {
|
|
1345
1376
|
currentBufferSource: null,
|
|
1346
|
-
volume:
|
|
1377
|
+
volume: 100 / 127,
|
|
1347
1378
|
pan: 0,
|
|
1348
1379
|
portamentoTime: 0,
|
|
1349
1380
|
reverb: 0,
|
|
@@ -1360,7 +1391,7 @@ Object.defineProperty(Midy, "channelSettings", {
|
|
|
1360
1391
|
pitchBend: 0,
|
|
1361
1392
|
fineTuning: 0,
|
|
1362
1393
|
coarseTuning: 0,
|
|
1363
|
-
modulationDepthRange:
|
|
1394
|
+
modulationDepthRange: 0.5,
|
|
1364
1395
|
}
|
|
1365
1396
|
});
|
|
1366
1397
|
Object.defineProperty(Midy, "effectSettings", {
|
|
@@ -1380,3 +1411,16 @@ Object.defineProperty(Midy, "effectSettings", {
|
|
|
1380
1411
|
pitchBendRange: 2,
|
|
1381
1412
|
}
|
|
1382
1413
|
});
|
|
1414
|
+
Object.defineProperty(Midy, "controllerDestinationSettings", {
|
|
1415
|
+
enumerable: true,
|
|
1416
|
+
configurable: true,
|
|
1417
|
+
writable: true,
|
|
1418
|
+
value: {
|
|
1419
|
+
pitchControl: 0,
|
|
1420
|
+
filterCutoffControl: 0,
|
|
1421
|
+
amplitudeControl: 1,
|
|
1422
|
+
lfoPitchDepth: 0,
|
|
1423
|
+
lfoFilterDepth: 0,
|
|
1424
|
+
lfoAmplitudeDepth: 0,
|
|
1425
|
+
}
|
|
1426
|
+
});
|
package/package.json
CHANGED
package/script/midy-GM1.d.ts
CHANGED
|
@@ -24,7 +24,6 @@ export class MidyGM1 {
|
|
|
24
24
|
};
|
|
25
25
|
constructor(audioContext: any);
|
|
26
26
|
ticksPerBeat: number;
|
|
27
|
-
secondsPerBeat: number;
|
|
28
27
|
totalTime: number;
|
|
29
28
|
noteCheckInterval: number;
|
|
30
29
|
lookAhead: number;
|
|
@@ -50,7 +49,6 @@ export class MidyGM1 {
|
|
|
50
49
|
pannerNode: any;
|
|
51
50
|
modulationEffect: {
|
|
52
51
|
lfo: any;
|
|
53
|
-
lfoGain: any;
|
|
54
52
|
};
|
|
55
53
|
expression: number;
|
|
56
54
|
modulation: number;
|
|
@@ -81,7 +79,6 @@ export class MidyGM1 {
|
|
|
81
79
|
pannerNode: any;
|
|
82
80
|
modulationEffect: {
|
|
83
81
|
lfo: any;
|
|
84
|
-
lfoGain: any;
|
|
85
82
|
};
|
|
86
83
|
};
|
|
87
84
|
createChannels(audioContext: any): {
|
|
@@ -91,7 +88,6 @@ export class MidyGM1 {
|
|
|
91
88
|
pannerNode: any;
|
|
92
89
|
modulationEffect: {
|
|
93
90
|
lfo: any;
|
|
94
|
-
lfoGain: any;
|
|
95
91
|
};
|
|
96
92
|
expression: number;
|
|
97
93
|
modulation: number;
|
|
@@ -137,31 +133,27 @@ export class MidyGM1 {
|
|
|
137
133
|
getActiveChannelNotes(scheduledNotes: any): any;
|
|
138
134
|
createModulationEffect(audioContext: any): {
|
|
139
135
|
lfo: any;
|
|
140
|
-
lfoGain: any;
|
|
141
|
-
};
|
|
142
|
-
createReverbEffect(audioContext: any, options?: {}): {
|
|
143
|
-
convolverNode: any;
|
|
144
|
-
dryGain: any;
|
|
145
|
-
wetGain: any;
|
|
146
136
|
};
|
|
147
137
|
connectNoteEffects(channel: any, gainNode: any): void;
|
|
148
138
|
cbToRatio(cb: any): number;
|
|
149
139
|
centToHz(cent: any): number;
|
|
140
|
+
calcSemitoneOffset(channel: any): any;
|
|
141
|
+
calcPlaybackRate(noteInfo: any, noteNumber: any, semitoneOffset: any): number;
|
|
150
142
|
createNoteAudioChain(channel: any, noteInfo: any, noteNumber: any, velocity: any, startTime: any, isSF3: any): Promise<{
|
|
151
143
|
bufferSource: any;
|
|
152
144
|
gainNode: any;
|
|
153
145
|
filterNode: any;
|
|
146
|
+
lfoGain: any;
|
|
154
147
|
}>;
|
|
155
148
|
scheduleNoteOn(channelNumber: any, noteNumber: any, velocity: any, startTime: any): Promise<void>;
|
|
156
149
|
noteOn(channelNumber: any, noteNumber: any, velocity: any): Promise<void>;
|
|
157
150
|
scheduleNoteRelease(channelNumber: any, noteNumber: any, velocity: any, stopTime: any, stopPedal?: boolean): Promise<any> | undefined;
|
|
158
151
|
releaseNote(channelNumber: any, noteNumber: any, velocity: any): Promise<any> | undefined;
|
|
159
|
-
releaseSustainPedal(channelNumber: any):
|
|
152
|
+
releaseSustainPedal(channelNumber: any, halfVelocity: any): any[];
|
|
160
153
|
handleMIDIMessage(statusByte: any, data1: any, data2: any): void | any[] | Promise<any>;
|
|
161
|
-
handlePolyphonicKeyPressure(channelNumber: any, noteNumber: any, pressure: any): void;
|
|
162
154
|
handleProgramChange(channelNumber: any, program: any): void;
|
|
163
|
-
|
|
164
|
-
handlePitchBend(channelNumber: any,
|
|
155
|
+
handlePitchBendMessage(channelNumber: any, lsb: any, msb: any): void;
|
|
156
|
+
handlePitchBend(channelNumber: any, pitchBend: any): void;
|
|
165
157
|
handleControlChange(channelNumber: any, controller: any, value: any): void | any[];
|
|
166
158
|
setModulation(channelNumber: any, modulation: any): void;
|
|
167
159
|
setVolume(channelNumber: any, volume: any): void;
|
package/script/midy-GM1.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"midy-GM1.d.ts","sourceRoot":"","sources":["../src/midy-GM1.js"],"names":[],"mappings":"AAMA;
|
|
1
|
+
{"version":3,"file":"midy-GM1.d.ts","sourceRoot":"","sources":["../src/midy-GM1.js"],"names":[],"mappings":"AAMA;IAmBE;;;;;;;;;;;;;;MAcE;IAEF;;;;;;;MAOE;IAEF,+BAMC;IAjDD,qBAAmB;IACnB,kBAAc;IACd,0BAAwB;IACxB,kBAAc;IACd,mBAAiB;IACjB,kBAAc;IACd,mBAAe;IACf,kBAAgB;IAChB,sBAA2C;IAC3C,mBAAkB;IAClB,mBAAkB;IAClB,kBAAiB;IACjB,oBAAmB;IACnB,mBAAkB;IAClB,gBAAc;IACd,mBAAiB;IACjB,oBAAkB;IA4BhB,kBAAgC;IAChC,gBAA4C;IAE5C;;;;;;;;;;;;;;;;;;;;;;;;;;;QAAiD;IAInD,4BAMC;IAED,mCASC;IAED,gDAMC;IAED,sCASC;IAED;;;;;;MAgBC;IAED;;;;;;;;;;;;;;;;;;;;;;;;;;;QAWC;IAED,0DAyBC;IAED,8DAUC;IAED,qDAOC;IAED,2EA+CC;IAED,mCAOC;IAED,0BA+CC;IAED,uDAEC;IAED,wDAEC;IAED;;;MAyEC;IAED,4BAsBC;IAED,uBAKC;IAED,aAGC;IAED,cAKC;IAED,wBAIC;IAED,0BAKC;IAED,wBAOC;IAED,sBAGC;IAED,4CASC;IAED,gDAKC;IAED;;MAOC;IAED,sDAEC;IAED,2BAEC;IAED,4BAEC;IAED,sCAGC;IAED,8EAEC;IAED;;;;;OA8EC;IAED,kGAuCC;IAED,0EAGC;IAED,sIA4CC;IAED,0FAGC;IAED,kEAeC;IAED,wFAiBC;IAED,4DAGC;IAED,qEAGC;IAED,0DAiBC;IAED,mFA+BC;IAED,yDAIC;IAED,iDAIC;IAED,2CAMC;IAED,yDAIC;IAED,sCAKC;IAED,sDAMC;IAED,gDAEC;IAED,gDAEC;IAED,+DAoBC;IAED,uCAoBC;IAED,8CAEC;IAED,uCAoBC;IAED,4DAgBC;IAED,oBAQC;IAED,yDAaC;IAED,yCAGC;IAED,sCAQC;IAED,wCAEC;IAED,6BASC;IAED,0DAUC;CACF"}
|