@marmooo/midy 0.3.2 → 0.3.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/esm/midy-GM1.d.ts +11 -10
- package/esm/midy-GM1.d.ts.map +1 -1
- package/esm/midy-GM1.js +114 -123
- package/esm/midy-GM2.d.ts +26 -42
- package/esm/midy-GM2.d.ts.map +1 -1
- package/esm/midy-GM2.js +295 -328
- package/esm/midy-GMLite.d.ts +12 -11
- package/esm/midy-GMLite.d.ts.map +1 -1
- package/esm/midy-GMLite.js +124 -141
- package/esm/midy.d.ts +27 -43
- package/esm/midy.d.ts.map +1 -1
- package/esm/midy.js +335 -352
- package/package.json +1 -1
- package/script/midy-GM1.d.ts +11 -10
- package/script/midy-GM1.d.ts.map +1 -1
- package/script/midy-GM1.js +114 -123
- package/script/midy-GM2.d.ts +26 -42
- package/script/midy-GM2.d.ts.map +1 -1
- package/script/midy-GM2.js +295 -328
- package/script/midy-GMLite.d.ts +12 -11
- package/script/midy-GMLite.d.ts.map +1 -1
- package/script/midy-GMLite.js +124 -141
- package/script/midy.d.ts +27 -43
- package/script/midy.d.ts.map +1 -1
- package/script/midy.js +335 -352
package/script/midy-GMLite.d.ts
CHANGED
|
@@ -53,16 +53,16 @@ export class MidyGMLite {
|
|
|
53
53
|
channels: any[];
|
|
54
54
|
initSoundFontTable(): any[];
|
|
55
55
|
addSoundFont(soundFont: any): void;
|
|
56
|
-
loadSoundFont(
|
|
57
|
-
loadMIDI(
|
|
58
|
-
|
|
56
|
+
loadSoundFont(input: any): Promise<void>;
|
|
57
|
+
loadMIDI(input: any): Promise<void>;
|
|
58
|
+
createChannelAudioNodes(audioContext: any): {
|
|
59
59
|
gainL: any;
|
|
60
60
|
gainR: any;
|
|
61
61
|
merger: any;
|
|
62
62
|
};
|
|
63
63
|
createChannels(audioContext: any): any[];
|
|
64
64
|
createNoteBuffer(voiceParams: any, isSF3: any): Promise<any>;
|
|
65
|
-
createBufferSource(voiceParams: any, audioBuffer: any): any;
|
|
65
|
+
createBufferSource(channel: any, voiceParams: any, audioBuffer: any): any;
|
|
66
66
|
scheduleTimelineEvents(t: any, resumeTime: any, queueIndex: any): Promise<any>;
|
|
67
67
|
getQueueIndex(second: any): number;
|
|
68
68
|
playNotes(): Promise<any>;
|
|
@@ -83,7 +83,7 @@ export class MidyGMLite {
|
|
|
83
83
|
seekTo(second: any): void;
|
|
84
84
|
calcTotalTime(): number;
|
|
85
85
|
currentTime(): number;
|
|
86
|
-
processScheduledNotes(channel: any, callback: any): void;
|
|
86
|
+
processScheduledNotes(channel: any, scheduleTime: any, callback: any): void;
|
|
87
87
|
processActiveNotes(channel: any, scheduleTime: any, callback: any): void;
|
|
88
88
|
cbToRatio(cb: any): number;
|
|
89
89
|
rateToCent(rate: any): number;
|
|
@@ -101,15 +101,15 @@ export class MidyGMLite {
|
|
|
101
101
|
createNote(channel: any, voice: any, noteNumber: any, velocity: any, startTime: any, isSF3: any): Promise<Note>;
|
|
102
102
|
handleExclusiveClass(note: any, channelNumber: any, startTime: any): void;
|
|
103
103
|
handleDrumExclusiveClass(note: any, channelNumber: any, startTime: any): void;
|
|
104
|
-
scheduleNoteOn(channelNumber: any, noteNumber: any, velocity: any, startTime: any
|
|
104
|
+
scheduleNoteOn(channelNumber: any, noteNumber: any, velocity: any, startTime: any): Promise<void>;
|
|
105
105
|
noteOn(channelNumber: any, noteNumber: any, velocity: any, scheduleTime: any): Promise<void>;
|
|
106
106
|
disconnectNote(note: any): void;
|
|
107
|
-
|
|
108
|
-
scheduleNoteOff(channelNumber: any,
|
|
107
|
+
releaseNote(channel: any, note: any, endTime: any): Promise<any>;
|
|
108
|
+
scheduleNoteOff(channelNumber: any, noteNumber: any, _velocity: any, endTime: any, force: any): void;
|
|
109
109
|
findNoteOffTarget(channel: any, noteNumber: any): any;
|
|
110
|
-
noteOff(channelNumber: any, noteNumber: any, velocity: any, scheduleTime: any):
|
|
111
|
-
releaseSustainPedal(channelNumber: any, halfVelocity: any, scheduleTime: any):
|
|
112
|
-
handleMIDIMessage(statusByte: any, data1: any, data2: any, scheduleTime: any): void | Promise<
|
|
110
|
+
noteOff(channelNumber: any, noteNumber: any, velocity: any, scheduleTime: any): void;
|
|
111
|
+
releaseSustainPedal(channelNumber: any, halfVelocity: any, scheduleTime: any): void[];
|
|
112
|
+
handleMIDIMessage(statusByte: any, data1: any, data2: any, scheduleTime: any): void | Promise<void>;
|
|
113
113
|
handleProgramChange(channelNumber: any, programNumber: any, _scheduleTime: any): void;
|
|
114
114
|
handlePitchBendMessage(channelNumber: any, lsb: any, msb: any, scheduleTime: any): void;
|
|
115
115
|
setPitchBend(channelNumber: any, value: any, scheduleTime: any): void;
|
|
@@ -168,6 +168,7 @@ export class MidyGMLite {
|
|
|
168
168
|
declare class Note {
|
|
169
169
|
constructor(noteNumber: any, velocity: any, startTime: any, voice: any, voiceParams: any);
|
|
170
170
|
index: number;
|
|
171
|
+
ending: boolean;
|
|
171
172
|
bufferSource: any;
|
|
172
173
|
filterNode: any;
|
|
173
174
|
filterDepth: any;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"midy-GMLite.d.ts","sourceRoot":"","sources":["../src/midy-GMLite.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"midy-GMLite.d.ts","sourceRoot":"","sources":["../src/midy-GMLite.js"],"names":[],"mappings":"AA0GA;IA2BE;;;;;;;;;MASE;IAEF,+BAcC;IAnDD,aAAa;IACb,oBAAiB;IACjB,qBAAmB;IACnB,kBAAc;IACd,0BAAwB;IACxB,kBAAc;IACd,mBAAiB;IACjB,kBAAc;IACd,mBAAe;IACf,kBAAgB;IAChB,sBAA2C;IAC3C,kCAA+B;IAC/B,gCAA6B;IAC7B,mBAAkB;IAClB,mBAAkB;IAClB,kBAAiB;IACjB,oBAAmB;IACnB,mBAAkB;IAClB,gBAAc;IACd,mBAAiB;IACjB,oBAAkB;IAClB,2BAAqC;IACrC,+BAEE;IAcA,kBAAgC;IAChC,kBAA8C;IAC9C,eAAwD;IACxD,qBAGE;IACF;;;;;;;;;;;MAA2D;IAC3D,6BAA+D;IAC/D,gBAAiD;IAMnD,4BAMC;IAED,mCAWC;IAED,yCAcC;IAED,oCAiBC;IAED;;;;MAeC;IAED,yCAaC;IAED,6DA2BC;IAED,0EAUC;IAED,+EAkDC;IAED,mCAOC;IAED,0BAiEC;IAED,uDAEC;IAED,wDAEC;IAED,6EAEC;IAED;;;MA6EC;IAED,kGAeC;IAED,mGAgBC;IAED,wEAMC;IAED,uBAKC;IAED,aAGC;IAED,cAKC;IAED,wBAIC;IAED,0BAKC;IAED,wBAOC;IAED,sBAGC;IAED,4EASC;IAED,yEASC;IAED,2BAEC;IAED,8BAEC;IAED,8BAEC;IAED,4BAEC;IAED,wCAIC;IAED,2DAIC;IAED,+DAIC;IAED,sDAeC;IAED,qDAoBC;IAED,6CAIC;IAED,sDAsBC;IAED,kEAoBC;IAED,+GA0BC;IAED,gHA6CC;IAED,0EAiBC;IAED,8EAiBC;IAED,kGAuCC;IAED,6FASC;IAED,gCASC;IAED,iEAoBC;IAED,qGAgBC;IAED,sDASC;IAED,qFASC;IAED,sFAeC;IAED,oGA2BC;IAED,sFAGC;IAED,wFAGC;IAED,sEAUC;IAED,mEAQC;IAED,wDAKC;IAED,sDAOC;IAED,mDAMC;IAED,kDAKC;IAED;;;;;;;;;;;MA2BC;IAED,oFAMC;IAED,6EAgCC;IAED,qCAeC;IAED,kGAWC;IAED,wDAUC;IAED,iFAKC;IAED,oEAKC;IAED;;;MAMC;IAED,8DAKC;IAED,4EAKC;IAED,sEAGC;IAED,2DAUC;IAED,yEAWC;IAED,kFAeC;IAED,uDAYC;IAED,gDAEC;IAED,gDAEC;IAED,sEAGC;IAED,qEAKC;IAED,2EAUC;IAED,gFAGC;IAED,yCAqBC;IAGD,8EAgCC;IAED,gFAGC;IAED,+EAgBC;IAED,qCAWC;IAED,4EAaC;IAED,4DAGC;IAED,sDASC;IAED,gDAYC;IAGD,6DAgBC;CACF;AAp9CD;IAWE,0FAMC;IAhBD,cAAW;IACX,gBAAe;IACf,kBAAa;IACb,gBAAW;IACX,iBAAY;IACZ,wBAAmB;IACnB,iBAAY;IACZ,mBAAc;IACd,qBAAgB;IAGd,gBAA4B;IAC5B,cAAwB;IACxB,eAA0B;IAC1B,WAAkB;IAClB,iBAA8B;CAEjC"}
|
package/script/midy-GMLite.js
CHANGED
|
@@ -11,6 +11,12 @@ class Note {
|
|
|
11
11
|
writable: true,
|
|
12
12
|
value: -1
|
|
13
13
|
});
|
|
14
|
+
Object.defineProperty(this, "ending", {
|
|
15
|
+
enumerable: true,
|
|
16
|
+
configurable: true,
|
|
17
|
+
writable: true,
|
|
18
|
+
value: false
|
|
19
|
+
});
|
|
14
20
|
Object.defineProperty(this, "bufferSource", {
|
|
15
21
|
enumerable: true,
|
|
16
22
|
configurable: true,
|
|
@@ -80,13 +86,11 @@ const defaultControllerState = {
|
|
|
80
86
|
pitchWheel: { type: 14, defaultValue: 8192 / 16383 },
|
|
81
87
|
pitchWheelSensitivity: { type: 16, defaultValue: 2 / 128 },
|
|
82
88
|
link: { type: 127, defaultValue: 0 },
|
|
83
|
-
// bankMSB: { type: 128 + 0, defaultValue: 121, },
|
|
84
89
|
modulationDepth: { type: 128 + 1, defaultValue: 0 },
|
|
85
90
|
// dataMSB: { type: 128 + 6, defaultValue: 0, },
|
|
86
91
|
volume: { type: 128 + 7, defaultValue: 100 / 127 },
|
|
87
92
|
pan: { type: 128 + 10, defaultValue: 64 / 127 },
|
|
88
93
|
expression: { type: 128 + 11, defaultValue: 1 },
|
|
89
|
-
// bankLSB: { type: 128 + 32, defaultValue: 0, },
|
|
90
94
|
// dataLSB: { type: 128 + 38, defaultValue: 0, },
|
|
91
95
|
sustainPedal: { type: 128 + 64, defaultValue: 0 },
|
|
92
96
|
// rpnLSB: { type: 128 + 100, defaultValue: 127 },
|
|
@@ -115,6 +119,16 @@ class ControllerState {
|
|
|
115
119
|
}
|
|
116
120
|
}
|
|
117
121
|
}
|
|
122
|
+
const volumeEnvelopeKeys = [
|
|
123
|
+
"volDelay",
|
|
124
|
+
"volAttack",
|
|
125
|
+
"volHold",
|
|
126
|
+
"volDecay",
|
|
127
|
+
"volSustain",
|
|
128
|
+
"volRelease",
|
|
129
|
+
"initialAttenuation",
|
|
130
|
+
];
|
|
131
|
+
const volumeEnvelopeKeySet = new Set(volumeEnvelopeKeys);
|
|
118
132
|
const filterEnvelopeKeys = [
|
|
119
133
|
"modEnvToPitch",
|
|
120
134
|
"initialFilterFc",
|
|
@@ -124,20 +138,18 @@ const filterEnvelopeKeys = [
|
|
|
124
138
|
"modHold",
|
|
125
139
|
"modDecay",
|
|
126
140
|
"modSustain",
|
|
127
|
-
"modRelease",
|
|
128
|
-
"playbackRate",
|
|
129
141
|
];
|
|
130
142
|
const filterEnvelopeKeySet = new Set(filterEnvelopeKeys);
|
|
131
|
-
const
|
|
132
|
-
"
|
|
133
|
-
"
|
|
134
|
-
"
|
|
135
|
-
"
|
|
136
|
-
"
|
|
137
|
-
"
|
|
138
|
-
"
|
|
143
|
+
const pitchEnvelopeKeys = [
|
|
144
|
+
"modEnvToPitch",
|
|
145
|
+
"modDelay",
|
|
146
|
+
"modAttack",
|
|
147
|
+
"modHold",
|
|
148
|
+
"modDecay",
|
|
149
|
+
"modSustain",
|
|
150
|
+
"playbackRate",
|
|
139
151
|
];
|
|
140
|
-
const
|
|
152
|
+
const pitchEnvelopeKeySet = new Set(pitchEnvelopeKeys);
|
|
141
153
|
class MidyGMLite {
|
|
142
154
|
constructor(audioContext) {
|
|
143
155
|
Object.defineProperty(this, "mode", {
|
|
@@ -311,24 +323,44 @@ class MidyGMLite {
|
|
|
311
323
|
}
|
|
312
324
|
}
|
|
313
325
|
}
|
|
314
|
-
async loadSoundFont(
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
326
|
+
async loadSoundFont(input) {
|
|
327
|
+
let uint8Array;
|
|
328
|
+
if (typeof input === "string") {
|
|
329
|
+
const response = await fetch(input);
|
|
330
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
331
|
+
uint8Array = new Uint8Array(arrayBuffer);
|
|
332
|
+
}
|
|
333
|
+
else if (input instanceof Uint8Array) {
|
|
334
|
+
uint8Array = input;
|
|
335
|
+
}
|
|
336
|
+
else {
|
|
337
|
+
throw new TypeError("input must be a URL string or Uint8Array");
|
|
338
|
+
}
|
|
339
|
+
const parsed = (0, soundfont_parser_1.parse)(uint8Array);
|
|
318
340
|
const soundFont = new soundfont_parser_1.SoundFont(parsed);
|
|
319
341
|
this.addSoundFont(soundFont);
|
|
320
342
|
}
|
|
321
|
-
async loadMIDI(
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
343
|
+
async loadMIDI(input) {
|
|
344
|
+
let uint8Array;
|
|
345
|
+
if (typeof input === "string") {
|
|
346
|
+
const response = await fetch(input);
|
|
347
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
348
|
+
uint8Array = new Uint8Array(arrayBuffer);
|
|
349
|
+
}
|
|
350
|
+
else if (input instanceof Uint8Array) {
|
|
351
|
+
uint8Array = input;
|
|
352
|
+
}
|
|
353
|
+
else {
|
|
354
|
+
throw new TypeError("input must be a URL string or Uint8Array");
|
|
355
|
+
}
|
|
356
|
+
const midi = (0, midi_file_1.parseMidi)(uint8Array);
|
|
325
357
|
this.ticksPerBeat = midi.header.ticksPerBeat;
|
|
326
358
|
const midiData = this.extractMidiData(midi);
|
|
327
359
|
this.instruments = midiData.instruments;
|
|
328
360
|
this.timeline = midiData.timeline;
|
|
329
361
|
this.totalTime = this.calcTotalTime();
|
|
330
362
|
}
|
|
331
|
-
|
|
363
|
+
createChannelAudioNodes(audioContext) {
|
|
332
364
|
const { gainLeft, gainRight } = this.panToGain(defaultControllerState.pan.defaultValue);
|
|
333
365
|
const gainL = new GainNode(audioContext, { gain: gainLeft });
|
|
334
366
|
const gainR = new GainNode(audioContext, { gain: gainRight });
|
|
@@ -349,7 +381,7 @@ class MidyGMLite {
|
|
|
349
381
|
isDrum: false,
|
|
350
382
|
state: new ControllerState(),
|
|
351
383
|
...this.constructor.channelSettings,
|
|
352
|
-
...this.
|
|
384
|
+
...this.createChannelAudioNodes(audioContext),
|
|
353
385
|
scheduledNotes: [],
|
|
354
386
|
sustainNotes: [],
|
|
355
387
|
};
|
|
@@ -385,10 +417,12 @@ class MidyGMLite {
|
|
|
385
417
|
return audioBuffer;
|
|
386
418
|
}
|
|
387
419
|
}
|
|
388
|
-
createBufferSource(voiceParams, audioBuffer) {
|
|
420
|
+
createBufferSource(channel, voiceParams, audioBuffer) {
|
|
389
421
|
const bufferSource = new AudioBufferSourceNode(this.audioContext);
|
|
390
422
|
bufferSource.buffer = audioBuffer;
|
|
391
423
|
bufferSource.loop = voiceParams.sampleModes % 2 !== 0;
|
|
424
|
+
if (channel.isDrum)
|
|
425
|
+
bufferSource.loop = false;
|
|
392
426
|
if (bufferSource.loop) {
|
|
393
427
|
bufferSource.loopStart = voiceParams.loopStart / voiceParams.sampleRate;
|
|
394
428
|
bufferSource.loopEnd = voiceParams.loopEnd / voiceParams.sampleRate;
|
|
@@ -403,12 +437,13 @@ class MidyGMLite {
|
|
|
403
437
|
const delay = this.startDelay - resumeTime;
|
|
404
438
|
const startTime = event.startTime + delay;
|
|
405
439
|
switch (event.type) {
|
|
406
|
-
case "noteOn":
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
440
|
+
case "noteOn":
|
|
441
|
+
await this.scheduleNoteOn(event.channel, event.noteNumber, event.velocity, startTime);
|
|
442
|
+
break;
|
|
443
|
+
case "noteOff": {
|
|
444
|
+
const notePromise = this.scheduleNoteOff(event.channel, event.noteNumber, event.velocity, startTime, false);
|
|
445
|
+
if (notePromise)
|
|
446
|
+
this.notePromises.push(notePromise);
|
|
412
447
|
break;
|
|
413
448
|
}
|
|
414
449
|
case "controller":
|
|
@@ -510,6 +545,7 @@ class MidyGMLite {
|
|
|
510
545
|
return `${programNumber}:${noteNumber}:${velocity}`;
|
|
511
546
|
}
|
|
512
547
|
extractMidiData(midi) {
|
|
548
|
+
this.audioBufferCounter.clear();
|
|
513
549
|
const instruments = new Set();
|
|
514
550
|
const timeline = [];
|
|
515
551
|
const tmpChannels = new Array(this.channels.length);
|
|
@@ -573,38 +609,13 @@ class MidyGMLite {
|
|
|
573
609
|
prevTempoTicks = event.ticks;
|
|
574
610
|
}
|
|
575
611
|
}
|
|
576
|
-
const activeNotes = new Array(this.channels.length * 128);
|
|
577
|
-
for (let i = 0; i < activeNotes.length; i++) {
|
|
578
|
-
activeNotes[i] = [];
|
|
579
|
-
}
|
|
580
|
-
for (let i = 0; i < timeline.length; i++) {
|
|
581
|
-
const event = timeline[i];
|
|
582
|
-
switch (event.type) {
|
|
583
|
-
case "noteOn": {
|
|
584
|
-
const index = event.channel * 128 + event.noteNumber;
|
|
585
|
-
activeNotes[index].push(event);
|
|
586
|
-
break;
|
|
587
|
-
}
|
|
588
|
-
case "noteOff": {
|
|
589
|
-
const index = event.channel * 128 + event.noteNumber;
|
|
590
|
-
const noteOn = activeNotes[index].pop();
|
|
591
|
-
if (noteOn) {
|
|
592
|
-
noteOn.noteOffEvent = event;
|
|
593
|
-
}
|
|
594
|
-
else {
|
|
595
|
-
const eventString = JSON.stringify(event, null, 2);
|
|
596
|
-
console.warn(`noteOff without matching noteOn: ${eventString}`);
|
|
597
|
-
}
|
|
598
|
-
}
|
|
599
|
-
}
|
|
600
|
-
}
|
|
601
612
|
return { instruments, timeline };
|
|
602
613
|
}
|
|
603
614
|
stopActiveNotes(channelNumber, velocity, force, scheduleTime) {
|
|
604
615
|
const channel = this.channels[channelNumber];
|
|
605
616
|
const promises = [];
|
|
606
617
|
this.processActiveNotes(channel, scheduleTime, (note) => {
|
|
607
|
-
const promise = this.scheduleNoteOff(channelNumber, note.noteNumber, velocity, scheduleTime, force
|
|
618
|
+
const promise = this.scheduleNoteOff(channelNumber, note.noteNumber, velocity, scheduleTime, force);
|
|
608
619
|
this.notePromises.push(promise);
|
|
609
620
|
promises.push(promise);
|
|
610
621
|
});
|
|
@@ -613,8 +624,8 @@ class MidyGMLite {
|
|
|
613
624
|
stopChannelNotes(channelNumber, velocity, force, scheduleTime) {
|
|
614
625
|
const channel = this.channels[channelNumber];
|
|
615
626
|
const promises = [];
|
|
616
|
-
this.processScheduledNotes(channel, (note) => {
|
|
617
|
-
const promise = this.scheduleNoteOff(channelNumber, note, velocity, scheduleTime, force);
|
|
627
|
+
this.processScheduledNotes(channel, scheduleTime, (note) => {
|
|
628
|
+
const promise = this.scheduleNoteOff(channelNumber, note.noteNumber, velocity, scheduleTime, force);
|
|
618
629
|
this.notePromises.push(promise);
|
|
619
630
|
promises.push(promise);
|
|
620
631
|
});
|
|
@@ -672,7 +683,7 @@ class MidyGMLite {
|
|
|
672
683
|
const now = this.audioContext.currentTime;
|
|
673
684
|
return this.resumeTime + now - this.startTime - this.startDelay;
|
|
674
685
|
}
|
|
675
|
-
processScheduledNotes(channel, callback) {
|
|
686
|
+
processScheduledNotes(channel, scheduleTime, callback) {
|
|
676
687
|
const scheduledNotes = channel.scheduledNotes;
|
|
677
688
|
for (let i = 0; i < scheduledNotes.length; i++) {
|
|
678
689
|
const note = scheduledNotes[i];
|
|
@@ -680,6 +691,8 @@ class MidyGMLite {
|
|
|
680
691
|
continue;
|
|
681
692
|
if (note.ending)
|
|
682
693
|
continue;
|
|
694
|
+
if (note.startTime < scheduleTime)
|
|
695
|
+
continue;
|
|
683
696
|
callback(note);
|
|
684
697
|
}
|
|
685
698
|
}
|
|
@@ -691,11 +704,8 @@ class MidyGMLite {
|
|
|
691
704
|
continue;
|
|
692
705
|
if (note.ending)
|
|
693
706
|
continue;
|
|
694
|
-
const noteOffEvent = note.noteOffEvent;
|
|
695
|
-
if (noteOffEvent && noteOffEvent.startTime < scheduleTime)
|
|
696
|
-
continue;
|
|
697
707
|
if (scheduleTime < note.startTime)
|
|
698
|
-
|
|
708
|
+
break;
|
|
699
709
|
callback(note);
|
|
700
710
|
}
|
|
701
711
|
}
|
|
@@ -717,7 +727,7 @@ class MidyGMLite {
|
|
|
717
727
|
return pitchWheel * pitchWheelSensitivity;
|
|
718
728
|
}
|
|
719
729
|
updateChannelDetune(channel, scheduleTime) {
|
|
720
|
-
this.processScheduledNotes(channel, (note) => {
|
|
730
|
+
this.processScheduledNotes(channel, scheduleTime, (note) => {
|
|
721
731
|
this.updateDetune(channel, note, scheduleTime);
|
|
722
732
|
});
|
|
723
733
|
}
|
|
@@ -835,7 +845,7 @@ class MidyGMLite {
|
|
|
835
845
|
const voiceParams = voice.getAllParams(controllerState);
|
|
836
846
|
const note = new Note(noteNumber, velocity, startTime, voice, voiceParams);
|
|
837
847
|
const audioBuffer = await this.getAudioBuffer(channel.programNumber, noteNumber, velocity, voiceParams, isSF3);
|
|
838
|
-
note.bufferSource = this.createBufferSource(voiceParams, audioBuffer);
|
|
848
|
+
note.bufferSource = this.createBufferSource(channel, voiceParams, audioBuffer);
|
|
839
849
|
note.volumeEnvelopeNode = new GainNode(this.audioContext);
|
|
840
850
|
note.filterNode = new BiquadFilterNode(this.audioContext, {
|
|
841
851
|
type: "lowpass",
|
|
@@ -861,7 +871,7 @@ class MidyGMLite {
|
|
|
861
871
|
if (prev) {
|
|
862
872
|
const [prevNote, prevChannelNumber] = prev;
|
|
863
873
|
if (prevNote && !prevNote.ending) {
|
|
864
|
-
this.scheduleNoteOff(prevChannelNumber, prevNote, 0, // velocity,
|
|
874
|
+
this.scheduleNoteOff(prevChannelNumber, prevNote.noteNumber, 0, // velocity,
|
|
865
875
|
startTime, true);
|
|
866
876
|
}
|
|
867
877
|
}
|
|
@@ -877,12 +887,12 @@ class MidyGMLite {
|
|
|
877
887
|
const index = drumExclusiveClass * this.channels.length + channelNumber;
|
|
878
888
|
const prevNote = this.drumExclusiveClassNotes[index];
|
|
879
889
|
if (prevNote && !prevNote.ending) {
|
|
880
|
-
this.scheduleNoteOff(channelNumber, prevNote, 0, // velocity,
|
|
890
|
+
this.scheduleNoteOff(channelNumber, prevNote.noteNumber, 0, // velocity,
|
|
881
891
|
startTime, true);
|
|
882
892
|
}
|
|
883
893
|
this.drumExclusiveClassNotes[index] = note;
|
|
884
894
|
}
|
|
885
|
-
async scheduleNoteOn(channelNumber, noteNumber, velocity, startTime
|
|
895
|
+
async scheduleNoteOn(channelNumber, noteNumber, velocity, startTime) {
|
|
886
896
|
const channel = this.channels[channelNumber];
|
|
887
897
|
const bankNumber = channel.bank;
|
|
888
898
|
const soundFontIndex = this.soundFontTable[channel.programNumber].get(bankNumber);
|
|
@@ -894,7 +904,6 @@ class MidyGMLite {
|
|
|
894
904
|
return;
|
|
895
905
|
const isSF3 = soundFont.parsed.info.version.major === 3;
|
|
896
906
|
const note = await this.createNote(channel, voice, noteNumber, velocity, startTime, isSF3);
|
|
897
|
-
note.noteOffEvent = noteOffEvent;
|
|
898
907
|
note.volumeEnvelopeNode.connect(channel.gainL);
|
|
899
908
|
note.volumeEnvelopeNode.connect(channel.gainR);
|
|
900
909
|
if (0.5 <= channel.state.sustainPedal) {
|
|
@@ -905,24 +914,6 @@ class MidyGMLite {
|
|
|
905
914
|
const scheduledNotes = channel.scheduledNotes;
|
|
906
915
|
note.index = scheduledNotes.length;
|
|
907
916
|
scheduledNotes.push(note);
|
|
908
|
-
if (channel.isDrum) {
|
|
909
|
-
const stopTime = startTime + note.bufferSource.buffer.duration;
|
|
910
|
-
const promise = new Promise((resolve) => {
|
|
911
|
-
note.bufferSource.onended = () => {
|
|
912
|
-
scheduledNotes[note.index] = undefined;
|
|
913
|
-
this.disconnectNote(note);
|
|
914
|
-
resolve();
|
|
915
|
-
};
|
|
916
|
-
note.bufferSource.stop(stopTime);
|
|
917
|
-
});
|
|
918
|
-
this.notePromises.push(promise);
|
|
919
|
-
}
|
|
920
|
-
else if (noteOffEvent) {
|
|
921
|
-
const notePromise = this.scheduleNoteOff(channelNumber, note, noteOffEvent.velocity, noteOffEvent.startTime, false);
|
|
922
|
-
if (notePromise) {
|
|
923
|
-
this.notePromises.push(notePromise);
|
|
924
|
-
}
|
|
925
|
-
}
|
|
926
917
|
}
|
|
927
918
|
noteOn(channelNumber, noteNumber, velocity, scheduleTime) {
|
|
928
919
|
scheduleTime ??= this.audioContext.currentTime;
|
|
@@ -938,36 +929,40 @@ class MidyGMLite {
|
|
|
938
929
|
note.modulationLFO.stop();
|
|
939
930
|
}
|
|
940
931
|
}
|
|
941
|
-
|
|
932
|
+
releaseNote(channel, note, endTime) {
|
|
933
|
+
const volRelease = endTime + note.voiceParams.volRelease;
|
|
934
|
+
const modRelease = endTime + note.voiceParams.modRelease;
|
|
935
|
+
const stopTime = Math.min(volRelease, modRelease);
|
|
936
|
+
note.filterNode.frequency
|
|
937
|
+
.cancelScheduledValues(endTime)
|
|
938
|
+
.linearRampToValueAtTime(0, modRelease);
|
|
942
939
|
note.volumeEnvelopeNode.gain
|
|
943
940
|
.cancelScheduledValues(endTime)
|
|
944
|
-
.linearRampToValueAtTime(0,
|
|
945
|
-
note.ending = true;
|
|
946
|
-
this.scheduleTask(() => {
|
|
947
|
-
note.bufferSource.loop = false;
|
|
948
|
-
}, stopTime);
|
|
941
|
+
.linearRampToValueAtTime(0, volRelease);
|
|
949
942
|
return new Promise((resolve) => {
|
|
950
|
-
|
|
951
|
-
|
|
943
|
+
this.scheduleTask(() => {
|
|
944
|
+
const bufferSource = note.bufferSource;
|
|
945
|
+
bufferSource.loop = false;
|
|
946
|
+
bufferSource.stop(stopTime);
|
|
952
947
|
this.disconnectNote(note);
|
|
948
|
+
channel.scheduledNotes[note.index] = undefined;
|
|
953
949
|
resolve();
|
|
954
|
-
};
|
|
955
|
-
note.bufferSource.stop(stopTime);
|
|
950
|
+
}, stopTime);
|
|
956
951
|
});
|
|
957
952
|
}
|
|
958
|
-
scheduleNoteOff(channelNumber,
|
|
953
|
+
scheduleNoteOff(channelNumber, noteNumber, _velocity, endTime, force) {
|
|
959
954
|
const channel = this.channels[channelNumber];
|
|
960
|
-
if (
|
|
961
|
-
|
|
962
|
-
|
|
955
|
+
if (!force) {
|
|
956
|
+
if (channel.isDrum)
|
|
957
|
+
return;
|
|
958
|
+
if (0.5 <= channel.state.sustainPedal)
|
|
959
|
+
return;
|
|
960
|
+
}
|
|
961
|
+
const note = this.findNoteOffTarget(channel, noteNumber);
|
|
962
|
+
if (!note)
|
|
963
963
|
return;
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
note.filterNode.frequency
|
|
967
|
-
.cancelScheduledValues(endTime)
|
|
968
|
-
.linearRampToValueAtTime(0, modRelease);
|
|
969
|
-
const stopTime = Math.min(volRelease, modRelease);
|
|
970
|
-
return this.stopNote(channel, note, endTime, stopTime);
|
|
964
|
+
note.ending = true;
|
|
965
|
+
this.releaseNote(channel, note, endTime);
|
|
971
966
|
}
|
|
972
967
|
findNoteOffTarget(channel, noteNumber) {
|
|
973
968
|
const scheduledNotes = channel.scheduledNotes;
|
|
@@ -984,16 +979,14 @@ class MidyGMLite {
|
|
|
984
979
|
}
|
|
985
980
|
noteOff(channelNumber, noteNumber, velocity, scheduleTime) {
|
|
986
981
|
scheduleTime ??= this.audioContext.currentTime;
|
|
987
|
-
|
|
988
|
-
const note = this.findNoteOffTarget(channel, noteNumber);
|
|
989
|
-
return this.scheduleNoteOff(channelNumber, note, velocity, scheduleTime, false);
|
|
982
|
+
return this.scheduleNoteOff(channelNumber, noteNumber, velocity, scheduleTime, false);
|
|
990
983
|
}
|
|
991
984
|
releaseSustainPedal(channelNumber, halfVelocity, scheduleTime) {
|
|
992
985
|
const velocity = halfVelocity * 2;
|
|
993
986
|
const channel = this.channels[channelNumber];
|
|
994
987
|
const promises = [];
|
|
995
988
|
for (let i = 0; i < channel.sustainNotes.length; i++) {
|
|
996
|
-
const promise = this.scheduleNoteOff(channelNumber, channel.sustainNotes[i], velocity, scheduleTime);
|
|
989
|
+
const promise = this.scheduleNoteOff(channelNumber, channel.sustainNotes[i].noteNumber, velocity, scheduleTime);
|
|
997
990
|
promises.push(promise);
|
|
998
991
|
}
|
|
999
992
|
channel.sustainNotes = [];
|
|
@@ -1107,11 +1100,12 @@ class MidyGMLite {
|
|
|
1107
1100
|
return state;
|
|
1108
1101
|
}
|
|
1109
1102
|
applyVoiceParams(channel, controllerType, scheduleTime) {
|
|
1110
|
-
this.processScheduledNotes(channel, (note) => {
|
|
1103
|
+
this.processScheduledNotes(channel, scheduleTime, (note) => {
|
|
1111
1104
|
const controllerState = this.getControllerState(channel, note.noteNumber, note.velocity);
|
|
1112
1105
|
const voiceParams = note.voice.getParams(controllerType, controllerState);
|
|
1113
|
-
let
|
|
1114
|
-
let
|
|
1106
|
+
let applyVolumeEnvelope = false;
|
|
1107
|
+
let applyFilterEnvelope = false;
|
|
1108
|
+
let applyPitchEnvelope = false;
|
|
1115
1109
|
for (const [key, value] of Object.entries(voiceParams)) {
|
|
1116
1110
|
const prevValue = note.voiceParams[key];
|
|
1117
1111
|
if (value === prevValue)
|
|
@@ -1120,32 +1114,21 @@ class MidyGMLite {
|
|
|
1120
1114
|
if (key in this.voiceParamsHandlers) {
|
|
1121
1115
|
this.voiceParamsHandlers[key](channel, note, prevValue, scheduleTime);
|
|
1122
1116
|
}
|
|
1123
|
-
else
|
|
1124
|
-
if (
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
if (key in voiceParams)
|
|
1131
|
-
noteVoiceParams[key] = voiceParams[key];
|
|
1132
|
-
}
|
|
1133
|
-
this.setFilterEnvelope(note, scheduleTime);
|
|
1134
|
-
this.setPitchEnvelope(note, scheduleTime);
|
|
1135
|
-
}
|
|
1136
|
-
else if (volumeEnvelopeKeySet.has(key)) {
|
|
1137
|
-
if (appliedVolumeEnvelope)
|
|
1138
|
-
continue;
|
|
1139
|
-
appliedVolumeEnvelope = true;
|
|
1140
|
-
const noteVoiceParams = note.voiceParams;
|
|
1141
|
-
for (let i = 0; i < volumeEnvelopeKeys.length; i++) {
|
|
1142
|
-
const key = volumeEnvelopeKeys[i];
|
|
1143
|
-
if (key in voiceParams)
|
|
1144
|
-
noteVoiceParams[key] = voiceParams[key];
|
|
1145
|
-
}
|
|
1146
|
-
this.setVolumeEnvelope(note, scheduleTime);
|
|
1117
|
+
else {
|
|
1118
|
+
if (volumeEnvelopeKeySet.has(key))
|
|
1119
|
+
applyVolumeEnvelope = true;
|
|
1120
|
+
if (filterEnvelopeKeySet.has(key))
|
|
1121
|
+
applyFilterEnvelope = true;
|
|
1122
|
+
if (pitchEnvelopeKeySet.has(key))
|
|
1123
|
+
applyPitchEnvelope = true;
|
|
1147
1124
|
}
|
|
1148
1125
|
}
|
|
1126
|
+
if (applyVolumeEnvelope)
|
|
1127
|
+
this.setVolumeEnvelope(note, scheduleTime);
|
|
1128
|
+
if (applyFilterEnvelope)
|
|
1129
|
+
this.setFilterEnvelope(note, scheduleTime);
|
|
1130
|
+
if (applyPitchEnvelope)
|
|
1131
|
+
this.setPitchEnvelope(note, scheduleTime);
|
|
1149
1132
|
});
|
|
1150
1133
|
}
|
|
1151
1134
|
createControlChangeHandlers() {
|
|
@@ -1177,7 +1160,7 @@ class MidyGMLite {
|
|
|
1177
1160
|
}
|
|
1178
1161
|
updateModulation(channel, scheduleTime) {
|
|
1179
1162
|
const depth = channel.state.modulationDepth * channel.modulationDepthRange;
|
|
1180
|
-
this.processScheduledNotes(channel, (note) => {
|
|
1163
|
+
this.processScheduledNotes(channel, scheduleTime, (note) => {
|
|
1181
1164
|
if (note.modulationDepth) {
|
|
1182
1165
|
note.modulationDepth.gain.setValueAtTime(depth, scheduleTime);
|
|
1183
1166
|
}
|
|
@@ -1238,7 +1221,7 @@ class MidyGMLite {
|
|
|
1238
1221
|
scheduleTime ??= this.audioContext.currentTime;
|
|
1239
1222
|
channel.state.sustainPedal = value / 127;
|
|
1240
1223
|
if (64 <= value) {
|
|
1241
|
-
this.processScheduledNotes(channel, (note) => {
|
|
1224
|
+
this.processScheduledNotes(channel, scheduleTime, (note) => {
|
|
1242
1225
|
channel.sustainNotes.push(note);
|
|
1243
1226
|
});
|
|
1244
1227
|
}
|