@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-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/esm/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"}
|
package/esm/midy-GM1.js
CHANGED
|
@@ -8,12 +8,6 @@ export class MidyGM1 {
|
|
|
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,
|
|
@@ -144,10 +138,10 @@ export class MidyGM1 {
|
|
|
144
138
|
const response = await fetch(midiUrl);
|
|
145
139
|
const arrayBuffer = await response.arrayBuffer();
|
|
146
140
|
const midi = parseMidi(new Uint8Array(arrayBuffer));
|
|
141
|
+
this.ticksPerBeat = midi.header.ticksPerBeat;
|
|
147
142
|
const midiData = this.extractMidiData(midi);
|
|
148
143
|
this.instruments = midiData.instruments;
|
|
149
144
|
this.timeline = midiData.timeline;
|
|
150
|
-
this.ticksPerBeat = midi.header.ticksPerBeat;
|
|
151
145
|
this.totalTime = this.calcTotalTime();
|
|
152
146
|
}
|
|
153
147
|
setChannelAudioNodes(audioContext) {
|
|
@@ -226,28 +220,30 @@ export class MidyGM1 {
|
|
|
226
220
|
async scheduleTimelineEvents(t, offset, queueIndex) {
|
|
227
221
|
while (queueIndex < this.timeline.length) {
|
|
228
222
|
const event = this.timeline[queueIndex];
|
|
229
|
-
|
|
230
|
-
if (time > t + this.lookAhead)
|
|
223
|
+
if (event.startTime > t + this.lookAhead)
|
|
231
224
|
break;
|
|
232
225
|
switch (event.type) {
|
|
233
|
-
case "controller":
|
|
234
|
-
this.handleControlChange(event.channel, event.controllerType, event.value);
|
|
235
|
-
break;
|
|
236
226
|
case "noteOn":
|
|
237
|
-
|
|
238
|
-
|
|
227
|
+
if (event.velocity !== 0) {
|
|
228
|
+
await this.scheduleNoteOn(event.channel, event.noteNumber, event.velocity, event.startTime + this.startDelay - offset);
|
|
229
|
+
break;
|
|
230
|
+
}
|
|
231
|
+
/* falls through */
|
|
239
232
|
case "noteOff": {
|
|
240
|
-
const notePromise = this.scheduleNoteRelease(event.channel, event.noteNumber, event.velocity,
|
|
233
|
+
const notePromise = this.scheduleNoteRelease(event.channel, event.noteNumber, event.velocity, event.startTime + this.startDelay - offset);
|
|
241
234
|
if (notePromise) {
|
|
242
235
|
this.notePromises.push(notePromise);
|
|
243
236
|
}
|
|
244
237
|
break;
|
|
245
238
|
}
|
|
239
|
+
case "controller":
|
|
240
|
+
this.handleControlChange(event.channel, event.controllerType, event.value);
|
|
241
|
+
break;
|
|
246
242
|
case "programChange":
|
|
247
243
|
this.handleProgramChange(event.channel, event.programNumber);
|
|
248
244
|
break;
|
|
249
|
-
case "
|
|
250
|
-
this.
|
|
245
|
+
case "pitchBend":
|
|
246
|
+
this.handlePitchBend(event.channel, event.value);
|
|
251
247
|
break;
|
|
252
248
|
case "sysEx":
|
|
253
249
|
this.handleSysEx(event.data);
|
|
@@ -257,9 +253,8 @@ export class MidyGM1 {
|
|
|
257
253
|
return queueIndex;
|
|
258
254
|
}
|
|
259
255
|
getQueueIndex(second) {
|
|
260
|
-
const ticks = this.secondToTicks(second, this.secondsPerBeat);
|
|
261
256
|
for (let i = 0; i < this.timeline.length; i++) {
|
|
262
|
-
if (
|
|
257
|
+
if (second <= this.timeline[i].startTime) {
|
|
263
258
|
return i;
|
|
264
259
|
}
|
|
265
260
|
}
|
|
@@ -367,18 +362,28 @@ export class MidyGM1 {
|
|
|
367
362
|
timeline.push(event);
|
|
368
363
|
});
|
|
369
364
|
});
|
|
365
|
+
const priority = {
|
|
366
|
+
setTempo: 0,
|
|
367
|
+
controller: 1,
|
|
368
|
+
};
|
|
370
369
|
timeline.sort((a, b) => {
|
|
371
|
-
if (a.ticks !== b.ticks)
|
|
370
|
+
if (a.ticks !== b.ticks)
|
|
372
371
|
return a.ticks - b.ticks;
|
|
373
|
-
|
|
374
|
-
if (a.type !== "controller" && b.type === "controller") {
|
|
375
|
-
return -1;
|
|
376
|
-
}
|
|
377
|
-
if (a.type === "controller" && b.type !== "controller") {
|
|
378
|
-
return 1;
|
|
379
|
-
}
|
|
380
|
-
return 0;
|
|
372
|
+
return (priority[a.type] || 2) - (priority[b.type] || 2);
|
|
381
373
|
});
|
|
374
|
+
let prevTempoTime = 0;
|
|
375
|
+
let prevTempoTicks = 0;
|
|
376
|
+
let secondsPerBeat = 0.5;
|
|
377
|
+
for (let i = 0; i < timeline.length; i++) {
|
|
378
|
+
const event = timeline[i];
|
|
379
|
+
const timeFromPrevTempo = this.ticksToSecond(event.ticks - prevTempoTicks, secondsPerBeat);
|
|
380
|
+
event.startTime = prevTempoTime + timeFromPrevTempo;
|
|
381
|
+
if (event.type === "setTempo") {
|
|
382
|
+
prevTempoTime += this.ticksToSecond(event.ticks - prevTempoTicks, secondsPerBeat);
|
|
383
|
+
secondsPerBeat = event.microsecondsPerBeat / 1000000;
|
|
384
|
+
prevTempoTicks = event.ticks;
|
|
385
|
+
}
|
|
386
|
+
}
|
|
382
387
|
return { instruments, timeline };
|
|
383
388
|
}
|
|
384
389
|
stopNotes() {
|
|
@@ -430,32 +435,12 @@ export class MidyGM1 {
|
|
|
430
435
|
}
|
|
431
436
|
}
|
|
432
437
|
calcTotalTime() {
|
|
433
|
-
const endOfTracks = [];
|
|
434
|
-
let prevTicks = 0;
|
|
435
438
|
let totalTime = 0;
|
|
436
|
-
let secondsPerBeat = 0.5;
|
|
437
439
|
for (let i = 0; i < this.timeline.length; i++) {
|
|
438
440
|
const event = this.timeline[i];
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
const durationTicks = event.ticks - prevTicks;
|
|
442
|
-
totalTime += this.ticksToSecond(durationTicks, secondsPerBeat);
|
|
443
|
-
secondsPerBeat = event.microsecondsPerBeat / 1000000;
|
|
444
|
-
prevTicks = event.ticks;
|
|
445
|
-
break;
|
|
446
|
-
}
|
|
447
|
-
case "endOfTrack":
|
|
448
|
-
endOfTracks.push(event);
|
|
449
|
-
}
|
|
450
|
-
}
|
|
451
|
-
let maxTicks = 0;
|
|
452
|
-
for (let i = 0; i < endOfTracks.length; i++) {
|
|
453
|
-
const event = endOfTracks[i];
|
|
454
|
-
if (maxTicks < event.ticks)
|
|
455
|
-
maxTicks = event.ticks;
|
|
441
|
+
if (totalTime < event.startTime)
|
|
442
|
+
totalTime = event.startTime;
|
|
456
443
|
}
|
|
457
|
-
const durationTicks = maxTicks - prevTicks;
|
|
458
|
-
totalTime += this.ticksToSecond(durationTicks, secondsPerBeat);
|
|
459
444
|
return totalTime;
|
|
460
445
|
}
|
|
461
446
|
currentTime() {
|
|
@@ -483,43 +468,8 @@ export class MidyGM1 {
|
|
|
483
468
|
const lfo = new OscillatorNode(audioContext, {
|
|
484
469
|
frequency: 5,
|
|
485
470
|
});
|
|
486
|
-
const lfoGain = new GainNode(audioContext);
|
|
487
|
-
lfo.connect(lfoGain);
|
|
488
471
|
return {
|
|
489
472
|
lfo,
|
|
490
|
-
lfoGain,
|
|
491
|
-
};
|
|
492
|
-
}
|
|
493
|
-
createReverbEffect(audioContext, options = {}) {
|
|
494
|
-
const { decay = 0.8, preDecay = 0, } = options;
|
|
495
|
-
const sampleRate = audioContext.sampleRate;
|
|
496
|
-
const length = sampleRate * decay;
|
|
497
|
-
const impulse = new AudioBuffer({
|
|
498
|
-
numberOfChannels: 2,
|
|
499
|
-
length,
|
|
500
|
-
sampleRate,
|
|
501
|
-
});
|
|
502
|
-
const preDecayLength = Math.min(sampleRate * preDecay, length);
|
|
503
|
-
for (let channel = 0; channel < impulse.numberOfChannels; channel++) {
|
|
504
|
-
const channelData = impulse.getChannelData(channel);
|
|
505
|
-
for (let i = 0; i < preDecayLength; i++) {
|
|
506
|
-
channelData[i] = Math.random() * 2 - 1;
|
|
507
|
-
}
|
|
508
|
-
for (let i = preDecayLength; i < length; i++) {
|
|
509
|
-
const attenuation = Math.exp(-(i - preDecayLength) / sampleRate / decay);
|
|
510
|
-
channelData[i] = (Math.random() * 2 - 1) * attenuation;
|
|
511
|
-
}
|
|
512
|
-
}
|
|
513
|
-
const convolverNode = new ConvolverNode(audioContext, {
|
|
514
|
-
buffer: impulse,
|
|
515
|
-
});
|
|
516
|
-
const dryGain = new GainNode(audioContext);
|
|
517
|
-
const wetGain = new GainNode(audioContext);
|
|
518
|
-
convolverNode.connect(wetGain);
|
|
519
|
-
return {
|
|
520
|
-
convolverNode,
|
|
521
|
-
dryGain,
|
|
522
|
-
wetGain,
|
|
523
473
|
};
|
|
524
474
|
}
|
|
525
475
|
connectNoteEffects(channel, gainNode) {
|
|
@@ -531,13 +481,17 @@ export class MidyGM1 {
|
|
|
531
481
|
centToHz(cent) {
|
|
532
482
|
return 8.176 * Math.pow(2, cent / 1200);
|
|
533
483
|
}
|
|
534
|
-
|
|
484
|
+
calcSemitoneOffset(channel) {
|
|
535
485
|
const tuning = channel.coarseTuning + channel.fineTuning;
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
486
|
+
return channel.pitchBend * channel.pitchBendRange + tuning;
|
|
487
|
+
}
|
|
488
|
+
calcPlaybackRate(noteInfo, noteNumber, semitoneOffset) {
|
|
489
|
+
return noteInfo.playbackRate(noteNumber) * Math.pow(2, semitoneOffset / 12);
|
|
490
|
+
}
|
|
491
|
+
async createNoteAudioChain(channel, noteInfo, noteNumber, velocity, startTime, isSF3) {
|
|
539
492
|
const bufferSource = await this.createNoteBufferNode(noteInfo, isSF3);
|
|
540
|
-
|
|
493
|
+
const semitoneOffset = this.calcSemitoneOffset(channel);
|
|
494
|
+
bufferSource.playbackRate.value = this.calcPlaybackRate(noteInfo, noteNumber, semitoneOffset);
|
|
541
495
|
// volume envelope
|
|
542
496
|
const gainNode = new GainNode(this.audioContext, {
|
|
543
497
|
gain: 0,
|
|
@@ -556,12 +510,6 @@ export class MidyGM1 {
|
|
|
556
510
|
.exponentialRampToValueAtTime(attackVolume, volAttack)
|
|
557
511
|
.setValueAtTime(attackVolume, volHold)
|
|
558
512
|
.linearRampToValueAtTime(sustainVolume, volDecay);
|
|
559
|
-
if (channel.modulation > 0) {
|
|
560
|
-
const lfoGain = channel.modulationEffect.lfoGain;
|
|
561
|
-
lfoGain.connect(bufferSource.detune);
|
|
562
|
-
lfoGain.gain.cancelScheduledValues(startTime + channel.vibratoDelay);
|
|
563
|
-
lfoGain.gain.setValueAtTime(channel.modulation, startTime + channel.vibratoDelay);
|
|
564
|
-
}
|
|
565
513
|
// filter envelope
|
|
566
514
|
const maxFreq = this.audioContext.sampleRate / 2;
|
|
567
515
|
const baseFreq = this.centToHz(noteInfo.initialFilterFc);
|
|
@@ -585,10 +533,23 @@ export class MidyGM1 {
|
|
|
585
533
|
.exponentialRampToValueAtTime(adjustedPeekFreq, modAttack)
|
|
586
534
|
.setValueAtTime(adjustedPeekFreq, modHold)
|
|
587
535
|
.linearRampToValueAtTime(adjustedSustainFreq, modDecay);
|
|
536
|
+
let lfoGain;
|
|
537
|
+
if (channel.modulation > 0) {
|
|
538
|
+
const vibratoDelay = startTime + channel.vibratoDelay;
|
|
539
|
+
const vibratoAttack = vibratoDelay + 0.1;
|
|
540
|
+
lfoGain = new GainNode(this.audioContext, {
|
|
541
|
+
gain: 0,
|
|
542
|
+
});
|
|
543
|
+
lfoGain.gain
|
|
544
|
+
.setValueAtTime(1e-6, vibratoDelay) // exponentialRampToValueAtTime() requires a non-zero value
|
|
545
|
+
.exponentialRampToValueAtTime(channel.modulation, vibratoAttack);
|
|
546
|
+
channel.modulationEffect.lfo.connect(lfoGain);
|
|
547
|
+
lfoGain.connect(bufferSource.detune);
|
|
548
|
+
}
|
|
588
549
|
bufferSource.connect(filterNode);
|
|
589
550
|
filterNode.connect(gainNode);
|
|
590
551
|
bufferSource.start(startTime, noteInfo.start / noteInfo.sampleRate);
|
|
591
|
-
return { bufferSource, gainNode, filterNode };
|
|
552
|
+
return { bufferSource, gainNode, filterNode, lfoGain };
|
|
592
553
|
}
|
|
593
554
|
async scheduleNoteOn(channelNumber, noteNumber, velocity, startTime) {
|
|
594
555
|
const channel = this.channels[channelNumber];
|
|
@@ -601,16 +562,17 @@ export class MidyGM1 {
|
|
|
601
562
|
const noteInfo = soundFont.getInstrumentKey(bankNumber, channel.program, noteNumber);
|
|
602
563
|
if (!noteInfo)
|
|
603
564
|
return;
|
|
604
|
-
const { bufferSource, gainNode, filterNode } = await this
|
|
565
|
+
const { bufferSource, gainNode, filterNode, lfoGain } = await this
|
|
605
566
|
.createNoteAudioChain(channel, noteInfo, noteNumber, velocity, startTime, isSF3);
|
|
606
567
|
this.connectNoteEffects(channel, gainNode);
|
|
607
568
|
const scheduledNotes = channel.scheduledNotes;
|
|
608
569
|
const scheduledNote = {
|
|
609
|
-
gainNode,
|
|
610
|
-
filterNode,
|
|
611
570
|
bufferSource,
|
|
612
|
-
|
|
571
|
+
filterNode,
|
|
572
|
+
gainNode,
|
|
573
|
+
lfoGain,
|
|
613
574
|
noteInfo,
|
|
575
|
+
noteNumber,
|
|
614
576
|
startTime,
|
|
615
577
|
};
|
|
616
578
|
if (scheduledNotes.has(noteNumber)) {
|
|
@@ -637,7 +599,7 @@ export class MidyGM1 {
|
|
|
637
599
|
continue;
|
|
638
600
|
if (targetNote.ending)
|
|
639
601
|
continue;
|
|
640
|
-
const { bufferSource, filterNode, gainNode, noteInfo } = targetNote;
|
|
602
|
+
const { bufferSource, filterNode, gainNode, lfoGain, noteInfo } = targetNote;
|
|
641
603
|
const velocityRate = (velocity + 127) / 127;
|
|
642
604
|
const volEndTime = stopTime + noteInfo.volRelease * velocityRate;
|
|
643
605
|
gainNode.gain.cancelScheduledValues(stopTime);
|
|
@@ -659,6 +621,8 @@ export class MidyGM1 {
|
|
|
659
621
|
bufferSource.disconnect(0);
|
|
660
622
|
filterNode.disconnect(0);
|
|
661
623
|
gainNode.disconnect(0);
|
|
624
|
+
if (lfoGain)
|
|
625
|
+
lfoGain.disconnect(0);
|
|
662
626
|
resolve();
|
|
663
627
|
};
|
|
664
628
|
bufferSource.stop(volEndTime);
|
|
@@ -669,28 +633,21 @@ export class MidyGM1 {
|
|
|
669
633
|
const now = this.audioContext.currentTime;
|
|
670
634
|
return this.scheduleNoteRelease(channelNumber, noteNumber, velocity, now);
|
|
671
635
|
}
|
|
672
|
-
releaseSustainPedal(channelNumber) {
|
|
673
|
-
const
|
|
636
|
+
releaseSustainPedal(channelNumber, halfVelocity) {
|
|
637
|
+
const velocity = halfVelocity * 2;
|
|
674
638
|
const channel = this.channels[channelNumber];
|
|
639
|
+
const promises = [];
|
|
675
640
|
channel.sustainPedal = false;
|
|
676
641
|
channel.scheduledNotes.forEach((scheduledNotes) => {
|
|
677
642
|
scheduledNotes.forEach((scheduledNote) => {
|
|
678
643
|
if (scheduledNote) {
|
|
679
|
-
const {
|
|
680
|
-
const
|
|
681
|
-
|
|
682
|
-
gainNode.gain.linearRampToValueAtTime(0, volEndTime);
|
|
683
|
-
const maxFreq = this.audioContext.sampleRate / 2;
|
|
684
|
-
const baseFreq = this.centToHz(noteInfo.initialFilterFc);
|
|
685
|
-
const adjustedBaseFreq = Math.min(maxFreq, baseFreq);
|
|
686
|
-
const modEndTime = now + noteInfo.modRelease;
|
|
687
|
-
filterNode.frequency
|
|
688
|
-
.cancelScheduledValues(stopTime)
|
|
689
|
-
.linearRampToValueAtTime(adjustedBaseFreq, modEndTime);
|
|
690
|
-
bufferSource.stop(volEndTime);
|
|
644
|
+
const { noteNumber } = scheduledNote;
|
|
645
|
+
const promise = this.releaseNote(channelNumber, noteNumber, velocity);
|
|
646
|
+
promises.push(promise);
|
|
691
647
|
}
|
|
692
648
|
});
|
|
693
649
|
});
|
|
650
|
+
return promises;
|
|
694
651
|
}
|
|
695
652
|
handleMIDIMessage(statusByte, data1, data2) {
|
|
696
653
|
const channelNumber = statusByte & 0x0F;
|
|
@@ -700,46 +657,37 @@ export class MidyGM1 {
|
|
|
700
657
|
return this.releaseNote(channelNumber, data1, data2);
|
|
701
658
|
case 0x90:
|
|
702
659
|
return this.noteOn(channelNumber, data1, data2);
|
|
703
|
-
case 0xA0:
|
|
704
|
-
return this.handlePolyphonicKeyPressure(channelNumber, data1, data2);
|
|
705
660
|
case 0xB0:
|
|
706
661
|
return this.handleControlChange(channelNumber, data1, data2);
|
|
707
662
|
case 0xC0:
|
|
708
663
|
return this.handleProgramChange(channelNumber, data1);
|
|
709
|
-
case 0xD0:
|
|
710
|
-
return this.handleChannelPressure(channelNumber, data1);
|
|
711
664
|
case 0xE0:
|
|
712
|
-
return this.
|
|
665
|
+
return this.handlePitchBendMessage(channelNumber, data1, data2);
|
|
713
666
|
default:
|
|
714
667
|
console.warn(`Unsupported MIDI message: ${messageType.toString(16)}`);
|
|
715
668
|
}
|
|
716
669
|
}
|
|
717
|
-
handlePolyphonicKeyPressure(channelNumber, noteNumber, pressure) {
|
|
718
|
-
const now = this.audioContext.currentTime;
|
|
719
|
-
const channel = this.channels[channelNumber];
|
|
720
|
-
const scheduledNotes = channel.scheduledNotes.get(noteNumber);
|
|
721
|
-
pressure /= 127;
|
|
722
|
-
if (scheduledNotes) {
|
|
723
|
-
scheduledNotes.forEach((scheduledNote) => {
|
|
724
|
-
if (scheduledNote) {
|
|
725
|
-
const { initialAttenuation } = scheduledNote.noteInfo;
|
|
726
|
-
const gain = this.cbToRatio(-initialAttenuation) * pressure;
|
|
727
|
-
scheduledNote.gainNode.gain.cancelScheduledValues(now);
|
|
728
|
-
scheduledNote.gainNode.gain.setValueAtTime(gain, now);
|
|
729
|
-
}
|
|
730
|
-
});
|
|
731
|
-
}
|
|
732
|
-
}
|
|
733
670
|
handleProgramChange(channelNumber, program) {
|
|
734
671
|
const channel = this.channels[channelNumber];
|
|
735
672
|
channel.program = program;
|
|
736
673
|
}
|
|
737
|
-
|
|
738
|
-
|
|
674
|
+
handlePitchBendMessage(channelNumber, lsb, msb) {
|
|
675
|
+
const pitchBend = msb * 128 + lsb;
|
|
676
|
+
this.handlePitchBend(channelNumber, pitchBend);
|
|
739
677
|
}
|
|
740
|
-
handlePitchBend(channelNumber,
|
|
741
|
-
const
|
|
742
|
-
this.channels[channelNumber]
|
|
678
|
+
handlePitchBend(channelNumber, pitchBend) {
|
|
679
|
+
const now = this.audioContext.currentTime;
|
|
680
|
+
const channel = this.channels[channelNumber];
|
|
681
|
+
channel.pitchBend = (pitchBend - 8192) / 8192;
|
|
682
|
+
const semitoneOffset = this.calcSemitoneOffset(channel);
|
|
683
|
+
const activeNotes = this.getActiveNotes(channel);
|
|
684
|
+
activeNotes.forEach((activeNote) => {
|
|
685
|
+
const { bufferSource, noteInfo, noteNumber } = activeNote;
|
|
686
|
+
const playbackRate = calcPlaybackRate(noteInfo, noteNumber, semitoneOffset);
|
|
687
|
+
bufferSource.playbackRate
|
|
688
|
+
.cancelScheduledValues(now)
|
|
689
|
+
.setValueAtTime(playbackRate * pressure, now);
|
|
690
|
+
});
|
|
743
691
|
}
|
|
744
692
|
handleControlChange(channelNumber, controller, value) {
|
|
745
693
|
switch (controller) {
|
|
@@ -772,13 +720,9 @@ export class MidyGM1 {
|
|
|
772
720
|
}
|
|
773
721
|
}
|
|
774
722
|
setModulation(channelNumber, modulation) {
|
|
775
|
-
const now = this.audioContext.currentTime;
|
|
776
723
|
const channel = this.channels[channelNumber];
|
|
777
|
-
channel.modulation = (modulation
|
|
778
|
-
channel.modulationDepthRange;
|
|
779
|
-
const lfoGain = channel.modulationEffect.lfoGain;
|
|
780
|
-
lfoGain.gain.cancelScheduledValues(now);
|
|
781
|
-
lfoGain.gain.setValueAtTime(channel.modulation, now);
|
|
724
|
+
channel.modulation = (modulation / 127) *
|
|
725
|
+
(channel.modulationDepthRange * 100);
|
|
782
726
|
}
|
|
783
727
|
setVolume(channelNumber, volume) {
|
|
784
728
|
const channel = this.channels[channelNumber];
|
|
@@ -807,7 +751,7 @@ export class MidyGM1 {
|
|
|
807
751
|
const isOn = value >= 64;
|
|
808
752
|
this.channels[channelNumber].sustainPedal = isOn;
|
|
809
753
|
if (!isOn) {
|
|
810
|
-
this.releaseSustainPedal(channelNumber);
|
|
754
|
+
this.releaseSustainPedal(channelNumber, value);
|
|
811
755
|
}
|
|
812
756
|
}
|
|
813
757
|
setRPNMSB(channelNumber, value) {
|
|
@@ -909,13 +853,18 @@ export class MidyGM1 {
|
|
|
909
853
|
}
|
|
910
854
|
}
|
|
911
855
|
handleMasterVolumeSysEx(data) {
|
|
912
|
-
const volume = (data[5] * 128 + data[4]
|
|
856
|
+
const volume = (data[5] * 128 + data[4]) / 16383;
|
|
913
857
|
this.handleMasterVolume(volume);
|
|
914
858
|
}
|
|
915
859
|
handleMasterVolume(volume) {
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
860
|
+
if (volume < 0 && 1 < volume) {
|
|
861
|
+
console.error("Master Volume is out of range");
|
|
862
|
+
}
|
|
863
|
+
else {
|
|
864
|
+
const now = this.audioContext.currentTime;
|
|
865
|
+
this.masterGain.gain.cancelScheduledValues(now);
|
|
866
|
+
this.masterGain.gain.setValueAtTime(volume * volume, now);
|
|
867
|
+
}
|
|
919
868
|
}
|
|
920
869
|
handleExclusiveMessage(data) {
|
|
921
870
|
console.warn(`Unsupported Exclusive Message ${data}`);
|
|
@@ -947,7 +896,7 @@ Object.defineProperty(MidyGM1, "channelSettings", {
|
|
|
947
896
|
configurable: true,
|
|
948
897
|
writable: true,
|
|
949
898
|
value: {
|
|
950
|
-
volume:
|
|
899
|
+
volume: 100 / 127,
|
|
951
900
|
pan: 0,
|
|
952
901
|
vibratoRate: 5,
|
|
953
902
|
vibratoDepth: 0.5,
|
|
@@ -959,7 +908,7 @@ Object.defineProperty(MidyGM1, "channelSettings", {
|
|
|
959
908
|
pitchBend: 0,
|
|
960
909
|
fineTuning: 0,
|
|
961
910
|
coarseTuning: 0,
|
|
962
|
-
modulationDepthRange:
|
|
911
|
+
modulationDepthRange: 0.5,
|
|
963
912
|
}
|
|
964
913
|
});
|
|
965
914
|
Object.defineProperty(MidyGM1, "effectSettings", {
|