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