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