@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/esm/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;
|
package/esm/midy-GMLite.d.ts.map
CHANGED
|
@@ -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/esm/midy-GMLite.js
CHANGED
|
@@ -8,6 +8,12 @@ class Note {
|
|
|
8
8
|
writable: true,
|
|
9
9
|
value: -1
|
|
10
10
|
});
|
|
11
|
+
Object.defineProperty(this, "ending", {
|
|
12
|
+
enumerable: true,
|
|
13
|
+
configurable: true,
|
|
14
|
+
writable: true,
|
|
15
|
+
value: false
|
|
16
|
+
});
|
|
11
17
|
Object.defineProperty(this, "bufferSource", {
|
|
12
18
|
enumerable: true,
|
|
13
19
|
configurable: true,
|
|
@@ -77,13 +83,11 @@ const defaultControllerState = {
|
|
|
77
83
|
pitchWheel: { type: 14, defaultValue: 8192 / 16383 },
|
|
78
84
|
pitchWheelSensitivity: { type: 16, defaultValue: 2 / 128 },
|
|
79
85
|
link: { type: 127, defaultValue: 0 },
|
|
80
|
-
// bankMSB: { type: 128 + 0, defaultValue: 121, },
|
|
81
86
|
modulationDepth: { type: 128 + 1, defaultValue: 0 },
|
|
82
87
|
// dataMSB: { type: 128 + 6, defaultValue: 0, },
|
|
83
88
|
volume: { type: 128 + 7, defaultValue: 100 / 127 },
|
|
84
89
|
pan: { type: 128 + 10, defaultValue: 64 / 127 },
|
|
85
90
|
expression: { type: 128 + 11, defaultValue: 1 },
|
|
86
|
-
// bankLSB: { type: 128 + 32, defaultValue: 0, },
|
|
87
91
|
// dataLSB: { type: 128 + 38, defaultValue: 0, },
|
|
88
92
|
sustainPedal: { type: 128 + 64, defaultValue: 0 },
|
|
89
93
|
// rpnLSB: { type: 128 + 100, defaultValue: 127 },
|
|
@@ -112,6 +116,16 @@ class ControllerState {
|
|
|
112
116
|
}
|
|
113
117
|
}
|
|
114
118
|
}
|
|
119
|
+
const volumeEnvelopeKeys = [
|
|
120
|
+
"volDelay",
|
|
121
|
+
"volAttack",
|
|
122
|
+
"volHold",
|
|
123
|
+
"volDecay",
|
|
124
|
+
"volSustain",
|
|
125
|
+
"volRelease",
|
|
126
|
+
"initialAttenuation",
|
|
127
|
+
];
|
|
128
|
+
const volumeEnvelopeKeySet = new Set(volumeEnvelopeKeys);
|
|
115
129
|
const filterEnvelopeKeys = [
|
|
116
130
|
"modEnvToPitch",
|
|
117
131
|
"initialFilterFc",
|
|
@@ -121,20 +135,18 @@ const filterEnvelopeKeys = [
|
|
|
121
135
|
"modHold",
|
|
122
136
|
"modDecay",
|
|
123
137
|
"modSustain",
|
|
124
|
-
"modRelease",
|
|
125
|
-
"playbackRate",
|
|
126
138
|
];
|
|
127
139
|
const filterEnvelopeKeySet = new Set(filterEnvelopeKeys);
|
|
128
|
-
const
|
|
129
|
-
"
|
|
130
|
-
"
|
|
131
|
-
"
|
|
132
|
-
"
|
|
133
|
-
"
|
|
134
|
-
"
|
|
135
|
-
"
|
|
140
|
+
const pitchEnvelopeKeys = [
|
|
141
|
+
"modEnvToPitch",
|
|
142
|
+
"modDelay",
|
|
143
|
+
"modAttack",
|
|
144
|
+
"modHold",
|
|
145
|
+
"modDecay",
|
|
146
|
+
"modSustain",
|
|
147
|
+
"playbackRate",
|
|
136
148
|
];
|
|
137
|
-
const
|
|
149
|
+
const pitchEnvelopeKeySet = new Set(pitchEnvelopeKeys);
|
|
138
150
|
export class MidyGMLite {
|
|
139
151
|
constructor(audioContext) {
|
|
140
152
|
Object.defineProperty(this, "mode", {
|
|
@@ -308,24 +320,44 @@ export class MidyGMLite {
|
|
|
308
320
|
}
|
|
309
321
|
}
|
|
310
322
|
}
|
|
311
|
-
async loadSoundFont(
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
323
|
+
async loadSoundFont(input) {
|
|
324
|
+
let uint8Array;
|
|
325
|
+
if (typeof input === "string") {
|
|
326
|
+
const response = await fetch(input);
|
|
327
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
328
|
+
uint8Array = new Uint8Array(arrayBuffer);
|
|
329
|
+
}
|
|
330
|
+
else if (input instanceof Uint8Array) {
|
|
331
|
+
uint8Array = input;
|
|
332
|
+
}
|
|
333
|
+
else {
|
|
334
|
+
throw new TypeError("input must be a URL string or Uint8Array");
|
|
335
|
+
}
|
|
336
|
+
const parsed = parse(uint8Array);
|
|
315
337
|
const soundFont = new SoundFont(parsed);
|
|
316
338
|
this.addSoundFont(soundFont);
|
|
317
339
|
}
|
|
318
|
-
async loadMIDI(
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
340
|
+
async loadMIDI(input) {
|
|
341
|
+
let uint8Array;
|
|
342
|
+
if (typeof input === "string") {
|
|
343
|
+
const response = await fetch(input);
|
|
344
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
345
|
+
uint8Array = new Uint8Array(arrayBuffer);
|
|
346
|
+
}
|
|
347
|
+
else if (input instanceof Uint8Array) {
|
|
348
|
+
uint8Array = input;
|
|
349
|
+
}
|
|
350
|
+
else {
|
|
351
|
+
throw new TypeError("input must be a URL string or Uint8Array");
|
|
352
|
+
}
|
|
353
|
+
const midi = parseMidi(uint8Array);
|
|
322
354
|
this.ticksPerBeat = midi.header.ticksPerBeat;
|
|
323
355
|
const midiData = this.extractMidiData(midi);
|
|
324
356
|
this.instruments = midiData.instruments;
|
|
325
357
|
this.timeline = midiData.timeline;
|
|
326
358
|
this.totalTime = this.calcTotalTime();
|
|
327
359
|
}
|
|
328
|
-
|
|
360
|
+
createChannelAudioNodes(audioContext) {
|
|
329
361
|
const { gainLeft, gainRight } = this.panToGain(defaultControllerState.pan.defaultValue);
|
|
330
362
|
const gainL = new GainNode(audioContext, { gain: gainLeft });
|
|
331
363
|
const gainR = new GainNode(audioContext, { gain: gainRight });
|
|
@@ -346,7 +378,7 @@ export class MidyGMLite {
|
|
|
346
378
|
isDrum: false,
|
|
347
379
|
state: new ControllerState(),
|
|
348
380
|
...this.constructor.channelSettings,
|
|
349
|
-
...this.
|
|
381
|
+
...this.createChannelAudioNodes(audioContext),
|
|
350
382
|
scheduledNotes: [],
|
|
351
383
|
sustainNotes: [],
|
|
352
384
|
};
|
|
@@ -382,10 +414,12 @@ export class MidyGMLite {
|
|
|
382
414
|
return audioBuffer;
|
|
383
415
|
}
|
|
384
416
|
}
|
|
385
|
-
createBufferSource(voiceParams, audioBuffer) {
|
|
417
|
+
createBufferSource(channel, voiceParams, audioBuffer) {
|
|
386
418
|
const bufferSource = new AudioBufferSourceNode(this.audioContext);
|
|
387
419
|
bufferSource.buffer = audioBuffer;
|
|
388
420
|
bufferSource.loop = voiceParams.sampleModes % 2 !== 0;
|
|
421
|
+
if (channel.isDrum)
|
|
422
|
+
bufferSource.loop = false;
|
|
389
423
|
if (bufferSource.loop) {
|
|
390
424
|
bufferSource.loopStart = voiceParams.loopStart / voiceParams.sampleRate;
|
|
391
425
|
bufferSource.loopEnd = voiceParams.loopEnd / voiceParams.sampleRate;
|
|
@@ -400,12 +434,13 @@ export class MidyGMLite {
|
|
|
400
434
|
const delay = this.startDelay - resumeTime;
|
|
401
435
|
const startTime = event.startTime + delay;
|
|
402
436
|
switch (event.type) {
|
|
403
|
-
case "noteOn":
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
437
|
+
case "noteOn":
|
|
438
|
+
await this.scheduleNoteOn(event.channel, event.noteNumber, event.velocity, startTime);
|
|
439
|
+
break;
|
|
440
|
+
case "noteOff": {
|
|
441
|
+
const notePromise = this.scheduleNoteOff(event.channel, event.noteNumber, event.velocity, startTime, false);
|
|
442
|
+
if (notePromise)
|
|
443
|
+
this.notePromises.push(notePromise);
|
|
409
444
|
break;
|
|
410
445
|
}
|
|
411
446
|
case "controller":
|
|
@@ -507,6 +542,7 @@ export class MidyGMLite {
|
|
|
507
542
|
return `${programNumber}:${noteNumber}:${velocity}`;
|
|
508
543
|
}
|
|
509
544
|
extractMidiData(midi) {
|
|
545
|
+
this.audioBufferCounter.clear();
|
|
510
546
|
const instruments = new Set();
|
|
511
547
|
const timeline = [];
|
|
512
548
|
const tmpChannels = new Array(this.channels.length);
|
|
@@ -570,38 +606,13 @@ export class MidyGMLite {
|
|
|
570
606
|
prevTempoTicks = event.ticks;
|
|
571
607
|
}
|
|
572
608
|
}
|
|
573
|
-
const activeNotes = new Array(this.channels.length * 128);
|
|
574
|
-
for (let i = 0; i < activeNotes.length; i++) {
|
|
575
|
-
activeNotes[i] = [];
|
|
576
|
-
}
|
|
577
|
-
for (let i = 0; i < timeline.length; i++) {
|
|
578
|
-
const event = timeline[i];
|
|
579
|
-
switch (event.type) {
|
|
580
|
-
case "noteOn": {
|
|
581
|
-
const index = event.channel * 128 + event.noteNumber;
|
|
582
|
-
activeNotes[index].push(event);
|
|
583
|
-
break;
|
|
584
|
-
}
|
|
585
|
-
case "noteOff": {
|
|
586
|
-
const index = event.channel * 128 + event.noteNumber;
|
|
587
|
-
const noteOn = activeNotes[index].pop();
|
|
588
|
-
if (noteOn) {
|
|
589
|
-
noteOn.noteOffEvent = event;
|
|
590
|
-
}
|
|
591
|
-
else {
|
|
592
|
-
const eventString = JSON.stringify(event, null, 2);
|
|
593
|
-
console.warn(`noteOff without matching noteOn: ${eventString}`);
|
|
594
|
-
}
|
|
595
|
-
}
|
|
596
|
-
}
|
|
597
|
-
}
|
|
598
609
|
return { instruments, timeline };
|
|
599
610
|
}
|
|
600
611
|
stopActiveNotes(channelNumber, velocity, force, scheduleTime) {
|
|
601
612
|
const channel = this.channels[channelNumber];
|
|
602
613
|
const promises = [];
|
|
603
614
|
this.processActiveNotes(channel, scheduleTime, (note) => {
|
|
604
|
-
const promise = this.scheduleNoteOff(channelNumber, note.noteNumber, velocity, scheduleTime, force
|
|
615
|
+
const promise = this.scheduleNoteOff(channelNumber, note.noteNumber, velocity, scheduleTime, force);
|
|
605
616
|
this.notePromises.push(promise);
|
|
606
617
|
promises.push(promise);
|
|
607
618
|
});
|
|
@@ -610,8 +621,8 @@ export class MidyGMLite {
|
|
|
610
621
|
stopChannelNotes(channelNumber, velocity, force, scheduleTime) {
|
|
611
622
|
const channel = this.channels[channelNumber];
|
|
612
623
|
const promises = [];
|
|
613
|
-
this.processScheduledNotes(channel, (note) => {
|
|
614
|
-
const promise = this.scheduleNoteOff(channelNumber, note, velocity, scheduleTime, force);
|
|
624
|
+
this.processScheduledNotes(channel, scheduleTime, (note) => {
|
|
625
|
+
const promise = this.scheduleNoteOff(channelNumber, note.noteNumber, velocity, scheduleTime, force);
|
|
615
626
|
this.notePromises.push(promise);
|
|
616
627
|
promises.push(promise);
|
|
617
628
|
});
|
|
@@ -669,7 +680,7 @@ export class MidyGMLite {
|
|
|
669
680
|
const now = this.audioContext.currentTime;
|
|
670
681
|
return this.resumeTime + now - this.startTime - this.startDelay;
|
|
671
682
|
}
|
|
672
|
-
processScheduledNotes(channel, callback) {
|
|
683
|
+
processScheduledNotes(channel, scheduleTime, callback) {
|
|
673
684
|
const scheduledNotes = channel.scheduledNotes;
|
|
674
685
|
for (let i = 0; i < scheduledNotes.length; i++) {
|
|
675
686
|
const note = scheduledNotes[i];
|
|
@@ -677,6 +688,8 @@ export class MidyGMLite {
|
|
|
677
688
|
continue;
|
|
678
689
|
if (note.ending)
|
|
679
690
|
continue;
|
|
691
|
+
if (note.startTime < scheduleTime)
|
|
692
|
+
continue;
|
|
680
693
|
callback(note);
|
|
681
694
|
}
|
|
682
695
|
}
|
|
@@ -688,11 +701,8 @@ export class MidyGMLite {
|
|
|
688
701
|
continue;
|
|
689
702
|
if (note.ending)
|
|
690
703
|
continue;
|
|
691
|
-
const noteOffEvent = note.noteOffEvent;
|
|
692
|
-
if (noteOffEvent && noteOffEvent.startTime < scheduleTime)
|
|
693
|
-
continue;
|
|
694
704
|
if (scheduleTime < note.startTime)
|
|
695
|
-
|
|
705
|
+
break;
|
|
696
706
|
callback(note);
|
|
697
707
|
}
|
|
698
708
|
}
|
|
@@ -714,7 +724,7 @@ export class MidyGMLite {
|
|
|
714
724
|
return pitchWheel * pitchWheelSensitivity;
|
|
715
725
|
}
|
|
716
726
|
updateChannelDetune(channel, scheduleTime) {
|
|
717
|
-
this.processScheduledNotes(channel, (note) => {
|
|
727
|
+
this.processScheduledNotes(channel, scheduleTime, (note) => {
|
|
718
728
|
this.updateDetune(channel, note, scheduleTime);
|
|
719
729
|
});
|
|
720
730
|
}
|
|
@@ -832,7 +842,7 @@ export class MidyGMLite {
|
|
|
832
842
|
const voiceParams = voice.getAllParams(controllerState);
|
|
833
843
|
const note = new Note(noteNumber, velocity, startTime, voice, voiceParams);
|
|
834
844
|
const audioBuffer = await this.getAudioBuffer(channel.programNumber, noteNumber, velocity, voiceParams, isSF3);
|
|
835
|
-
note.bufferSource = this.createBufferSource(voiceParams, audioBuffer);
|
|
845
|
+
note.bufferSource = this.createBufferSource(channel, voiceParams, audioBuffer);
|
|
836
846
|
note.volumeEnvelopeNode = new GainNode(this.audioContext);
|
|
837
847
|
note.filterNode = new BiquadFilterNode(this.audioContext, {
|
|
838
848
|
type: "lowpass",
|
|
@@ -858,7 +868,7 @@ export class MidyGMLite {
|
|
|
858
868
|
if (prev) {
|
|
859
869
|
const [prevNote, prevChannelNumber] = prev;
|
|
860
870
|
if (prevNote && !prevNote.ending) {
|
|
861
|
-
this.scheduleNoteOff(prevChannelNumber, prevNote, 0, // velocity,
|
|
871
|
+
this.scheduleNoteOff(prevChannelNumber, prevNote.noteNumber, 0, // velocity,
|
|
862
872
|
startTime, true);
|
|
863
873
|
}
|
|
864
874
|
}
|
|
@@ -874,12 +884,12 @@ export class MidyGMLite {
|
|
|
874
884
|
const index = drumExclusiveClass * this.channels.length + channelNumber;
|
|
875
885
|
const prevNote = this.drumExclusiveClassNotes[index];
|
|
876
886
|
if (prevNote && !prevNote.ending) {
|
|
877
|
-
this.scheduleNoteOff(channelNumber, prevNote, 0, // velocity,
|
|
887
|
+
this.scheduleNoteOff(channelNumber, prevNote.noteNumber, 0, // velocity,
|
|
878
888
|
startTime, true);
|
|
879
889
|
}
|
|
880
890
|
this.drumExclusiveClassNotes[index] = note;
|
|
881
891
|
}
|
|
882
|
-
async scheduleNoteOn(channelNumber, noteNumber, velocity, startTime
|
|
892
|
+
async scheduleNoteOn(channelNumber, noteNumber, velocity, startTime) {
|
|
883
893
|
const channel = this.channels[channelNumber];
|
|
884
894
|
const bankNumber = channel.bank;
|
|
885
895
|
const soundFontIndex = this.soundFontTable[channel.programNumber].get(bankNumber);
|
|
@@ -891,7 +901,6 @@ export class MidyGMLite {
|
|
|
891
901
|
return;
|
|
892
902
|
const isSF3 = soundFont.parsed.info.version.major === 3;
|
|
893
903
|
const note = await this.createNote(channel, voice, noteNumber, velocity, startTime, isSF3);
|
|
894
|
-
note.noteOffEvent = noteOffEvent;
|
|
895
904
|
note.volumeEnvelopeNode.connect(channel.gainL);
|
|
896
905
|
note.volumeEnvelopeNode.connect(channel.gainR);
|
|
897
906
|
if (0.5 <= channel.state.sustainPedal) {
|
|
@@ -902,24 +911,6 @@ export class MidyGMLite {
|
|
|
902
911
|
const scheduledNotes = channel.scheduledNotes;
|
|
903
912
|
note.index = scheduledNotes.length;
|
|
904
913
|
scheduledNotes.push(note);
|
|
905
|
-
if (channel.isDrum) {
|
|
906
|
-
const stopTime = startTime + note.bufferSource.buffer.duration;
|
|
907
|
-
const promise = new Promise((resolve) => {
|
|
908
|
-
note.bufferSource.onended = () => {
|
|
909
|
-
scheduledNotes[note.index] = undefined;
|
|
910
|
-
this.disconnectNote(note);
|
|
911
|
-
resolve();
|
|
912
|
-
};
|
|
913
|
-
note.bufferSource.stop(stopTime);
|
|
914
|
-
});
|
|
915
|
-
this.notePromises.push(promise);
|
|
916
|
-
}
|
|
917
|
-
else if (noteOffEvent) {
|
|
918
|
-
const notePromise = this.scheduleNoteOff(channelNumber, note, noteOffEvent.velocity, noteOffEvent.startTime, false);
|
|
919
|
-
if (notePromise) {
|
|
920
|
-
this.notePromises.push(notePromise);
|
|
921
|
-
}
|
|
922
|
-
}
|
|
923
914
|
}
|
|
924
915
|
noteOn(channelNumber, noteNumber, velocity, scheduleTime) {
|
|
925
916
|
scheduleTime ??= this.audioContext.currentTime;
|
|
@@ -935,36 +926,40 @@ export class MidyGMLite {
|
|
|
935
926
|
note.modulationLFO.stop();
|
|
936
927
|
}
|
|
937
928
|
}
|
|
938
|
-
|
|
929
|
+
releaseNote(channel, note, endTime) {
|
|
930
|
+
const volRelease = endTime + note.voiceParams.volRelease;
|
|
931
|
+
const modRelease = endTime + note.voiceParams.modRelease;
|
|
932
|
+
const stopTime = Math.min(volRelease, modRelease);
|
|
933
|
+
note.filterNode.frequency
|
|
934
|
+
.cancelScheduledValues(endTime)
|
|
935
|
+
.linearRampToValueAtTime(0, modRelease);
|
|
939
936
|
note.volumeEnvelopeNode.gain
|
|
940
937
|
.cancelScheduledValues(endTime)
|
|
941
|
-
.linearRampToValueAtTime(0,
|
|
942
|
-
note.ending = true;
|
|
943
|
-
this.scheduleTask(() => {
|
|
944
|
-
note.bufferSource.loop = false;
|
|
945
|
-
}, stopTime);
|
|
938
|
+
.linearRampToValueAtTime(0, volRelease);
|
|
946
939
|
return new Promise((resolve) => {
|
|
947
|
-
|
|
948
|
-
|
|
940
|
+
this.scheduleTask(() => {
|
|
941
|
+
const bufferSource = note.bufferSource;
|
|
942
|
+
bufferSource.loop = false;
|
|
943
|
+
bufferSource.stop(stopTime);
|
|
949
944
|
this.disconnectNote(note);
|
|
945
|
+
channel.scheduledNotes[note.index] = undefined;
|
|
950
946
|
resolve();
|
|
951
|
-
};
|
|
952
|
-
note.bufferSource.stop(stopTime);
|
|
947
|
+
}, stopTime);
|
|
953
948
|
});
|
|
954
949
|
}
|
|
955
|
-
scheduleNoteOff(channelNumber,
|
|
950
|
+
scheduleNoteOff(channelNumber, noteNumber, _velocity, endTime, force) {
|
|
956
951
|
const channel = this.channels[channelNumber];
|
|
957
|
-
if (
|
|
958
|
-
|
|
959
|
-
|
|
952
|
+
if (!force) {
|
|
953
|
+
if (channel.isDrum)
|
|
954
|
+
return;
|
|
955
|
+
if (0.5 <= channel.state.sustainPedal)
|
|
956
|
+
return;
|
|
957
|
+
}
|
|
958
|
+
const note = this.findNoteOffTarget(channel, noteNumber);
|
|
959
|
+
if (!note)
|
|
960
960
|
return;
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
note.filterNode.frequency
|
|
964
|
-
.cancelScheduledValues(endTime)
|
|
965
|
-
.linearRampToValueAtTime(0, modRelease);
|
|
966
|
-
const stopTime = Math.min(volRelease, modRelease);
|
|
967
|
-
return this.stopNote(channel, note, endTime, stopTime);
|
|
961
|
+
note.ending = true;
|
|
962
|
+
this.releaseNote(channel, note, endTime);
|
|
968
963
|
}
|
|
969
964
|
findNoteOffTarget(channel, noteNumber) {
|
|
970
965
|
const scheduledNotes = channel.scheduledNotes;
|
|
@@ -981,16 +976,14 @@ export class MidyGMLite {
|
|
|
981
976
|
}
|
|
982
977
|
noteOff(channelNumber, noteNumber, velocity, scheduleTime) {
|
|
983
978
|
scheduleTime ??= this.audioContext.currentTime;
|
|
984
|
-
|
|
985
|
-
const note = this.findNoteOffTarget(channel, noteNumber);
|
|
986
|
-
return this.scheduleNoteOff(channelNumber, note, velocity, scheduleTime, false);
|
|
979
|
+
return this.scheduleNoteOff(channelNumber, noteNumber, velocity, scheduleTime, false);
|
|
987
980
|
}
|
|
988
981
|
releaseSustainPedal(channelNumber, halfVelocity, scheduleTime) {
|
|
989
982
|
const velocity = halfVelocity * 2;
|
|
990
983
|
const channel = this.channels[channelNumber];
|
|
991
984
|
const promises = [];
|
|
992
985
|
for (let i = 0; i < channel.sustainNotes.length; i++) {
|
|
993
|
-
const promise = this.scheduleNoteOff(channelNumber, channel.sustainNotes[i], velocity, scheduleTime);
|
|
986
|
+
const promise = this.scheduleNoteOff(channelNumber, channel.sustainNotes[i].noteNumber, velocity, scheduleTime);
|
|
994
987
|
promises.push(promise);
|
|
995
988
|
}
|
|
996
989
|
channel.sustainNotes = [];
|
|
@@ -1104,11 +1097,12 @@ export class MidyGMLite {
|
|
|
1104
1097
|
return state;
|
|
1105
1098
|
}
|
|
1106
1099
|
applyVoiceParams(channel, controllerType, scheduleTime) {
|
|
1107
|
-
this.processScheduledNotes(channel, (note) => {
|
|
1100
|
+
this.processScheduledNotes(channel, scheduleTime, (note) => {
|
|
1108
1101
|
const controllerState = this.getControllerState(channel, note.noteNumber, note.velocity);
|
|
1109
1102
|
const voiceParams = note.voice.getParams(controllerType, controllerState);
|
|
1110
|
-
let
|
|
1111
|
-
let
|
|
1103
|
+
let applyVolumeEnvelope = false;
|
|
1104
|
+
let applyFilterEnvelope = false;
|
|
1105
|
+
let applyPitchEnvelope = false;
|
|
1112
1106
|
for (const [key, value] of Object.entries(voiceParams)) {
|
|
1113
1107
|
const prevValue = note.voiceParams[key];
|
|
1114
1108
|
if (value === prevValue)
|
|
@@ -1117,32 +1111,21 @@ export class MidyGMLite {
|
|
|
1117
1111
|
if (key in this.voiceParamsHandlers) {
|
|
1118
1112
|
this.voiceParamsHandlers[key](channel, note, prevValue, scheduleTime);
|
|
1119
1113
|
}
|
|
1120
|
-
else
|
|
1121
|
-
if (
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
if (key in voiceParams)
|
|
1128
|
-
noteVoiceParams[key] = voiceParams[key];
|
|
1129
|
-
}
|
|
1130
|
-
this.setFilterEnvelope(note, scheduleTime);
|
|
1131
|
-
this.setPitchEnvelope(note, scheduleTime);
|
|
1132
|
-
}
|
|
1133
|
-
else if (volumeEnvelopeKeySet.has(key)) {
|
|
1134
|
-
if (appliedVolumeEnvelope)
|
|
1135
|
-
continue;
|
|
1136
|
-
appliedVolumeEnvelope = true;
|
|
1137
|
-
const noteVoiceParams = note.voiceParams;
|
|
1138
|
-
for (let i = 0; i < volumeEnvelopeKeys.length; i++) {
|
|
1139
|
-
const key = volumeEnvelopeKeys[i];
|
|
1140
|
-
if (key in voiceParams)
|
|
1141
|
-
noteVoiceParams[key] = voiceParams[key];
|
|
1142
|
-
}
|
|
1143
|
-
this.setVolumeEnvelope(note, scheduleTime);
|
|
1114
|
+
else {
|
|
1115
|
+
if (volumeEnvelopeKeySet.has(key))
|
|
1116
|
+
applyVolumeEnvelope = true;
|
|
1117
|
+
if (filterEnvelopeKeySet.has(key))
|
|
1118
|
+
applyFilterEnvelope = true;
|
|
1119
|
+
if (pitchEnvelopeKeySet.has(key))
|
|
1120
|
+
applyPitchEnvelope = true;
|
|
1144
1121
|
}
|
|
1145
1122
|
}
|
|
1123
|
+
if (applyVolumeEnvelope)
|
|
1124
|
+
this.setVolumeEnvelope(note, scheduleTime);
|
|
1125
|
+
if (applyFilterEnvelope)
|
|
1126
|
+
this.setFilterEnvelope(note, scheduleTime);
|
|
1127
|
+
if (applyPitchEnvelope)
|
|
1128
|
+
this.setPitchEnvelope(note, scheduleTime);
|
|
1146
1129
|
});
|
|
1147
1130
|
}
|
|
1148
1131
|
createControlChangeHandlers() {
|
|
@@ -1174,7 +1157,7 @@ export class MidyGMLite {
|
|
|
1174
1157
|
}
|
|
1175
1158
|
updateModulation(channel, scheduleTime) {
|
|
1176
1159
|
const depth = channel.state.modulationDepth * channel.modulationDepthRange;
|
|
1177
|
-
this.processScheduledNotes(channel, (note) => {
|
|
1160
|
+
this.processScheduledNotes(channel, scheduleTime, (note) => {
|
|
1178
1161
|
if (note.modulationDepth) {
|
|
1179
1162
|
note.modulationDepth.gain.setValueAtTime(depth, scheduleTime);
|
|
1180
1163
|
}
|
|
@@ -1235,7 +1218,7 @@ export class MidyGMLite {
|
|
|
1235
1218
|
scheduleTime ??= this.audioContext.currentTime;
|
|
1236
1219
|
channel.state.sustainPedal = value / 127;
|
|
1237
1220
|
if (64 <= value) {
|
|
1238
|
-
this.processScheduledNotes(channel, (note) => {
|
|
1221
|
+
this.processScheduledNotes(channel, scheduleTime, (note) => {
|
|
1239
1222
|
channel.sustainNotes.push(note);
|
|
1240
1223
|
});
|
|
1241
1224
|
}
|