@marmooo/midy 0.1.6 → 0.2.0
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 +53 -27
- package/esm/midy-GM1.d.ts.map +1 -1
- package/esm/midy-GM1.js +398 -146
- package/esm/midy-GM2.d.ts +55 -35
- package/esm/midy-GM2.d.ts.map +1 -1
- package/esm/midy-GM2.js +646 -244
- package/esm/midy-GMLite.d.ts +51 -26
- package/esm/midy-GMLite.d.ts.map +1 -1
- package/esm/midy-GMLite.js +379 -148
- package/esm/midy.d.ts +55 -40
- package/esm/midy.d.ts.map +1 -1
- package/esm/midy.js +662 -263
- package/package.json +5 -1
- package/script/midy-GM1.d.ts +53 -27
- package/script/midy-GM1.d.ts.map +1 -1
- package/script/midy-GM1.js +401 -149
- package/script/midy-GM2.d.ts +55 -35
- package/script/midy-GM2.d.ts.map +1 -1
- package/script/midy-GM2.js +649 -247
- package/script/midy-GMLite.d.ts +51 -26
- package/script/midy-GMLite.d.ts.map +1 -1
- package/script/midy-GMLite.js +382 -151
- package/script/midy.d.ts +55 -40
- package/script/midy.d.ts.map +1 -1
- package/script/midy.js +665 -266
- package/esm/deps/cdn.jsdelivr.net/npm/@marmooo/soundfont-parser@0.0.6/+esm.d.ts +0 -149
- package/esm/deps/cdn.jsdelivr.net/npm/@marmooo/soundfont-parser@0.0.6/+esm.d.ts.map +0 -1
- package/esm/deps/cdn.jsdelivr.net/npm/@marmooo/soundfont-parser@0.0.6/+esm.js +0 -180
- package/esm/deps/cdn.jsdelivr.net/npm/midi-file@1.2.4/+esm.d.ts +0 -84
- package/esm/deps/cdn.jsdelivr.net/npm/midi-file@1.2.4/+esm.d.ts.map +0 -1
- package/esm/deps/cdn.jsdelivr.net/npm/midi-file@1.2.4/+esm.js +0 -216
- package/script/deps/cdn.jsdelivr.net/npm/@marmooo/soundfont-parser@0.0.6/+esm.d.ts +0 -149
- package/script/deps/cdn.jsdelivr.net/npm/@marmooo/soundfont-parser@0.0.6/+esm.d.ts.map +0 -1
- package/script/deps/cdn.jsdelivr.net/npm/@marmooo/soundfont-parser@0.0.6/+esm.js +0 -190
- package/script/deps/cdn.jsdelivr.net/npm/midi-file@1.2.4/+esm.d.ts +0 -84
- package/script/deps/cdn.jsdelivr.net/npm/midi-file@1.2.4/+esm.d.ts.map +0 -1
- package/script/deps/cdn.jsdelivr.net/npm/midi-file@1.2.4/+esm.js +0 -221
package/script/midy-GMLite.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.MidyGMLite = void 0;
|
|
4
|
-
const
|
|
5
|
-
const
|
|
4
|
+
const midi_file_1 = require("midi-file");
|
|
5
|
+
const soundfont_parser_1 = require("@marmooo/soundfont-parser");
|
|
6
6
|
class Note {
|
|
7
|
-
constructor(noteNumber, velocity, startTime,
|
|
7
|
+
constructor(noteNumber, velocity, startTime, voice, voiceParams) {
|
|
8
8
|
Object.defineProperty(this, "bufferSource", {
|
|
9
9
|
enumerable: true,
|
|
10
10
|
configurable: true,
|
|
@@ -44,9 +44,75 @@ class Note {
|
|
|
44
44
|
this.noteNumber = noteNumber;
|
|
45
45
|
this.velocity = velocity;
|
|
46
46
|
this.startTime = startTime;
|
|
47
|
-
this.
|
|
47
|
+
this.voice = voice;
|
|
48
|
+
this.voiceParams = voiceParams;
|
|
48
49
|
}
|
|
49
50
|
}
|
|
51
|
+
// normalized to 0-1 for use with the SF2 modulator model
|
|
52
|
+
const defaultControllerState = {
|
|
53
|
+
noteOnVelocity: { type: 2, defaultValue: 0 },
|
|
54
|
+
noteOnKeyNumber: { type: 3, defaultValue: 0 },
|
|
55
|
+
pitchWheel: { type: 14, defaultValue: 8192 / 16383 },
|
|
56
|
+
pitchWheelSensitivity: { type: 16, defaultValue: 2 / 128 },
|
|
57
|
+
link: { type: 127, defaultValue: 0 },
|
|
58
|
+
// bankMSB: { type: 128 + 0, defaultValue: 121, },
|
|
59
|
+
modulationDepth: { type: 128 + 1, defaultValue: 0 },
|
|
60
|
+
// dataMSB: { type: 128 + 6, defaultValue: 0, },
|
|
61
|
+
volume: { type: 128 + 7, defaultValue: 100 / 127 },
|
|
62
|
+
pan: { type: 128 + 10, defaultValue: 0.5 },
|
|
63
|
+
expression: { type: 128 + 11, defaultValue: 1 },
|
|
64
|
+
// bankLSB: { type: 128 + 32, defaultValue: 0, },
|
|
65
|
+
// dataLSB: { type: 128 + 38, defaultValue: 0, },
|
|
66
|
+
sustainPedal: { type: 128 + 64, defaultValue: 0 },
|
|
67
|
+
// rpnLSB: { type: 128 + 100, defaultValue: 127 },
|
|
68
|
+
// rpnMSB: { type: 128 + 101, defaultValue: 127 },
|
|
69
|
+
// allSoundOff: { type: 128 + 120, defaultValue: 0 },
|
|
70
|
+
// resetAllControllers: { type: 128 + 121, defaultValue: 0 },
|
|
71
|
+
// allNotesOff: { type: 128 + 123, defaultValue: 0 },
|
|
72
|
+
};
|
|
73
|
+
class ControllerState {
|
|
74
|
+
constructor() {
|
|
75
|
+
Object.defineProperty(this, "array", {
|
|
76
|
+
enumerable: true,
|
|
77
|
+
configurable: true,
|
|
78
|
+
writable: true,
|
|
79
|
+
value: new Float32Array(256)
|
|
80
|
+
});
|
|
81
|
+
const entries = Object.entries(defaultControllerState);
|
|
82
|
+
for (const [name, { type, defaultValue }] of entries) {
|
|
83
|
+
this.array[type] = defaultValue;
|
|
84
|
+
Object.defineProperty(this, name, {
|
|
85
|
+
get: () => this.array[type],
|
|
86
|
+
set: (value) => this.array[type] = value,
|
|
87
|
+
enumerable: true,
|
|
88
|
+
configurable: true,
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
const filterEnvelopeKeys = [
|
|
94
|
+
"modEnvToPitch",
|
|
95
|
+
"initialFilterFc",
|
|
96
|
+
"modEnvToFilterFc",
|
|
97
|
+
"modDelay",
|
|
98
|
+
"modAttack",
|
|
99
|
+
"modHold",
|
|
100
|
+
"modDecay",
|
|
101
|
+
"modSustain",
|
|
102
|
+
"modRelease",
|
|
103
|
+
"playbackRate",
|
|
104
|
+
];
|
|
105
|
+
const filterEnvelopeKeySet = new Set(filterEnvelopeKeys);
|
|
106
|
+
const volumeEnvelopeKeys = [
|
|
107
|
+
"volDelay",
|
|
108
|
+
"volAttack",
|
|
109
|
+
"volHold",
|
|
110
|
+
"volDecay",
|
|
111
|
+
"volSustain",
|
|
112
|
+
"volRelease",
|
|
113
|
+
"initialAttenuation",
|
|
114
|
+
];
|
|
115
|
+
const volumeEnvelopeKeySet = new Set(volumeEnvelopeKeys);
|
|
50
116
|
class MidyGMLite {
|
|
51
117
|
constructor(audioContext) {
|
|
52
118
|
Object.defineProperty(this, "ticksPerBeat", {
|
|
@@ -151,8 +217,15 @@ class MidyGMLite {
|
|
|
151
217
|
writable: true,
|
|
152
218
|
value: []
|
|
153
219
|
});
|
|
220
|
+
Object.defineProperty(this, "exclusiveClassMap", {
|
|
221
|
+
enumerable: true,
|
|
222
|
+
configurable: true,
|
|
223
|
+
writable: true,
|
|
224
|
+
value: new Map()
|
|
225
|
+
});
|
|
154
226
|
this.audioContext = audioContext;
|
|
155
227
|
this.masterGain = new GainNode(audioContext);
|
|
228
|
+
this.voiceParamsHandlers = this.createVoiceParamsHandlers();
|
|
156
229
|
this.controlChangeHandlers = this.createControlChangeHandlers();
|
|
157
230
|
this.channels = this.createChannels(audioContext);
|
|
158
231
|
this.masterGain.connect(audioContext.destination);
|
|
@@ -180,14 +253,14 @@ class MidyGMLite {
|
|
|
180
253
|
async loadSoundFont(soundFontUrl) {
|
|
181
254
|
const response = await fetch(soundFontUrl);
|
|
182
255
|
const arrayBuffer = await response.arrayBuffer();
|
|
183
|
-
const parsed = (0,
|
|
184
|
-
const soundFont = new
|
|
256
|
+
const parsed = (0, soundfont_parser_1.parse)(new Uint8Array(arrayBuffer));
|
|
257
|
+
const soundFont = new soundfont_parser_1.SoundFont(parsed);
|
|
185
258
|
this.addSoundFont(soundFont);
|
|
186
259
|
}
|
|
187
260
|
async loadMIDI(midiUrl) {
|
|
188
261
|
const response = await fetch(midiUrl);
|
|
189
262
|
const arrayBuffer = await response.arrayBuffer();
|
|
190
|
-
const midi = (0,
|
|
263
|
+
const midi = (0, midi_file_1.parseMidi)(new Uint8Array(arrayBuffer));
|
|
191
264
|
this.ticksPerBeat = midi.header.ticksPerBeat;
|
|
192
265
|
const midiData = this.extractMidiData(midi);
|
|
193
266
|
this.instruments = midiData.instruments;
|
|
@@ -195,7 +268,7 @@ class MidyGMLite {
|
|
|
195
268
|
this.totalTime = this.calcTotalTime();
|
|
196
269
|
}
|
|
197
270
|
setChannelAudioNodes(audioContext) {
|
|
198
|
-
const { gainLeft, gainRight } = this.panToGain(
|
|
271
|
+
const { gainLeft, gainRight } = this.panToGain(defaultControllerState.pan.defaultValue);
|
|
199
272
|
const gainL = new GainNode(audioContext, { gain: gainLeft });
|
|
200
273
|
const gainR = new GainNode(audioContext, { gain: gainRight });
|
|
201
274
|
const merger = new ChannelMergerNode(audioContext, { numberOfInputs: 2 });
|
|
@@ -212,45 +285,50 @@ class MidyGMLite {
|
|
|
212
285
|
const channels = Array.from({ length: 16 }, () => {
|
|
213
286
|
return {
|
|
214
287
|
...this.constructor.channelSettings,
|
|
215
|
-
|
|
288
|
+
state: new ControllerState(),
|
|
216
289
|
...this.setChannelAudioNodes(audioContext),
|
|
217
290
|
scheduledNotes: new Map(),
|
|
218
291
|
};
|
|
219
292
|
});
|
|
220
293
|
return channels;
|
|
221
294
|
}
|
|
222
|
-
async createNoteBuffer(
|
|
223
|
-
const sampleStart =
|
|
224
|
-
const sampleEnd =
|
|
295
|
+
async createNoteBuffer(voiceParams, isSF3) {
|
|
296
|
+
const sampleStart = voiceParams.start;
|
|
297
|
+
const sampleEnd = voiceParams.sample.length + voiceParams.end;
|
|
225
298
|
if (isSF3) {
|
|
226
|
-
const sample =
|
|
227
|
-
const
|
|
299
|
+
const sample = voiceParams.sample;
|
|
300
|
+
const start = sample.byteOffset + sampleStart;
|
|
301
|
+
const end = sample.byteOffset + sampleEnd;
|
|
302
|
+
const buffer = sample.buffer.slice(start, end);
|
|
303
|
+
const audioBuffer = await this.audioContext.decodeAudioData(buffer);
|
|
228
304
|
return audioBuffer;
|
|
229
305
|
}
|
|
230
306
|
else {
|
|
231
|
-
const sample =
|
|
307
|
+
const sample = voiceParams.sample;
|
|
308
|
+
const start = sample.byteOffset + sampleStart;
|
|
309
|
+
const end = sample.byteOffset + sampleEnd;
|
|
310
|
+
const buffer = sample.buffer.slice(start, end);
|
|
232
311
|
const audioBuffer = new AudioBuffer({
|
|
233
312
|
numberOfChannels: 1,
|
|
234
313
|
length: sample.length,
|
|
235
|
-
sampleRate:
|
|
314
|
+
sampleRate: voiceParams.sampleRate,
|
|
236
315
|
});
|
|
237
316
|
const channelData = audioBuffer.getChannelData(0);
|
|
238
|
-
const int16Array = new Int16Array(
|
|
317
|
+
const int16Array = new Int16Array(buffer);
|
|
239
318
|
for (let i = 0; i < int16Array.length; i++) {
|
|
240
319
|
channelData[i] = int16Array[i] / 32768;
|
|
241
320
|
}
|
|
242
321
|
return audioBuffer;
|
|
243
322
|
}
|
|
244
323
|
}
|
|
245
|
-
async createNoteBufferNode(
|
|
324
|
+
async createNoteBufferNode(voiceParams, isSF3) {
|
|
246
325
|
const bufferSource = new AudioBufferSourceNode(this.audioContext);
|
|
247
|
-
const audioBuffer = await this.createNoteBuffer(
|
|
326
|
+
const audioBuffer = await this.createNoteBuffer(voiceParams, isSF3);
|
|
248
327
|
bufferSource.buffer = audioBuffer;
|
|
249
|
-
bufferSource.loop =
|
|
328
|
+
bufferSource.loop = voiceParams.sampleModes % 2 !== 0;
|
|
250
329
|
if (bufferSource.loop) {
|
|
251
|
-
bufferSource.loopStart =
|
|
252
|
-
|
|
253
|
-
bufferSource.loopEnd = instrumentKey.loopEnd / instrumentKey.sampleRate;
|
|
330
|
+
bufferSource.loopStart = voiceParams.loopStart / voiceParams.sampleRate;
|
|
331
|
+
bufferSource.loopEnd = voiceParams.loopEnd / voiceParams.sampleRate;
|
|
254
332
|
}
|
|
255
333
|
return bufferSource;
|
|
256
334
|
}
|
|
@@ -280,7 +358,7 @@ class MidyGMLite {
|
|
|
280
358
|
this.handleProgramChange(event.channel, event.programNumber);
|
|
281
359
|
break;
|
|
282
360
|
case "pitchBend":
|
|
283
|
-
this.setPitchBend(event.channel, event.value);
|
|
361
|
+
this.setPitchBend(event.channel, event.value + 8192);
|
|
284
362
|
break;
|
|
285
363
|
case "sysEx":
|
|
286
364
|
this.handleSysEx(event.data);
|
|
@@ -309,6 +387,7 @@ class MidyGMLite {
|
|
|
309
387
|
if (queueIndex >= this.timeline.length) {
|
|
310
388
|
await Promise.all(this.notePromises);
|
|
311
389
|
this.notePromises = [];
|
|
390
|
+
this.exclusiveClassMap.clear();
|
|
312
391
|
resolve();
|
|
313
392
|
return;
|
|
314
393
|
}
|
|
@@ -324,6 +403,7 @@ class MidyGMLite {
|
|
|
324
403
|
}
|
|
325
404
|
else if (this.isStopping) {
|
|
326
405
|
await this.stopNotes(0, true);
|
|
406
|
+
this.exclusiveClassMap.clear();
|
|
327
407
|
this.notePromises = [];
|
|
328
408
|
resolve();
|
|
329
409
|
this.isStopping = false;
|
|
@@ -332,6 +412,7 @@ class MidyGMLite {
|
|
|
332
412
|
}
|
|
333
413
|
else if (this.isSeeking) {
|
|
334
414
|
this.stopNotes(0, true);
|
|
415
|
+
this.exclusiveClassMap.clear();
|
|
335
416
|
this.startTime = this.audioContext.currentTime;
|
|
336
417
|
queueIndex = this.getQueueIndex(this.resumeTime);
|
|
337
418
|
offset = this.resumeTime - this.startTime;
|
|
@@ -507,41 +588,49 @@ class MidyGMLite {
|
|
|
507
588
|
return 8.176 * Math.pow(2, cent / 1200);
|
|
508
589
|
}
|
|
509
590
|
calcSemitoneOffset(channel) {
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
return instrumentKey.playbackRate(noteNumber) *
|
|
514
|
-
Math.pow(2, semitoneOffset / 12);
|
|
591
|
+
const pitchWheel = channel.state.pitchWheel * 2 - 1;
|
|
592
|
+
const pitchWheelSensitivity = channel.state.pitchWheelSensitivity * 128;
|
|
593
|
+
return pitchWheel * pitchWheelSensitivity;
|
|
515
594
|
}
|
|
516
595
|
setVolumeEnvelope(note) {
|
|
517
|
-
const
|
|
518
|
-
const
|
|
519
|
-
const
|
|
520
|
-
const
|
|
521
|
-
const
|
|
522
|
-
const
|
|
523
|
-
const
|
|
596
|
+
const now = this.audioContext.currentTime;
|
|
597
|
+
const { voiceParams, startTime } = note;
|
|
598
|
+
const attackVolume = this.cbToRatio(-voiceParams.initialAttenuation);
|
|
599
|
+
const sustainVolume = attackVolume * (1 - voiceParams.volSustain);
|
|
600
|
+
const volDelay = startTime + voiceParams.volDelay;
|
|
601
|
+
const volAttack = volDelay + voiceParams.volAttack;
|
|
602
|
+
const volHold = volAttack + voiceParams.volHold;
|
|
603
|
+
const volDecay = volHold + voiceParams.volDecay;
|
|
524
604
|
note.volumeNode.gain
|
|
525
|
-
.cancelScheduledValues(
|
|
605
|
+
.cancelScheduledValues(now)
|
|
526
606
|
.setValueAtTime(0, startTime)
|
|
527
607
|
.setValueAtTime(1e-6, volDelay) // exponentialRampToValueAtTime() requires a non-zero value
|
|
528
608
|
.exponentialRampToValueAtTime(attackVolume, volAttack)
|
|
529
609
|
.setValueAtTime(attackVolume, volHold)
|
|
530
610
|
.linearRampToValueAtTime(sustainVolume, volDecay);
|
|
531
611
|
}
|
|
532
|
-
|
|
533
|
-
const
|
|
534
|
-
|
|
535
|
-
|
|
612
|
+
setPlaybackRate(note) {
|
|
613
|
+
const now = this.audioContext.currentTime;
|
|
614
|
+
note.bufferSource.playbackRate
|
|
615
|
+
.cancelScheduledValues(now)
|
|
616
|
+
.setValueAtTime(note.voiceParams.playbackRate, now);
|
|
617
|
+
}
|
|
618
|
+
setPitch(channel, note) {
|
|
619
|
+
const now = this.audioContext.currentTime;
|
|
620
|
+
const { startTime } = note;
|
|
621
|
+
const basePitch = this.calcSemitoneOffset(channel) * 100;
|
|
622
|
+
note.bufferSource.detune
|
|
623
|
+
.cancelScheduledValues(now)
|
|
624
|
+
.setValueAtTime(basePitch, startTime);
|
|
625
|
+
const modEnvToPitch = note.voiceParams.modEnvToPitch;
|
|
536
626
|
if (modEnvToPitch === 0)
|
|
537
627
|
return;
|
|
538
|
-
const
|
|
539
|
-
const
|
|
540
|
-
const
|
|
541
|
-
const
|
|
542
|
-
const
|
|
543
|
-
|
|
544
|
-
note.bufferSource.playbackRate.value
|
|
628
|
+
const peekPitch = basePitch + modEnvToPitch;
|
|
629
|
+
const modDelay = startTime + voiceParams.modDelay;
|
|
630
|
+
const modAttack = modDelay + voiceParams.modAttack;
|
|
631
|
+
const modHold = modAttack + voiceParams.modHold;
|
|
632
|
+
const modDecay = modHold + voiceParams.modDecay;
|
|
633
|
+
note.bufferSource.detune
|
|
545
634
|
.setValueAtTime(basePitch, modDelay)
|
|
546
635
|
.exponentialRampToValueAtTime(peekPitch, modAttack)
|
|
547
636
|
.setValueAtTime(peekPitch, modHold)
|
|
@@ -553,20 +642,21 @@ class MidyGMLite {
|
|
|
553
642
|
return Math.max(minFrequency, Math.min(frequency, maxFrequency));
|
|
554
643
|
}
|
|
555
644
|
setFilterEnvelope(note) {
|
|
556
|
-
const
|
|
557
|
-
const
|
|
558
|
-
const
|
|
645
|
+
const now = this.audioContext.currentTime;
|
|
646
|
+
const { voiceParams, startTime } = note;
|
|
647
|
+
const baseFreq = this.centToHz(voiceParams.initialFilterFc);
|
|
648
|
+
const peekFreq = this.centToHz(voiceParams.initialFilterFc + voiceParams.modEnvToFilterFc);
|
|
559
649
|
const sustainFreq = baseFreq +
|
|
560
|
-
(peekFreq - baseFreq) * (1 -
|
|
650
|
+
(peekFreq - baseFreq) * (1 - voiceParams.modSustain);
|
|
561
651
|
const adjustedBaseFreq = this.clampCutoffFrequency(baseFreq);
|
|
562
652
|
const adjustedPeekFreq = this.clampCutoffFrequency(peekFreq);
|
|
563
653
|
const adjustedSustainFreq = this.clampCutoffFrequency(sustainFreq);
|
|
564
|
-
const modDelay = startTime +
|
|
565
|
-
const modAttack = modDelay +
|
|
566
|
-
const modHold = modAttack +
|
|
567
|
-
const modDecay = modHold +
|
|
654
|
+
const modDelay = startTime + voiceParams.modDelay;
|
|
655
|
+
const modAttack = modDelay + voiceParams.modAttack;
|
|
656
|
+
const modHold = modAttack + voiceParams.modHold;
|
|
657
|
+
const modDecay = modHold + voiceParams.modDecay;
|
|
568
658
|
note.filterNode.frequency
|
|
569
|
-
.cancelScheduledValues(
|
|
659
|
+
.cancelScheduledValues(now)
|
|
570
660
|
.setValueAtTime(adjustedBaseFreq, startTime)
|
|
571
661
|
.setValueAtTime(adjustedBaseFreq, modDelay)
|
|
572
662
|
.exponentialRampToValueAtTime(adjustedPeekFreq, modAttack)
|
|
@@ -574,25 +664,18 @@ class MidyGMLite {
|
|
|
574
664
|
.linearRampToValueAtTime(adjustedSustainFreq, modDecay);
|
|
575
665
|
}
|
|
576
666
|
startModulation(channel, note, startTime) {
|
|
577
|
-
const {
|
|
578
|
-
const { modLfoToPitch, modLfoToVolume } = instrumentKey;
|
|
667
|
+
const { voiceParams } = note;
|
|
579
668
|
note.modulationLFO = new OscillatorNode(this.audioContext, {
|
|
580
|
-
frequency: this.centToHz(
|
|
669
|
+
frequency: this.centToHz(voiceParams.freqModLFO),
|
|
581
670
|
});
|
|
582
671
|
note.filterDepth = new GainNode(this.audioContext, {
|
|
583
|
-
gain:
|
|
672
|
+
gain: voiceParams.modLfoToFilterFc,
|
|
584
673
|
});
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
note.
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
const volumeDepth = this.cbToRatio(Math.abs(modLfoToVolume)) - 1;
|
|
591
|
-
const volumeDepthSign = (0 < modLfoToVolume) ? 1 : -1;
|
|
592
|
-
note.volumeDepth = new GainNode(this.audioContext, {
|
|
593
|
-
gain: volumeDepth * volumeDepthSign,
|
|
594
|
-
});
|
|
595
|
-
note.modulationLFO.start(startTime + instrumentKey.delayModLFO);
|
|
674
|
+
note.modulationDepth = new GainNode(this.audioContext);
|
|
675
|
+
this.setModLfoToPitch(channel, note);
|
|
676
|
+
note.volumeDepth = new GainNode(this.audioContext);
|
|
677
|
+
this.setModLfoToVolume(note);
|
|
678
|
+
note.modulationLFO.start(startTime + voiceParams.delayModLFO);
|
|
596
679
|
note.modulationLFO.connect(note.filterDepth);
|
|
597
680
|
note.filterDepth.connect(note.filterNode.frequency);
|
|
598
681
|
note.modulationLFO.connect(note.modulationDepth);
|
|
@@ -600,24 +683,23 @@ class MidyGMLite {
|
|
|
600
683
|
note.modulationLFO.connect(note.volumeDepth);
|
|
601
684
|
note.volumeDepth.connect(note.volumeNode.gain);
|
|
602
685
|
}
|
|
603
|
-
async createNote(channel,
|
|
604
|
-
const
|
|
605
|
-
const
|
|
606
|
-
note
|
|
686
|
+
async createNote(channel, voice, noteNumber, velocity, startTime, isSF3) {
|
|
687
|
+
const state = channel.state;
|
|
688
|
+
const voiceParams = voice.getAllParams(state.array);
|
|
689
|
+
const note = new Note(noteNumber, velocity, startTime, voice, voiceParams);
|
|
690
|
+
note.bufferSource = await this.createNoteBufferNode(voiceParams, isSF3);
|
|
607
691
|
note.volumeNode = new GainNode(this.audioContext);
|
|
608
692
|
note.filterNode = new BiquadFilterNode(this.audioContext, {
|
|
609
693
|
type: "lowpass",
|
|
610
|
-
Q:
|
|
694
|
+
Q: voiceParams.initialFilterQ / 10, // dB
|
|
611
695
|
});
|
|
612
696
|
this.setVolumeEnvelope(note);
|
|
613
697
|
this.setFilterEnvelope(note);
|
|
614
|
-
|
|
615
|
-
|
|
698
|
+
this.setPlaybackRate(note);
|
|
699
|
+
if (0 < state.modulationDepth) {
|
|
700
|
+
this.setPitch(channel, note);
|
|
616
701
|
this.startModulation(channel, note, startTime);
|
|
617
702
|
}
|
|
618
|
-
else {
|
|
619
|
-
note.bufferSource.playbackRate.value = this.calcPlaybackRate(instrumentKey, noteNumber, semitoneOffset);
|
|
620
|
-
}
|
|
621
703
|
note.bufferSource.connect(note.filterNode);
|
|
622
704
|
note.filterNode.connect(note.volumeNode);
|
|
623
705
|
note.bufferSource.start(startTime);
|
|
@@ -631,12 +713,25 @@ class MidyGMLite {
|
|
|
631
713
|
return;
|
|
632
714
|
const soundFont = this.soundFonts[soundFontIndex];
|
|
633
715
|
const isSF3 = soundFont.parsed.info.version.major === 3;
|
|
634
|
-
const
|
|
635
|
-
if (!
|
|
716
|
+
const voice = soundFont.getVoice(bankNumber, channel.program, noteNumber, velocity);
|
|
717
|
+
if (!voice)
|
|
636
718
|
return;
|
|
637
|
-
const note = await this.createNote(channel,
|
|
719
|
+
const note = await this.createNote(channel, voice, noteNumber, velocity, startTime, isSF3);
|
|
638
720
|
note.volumeNode.connect(channel.gainL);
|
|
639
721
|
note.volumeNode.connect(channel.gainR);
|
|
722
|
+
const exclusiveClass = note.voiceParams.exclusiveClass;
|
|
723
|
+
if (exclusiveClass !== 0) {
|
|
724
|
+
if (this.exclusiveClassMap.has(exclusiveClass)) {
|
|
725
|
+
const prevEntry = this.exclusiveClassMap.get(exclusiveClass);
|
|
726
|
+
const [prevNote, prevChannelNumber] = prevEntry;
|
|
727
|
+
if (!prevNote.ending) {
|
|
728
|
+
this.scheduleNoteRelease(prevChannelNumber, prevNote.noteNumber, 0, // velocity,
|
|
729
|
+
startTime, undefined, // portamentoNoteNumber
|
|
730
|
+
true);
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
this.exclusiveClassMap.set(exclusiveClass, [note, channelNumber]);
|
|
734
|
+
}
|
|
640
735
|
const scheduledNotes = channel.scheduledNotes;
|
|
641
736
|
if (scheduledNotes.has(noteNumber)) {
|
|
642
737
|
scheduledNotes.get(noteNumber).push(note);
|
|
@@ -649,15 +744,15 @@ class MidyGMLite {
|
|
|
649
744
|
const now = this.audioContext.currentTime;
|
|
650
745
|
return this.scheduleNoteOn(channelNumber, noteNumber, velocity, now);
|
|
651
746
|
}
|
|
652
|
-
stopNote(
|
|
747
|
+
stopNote(endTime, stopTime, scheduledNotes, index) {
|
|
653
748
|
const note = scheduledNotes[index];
|
|
654
749
|
note.volumeNode.gain
|
|
655
|
-
.cancelScheduledValues(
|
|
656
|
-
.linearRampToValueAtTime(0,
|
|
750
|
+
.cancelScheduledValues(endTime)
|
|
751
|
+
.linearRampToValueAtTime(0, stopTime);
|
|
657
752
|
note.ending = true;
|
|
658
753
|
this.scheduleTask(() => {
|
|
659
754
|
note.bufferSource.loop = false;
|
|
660
|
-
},
|
|
755
|
+
}, stopTime);
|
|
661
756
|
return new Promise((resolve) => {
|
|
662
757
|
note.bufferSource.onended = () => {
|
|
663
758
|
scheduledNotes[index] = null;
|
|
@@ -669,18 +764,14 @@ class MidyGMLite {
|
|
|
669
764
|
note.modulationDepth.disconnect();
|
|
670
765
|
note.modulationLFO.stop();
|
|
671
766
|
}
|
|
672
|
-
if (note.vibratoDepth) {
|
|
673
|
-
note.vibratoDepth.disconnect();
|
|
674
|
-
note.vibratoLFO.stop();
|
|
675
|
-
}
|
|
676
767
|
resolve();
|
|
677
768
|
};
|
|
678
|
-
note.bufferSource.stop(
|
|
769
|
+
note.bufferSource.stop(stopTime);
|
|
679
770
|
});
|
|
680
771
|
}
|
|
681
|
-
scheduleNoteRelease(channelNumber, noteNumber, _velocity,
|
|
772
|
+
scheduleNoteRelease(channelNumber, noteNumber, _velocity, endTime, force) {
|
|
682
773
|
const channel = this.channels[channelNumber];
|
|
683
|
-
if (!force && channel.sustainPedal)
|
|
774
|
+
if (!force && 0.5 < channel.state.sustainPedal)
|
|
684
775
|
return;
|
|
685
776
|
if (!channel.scheduledNotes.has(noteNumber))
|
|
686
777
|
return;
|
|
@@ -691,12 +782,13 @@ class MidyGMLite {
|
|
|
691
782
|
continue;
|
|
692
783
|
if (note.ending)
|
|
693
784
|
continue;
|
|
694
|
-
const
|
|
695
|
-
const modRelease =
|
|
785
|
+
const volRelease = endTime + note.voiceParams.volRelease;
|
|
786
|
+
const modRelease = endTime + note.voiceParams.modRelease;
|
|
696
787
|
note.filterNode.frequency
|
|
697
|
-
.cancelScheduledValues(
|
|
788
|
+
.cancelScheduledValues(endTime)
|
|
698
789
|
.linearRampToValueAtTime(0, modRelease);
|
|
699
|
-
|
|
790
|
+
const stopTime = Math.min(volRelease, modRelease);
|
|
791
|
+
return this.stopNote(endTime, stopTime, scheduledNotes, i);
|
|
700
792
|
}
|
|
701
793
|
}
|
|
702
794
|
releaseNote(channelNumber, noteNumber, velocity) {
|
|
@@ -707,7 +799,7 @@ class MidyGMLite {
|
|
|
707
799
|
const velocity = halfVelocity * 2;
|
|
708
800
|
const channel = this.channels[channelNumber];
|
|
709
801
|
const promises = [];
|
|
710
|
-
channel.sustainPedal =
|
|
802
|
+
channel.state.sustainPedal = halfVelocity;
|
|
711
803
|
channel.scheduledNotes.forEach((noteList) => {
|
|
712
804
|
for (let i = 0; i < noteList.length; i++) {
|
|
713
805
|
const note = noteList[i];
|
|
@@ -743,17 +835,137 @@ class MidyGMLite {
|
|
|
743
835
|
channel.program = program;
|
|
744
836
|
}
|
|
745
837
|
handlePitchBendMessage(channelNumber, lsb, msb) {
|
|
746
|
-
const pitchBend = msb * 128 + lsb
|
|
838
|
+
const pitchBend = msb * 128 + lsb;
|
|
747
839
|
this.setPitchBend(channelNumber, pitchBend);
|
|
748
840
|
}
|
|
749
|
-
setPitchBend(channelNumber,
|
|
841
|
+
setPitchBend(channelNumber, value) {
|
|
750
842
|
const channel = this.channels[channelNumber];
|
|
751
|
-
const
|
|
752
|
-
|
|
753
|
-
const
|
|
754
|
-
|
|
843
|
+
const state = channel.state;
|
|
844
|
+
state.pitchWheel = value / 16383;
|
|
845
|
+
const pitchWheel = (value - 8192) / 8192;
|
|
846
|
+
const detuneChange = pitchWheel * state.pitchWheelSensitivity * 12800;
|
|
755
847
|
this.updateDetune(channel, detuneChange);
|
|
756
848
|
}
|
|
849
|
+
setModLfoToPitch(channel, note) {
|
|
850
|
+
const now = this.audioContext.currentTime;
|
|
851
|
+
const modLfoToPitch = note.voiceParams.modLfoToPitch;
|
|
852
|
+
const modulationDepth = Math.abs(modLfoToPitch) +
|
|
853
|
+
channel.state.modulationDepth;
|
|
854
|
+
const modulationDepthSign = (0 < modLfoToPitch) ? 1 : -1;
|
|
855
|
+
note.modulationDepth.gain
|
|
856
|
+
.cancelScheduledValues(now)
|
|
857
|
+
.setValueAtTime(modulationDepth * modulationDepthSign, now);
|
|
858
|
+
}
|
|
859
|
+
setModLfoToVolume(note) {
|
|
860
|
+
const now = this.audioContext.currentTime;
|
|
861
|
+
const modLfoToVolume = note.voiceParams.modLfoToVolume;
|
|
862
|
+
const volumeDepth = this.cbToRatio(Math.abs(modLfoToVolume)) - 1;
|
|
863
|
+
const volumeDepthSign = (0 < modLfoToVolume) ? 1 : -1;
|
|
864
|
+
note.volumeDepth.gain
|
|
865
|
+
.cancelScheduledValues(now)
|
|
866
|
+
.setValueAtTime(volumeDepth * volumeDepthSign, now);
|
|
867
|
+
}
|
|
868
|
+
setModLfoToFilterFc(note) {
|
|
869
|
+
const now = this.audioContext.currentTime;
|
|
870
|
+
const modLfoToFilterFc = note.voiceParams.modLfoToFilterFc;
|
|
871
|
+
note.filterDepth.gain
|
|
872
|
+
.cancelScheduledValues(now)
|
|
873
|
+
.setValueAtTime(modLfoToFilterFc, now);
|
|
874
|
+
}
|
|
875
|
+
setDelayModLFO(note) {
|
|
876
|
+
const now = this.audioContext.currentTime;
|
|
877
|
+
const startTime = note.startTime;
|
|
878
|
+
if (startTime < now)
|
|
879
|
+
return;
|
|
880
|
+
note.modulationLFO.stop(now);
|
|
881
|
+
note.modulationLFO.start(startTime + note.voiceParams.delayModLFO);
|
|
882
|
+
note.modulationLFO.connect(note.filterDepth);
|
|
883
|
+
}
|
|
884
|
+
setFreqModLFO(note) {
|
|
885
|
+
const now = this.audioContext.currentTime;
|
|
886
|
+
const freqModLFO = note.voiceParams.freqModLFO;
|
|
887
|
+
note.modulationLFO.frequency
|
|
888
|
+
.cancelScheduledValues(now)
|
|
889
|
+
.setValueAtTime(freqModLFO, now);
|
|
890
|
+
}
|
|
891
|
+
createVoiceParamsHandlers() {
|
|
892
|
+
return {
|
|
893
|
+
modLfoToPitch: (channel, note, _prevValue) => {
|
|
894
|
+
if (0 < channel.state.modulationDepth) {
|
|
895
|
+
this.setModLfoToPitch(channel, note);
|
|
896
|
+
}
|
|
897
|
+
},
|
|
898
|
+
vibLfoToPitch: (_channel, _note, _prevValue) => { },
|
|
899
|
+
modLfoToFilterFc: (channel, note, _prevValue) => {
|
|
900
|
+
if (0 < channel.state.modulationDepth)
|
|
901
|
+
this.setModLfoToFilterFc(note);
|
|
902
|
+
},
|
|
903
|
+
modLfoToVolume: (channel, note) => {
|
|
904
|
+
if (0 < channel.state.modulationDepth)
|
|
905
|
+
this.setModLfoToVolume(note);
|
|
906
|
+
},
|
|
907
|
+
chorusEffectsSend: (_channel, _note, _prevValue) => { },
|
|
908
|
+
reverbEffectsSend: (_channel, _note, _prevValue) => { },
|
|
909
|
+
delayModLFO: (_channel, note, _prevValue) => this.setDelayModLFO(note),
|
|
910
|
+
freqModLFO: (_channel, note, _prevValue) => this.setFreqModLFO(note),
|
|
911
|
+
delayVibLFO: (_channel, _note, _prevValue) => { },
|
|
912
|
+
freqVibLFO: (_channel, _note, _prevValue) => { },
|
|
913
|
+
};
|
|
914
|
+
}
|
|
915
|
+
getControllerState(channel, noteNumber, velocity) {
|
|
916
|
+
const state = new Float32Array(channel.state.array.length);
|
|
917
|
+
state.set(channel.state.array);
|
|
918
|
+
state[2] = velocity / 127;
|
|
919
|
+
state[3] = noteNumber / 127;
|
|
920
|
+
return state;
|
|
921
|
+
}
|
|
922
|
+
applyVoiceParams(channel, controllerType) {
|
|
923
|
+
channel.scheduledNotes.forEach((noteList) => {
|
|
924
|
+
for (let i = 0; i < noteList.length; i++) {
|
|
925
|
+
const note = noteList[i];
|
|
926
|
+
if (!note)
|
|
927
|
+
continue;
|
|
928
|
+
const controllerState = this.getControllerState(channel, note.noteNumber, note.velocity);
|
|
929
|
+
const voiceParams = note.voice.getParams(controllerType, controllerState);
|
|
930
|
+
let appliedFilterEnvelope = false;
|
|
931
|
+
let appliedVolumeEnvelope = false;
|
|
932
|
+
for (const [key, value] of Object.entries(voiceParams)) {
|
|
933
|
+
const prevValue = note.voiceParams[key];
|
|
934
|
+
if (value === prevValue)
|
|
935
|
+
continue;
|
|
936
|
+
note.voiceParams[key] = value;
|
|
937
|
+
if (key in this.voiceParamsHandlers) {
|
|
938
|
+
this.voiceParamsHandlers[key](channel, note, prevValue);
|
|
939
|
+
}
|
|
940
|
+
else if (filterEnvelopeKeySet.has(key)) {
|
|
941
|
+
if (appliedFilterEnvelope)
|
|
942
|
+
continue;
|
|
943
|
+
appliedFilterEnvelope = true;
|
|
944
|
+
const noteVoiceParams = note.voiceParams;
|
|
945
|
+
for (let i = 0; i < filterEnvelopeKeys.length; i++) {
|
|
946
|
+
const key = filterEnvelopeKeys[i];
|
|
947
|
+
if (key in voiceParams)
|
|
948
|
+
noteVoiceParams[key] = voiceParams[key];
|
|
949
|
+
}
|
|
950
|
+
this.setFilterEnvelope(channel, note);
|
|
951
|
+
this.setPitch(channel, note);
|
|
952
|
+
}
|
|
953
|
+
else if (volumeEnvelopeKeySet.has(key)) {
|
|
954
|
+
if (appliedVolumeEnvelope)
|
|
955
|
+
continue;
|
|
956
|
+
appliedVolumeEnvelope = true;
|
|
957
|
+
const noteVoiceParams = note.voiceParams;
|
|
958
|
+
for (let i = 0; i < volumeEnvelopeKeys.length; i++) {
|
|
959
|
+
const key = volumeEnvelopeKeys[i];
|
|
960
|
+
if (key in voiceParams)
|
|
961
|
+
noteVoiceParams[key] = voiceParams[key];
|
|
962
|
+
}
|
|
963
|
+
this.setVolumeEnvelope(channel, note);
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
});
|
|
968
|
+
}
|
|
757
969
|
createControlChangeHandlers() {
|
|
758
970
|
return {
|
|
759
971
|
1: this.setModulationDepth,
|
|
@@ -770,13 +982,13 @@ class MidyGMLite {
|
|
|
770
982
|
123: this.allNotesOff,
|
|
771
983
|
};
|
|
772
984
|
}
|
|
773
|
-
handleControlChange(channelNumber,
|
|
774
|
-
const handler = this.controlChangeHandlers[
|
|
985
|
+
handleControlChange(channelNumber, controllerType, value) {
|
|
986
|
+
const handler = this.controlChangeHandlers[controllerType];
|
|
775
987
|
if (handler) {
|
|
776
988
|
handler.call(this, channelNumber, value);
|
|
777
989
|
}
|
|
778
990
|
else {
|
|
779
|
-
console.warn(`Unsupported Control change:
|
|
991
|
+
console.warn(`Unsupported Control change: controllerType=${controllerType} value=${value}`);
|
|
780
992
|
}
|
|
781
993
|
}
|
|
782
994
|
updateModulation(channel) {
|
|
@@ -787,11 +999,10 @@ class MidyGMLite {
|
|
|
787
999
|
if (!note)
|
|
788
1000
|
continue;
|
|
789
1001
|
if (note.modulationDepth) {
|
|
790
|
-
note.modulationDepth.gain.setValueAtTime(channel.modulationDepth, now);
|
|
1002
|
+
note.modulationDepth.gain.setValueAtTime(channel.state.modulationDepth, now);
|
|
791
1003
|
}
|
|
792
1004
|
else {
|
|
793
|
-
|
|
794
|
-
this.setPitch(note, semitoneOffset);
|
|
1005
|
+
this.setPitch(channel, note);
|
|
795
1006
|
this.startModulation(channel, note, now);
|
|
796
1007
|
}
|
|
797
1008
|
}
|
|
@@ -799,16 +1010,17 @@ class MidyGMLite {
|
|
|
799
1010
|
}
|
|
800
1011
|
setModulationDepth(channelNumber, modulation) {
|
|
801
1012
|
const channel = this.channels[channelNumber];
|
|
802
|
-
channel.modulationDepth = (modulation / 127) *
|
|
1013
|
+
channel.state.modulationDepth = (modulation / 127) *
|
|
1014
|
+
channel.modulationDepthRange;
|
|
803
1015
|
this.updateModulation(channel);
|
|
804
1016
|
}
|
|
805
1017
|
setVolume(channelNumber, volume) {
|
|
806
1018
|
const channel = this.channels[channelNumber];
|
|
807
|
-
channel.volume = volume / 127;
|
|
1019
|
+
channel.state.volume = volume / 127;
|
|
808
1020
|
this.updateChannelVolume(channel);
|
|
809
1021
|
}
|
|
810
1022
|
panToGain(pan) {
|
|
811
|
-
const theta = Math.PI / 2 * Math.max(0, pan - 1) / 126;
|
|
1023
|
+
const theta = Math.PI / 2 * Math.max(0, pan * 127 - 1) / 126;
|
|
812
1024
|
return {
|
|
813
1025
|
gainLeft: Math.cos(theta),
|
|
814
1026
|
gainRight: Math.sin(theta),
|
|
@@ -816,12 +1028,12 @@ class MidyGMLite {
|
|
|
816
1028
|
}
|
|
817
1029
|
setPan(channelNumber, pan) {
|
|
818
1030
|
const channel = this.channels[channelNumber];
|
|
819
|
-
channel.pan = pan;
|
|
1031
|
+
channel.state.pan = pan / 127;
|
|
820
1032
|
this.updateChannelVolume(channel);
|
|
821
1033
|
}
|
|
822
1034
|
setExpression(channelNumber, expression) {
|
|
823
1035
|
const channel = this.channels[channelNumber];
|
|
824
|
-
channel.expression = expression / 127;
|
|
1036
|
+
channel.state.expression = expression / 127;
|
|
825
1037
|
this.updateChannelVolume(channel);
|
|
826
1038
|
}
|
|
827
1039
|
dataEntryLSB(channelNumber, value) {
|
|
@@ -830,8 +1042,9 @@ class MidyGMLite {
|
|
|
830
1042
|
}
|
|
831
1043
|
updateChannelVolume(channel) {
|
|
832
1044
|
const now = this.audioContext.currentTime;
|
|
833
|
-
const
|
|
834
|
-
const
|
|
1045
|
+
const state = channel.state;
|
|
1046
|
+
const volume = state.volume * state.expression;
|
|
1047
|
+
const { gainLeft, gainRight } = this.panToGain(state.pan);
|
|
835
1048
|
channel.gainL.gain
|
|
836
1049
|
.cancelScheduledValues(now)
|
|
837
1050
|
.setValueAtTime(volume * gainLeft, now);
|
|
@@ -840,12 +1053,29 @@ class MidyGMLite {
|
|
|
840
1053
|
.setValueAtTime(volume * gainRight, now);
|
|
841
1054
|
}
|
|
842
1055
|
setSustainPedal(channelNumber, value) {
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
if (!isOn) {
|
|
1056
|
+
this.channels[channelNumber].state.sustainPedal = value / 127;
|
|
1057
|
+
if (value < 64) {
|
|
846
1058
|
this.releaseSustainPedal(channelNumber, value);
|
|
847
1059
|
}
|
|
848
1060
|
}
|
|
1061
|
+
limitData(channel, minMSB, maxMSB, minLSB, maxLSB) {
|
|
1062
|
+
if (maxLSB < channel.dataLSB) {
|
|
1063
|
+
channel.dataMSB++;
|
|
1064
|
+
channel.dataLSB = minLSB;
|
|
1065
|
+
}
|
|
1066
|
+
else if (channel.dataLSB < 0) {
|
|
1067
|
+
channel.dataMSB--;
|
|
1068
|
+
channel.dataLSB = maxLSB;
|
|
1069
|
+
}
|
|
1070
|
+
if (maxMSB < channel.dataMSB) {
|
|
1071
|
+
channel.dataMSB = maxMSB;
|
|
1072
|
+
channel.dataLSB = maxLSB;
|
|
1073
|
+
}
|
|
1074
|
+
else if (channel.dataMSB < 0) {
|
|
1075
|
+
channel.dataMSB = minMSB;
|
|
1076
|
+
channel.dataLSB = minLSB;
|
|
1077
|
+
}
|
|
1078
|
+
}
|
|
849
1079
|
handleRPN(channelNumber) {
|
|
850
1080
|
const channel = this.channels[channelNumber];
|
|
851
1081
|
const rpn = channel.rpnMSB * 128 + channel.rpnLSB;
|
|
@@ -867,7 +1097,7 @@ class MidyGMLite {
|
|
|
867
1097
|
this.channels[channelNumber].dataMSB = value;
|
|
868
1098
|
this.handleRPN(channelNumber);
|
|
869
1099
|
}
|
|
870
|
-
updateDetune(channel,
|
|
1100
|
+
updateDetune(channel, detune) {
|
|
871
1101
|
const now = this.audioContext.currentTime;
|
|
872
1102
|
channel.scheduledNotes.forEach((noteList) => {
|
|
873
1103
|
for (let i = 0; i < noteList.length; i++) {
|
|
@@ -875,7 +1105,6 @@ class MidyGMLite {
|
|
|
875
1105
|
if (!note)
|
|
876
1106
|
continue;
|
|
877
1107
|
const { bufferSource } = note;
|
|
878
|
-
const detune = bufferSource.detune.value + detuneChange;
|
|
879
1108
|
bufferSource.detune
|
|
880
1109
|
.cancelScheduledValues(now)
|
|
881
1110
|
.setValueAtTime(detune, now);
|
|
@@ -888,19 +1117,38 @@ class MidyGMLite {
|
|
|
888
1117
|
const pitchBendRange = channel.dataMSB + channel.dataLSB / 100;
|
|
889
1118
|
this.setPitchBendRange(channelNumber, pitchBendRange);
|
|
890
1119
|
}
|
|
891
|
-
setPitchBendRange(channelNumber,
|
|
1120
|
+
setPitchBendRange(channelNumber, pitchWheelSensitivity) {
|
|
892
1121
|
const channel = this.channels[channelNumber];
|
|
893
|
-
const
|
|
894
|
-
|
|
895
|
-
const
|
|
896
|
-
|
|
897
|
-
this.
|
|
1122
|
+
const state = channel.state;
|
|
1123
|
+
state.pitchWheelSensitivity = pitchWheelSensitivity / 128;
|
|
1124
|
+
const detune = (state.pitchWheel * 2 - 1) * pitchWheelSensitivity * 100;
|
|
1125
|
+
this.updateDetune(channel, detune);
|
|
1126
|
+
this.applyVoiceParams(channel, 16);
|
|
898
1127
|
}
|
|
899
1128
|
allSoundOff(channelNumber) {
|
|
900
1129
|
return this.stopChannelNotes(channelNumber, 0, true);
|
|
901
1130
|
}
|
|
902
1131
|
resetAllControllers(channelNumber) {
|
|
903
|
-
|
|
1132
|
+
const stateTypes = [
|
|
1133
|
+
"expression",
|
|
1134
|
+
"modulationDepth",
|
|
1135
|
+
"sustainPedal",
|
|
1136
|
+
"pitchWheelSensitivity",
|
|
1137
|
+
];
|
|
1138
|
+
const channel = this.channels[channelNumber];
|
|
1139
|
+
const state = channel.state;
|
|
1140
|
+
for (let i = 0; i < stateTypes.length; i++) {
|
|
1141
|
+
const type = stateTypes[i];
|
|
1142
|
+
state[type] = defaultControllerState[type];
|
|
1143
|
+
}
|
|
1144
|
+
const settingTypes = [
|
|
1145
|
+
"rpnMSB",
|
|
1146
|
+
"rpnLSB",
|
|
1147
|
+
];
|
|
1148
|
+
for (let i = 0; i < settingTypes.length; i++) {
|
|
1149
|
+
const type = settingTypes[i];
|
|
1150
|
+
channel[type] = this.constructor.channelSettings[type];
|
|
1151
|
+
}
|
|
904
1152
|
}
|
|
905
1153
|
allNotesOff(channelNumber) {
|
|
906
1154
|
return this.stopChannelNotes(channelNumber, 0, false);
|
|
@@ -925,11 +1173,8 @@ class MidyGMLite {
|
|
|
925
1173
|
GM1SystemOn() {
|
|
926
1174
|
for (let i = 0; i < this.channels.length; i++) {
|
|
927
1175
|
const channel = this.channels[i];
|
|
928
|
-
channel.bankMSB = 0;
|
|
929
|
-
channel.bankLSB = 0;
|
|
930
1176
|
channel.bank = 0;
|
|
931
1177
|
}
|
|
932
|
-
this.channels[9].bankMSB = 1;
|
|
933
1178
|
this.channels[9].bank = 128;
|
|
934
1179
|
}
|
|
935
1180
|
handleUniversalRealTimeExclusiveMessage(data) {
|
|
@@ -991,26 +1236,12 @@ Object.defineProperty(MidyGMLite, "channelSettings", {
|
|
|
991
1236
|
configurable: true,
|
|
992
1237
|
writable: true,
|
|
993
1238
|
value: {
|
|
994
|
-
|
|
995
|
-
|
|
1239
|
+
currentBufferSource: null,
|
|
1240
|
+
program: 0,
|
|
996
1241
|
bank: 0,
|
|
997
1242
|
dataMSB: 0,
|
|
998
1243
|
dataLSB: 0,
|
|
999
|
-
program: 0,
|
|
1000
|
-
pitchBend: 0,
|
|
1001
|
-
modulationDepthRange: 50, // cent
|
|
1002
|
-
}
|
|
1003
|
-
});
|
|
1004
|
-
Object.defineProperty(MidyGMLite, "effectSettings", {
|
|
1005
|
-
enumerable: true,
|
|
1006
|
-
configurable: true,
|
|
1007
|
-
writable: true,
|
|
1008
|
-
value: {
|
|
1009
|
-
expression: 1,
|
|
1010
|
-
modulationDepth: 0,
|
|
1011
|
-
sustainPedal: false,
|
|
1012
1244
|
rpnMSB: 127,
|
|
1013
1245
|
rpnLSB: 127,
|
|
1014
|
-
pitchBendRange: 2,
|
|
1015
1246
|
}
|
|
1016
1247
|
});
|