@marmooo/midy 0.0.3 → 0.0.5
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/deps/cdn.jsdelivr.net/npm/@marmooo/{soundfont-parser@0.0.1 → soundfont-parser@0.0.2}/+esm.d.ts +13 -6
- package/esm/deps/cdn.jsdelivr.net/npm/@marmooo/soundfont-parser@0.0.2/+esm.d.ts.map +1 -0
- package/esm/deps/cdn.jsdelivr.net/npm/@marmooo/{soundfont-parser@0.0.1 → soundfont-parser@0.0.2}/+esm.js +5 -5
- package/esm/midy-GM1.d.ts +27 -36
- package/esm/midy-GM1.d.ts.map +1 -1
- package/esm/midy-GM1.js +199 -135
- package/esm/midy-GM2.d.ts +51 -35
- package/esm/midy-GM2.d.ts.map +1 -1
- package/esm/midy-GM2.js +234 -141
- package/esm/midy-GMLite.d.ts +25 -36
- package/esm/midy-GMLite.d.ts.map +1 -1
- package/esm/midy-GMLite.js +187 -135
- package/esm/midy.d.ts +68 -24
- package/esm/midy.d.ts.map +1 -1
- package/esm/midy.js +274 -141
- package/package.json +1 -1
- package/script/deps/cdn.jsdelivr.net/npm/@marmooo/{soundfont-parser@0.0.1 → soundfont-parser@0.0.2}/+esm.d.ts +13 -6
- package/script/deps/cdn.jsdelivr.net/npm/@marmooo/soundfont-parser@0.0.2/+esm.d.ts.map +1 -0
- package/script/deps/cdn.jsdelivr.net/npm/@marmooo/{soundfont-parser@0.0.1 → soundfont-parser@0.0.2}/+esm.js +5 -5
- package/script/midy-GM1.d.ts +27 -36
- package/script/midy-GM1.d.ts.map +1 -1
- package/script/midy-GM1.js +199 -135
- package/script/midy-GM2.d.ts +51 -35
- package/script/midy-GM2.d.ts.map +1 -1
- package/script/midy-GM2.js +234 -141
- package/script/midy-GMLite.d.ts +25 -36
- package/script/midy-GMLite.d.ts.map +1 -1
- package/script/midy-GMLite.js +187 -135
- package/script/midy.d.ts +68 -24
- package/script/midy.d.ts.map +1 -1
- package/script/midy.js +274 -141
- package/esm/deps/cdn.jsdelivr.net/npm/@marmooo/soundfont-parser@0.0.1/+esm.d.ts.map +0 -1
- package/script/deps/cdn.jsdelivr.net/npm/@marmooo/soundfont-parser@0.0.1/+esm.d.ts.map +0 -1
package/esm/midy-GMLite.d.ts
CHANGED
|
@@ -2,9 +2,6 @@ export class MidyGMLite {
|
|
|
2
2
|
static channelSettings: {
|
|
3
3
|
volume: number;
|
|
4
4
|
pan: number;
|
|
5
|
-
vibratoRate: number;
|
|
6
|
-
vibratoDepth: number;
|
|
7
|
-
vibratoDelay: number;
|
|
8
5
|
bank: number;
|
|
9
6
|
dataMSB: number;
|
|
10
7
|
dataLSB: number;
|
|
@@ -42,12 +39,8 @@ export class MidyGMLite {
|
|
|
42
39
|
masterGain: any;
|
|
43
40
|
channels: {
|
|
44
41
|
scheduledNotes: Map<any, any>;
|
|
45
|
-
sostenutoNotes: Map<any, any>;
|
|
46
42
|
gainNode: any;
|
|
47
43
|
pannerNode: any;
|
|
48
|
-
modulationEffect: {
|
|
49
|
-
lfo: any;
|
|
50
|
-
};
|
|
51
44
|
expression: number;
|
|
52
45
|
modulation: number;
|
|
53
46
|
sustainPedal: boolean;
|
|
@@ -56,9 +49,6 @@ export class MidyGMLite {
|
|
|
56
49
|
pitchBendRange: number;
|
|
57
50
|
volume: number;
|
|
58
51
|
pan: number;
|
|
59
|
-
vibratoRate: number;
|
|
60
|
-
vibratoDepth: number;
|
|
61
|
-
vibratoDelay: number;
|
|
62
52
|
bank: number;
|
|
63
53
|
dataMSB: number;
|
|
64
54
|
dataLSB: number;
|
|
@@ -73,18 +63,11 @@ export class MidyGMLite {
|
|
|
73
63
|
setChannelAudioNodes(audioContext: any): {
|
|
74
64
|
gainNode: any;
|
|
75
65
|
pannerNode: any;
|
|
76
|
-
modulationEffect: {
|
|
77
|
-
lfo: any;
|
|
78
|
-
};
|
|
79
66
|
};
|
|
80
67
|
createChannels(audioContext: any): {
|
|
81
68
|
scheduledNotes: Map<any, any>;
|
|
82
|
-
sostenutoNotes: Map<any, any>;
|
|
83
69
|
gainNode: any;
|
|
84
70
|
pannerNode: any;
|
|
85
|
-
modulationEffect: {
|
|
86
|
-
lfo: any;
|
|
87
|
-
};
|
|
88
71
|
expression: number;
|
|
89
72
|
modulation: number;
|
|
90
73
|
sustainPedal: boolean;
|
|
@@ -93,9 +76,6 @@ export class MidyGMLite {
|
|
|
93
76
|
pitchBendRange: number;
|
|
94
77
|
volume: number;
|
|
95
78
|
pan: number;
|
|
96
|
-
vibratoRate: number;
|
|
97
|
-
vibratoDepth: number;
|
|
98
|
-
vibratoDelay: number;
|
|
99
79
|
bank: number;
|
|
100
80
|
dataMSB: number;
|
|
101
81
|
dataLSB: number;
|
|
@@ -103,8 +83,8 @@ export class MidyGMLite {
|
|
|
103
83
|
pitchBend: number;
|
|
104
84
|
modulationDepthRange: number;
|
|
105
85
|
}[];
|
|
106
|
-
createNoteBuffer(
|
|
107
|
-
createNoteBufferNode(
|
|
86
|
+
createNoteBuffer(instrumentKey: any, isSF3: any): Promise<any>;
|
|
87
|
+
createNoteBufferNode(instrumentKey: any, isSF3: any): Promise<any>;
|
|
108
88
|
convertToFloat32Array(uint8Array: any): Float32Array;
|
|
109
89
|
scheduleTimelineEvents(t: any, offset: any, queueIndex: any): Promise<any>;
|
|
110
90
|
getQueueIndex(second: any): number;
|
|
@@ -123,30 +103,26 @@ export class MidyGMLite {
|
|
|
123
103
|
seekTo(second: any): void;
|
|
124
104
|
calcTotalTime(): number;
|
|
125
105
|
currentTime(): number;
|
|
126
|
-
getActiveNotes(channel: any): Map<any, any>;
|
|
127
|
-
|
|
128
|
-
createModulationEffect(audioContext: any): {
|
|
129
|
-
lfo: any;
|
|
130
|
-
};
|
|
106
|
+
getActiveNotes(channel: any, time: any): Map<any, any>;
|
|
107
|
+
getActiveNote(noteList: any, time: any): any;
|
|
131
108
|
connectNoteEffects(channel: any, gainNode: any): void;
|
|
132
109
|
cbToRatio(cb: any): number;
|
|
133
110
|
centToHz(cent: any): number;
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
111
|
+
calcSemitoneOffset(channel: any): number;
|
|
112
|
+
calcPlaybackRate(instrumentKey: any, noteNumber: any, semitoneOffset: any): number;
|
|
113
|
+
setVolumeEnvelope(channel: any, note: any): void;
|
|
114
|
+
setFilterEnvelope(channel: any, note: any): void;
|
|
115
|
+
startModulation(channel: any, note: any, time: any): void;
|
|
116
|
+
createNote(channel: any, instrumentKey: any, noteNumber: any, velocity: any, startTime: any, isSF3: any): Promise<Note>;
|
|
140
117
|
scheduleNoteOn(channelNumber: any, noteNumber: any, velocity: any, startTime: any): Promise<void>;
|
|
141
118
|
noteOn(channelNumber: any, noteNumber: any, velocity: any): Promise<void>;
|
|
142
119
|
scheduleNoteRelease(channelNumber: any, noteNumber: any, velocity: any, stopTime: any, stopPedal?: boolean): Promise<any> | undefined;
|
|
143
120
|
releaseNote(channelNumber: any, noteNumber: any, velocity: any): Promise<any> | undefined;
|
|
144
121
|
releaseSustainPedal(channelNumber: any, halfVelocity: any): any[];
|
|
145
122
|
handleMIDIMessage(statusByte: any, data1: any, data2: any): void | any[] | Promise<any>;
|
|
146
|
-
handlePolyphonicKeyPressure(channelNumber: any, noteNumber: any, pressure: any): void;
|
|
147
123
|
handleProgramChange(channelNumber: any, program: any): void;
|
|
148
|
-
|
|
149
|
-
handlePitchBend(channelNumber: any,
|
|
124
|
+
handlePitchBendMessage(channelNumber: any, lsb: any, msb: any): void;
|
|
125
|
+
handlePitchBend(channelNumber: any, pitchBend: any): void;
|
|
150
126
|
handleControlChange(channelNumber: any, controller: any, value: any): void | any[];
|
|
151
127
|
setModulation(channelNumber: any, modulation: any): void;
|
|
152
128
|
setVolume(channelNumber: any, volume: any): void;
|
|
@@ -169,4 +145,17 @@ export class MidyGMLite {
|
|
|
169
145
|
handleSysEx(data: any): void;
|
|
170
146
|
scheduleTask(callback: any, startTime: any): Promise<any>;
|
|
171
147
|
}
|
|
148
|
+
declare class Note {
|
|
149
|
+
constructor(noteNumber: any, velocity: any, startTime: any, instrumentKey: any);
|
|
150
|
+
bufferSource: any;
|
|
151
|
+
gainNode: any;
|
|
152
|
+
filterNode: any;
|
|
153
|
+
modLFO: any;
|
|
154
|
+
modLFOGain: any;
|
|
155
|
+
noteNumber: any;
|
|
156
|
+
velocity: any;
|
|
157
|
+
startTime: any;
|
|
158
|
+
instrumentKey: any;
|
|
159
|
+
}
|
|
160
|
+
export {};
|
|
172
161
|
//# sourceMappingURL=midy-GMLite.d.ts.map
|
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":"AAqBA;IAmBE;;;;;;;;;MASE;IAEF;;;;;;;MAOE;IAEF,+BAMC;IA5CD,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;IAuBhB,kBAAgC;IAChC,gBAA4C;IAE5C;;;;;;;;;;;;;;;;;;QAAiD;IAInD,4BAMC;IAED,mCASC;IAED,gDAMC;IAED,sCASC;IAED;;;MAaC;IAED;;;;;;;;;;;;;;;;;;QAUC;IAED,+DAyBC;IAED,mEAWC;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,uDASC;IAED,6CAQC;IAED,sDAEC;IAED,2BAEC;IAED,4BAEC;IAED,yCAEC;IAED,mFAGC;IAED,iDAmBC;IAED,iDAiCC;IAED,0DAmBC;IAED,wHA6BC;IAED,kGA6BC;IAED,0EAGC;IAED,sIAmDC;IAED,0FAGC;IAED,kEAeC;IAED,wFAiBC;IAED,4DAGC;IAED,qEAGC;IAED,0DAiBC;IAED,mFA+BC;IAED,yDAiBC;IAED,iDAIC;IAED,2CAMC;IAED,yDAIC;IAED,sCAKC;IAED,sDAMC;IAED,gDAEC;IAED,gDAEC;IAED,+DAcC;IAED,uCAoBC;IAED,8CAEC;IAED,uCAoBC;IAED,4DAgBC;IAED,oBAQC;IAED,yDAaC;IAED,yCAGC;IAED,sCAQC;IAED,wCAEC;IAED,6BASC;IAED,0DAUC;CACF;AA59BD;IAOE,gFAKC;IAXD,kBAAa;IACb,cAAS;IACT,gBAAW;IACX,YAAO;IACP,gBAAW;IAGT,gBAA4B;IAC5B,cAAwB;IACxB,eAA0B;IAC1B,mBAAkC;CAErC"}
|
package/esm/midy-GMLite.js
CHANGED
|
@@ -1,5 +1,43 @@
|
|
|
1
1
|
import { parseMidi } from "./deps/cdn.jsdelivr.net/npm/midi-file@1.2.4/+esm.js";
|
|
2
|
-
import { parse, SoundFont, } from "./deps/cdn.jsdelivr.net/npm/@marmooo/soundfont-parser@0.0.
|
|
2
|
+
import { parse, SoundFont, } from "./deps/cdn.jsdelivr.net/npm/@marmooo/soundfont-parser@0.0.2/+esm.js";
|
|
3
|
+
class Note {
|
|
4
|
+
constructor(noteNumber, velocity, startTime, instrumentKey) {
|
|
5
|
+
Object.defineProperty(this, "bufferSource", {
|
|
6
|
+
enumerable: true,
|
|
7
|
+
configurable: true,
|
|
8
|
+
writable: true,
|
|
9
|
+
value: void 0
|
|
10
|
+
});
|
|
11
|
+
Object.defineProperty(this, "gainNode", {
|
|
12
|
+
enumerable: true,
|
|
13
|
+
configurable: true,
|
|
14
|
+
writable: true,
|
|
15
|
+
value: void 0
|
|
16
|
+
});
|
|
17
|
+
Object.defineProperty(this, "filterNode", {
|
|
18
|
+
enumerable: true,
|
|
19
|
+
configurable: true,
|
|
20
|
+
writable: true,
|
|
21
|
+
value: void 0
|
|
22
|
+
});
|
|
23
|
+
Object.defineProperty(this, "modLFO", {
|
|
24
|
+
enumerable: true,
|
|
25
|
+
configurable: true,
|
|
26
|
+
writable: true,
|
|
27
|
+
value: void 0
|
|
28
|
+
});
|
|
29
|
+
Object.defineProperty(this, "modLFOGain", {
|
|
30
|
+
enumerable: true,
|
|
31
|
+
configurable: true,
|
|
32
|
+
writable: true,
|
|
33
|
+
value: void 0
|
|
34
|
+
});
|
|
35
|
+
this.noteNumber = noteNumber;
|
|
36
|
+
this.velocity = velocity;
|
|
37
|
+
this.startTime = startTime;
|
|
38
|
+
this.instrumentKey = instrumentKey;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
3
41
|
export class MidyGMLite {
|
|
4
42
|
constructor(audioContext) {
|
|
5
43
|
Object.defineProperty(this, "ticksPerBeat", {
|
|
@@ -151,14 +189,11 @@ export class MidyGMLite {
|
|
|
151
189
|
const pannerNode = new StereoPannerNode(audioContext, {
|
|
152
190
|
pan: MidyGMLite.channelSettings.pan,
|
|
153
191
|
});
|
|
154
|
-
const modulationEffect = this.createModulationEffect(audioContext);
|
|
155
|
-
modulationEffect.lfo.start();
|
|
156
192
|
pannerNode.connect(gainNode);
|
|
157
193
|
gainNode.connect(this.masterGain);
|
|
158
194
|
return {
|
|
159
195
|
gainNode,
|
|
160
196
|
pannerNode,
|
|
161
|
-
modulationEffect,
|
|
162
197
|
};
|
|
163
198
|
}
|
|
164
199
|
createChannels(audioContext) {
|
|
@@ -168,16 +203,15 @@ export class MidyGMLite {
|
|
|
168
203
|
...MidyGMLite.effectSettings,
|
|
169
204
|
...this.setChannelAudioNodes(audioContext),
|
|
170
205
|
scheduledNotes: new Map(),
|
|
171
|
-
sostenutoNotes: new Map(),
|
|
172
206
|
};
|
|
173
207
|
});
|
|
174
208
|
return channels;
|
|
175
209
|
}
|
|
176
|
-
async createNoteBuffer(
|
|
177
|
-
const sampleEnd =
|
|
210
|
+
async createNoteBuffer(instrumentKey, isSF3) {
|
|
211
|
+
const sampleEnd = instrumentKey.sample.length + instrumentKey.end;
|
|
178
212
|
if (isSF3) {
|
|
179
|
-
const sample = new Uint8Array(
|
|
180
|
-
sample.set(
|
|
213
|
+
const sample = new Uint8Array(instrumentKey.sample.length);
|
|
214
|
+
sample.set(instrumentKey.sample);
|
|
181
215
|
const audioBuffer = await this.audioContext.decodeAudioData(sample.buffer);
|
|
182
216
|
for (let channel = 0; channel < audioBuffer.numberOfChannels; channel++) {
|
|
183
217
|
const channelData = audioBuffer.getChannelData(channel);
|
|
@@ -186,26 +220,27 @@ export class MidyGMLite {
|
|
|
186
220
|
return audioBuffer;
|
|
187
221
|
}
|
|
188
222
|
else {
|
|
189
|
-
const sample =
|
|
223
|
+
const sample = instrumentKey.sample.subarray(0, sampleEnd);
|
|
190
224
|
const floatSample = this.convertToFloat32Array(sample);
|
|
191
225
|
const audioBuffer = new AudioBuffer({
|
|
192
226
|
numberOfChannels: 1,
|
|
193
227
|
length: sample.length,
|
|
194
|
-
sampleRate:
|
|
228
|
+
sampleRate: instrumentKey.sampleRate,
|
|
195
229
|
});
|
|
196
230
|
const channelData = audioBuffer.getChannelData(0);
|
|
197
231
|
channelData.set(floatSample);
|
|
198
232
|
return audioBuffer;
|
|
199
233
|
}
|
|
200
234
|
}
|
|
201
|
-
async createNoteBufferNode(
|
|
235
|
+
async createNoteBufferNode(instrumentKey, isSF3) {
|
|
202
236
|
const bufferSource = new AudioBufferSourceNode(this.audioContext);
|
|
203
|
-
const audioBuffer = await this.createNoteBuffer(
|
|
237
|
+
const audioBuffer = await this.createNoteBuffer(instrumentKey, isSF3);
|
|
204
238
|
bufferSource.buffer = audioBuffer;
|
|
205
|
-
bufferSource.loop =
|
|
239
|
+
bufferSource.loop = instrumentKey.sampleModes % 2 !== 0;
|
|
206
240
|
if (bufferSource.loop) {
|
|
207
|
-
bufferSource.loopStart =
|
|
208
|
-
|
|
241
|
+
bufferSource.loopStart = instrumentKey.loopStart /
|
|
242
|
+
instrumentKey.sampleRate;
|
|
243
|
+
bufferSource.loopEnd = instrumentKey.loopEnd / instrumentKey.sampleRate;
|
|
209
244
|
}
|
|
210
245
|
return bufferSource;
|
|
211
246
|
}
|
|
@@ -223,9 +258,6 @@ export class MidyGMLite {
|
|
|
223
258
|
if (event.startTime > t + this.lookAhead)
|
|
224
259
|
break;
|
|
225
260
|
switch (event.type) {
|
|
226
|
-
case "controller":
|
|
227
|
-
this.handleControlChange(event.channel, event.controllerType, event.value);
|
|
228
|
-
break;
|
|
229
261
|
case "noteOn":
|
|
230
262
|
if (event.velocity !== 0) {
|
|
231
263
|
await this.scheduleNoteOn(event.channel, event.noteNumber, event.velocity, event.startTime + this.startDelay - offset);
|
|
@@ -239,9 +271,15 @@ export class MidyGMLite {
|
|
|
239
271
|
}
|
|
240
272
|
break;
|
|
241
273
|
}
|
|
274
|
+
case "controller":
|
|
275
|
+
this.handleControlChange(event.channel, event.controllerType, event.value);
|
|
276
|
+
break;
|
|
242
277
|
case "programChange":
|
|
243
278
|
this.handleProgramChange(event.channel, event.programNumber);
|
|
244
279
|
break;
|
|
280
|
+
case "pitchBend":
|
|
281
|
+
this.handlePitchBend(event.channel, event.value);
|
|
282
|
+
break;
|
|
245
283
|
case "sysEx":
|
|
246
284
|
this.handleSysEx(event.data);
|
|
247
285
|
}
|
|
@@ -444,30 +482,26 @@ export class MidyGMLite {
|
|
|
444
482
|
const now = this.audioContext.currentTime;
|
|
445
483
|
return this.resumeTime + now - this.startTime - this.startDelay;
|
|
446
484
|
}
|
|
447
|
-
getActiveNotes(channel) {
|
|
485
|
+
getActiveNotes(channel, time) {
|
|
448
486
|
const activeNotes = new Map();
|
|
449
|
-
channel.scheduledNotes.forEach((
|
|
450
|
-
const activeNote = this.
|
|
487
|
+
channel.scheduledNotes.forEach((noteList) => {
|
|
488
|
+
const activeNote = this.getActiveNote(noteList, time);
|
|
451
489
|
if (activeNote) {
|
|
452
490
|
activeNotes.set(activeNote.noteNumber, activeNote);
|
|
453
491
|
}
|
|
454
492
|
});
|
|
455
493
|
return activeNotes;
|
|
456
494
|
}
|
|
457
|
-
|
|
458
|
-
for (let i =
|
|
459
|
-
const
|
|
460
|
-
if (
|
|
461
|
-
return
|
|
495
|
+
getActiveNote(noteList, time) {
|
|
496
|
+
for (let i = noteList.length - 1; i >= 0; i--) {
|
|
497
|
+
const note = noteList[i];
|
|
498
|
+
if (!note)
|
|
499
|
+
return;
|
|
500
|
+
if (time < note.startTime)
|
|
501
|
+
continue;
|
|
502
|
+
return (note.ending) ? null : note;
|
|
462
503
|
}
|
|
463
|
-
|
|
464
|
-
createModulationEffect(audioContext) {
|
|
465
|
-
const lfo = new OscillatorNode(audioContext, {
|
|
466
|
-
frequency: 5,
|
|
467
|
-
});
|
|
468
|
-
return {
|
|
469
|
-
lfo,
|
|
470
|
-
};
|
|
504
|
+
return noteList[0];
|
|
471
505
|
}
|
|
472
506
|
connectNoteEffects(channel, gainNode) {
|
|
473
507
|
gainNode.connect(channel.pannerNode);
|
|
@@ -478,70 +512,92 @@ export class MidyGMLite {
|
|
|
478
512
|
centToHz(cent) {
|
|
479
513
|
return 8.176 * Math.pow(2, cent / 1200);
|
|
480
514
|
}
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
515
|
+
calcSemitoneOffset(channel) {
|
|
516
|
+
return channel.pitchBend * channel.pitchBendRange;
|
|
517
|
+
}
|
|
518
|
+
calcPlaybackRate(instrumentKey, noteNumber, semitoneOffset) {
|
|
519
|
+
return instrumentKey.playbackRate(noteNumber) *
|
|
484
520
|
Math.pow(2, semitoneOffset / 12);
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
521
|
+
}
|
|
522
|
+
setVolumeEnvelope(channel, note) {
|
|
523
|
+
const { instrumentKey, startTime, velocity } = note;
|
|
524
|
+
note.gainNode = new GainNode(this.audioContext, {
|
|
489
525
|
gain: 0,
|
|
490
526
|
});
|
|
491
527
|
let volume = (velocity / 127) * channel.volume * channel.expression;
|
|
492
528
|
if (volume === 0)
|
|
493
529
|
volume = 1e-6; // exponentialRampToValueAtTime() requires a non-zero value
|
|
494
|
-
const attackVolume = this.cbToRatio(-
|
|
495
|
-
|
|
496
|
-
const
|
|
497
|
-
const
|
|
498
|
-
const
|
|
499
|
-
const
|
|
500
|
-
|
|
530
|
+
const attackVolume = this.cbToRatio(-instrumentKey.initialAttenuation) *
|
|
531
|
+
volume;
|
|
532
|
+
const sustainVolume = attackVolume * (1 - instrumentKey.volSustain);
|
|
533
|
+
const volDelay = startTime + instrumentKey.volDelay;
|
|
534
|
+
const volAttack = volDelay + instrumentKey.volAttack;
|
|
535
|
+
const volHold = volAttack + instrumentKey.volHold;
|
|
536
|
+
const volDecay = volHold + instrumentKey.volDecay;
|
|
537
|
+
note.gainNode.gain
|
|
501
538
|
.setValueAtTime(1e-6, volDelay) // exponentialRampToValueAtTime() requires a non-zero value
|
|
502
539
|
.exponentialRampToValueAtTime(attackVolume, volAttack)
|
|
503
540
|
.setValueAtTime(attackVolume, volHold)
|
|
504
541
|
.linearRampToValueAtTime(sustainVolume, volDecay);
|
|
505
|
-
|
|
542
|
+
}
|
|
543
|
+
setFilterEnvelope(channel, note) {
|
|
544
|
+
const { instrumentKey, startTime, noteNumber } = note;
|
|
545
|
+
const softPedalFactor = 1 -
|
|
546
|
+
(0.1 + (noteNumber / 127) * 0.2) * channel.softPedal;
|
|
506
547
|
const maxFreq = this.audioContext.sampleRate / 2;
|
|
507
|
-
const baseFreq = this.centToHz(
|
|
508
|
-
|
|
509
|
-
const
|
|
510
|
-
|
|
548
|
+
const baseFreq = this.centToHz(instrumentKey.initialFilterFc) *
|
|
549
|
+
softPedalFactor;
|
|
550
|
+
const peekFreq = this.centToHz(instrumentKey.initialFilterFc + instrumentKey.modEnvToFilterFc) * softPedalFactor;
|
|
551
|
+
const sustainFreq = (baseFreq +
|
|
552
|
+
(peekFreq - baseFreq) * (1 - instrumentKey.modSustain)) * softPedalFactor;
|
|
553
|
+
const modDelay = startTime + instrumentKey.modDelay;
|
|
554
|
+
const modAttack = modDelay + instrumentKey.modAttack;
|
|
555
|
+
const modHold = modAttack + instrumentKey.modHold;
|
|
556
|
+
const modDecay = modHold + instrumentKey.modDecay;
|
|
511
557
|
const adjustedBaseFreq = Math.min(maxFreq, baseFreq);
|
|
512
558
|
const adjustedPeekFreq = Math.min(maxFreq, peekFreq);
|
|
513
559
|
const adjustedSustainFreq = Math.min(maxFreq, sustainFreq);
|
|
514
|
-
|
|
560
|
+
note.filterNode = new BiquadFilterNode(this.audioContext, {
|
|
515
561
|
type: "lowpass",
|
|
516
|
-
Q:
|
|
562
|
+
Q: instrumentKey.initialFilterQ / 10, // dB
|
|
517
563
|
frequency: adjustedBaseFreq,
|
|
518
564
|
});
|
|
519
|
-
|
|
520
|
-
const modAttack = modDelay + noteInfo.modAttack;
|
|
521
|
-
const modHold = modAttack + noteInfo.modHold;
|
|
522
|
-
const modDecay = modHold + noteInfo.modDecay;
|
|
523
|
-
filterNode.frequency
|
|
565
|
+
note.filterNode.frequency
|
|
524
566
|
.setValueAtTime(adjustedBaseFreq, modDelay)
|
|
525
567
|
.exponentialRampToValueAtTime(adjustedPeekFreq, modAttack)
|
|
526
568
|
.setValueAtTime(adjustedPeekFreq, modHold)
|
|
527
569
|
.linearRampToValueAtTime(adjustedSustainFreq, modDecay);
|
|
528
|
-
|
|
570
|
+
note.bufferSource.detune.setValueAtTime(note.bufferSource.detune.value + instrumentKey.modEnvToPitch, modDelay);
|
|
571
|
+
}
|
|
572
|
+
startModulation(channel, note, time) {
|
|
573
|
+
const { instrumentKey } = note;
|
|
574
|
+
note.modLFOGain = new GainNode(this.audioContext, {
|
|
575
|
+
gain: this.cbToRatio(instrumentKey.modLfoToVolume) * channel.modulation,
|
|
576
|
+
});
|
|
577
|
+
note.modLFO = new OscillatorNode(this.audioContext, {
|
|
578
|
+
frequency: this.centToHz(instrumentKey.freqModLFO),
|
|
579
|
+
});
|
|
580
|
+
note.modLFO.start(time);
|
|
581
|
+
note.filterNode.frequency.setValueAtTime(note.filterNode.frequency.value + instrumentKey.modLfoToFilterFc, time);
|
|
582
|
+
note.bufferSource.detune.setValueAtTime(note.bufferSource.detune.value + instrumentKey.modLfoToPitch, time);
|
|
583
|
+
note.modLFO.connect(note.modLFOGain);
|
|
584
|
+
note.modLFOGain.connect(note.bufferSource.detune);
|
|
585
|
+
}
|
|
586
|
+
async createNote(channel, instrumentKey, noteNumber, velocity, startTime, isSF3) {
|
|
587
|
+
const semitoneOffset = this.calcSemitoneOffset(channel);
|
|
588
|
+
const note = new Note(noteNumber, velocity, startTime, instrumentKey);
|
|
589
|
+
note.bufferSource = await this.createNoteBufferNode(instrumentKey, isSF3);
|
|
590
|
+
note.bufferSource.playbackRate.value = this.calcPlaybackRate(instrumentKey, noteNumber, semitoneOffset);
|
|
591
|
+
this.setVolumeEnvelope(channel, note);
|
|
592
|
+
this.setFilterEnvelope(channel, note);
|
|
529
593
|
if (channel.modulation > 0) {
|
|
530
|
-
const
|
|
531
|
-
|
|
532
|
-
lfoGain = new GainNode(this.audioContext, {
|
|
533
|
-
gain: 0,
|
|
534
|
-
});
|
|
535
|
-
lfoGain.gain
|
|
536
|
-
.setValueAtTime(1e-6, vibratoDelay) // exponentialRampToValueAtTime() requires a non-zero value
|
|
537
|
-
.exponentialRampToValueAtTime(channel.modulation, vibratoAttack);
|
|
538
|
-
channel.modulationEffect.lfo.connect(lfoGain);
|
|
539
|
-
lfoGain.connect(bufferSource.detune);
|
|
594
|
+
const delayModLFO = startTime + instrumentKey.delayModLFO;
|
|
595
|
+
this.startModulation(channel, note, delayModLFO);
|
|
540
596
|
}
|
|
541
|
-
bufferSource.connect(filterNode);
|
|
542
|
-
filterNode.connect(gainNode);
|
|
543
|
-
bufferSource.start(startTime,
|
|
544
|
-
return
|
|
597
|
+
note.bufferSource.connect(note.filterNode);
|
|
598
|
+
note.filterNode.connect(note.gainNode);
|
|
599
|
+
note.bufferSource.start(startTime, instrumentKey.start / instrumentKey.sampleRate);
|
|
600
|
+
return note;
|
|
545
601
|
}
|
|
546
602
|
async scheduleNoteOn(channelNumber, noteNumber, velocity, startTime) {
|
|
547
603
|
const channel = this.channels[channelNumber];
|
|
@@ -551,27 +607,17 @@ export class MidyGMLite {
|
|
|
551
607
|
return;
|
|
552
608
|
const soundFont = this.soundFonts[soundFontIndex];
|
|
553
609
|
const isSF3 = soundFont.parsed.info.version.major === 3;
|
|
554
|
-
const
|
|
555
|
-
if (!
|
|
610
|
+
const instrumentKey = soundFont.getInstrumentKey(bankNumber, channel.program, noteNumber);
|
|
611
|
+
if (!instrumentKey)
|
|
556
612
|
return;
|
|
557
|
-
const
|
|
558
|
-
|
|
559
|
-
this.connectNoteEffects(channel, gainNode);
|
|
613
|
+
const note = await this.createNote(channel, instrumentKey, noteNumber, velocity, startTime, isSF3);
|
|
614
|
+
this.connectNoteEffects(channel, note.gainNode);
|
|
560
615
|
const scheduledNotes = channel.scheduledNotes;
|
|
561
|
-
const scheduledNote = {
|
|
562
|
-
bufferSource,
|
|
563
|
-
filterNode,
|
|
564
|
-
gainNode,
|
|
565
|
-
lfoGain,
|
|
566
|
-
noteInfo,
|
|
567
|
-
noteNumber,
|
|
568
|
-
startTime,
|
|
569
|
-
};
|
|
570
616
|
if (scheduledNotes.has(noteNumber)) {
|
|
571
|
-
scheduledNotes.get(noteNumber).push(
|
|
617
|
+
scheduledNotes.get(noteNumber).push(note);
|
|
572
618
|
}
|
|
573
619
|
else {
|
|
574
|
-
scheduledNotes.set(noteNumber, [
|
|
620
|
+
scheduledNotes.set(noteNumber, [note]);
|
|
575
621
|
}
|
|
576
622
|
}
|
|
577
623
|
noteOn(channelNumber, noteNumber, velocity) {
|
|
@@ -591,15 +637,15 @@ export class MidyGMLite {
|
|
|
591
637
|
continue;
|
|
592
638
|
if (targetNote.ending)
|
|
593
639
|
continue;
|
|
594
|
-
const { bufferSource, filterNode, gainNode,
|
|
640
|
+
const { bufferSource, filterNode, gainNode, modLFO, modLFOGain, instrumentKey, } = targetNote;
|
|
595
641
|
const velocityRate = (velocity + 127) / 127;
|
|
596
|
-
const volEndTime = stopTime +
|
|
642
|
+
const volEndTime = stopTime + instrumentKey.volRelease * velocityRate;
|
|
597
643
|
gainNode.gain.cancelScheduledValues(stopTime);
|
|
598
644
|
gainNode.gain.linearRampToValueAtTime(0, volEndTime);
|
|
599
645
|
const maxFreq = this.audioContext.sampleRate / 2;
|
|
600
|
-
const baseFreq = this.centToHz(
|
|
646
|
+
const baseFreq = this.centToHz(instrumentKey.initialFilterFc);
|
|
601
647
|
const adjustedBaseFreq = Math.min(maxFreq, baseFreq);
|
|
602
|
-
const modEndTime = stopTime +
|
|
648
|
+
const modEndTime = stopTime + instrumentKey.modRelease * velocityRate;
|
|
603
649
|
filterNode.frequency
|
|
604
650
|
.cancelScheduledValues(stopTime)
|
|
605
651
|
.linearRampToValueAtTime(adjustedBaseFreq, modEndTime);
|
|
@@ -613,8 +659,10 @@ export class MidyGMLite {
|
|
|
613
659
|
bufferSource.disconnect(0);
|
|
614
660
|
filterNode.disconnect(0);
|
|
615
661
|
gainNode.disconnect(0);
|
|
616
|
-
if (
|
|
617
|
-
|
|
662
|
+
if (modLFOGain)
|
|
663
|
+
modLFOGain.disconnect(0);
|
|
664
|
+
if (modLFO)
|
|
665
|
+
modLFO.stop();
|
|
618
666
|
resolve();
|
|
619
667
|
};
|
|
620
668
|
bufferSource.stop(volEndTime);
|
|
@@ -649,46 +697,37 @@ export class MidyGMLite {
|
|
|
649
697
|
return this.releaseNote(channelNumber, data1, data2);
|
|
650
698
|
case 0x90:
|
|
651
699
|
return this.noteOn(channelNumber, data1, data2);
|
|
652
|
-
case 0xA0:
|
|
653
|
-
return this.handlePolyphonicKeyPressure(channelNumber, data1, data2);
|
|
654
700
|
case 0xB0:
|
|
655
701
|
return this.handleControlChange(channelNumber, data1, data2);
|
|
656
702
|
case 0xC0:
|
|
657
703
|
return this.handleProgramChange(channelNumber, data1);
|
|
658
|
-
case 0xD0:
|
|
659
|
-
return this.handleChannelPressure(channelNumber, data1);
|
|
660
704
|
case 0xE0:
|
|
661
|
-
return this.
|
|
705
|
+
return this.handlePitchBendMessage(channelNumber, data1, data2);
|
|
662
706
|
default:
|
|
663
707
|
console.warn(`Unsupported MIDI message: ${messageType.toString(16)}`);
|
|
664
708
|
}
|
|
665
709
|
}
|
|
666
|
-
handlePolyphonicKeyPressure(channelNumber, noteNumber, pressure) {
|
|
667
|
-
const now = this.audioContext.currentTime;
|
|
668
|
-
const channel = this.channels[channelNumber];
|
|
669
|
-
const scheduledNotes = channel.scheduledNotes.get(noteNumber);
|
|
670
|
-
pressure /= 127;
|
|
671
|
-
if (scheduledNotes) {
|
|
672
|
-
scheduledNotes.forEach((scheduledNote) => {
|
|
673
|
-
if (scheduledNote) {
|
|
674
|
-
const { initialAttenuation } = scheduledNote.noteInfo;
|
|
675
|
-
const gain = this.cbToRatio(-initialAttenuation) * pressure;
|
|
676
|
-
scheduledNote.gainNode.gain.cancelScheduledValues(now);
|
|
677
|
-
scheduledNote.gainNode.gain.setValueAtTime(gain, now);
|
|
678
|
-
}
|
|
679
|
-
});
|
|
680
|
-
}
|
|
681
|
-
}
|
|
682
710
|
handleProgramChange(channelNumber, program) {
|
|
683
711
|
const channel = this.channels[channelNumber];
|
|
684
712
|
channel.program = program;
|
|
685
713
|
}
|
|
686
|
-
|
|
687
|
-
|
|
714
|
+
handlePitchBendMessage(channelNumber, lsb, msb) {
|
|
715
|
+
const pitchBend = msb * 128 + lsb;
|
|
716
|
+
this.handlePitchBend(channelNumber, pitchBend);
|
|
688
717
|
}
|
|
689
|
-
handlePitchBend(channelNumber,
|
|
690
|
-
const
|
|
691
|
-
this.channels[channelNumber]
|
|
718
|
+
handlePitchBend(channelNumber, pitchBend) {
|
|
719
|
+
const now = this.audioContext.currentTime;
|
|
720
|
+
const channel = this.channels[channelNumber];
|
|
721
|
+
channel.pitchBend = (pitchBend - 8192) / 8192;
|
|
722
|
+
const semitoneOffset = this.calcSemitoneOffset(channel);
|
|
723
|
+
const activeNotes = this.getActiveNotes(channel, now);
|
|
724
|
+
activeNotes.forEach((activeNote) => {
|
|
725
|
+
const { bufferSource, instrumentKey, noteNumber } = activeNote;
|
|
726
|
+
const playbackRate = calcPlaybackRate(instrumentKey, noteNumber, semitoneOffset);
|
|
727
|
+
bufferSource.playbackRate
|
|
728
|
+
.cancelScheduledValues(now)
|
|
729
|
+
.setValueAtTime(playbackRate * pressure, now);
|
|
730
|
+
});
|
|
692
731
|
}
|
|
693
732
|
handleControlChange(channelNumber, controller, value) {
|
|
694
733
|
switch (controller) {
|
|
@@ -721,9 +760,20 @@ export class MidyGMLite {
|
|
|
721
760
|
}
|
|
722
761
|
}
|
|
723
762
|
setModulation(channelNumber, modulation) {
|
|
763
|
+
const now = this.audioContext.currentTime;
|
|
724
764
|
const channel = this.channels[channelNumber];
|
|
725
765
|
channel.modulation = (modulation / 127) *
|
|
726
766
|
(channel.modulationDepthRange * 100);
|
|
767
|
+
const activeNotes = this.getActiveNotes(channel, now);
|
|
768
|
+
activeNotes.forEach((activeNote) => {
|
|
769
|
+
if (activeNote.modLFO) {
|
|
770
|
+
activeNote.gainNode.gain.setValueAtTime(this.cbToRatio(activeNote.instrumentKey.modLfoToVolume) *
|
|
771
|
+
channel.modulation, now);
|
|
772
|
+
}
|
|
773
|
+
else {
|
|
774
|
+
this.startModulation(channel, activeNote, now);
|
|
775
|
+
}
|
|
776
|
+
});
|
|
727
777
|
}
|
|
728
778
|
setVolume(channelNumber, volume) {
|
|
729
779
|
const channel = this.channels[channelNumber];
|
|
@@ -780,8 +830,8 @@ export class MidyGMLite {
|
|
|
780
830
|
const velocity = 0;
|
|
781
831
|
const stopPedal = true;
|
|
782
832
|
const promises = [];
|
|
783
|
-
channel.scheduledNotes.forEach((
|
|
784
|
-
const activeNote = this.
|
|
833
|
+
channel.scheduledNotes.forEach((noteList) => {
|
|
834
|
+
const activeNote = this.getActiveNote(noteList, now);
|
|
785
835
|
if (activeNote) {
|
|
786
836
|
const notePromise = this.scheduleNoteRelease(channelNumber, noteNumber, velocity, now, stopPedal);
|
|
787
837
|
promises.push(notePromise);
|
|
@@ -798,8 +848,8 @@ export class MidyGMLite {
|
|
|
798
848
|
const velocity = 0;
|
|
799
849
|
const stopPedal = false;
|
|
800
850
|
const promises = [];
|
|
801
|
-
channel.scheduledNotes.forEach((
|
|
802
|
-
const activeNote = this.
|
|
851
|
+
channel.scheduledNotes.forEach((noteList) => {
|
|
852
|
+
const activeNote = this.getActiveNote(noteList, now);
|
|
803
853
|
if (activeNote) {
|
|
804
854
|
const notePromise = this.scheduleNoteRelease(channelNumber, noteNumber, velocity, now, stopPedal);
|
|
805
855
|
promises.push(notePromise);
|
|
@@ -848,13 +898,18 @@ export class MidyGMLite {
|
|
|
848
898
|
}
|
|
849
899
|
}
|
|
850
900
|
handleMasterVolumeSysEx(data) {
|
|
851
|
-
const volume = (data[5] * 128 + data[4]
|
|
901
|
+
const volume = (data[5] * 128 + data[4]) / 16383;
|
|
852
902
|
this.handleMasterVolume(volume);
|
|
853
903
|
}
|
|
854
904
|
handleMasterVolume(volume) {
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
905
|
+
if (volume < 0 && 1 < volume) {
|
|
906
|
+
console.error("Master Volume is out of range");
|
|
907
|
+
}
|
|
908
|
+
else {
|
|
909
|
+
const now = this.audioContext.currentTime;
|
|
910
|
+
this.masterGain.gain.cancelScheduledValues(now);
|
|
911
|
+
this.masterGain.gain.setValueAtTime(volume * volume, now);
|
|
912
|
+
}
|
|
858
913
|
}
|
|
859
914
|
handleExclusiveMessage(data) {
|
|
860
915
|
console.warn(`Unsupported Exclusive Message ${data}`);
|
|
@@ -888,9 +943,6 @@ Object.defineProperty(MidyGMLite, "channelSettings", {
|
|
|
888
943
|
value: {
|
|
889
944
|
volume: 100 / 127,
|
|
890
945
|
pan: 0,
|
|
891
|
-
vibratoRate: 5,
|
|
892
|
-
vibratoDepth: 0.5,
|
|
893
|
-
vibratoDelay: 2.5,
|
|
894
946
|
bank: 0,
|
|
895
947
|
dataMSB: 0,
|
|
896
948
|
dataLSB: 0,
|