@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/script/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
|
|
@@ -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/script/midy-GMLite.js
CHANGED
|
@@ -2,7 +2,45 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.MidyGMLite = void 0;
|
|
4
4
|
const _esm_js_1 = require("./deps/cdn.jsdelivr.net/npm/midi-file@1.2.4/+esm.js");
|
|
5
|
-
const _esm_js_2 = require("./deps/cdn.jsdelivr.net/npm/@marmooo/soundfont-parser@0.0.
|
|
5
|
+
const _esm_js_2 = require("./deps/cdn.jsdelivr.net/npm/@marmooo/soundfont-parser@0.0.2/+esm.js");
|
|
6
|
+
class Note {
|
|
7
|
+
constructor(noteNumber, velocity, startTime, instrumentKey) {
|
|
8
|
+
Object.defineProperty(this, "bufferSource", {
|
|
9
|
+
enumerable: true,
|
|
10
|
+
configurable: true,
|
|
11
|
+
writable: true,
|
|
12
|
+
value: void 0
|
|
13
|
+
});
|
|
14
|
+
Object.defineProperty(this, "gainNode", {
|
|
15
|
+
enumerable: true,
|
|
16
|
+
configurable: true,
|
|
17
|
+
writable: true,
|
|
18
|
+
value: void 0
|
|
19
|
+
});
|
|
20
|
+
Object.defineProperty(this, "filterNode", {
|
|
21
|
+
enumerable: true,
|
|
22
|
+
configurable: true,
|
|
23
|
+
writable: true,
|
|
24
|
+
value: void 0
|
|
25
|
+
});
|
|
26
|
+
Object.defineProperty(this, "modLFO", {
|
|
27
|
+
enumerable: true,
|
|
28
|
+
configurable: true,
|
|
29
|
+
writable: true,
|
|
30
|
+
value: void 0
|
|
31
|
+
});
|
|
32
|
+
Object.defineProperty(this, "modLFOGain", {
|
|
33
|
+
enumerable: true,
|
|
34
|
+
configurable: true,
|
|
35
|
+
writable: true,
|
|
36
|
+
value: void 0
|
|
37
|
+
});
|
|
38
|
+
this.noteNumber = noteNumber;
|
|
39
|
+
this.velocity = velocity;
|
|
40
|
+
this.startTime = startTime;
|
|
41
|
+
this.instrumentKey = instrumentKey;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
6
44
|
class MidyGMLite {
|
|
7
45
|
constructor(audioContext) {
|
|
8
46
|
Object.defineProperty(this, "ticksPerBeat", {
|
|
@@ -154,14 +192,11 @@ class MidyGMLite {
|
|
|
154
192
|
const pannerNode = new StereoPannerNode(audioContext, {
|
|
155
193
|
pan: MidyGMLite.channelSettings.pan,
|
|
156
194
|
});
|
|
157
|
-
const modulationEffect = this.createModulationEffect(audioContext);
|
|
158
|
-
modulationEffect.lfo.start();
|
|
159
195
|
pannerNode.connect(gainNode);
|
|
160
196
|
gainNode.connect(this.masterGain);
|
|
161
197
|
return {
|
|
162
198
|
gainNode,
|
|
163
199
|
pannerNode,
|
|
164
|
-
modulationEffect,
|
|
165
200
|
};
|
|
166
201
|
}
|
|
167
202
|
createChannels(audioContext) {
|
|
@@ -171,16 +206,15 @@ class MidyGMLite {
|
|
|
171
206
|
...MidyGMLite.effectSettings,
|
|
172
207
|
...this.setChannelAudioNodes(audioContext),
|
|
173
208
|
scheduledNotes: new Map(),
|
|
174
|
-
sostenutoNotes: new Map(),
|
|
175
209
|
};
|
|
176
210
|
});
|
|
177
211
|
return channels;
|
|
178
212
|
}
|
|
179
|
-
async createNoteBuffer(
|
|
180
|
-
const sampleEnd =
|
|
213
|
+
async createNoteBuffer(instrumentKey, isSF3) {
|
|
214
|
+
const sampleEnd = instrumentKey.sample.length + instrumentKey.end;
|
|
181
215
|
if (isSF3) {
|
|
182
|
-
const sample = new Uint8Array(
|
|
183
|
-
sample.set(
|
|
216
|
+
const sample = new Uint8Array(instrumentKey.sample.length);
|
|
217
|
+
sample.set(instrumentKey.sample);
|
|
184
218
|
const audioBuffer = await this.audioContext.decodeAudioData(sample.buffer);
|
|
185
219
|
for (let channel = 0; channel < audioBuffer.numberOfChannels; channel++) {
|
|
186
220
|
const channelData = audioBuffer.getChannelData(channel);
|
|
@@ -189,26 +223,27 @@ class MidyGMLite {
|
|
|
189
223
|
return audioBuffer;
|
|
190
224
|
}
|
|
191
225
|
else {
|
|
192
|
-
const sample =
|
|
226
|
+
const sample = instrumentKey.sample.subarray(0, sampleEnd);
|
|
193
227
|
const floatSample = this.convertToFloat32Array(sample);
|
|
194
228
|
const audioBuffer = new AudioBuffer({
|
|
195
229
|
numberOfChannels: 1,
|
|
196
230
|
length: sample.length,
|
|
197
|
-
sampleRate:
|
|
231
|
+
sampleRate: instrumentKey.sampleRate,
|
|
198
232
|
});
|
|
199
233
|
const channelData = audioBuffer.getChannelData(0);
|
|
200
234
|
channelData.set(floatSample);
|
|
201
235
|
return audioBuffer;
|
|
202
236
|
}
|
|
203
237
|
}
|
|
204
|
-
async createNoteBufferNode(
|
|
238
|
+
async createNoteBufferNode(instrumentKey, isSF3) {
|
|
205
239
|
const bufferSource = new AudioBufferSourceNode(this.audioContext);
|
|
206
|
-
const audioBuffer = await this.createNoteBuffer(
|
|
240
|
+
const audioBuffer = await this.createNoteBuffer(instrumentKey, isSF3);
|
|
207
241
|
bufferSource.buffer = audioBuffer;
|
|
208
|
-
bufferSource.loop =
|
|
242
|
+
bufferSource.loop = instrumentKey.sampleModes % 2 !== 0;
|
|
209
243
|
if (bufferSource.loop) {
|
|
210
|
-
bufferSource.loopStart =
|
|
211
|
-
|
|
244
|
+
bufferSource.loopStart = instrumentKey.loopStart /
|
|
245
|
+
instrumentKey.sampleRate;
|
|
246
|
+
bufferSource.loopEnd = instrumentKey.loopEnd / instrumentKey.sampleRate;
|
|
212
247
|
}
|
|
213
248
|
return bufferSource;
|
|
214
249
|
}
|
|
@@ -226,9 +261,6 @@ class MidyGMLite {
|
|
|
226
261
|
if (event.startTime > t + this.lookAhead)
|
|
227
262
|
break;
|
|
228
263
|
switch (event.type) {
|
|
229
|
-
case "controller":
|
|
230
|
-
this.handleControlChange(event.channel, event.controllerType, event.value);
|
|
231
|
-
break;
|
|
232
264
|
case "noteOn":
|
|
233
265
|
if (event.velocity !== 0) {
|
|
234
266
|
await this.scheduleNoteOn(event.channel, event.noteNumber, event.velocity, event.startTime + this.startDelay - offset);
|
|
@@ -242,9 +274,15 @@ class MidyGMLite {
|
|
|
242
274
|
}
|
|
243
275
|
break;
|
|
244
276
|
}
|
|
277
|
+
case "controller":
|
|
278
|
+
this.handleControlChange(event.channel, event.controllerType, event.value);
|
|
279
|
+
break;
|
|
245
280
|
case "programChange":
|
|
246
281
|
this.handleProgramChange(event.channel, event.programNumber);
|
|
247
282
|
break;
|
|
283
|
+
case "pitchBend":
|
|
284
|
+
this.handlePitchBend(event.channel, event.value);
|
|
285
|
+
break;
|
|
248
286
|
case "sysEx":
|
|
249
287
|
this.handleSysEx(event.data);
|
|
250
288
|
}
|
|
@@ -447,30 +485,26 @@ class MidyGMLite {
|
|
|
447
485
|
const now = this.audioContext.currentTime;
|
|
448
486
|
return this.resumeTime + now - this.startTime - this.startDelay;
|
|
449
487
|
}
|
|
450
|
-
getActiveNotes(channel) {
|
|
488
|
+
getActiveNotes(channel, time) {
|
|
451
489
|
const activeNotes = new Map();
|
|
452
|
-
channel.scheduledNotes.forEach((
|
|
453
|
-
const activeNote = this.
|
|
490
|
+
channel.scheduledNotes.forEach((noteList) => {
|
|
491
|
+
const activeNote = this.getActiveNote(noteList, time);
|
|
454
492
|
if (activeNote) {
|
|
455
493
|
activeNotes.set(activeNote.noteNumber, activeNote);
|
|
456
494
|
}
|
|
457
495
|
});
|
|
458
496
|
return activeNotes;
|
|
459
497
|
}
|
|
460
|
-
|
|
461
|
-
for (let i =
|
|
462
|
-
const
|
|
463
|
-
if (
|
|
464
|
-
return
|
|
498
|
+
getActiveNote(noteList, time) {
|
|
499
|
+
for (let i = noteList.length - 1; i >= 0; i--) {
|
|
500
|
+
const note = noteList[i];
|
|
501
|
+
if (!note)
|
|
502
|
+
return;
|
|
503
|
+
if (time < note.startTime)
|
|
504
|
+
continue;
|
|
505
|
+
return (note.ending) ? null : note;
|
|
465
506
|
}
|
|
466
|
-
|
|
467
|
-
createModulationEffect(audioContext) {
|
|
468
|
-
const lfo = new OscillatorNode(audioContext, {
|
|
469
|
-
frequency: 5,
|
|
470
|
-
});
|
|
471
|
-
return {
|
|
472
|
-
lfo,
|
|
473
|
-
};
|
|
507
|
+
return noteList[0];
|
|
474
508
|
}
|
|
475
509
|
connectNoteEffects(channel, gainNode) {
|
|
476
510
|
gainNode.connect(channel.pannerNode);
|
|
@@ -481,70 +515,92 @@ class MidyGMLite {
|
|
|
481
515
|
centToHz(cent) {
|
|
482
516
|
return 8.176 * Math.pow(2, cent / 1200);
|
|
483
517
|
}
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
518
|
+
calcSemitoneOffset(channel) {
|
|
519
|
+
return channel.pitchBend * channel.pitchBendRange;
|
|
520
|
+
}
|
|
521
|
+
calcPlaybackRate(instrumentKey, noteNumber, semitoneOffset) {
|
|
522
|
+
return instrumentKey.playbackRate(noteNumber) *
|
|
487
523
|
Math.pow(2, semitoneOffset / 12);
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
524
|
+
}
|
|
525
|
+
setVolumeEnvelope(channel, note) {
|
|
526
|
+
const { instrumentKey, startTime, velocity } = note;
|
|
527
|
+
note.gainNode = new GainNode(this.audioContext, {
|
|
492
528
|
gain: 0,
|
|
493
529
|
});
|
|
494
530
|
let volume = (velocity / 127) * channel.volume * channel.expression;
|
|
495
531
|
if (volume === 0)
|
|
496
532
|
volume = 1e-6; // exponentialRampToValueAtTime() requires a non-zero value
|
|
497
|
-
const attackVolume = this.cbToRatio(-
|
|
498
|
-
|
|
499
|
-
const
|
|
500
|
-
const
|
|
501
|
-
const
|
|
502
|
-
const
|
|
503
|
-
|
|
533
|
+
const attackVolume = this.cbToRatio(-instrumentKey.initialAttenuation) *
|
|
534
|
+
volume;
|
|
535
|
+
const sustainVolume = attackVolume * (1 - instrumentKey.volSustain);
|
|
536
|
+
const volDelay = startTime + instrumentKey.volDelay;
|
|
537
|
+
const volAttack = volDelay + instrumentKey.volAttack;
|
|
538
|
+
const volHold = volAttack + instrumentKey.volHold;
|
|
539
|
+
const volDecay = volHold + instrumentKey.volDecay;
|
|
540
|
+
note.gainNode.gain
|
|
504
541
|
.setValueAtTime(1e-6, volDelay) // exponentialRampToValueAtTime() requires a non-zero value
|
|
505
542
|
.exponentialRampToValueAtTime(attackVolume, volAttack)
|
|
506
543
|
.setValueAtTime(attackVolume, volHold)
|
|
507
544
|
.linearRampToValueAtTime(sustainVolume, volDecay);
|
|
508
|
-
|
|
545
|
+
}
|
|
546
|
+
setFilterEnvelope(channel, note) {
|
|
547
|
+
const { instrumentKey, startTime, noteNumber } = note;
|
|
548
|
+
const softPedalFactor = 1 -
|
|
549
|
+
(0.1 + (noteNumber / 127) * 0.2) * channel.softPedal;
|
|
509
550
|
const maxFreq = this.audioContext.sampleRate / 2;
|
|
510
|
-
const baseFreq = this.centToHz(
|
|
511
|
-
|
|
512
|
-
const
|
|
513
|
-
|
|
551
|
+
const baseFreq = this.centToHz(instrumentKey.initialFilterFc) *
|
|
552
|
+
softPedalFactor;
|
|
553
|
+
const peekFreq = this.centToHz(instrumentKey.initialFilterFc + instrumentKey.modEnvToFilterFc) * softPedalFactor;
|
|
554
|
+
const sustainFreq = (baseFreq +
|
|
555
|
+
(peekFreq - baseFreq) * (1 - instrumentKey.modSustain)) * softPedalFactor;
|
|
556
|
+
const modDelay = startTime + instrumentKey.modDelay;
|
|
557
|
+
const modAttack = modDelay + instrumentKey.modAttack;
|
|
558
|
+
const modHold = modAttack + instrumentKey.modHold;
|
|
559
|
+
const modDecay = modHold + instrumentKey.modDecay;
|
|
514
560
|
const adjustedBaseFreq = Math.min(maxFreq, baseFreq);
|
|
515
561
|
const adjustedPeekFreq = Math.min(maxFreq, peekFreq);
|
|
516
562
|
const adjustedSustainFreq = Math.min(maxFreq, sustainFreq);
|
|
517
|
-
|
|
563
|
+
note.filterNode = new BiquadFilterNode(this.audioContext, {
|
|
518
564
|
type: "lowpass",
|
|
519
|
-
Q:
|
|
565
|
+
Q: instrumentKey.initialFilterQ / 10, // dB
|
|
520
566
|
frequency: adjustedBaseFreq,
|
|
521
567
|
});
|
|
522
|
-
|
|
523
|
-
const modAttack = modDelay + noteInfo.modAttack;
|
|
524
|
-
const modHold = modAttack + noteInfo.modHold;
|
|
525
|
-
const modDecay = modHold + noteInfo.modDecay;
|
|
526
|
-
filterNode.frequency
|
|
568
|
+
note.filterNode.frequency
|
|
527
569
|
.setValueAtTime(adjustedBaseFreq, modDelay)
|
|
528
570
|
.exponentialRampToValueAtTime(adjustedPeekFreq, modAttack)
|
|
529
571
|
.setValueAtTime(adjustedPeekFreq, modHold)
|
|
530
572
|
.linearRampToValueAtTime(adjustedSustainFreq, modDecay);
|
|
531
|
-
|
|
573
|
+
note.bufferSource.detune.setValueAtTime(note.bufferSource.detune.value + instrumentKey.modEnvToPitch, modDelay);
|
|
574
|
+
}
|
|
575
|
+
startModulation(channel, note, time) {
|
|
576
|
+
const { instrumentKey } = note;
|
|
577
|
+
note.modLFOGain = new GainNode(this.audioContext, {
|
|
578
|
+
gain: this.cbToRatio(instrumentKey.modLfoToVolume) * channel.modulation,
|
|
579
|
+
});
|
|
580
|
+
note.modLFO = new OscillatorNode(this.audioContext, {
|
|
581
|
+
frequency: this.centToHz(instrumentKey.freqModLFO),
|
|
582
|
+
});
|
|
583
|
+
note.modLFO.start(time);
|
|
584
|
+
note.filterNode.frequency.setValueAtTime(note.filterNode.frequency.value + instrumentKey.modLfoToFilterFc, time);
|
|
585
|
+
note.bufferSource.detune.setValueAtTime(note.bufferSource.detune.value + instrumentKey.modLfoToPitch, time);
|
|
586
|
+
note.modLFO.connect(note.modLFOGain);
|
|
587
|
+
note.modLFOGain.connect(note.bufferSource.detune);
|
|
588
|
+
}
|
|
589
|
+
async createNote(channel, instrumentKey, noteNumber, velocity, startTime, isSF3) {
|
|
590
|
+
const semitoneOffset = this.calcSemitoneOffset(channel);
|
|
591
|
+
const note = new Note(noteNumber, velocity, startTime, instrumentKey);
|
|
592
|
+
note.bufferSource = await this.createNoteBufferNode(instrumentKey, isSF3);
|
|
593
|
+
note.bufferSource.playbackRate.value = this.calcPlaybackRate(instrumentKey, noteNumber, semitoneOffset);
|
|
594
|
+
this.setVolumeEnvelope(channel, note);
|
|
595
|
+
this.setFilterEnvelope(channel, note);
|
|
532
596
|
if (channel.modulation > 0) {
|
|
533
|
-
const
|
|
534
|
-
|
|
535
|
-
lfoGain = new GainNode(this.audioContext, {
|
|
536
|
-
gain: 0,
|
|
537
|
-
});
|
|
538
|
-
lfoGain.gain
|
|
539
|
-
.setValueAtTime(1e-6, vibratoDelay) // exponentialRampToValueAtTime() requires a non-zero value
|
|
540
|
-
.exponentialRampToValueAtTime(channel.modulation, vibratoAttack);
|
|
541
|
-
channel.modulationEffect.lfo.connect(lfoGain);
|
|
542
|
-
lfoGain.connect(bufferSource.detune);
|
|
597
|
+
const delayModLFO = startTime + instrumentKey.delayModLFO;
|
|
598
|
+
this.startModulation(channel, note, delayModLFO);
|
|
543
599
|
}
|
|
544
|
-
bufferSource.connect(filterNode);
|
|
545
|
-
filterNode.connect(gainNode);
|
|
546
|
-
bufferSource.start(startTime,
|
|
547
|
-
return
|
|
600
|
+
note.bufferSource.connect(note.filterNode);
|
|
601
|
+
note.filterNode.connect(note.gainNode);
|
|
602
|
+
note.bufferSource.start(startTime, instrumentKey.start / instrumentKey.sampleRate);
|
|
603
|
+
return note;
|
|
548
604
|
}
|
|
549
605
|
async scheduleNoteOn(channelNumber, noteNumber, velocity, startTime) {
|
|
550
606
|
const channel = this.channels[channelNumber];
|
|
@@ -554,27 +610,17 @@ class MidyGMLite {
|
|
|
554
610
|
return;
|
|
555
611
|
const soundFont = this.soundFonts[soundFontIndex];
|
|
556
612
|
const isSF3 = soundFont.parsed.info.version.major === 3;
|
|
557
|
-
const
|
|
558
|
-
if (!
|
|
613
|
+
const instrumentKey = soundFont.getInstrumentKey(bankNumber, channel.program, noteNumber);
|
|
614
|
+
if (!instrumentKey)
|
|
559
615
|
return;
|
|
560
|
-
const
|
|
561
|
-
|
|
562
|
-
this.connectNoteEffects(channel, gainNode);
|
|
616
|
+
const note = await this.createNote(channel, instrumentKey, noteNumber, velocity, startTime, isSF3);
|
|
617
|
+
this.connectNoteEffects(channel, note.gainNode);
|
|
563
618
|
const scheduledNotes = channel.scheduledNotes;
|
|
564
|
-
const scheduledNote = {
|
|
565
|
-
bufferSource,
|
|
566
|
-
filterNode,
|
|
567
|
-
gainNode,
|
|
568
|
-
lfoGain,
|
|
569
|
-
noteInfo,
|
|
570
|
-
noteNumber,
|
|
571
|
-
startTime,
|
|
572
|
-
};
|
|
573
619
|
if (scheduledNotes.has(noteNumber)) {
|
|
574
|
-
scheduledNotes.get(noteNumber).push(
|
|
620
|
+
scheduledNotes.get(noteNumber).push(note);
|
|
575
621
|
}
|
|
576
622
|
else {
|
|
577
|
-
scheduledNotes.set(noteNumber, [
|
|
623
|
+
scheduledNotes.set(noteNumber, [note]);
|
|
578
624
|
}
|
|
579
625
|
}
|
|
580
626
|
noteOn(channelNumber, noteNumber, velocity) {
|
|
@@ -594,15 +640,15 @@ class MidyGMLite {
|
|
|
594
640
|
continue;
|
|
595
641
|
if (targetNote.ending)
|
|
596
642
|
continue;
|
|
597
|
-
const { bufferSource, filterNode, gainNode,
|
|
643
|
+
const { bufferSource, filterNode, gainNode, modLFO, modLFOGain, instrumentKey, } = targetNote;
|
|
598
644
|
const velocityRate = (velocity + 127) / 127;
|
|
599
|
-
const volEndTime = stopTime +
|
|
645
|
+
const volEndTime = stopTime + instrumentKey.volRelease * velocityRate;
|
|
600
646
|
gainNode.gain.cancelScheduledValues(stopTime);
|
|
601
647
|
gainNode.gain.linearRampToValueAtTime(0, volEndTime);
|
|
602
648
|
const maxFreq = this.audioContext.sampleRate / 2;
|
|
603
|
-
const baseFreq = this.centToHz(
|
|
649
|
+
const baseFreq = this.centToHz(instrumentKey.initialFilterFc);
|
|
604
650
|
const adjustedBaseFreq = Math.min(maxFreq, baseFreq);
|
|
605
|
-
const modEndTime = stopTime +
|
|
651
|
+
const modEndTime = stopTime + instrumentKey.modRelease * velocityRate;
|
|
606
652
|
filterNode.frequency
|
|
607
653
|
.cancelScheduledValues(stopTime)
|
|
608
654
|
.linearRampToValueAtTime(adjustedBaseFreq, modEndTime);
|
|
@@ -616,8 +662,10 @@ class MidyGMLite {
|
|
|
616
662
|
bufferSource.disconnect(0);
|
|
617
663
|
filterNode.disconnect(0);
|
|
618
664
|
gainNode.disconnect(0);
|
|
619
|
-
if (
|
|
620
|
-
|
|
665
|
+
if (modLFOGain)
|
|
666
|
+
modLFOGain.disconnect(0);
|
|
667
|
+
if (modLFO)
|
|
668
|
+
modLFO.stop();
|
|
621
669
|
resolve();
|
|
622
670
|
};
|
|
623
671
|
bufferSource.stop(volEndTime);
|
|
@@ -652,46 +700,37 @@ class MidyGMLite {
|
|
|
652
700
|
return this.releaseNote(channelNumber, data1, data2);
|
|
653
701
|
case 0x90:
|
|
654
702
|
return this.noteOn(channelNumber, data1, data2);
|
|
655
|
-
case 0xA0:
|
|
656
|
-
return this.handlePolyphonicKeyPressure(channelNumber, data1, data2);
|
|
657
703
|
case 0xB0:
|
|
658
704
|
return this.handleControlChange(channelNumber, data1, data2);
|
|
659
705
|
case 0xC0:
|
|
660
706
|
return this.handleProgramChange(channelNumber, data1);
|
|
661
|
-
case 0xD0:
|
|
662
|
-
return this.handleChannelPressure(channelNumber, data1);
|
|
663
707
|
case 0xE0:
|
|
664
|
-
return this.
|
|
708
|
+
return this.handlePitchBendMessage(channelNumber, data1, data2);
|
|
665
709
|
default:
|
|
666
710
|
console.warn(`Unsupported MIDI message: ${messageType.toString(16)}`);
|
|
667
711
|
}
|
|
668
712
|
}
|
|
669
|
-
handlePolyphonicKeyPressure(channelNumber, noteNumber, pressure) {
|
|
670
|
-
const now = this.audioContext.currentTime;
|
|
671
|
-
const channel = this.channels[channelNumber];
|
|
672
|
-
const scheduledNotes = channel.scheduledNotes.get(noteNumber);
|
|
673
|
-
pressure /= 127;
|
|
674
|
-
if (scheduledNotes) {
|
|
675
|
-
scheduledNotes.forEach((scheduledNote) => {
|
|
676
|
-
if (scheduledNote) {
|
|
677
|
-
const { initialAttenuation } = scheduledNote.noteInfo;
|
|
678
|
-
const gain = this.cbToRatio(-initialAttenuation) * pressure;
|
|
679
|
-
scheduledNote.gainNode.gain.cancelScheduledValues(now);
|
|
680
|
-
scheduledNote.gainNode.gain.setValueAtTime(gain, now);
|
|
681
|
-
}
|
|
682
|
-
});
|
|
683
|
-
}
|
|
684
|
-
}
|
|
685
713
|
handleProgramChange(channelNumber, program) {
|
|
686
714
|
const channel = this.channels[channelNumber];
|
|
687
715
|
channel.program = program;
|
|
688
716
|
}
|
|
689
|
-
|
|
690
|
-
|
|
717
|
+
handlePitchBendMessage(channelNumber, lsb, msb) {
|
|
718
|
+
const pitchBend = msb * 128 + lsb;
|
|
719
|
+
this.handlePitchBend(channelNumber, pitchBend);
|
|
691
720
|
}
|
|
692
|
-
handlePitchBend(channelNumber,
|
|
693
|
-
const
|
|
694
|
-
this.channels[channelNumber]
|
|
721
|
+
handlePitchBend(channelNumber, pitchBend) {
|
|
722
|
+
const now = this.audioContext.currentTime;
|
|
723
|
+
const channel = this.channels[channelNumber];
|
|
724
|
+
channel.pitchBend = (pitchBend - 8192) / 8192;
|
|
725
|
+
const semitoneOffset = this.calcSemitoneOffset(channel);
|
|
726
|
+
const activeNotes = this.getActiveNotes(channel, now);
|
|
727
|
+
activeNotes.forEach((activeNote) => {
|
|
728
|
+
const { bufferSource, instrumentKey, noteNumber } = activeNote;
|
|
729
|
+
const playbackRate = calcPlaybackRate(instrumentKey, noteNumber, semitoneOffset);
|
|
730
|
+
bufferSource.playbackRate
|
|
731
|
+
.cancelScheduledValues(now)
|
|
732
|
+
.setValueAtTime(playbackRate * pressure, now);
|
|
733
|
+
});
|
|
695
734
|
}
|
|
696
735
|
handleControlChange(channelNumber, controller, value) {
|
|
697
736
|
switch (controller) {
|
|
@@ -724,9 +763,20 @@ class MidyGMLite {
|
|
|
724
763
|
}
|
|
725
764
|
}
|
|
726
765
|
setModulation(channelNumber, modulation) {
|
|
766
|
+
const now = this.audioContext.currentTime;
|
|
727
767
|
const channel = this.channels[channelNumber];
|
|
728
768
|
channel.modulation = (modulation / 127) *
|
|
729
769
|
(channel.modulationDepthRange * 100);
|
|
770
|
+
const activeNotes = this.getActiveNotes(channel, now);
|
|
771
|
+
activeNotes.forEach((activeNote) => {
|
|
772
|
+
if (activeNote.modLFO) {
|
|
773
|
+
activeNote.gainNode.gain.setValueAtTime(this.cbToRatio(activeNote.instrumentKey.modLfoToVolume) *
|
|
774
|
+
channel.modulation, now);
|
|
775
|
+
}
|
|
776
|
+
else {
|
|
777
|
+
this.startModulation(channel, activeNote, now);
|
|
778
|
+
}
|
|
779
|
+
});
|
|
730
780
|
}
|
|
731
781
|
setVolume(channelNumber, volume) {
|
|
732
782
|
const channel = this.channels[channelNumber];
|
|
@@ -783,8 +833,8 @@ class MidyGMLite {
|
|
|
783
833
|
const velocity = 0;
|
|
784
834
|
const stopPedal = true;
|
|
785
835
|
const promises = [];
|
|
786
|
-
channel.scheduledNotes.forEach((
|
|
787
|
-
const activeNote = this.
|
|
836
|
+
channel.scheduledNotes.forEach((noteList) => {
|
|
837
|
+
const activeNote = this.getActiveNote(noteList, now);
|
|
788
838
|
if (activeNote) {
|
|
789
839
|
const notePromise = this.scheduleNoteRelease(channelNumber, noteNumber, velocity, now, stopPedal);
|
|
790
840
|
promises.push(notePromise);
|
|
@@ -801,8 +851,8 @@ class MidyGMLite {
|
|
|
801
851
|
const velocity = 0;
|
|
802
852
|
const stopPedal = false;
|
|
803
853
|
const promises = [];
|
|
804
|
-
channel.scheduledNotes.forEach((
|
|
805
|
-
const activeNote = this.
|
|
854
|
+
channel.scheduledNotes.forEach((noteList) => {
|
|
855
|
+
const activeNote = this.getActiveNote(noteList, now);
|
|
806
856
|
if (activeNote) {
|
|
807
857
|
const notePromise = this.scheduleNoteRelease(channelNumber, noteNumber, velocity, now, stopPedal);
|
|
808
858
|
promises.push(notePromise);
|
|
@@ -851,13 +901,18 @@ class MidyGMLite {
|
|
|
851
901
|
}
|
|
852
902
|
}
|
|
853
903
|
handleMasterVolumeSysEx(data) {
|
|
854
|
-
const volume = (data[5] * 128 + data[4]
|
|
904
|
+
const volume = (data[5] * 128 + data[4]) / 16383;
|
|
855
905
|
this.handleMasterVolume(volume);
|
|
856
906
|
}
|
|
857
907
|
handleMasterVolume(volume) {
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
908
|
+
if (volume < 0 && 1 < volume) {
|
|
909
|
+
console.error("Master Volume is out of range");
|
|
910
|
+
}
|
|
911
|
+
else {
|
|
912
|
+
const now = this.audioContext.currentTime;
|
|
913
|
+
this.masterGain.gain.cancelScheduledValues(now);
|
|
914
|
+
this.masterGain.gain.setValueAtTime(volume * volume, now);
|
|
915
|
+
}
|
|
861
916
|
}
|
|
862
917
|
handleExclusiveMessage(data) {
|
|
863
918
|
console.warn(`Unsupported Exclusive Message ${data}`);
|
|
@@ -892,9 +947,6 @@ Object.defineProperty(MidyGMLite, "channelSettings", {
|
|
|
892
947
|
value: {
|
|
893
948
|
volume: 100 / 127,
|
|
894
949
|
pan: 0,
|
|
895
|
-
vibratoRate: 5,
|
|
896
|
-
vibratoDepth: 0.5,
|
|
897
|
-
vibratoDelay: 2.5,
|
|
898
950
|
bank: 0,
|
|
899
951
|
dataMSB: 0,
|
|
900
952
|
dataLSB: 0,
|