@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-GM1.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,
|
|
@@ -53,9 +53,75 @@ class Note {
|
|
|
53
53
|
this.noteNumber = noteNumber;
|
|
54
54
|
this.velocity = velocity;
|
|
55
55
|
this.startTime = startTime;
|
|
56
|
-
this.
|
|
56
|
+
this.voice = voice;
|
|
57
|
+
this.voiceParams = voiceParams;
|
|
57
58
|
}
|
|
58
59
|
}
|
|
60
|
+
// normalized to 0-1 for use with the SF2 modulator model
|
|
61
|
+
const defaultControllerState = {
|
|
62
|
+
noteOnVelocity: { type: 2, defaultValue: 0 },
|
|
63
|
+
noteOnKeyNumber: { type: 3, defaultValue: 0 },
|
|
64
|
+
pitchWheel: { type: 14, defaultValue: 8192 / 16383 },
|
|
65
|
+
pitchWheelSensitivity: { type: 16, defaultValue: 2 / 128 },
|
|
66
|
+
link: { type: 127, defaultValue: 0 },
|
|
67
|
+
// bankMSB: { type: 128 + 0, defaultValue: 121, },
|
|
68
|
+
modulationDepth: { type: 128 + 1, defaultValue: 0 },
|
|
69
|
+
// dataMSB: { type: 128 + 6, defaultValue: 0, },
|
|
70
|
+
volume: { type: 128 + 7, defaultValue: 100 / 127 },
|
|
71
|
+
pan: { type: 128 + 10, defaultValue: 0.5 },
|
|
72
|
+
expression: { type: 128 + 11, defaultValue: 1 },
|
|
73
|
+
// bankLSB: { type: 128 + 32, defaultValue: 0, },
|
|
74
|
+
// dataLSB: { type: 128 + 38, defaultValue: 0, },
|
|
75
|
+
sustainPedal: { type: 128 + 64, defaultValue: 0 },
|
|
76
|
+
// rpnLSB: { type: 128 + 100, defaultValue: 127 },
|
|
77
|
+
// rpnMSB: { type: 128 + 101, defaultValue: 127 },
|
|
78
|
+
// allSoundOff: { type: 128 + 120, defaultValue: 0 },
|
|
79
|
+
// resetAllControllers: { type: 128 + 121, defaultValue: 0 },
|
|
80
|
+
// allNotesOff: { type: 128 + 123, defaultValue: 0 },
|
|
81
|
+
};
|
|
82
|
+
class ControllerState {
|
|
83
|
+
constructor() {
|
|
84
|
+
Object.defineProperty(this, "array", {
|
|
85
|
+
enumerable: true,
|
|
86
|
+
configurable: true,
|
|
87
|
+
writable: true,
|
|
88
|
+
value: new Float32Array(256)
|
|
89
|
+
});
|
|
90
|
+
const entries = Object.entries(defaultControllerState);
|
|
91
|
+
for (const [name, { type, defaultValue }] of entries) {
|
|
92
|
+
this.array[type] = defaultValue;
|
|
93
|
+
Object.defineProperty(this, name, {
|
|
94
|
+
get: () => this.array[type],
|
|
95
|
+
set: (value) => this.array[type] = value,
|
|
96
|
+
enumerable: true,
|
|
97
|
+
configurable: true,
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
const filterEnvelopeKeys = [
|
|
103
|
+
"modEnvToPitch",
|
|
104
|
+
"initialFilterFc",
|
|
105
|
+
"modEnvToFilterFc",
|
|
106
|
+
"modDelay",
|
|
107
|
+
"modAttack",
|
|
108
|
+
"modHold",
|
|
109
|
+
"modDecay",
|
|
110
|
+
"modSustain",
|
|
111
|
+
"modRelease",
|
|
112
|
+
"playbackRate",
|
|
113
|
+
];
|
|
114
|
+
const filterEnvelopeKeySet = new Set(filterEnvelopeKeys);
|
|
115
|
+
const volumeEnvelopeKeys = [
|
|
116
|
+
"volDelay",
|
|
117
|
+
"volAttack",
|
|
118
|
+
"volHold",
|
|
119
|
+
"volDecay",
|
|
120
|
+
"volSustain",
|
|
121
|
+
"volRelease",
|
|
122
|
+
"initialAttenuation",
|
|
123
|
+
];
|
|
124
|
+
const volumeEnvelopeKeySet = new Set(volumeEnvelopeKeys);
|
|
59
125
|
export class MidyGM1 {
|
|
60
126
|
constructor(audioContext) {
|
|
61
127
|
Object.defineProperty(this, "ticksPerBeat", {
|
|
@@ -160,8 +226,15 @@ export class MidyGM1 {
|
|
|
160
226
|
writable: true,
|
|
161
227
|
value: []
|
|
162
228
|
});
|
|
229
|
+
Object.defineProperty(this, "exclusiveClassMap", {
|
|
230
|
+
enumerable: true,
|
|
231
|
+
configurable: true,
|
|
232
|
+
writable: true,
|
|
233
|
+
value: new Map()
|
|
234
|
+
});
|
|
163
235
|
this.audioContext = audioContext;
|
|
164
236
|
this.masterGain = new GainNode(audioContext);
|
|
237
|
+
this.voiceParamsHandlers = this.createVoiceParamsHandlers();
|
|
165
238
|
this.controlChangeHandlers = this.createControlChangeHandlers();
|
|
166
239
|
this.channels = this.createChannels(audioContext);
|
|
167
240
|
this.masterGain.connect(audioContext.destination);
|
|
@@ -204,7 +277,7 @@ export class MidyGM1 {
|
|
|
204
277
|
this.totalTime = this.calcTotalTime();
|
|
205
278
|
}
|
|
206
279
|
setChannelAudioNodes(audioContext) {
|
|
207
|
-
const { gainLeft, gainRight } = this.panToGain(
|
|
280
|
+
const { gainLeft, gainRight } = this.panToGain(defaultControllerState.pan.defaultValue);
|
|
208
281
|
const gainL = new GainNode(audioContext, { gain: gainLeft });
|
|
209
282
|
const gainR = new GainNode(audioContext, { gain: gainRight });
|
|
210
283
|
const merger = new ChannelMergerNode(audioContext, { numberOfInputs: 2 });
|
|
@@ -221,45 +294,50 @@ export class MidyGM1 {
|
|
|
221
294
|
const channels = Array.from({ length: 16 }, () => {
|
|
222
295
|
return {
|
|
223
296
|
...this.constructor.channelSettings,
|
|
224
|
-
|
|
297
|
+
state: new ControllerState(),
|
|
225
298
|
...this.setChannelAudioNodes(audioContext),
|
|
226
299
|
scheduledNotes: new Map(),
|
|
227
300
|
};
|
|
228
301
|
});
|
|
229
302
|
return channels;
|
|
230
303
|
}
|
|
231
|
-
async createNoteBuffer(
|
|
232
|
-
const sampleStart =
|
|
233
|
-
const sampleEnd =
|
|
304
|
+
async createNoteBuffer(voiceParams, isSF3) {
|
|
305
|
+
const sampleStart = voiceParams.start;
|
|
306
|
+
const sampleEnd = voiceParams.sample.length + voiceParams.end;
|
|
234
307
|
if (isSF3) {
|
|
235
|
-
const sample =
|
|
236
|
-
const
|
|
308
|
+
const sample = voiceParams.sample;
|
|
309
|
+
const start = sample.byteOffset + sampleStart;
|
|
310
|
+
const end = sample.byteOffset + sampleEnd;
|
|
311
|
+
const buffer = sample.buffer.slice(start, end);
|
|
312
|
+
const audioBuffer = await this.audioContext.decodeAudioData(buffer);
|
|
237
313
|
return audioBuffer;
|
|
238
314
|
}
|
|
239
315
|
else {
|
|
240
|
-
const sample =
|
|
316
|
+
const sample = voiceParams.sample;
|
|
317
|
+
const start = sample.byteOffset + sampleStart;
|
|
318
|
+
const end = sample.byteOffset + sampleEnd;
|
|
319
|
+
const buffer = sample.buffer.slice(start, end);
|
|
241
320
|
const audioBuffer = new AudioBuffer({
|
|
242
321
|
numberOfChannels: 1,
|
|
243
322
|
length: sample.length,
|
|
244
|
-
sampleRate:
|
|
323
|
+
sampleRate: voiceParams.sampleRate,
|
|
245
324
|
});
|
|
246
325
|
const channelData = audioBuffer.getChannelData(0);
|
|
247
|
-
const int16Array = new Int16Array(
|
|
326
|
+
const int16Array = new Int16Array(buffer);
|
|
248
327
|
for (let i = 0; i < int16Array.length; i++) {
|
|
249
328
|
channelData[i] = int16Array[i] / 32768;
|
|
250
329
|
}
|
|
251
330
|
return audioBuffer;
|
|
252
331
|
}
|
|
253
332
|
}
|
|
254
|
-
async createNoteBufferNode(
|
|
333
|
+
async createNoteBufferNode(voiceParams, isSF3) {
|
|
255
334
|
const bufferSource = new AudioBufferSourceNode(this.audioContext);
|
|
256
|
-
const audioBuffer = await this.createNoteBuffer(
|
|
335
|
+
const audioBuffer = await this.createNoteBuffer(voiceParams, isSF3);
|
|
257
336
|
bufferSource.buffer = audioBuffer;
|
|
258
|
-
bufferSource.loop =
|
|
337
|
+
bufferSource.loop = voiceParams.sampleModes % 2 !== 0;
|
|
259
338
|
if (bufferSource.loop) {
|
|
260
|
-
bufferSource.loopStart =
|
|
261
|
-
|
|
262
|
-
bufferSource.loopEnd = instrumentKey.loopEnd / instrumentKey.sampleRate;
|
|
339
|
+
bufferSource.loopStart = voiceParams.loopStart / voiceParams.sampleRate;
|
|
340
|
+
bufferSource.loopEnd = voiceParams.loopEnd / voiceParams.sampleRate;
|
|
263
341
|
}
|
|
264
342
|
return bufferSource;
|
|
265
343
|
}
|
|
@@ -289,7 +367,7 @@ export class MidyGM1 {
|
|
|
289
367
|
this.handleProgramChange(event.channel, event.programNumber);
|
|
290
368
|
break;
|
|
291
369
|
case "pitchBend":
|
|
292
|
-
this.setPitchBend(event.channel, event.value);
|
|
370
|
+
this.setPitchBend(event.channel, event.value + 8192);
|
|
293
371
|
break;
|
|
294
372
|
case "sysEx":
|
|
295
373
|
this.handleSysEx(event.data);
|
|
@@ -318,6 +396,7 @@ export class MidyGM1 {
|
|
|
318
396
|
if (queueIndex >= this.timeline.length) {
|
|
319
397
|
await Promise.all(this.notePromises);
|
|
320
398
|
this.notePromises = [];
|
|
399
|
+
this.exclusiveClassMap.clear();
|
|
321
400
|
resolve();
|
|
322
401
|
return;
|
|
323
402
|
}
|
|
@@ -333,6 +412,7 @@ export class MidyGM1 {
|
|
|
333
412
|
}
|
|
334
413
|
else if (this.isStopping) {
|
|
335
414
|
await this.stopNotes(0, true);
|
|
415
|
+
this.exclusiveClassMap.clear();
|
|
336
416
|
this.notePromises = [];
|
|
337
417
|
resolve();
|
|
338
418
|
this.isStopping = false;
|
|
@@ -341,6 +421,7 @@ export class MidyGM1 {
|
|
|
341
421
|
}
|
|
342
422
|
else if (this.isSeeking) {
|
|
343
423
|
this.stopNotes(0, true);
|
|
424
|
+
this.exclusiveClassMap.clear();
|
|
344
425
|
this.startTime = this.audioContext.currentTime;
|
|
345
426
|
queueIndex = this.getQueueIndex(this.resumeTime);
|
|
346
427
|
offset = this.resumeTime - this.startTime;
|
|
@@ -517,41 +598,50 @@ export class MidyGM1 {
|
|
|
517
598
|
}
|
|
518
599
|
calcSemitoneOffset(channel) {
|
|
519
600
|
const tuning = channel.coarseTuning + channel.fineTuning;
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
return
|
|
524
|
-
Math.pow(2, semitoneOffset / 12);
|
|
601
|
+
const pitchWheel = channel.state.pitchWheel * 2 - 1;
|
|
602
|
+
const pitchWheelSensitivity = channel.state.pitchWheelSensitivity * 128;
|
|
603
|
+
const pitch = pitchWheel * pitchWheelSensitivity;
|
|
604
|
+
return tuning + pitch;
|
|
525
605
|
}
|
|
526
606
|
setVolumeEnvelope(note) {
|
|
527
|
-
const
|
|
528
|
-
const
|
|
529
|
-
const
|
|
530
|
-
const
|
|
531
|
-
const
|
|
532
|
-
const
|
|
533
|
-
const
|
|
607
|
+
const now = this.audioContext.currentTime;
|
|
608
|
+
const { voiceParams, startTime } = note;
|
|
609
|
+
const attackVolume = this.cbToRatio(-voiceParams.initialAttenuation);
|
|
610
|
+
const sustainVolume = attackVolume * (1 - voiceParams.volSustain);
|
|
611
|
+
const volDelay = startTime + voiceParams.volDelay;
|
|
612
|
+
const volAttack = volDelay + voiceParams.volAttack;
|
|
613
|
+
const volHold = volAttack + voiceParams.volHold;
|
|
614
|
+
const volDecay = volHold + voiceParams.volDecay;
|
|
534
615
|
note.volumeNode.gain
|
|
535
|
-
.cancelScheduledValues(
|
|
616
|
+
.cancelScheduledValues(now)
|
|
536
617
|
.setValueAtTime(0, startTime)
|
|
537
618
|
.setValueAtTime(1e-6, volDelay) // exponentialRampToValueAtTime() requires a non-zero value
|
|
538
619
|
.exponentialRampToValueAtTime(attackVolume, volAttack)
|
|
539
620
|
.setValueAtTime(attackVolume, volHold)
|
|
540
621
|
.linearRampToValueAtTime(sustainVolume, volDecay);
|
|
541
622
|
}
|
|
542
|
-
|
|
543
|
-
const
|
|
544
|
-
|
|
545
|
-
|
|
623
|
+
setPlaybackRate(note) {
|
|
624
|
+
const now = this.audioContext.currentTime;
|
|
625
|
+
note.bufferSource.playbackRate
|
|
626
|
+
.cancelScheduledValues(now)
|
|
627
|
+
.setValueAtTime(note.voiceParams.playbackRate, now);
|
|
628
|
+
}
|
|
629
|
+
setPitch(channel, note) {
|
|
630
|
+
const now = this.audioContext.currentTime;
|
|
631
|
+
const { startTime } = note;
|
|
632
|
+
const basePitch = this.calcSemitoneOffset(channel) * 100;
|
|
633
|
+
note.bufferSource.detune
|
|
634
|
+
.cancelScheduledValues(now)
|
|
635
|
+
.setValueAtTime(basePitch, startTime);
|
|
636
|
+
const modEnvToPitch = note.voiceParams.modEnvToPitch;
|
|
546
637
|
if (modEnvToPitch === 0)
|
|
547
638
|
return;
|
|
548
|
-
const
|
|
549
|
-
const
|
|
550
|
-
const
|
|
551
|
-
const
|
|
552
|
-
const
|
|
553
|
-
|
|
554
|
-
note.bufferSource.playbackRate.value
|
|
639
|
+
const peekPitch = basePitch + modEnvToPitch;
|
|
640
|
+
const modDelay = startTime + voiceParams.modDelay;
|
|
641
|
+
const modAttack = modDelay + voiceParams.modAttack;
|
|
642
|
+
const modHold = modAttack + voiceParams.modHold;
|
|
643
|
+
const modDecay = modHold + voiceParams.modDecay;
|
|
644
|
+
note.bufferSource.detune
|
|
555
645
|
.setValueAtTime(basePitch, modDelay)
|
|
556
646
|
.exponentialRampToValueAtTime(peekPitch, modAttack)
|
|
557
647
|
.setValueAtTime(peekPitch, modHold)
|
|
@@ -563,20 +653,21 @@ export class MidyGM1 {
|
|
|
563
653
|
return Math.max(minFrequency, Math.min(frequency, maxFrequency));
|
|
564
654
|
}
|
|
565
655
|
setFilterEnvelope(note) {
|
|
566
|
-
const
|
|
567
|
-
const
|
|
568
|
-
const
|
|
656
|
+
const now = this.audioContext.currentTime;
|
|
657
|
+
const { voiceParams, startTime } = note;
|
|
658
|
+
const baseFreq = this.centToHz(voiceParams.initialFilterFc);
|
|
659
|
+
const peekFreq = this.centToHz(voiceParams.initialFilterFc + voiceParams.modEnvToFilterFc);
|
|
569
660
|
const sustainFreq = baseFreq +
|
|
570
|
-
(peekFreq - baseFreq) * (1 -
|
|
661
|
+
(peekFreq - baseFreq) * (1 - voiceParams.modSustain);
|
|
571
662
|
const adjustedBaseFreq = this.clampCutoffFrequency(baseFreq);
|
|
572
663
|
const adjustedPeekFreq = this.clampCutoffFrequency(peekFreq);
|
|
573
664
|
const adjustedSustainFreq = this.clampCutoffFrequency(sustainFreq);
|
|
574
|
-
const modDelay = startTime +
|
|
575
|
-
const modAttack = modDelay +
|
|
576
|
-
const modHold = modAttack +
|
|
577
|
-
const modDecay = modHold +
|
|
665
|
+
const modDelay = startTime + voiceParams.modDelay;
|
|
666
|
+
const modAttack = modDelay + voiceParams.modAttack;
|
|
667
|
+
const modHold = modAttack + voiceParams.modHold;
|
|
668
|
+
const modDecay = modHold + voiceParams.modDecay;
|
|
578
669
|
note.filterNode.frequency
|
|
579
|
-
.cancelScheduledValues(
|
|
670
|
+
.cancelScheduledValues(now)
|
|
580
671
|
.setValueAtTime(adjustedBaseFreq, startTime)
|
|
581
672
|
.setValueAtTime(adjustedBaseFreq, modDelay)
|
|
582
673
|
.exponentialRampToValueAtTime(adjustedPeekFreq, modAttack)
|
|
@@ -584,25 +675,18 @@ export class MidyGM1 {
|
|
|
584
675
|
.linearRampToValueAtTime(adjustedSustainFreq, modDecay);
|
|
585
676
|
}
|
|
586
677
|
startModulation(channel, note, startTime) {
|
|
587
|
-
const {
|
|
588
|
-
const { modLfoToPitch, modLfoToVolume } = instrumentKey;
|
|
678
|
+
const { voiceParams } = note;
|
|
589
679
|
note.modulationLFO = new OscillatorNode(this.audioContext, {
|
|
590
|
-
frequency: this.centToHz(
|
|
680
|
+
frequency: this.centToHz(voiceParams.freqModLFO),
|
|
591
681
|
});
|
|
592
682
|
note.filterDepth = new GainNode(this.audioContext, {
|
|
593
|
-
gain:
|
|
594
|
-
});
|
|
595
|
-
const modulationDepth = Math.abs(modLfoToPitch) + channel.modulationDepth;
|
|
596
|
-
const modulationDepthSign = (0 < modLfoToPitch) ? 1 : -1;
|
|
597
|
-
note.modulationDepth = new GainNode(this.audioContext, {
|
|
598
|
-
gain: modulationDepth * modulationDepthSign,
|
|
683
|
+
gain: voiceParams.modLfoToFilterFc,
|
|
599
684
|
});
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
note.volumeDepth = new GainNode(this.audioContext
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
note.modulationLFO.start(startTime + instrumentKey.delayModLFO);
|
|
685
|
+
note.modulationDepth = new GainNode(this.audioContext);
|
|
686
|
+
this.setModLfoToPitch(channel, note);
|
|
687
|
+
note.volumeDepth = new GainNode(this.audioContext);
|
|
688
|
+
this.setModLfoToVolume(note);
|
|
689
|
+
note.modulationLFO.start(startTime + voiceParams.delayModLFO);
|
|
606
690
|
note.modulationLFO.connect(note.filterDepth);
|
|
607
691
|
note.filterDepth.connect(note.filterNode.frequency);
|
|
608
692
|
note.modulationLFO.connect(note.modulationDepth);
|
|
@@ -610,24 +694,23 @@ export class MidyGM1 {
|
|
|
610
694
|
note.modulationLFO.connect(note.volumeDepth);
|
|
611
695
|
note.volumeDepth.connect(note.volumeNode.gain);
|
|
612
696
|
}
|
|
613
|
-
async createNote(channel,
|
|
614
|
-
const
|
|
615
|
-
const
|
|
616
|
-
note
|
|
697
|
+
async createNote(channel, voice, noteNumber, velocity, startTime, isSF3) {
|
|
698
|
+
const state = channel.state;
|
|
699
|
+
const voiceParams = voice.getAllParams(state.array);
|
|
700
|
+
const note = new Note(noteNumber, velocity, startTime, voice, voiceParams);
|
|
701
|
+
note.bufferSource = await this.createNoteBufferNode(voiceParams, isSF3);
|
|
617
702
|
note.volumeNode = new GainNode(this.audioContext);
|
|
618
703
|
note.filterNode = new BiquadFilterNode(this.audioContext, {
|
|
619
704
|
type: "lowpass",
|
|
620
|
-
Q:
|
|
705
|
+
Q: voiceParams.initialFilterQ / 10, // dB
|
|
621
706
|
});
|
|
622
707
|
this.setVolumeEnvelope(note);
|
|
623
708
|
this.setFilterEnvelope(note);
|
|
624
|
-
|
|
625
|
-
|
|
709
|
+
this.setPlaybackRate(note);
|
|
710
|
+
if (0 < state.modulationDepth) {
|
|
711
|
+
this.setPitch(channel, note);
|
|
626
712
|
this.startModulation(channel, note, startTime);
|
|
627
713
|
}
|
|
628
|
-
else {
|
|
629
|
-
note.bufferSource.playbackRate.value = this.calcPlaybackRate(instrumentKey, noteNumber, semitoneOffset);
|
|
630
|
-
}
|
|
631
714
|
note.bufferSource.connect(note.filterNode);
|
|
632
715
|
note.filterNode.connect(note.volumeNode);
|
|
633
716
|
note.bufferSource.start(startTime);
|
|
@@ -635,18 +718,31 @@ export class MidyGM1 {
|
|
|
635
718
|
}
|
|
636
719
|
async scheduleNoteOn(channelNumber, noteNumber, velocity, startTime) {
|
|
637
720
|
const channel = this.channels[channelNumber];
|
|
638
|
-
const bankNumber =
|
|
721
|
+
const bankNumber = channel.bank;
|
|
639
722
|
const soundFontIndex = this.soundFontTable[channel.program].get(bankNumber);
|
|
640
723
|
if (soundFontIndex === undefined)
|
|
641
724
|
return;
|
|
642
725
|
const soundFont = this.soundFonts[soundFontIndex];
|
|
643
726
|
const isSF3 = soundFont.parsed.info.version.major === 3;
|
|
644
|
-
const
|
|
645
|
-
if (!
|
|
727
|
+
const voice = soundFont.getVoice(bankNumber, channel.program, noteNumber, velocity);
|
|
728
|
+
if (!voice)
|
|
646
729
|
return;
|
|
647
|
-
const note = await this.createNote(channel,
|
|
730
|
+
const note = await this.createNote(channel, voice, noteNumber, velocity, startTime, isSF3);
|
|
648
731
|
note.volumeNode.connect(channel.gainL);
|
|
649
732
|
note.volumeNode.connect(channel.gainR);
|
|
733
|
+
const exclusiveClass = note.voiceParams.exclusiveClass;
|
|
734
|
+
if (exclusiveClass !== 0) {
|
|
735
|
+
if (this.exclusiveClassMap.has(exclusiveClass)) {
|
|
736
|
+
const prevEntry = this.exclusiveClassMap.get(exclusiveClass);
|
|
737
|
+
const [prevNote, prevChannelNumber] = prevEntry;
|
|
738
|
+
if (!prevNote.ending) {
|
|
739
|
+
this.scheduleNoteRelease(prevChannelNumber, prevNote.noteNumber, 0, // velocity,
|
|
740
|
+
startTime, undefined, // portamentoNoteNumber
|
|
741
|
+
true);
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
this.exclusiveClassMap.set(exclusiveClass, [note, channelNumber]);
|
|
745
|
+
}
|
|
650
746
|
const scheduledNotes = channel.scheduledNotes;
|
|
651
747
|
if (scheduledNotes.has(noteNumber)) {
|
|
652
748
|
scheduledNotes.get(noteNumber).push(note);
|
|
@@ -659,15 +755,15 @@ export class MidyGM1 {
|
|
|
659
755
|
const now = this.audioContext.currentTime;
|
|
660
756
|
return this.scheduleNoteOn(channelNumber, noteNumber, velocity, now);
|
|
661
757
|
}
|
|
662
|
-
stopNote(
|
|
758
|
+
stopNote(endTime, stopTime, scheduledNotes, index) {
|
|
663
759
|
const note = scheduledNotes[index];
|
|
664
760
|
note.volumeNode.gain
|
|
665
|
-
.cancelScheduledValues(
|
|
666
|
-
.linearRampToValueAtTime(0,
|
|
761
|
+
.cancelScheduledValues(endTime)
|
|
762
|
+
.linearRampToValueAtTime(0, stopTime);
|
|
667
763
|
note.ending = true;
|
|
668
764
|
this.scheduleTask(() => {
|
|
669
765
|
note.bufferSource.loop = false;
|
|
670
|
-
},
|
|
766
|
+
}, stopTime);
|
|
671
767
|
return new Promise((resolve) => {
|
|
672
768
|
note.bufferSource.onended = () => {
|
|
673
769
|
scheduledNotes[index] = null;
|
|
@@ -685,12 +781,12 @@ export class MidyGM1 {
|
|
|
685
781
|
}
|
|
686
782
|
resolve();
|
|
687
783
|
};
|
|
688
|
-
note.bufferSource.stop(
|
|
784
|
+
note.bufferSource.stop(stopTime);
|
|
689
785
|
});
|
|
690
786
|
}
|
|
691
|
-
scheduleNoteRelease(channelNumber, noteNumber, _velocity,
|
|
787
|
+
scheduleNoteRelease(channelNumber, noteNumber, _velocity, endTime, force) {
|
|
692
788
|
const channel = this.channels[channelNumber];
|
|
693
|
-
if (!force && channel.sustainPedal)
|
|
789
|
+
if (!force && 0.5 < channel.state.sustainPedal)
|
|
694
790
|
return;
|
|
695
791
|
if (!channel.scheduledNotes.has(noteNumber))
|
|
696
792
|
return;
|
|
@@ -701,12 +797,13 @@ export class MidyGM1 {
|
|
|
701
797
|
continue;
|
|
702
798
|
if (note.ending)
|
|
703
799
|
continue;
|
|
704
|
-
const
|
|
705
|
-
const modRelease =
|
|
800
|
+
const volRelease = endTime + note.voiceParams.volRelease;
|
|
801
|
+
const modRelease = endTime + note.voiceParams.modRelease;
|
|
706
802
|
note.filterNode.frequency
|
|
707
|
-
.cancelScheduledValues(
|
|
803
|
+
.cancelScheduledValues(endTime)
|
|
708
804
|
.linearRampToValueAtTime(0, modRelease);
|
|
709
|
-
|
|
805
|
+
const stopTime = Math.min(volRelease, modRelease);
|
|
806
|
+
return this.stopNote(endTime, stopTime, scheduledNotes, i);
|
|
710
807
|
}
|
|
711
808
|
}
|
|
712
809
|
releaseNote(channelNumber, noteNumber, velocity) {
|
|
@@ -717,7 +814,7 @@ export class MidyGM1 {
|
|
|
717
814
|
const velocity = halfVelocity * 2;
|
|
718
815
|
const channel = this.channels[channelNumber];
|
|
719
816
|
const promises = [];
|
|
720
|
-
channel.sustainPedal =
|
|
817
|
+
channel.state.sustainPedal = halfVelocity;
|
|
721
818
|
channel.scheduledNotes.forEach((noteList) => {
|
|
722
819
|
for (let i = 0; i < noteList.length; i++) {
|
|
723
820
|
const note = noteList[i];
|
|
@@ -753,17 +850,170 @@ export class MidyGM1 {
|
|
|
753
850
|
channel.program = program;
|
|
754
851
|
}
|
|
755
852
|
handlePitchBendMessage(channelNumber, lsb, msb) {
|
|
756
|
-
const pitchBend = msb * 128 + lsb
|
|
853
|
+
const pitchBend = msb * 128 + lsb;
|
|
757
854
|
this.setPitchBend(channelNumber, pitchBend);
|
|
758
855
|
}
|
|
759
|
-
setPitchBend(channelNumber,
|
|
856
|
+
setPitchBend(channelNumber, value) {
|
|
760
857
|
const channel = this.channels[channelNumber];
|
|
761
|
-
const
|
|
762
|
-
|
|
763
|
-
const
|
|
764
|
-
|
|
858
|
+
const state = channel.state;
|
|
859
|
+
state.pitchWheel = value / 16383;
|
|
860
|
+
const pitchWheel = (value - 8192) / 8192;
|
|
861
|
+
const detuneChange = pitchWheel * state.pitchWheelSensitivity * 12800;
|
|
765
862
|
this.updateDetune(channel, detuneChange);
|
|
766
863
|
}
|
|
864
|
+
setModLfoToPitch(channel, note) {
|
|
865
|
+
const now = this.audioContext.currentTime;
|
|
866
|
+
const modLfoToPitch = note.voiceParams.modLfoToPitch;
|
|
867
|
+
const modulationDepth = Math.abs(modLfoToPitch) +
|
|
868
|
+
channel.state.modulationDepth;
|
|
869
|
+
const modulationDepthSign = (0 < modLfoToPitch) ? 1 : -1;
|
|
870
|
+
note.modulationDepth.gain
|
|
871
|
+
.cancelScheduledValues(now)
|
|
872
|
+
.setValueAtTime(modulationDepth * modulationDepthSign, now);
|
|
873
|
+
}
|
|
874
|
+
setModLfoToVolume(note) {
|
|
875
|
+
const now = this.audioContext.currentTime;
|
|
876
|
+
const modLfoToVolume = note.voiceParams.modLfoToVolume;
|
|
877
|
+
const volumeDepth = this.cbToRatio(Math.abs(modLfoToVolume)) - 1;
|
|
878
|
+
const volumeDepthSign = (0 < modLfoToVolume) ? 1 : -1;
|
|
879
|
+
note.volumeDepth.gain
|
|
880
|
+
.cancelScheduledValues(now)
|
|
881
|
+
.setValueAtTime(volumeDepth * volumeDepthSign, now);
|
|
882
|
+
}
|
|
883
|
+
setVibLfoToPitch(channel, note) {
|
|
884
|
+
const now = this.audioContext.currentTime;
|
|
885
|
+
const vibLfoToPitch = note.voiceParams.vibLfoToPitch;
|
|
886
|
+
const vibratoDepth = Math.abs(vibLfoToPitch) * channel.state.vibratoDepth *
|
|
887
|
+
2;
|
|
888
|
+
const vibratoDepthSign = 0 < vibLfoToPitch;
|
|
889
|
+
note.vibratoDepth.gain
|
|
890
|
+
.cancelScheduledValues(now)
|
|
891
|
+
.setValueAtTime(vibratoDepth * vibratoDepthSign, now);
|
|
892
|
+
}
|
|
893
|
+
setModLfoToFilterFc(note) {
|
|
894
|
+
const now = this.audioContext.currentTime;
|
|
895
|
+
const modLfoToFilterFc = note.voiceParams.modLfoToFilterFc;
|
|
896
|
+
note.filterDepth.gain
|
|
897
|
+
.cancelScheduledValues(now)
|
|
898
|
+
.setValueAtTime(modLfoToFilterFc, now);
|
|
899
|
+
}
|
|
900
|
+
setDelayModLFO(note) {
|
|
901
|
+
const now = this.audioContext.currentTime;
|
|
902
|
+
const startTime = note.startTime;
|
|
903
|
+
if (startTime < now)
|
|
904
|
+
return;
|
|
905
|
+
note.modulationLFO.stop(now);
|
|
906
|
+
note.modulationLFO.start(startTime + note.voiceParams.delayModLFO);
|
|
907
|
+
note.modulationLFO.connect(note.filterDepth);
|
|
908
|
+
}
|
|
909
|
+
setFreqModLFO(note) {
|
|
910
|
+
const now = this.audioContext.currentTime;
|
|
911
|
+
const freqModLFO = note.voiceParams.freqModLFO;
|
|
912
|
+
note.modulationLFO.frequency
|
|
913
|
+
.cancelScheduledValues(now)
|
|
914
|
+
.setValueAtTime(freqModLFO, now);
|
|
915
|
+
}
|
|
916
|
+
createVoiceParamsHandlers() {
|
|
917
|
+
return {
|
|
918
|
+
modLfoToPitch: (channel, note, _prevValue) => {
|
|
919
|
+
if (0 < channel.state.modulationDepth) {
|
|
920
|
+
this.setModLfoToPitch(channel, note);
|
|
921
|
+
}
|
|
922
|
+
},
|
|
923
|
+
vibLfoToPitch: (channel, note, _prevValue) => {
|
|
924
|
+
if (0 < channel.state.vibratoDepth) {
|
|
925
|
+
this.setVibLfoToPitch(channel, note);
|
|
926
|
+
}
|
|
927
|
+
},
|
|
928
|
+
modLfoToFilterFc: (channel, note, _prevValue) => {
|
|
929
|
+
if (0 < channel.state.modulationDepth)
|
|
930
|
+
this.setModLfoToFilterFc(note);
|
|
931
|
+
},
|
|
932
|
+
modLfoToVolume: (channel, note) => {
|
|
933
|
+
if (0 < channel.state.modulationDepth)
|
|
934
|
+
this.setModLfoToVolume(note);
|
|
935
|
+
},
|
|
936
|
+
chorusEffectsSend: (_channel, _note, _prevValue) => { },
|
|
937
|
+
reverbEffectsSend: (_channel, _note, _prevValue) => { },
|
|
938
|
+
delayModLFO: (_channel, note, _prevValue) => this.setDelayModLFO(note),
|
|
939
|
+
freqModLFO: (_channel, note, _prevValue) => this.setFreqModLFO(note),
|
|
940
|
+
delayVibLFO: (channel, note, prevValue) => {
|
|
941
|
+
if (0 < channel.state.vibratoDepth) {
|
|
942
|
+
const now = this.audioContext.currentTime;
|
|
943
|
+
const prevStartTime = note.startTime +
|
|
944
|
+
prevValue * channel.state.vibratoDelay * 2;
|
|
945
|
+
if (now < prevStartTime)
|
|
946
|
+
return;
|
|
947
|
+
const startTime = note.startTime +
|
|
948
|
+
value * channel.state.vibratoDelay * 2;
|
|
949
|
+
note.vibratoLFO.stop(now);
|
|
950
|
+
note.vibratoLFO.start(startTime);
|
|
951
|
+
}
|
|
952
|
+
},
|
|
953
|
+
freqVibLFO: (channel, note, _prevValue) => {
|
|
954
|
+
if (0 < channel.state.vibratoDepth) {
|
|
955
|
+
const now = this.audioContext.currentTime;
|
|
956
|
+
note.vibratoLFO.frequency
|
|
957
|
+
.cancelScheduledValues(now)
|
|
958
|
+
.setValueAtTime(value * sate.vibratoRate, now);
|
|
959
|
+
}
|
|
960
|
+
},
|
|
961
|
+
};
|
|
962
|
+
}
|
|
963
|
+
getControllerState(channel, noteNumber, velocity) {
|
|
964
|
+
const state = new Float32Array(channel.state.array.length);
|
|
965
|
+
state.set(channel.state.array);
|
|
966
|
+
state[2] = velocity / 127;
|
|
967
|
+
state[3] = noteNumber / 127;
|
|
968
|
+
return state;
|
|
969
|
+
}
|
|
970
|
+
applyVoiceParams(channel, controllerType) {
|
|
971
|
+
channel.scheduledNotes.forEach((noteList) => {
|
|
972
|
+
for (let i = 0; i < noteList.length; i++) {
|
|
973
|
+
const note = noteList[i];
|
|
974
|
+
if (!note)
|
|
975
|
+
continue;
|
|
976
|
+
const controllerState = this.getControllerState(channel, note.noteNumber, note.velocity);
|
|
977
|
+
const voiceParams = note.voice.getParams(controllerType, controllerState);
|
|
978
|
+
let appliedFilterEnvelope = false;
|
|
979
|
+
let appliedVolumeEnvelope = false;
|
|
980
|
+
for (const [key, value] of Object.entries(voiceParams)) {
|
|
981
|
+
const prevValue = note.voiceParams[key];
|
|
982
|
+
if (value === prevValue)
|
|
983
|
+
continue;
|
|
984
|
+
note.voiceParams[key] = value;
|
|
985
|
+
if (key in this.voiceParamsHandlers) {
|
|
986
|
+
this.voiceParamsHandlers[key](channel, note, prevValue);
|
|
987
|
+
}
|
|
988
|
+
else if (filterEnvelopeKeySet.has(key)) {
|
|
989
|
+
if (appliedFilterEnvelope)
|
|
990
|
+
continue;
|
|
991
|
+
appliedFilterEnvelope = true;
|
|
992
|
+
const noteVoiceParams = note.voiceParams;
|
|
993
|
+
for (let i = 0; i < filterEnvelopeKeys.length; i++) {
|
|
994
|
+
const key = filterEnvelopeKeys[i];
|
|
995
|
+
if (key in voiceParams)
|
|
996
|
+
noteVoiceParams[key] = voiceParams[key];
|
|
997
|
+
}
|
|
998
|
+
this.setFilterEnvelope(channel, note);
|
|
999
|
+
this.setPitch(channel, note);
|
|
1000
|
+
}
|
|
1001
|
+
else if (volumeEnvelopeKeySet.has(key)) {
|
|
1002
|
+
if (appliedVolumeEnvelope)
|
|
1003
|
+
continue;
|
|
1004
|
+
appliedVolumeEnvelope = true;
|
|
1005
|
+
const noteVoiceParams = note.voiceParams;
|
|
1006
|
+
for (let i = 0; i < volumeEnvelopeKeys.length; i++) {
|
|
1007
|
+
const key = volumeEnvelopeKeys[i];
|
|
1008
|
+
if (key in voiceParams)
|
|
1009
|
+
noteVoiceParams[key] = voiceParams[key];
|
|
1010
|
+
}
|
|
1011
|
+
this.setVolumeEnvelope(channel, note);
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
});
|
|
1016
|
+
}
|
|
767
1017
|
createControlChangeHandlers() {
|
|
768
1018
|
return {
|
|
769
1019
|
1: this.setModulationDepth,
|
|
@@ -780,13 +1030,13 @@ export class MidyGM1 {
|
|
|
780
1030
|
123: this.allNotesOff,
|
|
781
1031
|
};
|
|
782
1032
|
}
|
|
783
|
-
handleControlChange(channelNumber,
|
|
784
|
-
const handler = this.controlChangeHandlers[
|
|
1033
|
+
handleControlChange(channelNumber, controllerType, value) {
|
|
1034
|
+
const handler = this.controlChangeHandlers[controllerType];
|
|
785
1035
|
if (handler) {
|
|
786
1036
|
handler.call(this, channelNumber, value);
|
|
787
1037
|
}
|
|
788
1038
|
else {
|
|
789
|
-
console.warn(`Unsupported Control change:
|
|
1039
|
+
console.warn(`Unsupported Control change: controllerType=${controllerType} value=${value}`);
|
|
790
1040
|
}
|
|
791
1041
|
}
|
|
792
1042
|
updateModulation(channel) {
|
|
@@ -797,11 +1047,10 @@ export class MidyGM1 {
|
|
|
797
1047
|
if (!note)
|
|
798
1048
|
continue;
|
|
799
1049
|
if (note.modulationDepth) {
|
|
800
|
-
note.modulationDepth.gain.setValueAtTime(channel.modulationDepth, now);
|
|
1050
|
+
note.modulationDepth.gain.setValueAtTime(channel.state.modulationDepth, now);
|
|
801
1051
|
}
|
|
802
1052
|
else {
|
|
803
|
-
|
|
804
|
-
this.setPitch(note, semitoneOffset);
|
|
1053
|
+
this.setPitch(channel, note);
|
|
805
1054
|
this.startModulation(channel, note, now);
|
|
806
1055
|
}
|
|
807
1056
|
}
|
|
@@ -809,16 +1058,17 @@ export class MidyGM1 {
|
|
|
809
1058
|
}
|
|
810
1059
|
setModulationDepth(channelNumber, modulation) {
|
|
811
1060
|
const channel = this.channels[channelNumber];
|
|
812
|
-
channel.modulationDepth = (modulation / 127) *
|
|
1061
|
+
channel.state.modulationDepth = (modulation / 127) *
|
|
1062
|
+
channel.modulationDepthRange;
|
|
813
1063
|
this.updateModulation(channel);
|
|
814
1064
|
}
|
|
815
1065
|
setVolume(channelNumber, volume) {
|
|
816
1066
|
const channel = this.channels[channelNumber];
|
|
817
|
-
channel.volume = volume / 127;
|
|
1067
|
+
channel.state.volume = volume / 127;
|
|
818
1068
|
this.updateChannelVolume(channel);
|
|
819
1069
|
}
|
|
820
1070
|
panToGain(pan) {
|
|
821
|
-
const theta = Math.PI / 2 * Math.max(0, pan - 1) / 126;
|
|
1071
|
+
const theta = Math.PI / 2 * Math.max(0, pan * 127 - 1) / 126;
|
|
822
1072
|
return {
|
|
823
1073
|
gainLeft: Math.cos(theta),
|
|
824
1074
|
gainRight: Math.sin(theta),
|
|
@@ -826,12 +1076,12 @@ export class MidyGM1 {
|
|
|
826
1076
|
}
|
|
827
1077
|
setPan(channelNumber, pan) {
|
|
828
1078
|
const channel = this.channels[channelNumber];
|
|
829
|
-
channel.pan = pan;
|
|
1079
|
+
channel.state.pan = pan / 127;
|
|
830
1080
|
this.updateChannelVolume(channel);
|
|
831
1081
|
}
|
|
832
1082
|
setExpression(channelNumber, expression) {
|
|
833
1083
|
const channel = this.channels[channelNumber];
|
|
834
|
-
channel.expression = expression / 127;
|
|
1084
|
+
channel.state.expression = expression / 127;
|
|
835
1085
|
this.updateChannelVolume(channel);
|
|
836
1086
|
}
|
|
837
1087
|
dataEntryLSB(channelNumber, value) {
|
|
@@ -840,8 +1090,9 @@ export class MidyGM1 {
|
|
|
840
1090
|
}
|
|
841
1091
|
updateChannelVolume(channel) {
|
|
842
1092
|
const now = this.audioContext.currentTime;
|
|
843
|
-
const
|
|
844
|
-
const
|
|
1093
|
+
const state = channel.state;
|
|
1094
|
+
const volume = state.volume * state.expression;
|
|
1095
|
+
const { gainLeft, gainRight } = this.panToGain(state.pan);
|
|
845
1096
|
channel.gainL.gain
|
|
846
1097
|
.cancelScheduledValues(now)
|
|
847
1098
|
.setValueAtTime(volume * gainLeft, now);
|
|
@@ -850,9 +1101,8 @@ export class MidyGM1 {
|
|
|
850
1101
|
.setValueAtTime(volume * gainRight, now);
|
|
851
1102
|
}
|
|
852
1103
|
setSustainPedal(channelNumber, value) {
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
if (!isOn) {
|
|
1104
|
+
this.channels[channelNumber].state.sustainPedal = value / 127;
|
|
1105
|
+
if (value < 64) {
|
|
856
1106
|
this.releaseSustainPedal(channelNumber, value);
|
|
857
1107
|
}
|
|
858
1108
|
}
|
|
@@ -909,7 +1159,7 @@ export class MidyGM1 {
|
|
|
909
1159
|
this.channels[channelNumber].dataMSB = value;
|
|
910
1160
|
this.handleRPN(channelNumber);
|
|
911
1161
|
}
|
|
912
|
-
updateDetune(channel,
|
|
1162
|
+
updateDetune(channel, detune) {
|
|
913
1163
|
const now = this.audioContext.currentTime;
|
|
914
1164
|
channel.scheduledNotes.forEach((noteList) => {
|
|
915
1165
|
for (let i = 0; i < noteList.length; i++) {
|
|
@@ -917,7 +1167,6 @@ export class MidyGM1 {
|
|
|
917
1167
|
if (!note)
|
|
918
1168
|
continue;
|
|
919
1169
|
const { bufferSource } = note;
|
|
920
|
-
const detune = bufferSource.detune.value + detuneChange;
|
|
921
1170
|
bufferSource.detune
|
|
922
1171
|
.cancelScheduledValues(now)
|
|
923
1172
|
.setValueAtTime(detune, now);
|
|
@@ -930,13 +1179,13 @@ export class MidyGM1 {
|
|
|
930
1179
|
const pitchBendRange = channel.dataMSB + channel.dataLSB / 100;
|
|
931
1180
|
this.setPitchBendRange(channelNumber, pitchBendRange);
|
|
932
1181
|
}
|
|
933
|
-
setPitchBendRange(channelNumber,
|
|
1182
|
+
setPitchBendRange(channelNumber, pitchWheelSensitivity) {
|
|
934
1183
|
const channel = this.channels[channelNumber];
|
|
935
|
-
const
|
|
936
|
-
|
|
937
|
-
const
|
|
938
|
-
|
|
939
|
-
this.
|
|
1184
|
+
const state = channel.state;
|
|
1185
|
+
state.pitchWheelSensitivity = pitchWheelSensitivity / 128;
|
|
1186
|
+
const detune = (state.pitchWheel * 2 - 1) * pitchWheelSensitivity * 100;
|
|
1187
|
+
this.updateDetune(channel, detune);
|
|
1188
|
+
this.applyVoiceParams(channel, 16);
|
|
940
1189
|
}
|
|
941
1190
|
handleFineTuningRPN(channelNumber) {
|
|
942
1191
|
const channel = this.channels[channelNumber];
|
|
@@ -968,7 +1217,26 @@ export class MidyGM1 {
|
|
|
968
1217
|
return this.stopChannelNotes(channelNumber, 0, true);
|
|
969
1218
|
}
|
|
970
1219
|
resetAllControllers(channelNumber) {
|
|
971
|
-
|
|
1220
|
+
const stateTypes = [
|
|
1221
|
+
"expression",
|
|
1222
|
+
"modulationDepth",
|
|
1223
|
+
"sustainPedal",
|
|
1224
|
+
"pitchWheelSensitivity",
|
|
1225
|
+
];
|
|
1226
|
+
const channel = this.channels[channelNumber];
|
|
1227
|
+
const state = channel.state;
|
|
1228
|
+
for (let i = 0; i < stateTypes.length; i++) {
|
|
1229
|
+
const type = stateTypes[i];
|
|
1230
|
+
state[type] = defaultControllerState[type];
|
|
1231
|
+
}
|
|
1232
|
+
const settingTypes = [
|
|
1233
|
+
"rpnMSB",
|
|
1234
|
+
"rpnLSB",
|
|
1235
|
+
];
|
|
1236
|
+
for (let i = 0; i < settingTypes.length; i++) {
|
|
1237
|
+
const type = settingTypes[i];
|
|
1238
|
+
channel[type] = this.constructor.channelSettings[type];
|
|
1239
|
+
}
|
|
972
1240
|
}
|
|
973
1241
|
allNotesOff(channelNumber) {
|
|
974
1242
|
return this.stopChannelNotes(channelNumber, 0, false);
|
|
@@ -993,11 +1261,8 @@ export class MidyGM1 {
|
|
|
993
1261
|
GM1SystemOn() {
|
|
994
1262
|
for (let i = 0; i < this.channels.length; i++) {
|
|
995
1263
|
const channel = this.channels[i];
|
|
996
|
-
channel.bankMSB = 0;
|
|
997
|
-
channel.bankLSB = 0;
|
|
998
1264
|
channel.bank = 0;
|
|
999
1265
|
}
|
|
1000
|
-
this.channels[9].bankMSB = 1;
|
|
1001
1266
|
this.channels[9].bank = 128;
|
|
1002
1267
|
}
|
|
1003
1268
|
handleUniversalRealTimeExclusiveMessage(data) {
|
|
@@ -1058,28 +1323,15 @@ Object.defineProperty(MidyGM1, "channelSettings", {
|
|
|
1058
1323
|
configurable: true,
|
|
1059
1324
|
writable: true,
|
|
1060
1325
|
value: {
|
|
1061
|
-
|
|
1062
|
-
|
|
1326
|
+
currentBufferSource: null,
|
|
1327
|
+
program: 0,
|
|
1063
1328
|
bank: 0,
|
|
1064
1329
|
dataMSB: 0,
|
|
1065
1330
|
dataLSB: 0,
|
|
1066
|
-
|
|
1067
|
-
|
|
1331
|
+
rpnMSB: 127,
|
|
1332
|
+
rpnLSB: 127,
|
|
1068
1333
|
fineTuning: 0, // cb
|
|
1069
1334
|
coarseTuning: 0, // cb
|
|
1070
1335
|
modulationDepthRange: 50, // cent
|
|
1071
1336
|
}
|
|
1072
1337
|
});
|
|
1073
|
-
Object.defineProperty(MidyGM1, "effectSettings", {
|
|
1074
|
-
enumerable: true,
|
|
1075
|
-
configurable: true,
|
|
1076
|
-
writable: true,
|
|
1077
|
-
value: {
|
|
1078
|
-
expression: 1,
|
|
1079
|
-
modulationDepth: 0,
|
|
1080
|
-
sustainPedal: false,
|
|
1081
|
-
rpnMSB: 127,
|
|
1082
|
-
rpnLSB: 127,
|
|
1083
|
-
pitchBendRange: 2,
|
|
1084
|
-
}
|
|
1085
|
-
});
|